Compare commits

...
Author SHA1 Message Date
Eliza Velasquez 84cecd334b materials: fix mutable spec constants nvidia bug
It turns out that I didn't understand how `FixedCapacityVector` works.
2025-09-03 18:13:09 -07:00
Eliza Velasquez bb6ebd3aea materials: reintroduce mutable spec constants
This change apparently breaks nvidia, still working on it.
2025-08-27 13:27:39 -07:00
Sungun Park 1aab0585da Disable amortized shader compile by default (#9151)
This is a temporary resolution until we fix an underlying issue.
2025-08-27 12:46:58 -07:00
Andy Hovingh 1d1e15d915 webgpu: profiling and system tracing support 2025-08-27 10:41:14 -05:00
Doris Wu ff394f7c1b feat: Add Visibility Bitmask method for GTAO (#9101)
* Quick test

* Fix incorrect logic and add some comments

* Set thickness parameter

* refactoring

* Update

* Generated files

* Some comments

* Update

* Update generated files

* Update

* Update

* Add comments
2025-08-26 23:27:02 +00:00
Syed Idris Shah 161b75b9b7 wgpu: Fix samples on linux
Window size should be calculated from windows not screen on XLIB.
XCB window is not implemented in Dawn. Remove the stale/wrong implementation in filament
2025-08-25 16:51:04 -04:00
Powei FengandSerge Metral bc12a56920 vk: check rebind flow if bindDescriptorSet called between draw2 (#9139)
In the case with external samplers, if another descriptor set gets
bound between draw2(), then we need to check to see if a rebinding
of the pipeline is necessary.

Co-authored-by: Serge Metral <sergemetral@google.com>
2025-08-25 09:04:11 -07:00
Mathias Agopian 15bb295ec6 A new froxel grid vizualization for FilamentApp
FilamentApp now has debugging options to enable or disable the
camera and directional shadow frustums, as well as the new
"froxel grid" visualization.

"froxel grid" is automatically enabled when "froxel debugging" is
enabled in the debug gui in gltf_viewer.


New corresponding debugging APIs were added to View.
2025-08-22 15:55:46 -07:00
Mathias Agopian cea178a40c froxel vizualization can now be turned on/off per view
Before this change once the froxel vizualizaition was enabled via
the Engine debug framework, it applied to all views. This adds a
debug API on View to control whether the view will display this
vizualization.
2025-08-22 15:55:46 -07:00
Mathias Agopian f34bb3d775 ViewerGui: hide stereo options when stereo is not enabled 2025-08-22 15:37:06 -07:00
Mathias Agopian 30112b5b5b gltf_Viewer: the -y (--eyes) options wasn't working 2025-08-22 15:37:06 -07:00
Mathias Agopian 9aaa1bf413 FilamentApp: fix projection matrix
For some reason we were not setting the aspect-ratio to 1 and scaling 
the clip-space instead. This could lead to lighting and clipping
issues.
2025-08-22 15:37:06 -07:00
Powei Feng 0471c5de2f vk: use firstBitSet API (#9128)
This is more efficient than the prevoius way
2025-08-22 22:31:48 +00:00
Juan Caldas 65cca71abf webGPU Run Load Image Tests (#9140) 2025-08-22 18:11:16 -04:00
Doris Wu e3384feee0 Clamp before assign to uint (#9132) (b/440220363) 2025-08-22 03:51:11 +00:00
Mathias Agopian 3650556de4 make sure PixelParams::thickness is initialized
If screen-space refraction was used enabled but the thickness
parameter wasn't explicitly set in the material, it would end-up
being uninitialized. Now it's initialized to 0.5 by default,
which is the value used for subsurface.

Also removed some calculations dependent on thickness being set, as
they assumed a thickness of 0.
2025-08-21 20:25:01 -07:00
Powei Feng 679b08b5db github: add script and action for getting gltf assets (#9085)
- Script for getting models from glTF-Sample-Assets
- Add support for reading gltf models from a file.
2025-08-21 22:16:12 +00:00
Juan Caldas 5fd5cd270c webgpu: Skip failing tests for WebGPU (#9133)
BUGS = [424157731]
2025-08-21 20:37:32 +00:00
Andy Hovingh 275ffb409f wgpu: add texture swizzle support (#9129)
BUGS = [433944896]
2025-08-21 20:03:37 +00:00
94 changed files with 1989 additions and 404 deletions
@@ -0,0 +1,23 @@
name: 'Get and cache glTF Assets'
description: 'Downloads and caches glTF assets by calling the get-gltf-sample-assets.sh script.'
runs:
using: "composite"
steps:
- uses: ./.github/actions/dep-versions
- name: Hash models file
id: hash-models
shell: bash
run: echo "hash=$(cat test/renderdiff/tests/gltf_models.txt | md5sum | sed 's/ -//g')" >> $GITHUB_OUTPUT
- name: Cache glTF assets
id: cache-gltf
uses: actions/cache@v4
with:
path: gltf
key: gltf-assets-${{ env.GITHUB_GLTF_SAMPLE_ASSETS_COMMIT }}-${{ steps.hash-models.outputs.hash }}
- name: Download assets via script if cache not found
if: steps.cache-gltf.outputs.cache-hit != 'true'
shell: bash
run: |
echo "Cache miss for commit ${{ env.GITHUB_GLTF_SAMPLE_ASSETS_COMMIT }}. Running download script..."
xargs bash build/common/get-gltf-sample-assets.sh < test/renderdiff/tests/gltf_models.txt
+3 -2
View File
@@ -1,5 +1,6 @@
name: 'Get Mesa'
description: 'Caches and installs Mesa'
name: 'Get and cache Mesa'
description: 'Get and cache Mesa'
runs:
using: "composite"
steps:
+2
View File
@@ -19,5 +19,7 @@ runs:
- name: Install Mac Prerequisites
shell: bash
run: |
# Install brew prereqs
brew install coreutils
# Install ninja
source ./build/common/get-ninja.sh
+5 -3
View File
@@ -118,11 +118,12 @@ jobs:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/mac-prereq
- uses: ./.github/actions/get-gltf-assets
- uses: ./.github/actions/get-mesa
- uses: ./.github/actions/get-vulkan-sdk
- id: get_commit_msg
uses: ./.github/actions/get-commit-msg
- uses: ./.github/actions/mac-prereq
- uses: ./.github/actions/get-mesa
- uses: ./.github/actions/get-vulkan-sdk
- name: Prerequisites
run: |
pip install tifffile numpy
@@ -132,6 +133,7 @@ jobs:
- name: Render and compare
id: render_compare
run: |
ls ./gltf/Models
TEST_DIR=test/renderdiff
source ${TEST_DIR}/src/preamble.sh
start_
+3
View File
@@ -7,3 +7,6 @@ We are chaning the way Vulkan buffers are handled. We need to switch over to a m
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- Rename `sampler` parameter `unfilterable` to `filterable` [⚠️ **New Material Version**]
- materials: new mutable specialization constants feature. See the [materials documentation](https://google.github.io/filament/Materials.html) for details. [⚠️ **New Material Version**]
@@ -60,6 +60,14 @@ static void setParameter(JNIEnv* env, jlong nativeMaterialInstance, jstring name
env->ReleaseStringUTFChars(name_, name);
}
template<typename T>
static void setConstant(JNIEnv* env, jlong nativeMaterialInstance, jstring name_, T v) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
instance->setConstant(name, v);
env->ReleaseStringUTFChars(name_, name);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetParameterBool(JNIEnv *env, jclass,
@@ -264,6 +272,13 @@ Java_com_google_android_filament_MaterialInstance_nSetParameterTexture(
env->ReleaseStringUTFChars(name_, name);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetConstantBool(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_, jboolean x) {
setConstant(env, nativeMaterialInstance, name_, bool(x));
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetScissor(
@@ -402,6 +402,16 @@ public class MaterialInstance {
nSetParameterFloat4(getNativeObject(), name, color[0], color[1], color[2], color[3]);
}
/**
* Sets the value of a bool constant.
*
* @param name the name of the material constant
* @param x the value of the material constant
*/
public void setConstant(@NonNull String name, boolean x) {
nSetConstantBool(getNativeObject(), name, x);
}
/**
* Set-up a custom scissor rectangle; by default it is disabled.
*
@@ -921,6 +931,9 @@ public class MaterialInstance {
@NonNull String name, int element, @NonNull @Size(min = 1) float[] v,
@IntRange(from = 0) int offset, @IntRange(from = 1) int count);
private static native void nSetConstantBool(long nativeMaterialInstance,
@NonNull String name, boolean x);
private static native void nSetParameterTexture(long nativeMaterialInstance,
@NonNull String name, long nativeTexture, long sampler);
@@ -1949,6 +1949,18 @@ public class View {
* Ground Truth-base Ambient Occlusion (GTAO) options
*/
public float gtaoThicknessHeuristic = 0.004f;
/**
* Ground Truth-base Ambient Occlusion (GTAO) options
*/
public boolean gtaoUseVisibilityBitmasks = false;
/**
* Ground Truth-base Ambient Occlusion (GTAO) options
*/
public float gtaoConstThickness = 0.5f;
/**
* Ground Truth-base Ambient Occlusion (GTAO) options
*/
public boolean gtaoLinearThickness = false;
}
+60
View File
@@ -0,0 +1,60 @@
# Copyright (C) 2025 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.
#!/usr/bin/bash
set -e
GLTF_SAMPLE_ASSETS_COMMIT=${GITHUB_GLTF_SAMPLE_ASSETS_COMMIT:-d441dfdb87413ff412c620849a649d61789a470f}
COMMIT_HASH="${GLTF_SAMPLE_ASSETS_COMMIT}"
REPO_URL="https://github.com/KhronosGroup/glTF-Sample-Assets.git"
TARGET_DIR="gltf"
# The default directories to check out if none are specified
DEFAULT_SPARSE_PATHS=(
"Models/Box/"
"Models/Triangle/"
"Models/AnimatedCube/"
)
# Check if command-line arguments are provided
if [ "$#" -gt 0 ]; then
# If arguments are provided, use them as the paths
SPARSE_PATHS=()
for model_name in "$@"; do
SPARSE_PATHS+=("Models/${model_name}/")
done
echo "Downloading specified models: $@"
else
# Otherwise, use the default list
SPARSE_PATHS=("${DEFAULT_SPARSE_PATHS[@]}")
echo "No models specified, downloading default set."
fi
echo "Removing old directory..."
rm -rf "${TARGET_DIR}"
# Clone the repository using a "treeless" clone, which is highly efficient.
# --filter=tree:0: Clones only the repository structure without file content (no historical directory listings), making the initial clone very small.
# --no-checkout: Prevents automatically checking out the main branch. We will check out a specific commit later.
# --sparse: Initializes the repository for sparse checkout, allowing us to fetch only specific directories.
git clone --filter=tree:0 --no-checkout --sparse "${REPO_URL}" "${TARGET_DIR}"
cd "${TARGET_DIR}"
git sparse-checkout set "${SPARSE_PATHS[@]}"
echo "Checking out commit ${COMMIT_HASH}..."
git checkout "${COMMIT_HASH}"
echo "Successfully checked out the specified models into the '${TARGET_DIR}' directory."
+2 -1
View File
@@ -5,4 +5,5 @@ GITHUB_MESA_VERSION=24.2.1
GITHUB_LLVM_VERSION=16
GITHUB_NDK_VERSION=27.0.11718014
GITHUB_EMSDK_VERSION=3.1.60
GITHUB_VULKANSDK_VERSION=1.4.321.0
GITHUB_VULKANSDK_VERSION=1.4.321.0
GITHUB_GLTF_SAMPLE_ASSETS_COMMIT=d441dfdb87413ff412c620849a649d61789a470f
+31 -24
View File
@@ -212,7 +212,7 @@ when using textures.
This property can dramatically change the appearance of a surface. Non-metallic surfaces have
chromatic diffuse reflection and achromatic specular reflection (reflected light does not change
color). Metallic surfaces do not have any diffuse reflection and chromatic specular reflection
(reflected light takes on the color of the surfaced as defined by `baseColor`).
(reflected light takes on the color of the surfaced as defined by `baseColor`).
The effect of `metallic` is shown in figure [metallicProperty] (click on the image to see a
larger version).
@@ -248,7 +248,7 @@ The effect of `roughness` on metallic surfaces is shown in figure [roughnessCond
When refraction through an object is enabled (using a `refractonType` of `thin` or `solid`), the
`roughness` property will also affect the refractions, as shown in figure
[roughnessRefractionProperty] (click on the image to see a larger version).
![Figure [roughnessRefractionProperty]: Refractive sphere with `roughness` varying from 0.0
(left) to 1.0 (right)](images/materials/refraction_roughness.png)
@@ -307,7 +307,7 @@ The sheen color controls the color appearance and strength of an optional sheen
base layer described by the properties above. The sheen layer always sits below the clear coat layer
if such a layer is present.
The sheen layer can be used to represent cloth and fabric materials. Please refer to
The sheen layer can be used to represent cloth and fabric materials. Please refer to
section [Cloth model] for more information about cloth and fabric materials.
The effect of `sheenColor` is shown in figure [materialSheenColor]
@@ -520,14 +520,14 @@ light to bend further away from the initial path.
Table [commonMatIOR] describes acceptable refractive indices for various types of materials.
Material | IOR
Material | IOR
--------------------------:|:-----------------
Air | 1.0
Water | 1.33
Common liquids | 1.33 to 1.5
Common gemstones | 1.58 to 2.33
Plastics, glass | 1.5 to 1.58
Other dielectric materials | 1.33 to 1.58
Water | 1.33
Common liquids | 1.33 to 1.5
Common gemstones | 1.58 to 2.33
Plastics, glass | 1.5 to 1.58
Other dielectric materials | 1.33 to 1.58
[Table [commonMatIOR]: Index of refraction of common materials]
The appearance of a refractive material will greatly depend on the `refractionType` and
@@ -1096,30 +1096,37 @@ Value
`bool` or `number`, depending on the `type` of the constant. The type must be one of the types
described in table [materialConstantsTypes].
Type | Description | Default
:----------------------|:-----------------------------------------|:------------------
int | A signed, 32 bit GLSL int | 0
float | A single-precision GLSL float | 0.0
bool | A GLSL bool | false
Constants may also be specified as mutable by setting the `mutable` property to `true`. Only
`bool` spec constants may be specified as mutable.
| Type | Description | Default | May be mutable? |
|:------|:------------------------------|:--------|-----------------|
| int | A signed, 32 bit GLSL int | | no |
| float | A single-precision GLSL float | 0.0 | no |
| bool | A GLSL bool | false | yes |
[Table [materialConstantsTypes]: Material constants types]
Description
: Lists the constant parameters accepted by your material. These constants can be set, or
"specialized", at runtime when loading a material package. Multiple materials can be loaded from
the same material package with differing constant parameter specializations. Once a material is
loaded from a material package, its constant parameters cannot be changed. Compared to regular
parameters, constant parameters allow the compiler to generate more efficient code. Access
constant parameters from the shader by prefixing the name with `materialConstant_`. For example,
a constant parameter named `myConstant` is accessed in the shader as
`materialConstant_myConstant`. If a constant parameter is not set at runtime, the default is
used.
the same material package with differing constant parameter specializations. If a constant
parameter is specialized as mutable, it may be changed at any time on a per-instance basis via
`MaterialInstance::setConstant(name, value)`. Otherwise, once a material is loaded from a
material package, its constant parameters cannot be changed.
Compared to regular parameters, constant parameters allow the compiler to generate more
efficient code. Access constant parameters from the shader by prefixing the name with
`materialConstant_`. For example, a constant parameter named `myConstant` is accessed in the
shader as `materialConstant_myConstant`. If a constant parameter is not set at runtime, the
default is used.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
constants : [
{
name : overrideAlpha,
type : bool
type : bool,
mutable : true,
},
{
name : customAlpha,
@@ -2639,7 +2646,7 @@ standard skybox material. It produces a list of 2 parameters, named `showSun` an
respectively a boolean and a cubemap texture.
```text
$ matc --reflect parameters filament/src/materials/skybox.mat
$ matc --reflect parameters filament/src/materials/skybox.mat
{
"parameters": [
{
@@ -2655,7 +2662,7 @@ $ matc --reflect parameters filament/src/materials/skybox.mat
}
]
}
```
```
### --variant-filter
@@ -0,0 +1,77 @@
# Performance Analysis
## Android
### Prerequisites
- Download and install Android GPU Inspector (AGI). See https://developer.android.com/agi.
### Android with the Vulkan Backend
---
#### Profiling
1. Build a release build of Filament with the applicable backend(s) enabled
_(+ any special flags for enabling sys strace. Nothing special is needed for Vulkan or WebGPU aside from
building a release build with no flags)_ _(debug builds for this are useless)_
```shell
# the following command assumes you are in the root filament directory
# and ANDROID_HOME is exported (and possibly also CC and CXX on linux as needed)
#
# NOTE: to build with WebGPU support you need to explicitly include the -W flag
# (it doesn't get compiled in by default), e.g.:
# ./build.sh -W -p android,desktop -i release
#
# Note that you can speed this up a bit (and reduce disk space usage) by limiting the target to just
# the ABI you plan on testing with the -q flag, e.g. -q arm64-v8a. If you do this, you
# will need to update the android/gradle.properties file to specify the ABI(s) you are targeting
# with the com.google.android.filament.abis property.
# Thus, a build command that would target BOTH Vulkan AND WebGPU AND only target the ARM64 ABI would look something
# like:
# ./build.sh -W -q arm64-v8a -p android,desktop -i release
./build.sh -p android,desktop -i release
```
1. Connect your Android device to your computer via a USB cord with USB debugging enabled and configure
the system property to default to the desired backend, e.g.
_(to determine how these numbers map to the backends, see the `enum class Backend`
definition in [filament/filament/backend/include/backend/DriverEnums.h](../../../../filament/backend/include/backend/DriverEnums.h))_:
```shell
# to set the backend to Vulkan:
adb shell setprop debug.filament.backend 2
# to set the backend to WebGPU:
adb shell setprop debug.filament.backend 4
# to view the current property:
adb shell getprop debug.filament.backend
```
1. Build and run a sample, e.g. sample-gltf-viewer, on your Android device
_([Android Studio](https://developer.android.com/studio) recommended)_.
1. Run [AGI](https://developer.android.com/agi) and follow instructions to profile the app/system
with trace capture(s). See https://developer.android.com/agi/start for more details to
get started.
- We typically only run "Capture System Profiler trace" _(not necessarily "Capture Frame Profiler trace")_
- When configuring the trace:
- For both the WebGPU and Vulkan backends configure the profiler for the Vulkan API
_(since WebGPU should be using Vulkan under the hood as well)_
- Running for ~1 seconds should suffice
- Hit the "Configure" button in Trace objects, select "Switch to advanced mode" and add:
```
data_sources {
config {
name: "track_event"
track_event_config {
disabled_categories: "*"
enabled_categories: "filament/filament"
enabled_categories: "filament/jobsystem"
enabled_categories: "filament/gltfio"
}
}
}
```
- One you open the trace, zoom into a series of frames to get a sense of generally how long they typically take
_(use `W`, `S`, `A` and `D` keys and mouse wheel for navigation)_ and find a representative one.
We are most interested in the performance of the
`FEngine::loop` thread, how long it takes, overlap in activities/processes/commands, reduction in queue submissions,
etc. Similarly, we can view GPU timeline as it relates to that. We want to see overlapping
shader invocations and non-interrupted fragment shader runs.
+1 -1
View File
@@ -530,7 +530,7 @@ target_link_libraries(${TARGET} PRIVATE
if (LINUX AND FILAMENT_SUPPORTS_WEBGPU)
# Unclear why this needs to be explicit while other linux libs do not, but it is necessary for
# WebGPU to build on linux.
target_link_libraries(${TARGET} PRIVATE -lxcb)
target_link_libraries(${TARGET} PRIVATE -lxcb -lX11)
endif()
# ==================================================================================================
+1 -1
View File
@@ -156,7 +156,7 @@ public:
* Set to `true` to forcibly disable amortized shader compilation in the backend.
* Currently only honored by the GL backend.
*/
bool disableAmortizedShaderCompile = false;
bool disableAmortizedShaderCompile = true;
/**
* Disable backend handles use-after-free checks.
+4 -1
View File
@@ -20,6 +20,7 @@
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h>
#include <utils/bitset.h>
#include <backend/DriverEnums.h>
@@ -66,6 +67,7 @@ public:
using DescriptorBindingsInfo = utils::FixedCapacityVector<Descriptor>;
using DescriptorSetInfo = std::array<DescriptorBindingsInfo, MAX_DESCRIPTOR_SET_COUNT>;
using SpecializationConstantsInfo = utils::FixedCapacityVector<SpecializationConstant>;
using MutableSpecConstantsInfo = utils::bitset8;
using ShaderBlob = utils::FixedCapacityVector<uint8_t>;
using ShaderSource = std::array<ShaderBlob, SHADER_TYPE_COUNT>;
@@ -103,7 +105,8 @@ public:
Program& descriptorBindings(backend::descriptor_set_t set,
DescriptorBindingsInfo descriptorBindings) noexcept;
Program& specializationConstants(SpecializationConstantsInfo specConstants) noexcept;
Program& specializationConstants(SpecializationConstantsInfo specConstants,
uint32_t firstMutableId, MutableSpecConstantsInfo mutableSpecConstants) noexcept;
struct PushConstant {
utils::CString name;
+13 -2
View File
@@ -82,8 +82,19 @@ Program& Program::attributes(AttributesInfo attributes) noexcept {
return *this;
}
Program& Program::specializationConstants(SpecializationConstantsInfo specConstants) noexcept {
mSpecializationConstants = std::move(specConstants);
Program& Program::specializationConstants(SpecializationConstantsInfo specConstants,
uint32_t firstMutableId, MutableSpecConstantsInfo mutableSpecConstants) noexcept {
// String the two lists together.
mSpecializationConstants = SpecializationConstantsInfo(
specConstants.size() + mutableSpecConstants.size());
std::uninitialized_move(specConstants.begin(), specConstants.end(),
mSpecializationConstants.begin());
for (uint32_t i = 0; i < mutableSpecConstants.size(); i++) {
mSpecializationConstants[i + specConstants.size()] = SpecializationConstant {
.id = i + firstMutableId,
.value = mutableSpecConstants[i],
};
}
return *this;
}
+13 -1
View File
@@ -452,7 +452,7 @@ void VulkanDriver::updateDescriptorSetTexture(
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
if (mExternalImageManager.isExternallySampledTexture(texture)) {
if (UTILS_UNLIKELY(mExternalImageManager.isExternallySampledTexture(texture))) {
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
mAppState.hasBoundExternalImages = true;
} else {
@@ -1939,6 +1939,18 @@ void VulkanDriver::bindDescriptorSet(
if (dsh) {
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
mDescriptorSetCache.bind(setIndex, set, std::move(offsets));
if (mAppState.hasExternalSamplers()) {
auto const& bindInDrawBundle = mPipelineState.bindInDraw.second;
// The set index being bound has already been bound or will be bound. If it's already
// been bound and this set has external samplers, we do the doBindindraw block in
// draw2() again. Because this set might potentially cause a new pipelineLayout
// (therefore pipeline) to be bound.
if (bindInDrawBundle.descriptorSetMask[setIndex] &&
mExternalImageManager.hasExternalSampler(set)) {
mPipelineState.bindInDraw.first = true;
}
}
} else {
mDescriptorSetCache.unbind(setIndex);
}
@@ -121,7 +121,7 @@ fvkutils::DescriptorSetMask VulkanExternalImageManager::prepareBindSets(LayoutAr
}
bool VulkanExternalImageManager::hasExternalSampler(
fvkmemory::resource_ptr<VulkanDescriptorSet> set) {
fvkmemory::resource_ptr<VulkanDescriptorSet> set) const {
auto itr = std::find_if(mSetBindings.begin(), mSetBindings.end(),
[&](SetBindingInfo const& info) { return info.set == set; });
return itr != mSetBindings.end();
@@ -86,9 +86,9 @@ public:
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
};
private:
bool hasExternalSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
bool hasExternalSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set) const;
private:
void updateSetAndLayout(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
@@ -46,17 +46,13 @@ fvkmemory::resource_ptr<VulkanTimerQuery> VulkanQueryManager::getNextQuery(
return {};
}
bool found = false;
std::pair<uint32_t, uint32_t> queryIndices;
unused.forEachSetBit([&](size_t index) {
if (found) {
return;
}
size_t const firstUnused = unused.firstSetBit();
{
std::unique_lock<utils::Mutex> lock(mMutex);
mUsed.set(index);
found = true;
queryIndices = std::make_pair(index * 2, index * 2 + 1);
});
mUsed.set(firstUnused);
queryIndices = { firstUnused * 2, firstUnused * 2 + 1 };
}
return fvkmemory::resource_ptr<VulkanTimerQuery>::construct(resourceManager, queryIndices.first,
queryIndices.second);
}
+51 -3
View File
@@ -49,8 +49,16 @@ constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
// textures, samplers, and buffers,...), which would otherwise spam the logs.
#define FWGPU_DEBUG_BIND_GROUPS 0x00000020
// Enables Android systrace
#define FWGPU_DEBUG_SYSTRACE 0x00000040
// Enable a minimal set of traces to assess the performance of the backend.
// All other debug features must be disabled.
#define FWGPU_DEBUG_PROFILING 0x00000080
// Useful default combinations
#define FWGPU_DEBUG_EVERYTHING 0xFFFFFFFF
#define FWGPU_DEBUG_EVERYTHING (0xFFFFFFFF & ~FWGPU_DEBUG_PROFILING)
#define FWGPU_DEBUG_PERFORMANCE FWGPU_DEBUG_SYSTRACE
#if defined(FILAMENT_BACKEND_DEBUG_FLAG)
#define FWGPU_DEBUG_FORWARDED_FLAG (FILAMENT_BACKEND_DEBUG_FLAG & FWGPU_DEBUG_EVERYTHING)
@@ -59,13 +67,53 @@ constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
#endif
#ifndef NDEBUG
#define FWGPU_DEBUG_FLAGS FWGPU_DEBUG_FORWARDED_FLAG
#define FWGPU_DEBUG_FLAGS (FWGPU_DEBUG_PERFORMANCE | FWGPU_DEBUG_FORWARDED_FLAG)
#else
#define FWGPU_DEBUG_FLAGS 0
#define FWGPU_DEBUG_FLAGS FWGPU_DEBUG_SYSTRACE
#endif
// Override the debug flags if we are forcing profiling mode
#if defined(FILAMENT_FORCE_PROFILING_MODE)
#undef FWGPU_DEBUG_FLAGS
#define FWGPU_DEBUG_FLAGS (FWGPU_DEBUG_PROFILING)
#endif
#define FWGPU_ENABLED(flags) (((FWGPU_DEBUG_FLAGS) & (flags)) == (flags))
#if FWGPU_ENABLED(FWGPU_DEBUG_PROFILING) && FWGPU_DEBUG_FLAGS != FWGPU_DEBUG_PROFILING
#error PROFILING is exclusive; all other debug features must be disabled.
#endif
#if FWGPU_DEBUG_FLAGS == FWGPU_DEBUG_PROFILING
#ifndef NDEBUG
#error PROFILING is meaningless in DEBUG mode.
#endif
#define FWGPU_SYSTRACE_CONTEXT()
#define FWGPU_SYSTRACE_START(marker)
#define FWGPU_SYSTRACE_END()
#define FWGPU_SYSTRACE_SCOPE()
#define FWGPU_PROFILE_MARKER(marker) PROFILE_SCOPE(marker)
#elif FWGPU_ENABLED(FWGPU_DEBUG_SYSTRACE)
#include <private/utils/Tracing.h>
#define FWGPU_SYSTRACE_CONTEXT() FILAMENT_TRACING_CONTEXT(FILAMENT_TRACING_CATEGORY_FILAMENT)
#define FWGPU_SYSTRACE_START(marker) FILAMENT_TRACING_NAME_BEGIN(FILAMENT_TRACING_CATEGORY_FILAMENT, marker)
#define FWGPU_SYSTRACE_END() FILAMENT_TRACING_NAME_END(FILAMENT_TRACING_CATEGORY_FILAMENT)
#define FWGPU_SYSTRACE_SCOPE() FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT)
#define FWGPU_PROFILE_MARKER(marker) FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT)
#else
#define FWGPU_SYSTRACE_CONTEXT()
#define FWGPU_SYSTRACE_START(marker)
#define FWGPU_SYSTRACE_END()
#define FWGPU_SYSTRACE_SCOPE()
#define FWGPU_PROFILE_MARKER(marker)
#endif
#if FWGPU_ENABLED(FWGPU_DEBUG_FORCE_LOG_TO_I)
#define FWGPU_LOGI LOG(INFO)
#define FWGPU_LOGD FWGPU_LOGI
+108 -31
View File
@@ -37,6 +37,7 @@
#include "CommandStreamDispatcher.h"
#include "DriverBase.h"
#include "SystraceProfile.h"
#include "private/backend/Dispatcher.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
@@ -148,6 +149,7 @@ void WebGPUDriver::tick(int) {
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) {
FWGPU_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
}
void WebGPUDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
@@ -164,6 +166,7 @@ void WebGPUDriver::setPresentationTime(int64_t monotonic_clock_ns) {
}
void WebGPUDriver::endFrame(const uint32_t /* frameId */) {
FWGPU_PROFILE_MARKER(PROFILE_NAME_ENDFRAME);
mPipelineLayoutCache.onFrameEnd();
mPipelineCache.onFrameEnd();
@@ -177,6 +180,7 @@ void WebGPUDriver::endFrame(const uint32_t /* frameId */) {
// A new command encoder is created fresh after it.
// If a command encoder is not in flight (nullptr), then this is a noop.
void WebGPUDriver::flush(int /* dummy */) {
FWGPU_SYSTRACE_SCOPE();
if (mCommandEncoder == nullptr) {
return;
}
@@ -200,6 +204,7 @@ void WebGPUDriver::flush(int /* dummy */) {
// Submits the currently recorded commands and waits for them to complete on the GPU.
// This is a synchronous operation and should be used sparingly.
void WebGPUDriver::finish(int /* dummy */) {
FWGPU_SYSTRACE_SCOPE();
mDevice.Tick();
mAdapter.GetInstance().ProcessEvents();
flush();
@@ -224,33 +229,43 @@ void WebGPUDriver::finish(int /* dummy */) {
}
void WebGPUDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
if (rph) {
destructHandle<WebGPURenderPrimitive>(rph);
if (!rph) {
return;
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPURenderPrimitive>(rph);
}
void WebGPUDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vertexBufferInfoHandle) {
if (vertexBufferInfoHandle) {
destructHandle<WebGPUVertexBufferInfo>(vertexBufferInfoHandle);
if (!vertexBufferInfoHandle) {
return;
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPUVertexBufferInfo>(vertexBufferInfoHandle);
}
void WebGPUDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vertexBufferHandle) {
if (vertexBufferHandle) {
destructHandle<WebGPUVertexBuffer>(vertexBufferHandle);
if (!vertexBufferHandle) {
return;
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPUVertexBuffer>(vertexBufferHandle);
}
void WebGPUDriver::destroyIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle) {
if (indexBufferHandle) {
destructHandle<WebGPUIndexBuffer>(indexBufferHandle);
if (!indexBufferHandle) {
return;
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPUIndexBuffer>(indexBufferHandle);
}
void WebGPUDriver::destroyBufferObject(Handle<HwBufferObject> bufferObjectHandle) {
if (bufferObjectHandle) {
destructHandle<WebGPUBufferObject>(bufferObjectHandle);
if (!bufferObjectHandle) {
return;
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPUBufferObject>(bufferObjectHandle);
}
void WebGPUDriver::destroyTexture(Handle<HwTexture> textureHandle) {
@@ -419,7 +434,9 @@ Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
// associated with a handle that was previously allocated by an S-method.
// ------------------------------------------------------------------------------------------------
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
const uint64_t flags) {
FWGPU_SYSTRACE_SCOPE();
mNativeWindow = nativeWindow;
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
@@ -455,12 +472,14 @@ void WebGPUDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t wi
void WebGPUDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vertexBufferInfoHandle,
const uint8_t bufferCount, const uint8_t attributeCount, const AttributeArray attributes) {
FWGPU_SYSTRACE_SCOPE();
constructHandle<WebGPUVertexBufferInfo>(vertexBufferInfoHandle, bufferCount, attributeCount,
attributes, mDeviceLimits);
}
void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vertexBufferHandle,
const uint32_t vertexCount, Handle<HwVertexBufferInfo> vertexBufferInfoHandle) {
FWGPU_SYSTRACE_SCOPE();
const auto vertexBufferInfo = handleCast<WebGPUVertexBufferInfo>(vertexBufferInfoHandle);
constructHandle<WebGPUVertexBuffer>(vertexBufferHandle, vertexCount,
vertexBufferInfo->bufferCount, vertexBufferInfoHandle);
@@ -468,12 +487,14 @@ void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vertexBufferHandle
void WebGPUDriver::createIndexBufferR(Handle<HwIndexBuffer> indexBufferHandle,
const ElementType elementType, const uint32_t indexCount, const BufferUsage usage) {
FWGPU_SYSTRACE_SCOPE();
const auto elementSize = static_cast<uint8_t>(getElementTypeSize(elementType));
constructHandle<WebGPUIndexBuffer>(indexBufferHandle, mDevice, elementSize, indexCount);
}
void WebGPUDriver::createBufferObjectR(Handle<HwBufferObject> bufferObjectHandle,
const uint32_t byteCount, const BufferObjectBinding bindingType, const BufferUsage usage) {
FWGPU_SYSTRACE_SCOPE();
constructHandle<WebGPUBufferObject>(bufferObjectHandle, mDevice, bindingType, byteCount);
}
@@ -481,6 +502,7 @@ void WebGPUDriver::createTextureR(Handle<HwTexture> textureHandle, const Sampler
const uint8_t levels, const TextureFormat format, const uint8_t samples,
const uint32_t width, const uint32_t height, const uint32_t depth,
const TextureUsage usage) {
FWGPU_SYSTRACE_SCOPE();
constructHandle<WebGPUTexture>(textureHandle, target, levels, format, samples, width, height,
depth, usage, mDevice);
}
@@ -496,13 +518,47 @@ void WebGPUDriver::createTextureViewSwizzleR(Handle<HwTexture> textureHandle,
Handle<HwTexture> sourceTextureHandle, const backend::TextureSwizzle r,
const backend::TextureSwizzle g, const backend::TextureSwizzle b,
const backend::TextureSwizzle a) {
PANIC_POSTCONDITION("Swizzle WebGPU Texture is not supported");
if (!isTextureSwizzleSupported()) {
FWGPU_LOGW << "WebGPUDriver::createTextureViewSwizzleR called while texture swizzling is "
"not supported by the device/driver";
}
auto sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
assert_invariant(sourceTexture);
wgpu::TextureComponentSwizzle swizzle
{
.r = toWGPUComponentSwizzle(r),
.g = toWGPUComponentSwizzle(g),
.b = toWGPUComponentSwizzle(b),
.a = toWGPUComponentSwizzle(a),
};
wgpu::TextureComponentSwizzleDescriptor swizzleDesc {};
swizzleDesc.swizzle = swizzle;
const wgpu::TextureViewDescriptor viewDesc {
.nextInChain = &swizzleDesc,
.label = "swizzled_texture_view",
.format = sourceTexture->getTexture().GetFormat(),
.dimension = sourceTexture->getViewDimension(),
.baseMipLevel = 0,
.mipLevelCount = sourceTexture->getTexture().GetMipLevelCount(),
.baseArrayLayer = 0,
.arrayLayerCount = sourceTexture->getTexture().GetDepthOrArrayLayers(),
};
wgpu::TextureView swizzledView{ sourceTexture->getTexture().CreateView(&viewDesc) };
FILAMENT_CHECK_POSTCONDITION(swizzledView) << "Failed to create swizzled Texture view";
constructHandle<WebGPUTexture>(textureHandle, sourceTexture, swizzledView);
}
void WebGPUDriver::createTextureExternalImage2R(Handle<HwTexture> textureHandle,
const backend::SamplerType target, const backend::TextureFormat format,
const uint32_t width, const uint32_t height, const backend::TextureUsage usage,
Platform::ExternalImageHandleRef externalImage) {
FWGPU_SYSTRACE_SCOPE();
PANIC_POSTCONDITION("External WebGPU Texture is not supported");
}
@@ -510,6 +566,7 @@ void WebGPUDriver::createTextureExternalImageR(Handle<HwTexture> textureHandle,
const backend::SamplerType target, const backend::TextureFormat format,
const uint32_t width, const uint32_t height, const backend::TextureUsage usage,
void* externalImage) {
FWGPU_SYSTRACE_SCOPE();
PANIC_POSTCONDITION("External WebGPU Texture is not supported");
}
@@ -529,6 +586,7 @@ void WebGPUDriver::importTextureR(Handle<HwTexture> textureHandle, const intptr_
void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> renderPrimitiveHandle,
Handle<HwVertexBuffer> vertexBufferHandle, Handle<HwIndexBuffer> indexBufferHandle,
const PrimitiveType primitiveType) {
FWGPU_SYSTRACE_SCOPE();
assert_invariant(mDevice);
const auto renderPrimitive = constructHandle<WebGPURenderPrimitive>(renderPrimitiveHandle);
const auto vertexBuffer = handleCast<WebGPUVertexBuffer>(vertexBufferHandle);
@@ -539,6 +597,7 @@ void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> renderPrimit
}
void WebGPUDriver::createProgramR(Handle<HwProgram> programHandle, Program&& program) {
FWGPU_SYSTRACE_SCOPE();
constructHandle<WebGPUProgram>(programHandle, mDevice, program);
}
@@ -556,6 +615,7 @@ void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> renderTargetHandle
const TargetBufferFlags targetFlags, const uint32_t width, const uint32_t height,
const uint8_t samples, const uint8_t layerCount, const MRT color,
const TargetBufferInfo depth, const TargetBufferInfo stencil) {
FWGPU_SYSTRACE_SCOPE();
constructHandle<WebGPURenderTarget>(
renderTargetHandle, width, height, samples, layerCount, color, depth, stencil,
targetFlags,
@@ -577,11 +637,13 @@ void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
void WebGPUDriver::createDescriptorSetLayoutR(
Handle<HwDescriptorSetLayout> descriptorSetLayoutHandle,
backend::DescriptorSetLayout&& info) {
FWGPU_SYSTRACE_SCOPE();
constructHandle<WebGPUDescriptorSetLayout>(descriptorSetLayoutHandle, std::move(info), mDevice);
}
void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> descriptorSetHandle,
Handle<HwDescriptorSetLayout> descriptorSetLayoutHandle) {
FWGPU_SYSTRACE_SCOPE();
auto layout = handleCast<WebGPUDescriptorSetLayout>(descriptorSetLayoutHandle);
constructHandle<WebGPUDescriptorSet>(descriptorSetHandle, layout->getLayout(),
layout->getBindGroupEntries());
@@ -641,7 +703,7 @@ bool WebGPUDriver::isTextureFormatSupported(const TextureFormat format) {
}
bool WebGPUDriver::isTextureSwizzleSupported() {
return false;
return mDevice.HasFeature(wgpu::FeatureName::TextureComponentSwizzle);
}
bool WebGPUDriver::isTextureFormatMipmappable(const TextureFormat format) {
@@ -923,7 +985,7 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
<< "Failed to create staging input texture for blit?";
const auto copyInfo{ wgpu::TexelCopyTextureInfo{
.texture = stagingTexture,
.mipLevel = level,
.mipLevel = 0,
.origin = { .x = 0, .y = 0, .z = 0 },
.aspect = texture->getAspect(),
} };
@@ -978,7 +1040,6 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
mQueue.Submit(1, &blitCommand);
mCommandEncoder = nullptr;
}
stagingTexture.Destroy();
} else {
// Direct copy without a blit.
const auto copyInfo { wgpu::TexelCopyTextureInfo{
@@ -1098,6 +1159,7 @@ void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority,
// resolve targets if necessary.
void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
RenderPassParams const& params) {
FWGPU_SYSTRACE_SCOPE();
if (!mCommandEncoder) {
wgpu::CommandEncoderDescriptor commandEncoderDescriptor{ .label = "frame_command_encoder" };
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
@@ -1251,6 +1313,7 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
}
void WebGPUDriver::endRenderPass(int /* dummy */) {
FWGPU_SYSTRACE_SCOPE();
mRenderPassEncoder.End();
mRenderPassEncoder = nullptr;
}
@@ -1259,10 +1322,12 @@ void WebGPUDriver::nextSubpass(int) {
//todo
}
void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
ASSERT_PRECONDITION_NON_FATAL(drawSch == readSch,
void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSwapChain,
Handle<HwSwapChain> readSwapChain) {
FWGPU_SYSTRACE_SCOPE();
ASSERT_PRECONDITION_NON_FATAL(drawSwapChain == readSwapChain,
"WebGPU driver does not support distinct draw/read swap chains.");
auto swapChain = handleCast<WebGPUSwapChain>(drawSch);
auto swapChain = handleCast<WebGPUSwapChain>(drawSwapChain);
mSwapChain = swapChain;
assert_invariant(mSwapChain);
@@ -1275,13 +1340,16 @@ void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
assert_invariant(mTextureView);
if (!mCommandEncoder) {
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = { .label = "frame_command_encoder" };
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
.label = "frame_command_encoder"
};
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
}
assert_invariant(mCommandEncoder);
}
void WebGPUDriver::commit(Handle<HwSwapChain> sch) {
void WebGPUDriver::commit(Handle<HwSwapChain> swapChainHandle) {
FWGPU_SYSTRACE_SCOPE();
if (UTILS_VERY_UNLIKELY(!mCommandEncoder)) {
// nothing to submit
if (UTILS_LIKELY(mSwapChain)) {
@@ -1332,18 +1400,20 @@ void WebGPUDriver::insertEventMarker(char const* string) {
}
void WebGPUDriver::pushGroupMarker(char const* string) {
FWGPU_SYSTRACE_CONTEXT();
FWGPU_SYSTRACE_START(string);
}
void WebGPUDriver::popGroupMarker(int /* dummy */) {
FWGPU_SYSTRACE_CONTEXT();
FWGPU_SYSTRACE_END();
}
void WebGPUDriver::startCapture(int /* dummy */) {
//todo
}
void WebGPUDriver::popGroupMarker(int) {
//todo
}
void WebGPUDriver::startCapture(int) {
//todo
}
void WebGPUDriver::stopCapture(int) {
void WebGPUDriver::stopCapture(int /* dummy */) {
//todo
}
@@ -1559,7 +1629,7 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
Handle<HwRenderTarget> destinationRenderTargetHandle, const Viewport destinationViewport,
Handle<HwRenderTarget> sourceRenderTargetHandle, const Viewport sourceViewport,
const SamplerMagFilter filter) {
FWGPU_SYSTRACE_SCOPE();
auto const sourceTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
auto const destinationTarget{ handleCast<WebGPURenderTarget>(destinationRenderTargetHandle) };
assert_invariant(sourceTarget && destinationTarget);
@@ -1605,7 +1675,7 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
static_cast<uint32_t>(sourceViewport.height) };
const wgpu::Extent2D destinationSize{ static_cast<uint32_t>(destinationViewport.width),
static_cast<uint32_t>(destinationViewport.height) };
bool reusedCommandEncoder{ true };
if (mCommandEncoder) {
flush();
@@ -1645,6 +1715,7 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
void WebGPUDriver::resolve(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
const uint8_t sourceLayer, Handle<HwTexture> sourceTextureHandle,
const uint8_t destinationLevel, const uint8_t destinationLayer) {
FWGPU_SYSTRACE_SCOPE();
if (!mCommandEncoder) {
const wgpu::CommandEncoderDescriptor encoderDescriptor{
.label = "command_created_with_resolve",
@@ -1682,6 +1753,7 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
const uint8_t sourceLayer, const math::uint2 destinationOrigin,
Handle<HwTexture> sourceTextureHandle, const uint8_t destinationLevel,
const uint8_t destinationLayer, const math::uint2 sourceOrigin, const math::uint2 size) {
FWGPU_SYSTRACE_SCOPE();
bool reusedCommandEncoder{ true };
if (mCommandEncoder) {
// make sure command elements (draws, etc.) prior to this blit are processed before the blit
@@ -1740,6 +1812,7 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
}
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
FWGPU_SYSTRACE_SCOPE();
assert_invariant(mRenderPassEncoder);
const auto program{ handleCast<WebGPUProgram>(pipelineState.program) };
assert_invariant(program);
@@ -1828,6 +1901,7 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
}
void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> renderPrimitiveHandle) {
FWGPU_SYSTRACE_SCOPE();
const auto renderPrimitive = handleCast<WebGPURenderPrimitive>(renderPrimitiveHandle);
const auto vertexBufferInfo = handleCast<WebGPUVertexBufferInfo>(
renderPrimitive->vertexBuffer->getVertexBufferInfoHandle());
@@ -1845,6 +1919,7 @@ void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> renderPrimitive
void WebGPUDriver::draw2(const uint32_t indexOffset, const uint32_t indexCount,
const uint32_t instanceCount) {
FWGPU_SYSTRACE_SCOPE();
// Bind groups are deferred until the actual draw call.
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
auto& binding = mCurrentDescriptorSets[i];
@@ -1891,6 +1966,7 @@ void WebGPUDriver::resetState(int) {
void WebGPUDriver::updateDescriptorSetBuffer(Handle<HwDescriptorSet> descriptorSetHandle,
const backend::descriptor_binding_t binding, Handle<HwBufferObject> bufferObjectHandle,
const uint32_t offset, const uint32_t size) {
FWGPU_SYSTRACE_SCOPE();
const auto bindGroup = handleCast<WebGPUDescriptorSet>(descriptorSetHandle);
const auto buffer = handleCast<WebGPUBufferObject>(bufferObjectHandle);
if (!bindGroup->getIsLocked()) {
@@ -1911,6 +1987,7 @@ void WebGPUDriver::updateDescriptorSetBuffer(Handle<HwDescriptorSet> descriptorS
void WebGPUDriver::updateDescriptorSetTexture(Handle<HwDescriptorSet> descriptorSetHandle,
const backend::descriptor_binding_t binding, Handle<HwTexture> textureHandle,
const SamplerParams params) {
FWGPU_SYSTRACE_SCOPE();
auto bindGroup = handleCast<WebGPUDescriptorSet>(descriptorSetHandle);
auto texture = handleCast<WebGPUTexture>(textureHandle);
@@ -372,6 +372,7 @@ bool WebGPUPipelineCache::RenderPipelineKeyEqual::operator()(RenderPipelineKey c
}
void WebGPUPipelineCache::removeExpiredPipelines() {
FWGPU_SYSTRACE_SCOPE();
using Iterator = decltype(mRenderPipelines)::const_iterator;
for (Iterator iterator{ mRenderPipelines.begin() }; iterator != mRenderPipelines.end();) {
RenderPipelineCacheEntry const& entry{ iterator.value() };
@@ -89,6 +89,7 @@ bool WebGPUPipelineLayoutCache::PipelineLayoutKeyEqual::operator()(PipelineLayou
}
void WebGPUPipelineLayoutCache::removeExpiredPipelineLayouts() {
FWGPU_SYSTRACE_SCOPE();
using Iterator = decltype(mPipelineLayouts)::const_iterator;
for (Iterator iterator{ mPipelineLayouts.begin() }; iterator != mPipelineLayouts.end();) {
PipelineLayoutCacheEntry const& entry{ iterator.value() };
@@ -283,6 +283,24 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
toWebGPUTextureViewDimension(src->target)) },
mMsaaSidecarTexture{ src->mMsaaSidecarTexture } {}
WebGPUTexture::WebGPUTexture(const WebGPUTexture* src, const wgpu::TextureView view) noexcept
: HwTexture{ src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage},
mViewFormat{ src->mViewFormat },
mMipmapGenerationStrategy{ src->mMipmapGenerationStrategy },
mWebGPUFormat{ src->mWebGPUFormat },
mAspect{ src->mAspect },
mWebGPUUsage{ src->mWebGPUUsage },
mViewUsage{ src->mViewUsage },
mBlockWidth{ src->mBlockWidth },
mBlockHeight{ src->mBlockHeight },
mArrayLayerCount{ src->mArrayLayerCount },
mTexture{ src->mTexture },
mDefaultMipLevel{ src->mDefaultMipLevel },
mDefaultBaseArrayLayer{ 0 },
mDefaultTextureView{ view },
mMsaaSidecarTexture{src->mMsaaSidecarTexture}{}
wgpu::Texture const& WebGPUTexture::getMsaaSidecarTexture(const uint8_t sampleCount) const {
if (mMsaaSidecarTexture == nullptr) {
return mMsaaSidecarTexture; // nullptr (no such sidecar)
@@ -34,11 +34,26 @@ public:
NONE,
};
/**
* Creates a Filament texture and a texture view
*/
WebGPUTexture(SamplerType, uint8_t levels, TextureFormat, uint8_t samples, uint32_t width,
uint32_t height, uint32_t depth, TextureUsage, wgpu::Device const&) noexcept;
/**
* Creates a "Filament Texture View", where the underlying texture is the same as the source,
* but the view elements differ
*/
WebGPUTexture(WebGPUTexture const* src, uint8_t baseLevel, uint8_t levelCount) noexcept;
/**
* @param textureView texture view to use for the respective texture. e.g. it can be a
* swizzled Texture view for that filament texture
*
* Associates the underlying texture with the given texture view
*/
WebGPUTexture(WebGPUTexture const* source, wgpu::TextureView textureView) noexcept;
[[nodiscard]] wgpu::TextureAspect getAspect() const { return mAspect; }
[[nodiscard]] size_t getBlockWidth() const { return mBlockWidth; }
@@ -295,6 +295,20 @@ namespace filament::backend {
}
}
[[nodiscard]] constexpr wgpu::ComponentSwizzle toWGPUComponentSwizzle(const backend::TextureSwizzle swizzle) {
switch (swizzle) {
case backend::TextureSwizzle::CHANNEL_0: return wgpu::ComponentSwizzle::R;
case backend::TextureSwizzle::CHANNEL_1: return wgpu::ComponentSwizzle::G;
case backend::TextureSwizzle::CHANNEL_2: return wgpu::ComponentSwizzle::B;
case backend::TextureSwizzle::CHANNEL_3: return wgpu::ComponentSwizzle::A;
case backend::TextureSwizzle::SUBSTITUTE_ZERO: return wgpu::ComponentSwizzle::Zero;
case backend::TextureSwizzle::SUBSTITUTE_ONE: return wgpu::ComponentSwizzle::One;
default:
FWGPU_LOGW << "invalid swizzle component returning wgpu::ComponentSwizzle::Undefined";
return wgpu::ComponentSwizzle::Undefined;
}
}
/**
* @return true if https://www.w3.org/TR/webgpu/#copy-compatible which states:
* Two GPUTextureFormats format1 and format2 are copy-compatible if:
@@ -67,9 +67,11 @@ constexpr std::array REQUIRED_FEATURES = {
};
constexpr std::array OPTIONAL_FEATURES = {
wgpu::FeatureName::CoreFeaturesAndLimits,
wgpu::FeatureName::DepthClipControl,
wgpu::FeatureName::Depth32FloatStencil8,
wgpu::FeatureName::CoreFeaturesAndLimits };
wgpu::FeatureName::TextureComponentSwizzle,
};
enum class LimitToValidate : uint8_t {
begin = 0,// needs to be first for iterating through all possible values in the enum
@@ -41,36 +41,21 @@
uint32_t height;
} wl;
}// namespace
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
// TODO: we should allow for headless on Linux explicitly. Right now this is the headless path
// (with no FILAMENT_SUPPORTS_XCB or FILAMENT_SUPPORTS_XLIB).
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11) && defined(FILAMENT_SUPPORTS_XLIB)
// TODO: we should allow for headless on Linux explicitly. Right now headless path doesn't work
#include <dlfcn.h>
#if defined(FILAMENT_SUPPORTS_XCB)
#include <xcb/xcb.h>
namespace {
typedef xcb_connection_t* (*XCB_CONNECT)(const char* displayname, int* screenp);
}// namespace
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
#include <X11/Xlib.h>
namespace {
typedef Display* (*X11_OPEN_DISPLAY)(const char*);
}// namespace
#endif
static constexpr const char* LIBRARY_X11 = "libX11.so.6";
#include <X11/Xlib.h>
static constexpr const char* LIBRARY_X11 { "libX11.so.6" };
namespace {
struct XEnv {
#if defined(FILAMENT_SUPPORTS_XCB)
XCB_CONNECT xcbConnect;
xcb_connection_t* connection;
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
X11_OPEN_DISPLAY openDisplay;
Display* display;
#endif
void* library = nullptr;
} g_x11;
}// namespace
typedef Display* (*X11_OPEN_DISPLAY)(const char*);
struct XEnv final {
X11_OPEN_DISPLAY openDisplay;
Display* display { nullptr };
void* library { nullptr };
} g_x11;
} // namespace
#else
#error Not a supported Linux or FeeBSD + WebGPU platform
#endif
@@ -116,59 +101,26 @@ wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
surfaceExtent.height = ptrval->height;
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Unable to get window size for Linux Wayland-backed surface.";
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11) && defined (FILAMENT_SUPPORTS_XLIB)
if (g_x11.library == nullptr) {
g_x11.library = dlopen(LIBRARY_X11, RTLD_LOCAL | RTLD_NOW);
FILAMENT_CHECK_PRECONDITION(g_x11.library) << "Unable to open X11 library.";
#if defined(FILAMENT_SUPPORTS_XCB)
g_x11.xcbConnect = (XCB_CONNECT) dlsym(g_x11.library, "xcb_connect");
int screen = 0;
g_x11.connection = g_x11.xcbConnect(nullptr, &screen);
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
g_x11.openDisplay = (X11_OPEN_DISPLAY) dlsym(g_x11.library, "XOpenDisplay");
g_x11.display = g_x11.openDisplay(NULL);
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
#endif
}
#if defined(FILAMENT_SUPPORTS_XCB) || defined(FILAMENT_SUPPORTS_XLIB)
bool useXcb = false;
#endif
#if defined(FILAMENT_SUPPORTS_XCB)
#if defined(FILAMENT_SUPPORTS_XLIB)
useXcb = (SWAP_CHAIN_CONFIG_ENABLE_XCB) != 0;
#else
useXcb = true;
#endif
if (useXcb) {
const xcb_setup_t* setup = xcb_get_setup(g_x11.connection);
xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(setup);
xcb_screen_t* screen = screen_iter.data;
surfaceExtent.width = static_cast<uint32_t>(screen->width_in_pixels);
surfaceExtent.height = static_cast<uint32_t>(screen->height_in_pixels);
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Unable to get window surface size for Linux (or FreeBSD) "
"XCB-backed surface.";
}
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
if (!useXcb) {
int screenNumber = DefaultScreen(g_x11.display);
Screen* screen = ScreenOfDisplay(g_x11.display, screenNumber);
surfaceExtent.width = static_cast<uint32_t>(WidthOfScreen(screen));
surfaceExtent.height = static_cast<uint32_t>(HeightOfScreen(screen));
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Unable to get window surface size for Linux (or FreeBSD) "
"XLib-backed surface.";
}
#endif
if (g_x11.openDisplay == nullptr) {
g_x11.openDisplay = reinterpret_cast<X11_OPEN_DISPLAY>(dlsym(g_x11.library, "XOpenDisplay"));
g_x11.display = g_x11.openDisplay(NULL);
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
}
Window window { reinterpret_cast<Window const>(nativeWindow) };
XWindowAttributes windowAttributes;
Status ok { XGetWindowAttributes(g_x11.display, window, &windowAttributes) };
FILAMENT_CHECK_PRECONDITION(ok != 0) << " XGetWindowAttributes failed";
surfaceExtent.width = static_cast<uint32_t>(windowAttributes.width);
surfaceExtent.height = static_cast<uint32_t>(windowAttributes.height);
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Cannot get window surface size for X11 surface for Linux (or FreeBSD) OS "
"(not built with support for XCB or XLIB?)";
#elif defined(__linux__)
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Cannot get window surface size for Linux (or FreeBSD) OS "
"(not built with support for Wayland or X11?)";
"(not built with support for XLIB?)";
#else
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Not a supported (Linux) OS + WebGPU platform";
@@ -176,80 +128,40 @@ wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
return surfaceExtent;
}
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t flags) {
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) {
wgpu::Surface surface = nullptr;
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
wl* ptrval = reinterpret_cast<wl*>(nativeWindow);
wgpu::SurfaceSourceWaylandSurface surfaceSourceWayland{};
surfaceSourceWayland.display = ptrval->display;
surfaceSourceWayland.surface = ptrval->surface;
wgpu::SurfaceDescriptor surfaceDescriptor{
wgpu::SurfaceDescriptor const surfaceDescriptor {
.nextInChain = &surfaceSourceWayland,
.label = "linux_wayland_surface"
};
surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
<< "Unable to create Linux Wayland-backed surface.";
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11) && defined(FILAMENT_SUPPORTS_XLIB)
if (g_x11.library == nullptr) {
g_x11.library = dlopen(LIBRARY_X11, RTLD_LOCAL | RTLD_NOW);
FILAMENT_CHECK_PRECONDITION(g_x11.library) << "Unable to open X11 library.";
#if defined(FILAMENT_SUPPORTS_XCB)
g_x11.xcbConnect = (XCB_CONNECT) dlsym(g_x11.library, "xcb_connect");
int screen = 0;
g_x11.connection = g_x11.xcbConnect(nullptr, &screen);
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
g_x11.openDisplay = (X11_OPEN_DISPLAY) dlsym(g_x11.library, "XOpenDisplay");
g_x11.display = g_x11.openDisplay(NULL);
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
#endif
}
#if defined(FILAMENT_SUPPORTS_XCB) || defined(FILAMENT_SUPPORTS_XLIB)
bool useXcb = false;
#endif
#if defined(FILAMENT_SUPPORTS_XCB)
#if defined(FILAMENT_SUPPORTS_XLIB)
useXcb = (flags & SWAP_CHAIN_CONFIG_ENABLE_XCB) != 0;
#else
useXcb = true;
#endif
if (useXcb) {
wgpu::SurfaceSourceXCBWindow surfaceSourceXcb{};
surfaceSourceXcb.connection = g_x11.connection;
// TODO: this looks really wrong, please fix!!
surfaceSourceXcb.window = *((uint32_t*) nativeWindow);
wgpu::SurfaceDescriptor surfaceDescriptor{
.nextInChain = &surfaceSourceXcb,
.label = "linux_xcb_surface"
};
surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
<< "Unable to create Linux (or FreeBSD) XCB-backed surface.";
}
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
if (!useXcb) {
wgpu::SurfaceSourceXlibWindow surfaceSourceXlib{};
surfaceSourceXlib.display = g_x11.display;
surfaceSourceXlib.window = reinterpret_cast<uint64_t>(nativeWindow);
wgpu::SurfaceDescriptor surfaceDescriptor{
.nextInChain = &surfaceSourceXlib,
.label = "linux_xlib_surface"
};
surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
<< "Unable to create Linux (or FreeBSD) XLib-backed surface.";
}
#endif
if (g_x11.openDisplay == nullptr) {
g_x11.openDisplay = (X11_OPEN_DISPLAY) dlsym(g_x11.library, "XOpenDisplay");
g_x11.display = g_x11.openDisplay(NULL);
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
}
wgpu::SurfaceSourceXlibWindow surfaceSourceXlib{};
surfaceSourceXlib.display = g_x11.display;
surfaceSourceXlib.window = reinterpret_cast<Window const>(nativeWindow);
wgpu::SurfaceDescriptor const surfaceDescriptor {
.nextInChain = &surfaceSourceXlib,
.label = "linux_xlib_surface"
};
surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
<< "Cannot create WebGPU X11 surface for Linux (or FreeBSD) OS "
"(not built with support for XCB or XLIB?)";
#elif defined(__linux__)
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
<< "Cannot create WebGPU surface for Linux (or FreeBSD) OS "
"(not built with support for Wayland or X11?)";
<< "Unable to create Linux (or FreeBSD) XLib-backed surface.";
#else
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
<< "Not a supported (Linux) OS + WebGPU platform";
+3
View File
@@ -144,6 +144,7 @@ static void createFaces(DriverApi& dapi, Handle<HwTexture> texture, int baseWidt
}
TEST_F(BlitTest, ColorMagnify) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
auto& api = getDriverApi();
mCleanup.addPostCall([&]() { executeCommands(); });
@@ -207,6 +208,7 @@ TEST_F(BlitTest, ColorMagnify) {
}
TEST_F(BlitTest, ColorMinify) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
auto& api = getDriverApi();
mCleanup.addPostCall([&]() { executeCommands(); });
@@ -262,6 +264,7 @@ TEST_F(BlitTest, ColorMinify) {
}
TEST_F(BlitTest, ColorResolve) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Nothing is drawn, see b/417229577");
auto& api = getDriverApi();
+1 -2
View File
@@ -104,7 +104,6 @@ TEST_F(BackendTest, FeedbackLoops) {
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL),
"OpenGL image is upside down due to readPixels failing for texture with uploaded image "
"data");
FAIL_IF(Backend::WEBGPU, "BUG");
auto& api = getDriverApi();
Cleanup cleanup(api);
@@ -127,7 +126,7 @@ TEST_F(BackendTest, FeedbackLoops) {
TrianglePrimitive const triangle(getDriverApi());
// Create a texture.
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
Handle<HwTexture> const texture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage));
+2 -1
View File
@@ -231,6 +231,7 @@ public:
};
TEST_F(LoadImageTest, UpdateImage2D) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
FAIL_IF(Backend::VULKAN, "Multiple test cases crash, see b/417481434");
// All of these test cases should result in the same rendered image, and thus the same hash.
@@ -569,7 +570,7 @@ TEST_F(LoadImageTest, UpdateImage3D) {
PixelDataType pixelType = PixelDataType::FLOAT;
TextureFormat textureFormat = TextureFormat::RGBA16F;
SamplerType samplerType = SamplerType::SAMPLER_2D_ARRAY;
TextureUsage usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
TextureUsage usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT;
// Create a platform-specific SwapChain and make it current.
auto swapChain = cleanup.add(createSwapChain());
@@ -80,6 +80,7 @@ public:
};
TEST_F(ReadPixelsTest, ReadPixels) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Two cases fail, see b/417255941 and b/417255943");
// These test scenarios use a known hash of the result pixel buffer to decide pass / fail,
@@ -46,8 +46,9 @@ Shader createShader(DriverApi& api, Cleanup& cleanup, Backend backend) {
// Rendering an external image without setting any data should not crash.
TEST_F(BackendTest, RenderExternalImageWithoutSet) {
SKIP_IF(Backend::METAL, "External images aren't supported in metal");
SKIP_IF(Backend::VULKAN, "External images aren't supported in vulkan");
SKIP_IF(Backend::METAL, "External images aren't supported in Metal");
SKIP_IF(Backend::VULKAN, "External images aren't supported in Vulkan");
SKIP_IF(Backend::WEBGPU, "External images aren't supported in WebGPU");
auto& api = getDriverApi();
Cleanup cleanup(api);
@@ -109,8 +110,9 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
}
TEST_F(BackendTest, RenderExternalImage) {
SKIP_IF(Backend::METAL, "External images aren't supported in metal");
SKIP_IF(Backend::VULKAN, "External images aren't supported in vulkan");
SKIP_IF(Backend::METAL, "External images aren't supported in Metal");
SKIP_IF(Backend::VULKAN, "External images aren't supported in Vulkan");
SKIP_IF(Backend::WEBGPU, "External images aren't supported in WebGPU");
auto& api = getDriverApi();
Cleanup cleanup(api);
+1
View File
@@ -31,6 +31,7 @@ using namespace filament;
using namespace filament::backend;
TEST_F(BackendTest, ScissorViewportRegion) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Affected area in wrong corner, see b/417229118");
auto& api = getDriverApi();
@@ -126,6 +126,7 @@ public:
};
TEST_F(BasicStencilBufferTest, StencilBuffer) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Stencil not supported, see b/417230776");
auto& api = getDriverApi();
@@ -152,6 +153,7 @@ TEST_F(BasicStencilBufferTest, StencilBuffer) {
}
TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Stencil not supported, see b/417230776");
auto& api = getDriverApi();
@@ -178,6 +180,7 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
}
TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Stencil not supported, see b/417230776");
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL), "Stencil isn't applied");
+53 -3
View File
@@ -87,6 +87,9 @@ public:
std::is_same_v<math::mat3f, T>
>;
template<typename T>
using is_supported_constant_parameter_t = std::enable_if_t<std::is_same_v<bool, T>>;
/**
* Creates a new MaterialInstance using another MaterialInstance as a template for initialization.
* The new MaterialInstance is an instance of the same Material of the template instance and
@@ -238,13 +241,13 @@ public:
/**
* Gets the value of a parameter by name.
*
*
* Note: Only supports non-texture parameters such as numeric and math types.
*
*
* @param name Name of the parameter as defined by Material. Cannot be nullptr.
* @param nameLength Length in `char` of the name parameter.
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
*
*
* @see Material::hasParameter
*/
template<typename T>
@@ -262,6 +265,53 @@ public:
return getParameter<T>(name, strlen(name));
}
/**
* Set a mutable constant parameter by name.
*
* @param name Name of the parameter as defined by Material. Cannot be nullptr.
* @param nameLength Length in `char` of the name parameter.
* @param value Value of the parameter to set.
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
*/
template<typename T, typename = is_supported_constant_parameter_t<T>>
void setConstant(const char* UTILS_NONNULL name, size_t nameLength, T const& value);
/** inline helper to provide the name as a null-terminated string literal */
template<typename T, typename = is_supported_constant_parameter_t<T>>
void setConstant(StringLiteral const name, T const& value) {
setConstant<T>(name.data, name.size, value);
}
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
void setConstant(const char* UTILS_NONNULL name, T const& value) {
setConstant<T>(name, strlen(name), value);
}
/**
* Gets the value of a mutable constant parameter by name.
*
* @param name Name of the parameter as defined by Material. Cannot be nullptr.
* @param nameLength Length in `char` of the name parameter.
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
*
* @see Material::hasConstant
*/
template<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(const char* UTILS_NONNULL name, size_t nameLength) const;
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(StringLiteral const name) const {
return getConstant<T>(name.data, name.size);
}
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(const char* UTILS_NONNULL name) const {
return getConstant<T>(name, strlen(name));
}
/**
* Set-up a custom scissor rectangle; by default it is disabled.
*
+14 -1
View File
@@ -437,7 +437,20 @@ struct AmbientOcclusionOptions {
struct Gtao {
uint8_t sampleSliceCount = 4; //!< # of slices. Higher value makes less noise.
uint8_t sampleStepsPerSlice = 3; //!< # of steps the radius is divided into for integration. Higher value makes less bias.
float thicknessHeuristic = 0.004f; //!< thickness heuristic, should be closed to 0
float thicknessHeuristic = 0.004f; //!< thickness heuristic, should be closed to 0. No effect when useVisibilityBitmasks sets to true.
/**
* Enables or disables visibility bitmasks mode. Notes that bent normal doesn't work under this mode.
* Caution: Changing this option at runtime is very expensive as it may trigger a shader re-compilation.
*/
bool useVisibilityBitmasks = false;
float constThickness = 0.5f; //!< constant thickness value of objects on the screen in world space. Only take effect when useVisibilityBitmasks is set to true.
/**
* Increase thickness with distance to maintain detail on distant surfaces.
* Caution: Changing this option at runtime is very expensive as it may trigger a shader re-compilation.
*/
bool linearThickness = false;
};
Gtao gtao; // %codegen_skip_javascript% %codegen_java_flatten%
};
+24
View File
@@ -27,6 +27,7 @@
#include <utils/FixedCapacityVector.h>
#include <math/mathfwd.h>
#include <math/mat4.h>
#include <utility>
@@ -757,6 +758,29 @@ public:
//! debugging: returns a Camera from the point of view of *the* dominant directional light used for shadowing.
utils::FixedCapacityVector<Camera const*> getDirectionalShadowCameras() const noexcept;
//! debugging: enable or disable froxel visualisation for this view.
void setFroxelVizEnabled(bool enabled) noexcept;
//! debugging: returns information about the froxel configuration
struct FroxelConfigurationInfo {
uint8_t width;
uint8_t height;
uint8_t depth;
uint32_t viewportWidth;
uint32_t viewportHeight;
math::uint2 froxelDimension;
float zLightFar;
float linearizer;
math::mat4f p;
math::float4 clipTransform;
};
struct FroxelConfigurationInfoWithAge {
FroxelConfigurationInfo info;
uint32_t age;
};
FroxelConfigurationInfoWithAge getFroxelConfigurationInfo() const noexcept;
/** Result of a picking query */
struct PickingQueryResult {
+27 -8
View File
@@ -124,6 +124,20 @@ size_t Froxelizer::getFroxelBufferByteCount(FEngine::DriverApi& driverApi) noexc
return std::min(FROXEL_BUFFER_MAX_ENTRY_COUNT * sizeof(FroxelEntry), targetSize);
}
View::FroxelConfigurationInfo Froxelizer::getFroxelConfigurationInfo() const noexcept {
return { uint8_t(getFroxelCountX()),
uint8_t(getFroxelCountY()),
uint8_t(getFroxelCountZ()),
mViewport.width,
mViewport.height,
mFroxelDimension,
mZLightFar,
mLinearizer[0],
mProjection,
mClipTransform
};
}
Froxelizer::Froxelizer(FEngine& engine)
: mArena("froxel", PER_FROXELDATA_ARENA_SIZE),
mZLightNear(FROXEL_FIRST_SLICE_DEPTH),
@@ -198,10 +212,14 @@ void Froxelizer::setProjection(const mat4f& projection,
bool Froxelizer::prepare(
FEngine::DriverApi& driverApi, RootArenaScope& rootArenaScope,
filament::Viewport const& viewport,
const mat4f& projection, float const projectionNear, float const projectionFar) noexcept {
const mat4f& projection, float const projectionNear, float const projectionFar,
float4 const& clipTransform) noexcept {
setViewport(viewport);
setProjection(projection, projectionNear, projectionFar);
// Only for debugging
mClipTransform = clipTransform;
bool uniformsNeedUpdating = false;
if (UTILS_UNLIKELY(mDirtyFlags)) {
uniformsNeedUpdating = update();
@@ -361,8 +379,10 @@ bool Froxelizer::update() noexcept {
getFroxelBufferEntryCount(), viewport);
mFroxelDimension = froxelDimension;
mClipToFroxelX = (0.5f * float(viewport.width)) / float(froxelDimension.x);
mClipToFroxelY = (0.5f * float(viewport.height)) / float(froxelDimension.y);
// note: because froxelDimension is a power-of-two and viewport is an integer, mClipFroxel
// is an exact value (which is not true for 1/mClipToFroxelX, btw)
mClipToFroxelX = float(viewport.width) / float(2 * froxelDimension.x);
mClipToFroxelY = float(viewport.height) / float(2 * froxelDimension.y);
uniformsNeedUpdating = true;
@@ -408,8 +428,7 @@ bool Froxelizer::update() noexcept {
}
// for the inverse-transformation (view-space z to z-slice)
mLinearizer = 1.0f / linearizer;
mZLightFar = zLightFar;
mLinearizer = { linearizer, 1.0f / linearizer };
mParamsZ[0] = 0; // updated when camera changes
mParamsZ[1] = 0; // updated when camera changes
@@ -466,7 +485,7 @@ bool Froxelizer::update() noexcept {
// ==> i = log2(z_screen * (far/near)) * (-1/linearizer) + zcount
mParamsZ[0] = mZLightFar / Pw;
mParamsZ[1] = 0.0f;
mParamsZ[2] = -mLinearizer;
mParamsZ[2] = -mLinearizer[1];
} else {
// orthographic projection
// z_view = (1 - z_screen) * (near - far) - near
@@ -476,7 +495,7 @@ bool Froxelizer::update() noexcept {
// Pw = far / (far - near)
mParamsZ[0] = -1.0f / (Pz * mZLightFar); // -(far-near) / mZLightFar
mParamsZ[1] = Pw / (Pz * mZLightFar); // far / mZLightFar
mParamsZ[2] = mLinearizer;
mParamsZ[2] = mLinearizer[1];
}
uniformsNeedUpdating = true;
}
@@ -507,7 +526,7 @@ size_t Froxelizer::findSliceZ(float const z) const noexcept {
// This whole function is now branch-less.
int s = int( fast::log2(-z / mZLightFar) * mLinearizer + float(mFroxelCountZ) );
int s = int( fast::log2(-z / mZLightFar) * mLinearizer[1] + float(mFroxelCountZ) );
// there are cases where z can be negative here, e.g.:
// - the light is visible, but its center is behind the camera
+8 -2
View File
@@ -22,6 +22,7 @@
#include "details/Scene.h"
#include "details/Engine.h"
#include <filament/View.h>
#include <filament/Viewport.h>
#include <backend/Handle.h>
@@ -31,6 +32,7 @@
#include <utils/Slice.h>
#include <math/mat4.h>
#include <math/vec2.h>
#include <math/vec4.h>
namespace filament {
@@ -112,7 +114,8 @@ public:
* return true if updateUniforms() needs to be called
*/
bool prepare(backend::DriverApi& driverApi, RootArenaScope& rootArenaScope, Viewport const& viewport,
const math::mat4f& projection, float projectionNear, float projectionFar) noexcept;
const math::mat4f& projection, float projectionNear, float projectionFar,
math::float4 const& clipTransform) noexcept;
Froxel getFroxelAt(size_t x, size_t y, size_t z) const noexcept;
size_t getFroxelCountX() const noexcept { return mFroxelCountX; }
@@ -161,6 +164,8 @@ public:
static size_t getFroxelBufferByteCount(FEngine::DriverApi& driverApi) noexcept;
View::FroxelConfigurationInfo getFroxelConfigurationInfo() const noexcept;
private:
size_t getFroxelBufferEntryCount() const noexcept {
return mFroxelBufferEntryCount;
@@ -260,9 +265,10 @@ private:
uint16_t mFroxelCountZ = 0;
uint32_t mFroxelCount = 0;
math::uint2 mFroxelDimension = {};
math::float4 mClipTransform = { 1, 1, 0, 0 };
math::mat4f mProjection;
float mLinearizer = 0.0f;
math::float2 mLinearizer{};
float mClipToFroxelX = 0.0f;
float mClipToFroxelY = 0.0f;
backend::BufferObjectHandle mRecordsBuffer;
+40
View File
@@ -233,6 +233,46 @@ template UTILS_PUBLIC mat3f MaterialInstance::getParameter<mat3f> (const ch
// ------------------------------------------------------------------------------------------------
template<>
inline void FMaterialInstance::setConstantImpl<bool>(std::string_view const name, bool const& value) {
std::optional<uint32_t> id = mMaterial->getMutableConstantId(name);
FILAMENT_CHECK_PRECONDITION(id.has_value()) << "No mutable constant with name " << name;
mConstants.set(*id, value);
}
template<typename T, typename>
void MaterialInstance::setConstant(const char* name, size_t nameLength, T const& value) {
downcast(this)->setConstantImpl({ name, nameLength }, value);
}
// Explicit template instantiation of our supported types.
//
// Mutable spec constants will probably only ever allow bools, but it's nice to keep the API
// forwards-compatible.
template UTILS_PUBLIC void MaterialInstance::setConstant<bool>(const char* name, size_t nameLength, bool const& v);
// ------------------------------------------------------------------------------------------------
template<>
inline bool FMaterialInstance::getConstantImpl<bool>(std::string_view const name) const {
std::optional<uint32_t> id = mMaterial->getMutableConstantId(name);
FILAMENT_CHECK_PRECONDITION(id.has_value()) << "No mutable constant with name " << name;
return mConstants[*id];
}
template<typename T, typename>
T MaterialInstance::getConstant(const char* name, size_t nameLength) const {
return downcast(this)->getConstantImpl<T>({ name, nameLength });
}
// Explicit template instantiation of our supported types.
//
// Mutable spec constants will probably only ever allow bools, but it's nice to keep the API
// forwards-compatible.
template UTILS_PUBLIC bool MaterialInstance::getConstant<bool>(const char* name, size_t nameLength) const;
// ------------------------------------------------------------------------------------------------
Material const* MaterialInstance::getMaterial() const noexcept {
return downcast(this)->getMaterial();
}
+37
View File
@@ -229,6 +229,11 @@ bool MaterialParser::getConstants(FixedCapacityVector<MaterialConstant>* contain
return get<ChunkMaterialConstants>(container);
}
bool MaterialParser::getMutableConstants(
FixedCapacityVector<MaterialMutableConstant>* container) const noexcept {
return get<ChunkMaterialMutableConstants>(container);
}
bool MaterialParser::getPushConstants(CString* structVarName,
FixedCapacityVector<MaterialPushConstant>* value) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialPushConstants);
@@ -761,6 +766,38 @@ bool ChunkMaterialConstants::unflatten(Unflattener& unflattener,
return true;
}
bool ChunkMaterialMutableConstants::unflatten(Unflattener& unflattener,
FixedCapacityVector<MaterialMutableConstant>* materialConstants) {
assert_invariant(materialConstants);
// Read number of constants.
uint64_t numConstants = 0;
if (!unflattener.read(&numConstants)) {
return false;
}
materialConstants->reserve(numConstants);
materialConstants->resize(numConstants);
for (uint64_t i = 0; i < numConstants; i++) {
CString constantName;
bool defaultValue;
if (!unflattener.read(&constantName)) {
return false;
}
if (!unflattener.read(&defaultValue)) {
return false;
}
(*materialConstants)[i].name = std::move(constantName);
(*materialConstants)[i].defaultValue = defaultValue;
}
return true;
}
bool ChunkMaterialPushConstants::unflatten(Unflattener& unflattener,
CString* structVarName,
FixedCapacityVector<MaterialPushConstant>* materialPushConstants) {
+10
View File
@@ -49,6 +49,7 @@ class BufferInterfaceBlock;
class SamplerInterfaceBlock;
struct SubpassInfo;
struct MaterialConstant;
struct MaterialMutableConstant;
struct MaterialPushConstant;
class MaterialParser {
@@ -79,6 +80,8 @@ public:
bool getShaderModels(uint32_t* value) const noexcept;
bool getMaterialProperties(uint64_t* value) const noexcept;
bool getConstants(utils::FixedCapacityVector<MaterialConstant>* value) const noexcept;
bool getMutableConstants(utils::FixedCapacityVector<MaterialMutableConstant>* value)
const noexcept;
bool getPushConstants(utils::CString* structVarName,
utils::FixedCapacityVector<MaterialPushConstant>* value) const noexcept;
@@ -238,6 +241,13 @@ struct ChunkMaterialConstants {
static filamat::ChunkType const tag = filamat::MaterialConstants;
};
struct ChunkMaterialMutableConstants {
static bool unflatten(filaflat::Unflattener& unflattener,
utils::FixedCapacityVector<MaterialMutableConstant>* materialConstants);
using Container = utils::FixedCapacityVector<MaterialMutableConstant>;
static filamat::ChunkType const tag = filamat::MaterialMutableConstants;
};
struct ChunkMaterialPushConstants {
static bool unflatten(filaflat::Unflattener& unflattener, utils::CString* structVarName,
utils::FixedCapacityVector<MaterialPushConstant>* materialPushConstants);
+18 -2
View File
@@ -199,6 +199,8 @@ FMaterial* PostProcessManager::PostProcessMaterial::getMaterial(FEngine& engine,
loadMaterial(engine);
}
mMaterial->prepareProgram(Variant{ Variant::type_t(variant) },
// TODO(exv): allow post process materials to use mutable spec constants?
Program::MutableSpecConstantsInfo(0),
CompilerPriorityQueue::CRITICAL);
return mMaterial;
}
@@ -465,7 +467,10 @@ UTILS_NOINLINE
PipelineState PostProcessManager::getPipelineState(
FMaterial const* const ma, PostProcessVariant variant) const noexcept {
return {
.program = ma->getProgram(Variant{ Variant::type_t(variant) }),
.program = ma->getProgram(Variant{ Variant::type_t(variant) },
// TODO(exv): allow post process materials to use mutable spec constants?
Program::MutableSpecConstantsInfo(0)
),
.vertexBufferInfo = mFullScreenQuadVbih,
.pipelineLayout = {
.setLayout = {
@@ -993,7 +998,17 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
#endif
auto& material = getPostProcessMaterial(materialName);
FMaterial const * const ma = material.getMaterial(mEngine);
FMaterial* ma = material.getMaterial(mEngine);
bool dirty = false;
setConstantParameter(ma, "useVisibilityBitmasks", options.gtao.useVisibilityBitmasks, dirty);
setConstantParameter(ma, "linearThickness", options.gtao.linearThickness, dirty);
if (dirty) {
ma->invalidate();
// TODO: call Material::compile(), we can't do that now because it works only
// with surface materials
}
ma = material.getMaterial(mEngine);
FMaterialInstance* const mi = getMaterialInstance(ma);
// Set AO type specific material parameters
@@ -1028,6 +1043,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
mi->setParameter("radius", options.radius);
mi->setParameter("intensity", options.intensity);
mi->setParameter("thicknessHeuristic", options.gtao.thicknessHeuristic);
mi->setParameter("constThickness", options.gtao.constThickness);
break;
}
+5 -2
View File
@@ -243,7 +243,9 @@ void RenderPass::appendCommands(FEngine const& engine,
for (Command const* first = curr, *last = curr + commandCount ; first != last ; ++first) {
if (UTILS_LIKELY((first->key & CUSTOM_MASK) == uint64_t(CustomCommand::PASS))) {
auto ma = first->info.mi->getMaterial();
ma->prepareProgram(first->info.materialVariant, CompilerPriorityQueue::CRITICAL);
ma->prepareProgram(first->info.materialVariant,
first->info.mi->getMutableSpecConstants(),
CompilerPriorityQueue::CRITICAL);
}
}
}
@@ -1096,7 +1098,8 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
}
assert_invariant(ma);
pipeline.program = ma->getProgram(info.materialVariant);
pipeline.program = ma->getProgram(info.materialVariant,
info.mi->getMutableSpecConstants());
if (UTILS_UNLIKELY(memcmp(&pipeline, &currentPipeline, sizeof(PipelineState)) != 0)) {
currentPipeline = pipeline;
+8
View File
@@ -77,6 +77,14 @@ utils::FixedCapacityVector<Camera const*> View::getDirectionalShadowCameras() co
return downcast(this)->getDirectionalShadowCameras();
}
void View::setFroxelVizEnabled(bool const enabled) noexcept {
downcast(this)->setFroxelVizEnabled(enabled);
}
View::FroxelConfigurationInfoWithAge View::getFroxelConfigurationInfo() const noexcept {
return downcast(this)->getFroxelConfigurationInfo();
}
void View::setShadowingEnabled(bool const enabled) noexcept {
downcast(this)->setShadowingEnabled(enabled);
}
+1 -1
View File
@@ -748,7 +748,7 @@ public:
bool enable_staging_buffer_bypass = false;
} vulkan;
bool disable_parallel_shader_compile = false;
bool disable_amortized_shader_compile = false;
bool disable_amortized_shader_compile = true;
bool disable_handle_use_after_free_check = false;
bool disable_heap_handle_tags = true; // FIXME: this should be false
} backend;
+96 -32
View File
@@ -364,8 +364,24 @@ FMaterial::FMaterial(FEngine& engine, const Builder& builder,
processBlendingMode(parser);
processSpecializationConstants(engine, builder, parser);
processMutableSpecializationConstants(engine, builder, parser);
processPushConstants(engine, parser);
processDescriptorSets(engine, parser);
switch (mMaterialDomain) {
case filament::MaterialDomain::SURFACE:
mVariantBits = VARIANT_BITS;
break;
case filament::MaterialDomain::POST_PROCESS:
mVariantBits = POST_PROCESS_VARIANT_BITS;
break;
case filament::MaterialDomain::COMPUTE:
mVariantBits = 0;
break;
}
mCachedPrograms = FixedCapacityVector<backend::Handle<backend::HwProgram>>(
1 << (mVariantBits + mMaterialMutableConstants.size()));
precacheDepthVariants(engine);
#if FILAMENT_ENABLE_MATDBG
@@ -466,13 +482,17 @@ void FMaterial::compile(CompilerPriorityQueue const priority,
UserVariantFilterMask const variantFilter =
~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL);
int mutableConstantVariations = 1 << mMaterialMutableConstants.size();
if (UTILS_LIKELY(mEngine.getDriverApi().isParallelShaderCompileSupported())) {
auto const& variants = isVariantLit() ?
VariantUtils::getLitVariants() : VariantUtils::getUnlitVariants();
for (auto const variant: variants) {
if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) {
if (hasVariant(variant)) {
prepareProgram(variant, priority);
for (int spec = 0; spec < mutableConstantVariations; spec++) {
prepareProgram(variant, Program::MutableSpecConstantsInfo(spec), priority);
}
}
}
}
@@ -553,14 +573,15 @@ bool FMaterial::hasVariant(Variant const variant) const noexcept {
}
void FMaterial::prepareProgramSlow(Variant const variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
backend::CompilerPriorityQueue const priorityQueue) const noexcept {
assert_invariant(mEngine.hasFeatureLevel(mFeatureLevel));
switch (getMaterialDomain()) {
case MaterialDomain::SURFACE:
getSurfaceProgramSlow(variant, priorityQueue);
getSurfaceProgramSlow(variant, mutableSpecConstants, priorityQueue);
break;
case MaterialDomain::POST_PROCESS:
getPostProcessProgramSlow(variant, priorityQueue);
getPostProcessProgramSlow(variant, mutableSpecConstants, priorityQueue);
break;
case MaterialDomain::COMPUTE:
// TODO: implement MaterialDomain::COMPUTE
@@ -569,6 +590,7 @@ void FMaterial::prepareProgramSlow(Variant const variant,
}
void FMaterial::getSurfaceProgramSlow(Variant const variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue const priorityQueue) const noexcept {
// filterVariant() has already been applied in generateCommands(), shouldn't be needed here
// if we're unlit, we don't have any bits that correspond to lit materials
@@ -579,25 +601,25 @@ void FMaterial::getSurfaceProgramSlow(Variant const variant,
Variant const vertexVariant = Variant::filterVariantVertex(variant);
Variant const fragmentVariant = Variant::filterVariantFragment(variant);
Program pb{ getProgramWithVariants(variant, vertexVariant, fragmentVariant) };
Program pb{ getProgramWithVariants(variant, vertexVariant, fragmentVariant, mutableSpecConstants) };
pb.priorityQueue(priorityQueue);
pb.multiview(
mEngine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW &&
Variant::isStereoVariant(variant));
createAndCacheProgram(std::move(pb), variant);
createAndCacheProgram(std::move(pb), variant, mutableSpecConstants);
}
void FMaterial::getPostProcessProgramSlow(Variant const variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue const priorityQueue) const noexcept {
Program pb{ getProgramWithVariants(variant, variant, variant) };
Program pb{ getProgramWithVariants(variant, variant, variant, mutableSpecConstants) };
pb.priorityQueue(priorityQueue);
createAndCacheProgram(std::move(pb), variant);
createAndCacheProgram(std::move(pb), variant, mutableSpecConstants);
}
Program FMaterial::getProgramWithVariants(
Variant variant,
Variant vertexVariant,
Variant fragmentVariant) const {
Program FMaterial::getProgramWithVariants(Variant variant,
Variant vertexVariant, Variant fragmentVariant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const {
FEngine const& engine = mEngine;
const ShaderModel sm = engine.getShaderModel();
const bool isNoop = engine.getBackend() == Backend::NOOP;
@@ -658,29 +680,34 @@ Program FMaterial::getProgramWithVariants(
mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_RENDERABLE]);
program.descriptorBindings(+DescriptorSetBindingPoints::PER_MATERIAL,
mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_MATERIAL]);
program.specializationConstants(mSpecializationConstants);
program.specializationConstants(mSpecializationConstants,
CONFIG_MAX_RESERVED_SPEC_CONSTANTS + mMaterialConstants.size(), mutableSpecConstants);
program.pushConstants(ShaderStage::VERTEX, mPushConstants[uint8_t(ShaderStage::VERTEX)]);
program.pushConstants(ShaderStage::FRAGMENT, mPushConstants[uint8_t(ShaderStage::FRAGMENT)]);
program.cacheId(hash::combine(size_t(mCacheId), variant.key));
program.cacheId(
hash::combine(size_t(mCacheId),
hash::combine(variant.key, mutableSpecConstants.getValue())));
return program;
}
void FMaterial::createAndCacheProgram(Program&& p, Variant const variant) const noexcept {
void FMaterial::createAndCacheProgram(Program&& p, Variant const variant,
Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept {
FEngine const& engine = mEngine;
DriverApi& driverApi = mEngine.getDriverApi();
bool const isShared = isSharedVariant(variant);
size_t const cacheIndex = getCachedProgramIndex(variant, mutableSpecConstants);
// Check if the default material has this program cached
if (isShared) {
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
if (pDefaultMaterial) {
auto const program = pDefaultMaterial->mCachedPrograms[variant.key];
auto const program = pDefaultMaterial->mCachedPrograms[cacheIndex];
if (program) {
mCachedPrograms[variant.key] = program;
mCachedPrograms[cacheIndex] = program;
return;
}
}
@@ -689,17 +716,17 @@ void FMaterial::createAndCacheProgram(Program&& p, Variant const variant) const
auto const program = driverApi.createProgram(std::move(p));
driverApi.setDebugTag(program.getId(), mName);
assert_invariant(program);
mCachedPrograms[variant.key] = program;
mCachedPrograms[cacheIndex] = program;
// If the default material doesn't already have this program cached, and all caching conditions
// are met (Surface Domain and no custom depth shader), cache it now.
// New Materials will inherit these program automatically.
if (isShared) {
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
if (pDefaultMaterial && !pDefaultMaterial->mCachedPrograms[variant.key]) {
if (pDefaultMaterial && !pDefaultMaterial->mCachedPrograms[cacheIndex]) {
// set the tag to the default material name
driverApi.setDebugTag(program.getId(), mName);
pDefaultMaterial->mCachedPrograms[variant.key] = program;
pDefaultMaterial->mCachedPrograms[cacheIndex] = program;
}
}
}
@@ -747,6 +774,14 @@ size_t FMaterial::getParameters(ParameterInfo* parameters, size_t count) const n
return count;
}
std::optional<uint32_t> FMaterial::getMutableConstantId(std::string_view name) const noexcept {
auto const pos = mMutableSpecializationConstantsNameToIndex.find(name);
if (pos != mMutableSpecializationConstantsNameToIndex.end()) {
return pos->second;
}
return std::nullopt;
}
#if FILAMENT_ENABLE_MATDBG
// Swaps in an edited version of the original package that was used to create the material. The
@@ -805,7 +840,9 @@ void FMaterial::onQueryCallback(void* userdata, VariantList* pActiveVariants) {
#endif // FILAMENT_ENABLE_MATDBG
[[nodiscard]] Handle<HwProgram> FMaterial::getProgramWithMATDBG(Variant const variant) const noexcept {
[[nodiscard]] Handle<HwProgram> FMaterial::getProgramWithMATDBG(Variant const variant,
Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept {
size_t cacheIndex = getCachedProgramIndex(variant, mutableSpecConstants);
#if FILAMENT_ENABLE_MATDBG
assert_invariant((size_t)variant.key < VARIANT_COUNT);
std::unique_lock lock(mActiveProgramsLock);
@@ -820,13 +857,13 @@ void FMaterial::onQueryCallback(void* userdata, VariantList* pActiveVariants) {
lock.unlock();
if (isSharedVariant(variant)) {
FMaterial const* const pDefaultMaterial = mEngine.getDefaultMaterial();
if (pDefaultMaterial && pDefaultMaterial->mCachedPrograms[variant.key]) {
return pDefaultMaterial->getProgram(variant);
if (pDefaultMaterial && pDefaultMaterial->mCachedPrograms[cacheIndex]) {
return pDefaultMaterial->getProgram(variant, mutableSpecConstants);
}
}
#endif
assert_invariant(mCachedPrograms[variant.key]);
return mCachedPrograms[variant.key];
assert_invariant(mCachedPrograms[cacheIndex]);
return mCachedPrograms[cacheIndex];
}
void FMaterial::destroyPrograms(FEngine& engine,
@@ -839,7 +876,7 @@ void FMaterial::destroyPrograms(FEngine& engine,
case MaterialDomain::SURFACE: {
if (mIsDefaultMaterial || mHasCustomDepthShader) {
// default material, or we have custom depth shaders, we destroy all variants
for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) {
for (size_t k = 0, n = cachedPrograms.size(); k < n; ++k) {
if ((k & variantMask) == variantValue) {
// Only destroy if the handle is valid. Not strictly needed, but we have a lot
// of variants, and this generates traffic in the command queue.
@@ -857,7 +894,7 @@ void FMaterial::destroyPrograms(FEngine& engine,
UTILS_UNUSED_IN_RELEASE
auto const UTILS_NULLABLE pDefaultMaterial = engine.getDefaultMaterial();
for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) {
for (size_t k = 0, n = cachedPrograms.size(); k < n; ++k) {
if ((k & variantMask) == variantValue) {
// Only destroy if the handle is valid. Not strictly needed, but we have a lot
// of variant, and this generates traffic in the command queue.
@@ -869,7 +906,7 @@ void FMaterial::destroyPrograms(FEngine& engine,
// During Engine::shutdown, auto-cleanup destroys the
// default material first
assert_invariant(!pDefaultMaterial ||
pDefaultMaterial->mCachedPrograms[k]);
pDefaultMaterial->mCachedPrograms[k & (VARIANT_COUNT - 1)]);
// we don't own this variant, skip, but clear the entry.
cachedPrograms[k].clear();
continue;
@@ -883,7 +920,7 @@ void FMaterial::destroyPrograms(FEngine& engine,
break;
}
case MaterialDomain::POST_PROCESS: {
for (size_t k = 0, n = POST_PROCESS_VARIANT_COUNT; k < n; ++k) {
for (size_t k = 0, n = cachedPrograms.size(); k < n; ++k) {
if ((k & variantMask) == variantValue) {
// Only destroy if the handle is valid. Not strictly needed, but we have a lot
// of variant, and this generates traffic in the command queue.
@@ -895,8 +932,13 @@ void FMaterial::destroyPrograms(FEngine& engine,
break;
}
case MaterialDomain::COMPUTE: {
// Compute programs don't have variants
driverApi.destroyProgram(std::move(cachedPrograms[0]));
for (size_t k = 0, n = cachedPrograms.size(); k < n; ++k) {
// Only destroy if the handle is valid. Not strictly needed, but we have a lot
// of variant, and this generates traffic in the command queue.
if (cachedPrograms[k]) {
driverApi.destroyProgram(std::move(cachedPrograms[k]));
}
}
break;
}
}
@@ -1131,6 +1173,19 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Builder const& b
}
}
void FMaterial::processMutableSpecializationConstants(FEngine& engine, Builder const& builder,
MaterialParser const* const parser) {
// Older materials won't have a mutable constants chunk, but that's okay.
parser->getMutableConstants(&mMaterialMutableConstants);
for (size_t i = 0, c = mMaterialMutableConstants.size(); i < c; i++) {
auto& item = mMaterialMutableConstants[i];
// the key can be a string_view because mMaterialMutableConstants owns the CString
std::string_view const key{ item.name.data(), item.name.size() };
mMutableSpecializationConstantsNameToIndex[key] = i;
mDefaultMutableSpecializationConstants.set(i, item.defaultValue);
}
}
void FMaterial::processPushConstants(FEngine&, MaterialParser const* parser) {
FixedCapacityVector<Program::PushConstant>& vertexConstants =
mPushConstants[uint8_t(ShaderStage::VERTEX)];
@@ -1186,7 +1241,9 @@ void FMaterial::precacheDepthVariants(FEngine& engine) {
}
assert_invariant(Variant::isValidDepthVariant(variant));
if (hasVariant(variant)) {
prepareProgram(variant, CompilerPriorityQueue::HIGH);
// The default material shouldn't ever have mutable spec constants.
prepareProgram(variant, Program::MutableSpecConstantsInfo(0),
CompilerPriorityQueue::HIGH);
}
}
return;
@@ -1199,9 +1256,16 @@ void FMaterial::precacheDepthVariants(FEngine& engine) {
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
assert_invariant(pDefaultMaterial);
auto const allDepthVariants = VariantUtils::getDepthVariants();
int mutableConstantVariations = 1 << mMaterialMutableConstants.size();
for (auto const variant: allDepthVariants) {
assert_invariant(Variant::isValidDepthVariant(variant));
mCachedPrograms[variant.key] = pDefaultMaterial->mCachedPrograms[variant.key];
size_t defaultCacheIndex = pDefaultMaterial->getCachedProgramIndex(variant,
Program::MutableSpecConstantsInfo(0));
for (int spec = 0; spec < mutableConstantVariations; spec++) {
size_t cacheIndex = getCachedProgramIndex(variant,
Program::MutableSpecConstantsInfo(spec));
mCachedPrograms[cacheIndex] = pDefaultMaterial->mCachedPrograms[defaultCacheIndex];
}
}
}
}
+47 -13
View File
@@ -132,8 +132,9 @@ public:
FEngine& getEngine() const noexcept { return mEngine; }
bool isCached(Variant const variant) const noexcept {
return bool(mCachedPrograms[variant.key]);
bool isCached(Variant const variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const noexcept {
return bool(mCachedPrograms[getCachedProgramIndex(variant, mutableSpecConstants)]);
}
void invalidate(Variant::type_t variantMask = 0, Variant::type_t variantValue = 0) noexcept;
@@ -142,22 +143,26 @@ public:
// Must be called outside of backend render pass.
// Must be called before getProgram() below.
void prepareProgram(Variant const variant,
backend::Program::MutableSpecConstantsInfo const mutableSpecConstants,
backend::CompilerPriorityQueue const priorityQueue) const noexcept {
// prepareProgram() is called for each RenderPrimitive in the scene, so it must be efficient.
if (UTILS_UNLIKELY(!isCached(variant))) {
prepareProgramSlow(variant, priorityQueue);
if (UTILS_UNLIKELY(!isCached(variant, mutableSpecConstants))) {
prepareProgramSlow(variant, mutableSpecConstants, priorityQueue);
}
}
// getProgram returns the backend program for the material's given variant.
// getProgram returns the backend program for the material's given variant and set of mutable
// spec constants.
// Must be called after prepareProgram().
[[nodiscard]]
backend::Handle<backend::HwProgram> getProgram(Variant const variant) const noexcept {
backend::Handle<backend::HwProgram> getProgram(Variant const variant,
backend::Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept {
#if FILAMENT_ENABLE_MATDBG
return getProgramWithMATDBG(variant);
return getProgramWithMATDBG(variant, mutableSpecConstants);
#endif
assert_invariant(mCachedPrograms[variant.key]);
return mCachedPrograms[variant.key];
size_t index = getCachedProgramIndex(variant, mutableSpecConstants);
assert_invariant(mCachedPrograms[index]);
return mCachedPrograms[index];
}
// MaterialInstance::use() binds descriptor sets before drawing. For shared variants,
@@ -178,7 +183,8 @@ public:
}
[[nodiscard]]
backend::Handle<backend::HwProgram> getProgramWithMATDBG(Variant variant) const noexcept;
backend::Handle<backend::HwProgram> getProgramWithMATDBG(Variant variant,
backend::Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept;
bool isVariantLit() const noexcept { return mIsVariantLit; }
@@ -235,6 +241,12 @@ public:
}
size_t getParameters(ParameterInfo* parameters, size_t count) const noexcept;
uint32_t getMutableConstantCount() const noexcept { return mMaterialMutableConstants.size(); }
std::optional<uint32_t> getMutableConstantId(std::string_view name) const noexcept;
backend::Program::MutableSpecConstantsInfo getDefaultMutableConstants() const noexcept {
return mDefaultMutableSpecializationConstants;
}
uint32_t generateMaterialInstanceId() const noexcept { return mMaterialInstanceId++; }
void destroyPrograms(FEngine& engine,
@@ -288,34 +300,48 @@ public:
private:
bool hasVariant(Variant variant) const noexcept;
void prepareProgramSlow(Variant variant,
backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue priorityQueue) const noexcept;
void getSurfaceProgramSlow(Variant variant,
backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue priorityQueue) const noexcept;
void getPostProcessProgramSlow(Variant variant,
backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue priorityQueue) const noexcept;
backend::Program getProgramWithVariants(Variant variant,
Variant vertexVariant, Variant fragmentVariant) const;
Variant vertexVariant, Variant fragmentVariant,
backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const;
void processBlendingMode(MaterialParser const* parser);
void processSpecializationConstants(FEngine& engine, Builder const& builder,
MaterialParser const* parser);
void processMutableSpecializationConstants(FEngine& engine, Builder const& builder,
MaterialParser const* parser);
void processPushConstants(FEngine& engine, MaterialParser const* parser);
void precacheDepthVariants(FEngine& engine);
void processDescriptorSets(FEngine& engine, MaterialParser const* parser);
void createAndCacheProgram(backend::Program&& p, Variant variant) const noexcept;
void createAndCacheProgram(backend::Program&& p, Variant variant,
backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const noexcept;
inline bool isSharedVariant(Variant const variant) const {
return (mMaterialDomain == MaterialDomain::SURFACE) && !mIsDefaultMaterial &&
!mHasCustomDepthShader && Variant::isValidDepthVariant(variant);
}
inline size_t getCachedProgramIndex(const Variant variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const noexcept {
return static_cast<size_t>(variant.key) |
(static_cast<size_t>(mutableSpecConstants.getValue()) << mVariantBits);
}
// try to order by frequency of use
mutable std::array<backend::Handle<backend::HwProgram>, VARIANT_COUNT> mCachedPrograms;
mutable utils::FixedCapacityVector<backend::Handle<backend::HwProgram>> mCachedPrograms;
DescriptorSetLayout mPerViewDescriptorSetLayout;
DescriptorSetLayout mPerViewDescriptorSetLayoutVsm;
DescriptorSetLayout mDescriptorSetLayout;
@@ -351,6 +377,7 @@ private:
bool mHasCustomDepthShader = false;
bool mIsDefaultMaterial = false;
bool mSpecularAntiAliasing = false;
uint8_t mVariantBits;
// reserve some space to construct the default material instance
mutable FMaterialInstance* mDefaultMaterialInstance = nullptr;
@@ -375,6 +402,13 @@ private:
// current specialization constants for the HwProgram
utils::FixedCapacityVector<backend::Program::SpecializationConstant> mSpecializationConstants;
// Mutable constants defined by this Material.
utils::FixedCapacityVector<MaterialMutableConstant> mMaterialMutableConstants;
// A map from the Constant name to the mMutableMaterialConstant index
std::unordered_map<std::string_view, uint32_t> mMutableSpecializationConstantsNameToIndex;
// Default values for the mutable constants.
backend::Program::MutableSpecConstantsInfo mDefaultMutableSpecializationConstants;
// current push constants for the HwProgram
std::array<utils::FixedCapacityVector<backend::Program::PushConstant>,
backend::Program::SHADER_TYPE_COUNT>
@@ -63,6 +63,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
const char* name) noexcept
: mMaterial(material),
mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()),
mConstants(material->getDefaultMutableConstants()),
mCulling(CullingMode::BACK),
mShadowCulling(CullingMode::BACK),
mDepthFunc(RasterState::DepthFunc::LE),
@@ -128,6 +129,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
mTextureParameters(other->mTextureParameters),
mDescriptorSet(other->mDescriptorSet.duplicate(
"MaterialInstance", mMaterial->getDescriptorSetLayout())),
mConstants(other->mConstants),
mPolygonOffset(other->mPolygonOffset),
mStencilState(other->mStencilState),
mMaskThreshold(other->mMaskThreshold),
+13 -1
View File
@@ -33,6 +33,7 @@
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <backend/Program.h>
#include <utils/BitmaskEnum.h>
#include <utils/bitset.h>
@@ -68,7 +69,7 @@ public:
void terminate(FEngine& engine);
void commitStreamUniformAssociations(FEngine::DriverApi& driver);
void commit(FEngine& engine) const;
void commit(FEngine::DriverApi& driver) const;
@@ -244,6 +245,10 @@ public:
using MaterialInstance::setParameter;
const backend::Program::MutableSpecConstantsInfo getMutableSpecConstants() const noexcept {
return mConstants;
}
private:
friend class FMaterial;
friend class MaterialInstance;
@@ -266,6 +271,12 @@ private:
template<typename T>
T getParameterImpl(std::string_view name) const;
template<typename T>
void setConstantImpl(std::string_view name, T const& value);
template<typename T>
T getConstantImpl(std::string_view name) const;
// keep these grouped, they're accessed together in the render-loop
FMaterial const* mMaterial = nullptr;
@@ -279,6 +290,7 @@ private:
mutable DescriptorSet mDescriptorSet;
UniformBuffer mUniforms;
bool mHasStreamUniformAssociations = false;
backend::Program::MutableSpecConstantsInfo mConstants;
backend::PolygonOffset mPolygonOffset{};
backend::StencilState mStencilState{};
+8 -2
View File
@@ -659,10 +659,12 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren
if (hasDynamicLighting()) {
auto& froxelizer = mFroxelizer;
if (froxelizer.prepare(driver, rootArenaScope, viewport,
cameraInfo.projection, cameraInfo.zn, cameraInfo.zf)) {
cameraInfo.projection, cameraInfo.zn, cameraInfo.zf,
cameraInfo.clipTransform)) {
// TODO: might be more consistent to do this in prepareLighting(), but it's not
// strictly necessary
getColorPassDescriptorSet().prepareDynamicLights(mFroxelizer);
getColorPassDescriptorSet().prepareDynamicLights(mFroxelizer, mFroxelVizEnabled);
mFroxelConfigurationAge++;
}
// We need to pass viewMatrix by value here because it extends the scope of this
// function.
@@ -1434,6 +1436,10 @@ void FView::setStereoscopicOptions(const StereoscopicOptions& options) noexcept
mStereoscopicOptions = options;
}
View::FroxelConfigurationInfoWithAge FView::getFroxelConfigurationInfo() const noexcept {
return { mFroxelizer.getFroxelConfigurationInfo(), mFroxelConfigurationAge };
}
void FView::setMaterialGlobal(uint32_t const index, float4 const& value) {
FILAMENT_CHECK_PRECONDITION(index < 4)
<< "material global variable index (" << +index << ") out of range";
+8
View File
@@ -240,6 +240,12 @@ public:
return mShadowMapManager->getDirectionalShadowCameras();
}
void setFroxelVizEnabled(bool const enabled) noexcept {
mFroxelVizEnabled = enabled;
}
FroxelConfigurationInfoWithAge getFroxelConfigurationInfo() const noexcept;
void setRenderTarget(FRenderTarget* renderTarget) noexcept {
assert_invariant(!renderTarget || !mMultiSampleAntiAliasingOptions.enabled ||
!renderTarget->hasSampleableDepth());
@@ -552,6 +558,8 @@ private:
mutable Froxelizer mFroxelizer;
utils::JobSystem::Job* mFroxelizerSync = nullptr;
bool mFroxelVizEnabled = false;
uint32_t mFroxelConfigurationAge = 0;
Viewport mViewport;
bool mCulling = true;
+2 -1
View File
@@ -372,12 +372,13 @@ void ColorPassDescriptorSet::prepareAmbientLight(FEngine const& engine, FIndirec
});
}
void ColorPassDescriptorSet::prepareDynamicLights(Froxelizer& froxelizer) noexcept {
void ColorPassDescriptorSet::prepareDynamicLights(Froxelizer& froxelizer, bool const enableFroxelViz) noexcept {
auto& s = mUniforms.edit();
froxelizer.updateUniforms(s);
float const f = froxelizer.getLightFar();
// TODO: make the falloff rate a parameter
s.lightFarAttenuationParams = 0.5f * float2{ 10.0f, 10.0f / (f * f) };
s.enableFroxelViz = enableFroxelViz;
}
void ColorPassDescriptorSet::prepareShadowMapping(BufferObjectHandle shadowUniforms) noexcept {
+1 -1
View File
@@ -118,7 +118,7 @@ public:
void prepareAmbientLight(FEngine const& engine,
FIndirectLight const& ibl, float intensity, float exposure) noexcept;
void prepareDynamicLights(Froxelizer& froxelizer) noexcept;
void prepareDynamicLights(Froxelizer& froxelizer, bool enableFroxelViz) noexcept;
void prepareShadowVSM(TextureHandle texture,
VsmShadowOptions const& options) noexcept;
+16
View File
@@ -65,6 +65,10 @@ material {
type : float,
name : thicknessHeuristic
},
{
type : float,
name : constThickness
},
{
type : float,
name : ssctShadowDistance
@@ -98,6 +102,18 @@ material {
name : ssctSampleCount
}
],
constants : [
{
type : bool,
name : useVisibilityBitmasks,
default : false
},
{
type : bool,
name : linearThickness,
default : false
}
],
variables : [
vertex
],
@@ -65,6 +65,10 @@ material {
type : float,
name : thicknessHeuristic
},
{
type : float,
name : constThickness
},
{
type : float,
name : ssctShadowDistance
@@ -98,6 +102,18 @@ material {
name : ssctSampleCount
}
],
constants : [
{
type : bool,
name : useVisibilityBitmasks,
default : false
},
{
type : bool,
name : linearThickness,
default : false
}
],
outputs : [
{
name : aoData,
+95 -24
View File
@@ -24,6 +24,7 @@
#include "../utils/geometry.fs"
#define rsqrt inversesqrt
#define SECTOR_COUNT 32u
#ifndef COMPUTE_BENT_NORMAL
#error COMPUTE_BENT_NORMAL must be set
@@ -58,6 +59,67 @@ float spatialOffsetsNoise(float2 uv) {
return 0.25 * float((position.y - position.x) & 3);
}
// If the new sample value is greater then the current one, update the value with some fallOff.
// Otherwise, apply thicknessHeuristic.
float updateHorizon(float sampleHorizonCos, float currentHorizonCos, float fallOff) {
return sampleHorizonCos > currentHorizonCos
? mix(sampleHorizonCos, currentHorizonCos, fallOff)
: mix(currentHorizonCos, sampleHorizonCos, materialParams.thicknessHeuristic);
}
float calculateHorizonCos(highp vec3 sampleDelta, highp vec3 viewDir, float horizonCos) {
highp float sqSampleDist = dot(sampleDelta, sampleDelta);
float invSampleDist = rsqrt(sqSampleDist);
// Use the view space radius to calculate the fallOff
float fallOff = saturate(sqSampleDist * materialParams.invRadiusSquared * 2.0);
// sample horizon cos
float shc = dot(sampleDelta, viewDir) * invSampleDist;
return updateHorizon(shc, horizonCos, fallOff);
}
// https://cdrinmatane.github.io/posts/ssaovb-code/
// https://github.com/cdrinmatane/SSRT3/blob/main/HDRP/Shaders/Resources/SSRTCS.compute
uint updateSectors(float minHorizon, float maxHorizon, uint globalOccludedBitfield) {
uint startHorizonInt = uint(minHorizon * float(SECTOR_COUNT));
uint angleHorizonInt = uint(ceil(saturate(maxHorizon-minHorizon) * float(SECTOR_COUNT)));
uint angleHorizonBitfield = angleHorizonInt > 0u ? (0xFFFFFFFFu >> (SECTOR_COUNT-angleHorizonInt)) : 0u;
uint currentOccludedBitfield = angleHorizonBitfield << startHorizonInt;
return globalOccludedBitfield | currentOccludedBitfield;
}
// https://cdrinmatane.github.io/posts/ssaovb-code/
// https://github.com/cdrinmatane/SSRT3/blob/main/HDRP/Shaders/Resources/SSRTCS.compute
// The visibility bitmask method replaces the traditional two horizon angles with a bitmask
// for each slice. This bitmask flags whether each sector is occluded or not,
// which enables surfaces to be modeled with constant thickness, overcoming the limitation
// of treating them as a simple height field.
uint calculateVisibilityMask(highp vec3 deltaPos, highp vec3 viewDir, float samplingDirection,
uint globalOccludedBitfield, float n, highp vec3 origin) {
vec2 frontBackHorizon;
float linearThicknessMultiplier = materialConstants_linearThickness
? saturate(origin.z * materialParams.invFarPlane) * 100.0
: 1.0;
vec3 deltaPosBackface = deltaPos - viewDir * materialParams.constThickness * linearThicknessMultiplier;
// Project sample onto the unit circle and compute the angle relative to V
frontBackHorizon.x = dot(normalize(deltaPos), viewDir);
frontBackHorizon.y = dot(normalize(deltaPosBackface), viewDir);
frontBackHorizon.x = acosFast(frontBackHorizon.x);
frontBackHorizon.y = acosFast(frontBackHorizon.y);
// Shift sample from V to normal, clamp in [0..1]
frontBackHorizon = clamp(((samplingDirection * -frontBackHorizon) + n + HALF_PI) / PI, 0.0, 1.0);
// Sampling direction inverts min/max angles
frontBackHorizon = samplingDirection >= 0.0 ? frontBackHorizon.yx : frontBackHorizon.xy;
return updateSectors(frontBackHorizon.x, frontBackHorizon.y, globalOccludedBitfield);
}
void groundTruthAmbientOcclusion(out float obscurance, out vec3 bentNormal,
highp vec2 uv, highp vec3 origin, vec3 normal) {
vec2 uvSamplePos = uv;
@@ -84,20 +146,26 @@ void groundTruthAmbientOcclusion(out float obscurance, out vec3 bentNormal,
vec3 direction = vec3(cosPhi, sinPhi, 0.0);
// Project direction onto the plane orthogonal to viewDir.
vec3 orthoDirection = normalize(direction - (dot(direction, viewDir)*viewDir));
// axis is orthogonal to direction and viewDir (basically the normal of the slice plane)
// `axis` is orthogonal to direction and viewDir (basically the normal of the slice plane)
// Used to define projectedNormal
vec3 axis = cross(orthoDirection, viewDir);
// Project the normal onto the slice plane
// `projNormal` is the normal projected onto the slice plane
vec3 projNormal = normal - axis * dot(normal, axis);
float signNorm = sign(dot(orthoDirection, projNormal));
float projNormalLength = length(projNormal);
float cosNorm = saturate(dot(projNormal, viewDir) / projNormalLength);
// `n` is the signed angle between projNormal and viewDir
float n = signNorm * acosFast(cosNorm);
// These are only used in non-bitmask mode
float horizonCos0 = -1.0;
float horizonCos1 = -1.0;
// This is only used in bitmask mode
uint globalOccludedBitfield = 0u;
for (float j = 0.0; j < materialParams.stepsPerSlice; j += 1.0) {
// At least move 1 pixel forward in the screen-space
vec2 sampleOffset = max((j + initialRayStep)*stepRadius, 1.0 + j) * omega;
@@ -116,33 +184,36 @@ void groundTruthAmbientOcclusion(out float obscurance, out vec3 bentNormal,
highp vec3 sampleDelta0 = (samplePos0 - origin);
highp vec3 sampleDelta1 = (samplePos1 - origin);
highp vec2 sqSampleDist = vec2(dot(sampleDelta0, sampleDelta0), dot(sampleDelta1, sampleDelta1));
vec2 invSampleDist = rsqrt(sqSampleDist);
// Use the view space radius to calculate the fallOff
vec2 fallOff = saturate(sqSampleDist.xy * materialParams.invRadiusSquared * 2.0);
// sample horizon cos
float shc0 = dot(sampleDelta0, viewDir) * invSampleDist.x;
float shc1 = dot(sampleDelta1, viewDir) * invSampleDist.y;
// If the new sample value is greater then the current one, update the value with some fallOff.
// Otherwise, apply thicknessHeuristic.
horizonCos0 = shc0 > horizonCos0 ? mix(shc0, horizonCos0, fallOff.x) : mix(horizonCos0, shc0, materialParams.thicknessHeuristic);
horizonCos1 = shc1 > horizonCos1 ? mix(shc1, horizonCos1, fallOff.y) : mix(horizonCos1, shc1, materialParams.thicknessHeuristic);
if (materialConstants_useVisibilityBitmasks) {
globalOccludedBitfield = calculateVisibilityMask(sampleDelta0, viewDir, 1.0, globalOccludedBitfield, n, origin);
globalOccludedBitfield = calculateVisibilityMask(sampleDelta1, viewDir, -1.0, globalOccludedBitfield, n, origin);
}
else {
horizonCos0 = calculateHorizonCos(sampleDelta0, viewDir, horizonCos0);
horizonCos1 = calculateHorizonCos(sampleDelta1, viewDir, horizonCos1);
}
}
float h0 = -acosFast(horizonCos1);
float h1 = acosFast(horizonCos0);
h0 = n + clamp(h0 - n, -HALF_PI, HALF_PI);
h1 = n + clamp(h1 - n, -HALF_PI, HALF_PI);
if (materialConstants_useVisibilityBitmasks) {
// Calculate the portion of the occluded sector
visibility += 1.0 - float(bitCount(globalOccludedBitfield)) / float(SECTOR_COUNT);
#if COMPUTE_BENT_NORMAL
float angle = 0.5 * (h0 + h1);
bentNormal += viewDir * cos(angle) - orthoDirection * sin(angle);
#endif
// Note: bent normal calculation is not supported in visibility bitmasks mode
}
else {
float h0 = -acosFast(horizonCos1);
float h1 = acosFast(horizonCos0);
h0 = n + clamp(h0 - n, -HALF_PI, HALF_PI);
h1 = n + clamp(h1 - n, -HALF_PI, HALF_PI);
visibility += projNormalLength * (integrateArcCosWeight(h0, n) + integrateArcCosWeight(h1, n));
#if COMPUTE_BENT_NORMAL
float angle = 0.5 * (h0 + h1);
bentNormal += viewDir * cos(angle) - orthoDirection * sin(angle);
#endif
visibility += projNormalLength * (integrateArcCosWeight(h0, n) + integrateArcCosWeight(h1, n));
}
}
obscurance = 1.0 - saturate(visibility * materialParams.sliceCount.y);
+1 -1
View File
@@ -672,7 +672,7 @@ TEST(FilamentTest, FroxelData) {
Froxelizer froxelData(*engine);
froxelData.setOptions(5, 100);
froxelData.prepare(engine->getDriverApi(), scope, vp, p, 0.1, 100);
froxelData.prepare(engine->getDriverApi(), scope, vp, p, 0.1, 100, {1,1,0,0});
Froxel f = froxelData.getFroxelAt(0,0,0);
@@ -54,6 +54,7 @@ enum UTILS_PUBLIC ChunkType : uint64_t {
MaterialDescriptorSetLayoutInfo = charTo64bitNum("MAT_DSLI"),
MaterialProperties = charTo64bitNum("MAT_PROP"),
MaterialConstants = charTo64bitNum("MAT_CONS"),
MaterialMutableConstants = charTo64bitNum("MAT_MCNS"),
MaterialPushConstants = charTo64bitNum("MAT_PCON"),
MaterialName = charTo64bitNum("MAT_NAME"),
@@ -30,7 +30,19 @@ struct MaterialConstant {
ConstantType type;
MaterialConstant() = default;
MaterialConstant(const char* name, ConstantType type) : name(name), type(type) {}
MaterialConstant(utils::CString name, ConstantType type) : name(std::move(name)), type(type) {}
};
struct MaterialMutableConstant {
utils::CString name;
// The Filament engine stores mutable spec constants as a bitfield, meaning that must override
// all of the default values built into in the shader souces. Therefore, unlike the immutable
// constants above, we have to store the default values in the material metadata.
bool defaultValue;
MaterialMutableConstant() = default;
MaterialMutableConstant(utils::CString name, bool defaultValue)
: name(std::move(name)), defaultValue(defaultValue) {}
};
}
@@ -120,6 +120,10 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
math::uint3 fParams; // stride-x, stride-y, stride-z
int32_t lightChannels; // light channel bits
math::float2 froxelCountXY;
int enableFroxelViz;
int dynReserved0;
int dynReserved1;
int dynReserved2;
// IBL
float iblLuminance;
@@ -204,7 +208,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
float es2Reserved2;
// bring PerViewUib to 2 KiB
math::float4 reserved[39];
math::float4 reserved[38];
};
// 2 KiB == 128 float4s
+7 -5
View File
@@ -244,11 +244,13 @@ void ImGuiHelper::processImGuiCommands(ImDrawData* commands, const ImGuiIO& io)
}
materialInstance = mMaterial2dInstances[material2dIndex++];
}
materialInstance->setScissor(
pcmd.ClipRect.x,
mFlipVertical ? pcmd.ClipRect.y : (fbheight - pcmd.ClipRect.w),
(uint16_t) (pcmd.ClipRect.z - pcmd.ClipRect.x),
(uint16_t) (pcmd.ClipRect.w - pcmd.ClipRect.y));
auto scissorLeft = static_cast<uint32_t>(std::max(0.0f, pcmd.ClipRect.x));
auto scissorBottom = static_cast<uint32_t>(std::max(0.0f,
mFlipVertical ? pcmd.ClipRect.y : (fbheight - pcmd.ClipRect.w)));
auto scissorWidth = static_cast<uint32_t>(pcmd.ClipRect.z - pcmd.ClipRect.x);
auto scissorHeight = static_cast<uint32_t>(pcmd.ClipRect.w - pcmd.ClipRect.y);
materialInstance->setScissor(scissorLeft, scissorBottom, scissorWidth, scissorHeight);
if (texture) {
TextureSampler sampler(MinFilter::LINEAR, MagFilter::LINEAR);
materialInstance->setParameter("albedo", texture, sampler);
@@ -319,14 +319,15 @@ public:
MaterialBuilder& parameter(const char* name, size_t size, UniformType type,
ParameterPrecision precision = ParameterPrecision::DEFAULT);
//! Add a constant parameter to this material.
//! Add a (mutable) constant parameter to this material.
template<typename T>
using is_supported_constant_parameter_t = typename std::enable_if<
std::is_same<int32_t, T>::value ||
std::is_same<float, T>::value ||
std::is_same<bool, T>::value>::type;
template<typename T, typename = is_supported_constant_parameter_t<T>>
MaterialBuilder& constant(const char *name, ConstantType type, T defaultValue = 0);
MaterialBuilder& constant(const char* name, ConstantType type, bool isMutable,
T defaultValue = 0);
/**
* Add a sampler parameter to this material.
@@ -927,6 +928,7 @@ private:
PropertyList mProperties;
ParameterList mParameters;
ConstantList mConstants;
ConstantList mMutableConstants;
PushConstantList mPushConstants;
SubpassList mSubpasses;
VariableList mVariables;
+29 -12
View File
@@ -322,12 +322,19 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType sample
}
template<typename T, typename>
MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType const type, T defaultValue) {
auto result = std::find_if(mConstants.begin(), mConstants.end(), [name](const Constant& c) {
return c.name == CString(name);
});
FILAMENT_CHECK_POSTCONDITION(result == mConstants.end())
MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType const type,
bool isMutable, T defaultValue) {
ConstantList& constants = isMutable ? mMutableConstants : mConstants;
const ConstantList& otherConstants = isMutable ? mConstants : mMutableConstants;
auto result = std::find_if(constants.begin(), constants.end(),
[name](const Constant& c) { return c.name == CString(name); });
FILAMENT_CHECK_POSTCONDITION(result == constants.end())
<< "There is already a constant parameter present with the name " << name << ".";
auto otherResult = std::find_if(otherConstants.begin(), otherConstants.end(),
[name](const Constant& c) { return c.name == CString(name); });
FILAMENT_CHECK_POSTCONDITION(otherResult == otherConstants.end())
<< "There is already a constant parameter present with the name " << name << ".";
Constant constant {
.name = CString(name),
.type = type,
@@ -359,15 +366,20 @@ MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType const
assert_invariant(false);
}
mConstants.push_back(constant);
if (isMutable) {
FILAMENT_CHECK_POSTCONDITION(type == ConstantType::BOOL)
<< "Mutable spec constants must be bool type.";
}
constants.push_back(constant);
return *this;
}
template MaterialBuilder& MaterialBuilder::constant<int32_t>(
const char* name, ConstantType type, int32_t defaultValue);
const char* name, ConstantType type, bool isMutable, int32_t defaultValue);
template MaterialBuilder& MaterialBuilder::constant<float>(
const char* name, ConstantType type, float defaultValue);
const char* name, ConstantType type, bool isMutable, float defaultValue);
template MaterialBuilder& MaterialBuilder::constant<bool>(
const char* name, ConstantType type, bool defaultValue);
const char* name, ConstantType type, bool isMutable, bool defaultValue);
MaterialBuilder& MaterialBuilder::buffer(BufferInterfaceBlock bib) {
FILAMENT_CHECK_POSTCONDITION(mBuffers.size() < MAX_BUFFERS_COUNT) << "Too many buffers";
@@ -946,7 +958,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
BlobDictionary spirvDictionary;
// End: must be protected by lock
ShaderGenerator sg(mProperties, mVariables, mOutputs, mDefines, mConstants, mPushConstants,
ShaderGenerator sg(mProperties, mVariables, mOutputs, mDefines, mConstants, mMutableConstants, mPushConstants,
mMaterialFragmentCode.getResolved(), mMaterialFragmentCode.getLineOffset(),
mMaterialVertexCode.getResolved(), mMaterialVertexCode.getLineOffset(),
mMaterialDomain);
@@ -1548,7 +1560,7 @@ bool MaterialBuilder::needsStandardDepthProgram() const noexcept {
std::string MaterialBuilder::peek(backend::ShaderStage const stage,
const CodeGenParams& params, const PropertyList& properties) noexcept {
ShaderGenerator const sg(properties, mVariables, mOutputs, mDefines, mConstants, mPushConstants,
ShaderGenerator const sg(properties, mVariables, mOutputs, mDefines, mConstants, mMutableConstants, mPushConstants,
mMaterialFragmentCode.getResolved(), mMaterialFragmentCode.getLineOffset(),
mMaterialVertexCode.getResolved(), mMaterialVertexCode.getLineOffset(),
mMaterialDomain);
@@ -1676,9 +1688,14 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo&
// User constant parameters
FixedCapacityVector<MaterialConstant> constantsEntry(mConstants.size());
std::transform(mConstants.begin(), mConstants.end(), constantsEntry.begin(),
[](Constant const& c) { return MaterialConstant(c.name.c_str(), c.type); });
[](Constant const& c) { return MaterialConstant(c.name, c.type); });
container.push<MaterialConstantParametersChunk>(std::move(constantsEntry));
FixedCapacityVector<MaterialMutableConstant> mutableConstantsEntry(mMutableConstants.size());
std::transform(mMutableConstants.begin(), mMutableConstants.end(), mutableConstantsEntry.begin(),
[](Constant const& c) { return MaterialMutableConstant(c.name, c.defaultValue.b); });
container.push<MaterialMutableConstantParametersChunk>(std::move(mutableConstantsEntry));
FixedCapacityVector<MaterialPushConstant> pushConstantsEntry(mPushConstants.size());
std::transform(mPushConstants.begin(), mPushConstants.end(), pushConstantsEntry.begin(),
[](PushConstant const& c) {
@@ -118,6 +118,20 @@ void MaterialConstantParametersChunk::flatten(Flattener& f) {
// ------------------------------------------------------------------------------------------------
MaterialMutableConstantParametersChunk::MaterialMutableConstantParametersChunk(
FixedCapacityVector<MaterialMutableConstant> constants)
: Chunk(MaterialMutableConstants), mConstants(std::move(constants)) {}
void MaterialMutableConstantParametersChunk::flatten(Flattener& f) {
f.writeUint64(mConstants.size());
for (const auto& constant : mConstants) {
f.writeString(constant.name.c_str());
f.writeBool(constant.defaultValue);
}
}
// ------------------------------------------------------------------------------------------------
MaterialPushConstantParametersChunk::MaterialPushConstantParametersChunk(
CString const& structVarName, FixedCapacityVector<MaterialPushConstant> constants)
: Chunk(MaterialPushConstants),
@@ -18,6 +18,7 @@
#define TNT_FILAMAT_MAT_INTEFFACE_BLOCK_CHUNK_H
#include "Chunk.h"
#include "private/filament/ConstantInfo.h"
#include <backend/Program.h>
@@ -91,6 +92,20 @@ private:
// ------------------------------------------------------------------------------------------------
class MaterialMutableConstantParametersChunk final : public Chunk {
public:
explicit MaterialMutableConstantParametersChunk(
FixedCapacityVector<filament::MaterialMutableConstant> constants);
~MaterialMutableConstantParametersChunk() override = default;
private:
void flatten(Flattener&) override;
FixedCapacityVector<filament::MaterialMutableConstant> mConstants;
};
// ------------------------------------------------------------------------------------------------
class MaterialPushConstantParametersChunk final : public Chunk {
public:
explicit MaterialPushConstantParametersChunk(CString const& structVarName,
+17 -8
View File
@@ -285,10 +285,17 @@ void ShaderGenerator::appendShader(io::sstream& ss,
}
}
void ShaderGenerator::generateUserSpecConstants(
const CodeGenerator& cg, io::sstream& fs, MaterialBuilder::ConstantList const& constants) {
void ShaderGenerator::generateAllUserSpecConstants(const CodeGenerator& cg, io::sstream& fs,
MaterialBuilder::ConstantList const& constants,
MaterialBuilder::ConstantList const& mutableConstants) {
generateUserSpecConstants(cg, fs, constants, 0);
generateUserSpecConstants(cg, fs, mutableConstants, constants.size());
}
void ShaderGenerator::generateUserSpecConstants(const CodeGenerator& cg, io::sstream& fs,
MaterialBuilder::ConstantList const& constants, size_t offset) {
// Constants 0 to CONFIG_MAX_RESERVED_SPEC_CONSTANTS - 1 are reserved by Filament.
size_t index = CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
size_t index = CONFIG_MAX_RESERVED_SPEC_CONSTANTS + offset;
for (const auto& constant : constants) {
std::string const fullName = std::string("materialConstants_") + constant.name.c_str();
switch (constant.type) {
@@ -316,6 +323,7 @@ ShaderGenerator::ShaderGenerator(
MaterialBuilder::OutputList const& outputs,
MaterialBuilder::PreprocessorDefineList const& defines,
MaterialBuilder::ConstantList const& constants,
MaterialBuilder::ConstantList const& mutableConstants,
MaterialBuilder::PushConstantList const& pushConstants,
CString const& materialCode, size_t const lineOffset,
CString const& materialVertexCode, size_t const vertexLineOffset,
@@ -338,6 +346,7 @@ ShaderGenerator::ShaderGenerator(
mMaterialDomain = materialDomain;
mDefines = defines;
mConstants = constants;
mMutableConstants = mutableConstants;
mPushConstants = pushConstants;
if (mMaterialFragmentCode.empty()) {
@@ -394,7 +403,7 @@ std::string ShaderGenerator::createSurfaceVertexProgram(ShaderModel const shader
cg.generateCommonProlog(vs, ShaderStage::VERTEX, material, variant);
generateUserSpecConstants(cg, vs, mConstants);
generateAllUserSpecConstants(cg, vs, mConstants, mMutableConstants);
// note: even if the user vertex shader is empty, we can't use the "optimized" version if
// we're in masked mode because fragment shader needs the color varyings
@@ -527,7 +536,7 @@ std::string ShaderGenerator::createSurfaceFragmentProgram(ShaderModel const shad
io::sstream fs;
cg.generateCommonProlog(fs, ShaderStage::FRAGMENT, material, variant);
generateUserSpecConstants(cg, fs, mConstants);
generateAllUserSpecConstants(cg, fs, mConstants, mMutableConstants);
generateSurfaceMaterialVariantDefines(
fs, ShaderStage::FRAGMENT, featureLevel, material, variant);
@@ -683,7 +692,7 @@ std::string ShaderGenerator::createSurfaceComputeProgram(ShaderModel const shade
cg.generateCommonProlog(s, ShaderStage::COMPUTE, material, {});
generateUserSpecConstants(cg, s, mConstants);
generateAllUserSpecConstants(cg, s, mConstants, mMutableConstants);
CodeGenerator::generateSurfaceTypes(s, ShaderStage::COMPUTE);
@@ -723,7 +732,7 @@ std::string ShaderGenerator::createPostProcessVertexProgram(ShaderModel const sm
io::sstream vs;
cg.generateCommonProlog(vs, ShaderStage::VERTEX, material, {});
generateUserSpecConstants(cg, vs, mConstants);
generateAllUserSpecConstants(cg, vs, mConstants, mMutableConstants);
CodeGenerator::generateDefine(vs, "LOCATION_POSITION", uint32_t(POSITION));
@@ -767,7 +776,7 @@ std::string ShaderGenerator::createPostProcessFragmentProgram(ShaderModel const
io::sstream fs;
cg.generateCommonProlog(fs, ShaderStage::FRAGMENT, material, {});
generateUserSpecConstants(cg, fs, mConstants);
generateAllUserSpecConstants(cg, fs, mConstants, mMutableConstants);
generatePostProcessMaterialVariantDefines(fs, PostProcessVariant(variant));
+8 -1
View File
@@ -51,6 +51,7 @@ public:
MaterialBuilder::OutputList const& outputs,
MaterialBuilder::PreprocessorDefineList const& defines,
MaterialBuilder::ConstantList const& constants,
MaterialBuilder::ConstantList const& mutableConstants,
MaterialBuilder::PushConstantList const& pushConstants,
utils::CString const& materialCode,
size_t lineOffset,
@@ -104,9 +105,14 @@ private:
static void generatePostProcessMaterialVariantDefines(utils::io::sstream& out,
filament::PostProcessVariant variant) noexcept;
static void generateAllUserSpecConstants(const CodeGenerator& cg, utils::io::sstream& fs,
MaterialBuilder::ConstantList const& constants,
MaterialBuilder::ConstantList const& mutableConstants);
static void generateUserSpecConstants(
const CodeGenerator& cg, utils::io::sstream& fs,
MaterialBuilder::ConstantList const& constants);
MaterialBuilder::ConstantList const& constants,
size_t offset);
std::string createPostProcessVertexProgram(filament::backend::ShaderModel sm,
MaterialBuilder::TargetApi targetApi, MaterialBuilder::TargetLanguage targetLanguage,
@@ -135,6 +141,7 @@ private:
MaterialBuilder::MaterialDomain mMaterialDomain;
MaterialBuilder::PreprocessorDefineList mDefines;
MaterialBuilder::ConstantList mConstants;
MaterialBuilder::ConstantList mMutableConstants;
MaterialBuilder::PushConstantList mPushConstants;
utils::CString mMaterialFragmentCode; // fragment or compute code
utils::CString mMaterialVertexCode;
@@ -140,6 +140,10 @@ BufferInterfaceBlock const& UibGenerator::getPerViewUib() noexcept {
{ "fParams", 0, Type::UINT3 },
{ "lightChannels", 0, Type::INT },
{ "froxelCountXY", 0, Type::FLOAT2 },
{ "enableFroxelViz", 0, Type::INT },
{ "dynReserved0", 0, Type::INT },
{ "dynReserved1", 0, Type::INT },
{ "dynReserved2", 0, Type::INT },
{ "iblLuminance", 0, Type::FLOAT, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 },
{ "iblRoughnessOneLevel", 0, Type::FLOAT, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 },
+7 -7
View File
@@ -888,10 +888,10 @@ TEST_F(MaterialCompiler, ConstantParameter) {
}
)");
filamat::MaterialBuilder builder;
builder.constant("myFloatConstant", ConstantType::FLOAT, 1.0f);
builder.constant("myIntConstant", ConstantType::INT, 123);
builder.constant("myBoolConstant", ConstantType::BOOL, true);
builder.constant<bool>("myOtherBoolConstant", ConstantType::BOOL);
builder.constant("myFloatConstant", ConstantType::FLOAT, /*isMutable=*/false, 1.0f);
builder.constant("myIntConstant", ConstantType::INT, /*isMutable=*/false, 123);
builder.constant("myBoolConstant", ConstantType::BOOL, /*isMutable=*/false, true);
builder.constant<bool>("myOtherBoolConstant", ConstantType::BOOL, /*isMutable=*/false);
builder.shading(filament::Shading::LIT);
builder.material(shaderCode.c_str());
@@ -904,8 +904,8 @@ TEST_F(MaterialCompiler, ConstantParameterSameName) {
#ifdef __EXCEPTIONS
EXPECT_THROW({
filamat::MaterialBuilder builder;
builder.constant("myFloatConstant", ConstantType::FLOAT, 1.0f);
builder.constant("myFloatConstant", ConstantType::FLOAT, 1.0f);
builder.constant("myFloatConstant", ConstantType::FLOAT, /*isMutable=*/false, 1.0f);
builder.constant("myFloatConstant", ConstantType::FLOAT, /*isMutable=*/false, 1.0f);
}, utils::PostconditionPanic);
#endif
}
@@ -914,7 +914,7 @@ TEST_F(MaterialCompiler, ConstantParameterWrongType) {
#ifdef __EXCEPTIONS
EXPECT_THROW({
filamat::MaterialBuilder builder;
builder.constant("myFloatConstant", ConstantType::FLOAT, 10);
builder.constant("myFloatConstant", ConstantType::FLOAT, /*isMutable=*/false, 10);
}, utils::PostconditionPanic);
#endif
}
+16 -3
View File
@@ -352,6 +352,19 @@ static bool processConstant(MaterialBuilder& builder, const JsonishObject& jsonO
return false;
}
const JsonishValue* mutableValue = jsonObject.getValue("mutable");
bool isMutable;
if (mutableValue) {
const JsonishBool* mutableBool = mutableValue->toJsonBool();
if (!mutableBool) {
std::cerr << "constants: mutable value must be BOOL." << std::endl;
return false;
}
isMutable = mutableBool->getBool();
} else {
isMutable = false;
}
auto typeString = typeValue->toJsonString()->getString();
auto nameString = nameValue->toJsonString()->getString();
const JsonishValue* defaultValue = jsonObject.getValue("default");
@@ -370,7 +383,7 @@ static bool processConstant(MaterialBuilder& builder, const JsonishObject& jsonO
// FIXME: Jsonish doesn't distinguish between integers and floats.
intDefault = (int32_t)defaultValue->toJsonNumber()->getFloat();
}
builder.constant(nameString.c_str(), type, intDefault);
builder.constant(nameString.c_str(), type, isMutable, intDefault);
break;
}
case ConstantType::FLOAT: {
@@ -383,7 +396,7 @@ static bool processConstant(MaterialBuilder& builder, const JsonishObject& jsonO
}
floatDefault = defaultValue->toJsonNumber()->getFloat();
}
builder.constant(nameString.c_str(), type, floatDefault);
builder.constant(nameString.c_str(), type, isMutable, floatDefault);
break;
}
case ConstantType::BOOL:
@@ -396,7 +409,7 @@ static bool processConstant(MaterialBuilder& builder, const JsonishObject& jsonO
}
boolDefault = defaultValue->toJsonBool()->getBool();
}
builder.constant(nameString.c_str(), type, boolDefault);
builder.constant(nameString.c_str(), type, isMutable, boolDefault);
break;
}
} else {
+2
View File
@@ -20,6 +20,7 @@ set(MATERIAL_DIR "${GENERATION_ROOT}/generated/material")
set(PUBLIC_HDRS
include/filamentapp/Config.h
include/filamentapp/Cube.h
include/filamentapp/Grid.h
include/filamentapp/FilamentApp.h
include/filamentapp/IBL.h
include/filamentapp/IcoSphere.h
@@ -30,6 +31,7 @@ set(PUBLIC_HDRS
set(SRCS
src/Cube.cpp
src/Grid.cpp
src/FilamentApp.cpp
src/IBL.cpp
src/IcoSphere.cpp
@@ -111,6 +111,15 @@ public:
void loadIBL(std::string_view path);
// debugging: enable/disable the froxel grid
void setCameraFrustumEnabled(bool enabled) noexcept;
void setDirectionalShadowFrustumEnabled(bool enabled) noexcept;
void setFroxelGridEnabled(bool enabled) noexcept;
bool isCameraFrustumEnabled() const noexcept;
bool isDirectionalShadowFrustumEnabled() const noexcept;
bool isFroxelGridEnabled() const noexcept;
FilamentApp(const FilamentApp& rhs) = delete;
FilamentApp(FilamentApp&& rhs) = delete;
FilamentApp& operator=(const FilamentApp& rhs) = delete;
@@ -262,6 +271,10 @@ private:
float mCameraNear = 0.1f;
float mCameraFar = 100.0f;
bool mReconfigureCameras = false;
uint8_t mFroxelInfoAge = 0x42;
uint8_t mFroxelGridEnabled = 0;
uint8_t mDirectionalShadowFrustumEnabled = 0x2;
uint8_t mCameraFrustumEnabled = 0x2;
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
filament::backend::VulkanPlatform* mVulkanPlatform = nullptr;
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_SAMPLE_GRID_H
#define TNT_FILAMENT_SAMPLE_GRID_H
#include <filament/Engine.h>
#include <filament/Box.h>
#include <filament/Camera.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <math/mat4.h>
#include <math/vec3.h>
#include <utils/Entity.h>
#include <functional>
class Grid {
public:
Grid(filament::Engine& engine, filament::Material const* material,
filament::math::float3 linearColor);
Grid(Grid const&) = delete;
Grid& operator=(Grid const&) = delete;
Grid(Grid&& rhs) noexcept;
utils::Entity getWireFrameRenderable() const {
return mWireFrameRenderable;
}
~Grid();
using Generator = std::function<float(int index)>;
void update(uint32_t width, uint32_t height, uint32_t depth);
void update(uint32_t width, uint32_t height, uint32_t depth,
Generator const& genWidth, Generator const& genHeight, Generator const& genDepth);
void mapFrustum(filament::Engine& engine, filament::Camera const* camera);
void mapFrustum(filament::Engine& engine, filament::math::mat4 const& transform);
void mapAabb(filament::Engine& engine, filament::Box const& box);
private:
filament::Engine& mEngine;
filament::VertexBuffer* mVertexBuffer = nullptr;
filament::IndexBuffer* mIndexBuffer = nullptr;
filament::Material const* mMaterial = nullptr;
filament::MaterialInstance* mMaterialInstanceWireFrame = nullptr;
utils::Entity mWireFrameRenderable{};
};
#endif // TNT_FILAMENT_SAMPLE_GRID_H
+98 -29
View File
@@ -55,6 +55,7 @@
#include <filagui/ImGuiHelper.h>
#include <filamentapp/Cube.h>
#include <filamentapp/Grid.h>
#include <filamentapp/NativeWindowHelper.h>
#include <stb_image.h>
@@ -179,6 +180,7 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
.build(*mEngine);
std::unique_ptr<Cube> cameraCube{ new Cube(*mEngine, mTransparentMaterial, { 1, 0, 0 }) };
std::unique_ptr<Grid> cameraGrid{ new Grid(*mEngine, mTransparentMaterial, { 1, 1, 0 }) };
// we can't cull the light-frustum because it's not applied a rigid transform
// and currently, filament assumes that for culling
@@ -192,20 +194,14 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
mScene = mEngine->createScene();
window->mMainView->getView()->setVisibleLayers(0x4, 0x4);
window->mMainView->getView()->setFroxelVizEnabled(true);
if (config.splitView) {
auto& rcm = mEngine->getRenderableManager();
rcm.setLayerMask(rcm.getInstance(cameraCube->getSolidRenderable()), 0x3, 0x2);
rcm.setLayerMask(rcm.getInstance(cameraCube->getWireFrameRenderable()), 0x3, 0x2);
mScene->addEntity(cameraCube->getWireFrameRenderable());
mScene->addEntity(cameraCube->getSolidRenderable());
mScene->addEntity(cameraCube->getWireFrameRenderable());
for (auto&& cube : lightmapCubes) {
rcm.setLayerMask(rcm.getInstance(cube.getSolidRenderable()), 0x3, 0x2);
rcm.setLayerMask(rcm.getInstance(cube.getWireFrameRenderable()), 0x3, 0x2);
mScene->addEntity(cube.getWireFrameRenderable());
mScene->addEntity(cube.getSolidRenderable());
mScene->addEntity(cube.getWireFrameRenderable());
}
window->mDepthView->getView()->setVisibleLayers(0x4, 0x4);
@@ -218,6 +214,9 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
window->mOrthoView->getView()->setShadowingEnabled(false);
}
// froxel debug grid always added (but hidden)
mScene->addEntity(cameraGrid->getWireFrameRenderable());
loadDirt(config);
loadIBL(config);
@@ -467,11 +466,60 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
filament::math::float3 eye, center, up;
window->mMainCameraMan->getLookAt(&eye, &center, &up);
window->mMainCamera->lookAt(eye, center, up);
window->mDebugCameraMan->getLookAt(&eye, &center, &up);
window->mDebugCamera->lookAt(eye, center, up);
window->mDebugCamera->setExposure(
window->mMainCamera->getAperture(),
window->mMainCamera->getShutterSpeed(),
window->mMainCamera->getSensitivity());
window->mOrthoCamera->setExposure(
window->mMainCamera->getAperture(),
window->mMainCamera->getShutterSpeed(),
window->mMainCamera->getSensitivity());
auto const fci = window->mMainView->getView()->getFroxelConfigurationInfo();
if (UTILS_UNLIKELY(fci.age != mFroxelInfoAge)) {
mFroxelInfoAge = fci.age;
auto width = fci.info.width;
auto height = fci.info.height;
auto depth = fci.info.depth;
auto froxelDimension = fci.info.froxelDimension;
auto viewportWidth = fci.info.viewportWidth;
auto viewportHeight = fci.info.viewportHeight;
auto zLightFar = fci.info.zLightFar;
auto linearizer = fci.info.linearizer;
auto p = fci.info.p;
auto ct = fci.info.clipTransform;
cameraGrid->update(width, height, depth,
[=](int const i) {
float x = float(2 * i * froxelDimension.x) / float(viewportWidth ) - 1.0f;
x = (x - ct.z) / ct.x;
return x;
},
[=](int const j) {
float y = float(2 * j * froxelDimension.y) / float(viewportHeight) - 1.0f;
y = (y - ct.w) / ct.y;
return y;
},
[=](int const k) {
float const z_view = -zLightFar * std::exp2(float(k - depth) * linearizer);
auto c = p * float4{ 0, 0, z_view, 1 };
float const z_clip_dx = k == 0 ? 1.0f : c.z / c.w;
float const z_clip_gl = (1 - z_clip_dx) * 2.0f - 1.0f;
return z_clip_gl;
});
}
auto& rcm = mEngine->getRenderableManager();
if (config.splitView) {
rcm.setLayerMask(rcm.getInstance(cameraCube->getSolidRenderable()), 0x3, mCameraFrustumEnabled);
rcm.setLayerMask(rcm.getInstance(cameraCube->getWireFrameRenderable()), 0x3, mCameraFrustumEnabled);
}
rcm.setLayerMask(rcm.getInstance(cameraGrid->getWireFrameRenderable()), 0x3, mFroxelGridEnabled);
// Update the cube distortion matrix used for frustum visualization.
auto& rcm = mEngine->getRenderableManager();
auto const csm = window->mMainView->getView()->getDirectionalShadowCameras();
// show/hide the cascades
for (size_t i = 0 ; i < 4; i++) {
@@ -483,15 +531,14 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
if (csm[i]) {
lightmapCubes[i].mapFrustum(*mEngine, csm[i]);
}
uint8_t const layer = csm[i] ? 0x2 : 0x0;
rcm.setLayerMask(rcm.getInstance(lightmapCubes[i].getSolidRenderable()),
0x3, layer);
rcm.setLayerMask(rcm.getInstance(lightmapCubes[i].getWireFrameRenderable()),
0x3, layer);
uint8_t const layer = csm[i] ? mDirectionalShadowFrustumEnabled : 0x0;
rcm.setLayerMask(rcm.getInstance(lightmapCubes[i].getSolidRenderable()), 0x3, layer);
rcm.setLayerMask(rcm.getInstance(lightmapCubes[i].getWireFrameRenderable()), 0x3, layer);
}
}
cameraCube->mapFrustum(*mEngine, window->mMainCamera);
cameraGrid->mapFrustum(*mEngine, window->mMainCamera);
// Delay rendering for roughly one monitor refresh interval
// TODO: Use SDL_GL_SetSwapInterval for proper vsync
@@ -549,6 +596,7 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
cleanupCallback(mEngine, window->mMainView->getView(), mScene);
cameraCube.reset();
cameraGrid.reset();
lightmapCubes.clear();
window.reset();
@@ -661,6 +709,30 @@ void FilamentApp::initSDL() {
FILAMENT_CHECK_POSTCONDITION(SDL_Init(SDL_INIT_EVENTS) == 0) << "SDL_Init Failure";
}
void FilamentApp::setCameraFrustumEnabled(bool enabled) noexcept {
mCameraFrustumEnabled = enabled ? 0x2 : 0x0;
}
void FilamentApp::setDirectionalShadowFrustumEnabled(bool enabled) noexcept {
mDirectionalShadowFrustumEnabled = enabled ? 0x2 : 0x0;
}
void FilamentApp::setFroxelGridEnabled(bool enabled) noexcept {
mFroxelGridEnabled = enabled ? 0x3 : 0x0;
}
bool FilamentApp::isCameraFrustumEnabled() const noexcept {
return !!mCameraFrustumEnabled;
}
bool FilamentApp::isDirectionalShadowFrustumEnabled() const noexcept {
return !!mDirectionalShadowFrustumEnabled;
}
bool FilamentApp::isFroxelGridEnabled() const noexcept {
return !!mFroxelGridEnabled;
}
// ------------------------------------------------------------------------------------------------
FilamentApp::Window::Window(FilamentApp* filamentApp,
@@ -979,25 +1051,22 @@ void FilamentApp::Window::configureCamerasForWindow() {
double near = mFilamentApp->mCameraNear;
double far = mFilamentApp->mCameraFar;
if (mMainView->getView()->getStereoscopicOptions().enabled) {
mat4 projections[4];
projections[0] = Camera::projection(mFilamentApp->mCameraFocalLength, 1.0, near, far);
projections[1] = projections[0];
// simulate foveated rendering
projections[2] = Camera::projection(mFilamentApp->mCameraFocalLength * 2.0, 1.0, near, far);
projections[3] = projections[2];
mMainCamera->setCustomEyeProjection(projections, 4, projections[0], near, far);
} else {
mMainCamera->setLensProjection(mFilamentApp->mCameraFocalLength, 1.0, near, far);
}
mDebugCamera->setProjection(45.0, double(mainWidth) / height, 0.0625, 4096, Camera::Fov::VERTICAL);
auto aspectRatio = double(mainWidth) / height;
if (mMainView->getView()->getStereoscopicOptions().enabled) {
const int ec = mConfig.stereoscopicEyeCount;
aspectRatio = double(mainWidth) / ec / height;
mat4 projections[4];
projections[0] = Camera::projection(mFilamentApp->mCameraFocalLength, aspectRatio, near, far);
projections[1] = projections[0];
// simulate foveated rendering
projections[2] = Camera::projection(mFilamentApp->mCameraFocalLength * 2.0, aspectRatio, near, far);
projections[3] = projections[2];
mMainCamera->setCustomEyeProjection(projections, 4, projections[0], near, far);
} else {
mMainCamera->setLensProjection(mFilamentApp->mCameraFocalLength, aspectRatio, near, far);
}
mMainCamera->setScaling({1.0 / aspectRatio, 1.0});
mDebugCamera->setProjection(45.0, aspectRatio, 0.0625, 4096, Camera::Fov::VERTICAL);
// We're in split view when there are more views than just the Main and UI views.
if (splitview) {
+204
View File
@@ -0,0 +1,204 @@
/*
* Copyright (C) 2025 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 <filamentapp/Grid.h>
#include <filament/Box.h>
#include <filament/Camera.h>
#include <filament/Color.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/Material.h>
#include <filament/MaterialEnums.h>
#include <filament/MaterialInstance.h>
#include <filament/RenderableManager.h>
#include <filament/TransformManager.h>
#include <filament/VertexBuffer.h>
#include <utils/EntityManager.h>
#include <math/vec3.h>
#include <math/mat4.h>
#include <cstdint>
#include <cstddef>
#include <utility>
#include <vector>
#include <utils/Log.h>
using namespace filament;
using namespace filament::math;
using namespace filament;
Grid::Grid(Engine& engine, Material const* material, float3 linearColor)
: mEngine(engine),
mMaterial(material) {
if (mMaterial) {
mMaterialInstanceWireFrame = mMaterial->createInstance();
mMaterialInstanceWireFrame->setDepthCulling(true);
mMaterialInstanceWireFrame->setParameter("color", RgbaType::LINEAR,
LinearColorA{linearColor.r, linearColor.g, linearColor.b, 0.25f});
}
utils::EntityManager& em = utils::EntityManager::get();
mWireFrameRenderable = em.create();
RenderableManager::Builder(1)
.boundingBox({ { -1.0f, -1.0f, -1.0f }, { 1.0f, 1.0f, 1.0f } })
.material(0, mMaterialInstanceWireFrame)
.priority(6)
.culling(false)
.build(engine, mWireFrameRenderable);
}
Grid::Grid(Grid&& rhs) noexcept
: mEngine(rhs.mEngine) {
using std::swap;
swap(rhs.mVertexBuffer, mVertexBuffer);
swap(rhs.mIndexBuffer, mIndexBuffer);
swap(rhs.mMaterial, mMaterial);
swap(rhs.mMaterialInstanceWireFrame, mMaterialInstanceWireFrame);
swap(rhs.mWireFrameRenderable, mWireFrameRenderable);
}
Grid::~Grid() {
mEngine.destroy(mVertexBuffer);
mEngine.destroy(mIndexBuffer);
// We don't own the material, only instances
mEngine.destroy(mWireFrameRenderable);
// material instances must be destroyed after the renderables
mEngine.destroy(mMaterialInstanceWireFrame);
utils::EntityManager& em = utils::EntityManager::get();
em.destroy(mWireFrameRenderable);
}
void Grid::mapFrustum(Engine& engine, Camera const* camera) {
// the Camera far plane is at infinity, but we want it closer for display
const mat4 vm(camera->getModelMatrix());
mat4 const p(vm * inverse(camera->getProjectionMatrix()));
mapFrustum(engine, p);
}
void Grid::mapFrustum(Engine& engine, mat4 const& transform) {
// the Camera far plane is at infinity, but we want it closer for display
mat4f const p(transform);
auto& tcm = engine.getTransformManager();
tcm.setTransform(tcm.getInstance(mWireFrameRenderable), p);
}
void Grid::mapAabb(Engine& engine, Box const& box) {
mat4 const p = mat4::translation(box.center) * mat4::scaling(box.halfExtent);
mapFrustum(engine, p);
}
void Grid::update(uint32_t width, uint32_t height, uint32_t depth) {
// [-1, 1] range (default behavior)
update(width, height, depth,
[](int const index) { return float(index) * 2.0f - 1.0f; },
[](int const index) { return float(index) * 2.0f - 1.0f; },
[](int const index) { return float(index) * 2.0f - 1.0f; });
}
void Grid::update(uint32_t const width, uint32_t const height, uint32_t const depth,
Generator const& genWidth, Generator const& genHeight, Generator const& genDepth) {
// First, destroy the existing buffers.
mEngine.destroy(mVertexBuffer);
mEngine.destroy(mIndexBuffer);
std::vector<float3> vertices;
std::vector<uint32_t> indices;
size_t const verticeCount = (depth + 1) * (height + 1) * (width + 1);
vertices.reserve(verticeCount);
indices.reserve(verticeCount * 2);
// Generate vertices
// The vertices are generated to form a grid in the [-1, 1] range on all axes.
for (int k = 0; k <= depth; ++k) {
auto const z = genDepth(k);
for (int j = 0; j <= height; ++j) {
auto const y = genHeight(j);
for (int i = 0; i <= width; ++i) {
auto const x = genWidth(i);
vertices.push_back({ x, y, z });
}
}
}
// Generate indices for the lines
// The indices connect the vertices to form the grid lines.
auto getVertexIndex = [=](uint32_t const i, uint32_t const j, uint32_t const k) {
return k * (width + 1) * (height + 1) + j * (width + 1) + i;
};
// Lines along X axis
for (uint32_t k = 0; k <= depth; ++k) {
for (uint32_t j = 0; j <= height; ++j) {
indices.push_back(getVertexIndex(0, j, k));
indices.push_back(getVertexIndex(width, j, k));
}
}
// Lines along Y axis
for (uint32_t k = 0; k <= depth; ++k) {
for (uint32_t i = 0; i <= width; ++i) {
indices.push_back(getVertexIndex(i, 0, k));
indices.push_back(getVertexIndex(i, height, k));
}
}
// Lines along Z axis
for (uint32_t j = 0; j <= height; ++j) {
for (uint32_t i = 0; i <= width; ++i) {
indices.push_back(getVertexIndex(i, j, 0));
indices.push_back(getVertexIndex(i, j, depth));
}
}
const size_t vertexCount = vertices.size();
mVertexBuffer = VertexBuffer::Builder()
.vertexCount(vertexCount)
.bufferCount(1)
.attribute(POSITION, 0, VertexBuffer::AttributeType::FLOAT3)
.build(mEngine);
auto vertexData = vertices.data();
mVertexBuffer->setBufferAt(mEngine, 0,
VertexBuffer::BufferDescriptor::make(
vertexData, vertexCount * sizeof(vertices[0]),
[v = std::move(vertices)](void*, size_t) {}));
const size_t indexCount = indices.size();
mIndexBuffer = IndexBuffer::Builder()
.indexCount(indexCount)
.build(mEngine);
auto indexData = indices.data();
mIndexBuffer->setBuffer(mEngine,
IndexBuffer::BufferDescriptor::make(
indexData, indexCount * sizeof(uint32_t),
[i = std::move(indices)](void*, size_t) {}));
auto& rcm = mEngine.getRenderableManager();
auto instance = rcm.getInstance(mWireFrameRenderable);
rcm.setGeometryAt(instance, 0, RenderableManager::PrimitiveType::LINES,
mVertexBuffer, mIndexBuffer, 0, indexCount);
}
+10 -1
View File
@@ -586,6 +586,12 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, AmbientOcclusio
i = parse(tokens, i + 1, jsonChunk, &out->sampleStepsPerSlice);
} else if (compare(tok, jsonChunk, "thicknessHeuristic") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->thicknessHeuristic);
} else if (compare(tok, jsonChunk, "useVisibilityBitmasks") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->useVisibilityBitmasks);
} else if (compare(tok, jsonChunk, "constThickness") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->constThickness);
} else if (compare(tok, jsonChunk, "linearThickness") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->linearThickness);
} else {
slog.w << "Invalid Gtao key: '" << STR(tok, jsonChunk) << "'" << io::endl;
i = parse(tokens, i + 1);
@@ -602,7 +608,10 @@ std::ostream& operator<<(std::ostream& out, const AmbientOcclusionOptions::Gtao&
return out << "{\n"
<< "\"sampleSliceCount\": " << int(in.sampleSliceCount) << ",\n"
<< "\"sampleStepsPerSlice\": " << int(in.sampleStepsPerSlice) << ",\n"
<< "\"thicknessHeuristic\": " << (in.thicknessHeuristic) << "\n"
<< "\"thicknessHeuristic\": " << (in.thicknessHeuristic) << ",\n"
<< "\"useVisibilityBitmasks\": " << to_string(in.useVisibilityBitmasks) << ",\n"
<< "\"constThickness\": " << (in.constThickness) << ",\n"
<< "\"linearThickness\": " << to_string(in.linearThickness) << "\n"
<< "}";
}
+8 -3
View File
@@ -854,6 +854,11 @@ void ViewerGui::updateUserInterface() {
ImGui::SliderInt("Slice Count", &sliceCount, 1, 10);
ImGui::SliderInt("Steps Per Slice", &stepsPerSlice, 1, 4);
ImGui::Checkbox("Use Visibility Bitmasks", &ssao.gtao.useVisibilityBitmasks);
if (ssao.gtao.useVisibilityBitmasks) {
ImGui::SliderFloat("Constant Thickness", &ssao.gtao.constThickness, 0.01f, 10.0f);
ImGui::Checkbox("Linear Thickness", &ssao.gtao.linearThickness);
}
ssao.gtao.sampleSliceCount = static_cast<uint8_t>(sliceCount);
ssao.gtao.sampleStepsPerSlice = static_cast<uint8_t>(stepsPerSlice);
@@ -1133,15 +1138,15 @@ void ViewerGui::updateUserInterface() {
debug.getPropertyAddress<bool>("d.stereo.combine_multiview_images"));
ImGui::Unindent();
#endif
#endif
ImGui::SliderFloat("Ocular distance", &mSettings.viewer.cameraEyeOcularDistance, 0.0f,
1.0f);
ImGui::SliderFloat("Ocular distance",
&mSettings.viewer.cameraEyeOcularDistance, 0.0f, 1.0f);
float toeInDegrees = mSettings.viewer.cameraEyeToeIn / f::PI * 180.0f;
ImGui::SliderFloat("Toe in", &toeInDegrees, 0.0f, 30.0, "%.3f°");
mSettings.viewer.cameraEyeToeIn = toeInDegrees / 180.0f * f::PI;
ImGui::Unindent();
#endif
}
colorGradingUI(mSettings, mRangePlot, mCurvePlot, mToneMapPlot);
+2
View File
@@ -19,6 +19,7 @@ set(RESOURCE_BINS)
set(MATERIAL_SRCS
materials/aiDefaultMat.mat
materials/bakedColor.mat
materials/bakedColorWithMutableConstants.mat
materials/bakedTexture.mat
materials/aoPreview.mat
materials/arrayTexture.mat
@@ -281,6 +282,7 @@ if (NOT ANDROID)
add_demo(lightbulb)
add_demo(material_sandbox)
add_demo(multiple_windows)
add_demo(mutable_constants)
# Tint does not support setting gl_PointSize, disable the relevant sample if using WebGPU
# https://github.com/gpuweb/gpuweb/issues/1190
if (NOT FILAMENT_SUPPORTS_WEBGPU)
+11 -1
View File
@@ -217,7 +217,7 @@ static std::ifstream::pos_type getFileSize(const char* filename) {
}
static int handleCommandLineArguments(int argc, char* argv[], App* app) {
static constexpr const char* OPTSTR = "ha:f:i:usc:rt:b:evg:dw:";
static constexpr const char* OPTSTR = "ha:f:i:usc:rt:y:b:evg:dw:";
static const struct option OPTIONS[] = {
{ "help", no_argument, nullptr, 'h' },
{ "api", required_argument, nullptr, 'a' },
@@ -949,12 +949,22 @@ int main(int argc, char** argv) {
"d.shadowmap.display_shadow_texture_channel"), 0, 3);
ImGui::Unindent();
}
bool cameraFrustum = FilamentApp::get().isCameraFrustumEnabled();
ImGui::Checkbox("Show Camera Frustum", &cameraFrustum);
FilamentApp::get().setCameraFrustumEnabled(cameraFrustum);
bool shadowFrustum = FilamentApp::get().isDirectionalShadowFrustumEnabled();
ImGui::Checkbox("Show Shadow Frustum", &shadowFrustum);
FilamentApp::get().setDirectionalShadowFrustumEnabled(shadowFrustum);
bool debugFroxelVisualization;
if (debug.getProperty("d.lighting.debug_froxel_visualization",
&debugFroxelVisualization)) {
ImGui::Checkbox("Froxel Visualization", &debugFroxelVisualization);
debug.setProperty("d.lighting.debug_froxel_visualization",
debugFroxelVisualization);
FilamentApp::get().setFroxelGridEnabled(debugFroxelVisualization);
}
auto dataSource = debug.getDataSource("d.view.frame_info");
@@ -0,0 +1,44 @@
material {
name : BakedColorWithMutableConstants,
requires : [
color
],
constants : [
{
name: swizzle,
type: bool,
mutable: true,
default: true,
},
{
name: darken,
type: bool,
mutable: true,
},
{
// In generated shader sources, we first specify immutable
// specialization constants, then mutable specialization constants.
// By including this dummy constant, we offset the ID of the first
// mutable constant by one. This is done here purely for testing
// purposes.
name: dummy,
type: bool,
}
],
shadingModel : unlit,
culling : none,
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
if (materialConstants_swizzle) {
material.baseColor = getColor().gbra;
} else {
material.baseColor = getColor();
}
if (materialConstants_darken) {
material.baseColor.rgb *= 0.5;
}
}
}
+214
View File
@@ -0,0 +1,214 @@
/*
* Copyright (C) 2025 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 <filament/Camera.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <filament/Skybox.h>
#include <filament/TransformManager.h>
#include <filament/VertexBuffer.h>
#include <filament/View.h>
#include <imgui.h>
#include <utils/EntityManager.h>
#include <filamentapp/Config.h>
#include <filamentapp/FilamentApp.h>
#include <getopt/getopt.h>
#include <cmath>
#include <iostream>
#include "generated/resources/resources.h"
using namespace filament;
using utils::Entity;
using utils::EntityManager;
struct App {
Config config;
VertexBuffer* vb;
IndexBuffer* ib;
Material* mat;
Camera* cam;
Entity camera;
Skybox* skybox;
Entity renderable;
};
struct Vertex {
filament::math::float2 position;
uint32_t color;
};
static const Vertex TRIANGLE_VERTICES[3] = {
{{1, 0}, 0xffff0000u},
{{cos(M_PI * 2 / 3), sin(M_PI * 2 / 3)}, 0xff00ff00u},
{{cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xff0000ffu},
};
static constexpr uint16_t TRIANGLE_INDICES[3] = { 0, 1, 2 };
static void printUsage(char* name) {
std::string exec_name(utils::Path(name).getName());
std::string usage(
"MUTABLE_CONSTANTS renders a spinning colored triangle\n"
"Usage:\n"
" MUTABLE_CONSTANTS [options]\n"
"Options:\n"
" --help, -h\n"
" Prints this message\n\n"
" --api, -a\n"
" Specify the backend API: opengl, vulkan, metal, or webgpu\n"
" (note: webgpu is a no-op for now, printing backend\n"
" component info if FILAMENT_BACKEND_DEBUG_FLAG, \n"
" set at build time, includes the \n"
" FWGPU_PRINT_SYSTEM bit flag 0x2)\n"
);
const std::string from("MUTABLE_CONSTANTS");
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
usage.replace(pos, from.length(), exec_name);
}
std::cout << usage;
}
static int handleCommandLineArguments(int argc, char* argv[], App* app) {
static constexpr const char* OPTSTR = "ha:";
static const struct option OPTIONS[] = {
{ "help", no_argument, nullptr, 'h' },
{ "api", required_argument, nullptr, 'a' },
{ nullptr, 0, nullptr, 0 }
};
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &option_index)) >= 0) {
std::string arg(optarg ? optarg : "");
switch (opt) {
default:
case 'h':
printUsage(argv[0]);
exit(0);
case 'a':
if (arg == "opengl") {
app->config.backend = Engine::Backend::OPENGL;
} else if (arg == "vulkan") {
app->config.backend = Engine::Backend::VULKAN;
} else if (arg == "metal") {
app->config.backend = Engine::Backend::METAL;
} else if (arg == "webgpu") {
app->config.backend = Engine::Backend::WEBGPU;
} else {
std::cerr << "Unrecognized backend. Must be "
"'opengl'|'vulkan'|'metal'|'webgpu'.\n";
exit(1);
}
break;
}
}
return optind;
}
int main(int argc, char** argv) {
App app{};
app.config.title = "mutable_constants";
handleCommandLineArguments(argc, argv, &app);
auto setup = [&app](Engine* engine, View* view, Scene* scene) {
app.skybox = Skybox::Builder().color({0.1, 0.125, 0.25, 1.0}).build(*engine);
scene->setSkybox(app.skybox);
view->setPostProcessingEnabled(false);
static_assert(sizeof(Vertex) == 12, "Strange vertex size.");
app.vb = VertexBuffer::Builder()
.vertexCount(3)
.bufferCount(1)
.attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 12)
.attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 8, 12)
.normalized(VertexAttribute::COLOR)
.build(*engine);
app.vb->setBufferAt(*engine, 0,
VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES, 36, nullptr));
app.ib = IndexBuffer::Builder()
.indexCount(3)
.bufferType(IndexBuffer::IndexType::USHORT)
.build(*engine);
app.ib->setBuffer(*engine,
IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, 6, nullptr));
app.mat = Material::Builder().package(RESOURCES_BAKEDCOLORWITHMUTABLECONSTANTS_DATA,
RESOURCES_BAKEDCOLORWITHMUTABLECONSTANTS_SIZE)
.build(*engine);
app.renderable = EntityManager::get().create();
RenderableManager::Builder(1)
.boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }})
.material(0, app.mat->getDefaultInstance())
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.vb, app.ib, 0, 3)
.culling(false)
.receiveShadows(false)
.castShadows(false)
.build(*engine, app.renderable);
scene->addEntity(app.renderable);
app.camera = utils::EntityManager::get().create();
app.cam = engine->createCamera(app.camera);
view->setCamera(app.cam);
};
auto cleanup = [&app](Engine* engine, View*, Scene*) {
engine->destroy(app.skybox);
engine->destroy(app.renderable);
engine->destroy(app.mat);
engine->destroy(app.vb);
engine->destroy(app.ib);
engine->destroyCameraComponent(app.camera);
utils::EntityManager::get().destroy(app.camera);
};
auto gui = [&app](Engine* engine, View* view) {
MaterialInstance* mi = app.mat->getDefaultInstance();
bool swizzle = mi->getConstant<bool>("swizzle");
bool darken = mi->getConstant<bool>("darken");
ImGui::Checkbox("Swizzle", &swizzle);
ImGui::Checkbox("Darken", &darken);
if (swizzle != mi->getConstant<bool>("swizzle")) {
mi->setConstant("swizzle", swizzle);
}
if (darken != mi->getConstant<bool>("darken")) {
mi->setConstant("darken", darken);
}
};
FilamentApp::get().animate([&app](Engine* engine, View* view, double now) {
constexpr float ZOOM = 1.5f;
const uint32_t w = view->getViewport().width;
const uint32_t h = view->getViewport().height;
const float aspect = (float) w / h;
app.cam->setProjection(Camera::Projection::ORTHO,
-aspect * ZOOM, aspect * ZOOM,
-ZOOM, ZOOM, 0, 1);
auto& tcm = engine->getTransformManager();
tcm.setTransform(tcm.getInstance(app.renderable),
filament::math::mat4f::rotation(now, filament::math::float3{ 0, 0, 1 }));
});
FilamentApp::get().run(app.config, setup, cleanup, gui);
return 0;
}
-4
View File
@@ -506,11 +506,7 @@ vec3 evaluateRefraction(
// compute transmission T
#if defined(MATERIAL_HAS_ABSORPTION)
#if defined(MATERIAL_HAS_THICKNESS) || defined(MATERIAL_HAS_MICRO_THICKNESS)
vec3 T = min(vec3(1.0), exp(-pixel.absorption * ray.d));
#else
vec3 T = 1.0 - pixel.absorption;
#endif
#endif
// Roughness remapping so that an IOR of 1.0 means no microfacet refraction and an IOR
+1 -1
View File
@@ -247,7 +247,7 @@ void evaluatePunctualLights(const MaterialInputs material,
}
if (CONFIG_DEBUG_FROXEL_VISUALIZATION) {
if (froxel.count > 0u) {
if (froxel.count > 0u && frameUniforms.enableFroxelViz != 0) {
const vec3 debugColors[17] = vec3[](
vec3(0.0, 0.0, 0.0), // black
vec3(0.0, 0.0, 0.1647), // darkest blue
+1 -5
View File
@@ -117,15 +117,11 @@ void getCommonPixelParams(const MaterialInputs material, inout PixelParams pixel
pixel.transmission = 1.0;
#endif
#if defined(MATERIAL_HAS_ABSORPTION)
#if defined(MATERIAL_HAS_THICKNESS) || defined(MATERIAL_HAS_MICRO_THICKNESS)
pixel.absorption = max(vec3(0.0), material.absorption);
#else
pixel.absorption = saturate(material.absorption);
#endif
#else
pixel.absorption = vec3(0.0);
#endif
#if defined(MATERIAL_HAS_THICKNESS)
#if defined(SHADING_MODEL_SUBSURFACE) || defined(MATERIAL_HAS_REFRACTION)
pixel.thickness = max(0.0, material.thickness);
#endif
#if defined(MATERIAL_HAS_MICRO_THICKNESS) && (REFRACTION_TYPE == REFRACTION_TYPE_THIN)
+4
View File
@@ -23,6 +23,10 @@ function start_render_() {
bash ${BUILD_COMMON_DIR}/get-mesa.sh
fi
if [ ! -d ${GLTF_DIR} ]; then
cat ${RENDERDIFF_TEST_DIR}/tests/gltf_models.txt | xargs bash ${BUILD_COMMON_DIR}/get-gltf-sample-assets.sh
fi
# Install python deps
python3 -m venv ${VENV_DIR}
source ${VENV_DIR}/bin/activate
+1
View File
@@ -22,6 +22,7 @@ GOLDEN_OUTPUT_DIR="$(pwd)/out/renderdiff/goldens"
RENDERDIFF_TEST_DIR="$(pwd)/test/renderdiff"
MESA_DIR="$(pwd)/mesa/out/"
VENV_DIR="$(pwd)/venv"
GLTF_DIR="$(pwd)/gltf/Models"
BUILD_COMMON_DIR="$(pwd)/build/common"
os_name=$(uname -s)
+16 -3
View File
@@ -44,11 +44,22 @@ class RenderingConfig():
class PresetConfig(RenderingConfig):
def __init__(self, data, existing_models):
RenderingConfig.__init__(self, data)
models = data.get('models')
if models:
self.models = []
def _check(models):
assert _is_list_of_strings(models)
assert all(m in existing_models for m in models)
models = data.get('models')
if models:
_check(models)
self.models = models
model_list_file = data.get('model_list_file')
if model_list_file and os.path.exists(model_list_file):
with open(model_list_file, 'r') as f:
models = list(filter(lambda a: len(a) > 0, map(lambda a: a.strip(), f.read().split('\n'))))
_check(models)
self.models += models
class TestConfig(RenderingConfig):
def __init__(self, data, existing_models, presets):
@@ -105,7 +116,9 @@ class RenderTestConfig():
assert all(path.isdir(p) for p in model_search_paths)
model_paths = list(
chain(*(glob.glob(f'{d}/**/*.glb', recursive=True) for d in model_search_paths)))
chain(*(glob.glob(f'{d}/**/*.glb', recursive=True) for d in model_search_paths))) + \
list(
chain(*(glob.glob(f'{d}/**/*.gltf', recursive=True) for d in model_search_paths)))
# This flatten the output for glob.glob
self.models = {path.splitext(path.basename(model))[0]: model for model in model_paths}
+20
View File
@@ -0,0 +1,20 @@
AlphaBlendModeTest
AttenuationTest
Box
BoomBoxWithAxes
BoxInterleaved
BoxTextured
BoxTexturedNonPowerOfTwo
Duck
IridescenceSuzanne
Lantern
MetalRoughSpheres
NegativeScaleTest
NormalTangentMirrorTest
NormalTangentTest
SpecularTest
Sponza
Suzanne
TextureCoordinateTest
TextureSettingsTest
TwoSidedPlane
+3
View File
@@ -113,6 +113,9 @@ Filament.loadGeneratedExtensions = function() {
sampleSliceCount: 4,
sampleStepsPerSlice: 3,
thicknessHeuristic: 0.004,
useVisibilityBitmasks: false,
constThickness: 0.5,
linearThickness: false,
};
return Object.assign(options, overrides);
};
+15 -1
View File
@@ -1595,9 +1595,23 @@ export interface View$AmbientOcclusionOptions$Gtao {
*/
sampleStepsPerSlice?: number;
/**
* thickness heuristic, should be closed to 0
* thickness heuristic, should be closed to 0. No effect when useVisibilityBitmasks sets to true.
*/
thicknessHeuristic?: number;
/**
* Enables or disables visibility bitmasks mode. Notes that bent normal doesn't work under this mode.
* Caution: Changing this option at runtime is very expensive as it may trigger a shader re-compilation.
*/
useVisibilityBitmasks?: boolean;
/**
* constant thickness value of objects on the screen in world space. Only take effect when useVisibilityBitmasks is set to true.
*/
constThickness?: number;
/**
* Increase thickness with distance to maintain detail on distant surfaces.
* Caution: Changing this option at runtime is very expensive as it may trigger a shader re-compilation.
*/
linearThickness?: boolean;
}
/**
+3
View File
@@ -99,6 +99,9 @@ value_object<View::AmbientOcclusionOptions::Gtao>("View$AmbientOcclusionOptions$
.field("sampleSliceCount", &View::AmbientOcclusionOptions::Gtao::sampleSliceCount)
.field("sampleStepsPerSlice", &View::AmbientOcclusionOptions::Gtao::sampleStepsPerSlice)
.field("thicknessHeuristic", &View::AmbientOcclusionOptions::Gtao::thicknessHeuristic)
.field("useVisibilityBitmasks", &View::AmbientOcclusionOptions::Gtao::useVisibilityBitmasks)
.field("constThickness", &View::AmbientOcclusionOptions::Gtao::constThickness)
.field("linearThickness", &View::AmbientOcclusionOptions::Gtao::linearThickness)
;
value_object<View::AmbientOcclusionOptions>("View$AmbientOcclusionOptions")