Compare commits

..
Author SHA1 Message Date
Run Yu d105dc01b1 This is a test for pushing branch to github 2025-10-17 16:16:15 -04:00
141 changed files with 1348 additions and 3143 deletions
-20
View File
@@ -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
View File
@@ -56,8 +56,6 @@ option(FILAMENT_ENABLE_FGVIEWER "Enable the frame graph viewer" OFF)
option(FILAMENT_USE_ABSEIL_LOGGING "Use Abseil to log, may increase binary size" OFF)
option(FILAMENT_ENABLE_EXPERIMENTAL_GCC_SUPPORT "Enable GCC support (unsupported)" OFF)
# This is to disable GTAO for the short-term while we investigate a way to better manage size increases.
# On the regular filament build (where size is of less concern), we enable GTAO by default.
option(FILAMENT_DISABLE_GTAO "Disable GTAO" OFF)
@@ -297,10 +295,15 @@ set(TOOLS ${CMAKE_CURRENT_SOURCE_DIR}/tools)
# ==================================================================================================
set(MIN_CLANG_VERSION "6.0")
set(FILAMENT_USING_GCC OFF)
if (CMAKE_C_COMPILER_ID MATCHES "GNU" AND FILAMENT_ENABLE_EXPERIMENTAL_GCC_SUPPORT)
set(FILAMENT_USING_GCC ON)
elseif (CMAKE_C_COMPILER_ID MATCHES "Clang")
if (CMAKE_C_COMPILER_ID MATCHES "Clang")
if (CMAKE_C_COMPILER_VERSION VERSION_LESS MIN_CLANG_VERSION)
message(FATAL_ERROR "Detected C compiler Clang ${CMAKE_C_COMPILER_VERSION} < ${MIN_CLANG_VERSION}")
endif()
elseif (NOT MSVC)
message(FATAL_ERROR "Detected C compiler ${CMAKE_C_COMPILER_ID} is unsupported")
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS MIN_CLANG_VERSION)
message(FATAL_ERROR "Detected CXX compiler Clang ${CMAKE_CXX_COMPILER_VERSION} < ${MIN_CLANG_VERSION}")
endif()
@@ -341,11 +344,7 @@ if (MSVC)
set(CXX_STANDARD "/std:c++latest")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} /W0 /Zc:__cplusplus")
else()
if(FILAMENT_USING_GCC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} -Wno-changes-meaning -Wno-return-type -Wno-attributes -Wno-unknown-pragmas -Wno-class-memaccess -Wno-multichar -Wno-deprecated-declarations -Wno-subobject-linkage -Wno-invalid-constexpr")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} -fstrict-aliasing -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} -fstrict-aliasing -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations")
if (APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-nullability-extension")
endif()
@@ -366,14 +365,10 @@ endif()
if (LINUX)
option(USE_STATIC_LIBCXX "Link against the static runtime libraries." ON)
if (${USE_STATIC_LIBCXX})
if (FILAMENT_USING_GCC)
link_libraries("-static-libgcc -static-libstdc++")
else ()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
link_libraries("-static-libgcc -static-libstdc++")
link_libraries(libc++.a)
link_libraries(libc++abi.a)
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
link_libraries("-static-libgcc -static-libstdc++")
link_libraries(libc++.a)
link_libraries(libc++abi.a)
endif()
# Only linux, clang doesn't want to use a shared library that is not PIC.
@@ -398,8 +393,8 @@ if (MSVC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE")
endif()
# Add colors to ninja builds (when not using gcc)
if (UNIX AND CMAKE_GENERATOR STREQUAL "Ninja" AND NOT FILAMENT_USING_GCC)
# Add colors to ninja builds
if (UNIX AND CMAKE_GENERATOR STREQUAL "Ninja")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics")
endif()
-2
View File
@@ -6,5 +6,3 @@
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- Metal: Add support for the `SwapChain::CONFIG_MSAA_4_SAMPLES` flag.
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.67.0'
implementation 'com.google.android.filament:filament-android:1.66.0'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.67.0'
pod 'Filament', '~> 1.66.0'
```
## Documentation
-8
View File
@@ -7,14 +7,6 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.67.0
- materials: Add a new API getParameterTransformName that will return the value of the transformName field of a sampler
parameter. [⚠️ **Recompile Materials**]
## v1.66.2
## v1.66.1
- filamat: Removed a dependency on Glslang's deprecated SPIR-V remapper.
@@ -557,8 +557,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
jboolean disableHandleUseAfterFreeCheck,
jint preferredShaderLanguage,
jboolean forceGLES2Context, jboolean assertNativeWindowIsValid,
jint gpuContextPriority,
jlong sharedUboInitialSizeInBytes) {
jint gpuContextPriority) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
Engine::Config config = {
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
@@ -577,7 +576,6 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
.forceGLES2Context = (bool) forceGLES2Context,
.assertNativeWindowIsValid = (bool) assertNativeWindowIsValid,
.gpuContextPriority = (Engine::GpuContextPriority) gpuContextPriority,
.sharedUboInitialSizeInBytes = (uint32_t) sharedUboInitialSizeInBytes,
};
builder->config(&config);
}
@@ -279,17 +279,6 @@ Java_com_google_android_filament_Material_nHasParameter(JNIEnv* env, jclass,
return (jboolean) hasParameter;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_google_android_filament_Material_nGetParameterTransformName(JNIEnv* env, jclass,
jlong nativeMaterial, jstring samplerName_) {
Material* material = (Material*) nativeMaterial;
const char* samplerName = env->GetStringUTFChars(samplerName_, 0);
const char* transformName = material->getParameterTransformName(samplerName);
jstring transformName_ = env->NewStringUTF(transformName ? transformName : "");
env->ReleaseStringUTFChars(samplerName_, samplerName);
return transformName_;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_Material_nCompile(JNIEnv *env, jclass clazz,
@@ -264,8 +264,7 @@ public class Engine {
config.disableHandleUseAfterFreeCheck,
config.preferredShaderLanguage.ordinal(),
config.forceGLES2Context, config.assertNativeWindowIsValid,
config.gpuContextPriority.ordinal(),
config.sharedUboInitialSizeInBytes);
config.gpuContextPriority.ordinal());
return this;
}
@@ -526,16 +525,6 @@ public class Engine {
* GPU context priority level. Controls GPU work scheduling and preemption.
*/
public GpuContextPriority gpuContextPriority = GpuContextPriority.DEFAULT;
/**
* The initial size in bytes of the shared uniform buffer used for material instance batching.
*
* If the buffer runs out of space during a frame, it will be automatically reallocated
* with a larger capacity. Setting an appropriate initial size can help avoid runtime
* reallocations, which can cause a minor performance stutter, at the cost of higher
* initial memory usage.
*/
public long sharedUboInitialSizeInBytes = 256 * 64;
}
private Engine(long nativeEngine, Config config) {
@@ -1540,8 +1529,7 @@ public class Engine {
boolean disableHandleUseAfterFreeCheck,
int preferredShaderLanguage,
boolean forceGLES2Context, boolean assertNativeWindowIsValid,
int gpuContextPriority,
long sharedUboInitialSizeInBytes);
int gpuContextPriority);
private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal);
private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
private static native void nSetBuilderPaused(long nativeBuilder, boolean paused);
@@ -787,21 +787,6 @@ public class Material {
return nHasParameter(getNativeObject(), name);
}
/**
*
* Returns the name of the transform parameter associated with the given sampler parameter.
* In the case the parameter doesn't have a transform name field, it will return an empty string.
*
* @param samplerName the name of the sampler parameter to query.
*
* @see
* <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/general:parameters">
* General: parameters</a>
*/
public String getParameterTransformName(@NonNull String samplerName) {
return nGetParameterTransformName(getNativeObject(), samplerName);
}
/**
* Sets the value of a bool parameter on this material's default instance.
*
@@ -1126,5 +1111,4 @@ public class Material {
private static native int nGetRequiredAttributes(long nativeMaterial);
private static native boolean nHasParameter(long nativeMaterial, @NonNull String name);
private static native String nGetParameterTransformName(long nativeMaterial, @NonNull String samplerName);
}
@@ -294,8 +294,8 @@ public class TextureSampler {
/**
* This controls anisotropic filtering.
*
* @param anisotropy Amount of anisotropy, should be a power-of-two. The default is 1.
* The maximum permissible value is 128.
* @param anisotropy Amount of anisotropy, should be a power-of-two. The default is 0.
* The maximum permissible value is 7.
*/
public void setAnisotropy(float anisotropy) {
mSampler = nSetAnisotropy(mSampler, anisotropy);
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.67.0
VERSION_NAME=1.66.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+2 -2
View File
@@ -165,8 +165,8 @@ Portal</a>.</p>
<p>To publish under the <code>com.google.android</code> namespace, you'll also need to email
<code>central-support@sonatype.com</code> with your account information to request access.</p>
<p>Then, generate a user token through the Sonatype website. Navigate to
<a href="https://central.sonatype.com/usertoken">https://central.sonatype.com/usertoken</a>
and select <strong>Generate User Token</strong>.</p>
<a href="https://central.sonatype.com/account">https://central.sonatype.com/account</a> and select <strong>Generate
User Token</strong>.</p>
<p>Finally, add the generated token credentials to <code>~/.gradle/gradle.properties</code>. (Note: these are
different from the credentials used to log into your Sonatype account):</p>
<pre><code class="language-gradle"># Generated Sonatype token
+2 -2
View File
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.66.1'
implementation 'com.google.android.filament:filament-android:1.66.0'
}
</code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
@@ -196,7 +196,7 @@ dependencies {
</div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<p>iOS projects can use CocoaPods to install the latest release:</p>
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.66.1'
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.66.0'
</code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul>
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -8,10 +8,10 @@ tar -xvzf filament-release-web.tgz
rm filament-release-web.tgz
rm filament-web.zip
rm filament.d.ts
cp ../../../web/samples/remote.html index.html
cp ../../web/samples/remote.html index.html
popd
git status
echo ""
echo "All done! Next, commit the changed files"
echo "All done! Next, make a git commit that updates docs/remote."
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-15
View File
@@ -1,15 +0,0 @@
attrs==25.4.0
certifi==2025.10.5
h11==0.16.0
idna==3.11
outcome==1.3.0.post0
PySocks==1.7.1
selenium==4.37.0
sniffio==1.3.1
sortedcontainers==2.4.0
trio==0.31.0
trio-websocket==0.12.2
typing_extensions==4.15.0
urllib3==2.5.0
websocket-client==1.9.0
wsproto==1.2.0
+1 -3
View File
@@ -140,8 +140,6 @@ if __name__ == "__main__":
res, err = execute('mdbook build', cwd=MDBOOK_DIR)
assert res == 0, f"failed to execute `mdbook`. return-code={res} err=\"{err}\""
RAW_IGNORES = shutil.ignore_patterns('update_remote_ui.sh')
shutil.copytree(RAW_COPIES_DIR, BOOK_OUPUT_DIR, ignore=RAW_IGNORES, dirs_exist_ok=True)
shutil.copytree(RAW_COPIES_DIR, BOOK_OUPUT_DIR, dirs_exist_ok=True)
shutil.copy(os.path.join(MARKDEEP_DIR, FILAMENT_MD), BOOK_OUPUT_DIR)
shutil.copy(os.path.join(MARKDEEP_DIR, MATERIALS_MD), BOOK_OUPUT_DIR)
@@ -9,8 +9,8 @@ To publish under the `com.google.android` namespace, you'll also need to email
`central-support@sonatype.com` with your account information to request access.
Then, generate a user token through the Sonatype website. Navigate to
[https://central.sonatype.com/usertoken](https://central.sonatype.com/usertoken)
and select **Generate User Token**.
[https://central.sonatype.com/account](https://central.sonatype.com/account) and select **Generate
User Token**.
Finally, add the generated token credentials to `~/.gradle/gradle.properties`. (Note: these are
different from the credentials used to log into your Sonatype account):
File diff suppressed because one or more lines are too long
Binary file not shown.
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
pushd "$(dirname "$0")" > /dev/null
curl -OL https://nightly.link/google/filament/workflows/web-continuous/main/filament-web.zip
unzip -q filament-web.zip
tar -xvzf filament-release-web.tgz
rm filament-release-web.tgz
rm filament-web.zip
rm filament.d.ts
cp ../../web/samples/remote.html index.html
popd
git status
echo ""
echo "All done! Next, make a git commit that updates docs/remote."
-19
View File
@@ -708,13 +708,6 @@ if (MSVC)
elseif(WEBGL)
# Avoid strict-vtable-pointers here, it is broken in WebAssembly.
set(OPTIMIZATION_FLAGS -fvisibility-inlines-hidden)
elseif(FILAMENT_USING_GCC)
set(OPTIMIZATION_FLAGS
-ffast-math
-fno-finite-math-only
-ffp-contract=fast
-fvisibility-inlines-hidden
)
else()
set(OPTIMIZATION_FLAGS
-ffast-math
@@ -736,18 +729,6 @@ set(LINUX_COMPILER_FLAGS
if (MSVC)
set(FILAMENT_WARNINGS /W3)
elseif (FILAMENT_USING_GCC)
# GCC is not officially supported, so use a weaker set of warnings (incl. not using -Werror).
set(FILAMENT_WARNINGS
-Wall -Wextra-semi -Wundef
-Wno-unused-parameter -Wno-missing-field-initializers
-Wno-unused-but-set-variable -Wno-unused-variable -Wno-unused-function
-Wnon-virtual-dtor -Wimplicit-fallthrough
-Wno-strict-aliasing
-Wno-changes-meaning -Wno-return-type
-Wno-invalid-constexpr -Wno-parentheses
-Wno-sign-compare -Wno-deprecated-declarations
)
else()
set(FILAMENT_WARNINGS
-Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers
-17
View File
@@ -104,8 +104,6 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
list(APPEND SRCS src/VirtualMachineEnv.cpp)
endif ()
if (ANDROID)
list(APPEND SRCS src/AndroidNativeWindow.cpp)
list(APPEND SRCS src/AndroidSwapChainHelper.cpp)
list(APPEND SRCS src/opengl/platforms/ExternalStreamManagerAndroid.cpp)
list(APPEND SRCS src/opengl/platforms/PlatformEGLAndroid.cpp)
elseif (IOS)
@@ -471,13 +469,6 @@ if (MSVC)
elseif(WEBGL)
# Avoid strict-vtable-pointers here, it is broken in WebAssembly.
set(OPTIMIZATION_FLAGS -fvisibility-inlines-hidden)
elseif(FILAMENT_USING_GCC)
set(OPTIMIZATION_FLAGS
-ffast-math
-fno-finite-math-only
-ffp-contract=fast
-fvisibility-inlines-hidden
)
else()
set(OPTIMIZATION_FLAGS
-ffast-math
@@ -504,13 +495,6 @@ endif()
if (MSVC)
set(FILAMENT_WARNINGS /W3)
elseif (FILAMENT_USING_GCC)
# GCC is not officially supported, so use a weaker set of warnings (incl. not using -Werror)
set(FILAMENT_WARNINGS
-Wall -Wno-unused-parameter -Wno-missing-field-initializers
-Wdeprecated -Wnon-virtual-dtor -Wno-strict-aliasing
-Wno-invalid-constexpr -Wno-parentheses -Wno-unused-variable
)
else()
set(FILAMENT_WARNINGS
-Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers
@@ -592,7 +576,6 @@ if (APPLE OR LINUX)
test/test_BufferUpdates.cpp
test/test_Callbacks.cpp
test/test_MemoryMappedBuffer.cpp
test/test_MsaaSwapChain.cpp
test/test_MRT.cpp
test/test_PushConstants.cpp
test/test_LoadImage.cpp
-102
View File
@@ -91,46 +91,6 @@ public:
using ExternalImageHandleRef = ExternalImageHandle const&;
struct CompositorTiming {
/** duration in nanosecond since epoch of std::steady_clock */
using time_point_ns = int64_t;
/** duration in nanosecond on the std::steady_clock */
using duration_ns = int64_t;
/**
* The timestamp [ns] since epoch of the next time the compositor will begin composition.
* This is effectively the deadline for when the compositor must receive a newly queued
* frame.
*/
time_point_ns compositeDeadline;
/**
* The time delta [ns] between subsequent composition events.
*/
duration_ns compositeInterval;
/**
* The time delta [ns] between the start of composition and the expected present time of
* that composition. This can be used to estimate the latency of the actual present time.
*/
duration_ns compositeToPresentLatency;
};
struct FrameTimestamps {
/** duration in nanosecond since epoch of std::steady_clock */
using time_point_ns = int64_t;
static constexpr time_point_ns INVALID = -1; //!< value not supported
static constexpr time_point_ns PENDING = -2; //!< value not yet available
time_point_ns requestedPresentTime;
time_point_ns acquireTime;
time_point_ns latchTime;
time_point_ns firstRefreshStartTime;
time_point_ns lastRefreshStartTime;
time_point_ns gpuCompositionDoneTime;
time_point_ns displayPresentTime;
time_point_ns dequeueReadyTime;
time_point_ns releaseTime;
};
/**
* The type of technique for stereoscopic rendering. (Note that the materials used will need to
* be compatible with the chosen technique.)
@@ -287,65 +247,6 @@ public:
*/
virtual bool pumpEvents() noexcept;
// --------------------------------------------------------------------------------------------
// Swapchain timing APIs
/**
* Whether this platform supports compositor timing querying.
*
* @return true if this Platform supports compositor timings, false otherwise [default]
* @see queryCompositorTiming()
* @see setPresentFrameId()
* @see queryFrameTimestamps()
*/
virtual bool isCompositorTimingSupported() const noexcept;
/**
* If compositor timing is supported, fills the provided CompositorTiming structure
* with timing information form the compositor the swapchain's native window is using.
* The swapchain'snative window must be valid (i.e. not a headless swapchain).
* @param swapchain to query the compositor timing from
* @return true on success, false otherwise (e.g. if not supported)
* @see isCompositorTimingSupported()
*/
virtual bool queryCompositorTiming(SwapChain const* UTILS_NONNULL swapchain,
CompositorTiming* UTILS_NONNULL outCompositorTiming) const noexcept;
/**
* Associate a generic frameId which must be monotonically increasing (albeit not strictly) with
* the next frame to be presented on the specified swapchain.
*
* This must be called from the backend thread.
*
* @param swapchain
* @param frameId
* @return true on success, false otherwise
* @see isCompositorTimingSupported()
* @see queryFrameTimestamps()
*/
virtual bool setPresentFrameId(SwapChain const* UTILS_NONNULL swapchain,
uint64_t frameId) noexcept;
/**
* If compositor timing is supported, fills the provided FrameTimestamps structure
* with timing information of a given frame, identified by the frame id, of the specified
* swapchain. The system only keeps a limited history of frames timings.
*
* This API is thread safe and can be called from any thread.
*
* @param swapchain swapchain to query the timestamps of
* @param frameId frame we're interested it
* @param outFrameTimestamps output structure receiving the timestamps
* @return true if successful, false otherwise
* @see isCompositorTimingSupported()
* @see setPresentFrameId()
*/
virtual bool queryFrameTimestamps(SwapChain const* UTILS_NONNULL swapchain,
uint64_t frameId, FrameTimestamps* UTILS_NONNULL outFrameTimestamps) const noexcept;
// --------------------------------------------------------------------------------------------
// Caching APIs
/**
* InsertBlobFunc is an Invocable to an application-provided function that a
* backend implementation may use to insert a key/value pair into the
@@ -438,9 +339,6 @@ public:
size_t retrieveBlob(const void* UTILS_NONNULL key, size_t keySize,
void* UTILS_NONNULL value, size_t valueSize);
// --------------------------------------------------------------------------------------------
// Debugging APIs
using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key, uint64_t value)>;
/**
@@ -430,7 +430,7 @@ public:
virtual bool isExtraContextSupported() const noexcept;
/**
* Creates an OpenGL context with the same configuration as the main context and makes it
* Creates an OpenGL context with the same configuration than the main context and makes it
* current to the current thread. Must not be called from the main driver thread.
* createContext() is only supported if isExtraContextSupported() returns true.
* These additional contexts will be automatically terminated in terminate.
@@ -443,7 +443,7 @@ public:
/**
* Detach and destroy the current context if any and releases all resources associated to
* this thread. This must be called from the same thread where createContext() was called.
* this thread.
*/
virtual void releaseContext() noexcept;
};
@@ -25,7 +25,6 @@
#include <EGL/eglext.h>
#include <EGL/eglplatform.h>
#include <utils/compiler.h>
#include <utils/Invocable.h>
#include <initializer_list>
@@ -101,6 +100,10 @@ protected:
bool isProtectedContextSupported() const noexcept override;
bool isSRGBSwapChainSupported() const noexcept override;
bool isMSAASwapChainSupported(uint32_t samples) const noexcept override;
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) override;
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
bool isSwapChainProtected(SwapChain* swapChain) noexcept override;
ContextType getCurrentContextType() const noexcept override;
@@ -155,9 +158,17 @@ protected:
return egl.makeCurrent(context, mEGLDummySurface, mEGLDummySurface);
}
EGLDisplay getEglDisplay() const noexcept { return mEGLDisplay; }
EGLConfig getEglConfig() const noexcept { return mEGLConfig; }
EGLConfig getSuitableConfigForSwapChain(uint64_t flags, bool window, bool pbuffer) const;
// TODO: this should probably use getters instead.
EGLDisplay mEGLDisplay = EGL_NO_DISPLAY;
EGLContext mEGLContext = EGL_NO_CONTEXT;
EGLContext mEGLContextProtected = EGL_NO_CONTEXT;
EGLSurface mEGLDummySurface = EGL_NO_SURFACE;
ContextType mCurrentContextType = ContextType::NONE;
// mEGLConfig is valid only if ext.egl.KHR_no_config_context is false
EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR;
Config mContextAttribs;
std::vector<EGLContext> mAdditionalContexts;
bool mMSAA4XSupport = false;
// supported extensions detected at runtime
struct {
@@ -174,11 +185,7 @@ protected:
} egl;
} ext;
struct SwapChainEGL : public SwapChain {
SwapChainEGL(PlatformEGL const& platform, void* nativeWindow, uint64_t flags);
SwapChainEGL(PlatformEGL const& platform, uint32_t width, uint32_t height, uint64_t flags);
void terminate(PlatformEGL& platform);
struct SwapChainEGL : public Platform::SwapChain {
EGLSurface sur = EGL_NO_SURFACE;
Config attribs{};
EGLNativeWindowType nativeWindow{};
@@ -194,25 +201,10 @@ protected:
~ExternalImageEGL() override;
};
private:
// prevent derived classes' implementations to call through
[[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override;
[[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
protected:
EGLConfig findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const;
EGLDisplay mEGLDisplay = EGL_NO_DISPLAY;
EGLContext mEGLContext = EGL_NO_CONTEXT;
EGLContext mEGLContextProtected = EGL_NO_CONTEXT;
EGLSurface mEGLDummySurface = EGL_NO_SURFACE;
ContextType mCurrentContextType = ContextType::NONE;
// mEGLConfig is valid only if ext.egl.KHR_no_config_context is false
EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR;
Config mContextAttribs;
std::vector<EGLContext> mAdditionalContexts;
bool mMSAA4XSupport = false;
private:
class EGL {
EGLDisplay& mEGLDisplay;
EGLSurface mCurrentDrawSurface = EGL_NO_SURFACE;
@@ -17,10 +17,7 @@
#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
#include "AndroidSwapChainHelper.h"
#include <backend/AcquiredImage.h>
#include <backend/DriverEnums.h>
#include <backend/Platform.h>
#include <backend/platforms/OpenGLPlatform.h>
#include <backend/platforms/PlatformEGL.h>
@@ -28,8 +25,6 @@
#include <utils/android/PerformanceHintManager.h>
#include <utils/compiler.h>
#include <math/mat3.h>
#include <chrono>
#include <stddef.h>
@@ -72,17 +67,9 @@ public:
* @return `true` on success, `false` on failure. The default implementation
* returns `false`.
*/
bool convertSyncToFd(Sync* sync, int* fd) noexcept;
bool convertSyncToFd(Platform::Sync* sync, int* fd) noexcept;
protected:
struct {
struct {
bool ANDROID_presentation_time = false;
bool ANDROID_get_frame_timestamps = false;
bool ANDROID_native_fence_sync = false;
} egl;
} ext;
// --------------------------------------------------------------------------------------------
// Platform Interface
@@ -93,22 +80,12 @@ protected:
int getOSVersion() const noexcept override;
Driver* createDriver(void* sharedContext,
const DriverConfig& driverConfig) override;
bool isCompositorTimingSupported() const noexcept override;
bool queryCompositorTiming(SwapChain const* swapchain,
CompositorTiming* outCompositorTiming) const noexcept override;
bool setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept override;
bool queryFrameTimestamps(SwapChain const* swapchain, uint64_t frameId,
FrameTimestamps* outFrameTimestamps) const noexcept override;
const Platform::DriverConfig& driverConfig) override;
// --------------------------------------------------------------------------------------------
// OpenGLPlatform Interface
struct SyncEGLAndroid : public Sync {
struct SyncEGLAndroid : public Platform::Sync {
EGLSyncKHR sync;
};
@@ -129,8 +106,8 @@ protected:
Stream* createStream(void* nativeStream) noexcept override;
void destroyStream(Stream* stream) noexcept override;
Sync* createSync() noexcept override;
void destroySync(Sync* sync) noexcept override;
Platform::Sync* createSync() noexcept override;
void destroySync(Platform::Sync* sync) noexcept override;
void attach(Stream* stream, intptr_t tname) noexcept override;
void detach(Stream* stream) noexcept override;
void updateTexImage(Stream* stream, int64_t* timestamp) noexcept override;
@@ -143,7 +120,7 @@ protected:
*/
AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept override;
ExternalTexture* createExternalImageTexture() noexcept override;
OpenGLPlatform::ExternalTexture* createExternalImageTexture() noexcept override;
void destroyExternalImageTexture(ExternalTexture* texture) noexcept override;
struct ExternalImageEGLAndroid : public ExternalImageEGL {
@@ -163,28 +140,12 @@ protected:
bool setImage(ExternalImageEGLAndroid const* eglExternalImage,
ExternalTexture* texture) noexcept;
protected:
bool makeCurrent(ContextType type,
SwapChain* drawSwapChain,
SwapChain* readSwapChain) override;
struct SwapChainEGLAndroid : public SwapChainEGL {
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
void* nativeWindow, uint64_t flags);
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
uint32_t width, uint32_t height, uint64_t flags);
void terminate(PlatformEGLAndroid& platform);
bool setPresentFrameId(uint64_t frameId) const noexcept;
uint64_t getFrameId(uint64_t frameId) const noexcept;
private:
AndroidSwapChainHelper mImpl{};
};
private:
// prevent derived classes' implementations to call through
[[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override;
[[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
struct InitializeJvmForPerformanceManagerIfNeeded {
InitializeJvmForPerformanceManagerIfNeeded();
};
@@ -198,15 +159,13 @@ private:
InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded;
utils::PerformanceHintManager mPerformanceHintManager;
utils::PerformanceHintManager::Session mPerformanceHintSession;
SwapChainEGLAndroid* mCurrentDrawSwapChain{};
using clock = std::chrono::high_resolution_clock;
clock::time_point mStartTimeOfActualWork;
int32_t (*ANativeWindow_setProducerThrottlingEnabled)(ANativeWindow* window, bool enabled) = nullptr;
int32_t (*ANativeWindow_isProducerThrottlingEnabled)(ANativeWindow* window, bool* outEnabled) = nullptr;
void* mNativeWindowLib = nullptr;
int32_t (*ANativeWindow_getBuffersDefaultDataSpace)(ANativeWindow* window) = nullptr;
bool mAssertNativeWindowIsValid = false;
bool mHasProducerThrottlingControl = false;
};
} // namespace filament::backend
@@ -26,10 +26,7 @@
#include <backend/DriverEnums.h>
#include <shared_mutex>
#include <thread>
#include <vector>
#include <unordered_map>
namespace filament::backend {
@@ -51,10 +48,6 @@ protected:
void terminate() noexcept override;
bool isExtraContextSupported() const noexcept override;
void createContext(bool shared) override;
void releaseContext() noexcept override;
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
@@ -67,10 +60,6 @@ private:
GLXFBConfig mGLXConfig{};
GLXPbuffer mDummySurface;
std::vector<GLXPbuffer> mPBuffers;
// Variables for shared contexts
std::unordered_map<std::thread::id, GLXContext> mAdditionalContexts;
std::shared_mutex mAdditionalContextsLock;
};
} // namespace filament::backend
@@ -366,7 +366,6 @@ DECL_DRIVER_API_SYNCHRONOUS_N(void, setupExternalImage, void*, image)
DECL_DRIVER_API_SYNCHRONOUS_N(backend::TimerQueryResult, getTimerQueryValue, backend::TimerQueryHandle, tqh, uint64_t*, elapsedTime)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isWorkaroundNeeded, backend::Workaround, workaround)
DECL_DRIVER_API_SYNCHRONOUS_0(backend::FeatureLevel, getFeatureLevel)
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getUniformBufferOffsetAlignment)
/*
* Updating driver objects
@@ -1,78 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AndroidNativeWindow.h"
#include <android/native_window.h>
#include <utils/compiler.h>
#include <cstdint>
#include <cerrno>
#include <utility>
namespace filament::backend {
std::pair<int, bool> NativeWindow::isValid(ANativeWindow* const anw) noexcept {
if (__builtin_available(android 28, *)) {
// this a proxy for is_valid()
auto const result = ANativeWindow_getBuffersDataSpace(anw);
return { result, result >= 0 };
}
// fallback on using private APIs
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
if (UTILS_LIKELY(pWindow->query)) {
int isValid = 0;
int const err = pWindow->query(anw, IS_VALID, &isValid);
return { err, bool(isValid) };
}
return { -EINVAL, false };
}
int NativeWindow::getNextFrameId(ANativeWindow* anw, uint64_t* frameId) {
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
return pWindow->perform(anw, GET_NEXT_FRAME_ID, frameId);
}
int NativeWindow::enableFrameTimestamps(ANativeWindow* anw, bool enable) {
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
return pWindow->perform(anw, ENABLE_FRAME_TIMESTAMPS, enable);
}
int NativeWindow::getCompositorTiming(ANativeWindow* anw,
int64_t* compositeDeadline, int64_t* compositeInterval,
int64_t* compositeToPresentLatency) {
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
return pWindow->perform(anw, GET_COMPOSITOR_TIMING,
compositeDeadline, compositeInterval, compositeToPresentLatency);
}
int NativeWindow::getFrameTimestamps(ANativeWindow* anw,
uint64_t frameId,
int64_t* outRequestedPresentTime, int64_t* outAcquireTime,
int64_t* outLatchTime, int64_t* outFirstRefreshStartTime,
int64_t* outLastRefreshStartTime, int64_t* outGpuCompositionDoneTime,
int64_t* outDisplayPresentTime, int64_t* outDequeueReadyTime,
int64_t* outReleaseTime) {
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
return pWindow->perform(anw, GET_FRAME_TIMESTAMPS,
frameId, outRequestedPresentTime, outAcquireTime, outLatchTime,
outFirstRefreshStartTime, outLastRefreshStartTime,
outGpuCompositionDoneTime, outDisplayPresentTime,
outDequeueReadyTime, outReleaseTime);
}
} // namespace filament::backend
@@ -1,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
-16
View File
@@ -122,22 +122,6 @@ bool Platform::pumpEvents() noexcept {
return false;
}
bool Platform::isCompositorTimingSupported() const noexcept {
return false;
}
bool Platform::queryCompositorTiming(SwapChain const*, CompositorTiming*) const noexcept {
return false;
}
bool Platform::setPresentFrameId(SwapChain const*, uint64_t) noexcept {
return false;
}
bool Platform::queryFrameTimestamps(SwapChain const*, uint64_t, FrameTimestamps*) const noexcept {
return false;
}
void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept {
mInsertBlob = std::move(insertBlob);
mRetrieveBlob = std::move(retrieveBlob);
+11 -2
View File
@@ -19,7 +19,6 @@
#include "MetalContext.h"
#include "MetalFlags.h"
#include "MetalUtils.h"
#include <backend/DriverEnums.h>
#include <backend/platforms/PlatformMetal.h>
@@ -261,8 +260,18 @@ public:
// In practice, MetalRingBuffer is used for argument buffers, which are kept in the constant
// address space. Constant buffers have specific alignment requirements when specifying an
// offset.
#if defined(FILAMENT_IOS)
#if TARGET_OS_SIMULATOR
// The iOS simulator has differing alignment requirements.
static constexpr auto METAL_CONSTANT_BUFFER_OFFSET_ALIGNMENT = 256;
#else
static constexpr auto METAL_CONSTANT_BUFFER_OFFSET_ALIGNMENT = 4;
#endif // TARGET_OS_SIMULATOR
#else
static constexpr auto METAL_CONSTANT_BUFFER_OFFSET_ALIGNMENT = 32;
#endif
static inline auto computeSlotSize(MTLSizeAndAlign layout) {
return align(align(layout.size, layout.align), getUniformBufferOffsetAlignment());
return align(align(layout.size, layout.align), METAL_CONSTANT_BUFFER_OFFSET_ALIGNMENT);
}
MetalRingBuffer(id<MTLDevice> device, MTLResourceOptions options, MTLSizeAndAlign layout,
+2 -33
View File
@@ -95,38 +95,6 @@ void initializeSupportedGpuFamilies(MetalContext* context) {
}
}
void logMTLCommandBufferError(MTLCommandBufferError error) {
#define MTL_COMMAND_ERROR_CASE(ERR) \
if (error == (ERR)) { \
LOG(ERROR) << "Filament Metal error: " #ERR "."; \
return; \
}
#if !defined(FILAMENT_IOS)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorDeviceRemoved)
#endif
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorNone)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorInternal)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorTimeout)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorPageFault)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorAccessRevoked)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorNotPermitted)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorOutOfMemory)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorInvalidResource)
if (@available(macOS 11.0, *)) {
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorMemoryless)
}
if (@available(iOS 15.0, macOS 12.0, *)) {
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorStackOverflow)
}
LOG(ERROR) << "Filament Metal unknown error.";
#undef MTL_COMMAND_ERROR_CASE
}
id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
if (context->pendingCommandBuffer) {
return context->pendingCommandBuffer;
@@ -152,7 +120,8 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
}
if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) {
logMTLCommandBufferError(errorCode);
LOG(ERROR) << "Filament Metal command buffer errored with code: " << errorCode << " ("
<< stringifyMTLCommandBufferError(errorCode) << ").";
}
}];
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)
+19 -29
View File
@@ -29,7 +29,6 @@
#include "MetalHandles.h"
#include "MetalState.h"
#include "MetalTimerQuery.h"
#include "MetalUtils.h"
#include <backend/platforms/PlatformMetal.h>
#include <backend/platforms/PlatformMetal-ObjC.h>
@@ -621,9 +620,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
auto& sc = mContext->sampleCountLookup;
samples = sc[std::min(MAX_SAMPLE_COUNT, samples)];
using AttachmentInfo = MetalRenderTarget::AttachmentInfo;
AttachmentInfo colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { {} };
MetalRenderTarget::Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {{}};
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (none(targetBufferFlags & getTargetBufferFlagsAt(i))) {
continue;
@@ -637,7 +634,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
colorAttachments[i] = { colorTexture, color[i].level, color[i].layer };
}
AttachmentInfo depthAttachment = {};
MetalRenderTarget::Attachment depthAttachment = {};
if (any(targetBufferFlags & TargetBufferFlags::DEPTH)) {
FILAMENT_CHECK_PRECONDITION(depth.handle)
<< "The DEPTH flag was specified, but invalid depth handle provided.";
@@ -647,7 +644,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
depthAttachment = { depthTexture, depth.level, depth.layer };
}
AttachmentInfo stencilAttachment = {};
MetalRenderTarget::Attachment stencilAttachment = {};
if (any(targetBufferFlags & TargetBufferFlags::STENCIL)) {
FILAMENT_CHECK_PRECONDITION(stencil.handle)
<< "The STENCIL flag was specified, but invalid stencil handle provided.";
@@ -671,6 +668,8 @@ void MetalDriver::createFenceR(Handle<HwFence> fh, utils::ImmutableCString&& tag
void MetalDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags,
utils::ImmutableCString&& tag) {
// TODO: support MSAA swapchain
if (UTILS_UNLIKELY(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER)) {
CVPixelBufferRef pixelBuffer = (CVPixelBufferRef) nativeWindow;
construct_handle<MetalSwapChain>(sch, *mContext, mPlatform, pixelBuffer, flags);
@@ -1039,8 +1038,6 @@ void MetalDriver::updateStreams(DriverApi* driver) {
void MetalDriver::destroyFence(Handle<HwFence> fh) {
if (fh) {
auto* fence = handle_cast<MetalFence>(fh);
fence->cancel();
destruct_handle<MetalFence>(fh);
}
}
@@ -1152,7 +1149,8 @@ bool MetalDriver::isSRGBSwapChainSupported() {
}
bool MetalDriver::isMSAASwapChainSupported(uint32_t) {
return true;
// TODO: support MSAA swapchain
return false;
}
bool MetalDriver::isProtectedContentSupported() {
@@ -1254,10 +1252,6 @@ size_t MetalDriver::getMaxArrayTextureLayers() {
return 256;
}
size_t MetalDriver::getUniformBufferOffsetAlignment() {
return ::filament::backend::getUniformBufferOffsetAlignment();
}
void MetalDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& data,
uint32_t byteOffset) {
FILAMENT_CHECK_PRECONDITION(data.buffer)
@@ -1562,9 +1556,9 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
auto srcTarget = handle_cast<MetalRenderTarget>(src);
// We always readPixels from the COLOR0 attachment.
MetalAttachment color = srcTarget->getDrawColorAttachment(0);
MetalRenderTarget::Attachment color = srcTarget->getDrawColorAttachment(0);
id<MTLTexture> srcTexture = color.getTexture();
size_t miplevel = color.getLevel();
size_t miplevel = color.level;
// Clamp height and width to actual texture's height and width
MTLSize srcTextureSize = MTLSizeMake(srcTexture.width >> miplevel, srcTexture.height >> miplevel, 1);
@@ -1772,8 +1766,8 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
};
// We always blit from/to the COLOR0 attachment.
MetalAttachment const srcColorAttachment = srcTarget->getReadColorAttachment(0);
MetalAttachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0);
MetalRenderTarget::Attachment const srcColorAttachment = srcTarget->getReadColorAttachment(0);
MetalRenderTarget::Attachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0);
if (srcColorAttachment && dstColorAttachment) {
FILAMENT_CHECK_PRECONDITION(
@@ -1785,13 +1779,13 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
args.filter = filter;
args.source.region = srcTarget->getRegionFromClientRect(srcRect);
args.source.texture = srcColorAttachment.getTexture();
args.source.level = srcColorAttachment.getLevel();
args.source.slice = srcColorAttachment.getLayer();
args.source.level = srcColorAttachment.level;
args.source.slice = srcColorAttachment.layer;
args.destination.region = dstTarget->getRegionFromClientRect(dstRect);
args.destination.texture = dstColorAttachment.getTexture();
args.destination.level = dstColorAttachment.getLevel();
args.destination.slice = dstColorAttachment.getLayer();
args.destination.level = dstColorAttachment.level;
args.destination.slice = dstColorAttachment.layer;
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blitDEPRECATED");
}
@@ -1827,28 +1821,24 @@ void MetalDriver::bindPipeline(PipelineState const& ps) {
auto [fragment, vertex] = functions.getRasterFunctions();
// Pipeline state
MetalRenderTarget* const rt = mContext->currentRenderTarget;
MTLPixelFormat colorPixelFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { MTLPixelFormatInvalid };
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
const auto& attachment = rt->getDrawColorAttachment(i);
const auto& attachment = mContext->currentRenderTarget->getDrawColorAttachment(i);
if (!attachment) {
continue;
}
colorPixelFormat[i] = attachment.getPixelFormat();
assert_invariant(attachment.getSampleCount() == rt->getSampleCount());
}
MTLPixelFormat depthPixelFormat = MTLPixelFormatInvalid;
const auto& depthAttachment = rt->getDepthAttachment();
const auto& depthAttachment = mContext->currentRenderTarget->getDepthAttachment();
if (depthAttachment) {
depthPixelFormat = depthAttachment.getPixelFormat();
assert_invariant(depthAttachment.getSampleCount() == rt->getSampleCount());
}
MTLPixelFormat stencilPixelFormat = MTLPixelFormatInvalid;
const auto& stencilAttachment = rt->getStencilAttachment();
const auto& stencilAttachment = mContext->currentRenderTarget->getStencilAttachment();
if (stencilAttachment) {
stencilPixelFormat = stencilAttachment.getPixelFormat();
assert_invariant(isMetalFormatStencil(stencilPixelFormat));
assert_invariant(stencilAttachment.getSampleCount() == rt->getSampleCount());
}
MetalPipelineState const pipelineState {
.vertexFunction = vertex,
@@ -1866,7 +1856,7 @@ void MetalDriver::bindPipeline(PipelineState const& ps) {
},
.depthAttachmentPixelFormat = depthPixelFormat,
.stencilAttachmentPixelFormat = stencilPixelFormat,
.sampleCount = rt->getSampleCount(),
.sampleCount = mContext->currentRenderTarget->getSamples(),
.blendState = BlendState {
.alphaBlendOperation = getMetalBlendOperation(rs.blendEquationAlpha),
.rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB),
+32
View File
@@ -451,6 +451,38 @@ inline MTLTextureSwizzleChannels getSwizzleChannels(TextureSwizzle r, TextureSwi
getSwizzle(a));
}
inline const char* stringifyMTLCommandBufferError(MTLCommandBufferError error) {
#if !defined(FILAMENT_IOS)
if (error == MTLCommandBufferErrorDeviceRemoved) {
return "MTLCommandBufferErrorDeviceRemoved";
}
#endif
switch (error) {
case MTLCommandBufferErrorNone:
return "MTLCommandBufferErrorNone";
case MTLCommandBufferErrorInternal:
return "MTLCommandBufferErrorInternal";
case MTLCommandBufferErrorTimeout:
return "MTLCommandBufferErrorTimeout";
case MTLCommandBufferErrorPageFault:
return "MTLCommandBufferErrorPageFault";
case MTLCommandBufferErrorAccessRevoked:
return "MTLCommandBufferErrorAccessRevoked";
case MTLCommandBufferErrorNotPermitted:
return "MTLCommandBufferErrorNotPermitted";
case MTLCommandBufferErrorOutOfMemory:
return "MTLCommandBufferErrorOutOfMemory";
case MTLCommandBufferErrorInvalidResource:
return "MTLCommandBufferErrorInvalidResource";
case MTLCommandBufferErrorMemoryless:
return "MTLCommandBufferErrorMemoryless";
case MTLCommandBufferErrorStackOverflow:
return "MTLCommandBufferErrorStackOverflow";
default:
return "Unknown";
}
}
} // namespace backend
} // namespace filament
+57 -93
View File
@@ -49,61 +49,6 @@
namespace filament {
namespace backend {
class MetalAttachment {
public:
MetalAttachment() = default;
MetalAttachment(id<MTLTexture> texture, uint8_t level = 0, uint16_t layer = 0)
: mLevel(level),
mLayer(layer),
mTexture(texture) {
assert_invariant(texture);
}
explicit operator bool() const { return mTexture != nil; }
id<MTLTexture> getTexture() const { return mTexture; }
id<MTLTexture> getMsaaTexture() const { return mMsaaTexture; }
MTLPixelFormat getPixelFormat() const {
return mTexture ? mTexture.pixelFormat : MTLPixelFormatInvalid;
}
NSUInteger getSampleCount() const {
if (mMsaaTexture) {
return mMsaaTexture.sampleCount;
}
if (mTexture) {
return mTexture.sampleCount;
}
return 1u;
}
uint8_t getLevel() const { return mLevel; }
uint16_t getLayer() const { return mLayer; }
MetalAttachment withMsaaTexture(id<MTLTexture> msaa) const {
assert_invariant(mTexture != nil);
assert_invariant(mTexture.sampleCount == 1u);
MetalAttachment result = *this;
result.mMsaaTexture = msaa;
return result;
}
static MetalAttachment invalidAttachment() { return MetalAttachment(); }
private:
uint8_t mLevel = 0;
uint16_t mLayer = 0;
// The main texture for this Attachment. May be single-sampled or MSAA.
id<MTLTexture> mTexture = nil;
// The MSAA "sidecar" texture that will resolve into mTexture.
// If this is non-nil, mTexture must be single-sampled.
id<MTLTexture> mMsaaTexture = nil;
};
class MetalSwapChain : public HwSwapChain {
public:
@@ -121,14 +66,14 @@ public:
~MetalSwapChain();
// Acquires a texture that can be used to render into this SwapChain and returns a
// MetalAttachment. The texture source depends on the type of SwapChain:
// Acquires a texture that can be used to render into this SwapChain.
// The texture source depends on the type of SwapChain:
// - CAMetalLayer-backed: acquires the CAMetalDrawable and returns its texture.
// - Headless: lazily creates and returns a headless texture.
// May return an invalid attachemnt if a drawable cannot be acquired.
MetalAttachment acquireDrawable();
MetalAttachment acquireDepthTexture();
MetalAttachment acquireStencilTexture();
id<MTLTexture> acquireDrawable();
id<MTLTexture> acquireDepthTexture();
id<MTLTexture> acquireStencilTexture();
void releaseDrawable();
@@ -143,7 +88,6 @@ public:
NSUInteger getSurfaceWidth() const;
NSUInteger getSurfaceHeight() const;
NSUInteger getSampleCount() const;
bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; }
@@ -162,24 +106,13 @@ private:
void scheduleFrameScheduledCallback();
void scheduleFrameCompletedCallback();
MetalAttachment acquireBaseDrawable();
id<MTLTexture> ensureDepthStencilTexture(uint32_t width, uint32_t height);
id<MTLTexture> ensureMsaaColorTexture(MTLPixelFormat format, uint32_t width, uint32_t height,
uint8_t samples);
id<MTLTexture> ensureMsaaDepthStencilTexture(MTLPixelFormat format, uint32_t width,
uint32_t height, uint8_t samples);
static MTLPixelFormat decideDepthStencilFormat(uint64_t flags);
static id<MTLTexture> createMultisampledTexture(MetalContext const& context,
MTLPixelFormat format, uint32_t width, uint32_t height, uint8_t samples);
void ensureDepthStencilTexture();
MetalContext& context;
PlatformMetal& platform;
id<CAMetalDrawable> drawable = nil;
id<MTLTexture> depthStencilTexture = nil;
id<MTLTexture> msaaColor = nil;
id<MTLTexture> msaaDepthStencil = nil;
id<MTLTexture> headlessDrawable = nil;
MTLPixelFormat depthStencilFormat = MTLPixelFormatInvalid;
NSUInteger headlessWidth = 0;
@@ -188,7 +121,6 @@ private:
std::shared_ptr<std::mutex> layerDrawableMutex;
MetalExternalImage externalImage;
SwapChainType type;
uint64_t flags;
int64_t abandonedUntilFrame = -1;
@@ -375,15 +307,51 @@ private:
class MetalRenderTarget : public HwRenderTarget {
public:
struct AttachmentInfo {
MetalTexture* texture = nullptr;
class Attachment {
public:
friend class MetalRenderTarget;
Attachment() = default;
Attachment(MetalTexture* metalTexture, uint8_t level = 0, uint16_t layer = 0) :
level(level), layer(layer),
texture(metalTexture->getMtlTextureForWrite()),
metalTexture(metalTexture) { }
id<MTLTexture> getTexture() const {
return texture;
}
NSUInteger getSampleCount() const {
return texture ? texture.sampleCount : 0u;
}
MTLPixelFormat getPixelFormat() const {
return texture ? texture.pixelFormat : MTLPixelFormatInvalid;
}
explicit operator bool() const {
return texture != nil;
}
uint8_t level = 0;
uint16_t layer = 0;
private:
id<MTLTexture> getMSAASidecarTexture() const {
// This should only be called from render targets associated with a MetalTexture.
assert_invariant(metalTexture);
return metalTexture->msaaSidecar;
}
id<MTLTexture> texture = nil;
MetalTexture* metalTexture = nullptr;
};
MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height, uint8_t samples,
AttachmentInfo colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
AttachmentInfo depthAttachment, AttachmentInfo stencilAttachment);
Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
Attachment depthAttachment, Attachment stencilAttachment);
explicit MetalRenderTarget(MetalContext* context)
: HwRenderTarget(0, 0), context(context), defaultRenderTarget(true) {}
@@ -419,14 +387,12 @@ public:
math::uint2 getAttachmentSize() noexcept;
MetalAttachment getDrawColorAttachment(size_t index);
MetalAttachment getReadColorAttachment(size_t index);
MetalAttachment getDepthAttachment();
MetalAttachment getStencilAttachment();
uint8_t getSamples() const { return samples; }
// Returns the number of samples that should be used in the pipeline state that renders to this
// render target. Takes into account "automagic" resolve and MSAA SwapChains.
NSUInteger getSampleCount() const;
Attachment getDrawColorAttachment(size_t index);
Attachment getReadColorAttachment(size_t index);
Attachment getDepthAttachment();
Attachment getStencilAttachment();
private:
@@ -436,12 +402,12 @@ private:
MetalContext* context;
bool defaultRenderTarget = false;
MetalAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
MetalAttachment depth = {};
MetalAttachment stencil = {};
math::uint2 attachmentSize = {};
uint8_t samples = 1;
Attachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
Attachment depth = {};
Attachment stencil = {};
math::uint2 attachmentSize = {};
};
// MetalFence is used to implement both Fences and Syncs.
@@ -463,8 +429,6 @@ public:
API_AVAILABLE(ios(12.0))
void onSignal(MetalFenceSignalBlock block);
void cancel();
private:
MetalContext& context;
+177 -272
View File
@@ -116,8 +116,7 @@ MetalSwapChain::MetalSwapChain(
depthStencilFormat(decideDepthStencilFormat(flags)),
layer(nativeWindow),
layerDrawableMutex(std::make_shared<std::mutex>()),
type(SwapChainType::CAMETALLAYER),
flags(flags) {
type(SwapChainType::CAMETALLAYER) {
FILAMENT_CHECK_PRECONDITION([nativeWindow isKindOfClass:[CAMetalLayer class]])
<< "nativeWindow pointer of class "
@@ -148,8 +147,7 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform, i
depthStencilFormat(decideDepthStencilFormat(flags)),
headlessWidth(width),
headlessHeight(height),
type(SwapChainType::HEADLESS),
flags(flags) {}
type(SwapChainType::HEADLESS) {}
MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform,
CVPixelBufferRef pixelBuffer, uint64_t flags)
@@ -157,8 +155,7 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform,
platform(platform),
depthStencilFormat(decideDepthStencilFormat(flags)),
externalImage(MetalExternalImage::createFromImage(context, pixelBuffer)),
type(SwapChainType::CVPIXELBUFFERREF),
flags(flags) {
type(SwapChainType::CVPIXELBUFFERREF) {
assert_invariant(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
MetalExternalImage::assertWritableImage(pixelBuffer);
assert_invariant(externalImage.isValid());
@@ -170,27 +167,6 @@ MTLPixelFormat MetalSwapChain::decideDepthStencilFormat(uint64_t flags) {
: MTLPixelFormatDepth32Float;
}
id<MTLTexture> MetalSwapChain::createMultisampledTexture(MetalContext const& context,
MTLPixelFormat format, uint32_t width, uint32_t height, uint8_t samples) {
MTLTextureDescriptor* descriptor =
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format
width:width
height:height
mipmapped:NO];
descriptor.textureType = MTLTextureType2DMultisample;
descriptor.sampleCount = samples;
descriptor.usage = MTLTextureUsageRenderTarget;
descriptor.resourceOptions = MTLResourceStorageModePrivate;
if (context.supportsMemorylessRenderTargets) {
if (@available(macOS 11.0, *)) {
descriptor.resourceOptions = MTLResourceStorageModeMemoryless;
}
}
return [context.device newTextureWithDescriptor:descriptor];
}
MetalSwapChain::~MetalSwapChain() {
}
@@ -214,18 +190,65 @@ NSUInteger MetalSwapChain::getSurfaceHeight() const {
return (NSUInteger) layer.drawableSize.height;
}
NSUInteger MetalSwapChain::getSampleCount() const {
if (flags & flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
return 4u;
}
return 1u;
}
bool MetalSwapChain::isAbandoned() const {
return context.currentFrame < abandonedUntilFrame;
}
id<MTLTexture> MetalSwapChain::acquireDrawable() {
if (drawable) {
return drawable.texture;
}
if (isHeadless()) {
if (headlessDrawable) {
return headlessDrawable;
}
// For headless surfaces we construct a "fake" drawable, which is simply a renderable
// texture.
MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor new];
textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
textureDescriptor.width = headlessWidth;
textureDescriptor.height = headlessHeight;
// Specify MTLTextureUsageShaderRead so the headless surface can be blitted from.
textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
#if defined(FILAMENT_IOS)
textureDescriptor.storageMode = MTLStorageModeShared;
#else
textureDescriptor.storageMode = MTLStorageModeManaged;
#endif
headlessDrawable = [context.device newTextureWithDescriptor:textureDescriptor];
return headlessDrawable;
}
if (isPixelBuffer()) {
return externalImage.getMtlTexture();
}
assert_invariant(isCaMetalLayer());
// CAMetalLayer's drawable pool is not thread safe. Use a mutex when
// calling -nextDrawable, or when releasing the last known reference
// to any CAMetalDrawable returned from a previous -nextDrawable.
{
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
drawable = [layer nextDrawable];
}
if (UTILS_UNLIKELY(drawable == nil)) {
switch (platform.getDrawableFailureBehavior()) {
case PlatformMetal::DrawableFailureBehavior::PANIC:
FILAMENT_CHECK_POSTCONDITION(drawable != nil) << "Could not obtain drawable.";
break;
case PlatformMetal::DrawableFailureBehavior::ABORT_FRAME:
abandonedUntilFrame = context.currentFrame + 1;
return nil;
}
}
return drawable.texture;
}
void MetalSwapChain::releaseDrawable() {
if (drawable) {
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
@@ -233,45 +256,32 @@ void MetalSwapChain::releaseDrawable() {
}
}
MetalAttachment MetalSwapChain::acquireDrawable() {
MetalAttachment attachment = acquireBaseDrawable();
if (auto texture = attachment.getTexture();
texture && flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
return attachment.withMsaaTexture(
ensureMsaaColorTexture(texture.pixelFormat, texture.width, texture.height, 4u));
}
return attachment;
id<MTLTexture> MetalSwapChain::acquireDepthTexture() {
ensureDepthStencilTexture();
assert_invariant(depthStencilTexture);
return depthStencilTexture;
}
MetalAttachment MetalSwapChain::acquireDepthTexture() {
MetalAttachment attachment =
MetalAttachment(ensureDepthStencilTexture(getSurfaceWidth(), getSurfaceHeight()));
if (auto texture = attachment.getTexture();
texture && flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
return attachment.withMsaaTexture(ensureMsaaDepthStencilTexture(texture.pixelFormat,
texture.width, texture.height, 4u));
id<MTLTexture> MetalSwapChain::acquireStencilTexture() {
if (!isMetalFormatStencil(depthStencilFormat)) {
return nil;
}
return attachment;
ensureDepthStencilTexture();
assert_invariant(depthStencilTexture);
return depthStencilTexture;
}
MetalAttachment MetalSwapChain::acquireStencilTexture() {
if (!(flags & SwapChain::CONFIG_HAS_STENCIL_BUFFER)) {
return MetalAttachment::invalidAttachment();
}
MetalAttachment attachment =
MetalAttachment(ensureDepthStencilTexture(getSurfaceWidth(), getSurfaceHeight()));
if (auto texture = attachment.getTexture();
texture && flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
return attachment.withMsaaTexture(ensureMsaaDepthStencilTexture(texture.pixelFormat,
texture.width, texture.height, 4u));
}
return attachment;
}
id<MTLTexture> MetalSwapChain::ensureDepthStencilTexture(uint32_t width, uint32_t height) {
if (UTILS_LIKELY(depthStencilTexture && depthStencilTexture.width == width &&
depthStencilTexture.height == height)) {
return depthStencilTexture;
void MetalSwapChain::ensureDepthStencilTexture() {
NSUInteger width = getSurfaceWidth();
NSUInteger height = getSurfaceHeight();
if (UTILS_LIKELY(depthStencilTexture)) {
// If the surface size has changed, we'll need to allocate a new depth/stencil texture.
if (UTILS_UNLIKELY(
depthStencilTexture.width != width || depthStencilTexture.height != height)) {
depthStencilTexture = nil;
} else {
return;
}
}
MTLTextureDescriptor* descriptor =
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:depthStencilFormat
@@ -280,26 +290,7 @@ id<MTLTexture> MetalSwapChain::ensureDepthStencilTexture(uint32_t width, uint32_
mipmapped:NO];
descriptor.usage = MTLTextureUsageRenderTarget;
descriptor.resourceOptions = MTLResourceStorageModePrivate;
return depthStencilTexture = [context.device newTextureWithDescriptor:descriptor];
}
id<MTLTexture> MetalSwapChain::ensureMsaaColorTexture(MTLPixelFormat format, uint32_t width, uint32_t height,
uint8_t samples) {
if (UTILS_LIKELY(msaaColor && msaaColor.pixelFormat == format && msaaColor.width == width &&
msaaColor.height == height && msaaColor.sampleCount == samples)) {
return msaaColor;
}
return msaaColor = createMultisampledTexture(context, format, width, height, samples);
}
id<MTLTexture> MetalSwapChain::ensureMsaaDepthStencilTexture(MTLPixelFormat format, uint32_t width,
uint32_t height, uint8_t samples) {
if (UTILS_LIKELY(msaaDepthStencil && msaaDepthStencil.pixelFormat == format &&
msaaDepthStencil.width == width && msaaDepthStencil.height == height &&
msaaDepthStencil.sampleCount == samples)) {
return msaaDepthStencil;
}
return msaaDepthStencil = createMultisampledTexture(context, format, width, height, samples);
depthStencilTexture = [context.device newTextureWithDescriptor:descriptor];
}
void MetalSwapChain::setFrameScheduledCallback(
@@ -463,62 +454,6 @@ void MetalSwapChain::scheduleFrameCompletedCallback() {
}];
}
MetalAttachment MetalSwapChain::acquireBaseDrawable() {
if (drawable) {
return MetalAttachment(drawable.texture);
}
if (isHeadless()) {
if (headlessDrawable) {
return MetalAttachment(headlessDrawable);
}
// For headless surfaces we construct a "fake" drawable, which is simply a renderable
// texture.
MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor new];
textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
textureDescriptor.width = headlessWidth;
textureDescriptor.height = headlessHeight;
// Specify MTLTextureUsageShaderRead so the headless surface can be blitted from.
textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
#if defined(FILAMENT_IOS)
textureDescriptor.storageMode = MTLStorageModeShared;
#else
textureDescriptor.storageMode = MTLStorageModeManaged;
#endif
headlessDrawable = [context.device newTextureWithDescriptor:textureDescriptor];
return MetalAttachment(headlessDrawable);
}
if (isPixelBuffer()) {
return MetalAttachment(externalImage.getMtlTexture());
}
assert_invariant(isCaMetalLayer());
// CAMetalLayer's drawable pool is not thread safe. Use a mutex when
// calling -nextDrawable, or when releasing the last known reference
// to any CAMetalDrawable returned from a previous -nextDrawable.
{
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
drawable = [layer nextDrawable];
}
if (UTILS_UNLIKELY(drawable == nil)) {
switch (platform.getDrawableFailureBehavior()) {
case PlatformMetal::DrawableFailureBehavior::PANIC:
FILAMENT_CHECK_POSTCONDITION(drawable != nil) << "Could not obtain drawable.";
break;
case PlatformMetal::DrawableFailureBehavior::ABORT_FRAME:
abandonedUntilFrame = context.currentFrame + 1;
return MetalAttachment::invalidAttachment();
}
}
return MetalAttachment(drawable.texture);
}
MetalBufferObject::MetalBufferObject(MetalContext& context, BufferObjectBinding bindingType,
BufferUsage usage, uint32_t byteCount)
: HwBufferObject(byteCount), buffer(context, bindingType, usage, byteCount) {}
@@ -1035,108 +970,90 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
}
MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height,
uint8_t samples, AttachmentInfo colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
AttachmentInfo depthAttachment, AttachmentInfo stencilAttachment)
: HwRenderTarget(width, height),
context(context),
samples(samples) {
math::uint2 tmin = { std::numeric_limits<uint32_t>::max() };
UTILS_UNUSED_IN_RELEASE math::uint2 tmax = { 0 };
uint8_t samples, Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
Attachment depthAttachment, Attachment stencilAttachment) :
HwRenderTarget(width, height), context(context), samples(samples) {
math::uint2 tmin = {std::numeric_limits<uint32_t>::max()};
UTILS_UNUSED_IN_RELEASE math::uint2 tmax = {0};
UTILS_UNUSED_IN_RELEASE size_t attachmentCount = 0;
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
MetalTexture* const texture = colorAttachments[i].texture;
const auto& level = colorAttachments[i].level;
const auto& layer = colorAttachments[i].layer;
if (!texture) {
if (!colorAttachments[i]) {
continue;
}
color[i] = colorAttachments[i];
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
FILAMENT_CHECK_PRECONDITION(color[i].getSampleCount() <= samples)
<< "MetalRenderTarget was initialized with a MSAA COLOR" << i
<< " texture, but sample count is " << samples << ".";
const auto twidth = std::max(1u, texture->width >> level);
const auto theight = std::max(1u, texture->height >> level);
auto t = color[i].metalTexture;
const auto twidth = std::max(1u, t->width >> color[i].level);
const auto theight = std::max(1u, t->height >> color[i].level);
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
attachmentCount++;
id<MTLTexture> mtlTexture = texture->getMtlTextureForWrite();
color[i] = MetalAttachment(mtlTexture, level, layer);
// If we were given a single-sampled texture but the samples parameter is > 1, we create
// a multisampled sidecar texture and do a resolve automatically.
if (samples > 1 && texture->samples == 1) {
auto& sidecar = texture->msaaSidecar;
if (samples > 1 && color[i].getSampleCount() == 1) {
auto& sidecar = color[i].metalTexture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
texture->height, samples);
sidecar = createMultisampledTexture(color[i].getPixelFormat(),
color[i].metalTexture->width, color[i].metalTexture->height, samples);
}
color[i] = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
}
}
if (depthAttachment.texture) {
MetalTexture* const texture = depthAttachment.texture;
const auto& level = depthAttachment.level;
const auto& layer = depthAttachment.layer;
if (depthAttachment) {
depth = depthAttachment;
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
FILAMENT_CHECK_PRECONDITION(depth.getSampleCount() <= samples)
<< "MetalRenderTarget was initialized with a MSAA DEPTH texture, but sample count "
"is "
<< samples << ".";
const auto twidth = std::max(1u, texture->width >> level);
const auto theight = std::max(1u, texture->height >> level);
auto t = depth.metalTexture;
const auto twidth = std::max(1u, t->width >> depth.level);
const auto theight = std::max(1u, t->height >> depth.level);
tmin = { math::min(tmin.x, twidth), math::min(tmin.y, theight) };
tmax = { math::max(tmax.x, twidth), math::max(tmax.y, theight) };
attachmentCount++;
id<MTLTexture> mtlTexture = texture->getMtlTextureForWrite();
depth = MetalAttachment(mtlTexture, level, layer);
// If we were given a single-sampled texture but the samples parameter is > 1, we create
// a multisampled sidecar texture and do a resolve automatically.
if (samples > 1 && texture->samples == 1) {
auto& sidecar = texture->msaaSidecar;
if (samples > 1 && depth.getSampleCount() == 1) {
auto& sidecar = depth.metalTexture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
texture->height, samples);
sidecar = createMultisampledTexture(depth.getPixelFormat(),
depth.metalTexture->width, depth.metalTexture->height, samples);
}
depth = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
}
}
if (stencilAttachment.texture) {
// stencil = stencilAttachment;
MetalTexture* const texture = stencilAttachment.texture;
const auto& level = stencilAttachment.level;
const auto& layer = stencilAttachment.layer;
if (stencilAttachment) {
stencil = stencilAttachment;
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
FILAMENT_CHECK_PRECONDITION(stencil.getSampleCount() <= samples)
<< "MetalRenderTarget was initialized with a MSAA STENCIL texture, but sample "
"count is "
<< samples << ".";
const auto twidth = std::max(1u, texture->width >> level);
const auto theight = std::max(1u, texture->height >> level);
auto t = stencil.metalTexture;
const auto twidth = std::max(1u, t->width >> stencil.level);
const auto theight = std::max(1u, t->height >> stencil.level);
tmin = { math::min(tmin.x, twidth), math::min(tmin.y, theight) };
tmax = { math::max(tmax.x, twidth), math::max(tmax.y, theight) };
attachmentCount++;
id<MTLTexture> mtlTexture = texture->getMtlTextureForWrite();
stencil = MetalAttachment(mtlTexture, level, layer);
// If we were given a single-sampled texture but the samples parameter is > 1, we create
// a multisampled sidecar texture and do a resolve automatically.
if (samples > 1 && texture->samples == 1) {
auto& sidecar = texture->msaaSidecar;
if (samples > 1 && stencil.getSampleCount() == 1) {
auto& sidecar = stencil.metalTexture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
texture->height, samples);
sidecar = createMultisampledTexture(stencil.getPixelFormat(),
stencil.metalTexture->width, stencil.metalTexture->height, samples);
}
stencil = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
}
}
@@ -1159,105 +1076,105 @@ void MetalRenderTarget::setUpRenderPassAttachments(MTLRenderPassDescriptor* desc
const auto discardFlags = params.flags.discardEnd;
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
MetalAttachment attachment = getDrawColorAttachment(i);
Attachment attachment = getDrawColorAttachment(i);
if (!attachment) {
continue;
}
descriptor.colorAttachments[i].texture = attachment.getTexture();
descriptor.colorAttachments[i].level = attachment.getLevel();
descriptor.colorAttachments[i].slice = attachment.getLayer();
descriptor.colorAttachments[i].level = attachment.level;
descriptor.colorAttachments[i].slice = attachment.layer;
descriptor.colorAttachments[i].loadAction = getLoadAction(params, getTargetBufferFlagsAt(i));
descriptor.colorAttachments[i].storeAction = getStoreAction(params,
getTargetBufferFlagsAt(i));
descriptor.colorAttachments[i].clearColor = MTLClearColorMake(
params.clearColor.r, params.clearColor.g, params.clearColor.b, params.clearColor.a);
if (attachment.getMsaaTexture()) {
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
const bool automaticResolve = samples > 1 && attachment.getSampleCount() == 1;
if (automaticResolve) {
// We're rendering into our temporary MSAA texture and doing an automatic resolve.
// We should not be attempting to load anything into the MSAA texture.
// This might happen if the user is rendering multiple views into a MSAA SwapChain,
// which is not supported. In that case, ignore the load action.
if (descriptor.colorAttachments[i].loadAction == MTLLoadActionLoad) {
descriptor.colorAttachments[i].loadAction = MTLLoadActionDontCare;
}
assert_invariant(descriptor.colorAttachments[i].loadAction != MTLLoadActionLoad);
assert_invariant(!defaultRenderTarget);
descriptor.colorAttachments[i].texture = attachment.getMsaaTexture();
id<MTLTexture> sidecar = attachment.getMSAASidecarTexture();
assert_invariant(sidecar);
descriptor.colorAttachments[i].texture = sidecar;
descriptor.colorAttachments[i].level = 0;
descriptor.colorAttachments[i].slice = 0;
const bool discard = any(discardFlags & getTargetBufferFlagsAt(i));
if (!discard) {
descriptor.colorAttachments[i].resolveTexture = attachment.getTexture();
descriptor.colorAttachments[i].resolveLevel = attachment.getLevel();
descriptor.colorAttachments[i].resolveSlice = attachment.getLayer();
descriptor.colorAttachments[i].resolveTexture = attachment.texture;
descriptor.colorAttachments[i].resolveLevel = attachment.level;
descriptor.colorAttachments[i].resolveSlice = attachment.layer;
descriptor.colorAttachments[i].storeAction = MTLStoreActionMultisampleResolve;
}
}
}
MetalAttachment depthAttachment = getDepthAttachment();
Attachment depthAttachment = getDepthAttachment();
if (depthAttachment) {
descriptor.depthAttachment.texture = depthAttachment.getTexture();
descriptor.depthAttachment.level = depthAttachment.getLevel();
descriptor.depthAttachment.slice = depthAttachment.getLayer();
descriptor.depthAttachment.level = depthAttachment.level;
descriptor.depthAttachment.slice = depthAttachment.layer;
descriptor.depthAttachment.loadAction = getLoadAction(params, TargetBufferFlags::DEPTH);
descriptor.depthAttachment.storeAction = getStoreAction(params, TargetBufferFlags::DEPTH);
descriptor.depthAttachment.clearDepth = params.clearDepth;
}
if (depthAttachment.getMsaaTexture()) {
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
const bool depthAutomaticResolve = samples > 1 && depthAttachment.getSampleCount() == 1;
if (depthAutomaticResolve) {
// We're rendering into our temporary MSAA texture and doing an automatic resolve.
// We should not be attempting to load anything into the MSAA texture.
// This might happen if the user is rendering multiple views into a MSAA SwapChain,
// which is not supported. In that case, ignore the load action.
if (descriptor.depthAttachment.loadAction == MTLLoadActionLoad) {
descriptor.depthAttachment.loadAction = MTLLoadActionDontCare;
}
assert_invariant(descriptor.depthAttachment.loadAction != MTLLoadActionLoad);
assert_invariant(!defaultRenderTarget);
descriptor.depthAttachment.texture = depthAttachment.getMsaaTexture();
id<MTLTexture> sidecar = depthAttachment.getMSAASidecarTexture();
assert_invariant(sidecar);
descriptor.depthAttachment.texture = sidecar;
descriptor.depthAttachment.level = 0;
descriptor.depthAttachment.slice = 0;
const bool discard = any(discardFlags & TargetBufferFlags::DEPTH);
if (!discard) {
assert_invariant(context->supportsAutoDepthResolve);
descriptor.depthAttachment.resolveTexture = depthAttachment.getTexture();
descriptor.depthAttachment.resolveLevel = depthAttachment.getLevel();
descriptor.depthAttachment.resolveSlice = depthAttachment.getLayer();
descriptor.depthAttachment.resolveLevel = depthAttachment.level;
descriptor.depthAttachment.resolveSlice = depthAttachment.layer;
descriptor.depthAttachment.storeAction = MTLStoreActionMultisampleResolve;
}
}
MetalAttachment stencilAttachment = getStencilAttachment();
Attachment stencilAttachment = getStencilAttachment();
if (stencilAttachment) {
descriptor.stencilAttachment.texture = stencilAttachment.getTexture();
descriptor.stencilAttachment.level = stencilAttachment.getLevel();
descriptor.stencilAttachment.slice = stencilAttachment.getLayer();
descriptor.stencilAttachment.level = stencilAttachment.level;
descriptor.stencilAttachment.slice = stencilAttachment.layer;
descriptor.stencilAttachment.loadAction = getLoadAction(params, TargetBufferFlags::STENCIL);
descriptor.stencilAttachment.storeAction = getStoreAction(params, TargetBufferFlags::STENCIL);
descriptor.stencilAttachment.clearStencil = params.clearStencil;
}
if (stencilAttachment.getMsaaTexture()) {
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
const bool stencilAutomaticResolve = samples > 1 && stencilAttachment.getSampleCount() == 1;
if (stencilAutomaticResolve) {
// We're rendering into our temporary MSAA texture and doing an automatic resolve.
// We should not be attempting to load anything into the MSAA texture.
// This might happen if the user is rendering multiple views into a MSAA SwapChain,
// which is not supported. In that case, ignore the load action.
if (descriptor.stencilAttachment.loadAction == MTLLoadActionLoad) {
descriptor.stencilAttachment.loadAction = MTLLoadActionDontCare;
}
assert_invariant(descriptor.stencilAttachment.loadAction != MTLLoadActionLoad);
assert_invariant(!defaultRenderTarget);
descriptor.stencilAttachment.texture = stencilAttachment.getMsaaTexture();
id<MTLTexture> sidecar = stencilAttachment.getMSAASidecarTexture();
assert_invariant(sidecar);
descriptor.stencilAttachment.texture = sidecar;
descriptor.stencilAttachment.level = 0;
descriptor.stencilAttachment.slice = 0;
const bool discard = any(discardFlags & TargetBufferFlags::STENCIL);
if (!discard) {
assert_invariant(context->supportsAutoDepthResolve);
descriptor.stencilAttachment.resolveTexture = stencilAttachment.getTexture();
descriptor.stencilAttachment.resolveLevel = stencilAttachment.getLevel();
descriptor.stencilAttachment.resolveSlice = stencilAttachment.getLayer();
descriptor.stencilAttachment.resolveLevel = stencilAttachment.level;
descriptor.stencilAttachment.resolveSlice = stencilAttachment.layer;
descriptor.stencilAttachment.storeAction = MTLStoreActionMultisampleResolve;
if (@available(iOS 12.0, *)) {
descriptor.stencilAttachment.stencilResolveFilter = MTLMultisampleStencilResolveFilterSample0;
@@ -1272,46 +1189,44 @@ bool MetalRenderTarget::involvesAbandonedSwapChain() const noexcept {
return (draw && draw->isAbandoned()) || (read && read->isAbandoned());
}
MetalAttachment MetalRenderTarget::getDrawColorAttachment(size_t index) {
MetalRenderTarget::Attachment MetalRenderTarget::getDrawColorAttachment(size_t index) {
assert_invariant(index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
Attachment result = color[index];
if (index == 0 && defaultRenderTarget) {
assert_invariant(context->currentDrawSwapChain);
return context->currentDrawSwapChain->acquireDrawable();
result.texture = context->currentDrawSwapChain->acquireDrawable();
// acquireDrawable may fail to acquire the drawable, in which case result.texture will be
// nil, and an invalid attachment
}
return color[index];
return result;
}
MetalAttachment MetalRenderTarget::getReadColorAttachment(size_t index) {
MetalRenderTarget::Attachment MetalRenderTarget::getReadColorAttachment(size_t index) {
assert_invariant(index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
Attachment result = color[index];
if (index == 0 && defaultRenderTarget) {
assert_invariant(context->currentReadSwapChain);
return context->currentReadSwapChain->acquireDrawable();
result.texture = context->currentReadSwapChain->acquireDrawable();
// acquireDrawable may fail to acquire the drawable, in which case result.texture will be
// nil, and an invalid attachment
}
return color[index];
return result;
}
MetalAttachment MetalRenderTarget::getDepthAttachment() {
MetalRenderTarget::Attachment MetalRenderTarget::getDepthAttachment() {
Attachment result = depth;
if (defaultRenderTarget) {
assert_invariant(context->currentDrawSwapChain);
return context->currentDrawSwapChain->acquireDepthTexture();
result.texture = context->currentDrawSwapChain->acquireDepthTexture();
}
return depth;
return result;
}
MetalAttachment MetalRenderTarget::getStencilAttachment() {
MetalRenderTarget::Attachment MetalRenderTarget::getStencilAttachment() {
Attachment result = stencil;
if (defaultRenderTarget) {
assert_invariant(context->currentDrawSwapChain);
return context->currentDrawSwapChain->acquireStencilTexture();
result.texture = context->currentDrawSwapChain->acquireStencilTexture();
}
return stencil;
}
NSUInteger MetalRenderTarget::getSampleCount() const {
if (defaultRenderTarget) {
assert_invariant(context->currentDrawSwapChain);
return context->currentDrawSwapChain->getSampleCount();
}
return samples;
return result;
}
MTLLoadAction MetalRenderTarget::getLoadAction(const RenderPassParams& params,
@@ -1395,30 +1310,20 @@ void MetalFence::onSignal(MetalFenceSignalBlock block) {
FenceStatus MetalFence::wait(uint64_t timeoutNs) {
if (@available(iOS 12, *)) {
using ns = std::chrono::nanoseconds;
// keep a reference on the stack so our state cannot be destroyed while we wait
auto state = this->state;
std::unique_lock<std::mutex> guard(state->mutex);
while (state->status == FenceStatus::TIMEOUT_EXPIRED) {
if (timeoutNs == FENCE_WAIT_FOR_EVER) {
state->cv.wait(guard);
} else if (timeoutNs == 0 ||
state->cv.wait_for(guard, ns(timeoutNs)) == std::cv_status::timeout) {
return state->status;
return FenceStatus::TIMEOUT_EXPIRED;
}
}
return state->status;
return FenceStatus::CONDITION_SATISFIED;
}
return FenceStatus::ERROR;
}
void MetalFence::cancel() {
std::unique_lock guard(state->mutex);
state->status = FenceStatus::ERROR;
state->cv.notify_all();
}
MetalDescriptorSetLayout::MetalDescriptorSetLayout(DescriptorSetLayout&& l) noexcept
: mLayout(std::move(l)) {
size_t dynamicBindings = 0;
-16
View File
@@ -50,22 +50,6 @@ id<MTLTexture> createTextureViewWithLodRange(id<MTLTexture> texture, NSUInteger
*/
id<MTLTexture> createTextureViewWithSingleSlice(id<MTLTexture> texture, NSUInteger slice);
/**
* Returns the required alignment for uniform buffer offsets.
*/
constexpr size_t getUniformBufferOffsetAlignment() {
#if defined(FILAMENT_IOS)
#if TARGET_OS_SIMULATOR
// The iOS simulator has differing alignment requirements.
return 256;
#else
return 4;
#endif // TARGET_OS_SIMULATOR
#else
return 32;
#endif
}
} // namespace filament::backend
#endif //TNT_FILAMENT_DRIVER_METALUTILS_H
-1
View File
@@ -68,5 +68,4 @@ id<MTLTexture> createTextureViewWithSingleSlice(id<MTLTexture> texture, NSUInteg
slices:NSMakeRange(slice, 1)];
}
} // namespace filament::backend
-4
View File
@@ -274,10 +274,6 @@ size_t NoopDriver::getMaxArrayTextureLayers() {
return 256u;
}
size_t NoopDriver::getUniformBufferOffsetAlignment() {
return 256u;
}
void NoopDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
uint32_t byteOffset) {
scheduleDestroy(std::move(p));
@@ -303,10 +303,13 @@ void GLDescriptorSet::bind(
offset += offsets[dynamicOffsetIndex++];
}
if (arg.bo) {
p.updateUniforms(bindingPoint, arg.bo->gl.id, arg.bo->gl.buffer, arg.bo->age, offset);
auto buffer = static_cast<char const*>(arg.bo->gl.buffer) + offset;
p.updateUniforms(bindingPoint, arg.bo->gl.id, buffer, arg.bo->age);
}
} else if constexpr (std::is_same_v<T, Sampler>) {
GLuint const unit = p.getTextureUnit(set, binding);
if (arg.handle) {
GLTexture const* const t = handleAllocator.handle_cast<GLTexture*>(arg.handle);
gl.bindTexture(unit, t->gl.target, t->gl.id, t->gl.external);
+1 -10
View File
@@ -2287,14 +2287,9 @@ mat3f OpenGLDriver::getStreamTransformMatrix(Handle<HwStream> sh) {
void OpenGLDriver::destroyFence(Handle<HwFence> fh) {
if (fh) {
GLFence const* const f = handle_cast<GLFence*>(fh);
GLFence const* f = handle_cast<GLFence*>(fh);
if (mPlatform.canCreateFence() || mContext.isES2()) {
mPlatform.destroyFence(f->fence);
} else {
// signal waiters it's time to give-up
std::unique_lock const lock(f->state->lock);
f->state->status = FenceStatus::ERROR;
f->state->cond.notify_all();
}
destruct(fh, f);
}
@@ -2671,10 +2666,6 @@ size_t OpenGLDriver::getMaxArrayTextureLayers() {
return mContext.gets.max_array_texture_layers;
}
size_t OpenGLDriver::getUniformBufferOffsetAlignment() {
return mContext.gets.uniform_buffer_offset_alignment;
}
// ------------------------------------------------------------------------------------------------
// Swap chains
// ------------------------------------------------------------------------------------------------
+6 -11
View File
@@ -52,8 +52,8 @@ using namespace backend;
struct OpenGLProgram::LazyInitializationData {
Program::DescriptorSetInfo descriptorBindings;
Program::BindingUniformsInfo bindingUniformInfo;
FixedCapacityVector<Program::PushConstant> vertexPushConstants;
FixedCapacityVector<Program::PushConstant> fragmentPushConstants;
utils::FixedCapacityVector<Program::PushConstant> vertexPushConstants;
utils::FixedCapacityVector<Program::PushConstant> fragmentPushConstants;
};
@@ -141,7 +141,7 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
context.useProgram(program);
UTILS_NOUNROLL
for (descriptor_set_t set = 0; set < MAX_DESCRIPTOR_SET_COUNT; set++) {
for (backend::descriptor_set_t set = 0; set < MAX_DESCRIPTOR_SET_COUNT; set++) {
for (Program::Descriptor const& entry: lazyInitializationData.descriptorBindings[set]) {
switch (entry.type) {
case DescriptorType::UNIFORM_BUFFER:
@@ -214,8 +214,8 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
case DescriptorType::INPUT_ATTACHMENT:
break;
}
CHECK_GL_ERROR()
}
CHECK_GL_ERROR()
}
if (context.isES2()) {
@@ -262,25 +262,20 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
}
void OpenGLProgram::updateUniforms(
uint32_t const index, GLuint const id, void const* buffer,
uint16_t const age, uint32_t const offset) const noexcept {
uint32_t index, GLuint id, void const* buffer, uint16_t age) const noexcept {
assert_invariant(mUniformsRecords);
assert_invariant(buffer);
// only update the uniforms if the UBO has changed since last time we updated
UniformsRecord const& records = mUniformsRecords[index];
if (records.id == id && records.age == age && records.offset == offset) {
if (records.id == id && records.age == age) {
return;
}
records.id = id;
records.age = age;
records.offset = offset;
assert_invariant(records.uniforms.size() == records.locations.size());
// apply the offset to the buffer
buffer = static_cast<char const*>(buffer) + offset;
for (size_t i = 0, c = records.uniforms.size(); i < c; i++) {
Program::Uniform const& u = records.uniforms[i];
GLint const loc = records.locations[i];
+1 -2
View File
@@ -88,7 +88,7 @@ public:
}
// For ES2 only
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age, uint32_t offset) const noexcept;
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) const noexcept;
void setRec709ColorSpace(bool rec709) const noexcept;
PushConstantBundle getPushConstants() {
@@ -123,7 +123,6 @@ private:
LocationInfo locations;
mutable GLuint id = 0;
mutable uint16_t age = std::numeric_limits<uint16_t>::max();
mutable uint32_t offset = 0;
};
UniformsRecord const* mUniformsRecords = nullptr;
GLint mRec709Location : 24; // 4 bytes
@@ -346,18 +346,9 @@ void ShaderCompilerService::compileProgram(
// Check status of program linking. If it failed, errors will be logged.
bool const linked = checkLinkStatusAndCleanupShaders(token);
// We panic if it failed to create the program.
#if FILAMENT_ENABLE_MATDBG
if (UTILS_UNLIKELY(!linked)) {
LOG(ERROR) << "OpenGL program " << token->name.c_str_safe()
<< " failed to link or compile";
// this will allow us to continue without crashing
token->gl.program = 0;
}
#else
FILAMENT_CHECK_POSTCONDITION(linked)
<< "OpenGL program " << token->name.c_str_safe()
<< " failed to link or compile";
#endif
}
// Now `token->gl.program` must be populated, so we signal the completion
// of the linking. We don't need to check the result of the program here
@@ -488,27 +479,16 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) {
assert_invariant(token);// This function should be called when the token is still alive.
ensureTokenIsReady(token);
#if !FILAMENT_ENABLE_MATDBG
assert_invariant(token->gl.program);
#endif
GLuint const program = token->gl.program;
if (mMode != Mode::THREAD_POOL) {
// Check status of program linking. If it failed, errors will be logged.
bool const linked = checkLinkStatusAndCleanupShaders(token);
#if FILAMENT_ENABLE_MATDBG
if (UTILS_UNLIKELY(!linked)) {
LOG(ERROR)
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
token->gl.program = 0;
}
#else
// We panic if it failed to create the program.
FILAMENT_CHECK_POSTCONDITION(linked)
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
#endif
// The program has been successfully created. Try caching the program blob for
// non-THREAD_POOL modes. In the THREAD_POOL mode, caching is performed in the pool.
tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
@@ -57,11 +57,11 @@ using namespace backend;
// The Android NDK doesn't expose extensions, fake it with eglGetProcAddress
namespace glext {
UTILS_PRIVATE PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR = {};
UTILS_PRIVATE PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR = {};
UTILS_PRIVATE PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR = {};
UTILS_PRIVATE PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = {};
UTILS_PRIVATE PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = {};
}
using namespace glext;
@@ -73,11 +73,11 @@ void PlatformEGL::logEglError(const char* name) noexcept {
logEglError(name, eglGetError());
}
void PlatformEGL::logEglError(const char* name, EGLint const error) noexcept {
void PlatformEGL::logEglError(const char* name, EGLint error) noexcept {
LOG(ERROR) << name << " failed with " << getEglErrorName(error);
}
const char* PlatformEGL::getEglErrorName(EGLint const error) noexcept {
const char* PlatformEGL::getEglErrorName(EGLint error) noexcept {
switch (error) {
case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED";
case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS";
@@ -129,13 +129,13 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& drive
if (!initialized) {
EGLDeviceEXT eglDevice;
EGLint numDevices;
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT =
(PFNEGLQUERYDEVICESEXTPROC)eglGetProcAddress("eglQueryDevicesEXT");
if (eglQueryDevicesEXT != nullptr) {
eglQueryDevicesEXT(1, &eglDevice, &numDevices);
if(auto* getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
eglGetProcAddress("eglGetPlatformDisplay"))) {
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, nullptr);
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, 0);
initialized = eglInitialize(mEGLDisplay, &major, &minor);
}
}
@@ -150,7 +150,7 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& drive
importGLESExtensionsEntryPoints();
#endif
auto const extensions = GLUtils::split(eglQueryString(mEGLDisplay, EGL_EXTENSIONS));
auto extensions = GLUtils::split(eglQueryString(mEGLDisplay, EGL_EXTENSIONS));
ext.egl.ANDROID_recordable = extensions.has("EGL_ANDROID_recordable");
ext.egl.KHR_gl_colorspace = extensions.has("EGL_KHR_gl_colorspace");
ext.egl.KHR_create_context = extensions.has("EGL_KHR_create_context");
@@ -158,12 +158,12 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& drive
ext.egl.KHR_surfaceless_context = extensions.has("EGL_KHR_surfaceless_context");
ext.egl.EXT_protected_content = extensions.has("EGL_EXT_protected_content");
eglCreateSyncKHR = PFNEGLCREATESYNCKHRPROC(eglGetProcAddress("eglCreateSyncKHR"));
eglDestroySyncKHR = PFNEGLDESTROYSYNCKHRPROC(eglGetProcAddress("eglDestroySyncKHR"));
eglClientWaitSyncKHR = PFNEGLCLIENTWAITSYNCKHRPROC(eglGetProcAddress("eglClientWaitSyncKHR"));
eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC) eglGetProcAddress("eglCreateSyncKHR");
eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress("eglDestroySyncKHR");
eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC) eglGetProcAddress("eglClientWaitSyncKHR");
eglCreateImageKHR = PFNEGLCREATEIMAGEKHRPROC(eglGetProcAddress("eglCreateImageKHR"));
eglDestroyImageKHR = PFNEGLDESTROYIMAGEKHRPROC(eglGetProcAddress("eglDestroyImageKHR"));
eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC) eglGetProcAddress("eglCreateImageKHR");
eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC) eglGetProcAddress("eglDestroyImageKHR");
EGLint const pbufferAttribs[] = {
EGL_WIDTH, 1,
@@ -210,27 +210,27 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& drive
#endif
// Configure GPU context priority level for scheduling and preemption
if (driverConfig.gpuContextPriority != GpuContextPriority::DEFAULT) {
if (driverConfig.gpuContextPriority != Platform::GpuContextPriority::DEFAULT) {
if (extensions.has("EGL_IMG_context_priority")) {
EGLint priorityLevel = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
const char* priorityName;
switch (driverConfig.gpuContextPriority) {
case GpuContextPriority::DEFAULT:
case Platform::GpuContextPriority::DEFAULT:
assert_invariant(false);
break;
case GpuContextPriority::LOW:
case Platform::GpuContextPriority::LOW:
priorityLevel = EGL_CONTEXT_PRIORITY_LOW_IMG;
priorityName = "LOW";
break;
case GpuContextPriority::MEDIUM:
case Platform::GpuContextPriority::MEDIUM:
priorityLevel = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
priorityName = "MEDIUM";
break;
case GpuContextPriority::HIGH:
case Platform::GpuContextPriority::HIGH:
priorityLevel = EGL_CONTEXT_PRIORITY_HIGH_IMG;
priorityName = "HIGH";
break;
case GpuContextPriority::REALTIME:
case Platform::GpuContextPriority::REALTIME:
priorityLevel = EGL_CONTEXT_PRIORITY_HIGH_IMG;
priorityName = "REALTIME(=HIGH)";
break;
@@ -263,7 +263,7 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& drive
for (size_t tries = 0; tries < 3; tries++) {
mEGLContext = eglCreateContext(mEGLDisplay, eglConfig,
sharedContext, contextAttribs.data());
(EGLContext)sharedContext, contextAttribs.data());
if (UTILS_LIKELY(mEGLContext != EGL_NO_CONTEXT)) {
break;
}
@@ -362,10 +362,10 @@ bool PlatformEGL::isProtectedContextSupported() const noexcept {
return ext.egl.EXT_protected_content;
}
void PlatformEGL::createContext(bool const shared) {
EGLConfig const config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig;
void PlatformEGL::createContext(bool shared) {
EGLConfig config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig;
EGLContext const context = eglCreateContext(mEGLDisplay, config,
EGLContext context = eglCreateContext(mEGLDisplay, config,
shared ? mEGLContext : EGL_NO_CONTEXT, mContextAttribs.data());
if (UTILS_UNLIKELY(context == EGL_NO_CONTEXT)) {
@@ -389,7 +389,7 @@ void PlatformEGL::releaseContext() noexcept {
mAdditionalContexts.erase(
std::remove_if(mAdditionalContexts.begin(), mAdditionalContexts.end(),
[context](EGLContext const c) {
[context](EGLContext c) {
return c == context;
}), mAdditionalContexts.end());
@@ -406,15 +406,14 @@ void PlatformEGL::terminate() noexcept {
if (mEGLContextProtected != EGL_NO_CONTEXT) {
eglDestroyContext(mEGLDisplay, mEGLContextProtected);
}
for (auto const context : mAdditionalContexts) {
for (auto context : mAdditionalContexts) {
eglDestroyContext(mEGLDisplay, context);
}
eglTerminate(mEGLDisplay);
eglReleaseThread();
}
EGLConfig PlatformEGL::findSwapChainConfig(
uint64_t const flags, bool const window, bool const pbuffer) const {
EGLConfig PlatformEGL::findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const {
// Find config that support ES3.
EGLConfig config = EGL_NO_CONFIG_KHR;
EGLint configsCount;
@@ -482,24 +481,13 @@ EGLConfig PlatformEGL::findSwapChainConfig(
return config;
}
EGLConfig PlatformEGL::getSuitableConfigForSwapChain(
uint64_t const flags, bool const window, bool const pbuffer) const {
EGLConfig config = EGL_NO_CONFIG_KHR;
if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) {
config = findSwapChainConfig(flags, window, pbuffer);
} else {
config = mEGLConfig;
}
return config;
}
// -----------------------------------------------------------------------------------------------
bool PlatformEGL::isSRGBSwapChainSupported() const noexcept {
return ext.egl.KHR_gl_colorspace;
}
bool PlatformEGL::isMSAASwapChainSupported(uint32_t const samples) const noexcept {
bool PlatformEGL::isMSAASwapChainSupported(uint32_t samples) const noexcept {
if (samples <= 1) {
return true;
}
@@ -512,22 +500,126 @@ bool PlatformEGL::isMSAASwapChainSupported(uint32_t const samples) const noexcep
}
Platform::SwapChain* PlatformEGL::createSwapChain(
void* nativeWindow, uint64_t const flags) {
auto* const sc = new(std::nothrow) SwapChainEGL(*this, nativeWindow, flags);
void* nativeWindow, uint64_t flags) {
Config attribs;
// Remove flags for unsupported features.
if (isSRGBSwapChainSupported()) {
if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) {
attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE;
}
if (isProtectedContextSupported()) {
if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT;
}
if (flags & SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES) {
if (!isMSAASwapChainSupported(4)) {
flags &= ~SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES;
}
}
// Retrieve a config for the given flags.
EGLConfig config = EGL_NO_CONFIG_KHR;
if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) {
config = findSwapChainConfig(flags, true, false);
} else {
config = mEGLConfig;
}
EGLSurface sur = EGL_NO_SURFACE;
if (UTILS_LIKELY(config != EGL_NO_CONFIG_KHR)) {
sur = eglCreateWindowSurface(mEGLDisplay, config,
(EGLNativeWindowType)nativeWindow, attribs.data());
if (UTILS_LIKELY(sur != EGL_NO_SURFACE)) {
// this is not fatal
eglSurfaceAttrib(mEGLDisplay, sur, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED);
} else {
logEglError("PlatformEGL::createSwapChain: eglCreateWindowSurface");
}
} else {
// error already logged
}
SwapChainEGL* const sc = new(std::nothrow) SwapChainEGL({
.sur = sur,
.attribs = std::move(attribs),
.nativeWindow = (EGLNativeWindowType)nativeWindow,
.config = config,
.flags = flags
});
return sc;
}
Platform::SwapChain* PlatformEGL::createSwapChain(
uint32_t const width, uint32_t const height, uint64_t const flags) {
auto* const sc = new(std::nothrow) SwapChainEGL(*this, width, height, flags);
uint32_t width, uint32_t height, uint64_t flags) {
Config attribs = {
{ EGL_WIDTH, EGLint(width) },
{ EGL_HEIGHT, EGLint(height) },
};
if (ext.egl.KHR_gl_colorspace) {
if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) {
attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE;
}
if (ext.egl.EXT_protected_content) {
if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT;
}
EGLConfig config = EGL_NO_CONFIG_KHR;
if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) {
config = findSwapChainConfig(flags, true, false);
} else {
config = mEGLConfig;
}
EGLSurface sur = EGL_NO_SURFACE;
if (UTILS_LIKELY(config != EGL_NO_CONFIG_KHR)) {
sur = eglCreatePbufferSurface(mEGLDisplay, config, attribs.data());
if (UTILS_UNLIKELY(sur == EGL_NO_SURFACE)) {
logEglError("PlatformEGL::createSwapChain: eglCreatePbufferSurface");
}
} else {
// error already logged
}
SwapChainEGL* const sc = new(std::nothrow) SwapChainEGL({
.sur = sur,
.attribs = std::move(attribs),
.config = config,
.flags = flags
});
return sc;
}
void PlatformEGL::destroySwapChain(SwapChain* swapChain) noexcept {
if (swapChain) {
SwapChainEGL* const sc = static_cast<SwapChainEGL*>(swapChain);
sc->terminate(*this);
delete sc;
SwapChainEGL const* const sc = static_cast<SwapChainEGL const*>(swapChain);
if (sc->sur != EGL_NO_SURFACE) {
// - if EGL_KHR_surfaceless_context is supported, mEGLDummySurface is EGL_NO_SURFACE.
// - this is actually a bit too aggressive, but it is a rare operation.
egl.makeCurrent(mEGLDummySurface, mEGLDummySurface);
eglDestroySurface(mEGLDisplay, sc->sur);
delete sc;
}
}
}
@@ -543,11 +635,11 @@ OpenGLPlatform::ContextType PlatformEGL::getCurrentContextType() const noexcept
return mCurrentContextType;
}
bool PlatformEGL::makeCurrent(ContextType const type,
bool PlatformEGL::makeCurrent(ContextType type,
SwapChain* drawSwapChain, SwapChain* readSwapChain) {
SwapChainEGL const* const dsc = static_cast<SwapChainEGL const*>(drawSwapChain);
SwapChainEGL const* const rsc = static_cast<SwapChainEGL const*>(readSwapChain);
EGLContext const context = getContextForType(type);
EGLContext context = getContextForType(type);
EGLBoolean const success = egl.makeCurrent(context, dsc->sur, rsc->sur);
return success == EGL_TRUE;
}
@@ -567,7 +659,7 @@ void PlatformEGL::makeCurrent(SwapChain* drawSwapChain,
// we need a protected context
if (UTILS_UNLIKELY(mEGLContextProtected == EGL_NO_CONTEXT)) {
// we don't have one, create it!
EGLConfig const config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig;
EGLConfig config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig;
Config protectedContextAttribs{ mContextAttribs };
protectedContextAttribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
mEGLContextProtected = eglCreateContext(mEGLDisplay, config, mEGLContext,
@@ -639,7 +731,7 @@ Platform::Fence* PlatformEGL::createFence() noexcept {
void PlatformEGL::destroyFence(Fence* fence) noexcept {
#ifdef EGL_KHR_reusable_sync
EGLSyncKHR const sync = fence;
EGLSyncKHR sync = (EGLSyncKHR) fence;
if (sync != EGL_NO_SYNC_KHR) {
eglDestroySyncKHR(mEGLDisplay, sync);
}
@@ -647,11 +739,11 @@ void PlatformEGL::destroyFence(Fence* fence) noexcept {
}
FenceStatus PlatformEGL::waitFence(
Fence* fence, uint64_t const timeout) noexcept {
Fence* fence, uint64_t timeout) noexcept {
#ifdef EGL_KHR_reusable_sync
EGLSyncKHR const sync = fence;
EGLSyncKHR sync = (EGLSyncKHR) fence;
if (sync != EGL_NO_SYNC_KHR) {
EGLint const status = eglClientWaitSyncKHR(mEGLDisplay, sync, 0, EGLTimeKHR(timeout));
EGLint const status = eglClientWaitSyncKHR(mEGLDisplay, sync, 0, (EGLTimeKHR)timeout);
if (status == EGL_CONDITION_SATISFIED_KHR) {
return FenceStatus::CONDITION_SATISFIED;
}
@@ -691,7 +783,7 @@ bool PlatformEGL::setExternalImage(void* externalImage,
return true;
}
Platform::ExternalImageHandle PlatformEGL::createExternalImage(EGLImageKHR const eglImage) noexcept {
Platform::ExternalImageHandle PlatformEGL::createExternalImage(EGLImageKHR eglImage) noexcept {
auto* const p = new(std::nothrow) ExternalImageEGL;
p->eglImage = eglImage;
return ExternalImageHandle{p};
@@ -707,12 +799,13 @@ bool PlatformEGL::setExternalImage(ExternalImageHandleRef externalImage,
void PlatformEGL::initializeGlExtensions() noexcept {
// We're guaranteed to be on an ES platform, since we're using EGL
GLUtils::unordered_string_set glExtensions;
const char* const extensions = (const char*)glGetString(GL_EXTENSIONS);
GLUtils::unordered_string_set const glExtensions = GLUtils::split(extensions);
glExtensions = GLUtils::split(extensions);
ext.gl.OES_EGL_image_external_essl3 = glExtensions.has("GL_OES_EGL_image_external_essl3");
}
EGLContext PlatformEGL::getContextForType(ContextType const type) const noexcept {
EGLContext PlatformEGL::getContextForType(ContextType type) const noexcept {
switch (type) {
case ContextType::NONE:
return EGL_NO_CONTEXT;
@@ -721,10 +814,9 @@ EGLContext PlatformEGL::getContextForType(ContextType const type) const noexcept
case ContextType::PROTECTED:
return mEGLContextProtected;
}
return EGL_NO_CONTEXT;
}
bool PlatformEGL::checkIfMSAASwapChainSupported(uint32_t const samples) const noexcept {
bool PlatformEGL::checkIfMSAASwapChainSupported(uint32_t samples) const noexcept {
// Retrieve the config to see if the given number of samples is supported. The result is cached.
Config configAttribs = {
{ EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT },
@@ -733,7 +825,7 @@ bool PlatformEGL::checkIfMSAASwapChainSupported(uint32_t const samples) const no
{ EGL_BLUE_SIZE, 8 },
{ EGL_DEPTH_SIZE, 24 },
{ EGL_SAMPLE_BUFFERS, 1 },
{ EGL_SAMPLES, EGLint(samples) },
{ EGL_SAMPLES, (EGLint)samples },
};
if (!ext.egl.KHR_no_config_context) {
@@ -757,108 +849,10 @@ bool PlatformEGL::checkIfMSAASwapChainSupported(uint32_t const samples) const no
}
// ---------------------------------------------------------------------------------------------
// PlatformEGL::SwapChainEGL
PlatformEGL::SwapChainEGL::SwapChainEGL(PlatformEGL const& platform, void* nativeWindow, uint64_t flags) {
// Remove flags for unsupported features.
if (platform.isSRGBSwapChainSupported()) {
if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) {
attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE;
}
if (platform.isProtectedContextSupported()) {
if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT;
}
if (flags & SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES) {
if (!platform.isMSAASwapChainSupported(4)) {
flags &= ~SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES;
}
}
// Retrieve a config for the given flags.
config = platform.getSuitableConfigForSwapChain(flags, true, false);
sur = EGL_NO_SURFACE;
if (UTILS_LIKELY(config != EGL_NO_CONFIG_KHR)) {
EGLDisplay const dpy = platform.getEglDisplay();
sur = eglCreateWindowSurface(dpy, config,
EGLNativeWindowType(nativeWindow), attribs.data());
if (UTILS_LIKELY(sur != EGL_NO_SURFACE)) {
// this is not fatal
eglSurfaceAttrib(dpy, sur, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED);
} else {
logEglError("PlatformEGL::createSwapChain: eglCreateWindowSurface");
}
} else {
// error already logged
}
this->nativeWindow = EGLNativeWindowType(nativeWindow);
this->flags = flags;
}
PlatformEGL::SwapChainEGL::SwapChainEGL(PlatformEGL const& platform,
uint32_t const width, uint32_t const height, uint64_t flags) {
attribs = {
{ EGL_WIDTH, EGLint(width) },
{ EGL_HEIGHT, EGLint(height) },
};
if (platform.ext.egl.KHR_gl_colorspace) {
if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) {
attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE;
}
if (platform.ext.egl.EXT_protected_content) {
if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
}
} else {
flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT;
}
config = platform.getSuitableConfigForSwapChain(flags, false, true);
sur = EGL_NO_SURFACE;
if (UTILS_LIKELY(config != EGL_NO_CONFIG_KHR)) {
EGLDisplay const dpy = platform.getEglDisplay();
sur = eglCreatePbufferSurface(dpy, config, attribs.data());
if (UTILS_UNLIKELY(sur == EGL_NO_SURFACE)) {
logEglError("PlatformEGL::createSwapChain: eglCreatePbufferSurface");
}
} else {
// error already logged
}
this->flags = flags;
}
void PlatformEGL::SwapChainEGL::terminate(PlatformEGL& platform) {
if (sur != EGL_NO_SURFACE) {
// - if EGL_KHR_surfaceless_context is supported, mEGLDummySurface is EGL_NO_SURFACE.
// - this is actually a bit too aggressive, but it is a rare operation.
platform.egl.makeCurrent(platform.mEGLDummySurface, platform.mEGLDummySurface);
eglDestroySurface(platform.mEGLDisplay, sur);
sur = EGL_NO_SURFACE;
}
}
// ---------------------------------------------------------------------------------------------
// PlatformEGL::Config
PlatformEGL::Config::Config() = default;
PlatformEGL::Config::Config(std::initializer_list<std::pair<EGLint, EGLint>> const list)
PlatformEGL::Config::Config(std::initializer_list<std::pair<EGLint, EGLint>> list)
: mConfig(list) {
mConfig.emplace_back(EGL_NONE, EGL_NONE);
}
@@ -874,7 +868,7 @@ EGLint& PlatformEGL::Config::operator[](EGLint name) {
}
EGLint PlatformEGL::Config::operator[](EGLint name) const {
auto const pos = std::find_if(mConfig.begin(), mConfig.end(),
auto pos = std::find_if(mConfig.begin(), mConfig.end(),
[name](auto&& v) { return v.first == name; });
assert_invariant(pos != mConfig.end());
return pos->second;
@@ -882,7 +876,7 @@ EGLint PlatformEGL::Config::operator[](EGLint name) const {
void PlatformEGL::Config::erase(EGLint name) noexcept {
if (name != EGL_NONE) {
auto const pos = std::find_if(mConfig.begin(), mConfig.end(),
auto pos = std::find_if(mConfig.begin(), mConfig.end(),
[name](auto&& v) { return v.first == name; });
if (pos != mConfig.end()) {
mConfig.erase(pos);
@@ -891,10 +885,9 @@ void PlatformEGL::Config::erase(EGLint name) noexcept {
}
// ------------------------------------------------------------------------------------------------
// PlatformEGL::EGL
EGLBoolean PlatformEGL::EGL::makeCurrent(EGLContext const context, EGLSurface const drawSurface,
EGLSurface const readSurface) {
EGLBoolean PlatformEGL::EGL::makeCurrent(EGLContext context, EGLSurface drawSurface,
EGLSurface readSurface) {
if (UTILS_UNLIKELY((
mCurrentContext != context ||
drawSurface != mCurrentDrawSurface || readSurface != mCurrentReadSurface))) {
@@ -14,35 +14,28 @@
* limitations under the License.
*/
#include <backend/platforms/PlatformEGLAndroid.h>
#include "opengl/GLUtils.h"
#include <backend/AcquiredImage.h>
#include <backend/DriverEnums.h>
#include <backend/Platform.h>
#include <backend/platforms/OpenGLPlatform.h>
#include <backend/platforms/PlatformEGL.h>
#include <backend/platforms/PlatformEGLAndroid.h>
#include <private/backend/BackendUtilsAndroid.h>
#include <private/backend/VirtualMachineEnv.h>
#include "AndroidNativeWindow.h"
#include "AndroidSwapChainHelper.h"
#include "opengl/GLUtils.h"
#include "ExternalStreamManagerAndroid.h"
#include <android/api-level.h>
#include <android/native_window.h>
#include <android/hardware_buffer.h>
#include <utils/android/PerformanceHintManager.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/android/PerformanceHintManager.h>
#include <utils/compiler.h>
#include <utils/compiler.h>
#include <utils/ostream.h>
#include <utils/ostream.h>
#include <math/mat3.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
@@ -51,15 +44,13 @@
#include <jni.h>
#include <array>
#include <chrono>
#include <limits>
#include <mutex>
#include <new>
#include <string_view>
#include <dlfcn.h>
#include <unistd.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
@@ -85,14 +76,14 @@ extern PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;
extern PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;
extern PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;
UTILS_PRIVATE PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC eglGetNativeClientBufferANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLPRESENTATIONTIMEANDROIDPROC eglPresentationTimeANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLGETCOMPOSITORTIMINGSUPPORTEDANDROIDPROC eglGetCompositorTimingSupportedANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLGETCOMPOSITORTIMINGANDROIDPROC eglGetCompositorTimingANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLGETNEXTFRAMEIDANDROIDPROC eglGetNextFrameIdANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSUPPORTEDANDROIDPROC eglGetFrameTimestampSupportedANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSANDROIDPROC eglGetFrameTimestampsANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID{}; // NOLINT(*-use-internal-linkage)
UTILS_PRIVATE PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC eglGetNativeClientBufferANDROID = {};
UTILS_PRIVATE PFNEGLPRESENTATIONTIMEANDROIDPROC eglPresentationTimeANDROID = {};
UTILS_PRIVATE PFNEGLGETCOMPOSITORTIMINGSUPPORTEDANDROIDPROC eglGetCompositorTimingSupportedANDROID = {};
UTILS_PRIVATE PFNEGLGETCOMPOSITORTIMINGANDROIDPROC eglGetCompositorTimingANDROID = {};
UTILS_PRIVATE PFNEGLGETNEXTFRAMEIDANDROIDPROC eglGetNextFrameIdANDROID = {};
UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSUPPORTEDANDROIDPROC eglGetFrameTimestampSupportedANDROID = {};
UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSANDROIDPROC eglGetFrameTimestampsANDROID = {};
UTILS_PRIVATE PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID = {};
}
using namespace glext;
@@ -111,35 +102,27 @@ PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmFor
// ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::PlatformEGLAndroid() noexcept
: mExternalStreamManager(ExternalStreamManagerAndroid::create()) {
: PlatformEGL(),
mExternalStreamManager(ExternalStreamManagerAndroid::create()),
mInitializeJvmForPerformanceManagerIfNeeded(),
mPerformanceHintManager() {
mOSVersion = android_get_device_api_level();
if (mOSVersion < 0) {
mOSVersion = __ANDROID_API_FUTURE__;
}
// TODO: remove this code one the new NDK is available
void* nativeWindowLibHandle = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW);
if (nativeWindowLibHandle) {
ANativeWindow_setProducerThrottlingEnabled =
(int32_t(*)(ANativeWindow*, bool))dlsym(nativeWindowLibHandle,
"ANativeWindow_setProducerThrottlingEnabled");
ANativeWindow_isProducerThrottlingEnabled =
(int32_t(*)(ANativeWindow*, bool*))dlsym(nativeWindowLibHandle,
"ANativeWindow_isProducerThrottlingEnabled");
if (ANativeWindow_setProducerThrottlingEnabled &&
ANativeWindow_isProducerThrottlingEnabled) {
mHasProducerThrottlingControl = true;
LOG(INFO) << "Producer Throttling API available";
}
mNativeWindowLib = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW);
if (mNativeWindowLib) {
ANativeWindow_getBuffersDefaultDataSpace =
(int32_t(*)(ANativeWindow*))dlsym(mNativeWindowLib,
"ANativeWindow_getBuffersDefaultDataSpace");
}
}
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
// note: we don't need to dlclose() mNativeWindowLib here, because the library will be cleaned
// when the process ends and dlopen() are ref-counted. dlclose() NDK documentation documents
// not to call dlclose().
if (mNativeWindowLib) {
dlclose(mNativeWindowLib);
}
}
void PlatformEGLAndroid::terminate() noexcept {
@@ -147,58 +130,65 @@ void PlatformEGLAndroid::terminate() noexcept {
PlatformEGL::terminate();
}
static constexpr std::string_view kNativeWindowInvalidMsg =
static constexpr const std::string_view kNativeWindowInvalidMsg =
"ANativeWindow is invalid. It probably has been destroyed. EGL surface = ";
bool PlatformEGLAndroid::makeCurrent(ContextType const type,
bool PlatformEGLAndroid::makeCurrent(ContextType type,
SwapChain* drawSwapChain,
SwapChain* readSwapChain) {
// remember the current swapchain for use in beginFrame()
mCurrentDrawSwapChain = static_cast<SwapChainEGLAndroid*>(drawSwapChain);
// fast & safe path
if (UTILS_LIKELY(!mAssertNativeWindowIsValid)) {
return PlatformEGL::makeCurrent(type, drawSwapChain, readSwapChain);
}
SwapChainEGL const* const dsc = static_cast<SwapChainEGL const*>(drawSwapChain);
// anw can be nullptr if we're using a pbuffer surface
if (dsc->nativeWindow) {
auto [err, valid] = NativeWindow::isValid(dsc->nativeWindow);
FILAMENT_CHECK_POSTCONDITION(!err && valid) << kNativeWindowInvalidMsg << dsc->sur;
if (ANativeWindow_getBuffersDefaultDataSpace) {
// anw can be nullptr if we're using a pbuffer surface
if (UTILS_LIKELY(dsc->nativeWindow)) {
// this a proxy of is_valid()
auto result = ANativeWindow_getBuffersDefaultDataSpace(dsc->nativeWindow);
FILAMENT_CHECK_POSTCONDITION(result >= 0) << kNativeWindowInvalidMsg << dsc->sur;
}
} else {
// If we don't have ANativeWindow_getBuffersDefaultDataSpace, we revert to using the
// private query() call.
// Shadow version if the real ANativeWindow, so we can access the query() hook. Query
// has existed since forever, probably Android 1.0.
struct NativeWindow {
// is valid query enum value
enum { IS_VALID = 17 };
uint64_t pad[18];
int (* query)(ANativeWindow const*, int, int*);
} const* pWindow = reinterpret_cast<NativeWindow const*>(dsc->nativeWindow);
int isValid = 0;
if (UTILS_LIKELY(pWindow->query)) { // just in case it's nullptr
int const err = pWindow->query(dsc->nativeWindow, NativeWindow::IS_VALID, &isValid);
if (UTILS_LIKELY(err >= 0)) { // in case the IS_VALID enum is not recognized
// query call succeeded
FILAMENT_CHECK_POSTCONDITION(isValid) << kNativeWindowInvalidMsg << dsc->sur;
}
}
}
return PlatformEGL::makeCurrent(type, drawSwapChain, readSwapChain);
}
void PlatformEGLAndroid::beginFrame(
int64_t const monotonic_clock_ns,
int64_t monotonic_clock_ns,
int64_t refreshIntervalNs,
uint32_t const frameId) noexcept {
// associate the user frameid with the system frame id
setPresentFrameId(mCurrentDrawSwapChain, frameId);
uint32_t frameId) noexcept {
if (mPerformanceHintSession.isValid()) {
if (refreshIntervalNs <= 0) {
// we're not provided with a target time, use the display period, if everything fails,
// assume 16.67ms
// we're not provided with a target time, assume 16.67ms
refreshIntervalNs = 16'666'667;
CompositorTiming compositorTiming{};
bool const hasCompositorTiming =
queryCompositorTiming(mCurrentDrawSwapChain, &compositorTiming);
if (hasCompositorTiming && compositorTiming.compositeInterval > 0) {
refreshIntervalNs = compositorTiming.compositeInterval;
}
}
mStartTimeOfActualWork = clock::time_point(std::chrono::nanoseconds(monotonic_clock_ns));
mPerformanceHintSession.updateTargetWorkDuration(refreshIntervalNs);
}
PlatformEGL::beginFrame(monotonic_clock_ns, refreshIntervalNs, frameId);
}
void PlatformEGLAndroid::preCommit() noexcept {
void backend::PlatformEGLAndroid::preCommit() noexcept {
if (mPerformanceHintSession.isValid()) {
auto const actualWorkDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(
clock::now() - mStartTimeOfActualWork);
@@ -208,7 +198,7 @@ void PlatformEGLAndroid::preCommit() noexcept {
}
Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
const DriverConfig& driverConfig) {
const Platform::DriverConfig& driverConfig) {
// the refresh rate default value doesn't matter, we change it later
int32_t const tid = gettid();
@@ -216,46 +206,29 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
mPerformanceHintManager, &tid, 1, 16'666'667 };
Driver* driver = PlatformEGL::createDriver(sharedContext, driverConfig);
auto const extensions = GLUtils::split(eglQueryString(getEglDisplay(), EGL_EXTENSIONS));
auto extensions = GLUtils::split(eglQueryString(mEGLDisplay, EGL_EXTENSIONS));
ext.egl.ANDROID_presentation_time = extensions.has("EGL_ANDROID_presentation_time");
ext.egl.ANDROID_get_frame_timestamps = extensions.has("EGL_ANDROID_get_frame_timestamps");
ext.egl.ANDROID_native_fence_sync = extensions.has("EGL_ANDROID_native_fence_sync");
eglGetNativeClientBufferANDROID = (PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC) eglGetProcAddress(
"eglGetNativeClientBufferANDROID");
eglGetNativeClientBufferANDROID =
PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC(eglGetProcAddress(
"eglGetNativeClientBufferANDROID"));
if (ext.egl.ANDROID_presentation_time) {
eglGetNativeClientBufferANDROID =
PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC(eglGetProcAddress(
"eglGetNativeClientBufferANDROID"));
if (extensions.has("EGL_ANDROID_presentation_time")) {
eglPresentationTimeANDROID = (PFNEGLPRESENTATIONTIMEANDROIDPROC)eglGetProcAddress(
"eglPresentationTimeANDROID");
}
if (ext.egl.ANDROID_get_frame_timestamps) {
eglGetCompositorTimingSupportedANDROID =
PFNEGLGETCOMPOSITORTIMINGSUPPORTEDANDROIDPROC(eglGetProcAddress(
"eglGetCompositorTimingSupportedANDROID"));
eglGetCompositorTimingANDROID =
PFNEGLGETCOMPOSITORTIMINGANDROIDPROC(eglGetProcAddress(
"eglGetCompositorTimingANDROID"));
eglGetNextFrameIdANDROID =
PFNEGLGETNEXTFRAMEIDANDROIDPROC(eglGetProcAddress(
"eglGetNextFrameIdANDROID"));
eglGetFrameTimestampSupportedANDROID =
PFNEGLGETFRAMETIMESTAMPSUPPORTEDANDROIDPROC(eglGetProcAddress(
"eglGetFrameTimestampSupportedANDROID"));
eglGetFrameTimestampsANDROID =
PFNEGLGETFRAMETIMESTAMPSANDROIDPROC(eglGetProcAddress(
"eglGetFrameTimestampsANDROID"));
}
if (ext.egl.ANDROID_native_fence_sync) {
if (extensions.has("EGL_ANDROID_get_frame_timestamps")) {
eglGetCompositorTimingSupportedANDROID = (PFNEGLGETCOMPOSITORTIMINGSUPPORTEDANDROIDPROC)eglGetProcAddress(
"eglGetCompositorTimingSupportedANDROID");
eglGetCompositorTimingANDROID = (PFNEGLGETCOMPOSITORTIMINGANDROIDPROC)eglGetProcAddress(
"eglGetCompositorTimingANDROID");
eglGetNextFrameIdANDROID = (PFNEGLGETNEXTFRAMEIDANDROIDPROC)eglGetProcAddress(
"eglGetNextFrameIdANDROID");
eglGetFrameTimestampSupportedANDROID = (PFNEGLGETFRAMETIMESTAMPSUPPORTEDANDROIDPROC)eglGetProcAddress(
"eglGetFrameTimestampSupportedANDROID");
eglGetFrameTimestampsANDROID = (PFNEGLGETFRAMETIMESTAMPSANDROIDPROC)eglGetProcAddress(
"eglGetFrameTimestampsANDROID");
eglDupNativeFenceFDANDROID =
PFNEGLDUPNATIVEFENCEFDANDROIDPROC(eglGetProcAddress(
"eglDupNativeFenceFDANDROID"));
} else {
LOG(ERROR) << "EGL_ANDROID_native_fence_sync not supported!";
(PFNEGLDUPNATIVEFENCEFDANDROIDPROC) eglGetProcAddress("eglDupNativeFenceFDANDROID");
}
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
@@ -271,152 +244,11 @@ PlatformEGLAndroid::ExternalImageEGLAndroid::~ExternalImageEGLAndroid() {
}
}
bool PlatformEGLAndroid::isCompositorTimingSupported() const noexcept {
return true;
}
bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
CompositorTiming* outCompositorTiming) const noexcept {
if (!swapchain) {
return false;
}
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
EGLSurface sur = static_cast<SwapChainEGL const *>(swapchain)->sur;
if (sur == EGL_NO_SURFACE) {
return false;
}
std::array<EGLnsecsANDROID, 3> values;
constexpr std::array<EGLint, 3> names{
EGL_COMPOSITE_DEADLINE_ANDROID,
EGL_COMPOSITE_INTERVAL_ANDROID,
EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID
};
EGLBoolean const success = eglGetCompositorTimingANDROID(getEglDisplay(), sur,
names.size(), names.data(), values.data());
if (!success) {
return false;
}
outCompositorTiming->compositeDeadline = values[0];
outCompositorTiming->compositeInterval = values[1];
outCompositorTiming->compositeToPresentLatency = values[2];
return true;
}
// fallback to private APIs
auto const anw = static_cast<SwapChainEGL const *>(swapchain)->nativeWindow;
int const status = NativeWindow::getCompositorTiming(anw,
&outCompositorTiming->compositeDeadline,
&outCompositorTiming->compositeInterval,
&outCompositorTiming->compositeToPresentLatency);
if (status == 0) {
return true;
}
return PlatformEGL::queryCompositorTiming(swapchain, outCompositorTiming);
}
bool PlatformEGLAndroid::setPresentFrameId(SwapChain const* swapchain,
uint64_t const frameId) noexcept {
if (swapchain) {
SwapChainEGLAndroid const* const sc = static_cast<SwapChainEGLAndroid const*>(swapchain);
return sc->setPresentFrameId(frameId);
}
return PlatformEGL::setPresentFrameId(swapchain, frameId);
}
bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64_t frameId,
FrameTimestamps* outFrameTimestamps) const noexcept {
if (!swapchain) {
return false;
}
SwapChainEGLAndroid const* const sc = static_cast<SwapChainEGLAndroid const*>(swapchain);
uint64_t const hwFrameId = sc->getFrameId(frameId);
if (hwFrameId == std::numeric_limits<uint64_t>::max()) {
return false;
}
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
EGLSurface const sur = sc->sur;
if (sur == EGL_NO_SURFACE) {
return false;
}
std::array<EGLnsecsANDROID, 9> values;
constexpr std::array<EGLint, 9> names{
EGL_REQUESTED_PRESENT_TIME_ANDROID, // requestedPresentTime
EGL_RENDERING_COMPLETE_TIME_ANDROID, // acquireTime
EGL_COMPOSITION_LATCH_TIME_ANDROID, // latchTime
EGL_FIRST_COMPOSITION_START_TIME_ANDROID, // firstRefreshStartTime
EGL_LAST_COMPOSITION_START_TIME_ANDROID, // lastRefreshStartTime
EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID, // gpuCompositionDoneTime
EGL_DISPLAY_PRESENT_TIME_ANDROID, // displayPresentTime
EGL_DEQUEUE_READY_TIME_ANDROID, // dequeueReadyTime
EGL_READS_DONE_TIME_ANDROID, // releaseTime
};
EGLBoolean const success = eglGetFrameTimestampsANDROID(getEglDisplay(), sur, hwFrameId,
names.size(), names.data(), values.data());
if (!success) {
return false;
}
outFrameTimestamps->requestedPresentTime = values[0];
outFrameTimestamps->acquireTime = values[1];
outFrameTimestamps->latchTime = values[2];
outFrameTimestamps->firstRefreshStartTime = values[3];
outFrameTimestamps->lastRefreshStartTime = values[4];
outFrameTimestamps->gpuCompositionDoneTime = values[5];
outFrameTimestamps->displayPresentTime = values[6];
outFrameTimestamps->dequeueReadyTime = values[7];
outFrameTimestamps->releaseTime = values[8];
return true;
}
// fallback to private APIs
auto const anw = sc->nativeWindow;
int const status = NativeWindow::getFrameTimestamps(anw, hwFrameId,
&outFrameTimestamps->requestedPresentTime,
&outFrameTimestamps->acquireTime,
&outFrameTimestamps->latchTime,
&outFrameTimestamps->firstRefreshStartTime,
&outFrameTimestamps->lastRefreshStartTime,
&outFrameTimestamps->gpuCompositionDoneTime,
&outFrameTimestamps->displayPresentTime,
&outFrameTimestamps->dequeueReadyTime,
&outFrameTimestamps->releaseTime);
if (status == 0) {
return true;
}
return PlatformEGL::queryFrameTimestamps(swapchain, frameId, outFrameTimestamps);
}
Platform::SwapChain* PlatformEGLAndroid::createSwapChain(void* nativeWindow, uint64_t const flags) {
auto* const sc = new(std::nothrow) SwapChainEGLAndroid(*this, nativeWindow, flags);
return sc;
}
Platform::SwapChain* PlatformEGLAndroid::createSwapChain(
uint32_t const width, uint32_t const height, uint64_t const flags) {
auto* const sc = new(std::nothrow) SwapChainEGLAndroid(*this, width, height, flags);
return sc;
}
void PlatformEGLAndroid::destroySwapChain(SwapChain* swapChain) noexcept {
if (swapChain) {
SwapChainEGLAndroid* const sc = static_cast<SwapChainEGLAndroid*>(swapChain);
sc->terminate(*this);
delete sc;
}
}
Platform::ExternalImageHandle PlatformEGLAndroid::createExternalImage(
AHardwareBuffer const* buffer, bool const sRGB) noexcept {
Platform::ExternalImageHandle PlatformEGLAndroid::createExternalImage(AHardwareBuffer const* buffer,
bool sRGB) noexcept {
if (__builtin_available(android 26, *)) {
auto* const p = new (std::nothrow) ExternalImageEGLAndroid;
auto const hardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
auto hardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
AHardwareBuffer_acquire(hardwareBuffer);
p->aHardwareBuffer = hardwareBuffer;
p->sRGB = sRGB;
@@ -424,13 +256,13 @@ Platform::ExternalImageHandle PlatformEGLAndroid::createExternalImage(
AHardwareBuffer_describe(hardwareBuffer, &hardwareBufferDescription);
p->height = hardwareBufferDescription.height;
p->width = hardwareBufferDescription.width;
auto const textureFormat = mapToFilamentFormat(hardwareBufferDescription.format, sRGB);
auto textureFormat = mapToFilamentFormat(hardwareBufferDescription.format, sRGB);
p->format = textureFormat;
p->usage = mapToFilamentUsage(hardwareBufferDescription.usage, textureFormat);
return ExternalImageHandle{ p };
}
return ExternalImageHandle{};
return Platform::ExternalImageHandle{};
}
PlatformEGLAndroid::ExternalImageDescAndroid PlatformEGLAndroid::getExternalImageDesc(
@@ -453,7 +285,7 @@ bool PlatformEGLAndroid::setExternalImage(ExternalImageHandleRef externalImage,
auto const* const eglExternalImage =
static_cast<ExternalImageEGLAndroid const*>(externalImage.get());
if (eglExternalImage->aHardwareBuffer) {
return setImage(eglExternalImage, texture);
return PlatformEGLAndroid::setImage(eglExternalImage, texture);
}
// not a AHardwareBuffer, fallback to the inherited version
return PlatformEGL::setExternalImage(externalImage, texture);
@@ -466,7 +298,7 @@ OpenGLPlatform::ExternalTexture* PlatformEGLAndroid::createExternalImageTexture(
}
void PlatformEGLAndroid::destroyExternalImageTexture(ExternalTexture* texture) noexcept {
ExternalTextureAndroid const* outTexture = static_cast<ExternalTextureAndroid*>(texture);
ExternalTextureAndroid* outTexture = static_cast<ExternalTextureAndroid*>(texture);
glDeleteTextures(1, &texture->id);
if (outTexture->eglImage != EGL_NO_IMAGE) {
@@ -477,10 +309,10 @@ void PlatformEGLAndroid::destroyExternalImageTexture(ExternalTexture* texture) n
bool PlatformEGLAndroid::setImage(ExternalImageEGLAndroid const* eglExternalImage,
UTILS_UNUSED_IN_RELEASE ExternalTexture* texture) noexcept {
AHardwareBuffer const* hardwareBuffer = eglExternalImage->aHardwareBuffer;
AHardwareBuffer* hardwareBuffer = eglExternalImage->aHardwareBuffer;
// Get the EGL client buffer from AHardwareBuffer
EGLClientBuffer const clientBuffer = eglGetNativeClientBufferANDROID(hardwareBuffer);
EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(hardwareBuffer);
EGLint imageAttrs[] = {
EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
EGL_NONE, EGL_NONE, // Reserve space
@@ -498,7 +330,7 @@ bool PlatformEGLAndroid::setImage(ExternalImageEGLAndroid const* eglExternalImag
imageAttrs[attrIndex++] = EGL_TRUE;
}
// Create an EGLImage from the client buffer
EGLImageKHR const eglImage = eglCreateImageKHR(eglGetCurrentDisplay(), EGL_NO_CONTEXT,
EGLImageKHR eglImage = eglCreateImageKHR(eglGetCurrentDisplay(), EGL_NO_CONTEXT,
EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs);
if (eglImage == EGL_NO_IMAGE_KHR) {
// Handle error
@@ -546,12 +378,12 @@ bool PlatformEGLAndroid::setImage(ExternalImageEGLAndroid const* eglExternalImag
return true;
}
void PlatformEGLAndroid::setPresentationTime(int64_t const presentationTimeInNanosecond) noexcept {
EGLSurface const currentDrawSurface = eglGetCurrentSurface(EGL_DRAW);
void PlatformEGLAndroid::setPresentationTime(int64_t presentationTimeInNanosecond) noexcept {
EGLSurface currentDrawSurface = eglGetCurrentSurface(EGL_DRAW);
if (currentDrawSurface != EGL_NO_SURFACE) {
if (UTILS_UNLIKELY(ext.egl.ANDROID_presentation_time)) {
if (eglPresentationTimeANDROID) {
eglPresentationTimeANDROID(
getEglDisplay(),
mEGLDisplay,
currentDrawSurface,
presentationTimeInNanosecond);
}
@@ -562,38 +394,19 @@ Platform::Stream* PlatformEGLAndroid::createStream(void* nativeStream) noexcept
return mExternalStreamManager.acquire(static_cast<jobject>(nativeStream));
}
void PlatformEGLAndroid::destroyStream(Stream* stream) noexcept {
void PlatformEGLAndroid::destroyStream(Platform::Stream* stream) noexcept {
mExternalStreamManager.release(stream);
}
Platform::Sync* PlatformEGLAndroid::createSync() noexcept {
EGLSyncKHR sync = EGL_NO_SYNC_KHR;
if (UTILS_LIKELY(ext.egl.ANDROID_native_fence_sync)) {
sync = eglCreateSyncKHR(getEglDisplay(), EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
if (sync == EGL_NO_SYNC_KHR) {
LOG(ERROR) << "Failed to create sync: " << eglGetError();
}
} else {
LOG(WARNING) << "Native fences not supported on this device.";
}
return new(std::nothrow) SyncEGLAndroid{ .sync = sync };
auto sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
return new SyncEGLAndroid{.sync = sync};
}
bool PlatformEGLAndroid::convertSyncToFd(Sync* sync, int* fd) noexcept {
bool PlatformEGLAndroid::convertSyncToFd(Platform::Sync* sync, int* fd) noexcept {
assert_invariant(sync && fd);
if (UTILS_UNLIKELY(!ext.egl.ANDROID_native_fence_sync)) {
LOG(WARNING) << "Native fences not supported, cannot convert to fd.";
return false;
}
SyncEGLAndroid const& eglSync = static_cast<SyncEGLAndroid&>(*sync);
if (eglSync.sync == EGL_NO_SYNC_KHR) {
LOG(ERROR) << "Invalid fence, cannot convert to fd.";
return false;
}
*fd = eglDupNativeFenceFDANDROID(getEglDisplay(), eglSync.sync);
SyncEGLAndroid& eglSync = static_cast<SyncEGLAndroid&>(*sync);
*fd = eglDupNativeFenceFDANDROID(mEGLDisplay, eglSync.sync);
// In the case where there was no native FD, -1 is returned. Return false
// to indicate there was an error in this case.
if (*fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {
@@ -603,18 +416,14 @@ bool PlatformEGLAndroid::convertSyncToFd(Sync* sync, int* fd) noexcept {
return true;
}
void PlatformEGLAndroid::destroySync(Sync* sync) noexcept {
void PlatformEGLAndroid::destroySync(Platform::Sync* sync) noexcept {
assert_invariant(sync);
if (UTILS_LIKELY(ext.egl.ANDROID_native_fence_sync)) {
SyncEGLAndroid const& eglSync = static_cast<SyncEGLAndroid&>(*sync);
if (eglSync.sync != EGL_NO_SYNC_KHR) {
eglDestroySyncKHR(getEglDisplay(), eglSync.sync);
}
}
SyncEGLAndroid& eglSync = static_cast<SyncEGLAndroid&>(*sync);
eglDestroySyncKHR(mEGLDisplay, eglSync.sync);
delete sync;
}
void PlatformEGLAndroid::attach(Stream* stream, intptr_t const tname) noexcept {
void PlatformEGLAndroid::attach(Stream* stream, intptr_t tname) noexcept {
mExternalStreamManager.attach(stream, tname);
}
@@ -634,30 +443,29 @@ int PlatformEGLAndroid::getOSVersion() const noexcept {
return mOSVersion;
}
AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage const source) noexcept {
AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage source) noexcept {
// Convert the AHardwareBuffer to EGLImage.
AHardwareBuffer const* const pHardwareBuffer = (const AHardwareBuffer*)source.image;
EGLClientBuffer const clientBuffer = eglGetNativeClientBufferANDROID(pHardwareBuffer);
EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(pHardwareBuffer);
if (!clientBuffer) {
LOG(ERROR) << "Unable to get EGLClientBuffer from AHardwareBuffer.";
return {};
}
Config attributes;
PlatformEGL::Config attributes;
if (__builtin_available(android 26, *)) {
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(pHardwareBuffer, &desc);
bool const isProtectedContent =
desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT;
desc.usage & AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT;
if (isProtectedContent) {
attributes[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
}
}
EGLDisplay const dpy = getEglDisplay();
EGLImageKHR const eglImage = eglCreateImageKHR(dpy,
EGLImageKHR eglImage = eglCreateImageKHR(mEGLDisplay,
EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attributes.data());
if (eglImage == EGL_NO_IMAGE_KHR) {
LOG(ERROR) << "eglCreateImageKHR returned no image.";
@@ -666,15 +474,15 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage const sou
// Destroy the EGLImage before invoking the user's callback.
struct Closure {
Closure(AcquiredImage const& acquiredImage, EGLDisplay const display)
Closure(AcquiredImage const& acquiredImage, EGLDisplay display)
: acquiredImage(acquiredImage), display(display) {}
AcquiredImage acquiredImage;
EGLDisplay display;
};
Closure* const closure = new(std::nothrow) Closure(source, dpy);
Closure* closure = new(std::nothrow) Closure(source, mEGLDisplay);
auto patchedCallback = [](void* image, void* userdata) {
Closure const* const closure = static_cast<Closure*>(userdata);
if (eglDestroyImageKHR(closure->display, EGLImageKHR(image)) == EGL_FALSE) {
Closure* closure = (Closure*)userdata;
if (eglDestroyImageKHR(closure->display, (EGLImageKHR) image) == EGL_FALSE) {
LOG(ERROR) << "eglDestroyImageKHR failed.";
}
closure->acquiredImage.callback(closure->acquiredImage.image, closure->acquiredImage.userData);
@@ -684,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>() };
+5 -20
View File
@@ -1156,7 +1156,6 @@ void VulkanDriver::updateStreams(CommandStream* driver) {
void VulkanDriver::destroyFence(Handle<HwFence> fh) {
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
fence->cancel();
fence.dec();
}
@@ -1165,10 +1164,6 @@ FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> const fh) {
}
FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout) {
if (!fh) {
return FenceStatus::ERROR;
}
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
// we have to take into account that the STL's wait_for() actually works with
@@ -1176,18 +1171,13 @@ FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout
using namespace std::chrono;
auto const now = steady_clock::now();
steady_clock::time_point until = steady_clock::time_point::max();
using TimeoutType = decltype(timeout);
constexpr TimeoutType maxTimeout = std::numeric_limits<TimeoutType>::max();
constexpr nanoseconds maxNano = nanoseconds::max();
if (timeout < maxNano.count() && timeout < maxTimeout && // Need to account for overflow
now <= steady_clock::time_point::max() - nanoseconds(timeout)) {
if (now <= steady_clock::time_point::max() - nanoseconds(timeout)) {
until = now + nanoseconds(timeout);
}
auto const [cmdfence, canceled] = fence->wait(until);
if (!cmdfence || canceled) {
return canceled ? FenceStatus::ERROR : FenceStatus::TIMEOUT_EXPIRED;
std::shared_ptr const cmdfence = fence->wait(until);
if (!cmdfence) {
return FenceStatus::TIMEOUT_EXPIRED;
}
// now we are holding a reference to our VulkanCmdFence, so we know it can't
@@ -1395,8 +1385,7 @@ uint8_t VulkanDriver::getMaxDrawBuffers() {
}
size_t VulkanDriver::getMaxUniformBufferSize() {
return std::max(mContext.getPhysicalDeviceLimits().maxUniformBufferRange,
static_cast<uint32_t>(mContext.getPhysicalDeviceLimits().nonCoherentAtomSize));
return mContext.getPhysicalDeviceLimits().maxUniformBufferRange;
}
size_t VulkanDriver::getMaxTextureSize(SamplerType type) {
@@ -1416,10 +1405,6 @@ size_t VulkanDriver::getMaxArrayTextureLayers() {
return mContext.getPhysicalDeviceLimits().maxImageArrayLayers;
}
size_t VulkanDriver::getUniformBufferOffsetAlignment() {
return mContext.getPhysicalDeviceLimits().minUniformBufferOffsetAlignment;
}
void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
Handle<HwBufferObject> boh) {
auto vb = resource_ptr<VulkanVertexBuffer>::cast(&mResourceManager, vbh);
@@ -267,8 +267,8 @@ void VulkanPipelineCache::bindVertexArray(VkVertexInputAttributeDescription cons
mPipelineRequirements.vertexAttributes[i] = attribDesc[i];
mPipelineRequirements.vertexBuffers[i] = bufferDesc[i];
} else {
mPipelineRequirements.vertexAttributes[i] = VertexInputAttributeDescription{};
mPipelineRequirements.vertexBuffers[i] = VertexInputBindingDescription{};
mPipelineRequirements.vertexAttributes[i] = {};
mPipelineRequirements.vertexBuffers[i] = {};
}
}
}
@@ -149,7 +149,7 @@ VulkanStage* VulkanStagePool::allocateNewStage(uint32_t capacity) {
return new VulkanStage(memory, buffer, capacity, pMapping);
}
void VulkanStagePool::destroyStage(VulkanStage const* stage) {
void VulkanStagePool::destroyStage(VulkanStage const*&& stage) {
assert(stage->isSafeToReset()); // Ensure all segments have been reset already.
vmaUnmapMemory(mAllocator, stage->memory());
vmaDestroyBuffer(mAllocator, stage->buffer(), stage->memory());
@@ -196,7 +196,7 @@ fvkmemory::resource_ptr<VulkanStageImage::Resource> VulkanStagePool::acquireImag
VkImage image;
VmaAllocation memory;
UTILS_UNUSED const VkResult result = vmaCreateImage(mAllocator, &imageInfo, &allocInfo,
const UTILS_UNUSED VkResult result = vmaCreateImage(mAllocator, &imageInfo, &allocInfo,
&image, &memory, nullptr);
assert_invariant(result == VK_SUCCESS);
@@ -239,7 +239,7 @@ void VulkanStagePool::gc() noexcept {
FVK_LOGD << "Destroying a staging buffer with hndl " << pair.second->buffer()
<< utils::io::endl;
#endif
destroyStage(pair.second);
destroyStage(std::move(pair.second));
continue;
}
@@ -276,7 +276,7 @@ void VulkanStagePool::gc() noexcept {
void VulkanStagePool::terminate() noexcept {
for (auto& pair : mStages) {
destroyStage(pair.second);
destroyStage(std::move(pair.second));
}
mStages.clear();
@@ -245,7 +245,7 @@ private:
// unmapping memory, freeing the allocation, and deleting the VulkanStage
// object. Note: takes an r-value because after this call, `stage` won't
// exist.
void destroyStage(VulkanStage const* stage);
void destroyStage(VulkanStage const*&& stage);
// Use an ordered multimap for quick (capacity => stage) lookups using lower_bound().
std::multimap<uint32_t, VulkanStage*> mStages;
@@ -134,9 +134,9 @@ void VulkanSwapChain::present(DriverBase& driver) {
mAcquired = false;
mIsFirstRenderPass = true;
if (mFrameScheduled.callback) {
driver.scheduleCallback(mFrameScheduled.handler,
[callback = mFrameScheduled.callback]() {
if (frameScheduled.callback) {
driver.scheduleCallback(frameScheduled.handler,
[callback = frameScheduled.callback]() {
PresentCallable noop = PresentCallable(PresentCallable::noopPresent, nullptr);
callback->operator()(noop);
});
@@ -84,12 +84,12 @@ struct VulkanSwapChain : public HwSwapChain, fvkmemory::Resource {
inline void setFrameScheduledCallback(CallbackHandler* handler,
FrameScheduledCallback&& callback) noexcept {
if (!callback) {
mFrameScheduled.handler = nullptr;
mFrameScheduled.callback.reset();
frameScheduled.handler = nullptr;
frameScheduled.callback.reset();
return;
}
mFrameScheduled.handler = handler;
mFrameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
frameScheduled.handler = handler;
frameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
}
private:
@@ -110,8 +110,8 @@ private:
// These fields store a callback to notify the client that Filament is commiting a frame.
struct {
CallbackHandler* handler = nullptr;
std::shared_ptr<FrameScheduledCallback> callback;
} mFrameScheduled;
std::shared_ptr<FrameScheduledCallback> callback = nullptr;
} frameScheduled;
// We create VulkanTextures based on VkImages. VulkanTexture has facilities for doing layout
// transitions, which are useful here.
@@ -29,6 +29,16 @@
#include <utils/Panic.h>
#include <utils/PrivateImplementation-impl.h>
#define SWAPCHAIN_RET_FUNC(func, handle, ...) \
if (mImpl->mSurfaceSwapChains.find(handle) != mImpl->mSurfaceSwapChains.end()) { \
return static_cast<VulkanPlatformSurfaceSwapChain*>(handle)->func(__VA_ARGS__); \
} else if (mImpl->mHeadlessSwapChains.find(handle) != mImpl->mHeadlessSwapChains.end()) { \
return static_cast<VulkanPlatformHeadlessSwapChain*>(handle)->func(__VA_ARGS__); \
} else { \
PANIC_PRECONDITION("Bad handle for swapchain"); \
return {}; \
}
using namespace utils;
using namespace bluevk;
@@ -675,11 +685,26 @@ struct VulkanPlatformPrivate {
VkQueue mProtectedGraphicsQueue = VK_NULL_HANDLE;
VulkanContext mContext = {};
// We use a map to both map a handle (i.e. SwapChainPtr) to the concrete type and also to
// store the actual swapchain struct, which is either backed-by-surface or headless.
std::unordered_set<SwapChainPtr> mSurfaceSwapChains;
std::unordered_set<SwapChainPtr> mHeadlessSwapChains;
bool mSharedContext = false;
bool mForceXCBSwapchain = false;
};
void VulkanPlatform::terminate() {
for (auto swapchain: mImpl->mHeadlessSwapChains) {
delete static_cast<VulkanPlatformHeadlessSwapChain*>(swapchain);
}
mImpl->mHeadlessSwapChains.clear();
for (auto swapchain: mImpl->mSurfaceSwapChains) {
delete static_cast<VulkanPlatformSurfaceSwapChain*>(swapchain);
}
mImpl->mSurfaceSwapChains.clear();
if (!mImpl->mSharedContext) {
vkDestroyDevice(mImpl->mDevice, VKALLOC);
vkDestroyInstance(mImpl->mInstance, VKALLOC);
@@ -915,31 +940,38 @@ VulkanPlatform::VulkanPlatform() = default;
VulkanPlatform::~VulkanPlatform() = default;
VulkanPlatform::SwapChainBundle VulkanPlatform::getSwapChainBundle(SwapChainPtr handle) {
return static_cast<VulkanPlatformSwapChainBase*>(handle)->getSwapChainBundle();
SWAPCHAIN_RET_FUNC(getSwapChainBundle, handle, )
}
VkResult VulkanPlatform::acquire(SwapChainPtr handle, ImageSyncData* outImageSyncData) {
return static_cast<VulkanPlatformSwapChainBase*>(handle)->acquire(outImageSyncData);
SWAPCHAIN_RET_FUNC(acquire, handle, outImageSyncData)
}
VkResult VulkanPlatform::present(SwapChainPtr handle, uint32_t index, VkSemaphore finishedDrawing) {
return static_cast<VulkanPlatformSwapChainBase*>(handle)->present(index, finishedDrawing);
VkResult VulkanPlatform::present(SwapChainPtr handle, uint32_t index,
VkSemaphore finishedDrawing) {
SWAPCHAIN_RET_FUNC(present, handle, index, finishedDrawing)
}
bool VulkanPlatform::hasResized(SwapChainPtr handle) {
return static_cast<VulkanPlatformSwapChainBase*>(handle)->hasResized();
SWAPCHAIN_RET_FUNC(hasResized, handle, )
}
bool VulkanPlatform::isProtected(SwapChainPtr handle) {
return static_cast<VulkanPlatformSwapChainBase*>(handle)->isProtected();
SWAPCHAIN_RET_FUNC(isProtected, handle, )
}
VkResult VulkanPlatform::recreate(SwapChainPtr handle) {
return static_cast<VulkanPlatformSwapChainBase*>(handle)->recreate();
SWAPCHAIN_RET_FUNC(recreate, handle, )
}
void VulkanPlatform::destroy(SwapChainPtr handle) {
delete static_cast<VulkanPlatformSwapChainBase*>(handle);
if (mImpl->mSurfaceSwapChains.erase(handle)) {
delete static_cast<VulkanPlatformSurfaceSwapChain*>(handle);
} else if (mImpl->mHeadlessSwapChains.erase(handle)) {
delete static_cast<VulkanPlatformHeadlessSwapChain*>(handle);
} else {
PANIC_PRECONDITION("Bad handle for swapchain");
}
}
SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
@@ -949,6 +981,7 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
if (headless) {
VulkanPlatformHeadlessSwapChain* swapchain = new VulkanPlatformHeadlessSwapChain(
mImpl->mContext, mImpl->mDevice, mImpl->mGraphicsQueue, extent, flags);
mImpl->mHeadlessSwapChains.insert(swapchain);
return swapchain;
}
@@ -967,6 +1000,7 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
VulkanPlatformSurfaceSwapChain* swapchain = new VulkanPlatformSurfaceSwapChain(mImpl->mContext,
mImpl->mPhysicalDevice, mImpl->mDevice, mImpl->mGraphicsQueue, mImpl->mInstance,
surface, fallbackExtent, flags);
mImpl->mSurfaceSwapChains.insert(swapchain);
return swapchain;
}
@@ -1030,5 +1064,6 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
VkInstance instance, uint64_t flags) const noexcept {
return createVkSurfaceKHRImpl(nativeWindow, instance, flags);
}
#undef SWAPCHAIN_RET_FUNC
}// namespace filament::backend
@@ -68,12 +68,12 @@ std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& co
= context.selectMemoryType(memReqs.memoryTypeBits, requiredMemoryFlags);
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES)
<< "VulkanPlatformSwapChainBase: unable to find a memory type that meets requirements.";
<< "VulkanPlatformSwapChainImpl: unable to find a memory type that meets requirements.";
VkMemoryAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
.memoryTypeIndex = memoryTypeIndex,
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
.memoryTypeIndex = memoryTypeIndex,
};
result = vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to allocate image memory."
@@ -94,15 +94,17 @@ VkFormat selectDepthFormat(fvkutils::VkFormatList const& depthFormats, bool hasS
}// anonymous namespace
VulkanPlatformSwapChainBase::VulkanPlatformSwapChainBase(VulkanContext const& context,
VulkanPlatformSwapChainImpl::VulkanPlatformSwapChainImpl(VulkanContext const& context,
VkDevice device, VkQueue queue)
: mContext(context),
mDevice(device),
mQueue(queue) {}
VulkanPlatformSwapChainBase::~VulkanPlatformSwapChainBase() = default;
VulkanPlatformSwapChainImpl::~VulkanPlatformSwapChainImpl() {
destroy();
}
void VulkanPlatformSwapChainBase::destroy() {
void VulkanPlatformSwapChainImpl::destroy() {
if (mSwapChainBundle.depth) {
vkDestroyImage(mDevice, mSwapChainBundle.depth, VKALLOC);
if (mMemory.find(mSwapChainBundle.depth) != mMemory.end()) {
@@ -116,7 +118,7 @@ void VulkanPlatformSwapChainBase::destroy() {
mSwapChainBundle.colors.clear();
}
VkImage VulkanPlatformSwapChainBase::createImage(VkExtent2D extent, VkFormat format,
VkImage VulkanPlatformSwapChainImpl::createImage(VkExtent2D extent, VkFormat format,
bool isProtected) {
auto [image, memory] = createImageAndMemory(mContext, mDevice, extent, format, isProtected);
mMemory.insert({image, memory});
@@ -126,7 +128,7 @@ VkImage VulkanPlatformSwapChainBase::createImage(VkExtent2D extent, VkFormat for
VulkanPlatformSurfaceSwapChain::VulkanPlatformSurfaceSwapChain(VulkanContext const& context,
VkPhysicalDevice physicalDevice, VkDevice device, VkQueue queue, VkInstance instance,
VkSurfaceKHR surface, VkExtent2D fallbackExtent, uint64_t flags)
: VulkanPlatformSwapChainBase(context, device, queue),
: VulkanPlatformSwapChainImpl(context, device, queue),
mInstance(instance),
mPhysicalDevice(physicalDevice),
mSurface(surface),
@@ -169,13 +171,13 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
FixedCapacityVector<VkSurfaceFormatKHR> const surfaceFormats
= fvkutils::enumerate(vkGetPhysicalDeviceSurfaceFormatsKHR, mPhysicalDevice, mSurface);
std::array<VkFormat, 2> expectedFormats = {
VK_FORMAT_R8G8B8A8_UNORM,
VK_FORMAT_B8G8R8A8_UNORM,
VK_FORMAT_R8G8B8A8_UNORM,
VK_FORMAT_B8G8R8A8_UNORM,
};
if (mUsesRGB) {
expectedFormats = {
VK_FORMAT_R8G8B8A8_SRGB,
VK_FORMAT_B8G8R8A8_SRGB,
VK_FORMAT_R8G8B8A8_SRGB,
VK_FORMAT_B8G8R8A8_SRGB,
};
}
for (VkSurfaceFormatKHR const& format: surfaceFormats) {
@@ -193,9 +195,13 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
VkPresentModeKHR const desiredPresentMode = VK_PRESENT_MODE_FIFO_KHR;
FixedCapacityVector<VkPresentModeKHR> presentModes = fvkutils::enumerate(
vkGetPhysicalDeviceSurfacePresentModesKHR, mPhysicalDevice, mSurface);
bool const foundSuitablePresentMode = std::find(presentModes.begin(), presentModes.end(),
desiredPresentMode) != presentModes.end();
bool foundSuitablePresentMode = false;
for (VkPresentModeKHR mode: presentModes) {
if (mode == desiredPresentMode) {
foundSuitablePresentMode = true;
break;
}
}
FILAMENT_CHECK_POSTCONDITION(foundSuitablePresentMode)
<< "Desired present mode is not supported by this device.";
@@ -313,7 +319,7 @@ VkResult VulkanPlatformSurfaceSwapChain::present(uint32_t index, VkSemaphore fin
return result;
}
bool VulkanPlatformSurfaceSwapChain::hasResized() const {
bool VulkanPlatformSurfaceSwapChain::hasResized() {
VkSurfaceCapabilitiesKHR caps;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDevice, mSurface, &caps);
VkExtent2D perceivedExtent = caps.currentExtent;
@@ -325,7 +331,7 @@ bool VulkanPlatformSurfaceSwapChain::hasResized() const {
return !fvkutils::equivalent(mSwapChainBundle.extent, perceivedExtent);
}
bool VulkanPlatformSurfaceSwapChain::isProtected() const {
bool VulkanPlatformSurfaceSwapChain::isProtected() {
return mIsProtected;
}
@@ -352,6 +358,8 @@ void VulkanPlatformSurfaceSwapChain::destroy() {
// phone). If necessary, we can revisit and implement the workaround [1].
vkQueueWaitIdle(mQueue);
VulkanPlatformSwapChainImpl::destroy();
for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) {
if (mImageReady[i] != VK_NULL_HANDLE) {
vkDestroySemaphore(mDevice, mImageReady[i], VKALLOC);
@@ -366,7 +374,7 @@ void VulkanPlatformSurfaceSwapChain::destroy() {
VulkanPlatformHeadlessSwapChain::VulkanPlatformHeadlessSwapChain(VulkanContext const& context,
VkDevice device, VkQueue queue, VkExtent2D extent, uint64_t flags)
: VulkanPlatformSwapChainBase(context, device, queue),
: VulkanPlatformSwapChainImpl(context, device, queue),
mCurrentIndex(0) {
mSwapChainBundle.extent = extent;
mSwapChainBundle.colorFormat = (flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0
@@ -32,27 +32,46 @@ namespace filament::backend {
static constexpr uint32_t const VULKAN_UNDEFINED_EXTENT = 0xFFFFFFFF;
struct VulkanPlatformSwapChainBase : public Platform::SwapChain {
VulkanPlatformSwapChainBase(VulkanContext const& context, VkDevice device, VkQueue queue);
struct VulkanPlatformSwapChainImpl : public Platform::SwapChain {
VulkanPlatformSwapChainImpl(VulkanContext const& context, VkDevice device, VkQueue queue);
inline VulkanPlatform::SwapChainBundle getSwapChainBundle() const {
return mSwapChainBundle;
}
virtual ~VulkanPlatformSwapChainBase();
~VulkanPlatformSwapChainImpl();
virtual VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData) = 0;
// Non-virtual override-able method
VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData) {
PANIC_PRECONDITION("Should not be called");
return VK_ERROR_UNKNOWN;
}
virtual VkResult present(uint32_t index, VkSemaphore finished) = 0;
// Non-virtual override-able method
VkResult present(uint32_t index, VkSemaphore finished) {
PANIC_PRECONDITION("Should not be called");
return VK_ERROR_UNKNOWN;
}
virtual VkResult recreate() = 0;
// Non-virtual override-able method
VkResult recreate() {
PANIC_PRECONDITION("Should not be called");
return VK_ERROR_UNKNOWN;
}
virtual bool hasResized() const = 0;
// Non-virtual override-able method
bool hasResized() {
return false;
}
virtual bool isProtected() const = 0;
// Non-virtual override-able method
bool isProtected() {
return false;
}
protected:
virtual void destroy() = 0;
// Non-virtual override-able method
void destroy();
VkImage createImage(VkExtent2D extent, VkFormat format, bool isProtected);
@@ -64,29 +83,35 @@ protected:
std::unordered_map<VkImage, VkDeviceMemory> mMemory;
};
struct VulkanPlatformSurfaceSwapChain : public VulkanPlatformSwapChainBase {
struct VulkanPlatformSurfaceSwapChain : public VulkanPlatformSwapChainImpl {
VulkanPlatformSurfaceSwapChain(VulkanContext const& context, VkPhysicalDevice physicalDevice,
VkDevice device, VkQueue queue, VkInstance instance, VkSurfaceKHR surface,
VkExtent2D fallbackExtent, uint64_t flags);
~VulkanPlatformSurfaceSwapChain() override;
~VulkanPlatformSurfaceSwapChain();
virtual VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData) override;
// Non-virtual override
VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData);
virtual VkResult present(uint32_t index, VkSemaphore finished) override;
// Non-virtual override
VkResult present(uint32_t index, VkSemaphore finished);
virtual VkResult recreate() override;
// Non-virtual override-able method
VkResult recreate();
virtual bool hasResized() const override;
// Non-virtual override-able method
bool hasResized();
virtual bool isProtected() const override;
// Non-virtual override-able method
bool isProtected();
protected:
virtual void destroy() override;
// Non-virtual override-able method
void destroy();
private:
static constexpr int IMAGE_READY_SEMAPHORE_COUNT = FVK_MAX_COMMAND_BUFFERS;
VkResult create();
VkInstance mInstance;
@@ -98,35 +123,29 @@ private:
VkSemaphore mImageReady[IMAGE_READY_SEMAPHORE_COUNT];
uint32_t mCurrentImageReadyIndex;
bool const mUsesRGB = false;
bool const mHasStencil = false;
bool const mIsProtected = false;
bool mUsesRGB = false;
bool mHasStencil = false;
bool mIsProtected = false;
bool mSuboptimal;
};
struct VulkanPlatformHeadlessSwapChain : public VulkanPlatformSwapChainBase {
struct VulkanPlatformHeadlessSwapChain : public VulkanPlatformSwapChainImpl {
static constexpr size_t const HEADLESS_SWAPCHAIN_SIZE = 2;
VulkanPlatformHeadlessSwapChain(VulkanContext const& context, VkDevice device, VkQueue queue,
VkExtent2D extent, uint64_t flags);
~VulkanPlatformHeadlessSwapChain() override;
~VulkanPlatformHeadlessSwapChain();
virtual VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData) override;
// Non-virtual override
VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData);
virtual VkResult present(uint32_t index, VkSemaphore finished) override;
virtual VkResult recreate() override {
PANIC_PRECONDITION("Should not be called");
return VK_ERROR_UNKNOWN;
}
virtual bool hasResized() const override { return false; }
virtual bool isProtected() const override { return false; }
// Non-virtual override
VkResult present(uint32_t index, VkSemaphore finished);
protected:
virtual void destroy() override;
// Non-virtual override-able method
void destroy();
private:
uint32_t mCurrentIndex;
+23 -26
View File
@@ -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;
}
+7 -5
View File
@@ -339,7 +339,7 @@ Handle<HwTimerQuery> WebGPUDriver::createTimerQueryS() noexcept {
}
Handle<HwIndexBuffer> WebGPUDriver::createIndexBufferS() noexcept {
return allocHandle<WebGPUIndexBuffer>();
return allocHandle<HwIndexBuffer>();
}
Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept {
@@ -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),
+1 -7
View File
@@ -39,15 +39,9 @@ FenceStatus WebGPUFence::wait(uint64_t timeout) {
using namespace std::chrono;
auto now = steady_clock::now();
steady_clock::time_point until = steady_clock::time_point::max();
using TimeoutType = decltype(timeout);
constexpr TimeoutType maxTimeout = std::numeric_limits<TimeoutType>::max();
constexpr nanoseconds maxNano = nanoseconds::max();
if (timeout < maxNano.count() && timeout < maxTimeout && // Need to account for overflow
now <= steady_clock::time_point::max() - nanoseconds(timeout)) {
if (now <= steady_clock::time_point::max() - nanoseconds(timeout)) {
until = now + nanoseconds(timeout);
}
{
std::unique_lock<std::mutex> lock(mLock);
bool const success = mCond.wait_until(lock, until, [this] {
@@ -338,10 +338,6 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
// To handle this, we check if any color targets are configured for this pipeline. If not, we
// create a pipeline *without* a fragment stage. This makes the pipeline valid for a
// depth-only pass, allowing depth writes to proceed correctly.
// Note that this needs to be defined in the same scope as the CreateRenderpipeline call
std::array<wgpu::ColorTargetState, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> colorTargets {};
if (request.fragmentShaderModule != nullptr && request.colorFormatCount > 0) {
fragmentState.module = request.fragmentShaderModule;
fragmentState.entryPoint = "main";
@@ -350,6 +346,7 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
fragmentState.constantCount = 0;
fragmentState.constants = nullptr;
fragmentState.targetCount = request.colorFormatCount;
std::array<wgpu::ColorTargetState, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> colorTargets {};
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
wgpu::ColorTargetState& colorTarget = colorTargets[targetIndex];
@@ -390,11 +390,11 @@ void WebGPUSwapChain::present(DriverBase& driver) {
if (!isHeadless()) {
mSurface.Present();
}
if (mFrameScheduled.callback) {
driver.scheduleCallback(mFrameScheduled.handler,
[callback = mFrameScheduled.callback]() {
if (frameScheduled.callback) {
driver.scheduleCallback(frameScheduled.handler,
[callback = std::move(frameScheduled.callback)]() {
PresentCallable noop = PresentCallable(PresentCallable::noopPresent, nullptr);
callback->operator()(noop);
callback(noop);
});
}
}
+4 -10
View File
@@ -23,7 +23,6 @@
#include <backend/Platform.h>
#include <cstdint>
#include <memory>
namespace filament::backend {
@@ -53,13 +52,8 @@ public:
void present(DriverBase& driver);
void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback) {
if (!callback) {
mFrameScheduled.handler = nullptr;
mFrameScheduled.callback.reset();
return;
}
mFrameScheduled.handler = handler;
mFrameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
frameScheduled.handler = handler;
frameScheduled.callback = std::move(callback);
}
private:
@@ -89,8 +83,8 @@ private:
struct {
CallbackHandler* handler = nullptr;
std::shared_ptr<FrameScheduledCallback> callback;
} mFrameScheduled;
FrameScheduledCallback callback;
} frameScheduled;
};
} // namespace filament::backend
@@ -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
+8 -12
View File
@@ -64,7 +64,6 @@ BackendTest::BackendTest() : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE,
CONFIG_COMMAND_BUFFERS_SIZE, /*mPaused=*/false) {
initializeDriver();
mImageExpectations.emplace(getDriverApi());
mCleanup = std::make_unique<Cleanup>(getDriverApi());
NativeView nativeView = getNativeView();
mScreenSize = {nativeView.width, nativeView.height};
}
@@ -73,15 +72,12 @@ BackendTest::~BackendTest() {
// Ensure all graphics commands and callbacks are finished.
flushAndWait();
mImageExpectations->evaluate();
// Note: Don't terminate the driver for OpenGL, as it wipes away the context and removes the buffer from the screen.
if (sBackend != Backend::OPENGL) {
driver->terminate();
delete driver;
}
// We need to clean up all the handles before the driver terminates. Note that this should
// happen before the above flushAndWait, which will complete readPixels.
mCleanup.reset();
// This flush and wait will execute all the destroy commands.
flushAndWait();
driver->terminate();
delete driver;
recordFailedImages();
}
@@ -111,12 +107,12 @@ void BackendTest::flushAndWait() {
getDriver().purge();
}
Handle<HwSwapChain> BackendTest::createSwapChain(uint64_t flags) {
Handle<HwSwapChain> BackendTest::createSwapChain() {
const NativeView& view = getNativeView();
if (!view.ptr) {
return getDriverApi().createSwapChainHeadless(view.width, view.height, flags);
return getDriverApi().createSwapChainHeadless(view.width, view.height, 0);
}
return getDriverApi().createSwapChain(view.ptr, flags);
return getDriverApi().createSwapChain(view.ptr, 0);
}
PipelineState BackendTest::getColorWritePipelineState() {
+2 -9
View File
@@ -28,7 +28,6 @@
#include "PlatformRunner.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
namespace test {
@@ -49,18 +48,15 @@ public:
static std::filesystem::path binaryDirectory();
protected:
BackendTest();
~BackendTest() override;
template<typename HandleType>
filament::backend::Handle<HandleType> addCleanup(filament::backend::Handle<HandleType> handle) {
return mCleanup->add(handle);
}
void initializeDriver();
void executeCommands();
void flushAndWait();
filament::backend::Handle<filament::backend::HwSwapChain> createSwapChain(uint64_t flags = 0);
filament::backend::Handle<filament::backend::HwSwapChain> createSwapChain();
static filament::backend::PipelineState getColorWritePipelineState();
@@ -81,9 +77,6 @@ protected:
static bool matchesEnvironment(Backend backend);
static bool matchesEnvironment(OperatingSystem operatingSystem);
std::unique_ptr<Cleanup> mCleanup;
private:
// Adds all the images that failed an ImageExpectation to the XML metadata for the current tests
// case. Add --gtest_output=xml as a command line argument to generate a test_detail.xml file in
+2
View File
@@ -19,12 +19,14 @@
#include <vector>
#include <functional>
#include <optional>
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "backend/Handle.h"
#include "backend/DriverApiForward.h"
#include "BackendTest.h"
class RenderFrame {
public:
+1 -3
View File
@@ -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.
};
-2
View File
@@ -19,8 +19,6 @@
#include "Lifetimes.h"
#include "private/filament/SamplerInterfaceBlock.h"
#include "private/backend/DriverApi.h"
#include "backend/DriverEnums.h"
namespace test {
+2 -2
View File
@@ -16,9 +16,9 @@
#include "SharedShaders.h"
#include "BackendTest.h"
#include "Shader.h"
#include "absl/strings/str_format.h"
#include "gtest/gtest.h"
namespace test {
@@ -252,4 +252,4 @@ std::string SharedShaders::getFragmentShaderText(FragmentShaderType fragment,
return fragmentText->withUniform(*uniformText);
}
} // namespace test
} // namespace test
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

-7
View File
@@ -145,10 +145,7 @@ static void createFaces(DriverApi& dapi, Handle<HwTexture> texture, int baseWidt
}
TEST_F(BlitTest, ColorMagnify) {
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "b/453757697");
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::VULKAN), "b/453777397");
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
auto& api = getDriverApi();
mCleanup.addPostCall([&]() { executeCommands(); });
@@ -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) {
+13 -9
View File
@@ -38,16 +38,18 @@ const UniformBindingConfig kBindingConfig = {
class BufferUpdatesTest : public BackendTest {
public:
BufferUpdatesTest() = default;
BufferUpdatesTest() : mCleanup(getDriverApi()) {}
protected:
Shader createShader() {
return SharedShaders::makeShader(getDriverApi(), *mCleanup, ShaderRequest{
return SharedShaders::makeShader(getDriverApi(), mCleanup, ShaderRequest{
.mVertexType = VertexShaderType::Simple,
.mFragmentType = FragmentShaderType::SolidColored,
.mUniformType = ShaderUniformType::SimpleWithPadding
});
}
Cleanup mCleanup;
};
TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
@@ -61,14 +63,15 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
// executeCommands().
{
auto& api = getDriverApi();
Cleanup cleanup(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
Shader shader = createShader();
auto defaultRenderTarget = addCleanup(api.createDefaultRenderTarget());
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget());
// To test large buffers (which exercise a different code path) create an extra large
// buffer. Only the first 3 vertices will be used.
@@ -83,7 +86,7 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
// Create a uniform buffer.
// We use STATIC here, even though the buffer is updated, to force the Metal backend to use
// a GPU buffer, which is more interesting to test.
auto ubuffer = addCleanup(api.createBufferObject(sizeof(SimpleMaterialParams) + 64,
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(SimpleMaterialParams) + 64,
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
shader.bindUniform<SimpleMaterialParams>(api, ubuffer, kBindingConfig);
@@ -153,11 +156,12 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
auto& api = getDriverApi();
Cleanup cleanup(api);
const TrianglePrimitive triangle(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a program.
@@ -166,16 +170,16 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
// Create a uniform buffer.
// We use STATIC here, even though the buffer is updated, to force the Metal backend to use a
// GPU buffer, which is more interesting to test.
auto ubuffer = addCleanup(api.createBufferObject(sizeof(SimpleMaterialParams) + 64,
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(SimpleMaterialParams) + 64,
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
shader.bindUniform<SimpleMaterialParams>(api, ubuffer, kBindingConfig);
// Create a render target.
auto colorTexture = addCleanup(
auto colorTexture = cleanup.add(
api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, screenWidth(),
screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
auto renderTarget = addCleanup(api.createRenderTarget(TargetBufferFlags::COLOR0, screenWidth(),
auto renderTarget = cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR0, screenWidth(),
screenHeight(), 1, 1, { { colorTexture } }, {}, {}));
// Upload uniforms for the first triangle.
+11 -10
View File
@@ -25,23 +25,23 @@ using namespace filament::backend;
namespace test {
TEST_F(BackendTest, FrameScheduledCallback) {
SKIP_IF(Backend::WEBGPU, "Frame callbacks are unsupported in WebGPU");
auto& api = getDriverApi();
Cleanup cleanup(api);
// Create a SwapChain.
// In order for the frameScheduledCallback to be called, this must be a real SwapChain (not
// headless) so we obtain a drawable.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
Handle<HwRenderTarget> renderTarget = addCleanup(api.createDefaultRenderTarget());
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createDefaultRenderTarget());
int callbackCountA = 0;
api.setFrameScheduledCallback(
swapChain, nullptr,
[&callbackCountA](PresentCallable callable) {
callable();
callbackCountA++;
},
0);
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountA](PresentCallable callable) {
callable();
callbackCountA++;
}, 0);
// Render the first frame.
api.makeCurrent(swapChain, swapChain);
@@ -100,9 +100,10 @@ TEST_F(BackendTest, FrameCompletedCallback) {
SKIP_IF(Backend::WEBGPU, "Frame callbacks are unsupported in WebGPU");
auto& api = getDriverApi();
Cleanup cleanup(api);
// Create a SwapChain.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
int callbackCountA = 0;
api.setFrameCompletedCallback(swapChain, nullptr,
+14 -15
View File
@@ -99,28 +99,25 @@ struct MaterialParams {
// The problems are caused by both uploading and rendering into the same texture, since the OpenGL
// backend's readPixels does not work correctly with textures that have image data uploaded.
TEST_F(BackendTest, FeedbackLoops) {
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Image is unexpectedly darker, see b/417226296");
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL),
"OpenGL image is upside down due to readPixels failing for texture with uploaded image "
"data");
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::WEBGPU),
"OpenGL image is upside down due to readPixels failing for texture with uploaded image "
"data");
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "b/453756688");
SKIP_IF(Backend::VULKAN, "Image is unexpectedly darker, see b/453776546");
auto& api = getDriverApi();
Cleanup cleanup(api);
// The test is executed within this block scope to force destructors to run before
// executeCommands().
{
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH, false };
Shader shader = Shader(api, *mCleanup, ShaderConfig {
Shader shader = Shader(api, cleanup, ShaderConfig {
.vertexShader = fullscreenVs,
.fragmentShader = fullscreenFs,
.uniforms = {{"test_tex", DescriptorType::SAMPLER_2D_FLOAT, samplerInfo}, {"Params"}}
@@ -130,18 +127,18 @@ TEST_F(BackendTest, FeedbackLoops) {
// Create a texture.
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
Handle<HwTexture> const texture = addCleanup(api.createTexture(
Handle<HwTexture> const texture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage));
// Create ubo
auto ubuffer = addCleanup(api.createBufferObject(sizeof(MaterialParams),
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(MaterialParams),
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
// Create a RenderTarget for each miplevel.
Handle<HwRenderTarget> renderTargets[kNumLevels];
for (uint8_t level = 0; level < kNumLevels; level++) {
LOG(INFO) << "Level " << int(level) << ": " << (kTexWidth >> level) << "x" << (kTexHeight >> level);
renderTargets[level] = addCleanup(api.createRenderTarget( TargetBufferFlags::COLOR,
renderTargets[level] = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
kTexWidth >> level, kTexHeight >> level, 1, 0, { texture, level, 0 }, {}, {}));
}
@@ -175,13 +172,14 @@ TEST_F(BackendTest, FeedbackLoops) {
params.flags.discardStart = TargetBufferFlags::ALL;
state.rasterState.disableBlending();
for (int targetLevel = 1; targetLevel < kNumLevels; targetLevel++) {
Cleanup passCleanup(api);
const uint32_t sourceLevel = targetLevel - 1;
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
auto descriptorSet = shader.createDescriptorSet(api);
auto textureView = addCleanup(api.createTextureView(texture, sourceLevel, 1));
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, SamplerParams{
auto textureView = passCleanup.add(api.createTextureView(texture, sourceLevel, 1));
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
@@ -211,13 +209,14 @@ TEST_F(BackendTest, FeedbackLoops) {
state.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
state.rasterState.blendFunctionDstRGB = BlendFunction::ONE;
for (int targetLevel = kNumLevels - 2; targetLevel >= 0; targetLevel--) {
Cleanup passCleanup(api);
const uint32_t sourceLevel = targetLevel + 1;
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
auto descriptorSet = shader.createDescriptorSet(api);
auto textureView = addCleanup(api.createTextureView(texture, sourceLevel, 1));
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, SamplerParams{
auto textureView = passCleanup.add(api.createTextureView(texture, sourceLevel, 1));
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
+34 -28
View File
@@ -18,7 +18,6 @@
#include "BackendTestUtils.h"
#include "Lifetimes.h"
#include "PlatformRunner.h"
#include "Shader.h"
#include "SharedShaders.h"
#include "Skip.h"
@@ -233,7 +232,7 @@ public:
TEST_F(LoadImageTest, UpdateImage2D) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
SKIP_IF(Backend::VULKAN, "b/453776547");
FAIL_IF(Backend::VULKAN, "Multiple test cases crash, see b/417481434");
// All of these test cases should result in the same rendered image, and thus the same hash.
static const uint32_t expectedHash = 1875922935;
@@ -319,17 +318,19 @@ TEST_F(LoadImageTest, UpdateImage2D) {
// The test is executed within this block scope to force destructors to run before
// executeCommands().
for (const auto& t : testCases) {
Cleanup cleanup(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto defaultRenderTarget = addCleanup(api.createDefaultRenderTarget());
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget());
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, getSamplerFormat(t.textureFormat), Precision::HIGH, false };
std::string const fragment = getFormattedFragment(fragmentTemplate, t.textureFormat);
Shader shader(api, *mCleanup, ShaderConfig{
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = mVertexShader,
.fragmentShader= fragment,
.uniforms = {{"test_tex", DescriptorType::SAMPLER_2D_FLOAT, samplerInfo}}
@@ -337,7 +338,7 @@ TEST_F(LoadImageTest, UpdateImage2D) {
// Create a Texture.
auto usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
Handle<HwTexture> const texture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1,
Handle<HwTexture> const texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
t.textureFormat, 1, kTexSize, kTexSize, 1u, usage));
// Upload some pixel data.
@@ -361,7 +362,7 @@ TEST_F(LoadImageTest, UpdateImage2D) {
}
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, SamplerParams{
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
@@ -391,10 +392,8 @@ TEST_F(LoadImageTest, UpdateImage2D) {
}
TEST_F(LoadImageTest, UpdateImageSRGB) {
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "see b/453756688");
SKIP_IF(Backend::VULKAN, "b/454040142");
auto& api = getDriverApi();
Cleanup cleanup(api);
api.startCapture();
PixelDataFormat const pixelFormat = PixelDataFormat::RGBA;
@@ -402,22 +401,22 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
TextureFormat const textureFormat = TextureFormat::SRGB8_A8;
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto defaultRenderTarget = addCleanup(api.createDefaultRenderTarget());
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget());
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, getSamplerFormat(textureFormat), Precision::HIGH, false };
std::string const fragment = getFormattedFragment(fragmentTemplate, textureFormat);
Shader shader(api, *mCleanup, ShaderConfig{
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = mVertexShader, .fragmentShader = fragment, .uniforms = {{
"test_tex", DescriptorType::SAMPLER_2D_FLOAT, samplerInfo
}}});
// Create a texture.
Handle<HwTexture> const texture =
addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1, textureFormat, 1, kTexSize,
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, textureFormat, 1, kTexSize,
kTexSize, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE));
// Create image data.
@@ -447,7 +446,7 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
// Update samplers.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, SamplerParams{
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
@@ -478,6 +477,7 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
TEST_F(LoadImageTest, UpdateImageMipLevel) {
auto& api = getDriverApi();
Cleanup cleanup(api);
api.startCapture();
PixelDataFormat pixelFormat = PixelDataFormat::RGBA;
@@ -485,15 +485,15 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
TextureFormat textureFormat = TextureFormat::RGBA32F;
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto defaultRenderTarget = addCleanup(api.createDefaultRenderTarget());
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget());
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, getSamplerFormat(textureFormat), Precision::HIGH, false };
std::string const fragment = getFormattedFragment(fragmentUpdateImageMip, textureFormat);
Shader shader(api, *mCleanup, ShaderConfig {
Shader shader(api, cleanup, ShaderConfig {
.vertexShader = mVertexShader,
.fragmentShader = fragment,
.uniforms = {{"test_tex", DescriptorType::SAMPLER_2D_FLOAT, samplerInfo}}
@@ -503,7 +503,7 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
// Base level: 1024
// Level 1: 512 <-- upload data and sample from this level
// Level 2: 256
Handle<HwTexture> texture = addCleanup(
Handle<HwTexture> texture = cleanup.add(
api.createTexture(SamplerType::SAMPLER_2D, 3, textureFormat, 1, kDoubleTexSize,
kDoubleTexSize, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE));
@@ -513,7 +513,7 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
// Update samplers.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, SamplerParams{
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
@@ -545,8 +545,14 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
}
TEST_F(LoadImageTest, UpdateImage3D) {
SKIP_IF(Backend::VULKAN, "b/453776983");
FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Crashing when reading pixels without a redundant call to makeCurrent right before the"
"render pass. b/422798473");
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Checkerboard not drawn, possibly due to using wrong z value of 3d texture, "
"see b/417254499");
auto& api = getDriverApi();
Cleanup cleanup(api);
api.startCapture();
PixelDataFormat pixelFormat = PixelDataFormat::RGBA;
@@ -556,22 +562,22 @@ TEST_F(LoadImageTest, UpdateImage3D) {
TextureUsage usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT;
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto defaultRenderTarget = addCleanup(api.createDefaultRenderTarget());
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget());
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D_ARRAY, getSamplerFormat(textureFormat), Precision::HIGH, false };
std::string fragment = getFormattedFragment(fragmentUpdateImage3DTemplate, samplerType);
Shader shader(api, *mCleanup, ShaderConfig {
Shader shader(api, cleanup, ShaderConfig {
.vertexShader = mVertexShader,
.fragmentShader = fragment,
.uniforms = {{"test_tex", DescriptorType::SAMPLER_2D_ARRAY_FLOAT, samplerInfo}}
});
// Create a texture.
Handle<HwTexture> texture = addCleanup(api.createTexture(samplerType, 1,
Handle<HwTexture> texture = cleanup.add(api.createTexture(samplerType, 1,
textureFormat, 1, kTexSize, kTexSize, 4, usage));
// Create image data for all 4 layers.
@@ -596,9 +602,9 @@ TEST_F(LoadImageTest, UpdateImage3D) {
// Update samplers.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, SamplerParams{
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST });
api.updateDescriptorSetTexture(descriptorSet, 0, texture,
{ .filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST });
api.bindDescriptorSet(descriptorSet, 0, {});
+7 -6
View File
@@ -48,15 +48,16 @@ using namespace filament::backend;
TEST_F(BackendTest, MRT) {
DriverApi& api = getDriverApi();
Cleanup cleanup(api);
// The test is executed within this block scope to force destructors to run before
// executeCommands().
{
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
Shader shader(api, *mCleanup, ShaderConfig{
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = SharedShaders::getVertexShaderText(VertexShaderType::Noop,
ShaderUniformType::None),
.fragmentShader = fragment,
@@ -65,11 +66,11 @@ TEST_F(BackendTest, MRT) {
TrianglePrimitive triangle(api);
auto defaultRenderTarget = addCleanup(api.createDefaultRenderTarget());
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget());
// Create two Textures.
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
Handle<HwTexture> textureA = addCleanup(api.createTexture(
Handle<HwTexture> textureA = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, // target
1, // levels
TextureFormat::RGBA8, // format
@@ -78,7 +79,7 @@ TEST_F(BackendTest, MRT) {
screenHeight(), // height
1, // depth
usage)); // usage
Handle<HwTexture> textureB = addCleanup(api.createTexture(
Handle<HwTexture> textureB = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, // target
1, // levels
TextureFormat::RGBA8, // format
@@ -89,7 +90,7 @@ TEST_F(BackendTest, MRT) {
usage)); // usage
// Create a RenderTarget with two attachments.
Handle<HwRenderTarget> renderTarget = addCleanup(api.createRenderTarget(
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR0 | TargetBufferFlags::COLOR1,
// The width and height must match the width and height of the respective mip
// level (at least for OpenGL).
@@ -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);
+11 -10
View File
@@ -57,15 +57,16 @@ using namespace filament::backend;
TEST_F(BackendTest, TextureViewLod) {
auto& api = getDriverApi();
api.startCapture(0);
Cleanup cleanup(api);
// The test is executed within this block scope to force destructors to run before
// executeCommands().
{
// Create a SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
Shader whiteShader = SharedShaders::makeShader(api, *mCleanup, ShaderRequest {
Shader whiteShader = SharedShaders::makeShader(api, cleanup, ShaderRequest {
.mVertexType = VertexShaderType::Textured,
.mFragmentType = FragmentShaderType::White,
.mUniformType = ShaderUniformType::Sampler
@@ -77,7 +78,7 @@ TEST_F(BackendTest, TextureViewLod) {
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo {
"backend_test", "sib_tex", 0,
SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH, false };
Shader texturedShader(api, *mCleanup, ShaderConfig {
Shader texturedShader(api, cleanup, ShaderConfig {
.vertexShader = vertexShader,
.fragmentShader = fragmentTexturedLod,
.uniforms = {{
@@ -92,7 +93,7 @@ TEST_F(BackendTest, TextureViewLod) {
// Level 3: 16x16 (yellow)
const size_t kTextureSize = 128;
const size_t kMipLevels = 4;
Handle<HwTexture> texture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D,
Handle<HwTexture> texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kMipLevels, TextureFormat::RGBA8, 1, kTextureSize, kTextureSize, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT
| TextureUsage::UPLOADABLE));
@@ -129,11 +130,11 @@ TEST_F(BackendTest, TextureViewLod) {
// Level 1: 64x64 (green) <-- base
// Level 2: 32x32 (blue) <--- white triangle rendered
// Level 3: 16x16 (yellow) <-- max
auto texture13 = addCleanup(api.createTextureView(texture, 1, 3));
auto texture13 = cleanup.add(api.createTextureView(texture, 1, 3));
// Render a white triangle into level 2.
// We specify mip level 2, because minMaxLevels has no effect when rendering into a texture.
Handle<HwRenderTarget> renderTarget = addCleanup(api.createRenderTarget(
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR, 32, 32, 1, 0,
{texture, 2 /* level */, 0 /* layer */}, {}, {}));
{
@@ -153,7 +154,7 @@ TEST_F(BackendTest, TextureViewLod) {
}
backend::Handle<HwRenderTarget> defaultRenderTarget =
addCleanup(api.createDefaultRenderTarget());
cleanup.add(api.createDefaultRenderTarget());
PipelineState state = getColorWritePipelineState();
texturedShader.addProgramToPipelineState(state);
@@ -162,7 +163,7 @@ TEST_F(BackendTest, TextureViewLod) {
params.viewport = getFullViewport();
DescriptorSetHandle descriptorSet13 = texturedShader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet13, 0, texture13, SamplerParams{
api.updateDescriptorSetTexture(descriptorSet13, 0, texture13, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
@@ -181,10 +182,10 @@ TEST_F(BackendTest, TextureViewLod) {
api.endRenderPass();
// Adjust the base mip to 2.
auto texture22 = addCleanup(api.createTextureView(texture, 2, 2));
auto texture22 = cleanup.add(api.createTextureView(texture, 2, 2));
DescriptorSetHandle descriptorSet22 = texturedShader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet22, 0, texture22, SamplerParams{
api.updateDescriptorSetTexture(descriptorSet22, 0, texture22, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
@@ -63,18 +63,19 @@ TEST_F(BackendTest, MissingRequiredAttributes) {
// executeCommands().
{
DriverApi& api = getDriverApi();
Cleanup cleanup(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = addCleanup(createSwapChain());
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a program.
Shader shader(api, *mCleanup, ShaderConfig{
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = vertex,
.fragmentShader = SharedShaders::getFragmentShaderText(FragmentShaderType::White,
ShaderUniformType::None),
});
auto defaultRenderTarget = addCleanup(api.createDefaultRenderTarget());
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget());
TrianglePrimitive triangle(api);
@@ -1,103 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "BackendTest.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
#include "Shader.h"
#include "Skip.h"
#include "SharedShaders.h"
#include "TrianglePrimitive.h"
namespace test {
using namespace filament;
using namespace filament::backend;
using namespace filament::math;
// Copied from filament/SwapChain.h
static constexpr uint64_t CONFIG_MSAA_4_SAMPLES = backend::SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES;
// Tests are parameterized by headless versus native SwapChain.
struct MsaaSwapChainTest : public BackendTest, public testing::WithParamInterface<bool> {};
TEST_P(MsaaSwapChainTest, Basic) {
SKIP_IF(Backend::OPENGL, "OpenGL does not support MSAA SwapChain on all platforms.");
SKIP_IF(Backend::VULKAN, "Vulkan does not support MSAA SwapChain on all platforms.");
constexpr int kRenderTargetSize = 512;
auto useHeadlessSwapChain = GetParam();
auto& api = getDriverApi();
api.startCapture(0);
Cleanup cleanup(api);
auto swapChain = [&]() {
auto flags = CONFIG_MSAA_4_SAMPLES;
if (useHeadlessSwapChain) {
return cleanup.add(
api.createSwapChainHeadless(kRenderTargetSize, kRenderTargetSize, flags));
}
return cleanup.add(createSwapChain(flags));
}();
api.makeCurrent(swapChain, swapChain);
RenderTargetHandle renderTarget = cleanup.add(api.createDefaultRenderTarget());
Shader shader = SharedShaders::makeShader(api, cleanup, ShaderRequest{
.mVertexType = VertexShaderType::Noop,
.mFragmentType = FragmentShaderType::White,
});
TrianglePrimitive triangle(api);
RenderPassParams params = getClearColorRenderPass();
params.viewport = getFullViewport();
PipelineState ps = getColorWritePipelineState();
shader.addProgramToPipelineState(ps);
{
RenderFrame frame(api);
api.beginRenderPass(renderTarget, params);
ps.primitiveType = PrimitiveType::TRIANGLES;
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(ps);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
EXPECT_IMAGE(renderTarget,
ScreenshotParams(kRenderTargetSize, kRenderTargetSize, "MsaaSwapChain", 0));
api.commit(swapChain);
}
api.stopCapture(0);
}
// Helper to give names to the tests.
static std::string testNameGenerator(const testing::TestParamInfo<bool>& info) {
const bool useHeadlessSwapChain = info.param;
return useHeadlessSwapChain ? "HeadlessSwapChain" : "NativeSwapChain";
}
INSTANTIATE_TEST_SUITE_P(MsaaSwapChainTests, MsaaSwapChainTest, testing::Values(true, false),
testNameGenerator);
} // namespace test
+5 -5
View File
@@ -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);
+15 -12
View File
@@ -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.
+11 -9
View File
@@ -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}, {}, {}));
+23 -19
View File
@@ -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 }));
+5 -4
View File
@@ -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
-11
View File
@@ -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;
};
-13
View File
@@ -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.
*
-2
View File
@@ -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]
+2 -5
View File
@@ -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