Compare commits

..
Author SHA1 Message Date
Doris Wu bd073119d2 Revert "buffer update opt: Add a flag to guard the feature (#9322)"
This reverts commit 49c4a5d62c.
2025-10-15 23:41:03 +08:00
165 changed files with 3660 additions and 16891 deletions
+11 -21
View File
@@ -1,53 +1,43 @@
# This action retrieves the latest commit message from a push or pull_request event
# and makes it available as an output variable named 'msg'.
name: 'Get commit message'
outputs:
msg:
value: ${{ steps.action_output.outputs.msg }}
hash:
value: ${{ steps.action_output.outputs.hash }}
runs:
using: "composite"
steps:
- name: Find commit message (on push)
if: github.event_name == 'push'
shell: bash
env:
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: |
AUTHOR_NAME="${{ github.event.head_commit.author.name }}"
AUTHOR_EMAIL="${{ github.event.head_commit.author.email }}"
TSTAMP="${{ github.event.head_commit.timestamp }}"
HASH="${{ github.event.head_commit.id }}"
echo "commit $HASH" >> /tmp/commit_msg.txt
echo "commit ${{ github.event.head_commit.id }}" >> /tmp/commit_msg.txt
echo "Author: ${AUTHOR_NAME}<${AUTHOR_EMAIL}>" >> /tmp/commit_msg.txt
echo "Date: ${TSTAMP}" >> /tmp/commit_msg.txt
echo "" >> /tmp/commit_msg.txt
echo "$COMMIT_MESSAGE" >> /tmp/commit_msg.txt
echo "$HASH" > /tmp/commit_hash.txt
echo "${{ github.event.head_commit.message }}" >> /tmp/commit_msg.txt
- name: Find commit message (PR)
shell: bash
id: checkout_code
if: github.event_name == 'pull_request'
run: |
BEFORE_HASH=$(git rev-parse HEAD)
echo "hash=$BEFORE_HASH" >> "$GITHUB_OUTPUT"
# Next we will checkout the actual head (not the merge commits) of the PR
AFTER_HASH="${{ github.event.pull_request.head.sha }}"
git checkout $AFTER_HASH
COMMIT_MESSAGE=$(git log -1 --no-merges)
echo "$COMMIT_MESSAGE" > /tmp/commit_msg.txt
echo "$AFTER_HASH" > /tmp/commit_hash.txt
echo "+++++ head commit message +++++"
echo "$(git log -1 --no-merges)"
echo "+++++++++++++++++++++++++++++++"
echo "hash=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
git checkout ${{ github.event.pull_request.head.sha }}
echo "$(git log -1 --no-merges)" >> /tmp/commit_msg.txt
- shell: bash
id: action_output
run: |
# Get the commit message
DELIMITER="EOF_FILE_CONTENT_$(date +%s)" # Using timestamp to make it more unique
echo "msg<<$DELIMITER" >> "$GITHUB_OUTPUT"
cat /tmp/commit_msg.txt >> "$GITHUB_OUTPUT"
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
# Get the commit hash
echo "hash=$(cat /tmp/commit_hash.txt)" >> "$GITHUB_OUTPUT"
echo "----- got commit message ---"
cat /tmp/commit_msg.txt
echo "----------------------------"
- name: Cleanup Find commit message (PR)
shell: bash
if: github.event_name == 'pull_request'
+2 -2
View File
@@ -23,7 +23,7 @@ jobs:
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
run: |
GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | python3 test/renderdiff/src/commit_msg.py)
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
COMMIT_HASH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | sed "s/commit //g")
if [[ "${GOLDEN_BRANCH}" != "main" ]]; then
git config --global user.email "filament.bot@gmail.com"
git config --global user.name "Filament Bot"
@@ -51,7 +51,7 @@ jobs:
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
run: |
bash docs_src/build/install_mdbook.sh && source ~/.bashrc
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
COMMIT_HASH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | sed "s/commit //g")
git config --global user.email "filament.bot@gmail.com"
git config --global user.name "Filament Bot"
git config --global credential.helper cache
+4 -25
View File
@@ -108,7 +108,8 @@ jobs:
uses: ./.github/actions/get-commit-msg
- name: Check for manual edits to /docs
run: |
bash docs_src/build/presubmit_check.sh ${{ steps.get_commit_msg.outputs.hash }}
COMMIT_ID=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | sed "s/commit //g")
bash docs_src/build/presubmit_check.sh ${COMMIT_ID}
test-renderdiff:
name: test-renderdiff
@@ -120,7 +121,7 @@ jobs:
- uses: ./.github/actions/mac-prereq
- uses: ./.github/actions/get-gltf-assets
- uses: ./.github/actions/get-mesa
- uses: ./.github/actions/get-vulkan-sdk
- uses: ./.github/actions/get-vulkan-sdk
- id: get_commit_msg
uses: ./.github/actions/get-commit-msg
- name: Prerequisites
@@ -131,14 +132,12 @@ jobs:
shell: bash
- name: Render and compare
id: render_compare
env:
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
run: |
ls ./gltf/Models
TEST_DIR=test/renderdiff
source ${TEST_DIR}/src/preamble.sh
start_
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 ${TEST_DIR}/src/commit_msg.py)
GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | python3 ${TEST_DIR}/src/commit_msg.py)
bash ${TEST_DIR}/generate.sh && \
python3 ${TEST_DIR}/src/golden_manager.py \
--branch=${GOLDEN_BRANCH} \
@@ -203,23 +202,3 @@ jobs:
./build.sh -p desktop debug gltf_viewer
- name: Run test
run: bash test/code-correctness/test.sh
test-backend:
name: test-backend
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/mac-prereq
- uses: ./.github/actions/get-mesa
- uses: ./.github/actions/get-vulkan-sdk
- name: Prerequisites
run: |
# Must have at least clang-16 for a webgpu/dawn build.
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
shell: bash
- name: Run backend tests
shell: bash
run: |
bash test/backend/test.sh
+16 -21
View File
@@ -56,8 +56,6 @@ option(FILAMENT_ENABLE_FGVIEWER "Enable the frame graph viewer" OFF)
option(FILAMENT_USE_ABSEIL_LOGGING "Use Abseil to log, may increase binary size" OFF)
option(FILAMENT_ENABLE_EXPERIMENTAL_GCC_SUPPORT "Enable GCC support (unsupported)" OFF)
# This is to disable GTAO for the short-term while we investigate a way to better manage size increases.
# On the regular filament build (where size is of less concern), we enable GTAO by default.
option(FILAMENT_DISABLE_GTAO "Disable GTAO" OFF)
@@ -297,10 +295,15 @@ set(TOOLS ${CMAKE_CURRENT_SOURCE_DIR}/tools)
# ==================================================================================================
set(MIN_CLANG_VERSION "6.0")
set(FILAMENT_USING_GCC OFF)
if (CMAKE_C_COMPILER_ID MATCHES "GNU" AND FILAMENT_ENABLE_EXPERIMENTAL_GCC_SUPPORT)
set(FILAMENT_USING_GCC ON)
elseif (CMAKE_C_COMPILER_ID MATCHES "Clang")
if (CMAKE_C_COMPILER_ID MATCHES "Clang")
if (CMAKE_C_COMPILER_VERSION VERSION_LESS MIN_CLANG_VERSION)
message(FATAL_ERROR "Detected C compiler Clang ${CMAKE_C_COMPILER_VERSION} < ${MIN_CLANG_VERSION}")
endif()
elseif (NOT MSVC)
message(FATAL_ERROR "Detected C compiler ${CMAKE_C_COMPILER_ID} is unsupported")
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS MIN_CLANG_VERSION)
message(FATAL_ERROR "Detected CXX compiler Clang ${CMAKE_CXX_COMPILER_VERSION} < ${MIN_CLANG_VERSION}")
endif()
@@ -341,11 +344,7 @@ if (MSVC)
set(CXX_STANDARD "/std:c++latest")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} /W0 /Zc:__cplusplus")
else()
if(FILAMENT_USING_GCC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} -Wno-changes-meaning -Wno-return-type -Wno-attributes -Wno-unknown-pragmas -Wno-class-memaccess -Wno-multichar -Wno-deprecated-declarations -Wno-subobject-linkage -Wno-invalid-constexpr")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} -fstrict-aliasing -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} -fstrict-aliasing -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations")
if (APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-nullability-extension")
endif()
@@ -366,14 +365,10 @@ endif()
if (LINUX)
option(USE_STATIC_LIBCXX "Link against the static runtime libraries." ON)
if (${USE_STATIC_LIBCXX})
if (FILAMENT_USING_GCC)
link_libraries("-static-libgcc -static-libstdc++")
else ()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
link_libraries("-static-libgcc -static-libstdc++")
link_libraries(libc++.a)
link_libraries(libc++abi.a)
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
link_libraries("-static-libgcc -static-libstdc++")
link_libraries(libc++.a)
link_libraries(libc++abi.a)
endif()
# Only linux, clang doesn't want to use a shared library that is not PIC.
@@ -398,8 +393,8 @@ if (MSVC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE")
endif()
# Add colors to ninja builds (when not using gcc)
if (UNIX AND CMAKE_GENERATOR STREQUAL "Ninja" AND NOT FILAMENT_USING_GCC)
# Add colors to ninja builds
if (UNIX AND CMAKE_GENERATOR STREQUAL "Ninja")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics")
endif()
-2
View File
@@ -6,5 +6,3 @@
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- Metal: Add support for the `SwapChain::CONFIG_MSAA_4_SAMPLES` flag.
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.67.0'
implementation 'com.google.android.filament:filament-android:1.66.0'
}
```
@@ -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.67.0'
pod 'Filament', '~> 1.66.0'
```
## Documentation
-8
View File
@@ -7,14 +7,6 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.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
## v1.66.1
- filamat: Removed a dependency on Glslang's deprecated SPIR-V remapper.
@@ -557,8 +557,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
jboolean disableHandleUseAfterFreeCheck,
jint preferredShaderLanguage,
jboolean forceGLES2Context, jboolean assertNativeWindowIsValid,
jint gpuContextPriority,
jlong sharedUboInitialSizeInBytes) {
jint gpuContextPriority) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
Engine::Config config = {
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
@@ -577,7 +576,6 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
.forceGLES2Context = (bool) forceGLES2Context,
.assertNativeWindowIsValid = (bool) assertNativeWindowIsValid,
.gpuContextPriority = (Engine::GpuContextPriority) gpuContextPriority,
.sharedUboInitialSizeInBytes = (uint32_t) sharedUboInitialSizeInBytes,
};
builder->config(&config);
}
@@ -279,17 +279,6 @@ Java_com_google_android_filament_Material_nHasParameter(JNIEnv* env, jclass,
return (jboolean) hasParameter;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_google_android_filament_Material_nGetParameterTransformName(JNIEnv* env, jclass,
jlong nativeMaterial, jstring samplerName_) {
Material* material = (Material*) nativeMaterial;
const char* samplerName = env->GetStringUTFChars(samplerName_, 0);
const char* transformName = material->getParameterTransformName(samplerName);
jstring transformName_ = env->NewStringUTF(transformName ? transformName : "");
env->ReleaseStringUTFChars(samplerName_, samplerName);
return transformName_;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_Material_nCompile(JNIEnv *env, jclass clazz,
@@ -264,8 +264,7 @@ public class Engine {
config.disableHandleUseAfterFreeCheck,
config.preferredShaderLanguage.ordinal(),
config.forceGLES2Context, config.assertNativeWindowIsValid,
config.gpuContextPriority.ordinal(),
config.sharedUboInitialSizeInBytes);
config.gpuContextPriority.ordinal());
return this;
}
@@ -526,16 +525,6 @@ public class Engine {
* GPU context priority level. Controls GPU work scheduling and preemption.
*/
public GpuContextPriority gpuContextPriority = GpuContextPriority.DEFAULT;
/**
* The initial size in bytes of the shared uniform buffer used for material instance batching.
*
* If the buffer runs out of space during a frame, it will be automatically reallocated
* with a larger capacity. Setting an appropriate initial size can help avoid runtime
* reallocations, which can cause a minor performance stutter, at the cost of higher
* initial memory usage.
*/
public long sharedUboInitialSizeInBytes = 256 * 64;
}
private Engine(long nativeEngine, Config config) {
@@ -1540,8 +1529,7 @@ public class Engine {
boolean disableHandleUseAfterFreeCheck,
int preferredShaderLanguage,
boolean forceGLES2Context, boolean assertNativeWindowIsValid,
int gpuContextPriority,
long sharedUboInitialSizeInBytes);
int gpuContextPriority);
private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal);
private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
private static native void nSetBuilderPaused(long nativeBuilder, boolean paused);
@@ -787,21 +787,6 @@ public class Material {
return nHasParameter(getNativeObject(), name);
}
/**
*
* Returns the name of the transform parameter associated with the given sampler parameter.
* In the case the parameter doesn't have a transform name field, it will return an empty string.
*
* @param samplerName the name of the sampler parameter to query.
*
* @see
* <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/general:parameters">
* General: parameters</a>
*/
public String getParameterTransformName(@NonNull String samplerName) {
return nGetParameterTransformName(getNativeObject(), samplerName);
}
/**
* Sets the value of a bool parameter on this material's default instance.
*
@@ -1126,5 +1111,4 @@ public class Material {
private static native int nGetRequiredAttributes(long nativeMaterial);
private static native boolean nHasParameter(long nativeMaterial, @NonNull String name);
private static native String nGetParameterTransformName(long nativeMaterial, @NonNull String samplerName);
}
@@ -294,8 +294,8 @@ public class TextureSampler {
/**
* This controls anisotropic filtering.
*
* @param anisotropy Amount of anisotropy, should be a power-of-two. The default is 1.
* The maximum permissible value is 128.
* @param anisotropy Amount of anisotropy, should be a power-of-two. The default is 0.
* The maximum permissible value is 7.
*/
public void setAnisotropy(float anisotropy) {
mSampler = nSetAnisotropy(mSampler, anisotropy);
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.67.0
VERSION_NAME=1.66.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+2 -2
View File
@@ -165,8 +165,8 @@ Portal</a>.</p>
<p>To publish under the <code>com.google.android</code> namespace, you'll also need to email
<code>central-support@sonatype.com</code> with your account information to request access.</p>
<p>Then, generate a user token through the Sonatype website. Navigate to
<a href="https://central.sonatype.com/usertoken">https://central.sonatype.com/usertoken</a>
and select <strong>Generate User Token</strong>.</p>
<a href="https://central.sonatype.com/account">https://central.sonatype.com/account</a> and select <strong>Generate
User Token</strong>.</p>
<p>Finally, add the generated token credentials to <code>~/.gradle/gradle.properties</code>. (Note: these are
different from the credentials used to log into your Sonatype account):</p>
<pre><code class="language-gradle"># Generated Sonatype token
+2 -2
View File
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.66.1'
implementation 'com.google.android.filament:filament-android:1.66.0'
}
</code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
@@ -196,7 +196,7 @@ dependencies {
</div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<p>iOS projects can use CocoaPods to install the latest release:</p>
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.66.1'
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.66.0'
</code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul>
+3 -3
View File
@@ -159,16 +159,16 @@
<div id="content" class="content">
<main>
<h1 id="rendering-difference-test"><a class="header" href="#rendering-difference-test">Rendering Difference Test</a></h1>
<p>This tool is a collections of scripts to run <code>gltf_viewer</code> and produce headless renderings.</p>
<p>We created a few scripts to run <code>gltf_viewer</code> and produce headless renderings.</p>
<p>This is mainly useful for continuous integration where GPUs are generally not available on cloud
machines. To perform software rasterization, these scripts are centered around <a href="https://docs.mesa3d.org">Mesa</a>'s
software rasterizers, but nothing bars us from using another rasterizer like <a href="https://github.com/google/swiftshader">SwiftShader</a>.
Additionally, we should be able to use GPUs where available (though this is more of a future
work).</p>
<p>The script <code>render.py</code> contains the core logic for taking input parameters (such as the test
description file) and then running <code>gltf_viewer</code> to produce the renderings.</p>
description file) and then running gltf_viewer to produce the renderings.</p>
<p>In the <code>test</code> directory is a list of test descriptions that are specified in json. Please see
<code>sample.json</code> to glean the structure.</p>
<code>sample.json</code> to parse the structure.</p>
<h2 id="setting-up-python"><a class="header" href="#setting-up-python">Setting up python</a></h2>
<p>The <code>renderdiff</code> project uses <code>python</code> extensively. To install the dependencies for producing
renderings, do the following step</p>
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -8,10 +8,10 @@ tar -xvzf filament-release-web.tgz
rm filament-release-web.tgz
rm filament-web.zip
rm filament.d.ts
cp ../../../web/samples/remote.html index.html
cp ../../web/samples/remote.html index.html
popd
git status
echo ""
echo "All done! Next, commit the changed files"
echo "All done! Next, make a git commit that updates docs/remote."
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -77,11 +77,11 @@ def check_has_source_edits(commit_hash, printing=True):
# Returns true in a given TAG is found in the commit msg
def commit_msg_has_tag(commit_hash, tag, printing=True):
res, ret = execute(f'git log -n1 --pretty=%B {commit_hash}', cwd=ROOT_DIR)
res, ret = execute(f'git log --pretty=%B {commit_hash}', cwd=ROOT_DIR)
for l in ret.split('\n'):
if tag == l.strip():
if printing:
print(f'Found tag={tag} in commit={commit_hash}')
print(f'Found tag={tag} in commit message')
return True
return False
-15
View File
@@ -1,15 +0,0 @@
attrs==25.4.0
certifi==2025.10.5
h11==0.16.0
idna==3.11
outcome==1.3.0.post0
PySocks==1.7.1
selenium==4.37.0
sniffio==1.3.1
sortedcontainers==2.4.0
trio==0.31.0
trio-websocket==0.12.2
typing_extensions==4.15.0
urllib3==2.5.0
websocket-client==1.9.0
wsproto==1.2.0
+1 -3
View File
@@ -140,8 +140,6 @@ if __name__ == "__main__":
res, err = execute('mdbook build', cwd=MDBOOK_DIR)
assert res == 0, f"failed to execute `mdbook`. return-code={res} err=\"{err}\""
RAW_IGNORES = shutil.ignore_patterns('update_remote_ui.sh')
shutil.copytree(RAW_COPIES_DIR, BOOK_OUPUT_DIR, ignore=RAW_IGNORES, dirs_exist_ok=True)
shutil.copytree(RAW_COPIES_DIR, BOOK_OUPUT_DIR, dirs_exist_ok=True)
shutil.copy(os.path.join(MARKDEEP_DIR, FILAMENT_MD), BOOK_OUPUT_DIR)
shutil.copy(os.path.join(MARKDEEP_DIR, MATERIALS_MD), BOOK_OUPUT_DIR)
@@ -9,8 +9,8 @@ To publish under the `com.google.android` namespace, you'll also need to email
`central-support@sonatype.com` with your account information to request access.
Then, generate a user token through the Sonatype website. Navigate to
[https://central.sonatype.com/usertoken](https://central.sonatype.com/usertoken)
and select **Generate User Token**.
[https://central.sonatype.com/account](https://central.sonatype.com/account) and select **Generate
User Token**.
Finally, add the generated token credentials to `~/.gradle/gradle.properties`. (Note: these are
different from the credentials used to log into your Sonatype account):
File diff suppressed because one or more lines are too long
Binary file not shown.
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
pushd "$(dirname "$0")" > /dev/null
curl -OL https://nightly.link/google/filament/workflows/web-continuous/main/filament-web.zip
unzip -q filament-web.zip
tar -xvzf filament-release-web.tgz
rm filament-release-web.tgz
rm filament-web.zip
rm filament.d.ts
cp ../../web/samples/remote.html index.html
popd
git status
echo ""
echo "All done! Next, make a git commit that updates docs/remote."
-19
View File
@@ -708,13 +708,6 @@ if (MSVC)
elseif(WEBGL)
# Avoid strict-vtable-pointers here, it is broken in WebAssembly.
set(OPTIMIZATION_FLAGS -fvisibility-inlines-hidden)
elseif(FILAMENT_USING_GCC)
set(OPTIMIZATION_FLAGS
-ffast-math
-fno-finite-math-only
-ffp-contract=fast
-fvisibility-inlines-hidden
)
else()
set(OPTIMIZATION_FLAGS
-ffast-math
@@ -736,18 +729,6 @@ set(LINUX_COMPILER_FLAGS
if (MSVC)
set(FILAMENT_WARNINGS /W3)
elseif (FILAMENT_USING_GCC)
# GCC is not officially supported, so use a weaker set of warnings (incl. not using -Werror).
set(FILAMENT_WARNINGS
-Wall -Wextra-semi -Wundef
-Wno-unused-parameter -Wno-missing-field-initializers
-Wno-unused-but-set-variable -Wno-unused-variable -Wno-unused-function
-Wnon-virtual-dtor -Wimplicit-fallthrough
-Wno-strict-aliasing
-Wno-changes-meaning -Wno-return-type
-Wno-invalid-constexpr -Wno-parentheses
-Wno-sign-compare -Wno-deprecated-declarations
)
else()
set(FILAMENT_WARNINGS
-Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers
-17
View File
@@ -104,8 +104,6 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
list(APPEND SRCS src/VirtualMachineEnv.cpp)
endif ()
if (ANDROID)
list(APPEND SRCS src/AndroidNativeWindow.cpp)
list(APPEND SRCS src/AndroidSwapChainHelper.cpp)
list(APPEND SRCS src/opengl/platforms/ExternalStreamManagerAndroid.cpp)
list(APPEND SRCS src/opengl/platforms/PlatformEGLAndroid.cpp)
elseif (IOS)
@@ -471,13 +469,6 @@ if (MSVC)
elseif(WEBGL)
# Avoid strict-vtable-pointers here, it is broken in WebAssembly.
set(OPTIMIZATION_FLAGS -fvisibility-inlines-hidden)
elseif(FILAMENT_USING_GCC)
set(OPTIMIZATION_FLAGS
-ffast-math
-fno-finite-math-only
-ffp-contract=fast
-fvisibility-inlines-hidden
)
else()
set(OPTIMIZATION_FLAGS
-ffast-math
@@ -504,13 +495,6 @@ endif()
if (MSVC)
set(FILAMENT_WARNINGS /W3)
elseif (FILAMENT_USING_GCC)
# GCC is not officially supported, so use a weaker set of warnings (incl. not using -Werror)
set(FILAMENT_WARNINGS
-Wall -Wno-unused-parameter -Wno-missing-field-initializers
-Wdeprecated -Wnon-virtual-dtor -Wno-strict-aliasing
-Wno-invalid-constexpr -Wno-parentheses -Wno-unused-variable
)
else()
set(FILAMENT_WARNINGS
-Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers
@@ -592,7 +576,6 @@ 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
-102
View File
@@ -91,46 +91,6 @@ public:
using ExternalImageHandleRef = ExternalImageHandle const&;
struct CompositorTiming {
/** duration in nanosecond since epoch of std::steady_clock */
using time_point_ns = int64_t;
/** duration in nanosecond on the std::steady_clock */
using duration_ns = int64_t;
/**
* The timestamp [ns] since epoch of the next time the compositor will begin composition.
* This is effectively the deadline for when the compositor must receive a newly queued
* frame.
*/
time_point_ns compositeDeadline;
/**
* The time delta [ns] between subsequent composition events.
*/
duration_ns compositeInterval;
/**
* 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.
*/
duration_ns compositeToPresentLatency;
};
struct FrameTimestamps {
/** duration in nanosecond since epoch of std::steady_clock */
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
time_point_ns requestedPresentTime;
time_point_ns acquireTime;
time_point_ns latchTime;
time_point_ns firstRefreshStartTime;
time_point_ns lastRefreshStartTime;
time_point_ns gpuCompositionDoneTime;
time_point_ns displayPresentTime;
time_point_ns dequeueReadyTime;
time_point_ns releaseTime;
};
/**
* The type of technique for stereoscopic rendering. (Note that the materials used will need to
* be compatible with the chosen technique.)
@@ -287,65 +247,6 @@ public:
*/
virtual bool pumpEvents() noexcept;
// --------------------------------------------------------------------------------------------
// Swapchain timing APIs
/**
* Whether this platform supports compositor timing querying.
*
* @return true if this Platform supports compositor timings, false otherwise [default]
* @see queryCompositorTiming()
* @see setPresentFrameId()
* @see queryFrameTimestamps()
*/
virtual bool isCompositorTimingSupported() const noexcept;
/**
* If compositor timing is supported, fills the provided CompositorTiming structure
* with timing information form the compositor the swapchain's native window is using.
* The swapchain'snative window must be valid (i.e. not a headless swapchain).
* @param swapchain to query the compositor timing from
* @return true on success, false otherwise (e.g. if not supported)
* @see isCompositorTimingSupported()
*/
virtual bool queryCompositorTiming(SwapChain const* UTILS_NONNULL swapchain,
CompositorTiming* UTILS_NONNULL outCompositorTiming) const noexcept;
/**
* Associate a generic frameId which must be monotonically increasing (albeit not strictly) with
* the next frame to be presented on the specified swapchain.
*
* This must be called from the backend thread.
*
* @param swapchain
* @param frameId
* @return true on success, false otherwise
* @see isCompositorTimingSupported()
* @see queryFrameTimestamps()
*/
virtual bool setPresentFrameId(SwapChain const* UTILS_NONNULL swapchain,
uint64_t frameId) noexcept;
/**
* If compositor timing is supported, fills the provided FrameTimestamps structure
* with timing information of a given frame, identified by the frame id, of the specified
* swapchain. The system only keeps a limited history of frames timings.
*
* This API is thread safe and can be called from any thread.
*
* @param swapchain swapchain to query the timestamps of
* @param frameId frame we're interested it
* @param outFrameTimestamps output structure receiving the timestamps
* @return true if successful, false otherwise
* @see isCompositorTimingSupported()
* @see setPresentFrameId()
*/
virtual bool queryFrameTimestamps(SwapChain const* UTILS_NONNULL swapchain,
uint64_t frameId, FrameTimestamps* UTILS_NONNULL outFrameTimestamps) const noexcept;
// --------------------------------------------------------------------------------------------
// Caching APIs
/**
* InsertBlobFunc is an Invocable to an application-provided function that a
* backend implementation may use to insert a key/value pair into the
@@ -438,9 +339,6 @@ public:
size_t retrieveBlob(const void* UTILS_NONNULL key, size_t keySize,
void* UTILS_NONNULL value, size_t valueSize);
// --------------------------------------------------------------------------------------------
// Debugging APIs
using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key, uint64_t value)>;
/**
@@ -430,7 +430,7 @@ public:
virtual bool isExtraContextSupported() const noexcept;
/**
* Creates an OpenGL context with the same configuration as the main context and makes it
* Creates an OpenGL context with the same configuration than the main context and makes it
* current to the current thread. Must not be called from the main driver thread.
* createContext() is only supported if isExtraContextSupported() returns true.
* These additional contexts will be automatically terminated in terminate.
@@ -443,7 +443,7 @@ public:
/**
* Detach and destroy the current context if any and releases all resources associated to
* this thread. This must be called from the same thread where createContext() was called.
* this thread.
*/
virtual void releaseContext() noexcept;
};
@@ -25,7 +25,6 @@
#include <EGL/eglext.h>
#include <EGL/eglplatform.h>
#include <utils/compiler.h>
#include <utils/Invocable.h>
#include <initializer_list>
@@ -101,6 +100,10 @@ protected:
bool isProtectedContextSupported() const noexcept override;
bool isSRGBSwapChainSupported() const noexcept override;
bool isMSAASwapChainSupported(uint32_t samples) const noexcept override;
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) override;
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
bool isSwapChainProtected(SwapChain* swapChain) noexcept override;
ContextType getCurrentContextType() const noexcept override;
@@ -155,9 +158,17 @@ protected:
return egl.makeCurrent(context, mEGLDummySurface, mEGLDummySurface);
}
EGLDisplay getEglDisplay() const noexcept { return mEGLDisplay; }
EGLConfig getEglConfig() const noexcept { return mEGLConfig; }
EGLConfig getSuitableConfigForSwapChain(uint64_t flags, bool window, bool pbuffer) const;
// TODO: this should probably use getters instead.
EGLDisplay mEGLDisplay = EGL_NO_DISPLAY;
EGLContext mEGLContext = EGL_NO_CONTEXT;
EGLContext mEGLContextProtected = EGL_NO_CONTEXT;
EGLSurface mEGLDummySurface = EGL_NO_SURFACE;
ContextType mCurrentContextType = ContextType::NONE;
// mEGLConfig is valid only if ext.egl.KHR_no_config_context is false
EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR;
Config mContextAttribs;
std::vector<EGLContext> mAdditionalContexts;
bool mMSAA4XSupport = false;
// supported extensions detected at runtime
struct {
@@ -174,11 +185,7 @@ protected:
} egl;
} ext;
struct SwapChainEGL : public SwapChain {
SwapChainEGL(PlatformEGL const& platform, void* nativeWindow, uint64_t flags);
SwapChainEGL(PlatformEGL const& platform, uint32_t width, uint32_t height, uint64_t flags);
void terminate(PlatformEGL& platform);
struct SwapChainEGL : public Platform::SwapChain {
EGLSurface sur = EGL_NO_SURFACE;
Config attribs{};
EGLNativeWindowType nativeWindow{};
@@ -194,25 +201,10 @@ protected:
~ExternalImageEGL() override;
};
private:
// prevent derived classes' implementations to call through
[[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override;
[[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
protected:
EGLConfig findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const;
EGLDisplay mEGLDisplay = EGL_NO_DISPLAY;
EGLContext mEGLContext = EGL_NO_CONTEXT;
EGLContext mEGLContextProtected = EGL_NO_CONTEXT;
EGLSurface mEGLDummySurface = EGL_NO_SURFACE;
ContextType mCurrentContextType = ContextType::NONE;
// mEGLConfig is valid only if ext.egl.KHR_no_config_context is false
EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR;
Config mContextAttribs;
std::vector<EGLContext> mAdditionalContexts;
bool mMSAA4XSupport = false;
private:
class EGL {
EGLDisplay& mEGLDisplay;
EGLSurface mCurrentDrawSurface = EGL_NO_SURFACE;
@@ -17,10 +17,7 @@
#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
#include "AndroidSwapChainHelper.h"
#include <backend/AcquiredImage.h>
#include <backend/DriverEnums.h>
#include <backend/Platform.h>
#include <backend/platforms/OpenGLPlatform.h>
#include <backend/platforms/PlatformEGL.h>
@@ -28,8 +25,6 @@
#include <utils/android/PerformanceHintManager.h>
#include <utils/compiler.h>
#include <math/mat3.h>
#include <chrono>
#include <stddef.h>
@@ -72,17 +67,9 @@ public:
* @return `true` on success, `false` on failure. The default implementation
* returns `false`.
*/
bool convertSyncToFd(Sync* sync, int* fd) noexcept;
bool convertSyncToFd(Platform::Sync* sync, int* fd) noexcept;
protected:
struct {
struct {
bool ANDROID_presentation_time = false;
bool ANDROID_get_frame_timestamps = false;
bool ANDROID_native_fence_sync = false;
} egl;
} ext;
// --------------------------------------------------------------------------------------------
// Platform Interface
@@ -93,22 +80,12 @@ protected:
int getOSVersion() const noexcept override;
Driver* createDriver(void* sharedContext,
const DriverConfig& driverConfig) override;
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;
const Platform::DriverConfig& driverConfig) override;
// --------------------------------------------------------------------------------------------
// OpenGLPlatform Interface
struct SyncEGLAndroid : public Sync {
struct SyncEGLAndroid : public Platform::Sync {
EGLSyncKHR sync;
};
@@ -129,8 +106,8 @@ protected:
Stream* createStream(void* nativeStream) noexcept override;
void destroyStream(Stream* stream) noexcept override;
Sync* createSync() noexcept override;
void destroySync(Sync* sync) noexcept override;
Platform::Sync* createSync() noexcept override;
void destroySync(Platform::Sync* sync) noexcept override;
void attach(Stream* stream, intptr_t tname) noexcept override;
void detach(Stream* stream) noexcept override;
void updateTexImage(Stream* stream, int64_t* timestamp) noexcept override;
@@ -143,7 +120,7 @@ protected:
*/
AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept override;
ExternalTexture* createExternalImageTexture() noexcept override;
OpenGLPlatform::ExternalTexture* createExternalImageTexture() noexcept override;
void destroyExternalImageTexture(ExternalTexture* texture) noexcept override;
struct ExternalImageEGLAndroid : public ExternalImageEGL {
@@ -163,28 +140,12 @@ protected:
bool setImage(ExternalImageEGLAndroid const* eglExternalImage,
ExternalTexture* texture) noexcept;
protected:
bool makeCurrent(ContextType type,
SwapChain* drawSwapChain,
SwapChain* readSwapChain) override;
struct SwapChainEGLAndroid : public SwapChainEGL {
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
void* nativeWindow, uint64_t flags);
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
uint32_t width, uint32_t height, uint64_t flags);
void terminate(PlatformEGLAndroid& platform);
bool setPresentFrameId(uint64_t frameId) const noexcept;
uint64_t getFrameId(uint64_t frameId) const noexcept;
private:
AndroidSwapChainHelper mImpl{};
};
private:
// prevent derived classes' implementations to call through
[[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override;
[[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
struct InitializeJvmForPerformanceManagerIfNeeded {
InitializeJvmForPerformanceManagerIfNeeded();
};
@@ -198,15 +159,13 @@ private:
InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded;
utils::PerformanceHintManager mPerformanceHintManager;
utils::PerformanceHintManager::Session mPerformanceHintSession;
SwapChainEGLAndroid* mCurrentDrawSwapChain{};
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;
void* mNativeWindowLib = nullptr;
int32_t (*ANativeWindow_getBuffersDefaultDataSpace)(ANativeWindow* window) = nullptr;
bool mAssertNativeWindowIsValid = false;
bool mHasProducerThrottlingControl = false;
};
} // namespace filament::backend
@@ -26,10 +26,7 @@
#include <backend/DriverEnums.h>
#include <shared_mutex>
#include <thread>
#include <vector>
#include <unordered_map>
namespace filament::backend {
@@ -51,10 +48,6 @@ protected:
void terminate() noexcept override;
bool isExtraContextSupported() const noexcept override;
void createContext(bool shared) override;
void releaseContext() noexcept override;
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
@@ -67,10 +60,6 @@ private:
GLXFBConfig mGLXConfig{};
GLXPbuffer mDummySurface;
std::vector<GLXPbuffer> mPBuffers;
// Variables for shared contexts
std::unordered_map<std::thread::id, GLXContext> mAdditionalContexts;
std::shared_mutex mAdditionalContextsLock;
};
} // namespace filament::backend
@@ -366,7 +366,6 @@ DECL_DRIVER_API_SYNCHRONOUS_N(void, setupExternalImage, void*, image)
DECL_DRIVER_API_SYNCHRONOUS_N(backend::TimerQueryResult, getTimerQueryValue, backend::TimerQueryHandle, tqh, uint64_t*, elapsedTime)
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)
/*
* Updating driver objects
@@ -1,78 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AndroidNativeWindow.h"
#include <android/native_window.h>
#include <utils/compiler.h>
#include <cstdint>
#include <cerrno>
#include <utility>
namespace filament::backend {
std::pair<int, bool> NativeWindow::isValid(ANativeWindow* const anw) noexcept {
if (__builtin_available(android 28, *)) {
// this a proxy for is_valid()
auto const result = ANativeWindow_getBuffersDataSpace(anw);
return { result, result >= 0 };
}
// fallback on using private APIs
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
if (UTILS_LIKELY(pWindow->query)) {
int isValid = 0;
int const err = pWindow->query(anw, IS_VALID, &isValid);
return { err, bool(isValid) };
}
return { -EINVAL, false };
}
int NativeWindow::getNextFrameId(ANativeWindow* anw, uint64_t* frameId) {
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
return pWindow->perform(anw, GET_NEXT_FRAME_ID, frameId);
}
int NativeWindow::enableFrameTimestamps(ANativeWindow* anw, bool enable) {
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
return pWindow->perform(anw, ENABLE_FRAME_TIMESTAMPS, enable);
}
int NativeWindow::getCompositorTiming(ANativeWindow* anw,
int64_t* compositeDeadline, int64_t* compositeInterval,
int64_t* compositeToPresentLatency) {
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
return pWindow->perform(anw, GET_COMPOSITOR_TIMING,
compositeDeadline, compositeInterval, compositeToPresentLatency);
}
int NativeWindow::getFrameTimestamps(ANativeWindow* anw,
uint64_t frameId,
int64_t* outRequestedPresentTime, int64_t* outAcquireTime,
int64_t* outLatchTime, int64_t* outFirstRefreshStartTime,
int64_t* outLastRefreshStartTime, int64_t* outGpuCompositionDoneTime,
int64_t* outDisplayPresentTime, int64_t* outDequeueReadyTime,
int64_t* outReleaseTime) {
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
return pWindow->perform(anw, GET_FRAME_TIMESTAMPS,
frameId, outRequestedPresentTime, outAcquireTime, outLatchTime,
outFirstRefreshStartTime, outLastRefreshStartTime,
outGpuCompositionDoneTime, outDisplayPresentTime,
outDequeueReadyTime, outReleaseTime);
}
} // namespace filament::backend
@@ -1,68 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FILAMENT_BACKEND_ANDROIDNATIVEWINDOW_H
#define FILAMENT_BACKEND_ANDROIDNATIVEWINDOW_H
#include <android/native_window.h>
#include <cstdint>
#include <utility>
namespace filament::backend {
struct NativeWindow {
// this class can't be instantiated. It's used by casting ANativeWindow* to it.
NativeWindow() noexcept = delete;
~NativeWindow() noexcept = delete;
// is valid query enum value
enum {
IS_VALID = 17,
GET_NEXT_FRAME_ID = 24,
ENABLE_FRAME_TIMESTAMPS = 25,
GET_COMPOSITOR_TIMING = 26,
GET_FRAME_TIMESTAMPS = 27,
};
#if defined(__LP64__)
uint64_t pad[18];
#else
uint32_t pad[21];
#endif
int (*query)(ANativeWindow const*, int, int*);
int (*perform)(ANativeWindow*, int, ...);
// return whether the ANativeWindow is valid
static std::pair<int, bool> isValid(ANativeWindow* anw) noexcept;
static int getNextFrameId(ANativeWindow* anw, uint64_t* frameId);
static int enableFrameTimestamps(ANativeWindow* anw, bool enable);
static int getCompositorTiming(ANativeWindow* anw,
int64_t* compositeDeadline, int64_t* compositeInterval,
int64_t* compositeToPresentLatency);
static int getFrameTimestamps(ANativeWindow* anw,
uint64_t frameId,
int64_t* outRequestedPresentTime, int64_t* outAcquireTime,
int64_t* outLatchTime, int64_t* outFirstRefreshStartTime,
int64_t* outLastRefreshStartTime, int64_t* outGpuCompositionDoneTime,
int64_t* outDisplayPresentTime, int64_t* outDequeueReadyTime,
int64_t* outReleaseTime);
};
} // namespace filament::backend
#endif //FILAMENT_BACKEND_ANDROIDNATIVEWINDOW_H
@@ -1,63 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AndroidSwapChainHelper.h"
#include "AndroidNativeWindow.h"
#include <android/native_window.h>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <mutex>
namespace filament::backend {
AndroidSwapChainHelper::AndroidSwapChainHelper() = default;
AndroidSwapChainHelper::~AndroidSwapChainHelper() noexcept = default;
bool AndroidSwapChainHelper::setPresentFrameId(
ANativeWindow* anw, uint64_t 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) {
// 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 });
return true;
}
return false;
}
uint64_t AndroidSwapChainHelper::getFrameId(uint64_t frameId) const noexcept {
std::lock_guard const lock(mLock);
auto pos = mFrameIdToSystemFrameId.find(frameId);
if (pos != mFrameIdToSystemFrameId.end()) {
return pos->second;
}
return std::numeric_limits<uint64_t>::max();
}
}
@@ -1,47 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_ANDROIDSWAPCHAINHELPER_H
#define TNT_FILAMENT_BACKEND_ANDROIDSWAPCHAINHELPER_H
#include <utils/Mutex.h>
#include <android/native_window.h>
#include <map>
#include <cstddef>
#include <cstdint>
namespace filament::backend {
struct AndroidSwapChainHelper {
AndroidSwapChainHelper();
~AndroidSwapChainHelper() noexcept;
// methods below are thread-safe
bool setPresentFrameId(ANativeWindow* anw, uint64_t frameId) const noexcept;
uint64_t getFrameId(uint64_t frameId) const noexcept;
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{};
};
}
#endif //TNT_FILAMENT_BACKEND_ANDROIDSWAPCHAINHELPER_H
-16
View File
@@ -122,22 +122,6 @@ bool Platform::pumpEvents() noexcept {
return false;
}
bool Platform::isCompositorTimingSupported() const noexcept {
return false;
}
bool Platform::queryCompositorTiming(SwapChain const*, CompositorTiming*) const noexcept {
return false;
}
bool Platform::setPresentFrameId(SwapChain const*, uint64_t) noexcept {
return false;
}
bool Platform::queryFrameTimestamps(SwapChain const*, uint64_t, FrameTimestamps*) const noexcept {
return false;
}
void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept {
mInsertBlob = std::move(insertBlob);
mRetrieveBlob = std::move(retrieveBlob);
+11 -2
View File
@@ -19,7 +19,6 @@
#include "MetalContext.h"
#include "MetalFlags.h"
#include "MetalUtils.h"
#include <backend/DriverEnums.h>
#include <backend/platforms/PlatformMetal.h>
@@ -261,8 +260,18 @@ public:
// In practice, MetalRingBuffer is used for argument buffers, which are kept in the constant
// address space. Constant buffers have specific alignment requirements when specifying an
// offset.
#if defined(FILAMENT_IOS)
#if TARGET_OS_SIMULATOR
// The iOS simulator has differing alignment requirements.
static constexpr auto METAL_CONSTANT_BUFFER_OFFSET_ALIGNMENT = 256;
#else
static constexpr auto METAL_CONSTANT_BUFFER_OFFSET_ALIGNMENT = 4;
#endif // TARGET_OS_SIMULATOR
#else
static constexpr auto METAL_CONSTANT_BUFFER_OFFSET_ALIGNMENT = 32;
#endif
static inline auto computeSlotSize(MTLSizeAndAlign layout) {
return align(align(layout.size, layout.align), getUniformBufferOffsetAlignment());
return align(align(layout.size, layout.align), METAL_CONSTANT_BUFFER_OFFSET_ALIGNMENT);
}
MetalRingBuffer(id<MTLDevice> device, MTLResourceOptions options, MTLSizeAndAlign layout,
+2 -33
View File
@@ -95,38 +95,6 @@ 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;
@@ -152,7 +120,8 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
}
if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) {
logMTLCommandBufferError(errorCode);
LOG(ERROR) << "Filament Metal command buffer errored with code: " << errorCode << " ("
<< stringifyMTLCommandBufferError(errorCode) << ").";
}
}];
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)
+19 -29
View File
@@ -29,7 +29,6 @@
#include "MetalHandles.h"
#include "MetalState.h"
#include "MetalTimerQuery.h"
#include "MetalUtils.h"
#include <backend/platforms/PlatformMetal.h>
#include <backend/platforms/PlatformMetal-ObjC.h>
@@ -621,9 +620,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
auto& sc = mContext->sampleCountLookup;
samples = sc[std::min(MAX_SAMPLE_COUNT, samples)];
using AttachmentInfo = MetalRenderTarget::AttachmentInfo;
AttachmentInfo colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { {} };
MetalRenderTarget::Attachment 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;
@@ -637,7 +634,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
colorAttachments[i] = { colorTexture, color[i].level, color[i].layer };
}
AttachmentInfo depthAttachment = {};
MetalRenderTarget::Attachment depthAttachment = {};
if (any(targetBufferFlags & TargetBufferFlags::DEPTH)) {
FILAMENT_CHECK_PRECONDITION(depth.handle)
<< "The DEPTH flag was specified, but invalid depth handle provided.";
@@ -647,7 +644,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
depthAttachment = { depthTexture, depth.level, depth.layer };
}
AttachmentInfo stencilAttachment = {};
MetalRenderTarget::Attachment stencilAttachment = {};
if (any(targetBufferFlags & TargetBufferFlags::STENCIL)) {
FILAMENT_CHECK_PRECONDITION(stencil.handle)
<< "The STENCIL flag was specified, but invalid stencil handle provided.";
@@ -671,6 +668,8 @@ 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,8 +1038,6 @@ 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);
}
}
@@ -1152,7 +1149,8 @@ bool MetalDriver::isSRGBSwapChainSupported() {
}
bool MetalDriver::isMSAASwapChainSupported(uint32_t) {
return true;
// TODO: support MSAA swapchain
return false;
}
bool MetalDriver::isProtectedContentSupported() {
@@ -1254,10 +1252,6 @@ size_t MetalDriver::getMaxArrayTextureLayers() {
return 256;
}
size_t MetalDriver::getUniformBufferOffsetAlignment() {
return ::filament::backend::getUniformBufferOffsetAlignment();
}
void MetalDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& data,
uint32_t byteOffset) {
FILAMENT_CHECK_PRECONDITION(data.buffer)
@@ -1562,9 +1556,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.
MetalAttachment color = srcTarget->getDrawColorAttachment(0);
MetalRenderTarget::Attachment color = srcTarget->getDrawColorAttachment(0);
id<MTLTexture> srcTexture = color.getTexture();
size_t miplevel = color.getLevel();
size_t miplevel = color.level;
// Clamp height and width to actual texture's height and width
MTLSize srcTextureSize = MTLSizeMake(srcTexture.width >> miplevel, srcTexture.height >> miplevel, 1);
@@ -1772,8 +1766,8 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
};
// We always blit from/to the COLOR0 attachment.
MetalAttachment const srcColorAttachment = srcTarget->getReadColorAttachment(0);
MetalAttachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0);
MetalRenderTarget::Attachment const srcColorAttachment = srcTarget->getReadColorAttachment(0);
MetalRenderTarget::Attachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0);
if (srcColorAttachment && dstColorAttachment) {
FILAMENT_CHECK_PRECONDITION(
@@ -1785,13 +1779,13 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
args.filter = filter;
args.source.region = srcTarget->getRegionFromClientRect(srcRect);
args.source.texture = srcColorAttachment.getTexture();
args.source.level = srcColorAttachment.getLevel();
args.source.slice = srcColorAttachment.getLayer();
args.source.level = srcColorAttachment.level;
args.source.slice = srcColorAttachment.layer;
args.destination.region = dstTarget->getRegionFromClientRect(dstRect);
args.destination.texture = dstColorAttachment.getTexture();
args.destination.level = dstColorAttachment.getLevel();
args.destination.slice = dstColorAttachment.getLayer();
args.destination.level = dstColorAttachment.level;
args.destination.slice = dstColorAttachment.layer;
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blitDEPRECATED");
}
@@ -1827,28 +1821,24 @@ 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 = rt->getDrawColorAttachment(i);
const auto& attachment = mContext->currentRenderTarget->getDrawColorAttachment(i);
if (!attachment) {
continue;
}
colorPixelFormat[i] = attachment.getPixelFormat();
assert_invariant(attachment.getSampleCount() == rt->getSampleCount());
}
MTLPixelFormat depthPixelFormat = MTLPixelFormatInvalid;
const auto& depthAttachment = rt->getDepthAttachment();
const auto& depthAttachment = mContext->currentRenderTarget->getDepthAttachment();
if (depthAttachment) {
depthPixelFormat = depthAttachment.getPixelFormat();
assert_invariant(depthAttachment.getSampleCount() == rt->getSampleCount());
}
MTLPixelFormat stencilPixelFormat = MTLPixelFormatInvalid;
const auto& stencilAttachment = rt->getStencilAttachment();
const auto& stencilAttachment = mContext->currentRenderTarget->getStencilAttachment();
if (stencilAttachment) {
stencilPixelFormat = stencilAttachment.getPixelFormat();
assert_invariant(isMetalFormatStencil(stencilPixelFormat));
assert_invariant(stencilAttachment.getSampleCount() == rt->getSampleCount());
}
MetalPipelineState const pipelineState {
.vertexFunction = vertex,
@@ -1866,7 +1856,7 @@ void MetalDriver::bindPipeline(PipelineState const& ps) {
},
.depthAttachmentPixelFormat = depthPixelFormat,
.stencilAttachmentPixelFormat = stencilPixelFormat,
.sampleCount = rt->getSampleCount(),
.sampleCount = mContext->currentRenderTarget->getSamples(),
.blendState = BlendState {
.alphaBlendOperation = getMetalBlendOperation(rs.blendEquationAlpha),
.rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB),
+32
View File
@@ -451,6 +451,38 @@ 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
+57 -93
View File
@@ -49,61 +49,6 @@
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:
@@ -121,14 +66,14 @@ public:
~MetalSwapChain();
// 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:
// Acquires a texture that can be used to render into this SwapChain.
// 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.
// May return an invalid attachemnt if a drawable cannot be acquired.
MetalAttachment acquireDrawable();
MetalAttachment acquireDepthTexture();
MetalAttachment acquireStencilTexture();
id<MTLTexture> acquireDrawable();
id<MTLTexture> acquireDepthTexture();
id<MTLTexture> acquireStencilTexture();
void releaseDrawable();
@@ -143,7 +88,6 @@ public:
NSUInteger getSurfaceWidth() const;
NSUInteger getSurfaceHeight() const;
NSUInteger getSampleCount() const;
bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; }
@@ -162,24 +106,13 @@ 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);
static id<MTLTexture> createMultisampledTexture(MetalContext const& context,
MTLPixelFormat format, uint32_t width, uint32_t height, uint8_t samples);
void ensureDepthStencilTexture();
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;
@@ -188,7 +121,6 @@ private:
std::shared_ptr<std::mutex> layerDrawableMutex;
MetalExternalImage externalImage;
SwapChainType type;
uint64_t flags;
int64_t abandonedUntilFrame = -1;
@@ -375,15 +307,51 @@ private:
class MetalRenderTarget : public HwRenderTarget {
public:
struct AttachmentInfo {
MetalTexture* texture = nullptr;
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;
}
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,
AttachmentInfo colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
AttachmentInfo depthAttachment, AttachmentInfo stencilAttachment);
Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
Attachment depthAttachment, Attachment stencilAttachment);
explicit MetalRenderTarget(MetalContext* context)
: HwRenderTarget(0, 0), context(context), defaultRenderTarget(true) {}
@@ -419,14 +387,12 @@ public:
math::uint2 getAttachmentSize() noexcept;
MetalAttachment getDrawColorAttachment(size_t index);
MetalAttachment getReadColorAttachment(size_t index);
MetalAttachment getDepthAttachment();
MetalAttachment getStencilAttachment();
uint8_t getSamples() const { return samples; }
// 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;
Attachment getDrawColorAttachment(size_t index);
Attachment getReadColorAttachment(size_t index);
Attachment getDepthAttachment();
Attachment getStencilAttachment();
private:
@@ -436,12 +402,12 @@ private:
MetalContext* context;
bool defaultRenderTarget = false;
MetalAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
MetalAttachment depth = {};
MetalAttachment stencil = {};
math::uint2 attachmentSize = {};
uint8_t samples = 1;
Attachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
Attachment depth = {};
Attachment stencil = {};
math::uint2 attachmentSize = {};
};
// MetalFence is used to implement both Fences and Syncs.
@@ -463,8 +429,6 @@ public:
API_AVAILABLE(ios(12.0))
void onSignal(MetalFenceSignalBlock block);
void cancel();
private:
MetalContext& context;
+177 -272
View File
@@ -116,8 +116,7 @@ MetalSwapChain::MetalSwapChain(
depthStencilFormat(decideDepthStencilFormat(flags)),
layer(nativeWindow),
layerDrawableMutex(std::make_shared<std::mutex>()),
type(SwapChainType::CAMETALLAYER),
flags(flags) {
type(SwapChainType::CAMETALLAYER) {
FILAMENT_CHECK_PRECONDITION([nativeWindow isKindOfClass:[CAMetalLayer class]])
<< "nativeWindow pointer of class "
@@ -148,8 +147,7 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform, i
depthStencilFormat(decideDepthStencilFormat(flags)),
headlessWidth(width),
headlessHeight(height),
type(SwapChainType::HEADLESS),
flags(flags) {}
type(SwapChainType::HEADLESS) {}
MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform,
CVPixelBufferRef pixelBuffer, uint64_t flags)
@@ -157,8 +155,7 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform,
platform(platform),
depthStencilFormat(decideDepthStencilFormat(flags)),
externalImage(MetalExternalImage::createFromImage(context, pixelBuffer)),
type(SwapChainType::CVPIXELBUFFERREF),
flags(flags) {
type(SwapChainType::CVPIXELBUFFERREF) {
assert_invariant(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
MetalExternalImage::assertWritableImage(pixelBuffer);
assert_invariant(externalImage.isValid());
@@ -170,27 +167,6 @@ 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() {
}
@@ -214,18 +190,65 @@ NSUInteger MetalSwapChain::getSurfaceHeight() const {
return (NSUInteger) layer.drawableSize.height;
}
NSUInteger MetalSwapChain::getSampleCount() const {
if (flags & flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
return 4u;
}
return 1u;
}
bool MetalSwapChain::isAbandoned() const {
return context.currentFrame < abandonedUntilFrame;
}
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;
}
void MetalSwapChain::releaseDrawable() {
if (drawable) {
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
@@ -233,45 +256,32 @@ void MetalSwapChain::releaseDrawable() {
}
}
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));
}
return attachment;
id<MTLTexture> MetalSwapChain::acquireDepthTexture() {
ensureDepthStencilTexture();
assert_invariant(depthStencilTexture);
return depthStencilTexture;
}
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));
id<MTLTexture> MetalSwapChain::acquireStencilTexture() {
if (!isMetalFormatStencil(depthStencilFormat)) {
return nil;
}
return attachment;
ensureDepthStencilTexture();
assert_invariant(depthStencilTexture);
return depthStencilTexture;
}
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;
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;
}
}
MTLTextureDescriptor* descriptor =
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:depthStencilFormat
@@ -280,26 +290,7 @@ id<MTLTexture> MetalSwapChain::ensureDepthStencilTexture(uint32_t width, uint32_
mipmapped:NO];
descriptor.usage = MTLTextureUsageRenderTarget;
descriptor.resourceOptions = MTLResourceStorageModePrivate;
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);
depthStencilTexture = [context.device newTextureWithDescriptor:descriptor];
}
void MetalSwapChain::setFrameScheduledCallback(
@@ -463,62 +454,6 @@ 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) {}
@@ -1035,108 +970,90 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
}
MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height,
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 };
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};
UTILS_UNUSED_IN_RELEASE size_t attachmentCount = 0;
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
MetalTexture* const texture = colorAttachments[i].texture;
const auto& level = colorAttachments[i].level;
const auto& layer = colorAttachments[i].layer;
if (!texture) {
if (!colorAttachments[i]) {
continue;
}
color[i] = colorAttachments[i];
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
FILAMENT_CHECK_PRECONDITION(color[i].getSampleCount() <= samples)
<< "MetalRenderTarget was initialized with a MSAA COLOR" << i
<< " texture, but sample count is " << samples << ".";
const auto twidth = std::max(1u, texture->width >> level);
const auto theight = std::max(1u, texture->height >> level);
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);
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 && texture->samples == 1) {
auto& sidecar = texture->msaaSidecar;
if (samples > 1 && color[i].getSampleCount() == 1) {
auto& sidecar = color[i].metalTexture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
texture->height, samples);
sidecar = createMultisampledTexture(color[i].getPixelFormat(),
color[i].metalTexture->width, color[i].metalTexture->height, samples);
}
color[i] = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
}
}
if (depthAttachment.texture) {
MetalTexture* const texture = depthAttachment.texture;
const auto& level = depthAttachment.level;
const auto& layer = depthAttachment.layer;
if (depthAttachment) {
depth = depthAttachment;
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
FILAMENT_CHECK_PRECONDITION(depth.getSampleCount() <= samples)
<< "MetalRenderTarget was initialized with a MSAA DEPTH texture, but sample count "
"is "
<< samples << ".";
const auto twidth = std::max(1u, texture->width >> level);
const auto theight = std::max(1u, texture->height >> level);
auto t = depth.metalTexture;
const auto twidth = std::max(1u, t->width >> depth.level);
const auto theight = std::max(1u, t->height >> depth.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 && texture->samples == 1) {
auto& sidecar = texture->msaaSidecar;
if (samples > 1 && depth.getSampleCount() == 1) {
auto& sidecar = depth.metalTexture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
texture->height, samples);
sidecar = createMultisampledTexture(depth.getPixelFormat(),
depth.metalTexture->width, depth.metalTexture->height, samples);
}
depth = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
}
}
if (stencilAttachment.texture) {
// stencil = stencilAttachment;
MetalTexture* const texture = stencilAttachment.texture;
const auto& level = stencilAttachment.level;
const auto& layer = stencilAttachment.layer;
if (stencilAttachment) {
stencil = stencilAttachment;
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
FILAMENT_CHECK_PRECONDITION(stencil.getSampleCount() <= samples)
<< "MetalRenderTarget was initialized with a MSAA STENCIL texture, but sample "
"count is "
<< samples << ".";
const auto twidth = std::max(1u, texture->width >> level);
const auto theight = std::max(1u, texture->height >> level);
auto t = stencil.metalTexture;
const auto twidth = std::max(1u, t->width >> stencil.level);
const auto theight = std::max(1u, t->height >> stencil.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 && texture->samples == 1) {
auto& sidecar = texture->msaaSidecar;
if (samples > 1 && stencil.getSampleCount() == 1) {
auto& sidecar = stencil.metalTexture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
texture->height, samples);
sidecar = createMultisampledTexture(stencil.getPixelFormat(),
stencil.metalTexture->width, stencil.metalTexture->height, samples);
}
stencil = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
}
}
@@ -1159,105 +1076,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++) {
MetalAttachment attachment = getDrawColorAttachment(i);
Attachment attachment = getDrawColorAttachment(i);
if (!attachment) {
continue;
}
descriptor.colorAttachments[i].texture = attachment.getTexture();
descriptor.colorAttachments[i].level = attachment.getLevel();
descriptor.colorAttachments[i].slice = attachment.getLayer();
descriptor.colorAttachments[i].level = attachment.level;
descriptor.colorAttachments[i].slice = attachment.layer;
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);
if (attachment.getMsaaTexture()) {
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
const bool automaticResolve = samples > 1 && attachment.getSampleCount() == 1;
if (automaticResolve) {
// 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.
// 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;
}
assert_invariant(descriptor.colorAttachments[i].loadAction != MTLLoadActionLoad);
assert_invariant(!defaultRenderTarget);
descriptor.colorAttachments[i].texture = attachment.getMsaaTexture();
id<MTLTexture> sidecar = attachment.getMSAASidecarTexture();
assert_invariant(sidecar);
descriptor.colorAttachments[i].texture = sidecar;
descriptor.colorAttachments[i].level = 0;
descriptor.colorAttachments[i].slice = 0;
const bool discard = any(discardFlags & getTargetBufferFlagsAt(i));
if (!discard) {
descriptor.colorAttachments[i].resolveTexture = attachment.getTexture();
descriptor.colorAttachments[i].resolveLevel = attachment.getLevel();
descriptor.colorAttachments[i].resolveSlice = attachment.getLayer();
descriptor.colorAttachments[i].resolveTexture = attachment.texture;
descriptor.colorAttachments[i].resolveLevel = attachment.level;
descriptor.colorAttachments[i].resolveSlice = attachment.layer;
descriptor.colorAttachments[i].storeAction = MTLStoreActionMultisampleResolve;
}
}
}
MetalAttachment depthAttachment = getDepthAttachment();
Attachment depthAttachment = getDepthAttachment();
if (depthAttachment) {
descriptor.depthAttachment.texture = depthAttachment.getTexture();
descriptor.depthAttachment.level = depthAttachment.getLevel();
descriptor.depthAttachment.slice = depthAttachment.getLayer();
descriptor.depthAttachment.level = depthAttachment.level;
descriptor.depthAttachment.slice = depthAttachment.layer;
descriptor.depthAttachment.loadAction = getLoadAction(params, TargetBufferFlags::DEPTH);
descriptor.depthAttachment.storeAction = getStoreAction(params, TargetBufferFlags::DEPTH);
descriptor.depthAttachment.clearDepth = params.clearDepth;
}
if (depthAttachment.getMsaaTexture()) {
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
const bool depthAutomaticResolve = samples > 1 && depthAttachment.getSampleCount() == 1;
if (depthAutomaticResolve) {
// 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.
// 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;
}
assert_invariant(descriptor.depthAttachment.loadAction != MTLLoadActionLoad);
assert_invariant(!defaultRenderTarget);
descriptor.depthAttachment.texture = depthAttachment.getMsaaTexture();
id<MTLTexture> sidecar = depthAttachment.getMSAASidecarTexture();
assert_invariant(sidecar);
descriptor.depthAttachment.texture = sidecar;
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.getLevel();
descriptor.depthAttachment.resolveSlice = depthAttachment.getLayer();
descriptor.depthAttachment.resolveLevel = depthAttachment.level;
descriptor.depthAttachment.resolveSlice = depthAttachment.layer;
descriptor.depthAttachment.storeAction = MTLStoreActionMultisampleResolve;
}
}
MetalAttachment stencilAttachment = getStencilAttachment();
Attachment stencilAttachment = getStencilAttachment();
if (stencilAttachment) {
descriptor.stencilAttachment.texture = stencilAttachment.getTexture();
descriptor.stencilAttachment.level = stencilAttachment.getLevel();
descriptor.stencilAttachment.slice = stencilAttachment.getLayer();
descriptor.stencilAttachment.level = stencilAttachment.level;
descriptor.stencilAttachment.slice = stencilAttachment.layer;
descriptor.stencilAttachment.loadAction = getLoadAction(params, TargetBufferFlags::STENCIL);
descriptor.stencilAttachment.storeAction = getStoreAction(params, TargetBufferFlags::STENCIL);
descriptor.stencilAttachment.clearStencil = params.clearStencil;
}
if (stencilAttachment.getMsaaTexture()) {
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
const bool stencilAutomaticResolve = samples > 1 && stencilAttachment.getSampleCount() == 1;
if (stencilAutomaticResolve) {
// 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.
// 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;
}
assert_invariant(descriptor.stencilAttachment.loadAction != MTLLoadActionLoad);
assert_invariant(!defaultRenderTarget);
descriptor.stencilAttachment.texture = stencilAttachment.getMsaaTexture();
id<MTLTexture> sidecar = stencilAttachment.getMSAASidecarTexture();
assert_invariant(sidecar);
descriptor.stencilAttachment.texture = sidecar;
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.getLevel();
descriptor.stencilAttachment.resolveSlice = stencilAttachment.getLayer();
descriptor.stencilAttachment.resolveLevel = stencilAttachment.level;
descriptor.stencilAttachment.resolveSlice = stencilAttachment.layer;
descriptor.stencilAttachment.storeAction = MTLStoreActionMultisampleResolve;
if (@available(iOS 12.0, *)) {
descriptor.stencilAttachment.stencilResolveFilter = MTLMultisampleStencilResolveFilterSample0;
@@ -1272,46 +1189,44 @@ bool MetalRenderTarget::involvesAbandonedSwapChain() const noexcept {
return (draw && draw->isAbandoned()) || (read && read->isAbandoned());
}
MetalAttachment MetalRenderTarget::getDrawColorAttachment(size_t index) {
MetalRenderTarget::Attachment 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);
return context->currentDrawSwapChain->acquireDrawable();
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 color[index];
return result;
}
MetalAttachment MetalRenderTarget::getReadColorAttachment(size_t index) {
MetalRenderTarget::Attachment 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);
return context->currentReadSwapChain->acquireDrawable();
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 color[index];
return result;
}
MetalAttachment MetalRenderTarget::getDepthAttachment() {
MetalRenderTarget::Attachment MetalRenderTarget::getDepthAttachment() {
Attachment result = depth;
if (defaultRenderTarget) {
assert_invariant(context->currentDrawSwapChain);
return context->currentDrawSwapChain->acquireDepthTexture();
result.texture = context->currentDrawSwapChain->acquireDepthTexture();
}
return depth;
return result;
}
MetalAttachment MetalRenderTarget::getStencilAttachment() {
MetalRenderTarget::Attachment MetalRenderTarget::getStencilAttachment() {
Attachment result = stencil;
if (defaultRenderTarget) {
assert_invariant(context->currentDrawSwapChain);
return context->currentDrawSwapChain->acquireStencilTexture();
result.texture = context->currentDrawSwapChain->acquireStencilTexture();
}
return stencil;
}
NSUInteger MetalRenderTarget::getSampleCount() const {
if (defaultRenderTarget) {
assert_invariant(context->currentDrawSwapChain);
return context->currentDrawSwapChain->getSampleCount();
}
return samples;
return result;
}
MTLLoadAction MetalRenderTarget::getLoadAction(const RenderPassParams& params,
@@ -1395,30 +1310,20 @@ 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 state->status;
return FenceStatus::TIMEOUT_EXPIRED;
}
}
return state->status;
return FenceStatus::CONDITION_SATISFIED;
}
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;
-16
View File
@@ -50,22 +50,6 @@ id<MTLTexture> createTextureViewWithLodRange(id<MTLTexture> texture, NSUInteger
*/
id<MTLTexture> createTextureViewWithSingleSlice(id<MTLTexture> texture, NSUInteger slice);
/**
* Returns the required alignment for uniform buffer offsets.
*/
constexpr size_t getUniformBufferOffsetAlignment() {
#if defined(FILAMENT_IOS)
#if TARGET_OS_SIMULATOR
// The iOS simulator has differing alignment requirements.
return 256;
#else
return 4;
#endif // TARGET_OS_SIMULATOR
#else
return 32;
#endif
}
} // namespace filament::backend
#endif //TNT_FILAMENT_DRIVER_METALUTILS_H
-1
View File
@@ -68,5 +68,4 @@ id<MTLTexture> createTextureViewWithSingleSlice(id<MTLTexture> texture, NSUInteg
slices:NSMakeRange(slice, 1)];
}
} // namespace filament::backend
-4
View File
@@ -274,10 +274,6 @@ size_t NoopDriver::getMaxArrayTextureLayers() {
return 256u;
}
size_t NoopDriver::getUniformBufferOffsetAlignment() {
return 256u;
}
void NoopDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
uint32_t byteOffset) {
scheduleDestroy(std::move(p));
@@ -303,10 +303,13 @@ void GLDescriptorSet::bind(
offset += offsets[dynamicOffsetIndex++];
}
if (arg.bo) {
p.updateUniforms(bindingPoint, arg.bo->gl.id, arg.bo->gl.buffer, arg.bo->age, offset);
auto buffer = static_cast<char const*>(arg.bo->gl.buffer) + offset;
p.updateUniforms(bindingPoint, arg.bo->gl.id, buffer, arg.bo->age);
}
} 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);
+1 -10
View File
@@ -2287,14 +2287,9 @@ mat3f OpenGLDriver::getStreamTransformMatrix(Handle<HwStream> sh) {
void OpenGLDriver::destroyFence(Handle<HwFence> fh) {
if (fh) {
GLFence const* const f = handle_cast<GLFence*>(fh);
GLFence 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);
}
@@ -2671,10 +2666,6 @@ size_t OpenGLDriver::getMaxArrayTextureLayers() {
return mContext.gets.max_array_texture_layers;
}
size_t OpenGLDriver::getUniformBufferOffsetAlignment() {
return mContext.gets.uniform_buffer_offset_alignment;
}
// ------------------------------------------------------------------------------------------------
// Swap chains
// ------------------------------------------------------------------------------------------------
+6 -11
View File
@@ -52,8 +52,8 @@ using namespace backend;
struct OpenGLProgram::LazyInitializationData {
Program::DescriptorSetInfo descriptorBindings;
Program::BindingUniformsInfo bindingUniformInfo;
FixedCapacityVector<Program::PushConstant> vertexPushConstants;
FixedCapacityVector<Program::PushConstant> fragmentPushConstants;
utils::FixedCapacityVector<Program::PushConstant> vertexPushConstants;
utils::FixedCapacityVector<Program::PushConstant> fragmentPushConstants;
};
@@ -141,7 +141,7 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
context.useProgram(program);
UTILS_NOUNROLL
for (descriptor_set_t set = 0; set < MAX_DESCRIPTOR_SET_COUNT; set++) {
for (backend::descriptor_set_t set = 0; set < MAX_DESCRIPTOR_SET_COUNT; set++) {
for (Program::Descriptor const& entry: lazyInitializationData.descriptorBindings[set]) {
switch (entry.type) {
case DescriptorType::UNIFORM_BUFFER:
@@ -214,8 +214,8 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
case DescriptorType::INPUT_ATTACHMENT:
break;
}
CHECK_GL_ERROR()
}
CHECK_GL_ERROR()
}
if (context.isES2()) {
@@ -262,25 +262,20 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
}
void OpenGLProgram::updateUniforms(
uint32_t const index, GLuint const id, void const* buffer,
uint16_t const age, uint32_t const offset) const noexcept {
uint32_t index, GLuint id, void const* buffer, uint16_t age) 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 && records.offset == offset) {
if (records.id == id && records.age == age) {
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];
+1 -2
View File
@@ -88,7 +88,7 @@ public:
}
// For ES2 only
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age, uint32_t offset) const noexcept;
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) const noexcept;
void setRec709ColorSpace(bool rec709) const noexcept;
PushConstantBundle getPushConstants() {
@@ -123,7 +123,6 @@ 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
@@ -346,18 +346,9 @@ void ShaderCompilerService::compileProgram(
// Check status of program linking. If it failed, errors will be logged.
bool const linked = checkLinkStatusAndCleanupShaders(token);
// We panic if it failed to create the program.
#if FILAMENT_ENABLE_MATDBG
if (UTILS_UNLIKELY(!linked)) {
LOG(ERROR) << "OpenGL program " << token->name.c_str_safe()
<< " failed to link or compile";
// this will allow us to continue without crashing
token->gl.program = 0;
}
#else
FILAMENT_CHECK_POSTCONDITION(linked)
<< "OpenGL program " << token->name.c_str_safe()
<< " failed to link or compile";
#endif
}
// Now `token->gl.program` must be populated, so we signal the completion
// of the linking. We don't need to check the result of the program here
@@ -488,27 +479,16 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) {
assert_invariant(token);// This function should be called when the token is still alive.
ensureTokenIsReady(token);
#if !FILAMENT_ENABLE_MATDBG
assert_invariant(token->gl.program);
#endif
GLuint const program = token->gl.program;
if (mMode != Mode::THREAD_POOL) {
// Check status of program linking. If it failed, errors will be logged.
bool const linked = checkLinkStatusAndCleanupShaders(token);
#if FILAMENT_ENABLE_MATDBG
if (UTILS_UNLIKELY(!linked)) {
LOG(ERROR)
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
token->gl.program = 0;
}
#else
// We panic if it failed to create the program.
FILAMENT_CHECK_POSTCONDITION(linked)
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
#endif
// The program has been successfully created. Try caching the program blob for
// non-THREAD_POOL modes. In the THREAD_POOL mode, caching is performed in the pool.
tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
@@ -57,11 +57,11 @@ using namespace backend;
// The Android NDK doesn't expose extensions, fake it with eglGetProcAddress
namespace glext {
UTILS_PRIVATE PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR = {};
UTILS_PRIVATE PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR = {};
UTILS_PRIVATE PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR = {};
UTILS_PRIVATE PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = {};
UTILS_PRIVATE PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = {};
}
using namespace glext;
@@ -73,11 +73,11 @@ void PlatformEGL::logEglError(const char* name) noexcept {
logEglError(name, eglGetError());
}
void PlatformEGL::logEglError(const char* name, EGLint const error) noexcept {
void PlatformEGL::logEglError(const char* name, EGLint error) noexcept {
LOG(ERROR) << name << " failed with " << getEglErrorName(error);
}
const char* PlatformEGL::getEglErrorName(EGLint const error) noexcept {
const char* PlatformEGL::getEglErrorName(EGLint error) noexcept {
switch (error) {
case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED";
case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS";
@@ -129,13 +129,13 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& drive
if (!initialized) {
EGLDeviceEXT eglDevice;
EGLint numDevices;
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT =
(PFNEGLQUERYDEVICESEXTPROC)eglGetProcAddress("eglQueryDevicesEXT");
if (eglQueryDevicesEXT != nullptr) {
eglQueryDevicesEXT(1, &eglDevice, &numDevices);
if(auto* getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
eglGetProcAddress("eglGetPlatformDisplay"))) {
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, nullptr);
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, 0);
initialized = eglInitialize(mEGLDisplay, &major, &minor);
}
}
@@ -150,7 +150,7 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& drive
importGLESExtensionsEntryPoints();
#endif
auto const extensions = GLUtils::split(eglQueryString(mEGLDisplay, EGL_EXTENSIONS));
auto extensions = GLUtils::split(eglQueryString(mEGLDisplay, EGL_EXTENSIONS));
ext.egl.ANDROID_recordable = extensions.has("EGL_ANDROID_recordable");
ext.egl.KHR_gl_colorspace = extensions.has("EGL_KHR_gl_colorspace");
ext.egl.KHR_create_context = extensions.has("EGL_KHR_create_context");
@@ -158,12 +158,12 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& drive
ext.egl.KHR_surfaceless_context = extensions.has("EGL_KHR_surfaceless_context");
ext.egl.EXT_protected_content = extensions.has("EGL_EXT_protected_content");
eglCreateSyncKHR = PFNEGLCREATESYNCKHRPROC(eglGetProcAddress("eglCreateSyncKHR"));
eglDestroySyncKHR = PFNEGLDESTROYSYNCKHRPROC(eglGetProcAddress("eglDestroySyncKHR"));
eglClientWaitSyncKHR = PFNEGLCLIENTWAITSYNCKHRPROC(eglGetProcAddress("eglClientWaitSyncKHR"));
eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC) eglGetProcAddress("eglCreateSyncKHR");
eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress("eglDestroySyncKHR");
eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC) eglGetProcAddress("eglClientWaitSyncKHR");
eglCreateImageKHR = PFNEGLCREATEIMAGEKHRPROC(eglGetProcAddress("eglCreateImageKHR"));
eglDestroyImageKHR = PFNEGLDESTROYIMAGEKHRPROC(eglGetProcAddress("eglDestroyImageKHR"));
eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC) eglGetProcAddress("eglCreateImageKHR");
eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC) eglGetProcAddress("eglDestroyImageKHR");
EGLint const pbufferAttribs[] = {
EGL_WIDTH, 1,
@@ -210,27 +210,27 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& drive
#endif
// Configure GPU context priority level for scheduling and preemption
if (driverConfig.gpuContextPriority != GpuContextPriority::DEFAULT) {
if (driverConfig.gpuContextPriority != Platform::GpuContextPriority::DEFAULT) {
if (extensions.has("EGL_IMG_context_priority")) {
EGLint priorityLevel = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
const char* priorityName;
switch (driverConfig.gpuContextPriority) {
case GpuContextPriority::DEFAULT:
case Platform::GpuContextPriority::DEFAULT:
assert_invariant(false);
break;
case GpuContextPriority::LOW:
case Platform::GpuContextPriority::LOW:
priorityLevel = EGL_CONTEXT_PRIORITY_LOW_IMG;
priorityName = "LOW";
break;
case GpuContextPriority::MEDIUM:
case Platform::GpuContextPriority::MEDIUM:
priorityLevel = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
priorityName = "MEDIUM";
break;
case GpuContextPriority::HIGH:
case Platform::GpuContextPriority::HIGH:
priorityLevel = EGL_CONTEXT_PRIORITY_HIGH_IMG;
priorityName = "HIGH";
break;
case GpuContextPriority::REALTIME:
case Platform::GpuContextPriority::REALTIME:
priorityLevel = EGL_CONTEXT_PRIORITY_HIGH_IMG;
priorityName = "REALTIME(=HIGH)";
break;
@@ -263,7 +263,7 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& drive
for (size_t tries = 0; tries < 3; tries++) {
mEGLContext = eglCreateContext(mEGLDisplay, eglConfig,
sharedContext, contextAttribs.data());
(EGLContext)sharedContext, contextAttribs.data());
if (UTILS_LIKELY(mEGLContext != EGL_NO_CONTEXT)) {
break;
}
@@ -362,10 +362,10 @@ bool PlatformEGL::isProtectedContextSupported() const noexcept {
return ext.egl.EXT_protected_content;
}
void PlatformEGL::createContext(bool const shared) {
EGLConfig const config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig;
void PlatformEGL::createContext(bool shared) {
EGLConfig config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig;
EGLContext const context = eglCreateContext(mEGLDisplay, config,
EGLContext context = eglCreateContext(mEGLDisplay, config,
shared ? mEGLContext : EGL_NO_CONTEXT, mContextAttribs.data());
if (UTILS_UNLIKELY(context == EGL_NO_CONTEXT)) {
@@ -389,7 +389,7 @@ void PlatformEGL::releaseContext() noexcept {
mAdditionalContexts.erase(
std::remove_if(mAdditionalContexts.begin(), mAdditionalContexts.end(),
[context](EGLContext const c) {
[context](EGLContext c) {
return c == context;
}), mAdditionalContexts.end());
@@ -406,15 +406,14 @@ void PlatformEGL::terminate() noexcept {
if (mEGLContextProtected != EGL_NO_CONTEXT) {
eglDestroyContext(mEGLDisplay, mEGLContextProtected);
}
for (auto const context : mAdditionalContexts) {
for (auto context : mAdditionalContexts) {
eglDestroyContext(mEGLDisplay, context);
}
eglTerminate(mEGLDisplay);
eglReleaseThread();
}
EGLConfig PlatformEGL::findSwapChainConfig(
uint64_t const flags, bool const window, bool const pbuffer) const {
EGLConfig PlatformEGL::findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const {
// Find config that support ES3.
EGLConfig config = EGL_NO_CONFIG_KHR;
EGLint configsCount;
@@ -482,24 +481,13 @@ EGLConfig PlatformEGL::findSwapChainConfig(
return config;
}
EGLConfig PlatformEGL::getSuitableConfigForSwapChain(
uint64_t const flags, bool const window, bool const pbuffer) const {
EGLConfig config = EGL_NO_CONFIG_KHR;
if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) {
config = findSwapChainConfig(flags, window, pbuffer);
} else {
config = mEGLConfig;
}
return config;
}
// -----------------------------------------------------------------------------------------------
bool PlatformEGL::isSRGBSwapChainSupported() const noexcept {
return ext.egl.KHR_gl_colorspace;
}
bool PlatformEGL::isMSAASwapChainSupported(uint32_t const samples) const noexcept {
bool PlatformEGL::isMSAASwapChainSupported(uint32_t samples) const noexcept {
if (samples <= 1) {
return true;
}
@@ -512,22 +500,126 @@ bool PlatformEGL::isMSAASwapChainSupported(uint32_t const samples) const noexcep
}
Platform::SwapChain* PlatformEGL::createSwapChain(
void* nativeWindow, uint64_t const flags) {
auto* const sc = new(std::nothrow) SwapChainEGL(*this, nativeWindow, flags);
void* nativeWindow, uint64_t flags) {
Config attribs;
// Remove flags for unsupported features.
if (isSRGBSwapChainSupported()) {
if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) {
attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE;
}
if (isProtectedContextSupported()) {
if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT;
}
if (flags & SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES) {
if (!isMSAASwapChainSupported(4)) {
flags &= ~SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES;
}
}
// Retrieve a config for the given flags.
EGLConfig config = EGL_NO_CONFIG_KHR;
if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) {
config = findSwapChainConfig(flags, true, false);
} else {
config = mEGLConfig;
}
EGLSurface sur = EGL_NO_SURFACE;
if (UTILS_LIKELY(config != EGL_NO_CONFIG_KHR)) {
sur = eglCreateWindowSurface(mEGLDisplay, config,
(EGLNativeWindowType)nativeWindow, attribs.data());
if (UTILS_LIKELY(sur != EGL_NO_SURFACE)) {
// this is not fatal
eglSurfaceAttrib(mEGLDisplay, sur, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED);
} else {
logEglError("PlatformEGL::createSwapChain: eglCreateWindowSurface");
}
} else {
// error already logged
}
SwapChainEGL* const sc = new(std::nothrow) SwapChainEGL({
.sur = sur,
.attribs = std::move(attribs),
.nativeWindow = (EGLNativeWindowType)nativeWindow,
.config = config,
.flags = flags
});
return sc;
}
Platform::SwapChain* PlatformEGL::createSwapChain(
uint32_t const width, uint32_t const height, uint64_t const flags) {
auto* const sc = new(std::nothrow) SwapChainEGL(*this, width, height, flags);
uint32_t width, uint32_t height, uint64_t flags) {
Config attribs = {
{ EGL_WIDTH, EGLint(width) },
{ EGL_HEIGHT, EGLint(height) },
};
if (ext.egl.KHR_gl_colorspace) {
if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) {
attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE;
}
if (ext.egl.EXT_protected_content) {
if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT;
}
EGLConfig config = EGL_NO_CONFIG_KHR;
if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) {
config = findSwapChainConfig(flags, true, false);
} else {
config = mEGLConfig;
}
EGLSurface sur = EGL_NO_SURFACE;
if (UTILS_LIKELY(config != EGL_NO_CONFIG_KHR)) {
sur = eglCreatePbufferSurface(mEGLDisplay, config, attribs.data());
if (UTILS_UNLIKELY(sur == EGL_NO_SURFACE)) {
logEglError("PlatformEGL::createSwapChain: eglCreatePbufferSurface");
}
} else {
// error already logged
}
SwapChainEGL* const sc = new(std::nothrow) SwapChainEGL({
.sur = sur,
.attribs = std::move(attribs),
.config = config,
.flags = flags
});
return sc;
}
void PlatformEGL::destroySwapChain(SwapChain* swapChain) noexcept {
if (swapChain) {
SwapChainEGL* const sc = static_cast<SwapChainEGL*>(swapChain);
sc->terminate(*this);
delete sc;
SwapChainEGL const* const sc = static_cast<SwapChainEGL const*>(swapChain);
if (sc->sur != EGL_NO_SURFACE) {
// - if EGL_KHR_surfaceless_context is supported, mEGLDummySurface is EGL_NO_SURFACE.
// - this is actually a bit too aggressive, but it is a rare operation.
egl.makeCurrent(mEGLDummySurface, mEGLDummySurface);
eglDestroySurface(mEGLDisplay, sc->sur);
delete sc;
}
}
}
@@ -543,11 +635,11 @@ OpenGLPlatform::ContextType PlatformEGL::getCurrentContextType() const noexcept
return mCurrentContextType;
}
bool PlatformEGL::makeCurrent(ContextType const type,
bool PlatformEGL::makeCurrent(ContextType type,
SwapChain* drawSwapChain, SwapChain* readSwapChain) {
SwapChainEGL const* const dsc = static_cast<SwapChainEGL const*>(drawSwapChain);
SwapChainEGL const* const rsc = static_cast<SwapChainEGL const*>(readSwapChain);
EGLContext const context = getContextForType(type);
EGLContext context = getContextForType(type);
EGLBoolean const success = egl.makeCurrent(context, dsc->sur, rsc->sur);
return success == EGL_TRUE;
}
@@ -567,7 +659,7 @@ void PlatformEGL::makeCurrent(SwapChain* drawSwapChain,
// we need a protected context
if (UTILS_UNLIKELY(mEGLContextProtected == EGL_NO_CONTEXT)) {
// we don't have one, create it!
EGLConfig const config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig;
EGLConfig config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig;
Config protectedContextAttribs{ mContextAttribs };
protectedContextAttribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
mEGLContextProtected = eglCreateContext(mEGLDisplay, config, mEGLContext,
@@ -639,7 +731,7 @@ Platform::Fence* PlatformEGL::createFence() noexcept {
void PlatformEGL::destroyFence(Fence* fence) noexcept {
#ifdef EGL_KHR_reusable_sync
EGLSyncKHR const sync = fence;
EGLSyncKHR sync = (EGLSyncKHR) fence;
if (sync != EGL_NO_SYNC_KHR) {
eglDestroySyncKHR(mEGLDisplay, sync);
}
@@ -647,11 +739,11 @@ void PlatformEGL::destroyFence(Fence* fence) noexcept {
}
FenceStatus PlatformEGL::waitFence(
Fence* fence, uint64_t const timeout) noexcept {
Fence* fence, uint64_t timeout) noexcept {
#ifdef EGL_KHR_reusable_sync
EGLSyncKHR const sync = fence;
EGLSyncKHR sync = (EGLSyncKHR) fence;
if (sync != EGL_NO_SYNC_KHR) {
EGLint const status = eglClientWaitSyncKHR(mEGLDisplay, sync, 0, EGLTimeKHR(timeout));
EGLint const status = eglClientWaitSyncKHR(mEGLDisplay, sync, 0, (EGLTimeKHR)timeout);
if (status == EGL_CONDITION_SATISFIED_KHR) {
return FenceStatus::CONDITION_SATISFIED;
}
@@ -691,7 +783,7 @@ bool PlatformEGL::setExternalImage(void* externalImage,
return true;
}
Platform::ExternalImageHandle PlatformEGL::createExternalImage(EGLImageKHR const eglImage) noexcept {
Platform::ExternalImageHandle PlatformEGL::createExternalImage(EGLImageKHR eglImage) noexcept {
auto* const p = new(std::nothrow) ExternalImageEGL;
p->eglImage = eglImage;
return ExternalImageHandle{p};
@@ -707,12 +799,13 @@ bool PlatformEGL::setExternalImage(ExternalImageHandleRef externalImage,
void PlatformEGL::initializeGlExtensions() noexcept {
// We're guaranteed to be on an ES platform, since we're using EGL
GLUtils::unordered_string_set glExtensions;
const char* const extensions = (const char*)glGetString(GL_EXTENSIONS);
GLUtils::unordered_string_set const glExtensions = GLUtils::split(extensions);
glExtensions = GLUtils::split(extensions);
ext.gl.OES_EGL_image_external_essl3 = glExtensions.has("GL_OES_EGL_image_external_essl3");
}
EGLContext PlatformEGL::getContextForType(ContextType const type) const noexcept {
EGLContext PlatformEGL::getContextForType(ContextType type) const noexcept {
switch (type) {
case ContextType::NONE:
return EGL_NO_CONTEXT;
@@ -721,10 +814,9 @@ EGLContext PlatformEGL::getContextForType(ContextType const type) const noexcept
case ContextType::PROTECTED:
return mEGLContextProtected;
}
return EGL_NO_CONTEXT;
}
bool PlatformEGL::checkIfMSAASwapChainSupported(uint32_t const samples) const noexcept {
bool PlatformEGL::checkIfMSAASwapChainSupported(uint32_t samples) const noexcept {
// Retrieve the config to see if the given number of samples is supported. The result is cached.
Config configAttribs = {
{ EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT },
@@ -733,7 +825,7 @@ bool PlatformEGL::checkIfMSAASwapChainSupported(uint32_t const samples) const no
{ EGL_BLUE_SIZE, 8 },
{ EGL_DEPTH_SIZE, 24 },
{ EGL_SAMPLE_BUFFERS, 1 },
{ EGL_SAMPLES, EGLint(samples) },
{ EGL_SAMPLES, (EGLint)samples },
};
if (!ext.egl.KHR_no_config_context) {
@@ -757,108 +849,10 @@ bool PlatformEGL::checkIfMSAASwapChainSupported(uint32_t const samples) const no
}
// ---------------------------------------------------------------------------------------------
// PlatformEGL::SwapChainEGL
PlatformEGL::SwapChainEGL::SwapChainEGL(PlatformEGL const& platform, void* nativeWindow, uint64_t flags) {
// Remove flags for unsupported features.
if (platform.isSRGBSwapChainSupported()) {
if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) {
attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE;
}
if (platform.isProtectedContextSupported()) {
if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT;
}
if (flags & SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES) {
if (!platform.isMSAASwapChainSupported(4)) {
flags &= ~SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES;
}
}
// Retrieve a config for the given flags.
config = platform.getSuitableConfigForSwapChain(flags, true, false);
sur = EGL_NO_SURFACE;
if (UTILS_LIKELY(config != EGL_NO_CONFIG_KHR)) {
EGLDisplay const dpy = platform.getEglDisplay();
sur = eglCreateWindowSurface(dpy, config,
EGLNativeWindowType(nativeWindow), attribs.data());
if (UTILS_LIKELY(sur != EGL_NO_SURFACE)) {
// this is not fatal
eglSurfaceAttrib(dpy, sur, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED);
} else {
logEglError("PlatformEGL::createSwapChain: eglCreateWindowSurface");
}
} else {
// error already logged
}
this->nativeWindow = EGLNativeWindowType(nativeWindow);
this->flags = flags;
}
PlatformEGL::SwapChainEGL::SwapChainEGL(PlatformEGL const& platform,
uint32_t const width, uint32_t const height, uint64_t flags) {
attribs = {
{ EGL_WIDTH, EGLint(width) },
{ EGL_HEIGHT, EGLint(height) },
};
if (platform.ext.egl.KHR_gl_colorspace) {
if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) {
attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE;
}
if (platform.ext.egl.EXT_protected_content) {
if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT;
}
config = platform.getSuitableConfigForSwapChain(flags, false, true);
sur = EGL_NO_SURFACE;
if (UTILS_LIKELY(config != EGL_NO_CONFIG_KHR)) {
EGLDisplay const dpy = platform.getEglDisplay();
sur = eglCreatePbufferSurface(dpy, config, attribs.data());
if (UTILS_UNLIKELY(sur == EGL_NO_SURFACE)) {
logEglError("PlatformEGL::createSwapChain: eglCreatePbufferSurface");
}
} else {
// error already logged
}
this->flags = flags;
}
void PlatformEGL::SwapChainEGL::terminate(PlatformEGL& platform) {
if (sur != EGL_NO_SURFACE) {
// - if EGL_KHR_surfaceless_context is supported, mEGLDummySurface is EGL_NO_SURFACE.
// - this is actually a bit too aggressive, but it is a rare operation.
platform.egl.makeCurrent(platform.mEGLDummySurface, platform.mEGLDummySurface);
eglDestroySurface(platform.mEGLDisplay, sur);
sur = EGL_NO_SURFACE;
}
}
// ---------------------------------------------------------------------------------------------
// PlatformEGL::Config
PlatformEGL::Config::Config() = default;
PlatformEGL::Config::Config(std::initializer_list<std::pair<EGLint, EGLint>> const list)
PlatformEGL::Config::Config(std::initializer_list<std::pair<EGLint, EGLint>> list)
: mConfig(list) {
mConfig.emplace_back(EGL_NONE, EGL_NONE);
}
@@ -874,7 +868,7 @@ EGLint& PlatformEGL::Config::operator[](EGLint name) {
}
EGLint PlatformEGL::Config::operator[](EGLint name) const {
auto const pos = std::find_if(mConfig.begin(), mConfig.end(),
auto pos = std::find_if(mConfig.begin(), mConfig.end(),
[name](auto&& v) { return v.first == name; });
assert_invariant(pos != mConfig.end());
return pos->second;
@@ -882,7 +876,7 @@ EGLint PlatformEGL::Config::operator[](EGLint name) const {
void PlatformEGL::Config::erase(EGLint name) noexcept {
if (name != EGL_NONE) {
auto const pos = std::find_if(mConfig.begin(), mConfig.end(),
auto pos = std::find_if(mConfig.begin(), mConfig.end(),
[name](auto&& v) { return v.first == name; });
if (pos != mConfig.end()) {
mConfig.erase(pos);
@@ -891,10 +885,9 @@ void PlatformEGL::Config::erase(EGLint name) noexcept {
}
// ------------------------------------------------------------------------------------------------
// PlatformEGL::EGL
EGLBoolean PlatformEGL::EGL::makeCurrent(EGLContext const context, EGLSurface const drawSurface,
EGLSurface const readSurface) {
EGLBoolean PlatformEGL::EGL::makeCurrent(EGLContext context, EGLSurface drawSurface,
EGLSurface readSurface) {
if (UTILS_UNLIKELY((
mCurrentContext != context ||
drawSurface != mCurrentDrawSurface || readSurface != mCurrentReadSurface))) {
@@ -14,35 +14,28 @@
* limitations under the License.
*/
#include <backend/platforms/PlatformEGLAndroid.h>
#include "opengl/GLUtils.h"
#include <backend/AcquiredImage.h>
#include <backend/DriverEnums.h>
#include <backend/Platform.h>
#include <backend/platforms/OpenGLPlatform.h>
#include <backend/platforms/PlatformEGL.h>
#include <backend/platforms/PlatformEGLAndroid.h>
#include <private/backend/BackendUtilsAndroid.h>
#include <private/backend/VirtualMachineEnv.h>
#include "AndroidNativeWindow.h"
#include "AndroidSwapChainHelper.h"
#include "opengl/GLUtils.h"
#include "ExternalStreamManagerAndroid.h"
#include <android/api-level.h>
#include <android/native_window.h>
#include <android/hardware_buffer.h>
#include <utils/android/PerformanceHintManager.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/android/PerformanceHintManager.h>
#include <utils/compiler.h>
#include <utils/compiler.h>
#include <utils/ostream.h>
#include <utils/ostream.h>
#include <math/mat3.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
@@ -51,15 +44,13 @@
#include <jni.h>
#include <array>
#include <chrono>
#include <limits>
#include <mutex>
#include <new>
#include <string_view>
#include <dlfcn.h>
#include <unistd.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
@@ -85,14 +76,14 @@ extern PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;
extern PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;
extern PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;
UTILS_PRIVATE PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC eglGetNativeClientBufferANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLPRESENTATIONTIMEANDROIDPROC eglPresentationTimeANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLGETCOMPOSITORTIMINGSUPPORTEDANDROIDPROC eglGetCompositorTimingSupportedANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLGETCOMPOSITORTIMINGANDROIDPROC eglGetCompositorTimingANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLGETNEXTFRAMEIDANDROIDPROC eglGetNextFrameIdANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSUPPORTEDANDROIDPROC eglGetFrameTimestampSupportedANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSANDROIDPROC eglGetFrameTimestampsANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC eglGetNativeClientBufferANDROID = {};
UTILS_PRIVATE PFNEGLPRESENTATIONTIMEANDROIDPROC eglPresentationTimeANDROID = {};
UTILS_PRIVATE PFNEGLGETCOMPOSITORTIMINGSUPPORTEDANDROIDPROC eglGetCompositorTimingSupportedANDROID = {};
UTILS_PRIVATE PFNEGLGETCOMPOSITORTIMINGANDROIDPROC eglGetCompositorTimingANDROID = {};
UTILS_PRIVATE PFNEGLGETNEXTFRAMEIDANDROIDPROC eglGetNextFrameIdANDROID = {};
UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSUPPORTEDANDROIDPROC eglGetFrameTimestampSupportedANDROID = {};
UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSANDROIDPROC eglGetFrameTimestampsANDROID = {};
UTILS_PRIVATE PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID = {};
}
using namespace glext;
@@ -111,35 +102,27 @@ PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmFor
// ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::PlatformEGLAndroid() noexcept
: mExternalStreamManager(ExternalStreamManagerAndroid::create()) {
: PlatformEGL(),
mExternalStreamManager(ExternalStreamManagerAndroid::create()),
mInitializeJvmForPerformanceManagerIfNeeded(),
mPerformanceHintManager() {
mOSVersion = android_get_device_api_level();
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";
}
mNativeWindowLib = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW);
if (mNativeWindowLib) {
ANativeWindow_getBuffersDefaultDataSpace =
(int32_t(*)(ANativeWindow*))dlsym(mNativeWindowLib,
"ANativeWindow_getBuffersDefaultDataSpace");
}
}
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().
if (mNativeWindowLib) {
dlclose(mNativeWindowLib);
}
}
void PlatformEGLAndroid::terminate() noexcept {
@@ -147,58 +130,65 @@ void PlatformEGLAndroid::terminate() noexcept {
PlatformEGL::terminate();
}
static constexpr std::string_view kNativeWindowInvalidMsg =
static constexpr const std::string_view kNativeWindowInvalidMsg =
"ANativeWindow is invalid. It probably has been destroyed. EGL surface = ";
bool PlatformEGLAndroid::makeCurrent(ContextType const type,
bool PlatformEGLAndroid::makeCurrent(ContextType type,
SwapChain* drawSwapChain,
SwapChain* readSwapChain) {
// remember the current swapchain for use in beginFrame()
mCurrentDrawSwapChain = static_cast<SwapChainEGLAndroid*>(drawSwapChain);
// fast & safe path
if (UTILS_LIKELY(!mAssertNativeWindowIsValid)) {
return PlatformEGL::makeCurrent(type, drawSwapChain, readSwapChain);
}
SwapChainEGL const* const dsc = static_cast<SwapChainEGL const*>(drawSwapChain);
// anw can be nullptr if we're using a pbuffer surface
if (dsc->nativeWindow) {
auto [err, valid] = NativeWindow::isValid(dsc->nativeWindow);
FILAMENT_CHECK_POSTCONDITION(!err && valid) << kNativeWindowInvalidMsg << dsc->sur;
if (ANativeWindow_getBuffersDefaultDataSpace) {
// anw can be nullptr if we're using a pbuffer surface
if (UTILS_LIKELY(dsc->nativeWindow)) {
// this a proxy of is_valid()
auto result = ANativeWindow_getBuffersDefaultDataSpace(dsc->nativeWindow);
FILAMENT_CHECK_POSTCONDITION(result >= 0) << kNativeWindowInvalidMsg << dsc->sur;
}
} else {
// If we don't have ANativeWindow_getBuffersDefaultDataSpace, we revert to using the
// private query() call.
// Shadow version if the real ANativeWindow, so we can access the query() hook. Query
// has existed since forever, probably Android 1.0.
struct NativeWindow {
// is valid query enum value
enum { IS_VALID = 17 };
uint64_t pad[18];
int (* query)(ANativeWindow const*, int, int*);
} const* pWindow = reinterpret_cast<NativeWindow const*>(dsc->nativeWindow);
int isValid = 0;
if (UTILS_LIKELY(pWindow->query)) { // just in case it's nullptr
int const err = pWindow->query(dsc->nativeWindow, NativeWindow::IS_VALID, &isValid);
if (UTILS_LIKELY(err >= 0)) { // in case the IS_VALID enum is not recognized
// query call succeeded
FILAMENT_CHECK_POSTCONDITION(isValid) << kNativeWindowInvalidMsg << dsc->sur;
}
}
}
return PlatformEGL::makeCurrent(type, drawSwapChain, readSwapChain);
}
void PlatformEGLAndroid::beginFrame(
int64_t const monotonic_clock_ns,
int64_t monotonic_clock_ns,
int64_t refreshIntervalNs,
uint32_t const frameId) noexcept {
// associate the user frameid with the system frame id
setPresentFrameId(mCurrentDrawSwapChain, frameId);
uint32_t frameId) noexcept {
if (mPerformanceHintSession.isValid()) {
if (refreshIntervalNs <= 0) {
// we're not provided with a target time, use the display period, if everything fails,
// assume 16.67ms
// we're not provided with a target time, assume 16.67ms
refreshIntervalNs = 16'666'667;
CompositorTiming compositorTiming{};
bool const hasCompositorTiming =
queryCompositorTiming(mCurrentDrawSwapChain, &compositorTiming);
if (hasCompositorTiming && compositorTiming.compositeInterval > 0) {
refreshIntervalNs = compositorTiming.compositeInterval;
}
}
mStartTimeOfActualWork = clock::time_point(std::chrono::nanoseconds(monotonic_clock_ns));
mPerformanceHintSession.updateTargetWorkDuration(refreshIntervalNs);
}
PlatformEGL::beginFrame(monotonic_clock_ns, refreshIntervalNs, frameId);
}
void PlatformEGLAndroid::preCommit() noexcept {
void backend::PlatformEGLAndroid::preCommit() noexcept {
if (mPerformanceHintSession.isValid()) {
auto const actualWorkDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(
clock::now() - mStartTimeOfActualWork);
@@ -208,7 +198,7 @@ void PlatformEGLAndroid::preCommit() noexcept {
}
Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
const DriverConfig& driverConfig) {
const Platform::DriverConfig& driverConfig) {
// the refresh rate default value doesn't matter, we change it later
int32_t const tid = gettid();
@@ -216,46 +206,29 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
mPerformanceHintManager, &tid, 1, 16'666'667 };
Driver* driver = PlatformEGL::createDriver(sharedContext, driverConfig);
auto const extensions = GLUtils::split(eglQueryString(getEglDisplay(), EGL_EXTENSIONS));
auto extensions = GLUtils::split(eglQueryString(mEGLDisplay, EGL_EXTENSIONS));
ext.egl.ANDROID_presentation_time = extensions.has("EGL_ANDROID_presentation_time");
ext.egl.ANDROID_get_frame_timestamps = extensions.has("EGL_ANDROID_get_frame_timestamps");
ext.egl.ANDROID_native_fence_sync = extensions.has("EGL_ANDROID_native_fence_sync");
eglGetNativeClientBufferANDROID = (PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC) eglGetProcAddress(
"eglGetNativeClientBufferANDROID");
eglGetNativeClientBufferANDROID =
PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC(eglGetProcAddress(
"eglGetNativeClientBufferANDROID"));
if (ext.egl.ANDROID_presentation_time) {
eglGetNativeClientBufferANDROID =
PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC(eglGetProcAddress(
"eglGetNativeClientBufferANDROID"));
if (extensions.has("EGL_ANDROID_presentation_time")) {
eglPresentationTimeANDROID = (PFNEGLPRESENTATIONTIMEANDROIDPROC)eglGetProcAddress(
"eglPresentationTimeANDROID");
}
if (ext.egl.ANDROID_get_frame_timestamps) {
eglGetCompositorTimingSupportedANDROID =
PFNEGLGETCOMPOSITORTIMINGSUPPORTEDANDROIDPROC(eglGetProcAddress(
"eglGetCompositorTimingSupportedANDROID"));
eglGetCompositorTimingANDROID =
PFNEGLGETCOMPOSITORTIMINGANDROIDPROC(eglGetProcAddress(
"eglGetCompositorTimingANDROID"));
eglGetNextFrameIdANDROID =
PFNEGLGETNEXTFRAMEIDANDROIDPROC(eglGetProcAddress(
"eglGetNextFrameIdANDROID"));
eglGetFrameTimestampSupportedANDROID =
PFNEGLGETFRAMETIMESTAMPSUPPORTEDANDROIDPROC(eglGetProcAddress(
"eglGetFrameTimestampSupportedANDROID"));
eglGetFrameTimestampsANDROID =
PFNEGLGETFRAMETIMESTAMPSANDROIDPROC(eglGetProcAddress(
"eglGetFrameTimestampsANDROID"));
}
if (ext.egl.ANDROID_native_fence_sync) {
if (extensions.has("EGL_ANDROID_get_frame_timestamps")) {
eglGetCompositorTimingSupportedANDROID = (PFNEGLGETCOMPOSITORTIMINGSUPPORTEDANDROIDPROC)eglGetProcAddress(
"eglGetCompositorTimingSupportedANDROID");
eglGetCompositorTimingANDROID = (PFNEGLGETCOMPOSITORTIMINGANDROIDPROC)eglGetProcAddress(
"eglGetCompositorTimingANDROID");
eglGetNextFrameIdANDROID = (PFNEGLGETNEXTFRAMEIDANDROIDPROC)eglGetProcAddress(
"eglGetNextFrameIdANDROID");
eglGetFrameTimestampSupportedANDROID = (PFNEGLGETFRAMETIMESTAMPSUPPORTEDANDROIDPROC)eglGetProcAddress(
"eglGetFrameTimestampSupportedANDROID");
eglGetFrameTimestampsANDROID = (PFNEGLGETFRAMETIMESTAMPSANDROIDPROC)eglGetProcAddress(
"eglGetFrameTimestampsANDROID");
eglDupNativeFenceFDANDROID =
PFNEGLDUPNATIVEFENCEFDANDROIDPROC(eglGetProcAddress(
"eglDupNativeFenceFDANDROID"));
} else {
LOG(ERROR) << "EGL_ANDROID_native_fence_sync not supported!";
(PFNEGLDUPNATIVEFENCEFDANDROIDPROC) eglGetProcAddress("eglDupNativeFenceFDANDROID");
}
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
@@ -271,152 +244,11 @@ PlatformEGLAndroid::ExternalImageEGLAndroid::~ExternalImageEGLAndroid() {
}
}
bool PlatformEGLAndroid::isCompositorTimingSupported() const noexcept {
return true;
}
bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
CompositorTiming* outCompositorTiming) const noexcept {
if (!swapchain) {
return false;
}
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
EGLSurface sur = static_cast<SwapChainEGL const *>(swapchain)->sur;
if (sur == EGL_NO_SURFACE) {
return false;
}
std::array<EGLnsecsANDROID, 3> values;
constexpr std::array<EGLint, 3> names{
EGL_COMPOSITE_DEADLINE_ANDROID,
EGL_COMPOSITE_INTERVAL_ANDROID,
EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID
};
EGLBoolean const success = eglGetCompositorTimingANDROID(getEglDisplay(), sur,
names.size(), names.data(), values.data());
if (!success) {
return false;
}
outCompositorTiming->compositeDeadline = values[0];
outCompositorTiming->compositeInterval = values[1];
outCompositorTiming->compositeToPresentLatency = values[2];
return true;
}
// fallback to private APIs
auto const anw = static_cast<SwapChainEGL const *>(swapchain)->nativeWindow;
int const status = NativeWindow::getCompositorTiming(anw,
&outCompositorTiming->compositeDeadline,
&outCompositorTiming->compositeInterval,
&outCompositorTiming->compositeToPresentLatency);
if (status == 0) {
return true;
}
return PlatformEGL::queryCompositorTiming(swapchain, outCompositorTiming);
}
bool PlatformEGLAndroid::setPresentFrameId(SwapChain const* swapchain,
uint64_t const frameId) noexcept {
if (swapchain) {
SwapChainEGLAndroid const* const sc = static_cast<SwapChainEGLAndroid const*>(swapchain);
return sc->setPresentFrameId(frameId);
}
return PlatformEGL::setPresentFrameId(swapchain, frameId);
}
bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64_t frameId,
FrameTimestamps* outFrameTimestamps) const noexcept {
if (!swapchain) {
return false;
}
SwapChainEGLAndroid const* const sc = static_cast<SwapChainEGLAndroid const*>(swapchain);
uint64_t const hwFrameId = sc->getFrameId(frameId);
if (hwFrameId == std::numeric_limits<uint64_t>::max()) {
return false;
}
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
EGLSurface const sur = sc->sur;
if (sur == EGL_NO_SURFACE) {
return false;
}
std::array<EGLnsecsANDROID, 9> values;
constexpr std::array<EGLint, 9> names{
EGL_REQUESTED_PRESENT_TIME_ANDROID, // requestedPresentTime
EGL_RENDERING_COMPLETE_TIME_ANDROID, // acquireTime
EGL_COMPOSITION_LATCH_TIME_ANDROID, // latchTime
EGL_FIRST_COMPOSITION_START_TIME_ANDROID, // firstRefreshStartTime
EGL_LAST_COMPOSITION_START_TIME_ANDROID, // lastRefreshStartTime
EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID, // gpuCompositionDoneTime
EGL_DISPLAY_PRESENT_TIME_ANDROID, // displayPresentTime
EGL_DEQUEUE_READY_TIME_ANDROID, // dequeueReadyTime
EGL_READS_DONE_TIME_ANDROID, // releaseTime
};
EGLBoolean const success = eglGetFrameTimestampsANDROID(getEglDisplay(), sur, hwFrameId,
names.size(), names.data(), values.data());
if (!success) {
return false;
}
outFrameTimestamps->requestedPresentTime = values[0];
outFrameTimestamps->acquireTime = values[1];
outFrameTimestamps->latchTime = values[2];
outFrameTimestamps->firstRefreshStartTime = values[3];
outFrameTimestamps->lastRefreshStartTime = values[4];
outFrameTimestamps->gpuCompositionDoneTime = values[5];
outFrameTimestamps->displayPresentTime = values[6];
outFrameTimestamps->dequeueReadyTime = values[7];
outFrameTimestamps->releaseTime = values[8];
return true;
}
// fallback to private APIs
auto const anw = sc->nativeWindow;
int const status = NativeWindow::getFrameTimestamps(anw, hwFrameId,
&outFrameTimestamps->requestedPresentTime,
&outFrameTimestamps->acquireTime,
&outFrameTimestamps->latchTime,
&outFrameTimestamps->firstRefreshStartTime,
&outFrameTimestamps->lastRefreshStartTime,
&outFrameTimestamps->gpuCompositionDoneTime,
&outFrameTimestamps->displayPresentTime,
&outFrameTimestamps->dequeueReadyTime,
&outFrameTimestamps->releaseTime);
if (status == 0) {
return true;
}
return PlatformEGL::queryFrameTimestamps(swapchain, frameId, outFrameTimestamps);
}
Platform::SwapChain* PlatformEGLAndroid::createSwapChain(void* nativeWindow, uint64_t const flags) {
auto* const sc = new(std::nothrow) SwapChainEGLAndroid(*this, nativeWindow, flags);
return sc;
}
Platform::SwapChain* PlatformEGLAndroid::createSwapChain(
uint32_t const width, uint32_t const height, uint64_t const flags) {
auto* const sc = new(std::nothrow) SwapChainEGLAndroid(*this, width, height, flags);
return sc;
}
void PlatformEGLAndroid::destroySwapChain(SwapChain* swapChain) noexcept {
if (swapChain) {
SwapChainEGLAndroid* const sc = static_cast<SwapChainEGLAndroid*>(swapChain);
sc->terminate(*this);
delete sc;
}
}
Platform::ExternalImageHandle PlatformEGLAndroid::createExternalImage(
AHardwareBuffer const* buffer, bool const sRGB) noexcept {
Platform::ExternalImageHandle PlatformEGLAndroid::createExternalImage(AHardwareBuffer const* buffer,
bool sRGB) noexcept {
if (__builtin_available(android 26, *)) {
auto* const p = new (std::nothrow) ExternalImageEGLAndroid;
auto const hardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
auto hardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
AHardwareBuffer_acquire(hardwareBuffer);
p->aHardwareBuffer = hardwareBuffer;
p->sRGB = sRGB;
@@ -424,13 +256,13 @@ Platform::ExternalImageHandle PlatformEGLAndroid::createExternalImage(
AHardwareBuffer_describe(hardwareBuffer, &hardwareBufferDescription);
p->height = hardwareBufferDescription.height;
p->width = hardwareBufferDescription.width;
auto const textureFormat = mapToFilamentFormat(hardwareBufferDescription.format, sRGB);
auto textureFormat = mapToFilamentFormat(hardwareBufferDescription.format, sRGB);
p->format = textureFormat;
p->usage = mapToFilamentUsage(hardwareBufferDescription.usage, textureFormat);
return ExternalImageHandle{ p };
}
return ExternalImageHandle{};
return Platform::ExternalImageHandle{};
}
PlatformEGLAndroid::ExternalImageDescAndroid PlatformEGLAndroid::getExternalImageDesc(
@@ -453,7 +285,7 @@ bool PlatformEGLAndroid::setExternalImage(ExternalImageHandleRef externalImage,
auto const* const eglExternalImage =
static_cast<ExternalImageEGLAndroid const*>(externalImage.get());
if (eglExternalImage->aHardwareBuffer) {
return setImage(eglExternalImage, texture);
return PlatformEGLAndroid::setImage(eglExternalImage, texture);
}
// not a AHardwareBuffer, fallback to the inherited version
return PlatformEGL::setExternalImage(externalImage, texture);
@@ -466,7 +298,7 @@ OpenGLPlatform::ExternalTexture* PlatformEGLAndroid::createExternalImageTexture(
}
void PlatformEGLAndroid::destroyExternalImageTexture(ExternalTexture* texture) noexcept {
ExternalTextureAndroid const* outTexture = static_cast<ExternalTextureAndroid*>(texture);
ExternalTextureAndroid* outTexture = static_cast<ExternalTextureAndroid*>(texture);
glDeleteTextures(1, &texture->id);
if (outTexture->eglImage != EGL_NO_IMAGE) {
@@ -477,10 +309,10 @@ void PlatformEGLAndroid::destroyExternalImageTexture(ExternalTexture* texture) n
bool PlatformEGLAndroid::setImage(ExternalImageEGLAndroid const* eglExternalImage,
UTILS_UNUSED_IN_RELEASE ExternalTexture* texture) noexcept {
AHardwareBuffer const* hardwareBuffer = eglExternalImage->aHardwareBuffer;
AHardwareBuffer* hardwareBuffer = eglExternalImage->aHardwareBuffer;
// Get the EGL client buffer from AHardwareBuffer
EGLClientBuffer const clientBuffer = eglGetNativeClientBufferANDROID(hardwareBuffer);
EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(hardwareBuffer);
EGLint imageAttrs[] = {
EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
EGL_NONE, EGL_NONE, // Reserve space
@@ -498,7 +330,7 @@ bool PlatformEGLAndroid::setImage(ExternalImageEGLAndroid const* eglExternalImag
imageAttrs[attrIndex++] = EGL_TRUE;
}
// Create an EGLImage from the client buffer
EGLImageKHR const eglImage = eglCreateImageKHR(eglGetCurrentDisplay(), EGL_NO_CONTEXT,
EGLImageKHR eglImage = eglCreateImageKHR(eglGetCurrentDisplay(), EGL_NO_CONTEXT,
EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs);
if (eglImage == EGL_NO_IMAGE_KHR) {
// Handle error
@@ -546,12 +378,12 @@ bool PlatformEGLAndroid::setImage(ExternalImageEGLAndroid const* eglExternalImag
return true;
}
void PlatformEGLAndroid::setPresentationTime(int64_t const presentationTimeInNanosecond) noexcept {
EGLSurface const currentDrawSurface = eglGetCurrentSurface(EGL_DRAW);
void PlatformEGLAndroid::setPresentationTime(int64_t presentationTimeInNanosecond) noexcept {
EGLSurface currentDrawSurface = eglGetCurrentSurface(EGL_DRAW);
if (currentDrawSurface != EGL_NO_SURFACE) {
if (UTILS_UNLIKELY(ext.egl.ANDROID_presentation_time)) {
if (eglPresentationTimeANDROID) {
eglPresentationTimeANDROID(
getEglDisplay(),
mEGLDisplay,
currentDrawSurface,
presentationTimeInNanosecond);
}
@@ -562,38 +394,19 @@ Platform::Stream* PlatformEGLAndroid::createStream(void* nativeStream) noexcept
return mExternalStreamManager.acquire(static_cast<jobject>(nativeStream));
}
void PlatformEGLAndroid::destroyStream(Stream* stream) noexcept {
void PlatformEGLAndroid::destroyStream(Platform::Stream* stream) noexcept {
mExternalStreamManager.release(stream);
}
Platform::Sync* PlatformEGLAndroid::createSync() noexcept {
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 };
auto sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
return new SyncEGLAndroid{.sync = sync};
}
bool PlatformEGLAndroid::convertSyncToFd(Sync* sync, int* fd) noexcept {
bool PlatformEGLAndroid::convertSyncToFd(Platform::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);
SyncEGLAndroid& eglSync = static_cast<SyncEGLAndroid&>(*sync);
*fd = eglDupNativeFenceFDANDROID(mEGLDisplay, 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.
if (*fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {
@@ -603,18 +416,14 @@ bool PlatformEGLAndroid::convertSyncToFd(Sync* sync, int* fd) noexcept {
return true;
}
void PlatformEGLAndroid::destroySync(Sync* sync) noexcept {
void PlatformEGLAndroid::destroySync(Platform::Sync* sync) noexcept {
assert_invariant(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);
}
}
SyncEGLAndroid& eglSync = static_cast<SyncEGLAndroid&>(*sync);
eglDestroySyncKHR(mEGLDisplay, eglSync.sync);
delete sync;
}
void PlatformEGLAndroid::attach(Stream* stream, intptr_t const tname) noexcept {
void PlatformEGLAndroid::attach(Stream* stream, intptr_t tname) noexcept {
mExternalStreamManager.attach(stream, tname);
}
@@ -634,30 +443,29 @@ int PlatformEGLAndroid::getOSVersion() const noexcept {
return mOSVersion;
}
AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage const source) noexcept {
AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage source) noexcept {
// Convert the AHardwareBuffer to EGLImage.
AHardwareBuffer const* const pHardwareBuffer = (const AHardwareBuffer*)source.image;
EGLClientBuffer const clientBuffer = eglGetNativeClientBufferANDROID(pHardwareBuffer);
EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(pHardwareBuffer);
if (!clientBuffer) {
LOG(ERROR) << "Unable to get EGLClientBuffer from AHardwareBuffer.";
return {};
}
Config attributes;
PlatformEGL::Config attributes;
if (__builtin_available(android 26, *)) {
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(pHardwareBuffer, &desc);
bool const isProtectedContent =
desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT;
desc.usage & AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT;
if (isProtectedContent) {
attributes[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
}
}
EGLDisplay const dpy = getEglDisplay();
EGLImageKHR const eglImage = eglCreateImageKHR(dpy,
EGLImageKHR eglImage = eglCreateImageKHR(mEGLDisplay,
EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attributes.data());
if (eglImage == EGL_NO_IMAGE_KHR) {
LOG(ERROR) << "eglCreateImageKHR returned no image.";
@@ -666,15 +474,15 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage const sou
// Destroy the EGLImage before invoking the user's callback.
struct Closure {
Closure(AcquiredImage const& acquiredImage, EGLDisplay const display)
Closure(AcquiredImage const& acquiredImage, EGLDisplay display)
: acquiredImage(acquiredImage), display(display) {}
AcquiredImage acquiredImage;
EGLDisplay display;
};
Closure* const closure = new(std::nothrow) Closure(source, dpy);
Closure* closure = new(std::nothrow) Closure(source, mEGLDisplay);
auto patchedCallback = [](void* image, void* userdata) {
Closure const* const closure = static_cast<Closure*>(userdata);
if (eglDestroyImageKHR(closure->display, EGLImageKHR(image)) == EGL_FALSE) {
Closure* closure = (Closure*)userdata;
if (eglDestroyImageKHR(closure->display, (EGLImageKHR) image) == EGL_FALSE) {
LOG(ERROR) << "eglDestroyImageKHR failed.";
}
closure->acquiredImage.callback(closure->acquiredImage.image, closure->acquiredImage.userData);
@@ -684,51 +492,6 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage const sou
return { eglImage, patchedCallback, closure, source.handler };
}
// ---------------------------------------------------------------------------------------------
// PlatformEGLAndroid::SwapChainEGLAndroid
PlatformEGLAndroid::SwapChainEGLAndroid::SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
void* nativeWindow, uint64_t const flags)
: SwapChainEGL(platform, nativeWindow, flags) {
if (nativeWindow && platform.mHasProducerThrottlingControl) {
// Disable Producer Throttling when supported. This allows eglSwapBuffers() to not stall
int32_t const result = platform.ANativeWindow_setProducerThrottlingEnabled(
EGLNativeWindowType(nativeWindow), false);
if (UTILS_UNLIKELY(result < 0)) {
LOG(WARNING) << "ANativeWindow_setProducerThrottlingEnabled(false) failed: " << result;
}
}
if (UTILS_LIKELY(platform.ext.egl.ANDROID_get_frame_timestamps)) {
if (sur != EGL_NO_SURFACE) {
// we ignore the result, it doesn't matter much if it fails
eglSurfaceAttrib(platform.getEglDisplay(), sur, EGL_TIMESTAMPS_ANDROID, EGL_TRUE);
}
} else {
NativeWindow::enableFrameTimestamps(EGLNativeWindowType(nativeWindow), true);
}
}
PlatformEGLAndroid::SwapChainEGLAndroid::SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
uint32_t const width, uint32_t const height, uint64_t const flags)
: SwapChainEGL(platform, width, height, flags) {
}
void PlatformEGLAndroid::SwapChainEGLAndroid::terminate(PlatformEGLAndroid& platform) {
SwapChainEGL::terminate(platform);
}
bool PlatformEGLAndroid::SwapChainEGLAndroid::setPresentFrameId(uint64_t frameId) const noexcept {
if (!nativeWindow) {
// nativeWindow is null in the headless case
return false;
}
return mImpl.setPresentFrameId(nativeWindow, frameId);
}
uint64_t PlatformEGLAndroid::SwapChainEGLAndroid::getFrameId(uint64_t const frameId) const noexcept {
return mImpl.getFrameId(frameId);
}
} // namespace filament::backend
// ---------------------------------------------------------------------------------------------
@@ -18,7 +18,6 @@
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/ThreadUtils.h>
#include <X11/Xlib.h>
#include <GL/glx.h>
@@ -26,8 +25,6 @@
#include <dlfcn.h>
#include <mutex>
#define LIBRARY_GLX "libGL.so.1"
#define LIBRARY_X11 "libX11.so.6"
@@ -233,70 +230,11 @@ Driver* PlatformGLX::createDriver(void* sharedGLContext,
void PlatformGLX::terminate() noexcept {
g_glx.setCurrentContext(mGLXDisplay, None, None, nullptr);
g_glx.destroyPbuffer(mGLXDisplay, mDummySurface);
for (auto it : mAdditionalContexts) {
g_glx.destroyContext(mGLXDisplay, it.second);
}
g_glx.destroyContext(mGLXDisplay, mGLXContext);
g_x11.closeDisplay(mGLXDisplay);
bluegl::unbind();
}
bool PlatformGLX::isExtraContextSupported() const noexcept {
return true;
}
void PlatformGLX::createContext(bool shared) {
std::thread::id currentThreadId = utils::ThreadUtils::getThreadId();
{
std::shared_lock<std::shared_mutex> lock(mAdditionalContextsLock);
auto it = mAdditionalContexts.find(currentThreadId);
if (it != mAdditionalContexts.end()) {
LOG(WARNING) << "Shared context is already created";
return;
}
}
int contextAttribs[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, 4,
GLX_CONTEXT_MINOR_VERSION_ARB, 1,
GL_NONE
};
GLXContext context = g_glx.createContext(mGLXDisplay, mGLXConfig,
mGLXContext, True, contextAttribs);
if (!context) {
LOG(ERROR) << "Failed to create shared context";
return;
}
g_glx.setCurrentContext(mGLXDisplay,
(GLXDrawable)None, (GLXDrawable)None, context);
std::lock_guard<std::shared_mutex> lock(mAdditionalContextsLock);
mAdditionalContexts[currentThreadId] = context;
}
void PlatformGLX::releaseContext() noexcept {
g_glx.setCurrentContext(mGLXDisplay,
(GLXDrawable)None, (GLXDrawable)None, nullptr);
std::thread::id currentThreadId = utils::ThreadUtils::getThreadId();
GLXContext context;
{
std::lock_guard<std::shared_mutex> lock(mAdditionalContextsLock);
auto it = mAdditionalContexts.find(currentThreadId);
if (it == mAdditionalContexts.end()) {
LOG(WARNING) << "Attempted to destroy non-existing shared context";
return;
}
context = it->second;
mAdditionalContexts.erase(it);
}
g_glx.destroyContext(mGLXDisplay, context);
}
Platform::SwapChain* PlatformGLX::createSwapChain(void* nativeWindow, uint64_t flags) noexcept {
return (SwapChain*)nativeWindow;
}
@@ -32,20 +32,19 @@ namespace filament::backend {
FenceStatus VulkanCmdFence::wait(VkDevice device, uint64_t const timeout,
std::chrono::steady_clock::time_point const until) {
// this lock MUST be held for READ when calling vkWaitForFences()
std::shared_lock rl(mLock);
std::shared_lock l(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(rl, until, [this] {
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 || mCanceled;
return mStatus != VK_INCOMPLETE;
});
if (!success) {
// !success indicates a timeout or cancel
return mCanceled ? FenceStatus::ERROR : FenceStatus::TIMEOUT_EXPIRED;
// !success indicates a timeout
return FenceStatus::TIMEOUT_EXPIRED;
}
}
@@ -54,11 +53,6 @@ FenceStatus VulkanCmdFence::wait(VkDevice device, uint64_t const timeout,
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
// to VK_NOT_READY).
// Now really wait for the fence while holding the shared_lock, this allows several
@@ -66,18 +60,13 @@ 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 const status = vkWaitForFences(device, 1, &mFence, VK_TRUE, timeout);
if (status == VK_TIMEOUT) {
return FenceStatus::TIMEOUT_EXPIRED;
}
if (status == VK_SUCCESS) {
rl.unlock();
std::lock_guard const wl(mLock);
mStatus = status;
mStatus = vkWaitForFences(device, 1, &mFence, VK_TRUE, timeout);
if (mStatus == VK_SUCCESS) {
return FenceStatus::CONDITION_SATISFIED;
}
if (mStatus == VK_TIMEOUT) {
return FenceStatus::TIMEOUT_EXPIRED;
}
return FenceStatus::ERROR; // not supported
}
@@ -56,16 +56,9 @@ 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.
@@ -77,43 +70,28 @@ struct VulkanFence : public HwFence, fvkmemory::ThreadSafeResource {
VulkanFence() {}
void setFence(std::shared_ptr<VulkanCmdFence> fence) {
std::lock_guard const lock(mState->lock);
std::lock_guard lock(mState->lock);
mState->sharedFence = std::move(fence);
mState->cond.notify_all();
}
std::shared_ptr<VulkanCmdFence>& getSharedFence() {
std::lock_guard const lock(mState->lock);
std::lock_guard lock(mState->lock);
return mState->sharedFence;
}
std::pair<std::shared_ptr<VulkanCmdFence>, bool>
wait(std::chrono::steady_clock::time_point const until) {
std::shared_ptr<VulkanCmdFence> 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->canceled;
});
state->cond.wait_until(lock, until, [&state] { return bool(state->sharedFence); });
// here mSharedFence will be null if we timed out
return { state->sharedFence, state->canceled };
return state->sharedFence;
}
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>() };
+5 -20
View File
@@ -1156,7 +1156,6 @@ void VulkanDriver::updateStreams(CommandStream* driver) {
void VulkanDriver::destroyFence(Handle<HwFence> fh) {
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
fence->cancel();
fence.dec();
}
@@ -1165,10 +1164,6 @@ FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> const fh) {
}
FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout) {
if (!fh) {
return FenceStatus::ERROR;
}
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
// we have to take into account that the STL's wait_for() actually works with
@@ -1176,18 +1171,13 @@ FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout
using namespace std::chrono;
auto const now = steady_clock::now();
steady_clock::time_point until = steady_clock::time_point::max();
using TimeoutType = decltype(timeout);
constexpr TimeoutType maxTimeout = std::numeric_limits<TimeoutType>::max();
constexpr nanoseconds maxNano = nanoseconds::max();
if (timeout < maxNano.count() && timeout < maxTimeout && // Need to account for overflow
now <= steady_clock::time_point::max() - nanoseconds(timeout)) {
if (now <= steady_clock::time_point::max() - nanoseconds(timeout)) {
until = now + nanoseconds(timeout);
}
auto const [cmdfence, canceled] = fence->wait(until);
if (!cmdfence || canceled) {
return canceled ? FenceStatus::ERROR : FenceStatus::TIMEOUT_EXPIRED;
std::shared_ptr const cmdfence = fence->wait(until);
if (!cmdfence) {
return FenceStatus::TIMEOUT_EXPIRED;
}
// now we are holding a reference to our VulkanCmdFence, so we know it can't
@@ -1395,8 +1385,7 @@ uint8_t VulkanDriver::getMaxDrawBuffers() {
}
size_t VulkanDriver::getMaxUniformBufferSize() {
return std::max(mContext.getPhysicalDeviceLimits().maxUniformBufferRange,
static_cast<uint32_t>(mContext.getPhysicalDeviceLimits().nonCoherentAtomSize));
return mContext.getPhysicalDeviceLimits().maxUniformBufferRange;
}
size_t VulkanDriver::getMaxTextureSize(SamplerType type) {
@@ -1416,10 +1405,6 @@ size_t VulkanDriver::getMaxArrayTextureLayers() {
return mContext.getPhysicalDeviceLimits().maxImageArrayLayers;
}
size_t VulkanDriver::getUniformBufferOffsetAlignment() {
return mContext.getPhysicalDeviceLimits().minUniformBufferOffsetAlignment;
}
void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
Handle<HwBufferObject> boh) {
auto vb = resource_ptr<VulkanVertexBuffer>::cast(&mResourceManager, vbh);
@@ -267,8 +267,8 @@ void VulkanPipelineCache::bindVertexArray(VkVertexInputAttributeDescription cons
mPipelineRequirements.vertexAttributes[i] = attribDesc[i];
mPipelineRequirements.vertexBuffers[i] = bufferDesc[i];
} else {
mPipelineRequirements.vertexAttributes[i] = VertexInputAttributeDescription{};
mPipelineRequirements.vertexBuffers[i] = VertexInputBindingDescription{};
mPipelineRequirements.vertexAttributes[i] = {};
mPipelineRequirements.vertexBuffers[i] = {};
}
}
}
@@ -149,7 +149,7 @@ VulkanStage* VulkanStagePool::allocateNewStage(uint32_t capacity) {
return new VulkanStage(memory, buffer, capacity, pMapping);
}
void VulkanStagePool::destroyStage(VulkanStage const* stage) {
void VulkanStagePool::destroyStage(VulkanStage const*&& stage) {
assert(stage->isSafeToReset()); // Ensure all segments have been reset already.
vmaUnmapMemory(mAllocator, stage->memory());
vmaDestroyBuffer(mAllocator, stage->buffer(), stage->memory());
@@ -196,7 +196,7 @@ fvkmemory::resource_ptr<VulkanStageImage::Resource> VulkanStagePool::acquireImag
VkImage image;
VmaAllocation memory;
UTILS_UNUSED const VkResult result = vmaCreateImage(mAllocator, &imageInfo, &allocInfo,
const UTILS_UNUSED VkResult result = vmaCreateImage(mAllocator, &imageInfo, &allocInfo,
&image, &memory, nullptr);
assert_invariant(result == VK_SUCCESS);
@@ -239,7 +239,7 @@ void VulkanStagePool::gc() noexcept {
FVK_LOGD << "Destroying a staging buffer with hndl " << pair.second->buffer()
<< utils::io::endl;
#endif
destroyStage(pair.second);
destroyStage(std::move(pair.second));
continue;
}
@@ -276,7 +276,7 @@ void VulkanStagePool::gc() noexcept {
void VulkanStagePool::terminate() noexcept {
for (auto& pair : mStages) {
destroyStage(pair.second);
destroyStage(std::move(pair.second));
}
mStages.clear();
@@ -245,7 +245,7 @@ private:
// unmapping memory, freeing the allocation, and deleting the VulkanStage
// object. Note: takes an r-value because after this call, `stage` won't
// exist.
void destroyStage(VulkanStage const* stage);
void destroyStage(VulkanStage const*&& stage);
// Use an ordered multimap for quick (capacity => stage) lookups using lower_bound().
std::multimap<uint32_t, VulkanStage*> mStages;
@@ -134,9 +134,9 @@ void VulkanSwapChain::present(DriverBase& driver) {
mAcquired = false;
mIsFirstRenderPass = true;
if (mFrameScheduled.callback) {
driver.scheduleCallback(mFrameScheduled.handler,
[callback = mFrameScheduled.callback]() {
if (frameScheduled.callback) {
driver.scheduleCallback(frameScheduled.handler,
[callback = frameScheduled.callback]() {
PresentCallable noop = PresentCallable(PresentCallable::noopPresent, nullptr);
callback->operator()(noop);
});
@@ -84,12 +84,12 @@ struct VulkanSwapChain : public HwSwapChain, fvkmemory::Resource {
inline void setFrameScheduledCallback(CallbackHandler* handler,
FrameScheduledCallback&& callback) noexcept {
if (!callback) {
mFrameScheduled.handler = nullptr;
mFrameScheduled.callback.reset();
frameScheduled.handler = nullptr;
frameScheduled.callback.reset();
return;
}
mFrameScheduled.handler = handler;
mFrameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
frameScheduled.handler = handler;
frameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
}
private:
@@ -110,8 +110,8 @@ private:
// These fields store a callback to notify the client that Filament is commiting a frame.
struct {
CallbackHandler* handler = nullptr;
std::shared_ptr<FrameScheduledCallback> callback;
} mFrameScheduled;
std::shared_ptr<FrameScheduledCallback> callback = nullptr;
} frameScheduled;
// We create VulkanTextures based on VkImages. VulkanTexture has facilities for doing layout
// transitions, which are useful here.
@@ -29,6 +29,16 @@
#include <utils/Panic.h>
#include <utils/PrivateImplementation-impl.h>
#define SWAPCHAIN_RET_FUNC(func, handle, ...) \
if (mImpl->mSurfaceSwapChains.find(handle) != mImpl->mSurfaceSwapChains.end()) { \
return static_cast<VulkanPlatformSurfaceSwapChain*>(handle)->func(__VA_ARGS__); \
} else if (mImpl->mHeadlessSwapChains.find(handle) != mImpl->mHeadlessSwapChains.end()) { \
return static_cast<VulkanPlatformHeadlessSwapChain*>(handle)->func(__VA_ARGS__); \
} else { \
PANIC_PRECONDITION("Bad handle for swapchain"); \
return {}; \
}
using namespace utils;
using namespace bluevk;
@@ -675,11 +685,26 @@ struct VulkanPlatformPrivate {
VkQueue mProtectedGraphicsQueue = VK_NULL_HANDLE;
VulkanContext mContext = {};
// We use a map to both map a handle (i.e. SwapChainPtr) to the concrete type and also to
// store the actual swapchain struct, which is either backed-by-surface or headless.
std::unordered_set<SwapChainPtr> mSurfaceSwapChains;
std::unordered_set<SwapChainPtr> mHeadlessSwapChains;
bool mSharedContext = false;
bool mForceXCBSwapchain = false;
};
void VulkanPlatform::terminate() {
for (auto swapchain: mImpl->mHeadlessSwapChains) {
delete static_cast<VulkanPlatformHeadlessSwapChain*>(swapchain);
}
mImpl->mHeadlessSwapChains.clear();
for (auto swapchain: mImpl->mSurfaceSwapChains) {
delete static_cast<VulkanPlatformSurfaceSwapChain*>(swapchain);
}
mImpl->mSurfaceSwapChains.clear();
if (!mImpl->mSharedContext) {
vkDestroyDevice(mImpl->mDevice, VKALLOC);
vkDestroyInstance(mImpl->mInstance, VKALLOC);
@@ -915,31 +940,38 @@ VulkanPlatform::VulkanPlatform() = default;
VulkanPlatform::~VulkanPlatform() = default;
VulkanPlatform::SwapChainBundle VulkanPlatform::getSwapChainBundle(SwapChainPtr handle) {
return static_cast<VulkanPlatformSwapChainBase*>(handle)->getSwapChainBundle();
SWAPCHAIN_RET_FUNC(getSwapChainBundle, handle, )
}
VkResult VulkanPlatform::acquire(SwapChainPtr handle, ImageSyncData* outImageSyncData) {
return static_cast<VulkanPlatformSwapChainBase*>(handle)->acquire(outImageSyncData);
SWAPCHAIN_RET_FUNC(acquire, handle, outImageSyncData)
}
VkResult VulkanPlatform::present(SwapChainPtr handle, uint32_t index, VkSemaphore finishedDrawing) {
return static_cast<VulkanPlatformSwapChainBase*>(handle)->present(index, finishedDrawing);
VkResult VulkanPlatform::present(SwapChainPtr handle, uint32_t index,
VkSemaphore finishedDrawing) {
SWAPCHAIN_RET_FUNC(present, handle, index, finishedDrawing)
}
bool VulkanPlatform::hasResized(SwapChainPtr handle) {
return static_cast<VulkanPlatformSwapChainBase*>(handle)->hasResized();
SWAPCHAIN_RET_FUNC(hasResized, handle, )
}
bool VulkanPlatform::isProtected(SwapChainPtr handle) {
return static_cast<VulkanPlatformSwapChainBase*>(handle)->isProtected();
SWAPCHAIN_RET_FUNC(isProtected, handle, )
}
VkResult VulkanPlatform::recreate(SwapChainPtr handle) {
return static_cast<VulkanPlatformSwapChainBase*>(handle)->recreate();
SWAPCHAIN_RET_FUNC(recreate, handle, )
}
void VulkanPlatform::destroy(SwapChainPtr handle) {
delete static_cast<VulkanPlatformSwapChainBase*>(handle);
if (mImpl->mSurfaceSwapChains.erase(handle)) {
delete static_cast<VulkanPlatformSurfaceSwapChain*>(handle);
} else if (mImpl->mHeadlessSwapChains.erase(handle)) {
delete static_cast<VulkanPlatformHeadlessSwapChain*>(handle);
} else {
PANIC_PRECONDITION("Bad handle for swapchain");
}
}
SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
@@ -949,6 +981,7 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
if (headless) {
VulkanPlatformHeadlessSwapChain* swapchain = new VulkanPlatformHeadlessSwapChain(
mImpl->mContext, mImpl->mDevice, mImpl->mGraphicsQueue, extent, flags);
mImpl->mHeadlessSwapChains.insert(swapchain);
return swapchain;
}
@@ -967,6 +1000,7 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
VulkanPlatformSurfaceSwapChain* swapchain = new VulkanPlatformSurfaceSwapChain(mImpl->mContext,
mImpl->mPhysicalDevice, mImpl->mDevice, mImpl->mGraphicsQueue, mImpl->mInstance,
surface, fallbackExtent, flags);
mImpl->mSurfaceSwapChains.insert(swapchain);
return swapchain;
}
@@ -1030,5 +1064,6 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
VkInstance instance, uint64_t flags) const noexcept {
return createVkSurfaceKHRImpl(nativeWindow, instance, flags);
}
#undef SWAPCHAIN_RET_FUNC
}// namespace filament::backend
@@ -68,12 +68,12 @@ std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& co
= context.selectMemoryType(memReqs.memoryTypeBits, requiredMemoryFlags);
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES)
<< "VulkanPlatformSwapChainBase: unable to find a memory type that meets requirements.";
<< "VulkanPlatformSwapChainImpl: unable to find a memory type that meets requirements.";
VkMemoryAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
.memoryTypeIndex = memoryTypeIndex,
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
.memoryTypeIndex = memoryTypeIndex,
};
result = vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to allocate image memory."
@@ -94,15 +94,17 @@ VkFormat selectDepthFormat(fvkutils::VkFormatList const& depthFormats, bool hasS
}// anonymous namespace
VulkanPlatformSwapChainBase::VulkanPlatformSwapChainBase(VulkanContext const& context,
VulkanPlatformSwapChainImpl::VulkanPlatformSwapChainImpl(VulkanContext const& context,
VkDevice device, VkQueue queue)
: mContext(context),
mDevice(device),
mQueue(queue) {}
VulkanPlatformSwapChainBase::~VulkanPlatformSwapChainBase() = default;
VulkanPlatformSwapChainImpl::~VulkanPlatformSwapChainImpl() {
destroy();
}
void VulkanPlatformSwapChainBase::destroy() {
void VulkanPlatformSwapChainImpl::destroy() {
if (mSwapChainBundle.depth) {
vkDestroyImage(mDevice, mSwapChainBundle.depth, VKALLOC);
if (mMemory.find(mSwapChainBundle.depth) != mMemory.end()) {
@@ -116,7 +118,7 @@ void VulkanPlatformSwapChainBase::destroy() {
mSwapChainBundle.colors.clear();
}
VkImage VulkanPlatformSwapChainBase::createImage(VkExtent2D extent, VkFormat format,
VkImage VulkanPlatformSwapChainImpl::createImage(VkExtent2D extent, VkFormat format,
bool isProtected) {
auto [image, memory] = createImageAndMemory(mContext, mDevice, extent, format, isProtected);
mMemory.insert({image, memory});
@@ -126,7 +128,7 @@ VkImage VulkanPlatformSwapChainBase::createImage(VkExtent2D extent, VkFormat for
VulkanPlatformSurfaceSwapChain::VulkanPlatformSurfaceSwapChain(VulkanContext const& context,
VkPhysicalDevice physicalDevice, VkDevice device, VkQueue queue, VkInstance instance,
VkSurfaceKHR surface, VkExtent2D fallbackExtent, uint64_t flags)
: VulkanPlatformSwapChainBase(context, device, queue),
: VulkanPlatformSwapChainImpl(context, device, queue),
mInstance(instance),
mPhysicalDevice(physicalDevice),
mSurface(surface),
@@ -169,13 +171,13 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
FixedCapacityVector<VkSurfaceFormatKHR> const surfaceFormats
= fvkutils::enumerate(vkGetPhysicalDeviceSurfaceFormatsKHR, mPhysicalDevice, mSurface);
std::array<VkFormat, 2> expectedFormats = {
VK_FORMAT_R8G8B8A8_UNORM,
VK_FORMAT_B8G8R8A8_UNORM,
VK_FORMAT_R8G8B8A8_UNORM,
VK_FORMAT_B8G8R8A8_UNORM,
};
if (mUsesRGB) {
expectedFormats = {
VK_FORMAT_R8G8B8A8_SRGB,
VK_FORMAT_B8G8R8A8_SRGB,
VK_FORMAT_R8G8B8A8_SRGB,
VK_FORMAT_B8G8R8A8_SRGB,
};
}
for (VkSurfaceFormatKHR const& format: surfaceFormats) {
@@ -193,9 +195,13 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
VkPresentModeKHR const desiredPresentMode = VK_PRESENT_MODE_FIFO_KHR;
FixedCapacityVector<VkPresentModeKHR> presentModes = fvkutils::enumerate(
vkGetPhysicalDeviceSurfacePresentModesKHR, mPhysicalDevice, mSurface);
bool const foundSuitablePresentMode = std::find(presentModes.begin(), presentModes.end(),
desiredPresentMode) != presentModes.end();
bool foundSuitablePresentMode = false;
for (VkPresentModeKHR mode: presentModes) {
if (mode == desiredPresentMode) {
foundSuitablePresentMode = true;
break;
}
}
FILAMENT_CHECK_POSTCONDITION(foundSuitablePresentMode)
<< "Desired present mode is not supported by this device.";
@@ -313,7 +319,7 @@ VkResult VulkanPlatformSurfaceSwapChain::present(uint32_t index, VkSemaphore fin
return result;
}
bool VulkanPlatformSurfaceSwapChain::hasResized() const {
bool VulkanPlatformSurfaceSwapChain::hasResized() {
VkSurfaceCapabilitiesKHR caps;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDevice, mSurface, &caps);
VkExtent2D perceivedExtent = caps.currentExtent;
@@ -325,7 +331,7 @@ bool VulkanPlatformSurfaceSwapChain::hasResized() const {
return !fvkutils::equivalent(mSwapChainBundle.extent, perceivedExtent);
}
bool VulkanPlatformSurfaceSwapChain::isProtected() const {
bool VulkanPlatformSurfaceSwapChain::isProtected() {
return mIsProtected;
}
@@ -352,6 +358,8 @@ void VulkanPlatformSurfaceSwapChain::destroy() {
// phone). If necessary, we can revisit and implement the workaround [1].
vkQueueWaitIdle(mQueue);
VulkanPlatformSwapChainImpl::destroy();
for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) {
if (mImageReady[i] != VK_NULL_HANDLE) {
vkDestroySemaphore(mDevice, mImageReady[i], VKALLOC);
@@ -366,7 +374,7 @@ void VulkanPlatformSurfaceSwapChain::destroy() {
VulkanPlatformHeadlessSwapChain::VulkanPlatformHeadlessSwapChain(VulkanContext const& context,
VkDevice device, VkQueue queue, VkExtent2D extent, uint64_t flags)
: VulkanPlatformSwapChainBase(context, device, queue),
: VulkanPlatformSwapChainImpl(context, device, queue),
mCurrentIndex(0) {
mSwapChainBundle.extent = extent;
mSwapChainBundle.colorFormat = (flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0
@@ -32,27 +32,46 @@ namespace filament::backend {
static constexpr uint32_t const VULKAN_UNDEFINED_EXTENT = 0xFFFFFFFF;
struct VulkanPlatformSwapChainBase : public Platform::SwapChain {
VulkanPlatformSwapChainBase(VulkanContext const& context, VkDevice device, VkQueue queue);
struct VulkanPlatformSwapChainImpl : public Platform::SwapChain {
VulkanPlatformSwapChainImpl(VulkanContext const& context, VkDevice device, VkQueue queue);
inline VulkanPlatform::SwapChainBundle getSwapChainBundle() const {
return mSwapChainBundle;
}
virtual ~VulkanPlatformSwapChainBase();
~VulkanPlatformSwapChainImpl();
virtual VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData) = 0;
// Non-virtual override-able method
VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData) {
PANIC_PRECONDITION("Should not be called");
return VK_ERROR_UNKNOWN;
}
virtual VkResult present(uint32_t index, VkSemaphore finished) = 0;
// Non-virtual override-able method
VkResult present(uint32_t index, VkSemaphore finished) {
PANIC_PRECONDITION("Should not be called");
return VK_ERROR_UNKNOWN;
}
virtual VkResult recreate() = 0;
// Non-virtual override-able method
VkResult recreate() {
PANIC_PRECONDITION("Should not be called");
return VK_ERROR_UNKNOWN;
}
virtual bool hasResized() const = 0;
// Non-virtual override-able method
bool hasResized() {
return false;
}
virtual bool isProtected() const = 0;
// Non-virtual override-able method
bool isProtected() {
return false;
}
protected:
virtual void destroy() = 0;
// Non-virtual override-able method
void destroy();
VkImage createImage(VkExtent2D extent, VkFormat format, bool isProtected);
@@ -64,29 +83,35 @@ protected:
std::unordered_map<VkImage, VkDeviceMemory> mMemory;
};
struct VulkanPlatformSurfaceSwapChain : public VulkanPlatformSwapChainBase {
struct VulkanPlatformSurfaceSwapChain : public VulkanPlatformSwapChainImpl {
VulkanPlatformSurfaceSwapChain(VulkanContext const& context, VkPhysicalDevice physicalDevice,
VkDevice device, VkQueue queue, VkInstance instance, VkSurfaceKHR surface,
VkExtent2D fallbackExtent, uint64_t flags);
~VulkanPlatformSurfaceSwapChain() override;
~VulkanPlatformSurfaceSwapChain();
virtual VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData) override;
// Non-virtual override
VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData);
virtual VkResult present(uint32_t index, VkSemaphore finished) override;
// Non-virtual override
VkResult present(uint32_t index, VkSemaphore finished);
virtual VkResult recreate() override;
// Non-virtual override-able method
VkResult recreate();
virtual bool hasResized() const override;
// Non-virtual override-able method
bool hasResized();
virtual bool isProtected() const override;
// Non-virtual override-able method
bool isProtected();
protected:
virtual void destroy() override;
// Non-virtual override-able method
void destroy();
private:
static constexpr int IMAGE_READY_SEMAPHORE_COUNT = FVK_MAX_COMMAND_BUFFERS;
VkResult create();
VkInstance mInstance;
@@ -98,35 +123,29 @@ private:
VkSemaphore mImageReady[IMAGE_READY_SEMAPHORE_COUNT];
uint32_t mCurrentImageReadyIndex;
bool const mUsesRGB = false;
bool const mHasStencil = false;
bool const mIsProtected = false;
bool mUsesRGB = false;
bool mHasStencil = false;
bool mIsProtected = false;
bool mSuboptimal;
};
struct VulkanPlatformHeadlessSwapChain : public VulkanPlatformSwapChainBase {
struct VulkanPlatformHeadlessSwapChain : public VulkanPlatformSwapChainImpl {
static constexpr size_t const HEADLESS_SWAPCHAIN_SIZE = 2;
VulkanPlatformHeadlessSwapChain(VulkanContext const& context, VkDevice device, VkQueue queue,
VkExtent2D extent, uint64_t flags);
~VulkanPlatformHeadlessSwapChain() override;
~VulkanPlatformHeadlessSwapChain();
virtual VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData) override;
// Non-virtual override
VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData);
virtual VkResult present(uint32_t index, VkSemaphore finished) override;
virtual VkResult recreate() override {
PANIC_PRECONDITION("Should not be called");
return VK_ERROR_UNKNOWN;
}
virtual bool hasResized() const override { return false; }
virtual bool isProtected() const override { return false; }
// Non-virtual override
VkResult present(uint32_t index, VkSemaphore finished);
protected:
virtual void destroy() override;
// Non-virtual override-able method
void destroy();
private:
uint32_t mCurrentIndex;
+208 -150
View File
@@ -84,12 +84,128 @@ constexpr uint32_t UNIFORM_BINDING_INDEX{ 1 };
constexpr uint32_t SAMPLER_BINDING_INDEX{ 2 };
constexpr std::string_view VERTEX_SHADER_ENTRY_POINT{ "vertexShaderMain" };
constexpr std::string_view FRAGMENT_SHADER_ENTRY_POINT{ "fragmentShaderMain" };
// note that the placeholders below must start and end with this prefix and suffix and should not
// otherwise be present in the template:
constexpr std::string_view PLACEHOLDER_PREFIX{ "{{" };
constexpr std::string_view PLACEHOLDER_SUFFIX{ "}}" };
// texture_2d<f32> or texture_multisampled_2d<f32> or texture_3d<f32> or
// texture_depth_2d or texture_depth_multisampled_2d
constexpr std::string_view TEXTURE_TYPE_PLACEHOLDER{ "TEXTURE_TYPE" };
// texture_2d<f32> or texture_depth_2d
[[maybe_unused]] constexpr std::string_view TEXTURE_2D_TYPE_PLACEHOLDER { "TEXTURE_2D_TYPE" };
// "" for no sampler, otherwise "@group(0) @binding(2) var sourceSampler: sampler;"
constexpr std::string_view SAMPLER_DECLARATION_PLACEHOLDER{ "SAMPLER_DECLARATION" };
constexpr std::string_view FRAGMENT_SHADER_SNIPPET_PLACEHOLDER{ "FRAGMENT_SHADER_SNIPPET" };
// @location(0) vec4<f32> or @builtin(frag_depth) f32
constexpr std::string_view FRAGMENT_RETURN_ATTRIBUTE_AND_TYPE_PLACEHOLDER{
"FRAGMENT_RETURN_ATTRIBUTE_AND_TYPE"
};
constexpr std::string_view SHADER_SOURCE_TEMPLATE{ R"(
struct BlitFragmentShaderArgs {
depthPlane: u32,
scale: vec2<f32>,
sourceOffset: vec2<u32>,
destinationOffset: vec2<u32>,
};
@group(0) @binding(0) var sourceTexture: {{TEXTURE_TYPE}};
@group(0) @binding(1) var<uniform> fragmentShaderArgs: BlitFragmentShaderArgs;
{{SAMPLER_DECLARATION}}
fn getUnnormalizedSourceTextureCoordinates(position: vec2<f32>) -> vec2<f32> {
// These coordinates match the Vulkan vkCmdBlitImage spec:
// https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdBlitImage.html
let uvOffset: vec2<f32> = position - vec2<f32>(f32(fragmentShaderArgs.destinationOffset.x),f32(fragmentShaderArgs.destinationOffset.y));
let uvScaled: vec2<f32> = uvOffset * fragmentShaderArgs.scale;
return uvScaled + vec2<f32>(f32(fragmentShaderArgs.sourceOffset.x), f32(fragmentShaderArgs.sourceOffset.y));
}
fn normalize2dSourceTextureCoordinates(
unnormalizedSourceTextureCoordinates: vec2<f32>,
sourceDimensions: vec2<u32>) -> vec2<f32> {
return unnormalizedSourceTextureCoordinates /
vec2<f32>(f32(sourceDimensions.x), f32(sourceDimensions.y));
}
fn getNormalized2dSourceTextureCoordinates(
position: vec2<f32>,
sourceTexture: {{TEXTURE_2D_TYPE}}) -> vec2<f32> {
let sourceDimensions: vec2<u32> = textureDimensions(sourceTexture);
return normalize2dSourceTextureCoordinates(
getUnnormalizedSourceTextureCoordinates(position),
sourceDimensions
);
}
fn normalize3dSourceTextureCoordinates(
unnormalizedSourceTextureCoordinates: vec2<f32>,
sourceDimensions: vec3<u32>) -> vec3<f32> {
let uvNormalized: vec2<f32> = normalize2dSourceTextureCoordinates(
unnormalizedSourceTextureCoordinates,
sourceDimensions.xy
);
return vec3<f32>(
uvNormalized,
(f32(fragmentShaderArgs.depthPlane) + 0.5) / f32(sourceDimensions.z)
);
}
fn getNormalized3dSourceTextureCoordinates(
position: vec2<f32>,
sourceTexture: texture_3d<f32>) -> vec3<f32> {
let sourceDimensions: vec3<u32> = textureDimensions(sourceTexture);
return normalize3dSourceTextureCoordinates(
getUnnormalizedSourceTextureCoordinates(position),
sourceDimensions
);
}
@vertex
fn vertexShaderMain(@builtin(vertex_index) vertexIndex: u32) -> @builtin(position) vec4<f32> {
let fullScreenTriangleVertices = array<vec2<f32>, 3>(
vec2<f32>(-1.0, -1.0),
vec2<f32>( 3.0, -1.0),
vec2<f32>(-1.0, 3.0)
);
return vec4<f32>(fullScreenTriangleVertices[vertexIndex].xy, 0.0, 1.0);
}
{{FRAGMENT_SHADER_SNIPPET}}
)" };
constexpr std::string_view FRAGMENT_SHADER_SNIPPET_MSAA_INPUT_TEMPLATE{ R"(
@fragment
fn fragmentShaderMain(@builtin(position) position: vec4<f32>) -> {{FRAGMENT_RETURN_ATTRIBUTE_AND_TYPE}} {
var color: vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 0.0);
let numberOfSamples: u32 = textureNumSamples(sourceTexture);
let coordinatesF: vec2<f32> = getUnnormalizedSourceTextureCoordinates(position.xy);
let coordinates: vec2<u32> = vec2<u32>(u32(coordinatesF.x), u32(coordinatesF.y));
for (var sampleIndex: u32 = 0; sampleIndex < numberOfSamples; sampleIndex++) {
color += textureLoad(sourceTexture, coordinates, sampleIndex);
}
color /= f32(numberOfSamples);
return color;
}
)" };
constexpr std::string_view FRAGMENT_SHADER_SNIPPET_3D_INPUT_TEMPLATE{ R"(
@fragment
fn fragmentShaderMain(@builtin(position) position: vec4<f32>) -> {{FRAGMENT_RETURN_ATTRIBUTE_AND_TYPE}} {
let coordinates: vec3<f32> = getNormalized3dSourceTextureCoordinates(position.xy, sourceTexture);
return textureSample(sourceTexture, sourceSampler, coordinates);
}
)" };
constexpr std::string_view FRAGMENT_SHADER_SNIPPET_2D_INPUT_TEMPLATE{ R"(
@fragment
fn fragmentShaderMain(@builtin(position) position: vec4<f32>) -> {{FRAGMENT_RETURN_ATTRIBUTE_AND_TYPE}} {
let coordinates: vec2<f32> = getNormalized2dSourceTextureCoordinates(position.xy, sourceTexture);
return textureSample(sourceTexture, sourceSampler, coordinates);
}
)" };
} // namespace
// Include the wgsl template sources
#include "WebGPUBlitter_wgsl.inc"
// It can perform a direct memory copy if the formats are compatible and no scaling or format
// conversion is needed. Otherwise, it uses a render pass with a custom shader to perform
// the blit. This allows for scaling, format conversion, and resolving multisampled textures.
@@ -279,25 +395,11 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
const bool multisampledSource{ args.source.texture.GetSampleCount() > 1 };
const bool depthSource{ hasDepth(args.source.texture.GetFormat()) };
const bool depthDestination{ hasDepth(args.destination.texture.GetFormat()) };
wgpu::TextureSampleType srcSampleType = {};
if (depthSource) {
srcSampleType = wgpu::TextureSampleType::Depth;
} else if (multisampledSource) {
srcSampleType = wgpu::TextureSampleType::UnfilterableFloat;
} else if (isIntFormat(args.source.texture.GetFormat())) {
srcSampleType = wgpu::TextureSampleType::Sint;
} else if (isUIntFormat(args.source.texture.GetFormat())) {
srcSampleType = wgpu::TextureSampleType::Uint;
} else {
srcSampleType = wgpu::TextureSampleType::Float;
}
const PipelineLayoutKey pipelineLayoutKey{
.sourceDimension = sourceDimension,
.sourceSampleType = srcSampleType,
.filterType = args.filter,
.multisampledSource = multisampledSource,
.depthSource = depthSource,
};
const wgpu::BindGroupDescriptor textureBindGroupDescriptor{
.label = "blit_texture_bind_group",
@@ -317,35 +419,32 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
.occlusionQuerySet = nullptr, // not applicable
.timestampWrites = nullptr, // not applicable
};
const wgpu::RenderPassDepthStencilAttachment depthStencilAttachment{
.view = destinationView,
.depthLoadOp = wgpu::LoadOp::Clear,
.depthStoreOp = wgpu::StoreOp::Store,
.depthClearValue = 0.0f, // explicitly defining for consistency
.depthReadOnly = false,
.stencilLoadOp = wgpu::LoadOp::Undefined, // stencil not supported
.stencilStoreOp = wgpu::StoreOp::Undefined, // stencil not supported
};
// color destination...
const wgpu::RenderPassColorAttachment colorAttachment{
.view = destinationView,
.depthSlice = args.destination.texture.GetDimension() == wgpu::TextureDimension::e3D
? args.destination.layerOrDepth
: wgpu::kDepthSliceUndefined,
// we don't need this resolveTarget because we are doing a resolve in software (the
// shader), but we probably should (leveraging a msaa sidecar texture) to take advantage
// of hardware acceleration. Thus, this might be an opportunity for optimization
.resolveTarget = nullptr,
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
.clearValue = { .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
};
if (depthDestination) {
const wgpu::RenderPassDepthStencilAttachment depthStencilAttachment{
.view = destinationView,
.depthLoadOp = wgpu::LoadOp::Clear,
.depthStoreOp = wgpu::StoreOp::Store,
.depthClearValue = 0.0f, // explicitly defining for consistency
.depthReadOnly = false,
.stencilLoadOp = wgpu::LoadOp::Undefined, // stencil not supported
.stencilStoreOp = wgpu::StoreOp::Undefined, // stencil not supported
};
renderPassDescriptor.depthStencilAttachment = &depthStencilAttachment;
} else {
// color destination...
const wgpu::RenderPassColorAttachment colorAttachment{
.view = destinationView,
.depthSlice = args.destination.texture.GetDimension() == wgpu::TextureDimension::e3D
? args.destination.layerOrDepth
: wgpu::kDepthSliceUndefined,
// we don't need this resolveTarget because we are doing a resolve in software (the
// shader), but we probably should (leveraging a msaa sidecar texture) to take advantage
// of hardware acceleration. Thus, this might be an opportunity for optimization
.resolveTarget = nullptr,
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
.clearValue = { .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
};
renderPassDescriptor.colorAttachmentCount = 1;
renderPassDescriptor.colorAttachments = &colorAttachment;
}
@@ -355,11 +454,10 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
<< "Failed to create render pass encoder for blit.";
const RenderPipelineKey renderPipelineKey{
.sourceDimension = sourceDimension,
.sourceTextureFormat = args.source.texture.GetFormat(),
.sourceSampleType = srcSampleType,
.destinationTextureFormat = args.destination.texture.GetFormat(),
.sourceSampleCount = static_cast<uint8_t>(args.source.texture.GetSampleCount()),
.filterType = args.filter,
.depthSource = depthSource,
};
renderPassEncoder.SetPipeline(getOrCreateRenderPipeline(renderPipelineKey));
renderPassEncoder.SetBindGroup(TEXTURE_BIND_GROUP_INDEX, textureBindGroup);
@@ -459,9 +557,9 @@ wgpu::RenderPipeline WebGPUBlitter::createRenderPipeline(RenderPipelineKey const
};
const ShaderModuleKey shaderModuleKey{
.sourceDimension = key.sourceDimension,
.sourceTextureFormat = key.sourceTextureFormat,
.destinationTextureFormat = key.destinationTextureFormat,
.multisampledSource = key.sourceSampleCount > 1,
.depthSource = key.depthSource,
.depthDestination = hasDepth(key.destinationTextureFormat),
};
wgpu::ShaderModule const& shaderModule{ getOrCreateShaderModule(shaderModuleKey) };
const wgpu::FragmentState fragmentState{
@@ -474,9 +572,9 @@ wgpu::RenderPipeline WebGPUBlitter::createRenderPipeline(RenderPipelineKey const
};
const PipelineLayoutKey pipelineLayoutKey{
.sourceDimension = key.sourceDimension,
.sourceSampleType = key.sourceSampleType,
.filterType = key.filterType,
.multisampledSource = key.sourceSampleCount > 1,
.depthSource = key.depthSource,
};
const wgpu::RenderPipelineDescriptor pipelineDescriptor{
.label = "render_pass_blit_pipeline",
@@ -565,7 +663,13 @@ wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(PipelineLayout
.binding = TEXTURE_BINDING_INDEX,
.visibility = wgpu::ShaderStage::Fragment,
.texture = {
.sampleType = key.sourceSampleType,
.sampleType = key.depthSource
? wgpu::TextureSampleType::Depth
: (key.multisampledSource
? wgpu::TextureSampleType::UnfilterableFloat
: wgpu::TextureSampleType::Float), // only F32 scalar sample
// type supported for now
// (aside from depth)
.viewDimension = key.sourceDimension,
.multisampled = key.multisampledSource,
},
@@ -591,7 +695,7 @@ wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(PipelineLayout
};
const wgpu::BindGroupLayoutDescriptor textureBindGroupLayoutDescriptor{
.label = "render_pass_blit_texture_bind_group_layout",
// No need for the last entry (sampler) if source is multisampled.
// TODO, doesnt make any sense but gets rid of the error. Are the entries 0 based?
.entryCount = key.multisampledSource ? (MAX_TEXTURE_BIND_GROUP_ENTRY_SIZE - 1)
: (MAX_TEXTURE_BIND_GROUP_ENTRY_SIZE),
.entries = bindGroupLayoutEntries,
@@ -616,109 +720,63 @@ wgpu::ShaderModule const& WebGPUBlitter::getOrCreateShaderModule(ShaderModuleKey
// The shader source is generated from a template, with placeholders filled in based on the
// blit configuration (e.g., texture type, sample count).
wgpu::ShaderModule WebGPUBlitter::createShaderModule(ShaderModuleKey const& key) {
bool const isDepthSrc = hasDepth(key.sourceTextureFormat);
bool const isDepthDst = hasDepth(key.destinationTextureFormat);
std::string_view vecDim;
if (key.sourceDimension == wgpu::TextureViewDimension::e3D) {
vecDim = "vec3";
std::string_view textureType;
if (key.depthSource) {
if (key.multisampledSource) {
textureType = "texture_depth_multisampled_2d";
} else {
textureType = "texture_depth_2d";
}
} else {
vecDim = "vec2";
if (key.multisampledSource) {
textureType = "texture_multisampled_2d<f32>";
} else {
if (key.sourceDimension == wgpu::TextureViewDimension::e3D) {
textureType = "texture_3d<f32>";
} else {
textureType = "texture_2d<f32>";
}
}
}
std::string_view srcPrimType;
std::string_view sampleImpl;
if (isIntFormat(key.sourceTextureFormat)) {
srcPrimType = "<i32>";
sampleImpl = INT_TEXTURE_SAMPLE_IMPL_TEMPLATE;
} else if (isUIntFormat(key.sourceTextureFormat)) {
srcPrimType = "<u32>";
sampleImpl = INT_TEXTURE_SAMPLE_IMPL_TEMPLATE;
} else {
srcPrimType = "<f32>";
sampleImpl = FLOAT_TEXTURE_SAMPLE_IMPL_TEMPLATE;
}
std::string_view fragmentTemplate;
std::string_view srcTextureType;
if (isDepthSrc && key.multisampledSource) {
srcTextureType = "texture_depth_multisampled_2d";
fragmentTemplate = FRAGMENT_SHADER_SNIPPET_2D_INPUT_TEMPLATE;
} else if (isDepthSrc && !key.multisampledSource) {
srcTextureType = "texture_depth_2d";
fragmentTemplate = FRAGMENT_SHADER_SNIPPET_2D_INPUT_TEMPLATE;
} else if (key.multisampledSource) {
srcTextureType = "texture_multisampled_2d{{SRC_PRIM_TYPE}}";
fragmentTemplate = FRAGMENT_SHADER_SNIPPET_MSAA_INPUT_TEMPLATE;
} else if (key.sourceDimension == wgpu::TextureViewDimension::e3D) {
srcTextureType = "texture_3d{{SRC_PRIM_TYPE}}";
fragmentTemplate = FRAGMENT_SHADER_SNIPPET_3D_INPUT_TEMPLATE;
} else {
srcTextureType = "texture_2d{{SRC_PRIM_TYPE}}";
fragmentTemplate = FRAGMENT_SHADER_SNIPPET_2D_INPUT_TEMPLATE;
}
std::string_view dstPrimType;
if (isIntFormat(key.destinationTextureFormat)) {
dstPrimType = "<i32>";
} else if (isUIntFormat(key.destinationTextureFormat)) {
dstPrimType = "<u32>";
} else {
dstPrimType = "<f32>";
}
std::string_view retAttribute;
std::string_view retType;
if (isDepthDst) {
retAttribute = "@builtin(frag_depth)";
retType = "f32";
} else {
retAttribute = "@location(0)";
retType = "vec4{{DST_PRIM_TYPE}}";
}
using ReplacementMap = std::unordered_map<std::string_view, std::string_view>;
auto const replace = [&](std::string_view src, ReplacementMap const& replacements) {
return webgpuutils::processPlaceholderTemplate(
src, PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, replacements);
const std::string_view texture2dType{ key.depthSource ? "texture_depth_2d"
: "texture_2d<f32>" };
// we don't declare a sampler in the shader or pipeline for the multisampled case
const std::string_view samplerDeclaration{
key.multisampledSource ? "" : "@group(0) @binding(2) var sourceSampler: sampler;"
};
// The dependency chain is shader module -> fragment source -> sampling function
// Hence we need to fill in the templates in reverse order.
// Fill in sampling implementation in the sampling func
std::string source = replace(TEXTURE_SAMPLE_IMPL_MAIN_TEMPLATE, {
{ TEXTURE_SAMPLE_IMPL, sampleImpl },
});
// Fill in sampling func in the fragment
source = replace(fragmentTemplate, {
{ TEXTURE_SAMPLE_IMPL_MAIN, source },
});
// Fill in fragment in the shader module, first pass
source = replace(SHADER_SOURCE_TEMPLATE, {
{ FRAGMENT_SHADER_SNIPPET, source },
});
// Fill in fragment in the shader module, second pass
source = replace(source, {
{ TEXTURE_TYPE, srcTextureType },
{ RET_ATTRIBUTE, retAttribute },
{ RET_TYPE, retType },
});
// Fill in fragment in the shader module, third pass
// TEXTURE_TYPE, RET_ATTRIBUTE, RET_TYPE have a dependency on SRC_PRIM_TYPE, DST_PRIM_TYPE,
// VECTOR_DIM so we need another pass to fill them in.
source = replace(source, {
{ VECTOR_DIM, vecDim },
{ SRC_PRIM_TYPE, srcPrimType },
{ DST_PRIM_TYPE, dstPrimType },
});
const std::string_view fragmentReturnAttributeAndType{
key.depthDestination ? "@builtin(frag_depth) f32" : "@location(0) vec4<f32>"
};
const std::unordered_map<std::string_view, std::string_view>
valueByPlaceholderNameForFragmentSnippet{
{ FRAGMENT_RETURN_ATTRIBUTE_AND_TYPE_PLACEHOLDER, fragmentReturnAttributeAndType },
};
std::string fragmentShaderSnippet;
if (key.multisampledSource) {
fragmentShaderSnippet = webgpuutils::processPlaceholderTemplate(
FRAGMENT_SHADER_SNIPPET_MSAA_INPUT_TEMPLATE, PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX,
valueByPlaceholderNameForFragmentSnippet);
} else {
if (key.sourceDimension == wgpu::TextureViewDimension::e3D) {
fragmentShaderSnippet = webgpuutils::processPlaceholderTemplate(
FRAGMENT_SHADER_SNIPPET_3D_INPUT_TEMPLATE, PLACEHOLDER_PREFIX,
PLACEHOLDER_SUFFIX, valueByPlaceholderNameForFragmentSnippet);
} else {
fragmentShaderSnippet = webgpuutils::processPlaceholderTemplate(
FRAGMENT_SHADER_SNIPPET_2D_INPUT_TEMPLATE, PLACEHOLDER_PREFIX,
PLACEHOLDER_SUFFIX, valueByPlaceholderNameForFragmentSnippet);
}
}
const std::unordered_map<std::string_view, std::string_view> valueByPlaceholderNameForModule{
{ TEXTURE_TYPE_PLACEHOLDER, textureType },
{ TEXTURE_2D_TYPE_PLACEHOLDER, texture2dType },
{ SAMPLER_DECLARATION_PLACEHOLDER, samplerDeclaration },
{ FRAGMENT_SHADER_SNIPPET_PLACEHOLDER, fragmentShaderSnippet },
};
const std::string shaderSource{ webgpuutils::processPlaceholderTemplate(SHADER_SOURCE_TEMPLATE,
PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, valueByPlaceholderNameForModule) };
wgpu::ShaderModuleWGSLDescriptor wgslDescriptor{};
wgslDescriptor.code = source.data();
wgslDescriptor.code = shaderSource.data();
const wgpu::ShaderModuleDescriptor shaderModuleDescriptor{
.nextInChain = &wgslDescriptor,
.label = "render_pass_blit_shaders",
+10 -11
View File
@@ -97,12 +97,11 @@ private:
struct RenderPipelineKey {
wgpu::TextureViewDimension sourceDimension; // 4 bytes
wgpu::TextureFormat sourceTextureFormat; // 4
wgpu::TextureSampleType sourceSampleType; // 4
wgpu::TextureFormat destinationTextureFormat; // 4
uint8_t sourceSampleCount; // 1
SamplerMagFilter filterType; // 1
uint8_t padding[2] = {}; // 2
bool depthSource; // 1
uint8_t padding = 0; // 1
bool operator==(const RenderPipelineKey& other) const;
using Hasher = utils::hash::MurmurHashFn<RenderPipelineKey>;
@@ -110,10 +109,10 @@ private:
struct PipelineLayoutKey {
wgpu::TextureViewDimension sourceDimension; // 4 bytes
wgpu::TextureSampleType sourceSampleType; // 4
SamplerMagFilter filterType; // 1
bool multisampledSource; // 1
uint8_t padding[2] = {}; // 2
bool depthSource; // 1
uint8_t padding = 0; // 1
bool operator==(const PipelineLayoutKey& other) const;
using Hasher = utils::hash::MurmurHashFn<PipelineLayoutKey>;
@@ -121,20 +120,20 @@ private:
struct ShaderModuleKey {
wgpu::TextureViewDimension sourceDimension; // 4 bytes
wgpu::TextureFormat sourceTextureFormat; // 4
wgpu::TextureFormat destinationTextureFormat; // 4
bool multisampledSource; // 1
uint8_t padding0[3] = {}; // 3
bool depthSource; // 1
bool depthDestination; // 1
uint8_t padding = 0; // 1
bool operator==(const ShaderModuleKey& other) const;
using Hasher = utils::hash::MurmurHashFn<ShaderModuleKey>;
};
static_assert(sizeof(RenderPipelineKey) == 20,
static_assert(sizeof(RenderPipelineKey) == 12,
"RenderPipelineKey must not have implicit padding.");
static_assert(sizeof(PipelineLayoutKey) == 12,
static_assert(sizeof(PipelineLayoutKey) == 8,
"PipelineLayoutKey must not have implicit padding.");
static_assert(sizeof(ShaderModuleKey) == 16, "ShaderModuleKey must not have implicit padding.");
static_assert(sizeof(ShaderModuleKey) == 8, "ShaderModuleKey must not have implicit padding.");
wgpu::Device mDevice;
wgpu::Sampler mNearestSampler{ nullptr };
@@ -1,174 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* NOTE: This file is meant to be included with #include in WebGPUBlitter.cpp
* It is separated out for clarity.
*/
namespace {
// note that the placeholders below must start and end with this prefix and suffix and should not
// otherwise be present in the template:
constexpr std::string_view PLACEHOLDER_PREFIX{ "{{" };
constexpr std::string_view PLACEHOLDER_SUFFIX{ "}}" };
#define PLACEHOLDER_DEF(name) constexpr std::string_view name = #name
PLACEHOLDER_DEF(FRAGMENT_SHADER_SNIPPET);
PLACEHOLDER_DEF(TEXTURE_SAMPLE_IMPL);
PLACEHOLDER_DEF(TEXTURE_SAMPLE_IMPL_MAIN);
// texture_multisampled_2d<f32>
// texture_2d<f32/i32/u32>
// texture_3d<f32/i32/u32>
// texture_depth_2d
// texture_depth_multisampled_2d
PLACEHOLDER_DEF(TEXTURE_TYPE);
// vec2, vec3
PLACEHOLDER_DEF(VECTOR_DIM);
// vec_<f32>, vec_<u32>, vec_<i32>, f32
PLACEHOLDER_DEF(RET_TYPE);
// @builtin(frag_depth), @location(0)
PLACEHOLDER_DEF(RET_ATTRIBUTE);
// <f32>, <u32>, <i32>
PLACEHOLDER_DEF(DST_PRIM_TYPE);
PLACEHOLDER_DEF(SRC_PRIM_TYPE);
#undef PLACEHOLDER_DEF
constexpr std::string_view SHADER_SOURCE_TEMPLATE{ R"(
struct BlitFragmentShaderArgs {
depthPlane: u32,
scale: vec2<f32>,
sourceOffset: vec2<u32>,
destinationOffset: vec2<u32>,
};
@group(0) @binding(0) var sourceTexture: {{TEXTURE_TYPE}};
@group(0) @binding(1) var<uniform> fragmentShaderArgs: BlitFragmentShaderArgs;
fn getUnnormalizedSourceTextureCoordinates(position: vec2<f32>) -> vec2<f32> {
// These coordinates match the Vulkan vkCmdBlitImage spec:
// https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdBlitImage.html
let uvOffset: vec2<f32> = position - vec2<f32>(f32(fragmentShaderArgs.destinationOffset.x),f32(fragmentShaderArgs.destinationOffset.y));
let uvScaled: vec2<f32> = uvOffset * fragmentShaderArgs.scale;
return uvScaled + vec2<f32>(f32(fragmentShaderArgs.sourceOffset.x), f32(fragmentShaderArgs.sourceOffset.y));
}
fn normalize2dSourceTextureCoordinates(
unnormalizedSourceTextureCoordinates: vec2<f32>,
sourceDimensions: vec2<u32>) -> vec2<f32> {
return unnormalizedSourceTextureCoordinates /
vec2<f32>(f32(sourceDimensions.x), f32(sourceDimensions.y));
}
@vertex
fn vertexShaderMain(@builtin(vertex_index) vertexIndex: u32) -> @builtin(position) vec4<f32> {
let fullScreenTriangleVertices = array<vec2<f32>, 3>(
vec2<f32>(-1.0, -1.0),
vec2<f32>( 3.0, -1.0),
vec2<f32>(-1.0, 3.0)
);
return vec4<f32>(fullScreenTriangleVertices[vertexIndex].xy, 0.0, 1.0);
}
{{FRAGMENT_SHADER_SNIPPET}}
)"
};
constexpr std::string_view FLOAT_TEXTURE_SAMPLE_IMPL_TEMPLATE{ R"(
return textureSample(sourceTexture, sourceSampler, coordinates);
)" };
constexpr std::string_view INT_TEXTURE_SAMPLE_IMPL_TEMPLATE { R"(
let texelCoords = {{VECTOR_DIM}}<u32>(coordinates * {{VECTOR_DIM}}<f32>(sourceDimensions));
return textureLoad(sourceTexture, texelCoords, 0);
)"};
constexpr std::string_view TEXTURE_SAMPLE_IMPL_MAIN_TEMPLATE { R"(
// Assumes that "@group(0) @binding(2) var sourceSampler: sampler;" has been declared in the main program;
fn sampleTextureImpl(sourceTexture: {{TEXTURE_TYPE}}, sourceDimensions: {{VECTOR_DIM}}<u32>,
coordinates: {{VECTOR_DIM}}<f32>) -> vec4{{DST_PRIM_TYPE}} {
{{TEXTURE_SAMPLE_IMPL}}
}
)"};
constexpr std::string_view FRAGMENT_SHADER_SNIPPET_MSAA_INPUT_TEMPLATE{ R"(
@fragment
fn fragmentShaderMain(@builtin(position) position: vec4<f32>) -> {{RET_ATTRIBUTE}} {{RET_TYPE}} {
var color: vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 0.0);
let numberOfSamples: u32 = textureNumSamples(sourceTexture);
let coordinatesF: vec2<f32> = getUnnormalizedSourceTextureCoordinates(position.xy);
let coordinates: vec2<u32> = vec2<u32>(u32(coordinatesF.x), u32(coordinatesF.y));
for (var sampleIndex: u32 = 0; sampleIndex < numberOfSamples; sampleIndex++) {
color += textureLoad(sourceTexture, coordinates, sampleIndex);
}
color /= f32(numberOfSamples);
return color;
}
)" };
constexpr std::string_view FRAGMENT_SHADER_SNIPPET_3D_INPUT_TEMPLATE{ R"(
@group(0) @binding(2) var sourceSampler: sampler;
{{TEXTURE_SAMPLE_IMPL_MAIN}}
fn normalize3dSourceTextureCoordinates(
unnormalizedSourceTextureCoordinates: vec2<f32>,
sourceDimensions: vec3<u32>) -> vec3<f32> {
let uvNormalized: vec2<f32> = normalize2dSourceTextureCoordinates(
unnormalizedSourceTextureCoordinates,
sourceDimensions.xy
);
return vec3<f32>(
uvNormalized,
(f32(fragmentShaderArgs.depthPlane) + 0.5) / f32(sourceDimensions.z)
);
}
@fragment
fn fragmentShaderMain(@builtin(position) position: vec4<f32>) -> {{RET_ATTRIBUTE}} {{RET_TYPE}} {
let sourceDimensions: vec3<u32> = textureDimensions(sourceTexture);
let coordinates: vec3<f32> = normalize3dSourceTextureCoordinates(
getUnnormalizedSourceTextureCoordinates(position),
sourceDimensions
);
return sampleTextureImpl(sourceTexture, sourceDimensions, coordinates);
}
)" };
constexpr std::string_view FRAGMENT_SHADER_SNIPPET_2D_INPUT_TEMPLATE { R"(
@group(0) @binding(2) var sourceSampler: sampler;
{{TEXTURE_SAMPLE_IMPL_MAIN}}
@fragment
fn fragmentShaderMain(@builtin(position) position: vec4<f32>) -> {{RET_ATTRIBUTE}} {{RET_TYPE}} {
let sourceDimensions: vec2<u32> = textureDimensions(sourceTexture);
let coordinates: vec2<f32> = normalize2dSourceTextureCoordinates(
getUnnormalizedSourceTextureCoordinates(position.xy),
sourceDimensions
);
return sampleTextureImpl(sourceTexture, sourceDimensions, coordinates);
}
)" };
} // namespace
+7 -6
View File
@@ -339,7 +339,7 @@ Handle<HwTimerQuery> WebGPUDriver::createTimerQueryS() noexcept {
}
Handle<HwIndexBuffer> WebGPUDriver::createIndexBufferS() noexcept {
return allocHandle<WebGPUIndexBuffer>();
return allocHandle<HwIndexBuffer>();
}
Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept {
@@ -841,10 +841,6 @@ size_t WebGPUDriver::getMaxArrayTextureLayers() {
return mDeviceLimits.maxTextureArrayLayers;
}
size_t WebGPUDriver::getUniformBufferOffsetAlignment(){
return mDeviceLimits.minUniformBufferOffsetAlignment;
}
void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
// make sure command elements (draws, etc.) prior to the buffer update are processed before the
@@ -1254,6 +1250,11 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
customDepthStencilMsaaSidecarTextureView);
mRenderPassEncoder = commandEncoder.BeginRenderPass(&renderPassDescriptor);
// Ensure viewport dimensions are not 0
FILAMENT_CHECK_POSTCONDITION(params.viewport.width > 0) << "viewport width is 0?";
FILAMENT_CHECK_POSTCONDITION(params.viewport.height > 0) << "viewport height is 0?";
mRenderPassEncoder.SetViewport(
static_cast<float>(params.viewport.left),
static_cast<float>(params.viewport.bottom),
@@ -1496,7 +1497,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
mReadPixelMapsCounter.startTask();
userData->buffer.MapAsync(
wgpu::MapMode::Read, 0, bufferSize, wgpu::CallbackMode::AllowSpontaneous,
wgpu::MapMode::Read, 0, bufferSize, wgpu::CallbackMode::AllowProcessEvents,
[](wgpu::MapAsyncStatus status, const char* message, UserData* userdata) {
std::unique_ptr<UserData> data(static_cast<UserData*>(userdata));
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
+1 -7
View File
@@ -39,15 +39,9 @@ FenceStatus WebGPUFence::wait(uint64_t timeout) {
using namespace std::chrono;
auto now = steady_clock::now();
steady_clock::time_point until = steady_clock::time_point::max();
using TimeoutType = decltype(timeout);
constexpr TimeoutType maxTimeout = std::numeric_limits<TimeoutType>::max();
constexpr nanoseconds maxNano = nanoseconds::max();
if (timeout < maxNano.count() && timeout < maxTimeout && // Need to account for overflow
now <= steady_clock::time_point::max() - nanoseconds(timeout)) {
if (now <= steady_clock::time_point::max() - nanoseconds(timeout)) {
until = now + nanoseconds(timeout);
}
{
std::unique_lock<std::mutex> lock(mLock);
bool const success = mCond.wait_until(lock, until, [this] {
@@ -338,10 +338,6 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
// To handle this, we check if any color targets are configured for this pipeline. If not, we
// create a pipeline *without* a fragment stage. This makes the pipeline valid for a
// depth-only pass, allowing depth writes to proceed correctly.
// Note that this needs to be defined in the same scope as the CreateRenderpipeline call
std::array<wgpu::ColorTargetState, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> colorTargets {};
if (request.fragmentShaderModule != nullptr && request.colorFormatCount > 0) {
fragmentState.module = request.fragmentShaderModule;
fragmentState.entryPoint = "main";
@@ -350,6 +346,7 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
fragmentState.constantCount = 0;
fragmentState.constants = nullptr;
fragmentState.targetCount = request.colorFormatCount;
std::array<wgpu::ColorTargetState, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> colorTargets {};
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
wgpu::ColorTargetState& colorTarget = colorTargets[targetIndex];
@@ -390,11 +390,11 @@ void WebGPUSwapChain::present(DriverBase& driver) {
if (!isHeadless()) {
mSurface.Present();
}
if (mFrameScheduled.callback) {
driver.scheduleCallback(mFrameScheduled.handler,
[callback = mFrameScheduled.callback]() {
if (frameScheduled.callback) {
driver.scheduleCallback(frameScheduled.handler,
[callback = std::move(frameScheduled.callback)]() {
PresentCallable noop = PresentCallable(PresentCallable::noopPresent, nullptr);
callback->operator()(noop);
callback(noop);
});
}
}
+4 -10
View File
@@ -23,7 +23,6 @@
#include <backend/Platform.h>
#include <cstdint>
#include <memory>
namespace filament::backend {
@@ -53,13 +52,8 @@ public:
void present(DriverBase& driver);
void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback) {
if (!callback) {
mFrameScheduled.handler = nullptr;
mFrameScheduled.callback.reset();
return;
}
mFrameScheduled.handler = handler;
mFrameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
frameScheduled.handler = handler;
frameScheduled.callback = std::move(callback);
}
private:
@@ -89,8 +83,8 @@ private:
struct {
CallbackHandler* handler = nullptr;
std::shared_ptr<FrameScheduledCallback> callback;
} mFrameScheduled;
FrameScheduledCallback callback;
} frameScheduled;
};
} // namespace filament::backend
@@ -41,45 +41,6 @@ namespace filament::backend {
textureFormat == wgpu::TextureFormat::Depth32FloatStencil8;
}
[[nodiscard]] constexpr bool isUIntFormat(const wgpu::TextureFormat format) {
// see https://www.w3.org/TR/webgpu/#texture-formats
// and https://www.w3.org/TR/webgpu/#texture-format-caps
switch (format) {
case wgpu::TextureFormat::R8Uint:
case wgpu::TextureFormat::R16Uint:
case wgpu::TextureFormat::RG8Uint:
case wgpu::TextureFormat::R32Uint:
case wgpu::TextureFormat::RG16Uint:
case wgpu::TextureFormat::RGBA8Uint:
case wgpu::TextureFormat::RGB10A2Uint:
case wgpu::TextureFormat::RG32Uint:
case wgpu::TextureFormat::RGBA16Uint:
case wgpu::TextureFormat::RGBA32Uint:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr bool isIntFormat(const wgpu::TextureFormat format) {
// see https://www.w3.org/TR/webgpu/#texture-formats
// and https://www.w3.org/TR/webgpu/#texture-format-caps
switch (format) {
case wgpu::TextureFormat::R8Sint:
case wgpu::TextureFormat::R16Sint:
case wgpu::TextureFormat::RG8Sint:
case wgpu::TextureFormat::R32Sint:
case wgpu::TextureFormat::RG16Sint:
case wgpu::TextureFormat::RGBA8Sint:
case wgpu::TextureFormat::RG32Sint:
case wgpu::TextureFormat::RGBA16Sint:
case wgpu::TextureFormat::RGBA32Sint:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr std::string_view toString(const PixelDataFormat format) {
switch (format) {
case PixelDataFormat::R: return "R";
@@ -56,8 +56,6 @@
void* library { nullptr };
} g_x11;
} // namespace
#elif defined(FILAMENT_SUPPORTS_OSMESA)
// No need
#else
#error Not a supported Linux or FeeBSD + WebGPU platform
#endif
@@ -52,14 +52,15 @@ std::string processPlaceholderTemplate(std::string_view const& stringTemplate,
"Malformed source with missing suffix to placeholder");
const std::string_view placeholderName{ std::string_view(sourceData + positionOfPlaceholder,
positionAfterPlaceholder - positionOfPlaceholder) };
if (const auto iter{ valueByPlaceholderName.find(placeholderName) };
iter != valueByPlaceholderName.end()) {
iter == valueByPlaceholderName.end()) {
// wrapping placeholderName in a std::string to null terminate the C string....
PANIC_POSTCONDITION("Found placeholder '%s' in template, but this is not present in "
"valueByPlaceholderName",
std::string{ placeholderName }.data());
} else {
const std::string_view value{ iter->second };
out << value;
} else {
// It's ok to not replace a template item. We assume replacement can take multiple passes.
out << placeholderPrefix << placeholderName << placeholderSuffix;
}
// update the cursor for after the placeholder...
positionCursorInTemplateString = positionAfterPlaceholder + placeholderSuffix.size();
+8 -18
View File
@@ -23,17 +23,13 @@
namespace test {
TestArguments parseArguments(int argc, char* argv[]) {
TestArguments arguments = {};
arguments.backend = Backend::OPENGL;
Backend parseArgumentsForBackend(int argc, char* argv[]) {
Backend backend = Backend::OPENGL;
// The first colon in OPTSTR turns on silent error reporting. This is important, as the
// arguments may also contain gtest parameters we don't know about.
static constexpr const char* OPTSTR = ":a:kc";
static constexpr const char* OPTSTR = ":a:";
static const struct option OPTIONS[] = {
{ "api", required_argument, nullptr, 'a' },
{ "headless_only", no_argument, nullptr, 'k' },
{ "ci", no_argument, nullptr, 'c' },
{ nullptr, 0, nullptr, 0 } // termination of the option list
};
@@ -45,13 +41,13 @@ TestArguments parseArguments(int argc, char* argv[]) {
switch (opt) {
case 'a':
if (arg == "opengl") {
arguments.backend = Backend::OPENGL;
backend = Backend::OPENGL;
} else if (arg == "vulkan") {
arguments.backend = Backend::VULKAN;
backend = Backend::VULKAN;
} else if (arg == "metal") {
arguments.backend = Backend::METAL;
backend = Backend::METAL;
} else if (arg == "webgpu") {
arguments.backend = Backend::WEBGPU;
backend = Backend::WEBGPU;
} else {
std::cerr << "Unrecognized target API. Must be 'opengl'|'vulkan'|'metal'|'webgpu'."
<< std::endl
@@ -59,16 +55,10 @@ TestArguments parseArguments(int argc, char* argv[]) {
<< std::endl;
}
break;
case 'k':
arguments.headlessOnly = true;
break;
case 'c':
arguments.isContinuousIntegration = true;
break;
}
}
return arguments;
return backend;
}
} // namespace test
+8 -12
View File
@@ -64,7 +64,6 @@ BackendTest::BackendTest() : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE,
CONFIG_COMMAND_BUFFERS_SIZE, /*mPaused=*/false) {
initializeDriver();
mImageExpectations.emplace(getDriverApi());
mCleanup = std::make_unique<Cleanup>(getDriverApi());
NativeView nativeView = getNativeView();
mScreenSize = {nativeView.width, nativeView.height};
}
@@ -73,15 +72,12 @@ BackendTest::~BackendTest() {
// Ensure all graphics commands and callbacks are finished.
flushAndWait();
mImageExpectations->evaluate();
// Note: Don't terminate the driver for OpenGL, as it wipes away the context and removes the buffer from the screen.
if (sBackend != Backend::OPENGL) {
driver->terminate();
delete driver;
}
// We need to clean up all the handles before the driver terminates. Note that this should
// happen before the above flushAndWait, which will complete readPixels.
mCleanup.reset();
// This flush and wait will execute all the destroy commands.
flushAndWait();
driver->terminate();
delete driver;
recordFailedImages();
}
@@ -111,12 +107,12 @@ void BackendTest::flushAndWait() {
getDriver().purge();
}
Handle<HwSwapChain> BackendTest::createSwapChain(uint64_t flags) {
Handle<HwSwapChain> BackendTest::createSwapChain() {
const NativeView& view = getNativeView();
if (!view.ptr) {
return getDriverApi().createSwapChainHeadless(view.width, view.height, flags);
return getDriverApi().createSwapChainHeadless(view.width, view.height, 0);
}
return getDriverApi().createSwapChain(view.ptr, flags);
return getDriverApi().createSwapChain(view.ptr, 0);
}
PipelineState BackendTest::getColorWritePipelineState() {
+2 -9
View File
@@ -28,7 +28,6 @@
#include "PlatformRunner.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
namespace test {
@@ -49,18 +48,15 @@ public:
static std::filesystem::path binaryDirectory();
protected:
BackendTest();
~BackendTest() override;
template<typename HandleType>
filament::backend::Handle<HandleType> addCleanup(filament::backend::Handle<HandleType> handle) {
return mCleanup->add(handle);
}
void initializeDriver();
void executeCommands();
void flushAndWait();
filament::backend::Handle<filament::backend::HwSwapChain> createSwapChain(uint64_t flags = 0);
filament::backend::Handle<filament::backend::HwSwapChain> createSwapChain();
static filament::backend::PipelineState getColorWritePipelineState();
@@ -81,9 +77,6 @@ protected:
static bool matchesEnvironment(Backend backend);
static bool matchesEnvironment(OperatingSystem operatingSystem);
std::unique_ptr<Cleanup> mCleanup;
private:
// Adds all the images that failed an ImageExpectation to the XML metadata for the current tests
// case. Add --gtest_output=xml as a command line argument to generate a test_detail.xml file in
+2
View File
@@ -19,12 +19,14 @@
#include <vector>
#include <functional>
#include <optional>
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "backend/Handle.h"
#include "backend/DriverApiForward.h"
#include "BackendTest.h"
class RenderFrame {
public:
+2 -11
View File
@@ -35,15 +35,12 @@ enum class Backend : uint8_t {
NOOP = 5,
};
enum class OperatingSystem : uint8_t {
enum class OperatingSystem: uint8_t {
OTHER = 1,
// Also represents android phones.
LINUX = 2,
// Also represents iOS phones.
APPLE = 3,
CONTINUOUS_INTEGRATION = 4,
CI = 4,
// TODO: When tests support windows add it here.
};
@@ -76,17 +73,11 @@ void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile,
*/
int runTests();
struct TestArguments {
Backend backend;
bool headlessOnly = false;
bool isContinuousIntegration = false;
};
/**
* A utility method that can be invoked by test runners to parse arguments.
* Looks through the provided command-line arguments and finds any -a <backend> arguments.
*/
TestArguments parseArguments(int argc, char* argv[]);
Backend parseArgumentsForBackend(int argc, char* argv[]);
} // namespace test
-2
View File
@@ -19,8 +19,6 @@
#include "Lifetimes.h"
#include "private/filament/SamplerInterfaceBlock.h"
#include "private/backend/DriverApi.h"
#include "backend/DriverEnums.h"
namespace test {
+2 -2
View File
@@ -16,9 +16,9 @@
#include "SharedShaders.h"
#include "BackendTest.h"
#include "Shader.h"
#include "absl/strings/str_format.h"
#include "gtest/gtest.h"
namespace test {
@@ -252,4 +252,4 @@ std::string SharedShaders::getFragmentShaderText(FragmentShaderType fragment,
return fragmentText->withUniform(*uniformText);
}
} // namespace test
} // namespace test
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

+2 -7
View File
@@ -43,10 +43,7 @@ std::array<test::Backend, 2> const VALID_BACKENDS{
}// namespace
int main(int argc, char* argv[]) {
const auto arguments = test::parseArguments(argc, argv);
const auto backend = arguments.backend;
// Note that Linux is headless-only.
auto backend = test::parseArgumentsForBackend(argc, argv);
if (!std::any_of(VALID_BACKENDS.begin(), VALID_BACKENDS.end(),
[backend](test::Backend validBackend) { return backend == validBackend; })) {
@@ -54,8 +51,6 @@ int main(int argc, char* argv[]) {
return 1;
}
const auto operatingSystem = arguments.isContinuousIntegration ?
test::OperatingSystem::CONTINUOUS_INTEGRATION : test::OperatingSystem::LINUX;
test::initTests(backend, operatingSystem, false, argc, argv);
test::initTests(backend, test::OperatingSystem::LINUX, false, argc, argv);
return test::runTests();
}
+20 -39
View File
@@ -32,34 +32,29 @@ test::NativeView getNativeView() {
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property test::Backend backend;
@property bool headlessOnly;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
if (self.headlessOnly) {
nativeView.ptr = nullptr;
nativeView.width = test::WINDOW_WIDTH;
nativeView.height = test::WINDOW_HEIGHT;
} else {
NSView* view = [self createView];
switch (self.backend) {
case test::Backend::OPENGL:
nativeView.ptr = (void*) view;
break;
case test::Backend::METAL:
case test::Backend::VULKAN:
case test::Backend::WEBGPU:
case test::Backend::NOOP:
nativeView.ptr = (void*) view.layer;
break;
}
CGSize drawableSize = ((CAMetalLayer*) view.layer).drawableSize;
nativeView.width = static_cast<size_t>(drawableSize.width);
nativeView.height = static_cast<size_t>(drawableSize.height);
NSView* view = [self createView];
if (self.backend == test::Backend::OPENGL) {
nativeView.ptr = (void*) view;
}
if (self.backend == test::Backend::METAL) {
nativeView.ptr = (void*) view.layer;
}
if (self.backend == test::Backend::VULKAN) {
nativeView.ptr = (void*) view.layer;
}
if (self.backend == test::Backend::WEBGPU) {
nativeView.ptr = (void*) view.layer;
}
CGSize drawableSize = ((CAMetalLayer*) view.layer).drawableSize;
nativeView.width = static_cast<size_t>(drawableSize.width);
nativeView.height = static_cast<size_t>(drawableSize.height);
exit(test::runTests());
}
@@ -105,25 +100,11 @@ test::NativeView getNativeView() {
@end
int main(int argc, char* argv[]) {
const auto arguments = test::parseArguments(argc, argv);
const auto operatingSystem = arguments.isContinuousIntegration ?
test::OperatingSystem::CONTINUOUS_INTEGRATION : test::OperatingSystem::APPLE;
test::initTests(arguments.backend, operatingSystem, false, argc, argv);
NSApplication* app = [NSApplication sharedApplication];
auto backend = test::parseArgumentsForBackend(argc, argv);
test::initTests(backend, test::OperatingSystem::APPLE, false, argc, argv);
AppDelegate* delegate = [AppDelegate new];
delegate.backend = arguments.backend;
delegate.headlessOnly = arguments.headlessOnly;
delegate.backend = backend;
NSApplication* app = [NSApplication sharedApplication];
[app setDelegate:delegate];
if (arguments.headlessOnly) {
// In headless mode, we don't want to start the NSApplication event loop.
// Instead, we can manually "finish" launching the app, which will trigger the tests to run.
[app finishLaunching];
[delegate applicationDidFinishLaunching:nil];
// The line above calls exit(), so we should not reach here.
return 0;
}
[app run];
}
+4 -11
View File
@@ -145,10 +145,7 @@ static void createFaces(DriverApi& dapi, Handle<HwTexture> texture, int baseWidt
}
TEST_F(BlitTest, ColorMagnify) {
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "b/453757697");
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::VULKAN), "b/453777397");
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
auto& api = getDriverApi();
mCleanup.addPostCall([&]() { executeCommands(); });
@@ -161,7 +158,7 @@ TEST_F(BlitTest, ColorMagnify) {
constexpr int kNumLevels = 3;
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
auto swapChain = mCleanup.add(createSwapChain());
auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
api.makeCurrent(swapChain, swapChain);
// Create a source texture.
@@ -213,9 +210,6 @@ TEST_F(BlitTest, ColorMagnify) {
TEST_F(BlitTest, ColorMinify) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "b/453758045");
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::VULKAN), "b/453776623");
auto& api = getDriverApi();
mCleanup.addPostCall([&]() { executeCommands(); });
@@ -228,7 +222,7 @@ TEST_F(BlitTest, ColorMinify) {
constexpr int kNumLevels = 3;
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
auto swapChain = mCleanup.add(createSwapChain());
auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
api.makeCurrent(swapChain, swapChain);
// Create a source texture.
@@ -273,7 +267,6 @@ TEST_F(BlitTest, ColorMinify) {
TEST_F(BlitTest, ColorResolve) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "b/453758075");
auto& api = getDriverApi();
if (api.getFeatureLevel() < FeatureLevel::FEATURE_LEVEL_2) {
@@ -378,7 +371,7 @@ TEST_F(BlitTest, Blit2DTextureArray) {
constexpr int kDstTexLayer = 0;
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
auto swapChain = mCleanup.add(createSwapChain());
auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
api.makeCurrent(swapChain, swapChain);
// Create a source texture.
@@ -444,7 +437,7 @@ TEST_F(BlitTest, BlitRegion) {
constexpr int kDstLevel = 0;
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
auto swapChain = mCleanup.add(createSwapChain());
auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
api.makeCurrent(swapChain, swapChain);
// Create a source texture.
+13 -9
View File
@@ -38,16 +38,18 @@ const UniformBindingConfig kBindingConfig = {
class BufferUpdatesTest : public BackendTest {
public:
BufferUpdatesTest() = default;
BufferUpdatesTest() : mCleanup(getDriverApi()) {}
protected:
Shader createShader() {
return SharedShaders::makeShader(getDriverApi(), *mCleanup, ShaderRequest{
return SharedShaders::makeShader(getDriverApi(), mCleanup, ShaderRequest{
.mVertexType = VertexShaderType::Simple,
.mFragmentType = FragmentShaderType::SolidColored,
.mUniformType = ShaderUniformType::SimpleWithPadding
});
}
Cleanup mCleanup;
};
TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
@@ -61,14 +63,15 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
// executeCommands().
{
auto& api = getDriverApi();
Cleanup cleanup(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
Shader shader = createShader();
auto defaultRenderTarget = addCleanup(api.createDefaultRenderTarget());
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget());
// To test large buffers (which exercise a different code path) create an extra large
// buffer. Only the first 3 vertices will be used.
@@ -83,7 +86,7 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
// Create a uniform buffer.
// We use STATIC here, even though the buffer is updated, to force the Metal backend to use
// a GPU buffer, which is more interesting to test.
auto ubuffer = addCleanup(api.createBufferObject(sizeof(SimpleMaterialParams) + 64,
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(SimpleMaterialParams) + 64,
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
shader.bindUniform<SimpleMaterialParams>(api, ubuffer, kBindingConfig);
@@ -153,11 +156,12 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
auto& api = getDriverApi();
Cleanup cleanup(api);
const TrianglePrimitive triangle(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a program.
@@ -166,16 +170,16 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
// Create a uniform buffer.
// We use STATIC here, even though the buffer is updated, to force the Metal backend to use a
// GPU buffer, which is more interesting to test.
auto ubuffer = addCleanup(api.createBufferObject(sizeof(SimpleMaterialParams) + 64,
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(SimpleMaterialParams) + 64,
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
shader.bindUniform<SimpleMaterialParams>(api, ubuffer, kBindingConfig);
// Create a render target.
auto colorTexture = addCleanup(
auto colorTexture = cleanup.add(
api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, screenWidth(),
screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
auto renderTarget = addCleanup(api.createRenderTarget(TargetBufferFlags::COLOR0, screenWidth(),
auto renderTarget = cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR0, screenWidth(),
screenHeight(), 1, 1, { { colorTexture } }, {}, {}));
// Upload uniforms for the first triangle.
+11 -10
View File
@@ -25,23 +25,23 @@ using namespace filament::backend;
namespace test {
TEST_F(BackendTest, FrameScheduledCallback) {
SKIP_IF(Backend::WEBGPU, "Frame callbacks are unsupported in WebGPU");
auto& api = getDriverApi();
Cleanup cleanup(api);
// Create a SwapChain.
// In order for the frameScheduledCallback to be called, this must be a real SwapChain (not
// headless) so we obtain a drawable.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
Handle<HwRenderTarget> renderTarget = addCleanup(api.createDefaultRenderTarget());
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createDefaultRenderTarget());
int callbackCountA = 0;
api.setFrameScheduledCallback(
swapChain, nullptr,
[&callbackCountA](PresentCallable callable) {
callable();
callbackCountA++;
},
0);
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountA](PresentCallable callable) {
callable();
callbackCountA++;
}, 0);
// Render the first frame.
api.makeCurrent(swapChain, swapChain);
@@ -100,9 +100,10 @@ TEST_F(BackendTest, FrameCompletedCallback) {
SKIP_IF(Backend::WEBGPU, "Frame callbacks are unsupported in WebGPU");
auto& api = getDriverApi();
Cleanup cleanup(api);
// Create a SwapChain.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
int callbackCountA = 0;
api.setFrameCompletedCallback(swapChain, nullptr,
+14 -15
View File
@@ -99,28 +99,25 @@ struct MaterialParams {
// The problems are caused by both uploading and rendering into the same texture, since the OpenGL
// backend's readPixels does not work correctly with textures that have image data uploaded.
TEST_F(BackendTest, FeedbackLoops) {
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Image is unexpectedly darker, see b/417226296");
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL),
"OpenGL image is upside down due to readPixels failing for texture with uploaded image "
"data");
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::WEBGPU),
"OpenGL image is upside down due to readPixels failing for texture with uploaded image "
"data");
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "b/453756688");
SKIP_IF(Backend::VULKAN, "Image is unexpectedly darker, see b/453776546");
auto& api = getDriverApi();
Cleanup cleanup(api);
// The test is executed within this block scope to force destructors to run before
// executeCommands().
{
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH, false };
Shader shader = Shader(api, *mCleanup, ShaderConfig {
Shader shader = Shader(api, cleanup, ShaderConfig {
.vertexShader = fullscreenVs,
.fragmentShader = fullscreenFs,
.uniforms = {{"test_tex", DescriptorType::SAMPLER_2D_FLOAT, samplerInfo}, {"Params"}}
@@ -130,18 +127,18 @@ TEST_F(BackendTest, FeedbackLoops) {
// Create a texture.
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
Handle<HwTexture> const texture = addCleanup(api.createTexture(
Handle<HwTexture> const texture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage));
// Create ubo
auto ubuffer = addCleanup(api.createBufferObject(sizeof(MaterialParams),
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(MaterialParams),
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
// Create a RenderTarget for each miplevel.
Handle<HwRenderTarget> renderTargets[kNumLevels];
for (uint8_t level = 0; level < kNumLevels; level++) {
LOG(INFO) << "Level " << int(level) << ": " << (kTexWidth >> level) << "x" << (kTexHeight >> level);
renderTargets[level] = addCleanup(api.createRenderTarget( TargetBufferFlags::COLOR,
renderTargets[level] = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
kTexWidth >> level, kTexHeight >> level, 1, 0, { texture, level, 0 }, {}, {}));
}
@@ -175,13 +172,14 @@ TEST_F(BackendTest, FeedbackLoops) {
params.flags.discardStart = TargetBufferFlags::ALL;
state.rasterState.disableBlending();
for (int targetLevel = 1; targetLevel < kNumLevels; targetLevel++) {
Cleanup passCleanup(api);
const uint32_t sourceLevel = targetLevel - 1;
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
auto descriptorSet = shader.createDescriptorSet(api);
auto textureView = addCleanup(api.createTextureView(texture, sourceLevel, 1));
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, SamplerParams{
auto textureView = passCleanup.add(api.createTextureView(texture, sourceLevel, 1));
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
@@ -211,13 +209,14 @@ TEST_F(BackendTest, FeedbackLoops) {
state.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
state.rasterState.blendFunctionDstRGB = BlendFunction::ONE;
for (int targetLevel = kNumLevels - 2; targetLevel >= 0; targetLevel--) {
Cleanup passCleanup(api);
const uint32_t sourceLevel = targetLevel + 1;
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
auto descriptorSet = shader.createDescriptorSet(api);
auto textureView = addCleanup(api.createTextureView(texture, sourceLevel, 1));
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, SamplerParams{
auto textureView = passCleanup.add(api.createTextureView(texture, sourceLevel, 1));
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
+34 -28
View File
@@ -18,7 +18,6 @@
#include "BackendTestUtils.h"
#include "Lifetimes.h"
#include "PlatformRunner.h"
#include "Shader.h"
#include "SharedShaders.h"
#include "Skip.h"
@@ -233,7 +232,7 @@ public:
TEST_F(LoadImageTest, UpdateImage2D) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
SKIP_IF(Backend::VULKAN, "b/453776547");
FAIL_IF(Backend::VULKAN, "Multiple test cases crash, see b/417481434");
// All of these test cases should result in the same rendered image, and thus the same hash.
static const uint32_t expectedHash = 1875922935;
@@ -319,17 +318,19 @@ TEST_F(LoadImageTest, UpdateImage2D) {
// The test is executed within this block scope to force destructors to run before
// executeCommands().
for (const auto& t : testCases) {
Cleanup cleanup(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto defaultRenderTarget = addCleanup(api.createDefaultRenderTarget());
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget());
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, getSamplerFormat(t.textureFormat), Precision::HIGH, false };
std::string const fragment = getFormattedFragment(fragmentTemplate, t.textureFormat);
Shader shader(api, *mCleanup, ShaderConfig{
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = mVertexShader,
.fragmentShader= fragment,
.uniforms = {{"test_tex", DescriptorType::SAMPLER_2D_FLOAT, samplerInfo}}
@@ -337,7 +338,7 @@ TEST_F(LoadImageTest, UpdateImage2D) {
// Create a Texture.
auto usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
Handle<HwTexture> const texture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1,
Handle<HwTexture> const texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
t.textureFormat, 1, kTexSize, kTexSize, 1u, usage));
// Upload some pixel data.
@@ -361,7 +362,7 @@ TEST_F(LoadImageTest, UpdateImage2D) {
}
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, SamplerParams{
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
@@ -391,10 +392,8 @@ TEST_F(LoadImageTest, UpdateImage2D) {
}
TEST_F(LoadImageTest, UpdateImageSRGB) {
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "see b/453756688");
SKIP_IF(Backend::VULKAN, "b/454040142");
auto& api = getDriverApi();
Cleanup cleanup(api);
api.startCapture();
PixelDataFormat const pixelFormat = PixelDataFormat::RGBA;
@@ -402,22 +401,22 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
TextureFormat const textureFormat = TextureFormat::SRGB8_A8;
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto defaultRenderTarget = addCleanup(api.createDefaultRenderTarget());
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget());
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, getSamplerFormat(textureFormat), Precision::HIGH, false };
std::string const fragment = getFormattedFragment(fragmentTemplate, textureFormat);
Shader shader(api, *mCleanup, ShaderConfig{
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = mVertexShader, .fragmentShader = fragment, .uniforms = {{
"test_tex", DescriptorType::SAMPLER_2D_FLOAT, samplerInfo
}}});
// Create a texture.
Handle<HwTexture> const texture =
addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1, textureFormat, 1, kTexSize,
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, textureFormat, 1, kTexSize,
kTexSize, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE));
// Create image data.
@@ -447,7 +446,7 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
// Update samplers.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, SamplerParams{
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
@@ -478,6 +477,7 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
TEST_F(LoadImageTest, UpdateImageMipLevel) {
auto& api = getDriverApi();
Cleanup cleanup(api);
api.startCapture();
PixelDataFormat pixelFormat = PixelDataFormat::RGBA;
@@ -485,15 +485,15 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
TextureFormat textureFormat = TextureFormat::RGBA32F;
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto defaultRenderTarget = addCleanup(api.createDefaultRenderTarget());
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget());
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, getSamplerFormat(textureFormat), Precision::HIGH, false };
std::string const fragment = getFormattedFragment(fragmentUpdateImageMip, textureFormat);
Shader shader(api, *mCleanup, ShaderConfig {
Shader shader(api, cleanup, ShaderConfig {
.vertexShader = mVertexShader,
.fragmentShader = fragment,
.uniforms = {{"test_tex", DescriptorType::SAMPLER_2D_FLOAT, samplerInfo}}
@@ -503,7 +503,7 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
// Base level: 1024
// Level 1: 512 <-- upload data and sample from this level
// Level 2: 256
Handle<HwTexture> texture = addCleanup(
Handle<HwTexture> texture = cleanup.add(
api.createTexture(SamplerType::SAMPLER_2D, 3, textureFormat, 1, kDoubleTexSize,
kDoubleTexSize, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE));
@@ -513,7 +513,7 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
// Update samplers.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, SamplerParams{
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
@@ -545,8 +545,14 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
}
TEST_F(LoadImageTest, UpdateImage3D) {
SKIP_IF(Backend::VULKAN, "b/453776983");
FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Crashing when reading pixels without a redundant call to makeCurrent right before the"
"render pass. b/422798473");
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Checkerboard not drawn, possibly due to using wrong z value of 3d texture, "
"see b/417254499");
auto& api = getDriverApi();
Cleanup cleanup(api);
api.startCapture();
PixelDataFormat pixelFormat = PixelDataFormat::RGBA;
@@ -556,22 +562,22 @@ TEST_F(LoadImageTest, UpdateImage3D) {
TextureUsage usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT;
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto defaultRenderTarget = addCleanup(api.createDefaultRenderTarget());
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget());
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D_ARRAY, getSamplerFormat(textureFormat), Precision::HIGH, false };
std::string fragment = getFormattedFragment(fragmentUpdateImage3DTemplate, samplerType);
Shader shader(api, *mCleanup, ShaderConfig {
Shader shader(api, cleanup, ShaderConfig {
.vertexShader = mVertexShader,
.fragmentShader = fragment,
.uniforms = {{"test_tex", DescriptorType::SAMPLER_2D_ARRAY_FLOAT, samplerInfo}}
});
// Create a texture.
Handle<HwTexture> texture = addCleanup(api.createTexture(samplerType, 1,
Handle<HwTexture> texture = cleanup.add(api.createTexture(samplerType, 1,
textureFormat, 1, kTexSize, kTexSize, 4, usage));
// Create image data for all 4 layers.
@@ -596,9 +602,9 @@ TEST_F(LoadImageTest, UpdateImage3D) {
// Update samplers.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, SamplerParams{
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST });
api.updateDescriptorSetTexture(descriptorSet, 0, texture,
{ .filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST });
api.bindDescriptorSet(descriptorSet, 0, {});
+7 -6
View File
@@ -48,15 +48,16 @@ using namespace filament::backend;
TEST_F(BackendTest, MRT) {
DriverApi& api = getDriverApi();
Cleanup cleanup(api);
// The test is executed within this block scope to force destructors to run before
// executeCommands().
{
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
Shader shader(api, *mCleanup, ShaderConfig{
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = SharedShaders::getVertexShaderText(VertexShaderType::Noop,
ShaderUniformType::None),
.fragmentShader = fragment,
@@ -65,11 +66,11 @@ TEST_F(BackendTest, MRT) {
TrianglePrimitive triangle(api);
auto defaultRenderTarget = addCleanup(api.createDefaultRenderTarget());
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget());
// Create two Textures.
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
Handle<HwTexture> textureA = addCleanup(api.createTexture(
Handle<HwTexture> textureA = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, // target
1, // levels
TextureFormat::RGBA8, // format
@@ -78,7 +79,7 @@ TEST_F(BackendTest, MRT) {
screenHeight(), // height
1, // depth
usage)); // usage
Handle<HwTexture> textureB = addCleanup(api.createTexture(
Handle<HwTexture> textureB = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, // target
1, // levels
TextureFormat::RGBA8, // format
@@ -89,7 +90,7 @@ TEST_F(BackendTest, MRT) {
usage)); // usage
// Create a RenderTarget with two attachments.
Handle<HwRenderTarget> renderTarget = addCleanup(api.createRenderTarget(
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR0 | TargetBufferFlags::COLOR1,
// The width and height must match the width and height of the respective mip
// level (at least for OpenGL).
@@ -22,7 +22,6 @@
#include "SharedShaders.h"
#include "SharedShadersConstants.h"
#include "Skip.h"
#include "Workarounds.h"
#include <backend/BufferDescriptor.h>
#include <backend/DriverEnums.h>
@@ -56,7 +55,7 @@ protected:
auto colorTexture = cleanup.add(
api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1,
screenWidth(), screenHeight(), 1,
TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE TEXTURE_USAGE_READ_PIXELS));
TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE));
auto renderTarget = cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR0,
screenWidth(), screenHeight(), 1, 1, { { colorTexture } }, {}, {}));
return renderTarget;
@@ -91,27 +90,19 @@ protected:
return { vbih, vbh, ibh };
}
std::pair<Shader, DescriptorSetHandle> getSimpleShader(Cleanup& cleanup,
math::float4 const& color) {
Shader getSimpleShader(Cleanup& cleanup, math::float4 const& color) {
auto& api = getDriverApi();
Shader const shader = SharedShaders::makeShader(getDriverApi(), cleanup,
ShaderRequest{
.mVertexType = VertexShaderType::Simple,
.mFragmentType = FragmentShaderType::SolidColored,
.mUniformType = ShaderUniformType::Simple,
});
Shader const shader = SharedShaders::makeShader(getDriverApi(), cleanup, ShaderRequest{
.mVertexType = VertexShaderType::Simple,
.mFragmentType = FragmentShaderType::SolidColored,
.mUniformType = ShaderUniformType::Simple
});
auto descSet = shader.createDescriptorSet(api);
UniformBindingConfig uboBindingConfig = {
.descriptorSet = descSet,
};
auto const ubuffer = cleanup.add(api.createBufferObject(sizeof(SimpleMaterialParams),
BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC_BIT));
// This will bind the UBO to descSet and also bind descSet. But we also need to manually
// bind descset every begin/end frame.
shader.bindUniform<SimpleMaterialParams>(api, ubuffer, uboBindingConfig);
shader.bindUniform<SimpleMaterialParams>(api, ubuffer);
shader.uploadUniform(api, ubuffer, SimpleMaterialParams{ .color = color });
return { shader, descSet };
return shader;
}
void copyData(const void* data, size_t const size, size_t const offset,
@@ -131,9 +122,7 @@ protected:
int64_t const frame,
SwapChainHandle swapChain,
RenderTargetHandle renderTarget,
RenderPrimitiveHandle renderPrimitive,
DescriptorSetHandle descSet,
PipelineState const& state) {
RenderPrimitiveHandle renderPrimitive, PipelineState const& state) {
auto& api = getDriverApi();
RenderPassParams params = getClearColorRenderPass({0,0,0,1});
params.viewport = getFullViewport();
@@ -141,8 +130,6 @@ protected:
api.beginFrame(frame, 0, 0);
api.beginRenderPass(renderTarget, params);
api.bindPipeline(state);
// The binding index is always 0 for the simple shaders we're using.
api.bindDescriptorSet(descSet, 0, {});
api.bindRenderPrimitive(renderPrimitive);
api.draw2(0, count, 1);
api.endRenderPass();
@@ -160,11 +147,12 @@ protected:
SKIP_IF(Backend::WEBGPU, "HwMemoryMappedBuffer APIs not yet implemented");
auto& api = getDriverApi();
Cleanup cleanup(api);
auto const swapChain = addCleanup(createSwapChain());
auto const swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto const renderTarget = makeRenderTarget(*mCleanup);
auto const renderTarget = makeRenderTarget(cleanup);
constexpr std::array<math::float2, 3> vertices = {{
{-0.25f, -0.25f},
@@ -181,7 +169,7 @@ protected:
const uint32_t baseVertex = totalOffset / stride;
// Create a buffer large enough to hold the offset and the vertex data.
auto const bufferObject = addCleanup(api.createBufferObject(vertexDataSize + totalOffset,
auto const bufferObject = cleanup.add(api.createBufferObject(vertexDataSize + totalOffset,
BufferObjectBinding::VERTEX, BufferUsage::SHARED_WRITE_BIT));
int callbackExecuted = 0;
@@ -197,11 +185,11 @@ protected:
flushAndWait();
EXPECT_EQ(callbackExecuted, 1);
auto [shader, descset] = getSimpleShader(*mCleanup, color);
Shader const shader = getSimpleShader(cleanup, color);
auto [vbih, vbh, ibh] = setupGeometryBuffer(*mCleanup, 3, stride, baseVertex, bufferObject);
auto [vbih, vbh, ibh] = setupGeometryBuffer(cleanup, 3, stride, baseVertex, bufferObject);
auto const renderPrimitive = addCleanup(api.createRenderPrimitive(vbh, ibh, PrimitiveType::TRIANGLES));
auto const renderPrimitive = cleanup.add(api.createRenderPrimitive(vbh, ibh, PrimitiveType::TRIANGLES));
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
@@ -209,7 +197,7 @@ protected:
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = vbih;
render(3, 0, swapChain, renderTarget, renderPrimitive, descset, state);
render(3, 0, swapChain, renderTarget, renderPrimitive, state);
EXPECT_IMAGE(renderTarget, ScreenshotParams(screenWidth(), screenHeight(), screenshotName, 0));
}
@@ -219,9 +207,10 @@ TEST_F(MemoryMappedTest, MapCopyUnmap) {
SKIP_IF(Backend::WEBGPU, "HwMemoryMappedBuffer APIs not yet implemented");
auto& api = getDriverApi();
Cleanup cleanup(api);
// Create a buffer object.
BufferObjectHandle const bufferObject = addCleanup(api.createBufferObject(1024,
BufferObjectHandle const bufferObject = cleanup.add(api.createBufferObject(1024,
BufferObjectBinding::VERTEX, BufferUsage::DYNAMIC_BIT | BufferUsage::SHARED_WRITE_BIT));
// Map the buffer.
@@ -267,11 +256,12 @@ TEST_F(MemoryMappedTest, MultipleCopies) {
SKIP_IF(Backend::WEBGPU, "HwMemoryMappedBuffer APIs not yet implemented");
auto& api = getDriverApi();
Cleanup cleanup(api);
auto const swapChain = addCleanup(createSwapChain());
auto const swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto const renderTarget = makeRenderTarget(*mCleanup);
auto const renderTarget = makeRenderTarget(cleanup);
constexpr std::array<math::float2, 3> triangle1 = {{{-0.5f, 0.5f}, {-0.8f, 0.2f}, {-0.2f, 0.2f}}};
constexpr std::array<math::float2, 3> triangle2 = {{{0.5f, 0.5f}, {0.2f, 0.2f}, {0.8f, 0.2f}}};
@@ -279,7 +269,7 @@ TEST_F(MemoryMappedTest, MultipleCopies) {
constexpr size_t singleTriangleSize = triangle1.size() * sizeof(math::float2);
constexpr size_t totalDataSize = singleTriangleSize * 3;
auto const bufferObject = addCleanup(api.createBufferObject(totalDataSize,
auto const bufferObject = cleanup.add(api.createBufferObject(totalDataSize,
BufferObjectBinding::VERTEX, BufferUsage::SHARED_WRITE_BIT));
int callbacksExecuted = 0;
@@ -298,11 +288,11 @@ TEST_F(MemoryMappedTest, MultipleCopies) {
flushAndWait();
EXPECT_EQ(callbacksExecuted, 3);
auto [shader, descset] = getSimpleShader(*mCleanup, { 1, 1, 0, 1 });
Shader const shader = getSimpleShader(cleanup, { 1, 1, 0, 1 });
auto [vbih, vbh, ibh] = setupGeometryBuffer(*mCleanup, 9, sizeof(math::float2), 0, bufferObject);
auto [vbih, vbh, ibh] = setupGeometryBuffer(cleanup, 9, sizeof(math::float2), 0, bufferObject);
auto const renderPrimitive = addCleanup(api.createRenderPrimitive(vbh, ibh, PrimitiveType::TRIANGLES));
auto const renderPrimitive = cleanup.add(api.createRenderPrimitive(vbh, ibh, PrimitiveType::TRIANGLES));
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
@@ -310,7 +300,7 @@ TEST_F(MemoryMappedTest, MultipleCopies) {
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = vbih;
render(9, 0, swapChain, renderTarget, renderPrimitive, descset, state);
render(9, 0, swapChain, renderTarget, renderPrimitive, state);
EXPECT_IMAGE(renderTarget, ScreenshotParams(screenWidth(), screenHeight(), "MultipleCopies", 0));
}
@@ -319,11 +309,12 @@ TEST_F(MemoryMappedTest, UpdatePartial) {
SKIP_IF(Backend::WEBGPU, "HwMemoryMappedBuffer APIs not yet implemented");
auto& api = getDriverApi();
Cleanup cleanup(api);
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto renderTarget = makeRenderTarget(*mCleanup);
auto renderTarget = makeRenderTarget(cleanup);
constexpr std::array<math::float2, 3> triangle1 = {{{-0.5f, 0.5f}, {-0.8f, 0.2f}, {-0.2f, 0.2f}}};
constexpr std::array<math::float2, 3> triangle2 = {{{0.5f, 0.5f}, {0.2f, 0.2f}, {0.8f, 0.2f}}};
@@ -331,7 +322,7 @@ TEST_F(MemoryMappedTest, UpdatePartial) {
constexpr size_t singleTriangleSize = triangle1.size() * sizeof(math::float2);
constexpr size_t totalDataSize = singleTriangleSize * 3;
auto bufferObject = addCleanup(api.createBufferObject(totalDataSize,
auto bufferObject = cleanup.add(api.createBufferObject(totalDataSize,
BufferObjectBinding::VERTEX, BufferUsage::SHARED_WRITE_BIT));
int callbacksExecuted = 0;
@@ -354,11 +345,11 @@ TEST_F(MemoryMappedTest, UpdatePartial) {
}
auto [shader, descset] = getSimpleShader(*mCleanup, { 1, 1, 0, 1 });
Shader const shader = getSimpleShader(cleanup, { 1, 1, 0, 1 });
auto [vbih, vbh, ibh] = setupGeometryBuffer(*mCleanup, 9, sizeof(math::float2), 0, bufferObject);
auto [vbih, vbh, ibh] = setupGeometryBuffer(cleanup, 9, sizeof(math::float2), 0, bufferObject);
auto renderPrimitive = addCleanup(api.createRenderPrimitive(vbh, ibh, PrimitiveType::TRIANGLES));
auto renderPrimitive = cleanup.add(api.createRenderPrimitive(vbh, ibh, PrimitiveType::TRIANGLES));
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
@@ -366,7 +357,7 @@ TEST_F(MemoryMappedTest, UpdatePartial) {
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = vbih;
render(9, 0, swapChain, renderTarget, renderPrimitive, descset, state);
render(9, 0, swapChain, renderTarget, renderPrimitive, state);
EXPECT_IMAGE(renderTarget, ScreenshotParams(screenWidth(), screenHeight(), "UpdatePartial_before", 0));
@@ -389,7 +380,7 @@ TEST_F(MemoryMappedTest, UpdatePartial) {
api.makeCurrent(swapChain, swapChain);
// Second render, after update
render(9, 1, swapChain, renderTarget, renderPrimitive, descset, state);
render(9, 1, swapChain, renderTarget, renderPrimitive, state);
EXPECT_IMAGE(renderTarget, ScreenshotParams(screenWidth(), screenHeight(), "UpdatePartial_after", 0));
}
+11 -10
View File
@@ -57,15 +57,16 @@ using namespace filament::backend;
TEST_F(BackendTest, TextureViewLod) {
auto& api = getDriverApi();
api.startCapture(0);
Cleanup cleanup(api);
// The test is executed within this block scope to force destructors to run before
// executeCommands().
{
// Create a SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
Shader whiteShader = SharedShaders::makeShader(api, *mCleanup, ShaderRequest {
Shader whiteShader = SharedShaders::makeShader(api, cleanup, ShaderRequest {
.mVertexType = VertexShaderType::Textured,
.mFragmentType = FragmentShaderType::White,
.mUniformType = ShaderUniformType::Sampler
@@ -77,7 +78,7 @@ TEST_F(BackendTest, TextureViewLod) {
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo {
"backend_test", "sib_tex", 0,
SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH, false };
Shader texturedShader(api, *mCleanup, ShaderConfig {
Shader texturedShader(api, cleanup, ShaderConfig {
.vertexShader = vertexShader,
.fragmentShader = fragmentTexturedLod,
.uniforms = {{
@@ -92,7 +93,7 @@ TEST_F(BackendTest, TextureViewLod) {
// Level 3: 16x16 (yellow)
const size_t kTextureSize = 128;
const size_t kMipLevels = 4;
Handle<HwTexture> texture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D,
Handle<HwTexture> texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kMipLevels, TextureFormat::RGBA8, 1, kTextureSize, kTextureSize, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT
| TextureUsage::UPLOADABLE));
@@ -129,11 +130,11 @@ TEST_F(BackendTest, TextureViewLod) {
// Level 1: 64x64 (green) <-- base
// Level 2: 32x32 (blue) <--- white triangle rendered
// Level 3: 16x16 (yellow) <-- max
auto texture13 = addCleanup(api.createTextureView(texture, 1, 3));
auto texture13 = cleanup.add(api.createTextureView(texture, 1, 3));
// Render a white triangle into level 2.
// We specify mip level 2, because minMaxLevels has no effect when rendering into a texture.
Handle<HwRenderTarget> renderTarget = addCleanup(api.createRenderTarget(
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR, 32, 32, 1, 0,
{texture, 2 /* level */, 0 /* layer */}, {}, {}));
{
@@ -153,7 +154,7 @@ TEST_F(BackendTest, TextureViewLod) {
}
backend::Handle<HwRenderTarget> defaultRenderTarget =
addCleanup(api.createDefaultRenderTarget());
cleanup.add(api.createDefaultRenderTarget());
PipelineState state = getColorWritePipelineState();
texturedShader.addProgramToPipelineState(state);
@@ -162,7 +163,7 @@ TEST_F(BackendTest, TextureViewLod) {
params.viewport = getFullViewport();
DescriptorSetHandle descriptorSet13 = texturedShader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet13, 0, texture13, SamplerParams{
api.updateDescriptorSetTexture(descriptorSet13, 0, texture13, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
@@ -181,10 +182,10 @@ TEST_F(BackendTest, TextureViewLod) {
api.endRenderPass();
// Adjust the base mip to 2.
auto texture22 = addCleanup(api.createTextureView(texture, 2, 2));
auto texture22 = cleanup.add(api.createTextureView(texture, 2, 2));
DescriptorSetHandle descriptorSet22 = texturedShader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet22, 0, texture22, SamplerParams{
api.updateDescriptorSetTexture(descriptorSet22, 0, texture22, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
@@ -63,18 +63,19 @@ TEST_F(BackendTest, MissingRequiredAttributes) {
// executeCommands().
{
DriverApi& api = getDriverApi();
Cleanup cleanup(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a program.
Shader shader(api, *mCleanup, ShaderConfig{
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = vertex,
.fragmentShader = SharedShaders::getFragmentShaderText(FragmentShaderType::White,
ShaderUniformType::None),
});
auto defaultRenderTarget = addCleanup(api.createDefaultRenderTarget());
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget());
TrianglePrimitive triangle(api);
@@ -1,103 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "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

Some files were not shown because too many files have changed in this diff Show More