Compare commits

..
Author SHA1 Message Date
Run Yu 0ad90d139d wgpu: code clean up based on feedback 2026-01-28 21:35:45 -05:00
Run Yu cf694a3334 wgpu: add TextureUsage::SAMPLEABLE to the depth texture in test_Scissor,
which is needed to draw the triangle if the test is enabled.
2026-01-23 15:17:12 -05:00
Run Yu 863379b5b4 wgpu: fix the readPixels function so test_Scissor can draw the triangle 2026-01-23 15:17:12 -05:00
84 changed files with 948 additions and 2612 deletions
+1 -2
View File
@@ -245,8 +245,7 @@ jobs:
env:
TAG: ${{ steps.git_ref.outputs.tag }}
run: |
@REMARK 'call' is required to ensure control returns to this script after the batch file finishes.
call build\windows\build-github.bat release
build\windows\build-github.bat release
echo on
move out\filament-windows.tgz out\filament-%TAG%-windows.tgz
shell: cmd
-3
View File
@@ -6,6 +6,3 @@
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- engine: fix shader compilation failure in TAA material
- engine: fix stereo & parallel shader compilation
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.69.1'
implementation 'com.google.android.filament:filament-android:1.68.5'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.69.1'
pod 'Filament', '~> 1.68.5'
```
## Documentation
-3
View File
@@ -7,9 +7,6 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.69.1
## v1.69.0
- engine: Support custom attributes morphing, and allow for omitting position and/or normal data. [⚠️ **Recompile Materials**]
+1 -1
View File
@@ -7,7 +7,7 @@ apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
def tools = ['matc', 'cmgen']
def platforms = [
'mac': [classifier: 'osx-aarch_64', archive: "filament-v${VERSION_NAME}-mac.tgz", path: { t -> "filament/bin/${t}" }],
'mac': [classifier: 'osx-aarch64', archive: "filament-v${VERSION_NAME}-mac.tgz", path: { t -> "filament/bin/${t}" }],
'linux': [classifier: 'linux-x86_64', archive: "filament-v${VERSION_NAME}-linux.tgz", path: { t -> "filament/bin/${t}" }],
'windows': [classifier: 'windows-x86_64', archive: "filament-v${VERSION_NAME}-windows.tgz", path: { t -> "bin/${t}.exe" }]
]
@@ -159,50 +159,37 @@ extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_utils_AutomationEngine_nGetViewerOptions(JNIEnv* env, jclass,
jlong nativeObject, jobject result) {
AutomationEngine* automation = (AutomationEngine*) nativeObject;
const auto& settings = automation->getSettings();
const auto& options = settings.viewer;
auto options = automation->getViewerOptions();
const jclass klass = env->GetObjectClass(result);
const jfieldID cameraAperture = env->GetFieldID(klass, "cameraAperture", "F");
const jfieldID cameraSpeed = env->GetFieldID(klass, "cameraSpeed", "F");
const jfieldID cameraISO = env->GetFieldID(klass, "cameraISO", "F");
const jfieldID cameraNear = env->GetFieldID(klass, "cameraNear", "F");
const jfieldID cameraFar = env->GetFieldID(klass, "cameraFar", "F");
const jfieldID groundShadowStrength = env->GetFieldID(klass, "groundShadowStrength", "F");
const jfieldID groundPlaneEnabled = env->GetFieldID(klass, "groundPlaneEnabled", "Z");
const jfieldID skyboxEnabled = env->GetFieldID(klass, "skyboxEnabled", "Z");
const jfieldID cameraFocalLength = env->GetFieldID(klass, "cameraFocalLength", "F");
const jfieldID cameraFocusDistance = env->GetFieldID(klass, "cameraFocusDistance", "F");
const jfieldID autoScaleEnabled = env->GetFieldID(klass, "autoScaleEnabled", "Z");
const jfieldID autoInstancingEnabled = env->GetFieldID(klass, "autoInstancingEnabled", "Z");
env->SetFloatField(result, cameraAperture, options.cameraAperture);
env->SetFloatField(result, cameraSpeed, options.cameraSpeed);
env->SetFloatField(result, cameraISO, options.cameraISO);
env->SetFloatField(result, cameraNear, options.cameraNear);
env->SetFloatField(result, cameraFar, options.cameraFar);
env->SetFloatField(result, groundShadowStrength, options.groundShadowStrength);
env->SetBooleanField(result, groundPlaneEnabled, options.groundPlaneEnabled);
env->SetBooleanField(result, skyboxEnabled, options.skyboxEnabled);
env->SetFloatField(result, cameraFocalLength, options.cameraFocalLength);
env->SetFloatField(result, cameraFocusDistance, options.cameraFocusDistance);
env->SetBooleanField(result, autoScaleEnabled, options.autoScaleEnabled);
env->SetBooleanField(result, autoInstancingEnabled, options.autoInstancingEnabled);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_utils_AutomationEngine_nGetCameraSettings(JNIEnv* env, jclass,
jlong nativeObject, jobject result) {
AutomationEngine* automation = (AutomationEngine*) nativeObject;
const auto& settings = automation->getSettings();
const auto& camera = settings.camera;
const jclass klass = env->GetObjectClass(result);
const jfieldID aperture = env->GetFieldID(klass, "aperture", "F");
const jfieldID shutterSpeed = env->GetFieldID(klass, "shutterSpeed", "F");
const jfieldID sensitivity = env->GetFieldID(klass, "sensitivity", "F");
const jfieldID near = env->GetFieldID(klass, "near", "F");
const jfieldID far = env->GetFieldID(klass, "far", "F");
const jfieldID focalLength = env->GetFieldID(klass, "focalLength", "F");
const jfieldID focusDistance = env->GetFieldID(klass, "focusDistance", "F");
env->SetFloatField(result, aperture, camera.aperture);
env->SetFloatField(result, shutterSpeed, camera.shutterSpeed);
env->SetFloatField(result, sensitivity, camera.sensitivity);
env->SetFloatField(result, near, camera.near);
env->SetFloatField(result, far, camera.far);
env->SetFloatField(result, focalLength, camera.focalLength);
env->SetFloatField(result, focusDistance, camera.focusDistance);
}
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_utils_AutomationEngine_nGetColorGrading(JNIEnv*, jclass,
jlong nativeObject, jlong nativeEngine) {
@@ -94,23 +94,20 @@ public class AutomationEngine {
* Allows remote control for the viewer.
*/
public static class ViewerOptions {
public float cameraAperture = 16.0f;
public float cameraSpeed = 125.0f;
public float cameraISO = 100.0f;
public float cameraNear = 0.1f;
public float cameraFar = 100.0f;
public float groundShadowStrength = 0.75f;
public boolean groundPlaneEnabled = false;
public boolean skyboxEnabled = true;
public float cameraFocalLength = 28.0f;
public float cameraFocusDistance = 0.0f;
public boolean autoScaleEnabled = true;
public boolean autoInstancingEnabled = false;
}
public static class CameraSettings {
public float aperture = 16.0f;
public float shutterSpeed = 125.0f;
public float sensitivity = 100.0f;
public float near = 0.1f;
public float far = 100.0f;
public float focalLength = 28.0f;
public float focusDistance = 10.0f;
}
/**
* Creates an automation engine from a JSON specification.
*
@@ -232,13 +229,6 @@ public class AutomationEngine {
return result;
}
@NonNull
public CameraSettings getCameraSettings() {
CameraSettings result = new CameraSettings();
nGetCameraSettings(mNativeObject, result);
return result;
}
/**
* Gets a color grading object that corresponds to the latest settings.
*
@@ -290,7 +280,6 @@ public class AutomationEngine {
long[] materials, long ibl, int sunlight, int[] assetLights, long lightManager,
long scene, long renderer);
private static native void nGetViewerOptions(long nativeObject, Object result);
private static native void nGetCameraSettings(long nativeObject, Object result);
private static native long nGetColorGrading(long nativeObject, long nativeEngine);
private static native void nSignalBatchMode(long nativeObject);
private static native void nStopRunning(long nativeObject);
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.69.1
VERSION_NAME=1.68.5
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -389,9 +389,9 @@ class MainActivity : Activity() {
viewerContent.assetLights = modelViewer.asset?.lightEntities
automation.applySettings(modelViewer.engine, json, viewerContent)
modelViewer.view.colorGrading = automation.getColorGrading(modelViewer.engine)
modelViewer.cameraFocalLength = automation.cameraSettings.focalLength
modelViewer.cameraNear = automation.cameraSettings.near
modelViewer.cameraFar = automation.cameraSettings.far
modelViewer.cameraFocalLength = automation.viewerOptions.cameraFocalLength
modelViewer.cameraNear = automation.viewerOptions.cameraNear
modelViewer.cameraFar = automation.viewerOptions.cameraFar
updateRootTransform()
}
+2 -2
View File
@@ -352,7 +352,7 @@ used to create the SPIR-V is not available.</p>
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/viewer.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="../dup/uberz.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
@@ -366,7 +366,7 @@ used to create the SPIR-V is not available.</p>
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/viewer.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="../dup/uberz.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
+2 -2
View File
@@ -236,7 +236,7 @@ mapping should be specified as an <code>optional</code> feature of the ubershade
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/viewer.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="../dup/matdbg.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
@@ -250,7 +250,7 @@ mapping should be specified as an <code>optional</code> feature of the ubershade
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/viewer.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="../dup/matdbg.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
-333
View File
@@ -1,333 +0,0 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>viewer - Filament</title>
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- MathJax -->
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<!-- Provide site root to javascript -->
<script>
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div style="display:flex;align-items:center;justify-content:center">
<img class="flogo" src="../images/filament_logo_small.png"></img>
</div>
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<!-- Filament: disable themes because the markdeep part does not look good for dark themes -->
<!--
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
-->
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Filament</h1>
<div class="right-buttons">
<a href="https://github.com/google/filament" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="viewer-library"><a class="header" href="#viewer-library">Viewer Library</a></h1>
<p>The <strong>Viewer Library</strong> (<code>libs/viewer</code>) provides a high-level abstraction for configuring and rendering Filament scenes. It is used by tools like <code>gltf_viewer</code> to load assets, manage settings, and drive the rendering loop.</p>
<h2 id="features"><a class="header" href="#features">Features</a></h2>
<ul>
<li><strong>Settings Management</strong>: Centralized configuration for View, Camera, Lights, and Materials via the <code>Settings</code> struct.</li>
<li><strong>JSON Serialization</strong>: Full support for loading and saving settings via JSON.</li>
<li><strong>Automation</strong>: <code>AutomationEngine</code> allows scripting the viewer with a sequence of JSON-based test cases (batch mode).</li>
<li><strong>GUI Integration</strong>: Built-in support for <code>imgui</code> via <code>ViewerGui</code> and <code>Settings</code> binding.</li>
</ul>
<h2 id="json-settings-schema"><a class="header" href="#json-settings-schema">JSON Settings Schema</a></h2>
<p>The viewer settings can be configured using a JSON object. This is used for <code>gltf_viewer --settings</code> or in automation specs.</p>
<h3 id="root-object"><a class="header" href="#root-object">Root Object</a></h3>
<p>The root object contains the following categories:</p>
<div class="table-wrapper"><table><thead><tr><th style="text-align: left">Key</th><th style="text-align: left">Type</th><th style="text-align: left">Description</th></tr></thead><tbody>
<tr><td style="text-align: left"><code>view</code></td><td style="text-align: left">Object</td><td style="text-align: left">Post-processing and rendering quality settings.</td></tr>
<tr><td style="text-align: left"><code>camera</code></td><td style="text-align: left">Object</td><td style="text-align: left"><strong>[NEW]</strong> Explicit camera control (pose, projection, exposure).</td></tr>
<tr><td style="text-align: left"><code>lighting</code></td><td style="text-align: left">Object</td><td style="text-align: left"><strong>[NEW]</strong> Environment and dynamic light settings.</td></tr>
<tr><td style="text-align: left"><code>viewer</code></td><td style="text-align: left">Object</td><td style="text-align: left">Global viewer options (skybox, background, scaling).</td></tr>
<tr><td style="text-align: left"><code>animation</code></td><td style="text-align: left">Object</td><td style="text-align: left"><strong>[NEW]</strong> Animation playback control.</td></tr>
<tr><td style="text-align: left"><code>material</code></td><td style="text-align: left">Object</td><td style="text-align: left">Material overrides.</td></tr>
</tbody></table>
</div>
<hr />
<h3 id="camera-settings-camera"><a class="header" href="#camera-settings-camera">Camera Settings (<code>camera</code>)</a></h3>
<p>Allows explicit control over the camera. If <code>enabled</code> is false, the viewer uses its default orbit camera logic (auto-scaling/centering).</p>
<pre><code class="language-json">"camera": {
"enabled": true, // Must be true to use these explicit settings
"projection": "PERSPECTIVE", // "PERSPECTIVE" or "ORTHO"
"center": [0, 0, 0], // World-space look-at point
"lookAt": [0, 0, -1], // World-space eye position (confusingly named 'lookAt' in internal legacy, often 'eye')
"up": [0, 1, 0], // Up vector
"near": 0.1, // Near plane
"far": 100.0, // Far plane
"focalLength": 28.0, // Focal length in mm (Perspective only)
"fov": 0.0, // Field of view in degrees (overrides focalLength if &gt; 0)
"aperture": 16.0, // f-stop
"shutterSpeed": 125.0, // 1/seconds
"sensitivity": 100.0, // ISO
"focusDistance": 10.0, // Focus distance in world units
"scaling": [1.0, 1.0], // Custom projection matrix scaling (mostly for Ortho)
"shift": [0.0, 0.0] // Custom projection matrix shift
}
</code></pre>
<h3 id="lighting-settings-lighting"><a class="header" href="#lighting-settings-lighting">Lighting Settings (<code>lighting</code>)</a></h3>
<p>Controls the Image Based Lighting (IBL), the Sun, and additional dynamic lights.</p>
<pre><code class="language-json">"lighting": {
"iblIntensity": 30000.0,
"iblRotation": 0.0, // Rotation in degrees
"enableSunlight": true,
"enableShadows": true,
"sunlight": { // **[NEW]** Nested sunlight properties
"intensity": 100000.0,
"color": [0.98, 0.92, 0.89],
"direction": [0.6, -1.0, -0.8],
"sunHaloSize": 10.0,
"sunHaloFalloff": 80.0,
"sunAngularRadius": 1.9,
"castShadows": true,
"shadowOptions": { // Per-light shadow options
"mapSize": 1024,
"shadowCascades": 1,
"stable": false
}
},
"lights": [ // **[NEW]** Array of custom lights
{
"type": "POINT", // "POINT", "SPOT", "FOCUSED_SPOT", "DIRECTIONAL", "SUN"
"position": [0, 2, 0],
"color": [1, 0, 0],
"intensity": 5000.0,
"falloff": 10.0,
"castShadows": true,
"shadowOptions": { "mapSize": 512 }
},
{
"type": "SPOT",
"position": [2, 5, 2],
"direction": [0, -1, 0],
"spotInner": 0.5, // Inner cone angle (radians)
"spotOuter": 0.8 // Outer cone angle (radians)
}
]
}
</code></pre>
<h3 id="view-settings-view"><a class="header" href="#view-settings-view">View Settings (<code>view</code>)</a></h3>
<p>Standard Filament view settings.</p>
<pre><code class="language-json">"view": {
"postProcessingEnabled": true,
"antiAliasing": "FXAA", // "NONE", "FXAA"
"msaa": {
"enabled": true,
"sampleCount": 4
},
"ssao": { "enabled": true, ... },
"bloom": { "enabled": true, ... },
"dof": { "enabled": false, ... },
"vignette": { "enabled": false, ... },
"colorGrading": {
"toneMapping": "ACES_LEGACY", // "LINEAR", "ACES", "FILMIC", "PBR_NEUTRAL", etc.
"exposure": 0.0,
"gamma": [1.0, 1.0, 1.0]
}
}
</code></pre>
<h3 id="viewer-options-viewer"><a class="header" href="#viewer-options-viewer">Viewer Options (<code>viewer</code>)</a></h3>
<p>General app-level settings.</p>
<pre><code class="language-json">"viewer": {
"skyboxEnabled": true,
"backgroundColor": [0, 0, 0], // Used if skybox is disabled
"autoScaleEnabled": true, // Fit model to unit cube
"groundPlaneEnabled": false
}
</code></pre>
<h3 id="animation-settings-animation"><a class="header" href="#animation-settings-animation">Animation Settings (<code>animation</code>)</a></h3>
<p>Control glTF animation playback.</p>
<pre><code class="language-json">"animation": {
"enabled": true,
"speed": 1.0,
"time": -1.0 // If &gt;= 0, forces animation to this specific time (seconds)
}
</code></pre>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/matdbg.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/uberz.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/matdbg.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/uberz.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>
File diff suppressed because one or more lines are too long
Binary file not shown.
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
-3
View File
@@ -49,9 +49,6 @@
"libs/fgviewer/README.md": {
"dest": "dup/fgviewer.md"
},
"libs/viewer/README.md": {
"dest": "dup/viewer.md"
},
"ios/CocoaPods/README.md": {
"dest": "release/cocoapods.md"
},
-1
View File
@@ -41,7 +41,6 @@
- [gltfio](./dup/gltfio.md)
- [iblprefilter](./dup/iblprefilter.md)
- [matdbg](./dup/matdbg.md)
- [viewer](./dup/viewer.md)
- [uberz](./dup/uberz.md)
- [Tools](./notes/tools.md)
- [beamsplitter](./dup/beamsplitter.md)
File diff suppressed because one or more lines are too long
Binary file not shown.
-2
View File
@@ -91,7 +91,6 @@ set(SRCS
src/MaterialParser.cpp
src/MorphTargetBuffer.cpp
src/PostProcessManager.cpp
src/ProgramSpecialization.cpp
src/RenderPass.cpp
src/RenderPrimitive.cpp
src/RenderTarget.cpp
@@ -188,7 +187,6 @@ set(PRIVATE_HDRS
src/MaterialInstanceManager.h
src/PIDController.h
src/PostProcessManager.h
src/ProgramSpecialization.h
src/RenderPass.h
src/RenderPrimitive.h
src/RendererUtils.h
-2
View File
@@ -305,8 +305,6 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPUFence.h
src/webgpu/WebGPUIndexBuffer.cpp
src/webgpu/WebGPUIndexBuffer.h
src/webgpu/WebGPUMemoryMappedBuffer.cpp
src/webgpu/WebGPUMemoryMappedBuffer.h
src/webgpu/WebGPUMsaaTextureResolver.cpp
src/webgpu/WebGPUMsaaTextureResolver.h
src/webgpu/WebGPUPipelineCache.cpp
+4 -1
View File
@@ -20,7 +20,6 @@
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h>
#include <utils/Slice.h>
#include <backend/DriverEnums.h>
@@ -146,6 +145,10 @@ public:
return mSpecializationConstants;
}
SpecializationConstantsInfo& getSpecializationConstants() noexcept {
return mSpecializationConstants;
}
DescriptorSetInfo& getDescriptorBindings() noexcept {
return mDescriptorBindings;
}
+2 -3
View File
@@ -17,11 +17,10 @@
#include <backend/Program.h>
#include <backend/DriverEnums.h>
#include <utils/CString.h>
#include <utils/Invocable.h>
#include <utils/Panic.h>
#include <utils/debug.h>
#include <utils/CString.h>
#include <utils/ostream.h>
#include <utils/Invocable.h>
#include <utility>
@@ -23,7 +23,6 @@
#include <private/utils/Tracing.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Logger.h>
namespace filament::backend {
@@ -48,9 +47,7 @@ GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform,
GLuint programId = 0;
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
// TODO(exv): Don't copy these spec constants!
BlobCacheKey::SpecializationConstants constants(program.getSpecializationConstants());
BlobCacheKey key{ program.getCacheId(), std::move(constants) };
BlobCacheKey key{ program.getCacheId(), program.getSpecializationConstants() };
// FIXME: use a static buffer to avoid systematic allocation
// always attempt with 64 KiB
+1 -7
View File
@@ -2161,7 +2161,7 @@ void OpenGLDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
DEBUG_MARKER()
GLDescriptorSetLayout const* dsl = handle_cast<GLDescriptorSetLayout*>(dslh);
construct<GLDescriptorSet>(dsh, mContext, dslh, dsl);
mHandleAllocator.associateTagToHandle(dsh.getId(), std::move(tag));
mHandleAllocator.associateTagToHandle(dslh.getId(), std::move(tag));
}
void OpenGLDriver::mapBufferR(MemoryMappedBufferHandle mmbh,
@@ -4685,9 +4685,6 @@ void OpenGLDriver::bindDescriptorSet(
if (UTILS_UNLIKELY(!dsh)) {
mBoundDescriptorSets[set].dsh = dsh;
#ifndef NDEBUG
mBoundDescriptorSets[set].tag = "null";
#endif
mInvalidDescriptorSetBindings.set(set, true);
mInvalidDescriptorSetBindingOffsets.set(set, true);
return;
@@ -4709,9 +4706,6 @@ void OpenGLDriver::bindDescriptorSet(
// `offsets` data's lifetime will end when this function returns. We have to make a copy.
// (the data is allocated inside the CommandStream)
mBoundDescriptorSets[set].dsh = dsh;
#ifndef NDEBUG
mBoundDescriptorSets[set].tag = mHandleAllocator.getHandleTag(dsh.getId());
#endif
assert_invariant(offsets.data() != nullptr || ds->getDynamicBufferCount() == 0);
std::copy_n(offsets.data(), ds->getDynamicBufferCount(),
mBoundDescriptorSets[set].offsets.data());
@@ -407,9 +407,6 @@ private:
struct {
DescriptorSetHandle dsh;
std::array<uint32_t, CONFIG_UNIFORM_BINDING_COUNT> offsets;
#ifndef NDEBUG
utils::ImmutableCString tag;
#endif
} mBoundDescriptorSets[MAX_DESCRIPTOR_SET_COUNT] = {};
void clearWithRasterPipe(TargetBufferFlags clearFlags,
@@ -120,33 +120,27 @@ bool PlatformEGL::isOpenGL() const noexcept {
PlatformEGL::ExternalImageEGL::~ExternalImageEGL() = default;
Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& driverConfig) {
static constexpr int kMaxNumEGLDevices = 32;
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
EGLint major, minor;
EGLBoolean initialized = false;
EGLBoolean initialized = eglInitialize(mEGLDisplay, &major, &minor);
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
PFNEGLGETPLATFORMDISPLAYEXTPROC const getPlatformDisplay =
PFNEGLGETPLATFORMDISPLAYEXTPROC(eglGetProcAddress("eglGetPlatformDisplay"));
if (eglQueryDevicesEXT != nullptr && getPlatformDisplay != nullptr) {
EGLint numDevices = 0;
EGLDeviceEXT eglDevices[kMaxNumEGLDevices];
if (eglQueryDevicesEXT(kMaxNumEGLDevices, eglDevices, &numDevices)) {
for (int i = 0; i < numDevices && !initialized; ++i) {
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevices[i], nullptr);
if (!initialized) {
EGLDeviceEXT eglDevice;
EGLint numDevices;
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
if (eglQueryDevicesEXT != nullptr) {
eglQueryDevicesEXT(1, &eglDevice, &numDevices);
if(auto* getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
eglGetProcAddress("eglGetPlatformDisplay"))) {
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, nullptr);
initialized = eglInitialize(mEGLDisplay, &major, &minor);
}
}
}
if (!initialized) {
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
initialized = eglInitialize(mEGLDisplay, &major, &minor);
}
if (UTILS_UNLIKELY(!initialized)) {
LOG(ERROR) << "eglInitialize failed";
return nullptr;
@@ -31,19 +31,22 @@ namespace filament::backend {
VulkanBufferProxy::VulkanBufferProxy(VulkanContext const& context, VmaAllocator allocator,
VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, VulkanBufferBinding binding,
BufferUsage usage, uint32_t numBytes)
: mStagingBufferBypassEnabled(context.stagingBufferBypassEnabled()),
mAllocator(allocator),
mStagePool(stagePool),
mBufferCache(bufferCache),
mBuffer(mBufferCache.acquire(binding, numBytes)),
mLastReadAge(0),
mUsage(usage) {}
: mStagingBufferBypassEnabled(context.stagingBufferBypassEnabled()),
mAllocator(allocator),
mStagePool(stagePool),
mBufferCache(bufferCache),
mBuffer(mBufferCache.acquire(binding, numBytes)),
mLastReadAge(0),
mUsage(usage) {}
void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData,
uint32_t byteOffset, uint32_t numBytes) {
// This means that the buffer is not currently in use by the GPU
bool const isAvailable = mBuffer->getCount() == 1;
// This means that we're recording a write into a command buffer without a previous read, so it
// should be safe to
// 1) Do a direct memcpy in UMA mode
// 2) Skip adding a barrier (to protect the write from writing over a read).
bool const isAvailable = commands.age() != mLastReadAge;
// Keep track of the VulkanBuffer usage
commands.acquire(mBuffer);
@@ -78,13 +81,9 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c
memcpy(stage->mapping(), cpuData, numBytes);
vmaFlushAllocation(mAllocator, stage->memory(), stage->offset(), numBytes);
// This means that we're recording a write into a command buffer with a previous read, so it
// needs to add a barrier (to protect the write from writing over a read).
bool const issueBarrier = commands.age() == mLastReadAge;
// If there was a previous read, then we need to make sure the following write is properly
// synced with the previous read.
if (issueBarrier) {
if (!isAvailable) {
VkAccessFlags srcAccess = 0;
VkPipelineStageFlags srcStage = 0;
if (getBinding() == VulkanBufferBinding::UNIFORM) {
@@ -62,7 +62,7 @@ uint32_t appendBindings(VkDescriptorSetLayoutBinding* toBind, VkDescriptorType t
uint32_t appendSamplerBindings(VkDescriptorSetLayoutBinding* toBind,
fvkutils::SamplerBitmask const& mask, fvkutils::SamplerBitmask const& external,
utils::FixedCapacityVector<std::pair<uint64_t, VkSampler>> const& immutableSamplers) {
utils::FixedCapacityVector<VkSampler> const& immutableSamplers) {
using Bitmask = fvkutils::SamplerBitmask;
uint32_t count = 0;
Bitmask alreadySeen;
@@ -92,7 +92,7 @@ uint32_t appendSamplerBindings(VkDescriptorSetLayoutBinding* toBind,
.descriptorCount = 1,
.stageFlags = stages,
.pImmutableSamplers = external[index] && immutableSamplerCount > immutableIndex
? &(immutableSamplers[immutableIndex++].second)
? &immutableSamplers[immutableIndex++]
: nullptr,
};
}
@@ -100,14 +100,14 @@ uint32_t appendSamplerBindings(VkDescriptorSetLayoutBinding* toBind,
return count;
}
uint64_t computeImmutableSamplerHash(
utils::FixedCapacityVector<std::pair<uint64_t, VkSampler>> const& samplers) {
uint64_t computeImmutableSamplerHash(utils::FixedCapacityVector<VkSampler> const& samplers) {
size_t const size = samplers.size();
if (size == 0) {
return 0;
} else if (size == 1) {
return (uint64_t) samplers[0];
}
//64bit + 64bit per element = 4 words
return utils::hash::murmur3((uint32_t*) samplers.data(), samplers.size() * 4, 0);
return utils::hash::murmur3((uint32_t*) samplers.data(), samplers.size() * 2, 0);
}
} // anonymous namespace
@@ -128,7 +128,7 @@ void VulkanDescriptorSetLayoutCache::terminate() noexcept {
VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
VulkanDescriptorSetLayout::Bitmask const& bitmasks,
fvkutils::SamplerBitmask externalSamplers,
utils::FixedCapacityVector<std::pair<uint64_t, VkSampler>> immutableSamplers) {
utils::FixedCapacityVector<VkSampler> immutableSamplers) {
LayoutKey key = {
.bitmask = bitmasks,
.immutableSamplerHash = computeImmutableSamplerHash(immutableSamplers),
@@ -47,7 +47,7 @@ public:
// This method is meant to be used with external samplers
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks,
fvkutils::SamplerBitmask externalSamplers,
utils::FixedCapacityVector<std::pair<uint64_t, VkSampler>> immutableSamplers = {});
utils::FixedCapacityVector<VkSampler> immutableSamplers = {});
private:
VkDevice mDevice;
+1 -2
View File
@@ -882,8 +882,7 @@ void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program, utils
// formats. It seems to be enough, in practicce, to simply run through a list of the types of
// samplers that *might* appear. As long as the real pipeline is close enough to something that
// the driver has seen before, we are able to get a cache hit.
utils::FixedCapacityVector<std::pair<uint64_t, VkSampler>> externalSamplers(
layouts[i]->bitmask.externalSampler.count(), { 0, externalSampler });
utils::FixedCapacityVector<VkSampler> externalSamplers (layouts[i]->bitmask.externalSampler.count(), externalSampler);
vkLayouts[i] = mDescriptorSetLayoutCache.getVkLayout(
layouts[i]->bitmask, layouts[i]->bitmask.externalSampler, externalSamplers);
}
@@ -142,10 +142,10 @@ void VulkanExternalImageManager::updateSetAndLayout(
return std::get<0>(a) < std::get<0>(b);
});
utils::FixedCapacityVector<std::pair<uint64_t,VkSampler>> outSamplers;
utils::FixedCapacityVector<VkSampler> outSamplers;
outSamplers.reserve(MAX_SAMPLER_COUNT);
std::for_each(samplerAndBindings.begin(), samplerAndBindings.end(),
[&](auto const& b) { outSamplers.push_back({ static_cast<uint64_t>(std::get<0>(b)), std::get<1>(b) }); });
[&](auto const& b) { outSamplers.push_back(std::get<1>(b)); });
VkDescriptorSetLayout const newLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
actualExternalSamplers, outSamplers);
@@ -81,8 +81,6 @@ struct Resource {
return getTypeEnum<D>() == restype;
}
uint32_t getCount() const { return mCount; }
private:
inline void inc() noexcept {
mCount++;
+20 -25
View File
@@ -1472,6 +1472,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
const auto srcTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
assert_invariant(srcTarget);
uint32_t srcMipLevel = 0;
wgpu::Texture srcTexture{ nullptr };
if (srcTarget->isDefaultRenderTarget()) {
assert_invariant(mSwapChain);
@@ -1482,6 +1483,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
// TODO we are currently assuming the first attachment is the desired texture.
if (colorAttachmentInfos[0].handle) {
auto texture = handleCast<WebGPUTexture>(colorAttachmentInfos[0].handle);
srcMipLevel = colorAttachmentInfos[0].level;
if (texture) {
srcTexture = texture->getTexture();
}
@@ -1494,15 +1496,20 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
return;
}
const uint32_t srcWidth {srcTexture.GetWidth()};
const uint32_t srcHeight{srcTexture.GetHeight()};
const uint32_t srcHeight {srcTexture.GetHeight()};
const uint32_t srcMipLevelCount {srcTexture.GetMipLevelCount()};
constexpr uint32_t minValidTextureSize = 1;
const uint32_t mipLeveledSrcWidth = std::max(minValidTextureSize, srcWidth >> srcMipLevel);
const uint32_t mipLeveledSrcHeight = std::max(minValidTextureSize, srcHeight >> srcMipLevel);
// Clamp read region to texture bounds
if (UTILS_UNLIKELY(x >= srcWidth || y >= srcHeight)) {
if (UTILS_UNLIKELY(x >= mipLeveledSrcWidth || y >= mipLeveledSrcHeight)) {
scheduleDestroy(std::move(pixelBufferDescriptor));
return;
}
auto actualWidth{ std::min(width, srcWidth - x) };
auto actualHeight{ std::min(height, srcHeight - y)};
auto actualWidth{ std::min(width, mipLeveledSrcWidth - x) };
auto actualHeight{ std::min(height, mipLeveledSrcHeight - y)};
if (UTILS_UNLIKELY(actualWidth == 0 || actualHeight == 0)) {
scheduleDestroy(std::move(pixelBufferDescriptor));
return;
@@ -1526,6 +1533,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
// If the source format is different from the destination (e.g. BGRA vs RGBA),
// we need to perform a conversion using an intermediate blit.
if (conversionNecessary(srcFormat, dstFormat, pixelBufferDescriptor.type)) {
// TODO: check if the blit process here is correct when mipmap level > 0
const wgpu::TextureDescriptor stagingDescriptor{
.label = "readpixels_staging_texture",
.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment,
@@ -1536,7 +1544,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
.depthOrArrayLayers = 1,
},
.format = dstFormat,
.mipLevelCount = 1,
.mipLevelCount = srcMipLevelCount,
.sampleCount = srcTexture.GetSampleCount(),
};
stagingTexture = mDevice.CreateTexture(&stagingDescriptor);
@@ -1586,12 +1594,11 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
// WebGPU's texture coordinates for copies are top-left, but Filament's y-coordinate is
// bottom-left. We must flip the y-coordinate relative to the texture we are reading from.
const uint32_t textureHeight{ textureToReadFrom.GetHeight() };
const uint32_t flippedY{ textureHeight - readY - actualHeight };
const uint32_t flippedY{ mipLeveledSrcHeight - readY - actualHeight };
const wgpu::TexelCopyTextureInfo source{
.texture = textureToReadFrom, // Read from the original or intermediate texture
.mipLevel = 0,
.mipLevel = srcMipLevel,
.origin = {.x = readX, .y = flippedY, .z = 0,},
};
const wgpu::TexelCopyBufferInfo destination{
@@ -2151,35 +2158,23 @@ wgpu::AddressMode WebGPUDriver::fWrapModeToWAddressMode(const SamplerWrapMode& f
}
MemoryMappedBufferHandle WebGPUDriver::mapBufferS() noexcept {
return allocHandle<WebGPUMemoryMappedBuffer>();
// TODO: MetalDriver::mapBufferS
return {};
}
void WebGPUDriver::mapBufferR(MemoryMappedBufferHandle mmbh,
BufferObjectHandle boh, size_t offset,
size_t size, MapBufferAccessFlags access, utils::ImmutableCString&& tag) {
constructHandle<WebGPUMemoryMappedBuffer>(mmbh, boh, offset, size, access);
setDebugTag(mmbh.getId(), std::move(tag));
// TODO: MetalDriver::mapBufferR
}
void WebGPUDriver::unmapBuffer(MemoryMappedBufferHandle mmbh) {
destructHandle<WebGPUMemoryMappedBuffer>(mmbh);
// TODO: MetalDriver::unmapBuffer
}
void WebGPUDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t offset,
BufferDescriptor&& data) {
auto mmb = handleCast<WebGPUMemoryMappedBuffer>(mmbh);
assert_invariant(mmb);
assert_invariant(offset + data.size <= mmb->size);
assert_invariant(any(mmb->access & MapBufferAccessFlags::WRITE_BIT));
const size_t absoluteOffset = mmb->offset + offset;
auto bo = handleCast<WebGPUBufferObject>(mmb->bufferObject);
assert_invariant(bo);
// TODO: this is a zero-effort implementation of copyToMemoryMappedBuffer(), where we just
// call updateBufferObject().
bo->updateGPUBuffer(data, absoluteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(data));
// TODO: MetalDriver::copyToMemoryMappedBuffer
}
void WebGPUDriver::queueCommandAsyncR(AsyncCallId jobId, utils::Invocable<void()>&& command,
+1 -2
View File
@@ -20,12 +20,11 @@
#include "WebGPURenderTarget.h"
#include "webgpu/WebGPUBlitter.h"
#include "webgpu/WebGPUConstants.h"
#include "webgpu/WebGPUMemoryMappedBuffer.h"
#include "webgpu/WebGPUMsaaTextureResolver.h"
#include "webgpu/WebGPUPipelineCache.h"
#include "webgpu/WebGPUPipelineLayoutCache.h"
#include "webgpu/WebGPUQueueManager.h"
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
#include "webgpu/WebGPUQueueManager.h"
#include "webgpu/WebGPUStagePool.h"
#include "webgpu/utils/AsyncTaskCounter.h"
#include <backend/platforms/WebGPUPlatform.h>
@@ -1,26 +0,0 @@
/*
* Copyright (C) 2026 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 "WebGPUMemoryMappedBuffer.h"
namespace filament::backend {
WebGPUMemoryMappedBuffer::WebGPUMemoryMappedBuffer(BufferObjectHandle bo, size_t offset, size_t size,
MapBufferAccessFlags access) noexcept
: bufferObject(bo), offset(offset), size(size), access(access) {
}
} // namespace filament::backend
@@ -1,39 +0,0 @@
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_WEBGPU_MEMORYMAPPEDBUFFER_H
#define TNT_FILAMENT_BACKEND_WEBGPU_MEMORYMAPPEDBUFFER_H
#include "DriverBase.h"
#include <backend/DriverEnums.h>
namespace filament::backend {
struct WebGPUMemoryMappedBuffer : public HwMemoryMappedBuffer {
WebGPUMemoryMappedBuffer(BufferObjectHandle bo, size_t offset, size_t size,
MapBufferAccessFlags access) noexcept;
BufferObjectHandle bufferObject;
size_t offset;
size_t size;
MapBufferAccessFlags access;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPU_MEMORYMAPPEDBUFFER_H
+4 -2
View File
@@ -83,8 +83,10 @@ TEST_F(BackendTest, ScissorViewportRegion) {
Handle<HwTexture> srcTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D,
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
Handle<HwTexture> depthTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::DEPTH16, 1, 512, 512, 1, TextureUsage::DEPTH_ATTACHMENT));
Handle<HwTexture> depthTexture =
addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::DEPTH16, 1,
512, 512, 1, TextureUsage::DEPTH_ATTACHMENT | TextureUsage::SAMPLEABLE));
// Render into the bottom-left quarter of the texture.
Viewport srcRect = {
-48
View File
@@ -72,58 +72,10 @@ AtlasAllocator::Allocation AtlasAllocator::allocate(size_t const textureSize) no
result.viewport.width = dimension;
result.viewport.height = dimension;
result.layer = loc.code >> (2 * layer);
result.code = loc.code;
result.level = loc.l;
}
return result;
}
void AtlasAllocator::free(Allocation const& allocation) noexcept {
if (UTILS_UNLIKELY(!allocation.isValid())) {
return;
}
// 1. Mark the node as unallocated
const size_t i = QuadTreeUtils::index(allocation.level, allocation.code);
Node& node = mQuadTree[i];
// In debug builds, ensure we are freeing something that was actually allocated
assert_invariant(node.isAllocated());
assert_invariant(!node.hasChildren());
node.allocated = false;
// 2. Coalesce up the tree
// We walk up to the root. If a parent's children count drops to 0, it means all its children are free.
// However, we don't mark the parent as 'allocated' (that would mean it's occupied by a larger block).
// We just ensure the parent knows it has one less child.
// The 'allocated' flag is only true if a specific block is claimed.
// The 'children' count tracks how many sub-blocks are claimed or split.
int8_t currentL = allocation.level;
uint16_t currentCode = allocation.code;
while (currentL > 0) {
const size_t p = QuadTreeUtils::parent(currentL, currentCode);
Node& parent = mQuadTree[p];
// The parent must have children (since we just came from one)
assert_invariant(parent.hasChildren());
parent.children--;
// If the parent still has children, we stop coalescing because this branch is still partially in use.
if (parent.hasChildren()) {
break;
}
// If the parent has no more children, it is now a candidate for being allocated as a larger block later.
// We continue up the tree to update its parent.
currentL--;
currentCode >>= 2;
}
}
void AtlasAllocator::clear(size_t const maxTextureSize) noexcept {
std::fill(mQuadTree.begin(), mQuadTree.end(), Node{});
mMaxTextureSizePot = (sizeof(maxTextureSize) * 8 - 1u) - clz(maxTextureSize);
+12 -53
View File
@@ -34,18 +34,10 @@ class AtlasAllocator_AllocateMixed2_Test;
namespace filament {
/**
* A 2D allocator based on a QuadTree (Buddy Allocator).
*
* Allocations must be square and have a power-of-two size.
/*
* A 2D allocator. Allocations must be square and have a power-of-two size.
* AtlasAllocator hard codes a depth of 4, that is only 4 allocation sizes are permitted.
* This doesn't actually allocate memory, just manages space within an abstract 2D image.
*
* The allocator supports both allocation and deallocation.
* - Allocation uses a "best-fit" strategy, preferring to split nodes that already have children
* to preserve large contiguous blocks.
* - Deallocation (free) automatically coalesces empty sibling nodes back into larger parent blocks
* (standard Buddy Allocator behavior).
*/
class AtlasAllocator {
@@ -81,58 +73,25 @@ class AtlasAllocator {
using NodeId = QuadTree::NodeId;
public:
/**
* Create allocator and specify the maximum texture size.
*
* @param maxTextureSize The maximum size of the texture atlas. Must be a power of two.
* Allocations size allowed are the four power-of-two smaller or equal
* to this size.
/*
* Create allocator and specify the maximum texture size. Must be a power of two.
* Allocations size allowed are the four power-of-two smaller or equal to this size.
*/
explicit AtlasAllocator(size_t maxTextureSize) noexcept;
/**
* Represents a successful allocation within the atlas.
/*
* Allocates a square of size `textureSize`. Must be one of the power-of-two allowed
* (see above).
* Returns the location of the allocation within the maxTextureSize^2 square.
*/
struct Allocation {
/** The viewport of the allocation within the atlas layer. */
int32_t layer = -1;
Viewport viewport;
/** The layer index of the allocation (for texture arrays). 0xffff if invalid. */
uint16_t layer = 0xffff;
/** Internal Morton code used for deallocation. */
uint16_t code = 0;
/** Internal QuadTree level used for deallocation. */
int8_t level = 0;
/** Returns true if the allocation is valid. */
bool isValid() const noexcept { return layer != 0xffff; }
};
/**
* Allocates a square region of the specified size.
*
* @param textureSize The size of the square to allocate. Must be a power of two.
* If the size is not a power of two, it will be rounded down to the
* nearest power of two.
* @return An Allocation struct containing the viewport and layer of the allocated region.
* If the allocation fails (e.g. no space left or size too small/large),
* an invalid Allocation is returned (isValid() returns false).
*/
Allocation allocate(size_t textureSize) noexcept;
/**
* Frees an allocation returned by allocate().
*
* This method marks the region as free and attempts to coalesce empty sibling nodes
* into larger parent blocks, making them available for future large allocations.
*
* @param allocation The allocation to free. If the allocation is invalid, this method does nothing.
*/
void free(Allocation const& allocation) noexcept;
/**
* Frees all allocations and resets the allocator with a new maximum texture size.
*
* @param maxTextureSize The new maximum size of the texture atlas. Defaults to 1024.
* Must be a power of two.
/*
* Frees all allocations and reset the maximum texture size.
*/
void clear(size_t maxTextureSize = 1024) noexcept;
+10 -11
View File
@@ -26,8 +26,8 @@
namespace filament {
size_t MaterialCache::MaterialKey::Hash::operator()(
filament::MaterialCache::MaterialKey const& key) const noexcept {
size_t MaterialCache::Key::Hash::operator()(
filament::MaterialCache::Key const& key) const noexcept {
uint32_t crc;
if (key.parser->getMaterialCrc32(&crc)) {
return size_t(crc);
@@ -35,17 +35,17 @@ size_t MaterialCache::MaterialKey::Hash::operator()(
return size_t(key.parser->computeCrc32());
}
bool MaterialCache::MaterialKey::operator==(MaterialKey const& rhs) const noexcept {
bool MaterialCache::Key::operator==(Key const& rhs) const noexcept {
return parser == rhs.parser;
}
MaterialCache::~MaterialCache() {
assert_invariant(mDefinitions.empty());
assert_invariant(mPrograms.empty());
assert_invariant(mSpecializationConstantsInternPool.empty());
if (!mDefinitions.empty()) {
LOG(WARNING) << "MaterialCache was destroyed but wasn't empty";
}
}
MaterialDefinition* UTILS_NULLABLE MaterialCache::acquireMaterial(FEngine& engine,
MaterialDefinition* UTILS_NULLABLE MaterialCache::acquire(FEngine& engine,
const void* UTILS_NONNULL data, size_t size) noexcept {
std::unique_ptr<MaterialParser> parser = MaterialDefinition::createParser(engine.getBackend(),
engine.getShaderLanguage(), data, size);
@@ -56,16 +56,15 @@ MaterialDefinition* UTILS_NULLABLE MaterialCache::acquireMaterial(FEngine& engin
// object is unsafe. The validity of the generated key is guaranteed because the
// MaterialDefinition object (which owns the same parser object) created within the lambda is
// subsequently used as the associated value in the map.
const MaterialKey key{ parser.get() };
const Key key{ parser.get() };
return mDefinitions.acquire(key, [&engine, parser = std::move(parser)]() mutable {
return MaterialDefinition::create(engine, std::move(parser));
});
}
void MaterialCache::releaseMaterial(FEngine& engine,
MaterialDefinition const& definition) noexcept {
mDefinitions.release(MaterialKey{ &definition.getMaterialParser() },
void MaterialCache::release(FEngine& engine, MaterialDefinition const& definition) noexcept {
mDefinitions.release(Key{ &definition.getMaterialParser() },
[&engine](MaterialDefinition& definition) {
definition.terminate(engine);
});
+7 -35
View File
@@ -17,7 +17,6 @@
#define TNT_FILAMENT_MATERIALCACHE_H
#include "MaterialDefinition.h"
#include "ProgramSpecialization.h"
#include <private/filament/Variant.h>
@@ -26,8 +25,6 @@
#include <backend/Handle.h>
#include <backend/Program.h>
#include <utils/FixedCapacityVector.h>
#include <utils/InternPool.h>
#include <utils/Invocable.h>
#include <utils/RefCountedMap.h>
@@ -38,54 +35,29 @@ class Material;
class MaterialCache {
// A newtype around a material parser used as a key for the material cache. The material file's
// CRC32 is used as the hash function.
//
// See mDefinitions.
struct MaterialKey {
struct Key {
struct Hash {
size_t operator()(MaterialKey const& x) const noexcept;
size_t operator()(Key const& x) const noexcept;
};
bool operator==(MaterialKey const& rhs) const noexcept;
bool operator==(Key const& rhs) const noexcept;
MaterialParser const* UTILS_NONNULL parser;
};
public:
using SpecializationConstantInternPool =
utils::InternPool<backend::Program::SpecializationConstant>;
using ProgramCache =
utils::RefCountedMap<ProgramSpecialization, backend::Handle<backend::HwProgram>>;
~MaterialCache();
SpecializationConstantInternPool& getSpecializationConstantsInternPool() {
return mSpecializationConstantsInternPool;
}
ProgramCache& getProgramCache() {
return mPrograms;
}
// Acquire or create a new entry in the cache for the given material data.
MaterialDefinition* UTILS_NULLABLE acquireMaterial(FEngine& engine, const void* UTILS_NONNULL data,
MaterialDefinition* UTILS_NULLABLE acquire(FEngine& engine, const void* UTILS_NONNULL data,
size_t size) noexcept;
// Release an entry in the cache, potentially freeing its GPU resources.
void releaseMaterial(FEngine& engine, MaterialDefinition const& definition) noexcept;
void release(FEngine& engine, MaterialDefinition const& definition) noexcept;
private:
// TODO: investigate using custom allocators for the below data structures?
// We use unique_ptr here because we need these pointers to be stable.
//
// Each MaterialKey points to its value's MaterialParser. We take care to not expose this
// lifetime concern to the clients of MaterialCache.
utils::RefCountedMap<MaterialKey, std::unique_ptr<MaterialDefinition>, MaterialKey::Hash>
mDefinitions;
utils::RefCountedMap<ProgramSpecialization, backend::Handle<backend::HwProgram>> mPrograms;
utils::InternPool<backend::Program::SpecializationConstant> mSpecializationConstantsInternPool;
// TODO: investigate using a custom allocator here?
utils::RefCountedMap<Key, std::unique_ptr<MaterialDefinition>, Key::Hash> mDefinitions;
};
} // namespace filament
+11 -372
View File
@@ -18,16 +18,25 @@
#include "Froxelizer.h"
#include "MaterialParser.h"
#include "filament/MaterialEnums.h"
#include <ds/ColorPassDescriptorSet.h>
#include <details/Engine.h>
#include <private/filament/EngineEnums.h>
#include <private/filament/DescriptorSets.h>
#include <private/filament/PushConstantInfo.h>
#include <utils/Hash.h>
#include <filament/MaterialEnums.h>
#include <backend/DriverEnums.h>
#include <string_view>
#include <utils/CString.h>
#include <utils/bitset.h>
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <utils/compiler.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
@@ -45,123 +54,6 @@ namespace filament {
using namespace backend;
using namespace utils;
namespace {
template<bool useCache>
void acquireProgramsImpl(FEngine& engine, utils::Slice<Handle<HwProgram>> programCache,
MaterialDefinition const& definition, MaterialParser const& parser,
utils::Slice<const backend::Program::SpecializationConstant> specializationConstants,
bool isDefaultMaterial) {
MaterialCache::ProgramCache& globalProgramCache = engine.getMaterialCache().getProgramCache();
ShaderModel const shaderModel = engine.getShaderModel();
bool const isStereoSupported = engine.getDriverApi().isStereoSupported();
ProgramSpecialization specialization = {
.materialCrc32 = definition.getMaterialParser().getCrc32(),
.specializationConstants = specializationConstants,
};
// We acquire an entry for all variants in the program cache, but we don't compile them.
// Programs that are acquired but aren't compiled simply hold onto an empty entry in the program
// cache which is initialized later.
if constexpr (useCache) {
for (auto variant: definition.getVariants()) {
if (UTILS_LIKELY(definition.hasVariant(variant, shaderModel, isStereoSupported))) {
specialization.variant = variant;
Handle<HwProgram> const* program = globalProgramCache.acquire(specialization);
if (program) {
programCache[variant.key] = *program;
}
}
}
}
if (UTILS_UNLIKELY(isDefaultMaterial && !engine.getDriverApi().isWorkaroundNeeded(
Workaround::DISABLE_DEPTH_PRECACHE_FOR_DEFAULT_MATERIAL))) {
// Precache depth programs.
for (auto variant: definition.getDepthVariants()) {
if (UTILS_LIKELY(definition.hasVariant(variant, shaderModel, isStereoSupported))) {
specialization.variant = variant;
if constexpr (useCache) {
Handle<HwProgram> const* program = globalProgramCache.acquire(specialization,
[&engine, &definition, &parser, &specialization]() {
return definition.compileProgram(engine, parser, specialization,
CompilerPriorityQueue::HIGH);
});
if (program) {
programCache[variant.key] = *program;
}
} else {
programCache[variant.key] = definition.compileProgram(engine, parser,
specialization, CompilerPriorityQueue::HIGH);
}
}
}
} else if constexpr (useCache) {
// Don't precache depth programs, but acquire them.
for (auto variant: definition.getDepthVariants()) {
if (UTILS_LIKELY(definition.hasVariant(variant, shaderModel, isStereoSupported))) {
specialization.variant = variant;
Handle<HwProgram> const* program = globalProgramCache.acquire(specialization);
if (program) {
programCache[variant.key] = *program;
}
}
}
}
}
template<bool useCache>
void releaseProgramsImpl(FEngine& engine, utils::Slice<Handle<HwProgram>> programCache,
MaterialDefinition const& definition,
utils::Slice<const backend::Program::SpecializationConstant> specializationConstants,
bool isDefaultMaterial) {
MaterialCache::ProgramCache& globalProgramCache = engine.getMaterialCache().getProgramCache();
ShaderModel const shaderModel = engine.getShaderModel();
bool const isStereoSupported = engine.getDriverApi().isStereoSupported();
ProgramSpecialization specialization = {
.materialCrc32 = definition.getMaterialParser().getCrc32(),
.specializationConstants = specializationConstants,
};
for (auto variant : definition.getVariants()) {
if (UTILS_LIKELY(definition.hasVariant(variant, shaderModel, isStereoSupported))) {
Handle<HwProgram>& program = programCache[variant.key];
if constexpr (useCache) {
specialization.variant = variant;
globalProgramCache.release(specialization, [&engine](Handle<HwProgram> p) {
engine.getDriverApi().destroyProgram(p);
});
} else if (program) {
engine.getDriverApi().destroyProgram(program);
}
program.clear();
}
}
// Only destroy the "shared variants" if this is the default material (i.e. the programs in
// question that are shared) or if this material has custom depth shaders.
const bool destroySharedVariants = isDefaultMaterial || definition.hasCustomDepthShader;
for (auto variant: definition.getDepthVariants()) {
if (UTILS_LIKELY(definition.hasVariant(variant, shaderModel, isStereoSupported))) {
Handle<HwProgram>& program = programCache[variant.key];
if constexpr (useCache) {
specialization.variant = variant;
globalProgramCache.release(specialization, [&engine](Handle<HwProgram> p) {
engine.getDriverApi().destroyProgram(p);
});
} else if (destroySharedVariants && program) {
engine.getDriverApi().destroyProgram(program);
}
program.clear();
}
}
}
} // namespace
std::unique_ptr<MaterialParser> MaterialDefinition::createParser(Backend const backend,
FixedCapacityVector<ShaderLanguage> languages, const void* data, size_t size) {
// unique_ptr so we don't leak MaterialParser on failures below
@@ -645,257 +537,4 @@ void MaterialDefinition::processDescriptorSets(FEngine& engine) {
this->perViewDescriptorSetLayoutVsmDescription };
}
backend::DescriptorSetLayout const& MaterialDefinition::getPerViewDescriptorSetLayoutDescription(
Variant const variant, bool const useVsmDescriptorSetLayout) const noexcept {
if (materialDomain == MaterialDomain::SURFACE) {
if (Variant::isValidDepthVariant(variant)) {
// Use the layout description used to create the per view depth variant layout.
return descriptor_sets::getDepthVariantLayout();
}
if (Variant::isSSRVariant(variant)) {
// Use the layout description used to create the per view SSR variant layout.
return descriptor_sets::getSsrVariantLayout();
}
}
if (useVsmDescriptorSetLayout) {
return perViewDescriptorSetLayoutVsmDescription;
}
return perViewDescriptorSetLayoutDescription;
}
Handle<HwProgram> MaterialDefinition::compileProgram(
FEngine& engine, MaterialParser const& parser,
ProgramSpecialization const& specialization,
backend::CompilerPriorityQueue const priorityQueue) const noexcept {
assert_invariant(engine.hasFeatureLevel(featureLevel));
Program pb;
switch (materialDomain) {
case MaterialDomain::SURFACE:
pb = getSurfaceProgram(engine, parser, specialization);
break;
case MaterialDomain::POST_PROCESS:
pb = getProgramWithVariants(engine, parser, specialization, specialization.variant,
specialization.variant);
break;
case MaterialDomain::COMPUTE:
// TODO: implement MaterialDomain::COMPUTE
PANIC_PRECONDITION("Compute shaders not yet supported");
}
pb.priorityQueue(priorityQueue);
// Set descriptor sets for the program.
// Note: right now, we're going to assume VSM is disabled. In the future, we
// may want to provide both to the backend, so that both can be built.
pb.descriptorLayout(+DescriptorSetBindingPoints::PER_VIEW,
getPerViewDescriptorSetLayoutDescription(
specialization.variant,
Variant::isVSMVariant(specialization.variant)));
pb.descriptorLayout(+DescriptorSetBindingPoints::PER_RENDERABLE,
descriptor_sets::getPerRenderableLayout());
pb.descriptorLayout(
+DescriptorSetBindingPoints::PER_MATERIAL, descriptorSetLayoutDescription);
auto const program = engine.getDriverApi().createProgram(
std::move(pb), ImmutableCString{name.c_str_safe()});
assert_invariant(program);
return program;
}
Program MaterialDefinition::getSurfaceProgram(FEngine& engine, MaterialParser const& parser,
ProgramSpecialization const& specialization) 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
assert_invariant(specialization.variant ==
Variant::filterVariant(specialization.variant, isVariantLit));
assert_invariant(!Variant::isReserved(specialization.variant));
Variant const vertexVariant = Variant::filterVariantVertex(specialization.variant);
Variant const fragmentVariant = Variant::filterVariantFragment(specialization.variant);
Program pb = getProgramWithVariants(engine, parser, specialization, vertexVariant, fragmentVariant);
pb.multiview(
engine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW &&
Variant::isStereoVariant(specialization.variant));
return pb;
}
Program MaterialDefinition::getProgramWithVariants(FEngine const& engine,
MaterialParser const& parser,
ProgramSpecialization const& specialization,
Variant vertexVariant,
Variant fragmentVariant) const {
const ShaderModel sm = engine.getShaderModel();
const bool isNoop = engine.getBackend() == Backend::NOOP;
const Variant variant = specialization.variant;
/*
* Vertex shader
*/
filaflat::ShaderContent& vsBuilder = engine.getVertexShaderContent();
UTILS_UNUSED_IN_RELEASE bool const vsOK = parser.getShader(vsBuilder, sm,
vertexVariant, ShaderStage::VERTEX);
FILAMENT_CHECK_POSTCONDITION(isNoop || (vsOK && !vsBuilder.empty()))
<< "The material '" << name.c_str()
<< "' has not been compiled to include the required GLSL or SPIR-V chunks for the "
"vertex shader (variant="
<< +variant.key << ", filtered=" << +vertexVariant.key << ").";
/*
* Fragment shader
*/
filaflat::ShaderContent& fsBuilder = engine.getFragmentShaderContent();
UTILS_UNUSED_IN_RELEASE bool const fsOK = parser.getShader(fsBuilder, sm,
fragmentVariant, ShaderStage::FRAGMENT);
FILAMENT_CHECK_POSTCONDITION(isNoop || (fsOK && !fsBuilder.empty()))
<< "The material '" << name.c_str()
<< "' has not been compiled to include the required GLSL or SPIR-V chunks for the "
"fragment shader (variant="
<< +variant.key << ", filtered=" << +fragmentVariant.key << ").";
Program program;
program.shader(ShaderStage::VERTEX, vsBuilder.data(), vsBuilder.size())
.shader(ShaderStage::FRAGMENT, fsBuilder.data(), fsBuilder.size())
.shaderLanguage(parser.getShaderLanguage())
.diagnostics(name,
[variant, vertexVariant, fragmentVariant](utils::CString const& name,
io::ostream& out) -> io::ostream& {
return out << name.c_str_safe() << ", variant=(" << io::hex << +variant.key
<< io::dec << "), vertexVariant=(" << io::hex
<< +vertexVariant.key << io::dec << "), fragmentVariant=("
<< io::hex << +fragmentVariant.key << io::dec << ")";
});
if (UTILS_UNLIKELY(parser.getShaderLanguage() == ShaderLanguage::ESSL1)) {
assert_invariant(!bindingUniformInfo.empty());
for (auto const& [index, name, uniforms] : bindingUniformInfo) {
program.uniforms(uint32_t(index), name, uniforms);
}
program.attributes(attributeInfo);
}
program.descriptorBindings(+DescriptorSetBindingPoints::PER_VIEW,
programDescriptorBindings[+DescriptorSetBindingPoints::PER_VIEW]);
program.descriptorBindings(+DescriptorSetBindingPoints::PER_RENDERABLE,
programDescriptorBindings[+DescriptorSetBindingPoints::PER_RENDERABLE]);
program.descriptorBindings(+DescriptorSetBindingPoints::PER_MATERIAL,
programDescriptorBindings[+DescriptorSetBindingPoints::PER_MATERIAL]);
program.specializationConstants(
utils::FixedCapacityVector(specialization.specializationConstants));
program.pushConstants(ShaderStage::VERTEX, pushConstants[uint8_t(ShaderStage::VERTEX)]);
program.pushConstants(ShaderStage::FRAGMENT, pushConstants[uint8_t(ShaderStage::FRAGMENT)]);
// TODO(exv): we'll probably eventually want to replace this with the hash of the
// specialization, but there may be clients which depend on this value being stable across
// versions.
program.cacheId(hash::combine(size_t(cacheId), variant.key));
return program;
}
Handle<HwProgram> MaterialDefinition::prepareProgram(FEngine& engine, DriverApi& driver,
MaterialParser const& parser, ProgramSpecialization const& specialization,
backend::CompilerPriorityQueue priorityQueue) const {
if (!hasVariant(specialization.variant, engine.getShaderModel(), driver.isStereoSupported())) {
return {};
}
if (UTILS_LIKELY(engine.features.engine.enable_program_cache && parser == *mMaterialParser)) {
Handle<HwProgram>* program = engine.getMaterialCache().getProgramCache().get(specialization,
[this, &engine, &parser, &specialization, priorityQueue]() {
return compileProgram(engine, parser, specialization, priorityQueue);
});
assert_invariant(*program);
return *program;
} else {
return compileProgram(engine, parser, specialization, priorityQueue);
}
}
void MaterialDefinition::acquirePrograms(FEngine& engine,
utils::Slice<Handle<HwProgram>> programCache,
MaterialParser const& parser,
utils::Slice<const backend::Program::SpecializationConstant> specializationConstants,
bool isDefaultMaterial) const {
if (UTILS_LIKELY(engine.features.engine.enable_program_cache && parser == *mMaterialParser)) {
acquireProgramsImpl<true>(engine, programCache, *this, parser, specializationConstants,
isDefaultMaterial);
} else {
acquireProgramsImpl<false>(engine, programCache, *this, parser, specializationConstants,
isDefaultMaterial);
}
}
void MaterialDefinition::releasePrograms(FEngine& engine,
utils::Slice<Handle<HwProgram>> programCache, MaterialParser const& parser,
utils::Slice<const backend::Program::SpecializationConstant> specializationConstants,
bool isDefaultMaterial) const {
if (UTILS_LIKELY(engine.features.engine.enable_program_cache && parser == *mMaterialParser)) {
releaseProgramsImpl<true>(engine, programCache, *this, specializationConstants,
isDefaultMaterial);
} else {
releaseProgramsImpl<false>(engine, programCache, *this, specializationConstants,
isDefaultMaterial);
}
}
bool MaterialDefinition::hasVariant(Variant const variant,
ShaderModel const sm, bool isStereoSupported) const noexcept {
if (!isStereoSupported && Variant::isStereoVariant(variant)) {
return false;
}
Variant vertexVariant, fragmentVariant;
switch (materialDomain) {
case MaterialDomain::SURFACE:
vertexVariant = Variant::filterVariantVertex(variant);
fragmentVariant = Variant::filterVariantFragment(variant);
break;
case MaterialDomain::POST_PROCESS:
vertexVariant = fragmentVariant = variant;
break;
case MaterialDomain::COMPUTE:
// TODO: implement MaterialDomain::COMPUTE
return false;
}
if (!mMaterialParser->hasShader(sm, vertexVariant, ShaderStage::VERTEX)) {
return false;
}
if (!mMaterialParser->hasShader(sm, fragmentVariant, ShaderStage::FRAGMENT)) {
return false;
}
return true;
}
utils::Slice<const Variant> MaterialDefinition::getVariants() const noexcept {
switch (materialDomain) {
case MaterialDomain::SURFACE:
return isVariantLit ? VariantUtils::getLitVariants()
: VariantUtils::getUnlitVariants();
case MaterialDomain::POST_PROCESS:
return VariantUtils::getPostProcessVariants();
case MaterialDomain::COMPUTE:
// TODO: implement MaterialDomain::COMPUTE
PANIC_PRECONDITION("Compute shaders not yet supported");
}
}
utils::Slice<const Variant> MaterialDefinition::getDepthVariants() const noexcept {
switch (materialDomain) {
case MaterialDomain::SURFACE:
return VariantUtils::getDepthVariants();
case MaterialDomain::POST_PROCESS:
return {};
case MaterialDomain::COMPUTE:
// TODO: implement MaterialDomain::COMPUTE
PANIC_PRECONDITION("Compute shaders not yet supported");
}
}
} // namespace filament
-46
View File
@@ -16,9 +16,6 @@
#ifndef TNT_FILAMENT_MATERIALDEFINITION_H
#define TNT_FILAMENT_MATERIALDEFINITION_H
#include "ProgramSpecialization.h"
#include "backend/DriverApiForward.h"
#include <private/filament/Variant.h>
#include <private/filament/BufferInterfaceBlock.h>
#include <private/filament/SamplerInterfaceBlock.h>
@@ -58,44 +55,8 @@ struct MaterialDefinition {
// Free GPU resources owned by this MaterialDefinition.
void terminate(FEngine& engine);
// Creates the program for the material's given variant at the backend level. This function
// unconditionally compiles the program, ignoring the cache.
backend::Handle<backend::HwProgram> compileProgram(FEngine& engine,
MaterialParser const& parser, ProgramSpecialization const& specialization,
backend::CompilerPriorityQueue priorityQueue) const noexcept;
// Returns a compiled program. It first consults MaterialCache, and only calls compileProgram()
// when there is a cache miss.
//
// Must be called outside of backend render pass.
// Must be called before Material::getProgram().
backend::Handle<backend::HwProgram> prepareProgram(FEngine& engine, backend::DriverApi& driver,
MaterialParser const& parser, ProgramSpecialization const& specialization,
backend::CompilerPriorityQueue priorityQueue) const;
void acquirePrograms(FEngine& engine,
utils::Slice<backend::Handle<backend::HwProgram>> programCache,
MaterialParser const& parser,
utils::Slice<const backend::Program::SpecializationConstant> specializationConstants,
bool isDefaultMaterial) const;
void releasePrograms(FEngine& engine,
utils::Slice<backend::Handle<backend::HwProgram>> programCache,
MaterialParser const& parser,
utils::Slice<const backend::Program::SpecializationConstant> specializationConstants,
bool isDefaultMaterial) const;
MaterialParser const& getMaterialParser() const noexcept { return *mMaterialParser; }
utils::Slice<const Variant> getVariants() const noexcept;
utils::Slice<const Variant> getDepthVariants() const noexcept;
bool hasVariant(Variant const variant,
backend::ShaderModel const sm, bool isStereoSupported) const noexcept;
backend::DescriptorSetLayout const& getPerViewDescriptorSetLayoutDescription(
Variant const variant, bool useVsmDescriptorSetLayout) const noexcept;
// Keep track of the definitions of the descriptor set layouts, as these
// may be used by some backends in parallel compilation of programs.
backend::DescriptorSetLayout perViewDescriptorSetLayoutDescription;
@@ -178,13 +139,6 @@ private:
void processPushConstants();
void processDescriptorSets(FEngine& engine);
backend::Program getSurfaceProgram(FEngine& engine, MaterialParser const& parser,
ProgramSpecialization const& specialization) const noexcept;
backend::Program getProgramWithVariants(FEngine const& engine, MaterialParser const& parser,
ProgramSpecialization const& specialization, Variant vertexVariant,
Variant fragmentVariant) const;
std::unique_ptr<MaterialParser> mMaterialParser;
};
+1 -9
View File
@@ -208,7 +208,7 @@ uint32_t MaterialParser::computeCrc32() const noexcept {
}
std::optional<uint32_t> MaterialParser::getPrecomputedCrc32() const noexcept {
uint32_t cachedCrc32 = mCrc32.load(std::memory_order_relaxed);
std::optional<uint32_t> cachedCrc32 = mCrc32.load(std::memory_order_relaxed);
if (cachedCrc32) {
return cachedCrc32;
}
@@ -219,14 +219,6 @@ std::optional<uint32_t> MaterialParser::getPrecomputedCrc32() const noexcept {
return std::nullopt;
}
uint32_t MaterialParser::getCrc32() const noexcept {
std::optional<uint32_t> crc32 = getPrecomputedCrc32();
if (crc32) {
return *crc32;
}
return computeCrc32();
}
ShaderLanguage MaterialParser::getShaderLanguage() const noexcept {
return mImpl.mChosenLanguage;
}
-2
View File
@@ -74,8 +74,6 @@ public:
uint32_t computeCrc32() const noexcept;
// Return the cached computed CRC32 or the CRC32 built into the material file if one exists.
std::optional<uint32_t> getPrecomputedCrc32() const noexcept;
// Return the CRC32 of the material.
uint32_t getCrc32() const noexcept;
backend::ShaderLanguage getShaderLanguage() const noexcept;
+44 -26
View File
@@ -126,6 +126,17 @@ constexpr float halton(unsigned int i, unsigned int const b) noexcept {
return r;
}
template <typename ValueType>
void setConstantParameter(FMaterial* const material, std::string_view const name,
ValueType value, bool& dirty) noexcept {
auto id = material->getSpecializationConstantId(name);
if (id.has_value()) {
if (material->setConstant(id.value(), value)) {
dirty = true;
}
}
}
} // anonymous
// ------------------------------------------------------------------------------------------------
@@ -316,7 +327,7 @@ void PostProcessManager::init() noexcept {
mFullScreenQuadVbih = engine.getFullScreenVertexBuffer()->getVertexBufferInfoHandle();
mPerRenderableDslh = engine.getPerRenderableDescriptorSetLayout().getHandle();
mDummyPerRenderableDsh = driver.createDescriptorSet(mPerRenderableDslh, "mDummyPerRenderableDsh");
mDummyPerRenderableDsh = driver.createDescriptorSet(mPerRenderableDslh);
driver.updateDescriptorSetBuffer(mDummyPerRenderableDsh,
+PerRenderableBindingPoints::OBJECT_UNIFORMS, engine.getDummyUniformBuffer(), 0,
@@ -996,12 +1007,13 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
auto& material = getPostProcessMaterial(materialName);
FMaterial* ma = material.getMaterial(mEngine, driver);
{
FMaterial::SpecializationConstantsBuilder maConstants =
ma->getSpecializationConstantsBuilder();
maConstants.set("useVisibilityBitmasks", options.gtao.useVisibilityBitmasks);
maConstants.set("linearThickness", options.gtao.linearThickness);
ma->setSpecializationConstants(std::move(maConstants));
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, driver);
@@ -2901,20 +2913,23 @@ void PostProcessManager::configureTemporalAntiAliasingMaterial(backend::DriverAp
TemporalAntiAliasingOptions const& taaOptions) noexcept {
FMaterial* const ma = getPostProcessMaterial("taa").getMaterial(mEngine, driver);
FMaterial::SpecializationConstantsBuilder maConstants = ma->getSpecializationConstantsBuilder();
bool dirty = false;
maConstants.set("upscaling", taaOptions.upscaling > 1.0f);
maConstants.set("historyReprojection", taaOptions.historyReprojection);
maConstants.set("filterHistory", taaOptions.filterHistory);
maConstants.set("filterInput", taaOptions.filterInput);
maConstants.set("useYCoCg", taaOptions.useYCoCg);
maConstants.set("hdr", taaOptions.hdr);
maConstants.set("preventFlickering", taaOptions.preventFlickering);
maConstants.set("boxType", int32_t(taaOptions.boxType));
maConstants.set("boxClipping", int32_t(taaOptions.boxClipping));
maConstants.set("varianceGamma", taaOptions.varianceGamma);
ma->setSpecializationConstants(std::move(maConstants));
setConstantParameter(ma, "upscaling", taaOptions.upscaling > 1.0f, dirty);
setConstantParameter(ma, "historyReprojection", taaOptions.historyReprojection, dirty);
setConstantParameter(ma, "filterHistory", taaOptions.filterHistory, dirty);
setConstantParameter(ma, "filterInput", taaOptions.filterInput, dirty);
setConstantParameter(ma, "useYCoCg", taaOptions.useYCoCg, dirty);
setConstantParameter(ma, "hdr", taaOptions.hdr, dirty);
setConstantParameter(ma, "preventFlickering", taaOptions.preventFlickering, dirty);
setConstantParameter(ma, "boxType", int32_t(taaOptions.boxType), dirty);
setConstantParameter(ma, "boxClipping", int32_t(taaOptions.boxClipping), dirty);
setConstantParameter(ma, "varianceGamma", taaOptions.varianceGamma, dirty);
if (dirty) {
ma->invalidate();
// TODO: call Material::compile(), we can't do that now because it works only
// with surface materials
}
}
FMaterialInstance* PostProcessManager::configureColorGradingMaterial(backend::DriverApi& driver,
@@ -2922,12 +2937,15 @@ FMaterialInstance* PostProcessManager::configureColorGradingMaterial(backend::Dr
ColorGradingConfig const& colorGradingConfig, VignetteOptions const& vignetteOptions,
uint32_t const width, uint32_t const height) noexcept {
FMaterial* ma = material.getMaterial(mEngine, driver);
{
FMaterial::SpecializationConstantsBuilder maConstants =
ma->getSpecializationConstantsBuilder();
maConstants.set("isOneDimensional", colorGrading->isOneDimensional());
maConstants.set("isLDR", colorGrading->isLDR());
ma->setSpecializationConstants(std::move(maConstants));
bool dirty = false;
setConstantParameter(ma, "isOneDimensional", colorGrading->isOneDimensional(), dirty);
setConstantParameter(ma, "isLDR", colorGrading->isLDR(), dirty);
if (dirty) {
ma->invalidate();
// TODO: call Material::compile(), we can't do that now because it works only
// with surface materials
}
PostProcessVariant const variant = colorGradingConfig.translucent
-38
View File
@@ -1,38 +0,0 @@
/*
* 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 "ProgramSpecialization.h"
#include <utils/Hash.h>
namespace filament {
size_t ProgramSpecialization::hash() const noexcept {
size_t seed = 0;
utils::hash::combine_fast(seed, materialCrc32);
utils::hash::combine_fast(seed, variant.key);
utils::hash::combine_fast(seed, specializationConstants.hash());
return seed;
}
bool ProgramSpecialization::operator==(ProgramSpecialization const& rhs) const noexcept {
if (this == &rhs) {
return true;
}
return materialCrc32 == rhs.materialCrc32 && variant == rhs.variant &&
specializationConstants == rhs.specializationConstants;
}
} // namespace filament
-60
View File
@@ -1,60 +0,0 @@
/*
* 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_PROGRAMSPECIALIZATION_H
#define TNT_FILAMENT_PROGRAMSPECIALIZATION_H
#include <backend/Program.h>
#include <private/filament/Variant.h>
#include <utils/FixedCapacityVector.h>
#include <cstdint>
namespace filament {
// A program specialization is a collection of all properties which could yield a different compiled
// program object.
struct ProgramSpecialization {
// TODO(exv): We would like to use the cache ID of the material instead of the CRC32. The cache
// ID is supposed to uniquely identify shader programs, which may be shared across materials
// (especially in the case of depth variants). However, due to runtime code generation, in
// practice, these shader programs can end up differing significantly across materials.
//
// The proper fix for this problem is to identify every single concern that could cause a shader
// program to possibly change at runtime and add those variables to ProgramSpecialization. Only
// then can we switch from the CRC32 back to the cache ID and gain savings across material files
// with identical shader programs.
uint32_t materialCrc32;
Variant variant;
utils::Slice<const backend::Program::SpecializationConstant> specializationConstants;
size_t hash() const noexcept;
bool operator==(ProgramSpecialization const& rhs) const noexcept;
};
} // namespace filament
namespace std {
template<>
struct hash<filament::ProgramSpecialization> {
inline size_t operator()(const filament::ProgramSpecialization& lhs) const noexcept {
return lhs.hash();
}
};
} // namespace std
#endif // TNT_FILAMENT_PROGRAMSPECIALIZATION_H
+1 -1
View File
@@ -969,7 +969,7 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
// check we have enough capacity to write these commandCount commands, if not,
// request a new CircularBuffer allocation of `capacity` bytes.
if (UTILS_UNLIKELY(circularBuffer.getUsed() + commandSizeInBytes > capacity)) {
if (UTILS_UNLIKELY(circularBuffer.getUsed() > capacity - commandSizeInBytes)) {
// FIXME: eventually we can't flush here because this will be a secondary
// command buffer. We will need another solution for overflows.
const_cast<FEngine&>(engine).flush();
+5 -5
View File
@@ -1055,11 +1055,11 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
ShadowMap* pShadowMap) mutable {
// Allocate shadowmap from our Atlas Allocator
auto const& options = pShadowMap->getShadowOptions();
auto allocation = allocator.allocate(options->mapSize);
assert_invariant(allocation.isValid());
assert_invariant(!allocation.viewport.empty());
pShadowMap->setAllocation(allocation.layer, allocation.viewport);
layersNeeded = std::max(uint8_t(allocation.layer + 1), layersNeeded);
auto [layer, pos] = allocator.allocate(options->mapSize);
assert_invariant(layer >= 0);
assert_invariant(!pos.empty());
pShadowMap->setAllocation(layer, pos);
layersNeeded = std::max(uint8_t(layer + 1), layersNeeded);
};
std::function const allocateFromTextureArray =
+14 -10
View File
@@ -538,12 +538,14 @@ void FEngine::init() {
&debug.shadowmap.debug_directional_shadowmap, [this] {
mMaterials.forEach([this](FMaterial* material) {
if (material->getMaterialDomain() == MaterialDomain::SURFACE) {
FMaterial::SpecializationConstantsBuilder constants =
material->getSpecializationConstantsBuilder();
constants.set(+ReservedSpecializationConstants::
CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP,
material->setConstant(
+ReservedSpecializationConstants::CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP,
debug.shadowmap.debug_directional_shadowmap);
material->setSpecializationConstants(std::move(constants));
material->invalidate(
Variant::DIR | Variant::SRE | Variant::DEP,
Variant::DIR | Variant::SRE);
}
});
});
@@ -552,12 +554,14 @@ void FEngine::init() {
&debug.lighting.debug_froxel_visualization, [this] {
mMaterials.forEach([this](FMaterial* material) {
if (material->getMaterialDomain() == MaterialDomain::SURFACE) {
FMaterial::SpecializationConstantsBuilder constants =
material->getSpecializationConstantsBuilder();
constants.set(+ReservedSpecializationConstants::
CONFIG_DEBUG_FROXEL_VISUALIZATION,
material->setConstant(
+ReservedSpecializationConstants::CONFIG_DEBUG_FROXEL_VISUALIZATION,
debug.lighting.debug_froxel_visualization);
material->setSpecializationConstants(std::move(constants));
material->invalidate(
Variant::DYN | Variant::DEP,
Variant::DYN);
}
});
});
+427 -97
View File
@@ -58,6 +58,7 @@
#include <algorithm>
#include <array>
#include <new>
#include <optional>
#include <string_view>
#include <unordered_map>
#include <utility>
@@ -149,7 +150,7 @@ const char* toString(ShaderModel model) {
}
Material* Material::Builder::build(Engine& engine) const {
MaterialDefinition* r = downcast(engine).getMaterialCache().acquireMaterial(downcast(engine),
MaterialDefinition* r = downcast(engine).getMaterialCache().acquire(downcast(engine),
mImpl->mPayload, mImpl->mSize);
if (r) {
return downcast(engine).createMaterial(*this, *r);
@@ -172,32 +173,12 @@ FMaterial::FMaterial(FEngine& engine, const Builder& builder, MaterialDefinition
mIsStereoSupported = driver.isStereoSupported();
mIsParallelShaderCompileSupported = driver.isParallelShaderCompileSupported();
mDepthPrecacheDisabled =
driver.isWorkaroundNeeded(Workaround::DISABLE_DEPTH_PRECACHE_FOR_DEFAULT_MATERIAL);
mDepthPrecacheDisabled = driver.isWorkaroundNeeded(Workaround::DISABLE_DEPTH_PRECACHE_FOR_DEFAULT_MATERIAL);
mDefaultMaterial = engine.getDefaultMaterial();
FixedCapacityVector<Program::SpecializationConstant> specializationConstants =
processSpecializationConstants(builder);
mSpecializationConstants =
engine.getMaterialCache().getSpecializationConstantsInternPool().acquire(
std::move(specializationConstants));
mSpecializationConstants = processSpecializationConstants(builder);
size_t cachedProgramsSize;
switch (mDefinition.materialDomain) {
case filament::MaterialDomain::SURFACE:
cachedProgramsSize = 1 << VARIANT_BITS;
break;
case filament::MaterialDomain::POST_PROCESS:
cachedProgramsSize = 1 << POST_PROCESS_VARIANT_BITS;
break;
case filament::MaterialDomain::COMPUTE:
cachedProgramsSize = 1;
break;
}
mCachedPrograms = FixedCapacityVector<backend::Handle<backend::HwProgram>>(cachedProgramsSize);
mDefinition.acquirePrograms(engine, mCachedPrograms.as_slice(), getMaterialParser(),
mSpecializationConstants, mIsDefaultMaterial);
precacheDepthVariants(driver);
#if FILAMENT_ENABLE_MATDBG
// Register the material with matdbg.
@@ -211,6 +192,27 @@ FMaterial::FMaterial(FEngine& engine, const Builder& builder, MaterialDefinition
FMaterial::~FMaterial() noexcept = default;
void FMaterial::invalidate(Variant::type_t variantMask, Variant::type_t variantValue) noexcept {
// Note: This API is not public at the moment, so it's okay to have some debugging logs
// and extra checks.
if (mDefinition.materialDomain == MaterialDomain::SURFACE &&
!mIsDefaultMaterial &&
!mDefinition.hasCustomDepthShader) {
// it would be unsafe to invalidate any of the cached depth variant
if (UTILS_UNLIKELY(!((variantMask & Variant::DEP) && !(variantValue & Variant::DEP)))) {
char variantMaskString[16];
snprintf(variantMaskString, sizeof(variantMaskString), "%#x", +variantMask);
char variantValueString[16];
snprintf(variantValueString, sizeof(variantValueString), "%#x", +variantValue);
LOG(WARNING) << "FMaterial::invalidate(" << variantMaskString << ", "
<< variantValueString << ") would corrupt the depth variant cache";
}
variantMask |= Variant::DEP;
variantValue &= ~Variant::DEP;
}
destroyPrograms(mEngine, variantMask, variantValue);
}
void FMaterial::terminate(FEngine& engine) {
if (mDefaultMaterialInstance) {
mDefaultMaterialInstance->setDefaultInstance(false);
@@ -237,11 +239,26 @@ void FMaterial::terminate(FEngine& engine) {
}
#endif
mDefinition.releasePrograms(engine, mCachedPrograms.as_slice(),
getMaterialParser(), mSpecializationConstants, mIsDefaultMaterial);
engine.getMaterialCache().releaseMaterial(engine, mDefinition);
engine.getMaterialCache().getSpecializationConstantsInternPool().release(
mSpecializationConstants);
destroyPrograms(engine);
engine.getMaterialCache().release(engine, mDefinition);
}
backend::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayoutDescription(
Variant const variant, bool const useVsmDescriptorSetLayout) const noexcept {
if (mDefinition.materialDomain == MaterialDomain::SURFACE) {
if (Variant::isValidDepthVariant(variant)) {
// Use the layout description used to create the per view depth variant layout.
return descriptor_sets::getDepthVariantLayout();
}
if (Variant::isSSRVariant(variant)) {
// Use the layout description used to create the per view SSR variant layout.
return descriptor_sets::getSsrVariantLayout();
}
}
if (useVsmDescriptorSetLayout) {
return mDefinition.perViewDescriptorSetLayoutVsmDescription;
}
return mDefinition.perViewDescriptorSetLayoutDescription;
}
filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout(
@@ -262,6 +279,17 @@ filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout(
return mDefinition.perViewDescriptorSetLayout;
}
backend::DescriptorSetLayout const& FMaterial::getDescriptorSetLayoutDescription(Variant variant)
const noexcept {
if (!isSharedVariant(variant)) {
return mDefinition.descriptorSetLayoutDescription;
}
if (UTILS_UNLIKELY(!mDefaultMaterial)) {
return mDefinition.descriptorSetLayoutDescription;
}
return mDefaultMaterial->getDescriptorSetLayoutDescription();
}
void FMaterial::compile(CompilerPriorityQueue const priority,
UserVariantFilterMask variantSpec,
CallbackHandler* handler,
@@ -276,13 +304,13 @@ void FMaterial::compile(CompilerPriorityQueue const priority,
UserVariantFilterMask const variantFilter =
~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL);
ShaderModel const shaderModel = mEngine.getShaderModel();
bool const isStereoSupported = mEngine.getDriverApi().isStereoSupported();
if (UTILS_LIKELY(mIsParallelShaderCompileSupported)) {
for (auto const variant: mDefinition.getVariants()) {
auto const& variants = isVariantLit() ?
VariantUtils::getLitVariants() : VariantUtils::getUnlitVariants();
for (auto const variant: variants) {
if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) {
if (mDefinition.hasVariant(variant, shaderModel, isStereoSupported)) {
if (hasVariant(variant)) {
prepareProgram(driver, variant, priority);
}
}
@@ -306,22 +334,6 @@ void FMaterial::compile(CompilerPriorityQueue const priority,
}
}
Handle<HwProgram> FMaterial::prepareProgramSlow(DriverApi& driver, Variant const variant,
CompilerPriorityQueue const priorityQueue) const noexcept {
if (isSharedVariant(variant)) {
FMaterial const* defaultMaterial = mEngine.getDefaultMaterial();
FILAMENT_CHECK_PRECONDITION(defaultMaterial);
Handle<HwProgram> program = defaultMaterial->mCachedPrograms[variant.key];
if (program) {
return mCachedPrograms[variant.key] = program;
}
return mCachedPrograms[variant.key] =
defaultMaterial->prepareProgram(driver, variant, priorityQueue);
}
return mCachedPrograms[variant.key] = mDefinition.prepareProgram(mEngine, driver,
getMaterialParser(), getProgramSpecialization(variant), priorityQueue);
}
FMaterialInstance* FMaterial::createInstance(const char* name) const noexcept {
if (mDefaultMaterialInstance) {
// if we have a default instance, use it to create a new one
@@ -356,12 +368,196 @@ BufferInterfaceBlock::FieldInfo const* FMaterial::reflect(
return mDefinition.uniformInterfaceBlock.getFieldInfo(name);
}
ProgramSpecialization FMaterial::getProgramSpecialization(Variant const variant) const noexcept {
return ProgramSpecialization {
.materialCrc32 = mDefinition.getMaterialParser().getCrc32(),
.variant = variant,
.specializationConstants = mSpecializationConstants,
};
MaterialParser const& FMaterial::getMaterialParser() const noexcept {
#if FILAMENT_ENABLE_MATDBG
if (mEditedMaterialParser) {
return *mEditedMaterialParser;
}
#endif
return mDefinition.getMaterialParser();
}
bool FMaterial::hasVariant(Variant const variant) const noexcept {
Variant vertexVariant, fragmentVariant;
switch (getMaterialDomain()) {
case MaterialDomain::SURFACE:
vertexVariant = Variant::filterVariantVertex(variant);
fragmentVariant = Variant::filterVariantFragment(variant);
break;
case MaterialDomain::POST_PROCESS:
vertexVariant = fragmentVariant = variant;
break;
case MaterialDomain::COMPUTE:
// TODO: implement MaterialDomain::COMPUTE
return false;
}
const ShaderModel sm = mEngine.getShaderModel();
if (!mDefinition.getMaterialParser().hasShader(sm, vertexVariant, ShaderStage::VERTEX)) {
return false;
}
if (!mDefinition.getMaterialParser().hasShader(sm, fragmentVariant, ShaderStage::FRAGMENT)) {
return false;
}
return true;
}
void FMaterial::prepareProgramSlow(DriverApi& driver, Variant const variant,
backend::CompilerPriorityQueue const priorityQueue) const noexcept {
assert_invariant(mEngine.hasFeatureLevel(mDefinition.featureLevel));
switch (getMaterialDomain()) {
case MaterialDomain::SURFACE:
getSurfaceProgramSlow(driver, variant, priorityQueue);
break;
case MaterialDomain::POST_PROCESS:
getPostProcessProgramSlow(driver, variant, priorityQueue);
break;
case MaterialDomain::COMPUTE:
// TODO: implement MaterialDomain::COMPUTE
break;
}
}
void FMaterial::getSurfaceProgramSlow(DriverApi& driver, Variant const variant,
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
assert_invariant(variant == Variant::filterVariant(variant, isVariantLit()) );
assert_invariant(!Variant::isReserved(variant));
Variant const vertexVariant = Variant::filterVariantVertex(variant);
Variant const fragmentVariant = Variant::filterVariantFragment(variant);
Program pb{ getProgramWithVariants(variant, vertexVariant, fragmentVariant) };
pb.priorityQueue(priorityQueue);
pb.multiview(
mEngine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW &&
Variant::isStereoVariant(variant));
createAndCacheProgram(driver, std::move(pb), variant);
}
void FMaterial::getPostProcessProgramSlow(DriverApi& driver, Variant const variant,
CompilerPriorityQueue const priorityQueue) const noexcept {
Program pb{ getProgramWithVariants(variant, variant, variant) };
pb.priorityQueue(priorityQueue);
createAndCacheProgram(driver, std::move(pb), variant);
}
Program FMaterial::getProgramWithVariants(
Variant variant,
Variant vertexVariant,
Variant fragmentVariant) const {
FEngine const& engine = mEngine;
const ShaderModel sm = engine.getShaderModel();
const bool isNoop = engine.getBackend() == Backend::NOOP;
/*
* Vertex shader
*/
MaterialParser const& parser = getMaterialParser();
ShaderContent& vsBuilder = engine.getVertexShaderContent();
UTILS_UNUSED_IN_RELEASE bool const vsOK = parser.getShader(vsBuilder, sm,
vertexVariant, ShaderStage::VERTEX);
FILAMENT_CHECK_POSTCONDITION(isNoop || (vsOK && !vsBuilder.empty()))
<< "The material '" << mDefinition.name.c_str()
<< "' has not been compiled to include the required GLSL or SPIR-V chunks for the "
"vertex shader (variant="
<< +variant.key << ", filtered=" << +vertexVariant.key << ").";
/*
* Fragment shader
*/
ShaderContent& fsBuilder = engine.getFragmentShaderContent();
UTILS_UNUSED_IN_RELEASE bool const fsOK = parser.getShader(fsBuilder, sm,
fragmentVariant, ShaderStage::FRAGMENT);
FILAMENT_CHECK_POSTCONDITION(isNoop || (fsOK && !fsBuilder.empty()))
<< "The material '" << mDefinition.name.c_str()
<< "' has not been compiled to include the required GLSL or SPIR-V chunks for the "
"fragment shader (variant="
<< +variant.key << ", filtered=" << +fragmentVariant.key << ").";
Program program;
program.shader(ShaderStage::VERTEX, vsBuilder.data(), vsBuilder.size())
.shader(ShaderStage::FRAGMENT, fsBuilder.data(), fsBuilder.size())
.shaderLanguage(parser.getShaderLanguage())
.diagnostics(mDefinition.name,
[variant, vertexVariant, fragmentVariant](CString const& name,
io::ostream& out) -> io::ostream& {
return out << name.c_str_safe() << ", variant=(" << io::hex << +variant.key
<< io::dec << "), vertexVariant=(" << io::hex
<< +vertexVariant.key << io::dec << "), fragmentVariant=("
<< io::hex << +fragmentVariant.key << io::dec << ")";
});
if (UTILS_UNLIKELY(parser.getShaderLanguage() == ShaderLanguage::ESSL1)) {
assert_invariant(!mDefinition.bindingUniformInfo.empty());
for (auto const& [index, name, uniforms] : mDefinition.bindingUniformInfo) {
program.uniforms(uint32_t(index), name, uniforms);
}
program.attributes(mDefinition.attributeInfo);
}
program.descriptorBindings(+DescriptorSetBindingPoints::PER_VIEW,
mDefinition.programDescriptorBindings[+DescriptorSetBindingPoints::PER_VIEW]);
program.descriptorBindings(+DescriptorSetBindingPoints::PER_RENDERABLE,
mDefinition.programDescriptorBindings[+DescriptorSetBindingPoints::PER_RENDERABLE]);
program.descriptorBindings(+DescriptorSetBindingPoints::PER_MATERIAL,
mDefinition.programDescriptorBindings[+DescriptorSetBindingPoints::PER_MATERIAL]);
program.specializationConstants(mSpecializationConstants);
program.pushConstants(ShaderStage::VERTEX,
mDefinition.pushConstants[uint8_t(ShaderStage::VERTEX)]);
program.pushConstants(ShaderStage::FRAGMENT,
mDefinition.pushConstants[uint8_t(ShaderStage::FRAGMENT)]);
program.cacheId(hash::combine(size_t(mDefinition.cacheId), variant.key));
return program;
}
void FMaterial::createAndCacheProgram(DriverApi& driver, Program&& p, Variant const variant) const noexcept {
bool const isShared = isSharedVariant(variant);
// Check if the default material has this program cached
if (isShared) {
if (mDefaultMaterial) {
auto const program = mDefaultMaterial->mCachedPrograms[variant.key];
if (program) {
mCachedPrograms[variant.key] = program;
return;
}
}
}
// Set descriptor sets for the program.
// Note: right now, we're going to assume VSM is disabled. In the future, we may
// want to provide both to the backend, so that both can be built.
p.descriptorLayout(+DescriptorSetBindingPoints::PER_VIEW,
getPerViewDescriptorSetLayoutDescription(variant, Variant::isVSMVariant(variant)));
p.descriptorLayout(+DescriptorSetBindingPoints::PER_RENDERABLE,
descriptor_sets::getPerRenderableLayout());
p.descriptorLayout(+DescriptorSetBindingPoints::PER_MATERIAL,
getDescriptorSetLayoutDescription(variant));
auto const program = driver.createProgram(std::move(p),
ImmutableCString{ mDefinition.name.c_str_safe() });
assert_invariant(program);
mCachedPrograms[variant.key] = 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) {
if (mDefaultMaterial && !mDefaultMaterial->mCachedPrograms[variant.key]) {
mDefaultMaterial->mCachedPrograms[variant.key] = program;
}
}
}
size_t FMaterial::getParameters(ParameterInfo* parameters, size_t count) const noexcept {
@@ -409,20 +605,6 @@ size_t FMaterial::getParameters(ParameterInfo* parameters, size_t count) const n
#if FILAMENT_ENABLE_MATDBG
void FMaterial::updateActiveProgramsForMatdbg(Variant const variant) const noexcept {
assert_invariant((size_t)variant.key < VARIANT_COUNT);
std::unique_lock lock(mActiveProgramsLock);
if (getMaterialDomain() == MaterialDomain::SURFACE) {
auto vert = Variant::filterVariantVertex(variant);
auto frag = Variant::filterVariantFragment(variant);
mActivePrograms.set(vert.key);
mActivePrograms.set(frag.key);
} else {
mActivePrograms.set(variant.key);
}
lock.unlock();
}
// Swaps in an edited version of the original package that was used to create the material. The
// edited package was stashed in response to a debugger event. This is invoked only when the
// Material Debugger is attached. The only editable features of a material package are the shader
@@ -430,8 +612,7 @@ void FMaterial::updateActiveProgramsForMatdbg(Variant const variant) const noexc
void FMaterial::applyPendingEdits() noexcept {
const char* name = mDefinition.name.c_str();
DLOG(INFO) << "Applying edits to " << (name ? name : "(untitled)");
mDefinition.releasePrograms(mEngine, mCachedPrograms.as_slice(), getMaterialParser(),
mSpecializationConstants, mIsDefaultMaterial);
destroyPrograms(mEngine); // FIXME: this will not destroy the shared variants
latchPendingEdits();
}
@@ -480,28 +661,144 @@ void FMaterial::onQueryCallback(void* userdata, VariantList* pActiveVariants) {
#endif // FILAMENT_ENABLE_MATDBG
void FMaterial::setSpecializationConstants(SpecializationConstantsBuilder&& builder) noexcept {
if (builder.mConstants.empty()) {
// Nothing was changed.
return;
[[nodiscard]] Handle<HwProgram> FMaterial::getProgramWithMATDBG(Variant const variant) const noexcept {
#if FILAMENT_ENABLE_MATDBG
assert_invariant((size_t)variant.key < VARIANT_COUNT);
std::unique_lock lock(mActiveProgramsLock);
if (getMaterialDomain() == MaterialDomain::SURFACE) {
auto vert = Variant::filterVariantVertex(variant);
auto frag = Variant::filterVariantFragment(variant);
mActivePrograms.set(vert.key);
mActivePrograms.set(frag.key);
} else {
mActivePrograms.set(variant.key);
}
auto& internPool = mEngine.getMaterialCache().getSpecializationConstantsInternPool();
MaterialParser const& materialParser = getMaterialParser();
// Release old resources...
mDefinition.releasePrograms(mEngine, mCachedPrograms.as_slice(), materialParser,
mSpecializationConstants, mIsDefaultMaterial);
internPool.release(mSpecializationConstants);
// Then acquire new ones.
mSpecializationConstants = internPool.acquire(std::move(builder.mConstants));
mDefinition.acquirePrograms(mEngine, mCachedPrograms.as_slice(), materialParser,
mSpecializationConstants, mIsDefaultMaterial);
lock.unlock();
if (isSharedVariant(variant)) {
FMaterial const* const pDefaultMaterial = mEngine.getDefaultMaterial();
if (pDefaultMaterial && pDefaultMaterial->mCachedPrograms[variant.key]) {
return pDefaultMaterial->getProgram(variant);
}
}
#endif
assert_invariant(mCachedPrograms[variant.key]);
return mCachedPrograms[variant.key];
}
FixedCapacityVector<Program::SpecializationConstant> FMaterial::processSpecializationConstants(
Builder const& builder) {
void FMaterial::destroyPrograms(FEngine& engine,
Variant::type_t const variantMask, Variant::type_t const variantValue) {
DriverApi& driverApi = engine.getDriverApi();
auto& cachedPrograms = mCachedPrograms;
switch (mDefinition.materialDomain) {
case MaterialDomain::SURFACE: {
if (mIsDefaultMaterial || mDefinition.hasCustomDepthShader) {
// default material, or we have custom depth shaders, we destroy all variants
for (size_t k = 0, n = VARIANT_COUNT; 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.
if (cachedPrograms[k]) {
driverApi.destroyProgram(std::move(cachedPrograms[k]));
}
}
}
} else {
// The depth variants may be shared with the default material, in which case
// we should not free them now.
// During Engine::shutdown(), auto-cleanup destroys the default material first,
// so this can be null, but this is only used for debugging.
UTILS_UNUSED_IN_RELEASE
auto const UTILS_NULLABLE pDefaultMaterial = engine.getDefaultMaterial();
for (size_t k = 0, n = VARIANT_COUNT; 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.
if (cachedPrograms[k]) {
if (Variant::isValidDepthVariant(Variant(k))) {
// By construction this should always be true, because this
// field is populated only when a material creates the program
// for this variant.
// During Engine::shutdown, auto-cleanup destroys the
// default material first
assert_invariant(!pDefaultMaterial ||
pDefaultMaterial->mCachedPrograms[k]);
// we don't own this variant, skip, but clear the entry.
cachedPrograms[k].clear();
continue;
}
driverApi.destroyProgram(std::move(cachedPrograms[k]));
}
}
}
}
break;
}
case MaterialDomain::POST_PROCESS: {
for (size_t k = 0, n = POST_PROCESS_VARIANT_COUNT; 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.
if (cachedPrograms[k]) {
driverApi.destroyProgram(std::move(cachedPrograms[k]));
}
}
}
break;
}
case MaterialDomain::COMPUTE: {
// Compute programs don't have variants
driverApi.destroyProgram(std::move(cachedPrograms[0]));
break;
}
}
}
std::optional<uint32_t> FMaterial::getSpecializationConstantId(std::string_view const name) const noexcept {
auto const pos = mDefinition.specializationConstantsNameToIndex.find(name);
if (pos != mDefinition.specializationConstantsNameToIndex.end()) {
return pos->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
}
return std::nullopt;
}
template<typename T, typename>
bool FMaterial::setConstant(uint32_t id, T value) noexcept {
if (UTILS_UNLIKELY(id >= mSpecializationConstants.size())) {
return false;
}
if (id >= CONFIG_MAX_RESERVED_SPEC_CONSTANTS) {
// Constant from the material itself (as opposed to the reserved ones)
auto const& constant =
mDefinition.materialConstants[id - CONFIG_MAX_RESERVED_SPEC_CONSTANTS];
using ConstantType = ConstantType;
switch (constant.type) {
case ConstantType::INT:
if (!std::is_same_v<T, int32_t>) return false;
break;
case ConstantType::FLOAT:
if (!std::is_same_v<T, float>) return false;
break;
case ConstantType::BOOL:
if (!std::is_same_v<T, bool>) return false;
break;
}
}
if (std::get<T>(mSpecializationConstants[id]) != value) {
mSpecializationConstants[id] = value;
return true;
}
return false;
}
FixedCapacityVector<Program::SpecializationConstant>
FMaterial::processSpecializationConstants(Builder const& builder) {
FixedCapacityVector<Program::SpecializationConstant> specializationConstants =
mDefinition.specializationConstants;
@@ -546,6 +843,43 @@ FixedCapacityVector<Program::SpecializationConstant> FMaterial::processSpecializ
return specializationConstants;
}
void FMaterial::precacheDepthVariants(DriverApi& driver) {
bool const disableDepthPrecacheForDefaultMaterial = mDepthPrecacheDisabled;
// pre-cache all depth variants inside the default material. Note that this should be
// entirely optional; if we remove this pre-caching, these variants will be populated
// later, when/if needed by createAndCacheProgram(). Doing it now potentially uses more
// memory and increases init time, but reduces hiccups during the first frame.
if (UTILS_UNLIKELY(mIsDefaultMaterial && !disableDepthPrecacheForDefaultMaterial)) {
const bool stereoSupported = driver.isStereoSupported();
auto const allDepthVariants = VariantUtils::getDepthVariants();
for (auto const variant: allDepthVariants) {
// Don't precache any stereo variants if stereo is not supported.
if (!stereoSupported && Variant::isStereoVariant(variant)) {
continue;
}
assert_invariant(Variant::isValidDepthVariant(variant));
if (hasVariant(variant)) {
prepareProgram(driver, variant, CompilerPriorityQueue::HIGH);
}
}
return;
}
// if possible pre-cache all depth variants from the default material
if (mDefinition.materialDomain == MaterialDomain::SURFACE &&
!mIsDefaultMaterial &&
!mDefinition.hasCustomDepthShader) {
assert_invariant(mDefaultMaterial);
auto const allDepthVariants = VariantUtils::getDepthVariants();
for (auto const variant: allDepthVariants) {
assert_invariant(Variant::isValidDepthVariant(variant));
mCachedPrograms[variant.key] = mDefaultMaterial->mCachedPrograms[variant.key];
}
}
}
descriptor_binding_t FMaterial::getSamplerBinding(
std::string_view const& name) const {
return mDefinition.samplerInterfaceBlock.getSamplerInfo(name)->binding;
@@ -560,12 +894,8 @@ const char* FMaterial::getParameterTransformName(std::string_view samplerName) c
return info->transformName.c_str();
}
template FMaterial::SpecializationConstantsBuilder& FMaterial::SpecializationConstantsBuilder::set<int32_t>(uint32_t id, int32_t value) noexcept;
template FMaterial::SpecializationConstantsBuilder& FMaterial::SpecializationConstantsBuilder::set<float>(uint32_t id, float value) noexcept;
template FMaterial::SpecializationConstantsBuilder& FMaterial::SpecializationConstantsBuilder::set<bool>(uint32_t id, bool value) noexcept;
template FMaterial::SpecializationConstantsBuilder& FMaterial::SpecializationConstantsBuilder::set<int32_t>(std::string_view name, int32_t value) noexcept;
template FMaterial::SpecializationConstantsBuilder& FMaterial::SpecializationConstantsBuilder::set<float>(std::string_view name, float value) noexcept;
template FMaterial::SpecializationConstantsBuilder& FMaterial::SpecializationConstantsBuilder::set<bool>(std::string_view name, bool value) noexcept;
template bool FMaterial::setConstant<int32_t>(uint32_t id, int32_t value) noexcept;
template bool FMaterial::setConstant<float>(uint32_t id, float value) noexcept;
template bool FMaterial::setConstant<bool>(uint32_t id, bool value) noexcept;
} // namespace filament
+45 -87
View File
@@ -45,6 +45,7 @@
#include <utils/Invocable.h>
#include <utils/Mutex.h>
#include <array>
#include <optional>
#include <string_view>
@@ -72,61 +73,6 @@ public:
DefaultMaterialBuilder();
};
// Used to change specialization constants at runtime internally by Filament.
//
// Call Material::getSpecializationConstantsBuilder() to return an instance of
// SpecializationConstantsBuilder. The lifetime of this object must not exceed the lifetime of
// the corresponding Material. After spec constants are changed with calls to set(), apply the
// changes to the original Material via setSpecializationConstants().
//
// It would be nice to reuse Builder for this, but Builder allocates an entire map and asserts
// if specified constants don't exist. In constrast, SpecializationConstantsBuilder is
// copy-on-write and allows non-existent specialization constants to be named.
class SpecializationConstantsBuilder {
friend class FMaterial;
public:
SpecializationConstantsBuilder(SpecializationConstantsBuilder const& rhs) = delete;
SpecializationConstantsBuilder& operator=(
SpecializationConstantsBuilder const& rhs) = delete;
SpecializationConstantsBuilder(SpecializationConstantsBuilder&& rhs) = default;
SpecializationConstantsBuilder& operator=(SpecializationConstantsBuilder&& rhs) = default;
template<typename T, typename = Builder::is_supported_constant_parameter_t<T>>
SpecializationConstantsBuilder& set(uint32_t id, T value) noexcept {
// Don't allocate if we can help it.
if (mConstants.empty()) {
if (std::get<T>(mDefaultConstants[id]) == value) {
return *this;
}
mConstants = utils::FixedCapacityVector<backend::Program::SpecializationConstant>(
mDefaultConstants);
}
mConstants[id] = value;
return *this;
}
template<typename T, typename = Builder::is_supported_constant_parameter_t<T>>
SpecializationConstantsBuilder& set(std::string_view name, T value) noexcept {
auto it = mDefinition->specializationConstantsNameToIndex.find(name);
if (it != mDefinition->specializationConstantsNameToIndex.cend()) {
set(it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS, value);
}
return *this;
}
private:
SpecializationConstantsBuilder(MaterialDefinition const& definition,
utils::Slice<const backend::Program::SpecializationConstant> defaultConstants)
: mDefinition(&definition),
mDefaultConstants(defaultConstants) {}
MaterialDefinition const* mDefinition;
utils::Slice<const backend::Program::SpecializationConstant> mDefaultConstants;
// Copy-on-write vector.
utils::FixedCapacityVector<backend::Program::SpecializationConstant> mConstants;
};
void terminate(FEngine& engine);
@@ -179,17 +125,21 @@ public:
FEngine& getEngine() const noexcept { return mEngine; }
bool isCached(Variant const variant) const noexcept {
return bool(mCachedPrograms[variant.key]);
}
void invalidate(Variant::type_t variantMask = 0, Variant::type_t variantValue = 0) noexcept;
// prepareProgram creates the program for the material's given variant at the backend level.
// Must be called outside of backend render pass.
// Must be called before getProgram() below.
backend::Handle<backend::HwProgram> prepareProgram(backend::DriverApi& driver,
Variant const variant,
void prepareProgram(backend::DriverApi& driver, Variant const variant,
backend::CompilerPriorityQueue const priorityQueue) const noexcept {
backend::Handle<backend::HwProgram> program = mCachedPrograms[variant.key];
if (UTILS_LIKELY(program)) {
return program;
// prepareProgram() is called for each RenderPrimitive in the scene, so it must be efficient.
if (UTILS_UNLIKELY(!isCached(variant))) {
prepareProgramSlow(driver, variant, priorityQueue);
}
return prepareProgramSlow(driver, variant, priorityQueue);
}
// getProgram returns the backend program for the material's given variant.
@@ -197,11 +147,10 @@ public:
[[nodiscard]]
backend::Handle<backend::HwProgram> getProgram(Variant const variant) const noexcept {
#if FILAMENT_ENABLE_MATDBG
updateActiveProgramsForMatdbg(variant);
return getProgramWithMATDBG(variant);
#endif
backend::Handle<backend::HwProgram> program = mCachedPrograms[variant.key];
assert_invariant(program);
return program;
assert_invariant(mCachedPrograms[variant.key]);
return mCachedPrograms[variant.key];
}
// MaterialInstance::use() binds descriptor sets before drawing. For shared variants,
@@ -221,6 +170,9 @@ public:
return true;
}
[[nodiscard]]
backend::Handle<backend::HwProgram> getProgramWithMATDBG(Variant variant) const noexcept;
bool isVariantLit() const noexcept { return mDefinition.isVariantLit; }
const utils::CString& getName() const noexcept { return mDefinition.name; }
@@ -280,12 +232,17 @@ public:
uint32_t generateMaterialInstanceId() const noexcept { return mMaterialInstanceId++; }
// Update specialization constants.
SpecializationConstantsBuilder getSpecializationConstantsBuilder() const noexcept {
return SpecializationConstantsBuilder(mDefinition, mSpecializationConstants);
}
void destroyPrograms(FEngine& engine,
Variant::type_t variantMask = 0,
Variant::type_t variantValue = 0);
void setSpecializationConstants(SpecializationConstantsBuilder&& builder) noexcept;
// return the id of a specialization constant specified by name for this material
std::optional<uint32_t> getSpecializationConstantId(std::string_view name) const noexcept ;
// Sets a specialization constant by id. call is no-op if the id is invalid.
// Return true is the value was changed.
template<typename T, typename = Builder::is_supported_constant_parameter_t<T>>
bool setConstant(uint32_t id, T value) noexcept;
uint8_t getPerViewLayoutIndex() const noexcept {
return mDefinition.perViewLayoutIndex;
@@ -332,20 +289,17 @@ public:
#endif
private:
MaterialParser const& getMaterialParser() const noexcept {
#if FILAMENT_ENABLE_MATDBG
if (mEditedMaterialParser) {
return *mEditedMaterialParser;
}
#endif
return mDefinition.getMaterialParser();
}
MaterialParser const& getMaterialParser() const noexcept;
ProgramSpecialization getProgramSpecialization(Variant const variant) const noexcept;
backend::Handle<backend::HwProgram> prepareProgramSlow(backend::DriverApi& driver,
Variant const variant,
backend::CompilerPriorityQueue const priorityQueue) const noexcept;
bool hasVariant(Variant variant) const noexcept;
void prepareProgramSlow(backend::DriverApi& driver, Variant variant,
CompilerPriorityQueue priorityQueue) const noexcept;
void getSurfaceProgramSlow(backend::DriverApi& driver, Variant variant,
CompilerPriorityQueue priorityQueue) const noexcept;
void getPostProcessProgramSlow(backend::DriverApi& driver, Variant variant,
CompilerPriorityQueue priorityQueue) const noexcept;
backend::Program getProgramWithVariants(Variant variant,
Variant vertexVariant, Variant fragmentVariant) const;
utils::FixedCapacityVector<backend::Program::SpecializationConstant>
processSpecializationConstants(Builder const& builder);
@@ -354,12 +308,18 @@ private:
void createAndCacheProgram(backend::DriverApi& driver, backend::Program&& p, Variant variant) const noexcept;
backend::DescriptorSetLayout const& getPerViewDescriptorSetLayoutDescription(
Variant const variant, bool const useVsmDescriptorSetLayout) const noexcept;
backend::DescriptorSetLayout const& getDescriptorSetLayoutDescription(
Variant variant = {}) const noexcept;
inline bool isSharedVariant(Variant const variant) const {
return (mDefinition.materialDomain == MaterialDomain::SURFACE) && !mIsDefaultMaterial &&
!mDefinition.hasCustomDepthShader && Variant::isValidDepthVariant(variant);
}
mutable utils::FixedCapacityVector<backend::Handle<backend::HwProgram>> mCachedPrograms;
mutable std::array<backend::Handle<backend::HwProgram>, VARIANT_COUNT> mCachedPrograms;
MaterialDefinition const& mDefinition;
bool mIsDefaultMaterial = false;
@@ -375,7 +335,7 @@ private:
mutable FMaterialInstance* mDefaultMaterialInstance = nullptr;
// current specialization constants for the HwProgram
utils::Slice<const backend::Program::SpecializationConstant> mSpecializationConstants;
utils::FixedCapacityVector<backend::Program::SpecializationConstant> mSpecializationConstants;
#if FILAMENT_ENABLE_MATDBG
matdbg::MaterialKey mDebuggerId;
@@ -384,8 +344,6 @@ private:
mutable utils::Mutex mPendingEditsLock;
std::unique_ptr<MaterialParser> mPendingEdits;
std::unique_ptr<MaterialParser> mEditedMaterialParser;
// Called by getProgram() to update active program list for matdbg UI.
void updateActiveProgramsForMatdbg(Variant const variant) const noexcept;
void setPendingEdits(std::unique_ptr<MaterialParser> pendingEdits) noexcept;
bool hasPendingEdits() const noexcept;
void latchPendingEdits() noexcept;
@@ -199,180 +199,3 @@ TEST(AtlasAllocator, AllocateBySizeFullLayers) {
EXPECT_EQ(vp3.viewport, r);
}
TEST(AtlasAllocator, FreeSimple) {
AtlasAllocator allocator(256);
// Allocate a 256x256 block (takes the whole layer 0)
auto vp0 = allocator.allocate(256);
EXPECT_EQ(vp0.layer, 0);
EXPECT_FALSE(vp0.viewport.empty());
// Free it
allocator.free(vp0);
// Allocate it again, should get the same spot
auto vp1 = allocator.allocate(256);
EXPECT_EQ(vp1.layer, 0);
EXPECT_EQ(vp1.viewport, vp0.viewport);
}
TEST(AtlasAllocator, FreeAndCoalesce) {
AtlasAllocator allocator(256);
// Allocate 4 128x128 blocks. They should fill layer 0.
auto vp0 = allocator.allocate(128);
auto vp1 = allocator.allocate(128);
auto vp2 = allocator.allocate(128);
auto vp3 = allocator.allocate(128);
EXPECT_EQ(vp0.layer, 0);
EXPECT_EQ(vp1.layer, 0);
EXPECT_EQ(vp2.layer, 0);
EXPECT_EQ(vp3.layer, 0);
// Free them all
allocator.free(vp0);
allocator.free(vp1);
allocator.free(vp2);
allocator.free(vp3);
// Now allocate a 256x256 block. It should fit in layer 0 because the 4 128s were coalesced.
auto vpBig = allocator.allocate(256);
EXPECT_EQ(vpBig.layer, 0);
EXPECT_FALSE(vpBig.viewport.empty());
}
TEST(AtlasAllocator, FreePartialCoalesce) {
AtlasAllocator allocator(256);
// Allocate 4 128x128 blocks.
auto vp0 = allocator.allocate(128);
auto vp1 = allocator.allocate(128);
auto vp2 = allocator.allocate(128);
auto vp3 = allocator.allocate(128);
// Free 3 of them
allocator.free(vp0);
allocator.free(vp1);
allocator.free(vp2);
// Try to allocate 256. Should fail (go to next layer) because vp3 is still there.
auto vpBig = allocator.allocate(256);
EXPECT_NE(vpBig.layer, 0);
// Free the last one
allocator.free(vp3);
// Now allocate 256. Should succeed in layer 0.
// Note: vpBig took layer 1, so layer 0 is now free.
auto vpBig2 = allocator.allocate(256);
EXPECT_EQ(vpBig2.layer, 0);
}
TEST(AtlasAllocator, FreeDeepHierarchy) {
AtlasAllocator allocator(512);
// Allocate a small block deep in the tree
// 512 -> 256 -> 128 -> 64. 32 is too small for QUAD_TREE_DEPTH=4
auto vpSmall = allocator.allocate(64);
EXPECT_EQ(vpSmall.layer, 0);
// Free it
allocator.free(vpSmall);
// Allocate a huge block. Should fit in layer 0 if the small block was properly coalesced up to the root.
auto vpHuge = allocator.allocate(512);
EXPECT_EQ(vpHuge.layer, 0);
}
TEST(AtlasAllocator, FreeInvalid) {
AtlasAllocator allocator(256);
AtlasAllocator::Allocation invalid;
EXPECT_FALSE(invalid.isValid());
// Should not crash
allocator.free(invalid);
}
TEST(AtlasAllocator, Checkerboard) {
AtlasAllocator allocator(256);
// Allocate 4 128x128 blocks (indices 0, 1, 2, 3 in Morton order)
auto vp0 = allocator.allocate(128);
auto vp1 = allocator.allocate(128);
auto vp2 = allocator.allocate(128);
auto vp3 = allocator.allocate(128);
// Free diagonals (0 and 3)
allocator.free(vp0);
allocator.free(vp3);
// Try to allocate 256. Should fail (layer 0 is fragmented).
auto vpBig = allocator.allocate(256);
EXPECT_NE(vpBig.layer, 0);
// Free remaining
allocator.free(vp1);
allocator.free(vp2);
// Now allocate 256. Should succeed in layer 0.
auto vpBig2 = allocator.allocate(256);
EXPECT_EQ(vpBig2.layer, 0);
}
TEST(AtlasAllocator, LayerIndependence) {
AtlasAllocator allocator(256);
// Fill Layer 0 with a big block
auto l0 = allocator.allocate(256);
EXPECT_EQ(l0.layer, 0);
// Fill Layer 1 with a big block
auto l1 = allocator.allocate(256);
EXPECT_EQ(l1.layer, 1);
// Free Layer 0
allocator.free(l0);
// Allocate big block again. Should go to Layer 0.
auto l0_new = allocator.allocate(256);
EXPECT_EQ(l0_new.layer, 0);
// Layer 1 should still be occupied.
// If we try to allocate another big block, it should go to Layer 2.
auto l2 = allocator.allocate(256);
EXPECT_EQ(l2.layer, 2);
}
TEST(AtlasAllocator, ReuseHole) {
AtlasAllocator allocator(256);
// Allocate 4 128x128 blocks.
auto vp0 = allocator.allocate(128);
auto vp1 = allocator.allocate(128);
auto vp2 = allocator.allocate(128);
auto vp3 = allocator.allocate(128);
// Free the second one (vp1)
allocator.free(vp1);
// Allocate a new 128 block. It should fit in the hole we just made in Layer 0.
auto vpNew = allocator.allocate(128);
EXPECT_EQ(vpNew.layer, 0);
EXPECT_EQ(vpNew.viewport, vp1.viewport);
}
TEST(AtlasAllocator, NonPowerOfTwo) {
AtlasAllocator allocator(512);
// Request 100. Should round down to 64.
auto vp = allocator.allocate(100);
EXPECT_EQ(vp.viewport.width, 64);
EXPECT_EQ(vp.viewport.height, 64);
EXPECT_TRUE(vp.isValid());
// Request 500. Should round down to 256.
auto vp2 = allocator.allocate(500);
EXPECT_EQ(vp2.viewport.width, 256);
EXPECT_EQ(vp2.viewport.height, 256);
EXPECT_TRUE(vp2.isValid());
}
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.69.1"
spec.version = "1.68.5"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.69.1/filament-v1.69.1-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.68.5/filament-v1.68.5-ios.tgz" }
spec.libraries = 'c++'
@@ -274,7 +274,7 @@ const float kToastDelayDuration = 2.0f;
self.modelView.engine, message->buffer, message->bufferByteCount, content);
ColorGrading* const colorGrading = _automation->getColorGrading(self.modelView.engine);
self.modelView.view->setColorGrading(colorGrading);
self.modelView.cameraFocalLength = _automation->getSettings().camera.focalLength;
self.modelView.cameraFocalLength = _automation->getViewerOptions().cameraFocalLength;
}
- (void)loadGlb:(viewer::ReceivedMessage const*)message {
@@ -28,7 +28,7 @@
namespace filament {
// update this when a new version of filament wouldn't work with older materials
static constexpr size_t MATERIAL_VERSION = 69;
static constexpr size_t MATERIAL_VERSION = 68;
// Those are the api levels that are used in the source material file (.mat)
//
@@ -275,8 +275,6 @@ utils::Slice<const Variant> getLitVariants() noexcept UTILS_PURE;
utils::Slice<const Variant> getUnlitVariants() noexcept UTILS_PURE;
// list of depth variants
utils::Slice<const Variant> getDepthVariants() noexcept UTILS_PURE;
// list of post process variants
utils::Slice<const Variant> getPostProcessVariants() noexcept UTILS_PURE;
}
} // namespace filament
+2 -18
View File
@@ -16,8 +16,6 @@
#include <private/filament/Variant.h>
#include <private/filament/EngineEnums.h>
#include <filament/MaterialEnums.h>
#include <utils/Slice.h>
@@ -81,7 +79,7 @@ constexpr inline size_t variant_count(bool lit) noexcept {
size_t count = 0;
for (size_t i = 0; i < VARIANT_COUNT; i++) {
Variant variant(i);
if (!Variant::isValidStandardVariant(variant)) {
if (!Variant::isValid(variant)) {
continue;
}
variant = Variant::filterVariant(variant, lit);
@@ -110,7 +108,7 @@ constexpr auto get_variants() noexcept {
size_t count = 0;
for (size_t i = 0; i < VARIANT_COUNT; i++) {
Variant variant(i);
if (!Variant::isValidStandardVariant(variant)) {
if (Variant::isReserved(variant)) {
continue;
}
variant = Variant::filterVariant(variant, LIT);
@@ -133,15 +131,6 @@ constexpr auto get_depth_variants() noexcept {
return variants;
}
constexpr auto get_post_process_variants() noexcept {
std::array<Variant, POST_PROCESS_VARIANT_COUNT> variants;
for (size_t i = 0; i < POST_PROCESS_VARIANT_COUNT; i++) {
Variant const variant(i);
variants[i] = variant;
}
return variants;
}
// Below are compile time sanity-check tests
constexpr inline bool reserved_is_not_valid() noexcept {
for (size_t i = 0; i < VARIANT_COUNT; i++) {
@@ -209,7 +198,6 @@ constexpr inline size_t fragment_variant_count() noexcept {
static auto const gLitVariants{ details::get_variants<true>() };
static auto const gUnlitVariants{ details::get_variants<false>() };
static auto const gDepthVariants{ details::get_depth_variants() };
static auto const gPostProcessVariants{ details::get_post_process_variants() };
static_assert(reserved_is_not_valid());
static_assert(reserved_variant_count() == 160);
@@ -234,10 +222,6 @@ utils::Slice<const Variant> getDepthVariants() noexcept {
return { details::gDepthVariants.data(), details::gDepthVariants.size() };
}
utils::Slice<const Variant> getPostProcessVariants() noexcept {
return { details::gPostProcessVariants.data(), details::gPostProcessVariants.size() };
}
}; // VariantUtils
} // namespace filament
+1 -1
View File
@@ -583,7 +583,7 @@ public:
template<typename F>
void traverseAggregate(TIntermNode* root, F&& closure) {
AggregateTraverserAdapter adapter(std::forward<F>(closure));
AggregateTraverserAdapter adapter(std::forward<std::decay_t<F>>(closure));
root->traverse(&adapter);
}
-9
View File
@@ -23,14 +23,11 @@
#include <private/utils/Tracing.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#define CGLTF_IMPLEMENTATION
#include <cgltf.h>
#include <meshoptimizer.h>
#include <limits>
namespace filament::gltfio::utility {
using namespace utils;
@@ -97,12 +94,6 @@ void decodeMeshoptCompression(cgltf_data* data) {
assert_invariant(source);
source += compression->offset;
size_t const theoreticalMaxCount = std::numeric_limits<size_t>::max() / compression->stride;
FILAMENT_CHECK_PRECONDITION(compression->count <= theoreticalMaxCount)
<< "gltfio: meshopt decompression exceeds maximum count of " << theoreticalMaxCount
<< " (actual=" << compression->count << ") given stride of " << compression->stride
<< ".";
// This memory is freed by cgltf.
void* destination = malloc(compression->count * compression->stride);
assert_invariant(destination);
@@ -68,7 +68,6 @@ public:
// Automatically requests a frame skip when the CPU gets too much ahead of the display. This can prevent
// stalls on Android, e.g. when running out buffers, but this also keeps the expected latency in check.
bool skip_frame_when_cpu_ahead_of_display = false;
bool enable_program_cache = false;
} engine;
struct {
struct {
@@ -128,7 +128,7 @@ public:
this->swap(rhs);
}
explicit FixedCapacityVector(utils::Slice<const T> rhs,
FixedCapacityVector(utils::Slice<const T> rhs,
const allocator_type& alloc = allocator_type())
: mSize(rhs.size()),
mCapacityAllocator(mSize, alloc) {
-3
View File
@@ -115,9 +115,6 @@ FeatureFlagManager::FeatureFlagManager() : mFeatures{{
{ "backend.vulkan.enable_pipeline_cache_prewarming",
"Enables an experimental approach to parallel shader compilation on Vulkan.",
&features.backend.vulkan.enable_pipeline_cache_prewarming, false },
{ "engine.enable_program_cache",
"Enable the shader program cache.",
&features.engine.enable_program_cache },
{ "backend.vulkan.enable_staging_buffer_bypass",
"vulkan: enable a staging bypass logic for unified memory architecture.",
&features.backend.vulkan.enable_staging_buffer_bypass, false },
+1 -1
View File
@@ -41,7 +41,7 @@ TEST(InternPoolTest, AcquireWithMove) {
Slice<const int> interned = pool.acquire(std::move(value));
EXPECT_FALSE(pool.empty());
EXPECT_EQ(copy.as_slice(), interned);
EXPECT_EQ(copy, interned);
EXPECT_EQ(data, interned.data());
}
-147
View File
@@ -1,147 +0,0 @@
# Viewer Library
The **Viewer Library** (`libs/viewer`) provides a high-level abstraction for configuring and rendering Filament scenes. It is used by tools like `gltf_viewer` to load assets, manage settings, and drive the rendering loop.
## Features
- **Settings Management**: Centralized configuration for View, Camera, Lights, and Materials via the `Settings` struct.
- **JSON Serialization**: Full support for loading and saving settings via JSON.
- **Automation**: `AutomationEngine` allows scripting the viewer with a sequence of JSON-based test cases (batch mode).
- **GUI Integration**: Built-in support for `imgui` via `ViewerGui` and `Settings` binding.
## JSON Settings Schema
The viewer settings can be configured using a JSON object. This is used for `gltf_viewer --settings` or in automation specs.
### Root Object
The root object contains the following categories:
| Key | Type | Description |
| :--- | :--- | :--- |
| `view` | Object | Post-processing and rendering quality settings. |
| `camera` | Object | **[NEW]** Explicit camera control (pose, projection, exposure). |
| `lighting` | Object | **[NEW]** Environment and dynamic light settings. |
| `viewer` | Object | Global viewer options (skybox, background, scaling). |
| `animation` | Object | **[NEW]** Animation playback control. |
| `material` | Object | Material overrides. |
---
### Camera Settings (`camera`)
Allows explicit control over the camera. If `enabled` is false, the viewer uses its default orbit camera logic (auto-scaling/centering).
```json
"camera": {
"enabled": true, // Must be true to use these explicit settings
"projection": "PERSPECTIVE", // "PERSPECTIVE" or "ORTHO"
"center": [0, 0, 0], // World-space look-at point
"lookAt": [0, 0, -1], // World-space eye position (confusingly named 'lookAt' in internal legacy, often 'eye')
"up": [0, 1, 0], // Up vector
"near": 0.1, // Near plane
"far": 100.0, // Far plane
"focalLength": 28.0, // Focal length in mm (Perspective only)
"fov": 0.0, // Field of view in degrees (overrides focalLength if > 0)
"aperture": 16.0, // f-stop
"shutterSpeed": 125.0, // 1/seconds
"sensitivity": 100.0, // ISO
"focusDistance": 10.0, // Focus distance in world units
"scaling": [1.0, 1.0], // Custom projection matrix scaling (mostly for Ortho)
"shift": [0.0, 0.0] // Custom projection matrix shift
}
```
### Lighting Settings (`lighting`)
Controls the Image Based Lighting (IBL), the Sun, and additional dynamic lights.
```json
"lighting": {
"iblIntensity": 30000.0,
"iblRotation": 0.0, // Rotation in degrees
"enableSunlight": true,
"enableShadows": true,
"sunlight": { // **[NEW]** Nested sunlight properties
"intensity": 100000.0,
"color": [0.98, 0.92, 0.89],
"direction": [0.6, -1.0, -0.8],
"sunHaloSize": 10.0,
"sunHaloFalloff": 80.0,
"sunAngularRadius": 1.9,
"castShadows": true,
"shadowOptions": { // Per-light shadow options
"mapSize": 1024,
"shadowCascades": 1,
"stable": false
}
},
"lights": [ // **[NEW]** Array of custom lights
{
"type": "POINT", // "POINT", "SPOT", "FOCUSED_SPOT", "DIRECTIONAL", "SUN"
"position": [0, 2, 0],
"color": [1, 0, 0],
"intensity": 5000.0,
"falloff": 10.0,
"castShadows": true,
"shadowOptions": { "mapSize": 512 }
},
{
"type": "SPOT",
"position": [2, 5, 2],
"direction": [0, -1, 0],
"spotInner": 0.5, // Inner cone angle (radians)
"spotOuter": 0.8 // Outer cone angle (radians)
}
]
}
```
### View Settings (`view`)
Standard Filament view settings.
```json
"view": {
"postProcessingEnabled": true,
"antiAliasing": "FXAA", // "NONE", "FXAA"
"msaa": {
"enabled": true,
"sampleCount": 4
},
"ssao": { "enabled": true, ... },
"bloom": { "enabled": true, ... },
"dof": { "enabled": false, ... },
"vignette": { "enabled": false, ... },
"colorGrading": {
"toneMapping": "ACES_LEGACY", // "LINEAR", "ACES", "FILMIC", "PBR_NEUTRAL", etc.
"exposure": 0.0,
"gamma": [1.0, 1.0, 1.0]
}
}
```
### Viewer Options (`viewer`)
General app-level settings.
```json
"viewer": {
"skyboxEnabled": true,
"backgroundColor": [0, 0, 0], // Used if skybox is disabled
"autoScaleEnabled": true, // Fit model to unit cube
"groundPlaneEnabled": false
}
```
### Animation Settings (`animation`)
Control glTF animation playback.
```json
"animation": {
"enabled": true,
"speed": 1.0,
"time": -1.0 // If >= 0, forces animation to this specific time (seconds)
}
```
+1 -10
View File
@@ -117,7 +117,7 @@ public:
Scene* scene;
IndirectLight* indirectLight;
utils::Entity sunlight;
const utils::Entity* assetLights;
utils::Entity* assetLights;
size_t assetLightCount;
};
@@ -211,11 +211,6 @@ public:
*/
ViewerOptions getViewerOptions() const;
/**
* Gets the current full settings object.
*/
const Settings& getSettings() const { return *mSettings; }
/**
* Signals that batch mode can begin. Call this after all meshes and textures finish loading.
*/
@@ -269,10 +264,6 @@ private:
Engine* mColorGradingEngine = nullptr;
ColorGrading* mColorGrading = nullptr;
ColorGradingSettings mColorGradingSettings = {};
std::vector<utils::Entity> mCustomLights;
void updateCustomLights(Engine* engine, const std::vector<LightDefinition>& lights,
Scene* scene);
size_t mCurrentTest;
float mElapsedTime;
+17 -76
View File
@@ -17,13 +17,11 @@
#ifndef VIEWER_SETTINGS_H
#define VIEWER_SETTINGS_H
#include <filament/Camera.h>
#include <filament/ColorGrading.h>
#include <filament/ColorSpace.h>
#include <filament/IndirectLight.h>
#include <filament/LightManager.h>
#include <filament/MaterialInstance.h>
#include <filament/Renderer.h>
#include <filament/Scene.h>
#include <filament/View.h>
@@ -46,11 +44,6 @@ class Renderer;
namespace viewer {
struct ColorGradingSettings;
struct DynamicLightingSettings;
struct MaterialSettings;
struct Settings;
struct ViewSettings;
struct ColorGradingSettings;
struct DynamicLightingSettings;
struct MaterialSettings;
@@ -59,10 +52,6 @@ struct ViewSettings;
struct LightSettings;
struct ViewerOptions;
struct DebugOptions;
struct CameraSettings;
struct AnimationSettings;
struct RenderSettings;
struct LightDefinition;
enum class ToneMapping : uint8_t {
LINEAR = 0,
@@ -93,8 +82,6 @@ using VsmShadowOptions = filament::View::VsmShadowOptions;
using GuardBandOptions = filament::View::GuardBandOptions;
using StereoscopicOptions = filament::View::StereoscopicOptions;
using LightManager = filament::LightManager;
using CameraProjection = filament::Camera::Projection;
using BlendMode = filament::BlendMode;
// These functions push all editable property values to their respective Filament objects.
void applySettings(Engine* engine, const ViewSettings& settings, View* dest);
@@ -105,8 +92,6 @@ void applySettings(Engine* engine, const ViewerOptions& settings, Camera* camera
Renderer* renderer);
void applySettings(Engine* engine, const DebugOptions& settings,
Renderer* renderer);
void applySettings(Engine* engine, const CameraSettings& settings, Camera* camera,
double aspectRatio = 0.0);
// Creates a new ColorGrading object based on the given settings.
UTILS_PUBLIC
@@ -219,9 +204,6 @@ struct ViewSettings {
ColorGradingSettings colorGrading;
DynamicLightingSettings dynamicLighting;
FogSettings fogSettings;
BlendMode blendMode = BlendMode::OPAQUE;
bool stencilBufferEnabled = false;
uint8_t visibleLayers = 0x01;
};
template <typename T>
@@ -235,73 +217,35 @@ struct MaterialSettings {
MaterialProperty<math::float4> float4[MAX_COUNT];
};
struct LightDefinition {
LightManager::Type type = LightManager::Type::POINT;
math::float3 position = { 0.0f, 0.0f, 0.0f };
math::float3 direction = { 0.0f, -1.0f, 0.0f };
math::float3 color = { 1.0f, 1.0f, 1.0f };
float intensity = 0.0f;
float falloff = 0.0f;
float spotInner = 0.0f;
float spotOuter = 0.0f;
float sunHaloSize = 10.0f;
float sunHaloFalloff = 80.0f;
float sunAngularRadius = 1.9f;
bool castShadows = false;
LightManager::ShadowOptions shadowOptions;
};
struct LightSettings {
bool enableShadows = true; // Global toggle to enabling/disabling shadows
bool enableShadows = true;
bool enableSunlight = true;
LightManager::ShadowOptions shadowOptions;
SoftShadowOptions softShadowOptions;
float sunlightIntensity = 100000.0f;
float sunlightHaloSize = 10.0f;
float sunlightHaloFalloff = 80.0f;
float sunlightAngularRadius = 1.9f;
math::float3 sunlightDirection = {0.6, -1.0, -0.8};
math::float3 sunlightColor = filament::Color::toLinear<filament::ACCURATE>({ 0.98, 0.92, 0.89});
float iblIntensity = 30000.0f;
float iblRotation = 0.0f;
LightDefinition sunlight = {
.type= LightManager::Type::SUN,
.direction = {0.6, -1.0, -0.8},
.color = filament::Color::toLinear<filament::ACCURATE>({ 0.98, 0.92, 0.89}),
.intensity = 100000.0f,
};
std::vector<LightDefinition> lights;
};
struct CameraSettings {
math::float3 center = { 0.0f, 0.0f, 0.0f };
math::float3 lookAt = { 0.0f, 0.0f, -1.0f };
math::float3 up = { 0.0f, 1.0f, 0.0f };
float horizontalFov = 0.0f; // degrees, if 0 use focal length
float near = 0.1f;
float far = 100.0f;
float focalLength = 28.0f; // mm, if 0 use fov
float aperture = 16.0f;
float shutterSpeed = 125.0f;
float sensitivity = 100.0f; // ISO
float focusDistance = 10.0f;
float eyeOcularDistance = 0.0f;
float eyeToeIn = 0.0f;
CameraProjection projection = CameraProjection::PERSPECTIVE;
bool enabled = false;
math::float2 scaling = { 1.0, 1.0 };
math::float2 shift = { 0.0, 0.0 };
};
struct AnimationSettings {
bool enabled = false;
float time = -1.0f;
float speed = 1.0f;
};
struct RenderSettings {
Renderer::ClearOptions clearOptions = {};
Renderer::FrameRateOptions frameRateOptions = {};
};
struct ViewerOptions {
float cameraAperture = 16.0f;
float cameraSpeed = 125.0f;
float cameraISO = 100.0f;
float cameraNear = 0.1f;
float cameraFar = 100.0f;
float cameraEyeOcularDistance = 0.0f;
float cameraEyeToeIn = 0.0f;
float groundShadowStrength = 0.75f;
bool groundPlaneEnabled = false;
bool skyboxEnabled = true;
sRGBColor backgroundColor = { 0.0f };
float cameraFocalLength = 28.0f;
float cameraFocusDistance = 10.0f;
bool autoScaleEnabled = true;
bool autoInstancingEnabled = false;
};
@@ -315,9 +259,6 @@ struct Settings {
MaterialSettings material;
LightSettings lighting;
ViewerOptions viewer;
CameraSettings camera;
AnimationSettings animation;
RenderSettings render;
DebugOptions debug;
};
-3
View File
@@ -231,9 +231,6 @@ public:
int getCurrentCamera() const { return mCurrentCamera; }
utils::Entity getSunlight() const { return mSunlight; }
IndirectLight* getIndirectLight() const { return mIndirectLight; }
private:
using SceneMask = gltfio::NodeManager::SceneMask;
+14
View File
@@ -0,0 +1,14 @@
The schema in this folder can be used to enable autocomplete and help features when authoring
automation specs in your favorite text editor. For example, for Visual Studio Code you can add the
following to `.vscode/settings`:
```
"json.schemas": [
{
"fileMatch": [
"libs/viewer/tests/basic.json"
],
"url": "./libs/viewer/schemas/automation.json"
}
]
```
+43
View File
@@ -0,0 +1,43 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://github.com/google/filament/automation.json",
"title": "Spec",
"description": "A specification that generates a set of Settings objects",
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Prefix used for screenshots and exported settings"
},
"base": {
"type": "object",
"description": "The base configuration to which permutations are applied",
"patternProperties": {
"[A-Z0-9_\\.]+": { "type": ["number", "boolean", "string" ] }
},
"additionalProperties": false
},
"permute": {
"type": "object",
"description": "Specifies a cross-product of property values",
"patternProperties": {
"[A-Z0-9_\\.]+": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"oneOf": [
{ "items": { "type": "number" } },
{ "items": { "type": "string" } },
{ "items": { "type": "boolean" } }
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"minItems": 1
}
+9 -77
View File
@@ -20,14 +20,9 @@
#include <filament/Camera.h>
#include <filament/Engine.h>
#include <filament/LightManager.h>
#include <filament/Renderer.h>
#include <filament/Scene.h>
#include <filament/TransformManager.h>
#include <filament/Viewport.h>
#include <utils/EntityManager.h>
#include <backend/PixelBufferDescriptor.h>
#include <utils/Log.h>
@@ -203,21 +198,10 @@ void AutomationEngine::applySettings(Engine* engine, const char* json, size_t js
}
viewer::applySettings(engine, mSettings->lighting, content.indirectLight, content.sunlight,
content.assetLights, content.assetLightCount, content.lightManager, content.scene, content.view);
updateCustomLights(engine, mSettings->lighting.lights, content.scene);
Camera* camera = &content.view->getCamera();
Skybox* skybox = content.scene->getSkybox();
viewer::applySettings(engine, mSettings->viewer, camera, skybox, content.renderer);
viewer::applySettings(engine, mSettings->debug, content.renderer);
// Apply CameraSettings
double const aspect = (double) content.view->getViewport().width /
(double) content.view->getViewport().height;
viewer::applySettings(engine, mSettings->camera, camera, aspect);
// Apply RenderSettings
content.renderer->setClearOptions(mSettings->render.clearOptions);
content.renderer->setFrameRateOptions(mSettings->render.frameRateOptions);
}
ColorGrading* AutomationEngine::getColorGrading(Engine* engine) {
@@ -233,6 +217,14 @@ ColorGrading* AutomationEngine::getColorGrading(Engine* engine) {
}
ViewerOptions AutomationEngine::getViewerOptions() const {
ViewerOptions options = mSettings->viewer;
const auto dofOptions = mSettings->view.dof;
if (dofOptions.enabled) {
options.cameraFocalLength = Camera::computeEffectiveFocalLength(
options.cameraFocalLength / 1000.0,
std::max(0.1f, options.cameraFocusDistance)) * 1000.0;
}
return mSettings->viewer;
}
@@ -245,20 +237,6 @@ void AutomationEngine::tick(Engine* engine, const ViewerContent& content, float
for (size_t i = 0; i < content.materialCount; i++) {
viewer::applySettings(engine, mSettings->material, content.materials[i]);
}
viewer::applySettings(engine, mSettings->lighting, content.indirectLight, content.sunlight,
content.assetLights, content.assetLightCount, content.lightManager, content.scene,
content.view);
updateCustomLights(engine, mSettings->lighting.lights, content.scene);
// Apply CameraSettings
double const aspect = (double) content.view->getViewport().width /
(double) content.view->getViewport().height;
viewer::applySettings(engine, mSettings->camera, &content.view->getCamera(), aspect);
// Apply RenderSettings
content.renderer->setClearOptions(mSettings->render.clearOptions);
content.renderer->setFrameRateOptions(mSettings->render.frameRateOptions);
if (mOptions.verbose) {
utils::slog.i << "Running test " << mCurrentTest << utils::io::endl;
}
@@ -285,7 +263,7 @@ void AutomationEngine::tick(Engine* engine, const ViewerContent& content, float
const bool isLastTest = mCurrentTest == mSpec->size() - 1;
int const digits = (int) log10((double) mSpec->size()) + 1;
const int digits = (int) log10 ((double) mSpec->size()) + 1;
std::ostringstream stringStream;
stringStream << mSpec->getName(mCurrentTest)
<< std::setfill('0') << std::setw(digits) << mCurrentTest;
@@ -318,51 +296,5 @@ const char* AutomationEngine::getStatusMessage() const {
return gStatus.c_str();
}
void AutomationEngine::updateCustomLights(Engine* engine,
const std::vector<LightDefinition>& lights, Scene* scene) {
auto& em = utils::EntityManager::get();
LightManager* lm = &engine->getLightManager();
// Destroy old lights
for (auto entity: mCustomLights) {
lm->destroy(entity);
scene->remove(entity);
em.destroy(entity);
}
mCustomLights.clear();
if (lights.empty()) {
return;
}
// Create new lights
mCustomLights.resize(lights.size());
em.create(mCustomLights.size(), mCustomLights.data());
for (size_t i = 0; i < lights.size(); ++i) {
const auto& def = lights[i];
LightManager::Builder builder(def.type);
builder.color(def.color)
.intensity(def.intensity)
.position(def.position)
.direction(def.direction)
.falloff(def.falloff)
.spotLightCone(def.spotInner, def.spotOuter)
.castShadows(def.castShadows)
.sunHaloSize(def.sunHaloSize)
.sunHaloFalloff(def.sunHaloFalloff)
.sunAngularRadius(def.sunAngularRadius)
.build(*engine, mCustomLights[i]);
// Shadow options must be set on the instance after creation
auto instance = lm->getInstance(mCustomLights[i]);
if (instance) {
lm->setShadowOptions(instance, def.shadowOptions);
}
scene->addEntity(mCustomLights[i]);
}
}
} // namespace viewer
} // namespace filament
+3 -20
View File
@@ -47,7 +47,7 @@ static const char* DEFAULT_AUTOMATION = R"TXT([
{
"name": "vieweropts",
"base": {
"camera.focusDistance": 0.1
"viewer.cameraFocusDistance": 0.1
}
},
{
@@ -59,7 +59,7 @@ static const char* DEFAULT_AUTOMATION = R"TXT([
"view.taa.enabled": [false, true],
"view.antiAliasing": ["NONE", "FXAA"],
"view.ssao.enabled": [false, true],
"view.screenSpaceReflections.enabled": [false, true],
"view.screenSpaceReflections.enabled": [false, true]
"view.bloom.enabled": [false, true],
"view.dof.enabled": [false, true],
"view.guardBand.enabled": [false, true]
@@ -89,20 +89,10 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, std::str
return i + 1;
}
static int skip(jsmntok_t const* tokens, int i) {
int size = tokens[i].size;
i++;
for (int j = 0; j < size; ++j) {
i = skip(tokens, i);
}
return i;
}
static int parseBaseSettings(jsmntok_t const* tokens, int i, const char* jsonChunk, Settings* out) {
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int size = tokens[i++].size;
for (int j = 0; j < size; ++j) {
// Current token is Key
for (int j = 0; j < size; ++j, i += 2) {
std::stringstream dk(STR(tokens[i], jsonChunk));
std::string token;
std::string prefix;
@@ -113,8 +103,6 @@ static int parseBaseSettings(jsmntok_t const* tokens, int i, const char* jsonChu
prefix += "{ \"" + token + "\": ";
depth++;
}
// Next token is Value
std::string json = prefix + STR(tokens[i + 1], jsonChunk);
for (int d = 0; d < depth; d++) { json += " } "; }
if (VERBOSE) {
@@ -124,11 +112,6 @@ static int parseBaseSettings(jsmntok_t const* tokens, int i, const char* jsonChu
// Now that we have a complete JSON string, apply this property change.
JsonSerializer serializer;
serializer.readJson(json.c_str(), json.size(), out);
// Advance past Key
i++;
// Advance past Value (recursively)
i = skip(tokens, i);
}
return i;
}
+103 -410
View File
@@ -37,7 +37,6 @@
using namespace utils;
namespace filament::viewer {
std::string_view to_string(color::ColorSpace const& colorspace) noexcept {
using namespace color;
if (colorspace == Rec709-Linear-D65) {
@@ -68,7 +67,6 @@ int parse(jsmntok_t const* tokens, int i) {
return i;
}
using CGQL = filament::ColorGrading::QualityLevel;
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, CGQL* out) {
@@ -108,35 +106,6 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, color::C
return i + 1;
}
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, CameraProjection* out) {
if (0 == compare(tokens[i], jsonChunk, "PERSPECTIVE")) {
*out = CameraProjection::PERSPECTIVE;
} else if (0 == compare(tokens[i], jsonChunk, "ORTHO")) {
*out = CameraProjection::ORTHO;
} else {
slog.w << "Invalid CameraProjection: '" << STR(tokens[i], jsonChunk) << "'" << io::endl;
}
return i + 1;
}
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, LightManager::Type* out) {
if (0 == compare(tokens[i], jsonChunk, "SUN")) {
*out = LightManager::Type::SUN;
} else if (0 == compare(tokens[i], jsonChunk, "DIRECTIONAL")) {
*out = LightManager::Type::DIRECTIONAL;
} else if (0 == compare(tokens[i], jsonChunk, "POINT")) {
*out = LightManager::Type::POINT;
} else if (0 == compare(tokens[i], jsonChunk, "FOCUSED_SPOT")) {
*out = LightManager::Type::FOCUSED_SPOT;
} else if (0 == compare(tokens[i], jsonChunk, "SPOT")) {
*out = LightManager::Type::SPOT;
} else {
slog.w << "Invalid Light Type: '" << STR(tokens[i], jsonChunk) << "'" << io::endl;
}
return i + 1;
}
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, GenericToneMapperSettings* out) {
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int size = tokens[i++].size;
@@ -336,12 +305,6 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ViewSett
i = parse(tokens, i + 1, jsonChunk, &out->postProcessingEnabled);
} else if (compare(tok, jsonChunk, "stereoscopicOptions") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->stereoscopicOptions);
} else if (compare(tok, jsonChunk, "blendMode") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->blendMode);
} else if (compare(tok, jsonChunk, "stencilBufferEnabled") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->stencilBufferEnabled);
} else if (compare(tok, jsonChunk, "visibleLayers") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->visibleLayers);
} else {
slog.w << "Invalid view setting key: '" << STR(tok, jsonChunk) << "'" << io::endl;
i = parse(tokens, i + 1);
@@ -491,220 +454,9 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk,
return i;
}
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, LightDefinition* out) {
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int size = tokens[i++].size;
for (int j = 0; j < size; ++j) {
const jsmntok_t tok = tokens[i];
CHECK_KEY(tok);
if (compare(tok, jsonChunk, "type") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->type);
} else if (compare(tok, jsonChunk, "position") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->position);
} else if (compare(tok, jsonChunk, "direction") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->direction);
} else if (compare(tok, jsonChunk, "color") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->color);
} else if (compare(tok, jsonChunk, "intensity") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->intensity);
} else if (compare(tok, jsonChunk, "falloff") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->falloff);
} else if (compare(tok, jsonChunk, "spotInner") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->spotInner);
} else if (compare(tok, jsonChunk, "spotOuter") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->spotOuter);
} else if (compare(tok, jsonChunk, "spotOuter") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->spotOuter);
} else if (compare(tok, jsonChunk, "sunHaloSize") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->sunHaloSize);
} else if (compare(tok, jsonChunk, "sunHaloFalloff") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->sunHaloFalloff);
} else if (compare(tok, jsonChunk, "sunAngularRadius") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->sunAngularRadius);
} else if (compare(tok, jsonChunk, "castShadows") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->castShadows);
} else if (compare(tok, jsonChunk, "shadowOptions") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->shadowOptions);
} else {
slog.w << "Invalid light definition key: '" << STR(tok, jsonChunk) << "'" << io::endl;
i = parse(tokens, i + 1);
}
if (i < 0) {
slog.e << "Invalid light definition value: '" << STR(tok, jsonChunk) << "'" << io::endl;
return i;
}
}
return i;
}
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk,
std::vector<LightDefinition>* out) {
CHECK_TOKTYPE(tokens[i], JSMN_ARRAY);
int size = tokens[i++].size;
out->resize(size);
for (int j = 0; j < size; ++j) {
i = parse(tokens, i, jsonChunk, &(*out)[j]);
}
return i;
}
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, CameraSettings* out) {
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int size = tokens[i++].size;
for (int j = 0; j < size; ++j) {
const jsmntok_t tok = tokens[i];
CHECK_KEY(tok);
if (compare(tok, jsonChunk, "center") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->center);
} else if (compare(tok, jsonChunk, "lookAt") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->lookAt);
} else if (compare(tok, jsonChunk, "up") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->up);
} else if (compare(tok, jsonChunk, "horizontalFov") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->horizontalFov);
} else if (compare(tok, jsonChunk, "near") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->near);
} else if (compare(tok, jsonChunk, "far") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->far);
} else if (compare(tok, jsonChunk, "focalLength") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->focalLength);
} else if (compare(tok, jsonChunk, "projection") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->projection);
} else if (compare(tok, jsonChunk, "enabled") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->enabled);
} else if (compare(tok, jsonChunk, "scaling") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->scaling);
} else if (compare(tok, jsonChunk, "shift") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->shift);
} else if (compare(tok, jsonChunk, "aperture") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->aperture);
} else if (compare(tok, jsonChunk, "shutterSpeed") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->shutterSpeed);
} else if (compare(tok, jsonChunk, "sensitivity") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->sensitivity);
} else if (compare(tok, jsonChunk, "focusDistance") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->focusDistance);
} else if (compare(tok, jsonChunk, "eyeOcularDistance") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->eyeOcularDistance);
} else if (compare(tok, jsonChunk, "eyeToeIn") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->eyeToeIn);
} else {
slog.w << "Invalid camera options key: '" << STR(tok, jsonChunk) << "'" << io::endl;
i = parse(tokens, i + 1);
}
if (i < 0) {
slog.e << "Invalid camera options value: '" << STR(tok, jsonChunk) << "'" << io::endl;
return i;
}
}
return i;
}
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, AnimationSettings* out) {
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int size = tokens[i++].size;
for (int j = 0; j < size; ++j) {
const jsmntok_t tok = tokens[i];
CHECK_KEY(tok);
if (compare(tok, jsonChunk, "enabled") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->enabled);
} else if (compare(tok, jsonChunk, "time") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->time);
} else if (compare(tok, jsonChunk, "speed") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->speed);
} else {
slog.w << "Invalid animation options key: '" << STR(tok, jsonChunk) << "'" << io::endl;
i = parse(tokens, i + 1);
}
if (i < 0) {
slog.e << "Invalid animation options value: '" << STR(tok, jsonChunk) << "'"
<< io::endl;
return i;
}
}
return i;
}
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk,
Renderer::ClearOptions* out) {
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int size = tokens[i++].size;
for (int j = 0; j < size; ++j) {
const jsmntok_t tok = tokens[i];
CHECK_KEY(tok);
if (compare(tok, jsonChunk, "clearColor") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->clearColor);
} else if (compare(tok, jsonChunk, "clear") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->clear);
} else if (compare(tok, jsonChunk, "discard") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->discard);
} else if (compare(tok, jsonChunk, "clearStencil") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->clearStencil);
} else {
slog.w << "Invalid clear options key: '" << STR(tok, jsonChunk) << "'" << io::endl;
i = parse(tokens, i + 1);
}
if (i < 0) {
slog.e << "Invalid clear options value: '" << STR(tok, jsonChunk) << "'" << io::endl;
return i;
}
}
return i;
}
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk,
Renderer::FrameRateOptions* out) {
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int const size = tokens[i++].size;
for (int j = 0; j < size; ++j) {
const jsmntok_t tok = tokens[i];
CHECK_KEY(tok);
if (compare(tok, jsonChunk, "headRoomRatio") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->headRoomRatio);
} else if (compare(tok, jsonChunk, "history") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->history);
} else if (compare(tok, jsonChunk, "scaleRate") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->scaleRate);
} else {
slog.w << "Invalid frame rate options key: '" << STR(tok, jsonChunk) << "'" << io::endl;
i = parse(tokens, i + 1);
}
if (i < 0) {
slog.e << "Invalid frame rate options value: '" << STR(tok, jsonChunk) << "'"
<< io::endl;
return i;
}
}
return i;
}
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, RenderSettings* out) {
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int const size = tokens[i++].size;
for (int j = 0; j < size; ++j) {
const jsmntok_t tok = tokens[i];
CHECK_KEY(tok);
if (compare(tok, jsonChunk, "clearOptions") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->clearOptions);
} else if (compare(tok, jsonChunk, "frameRateOptions") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->frameRateOptions);
} else {
slog.w << "Invalid render settings key: '" << STR(tok, jsonChunk) << "'" << io::endl;
i = parse(tokens, i + 1);
}
if (i < 0) {
slog.e << "Invalid render settings value: '" << STR(tok, jsonChunk) << "'" << io::endl;
return i;
}
}
return i;
}
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, LightSettings* out) {
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int const size = tokens[i++].size;
int size = tokens[i++].size;
for (int j = 0; j < size; ++j) {
const jsmntok_t tok = tokens[i];
CHECK_KEY(tok);
@@ -712,16 +464,26 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, LightSet
i = parse(tokens, i + 1, jsonChunk, &out->enableShadows);
} else if (compare(tok, jsonChunk, "enableSunlight") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->enableSunlight);
} else if (compare(tok, jsonChunk, "shadowOptions") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->shadowOptions);
} else if (compare(tok, jsonChunk, "softShadowOptions") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->softShadowOptions);
} else if (compare(tok, jsonChunk, "sunlight") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->sunlight);
} else if (compare(tok, jsonChunk, "sunlightIntensity") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->sunlightIntensity);
} else if (compare(tok, jsonChunk, "sunlightHaloSize") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->sunlightHaloSize);
} else if (compare(tok, jsonChunk, "sunlightHaloFalloff") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->sunlightHaloFalloff);
} else if (compare(tok, jsonChunk, "sunlightAngularRadius") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->sunlightAngularRadius);
} else if (compare(tok, jsonChunk, "sunlightDirection") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->sunlightDirection);
} else if (compare(tok, jsonChunk, "sunlightColor") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->sunlightColor);
} else if (compare(tok, jsonChunk, "iblIntensity") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->iblIntensity);
} else if (compare(tok, jsonChunk, "iblRotation") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->iblRotation);
} else if (compare(tok, jsonChunk, "lights") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->lights);
} else {
slog.w << "Invalid light setting key: '" << STR(tok, jsonChunk) << "'" << io::endl;
i = parse(tokens, i + 1);
@@ -736,22 +498,40 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, LightSet
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ViewerOptions* out) {
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int const size = tokens[i++].size;
int size = tokens[i++].size;
for (int j = 0; j < size; ++j) {
const jsmntok_t tok = tokens[i];
CHECK_KEY(tok);
if (compare(tok, jsonChunk, "groundShadowStrength") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->groundShadowStrength);
if (compare(tok, jsonChunk, "cameraAperture") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->cameraAperture);
} else if (compare(tok, jsonChunk, "cameraSpeed") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->cameraSpeed);
} else if (compare(tok, jsonChunk, "cameraISO") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->cameraISO);
} else if (compare(tok, jsonChunk, "cameraNear") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->cameraNear);
} else if (compare(tok, jsonChunk, "cameraFar") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->cameraFar);
} else if (compare(tok, jsonChunk, "cameraEyeOcularDistance") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->cameraEyeOcularDistance);
} else if (compare(tok, jsonChunk, "cameraEyeToeIn") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->cameraEyeToeIn);
} else if (compare(tok, jsonChunk, "groundShadowStrength") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->groundShadowStrength);
} else if (compare(tok, jsonChunk, "groundPlaneEnabled") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->groundPlaneEnabled);
i = parse(tokens, i + 1, jsonChunk, &out->groundPlaneEnabled);
} else if (compare(tok, jsonChunk, "skyboxEnabled") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->skyboxEnabled);
i = parse(tokens, i + 1, jsonChunk, &out->skyboxEnabled);
} else if (compare(tok, jsonChunk, "backgroundColor") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->backgroundColor);
i = parse(tokens, i + 1, jsonChunk, &out->backgroundColor);
} else if (compare(tok, jsonChunk, "cameraFocalLength") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->cameraFocalLength);
} else if (compare(tok, jsonChunk, "cameraFocusDistance") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->cameraFocusDistance);
} else if (compare(tok, jsonChunk, "autoInstancingEnabled") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->autoInstancingEnabled);
i = parse(tokens, i + 1, jsonChunk, &out->autoInstancingEnabled);
} else if (compare(tok, jsonChunk, "autoScaleEnabled") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->autoScaleEnabled);
i = parse(tokens, i + 1, jsonChunk, &out->autoScaleEnabled);
} else {
slog.w << "Invalid viewer options key: '" << STR(tok, jsonChunk) << "'" << io::endl;
i = parse(tokens, i + 1);
@@ -766,7 +546,7 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ViewerOp
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, DebugOptions* out) {
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int const size = tokens[i++].size;
int size = tokens[i++].size;
for (int j = 0; j < size; ++j) {
const jsmntok_t tok = tokens[i];
CHECK_KEY(tok);
@@ -786,7 +566,7 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, DebugOpt
int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, Settings* out) {
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int const size = tokens[i++].size;
int size = tokens[i++].size;
for (int j = 0; j < size; ++j) {
const jsmntok_t tok = tokens[i];
CHECK_KEY(tok);
@@ -798,12 +578,6 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, Settings* out)
i = parse(tokens, i + 1, jsonChunk, &out->lighting);
} else if (compare(tok, jsonChunk, "viewer") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->viewer);
} else if (compare(tok, jsonChunk, "camera") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->camera);
} else if (compare(tok, jsonChunk, "animation") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->animation);
} else if (compare(tok, jsonChunk, "render") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->render);
} else if (compare(tok, jsonChunk, "debug") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->debug);
} else {
@@ -838,9 +612,6 @@ void applySettings(Engine* engine, const ViewSettings& settings, View* dest) {
dest->setGuardBandOptions(settings.guardBand);
dest->setStereoscopicOptions(settings.stereoscopicOptions);
dest->setPostProcessingEnabled(settings.postProcessingEnabled);
dest->setBlendMode(static_cast<View::BlendMode>(settings.blendMode));
dest->setStencilBufferEnabled(settings.stencilBufferEnabled);
dest->setVisibleLayers(settings.visibleLayers, settings.visibleLayers);
}
template <typename T>
@@ -865,14 +636,14 @@ void applySettings(Engine* engine, const LightSettings& settings, IndirectLight*
} else {
scene->remove(sunlight);
}
lm->setIntensity(light, settings.sunlight.intensity);
lm->setSunHaloSize(light, settings.sunlight.sunHaloSize);
lm->setSunHaloFalloff(light, settings.sunlight.sunHaloFalloff);
lm->setSunAngularRadius(light, settings.sunlight.sunAngularRadius);
lm->setDirection(light, normalize(settings.sunlight.direction));
lm->setColor(light, settings.sunlight.color);
lm->setShadowCaster(light, settings.sunlight.castShadows && settings.enableShadows);
lm->setShadowOptions(light, settings.sunlight.shadowOptions);
lm->setIntensity(light, settings.sunlightIntensity);
lm->setSunHaloSize(light, settings.sunlightHaloSize);
lm->setSunHaloFalloff(light, settings.sunlightHaloFalloff);
lm->setSunAngularRadius(light, settings.sunlightAngularRadius);
lm->setDirection(light, normalize(settings.sunlightDirection));
lm->setColor(light, settings.sunlightColor);
lm->setShadowCaster(light, settings.enableShadows);
lm->setShadowOptions(light, settings.shadowOptions);
}
if (ibl) {
ibl->setIntensity(settings.iblIntensity);
@@ -881,13 +652,8 @@ void applySettings(Engine* engine, const LightSettings& settings, IndirectLight*
for (size_t i = 0; i < sceneLightCount; i++) {
auto const li = lm->getInstance(sceneLights[i]);
if (li) {
// Note: We don't have individual settings for these "scene lights" (IBL maps etc)
// in the current structure unless we map them by index or name.
// For now, we only enforce the global shadow toggle.
lm->setShadowCaster(li, settings.enableShadows);
// We removed the global shadowOptions. If they need defaults, they should be set elsewhere or
// we should keep a "defaultShadowOptions" but for now assuming individual control via automation
// or existing defaults.
lm->setShadowOptions(li, settings.shadowOptions);
}
}
view->setSoftShadowOptions(settings.softShadowOptions);
@@ -910,57 +676,36 @@ void applySettings(Engine* engine, const ViewerOptions& settings, Camera* camera
if (skybox) {
skybox->setLayerMask(0xff, settings.skyboxEnabled ? 0xff : 0x00);
}
engine->setAutomaticInstancingEnabled(settings.autoInstancingEnabled);
}
if (camera) {
camera->setExposure(
settings.cameraAperture,
1.0f / settings.cameraSpeed,
settings.cameraISO);
void applySettings(Engine* engine, const DebugOptions& settings, Renderer* renderer) {
if (!renderer->getFrameToSkipCount()) {
renderer->skipNextFrames(settings.skipFrames);
camera->setFocusDistance(settings.cameraFocusDistance);
}
}
void applySettings(Engine* engine, const CameraSettings& settings, Camera* camera,
double aspectRatio) {
camera->setExposure(settings.aperture, 1.0f / settings.shutterSpeed, settings.sensitivity);
camera->setFocusDistance(settings.focusDistance);
engine->setAutomaticInstancingEnabled(settings.autoInstancingEnabled);
// Eyes are rendered from left-to-right, i.e., eye 0 is rendered to the left side of the
// window.
// For testing, we want to render a side-by-side layout so users can view with
// "cross-eyed" stereo.
// For cross-eyed stereo, Eye 0 is really the RIGHT eye, while Eye 1 is the LEFT eye.
double const od = settings.eyeOcularDistance;
double const toeIn = settings.eyeToeIn;
size_t const eyeCount = engine->getConfig().stereoscopicEyeCount;
double3 const up = double3(0.0, 1.0, 0.0);
mat4 const rightEye = mat4::translation(double3{ od, 0.0, 0.0 }) * mat4::rotation(toeIn, up);
mat4 const leftEye = mat4::translation(double3{ -od, 0.0, 0.0 }) * mat4::rotation(-toeIn, up);
mat4 const modelMatrices[2] = { rightEye, leftEye };
for (size_t i = 0; i < eyeCount; i++) {
const auto od = settings.cameraEyeOcularDistance;
const auto toeIn = settings.cameraEyeToeIn;
const auto eyeCount = engine->getConfig().stereoscopicEyeCount;
const double3 up = double3(0.0, 1.0, 0.0);
const mat4 rightEye = mat4::translation(double3{ od, 0.0, 0.0}) * mat4::rotation( toeIn, up);
const mat4 leftEye = mat4::translation(double3{-od, 0.0, 0.0}) * mat4::rotation(-toeIn, up);
const mat4 modelMatrices[2] = { rightEye, leftEye };
for (int i = 0; i < eyeCount; i++) {
camera->setEyeModelMatrix(i, modelMatrices[i % 2]);
}
}
if (settings.enabled && aspectRatio > 0.0) {
camera->lookAt(settings.center, settings.lookAt, settings.up);
if (settings.projection == CameraProjection::PERSPECTIVE) {
if (settings.focalLength > 0.0f) {
camera->setLensProjection(settings.focalLength, aspectRatio, settings.near,
settings.far);
} else {
camera->setProjection(settings.horizontalFov, aspectRatio, settings.near,
settings.far, Camera::Fov::VERTICAL);
}
} else {
// ORTHO
double const left = -settings.scaling.x * aspectRatio;
double const right = settings.scaling.x * aspectRatio;
double const bottom = -settings.scaling.y;
double const top = settings.scaling.y;
camera->setProjection(Camera::Projection::ORTHO, left + settings.shift.x,
right + settings.shift.x, bottom + settings.shift.y, top + settings.shift.y,
settings.near, settings.far);
}
void applySettings(Engine* engine, const DebugOptions& settings, Renderer* renderer) {
if (!renderer->getFrameToSkipCount()) {
renderer->skipNextFrames(settings.skipFrames);
}
}
@@ -1021,25 +766,6 @@ static std::ostream& operator<<(std::ostream& out, CGQL in) {
return out << "\"INVALID\"";
}
static std::ostream& operator<<(std::ostream& out, CameraProjection in) {
switch (in) {
case CameraProjection::ORTHO: return out << "\"ORTHO\"";
case CameraProjection::PERSPECTIVE: return out << "\"PERSPECTIVE\"";
}
return out << "\"INVALID\"";
}
static std::ostream& operator<<(std::ostream& out, LightManager::Type in) {
switch (in) {
case LightManager::Type::SUN: return out << "\"SUN\"";
case LightManager::Type::DIRECTIONAL: return out << "\"DIRECTIONAL\"";
case LightManager::Type::POINT: return out << "\"POINT\"";
case LightManager::Type::FOCUSED_SPOT: return out << "\"FOCUSED_SPOT\"";
case LightManager::Type::SPOT: return out << "\"SPOT\"";
}
return out << "\"INVALID\"";
}
static std::ostream& operator<<(std::ostream& out, ToneMapping in) {
switch (in) {
case ToneMapping::LINEAR: return out << "\"LINEAR\"";
@@ -1181,74 +907,41 @@ static std::ostream& operator<<(std::ostream& out, const MaterialSettings& in) {
return out << result;
}
static std::ostream& operator<<(std::ostream& out, const LightDefinition& in) {
return out << "{\n"
<< "\"type\": " << in.type << ",\n"
<< "\"position\": " << in.position << ",\n"
<< "\"direction\": " << in.direction << ",\n"
<< "\"color\": " << in.color << ",\n"
<< "\"intensity\": " << in.intensity << ",\n"
<< "\"falloff\": " << in.falloff << ",\n"
<< "\"spotInner\": " << in.spotInner << ",\n"
<< "\"spotOuter\": " << in.spotOuter << ",\n"
<< "\"sunHaloSize\": " << in.sunHaloSize << ",\n"
<< "\"sunHaloFalloff\": " << in.sunHaloFalloff << ",\n"
<< "\"sunAngularRadius\": " << in.sunAngularRadius << ",\n"
<< "\"castShadows\": " << to_string(in.castShadows) << ",\n"
<< "\"shadowOptions\": " << in.shadowOptions << "\n"
<< "}";
}
static std::ostream& operator<<(std::ostream& out, const LightSettings& in) {
out << "{\n"
return out << "{\n"
<< "\"enableShadows\": " << to_string(in.enableShadows) << ",\n"
<< "\"enableSunlight\": " << to_string(in.enableSunlight) << ",\n"
<< "\"shadowOptions\": " << (in.shadowOptions) << ",\n"
<< "\"softShadowOptions\": " << (in.softShadowOptions) << ",\n"
<< "\"sunlight\": " << (in.sunlight) << ",\n"
<< "\"sunlightIntensity\": " << (in.sunlightIntensity) << ",\n"
<< "\"sunlightHaloSize\": " << (in.sunlightHaloSize) << ",\n"
<< "\"sunlightHaloFalloff\": " << (in.sunlightHaloFalloff) << ",\n"
<< "\"sunlightAngularRadius\": " << (in.sunlightAngularRadius) << ",\n"
<< "\"sunlightDirection\": " << (in.sunlightDirection) << ",\n"
<< "\"sunlightColor\": " << (in.sunlightColor) << ",\n"
<< "\"iblIntensity\": " << (in.iblIntensity) << ",\n"
<< "\"iblRotation\": " << (in.iblRotation) << ",\n"
<< "\"lights\": [\n";
for (size_t i = 0; i < in.lights.size(); ++i) {
out << in.lights[i];
if (i < in.lights.size() - 1) {
out << ",";
}
out << "\n";
}
return out << "]\n" << "}";
}
static std::ostream& operator<<(std::ostream& out, const CameraSettings& in) {
return out << "{\n"
<< "\"center\": " << in.center << ",\n"
<< "\"lookAt\": " << in.lookAt << ",\n"
<< "\"up\": " << in.up << ",\n"
<< "\"horizontalFov\": " << in.horizontalFov << ",\n"
<< "\"near\": " << in.near << ",\n"
<< "\"far\": " << in.far << ",\n"
<< "\"focalLength\": " << in.focalLength << ",\n"
<< "\"aperture\": " << in.aperture << ",\n"
<< "\"shutterSpeed\": " << in.shutterSpeed << ",\n"
<< "\"sensitivity\": " << in.sensitivity << ",\n"
<< "\"focusDistance\": " << in.focusDistance << ",\n"
<< "\"eyeOcularDistance\": " << in.eyeOcularDistance << ",\n"
<< "\"eyeToeIn\": " << in.eyeToeIn << ",\n"
<< "\"projection\": " << in.projection << ",\n"
<< "\"enabled\": " << to_string(in.enabled) << ",\n"
<< "\"scaling\": " << in.scaling << ",\n"
<< "\"shift\": " << in.shift << "\n"
<< "}";
<< "\"iblRotation\": " << (in.iblRotation) << "\n"
<< "}";
}
static std::ostream& operator<<(std::ostream& out, const ViewerOptions& in) {
return out << "{\n"
<< "\"groundShadowStrength\": " << (in.groundShadowStrength) << ",\n"
<< "\"groundPlaneEnabled\": " << to_string(in.groundPlaneEnabled) << ",\n"
<< "\"skyboxEnabled\": " << to_string(in.skyboxEnabled) << ",\n"
<< "\"backgroundColor\": " << (in.backgroundColor) << ",\n"
<< "\"autoInstancingEnabled\": " << to_string(in.autoInstancingEnabled) << ",\n"
<< "\"autoScaleEnabled\": " << to_string(in.autoScaleEnabled) << "\n"
<< "}";
<< "\"cameraAperture\": " << (in.cameraAperture) << ",\n"
<< "\"cameraSpeed\": " << (in.cameraSpeed) << ",\n"
<< "\"cameraISO\": " << (in.cameraISO) << ",\n"
<< "\"cameraNear\": " << (in.cameraNear) << ",\n"
<< "\"cameraFar\": " << (in.cameraFar) << ",\n"
<< "\"cameraEyeOcularDistance\": " << (in.cameraEyeOcularDistance) << ",\n"
<< "\"cameraEyeToeIn\": " << (in.cameraEyeToeIn) << ",\n"
<< "\"groundShadowStrength\": " << (in.groundShadowStrength) << ",\n"
<< "\"groundPlaneEnabled\": " << to_string(in.groundPlaneEnabled) << ",\n"
<< "\"skyboxEnabled\": " << to_string(in.skyboxEnabled) << ",\n"
<< "\"backgroundColor\": " << (in.backgroundColor) << ",\n"
<< "\"cameraFocalLength\": " << (in.cameraFocalLength) << ",\n"
<< "\"cameraFocusDistance\": " << (in.cameraFocusDistance) << ",\n"
<< "\"autoInstancingEnabled\": " << to_string(in.autoInstancingEnabled) << ",\n"
<< "\"autoScaleEnabled\": " << to_string(in.autoScaleEnabled) << "\n"
<< "}";
}
static std::ostream& operator<<(std::ostream& out, const DebugOptions& in) {
@@ -1290,12 +983,12 @@ static std::ostream& operator<<(std::ostream& out, const ViewSettings& in) {
static std::ostream& operator<<(std::ostream& out, const Settings& in) {
return out << "{\n"
<< "\"view\": " << (in.view) << ",\n"
<< "\"material\": " << (in.material) << ",\n"
<< "\"lighting\": " << (in.lighting) << ",\n"
<< "\"viewer\": " << (in.viewer) << ",\n"
<< "\"camera\": " << (in.camera) << ",\n"
<< "\"debug\": " << (in.debug) << "}";
<< "\"view\": " << (in.view) << ",\n"
<< "\"material\": " << (in.material) << ",\n"
<< "\"lighting\": " << (in.lighting) << ",\n"
<< "\"viewer\": " << (in.viewer) << ",\n"
<< "\"debug\": " << (in.debug)
<< "}";
}
bool GenericToneMapperSettings::operator==(const GenericToneMapperSettings& rhs) const {
+44 -46
View File
@@ -429,13 +429,13 @@ ViewerGui::ViewerGui(filament::Engine* engine, filament::Scene* scene, filament:
using namespace filament;
LightManager::Builder(LightManager::Type::SUN)
.color(mSettings.lighting.sunlight.color)
.intensity(mSettings.lighting.sunlight.intensity)
.direction(normalize(mSettings.lighting.sunlight.direction))
.color(mSettings.lighting.sunlightColor)
.intensity(mSettings.lighting.sunlightIntensity)
.direction(normalize(mSettings.lighting.sunlightDirection))
.castShadows(true)
.sunAngularRadius(mSettings.lighting.sunlight.sunAngularRadius)
.sunHaloSize(mSettings.lighting.sunlight.sunHaloSize)
.sunHaloFalloff(mSettings.lighting.sunlight.sunHaloFalloff)
.sunAngularRadius(mSettings.lighting.sunlightAngularRadius)
.sunHaloSize(mSettings.lighting.sunlightHaloSize)
.sunHaloFalloff(mSettings.lighting.sunlightHaloFalloff)
.build(*engine, mSunlight);
if (mSettings.lighting.enableSunlight) {
mScene->addEntity(mSunlight);
@@ -510,9 +510,9 @@ void ViewerGui::setIndirectLight(filament::IndirectLight* ibl,
bool const dIsValid = std::none_of(std::begin(d.v), std::end(d.v), is_not_a_number);
bool const cIsValid = std::none_of(std::begin(c.v), std::end(c.v), is_not_a_number);
if (dIsValid && cIsValid) {
mSettings.lighting.sunlight.direction = d;
mSettings.lighting.sunlight.color = c.rgb;
mSettings.lighting.sunlight.intensity = c[3] * ibl->getIntensity();
mSettings.lighting.sunlightDirection = d;
mSettings.lighting.sunlightColor = c.rgb;
mSettings.lighting.sunlightIntensity = c[3] * ibl->getIntensity();
}
}
}
@@ -945,31 +945,31 @@ void ViewerGui::updateUserInterface() {
}
if (ImGui::CollapsingHeader("Sunlight")) {
ImGui::Checkbox("Enable sunlight", &light.enableSunlight);
ImGui::SliderFloat("Sun intensity", &light.sunlight.intensity, 0.0f, 150000.0f);
ImGui::SliderFloat("Halo size", &light.sunlight.sunHaloSize, 1.01f, 40.0f);
ImGui::SliderFloat("Halo falloff", &light.sunlight.sunHaloFalloff, 4.0f, 1024.0f);
ImGui::SliderFloat("Sun radius", &light.sunlight.sunAngularRadius, 0.1f, 10.0f);
ImGuiExt::DirectionWidget("Sun direction", light.sunlight.direction.v);
ImGui::SliderFloat("Shadow Far", &light.sunlight.shadowOptions.shadowFar, 0.0f,
mSettings.camera.far);
ImGui::SliderFloat("Sun intensity", &light.sunlightIntensity, 0.0f, 150000.0f);
ImGui::SliderFloat("Halo size", &light.sunlightHaloSize, 1.01f, 40.0f);
ImGui::SliderFloat("Halo falloff", &light.sunlightHaloFalloff, 4.0f, 1024.0f);
ImGui::SliderFloat("Sun radius", &light.sunlightAngularRadius, 0.1f, 10.0f);
ImGuiExt::DirectionWidget("Sun direction", light.sunlightDirection.v);
ImGui::SliderFloat("Shadow Far", &light.shadowOptions.shadowFar, 0.0f,
mSettings.viewer.cameraFar);
if (ImGui::CollapsingHeader("Shadow direction")) {
float3 shadowDirection = light.sunlight.shadowOptions.transform * light.sunlight.direction;
float3 shadowDirection = light.shadowOptions.transform * light.sunlightDirection;
ImGuiExt::DirectionWidget("Shadow direction", shadowDirection.v);
light.sunlight.shadowOptions.transform = normalize(quatf{
cross(light.sunlight.direction, shadowDirection),
sqrt(length2(light.sunlight.direction) * length2(shadowDirection))
+ dot(light.sunlight.direction, shadowDirection)
light.shadowOptions.transform = normalize(quatf{
cross(light.sunlightDirection, shadowDirection),
sqrt(length2(light.sunlightDirection) * length2(shadowDirection))
+ dot(light.sunlightDirection, shadowDirection)
});
}
}
if (ImGui::CollapsingHeader("Shadows")) {
ImGui::Checkbox("Enable shadows", &light.enableShadows);
int mapSize = light.sunlight.shadowOptions.mapSize;
int mapSize = light.shadowOptions.mapSize;
ImGui::SliderInt("Shadow map size", &mapSize, 32, 1024);
light.sunlight.shadowOptions.mapSize = mapSize;
ImGui::Checkbox("Stable Shadows", &light.sunlight.shadowOptions.stable);
ImGui::Checkbox("Enable LiSPSM", &light.sunlight.shadowOptions.lispsm);
light.shadowOptions.mapSize = mapSize;
ImGui::Checkbox("Stable Shadows", &light.shadowOptions.stable);
ImGui::Checkbox("Enable LiSPSM", &light.shadowOptions.lispsm);
int shadowType = (int)mSettings.view.shadowType;
ImGui::Combo("Shadow type", &shadowType, "PCF\0VSM\0DPCF\0PCSS\0PCFd\0\0");
@@ -977,7 +977,7 @@ void ViewerGui::updateUserInterface() {
if (mSettings.view.shadowType == ShadowType::VSM) {
ImGui::Checkbox("High precision", &mSettings.view.vsmShadowOptions.highPrecision);
ImGui::Checkbox("ELVSM", &mSettings.lighting.sunlight.shadowOptions.vsm.elvsm);
ImGui::Checkbox("ELVSM", &mSettings.lighting.shadowOptions.vsm.elvsm);
char label[32];
snprintf(label, 32, "%d", 1 << mVsmMsaaSamplesLog2);
ImGui::SliderInt("VSM MSAA samples", &mVsmMsaaSamplesLog2, 0, 3, label);
@@ -989,7 +989,7 @@ void ViewerGui::updateUserInterface() {
ImGui::SliderInt("VSM anisotropy", &vsmAnisotropy, 0, 3, label);
mSettings.view.vsmShadowOptions.anisotropy = vsmAnisotropy;
ImGui::Checkbox("VSM mipmapping", &mSettings.view.vsmShadowOptions.mipmapping);
ImGui::SliderFloat("VSM blur", &light.sunlight.shadowOptions.vsm.blurWidth, 0.0f, 125.0f);
ImGui::SliderFloat("VSM blur", &light.shadowOptions.vsm.blurWidth, 0.0f, 125.0f);
// These are not very useful in practice (defaults are good), but we keep them here for debugging
//ImGui::SliderFloat("VSM exponent", &mSettings.view.vsmShadowOptions.exponent, 0.0, 6.0f);
@@ -1000,15 +1000,15 @@ void ViewerGui::updateUserInterface() {
ImGui::SliderFloat("Penumbra Ratio scale", &light.softShadowOptions.penumbraRatioScale, 1.0f, 100.0f);
}
int shadowCascades = light.sunlight.shadowOptions.shadowCascades;
int shadowCascades = light.shadowOptions.shadowCascades;
ImGui::SliderInt("Cascades", &shadowCascades, 1, 4);
ImGui::Checkbox("Debug cascades",
debug.getPropertyAddress<bool>("d.shadowmap.visualize_cascades"));
ImGui::Checkbox("Enable contact shadows", &light.sunlight.shadowOptions.screenSpaceContactShadows);
ImGui::SliderFloat("Split pos 0", &light.sunlight.shadowOptions.cascadeSplitPositions[0], 0.0f, 1.0f);
ImGui::SliderFloat("Split pos 1", &light.sunlight.shadowOptions.cascadeSplitPositions[1], 0.0f, 1.0f);
ImGui::SliderFloat("Split pos 2", &light.sunlight.shadowOptions.cascadeSplitPositions[2], 0.0f, 1.0f);
light.sunlight.shadowOptions.shadowCascades = shadowCascades;
ImGui::Checkbox("Enable contact shadows", &light.shadowOptions.screenSpaceContactShadows);
ImGui::SliderFloat("Split pos 0", &light.shadowOptions.cascadeSplitPositions[0], 0.0f, 1.0f);
ImGui::SliderFloat("Split pos 1", &light.shadowOptions.cascadeSplitPositions[1], 0.0f, 1.0f);
ImGui::SliderFloat("Split pos 2", &light.shadowOptions.cascadeSplitPositions[2], 0.0f, 1.0f);
light.shadowOptions.shadowCascades = shadowCascades;
}
ImGui::Unindent();
}
@@ -1083,12 +1083,12 @@ void ViewerGui::updateUserInterface() {
if (ImGui::CollapsingHeader("Camera")) {
ImGui::Indent();
ImGui::SliderFloat("Focal length (mm)", &mSettings.camera.focalLength, 16.0f, 90.0f);
ImGui::SliderFloat("Aperture", &mSettings.camera.aperture, 1.0f, 32.0f);
ImGui::SliderFloat("Speed (1/s)", &mSettings.camera.shutterSpeed, 1000.0f, 1.0f);
ImGui::SliderFloat("ISO", &mSettings.camera.sensitivity, 25.0f, 6400.0f);
ImGui::SliderFloat("Near", &mSettings.camera.near, 0.001f, 1.0f);
ImGui::SliderFloat("Far", &mSettings.camera.far, 1.0f, 10000.0f);
ImGui::SliderFloat("Focal length (mm)", &mSettings.viewer.cameraFocalLength, 16.0f, 90.0f);
ImGui::SliderFloat("Aperture", &mSettings.viewer.cameraAperture, 1.0f, 32.0f);
ImGui::SliderFloat("Speed (1/s)", &mSettings.viewer.cameraSpeed, 1000.0f, 1.0f);
ImGui::SliderFloat("ISO", &mSettings.viewer.cameraISO, 25.0f, 6400.0f);
ImGui::SliderFloat("Near", &mSettings.viewer.cameraNear, 0.001f, 1.0f);
ImGui::SliderFloat("Far", &mSettings.viewer.cameraFar, 1.0f, 10000.0f);
if (ImGui::CollapsingHeader("DoF")) {
bool dofMedian = mSettings.view.dof.filter == View::DepthOfFieldOptions::Filter::MEDIAN;
@@ -1097,7 +1097,7 @@ void ViewerGui::updateUserInterface() {
if (!dofRingCount) dofRingCount = 5;
if (!dofMaxCoC) dofMaxCoC = 32;
ImGui::Checkbox("Enabled##dofEnabled", &mSettings.view.dof.enabled);
ImGui::SliderFloat("Focus distance", &mSettings.camera.focusDistance, 0.0f, 30.0f);
ImGui::SliderFloat("Focus distance", &mSettings.viewer.cameraFocusDistance, 0.0f, 30.0f);
ImGui::SliderFloat("Blur scale", &mSettings.view.dof.cocScale, 0.1f, 10.0f);
ImGui::SliderFloat("CoC aspect-ratio", &mSettings.view.dof.cocAspectRatio, 0.25f, 4.0f);
ImGui::SliderInt("Ring count", &dofRingCount, 1, 17);
@@ -1162,11 +1162,12 @@ void ViewerGui::updateUserInterface() {
debug.getPropertyAddress<bool>("d.stereo.combine_multiview_images"));
ImGui::Unindent();
#endif
ImGui::SliderFloat("Ocular distance", &mSettings.camera.eyeOcularDistance, 0.0f, 1.0f);
ImGui::SliderFloat("Ocular distance",
&mSettings.viewer.cameraEyeOcularDistance, 0.0f, 1.0f);
float toeInDegrees = mSettings.camera.eyeToeIn / f::PI * 180.0f;
float toeInDegrees = mSettings.viewer.cameraEyeToeIn / f::PI * 180.0f;
ImGui::SliderFloat("Toe in", &toeInDegrees, 0.0f, 30.0, "%.3f°");
mSettings.camera.eyeToeIn = toeInDegrees / 180.0f * f::PI;
mSettings.viewer.cameraEyeToeIn = toeInDegrees / 180.0f * f::PI;
ImGui::Unindent();
#endif
@@ -1184,9 +1185,6 @@ void ViewerGui::updateUserInterface() {
// At this point, all View settings have been modified,
// so we can now push them into the Filament View.
applySettings(mEngine, mSettings.view, mView);
double const aspect =
(double) mView->getViewport().width / (double) mView->getViewport().height;
applySettings(mEngine, mSettings.camera, &mView->getCamera(), aspect);
auto lights = utils::FixedCapacityVector<utils::Entity>::with_capacity(mScene->getEntityCount());
mScene->forEach([&](utils::Entity entity) {
-85
View File
@@ -221,88 +221,3 @@ int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
static const char* JSON_TEST_CAMERA = R"TXT(
{
"camera": {
"aperture": 4.0,
"shutterSpeed": 200,
"sensitivity": 400,
"focalLength": 50,
"focusDistance": 5.0,
"projection": "ORTHO",
"near": 0.5,
"far": 1000.0,
"scaling": [0.5, 0.6],
"shift": [0.1, 0.2]
}
}
)TXT";
TEST_F(ViewSettingsTest, JsonTestCamera) {
JsonSerializer serializer;
Settings settings;
ASSERT_TRUE(serializer.readJson(JSON_TEST_CAMERA, strlen(JSON_TEST_CAMERA), &settings));
EXPECT_FLOAT_EQ(settings.camera.aperture, 4.0f);
EXPECT_FLOAT_EQ(settings.camera.shutterSpeed, 200.0f);
EXPECT_FLOAT_EQ(settings.camera.sensitivity, 400.0f);
EXPECT_FLOAT_EQ(settings.camera.focalLength, 50.0f);
EXPECT_FLOAT_EQ(settings.camera.focusDistance, 5.0f);
EXPECT_EQ(settings.camera.projection, CameraProjection::ORTHO);
EXPECT_FLOAT_EQ(settings.camera.near, 0.5f);
EXPECT_FLOAT_EQ(settings.camera.far, 1000.0f);
EXPECT_FLOAT_EQ(settings.camera.scaling.x, 0.5f);
EXPECT_FLOAT_EQ(settings.camera.scaling.y, 0.6f);
EXPECT_FLOAT_EQ(settings.camera.shift.x, 0.1f);
EXPECT_FLOAT_EQ(settings.camera.shift.y, 0.2f);
}
static const char* JSON_TEST_LIGHTS = R"TXT(
{
"lighting": {
"enableSunlight": false,
"lights": [
{
"type": "POINT",
"position": [1.0, 2.0, 3.0],
"color": [1.0, 0.0, 0.0],
"intensity": 1000.0,
"falloff": 5.0,
"castShadows": true
},
{
"type": "SPOT",
"direction": [0.0, -1.0, 0.0],
"spotInner": 0.5,
"spotOuter": 0.8
}
]
}
}
)TXT";
TEST_F(ViewSettingsTest, JsonTestLights) {
JsonSerializer serializer;
Settings settings;
ASSERT_TRUE(serializer.readJson(JSON_TEST_LIGHTS, strlen(JSON_TEST_LIGHTS), &settings));
EXPECT_FALSE(settings.lighting.enableSunlight);
ASSERT_EQ(settings.lighting.lights.size(), 2);
const auto& light0 = settings.lighting.lights[0];
EXPECT_EQ(light0.type, LightManager::Type::POINT);
EXPECT_FLOAT_EQ(light0.position.x, 1.0f);
EXPECT_FLOAT_EQ(light0.position.y, 2.0f);
EXPECT_FLOAT_EQ(light0.position.z, 3.0f);
EXPECT_FLOAT_EQ(light0.color.r, 1.0f);
EXPECT_FLOAT_EQ(light0.intensity, 1000.0f);
EXPECT_FLOAT_EQ(light0.falloff, 5.0f);
EXPECT_TRUE(light0.castShadows);
const auto& light1 = settings.lighting.lights[1];
EXPECT_EQ(light1.type, LightManager::Type::SPOT);
EXPECT_FLOAT_EQ(light1.direction.y, -1.0f);
EXPECT_FLOAT_EQ(light1.spotInner, 0.5f);
EXPECT_FLOAT_EQ(light1.spotOuter, 0.8f);
}
+8 -34
View File
@@ -781,7 +781,7 @@ int main(int argc, char** argv) {
? AutomationEngine::Options::ExportFormat::PPM
: AutomationEngine::Options::ExportFormat::TIFF;
app.automationEngine->setOptions(options);
app.viewer->getSettings().animation.enabled = false;
app.viewer->stopAnimation();
}
if (!app.settingsFile.empty()) {
@@ -1084,16 +1084,7 @@ int main(int argc, char** argv) {
// Gradually add renderables to the scene as their textures become ready.
app.viewer->populateScene();
auto const& animSettings = app.viewer->getSettings().animation;
if (animSettings.enabled) {
double animTime = now;
if (animSettings.time >= 0.0f) {
animTime = animSettings.time;
} else {
animTime *= animSettings.speed;
}
app.viewer->applyAnimation(animTime);
}
app.viewer->applyAnimation(now);
};
auto resize = [&app](Engine*, View* view) {
@@ -1125,21 +1116,13 @@ int main(int argc, char** argv) {
// Note that this focal length might be different from the slider value because the
// automation engine applies Camera::computeEffectiveFocalLength when DoF is enabled.
float focalLength = app.viewer->getSettings().camera.focalLength;
float const focusDistance = app.viewer->getSettings().camera.focusDistance;
if (app.viewer->getSettings().view.dof.enabled) {
focalLength = Camera::computeEffectiveFocalLength(focalLength / 1000.0,
std::max(0.1f, focusDistance)) *
1000.0;
}
FilamentApp::get().setCameraFocalLength(focalLength);
FilamentApp::get().setCameraNearFar(app.viewer->getSettings().camera.near,
app.viewer->getSettings().camera.far);
FilamentApp::get().setCameraFocalLength(viewerOptions.cameraFocalLength);
FilamentApp::get().setCameraNearFar(viewerOptions.cameraNear, viewerOptions.cameraFar);
size_t const cameraCount = app.asset->getCameraEntityCount();
const size_t cameraCount = app.asset->getCameraEntityCount();
view->setCamera(app.mainCamera);
int const currentCamera = app.viewer->getCurrentCamera();
const int currentCamera = app.viewer->getCurrentCamera();
if (currentCamera > 0 && currentCamera <= cameraCount) {
const utils::Entity* cameras = app.asset->getCameraEntities();
Camera* camera = engine->getCameraComponent(cameras[currentCamera - 1]);
@@ -1167,11 +1150,8 @@ int main(int argc, char** argv) {
Camera& camera = view->getCamera();
Skybox* skybox = scene->getSkybox();
applySettings(engine, app.viewer->getSettings().viewer, &camera, skybox, renderer);
double const aspect =
(double) view->getViewport().width / (double) view->getViewport().height;
applySettings(engine, app.viewer->getSettings().camera, &camera, aspect);
// FIXME: This applySettings() is done here instead of in AutomationEngine.cpp because
// FIMXE: This applySettings() is done here instead of in AutomationEngine.cpp because
// we need access to the Renderer, which AutomationEngine does not provide.
applySettings(engine, app.viewer->getSettings().debug, renderer);
@@ -1203,7 +1183,7 @@ int main(int argc, char** argv) {
}
};
auto postRender = [&app](Engine* engine, View* view, Scene* scene, Renderer* renderer) {
auto postRender = [&app](Engine* engine, View* view, Scene*, Renderer* renderer) {
if (app.screenshot) {
std::ostringstream stringStream;
stringStream << "screenshot" << std::setfill('0') << std::setw(2) << +app.screenshotSeq;
@@ -1222,12 +1202,6 @@ int main(int argc, char** argv) {
.renderer = renderer,
.materials = app.instance->getMaterialInstances(),
.materialCount = app.instance->getMaterialInstanceCount(),
.lightManager = &engine->getLightManager(),
.scene = scene,
.indirectLight = app.viewer->getIndirectLight(),
.sunlight = app.viewer->getSunlight(),
.assetLights = app.asset->getLightEntities(),
.assetLightCount = app.asset->getLightEntityCount(),
};
app.automationEngine->tick(engine, content, ImGui::GetIO().DeltaTime);
};
+2 -2
View File
@@ -7,7 +7,7 @@
"name": "base",
"models": ["Box", "BoxTextured", "FlightHelmet", "lucy"],
"rendering": {
"camera.focalLength": 35.0,
"viewer.cameraFocalLength": 35.0,
"view.postProcessingEnabled": true,
"view.dithering": "NONE"
},
@@ -56,7 +56,7 @@
"description": "transmission",
"apply_presets": ["base", "transmission_models"],
"rendering": {
"camera.focalLength": 52.0
"viewer.cameraFocalLength": 52.0
}
},
{
+1 -1
View File
@@ -12,7 +12,7 @@
"models": ["lucy", "DamagedHelmet"], // [optional] Base name for the gltf file used in the test. For
// example, source files are lucy.glb and DamagedHelmet.gltf
"rendering": { // [required] Rendering settings used in the test. The json format
"camera.focusDistance": 0, // is taken from AutomationSpec in libs/viewer
"viewer.cameraFocusDistance": 0, // is taken from AutomationSpec in libs/viewer
"view.postProcessingEnabled": true
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "filament",
"version": "1.69.1",
"version": "1.68.5",
"description": "Real-time physically based rendering engine",
"main": "filament.js",
"module": "filament.js",