Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d105dc01b1 |
@@ -203,23 +203,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>
|
||||
|
||||
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
@@ -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,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.
|
||||
*/
|
||||
|
||||
#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,
|
||||
};
|
||||
uint64_t pad[18];
|
||||
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,47 +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 {
|
||||
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;
|
||||
|
||||
@@ -317,35 +317,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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -446,6 +446,7 @@ void WebGPUDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t wi
|
||||
void WebGPUDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vertexBufferInfoHandle,
|
||||
const uint8_t bufferCount, const uint8_t attributeCount, const AttributeArray attributes,
|
||||
utils::ImmutableCString&& tag) {
|
||||
// Hello world! This is a test for pushing branch to Github
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
constructHandle<WebGPUVertexBufferInfo>(vertexBufferInfoHandle, bufferCount, attributeCount,
|
||||
attributes, mDeviceLimits);
|
||||
@@ -841,10 +842,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 +1251,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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,13 @@ 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.
|
||||
};
|
||||
|
||||
|
||||
@@ -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 |
@@ -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(); });
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(createSwapChain());
|
||||
|
||||
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).
|
||||
|
||||
@@ -160,11 +160,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 +182,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 +198,11 @@ protected:
|
||||
flushAndWait();
|
||||
EXPECT_EQ(callbackExecuted, 1);
|
||||
|
||||
auto [shader, descset] = getSimpleShader(*mCleanup, color);
|
||||
auto [shader, descset] = 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);
|
||||
@@ -219,9 +220,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 +269,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 +282,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 +301,11 @@ TEST_F(MemoryMappedTest, MultipleCopies) {
|
||||
flushAndWait();
|
||||
EXPECT_EQ(callbacksExecuted, 3);
|
||||
|
||||
auto [shader, descset] = getSimpleShader(*mCleanup, { 1, 1, 0, 1 });
|
||||
auto [shader, descset] = 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);
|
||||
@@ -319,11 +322,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 +335,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 +358,11 @@ TEST_F(MemoryMappedTest, UpdatePartial) {
|
||||
}
|
||||
|
||||
|
||||
auto [shader, descset] = getSimpleShader(*mCleanup, { 1, 1, 0, 1 });
|
||||
auto [shader, descset] = 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);
|
||||
|
||||
@@ -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
|
||||
@@ -104,8 +104,7 @@ void initPushConstants() {
|
||||
}
|
||||
|
||||
TEST_F(BackendTest, PushConstants) {
|
||||
SKIP_IF(Backend::OPENGL, "see b/453757504");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::VULKAN), "see b/453776664");
|
||||
SKIP_IF(Backend::OPENGL, "Push constants not supported on OpenGL");
|
||||
SKIP_IF(Backend::WEBGPU, "Push constants not supported on WebGPU");
|
||||
|
||||
initPushConstants();
|
||||
@@ -113,21 +112,22 @@ TEST_F(BackendTest, PushConstants) {
|
||||
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);
|
||||
|
||||
// Create a program.
|
||||
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
|
||||
Program p =
|
||||
shaderGen.getProgramWithPushConstants(api, { gVertConstants, gFragConstants, {} });
|
||||
ProgramHandle program = addCleanup(api.createProgram(std::move(p)));
|
||||
ProgramHandle program = cleanup.add(api.createProgram(std::move(p)));
|
||||
|
||||
Handle<HwRenderTarget> renderTarget = addCleanup(api.createDefaultRenderTarget());
|
||||
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createDefaultRenderTarget());
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
|
||||
|
||||
@@ -256,15 +256,16 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
TestCase const testCases[] = { t0, t2, t3, t4, t5, t6, t7, t8 };
|
||||
|
||||
DriverApi& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
std::string vertexShader = SharedShaders::getVertexShaderText(VertexShaderType::Noop,
|
||||
ShaderUniformType::None);
|
||||
Shader floatShader(api, *mCleanup, ShaderConfig{
|
||||
Shader floatShader(api, cleanup, ShaderConfig{
|
||||
.vertexShader = vertexShader,
|
||||
.fragmentShader = fragmentFloat,
|
||||
.uniforms = {}
|
||||
});
|
||||
Shader uintShader(api, *mCleanup, ShaderConfig{
|
||||
Shader uintShader(api, cleanup, ShaderConfig{
|
||||
.vertexShader = vertexShader,
|
||||
.fragmentShader = fragmentUint,
|
||||
.uniforms = {}
|
||||
@@ -274,9 +275,9 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
// Create a platform-specific SwapChain and make it current.
|
||||
Handle<HwSwapChain> swapChain;
|
||||
if (t.useDefaultRT) {
|
||||
swapChain = addCleanup(createSwapChain());
|
||||
swapChain = cleanup.add(createSwapChain());
|
||||
} else {
|
||||
swapChain = addCleanup(api.createSwapChainHeadless(t.getRenderTargetSize(),
|
||||
swapChain = cleanup.add(api.createSwapChainHeadless(t.getRenderTargetSize(),
|
||||
t.getRenderTargetSize(), 0));
|
||||
}
|
||||
|
||||
@@ -284,7 +285,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
|
||||
// Create a Texture and RenderTarget to render into.
|
||||
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
|
||||
Handle<HwTexture> const texture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
Handle<HwTexture> const texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
t.mipLevels, t.textureFormat, 1, renderTargetBaseSize, renderTargetBaseSize, 1,
|
||||
usage));
|
||||
|
||||
@@ -292,11 +293,11 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
if (t.useDefaultRT) {
|
||||
// The width and height must match the width and height of the respective mip
|
||||
// level (at least for OpenGL).
|
||||
renderTarget = addCleanup(api.createDefaultRenderTarget());
|
||||
renderTarget = cleanup.add(api.createDefaultRenderTarget());
|
||||
} else {
|
||||
// The width and height must match the width and height of the respective mip
|
||||
// level (at least for OpenGL).
|
||||
renderTarget = addCleanup(api.createRenderTarget(
|
||||
renderTarget = cleanup.add(api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR, t.getRenderTargetSize(),
|
||||
t.getRenderTargetSize(), t.samples, 0, {{ texture, uint8_t(t.mipLevel) }}, {},
|
||||
{}));
|
||||
@@ -329,10 +330,11 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
api.endRenderPass();
|
||||
|
||||
if (t.mipLevel > 0) {
|
||||
Cleanup localCleanup(api);
|
||||
// Render red to the first mip level to check that the backend is actually reading the
|
||||
// correct mip.
|
||||
RenderPassParams p = params;
|
||||
Handle<HwRenderTarget> mipLevelOneRT = addCleanup(api.createRenderTarget(
|
||||
Handle<HwRenderTarget> mipLevelOneRT = localCleanup.add(api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR, renderTargetBaseSize, renderTargetBaseSize, 1, 0,
|
||||
{{ texture }}, {}, {}));
|
||||
p.clearColor = { 1.f, 0.f, 0.f, 1.f };
|
||||
@@ -380,12 +382,13 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
|
||||
const int iterationCount = 100;
|
||||
|
||||
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);
|
||||
|
||||
Shader shader = SharedShaders::makeShader(api, *mCleanup, ShaderRequest{
|
||||
Shader shader = SharedShaders::makeShader(api, cleanup, ShaderRequest{
|
||||
.mVertexType = VertexShaderType::Noop,
|
||||
.mFragmentType = FragmentShaderType::White,
|
||||
.mUniformType = ShaderUniformType::None
|
||||
@@ -393,7 +396,7 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
|
||||
|
||||
// Create a Texture and RenderTarget to render into.
|
||||
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
|
||||
Handle<HwTexture> texture = addCleanup(api.createTexture(
|
||||
Handle<HwTexture> texture = cleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, // target
|
||||
1, // levels
|
||||
TextureFormat::RGBA8, // format
|
||||
@@ -403,7 +406,7 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
|
||||
1, // depth
|
||||
usage)); // usage
|
||||
|
||||
Handle<HwRenderTarget> renderTarget = addCleanup(api.createRenderTarget(
|
||||
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR,
|
||||
renderTargetSize, // width
|
||||
renderTargetSize, // height
|
||||
|
||||
@@ -47,23 +47,24 @@ Shader createShader(DriverApi& api, Cleanup& cleanup, Backend backend) {
|
||||
// Rendering an external image without setting any data should not crash.
|
||||
TEST_F(BackendTest, RenderExternalImageWithoutSet) {
|
||||
SKIP_IF(Backend::METAL, "External images aren't supported in Metal");
|
||||
SKIP_IF(Backend::VULKAN, "b/453776730");
|
||||
SKIP_IF(Backend::VULKAN, "External images aren't supported in Vulkan");
|
||||
SKIP_IF(Backend::WEBGPU, "External images aren't supported in WebGPU");
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
|
||||
auto swapChain = addCleanup(createSwapChain());
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
|
||||
Shader shader = createShader(api, *mCleanup, sBackend);
|
||||
Shader shader = createShader(api, cleanup, sBackend);
|
||||
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = addCleanup(
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = cleanup.add(
|
||||
api.createDefaultRenderTarget());
|
||||
|
||||
// Create a texture that will be backed by an external image.
|
||||
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
|
||||
const NativeView& view = getNativeView();
|
||||
backend::Handle<HwTexture> texture = addCleanup(api.createTexture(
|
||||
backend::Handle<HwTexture> texture = cleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_EXTERNAL, // target
|
||||
1, // levels
|
||||
TextureFormat::RGBA8, // format
|
||||
@@ -110,19 +111,19 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
|
||||
|
||||
TEST_F(BackendTest, RenderExternalImage) {
|
||||
SKIP_IF(Backend::METAL, "External images aren't supported in Metal");
|
||||
SKIP_IF(Backend::VULKAN, "b/453777319");
|
||||
SKIP_IF(Backend::VULKAN, "External images aren't supported in Vulkan");
|
||||
SKIP_IF(Backend::WEBGPU, "External images aren't supported in WebGPU");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "b/453758594");
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
|
||||
auto swapChain = addCleanup(createSwapChain());
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
|
||||
Shader shader = createShader(api, *mCleanup, sBackend);
|
||||
Shader shader = createShader(api, cleanup, sBackend);
|
||||
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
||||
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = addCleanup(
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = cleanup.add(
|
||||
api.createDefaultRenderTarget());
|
||||
|
||||
// require users to create two Filament textures and have two material parameters
|
||||
@@ -169,7 +170,7 @@ TEST_F(BackendTest, RenderExternalImage) {
|
||||
|
||||
api.setupExternalImage(pixBuffer);
|
||||
backend::Handle<HwTexture> texture =
|
||||
addCleanup(api.createTextureExternalImage(SamplerType::SAMPLER_EXTERNAL,
|
||||
cleanup.add(api.createTextureExternalImage(SamplerType::SAMPLER_EXTERNAL,
|
||||
TextureFormat::RGBA8, 1024, 1024, usage, pixBuffer));
|
||||
|
||||
// We're now free to release the buffer.
|
||||
|
||||
@@ -44,6 +44,7 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
constexpr int kSrcRtHeight = kSrcTexHeight >> kSrcLevel;
|
||||
|
||||
api.startCapture(0);
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// color texture/RT (mip level 1) 512x512 depth texture (mip level 0) 512x512
|
||||
// +----------------------------------------+ +------------------------------------------+
|
||||
@@ -69,10 +70,10 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
// executeCommands().
|
||||
{
|
||||
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
|
||||
auto swapChain = addCleanup(createSwapChain());
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
Shader shader = SharedShaders::makeShader(api, *mCleanup,
|
||||
Shader shader = SharedShaders::makeShader(api, cleanup,
|
||||
ShaderRequest{
|
||||
.mVertexType = VertexShaderType::Noop,
|
||||
.mFragmentType = FragmentShaderType::White,
|
||||
@@ -80,10 +81,10 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
});
|
||||
|
||||
// Create source color and depth textures.
|
||||
Handle<HwTexture> srcTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
Handle<HwTexture> srcTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
|
||||
Handle<HwTexture> depthTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
Handle<HwTexture> depthTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::DEPTH16, 1, 512, 512, 1, TextureUsage::DEPTH_ATTACHMENT));
|
||||
|
||||
// Render into the bottom-left quarter of the texture.
|
||||
@@ -102,7 +103,7 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
|
||||
// We purposely set the render target width and height to smaller than the texture, to check
|
||||
// that this case is handled correctly.
|
||||
Handle<HwRenderTarget> rt = addCleanup(api.createRenderTarget(
|
||||
Handle<HwRenderTarget> rt = cleanup.add(api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, kSrcRtHeight, kSrcRtHeight, 1,
|
||||
1, { srcTexture, kSrcLevel, 0 }, { depthTexture, 0, 0 }, {}));
|
||||
|
||||
@@ -142,22 +143,23 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
|
||||
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. We don't really use it so the res doesn't matter.
|
||||
auto swapChain = addCleanup(createSwapChain());
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
Shader shader = SharedShaders::makeShader(api, *mCleanup, ShaderRequest{
|
||||
Shader shader = SharedShaders::makeShader(api, cleanup, ShaderRequest{
|
||||
.mVertexType = VertexShaderType::Noop,
|
||||
.mFragmentType = FragmentShaderType::White,
|
||||
.mUniformType = ShaderUniformType::None,
|
||||
});
|
||||
|
||||
// Create a source color textures.
|
||||
Handle<HwTexture> srcTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
Handle<HwTexture> srcTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 512, 512, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
|
||||
|
||||
@@ -181,7 +183,7 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
|
||||
Viewport scissor = {0, 0, (uint32_t)std::numeric_limits<int32_t>::max(),
|
||||
(uint32_t)std::numeric_limits<int32_t>::max()};
|
||||
|
||||
Handle<HwRenderTarget> renderTarget = addCleanup(api.createRenderTarget(
|
||||
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR, 512, 512, 1, 1,
|
||||
{srcTexture, 0, 0}, {}, {}));
|
||||
|
||||
|
||||
@@ -38,17 +38,18 @@ public:
|
||||
|
||||
Handle <HwSwapChain> mSwapChain;
|
||||
ProgramHandle mProgram;
|
||||
Cleanup mCleanup;
|
||||
|
||||
BasicStencilBufferTest() = default;
|
||||
BasicStencilBufferTest() : mCleanup(getDriverApi()) {}
|
||||
|
||||
void SetUp() override {
|
||||
auto& api = getDriverApi();
|
||||
|
||||
// Create a platform-specific SwapChain and make it current.
|
||||
mSwapChain = addCleanup(createSwapChain());
|
||||
mSwapChain = mCleanup.add(createSwapChain());
|
||||
api.makeCurrent(mSwapChain, mSwapChain);
|
||||
|
||||
Shader shader = SharedShaders::makeShader(api, *mCleanup, ShaderRequest{
|
||||
Shader shader = SharedShaders::makeShader(api, mCleanup, ShaderRequest{
|
||||
.mVertexType = VertexShaderType::Noop,
|
||||
.mFragmentType = FragmentShaderType::White,
|
||||
.mUniformType = ShaderUniformType::None
|
||||
@@ -126,18 +127,19 @@ public:
|
||||
|
||||
TEST_F(BasicStencilBufferTest, StencilBuffer) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
SKIP_IF(Backend::VULKAN, "b/453776821");
|
||||
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Stencil not supported, see b/417230776");
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// Create two textures: a color and a stencil, and an associated RenderTarget.
|
||||
auto colorTexture =
|
||||
addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1,
|
||||
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1,
|
||||
screenWidth(), screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT));
|
||||
auto stencilTexture =
|
||||
addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::STENCIL8, 1,
|
||||
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::STENCIL8, 1,
|
||||
screenWidth(), screenHeight(), 1, TextureUsage::STENCIL_ATTACHMENT));
|
||||
auto renderTarget = addCleanup(api.createRenderTarget(TargetBufferFlags::COLOR0 |
|
||||
auto renderTarget = cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR0 |
|
||||
TargetBufferFlags::STENCIL,
|
||||
screenWidth(), screenHeight(), 1, 0, { { colorTexture } }, {}, { { stencilTexture } }));
|
||||
|
||||
@@ -152,17 +154,19 @@ TEST_F(BasicStencilBufferTest, StencilBuffer) {
|
||||
|
||||
TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
SKIP_IF(Backend::VULKAN, "b/453776965");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Stencil not supported, see b/417230776");
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// Create two textures: a color and a stencil, and an associated RenderTarget.
|
||||
auto colorTexture =
|
||||
addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1,
|
||||
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1,
|
||||
screenWidth(), screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT));
|
||||
auto depthStencilTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
auto depthStencilTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::DEPTH24_STENCIL8, 1, screenWidth(), screenHeight(), 1,
|
||||
TextureUsage::STENCIL_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT));
|
||||
auto renderTarget = addCleanup(api.createRenderTarget(
|
||||
auto renderTarget = cleanup.add(api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR0 | TargetBufferFlags::STENCIL, screenWidth(), screenHeight(),
|
||||
1, 0, { { colorTexture } }, { depthStencilTexture }, { { depthStencilTexture } }));
|
||||
|
||||
@@ -177,27 +181,27 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
|
||||
|
||||
TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Stencil not supported, see b/417230776");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL), "Stencil isn't applied");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "see b/453757787");
|
||||
SKIP_IF(Backend::VULKAN, "b/453777217");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// Create two textures: a single-sampled color and a MSAA stencil texture.
|
||||
// We also create two RenderTargets, one for each pass:
|
||||
// Pass 0: Render a triangle only into the MSAA stencil buffer.
|
||||
// Pass 1: Render a triangle into (an auto-created) MSAA color buffer using the stencil test.
|
||||
// Performs an auto-resolve on the color.
|
||||
auto colorTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
auto colorTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, screenWidth(), screenHeight(), 1,
|
||||
TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE));
|
||||
auto depthStencilTextureMSAA = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
auto depthStencilTextureMSAA = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::DEPTH24_STENCIL8, 4, screenWidth(), screenHeight(), 1,
|
||||
TextureUsage::STENCIL_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT));
|
||||
auto renderTarget0 = addCleanup(api.createRenderTarget(
|
||||
auto renderTarget0 = cleanup.add(api.createRenderTarget(
|
||||
TargetBufferFlags::DEPTH_AND_STENCIL, screenWidth(), screenHeight(), 4, 0,
|
||||
{{}}, { depthStencilTextureMSAA }, { depthStencilTextureMSAA }));
|
||||
auto renderTarget1 = addCleanup(
|
||||
auto renderTarget1 = cleanup.add(
|
||||
api.createRenderTarget(TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH_AND_STENCIL,
|
||||
screenWidth(), screenHeight(), 4, 0, { { colorTexture } },
|
||||
{ depthStencilTextureMSAA }, { depthStencilTextureMSAA }));
|
||||
|
||||
@@ -32,11 +32,12 @@ TEST_F(BackendTest, TestTemplate) {
|
||||
constexpr int kRenderTargetSize = 512;
|
||||
|
||||
auto& api = getDriverApi();
|
||||
auto swapChain = addCleanup(createSwapChain());
|
||||
Cleanup cleanup(api);
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
RenderTargetHandle renderTarget = addCleanup(api.createDefaultRenderTarget());
|
||||
RenderTargetHandle renderTarget = cleanup.add(api.createDefaultRenderTarget());
|
||||
|
||||
Shader shader = SharedShaders::makeShader(api, *mCleanup, ShaderRequest{
|
||||
Shader shader = SharedShaders::makeShader(api, cleanup, ShaderRequest{
|
||||
.mVertexType = VertexShaderType::Simple,
|
||||
.mFragmentType = FragmentShaderType::SolidColored,
|
||||
.mUniformType = ShaderUniformType::Simple,
|
||||
@@ -49,7 +50,7 @@ TEST_F(BackendTest, TestTemplate) {
|
||||
PipelineState ps = getColorWritePipelineState();
|
||||
shader.addProgramToPipelineState(ps);
|
||||
|
||||
auto ubuffer = addCleanup(api.createBufferObject(sizeof(SimpleMaterialParams),
|
||||
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(SimpleMaterialParams),
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
|
||||
shader.uploadUniform(api, ubuffer, SimpleMaterialParams{
|
||||
.color = float4(1, 0, 0, 1),
|
||||
|
||||
@@ -16,161 +16,156 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "webgpu/utils/AsyncTaskCounter.h"
|
||||
|
||||
// TODO: This test is broken and needs to be repaired or removed.
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <thread>
|
||||
|
||||
namespace test {
|
||||
|
||||
// #include "webgpu/utils/AsyncTaskCounter.h"
|
||||
namespace {
|
||||
|
||||
// #include <chrono>
|
||||
// #include <future>
|
||||
// #include <thread>
|
||||
constexpr int TOLERANCE_MILLISECONDS{ 5 };
|
||||
constexpr int SMALLER_JOB_MILLISECONDS{ 10 };
|
||||
static_assert(SMALLER_JOB_MILLISECONDS > TOLERANCE_MILLISECONDS);
|
||||
constexpr int LARGER_JOB_MILLISECONDS{ 20 };
|
||||
static_assert(LARGER_JOB_MILLISECONDS > (SMALLER_JOB_MILLISECONDS + TOLERANCE_MILLISECONDS));
|
||||
|
||||
// namespace test {
|
||||
} // namespace
|
||||
|
||||
// namespace {
|
||||
TEST(AsyncTaskCounter, noWorkJobs) {
|
||||
filament::backend::webgpuutils::AsyncTaskCounter counter{};
|
||||
const auto waitFuture{ std::async(std::launch::async,
|
||||
[&counter] { counter.waitForAllToFinish(); }) };
|
||||
const auto status{ waitFuture.wait_for(std::chrono::milliseconds(TOLERANCE_MILLISECONDS)) };
|
||||
ASSERT_NE(status, std::future_status::timeout)
|
||||
<< "Timed out waiting for no work to finish. This should have returned immediately. "
|
||||
"Tolerance/timeout: "
|
||||
<< TOLERANCE_MILLISECONDS << " milliseconds.";
|
||||
}
|
||||
|
||||
// constexpr int TOLERANCE_MILLISECONDS{ 5 };
|
||||
// constexpr int SMALLER_JOB_MILLISECONDS{ 10 };
|
||||
// static_assert(SMALLER_JOB_MILLISECONDS > TOLERANCE_MILLISECONDS);
|
||||
// constexpr int LARGER_JOB_MILLISECONDS{ 20 };
|
||||
// static_assert(LARGER_JOB_MILLISECONDS > (SMALLER_JOB_MILLISECONDS + TOLERANCE_MILLISECONDS));
|
||||
TEST(AsyncTaskCounter, oneWorkJob) {
|
||||
filament::backend::webgpuutils::AsyncTaskCounter counter{};
|
||||
const auto task{ [&counter]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
counter.finishTask();
|
||||
} };
|
||||
const auto startTime{ std::chrono::steady_clock::now() };
|
||||
counter.startTask();
|
||||
std::thread t{ task };
|
||||
const auto waitFuture{ std::async(std::launch::async,
|
||||
[&counter] { counter.waitForAllToFinish(); }) };
|
||||
const auto expectedDuration{ SMALLER_JOB_MILLISECONDS };
|
||||
const auto status{ waitFuture.wait_for(
|
||||
std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
|
||||
const auto endTime{ std::chrono::steady_clock::now() };
|
||||
const auto duration{ std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
endTime - startTime) };
|
||||
ASSERT_NE(status, std::future_status::timeout)
|
||||
<< "Timed out waiting for one work item to finish. Expected job duration: "
|
||||
<< expectedDuration
|
||||
<< " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
|
||||
<< " milliseconds.";
|
||||
ASSERT_GT(duration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
|
||||
<< "The one job should have taken at least its expected duration of "
|
||||
<< expectedDuration << " milliseconds (" << TOLERANCE_MILLISECONDS
|
||||
<< " milliseconds tolerance), but took " << duration << " milliseconds";
|
||||
t.join();
|
||||
}
|
||||
|
||||
// } // namespace
|
||||
TEST(AsyncTaskCounter, multipleWorkJobs) {
|
||||
filament::backend::webgpuutils::AsyncTaskCounter counter{};
|
||||
const auto smallerTasks{ [&counter]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
// validates the counter incrementing and decrementing before the work is done.
|
||||
counter.startTask();
|
||||
counter.finishTask();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
counter.finishTask();
|
||||
} };
|
||||
const auto largerTask{ [&counter]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(LARGER_JOB_MILLISECONDS));
|
||||
counter.finishTask();
|
||||
} };
|
||||
const auto startTime{ std::chrono::steady_clock::now() };
|
||||
counter.startTask();
|
||||
std::thread t1{ smallerTasks };
|
||||
counter.startTask();
|
||||
std::thread t2{ largerTask };
|
||||
const auto expectedDuration{ std::max(SMALLER_JOB_MILLISECONDS * 2, LARGER_JOB_MILLISECONDS) };
|
||||
const auto waitFuture{ std::async(std::launch::async,
|
||||
[&counter] { counter.waitForAllToFinish(); }) };
|
||||
const auto status{ waitFuture.wait_for(
|
||||
std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
|
||||
const auto endTime{ std::chrono::steady_clock::now() };
|
||||
const auto duration{ std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
endTime - startTime) };
|
||||
ASSERT_NE(status, std::future_status::timeout)
|
||||
<< "Timed out waiting for 3 work items to finish. Expected job duration: "
|
||||
<< expectedDuration
|
||||
<< " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
|
||||
<< " milliseconds.";
|
||||
ASSERT_GT(duration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
|
||||
<< "The jobs should have taken at least their expected duration of " << expectedDuration
|
||||
<< " milliseconds (" << TOLERANCE_MILLISECONDS << " milliseconds tolerance), but took "
|
||||
<< duration << " milliseconds";
|
||||
t1.join();
|
||||
t2.join();
|
||||
}
|
||||
|
||||
// TEST(AsyncTaskCounter, noWorkJobs) {
|
||||
// filament::backend::webgpuutils::AsyncTaskCounter counter{};
|
||||
// const auto waitFuture{ std::async(std::launch::async,
|
||||
// [&counter] { counter.waitForAllToFinish(); }) };
|
||||
// const auto status{ waitFuture.wait_for(std::chrono::milliseconds(TOLERANCE_MILLISECONDS)) };
|
||||
// ASSERT_NE(status, std::future_status::timeout)
|
||||
// << "Timed out waiting for no work to finish. This should have returned immediately. "
|
||||
// "Tolerance/timeout: "
|
||||
// << TOLERANCE_MILLISECONDS << " milliseconds.";
|
||||
// }
|
||||
TEST(AsyncTaskCounter, counterReuse) {
|
||||
filament::backend::webgpuutils::AsyncTaskCounter counter{};
|
||||
const auto batchOfTasks{ [&counter]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
counter.startTask();
|
||||
counter.finishTask();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
counter.finishTask();
|
||||
} };
|
||||
// first batch of tasks...
|
||||
const auto firstStartTime{ std::chrono::steady_clock::now() };
|
||||
counter.startTask();
|
||||
std::thread firstBatch{ batchOfTasks };
|
||||
const auto firstWaitFuture{ std::async(std::launch::async,
|
||||
[&counter] { counter.waitForAllToFinish(); }) };
|
||||
const auto expectedDuration{ SMALLER_JOB_MILLISECONDS * 2 };
|
||||
const auto firstStatus{ firstWaitFuture.wait_for(
|
||||
std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
|
||||
const auto firstEndTime{ std::chrono::steady_clock::now() };
|
||||
const auto firstDuration{ std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
firstEndTime - firstStartTime) };
|
||||
ASSERT_NE(firstStatus, std::future_status::timeout)
|
||||
<< "Timed out waiting for first batch to finish. Expected job duration: "
|
||||
<< expectedDuration
|
||||
<< " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
|
||||
<< " milliseconds.";
|
||||
ASSERT_GT(firstDuration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
|
||||
<< "The first batch of tasks should have taken at least its expected duration of "
|
||||
<< expectedDuration << " milliseconds (" << TOLERANCE_MILLISECONDS
|
||||
<< " milliseconds tolerance), but took " << firstDuration << " milliseconds";
|
||||
firstBatch.join();
|
||||
// second batch of tasks...
|
||||
const auto secondStartTime{ std::chrono::steady_clock::now() };
|
||||
counter.startTask();
|
||||
std::thread secondBatch{ batchOfTasks };
|
||||
const auto secondWaitFuture{ std::async(std::launch::async,
|
||||
[&counter] { counter.waitForAllToFinish(); }) };
|
||||
const auto secondStatus{ secondWaitFuture.wait_for(
|
||||
std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
|
||||
const auto secondEndTime{ std::chrono::steady_clock::now() };
|
||||
const auto secondDuration{ std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
secondEndTime - secondStartTime) };
|
||||
ASSERT_NE(secondStatus, std::future_status::timeout)
|
||||
<< "Timed out waiting for second batch to finish. Expected job duration: "
|
||||
<< expectedDuration
|
||||
<< " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
|
||||
<< " milliseconds.";
|
||||
ASSERT_GT(secondDuration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
|
||||
<< "The second batch of tasks should have taken at least its expected duration of "
|
||||
<< expectedDuration << " milliseconds (" << TOLERANCE_MILLISECONDS
|
||||
<< " milliseconds tolerance), but took " << secondDuration << " milliseconds";
|
||||
secondBatch.join();
|
||||
}
|
||||
|
||||
// TEST(AsyncTaskCounter, oneWorkJob) {
|
||||
// filament::backend::webgpuutils::AsyncTaskCounter counter{};
|
||||
// const auto task{ [&counter]() {
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
// counter.finishTask();
|
||||
// } };
|
||||
// const auto startTime{ std::chrono::steady_clock::now() };
|
||||
// counter.startTask();
|
||||
// std::thread t{ task };
|
||||
// const auto waitFuture{ std::async(std::launch::async,
|
||||
// [&counter] { counter.waitForAllToFinish(); }) };
|
||||
// const auto expectedDuration{ SMALLER_JOB_MILLISECONDS };
|
||||
// const auto status{ waitFuture.wait_for(
|
||||
// std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
|
||||
// const auto endTime{ std::chrono::steady_clock::now() };
|
||||
// const auto duration{ std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
// endTime - startTime) };
|
||||
// ASSERT_NE(status, std::future_status::timeout)
|
||||
// << "Timed out waiting for one work item to finish. Expected job duration: "
|
||||
// << expectedDuration
|
||||
// << " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
|
||||
// << " milliseconds.";
|
||||
// ASSERT_GT(duration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
|
||||
// << "The one job should have taken at least its expected duration of "
|
||||
// << expectedDuration << " milliseconds (" << TOLERANCE_MILLISECONDS
|
||||
// << " milliseconds tolerance), but took " << duration << " milliseconds";
|
||||
// t.join();
|
||||
// }
|
||||
|
||||
// TEST(AsyncTaskCounter, multipleWorkJobs) {
|
||||
// filament::backend::webgpuutils::AsyncTaskCounter counter{};
|
||||
// const auto smallerTasks{ [&counter]() {
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
// // validates the counter incrementing and decrementing before the work is done.
|
||||
// counter.startTask();
|
||||
// counter.finishTask();
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
// counter.finishTask();
|
||||
// } };
|
||||
// const auto largerTask{ [&counter]() {
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(LARGER_JOB_MILLISECONDS));
|
||||
// counter.finishTask();
|
||||
// } };
|
||||
// const auto startTime{ std::chrono::steady_clock::now() };
|
||||
// counter.startTask();
|
||||
// std::thread t1{ smallerTasks };
|
||||
// counter.startTask();
|
||||
// std::thread t2{ largerTask };
|
||||
// const auto expectedDuration{ std::max(SMALLER_JOB_MILLISECONDS * 2, LARGER_JOB_MILLISECONDS) };
|
||||
// const auto waitFuture{ std::async(std::launch::async,
|
||||
// [&counter] { counter.waitForAllToFinish(); }) };
|
||||
// const auto status{ waitFuture.wait_for(
|
||||
// std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
|
||||
// const auto endTime{ std::chrono::steady_clock::now() };
|
||||
// const auto duration{ std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
// endTime - startTime) };
|
||||
// ASSERT_NE(status, std::future_status::timeout)
|
||||
// << "Timed out waiting for 3 work items to finish. Expected job duration: "
|
||||
// << expectedDuration
|
||||
// << " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
|
||||
// << " milliseconds.";
|
||||
// ASSERT_GT(duration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
|
||||
// << "The jobs should have taken at least their expected duration of " << expectedDuration
|
||||
// << " milliseconds (" << TOLERANCE_MILLISECONDS << " milliseconds tolerance), but took "
|
||||
// << duration << " milliseconds";
|
||||
// t1.join();
|
||||
// t2.join();
|
||||
// }
|
||||
|
||||
// TODO: This test is broken and needs to be repaired or removed.
|
||||
// TEST(AsyncTaskCounter, counterReuse) {
|
||||
// filament::backend::webgpuutils::AsyncTaskCounter counter{};
|
||||
// const auto batchOfTasks{ [&counter]() {
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
// counter.startTask();
|
||||
// counter.finishTask();
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
// counter.finishTask();
|
||||
// } };
|
||||
// // first batch of tasks...
|
||||
// const auto firstStartTime{ std::chrono::steady_clock::now() };
|
||||
// counter.startTask();
|
||||
// std::thread firstBatch{ batchOfTasks };
|
||||
// const auto firstWaitFuture{ std::async(std::launch::async,
|
||||
// [&counter] { counter.waitForAllToFinish(); }) };
|
||||
// const auto expectedDuration{ SMALLER_JOB_MILLISECONDS * 2 };
|
||||
// const auto firstStatus{ firstWaitFuture.wait_for(
|
||||
// std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
|
||||
// const auto firstEndTime{ std::chrono::steady_clock::now() };
|
||||
// const auto firstDuration{ std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
// firstEndTime - firstStartTime) };
|
||||
// ASSERT_NE(firstStatus, std::future_status::timeout)
|
||||
// << "Timed out waiting for first batch to finish. Expected job duration: "
|
||||
// << expectedDuration
|
||||
// << " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
|
||||
// << " milliseconds.";
|
||||
// ASSERT_GT(firstDuration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
|
||||
// << "The first batch of tasks should have taken at least its expected duration of "
|
||||
// << expectedDuration << " milliseconds (" << TOLERANCE_MILLISECONDS
|
||||
// << " milliseconds tolerance), but took " << firstDuration << " milliseconds";
|
||||
// firstBatch.join();
|
||||
// // second batch of tasks...
|
||||
// const auto secondStartTime{ std::chrono::steady_clock::now() };
|
||||
// counter.startTask();
|
||||
// std::thread secondBatch{ batchOfTasks };
|
||||
// const auto secondWaitFuture{ std::async(std::launch::async,
|
||||
// [&counter] { counter.waitForAllToFinish(); }) };
|
||||
// const auto secondStatus{ secondWaitFuture.wait_for(
|
||||
// std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
|
||||
// const auto secondEndTime{ std::chrono::steady_clock::now() };
|
||||
// const auto secondDuration{ std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
// secondEndTime - secondStartTime) };
|
||||
// ASSERT_NE(secondStatus, std::future_status::timeout)
|
||||
// << "Timed out waiting for second batch to finish. Expected job duration: "
|
||||
// << expectedDuration
|
||||
// << " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
|
||||
// << " milliseconds.";
|
||||
// ASSERT_GT(secondDuration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
|
||||
// << "The second batch of tasks should have taken at least its expected duration of "
|
||||
// << expectedDuration << " milliseconds (" << TOLERANCE_MILLISECONDS
|
||||
// << " milliseconds tolerance), but took " << secondDuration << " milliseconds";
|
||||
// secondBatch.join();
|
||||
// }
|
||||
|
||||
// } // namespace test
|
||||
} // namespace test
|
||||
|
||||
@@ -417,17 +417,6 @@ public:
|
||||
* GPU context priority level. Controls GPU work scheduling and preemption.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
uint32_t sharedUboInitialSizeInBytes = 256 * 64;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -350,19 +350,6 @@ public:
|
||||
//! Indicates whether an existing parameter is a sampler or not.
|
||||
bool isSampler(const char* UTILS_NONNULL name) const noexcept;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the name of the transform field associated for the given sampler parameter.
|
||||
* In the case where the parameter does not have a transform name field, it will return nullptr.
|
||||
*
|
||||
* @param samplerName the name of the sampler parameter to query.
|
||||
*
|
||||
* @return If exists, the transform name value otherwise returns a nullptr.
|
||||
*/
|
||||
const char* UTILS_NULLABLE getParameterTransformName(
|
||||
const char* UTILS_NONNULL samplerName) const noexcept;
|
||||
|
||||
/**
|
||||
* Sets the value of the given parameter on this material's default instance.
|
||||
*
|
||||
|
||||
@@ -88,9 +88,7 @@ public:
|
||||
* @see getFrameInfoHistory()
|
||||
*/
|
||||
struct FrameInfo {
|
||||
/** 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;
|
||||
uint32_t frameId; //!< monotonically increasing frame identifier
|
||||
duration_ns frameTime; //!< frame duration on the GPU in nanosecond [ns]
|
||||
|
||||
@@ -238,11 +238,8 @@ public:
|
||||
* given sample points within each pixel. Only supported when isMSAASwapChainSupported(4) is
|
||||
* true.
|
||||
*
|
||||
* This is supported by EGL(Android) and Metal. Other GL platforms (GLX, WGL, etc) don't support
|
||||
* it because the swapchain MSAA settings must be configured before window creation.
|
||||
*
|
||||
* With Metal, this flag should only be used when rendering a single View into a SwapChain. This
|
||||
* flag is not supported when rendering multiple Filament Views into this SwapChain.
|
||||
* This is only supported by EGL(Android). Other GL platforms (GLX, WGL, etc) don't support it
|
||||
* because the swapchain MSAA settings must be configured before window creation.
|
||||
*
|
||||
* @see isMSAASwapChainSupported(4)
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user