Merge branch 'google:main' into main
This commit is contained in:
@@ -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
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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><manifest></code> element.</p>
|
||||
<pre><code><uses-permission android:name="android.permission.INTERNET" />
|
||||
</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>
|
||||
@@ -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
@@ -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', '~> 1.66.1'
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.66.2'
|
||||
</code></pre>
|
||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||
<ul>
|
||||
|
||||
@@ -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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>() };
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ endif()
|
||||
# Compile resources
|
||||
# ==================================================================================================
|
||||
|
||||
if (CMAKE_CROSSCOMPILING)
|
||||
if (CMAKE_CROSSCOMPILING OR FILAMENT_IMPORT_PREBUILT_EXECUTABLES)
|
||||
include(${IMPORT_EXECUTABLES})
|
||||
endif()
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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,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",
|
||||
|
||||
Reference in New Issue
Block a user