Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd073119d2 |
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+2
-2
@@ -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,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.
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -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
@@ -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', '~> 1.66.1'
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.66.0'
|
||||
</code></pre>
|
||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||
<ul>
|
||||
|
||||
@@ -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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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.
Executable
+17
@@ -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."
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -68,5 +68,4 @@ id<MTLTexture> createTextureViewWithSingleSlice(id<MTLTexture> texture, NSUInteg
|
||||
slices:NSMakeRange(slice, 1)];
|
||||
}
|
||||
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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>() };
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
#include "Lifetimes.h"
|
||||
#include "private/filament/SamplerInterfaceBlock.h"
|
||||
#include "private/backend/DriverApi.h"
|
||||
#include "backend/DriverEnums.h"
|
||||
|
||||
namespace test {
|
||||
|
||||
|
||||
@@ -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 |
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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, {});
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user