Merge branch 'google:main' into main

This commit is contained in:
Serge Metral
2025-11-04 11:20:17 -08:00
committed by GitHub
89 changed files with 2092 additions and 602 deletions
+5
View File
@@ -62,6 +62,11 @@ force a clean build by adding the `-c` flag in that case.
To install the libraries and executables in `out/debug/` and `out/release/`, add the `-i` flag.
The script offers more features described by executing `build.sh -h`.
For more specialized options, please also consider the following pages:
- `-d`: [`matdbg`](https://google.github.io/filament/dup/matdbg.html)
- `-t`: [`fgviewer`](https://google.github.io/filament/dup/fgviewer.html)
- `-b` and `-y`: [ASAN/UBSAN builds](https://google.github.io/filament/notes/asan_ubsan.html)
### Filament-specific CMake Options
The following CMake options are boolean options specific to Filament:
+52 -3
View File
@@ -753,12 +753,61 @@ function(combine_static_libs TARGET OUTPUT DEPS)
endfunction()
# ==================================================================================================
# Configuration for CMAKE_CROSSCOMPILING.
# Configuration importing/exporting prebuilt "/tools" executables
# ==================================================================================================
# In certain cases, we want the ability to separate the build type/flags (release, debug) of tools
# (matc, resgen, etc...) from filament. Cross compilation is one such case (e.g. building material
# with matc running on host while building filament for Android) [1]. Another example is when client
# wants to enable ASAN for filament but not the tool [2].
#
# Here are the varibles that are used to determine behavior is such flows:
# - CMAKE_CROSSCOMPILING : Set by cmake to indicate the build's target platform is different
# from the host platform.
# - IMPORT_EXECUTABLES_DIR : This is the directory containing the /ImportExecutables-type.cmake
# which are then included in other CMakeLists.txt to enable finding targets that have been
# prebuilt. This is set by the client in their `cmake` invocation.
# - IMPORT_EXECUTABLES : Path to a file to for 1) exporting the prebuilt targets or 2)
# importing a previously exported targets. (Used internally and should not be set by client).
# - FILAMENT_EXPORT_PREBUILT_EXECUTABLES_DIR : A path set by the client to indicate that
# they wish to export the tools as prebuilts, and the corresponding targets will be recorded
# in a cmake file (i.e IMPORT_EXECUTABLES). IMPORT_EXECUTABLES will be set relative to the the
# given path. This will set FILAMENT_EXPORT_PREBUILT_EXECUTABLES=ON.
# - FILAMENT_IMPORT_PREBUILT_EXECUTABLES_DIR : A path set by the client to indicate that they wish
# to import the tools as prebuilts from a .cmake file. The location (IMPORT_EXECUTABLES) of the
# file is relative to the path given. This will set FILAMENT_IMPORT_PREBUILT_EXECUTABLES=ON.
# - IMPORT_PREBUILT_EXECUTABLES and EXPORT_PREBUILT_EXECUTABLES are internal booleans set to
# ON based on the prescence of FILAMENT_IMPORT_PREBUILT_EXECUTABLES_DIR and
# FILAMENT_EXPORT_PREBUILT_EXECUTABLES_DIR.
#
# In conclusion, for cases
# - [1] (crosscompiling), the client must set IMPORT_EXECUTABLES_DIR for both when they are building
# the tools and when they are building the target-platform filament.
# - [2] (all other instances), the client must set FILAMENT_EXPORT_PREBUILT_EXECUTABLES_DIR in
# the prebuilt exporting step, and set FILAMENT_IMPORT_PREBUILT_EXECUTABLES_DIR in the importing
# step.
# TODO: Fold the cross compilation case into the more general FILAMENT_IMPORT/EXPORT variables.
if (FILAMENT_EXPORT_PREBUILT_EXECUTABLES_DIR)
set(FILAMENT_EXPORT_PREBUILT_EXECUTABLES ON)
set(IMPORT_EXECUTABLES_DIR ${FILAMENT_EXPORT_PREBUILT_EXECUTABLES_DIR})
endif()
if (FILAMENT_IMPORT_PREBUILT_EXECUTABLES_DIR)
set(FILAMENT_IMPORT_PREBUILT_EXECUTABLES ON)
set(IMPORT_EXECUTABLES_DIR ${FILAMENT_IMPORT_PREBUILT_EXECUTABLES_DIR})
endif()
if (WEBGL)
set(IMPORT_EXECUTABLES ${FILAMENT}/${IMPORT_EXECUTABLES_DIR}/ImportExecutables-Release.cmake)
else()
set(IMPORT_EXECUTABLES ${FILAMENT}/${IMPORT_EXECUTABLES_DIR}/ImportExecutables-${CMAKE_BUILD_TYPE}.cmake)
if (FILAMENT_EXPORT_PREBUILT_EXECUTABLES OR FILAMENT_IMPORT_PREBUILT_EXECUTABLES)
set(IMPORT_EXECUTABLES_BUILD_TYPE Prebuilt)
else()
set(IMPORT_EXECUTABLES_BUILD_TYPE ${CMAKE_BUILD_TYPE})
endif()
set(IMPORT_EXECUTABLES ${FILAMENT}/${IMPORT_EXECUTABLES_DIR}/ImportExecutables-${IMPORT_EXECUTABLES_BUILD_TYPE}.cmake)
endif()
# ==================================================================================================
@@ -917,6 +966,6 @@ if (IS_HOST_PLATFORM)
endif()
# Generate exported executables for cross-compiled builds (Android, WebGL, and iOS)
if (NOT CMAKE_CROSSCOMPILING)
if ((NOT CMAKE_CROSSCOMPILING AND NOT FILAMENT_IMPORT_PREBUILT_EXECUTABLES) OR FILAMENT_EXPORT_PREBUILT_EXECUTABLES)
export(TARGETS matc cmgen filamesh mipgen resgen uberz glslminifier FILE ${IMPORT_EXECUTABLES})
endif()
+1 -2
View File
@@ -7,5 +7,4 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- materials: Add a new API getParameterTransformName that will return the value of the transformName field of a sampler
parameter. [⚠️ **Recompile Materials**]
- Metal: Add support for the `SwapChain::CONFIG_MSAA_4_SAMPLES` flag.
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.66.1'
implementation 'com.google.android.filament:filament-android:1.66.2'
}
```
@@ -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.66.1'
pod 'Filament', '~> 1.66.2'
```
## Documentation
+5
View File
@@ -7,6 +7,11 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.67.0
- materials: Add a new API getParameterTransformName that will return the value of the transformName field of a sampler
parameter. [⚠️ **Recompile Materials**]
## v1.66.2
-5
View File
@@ -51,10 +51,6 @@ add_library(bluevk STATIC IMPORTED)
set_target_properties(bluevk PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libbluevk.a)
add_library(vkshaders STATIC IMPORTED)
set_target_properties(vkshaders PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libvkshaders.a)
if (FILAMENT_SUPPORTS_WEBGPU)
add_library(webgpu_dawn STATIC IMPORTED)
set_target_properties(webgpu_dawn PROPERTIES IMPORTED_LOCATION
@@ -165,7 +161,6 @@ target_link_libraries(filament-jni
PRIVATE $<$<STREQUAL:${FILAMENT_ENABLE_MATDBG},ON>:matdbg>
PRIVATE $<$<STREQUAL:${FILAMENT_ENABLE_MATDBG},ON>:filamat>
PRIVATE $<$<STREQUAL:${FILAMENT_SUPPORTS_VULKAN},ON>:bluevk>
PRIVATE $<$<STREQUAL:${FILAMENT_SUPPORTS_VULKAN},ON>:vkshaders>
PRIVATE $<$<STREQUAL:${FILAMENT_SUPPORTS_VULKAN},ON>:smol-v>
PRIVATE $<$<STREQUAL:${FILAMENT_SUPPORTS_WEBGPU},ON>:webgpu_dawn>
)
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.66.1
VERSION_NAME=1.66.2
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+66 -5
View File
@@ -77,6 +77,10 @@ function print_help {
echo " meant for building the samples."
echo " -P"
echo " Enable perfetto traces on Android. Disabled by default on the Release build, enabled otherwise."
echo " -y build_type"
echo " Build the filament dependent tools (matc, resgen) separately from the project. This will set"
echo " the tools as prebuilts that filament target will then use to build. The built_type option"
echo " (debug|release) is meant to indicate the type of build of the resulting prebuilts."
echo ""
echo "Build types:"
echo " release"
@@ -217,6 +221,11 @@ OSMESA_OPTION=""
IOS_BUILD_SIMULATOR=false
BUILD_UNIVERSAL_LIBRARIES=false
ISSUE_SPLIT_BUILD=false
SPLIT_BUILD_TYPE=""
PREBUILT_TOOLS_DIR=""
IMPORT_EXECUTABLES_DIR_OPTION="-DIMPORT_EXECUTABLES_DIR=out"
BUILD_GENERATOR=Ninja
BUILD_COMMAND=ninja
BUILD_CUSTOM_TARGETS=
@@ -242,6 +251,37 @@ function build_clean_aggressive {
git clean -qfX android
}
function build_tools_for_split_build {
local build_type_arg=$1
local lc_build_type=$(echo "${build_type_arg}" | tr '[:upper:]' '[:lower:]')
PREBUILT_TOOLS_DIR="out/prebuilt-tools-${lc_build_type}"
echo "Building tools for split build (${lc_build_type}) in ${PREBUILT_TOOLS_DIR}..."
mkdir -p "${PREBUILT_TOOLS_DIR}"
pushd "${PREBUILT_TOOLS_DIR}" > /dev/null
local lc_name=$(echo "${UNAME}" | tr '[:upper:]' '[:lower:]')
local architectures=""
if [[ "${lc_name}" == "darwin" ]]; then
if [[ "${BUILD_UNIVERSAL_LIBRARIES}" == "true" ]]; then
architectures="-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64"
fi
fi
cmake \
-G "${BUILD_GENERATOR}" \
-DFILAMENT_EXPORT_PREBUILT_EXECUTABLES_DIR=${PREBUILT_TOOLS_DIR} \
-DCMAKE_BUILD_TYPE="${build_type_arg}" \
${WEBGPU_OPTION} \
${architectures} \
../..
${BUILD_COMMAND} ${WEB_HOST_TOOLS}
popd > /dev/null
}
function build_desktop_target {
local lc_target=$(echo "$1" | tr '[:upper:]' '[:lower:]')
local build_targets=$2
@@ -265,7 +305,7 @@ function build_desktop_target {
if [[ ! -d "CMakeFiles" ]] || [[ "${ISSUE_CMAKE_ALWAYS}" == "true" ]]; then
cmake \
-G "${BUILD_GENERATOR}" \
-DIMPORT_EXECUTABLES_DIR=out \
${IMPORT_EXECUTABLES_DIR_OPTION} \
-DCMAKE_BUILD_TYPE="$1" \
-DCMAKE_INSTALL_PREFIX="../${lc_target}/filament" \
${EGL_ON_LINUX_OPTION} \
@@ -331,7 +371,7 @@ function build_webgl_with_target {
source "${EMSDK}/emsdk_env.sh"
cmake \
-G "${BUILD_GENERATOR}" \
-DIMPORT_EXECUTABLES_DIR=out \
${IMPORT_EXECUTABLES_DIR_OPTION} \
-DCMAKE_TOOLCHAIN_FILE="${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" \
-DCMAKE_BUILD_TYPE="$1" \
-DCMAKE_INSTALL_PREFIX="../webgl-${lc_target}/filament" \
@@ -404,7 +444,7 @@ function build_android_target {
if [[ ! -d "CMakeFiles" ]] || [[ "${ISSUE_CMAKE_ALWAYS}" == "true" ]]; then
cmake \
-G "${BUILD_GENERATOR}" \
-DIMPORT_EXECUTABLES_DIR=out \
${IMPORT_EXECUTABLES_DIR_OPTION} \
-DCMAKE_BUILD_TYPE="$1" \
-DFILAMENT_NDK_VERSION="${FILAMENT_NDK_VERSION}" \
-DCMAKE_INSTALL_PREFIX="../android-${lc_target}/filament" \
@@ -638,7 +678,7 @@ function build_ios_target {
if [[ ! -d "CMakeFiles" ]] || [[ "${ISSUE_CMAKE_ALWAYS}" == "true" ]]; then
cmake \
-G "${BUILD_GENERATOR}" \
-DIMPORT_EXECUTABLES_DIR=out \
${IMPORT_EXECUTABLES_DIR_OPTION} \
-DCMAKE_BUILD_TYPE="$1" \
-DCMAKE_INSTALL_PREFIX="../ios-${lc_target}/filament" \
-DIOS_ARCH="${arch}" \
@@ -810,7 +850,7 @@ function check_debug_release_build {
pushd "$(dirname "$0")" > /dev/null
while getopts ":hacCfgimp:q:uvWslwedtk:bVx:S:X:P" opt; do
while getopts ":hacCfgimp:q:uvWslwedtk:bVx:S:X:Py:" opt; do
case ${opt} in
h)
print_help
@@ -979,6 +1019,20 @@ while getopts ":hacCfgimp:q:uvWslwedtk:bVx:S:X:P" opt; do
;;
X) OSMESA_OPTION="-DFILAMENT_OSMESA_PATH=${OPTARG}"
;;
y)
ISSUE_SPLIT_BUILD=true
SPLIT_BUILD_TYPE=${OPTARG}
case $(echo "${SPLIT_BUILD_TYPE}" | tr '[:upper:]' '[:lower:]') in
debug|release)
;;
*)
echo "Unknown build type for -y: ${SPLIT_BUILD_TYPE}"
echo "Build type must be one of [debug|release]"
echo ""
exit 1
;;
esac
;;
\?)
echo "Invalid option: -${OPTARG}" >&2
echo ""
@@ -1013,6 +1067,13 @@ done
validate_build_command
if [[ "${ISSUE_SPLIT_BUILD}" == "true" ]]; then
# Capitalize first letter of SPLIT_BUILD_TYPE
SPLIT_BUILD_TYPE_CAPITALIZED="$(echo ${SPLIT_BUILD_TYPE:0:1} | tr '[:lower:]' '[:upper:]')${SPLIT_BUILD_TYPE:1}"
build_tools_for_split_build "${SPLIT_BUILD_TYPE_CAPITALIZED}"
IMPORT_EXECUTABLES_DIR_OPTION="-DFILAMENT_IMPORT_PREBUILT_EXECUTABLES_DIR=${PREBUILT_TOOLS_DIR}"
fi
if [[ "${ISSUE_CLEAN}" == "true" ]]; then
build_clean
fi
+2 -2
View File
@@ -180,7 +180,7 @@ rm -r Vulkan-Headers-1.3.232 v1.3.232.zip
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/filamat.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="../dup/fgviewer.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
@@ -194,7 +194,7 @@ rm -r Vulkan-Headers-1.3.232 v1.3.232.zip
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/filamat.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="../dup/fgviewer.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
+6
View File
@@ -200,6 +200,12 @@ inside the Filament source tree.</p>
force a clean build by adding the <code>-c</code> flag in that case.</p>
<p>To install the libraries and executables in <code>out/debug/</code> and <code>out/release/</code>, add the <code>-i</code> flag.
The script offers more features described by executing <code>build.sh -h</code>.</p>
<p>For more specialized options, please also consider the following pages:</p>
<ul>
<li><code>-d</code>: <a href="https://google.github.io/filament/dup/matdbg.html"><code>matdbg</code></a></li>
<li><code>-t</code>: <a href="https://google.github.io/filament/dup/fgviewer.html"><code>fgviewer</code></a></li>
<li><code>-b</code> and <code>-y</code>: <a href="https://google.github.io/filament/notes/asan_ubsan.html">ASAN/UBSAN builds</a></li>
</ul>
<h3 id="filament-specific-cmake-options"><a class="header" href="#filament-specific-cmake-options">Filament-specific CMake Options</a></h3>
<p>The following CMake options are boolean options specific to Filament:</p>
<ul>
+345
View File
@@ -0,0 +1,345 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>fgviewer - 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="fgviewer"><a class="header" href="#fgviewer">fgviewer</a></h1>
<ol>
<li><a href="#capabilities">Capabilities</a></li>
<li><a href="#setup-for-desktop">Setup for Desktop</a></li>
<li><a href="#setup-for-android">Setup for Android</a></li>
<li><a href="#debugger-usage">Debugger Usage</a></li>
<li><a href="#architecture-overview">Architecture Overview</a></li>
<li><a href="#c-server">C++ Server</a></li>
<li><a href="#javascript-client">JavaScript Client</a></li>
<li><a href="#http-requests">HTTP Requests</a></li>
<li><a href="#wish-list">Wish List</a></li>
</ol>
<h2 id="capabilities"><a class="header" href="#capabilities">Capabilities</a></h2>
<p>fgviewer is a library and web application for real-time visualization of the frame graph in Filament.
It displays active passes and resource usage, providing insights into the rendering pipeline.</p>
<h2 id="setup-for-desktop"><a class="header" href="#setup-for-desktop">Setup for Desktop</a></h2>
<p>When using the easy build script, include the <code>-t</code> argument. For example:</p>
<pre><code>./build.sh -ft debug gltf_viewer
</code></pre>
<p>The <code>t</code> enables a CMake option called <code>FILAMENT_ENABLE_FGVIEWER</code> and the <code>f</code> ensures that CMake gets
re-run so that the option is honored.</p>
<p>Next, set an environment variable as follows. In Windows, use <code>set</code> instead of <code>export</code>.</p>
<pre><code>export FILAMENT_FGVIEWER_PORT=8050
</code></pre>
<p>Next, launch any app that links against a debug build of a Filament and point your web browser to
http://localhost:8050. Skip ahead to <strong>Debugger Usage</strong>.</p>
<h2 id="setup-for-android"><a class="header" href="#setup-for-android">Setup for Android</a></h2>
<p>Rebuild Filament for Android after enabling a CMake option called <code>FILAMENT_ENABLE_FGVIEWER</code>. Note that
CMake is invoked from several places for Android (both gradle and our easy build script), so one
pragmatic and reliable way of doing this is to simply hack <code>CMakeLists.txt</code> and
<code>filament-android/CMakeLists.txt</code> by unconditionally setting <code>FILAMENT_ENABLE_FGVIEWER</code> to <code>ON</code>.</p>
<p>After rebuilding Filament with the option enabled, ensure that internet permissions are enabled in
your app by adding the following into your manifest as a child of the <code>&lt;manifest&gt;</code> element.</p>
<pre><code>&lt;uses-permission android:name="android.permission.INTERNET" /&gt;
</code></pre>
<p>Now launch your app as usual. The Filament Engine sets up a server that is hardcoded to listen to
port <code>8085</code>. Next, you will need to forward your device's TCP port <code>8085</code> to your host port of choice.
For example, to forward the fgviewer server on your device to port <code>8085</code> on your host machine, do the
following:</p>
<pre><code>adb forward tcp:8085 tcp:8085
</code></pre>
<p>This lets you go to http://localhost:8085 in Chrome on your host machine.</p>
<h2 id="debugger-usage"><a class="header" href="#debugger-usage">Debugger Usage</a></h2>
<p>After opening the fgviewer page in your browser, you can see the active views are on the left panel.
Then you can select any of them to see the active passes and resources for that view.</p>
<p align="center">
<img width="600px" src=https://github.com/user-attachments/assets/2d31767f-fc25-4f17-8c14-528fe5c6b698>
</p>
<h2 id="architecture-overview"><a class="header" href="#architecture-overview">Architecture Overview</a></h2>
<p align="center">
<img width="450" src=https://github.com/user-attachments/assets/537ebb89-6ad0-4b93-bbeb-207d4fe9ec5a>
</p>
<p>The fgviewer library has two parts: a C++ server and a JavaScript client. The C++ server is
responsible for instancing a <a href="https://github.com/civetweb/civetweb">civetweb</a> context that handles HTTP requests. The
JavaScript client is a small web app that contains a view into an in-browser database of framegraphs.</p>
<p>When a new connection is established, the client asks the server for a list of framegraphs
in order to populate its in-browser database. If the connection is lost (e.g. if the app crashes),
then the database stays intact and the web app is still functional.</p>
<h2 id="c-server"><a class="header" href="#c-server">C++ Server</a></h2>
<p>The civetweb server is wrapped by our <code>DebugServer</code> class, which provides a public interface consisting of
a few methods invoked by the Filament engine.</p>
<p>Since each view corresponds to a frame graph, the engine should notify <code>DebugServer</code> of any changes to the views
on the engine side.</p>
<ul>
<li><strong>createView</strong> Notifies the debugger that a new view has been created.</li>
<li><strong>updateView</strong> Notifies the debugger of updates to an existing view.</li>
<li><strong>destroyView</strong> Notifies the debugger that a view is being removed.</li>
</ul>
<h2 id="javascript-client"><a class="header" href="#javascript-client">JavaScript Client</a></h2>
<p>The web app is built using LitElement, a lightweight library for creating Web Components. Our goal is to keep the code simple and modern, avoiding frameworks like React or Angular.</p>
<p>The app presents a view over a pseudo-database, which is essentially a global variable holding a dictionary that maps frame graph ids to objects following the JSON structure described below.</p>
<h2 id="http-requests"><a class="header" href="#http-requests">HTTP requests</a></h2>
<p>The server responds to the following GET requests by returning a JSON blob. The <code>{id}</code> in these
requests is a concept specific to fgviewer (not Filament) which is an 8-digit hex string for identifying frame graphs.</p>
<hr />
<p><code>/api/framegraphs</code></p>
<p>Returns an array containing all framegraphs in an app. Example:</p>
<pre><code class="language-json">[{
"fgid": "00000000",
"viewName": "Main View",
"passes": [{
"name": "shadow pass",
"reads": [],
"writes": ["0"]
}],
"resources": [{
"id": "0",
"name": "shadowmap",
"properties": [{"resolution": "256x256"}, {"is_subresource": "false"}]
}]
},
{
"fgid": "00000001",
"viewName": "UI view",
...
}]
</code></pre>
<hr />
<p><code>/api/framegraph?fg={id}</code></p>
<p>Returns a specific framegraph info. Example:</p>
<pre><code class="language-json">{
"fgid": "00000000",
"viewName": "Main View",
"passes": [{
"name": "shadow pass",
"reads": [],
"writes": ["0"]
}],
"resources": [{
"id": "0",
"name": "shadowmap",
"properties": [{"resolution": "256x256"}, {"is_subresource": "false"}]
}]
}
</code></pre>
<hr />
<p><code>/api/status</code></p>
<p>Returns one of the below:</p>
<ul>
<li><code>0</code>: first time connected</li>
<li><code>1</code>: no-op</li>
<li><code>{fgid}</code>: the corresponding frame graph has an update
<ul>
<li>Then the web view can request for the actual info using the previous api</li>
</ul>
</li>
</ul>
<p>If the request gets timeout, the web page show disconnected to the user.</p>
<hr />
<h2 id="wish-list"><a class="header" href="#wish-list">Wish List</a></h2>
<ul>
<li>Display the texture contents on the webview</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/bluevk.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/filamat.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/bluevk.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/filamat.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>
+2 -2
View File
@@ -336,7 +336,7 @@ void material(inout MaterialInputs material) {
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/bluevk.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="../dup/fgviewer.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
@@ -350,7 +350,7 @@ void material(inout MaterialInputs material) {
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/bluevk.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="../dup/fgviewer.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
+2 -2
View File
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.66.1'
implementation 'com.google.android.filament:filament-android:1.66.2'
}
</code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
@@ -196,7 +196,7 @@ dependencies {
</div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<p>iOS projects can use CocoaPods to install the latest release:</p>
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.66.1'
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.66.2'
</code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul>
+7 -2
View File
@@ -160,10 +160,15 @@
<main>
<h1 id="running-with-asanubsan"><a class="header" href="#running-with-asanubsan">Running with ASAN/UBSAN</a></h1>
<h2 id="enabling"><a class="header" href="#enabling">Enabling</a></h2>
<p>When building though build.sh, pass the <code>-b</code> flag. This sets the cmake variable
<p>When building though <code>build.sh</code>, pass the <code>-b</code> flag. This sets the cmake variable
<code>FILAMENT_ENABLE_ASAN_UBSAN=ON</code> which eventually passes <code>"-fsanitize=address -fsanitize=undefined"</code>
to all compile and link operations.</p>
<p>If building through CMake directly, or an IDE like CLion that doesn't use build.sh, instead pass
<p>It might be desirable to pair the <code>-b</code> with <code>-y release</code>. The <code>-y</code> flag indicates that targets
in <code>/tools</code> will be built separately from the filament target, and the filament build depends on
the <code>/tools</code> targets as prebuilt binaries. This separation reduces ASAN/UBSAN build time
considerably. This option assumes that the user is trying to catch sanitization issues for
filament and not the tools that are used to build filament.</p>
<p>If building through CMake directly, or an IDE like CLion that doesn't use <code>build.sh</code>, instead pass
<code>-DFILAMENT_ENABLE_ASAN_UBSAN=ON</code> to cmake in order to get the same result.</p>
<h2 id="getting-memory-leak-detection-on-mac"><a class="header" href="#getting-memory-leak-detection-on-mac">Getting memory leak detection on Mac</a></h2>
<p>Memory leak detection isn't enabled by default on MacOS. There are two issues to address, first is
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+3
View File
@@ -46,6 +46,9 @@
"libs/matdbg/README.md": {
"dest": "dup/matdbg.md"
},
"libs/fgviewer/README.md": {
"dest": "dup/fgviewer.md"
},
"tools/normal-blending/README.md": {
"dest": "dup/normal_blending.md"
},
+1
View File
@@ -32,6 +32,7 @@
- [Libraries](./notes/libs.md)
- [bluegl](./dup/bluegl.md)
- [bluevk](./dup/bluevk.md)
- [fgviewer](./dup/fgviewer.md)
- [filamat](./dup/filamat.md)
- [gltfio](./dup/gltfio.md)
- [iblprefilter](./dup/iblprefilter.md)
+8 -2
View File
@@ -2,11 +2,17 @@
## Enabling
When building though build.sh, pass the `-b` flag. This sets the cmake variable
When building though `build.sh`, pass the `-b` flag. This sets the cmake variable
`FILAMENT_ENABLE_ASAN_UBSAN=ON` which eventually passes `"-fsanitize=address -fsanitize=undefined"`
to all compile and link operations.
If building through CMake directly, or an IDE like CLion that doesn't use build.sh, instead pass
It might be desirable to pair the `-b` with `-y release`. The `-y` flag indicates that targets
in `/tools` will be built separately from the filament target, and the filament build depends on
the `/tools` targets as prebuilt binaries. This separation reduces ASAN/UBSAN build time
considerably. This option assumes that the user is trying to catch sanitization issues for
filament and not the tools that are used to build filament.
If building through CMake directly, or an IDE like CLion that doesn't use `build.sh`, instead pass
`-DFILAMENT_ENABLE_ASAN_UBSAN=ON` to cmake in order to get the same result.
## Getting memory leak detection on Mac
+1 -1
View File
@@ -363,7 +363,7 @@ add_definitions(
# Generate all .filamat: default material, skyboxes, and post-process
# ==================================================================================================
if (CMAKE_CROSSCOMPILING)
if (CMAKE_CROSSCOMPILING OR FILAMENT_IMPORT_PREBUILT_EXECUTABLES)
include(${IMPORT_EXECUTABLES})
endif()
+3 -3
View File
@@ -92,7 +92,7 @@ Copy your platform's Makefile below into a `Makefile` inside the same directory.
### Linux
```make
FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -lvkshaders -libl -labseil
FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -libl -labseil
CC=clang++
main: main.o
@@ -110,7 +110,7 @@ clean:
### macOS
```make
FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -lvkshaders -libl -labseil
FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -libl -labseil
FRAMEWORKS=-framework Cocoa -framework Metal -framework CoreVideo
CC=clang++
ARCH ?= $(shell uname -m)
@@ -140,7 +140,7 @@ used to change the run-time library version.
```make
FILAMENT_LIBS=filament.lib backend.lib bluegl.lib bluevk.lib filabridge.lib filaflat.lib \
utils.lib geometry.lib smol-v.lib ibl.lib vkshaders.lib abseil.lib
utils.lib geometry.lib smol-v.lib ibl.lib abseil.lib
CC=cl.exe
main.exe: main.obj
+3 -42
View File
@@ -369,46 +369,6 @@ endif()
add_library(${TARGET}_headers INTERFACE)
target_include_directories(${TARGET}_headers INTERFACE ${PUBLIC_HDR_DIR})
# ==================================================================================================
# Build SPIRV snippets used by the Vulkan backend.
# ==================================================================================================
set(VKSHADERS_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/vkshaders")
file(MAKE_DIRECTORY ${VKSHADERS_DIR})
get_resgen_vars(${VKSHADERS_DIR} vkshaders)
set(VKSHADER_BINS)
function(build_vkshader SOURCE TARGET_PATH)
set(SOURCE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/vulkan/${SOURCE}")
set(VKSHADER_BINS ${VKSHADER_BINS} ${TARGET_PATH} PARENT_SCOPE)
add_custom_command(
OUTPUT ${TARGET_PATH}
COMMAND matc --raw -o ${TARGET_PATH} ${SOURCE_PATH}
MAIN_DEPENDENCY ${SOURCE_PATH}
DEPENDS matc ${SOURCE_PATH}
COMMENT "Building SPIR-V")
endfunction()
build_vkshader("BlitDepth.vs" "BlitDepthVs.spv")
build_vkshader("BlitDepth.fs" "BlitDepthFs.spv")
add_custom_command(
OUTPUT ${RESGEN_OUTPUTS}
COMMAND resgen ${RESGEN_FLAGS} ${VKSHADER_BINS}
DEPENDS resgen ${VKSHADER_BINS}
COMMENT "Aggregating compiled VK shaders")
if (DEFINED RESGEN_SOURCE_FLAGS)
set_source_files_properties(${RESGEN_SOURCE} PROPERTIES COMPILE_FLAGS ${RESGEN_SOURCE_FLAGS})
endif()
set(DUMMY_SRC "${VKSHADERS_DIR}/dummy.c")
add_custom_command(OUTPUT ${DUMMY_SRC} COMMAND echo "//" > ${DUMMY_SRC})
add_library(vkshaders STATIC ${DUMMY_SRC} ${RESGEN_SOURCE})
set_target_properties(vkshaders PROPERTIES FOLDER Filament/Generated)
# ==================================================================================================
# Dependencies
# ==================================================================================================
@@ -442,7 +402,7 @@ if(FILAMENT_SUPPORTS_OPENGL AND NOT IOS AND NOT ANDROID AND NOT WEBGL)
endif()
if (FILAMENT_SUPPORTS_VULKAN)
target_link_libraries(${TARGET} PUBLIC bluevk vkmemalloc vkshaders smol-v)
target_link_libraries(${TARGET} PUBLIC bluevk vkmemalloc smol-v)
target_link_libraries(${TARGET} PRIVATE SPIRV-Headers)
endif()
@@ -561,7 +521,7 @@ endif()
# ==================================================================================================
set(INSTALL_TYPE ARCHIVE)
install(TARGETS ${TARGET} ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
install(TARGETS vkshaders ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
install(TARGETS ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
install(DIRECTORY ${PUBLIC_HDR_DIR}/backend DESTINATION include)
if (FILAMENT_SUPPORTS_WEBGPU)
@@ -592,6 +552,7 @@ if (APPLE OR LINUX)
test/test_BufferUpdates.cpp
test/test_Callbacks.cpp
test/test_MemoryMappedBuffer.cpp
test/test_MsaaSwapChain.cpp
test/test_MRT.cpp
test/test_PushConstants.cpp
test/test_LoadImage.cpp
@@ -1681,7 +1681,7 @@ static_assert(sizeof(StencilState::StencilOperations) == 5u,
static_assert(sizeof(StencilState) == 12u,
"StencilState size not what was intended");
using FrameScheduledCallback = utils::Invocable<void(backend::PresentCallable)>;
using FrameScheduledCallback = utils::Invocable<void(PresentCallable)>;
enum class Workaround : uint16_t {
// The EASU pass must split because shader compiler flattens early-exit branch
@@ -1707,7 +1707,11 @@ enum class Workaround : uint16_t {
EMULATE_SRGB_SWAPCHAIN,
};
using StereoscopicType = backend::Platform::StereoscopicType;
using StereoscopicType = Platform::StereoscopicType;
using FrameTimestamps = Platform::FrameTimestamps;
using CompositorTiming = Platform::CompositorTiming;
} // namespace filament::backend
+51 -3
View File
@@ -110,7 +110,7 @@ public:
/**
* The time delta [ns] between the start of composition and the expected present time of
* that composition. This can be used to estimate the latency of the actual present time.
* that composition. This can be used to estimate the latency of the actual present time.
*/
duration_ns compositeToPresentLatency;
};
@@ -120,14 +120,62 @@ public:
using time_point_ns = int64_t;
static constexpr time_point_ns INVALID = -1; //!< value not supported
static constexpr time_point_ns PENDING = -2; //!< value not yet available
/**
* The time the application requested this frame be presented.
* If the application does not request a presentation time explicitly,
* this will correspond to buffer's queue time.
*/
time_point_ns requestedPresentTime;
/**
* The time when all the application's rendering to the surface was completed.
*/
time_point_ns acquireTime;
/**
* The time when the compositor selected this frame as the one to use for the next
* composition. This is the earliest indication that the frame was submitted in time.
*/
time_point_ns latchTime;
time_point_ns firstRefreshStartTime;
time_point_ns lastRefreshStartTime;
/**
* The first time at which the compositor began preparing composition for this frame.
* Zero if composition was handled by the display and the compositor didn't do any
* rendering.
*/
time_point_ns firstCompositionStartTime;
/**
* The last time at which the compositor began preparing composition for this frame, for
* frames composited more than once. Zero if composition was handled by the display and the
* compositor didn't do any rendering.
*/
time_point_ns lastCompositionStartTime;
/**
* The time at which the compositor's rendering work for this frame finished. This will be
* INVALID if composition was handled by the display and the compositor didn't do any
* rendering.
*/
time_point_ns gpuCompositionDoneTime;
/**
* The time at which this frame started to scan out to the physical display.
*/
time_point_ns displayPresentTime;
/**
* The time when the buffer became available for reuse as a buffer the client can target
* without blocking. This is generally the point when all read commands of the buffer have
* been submitted, but not necessarily completed.
*/
time_point_ns dequeueReadyTime;
/**
* The time at which all reads for the purpose of display/composition were completed for
* this frame.
*/
time_point_ns releaseTime;
};
@@ -30,6 +30,8 @@
#include <math/mat3.h>
#include "AndroidNativeWindow.h"
#include <chrono>
#include <stddef.h>
@@ -185,6 +187,10 @@ private:
[[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
bool isProducerThrottlingControlSupported() const;
int32_t setProducerThrottlingEnabled(EGLNativeWindowType nativeWindow, bool enabled) const;
struct InitializeJvmForPerformanceManagerIfNeeded {
InitializeJvmForPerformanceManagerIfNeeded();
};
@@ -202,11 +208,8 @@ private:
using clock = std::chrono::high_resolution_clock;
clock::time_point mStartTimeOfActualWork;
int32_t (*ANativeWindow_setProducerThrottlingEnabled)(ANativeWindow* window, bool enabled) = nullptr;
int32_t (*ANativeWindow_isProducerThrottlingEnabled)(ANativeWindow* window, bool* outEnabled) = nullptr;
AndroidProducerThrottling mProducerThrottling;
bool mAssertNativeWindowIsValid = false;
bool mHasProducerThrottlingControl = false;
};
} // namespace filament::backend
@@ -431,6 +431,16 @@ public:
return {};
}
bool isCompositorTimingSupported() const noexcept override;
bool queryCompositorTiming(SwapChain const* swapchain,
CompositorTiming* outCompositorTiming) const noexcept override;
bool setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept override;
bool queryFrameTimestamps(SwapChain const* swapchain, uint64_t frameId,
FrameTimestamps* outFrameTimestamps) const noexcept override;
protected:
struct VulkanSync : public Platform::Sync {
VkFence fence;
@@ -367,6 +367,9 @@ DECL_DRIVER_API_SYNCHRONOUS_N(backend::TimerQueryResult, getTimerQueryValue, bac
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isWorkaroundNeeded, backend::Workaround, workaround)
DECL_DRIVER_API_SYNCHRONOUS_0(backend::FeatureLevel, getFeatureLevel)
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getUniformBufferOffsetAlignment)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isCompositorTimingSupported)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, queryFrameTimestamps, backend::SwapChainHandle, swapChain, uint64_t, frameId, backend::FrameTimestamps*, outFrameTimestamps)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, queryCompositorTiming, backend::SwapChainHandle, swapChain, backend::CompositorTiming*, outCompositorTiming)
/*
* Updating driver objects
@@ -208,7 +208,7 @@ public:
HandleBase::HandleId const index = (handle.getId() & HANDLE_INDEX_MASK);
// if we've already handed out this handle index before, it's definitely a
// use-after-free, otherwise it's probably just a corrupted handle
if (index < mId) {
if (index < mId.load(std::memory_order_relaxed)) {
FILAMENT_CHECK_POSTCONDITION(p != nullptr)
<< "use-after-free of heap Handle with id=" << handle.getId()
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
@@ -19,11 +19,14 @@
#include <android/native_window.h>
#include <utils/compiler.h>
#include <utils/Logger.h>
#include <cstdint>
#include <cerrno>
#include <utility>
#include <dlfcn.h>
namespace filament::backend {
std::pair<int, bool> NativeWindow::isValid(ANativeWindow* const anw) noexcept {
@@ -75,4 +78,44 @@ int NativeWindow::getFrameTimestamps(ANativeWindow* anw,
outDequeueReadyTime, outReleaseTime);
}
AndroidProducerThrottling::AndroidProducerThrottling() {
// note: we don't need to dlclose() mNativeWindowLib here, because the library will be cleaned
// when the process ends and dlopen() are ref-counted. dlclose() NDK documentation documents
// not to call dlclose().
void* nativeWindowLibHandle = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW);
if (nativeWindowLibHandle) {
mSetProducerThrottlingEnabled =
(int32_t(*)(ANativeWindow*, bool)) dlsym(nativeWindowLibHandle,
"ANativeWindow_setProducerThrottlingEnabled");
mIsProducerThrottlingEnabled =
(int32_t(*)(ANativeWindow*, bool*)) dlsym(nativeWindowLibHandle,
"ANativeWindow_isProducerThrottlingEnabled");
if (mSetProducerThrottlingEnabled && mIsProducerThrottlingEnabled) {
LOG(INFO) << "Producer Throttling API available";
}
}
}
int32_t AndroidProducerThrottling::setProducerThrottlingEnabled(
ANativeWindow* window, bool enabled) const {
if (mSetProducerThrottlingEnabled) {
return mSetProducerThrottlingEnabled(window, enabled);
}
return -1;
}
int32_t AndroidProducerThrottling::isProducerThrottlingEnabled(
ANativeWindow* window, bool* outEnabled) const {
if (mIsProducerThrottlingEnabled) {
return mIsProducerThrottlingEnabled(window, outEnabled);
}
return -1;
}
bool AndroidProducerThrottling::isSupported() const noexcept {
return mSetProducerThrottlingEnabled && mIsProducerThrottlingEnabled;
}
} // namespace filament::backend
@@ -58,6 +58,17 @@ struct NativeWindow {
int64_t* outReleaseTime);
};
struct AndroidProducerThrottling {
AndroidProducerThrottling();
int32_t setProducerThrottlingEnabled(ANativeWindow* window, bool enabled) const;
int32_t isProducerThrottlingEnabled(ANativeWindow* window, bool* outEnabled) const;
bool isSupported() const noexcept;
private:
int32_t (*mSetProducerThrottlingEnabled)(ANativeWindow* window, bool enabled) = nullptr;
int32_t (*mIsProducerThrottlingEnabled)(ANativeWindow* window, bool* outEnabled) = nullptr;
};
} // namespace filament::backend
#endif //FILAMENT_BACKEND_ANDROIDNATIVEWINDOW_H
@@ -31,31 +31,27 @@ AndroidSwapChainHelper::AndroidSwapChainHelper() = default;
AndroidSwapChainHelper::~AndroidSwapChainHelper() noexcept = default;
bool AndroidSwapChainHelper::setPresentFrameId(
ANativeWindow* anw, uint64_t frameId) const noexcept {
ANativeWindow* anw, uint64_t const frameId) const noexcept {
uint64_t sysFrameId{};
int const status = NativeWindow::getNextFrameId(anw, &sysFrameId);
if (status == 0) {
std::lock_guard const lock(mLock);
auto const pos = mFrameIdToSystemFrameId.find(frameId);
if (pos != mFrameIdToSystemFrameId.end() && pos->second != sysFrameId) {
if (pos && *pos != sysFrameId) {
// we're trying to associate the same frame id to a different frame!
return false;
}
// make space by destroying the oldest entry
if (mFrameIdToSystemFrameId.size() >= MAX_HISTORY_SIZE) {
mFrameIdToSystemFrameId.erase(mFrameIdToSystemFrameId.begin());
}
mFrameIdToSystemFrameId.insert(mFrameIdToSystemFrameId.end(), { frameId, sysFrameId });
// oldest entry is removed
mFrameIdToSystemFrameId.insert(frameId, sysFrameId);
return true;
}
return false;
}
uint64_t AndroidSwapChainHelper::getFrameId(uint64_t frameId) const noexcept {
uint64_t AndroidSwapChainHelper::getFrameId(uint64_t const frameId) const noexcept {
std::lock_guard const lock(mLock);
auto pos = mFrameIdToSystemFrameId.find(frameId);
if (pos != mFrameIdToSystemFrameId.end()) {
return pos->second;
if (auto const* const pos = mFrameIdToSystemFrameId.find(frameId)) {
return *pos;
}
return std::numeric_limits<uint64_t>::max();
}
@@ -18,6 +18,7 @@
#define TNT_FILAMENT_BACKEND_ANDROIDSWAPCHAINHELPER_H
#include <utils/Mutex.h>
#include <utils/MonotonicRingMap.h>
#include <android/native_window.h>
@@ -39,7 +40,7 @@ struct AndroidSwapChainHelper {
private:
static constexpr size_t MAX_HISTORY_SIZE = 32;
mutable utils::Mutex mLock; // very low-contention lock
mutable std::map<uint64_t, uint64_t> mFrameIdToSystemFrameId{};
mutable utils::MonotonicRingMap<MAX_HISTORY_SIZE, uint64_t, uint64_t> mFrameIdToSystemFrameId{};
};
}
+4 -1
View File
@@ -25,8 +25,11 @@ TextureFormat mapToFilamentFormat(unsigned int format, bool isSrgbTransfer) noex
switch (format) {
case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
return TextureFormat::SRGB8;
default:
case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
return TextureFormat::SRGB8_A8;
default:
break;
}
}
switch (format) {
+6 -5
View File
@@ -108,7 +108,7 @@ template <size_t P0, size_t P1, size_t P2>
UTILS_NOINLINE
void* HandleAllocator<P0, P1, P2>::handleToPointerSlow(HandleBase::HandleId id) const noexcept {
auto& overflowMap = mOverflowMap;
std::lock_guard lock(mLock);
std::lock_guard const lock(mLock);
auto pos = overflowMap.find(id);
if (pos != overflowMap.end()) {
return pos.value();
@@ -119,14 +119,15 @@ void* HandleAllocator<P0, P1, P2>::handleToPointerSlow(HandleBase::HandleId id)
template <size_t P0, size_t P1, size_t P2>
HandleBase::HandleId HandleAllocator<P0, P1, P2>::allocateHandleSlow(size_t size) {
void* p = ::malloc(size);
std::unique_lock lock(mLock);
HandleBase::HandleId id = (++mId) | HANDLE_HEAP_FLAG;
FILAMENT_CHECK_POSTCONDITION(mId < HANDLE_HEAP_FLAG) <<
auto const nextId = mId.fetch_add(1, std::memory_order_relaxed) + 1;
FILAMENT_CHECK_POSTCONDITION(nextId < HANDLE_HEAP_FLAG) <<
"No more Handle ids available! This can happen if HandleAllocator arena has been full"
" for a while. Please increase FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB";
HandleBase::HandleId id = nextId | HANDLE_HEAP_FLAG;
std::unique_lock lock(mLock);
mOverflowMap.emplace(id, p);
lock.unlock();
+33 -2
View File
@@ -95,6 +95,38 @@ void initializeSupportedGpuFamilies(MetalContext* context) {
}
}
void logMTLCommandBufferError(MTLCommandBufferError error) {
#define MTL_COMMAND_ERROR_CASE(ERR) \
if (error == (ERR)) { \
LOG(ERROR) << "Filament Metal error: " #ERR "."; \
return; \
}
#if !defined(FILAMENT_IOS)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorDeviceRemoved)
#endif
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorNone)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorInternal)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorTimeout)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorPageFault)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorAccessRevoked)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorNotPermitted)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorOutOfMemory)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorInvalidResource)
if (@available(macOS 11.0, *)) {
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorMemoryless)
}
if (@available(iOS 15.0, macOS 12.0, *)) {
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorStackOverflow)
}
LOG(ERROR) << "Filament Metal unknown error.";
#undef MTL_COMMAND_ERROR_CASE
}
id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
if (context->pendingCommandBuffer) {
return context->pendingCommandBuffer;
@@ -120,8 +152,7 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
}
if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) {
LOG(ERROR) << "Filament Metal command buffer errored with code: " << errorCode << " ("
<< stringifyMTLCommandBufferError(errorCode) << ").";
logMTLCommandBufferError(errorCode);
}
}];
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)
+38 -19
View File
@@ -621,7 +621,9 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
auto& sc = mContext->sampleCountLookup;
samples = sc[std::min(MAX_SAMPLE_COUNT, samples)];
MetalRenderTarget::Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {{}};
using AttachmentInfo = MetalRenderTarget::AttachmentInfo;
AttachmentInfo colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { {} };
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (none(targetBufferFlags & getTargetBufferFlagsAt(i))) {
continue;
@@ -635,7 +637,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
colorAttachments[i] = { colorTexture, color[i].level, color[i].layer };
}
MetalRenderTarget::Attachment depthAttachment = {};
AttachmentInfo depthAttachment = {};
if (any(targetBufferFlags & TargetBufferFlags::DEPTH)) {
FILAMENT_CHECK_PRECONDITION(depth.handle)
<< "The DEPTH flag was specified, but invalid depth handle provided.";
@@ -645,7 +647,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
depthAttachment = { depthTexture, depth.level, depth.layer };
}
MetalRenderTarget::Attachment stencilAttachment = {};
AttachmentInfo stencilAttachment = {};
if (any(targetBufferFlags & TargetBufferFlags::STENCIL)) {
FILAMENT_CHECK_PRECONDITION(stencil.handle)
<< "The STENCIL flag was specified, but invalid stencil handle provided.";
@@ -669,8 +671,6 @@ void MetalDriver::createFenceR(Handle<HwFence> fh, utils::ImmutableCString&& tag
void MetalDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags,
utils::ImmutableCString&& tag) {
// TODO: support MSAA swapchain
if (UTILS_UNLIKELY(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER)) {
CVPixelBufferRef pixelBuffer = (CVPixelBufferRef) nativeWindow;
construct_handle<MetalSwapChain>(sch, *mContext, mPlatform, pixelBuffer, flags);
@@ -1039,6 +1039,8 @@ void MetalDriver::updateStreams(DriverApi* driver) {
void MetalDriver::destroyFence(Handle<HwFence> fh) {
if (fh) {
auto* fence = handle_cast<MetalFence>(fh);
fence->cancel();
destruct_handle<MetalFence>(fh);
}
}
@@ -1150,8 +1152,7 @@ bool MetalDriver::isSRGBSwapChainSupported() {
}
bool MetalDriver::isMSAASwapChainSupported(uint32_t) {
// TODO: support MSAA swapchain
return false;
return true;
}
bool MetalDriver::isProtectedContentSupported() {
@@ -1475,6 +1476,20 @@ void MetalDriver::setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph, Primit
primitive->type = pt;
}
bool MetalDriver::isCompositorTimingSupported() {
return false;
}
bool MetalDriver::queryCompositorTiming(backend::SwapChainHandle swapChain,
CompositorTiming* outCompositorTiming) {
return false;
}
bool MetalDriver::queryFrameTimestamps(SwapChainHandle swapChain, uint64_t frameId,
FrameTimestamps* outFrameTimestamps) {
return false;
}
void MetalDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain> schRead) {
ASSERT_PRECONDITION_NON_FATAL(schDraw, "A draw SwapChain must be set.");
auto* drawSwapChain = handle_cast<MetalSwapChain>(schDraw);
@@ -1561,9 +1576,9 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
auto srcTarget = handle_cast<MetalRenderTarget>(src);
// We always readPixels from the COLOR0 attachment.
MetalRenderTarget::Attachment color = srcTarget->getDrawColorAttachment(0);
MetalAttachment color = srcTarget->getDrawColorAttachment(0);
id<MTLTexture> srcTexture = color.getTexture();
size_t miplevel = color.level;
size_t miplevel = color.getLevel();
// Clamp height and width to actual texture's height and width
MTLSize srcTextureSize = MTLSizeMake(srcTexture.width >> miplevel, srcTexture.height >> miplevel, 1);
@@ -1771,8 +1786,8 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
};
// We always blit from/to the COLOR0 attachment.
MetalRenderTarget::Attachment const srcColorAttachment = srcTarget->getReadColorAttachment(0);
MetalRenderTarget::Attachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0);
MetalAttachment const srcColorAttachment = srcTarget->getReadColorAttachment(0);
MetalAttachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0);
if (srcColorAttachment && dstColorAttachment) {
FILAMENT_CHECK_PRECONDITION(
@@ -1784,13 +1799,13 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
args.filter = filter;
args.source.region = srcTarget->getRegionFromClientRect(srcRect);
args.source.texture = srcColorAttachment.getTexture();
args.source.level = srcColorAttachment.level;
args.source.slice = srcColorAttachment.layer;
args.source.level = srcColorAttachment.getLevel();
args.source.slice = srcColorAttachment.getLayer();
args.destination.region = dstTarget->getRegionFromClientRect(dstRect);
args.destination.texture = dstColorAttachment.getTexture();
args.destination.level = dstColorAttachment.level;
args.destination.slice = dstColorAttachment.layer;
args.destination.level = dstColorAttachment.getLevel();
args.destination.slice = dstColorAttachment.getLayer();
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blitDEPRECATED");
}
@@ -1826,24 +1841,28 @@ void MetalDriver::bindPipeline(PipelineState const& ps) {
auto [fragment, vertex] = functions.getRasterFunctions();
// Pipeline state
MetalRenderTarget* const rt = mContext->currentRenderTarget;
MTLPixelFormat colorPixelFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { MTLPixelFormatInvalid };
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
const auto& attachment = mContext->currentRenderTarget->getDrawColorAttachment(i);
const auto& attachment = rt->getDrawColorAttachment(i);
if (!attachment) {
continue;
}
colorPixelFormat[i] = attachment.getPixelFormat();
assert_invariant(attachment.getSampleCount() == rt->getSampleCount());
}
MTLPixelFormat depthPixelFormat = MTLPixelFormatInvalid;
const auto& depthAttachment = mContext->currentRenderTarget->getDepthAttachment();
const auto& depthAttachment = rt->getDepthAttachment();
if (depthAttachment) {
depthPixelFormat = depthAttachment.getPixelFormat();
assert_invariant(depthAttachment.getSampleCount() == rt->getSampleCount());
}
MTLPixelFormat stencilPixelFormat = MTLPixelFormatInvalid;
const auto& stencilAttachment = mContext->currentRenderTarget->getStencilAttachment();
const auto& stencilAttachment = rt->getStencilAttachment();
if (stencilAttachment) {
stencilPixelFormat = stencilAttachment.getPixelFormat();
assert_invariant(isMetalFormatStencil(stencilPixelFormat));
assert_invariant(stencilAttachment.getSampleCount() == rt->getSampleCount());
}
MetalPipelineState const pipelineState {
.vertexFunction = vertex,
@@ -1861,7 +1880,7 @@ void MetalDriver::bindPipeline(PipelineState const& ps) {
},
.depthAttachmentPixelFormat = depthPixelFormat,
.stencilAttachmentPixelFormat = stencilPixelFormat,
.sampleCount = mContext->currentRenderTarget->getSamples(),
.sampleCount = rt->getSampleCount(),
.blendState = BlendState {
.alphaBlendOperation = getMetalBlendOperation(rs.blendEquationAlpha),
.rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB),
-32
View File
@@ -451,38 +451,6 @@ inline MTLTextureSwizzleChannels getSwizzleChannels(TextureSwizzle r, TextureSwi
getSwizzle(a));
}
inline const char* stringifyMTLCommandBufferError(MTLCommandBufferError error) {
#if !defined(FILAMENT_IOS)
if (error == MTLCommandBufferErrorDeviceRemoved) {
return "MTLCommandBufferErrorDeviceRemoved";
}
#endif
switch (error) {
case MTLCommandBufferErrorNone:
return "MTLCommandBufferErrorNone";
case MTLCommandBufferErrorInternal:
return "MTLCommandBufferErrorInternal";
case MTLCommandBufferErrorTimeout:
return "MTLCommandBufferErrorTimeout";
case MTLCommandBufferErrorPageFault:
return "MTLCommandBufferErrorPageFault";
case MTLCommandBufferErrorAccessRevoked:
return "MTLCommandBufferErrorAccessRevoked";
case MTLCommandBufferErrorNotPermitted:
return "MTLCommandBufferErrorNotPermitted";
case MTLCommandBufferErrorOutOfMemory:
return "MTLCommandBufferErrorOutOfMemory";
case MTLCommandBufferErrorInvalidResource:
return "MTLCommandBufferErrorInvalidResource";
case MTLCommandBufferErrorMemoryless:
return "MTLCommandBufferErrorMemoryless";
case MTLCommandBufferErrorStackOverflow:
return "MTLCommandBufferErrorStackOverflow";
default:
return "Unknown";
}
}
} // namespace backend
} // namespace filament
+92 -56
View File
@@ -49,6 +49,61 @@
namespace filament {
namespace backend {
class MetalAttachment {
public:
MetalAttachment() = default;
MetalAttachment(id<MTLTexture> texture, uint8_t level = 0, uint16_t layer = 0)
: mLevel(level),
mLayer(layer),
mTexture(texture) {
assert_invariant(texture);
}
explicit operator bool() const { return mTexture != nil; }
id<MTLTexture> getTexture() const { return mTexture; }
id<MTLTexture> getMsaaTexture() const { return mMsaaTexture; }
MTLPixelFormat getPixelFormat() const {
return mTexture ? mTexture.pixelFormat : MTLPixelFormatInvalid;
}
NSUInteger getSampleCount() const {
if (mMsaaTexture) {
return mMsaaTexture.sampleCount;
}
if (mTexture) {
return mTexture.sampleCount;
}
return 1u;
}
uint8_t getLevel() const { return mLevel; }
uint16_t getLayer() const { return mLayer; }
MetalAttachment withMsaaTexture(id<MTLTexture> msaa) const {
assert_invariant(mTexture != nil);
assert_invariant(mTexture.sampleCount == 1u);
MetalAttachment result = *this;
result.mMsaaTexture = msaa;
return result;
}
static MetalAttachment invalidAttachment() { return MetalAttachment(); }
private:
uint8_t mLevel = 0;
uint16_t mLayer = 0;
// The main texture for this Attachment. May be single-sampled or MSAA.
id<MTLTexture> mTexture = nil;
// The MSAA "sidecar" texture that will resolve into mTexture.
// If this is non-nil, mTexture must be single-sampled.
id<MTLTexture> mMsaaTexture = nil;
};
class MetalSwapChain : public HwSwapChain {
public:
@@ -66,14 +121,14 @@ public:
~MetalSwapChain();
// Acquires a texture that can be used to render into this SwapChain.
// The texture source depends on the type of SwapChain:
// Acquires a texture that can be used to render into this SwapChain and returns a
// MetalAttachment. The texture source depends on the type of SwapChain:
// - CAMetalLayer-backed: acquires the CAMetalDrawable and returns its texture.
// - Headless: lazily creates and returns a headless texture.
id<MTLTexture> acquireDrawable();
id<MTLTexture> acquireDepthTexture();
id<MTLTexture> acquireStencilTexture();
// May return an invalid attachemnt if a drawable cannot be acquired.
MetalAttachment acquireDrawable();
MetalAttachment acquireDepthTexture();
MetalAttachment acquireStencilTexture();
void releaseDrawable();
@@ -88,6 +143,7 @@ public:
NSUInteger getSurfaceWidth() const;
NSUInteger getSurfaceHeight() const;
NSUInteger getSampleCount() const;
bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; }
@@ -106,13 +162,24 @@ private:
void scheduleFrameScheduledCallback();
void scheduleFrameCompletedCallback();
MetalAttachment acquireBaseDrawable();
id<MTLTexture> ensureDepthStencilTexture(uint32_t width, uint32_t height);
id<MTLTexture> ensureMsaaColorTexture(MTLPixelFormat format, uint32_t width, uint32_t height,
uint8_t samples);
id<MTLTexture> ensureMsaaDepthStencilTexture(MTLPixelFormat format, uint32_t width,
uint32_t height, uint8_t samples);
static MTLPixelFormat decideDepthStencilFormat(uint64_t flags);
void ensureDepthStencilTexture();
static id<MTLTexture> createMultisampledTexture(MetalContext const& context,
MTLPixelFormat format, uint32_t width, uint32_t height, uint8_t samples);
MetalContext& context;
PlatformMetal& platform;
id<CAMetalDrawable> drawable = nil;
id<MTLTexture> depthStencilTexture = nil;
id<MTLTexture> msaaColor = nil;
id<MTLTexture> msaaDepthStencil = nil;
id<MTLTexture> headlessDrawable = nil;
MTLPixelFormat depthStencilFormat = MTLPixelFormatInvalid;
NSUInteger headlessWidth = 0;
@@ -121,6 +188,7 @@ private:
std::shared_ptr<std::mutex> layerDrawableMutex;
MetalExternalImage externalImage;
SwapChainType type;
uint64_t flags;
int64_t abandonedUntilFrame = -1;
@@ -307,51 +375,15 @@ private:
class MetalRenderTarget : public HwRenderTarget {
public:
class Attachment {
public:
friend class MetalRenderTarget;
Attachment() = default;
Attachment(MetalTexture* metalTexture, uint8_t level = 0, uint16_t layer = 0) :
level(level), layer(layer),
texture(metalTexture->getMtlTextureForWrite()),
metalTexture(metalTexture) { }
id<MTLTexture> getTexture() const {
return texture;
}
NSUInteger getSampleCount() const {
return texture ? texture.sampleCount : 0u;
}
MTLPixelFormat getPixelFormat() const {
return texture ? texture.pixelFormat : MTLPixelFormatInvalid;
}
explicit operator bool() const {
return texture != nil;
}
struct AttachmentInfo {
MetalTexture* texture = nullptr;
uint8_t level = 0;
uint16_t layer = 0;
private:
id<MTLTexture> getMSAASidecarTexture() const {
// This should only be called from render targets associated with a MetalTexture.
assert_invariant(metalTexture);
return metalTexture->msaaSidecar;
}
id<MTLTexture> texture = nil;
MetalTexture* metalTexture = nullptr;
};
MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height, uint8_t samples,
Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
Attachment depthAttachment, Attachment stencilAttachment);
AttachmentInfo colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
AttachmentInfo depthAttachment, AttachmentInfo stencilAttachment);
explicit MetalRenderTarget(MetalContext* context)
: HwRenderTarget(0, 0), context(context), defaultRenderTarget(true) {}
@@ -387,12 +419,14 @@ public:
math::uint2 getAttachmentSize() noexcept;
uint8_t getSamples() const { return samples; }
MetalAttachment getDrawColorAttachment(size_t index);
MetalAttachment getReadColorAttachment(size_t index);
MetalAttachment getDepthAttachment();
MetalAttachment getStencilAttachment();
Attachment getDrawColorAttachment(size_t index);
Attachment getReadColorAttachment(size_t index);
Attachment getDepthAttachment();
Attachment getStencilAttachment();
// Returns the number of samples that should be used in the pipeline state that renders to this
// render target. Takes into account "automagic" resolve and MSAA SwapChains.
NSUInteger getSampleCount() const;
private:
@@ -402,12 +436,12 @@ private:
MetalContext* context;
bool defaultRenderTarget = false;
uint8_t samples = 1;
Attachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
Attachment depth = {};
Attachment stencil = {};
MetalAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
MetalAttachment depth = {};
MetalAttachment stencil = {};
math::uint2 attachmentSize = {};
uint8_t samples = 1;
};
// MetalFence is used to implement both Fences and Syncs.
@@ -429,6 +463,8 @@ public:
API_AVAILABLE(ios(12.0))
void onSignal(MetalFenceSignalBlock block);
void cancel();
private:
MetalContext& context;
+274 -179
View File
@@ -116,7 +116,8 @@ MetalSwapChain::MetalSwapChain(
depthStencilFormat(decideDepthStencilFormat(flags)),
layer(nativeWindow),
layerDrawableMutex(std::make_shared<std::mutex>()),
type(SwapChainType::CAMETALLAYER) {
type(SwapChainType::CAMETALLAYER),
flags(flags) {
FILAMENT_CHECK_PRECONDITION([nativeWindow isKindOfClass:[CAMetalLayer class]])
<< "nativeWindow pointer of class "
@@ -147,7 +148,8 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform, i
depthStencilFormat(decideDepthStencilFormat(flags)),
headlessWidth(width),
headlessHeight(height),
type(SwapChainType::HEADLESS) {}
type(SwapChainType::HEADLESS),
flags(flags) {}
MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform,
CVPixelBufferRef pixelBuffer, uint64_t flags)
@@ -155,7 +157,8 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform,
platform(platform),
depthStencilFormat(decideDepthStencilFormat(flags)),
externalImage(MetalExternalImage::createFromImage(context, pixelBuffer)),
type(SwapChainType::CVPIXELBUFFERREF) {
type(SwapChainType::CVPIXELBUFFERREF),
flags(flags) {
assert_invariant(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
MetalExternalImage::assertWritableImage(pixelBuffer);
assert_invariant(externalImage.isValid());
@@ -167,6 +170,27 @@ MTLPixelFormat MetalSwapChain::decideDepthStencilFormat(uint64_t flags) {
: MTLPixelFormatDepth32Float;
}
id<MTLTexture> MetalSwapChain::createMultisampledTexture(MetalContext const& context,
MTLPixelFormat format, uint32_t width, uint32_t height, uint8_t samples) {
MTLTextureDescriptor* descriptor =
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format
width:width
height:height
mipmapped:NO];
descriptor.textureType = MTLTextureType2DMultisample;
descriptor.sampleCount = samples;
descriptor.usage = MTLTextureUsageRenderTarget;
descriptor.resourceOptions = MTLResourceStorageModePrivate;
if (context.supportsMemorylessRenderTargets) {
if (@available(macOS 11.0, *)) {
descriptor.resourceOptions = MTLResourceStorageModeMemoryless;
}
}
return [context.device newTextureWithDescriptor:descriptor];
}
MetalSwapChain::~MetalSwapChain() {
}
@@ -190,63 +214,16 @@ NSUInteger MetalSwapChain::getSurfaceHeight() const {
return (NSUInteger) layer.drawableSize.height;
}
bool MetalSwapChain::isAbandoned() const {
return context.currentFrame < abandonedUntilFrame;
NSUInteger MetalSwapChain::getSampleCount() const {
if (flags & flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
return 4u;
}
return 1u;
}
id<MTLTexture> MetalSwapChain::acquireDrawable() {
if (drawable) {
return drawable.texture;
}
if (isHeadless()) {
if (headlessDrawable) {
return headlessDrawable;
}
// For headless surfaces we construct a "fake" drawable, which is simply a renderable
// texture.
MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor new];
textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
textureDescriptor.width = headlessWidth;
textureDescriptor.height = headlessHeight;
// Specify MTLTextureUsageShaderRead so the headless surface can be blitted from.
textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
#if defined(FILAMENT_IOS)
textureDescriptor.storageMode = MTLStorageModeShared;
#else
textureDescriptor.storageMode = MTLStorageModeManaged;
#endif
headlessDrawable = [context.device newTextureWithDescriptor:textureDescriptor];
return headlessDrawable;
}
if (isPixelBuffer()) {
return externalImage.getMtlTexture();
}
assert_invariant(isCaMetalLayer());
// CAMetalLayer's drawable pool is not thread safe. Use a mutex when
// calling -nextDrawable, or when releasing the last known reference
// to any CAMetalDrawable returned from a previous -nextDrawable.
{
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
drawable = [layer nextDrawable];
}
if (UTILS_UNLIKELY(drawable == nil)) {
switch (platform.getDrawableFailureBehavior()) {
case PlatformMetal::DrawableFailureBehavior::PANIC:
FILAMENT_CHECK_POSTCONDITION(drawable != nil) << "Could not obtain drawable.";
break;
case PlatformMetal::DrawableFailureBehavior::ABORT_FRAME:
abandonedUntilFrame = context.currentFrame + 1;
return nil;
}
}
return drawable.texture;
bool MetalSwapChain::isAbandoned() const {
return context.currentFrame < abandonedUntilFrame;
}
void MetalSwapChain::releaseDrawable() {
@@ -256,32 +233,45 @@ void MetalSwapChain::releaseDrawable() {
}
}
id<MTLTexture> MetalSwapChain::acquireDepthTexture() {
ensureDepthStencilTexture();
assert_invariant(depthStencilTexture);
return depthStencilTexture;
}
id<MTLTexture> MetalSwapChain::acquireStencilTexture() {
if (!isMetalFormatStencil(depthStencilFormat)) {
return nil;
MetalAttachment MetalSwapChain::acquireDrawable() {
MetalAttachment attachment = acquireBaseDrawable();
if (auto texture = attachment.getTexture();
texture && flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
return attachment.withMsaaTexture(
ensureMsaaColorTexture(texture.pixelFormat, texture.width, texture.height, 4u));
}
ensureDepthStencilTexture();
assert_invariant(depthStencilTexture);
return depthStencilTexture;
return attachment;
}
void MetalSwapChain::ensureDepthStencilTexture() {
NSUInteger width = getSurfaceWidth();
NSUInteger height = getSurfaceHeight();
if (UTILS_LIKELY(depthStencilTexture)) {
// If the surface size has changed, we'll need to allocate a new depth/stencil texture.
if (UTILS_UNLIKELY(
depthStencilTexture.width != width || depthStencilTexture.height != height)) {
depthStencilTexture = nil;
} else {
return;
}
MetalAttachment MetalSwapChain::acquireDepthTexture() {
MetalAttachment attachment =
MetalAttachment(ensureDepthStencilTexture(getSurfaceWidth(), getSurfaceHeight()));
if (auto texture = attachment.getTexture();
texture && flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
return attachment.withMsaaTexture(ensureMsaaDepthStencilTexture(texture.pixelFormat,
texture.width, texture.height, 4u));
}
return attachment;
}
MetalAttachment MetalSwapChain::acquireStencilTexture() {
if (!(flags & SwapChain::CONFIG_HAS_STENCIL_BUFFER)) {
return MetalAttachment::invalidAttachment();
}
MetalAttachment attachment =
MetalAttachment(ensureDepthStencilTexture(getSurfaceWidth(), getSurfaceHeight()));
if (auto texture = attachment.getTexture();
texture && flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
return attachment.withMsaaTexture(ensureMsaaDepthStencilTexture(texture.pixelFormat,
texture.width, texture.height, 4u));
}
return attachment;
}
id<MTLTexture> MetalSwapChain::ensureDepthStencilTexture(uint32_t width, uint32_t height) {
if (UTILS_LIKELY(depthStencilTexture && depthStencilTexture.width == width &&
depthStencilTexture.height == height)) {
return depthStencilTexture;
}
MTLTextureDescriptor* descriptor =
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:depthStencilFormat
@@ -290,7 +280,26 @@ void MetalSwapChain::ensureDepthStencilTexture() {
mipmapped:NO];
descriptor.usage = MTLTextureUsageRenderTarget;
descriptor.resourceOptions = MTLResourceStorageModePrivate;
depthStencilTexture = [context.device newTextureWithDescriptor:descriptor];
return depthStencilTexture = [context.device newTextureWithDescriptor:descriptor];
}
id<MTLTexture> MetalSwapChain::ensureMsaaColorTexture(MTLPixelFormat format, uint32_t width, uint32_t height,
uint8_t samples) {
if (UTILS_LIKELY(msaaColor && msaaColor.pixelFormat == format && msaaColor.width == width &&
msaaColor.height == height && msaaColor.sampleCount == samples)) {
return msaaColor;
}
return msaaColor = createMultisampledTexture(context, format, width, height, samples);
}
id<MTLTexture> MetalSwapChain::ensureMsaaDepthStencilTexture(MTLPixelFormat format, uint32_t width,
uint32_t height, uint8_t samples) {
if (UTILS_LIKELY(msaaDepthStencil && msaaDepthStencil.pixelFormat == format &&
msaaDepthStencil.width == width && msaaDepthStencil.height == height &&
msaaDepthStencil.sampleCount == samples)) {
return msaaDepthStencil;
}
return msaaDepthStencil = createMultisampledTexture(context, format, width, height, samples);
}
void MetalSwapChain::setFrameScheduledCallback(
@@ -454,6 +463,62 @@ void MetalSwapChain::scheduleFrameCompletedCallback() {
}];
}
MetalAttachment MetalSwapChain::acquireBaseDrawable() {
if (drawable) {
return MetalAttachment(drawable.texture);
}
if (isHeadless()) {
if (headlessDrawable) {
return MetalAttachment(headlessDrawable);
}
// For headless surfaces we construct a "fake" drawable, which is simply a renderable
// texture.
MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor new];
textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
textureDescriptor.width = headlessWidth;
textureDescriptor.height = headlessHeight;
// Specify MTLTextureUsageShaderRead so the headless surface can be blitted from.
textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
#if defined(FILAMENT_IOS)
textureDescriptor.storageMode = MTLStorageModeShared;
#else
textureDescriptor.storageMode = MTLStorageModeManaged;
#endif
headlessDrawable = [context.device newTextureWithDescriptor:textureDescriptor];
return MetalAttachment(headlessDrawable);
}
if (isPixelBuffer()) {
return MetalAttachment(externalImage.getMtlTexture());
}
assert_invariant(isCaMetalLayer());
// CAMetalLayer's drawable pool is not thread safe. Use a mutex when
// calling -nextDrawable, or when releasing the last known reference
// to any CAMetalDrawable returned from a previous -nextDrawable.
{
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
drawable = [layer nextDrawable];
}
if (UTILS_UNLIKELY(drawable == nil)) {
switch (platform.getDrawableFailureBehavior()) {
case PlatformMetal::DrawableFailureBehavior::PANIC:
FILAMENT_CHECK_POSTCONDITION(drawable != nil) << "Could not obtain drawable.";
break;
case PlatformMetal::DrawableFailureBehavior::ABORT_FRAME:
abandonedUntilFrame = context.currentFrame + 1;
return MetalAttachment::invalidAttachment();
}
}
return MetalAttachment(drawable.texture);
}
MetalBufferObject::MetalBufferObject(MetalContext& context, BufferObjectBinding bindingType,
BufferUsage usage, uint32_t byteCount)
: HwBufferObject(byteCount), buffer(context, bindingType, usage, byteCount) {}
@@ -970,90 +1035,108 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
}
MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height,
uint8_t samples, Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
Attachment depthAttachment, Attachment stencilAttachment) :
HwRenderTarget(width, height), context(context), samples(samples) {
math::uint2 tmin = {std::numeric_limits<uint32_t>::max()};
UTILS_UNUSED_IN_RELEASE math::uint2 tmax = {0};
uint8_t samples, AttachmentInfo colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
AttachmentInfo depthAttachment, AttachmentInfo stencilAttachment)
: HwRenderTarget(width, height),
context(context),
samples(samples) {
math::uint2 tmin = { std::numeric_limits<uint32_t>::max() };
UTILS_UNUSED_IN_RELEASE math::uint2 tmax = { 0 };
UTILS_UNUSED_IN_RELEASE size_t attachmentCount = 0;
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (!colorAttachments[i]) {
MetalTexture* const texture = colorAttachments[i].texture;
const auto& level = colorAttachments[i].level;
const auto& layer = colorAttachments[i].layer;
if (!texture) {
continue;
}
color[i] = colorAttachments[i];
FILAMENT_CHECK_PRECONDITION(color[i].getSampleCount() <= samples)
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
<< "MetalRenderTarget was initialized with a MSAA COLOR" << i
<< " texture, but sample count is " << samples << ".";
auto t = color[i].metalTexture;
const auto twidth = std::max(1u, t->width >> color[i].level);
const auto theight = std::max(1u, t->height >> color[i].level);
const auto twidth = std::max(1u, texture->width >> level);
const auto theight = std::max(1u, texture->height >> level);
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
attachmentCount++;
id<MTLTexture> mtlTexture = texture->getMtlTextureForWrite();
color[i] = MetalAttachment(mtlTexture, level, layer);
// If we were given a single-sampled texture but the samples parameter is > 1, we create
// a multisampled sidecar texture and do a resolve automatically.
if (samples > 1 && color[i].getSampleCount() == 1) {
auto& sidecar = color[i].metalTexture->msaaSidecar;
if (samples > 1 && texture->samples == 1) {
auto& sidecar = texture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(color[i].getPixelFormat(),
color[i].metalTexture->width, color[i].metalTexture->height, samples);
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
texture->height, samples);
}
color[i] = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
}
}
if (depthAttachment) {
depth = depthAttachment;
if (depthAttachment.texture) {
MetalTexture* const texture = depthAttachment.texture;
const auto& level = depthAttachment.level;
const auto& layer = depthAttachment.layer;
FILAMENT_CHECK_PRECONDITION(depth.getSampleCount() <= samples)
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
<< "MetalRenderTarget was initialized with a MSAA DEPTH texture, but sample count "
"is "
<< samples << ".";
auto t = depth.metalTexture;
const auto twidth = std::max(1u, t->width >> depth.level);
const auto theight = std::max(1u, t->height >> depth.level);
const auto twidth = std::max(1u, texture->width >> level);
const auto theight = std::max(1u, texture->height >> level);
tmin = { math::min(tmin.x, twidth), math::min(tmin.y, theight) };
tmax = { math::max(tmax.x, twidth), math::max(tmax.y, theight) };
attachmentCount++;
id<MTLTexture> mtlTexture = texture->getMtlTextureForWrite();
depth = MetalAttachment(mtlTexture, level, layer);
// If we were given a single-sampled texture but the samples parameter is > 1, we create
// a multisampled sidecar texture and do a resolve automatically.
if (samples > 1 && depth.getSampleCount() == 1) {
auto& sidecar = depth.metalTexture->msaaSidecar;
if (samples > 1 && texture->samples == 1) {
auto& sidecar = texture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(depth.getPixelFormat(),
depth.metalTexture->width, depth.metalTexture->height, samples);
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
texture->height, samples);
}
depth = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
}
}
if (stencilAttachment) {
stencil = stencilAttachment;
if (stencilAttachment.texture) {
// stencil = stencilAttachment;
MetalTexture* const texture = stencilAttachment.texture;
const auto& level = stencilAttachment.level;
const auto& layer = stencilAttachment.layer;
FILAMENT_CHECK_PRECONDITION(stencil.getSampleCount() <= samples)
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
<< "MetalRenderTarget was initialized with a MSAA STENCIL texture, but sample "
"count is "
<< samples << ".";
auto t = stencil.metalTexture;
const auto twidth = std::max(1u, t->width >> stencil.level);
const auto theight = std::max(1u, t->height >> stencil.level);
const auto twidth = std::max(1u, texture->width >> level);
const auto theight = std::max(1u, texture->height >> level);
tmin = { math::min(tmin.x, twidth), math::min(tmin.y, theight) };
tmax = { math::max(tmax.x, twidth), math::max(tmax.y, theight) };
attachmentCount++;
id<MTLTexture> mtlTexture = texture->getMtlTextureForWrite();
stencil = MetalAttachment(mtlTexture, level, layer);
// If we were given a single-sampled texture but the samples parameter is > 1, we create
// a multisampled sidecar texture and do a resolve automatically.
if (samples > 1 && stencil.getSampleCount() == 1) {
auto& sidecar = stencil.metalTexture->msaaSidecar;
if (samples > 1 && texture->samples == 1) {
auto& sidecar = texture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(stencil.getPixelFormat(),
stencil.metalTexture->width, stencil.metalTexture->height, samples);
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
texture->height, samples);
}
stencil = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
}
}
@@ -1076,105 +1159,105 @@ void MetalRenderTarget::setUpRenderPassAttachments(MTLRenderPassDescriptor* desc
const auto discardFlags = params.flags.discardEnd;
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
Attachment attachment = getDrawColorAttachment(i);
MetalAttachment attachment = getDrawColorAttachment(i);
if (!attachment) {
continue;
}
descriptor.colorAttachments[i].texture = attachment.getTexture();
descriptor.colorAttachments[i].level = attachment.level;
descriptor.colorAttachments[i].slice = attachment.layer;
descriptor.colorAttachments[i].level = attachment.getLevel();
descriptor.colorAttachments[i].slice = attachment.getLayer();
descriptor.colorAttachments[i].loadAction = getLoadAction(params, getTargetBufferFlagsAt(i));
descriptor.colorAttachments[i].storeAction = getStoreAction(params,
getTargetBufferFlagsAt(i));
descriptor.colorAttachments[i].clearColor = MTLClearColorMake(
params.clearColor.r, params.clearColor.g, params.clearColor.b, params.clearColor.a);
const bool automaticResolve = samples > 1 && attachment.getSampleCount() == 1;
if (automaticResolve) {
if (attachment.getMsaaTexture()) {
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
// We're rendering into our temporary MSAA texture and doing an automatic resolve.
// We should not be attempting to load anything into the MSAA texture.
assert_invariant(descriptor.colorAttachments[i].loadAction != MTLLoadActionLoad);
assert_invariant(!defaultRenderTarget);
// This might happen if the user is rendering multiple views into a MSAA SwapChain,
// which is not supported. In that case, ignore the load action.
if (descriptor.colorAttachments[i].loadAction == MTLLoadActionLoad) {
descriptor.colorAttachments[i].loadAction = MTLLoadActionDontCare;
}
id<MTLTexture> sidecar = attachment.getMSAASidecarTexture();
assert_invariant(sidecar);
descriptor.colorAttachments[i].texture = sidecar;
descriptor.colorAttachments[i].texture = attachment.getMsaaTexture();
descriptor.colorAttachments[i].level = 0;
descriptor.colorAttachments[i].slice = 0;
const bool discard = any(discardFlags & getTargetBufferFlagsAt(i));
if (!discard) {
descriptor.colorAttachments[i].resolveTexture = attachment.texture;
descriptor.colorAttachments[i].resolveLevel = attachment.level;
descriptor.colorAttachments[i].resolveSlice = attachment.layer;
descriptor.colorAttachments[i].resolveTexture = attachment.getTexture();
descriptor.colorAttachments[i].resolveLevel = attachment.getLevel();
descriptor.colorAttachments[i].resolveSlice = attachment.getLayer();
descriptor.colorAttachments[i].storeAction = MTLStoreActionMultisampleResolve;
}
}
}
Attachment depthAttachment = getDepthAttachment();
MetalAttachment depthAttachment = getDepthAttachment();
if (depthAttachment) {
descriptor.depthAttachment.texture = depthAttachment.getTexture();
descriptor.depthAttachment.level = depthAttachment.level;
descriptor.depthAttachment.slice = depthAttachment.layer;
descriptor.depthAttachment.level = depthAttachment.getLevel();
descriptor.depthAttachment.slice = depthAttachment.getLayer();
descriptor.depthAttachment.loadAction = getLoadAction(params, TargetBufferFlags::DEPTH);
descriptor.depthAttachment.storeAction = getStoreAction(params, TargetBufferFlags::DEPTH);
descriptor.depthAttachment.clearDepth = params.clearDepth;
}
const bool depthAutomaticResolve = samples > 1 && depthAttachment.getSampleCount() == 1;
if (depthAutomaticResolve) {
if (depthAttachment.getMsaaTexture()) {
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
// We're rendering into our temporary MSAA texture and doing an automatic resolve.
// We should not be attempting to load anything into the MSAA texture.
assert_invariant(descriptor.depthAttachment.loadAction != MTLLoadActionLoad);
assert_invariant(!defaultRenderTarget);
// This might happen if the user is rendering multiple views into a MSAA SwapChain,
// which is not supported. In that case, ignore the load action.
if (descriptor.depthAttachment.loadAction == MTLLoadActionLoad) {
descriptor.depthAttachment.loadAction = MTLLoadActionDontCare;
}
id<MTLTexture> sidecar = depthAttachment.getMSAASidecarTexture();
assert_invariant(sidecar);
descriptor.depthAttachment.texture = sidecar;
descriptor.depthAttachment.texture = depthAttachment.getMsaaTexture();
descriptor.depthAttachment.level = 0;
descriptor.depthAttachment.slice = 0;
const bool discard = any(discardFlags & TargetBufferFlags::DEPTH);
if (!discard) {
assert_invariant(context->supportsAutoDepthResolve);
descriptor.depthAttachment.resolveTexture = depthAttachment.getTexture();
descriptor.depthAttachment.resolveLevel = depthAttachment.level;
descriptor.depthAttachment.resolveSlice = depthAttachment.layer;
descriptor.depthAttachment.resolveLevel = depthAttachment.getLevel();
descriptor.depthAttachment.resolveSlice = depthAttachment.getLayer();
descriptor.depthAttachment.storeAction = MTLStoreActionMultisampleResolve;
}
}
Attachment stencilAttachment = getStencilAttachment();
MetalAttachment stencilAttachment = getStencilAttachment();
if (stencilAttachment) {
descriptor.stencilAttachment.texture = stencilAttachment.getTexture();
descriptor.stencilAttachment.level = stencilAttachment.level;
descriptor.stencilAttachment.slice = stencilAttachment.layer;
descriptor.stencilAttachment.level = stencilAttachment.getLevel();
descriptor.stencilAttachment.slice = stencilAttachment.getLayer();
descriptor.stencilAttachment.loadAction = getLoadAction(params, TargetBufferFlags::STENCIL);
descriptor.stencilAttachment.storeAction = getStoreAction(params, TargetBufferFlags::STENCIL);
descriptor.stencilAttachment.clearStencil = params.clearStencil;
}
const bool stencilAutomaticResolve = samples > 1 && stencilAttachment.getSampleCount() == 1;
if (stencilAutomaticResolve) {
if (stencilAttachment.getMsaaTexture()) {
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
// We're rendering into our temporary MSAA texture and doing an automatic resolve.
// We should not be attempting to load anything into the MSAA texture.
assert_invariant(descriptor.stencilAttachment.loadAction != MTLLoadActionLoad);
assert_invariant(!defaultRenderTarget);
// This might happen if the user is rendering multiple views into a MSAA SwapChain,
// which is not supported. In that case, ignore the load action.
if (descriptor.stencilAttachment.loadAction == MTLLoadActionLoad) {
descriptor.stencilAttachment.loadAction = MTLLoadActionDontCare;
}
id<MTLTexture> sidecar = stencilAttachment.getMSAASidecarTexture();
assert_invariant(sidecar);
descriptor.stencilAttachment.texture = sidecar;
descriptor.stencilAttachment.texture = stencilAttachment.getMsaaTexture();
descriptor.stencilAttachment.level = 0;
descriptor.stencilAttachment.slice = 0;
const bool discard = any(discardFlags & TargetBufferFlags::STENCIL);
if (!discard) {
assert_invariant(context->supportsAutoDepthResolve);
descriptor.stencilAttachment.resolveTexture = stencilAttachment.getTexture();
descriptor.stencilAttachment.resolveLevel = stencilAttachment.level;
descriptor.stencilAttachment.resolveSlice = stencilAttachment.layer;
descriptor.stencilAttachment.resolveLevel = stencilAttachment.getLevel();
descriptor.stencilAttachment.resolveSlice = stencilAttachment.getLayer();
descriptor.stencilAttachment.storeAction = MTLStoreActionMultisampleResolve;
if (@available(iOS 12.0, *)) {
descriptor.stencilAttachment.stencilResolveFilter = MTLMultisampleStencilResolveFilterSample0;
@@ -1189,44 +1272,46 @@ bool MetalRenderTarget::involvesAbandonedSwapChain() const noexcept {
return (draw && draw->isAbandoned()) || (read && read->isAbandoned());
}
MetalRenderTarget::Attachment MetalRenderTarget::getDrawColorAttachment(size_t index) {
MetalAttachment MetalRenderTarget::getDrawColorAttachment(size_t index) {
assert_invariant(index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
Attachment result = color[index];
if (index == 0 && defaultRenderTarget) {
assert_invariant(context->currentDrawSwapChain);
result.texture = context->currentDrawSwapChain->acquireDrawable();
// acquireDrawable may fail to acquire the drawable, in which case result.texture will be
// nil, and an invalid attachment
return context->currentDrawSwapChain->acquireDrawable();
}
return result;
return color[index];
}
MetalRenderTarget::Attachment MetalRenderTarget::getReadColorAttachment(size_t index) {
MetalAttachment MetalRenderTarget::getReadColorAttachment(size_t index) {
assert_invariant(index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
Attachment result = color[index];
if (index == 0 && defaultRenderTarget) {
assert_invariant(context->currentReadSwapChain);
result.texture = context->currentReadSwapChain->acquireDrawable();
// acquireDrawable may fail to acquire the drawable, in which case result.texture will be
// nil, and an invalid attachment
return context->currentReadSwapChain->acquireDrawable();
}
return result;
return color[index];
}
MetalRenderTarget::Attachment MetalRenderTarget::getDepthAttachment() {
Attachment result = depth;
MetalAttachment MetalRenderTarget::getDepthAttachment() {
if (defaultRenderTarget) {
result.texture = context->currentDrawSwapChain->acquireDepthTexture();
assert_invariant(context->currentDrawSwapChain);
return context->currentDrawSwapChain->acquireDepthTexture();
}
return result;
return depth;
}
MetalRenderTarget::Attachment MetalRenderTarget::getStencilAttachment() {
Attachment result = stencil;
MetalAttachment MetalRenderTarget::getStencilAttachment() {
if (defaultRenderTarget) {
result.texture = context->currentDrawSwapChain->acquireStencilTexture();
assert_invariant(context->currentDrawSwapChain);
return context->currentDrawSwapChain->acquireStencilTexture();
}
return result;
return stencil;
}
NSUInteger MetalRenderTarget::getSampleCount() const {
if (defaultRenderTarget) {
assert_invariant(context->currentDrawSwapChain);
return context->currentDrawSwapChain->getSampleCount();
}
return samples;
}
MTLLoadAction MetalRenderTarget::getLoadAction(const RenderPassParams& params,
@@ -1310,20 +1395,30 @@ void MetalFence::onSignal(MetalFenceSignalBlock block) {
FenceStatus MetalFence::wait(uint64_t timeoutNs) {
if (@available(iOS 12, *)) {
using ns = std::chrono::nanoseconds;
// keep a reference on the stack so our state cannot be destroyed while we wait
auto state = this->state;
std::unique_lock<std::mutex> guard(state->mutex);
while (state->status == FenceStatus::TIMEOUT_EXPIRED) {
if (timeoutNs == FENCE_WAIT_FOR_EVER) {
state->cv.wait(guard);
} else if (timeoutNs == 0 ||
state->cv.wait_for(guard, ns(timeoutNs)) == std::cv_status::timeout) {
return FenceStatus::TIMEOUT_EXPIRED;
return state->status;
}
}
return FenceStatus::CONDITION_SATISFIED;
return state->status;
}
return FenceStatus::ERROR;
}
void MetalFence::cancel() {
std::unique_lock guard(state->mutex);
state->status = FenceStatus::ERROR;
state->cv.notify_all();
}
MetalDescriptorSetLayout::MetalDescriptorSetLayout(DescriptorSetLayout&& l) noexcept
: mLayout(std::move(l)) {
size_t dynamicBindings = 0;
+14
View File
@@ -450,4 +450,18 @@ void NoopDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t
BufferDescriptor&& data) {
}
bool NoopDriver::isCompositorTimingSupported() {
return false;
}
bool NoopDriver::queryCompositorTiming(backend::SwapChainHandle swapChain,
backend::CompositorTiming* outCompositorTiming) {
return false;
}
bool NoopDriver::queryFrameTimestamps(SwapChainHandle swapChain, uint64_t frameId,
FrameTimestamps* outFrameTimestamps) {
return false;
}
} // namespace filament
@@ -303,13 +303,10 @@ void GLDescriptorSet::bind(
offset += offsets[dynamicOffsetIndex++];
}
if (arg.bo) {
auto buffer = static_cast<char const*>(arg.bo->gl.buffer) + offset;
p.updateUniforms(bindingPoint, arg.bo->gl.id, buffer, arg.bo->age);
p.updateUniforms(bindingPoint, arg.bo->gl.id, arg.bo->gl.buffer, arg.bo->age, offset);
}
} else if constexpr (std::is_same_v<T, Sampler>) {
GLuint const unit = p.getTextureUnit(set, binding);
if (arg.handle) {
GLTexture const* const t = handleAllocator.handle_cast<GLTexture*>(arg.handle);
gl.bindTexture(unit, t->gl.target, t->gl.id, t->gl.external);
+42 -3
View File
@@ -807,8 +807,9 @@ void OpenGLDriver::textureStorage(GLTexture* t,
for (GLint face = 0 ; face < 6 ; face++) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
level, GLint(t->gl.internalFormat),
GLsizei(width), GLsizei(height), 0,
format, type, nullptr);
std::max(GLsizei(1), GLsizei(width >> level)),
std::max(GLsizei(1), GLsizei(height >> level)),
0, format, type, nullptr);
}
} else {
glTexImage2D(t->gl.target, level, GLint(t->gl.internalFormat),
@@ -2287,9 +2288,14 @@ mat3f OpenGLDriver::getStreamTransformMatrix(Handle<HwStream> sh) {
void OpenGLDriver::destroyFence(Handle<HwFence> fh) {
if (fh) {
GLFence const* f = handle_cast<GLFence*>(fh);
GLFence const* const f = handle_cast<GLFence*>(fh);
if (mPlatform.canCreateFence() || mContext.isES2()) {
mPlatform.destroyFence(f->fence);
} else {
// signal waiters it's time to give-up
std::unique_lock const lock(f->state->lock);
f->state->status = FenceStatus::ERROR;
f->state->cond.notify_all();
}
destruct(fh, f);
}
@@ -2698,6 +2704,39 @@ void OpenGLDriver::commit(Handle<HwSwapChain> sch) {
#endif
}
bool OpenGLDriver::isCompositorTimingSupported() {
// this is a synchronous call
return mPlatform.isCompositorTimingSupported();
}
bool OpenGLDriver::queryCompositorTiming(SwapChainHandle swapChain,
CompositorTiming* outCompositorTiming) {
// this is a synchronous call
if (!swapChain) {
return false;
}
GLSwapChain const* const sc = handle_cast<GLSwapChain*>(swapChain);
if (!sc) {
// can happen if the SwapChainHandle is not initialized yet (still in CommandStream)
return false;
}
return mPlatform.queryCompositorTiming(sc->swapChain, outCompositorTiming);
}
bool OpenGLDriver::queryFrameTimestamps(SwapChainHandle swapChain, uint64_t const frameId,
FrameTimestamps* outFrameTimestamps) {
// this is a synchronous call
if (!swapChain) {
return false;
}
GLSwapChain const* const sc = handle_cast<GLSwapChain*>(swapChain);
if (!sc) {
// can happen if the SwapChainHandle is not initialized yet (still in CommandStream)
return false;
}
return mPlatform.queryFrameTimestamps(sc->swapChain, frameId, outFrameTimestamps);
}
void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain> schRead) {
DEBUG_MARKER()
@@ -262,20 +262,25 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
}
void OpenGLProgram::updateUniforms(
uint32_t index, GLuint id, void const* buffer, uint16_t age) const noexcept {
uint32_t const index, GLuint const id, void const* buffer,
uint16_t const age, uint32_t const offset) const noexcept {
assert_invariant(mUniformsRecords);
assert_invariant(buffer);
// only update the uniforms if the UBO has changed since last time we updated
UniformsRecord const& records = mUniformsRecords[index];
if (records.id == id && records.age == age) {
if (records.id == id && records.age == age && records.offset == offset) {
return;
}
records.id = id;
records.age = age;
records.offset = offset;
assert_invariant(records.uniforms.size() == records.locations.size());
// apply the offset to the buffer
buffer = static_cast<char const*>(buffer) + offset;
for (size_t i = 0, c = records.uniforms.size(); i < c; i++) {
Program::Uniform const& u = records.uniforms[i];
GLint const loc = records.locations[i];
+2 -1
View File
@@ -88,7 +88,7 @@ public:
}
// For ES2 only
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) const noexcept;
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age, uint32_t offset) const noexcept;
void setRec709ColorSpace(bool rec709) const noexcept;
PushConstantBundle getPushConstants() {
@@ -123,6 +123,7 @@ private:
LocationInfo locations;
mutable GLuint id = 0;
mutable uint16_t age = std::numeric_limits<uint16_t>::max();
mutable uint32_t offset = 0;
};
UniformsRecord const* mUniformsRecords = nullptr;
GLint mRec709Location : 24; // 4 bytes
@@ -58,7 +58,6 @@
#include <new>
#include <string_view>
#include <dlfcn.h>
#include <unistd.h>
#include <stddef.h>
#include <stdint.h>
@@ -116,31 +115,9 @@ PlatformEGLAndroid::PlatformEGLAndroid() noexcept
if (mOSVersion < 0) {
mOSVersion = __ANDROID_API_FUTURE__;
}
// TODO: remove this code one the new NDK is available
void* nativeWindowLibHandle = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW);
if (nativeWindowLibHandle) {
ANativeWindow_setProducerThrottlingEnabled =
(int32_t(*)(ANativeWindow*, bool))dlsym(nativeWindowLibHandle,
"ANativeWindow_setProducerThrottlingEnabled");
ANativeWindow_isProducerThrottlingEnabled =
(int32_t(*)(ANativeWindow*, bool*))dlsym(nativeWindowLibHandle,
"ANativeWindow_isProducerThrottlingEnabled");
if (ANativeWindow_setProducerThrottlingEnabled &&
ANativeWindow_isProducerThrottlingEnabled) {
mHasProducerThrottlingControl = true;
LOG(INFO) << "Producer Throttling API available";
}
}
}
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
// note: we don't need to dlclose() mNativeWindowLib here, because the library will be cleaned
// when the process ends and dlopen() are ref-counted. dlclose() NDK documentation documents
// not to call dlclose().
}
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept = default;
void PlatformEGLAndroid::terminate() noexcept {
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
@@ -365,8 +342,8 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
outFrameTimestamps->requestedPresentTime = values[0];
outFrameTimestamps->acquireTime = values[1];
outFrameTimestamps->latchTime = values[2];
outFrameTimestamps->firstRefreshStartTime = values[3];
outFrameTimestamps->lastRefreshStartTime = values[4];
outFrameTimestamps->firstCompositionStartTime = values[3];
outFrameTimestamps->lastCompositionStartTime = values[4];
outFrameTimestamps->gpuCompositionDoneTime = values[5];
outFrameTimestamps->displayPresentTime = values[6];
outFrameTimestamps->dequeueReadyTime = values[7];
@@ -380,8 +357,8 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
&outFrameTimestamps->requestedPresentTime,
&outFrameTimestamps->acquireTime,
&outFrameTimestamps->latchTime,
&outFrameTimestamps->firstRefreshStartTime,
&outFrameTimestamps->lastRefreshStartTime,
&outFrameTimestamps->firstCompositionStartTime,
&outFrameTimestamps->lastCompositionStartTime,
&outFrameTimestamps->gpuCompositionDoneTime,
&outFrameTimestamps->displayPresentTime,
&outFrameTimestamps->dequeueReadyTime,
@@ -419,12 +396,13 @@ Platform::ExternalImageHandle PlatformEGLAndroid::createExternalImage(
auto const hardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
AHardwareBuffer_acquire(hardwareBuffer);
p->aHardwareBuffer = hardwareBuffer;
p->sRGB = sRGB;
AHardwareBuffer_Desc hardwareBufferDescription = {};
AHardwareBuffer_describe(hardwareBuffer, &hardwareBufferDescription);
p->height = hardwareBufferDescription.height;
p->width = hardwareBufferDescription.width;
auto const textureFormat = mapToFilamentFormat(hardwareBufferDescription.format, sRGB);
// Only set sRGB as true if the filament format requires it, otherwise the eglCreateImage might fail.
p->sRGB = textureFormat == TextureFormat::SRGB8 || textureFormat == TextureFormat::SRGB8_A8;
p->format = textureFormat;
p->usage = mapToFilamentUsage(hardwareBufferDescription.usage, textureFormat);
return ExternalImageHandle{ p };
@@ -567,7 +545,15 @@ void PlatformEGLAndroid::destroyStream(Stream* stream) noexcept {
}
Platform::Sync* PlatformEGLAndroid::createSync() noexcept {
auto const sync = eglCreateSyncKHR(getEglDisplay(), EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
EGLSyncKHR sync = EGL_NO_SYNC_KHR;
if (UTILS_LIKELY(ext.egl.ANDROID_native_fence_sync)) {
sync = eglCreateSyncKHR(getEglDisplay(), EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
if (sync == EGL_NO_SYNC_KHR) {
LOG(ERROR) << "Failed to create sync: " << eglGetError();
}
} else {
LOG(WARNING) << "Native fences not supported on this device.";
}
return new(std::nothrow) SyncEGLAndroid{ .sync = sync };
}
@@ -575,10 +561,16 @@ bool PlatformEGLAndroid::convertSyncToFd(Sync* sync, int* fd) noexcept {
assert_invariant(sync && fd);
if (UTILS_UNLIKELY(!ext.egl.ANDROID_native_fence_sync)) {
LOG(WARNING) << "Native fences not supported, cannot convert to fd.";
return false;
}
SyncEGLAndroid const& eglSync = static_cast<SyncEGLAndroid&>(*sync);
if (eglSync.sync == EGL_NO_SYNC_KHR) {
LOG(ERROR) << "Invalid fence, cannot convert to fd.";
return false;
}
*fd = eglDupNativeFenceFDANDROID(getEglDisplay(), eglSync.sync);
// In the case where there was no native FD, -1 is returned. Return false
// to indicate there was an error in this case.
@@ -591,8 +583,12 @@ bool PlatformEGLAndroid::convertSyncToFd(Sync* sync, int* fd) noexcept {
void PlatformEGLAndroid::destroySync(Sync* sync) noexcept {
assert_invariant(sync);
SyncEGLAndroid const& eglSync = static_cast<SyncEGLAndroid&>(*sync);
eglDestroySyncKHR(getEglDisplay(), eglSync.sync);
if (UTILS_LIKELY(ext.egl.ANDROID_native_fence_sync)) {
SyncEGLAndroid const& eglSync = static_cast<SyncEGLAndroid&>(*sync);
if (eglSync.sync != EGL_NO_SYNC_KHR) {
eglDestroySyncKHR(getEglDisplay(), eglSync.sync);
}
}
delete sync;
}
@@ -666,6 +662,16 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage const sou
return { eglImage, patchedCallback, closure, source.handler };
}
bool PlatformEGLAndroid::isProducerThrottlingControlSupported() const {
return mProducerThrottling.isSupported();
}
int32_t PlatformEGLAndroid::setProducerThrottlingEnabled(
EGLNativeWindowType const nativeWindow, bool const enabled) const {
return mProducerThrottling.setProducerThrottlingEnabled(nativeWindow, enabled);
}
// ---------------------------------------------------------------------------------------------
// PlatformEGLAndroid::SwapChainEGLAndroid
@@ -673,9 +679,9 @@ PlatformEGLAndroid::SwapChainEGLAndroid::SwapChainEGLAndroid(PlatformEGLAndroid
void* nativeWindow, uint64_t const flags)
: SwapChainEGL(platform, nativeWindow, flags) {
if (nativeWindow && platform.mHasProducerThrottlingControl) {
if (nativeWindow && platform.isProducerThrottlingControlSupported()) {
// Disable Producer Throttling when supported. This allows eglSwapBuffers() to not stall
int32_t const result = platform.ANativeWindow_setProducerThrottlingEnabled(
int32_t const result = platform.setProducerThrottlingEnabled(
EGLNativeWindowType(nativeWindow), false);
if (UTILS_UNLIKELY(result < 0)) {
LOG(WARNING) << "ANativeWindow_setProducerThrottlingEnabled(false) failed: " << result;
-19
View File
@@ -1,19 +0,0 @@
#version 320 es
precision mediump float;
precision lowp sampler2DMS;
layout(set = 0, binding = 0, std140) uniform ParamsBlock {
int sampleCount;
float inverseSampleCount;
} params;
layout(set = 1, binding = 0) uniform sampler2DMS tex;
void main() {
float depth = 0.0;
for (int sampleIndex = 0; sampleIndex < params.sampleCount; sampleIndex++) {
depth += texelFetch(tex, ivec2(gl_FragCoord.xy), sampleIndex).r;
}
gl_FragDepth = depth * params.inverseSampleCount;
}
-7
View File
@@ -1,7 +0,0 @@
#version 320 es
layout(location = 0) in vec4 pos;
void main() {
gl_Position = pos;
}
@@ -32,27 +32,31 @@ namespace filament::backend {
FenceStatus VulkanCmdFence::wait(VkDevice device, uint64_t const timeout,
std::chrono::steady_clock::time_point const until) {
{
std::shared_lock l(mLock);
// this lock MUST be held for READ when calling vkWaitForFences()
std::shared_lock rl(mLock);
// If the vulkan fence has not been submitted yet, we need to wait for that before we
// can use vkWaitForFences()
if (mStatus == VK_INCOMPLETE) {
bool const success = mCond.wait_until(l, until, [this] {
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted".
// When this fence gets submitted, its status changes to VK_NOT_READY.
return mStatus != VK_INCOMPLETE;
});
if (!success) {
// !success indicates a timeout
return FenceStatus::TIMEOUT_EXPIRED;
}
// If the vulkan fence has not been submitted yet, we need to wait for that before we
// can use vkWaitForFences()
if (mStatus == VK_INCOMPLETE) {
bool const success = mCond.wait_until(rl, until, [this] {
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted".
// When this fence gets submitted, its status changes to VK_NOT_READY.
return mStatus != VK_INCOMPLETE || mCanceled;
});
if (!success) {
// !success indicates a timeout or cancel
return mCanceled ? FenceStatus::ERROR : FenceStatus::TIMEOUT_EXPIRED;
}
}
// The fence could have already signaled, avoid calling into vkWaitForFences()
if (mStatus == VK_SUCCESS) {
return FenceStatus::CONDITION_SATISFIED;
}
// The fence could have already signaled, avoid calling into vkWaitForFences()
if (mStatus == VK_SUCCESS) {
return FenceStatus::CONDITION_SATISFIED;
}
// Or it could have been canceled, return immediately
if (mCanceled) {
return FenceStatus::ERROR;
}
// If we're here, we know that vkQueueSubmit has been called (because it sets the status
@@ -62,13 +66,14 @@ FenceStatus VulkanCmdFence::wait(VkDevice device, uint64_t const timeout,
// place simultaneously. vkResetFence is only called once it knows the fence has signaled,
// which guaranties that vkResetFence won't have to wait too long, just enough for
// all the vkWaitForFences() to return.
VkResult status = vkWaitForFences(device, 1, &mFence, VK_TRUE, timeout);
VkResult const status = vkWaitForFences(device, 1, &mFence, VK_TRUE, timeout);
if (status == VK_TIMEOUT) {
return FenceStatus::TIMEOUT_EXPIRED;
}
if (status == VK_SUCCESS) {
std::lock_guard const l(mLock);
rl.unlock();
std::lock_guard const wl(mLock);
mStatus = status;
return FenceStatus::CONDITION_SATISFIED;
}
@@ -56,9 +56,16 @@ struct VulkanCmdFence {
FenceStatus wait(VkDevice device, uint64_t timeout,
std::chrono::steady_clock::time_point until);
void cancel() {
std::lock_guard const l(mLock);
mCanceled = true;
mCond.notify_all();
}
private:
std::shared_mutex mLock; // NOLINT(*-include-cleaner)
std::condition_variable_any mCond;
bool mCanceled = false;
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence
// gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually
// finishes executing the command buffer, the status changes to VK_SUCCESS.
@@ -70,28 +77,43 @@ struct VulkanFence : public HwFence, fvkmemory::ThreadSafeResource {
VulkanFence() {}
void setFence(std::shared_ptr<VulkanCmdFence> fence) {
std::lock_guard lock(mState->lock);
std::lock_guard const lock(mState->lock);
mState->sharedFence = std::move(fence);
mState->cond.notify_all();
}
std::shared_ptr<VulkanCmdFence>& getSharedFence() {
std::lock_guard lock(mState->lock);
std::lock_guard const lock(mState->lock);
return mState->sharedFence;
}
std::shared_ptr<VulkanCmdFence> wait(std::chrono::steady_clock::time_point const until) {
std::pair<std::shared_ptr<VulkanCmdFence>, bool>
wait(std::chrono::steady_clock::time_point const until) {
// hold a reference so that our state doesn't disappear while we wait
std::shared_ptr state{ mState };
std::unique_lock lock(state->lock);
state->cond.wait_until(lock, until, [&state] { return bool(state->sharedFence); });
state->cond.wait_until(lock, until, [&state] {
return bool(state->sharedFence) || state->canceled;
});
// here mSharedFence will be null if we timed out
return state->sharedFence;
return { state->sharedFence, state->canceled };
}
void cancel() const {
std::shared_ptr const state{ mState };
std::unique_lock const lock(state->lock);
if (state->sharedFence) {
state->sharedFence->cancel();
}
state->canceled = true;
state->cond.notify_all();
}
private:
struct State {
std::mutex lock;
std::condition_variable cond;
bool canceled = false;
std::shared_ptr<VulkanCmdFence> sharedFence;
};
std::shared_ptr<State> mState{ std::make_shared<State>() };
+49 -3
View File
@@ -419,6 +419,10 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) {
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
if (mCurrentSwapChain) { // This should be guaranteed
mPlatform->setPresentFrameId(mCurrentSwapChain->swapChain, frameId);
}
// Check if any command have finished and reset all its used resources. The resources
// wont be destroyed but their reference count will decreased if the command is already
// completed.
@@ -915,6 +919,15 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
FVK_LOGW << "protected swapchain requested, but Platform does not support it";
}
}
#if defined(__ANDROID__)
// on Android, disable producer throttling
if (mProducerThrottling.isSupported()) {
mProducerThrottling.setProducerThrottlingEnabled(
static_cast<ANativeWindow*>(nativeWindow), false);
}
#endif
auto swapChain = resource_ptr<VulkanSwapChain>::make(&mResourceManager, sch, mPlatform,
mContext, &mResourceManager, mAllocator, &mCommands, mStagePool, nativeWindow, flags);
swapChain.inc();
@@ -1156,6 +1169,7 @@ void VulkanDriver::updateStreams(CommandStream* driver) {
void VulkanDriver::destroyFence(Handle<HwFence> fh) {
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
fence->cancel();
fence.dec();
}
@@ -1180,9 +1194,9 @@ FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout
until = now + nanoseconds(timeout);
}
std::shared_ptr const cmdfence = fence->wait(until);
if (!cmdfence) {
return FenceStatus::TIMEOUT_EXPIRED;
auto const [cmdfence, canceled] = fence->wait(until);
if (!cmdfence || canceled) {
return canceled ? FenceStatus::ERROR : FenceStatus::TIMEOUT_EXPIRED;
}
// now we are holding a reference to our VulkanCmdFence, so we know it can't
@@ -1759,6 +1773,38 @@ void VulkanDriver::nextSubpass(int) {
}
}
bool VulkanDriver::isCompositorTimingSupported() {
return mPlatform->isCompositorTimingSupported();
}
bool VulkanDriver::queryCompositorTiming(Handle<HwSwapChain> const swapChain,
CompositorTiming* outCompositorTiming) {
// this is a synchronous call
if (!swapChain) {
return false;
}
auto sc = resource_ptr<VulkanSwapChain>::cast(&mResourceManager, swapChain);
if (!sc) {
// can happen if the SwapChainHandle is not initialized yet (still in CommandStream)
return false;
}
return sc->queryCompositorTiming(outCompositorTiming);
}
bool VulkanDriver::queryFrameTimestamps(Handle<HwSwapChain> const swapChain, uint64_t const frameId,
FrameTimestamps* outFrameTimestamps) {
// this is a synchronous call
if (!swapChain) {
return false;
}
auto sc = resource_ptr<VulkanSwapChain>::cast(&mResourceManager, swapChain);
if (!sc) {
// can happen if the SwapChainHandle is not initialized yet (still in CommandStream)
return false;
}
return sc->queryFrameTimestamps(frameId, outFrameTimestamps);
}
void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
FVK_SYSTRACE_SCOPE();
@@ -48,6 +48,10 @@
#include <utils/Allocator.h>
#include <utils/compiler.h>
#if defined(__ANDROID__)
#include "AndroidNativeWindow.h"
#endif
namespace filament::backend {
class VulkanPlatform;
@@ -193,6 +197,9 @@ private:
bool const mIsSRGBSwapChainSupported;
bool const mIsMSAASwapChainSupported;
backend::StereoscopicType const mStereoscopicType;
#if defined(__ANDROID__)
AndroidProducerThrottling mProducerThrottling;
#endif
};
} // namespace filament::backend
@@ -35,8 +35,6 @@ using namespace bluevk;
namespace filament::backend {
struct VulkanHeadlessSwapChain;
struct VulkanSurfaceSwapChain;
class VulkanCommands;
// A wrapper around the platform implementation of swapchain.
@@ -92,6 +90,14 @@ struct VulkanSwapChain : public HwSwapChain, fvkmemory::Resource {
mFrameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
}
bool queryCompositorTiming(CompositorTiming* outCompositorTiming) const {
return mPlatform->queryCompositorTiming(swapChain, outCompositorTiming);
}
bool queryFrameTimestamps(uint64_t frameId, FrameTimestamps* outFrameTimestamps) const {
return mPlatform->queryFrameTimestamps(swapChain, frameId, outFrameTimestamps);
}
private:
static constexpr int IMAGE_READY_SEMAPHORE_COUNT = FVK_MAX_COMMAND_BUFFERS;
@@ -966,10 +966,35 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
// The VulkanPlatformSurfaceSwapChain now `owns` the surface.
VulkanPlatformSurfaceSwapChain* swapchain = new VulkanPlatformSurfaceSwapChain(mImpl->mContext,
mImpl->mPhysicalDevice, mImpl->mDevice, mImpl->mGraphicsQueue, mImpl->mInstance,
surface, fallbackExtent, flags);
surface, fallbackExtent, nativeWindow, flags);
return swapchain;
}
bool VulkanPlatform::isCompositorTimingSupported() const noexcept {
#ifdef __ANDROID__
return true;
#else
return Platform::isCompositorTimingSupported();
#endif
}
bool VulkanPlatform::queryCompositorTiming(SwapChain const* swapchain,
CompositorTiming* outCompositorTiming) const noexcept {
auto vulkanSwapchain = static_cast<VulkanPlatformSwapChainBase const *>(swapchain);
return vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
}
bool VulkanPlatform::setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept {
auto vulkanSwapchain = static_cast<VulkanPlatformSwapChainBase const *>(swapchain);
return vulkanSwapchain->setPresentFrameId(frameId);
}
bool VulkanPlatform::queryFrameTimestamps(SwapChain const* swapchain, uint64_t frameId,
FrameTimestamps* outFrameTimestamps) const noexcept {
auto vulkanSwapchain = static_cast<VulkanPlatformSwapChainBase const *>(swapchain);
return vulkanSwapchain->queryFrameTimestamps(frameId, outFrameTimestamps);
}
Platform::Sync* VulkanPlatform::createSync(VkFence fence,
std::shared_ptr<VulkanCmdFence> fenceStatus) noexcept {
return new VulkanSync{.fence = fence, .fenceStatus = fenceStatus};
@@ -23,6 +23,10 @@
#include <backend/DriverEnums.h>
#ifdef __ANDROID__
#include <AndroidNativeWindow.h>
#endif
using namespace bluevk;
using namespace utils;
@@ -123,9 +127,23 @@ VkImage VulkanPlatformSwapChainBase::createImage(VkExtent2D extent, VkFormat for
return image;
}
bool VulkanPlatformSwapChainBase::queryCompositorTiming(
CompositorTiming* outCompositorTiming) const {
return false;
}
bool VulkanPlatformSwapChainBase::setPresentFrameId(uint64_t frameId) const {
return false;
}
bool VulkanPlatformSwapChainBase::queryFrameTimestamps(uint64_t frameId,
FrameTimestamps* outFrameTimestamps) const {
return false;
}
VulkanPlatformSurfaceSwapChain::VulkanPlatformSurfaceSwapChain(VulkanContext const& context,
VkPhysicalDevice physicalDevice, VkDevice device, VkQueue queue, VkInstance instance,
VkSurfaceKHR surface, VkExtent2D fallbackExtent, uint64_t flags)
VkSurfaceKHR surface, VkExtent2D fallbackExtent, void* nativeWindow, uint64_t flags)
: VulkanPlatformSwapChainBase(context, device, queue),
mInstance(instance),
mPhysicalDevice(physicalDevice),
@@ -133,7 +151,8 @@ VulkanPlatformSurfaceSwapChain::VulkanPlatformSurfaceSwapChain(VulkanContext con
mFallbackExtent(fallbackExtent),
mUsesRGB((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0),
mHasStencil((flags & backend::SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0),
mIsProtected((flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) != 0) {
mIsProtected((flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) != 0),
mNativeWindow(nativeWindow) {
assert_invariant(surface);
create();
}
@@ -144,6 +163,10 @@ VulkanPlatformSurfaceSwapChain::~VulkanPlatformSurfaceSwapChain() {
}
VkResult VulkanPlatformSurfaceSwapChain::create() {
#ifdef __ANDROID__
NativeWindow::enableFrameTimestamps(static_cast<ANativeWindow*>(mNativeWindow), true);
#endif
VkSurfaceFormatKHR surfaceFormat = {};
VkSurfaceCapabilitiesKHR caps;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDevice, mSurface, &caps);
@@ -329,6 +352,55 @@ bool VulkanPlatformSurfaceSwapChain::isProtected() const {
return mIsProtected;
}
bool VulkanPlatformSurfaceSwapChain::queryCompositorTiming(
CompositorTiming* outCompositorTiming) const {
#ifdef __ANDROID__
// fallback to private APIs
int const status = NativeWindow::getCompositorTiming(
static_cast<ANativeWindow*>(mNativeWindow),
&outCompositorTiming->compositeDeadline,
&outCompositorTiming->compositeInterval,
&outCompositorTiming->compositeToPresentLatency);
if (status == 0) {
return true;
}
#endif
return VulkanPlatformSwapChainBase::queryCompositorTiming(outCompositorTiming);
}
bool VulkanPlatformSurfaceSwapChain::setPresentFrameId(uint64_t frameId) const {
#ifdef __ANDROID__
return mImpl.setPresentFrameId(static_cast<ANativeWindow*>(mNativeWindow), frameId);
#endif
return VulkanPlatformSwapChainBase::setPresentFrameId(frameId);
}
bool VulkanPlatformSurfaceSwapChain::queryFrameTimestamps(uint64_t const frameId,
FrameTimestamps* outFrameTimestamps) const {
#ifdef __ANDROID__
uint64_t const hwFrameId = mImpl.getFrameId(frameId);
if (hwFrameId == std::numeric_limits<uint64_t>::max()) {
return false;
}
// fallback to private APIs
int const status = NativeWindow::getFrameTimestamps(
static_cast<ANativeWindow*>(mNativeWindow), hwFrameId,
&outFrameTimestamps->requestedPresentTime,
&outFrameTimestamps->acquireTime,
&outFrameTimestamps->latchTime,
&outFrameTimestamps->firstCompositionStartTime,
&outFrameTimestamps->lastCompositionStartTime,
&outFrameTimestamps->gpuCompositionDoneTime,
&outFrameTimestamps->displayPresentTime,
&outFrameTimestamps->dequeueReadyTime,
&outFrameTimestamps->releaseTime);
if (status == 0) {
return true;
}
#endif
return VulkanPlatformSwapChainBase::queryFrameTimestamps(frameId, outFrameTimestamps);
}
// Non-virtual override
VkResult VulkanPlatformSurfaceSwapChain::recreate() {
destroy();
@@ -24,6 +24,10 @@
#include <bluevk/BlueVK.h>
#ifdef __ANDROID__
#include <AndroidSwapChainHelper.h>
#endif
#include <unordered_map>
using namespace bluevk;
@@ -51,6 +55,12 @@ struct VulkanPlatformSwapChainBase : public Platform::SwapChain {
virtual bool isProtected() const = 0;
virtual bool queryCompositorTiming(CompositorTiming* outCompositorTiming) const;
virtual bool setPresentFrameId(uint64_t frameId) const;
virtual bool queryFrameTimestamps(uint64_t frameId, FrameTimestamps* outFrameTimestamps) const;
protected:
virtual void destroy() = 0;
@@ -67,7 +77,7 @@ protected:
struct VulkanPlatformSurfaceSwapChain : public VulkanPlatformSwapChainBase {
VulkanPlatformSurfaceSwapChain(VulkanContext const& context, VkPhysicalDevice physicalDevice,
VkDevice device, VkQueue queue, VkInstance instance, VkSurfaceKHR surface,
VkExtent2D fallbackExtent, uint64_t flags);
VkExtent2D fallbackExtent, void* nativeWindow, uint64_t flags);
~VulkanPlatformSurfaceSwapChain() override;
@@ -84,6 +94,12 @@ struct VulkanPlatformSurfaceSwapChain : public VulkanPlatformSwapChainBase {
protected:
virtual void destroy() override;
bool queryCompositorTiming(CompositorTiming* outCompositorTiming) const override;
bool setPresentFrameId(uint64_t frameId) const override;
bool queryFrameTimestamps(uint64_t frameId, FrameTimestamps* outFrameTimestamps) const override;
private:
static constexpr int IMAGE_READY_SEMAPHORE_COUNT = FVK_MAX_COMMAND_BUFFERS;
@@ -102,6 +118,11 @@ private:
bool const mHasStencil = false;
bool const mIsProtected = false;
bool mSuboptimal;
UTILS_UNUSED void* mNativeWindow = nullptr;
#ifdef __ANDROID__
AndroidSwapChainHelper mImpl{};
#endif
};
struct VulkanPlatformHeadlessSwapChain : public VulkanPlatformSwapChainBase {
@@ -17,6 +17,7 @@
#include "WebGPUBufferBase.h"
#include "WebGPUConstants.h"
#include "WebGPUQueueManager.h"
#include "DriverBase.h"
#include <backend/BufferDescriptor.h>
@@ -59,12 +60,12 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
: mBuffer{ createBuffer(device, usage, size, label) } {}
// Updates the GPU buffer with data from a BufferDescriptor.
// WebGPU requires that the size of the data written to a buffer is a multiple of 4.
// This function handles cases where the buffer descriptor's size is not a multiple of 4
// by writing the bulk of the data first, and then copying the remaining bytes into a
// padded temporary chunk which is then written to the buffer.
// WebGPU requires that the size of the data copied from the staging buffer to the GPU buffer is a
// multiple of 4. This function handles cases where the buffer descriptor's size is not a multiple
// of 4 by padding with zeros.
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
const uint32_t byteOffset, wgpu::Queue const& queue) {
const uint32_t byteOffset, wgpu::Device const& device,
WebGPUQueueManager* const webGPUQueueManager) {
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
<< "updateGPUBuffer called with a null buffer";
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
@@ -78,24 +79,34 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
// This may have some performance implications. That should be investigated later.
assert_invariant(mBuffer.GetUsage() & wgpu::BufferUsage::CopyDst);
// Calculate some alignment related sizes
const size_t remainder = bufferDescriptor.size % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
const size_t mainBulk = bufferDescriptor.size - remainder;
const size_t stagingBufferSize =
remainder == 0 ? bufferDescriptor.size : mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
// WriteBuffer is an async call. But cpu buffer data is already written to the staging
// buffer on return from the WriteBuffer.
const size_t legalSize = bufferDescriptor.size - remainder;
queue.WriteBuffer(mBuffer, byteOffset, bufferDescriptor.buffer, legalSize);
// create a staging buffer
wgpu::BufferDescriptor descriptor{
.label = "Filament WebGPU Staging Buffer",
.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
.size = stagingBufferSize,
.mappedAtCreation = true };
wgpu::Buffer stagingBuffer = device.CreateBuffer(&descriptor);
void* mappedRange = stagingBuffer.GetMappedRange();
memcpy(mappedRange, bufferDescriptor.buffer, bufferDescriptor.size);
// Make sure the padded memory is set to 0 to have deterministic behaviors
if (remainder != 0) {
const uint8_t* remainderStart =
static_cast<const uint8_t*>(bufferDescriptor.buffer) + legalSize;
memcpy(mRemainderChunk.data(), remainderStart, remainder);
// Pad the remainder with zeros to ensure deterministic behavior, though GPU shouldn't
// access this
std::memset(mRemainderChunk.data() + remainder, 0,
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - remainder);
queue.WriteBuffer(mBuffer, byteOffset + legalSize, &mRemainderChunk,
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS);
uint8_t* paddingStart = static_cast<uint8_t*>(mappedRange) + bufferDescriptor.size;
memset(paddingStart, 0, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - remainder);
}
stagingBuffer.Unmap();
// Copy the staging buffer contents to the destination buffer.
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stagingBuffer, 0, mBuffer,
byteOffset, stagingBufferSize);
}
}// namespace filament::backend
} // namespace filament::backend
@@ -17,16 +17,14 @@
#ifndef TNT_FILAMENT_BACKEND_WEBGPUBUFFERBASE_H
#define TNT_FILAMENT_BACKEND_WEBGPUBUFFERBASE_H
#include "WebGPUConstants.h"
#include <webgpu/webgpu_cpp.h>
#include <array>
#include <cstdint>
namespace filament::backend {
class BufferDescriptor;
class WebGPUQueueManager;
/**
* A base class for WebGPU buffer objects, providing common functionality for creating and
@@ -41,7 +39,8 @@ public:
* happen after draw commands encoded in the encoder. Submitting any commands up to this point
* ensures the calls happen in the expected sequence.
*/
void updateGPUBuffer(BufferDescriptor const&, uint32_t byteOffset, wgpu::Queue const&);
void updateGPUBuffer(BufferDescriptor const&, uint32_t byteOffset, wgpu::Device const& device,
WebGPUQueueManager* const webGPUQueueManager);
[[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; }
@@ -50,9 +49,6 @@ protected:
private:
const wgpu::Buffer mBuffer;
// WebGPU requires that the source buffer of a writeBuffer call has a size that is a multiple
// of 4. This member is used to pad the data if the source size is not a multiple of 4.
std::array<uint8_t, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS> mRemainderChunk{};
};
} // namespace filament::backend
+18 -4
View File
@@ -339,7 +339,7 @@ Handle<HwTimerQuery> WebGPUDriver::createTimerQueryS() noexcept {
}
Handle<HwIndexBuffer> WebGPUDriver::createIndexBufferS() noexcept {
return allocHandle<HwIndexBuffer>();
return allocHandle<WebGPUIndexBuffer>();
}
Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept {
@@ -852,7 +852,7 @@ void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
// draw calls are made.
flush();
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice.GetQueue());
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
scheduleDestroy(std::move(bufferDescriptor));
}
@@ -863,14 +863,14 @@ void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
// draw calls are made.
flush();
handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice.GetQueue());
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
scheduleDestroy(std::move(bufferDescriptor));
}
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice.GetQueue());
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
scheduleDestroy(std::move(bufferDescriptor));
}
@@ -1273,6 +1273,20 @@ void WebGPUDriver::nextSubpass(int) {
//todo
}
bool WebGPUDriver::isCompositorTimingSupported() {
return false;
}
bool WebGPUDriver::queryCompositorTiming(backend::SwapChainHandle swapChain,
CompositorTiming* outCompositorTiming) {
return false;
}
bool WebGPUDriver::queryFrameTimestamps(SwapChainHandle swapChain, uint64_t frameId,
FrameTimestamps* outFrameTimestamps) {
return false;
}
void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSwapChain,
Handle<HwSwapChain> readSwapChain) {
FWGPU_SYSTRACE_SCOPE();
+3 -3
View File
@@ -111,12 +111,12 @@ void BackendTest::flushAndWait() {
getDriver().purge();
}
Handle<HwSwapChain> BackendTest::createSwapChain() {
Handle<HwSwapChain> BackendTest::createSwapChain(uint64_t flags) {
const NativeView& view = getNativeView();
if (!view.ptr) {
return getDriverApi().createSwapChainHeadless(view.width, view.height, 0);
return getDriverApi().createSwapChainHeadless(view.width, view.height, flags);
}
return getDriverApi().createSwapChain(view.ptr, 0);
return getDriverApi().createSwapChain(view.ptr, flags);
}
PipelineState BackendTest::getColorWritePipelineState() {
+1 -1
View File
@@ -60,7 +60,7 @@ protected:
void executeCommands();
void flushAndWait();
filament::backend::Handle<filament::backend::HwSwapChain> createSwapChain();
filament::backend::Handle<filament::backend::HwSwapChain> createSwapChain(uint64_t flags = 0);
static filament::backend::PipelineState getColorWritePipelineState();
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

@@ -0,0 +1,103 @@
/*
* 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 "BackendTest.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
#include "Shader.h"
#include "Skip.h"
#include "SharedShaders.h"
#include "TrianglePrimitive.h"
namespace test {
using namespace filament;
using namespace filament::backend;
using namespace filament::math;
// Copied from filament/SwapChain.h
static constexpr uint64_t CONFIG_MSAA_4_SAMPLES = backend::SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES;
// Tests are parameterized by headless versus native SwapChain.
struct MsaaSwapChainTest : public BackendTest, public testing::WithParamInterface<bool> {};
TEST_P(MsaaSwapChainTest, Basic) {
SKIP_IF(Backend::OPENGL, "OpenGL does not support MSAA SwapChain on all platforms.");
SKIP_IF(Backend::VULKAN, "Vulkan does not support MSAA SwapChain on all platforms.");
constexpr int kRenderTargetSize = 512;
auto useHeadlessSwapChain = GetParam();
auto& api = getDriverApi();
api.startCapture(0);
Cleanup cleanup(api);
auto swapChain = [&]() {
auto flags = CONFIG_MSAA_4_SAMPLES;
if (useHeadlessSwapChain) {
return cleanup.add(
api.createSwapChainHeadless(kRenderTargetSize, kRenderTargetSize, flags));
}
return cleanup.add(createSwapChain(flags));
}();
api.makeCurrent(swapChain, swapChain);
RenderTargetHandle renderTarget = cleanup.add(api.createDefaultRenderTarget());
Shader shader = SharedShaders::makeShader(api, cleanup, ShaderRequest{
.mVertexType = VertexShaderType::Noop,
.mFragmentType = FragmentShaderType::White,
});
TrianglePrimitive triangle(api);
RenderPassParams params = getClearColorRenderPass();
params.viewport = getFullViewport();
PipelineState ps = getColorWritePipelineState();
shader.addProgramToPipelineState(ps);
{
RenderFrame frame(api);
api.beginRenderPass(renderTarget, params);
ps.primitiveType = PrimitiveType::TRIANGLES;
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(ps);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
EXPECT_IMAGE(renderTarget,
ScreenshotParams(kRenderTargetSize, kRenderTargetSize, "MsaaSwapChain", 0));
api.commit(swapChain);
}
api.stopCapture(0);
}
// Helper to give names to the tests.
static std::string testNameGenerator(const testing::TestParamInfo<bool>& info) {
const bool useHeadlessSwapChain = info.param;
return useHeadlessSwapChain ? "HeadlessSwapChain" : "NativeSwapChain";
}
INSTANTIATE_TEST_SUITE_P(MsaaSwapChainTests, MsaaSwapChainTest, testing::Values(true, false),
testNameGenerator);
} // namespace test
+9 -3
View File
@@ -92,19 +92,25 @@ public:
using time_point_ns = int64_t;
/** duration in nanosecond on the std::steady_clock */
using duration_ns = int64_t;
static constexpr time_point_ns INVALID = -1; //!< value not supported
static constexpr time_point_ns PENDING = -2; //!< value not yet available
uint32_t frameId; //!< monotonically increasing frame identifier
duration_ns frameTime; //!< frame duration on the GPU in nanosecond [ns]
duration_ns denoisedFrameTime; //!< denoised frame duration on the GPU in [ns]
duration_ns gpuFrameDuration; //!< frame duration on the GPU in nanosecond [ns]
duration_ns denoisedGpuFrameDuration; //!< denoised frame duration on the GPU in [ns]
time_point_ns beginFrame; //!< Renderer::beginFrame() time since epoch [ns]
time_point_ns endFrame; //!< Renderer::endFrame() time since epoch [ns]
time_point_ns backendBeginFrame; //!< Backend thread time of frame start since epoch [ns]
time_point_ns backendEndFrame; //!< Backend thread time of frame end since epoch [ns]
time_point_ns gpuFrameComplete; //!< GPU thread time of frame end since epoch [ns] or 0
time_point_ns vsync; //!< VSYNC time of this frame since epoch [ns]
time_point_ns displayPresent; //!< Actual presentation time of this frame since epoch [ns]
time_point_ns presentDeadline; //!< deadline for queuing a frame [ns]
duration_ns displayPresentInterval; //!< display refresh rate [ns]
duration_ns compositionToPresentLatency; //!< time between the start of composition and the expected present time [ns]
};
/**
* Retrieve an historic of frame timing information. The maximum frame history size is
* Retrieve a history of frame timing information. The maximum frame history size is
* given by getMaxFrameHistorySize().
* @param historySize requested history size. The returned vector could be smaller.
* @return A vector of FrameInfo.
+5 -2
View File
@@ -238,8 +238,11 @@ public:
* given sample points within each pixel. Only supported when isMSAASwapChainSupported(4) is
* true.
*
* This is only supported by EGL(Android). Other GL platforms (GLX, WGL, etc) don't support it
* because the swapchain MSAA settings must be configured before window creation.
* This is supported by EGL(Android) and Metal. Other GL platforms (GLX, WGL, etc) don't support
* it because the swapchain MSAA settings must be configured before window creation.
*
* With Metal, this flag should only be used when rendering a single View into a SwapChain. This
* flag is not supported when rendering multiple Filament Views into this SwapChain.
*
* @see isMSAASwapChainSupported(4)
*/
+96 -24
View File
@@ -16,6 +16,8 @@
#include "FrameInfo.h"
#include <details/Engine.h>
#include <filament/Renderer.h>
#include <backend/DriverEnums.h>
@@ -28,11 +30,11 @@
#include <utils/FixedCapacityVector.h>
#include <utils/JobSystem.h>
#include <utils/Logger.h>
#include <utils/ostream.h>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cmath>
#include <memory>
#include <ratio>
#include <utility>
@@ -57,27 +59,39 @@ FrameInfoManager::FrameInfoManager(DriverApi& driver) noexcept
FrameInfoManager::~FrameInfoManager() noexcept = default;
void FrameInfoManager::terminate(DriverApi& driver) noexcept {
void FrameInfoManager::terminate(FEngine& engine) noexcept {
DriverApi& driver = engine.getDriverApi();
if (mHasTimerQueries) {
for (auto const& query : mQueries) {
driver.destroyTimerQuery(query.handle);
}
}
// wait for all pending callbacks to be called & terminate the thread
mJobQueue.drainAndExit();
// destroy the fences that are still alive
// Destroy the fences that are still alive, they will error out.
for (size_t i = 0, c = mFrameTimeHistory.size(); i < c; i++) {
auto& info = mFrameTimeHistory[i];
if (info.fence) {
driver.destroyFence(std::move(info.fence));
}
}
// for extra safety submit the current command buffer (because nothing else will while we
// wait in drainAndExit()), this is in case the backend is already waiting on a h/w fence
// e.g. vkWaitForFences().
driver.flush();
// make sure the driver commands above will be processed
engine.flush();
// wait for all pending callbacks to be called & terminate the thread
mJobQueue.drainAndExit();
}
void FrameInfoManager::beginFrame(DriverApi& driver, Config const& config,
uint32_t frameId, std::chrono::steady_clock::time_point vsync) noexcept {
void FrameInfoManager::beginFrame(FSwapChain* swapChain, DriverApi& driver,
Config const& config, uint32_t frameId, std::chrono::steady_clock::time_point const vsync) noexcept {
auto const now = std::chrono::steady_clock::now();
auto& history = mFrameTimeHistory;
// don't exceed the capacity, drop the oldest entry
if (UTILS_LIKELY(history.size() == history.capacity())) {
@@ -87,11 +101,20 @@ void FrameInfoManager::beginFrame(DriverApi& driver, Config const& config,
}
// create a new entry
auto& front = history.emplace_front(frameId);
FrameInfoImpl& front = history.emplace_front(frameId);
// store the current time
front.vsync = vsync;
front.beginFrame = std::chrono::steady_clock::now();
front.beginFrame = now;
// store compositor timings if supported
CompositorTiming compositorTiming{};
if (driver.isCompositorTimingSupported() &&
driver.queryCompositorTiming(swapChain->getHwHandle(), &compositorTiming)) {
front.presentDeadline = compositorTiming.compositeDeadline;
front.displayPresentInterval = compositorTiming.compositeInterval;
front.compositionToPresentLatency = compositorTiming.compositeToPresentLatency;
}
if (mHasTimerQueries) {
// references are not invalidated by CircularQueue<>, so we can associate a reference to
@@ -150,7 +173,7 @@ void FrameInfoManager::beginFrame(DriverApi& driver, Config const& config,
<< ", Dm=" << h[0].endFrame - h[0].beginFrame
<< ", L =" << h[0].backendBeginFrame - h[0].beginFrame
<< ", Db=" << h[0].backendEndFrame - h[0].backendBeginFrame
<< ", T =" << h[0].frameTime;
<< ", T =" << h[0].gpuFrameDuration;
}
#endif
}
@@ -235,10 +258,16 @@ void FrameInfoManager::denoiseFrameTime(FrameHistoryQueue& history, Config const
}
}
FixedCapacityVector<Renderer::FrameInfo> FrameInfoManager::getFrameInfoHistory(
size_t historySize) const {
void FrameInfoManager::updateUserHistory(FSwapChain* swapChain, DriverApi& driver) {
if (!swapChain) {
swapChain = mLastSeenSwapChain;
} else {
mLastSeenSwapChain = swapChain;
}
auto result = FixedCapacityVector<Renderer::FrameInfo>::with_capacity(MAX_FRAMETIME_HISTORY);
auto const& history = mFrameTimeHistory;
auto& history = mFrameTimeHistory;
size_t i = 0;
size_t const c = history.size();
for (; i < c; ++i) {
@@ -249,22 +278,65 @@ FixedCapacityVector<Renderer::FrameInfo> FrameInfoManager::getFrameInfoHistory(
break;
}
}
size_t historySize = MAX_FRAMETIME_HISTORY;
for (; i < c && historySize; ++i, --historySize) {
auto const& entry = history[i];
auto& entry = history[i];
// retrieve the displayPresentTime only we don't already have it
if (entry.displayPresent == Renderer::FrameInfo::PENDING) {
FrameTimestamps frameTimestamps{
.displayPresentTime = FrameTimestamps::INVALID
};
if (swapChain && driver.isCompositorTimingSupported()) {
// queryFrameTimestamps could fail if this frameid is no longer available
bool const success = driver.queryFrameTimestamps(swapChain->getHwHandle(),
entry.frameId, &frameTimestamps);
if (success) {
assert_invariant(entry.displayPresent < 0 ||
entry.displayPresent == frameTimestamps.displayPresentTime);
entry.displayPresent = frameTimestamps.displayPresentTime;
}
} else {
entry.displayPresent = Renderer::FrameInfo::INVALID;
}
}
using namespace std::chrono;
// can't throw by construction
auto toDuration = [](details::FrameInfo::duration const d) {
return duration_cast<nanoseconds>(d).count();
};
auto toTimepoint = [](FrameInfoImpl::time_point const tp) {
return duration_cast<nanoseconds>(tp.time_since_epoch()).count();
};
result.push_back({
entry.frameId,
duration_cast<nanoseconds>(entry.frameTime).count(),
duration_cast<nanoseconds>(entry.denoisedFrameTime).count(),
duration_cast<nanoseconds>(entry.beginFrame.time_since_epoch()).count(),
duration_cast<nanoseconds>(entry.endFrame.time_since_epoch()).count(),
duration_cast<nanoseconds>(entry.backendBeginFrame.time_since_epoch()).count(),
duration_cast<nanoseconds>(entry.backendEndFrame.time_since_epoch()).count(),
duration_cast<nanoseconds>(entry.gpuFrameComplete.time_since_epoch()).count(),
duration_cast<nanoseconds>(entry.vsync.time_since_epoch()).count()
.frameId = entry.frameId,
.gpuFrameDuration = toDuration(entry.frameTime),
.denoisedGpuFrameDuration = toDuration(entry.denoisedFrameTime),
.beginFrame = toTimepoint(entry.beginFrame),
.endFrame = toTimepoint(entry.endFrame),
.backendBeginFrame = toTimepoint(entry.backendBeginFrame),
.backendEndFrame = toTimepoint(entry.backendEndFrame),
.gpuFrameComplete = toTimepoint(entry.gpuFrameComplete),
.vsync = toTimepoint(entry.vsync),
.displayPresent = entry.displayPresent,
.presentDeadline = entry.presentDeadline,
.displayPresentInterval = entry.displayPresentInterval,
.compositionToPresentLatency = entry.compositionToPresentLatency
});
}
std::swap(mUserFrameHistory, result);
}
FixedCapacityVector<Renderer::FrameInfo> FrameInfoManager::getFrameInfoHistory(
size_t const historySize) const {
auto result = mUserFrameHistory;
if (result.capacity() >= historySize) {
result.resize(historySize);
}
return result;
}
+39 -11
View File
@@ -19,6 +19,10 @@
#include <filament/Renderer.h>
#include <details/SwapChain.h>
#include <backend/Platform.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <private/backend/DriverApi.h>
@@ -50,17 +54,35 @@ struct FrameInfo {
} // namespace details
struct FrameInfoImpl : public details::FrameInfo {
using FrameTimestamps = backend::FrameTimestamps;
using CompositorTiming = backend::CompositorTiming;
using clock = std::chrono::steady_clock;
using time_point = clock::time_point;
uint32_t frameId;
time_point beginFrame; // main thread beginFrame time
time_point endFrame; // main thread endFrame time
time_point backendBeginFrame; // backend thread beginFrame time (makeCurrent time)
time_point backendEndFrame; // backend thread endFrame time (present time)
time_point gpuFrameComplete; // the frame is done rendering on the gpu
time_point vsync; // vsync time
backend::FenceHandle fence{}; // the fence used for gpuFrameComplete
std::atomic_bool ready{}; // true once backend thread has populated its data
// main thread beginFrame time
time_point beginFrame{};
// main thread endFrame time
time_point endFrame{};
// backend thread beginFrame time (makeCurrent time)
time_point backendBeginFrame{};
// backend thread endFrame time (present time)
time_point backendEndFrame{};
// the frame is done rendering on the gpu
time_point gpuFrameComplete{};
// vsync time
time_point vsync{};
// Actual presentation time of this frame
FrameTimestamps::time_point_ns displayPresent{ FrameTimestamps::PENDING };
// deadline for queuing a frame [ns]
CompositorTiming::time_point_ns presentDeadline{ FrameTimestamps::INVALID };
// display refresh rate [ns]
CompositorTiming::duration_ns displayPresentInterval{ FrameTimestamps::INVALID };
// time between the start of composition and the expected present time [ns]
CompositorTiming::duration_ns compositionToPresentLatency{ FrameTimestamps::INVALID };
// the fence used for gpuFrameComplete
backend::FenceHandle fence{};
// true once backend thread has populated its data
std::atomic_bool ready{};
explicit FrameInfoImpl(uint32_t const id) noexcept
: frameId(id) {
}
@@ -234,11 +256,13 @@ public:
explicit FrameInfoManager(backend::DriverApi& driver) noexcept;
~FrameInfoManager() noexcept;
void terminate(backend::DriverApi& driver) noexcept;
// The command queue must be empty before calling terminate()
void terminate(FEngine& engine) noexcept;
// call this immediately after "make current"
void beginFrame(backend::DriverApi& driver, Config const& config,
uint32_t frameId, std::chrono::steady_clock::time_point vsync) noexcept;
void beginFrame(FSwapChain* swapChain, backend::DriverApi& driver,
Config const& config, uint32_t frameId, std::chrono::steady_clock::time_point vsync) noexcept;
// call this immediately before "swap buffers"
void endFrame(backend::DriverApi& driver) noexcept;
@@ -248,6 +272,8 @@ public:
return pFront ? *pFront : details::FrameInfo{};
}
void updateUserHistory(FSwapChain* swapChain, backend::DriverApi& driver);
utils::FixedCapacityVector<Renderer::FrameInfo> getFrameInfoHistory(size_t historySize) const;
private:
@@ -257,12 +283,14 @@ private:
backend::Handle<backend::HwTimerQuery> handle{};
FrameInfoImpl* pInfo = nullptr;
};
utils::FixedCapacityVector<Renderer::FrameInfo> mUserFrameHistory;
std::array<Query, POOL_COUNT> mQueries{};
uint32_t mIndex = 0; // index of current query
uint32_t mLast = 0; // index of oldest query still active
FrameInfoImpl* pFront = nullptr; // the most recent slot with a valid frame time
FrameHistoryQueue mFrameTimeHistory{};
utils::AsyncJobQueue mJobQueue;
FSwapChain* mLastSeenSwapChain = nullptr;
bool const mHasTimerQueries = false;
};
+1 -1
View File
@@ -682,7 +682,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::transparentPicking(FrameGrap
data.picking = builder.createTexture("Picking Buffer", {
.width = width, .height = height,
.format = isFL0 ? TextureFormat::RGBA8 : TextureFormat::RG32F });
.format = isFL0 ? TextureFormat::RGBA8 : TextureFormat::RG32UI });
data.picking = builder.write(data.picking,
FrameGraphTexture::Usage::COLOR_ATTACHMENT);
+14 -8
View File
@@ -184,7 +184,8 @@ void FRenderer::terminate(FEngine& engine) {
// to initialize themselves, otherwise the engine tries to destroy invalid handles.
engine.execute();
}
mFrameInfoManager.terminate(driver);
mFrameInfoManager.terminate(engine);
mFrameSkipper.terminate(driver);
mResourceAllocator->terminate();
}
@@ -220,6 +221,9 @@ std::pair<Handle<HwRenderTarget>, TargetBufferFlags>
if (!outTarget) {
outTarget = mRenderTargetHandle;
outAttachmentMask = TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH;
if (mSwapChain->hasStencilBuffer()) {
outAttachmentMask |= TargetBufferFlags::STENCIL;
}
}
return { outTarget, outAttachmentMask };
}
@@ -279,6 +283,8 @@ void FRenderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) {
FEngine& engine = mEngine;
FEngine::DriverApi& driver = engine.getDriverApi();
mFrameInfoManager.updateUserHistory(nullptr, driver);
// Gives the backend a chance to execute periodic tasks. This must be called before
// the frame skipper.
driver.tick();
@@ -389,8 +395,9 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN
// This need to occur after the backend beginFrame() because some backends need to start
// a command buffer before creating a fence.
mFrameInfoManager.beginFrame(driver, {
.historySize = mFrameRateOptions.history
mFrameInfoManager.updateUserHistory(swapChain, driver);
mFrameInfoManager.beginFrame(swapChain, driver, {
.historySize = mFrameRateOptions.history
}, mFrameId, appVsync);
// ask the engine to do what it needs to (e.g. updates light buffer, materials...)
@@ -435,13 +442,12 @@ void FRenderer::endFrame() {
driver.debugThreading();
}
FILAMENT_CHECK_PRECONDITION(engine.isValid(mSwapChain))
FILAMENT_CHECK_PRECONDITION(mSwapChain && engine.isValid(mSwapChain))
<< "SwapChain must remain valid until endFrame is called.";
if (mSwapChain) {
mSwapChain->commit(driver);
mSwapChain = nullptr;
}
// mSwapChain cannot be null by construction
mSwapChain->commit(driver);
mSwapChain = nullptr;
mFrameInfoManager.endFrame(driver);
mFrameSkipper.submitFrame(driver);
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.66.1"
spec.version = "1.66.2"
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.66.1/filament-v1.66.1-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.66.2/filament-v1.66.2-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
+1 -1
View File
@@ -4,7 +4,7 @@ project(fgviewer C ASM)
set(TARGET fgviewer)
set(PUBLIC_HDR_DIR include)
if (CMAKE_CROSSCOMPILING)
if (CMAKE_CROSSCOMPILING OR FILAMENT_IMPORT_PREBUILT_EXECUTABLES)
include(${IMPORT_EXECUTABLES})
endif()
+1 -1
View File
@@ -26,7 +26,7 @@ set(SRCS
# Compile material files (mat) into headers (inc)
# ==================================================================================================
if (CMAKE_CROSSCOMPILING)
if (CMAKE_CROSSCOMPILING OR FILAMENT_IMPORT_PREBUILT_EXECUTABLES)
include(${IMPORT_EXECUTABLES})
endif()
+1 -1
View File
@@ -83,7 +83,7 @@ endif()
# Compile resources
# ==================================================================================================
if (CMAKE_CROSSCOMPILING)
if (CMAKE_CROSSCOMPILING OR FILAMENT_IMPORT_PREBUILT_EXECUTABLES)
include(${IMPORT_EXECUTABLES})
endif()
+2 -2
View File
@@ -834,8 +834,8 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
// Write back the active feature level.
config.featureLevel = mFilamentApp->mEngine->getActiveFeatureLevel();
mSwapChain = mFilamentApp->mEngine->createSwapChain(
nativeSwapChain, filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
mSwapChain = mFilamentApp->mEngine->createSwapChain(nativeSwapChain,
filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
}
mRenderer = mFilamentApp->mEngine->createRenderer();
+1 -1
View File
@@ -68,7 +68,7 @@ set(SRCS
set(RESOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR})
if (CMAKE_CROSSCOMPILING)
if (CMAKE_CROSSCOMPILING OR FILAMENT_IMPORT_PREBUILT_EXECUTABLES)
include(${IMPORT_EXECUTABLES})
endif()
+1 -1
View File
@@ -36,7 +36,7 @@ list(APPEND SRCS ${RESGEN_SOURCE})
# Generate all .filamat: default material, skyboxes, and post-process
# ==================================================================================================
if (CMAKE_CROSSCOMPILING)
if (CMAKE_CROSSCOMPILING OR FILAMENT_IMPORT_PREBUILT_EXECUTABLES)
include(${IMPORT_EXECUTABLES})
endif()
+1 -1
View File
@@ -4,7 +4,7 @@ project(matdbg C ASM)
set(TARGET matdbg)
set(PUBLIC_HDR_DIR include)
if (CMAKE_CROSSCOMPILING)
if (CMAKE_CROSSCOMPILING OR FILAMENT_IMPORT_PREBUILT_EXECUTABLES)
include(${IMPORT_EXECUTABLES})
endif()
+2
View File
@@ -32,6 +32,7 @@ set(DIST_HDRS
${PUBLIC_HDR_DIR}/${TARGET}/Log.h
${PUBLIC_HDR_DIR}/${TARGET}/Logger.h
${PUBLIC_HDR_DIR}/${TARGET}/memalign.h
${PUBLIC_HDR_DIR}/${TARGET}/MonotonicRingMap.h
${PUBLIC_HDR_DIR}/${TARGET}/Mutex.h
${PUBLIC_HDR_DIR}/${TARGET}/NameComponentManager.h
${PUBLIC_HDR_DIR}/${TARGET}/ostream.h
@@ -179,6 +180,7 @@ set(TEST_SRCS
test/test_Hash.cpp
test/test_InternPool.cpp
test/test_JobSystem.cpp
test/test_MonotonicRingMap.cpp
test/test_QuadTreeArray.cpp
test/test_RangeMap.cpp
test/test_RefCountedMap.cpp
+154
View File
@@ -0,0 +1,154 @@
/*
* 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_UTILS_MONOTONICRINGMAP_H
#define TNT_UTILS_MONOTONICRINGMAP_H
#include <utils/compiler.h>
#include <algorithm>
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <utility>
namespace utils {
/**
* A map-like container with a fixed capacity and monotonically increasing keys.
* When the map is full, inserting a new element overwrites the oldest one.
* This container doesn't allocate any memory on the heap.
* Lookups are O(log N).
*/
template<size_t N, typename KEY, typename VALUE>
class MonotonicRingMap {
public:
using key_type = KEY; //!< The key type.
using mapped_type = VALUE; //!< The value type.
using value_type = std::pair<key_type, mapped_type>; //!< The key-value pair type.
//! Creates an empty map.
MonotonicRingMap() noexcept = default;
//! Returns the number of elements in the map.
size_t size() const noexcept { return mSize; }
//! Returns the maximum number of elements the map can hold.
static constexpr size_t capacity() noexcept { return N; }
//! Returns true if the map is empty.
bool empty() const noexcept { return mSize == 0; }
//! Returns true if the map is full.
bool full() const noexcept { return mSize == N; }
/**
* Inserts a new key-value pair.
* The key must be greater than the key of the last inserted element.
* If the map is full, the oldest element is overwritten.
* @param key The key to insert.
* @param value The value to associate with the key.
*/
UTILS_NOINLINE void insert(key_type key, mapped_type value) {
assert(empty() || key > back().first); // assert monotonic
if (full()) {
// container is full, replace the oldest element
mStorage[mHead] = { key, value };
mHead = (mHead + 1) % N;
} else {
// container is not full, add to the end
const uint32_t index = (mHead + mSize) % N;
mStorage[index] = { key, value };
mSize++;
}
}
/**
* Finds a value by its key.
* @param key The key to look for.
* @return A pointer to the value if the key is found, nullptr otherwise.
*/
mapped_type* find(key_type key) {
return const_cast<mapped_type*>(static_cast<const MonotonicRingMap*>(this)->find(key));
}
/**
* Finds a value by its key.
* @param key The key to look for.
* @return A pointer to the const value if the key is found, nullptr otherwise.
*/
UTILS_NOINLINE const mapped_type* find(key_type key) const {
if (empty()) {
return nullptr;
}
if (key < front().first || key > back().first) {
return nullptr;
}
const auto comparator = [](const value_type& element, key_type k) {
return element.first < k;
};
const auto endOfStorage = mStorage.cbegin() + N;
const auto headIter = mStorage.cbegin() + mHead;
if (mHead + mSize <= N) {
// The logical sequence is contiguous in memory
const auto logicalEnd = headIter + mSize;
auto it = std::lower_bound(headIter, logicalEnd, key, comparator);
if (it != logicalEnd && it->first == key) {
return &it->second;
}
} else { // Wrapped around
// First part: mStorage[mHead...N-1]
auto it1 = std::lower_bound(headIter, endOfStorage, key, comparator);
if (it1 != endOfStorage && it1->first == key) {
return &it1->second;
}
// Second part: mStorage[0...head-1]
const auto wrapPartEndIter = mStorage.cbegin() + ((mHead + mSize) % N);
auto it2 = std::lower_bound(mStorage.cbegin(), wrapPartEndIter, key, comparator);
if (it2 != wrapPartEndIter && it2->first == key) {
return &it2->second;
}
}
return nullptr;
}
//! Returns a reference to the oldest element.
const value_type& front() const {
assert(!empty());
return mStorage[mHead];
}
//! Returns a reference to the newest element.
const value_type& back() const {
assert(!empty());
return mStorage[(mHead + mSize - 1) % N];
}
private:
std::array<value_type, N> mStorage;
uint32_t mSize = 0;
uint32_t mHead = 0;
};
} // namespace utils
#endif // TNT_UTILS_MONOTONICRINGMAP_H
+137
View File
@@ -0,0 +1,137 @@
/*
* 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 <gtest/gtest.h>
#include <utils/MonotonicRingMap.h>
using namespace utils;
TEST(MonotonicRingMap, Empty) {
MonotonicRingMap<4, uint64_t, uint64_t> map;
EXPECT_TRUE(map.empty());
EXPECT_FALSE(map.full());
EXPECT_EQ(0, map.size());
EXPECT_EQ(4, map.capacity());
EXPECT_EQ(nullptr, map.find(100));
}
TEST(MonotonicRingMap, Insert) {
MonotonicRingMap<4, uint64_t, uint64_t> map;
map.insert(10, 100);
map.insert(20, 200);
EXPECT_EQ(2, map.size());
EXPECT_EQ(10, map.front().first);
EXPECT_EQ(100, map.front().second);
EXPECT_EQ(20, map.back().first);
EXPECT_EQ(200, map.back().second);
}
TEST(MonotonicRingMap, Find) {
MonotonicRingMap<4, uint64_t, uint64_t> map;
map.insert(10, 100);
map.insert(20, 200);
map.insert(30, 300);
const uint64_t* v1 = map.find(10);
ASSERT_NE(nullptr, v1);
EXPECT_EQ(100, *v1);
const uint64_t* v2 = map.find(20);
ASSERT_NE(nullptr, v2);
EXPECT_EQ(200, *v2);
const uint64_t* v3 = map.find(30);
ASSERT_NE(nullptr, v3);
EXPECT_EQ(300, *v3);
EXPECT_EQ(nullptr, map.find(5)); // smaller than min
EXPECT_EQ(nullptr, map.find(25)); // in between
EXPECT_EQ(nullptr, map.find(35)); // larger than max
}
TEST(MonotonicRingMap, Full) {
MonotonicRingMap<4, uint64_t, uint64_t> map;
map.insert(10, 100);
map.insert(20, 200);
map.insert(30, 300);
map.insert(40, 400);
EXPECT_TRUE(map.full());
EXPECT_EQ(4, map.size());
EXPECT_EQ(10, map.front().first);
EXPECT_EQ(40, map.back().first);
// Now insert a new element, which should overwrite the oldest (10)
map.insert(50, 500);
EXPECT_TRUE(map.full());
EXPECT_EQ(4, map.size());
EXPECT_EQ(20, map.front().first); // 20 is the new oldest
EXPECT_EQ(50, map.back().first);
EXPECT_EQ(nullptr, map.find(10));
ASSERT_NE(nullptr, map.find(20));
ASSERT_NE(nullptr, map.find(50));
}
TEST(MonotonicRingMap, FindAfterWrapAround) {
MonotonicRingMap<4, uint64_t, uint64_t> map;
map.insert(10, 100);
map.insert(20, 200);
map.insert(30, 300);
map.insert(40, 400);
map.insert(50, 500); // oldest is 20, head is at index 1
EXPECT_EQ(20, map.front().first);
EXPECT_EQ(50, map.back().first);
ASSERT_NE(nullptr, map.find(20));
ASSERT_NE(nullptr, map.find(30));
ASSERT_NE(nullptr, map.find(40));
ASSERT_NE(nullptr, map.find(50));
map.insert(60, 600); // oldest is 30, head is at index 2
EXPECT_EQ(30, map.front().first);
EXPECT_EQ(60, map.back().first);
EXPECT_EQ(nullptr, map.find(20));
ASSERT_NE(nullptr, map.find(30));
ASSERT_NE(nullptr, map.find(60));
map.insert(70, 700); // oldest is 40, head is at index 3
map.insert(80, 800); // oldest is 50, head is at index 0
EXPECT_EQ(50, map.front().first);
EXPECT_EQ(80, map.back().first);
EXPECT_EQ(nullptr, map.find(40));
ASSERT_NE(nullptr, map.find(50));
ASSERT_NE(nullptr, map.find(60));
ASSERT_NE(nullptr, map.find(70));
ASSERT_NE(nullptr, map.find(80));
}
#if !defined(NDEBUG) && defined(GTEST_HAS_DEATH_TEST)
TEST(MonotonicRingMap, MonotonicityDeathTest) {
MonotonicRingMap<4, uint64_t, uint64_t> map;
map.insert(10, 100);
map.insert(20, 200);
EXPECT_DEATH(map.insert(15, 150), "");
}
#endif
+1 -1
View File
@@ -48,7 +48,7 @@ if (NOT FILAMENT_SUPPORTS_WEBGPU)
set(MATERIAL_SRCS ${MATERIAL_SRCS} materials/pointSprites.mat)
endif ()
if (CMAKE_CROSSCOMPILING)
if (CMAKE_CROSSCOMPILING OR FILAMENT_IMPORT_PREBUILT_EXECUTABLES)
include(${IMPORT_EXECUTABLES})
endif()
+1 -1
View File
@@ -3,7 +3,7 @@ project(shaders C ASM)
set(TARGET shaders)
if (CMAKE_CROSSCOMPILING)
if (CMAKE_CROSSCOMPILING OR FILAMENT_IMPORT_PREBUILT_EXECUTABLES)
include(${IMPORT_EXECUTABLES})
endif()
+2
View File
@@ -322,5 +322,7 @@ void addEmissive(const MaterialInputs material, inout vec4 color) {
vec4 evaluateMaterial(const MaterialInputs material) {
vec4 color = evaluateLights(material);
addEmissive(material, color);
// kill +inf generated by strong highlights
color.rgb = min(color.rgb, MEDIUMP_FLT_MAX);
return color;
}
+1 -1
View File
@@ -103,7 +103,7 @@ def _render_test_config(gltf_viewer,
with open(test_json_path, 'w') as f:
f.write(f'[{test.to_filament_format()}]')
for backend in test_config.backends:
for backend in test.backends:
if backend == 'vulkan':
assert vk_icd, "VK ICD must be specified when testing vulkan backend"
for model in test.models:
+5 -4
View File
@@ -110,13 +110,15 @@ class PresetConfig(RenderingConfig):
assert 0 <= tolerance['allowed_diff_pixels'] <= 100, "allowed_diff_pixels must be 0-100%"
class TestConfig(RenderingConfig):
def __init__(self, data, existing_models, presets):
def __init__(self, data, existing_models, presets, backends):
RenderingConfig.__init__(self, data)
description = data.get('description')
if description:
assert _is_string(description)
self.description = description
self.backends = data.get('backends', backends)
apply_presets = data.get('apply_presets')
rendering = {}
preset_models = []
@@ -179,7 +181,6 @@ class RenderTestConfig():
assert 'backends' in data
backends = data['backends']
assert _is_list_of_strings(backends)
self.backends = backends
assert 'model_search_paths' in data
model_search_paths = data.get('model_search_paths')
@@ -199,7 +200,7 @@ class RenderTestConfig():
presets = [PresetConfig(p, self.models) for p in preset_data]
assert 'tests' in data
self.tests = [TestConfig(t, self.models, presets) for t in data['tests']]
self.tests = [TestConfig(t, self.models, presets, backends) for t in data['tests']]
test_names = list([t.name for t in self.tests])
# We cannot have duplicate test names
@@ -224,4 +225,4 @@ if __name__ == "__main__":
parser.add_argument('--test', help='Configuration of the test', required=True)
args, _ = parser.parse_known_args(sys.argv[1:])
test = parse_test_config_from_path(args.test)
test = parse_from_path(args.test)
+2 -2
View File
@@ -14,6 +14,7 @@
import subprocess
import os
import shutil
import argparse
import sys
import pathlib
@@ -104,8 +105,7 @@ def mkdir_p(path_str):
pathlib.Path(path_str).mkdir(parents=True, exist_ok=True)
def mv_f(src_str, dst_str):
src = pathlib.Path(src_str)
src.replace(dst_str)
shutil.move(src_str, dst_str)
def important_print(msg):
lines = msg.split('\n')
+2
View File
@@ -37,6 +37,8 @@
"name": "Bloom",
"description": "Bloom",
"apply_presets": ["base", "bloom_models"],
// Do not include "webgpu" in the following backend since it is flaky (b/454921221)
"backends": ["opengl", "vulkan"],
"rendering": {
"view.bloom.enabled": true
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "filament",
"version": "1.66.1",
"version": "1.66.2",
"description": "Real-time physically based rendering engine",
"main": "filament.js",
"module": "filament.js",