Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ad90d139d | ||
|
|
cf694a3334 | ||
|
|
863379b5b4 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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**]
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+7
-18
@@ -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,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.
|
||||
|
||||
|
||||
+3
-3
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
|
||||
@@ -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 > 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 >= 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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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.
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
```
|
||||
@@ -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,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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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,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",
|
||||
|
||||
Reference in New Issue
Block a user