Compare commits

..
Author SHA1 Message Date
Powei Feng c3fa1c7035 renderdiff: Use single lib for osmesa (#8255)
Previously, we linked against libOSMesa through the linker and
then used dlopen to link against libGL. This is not how libOSMesa
is intended to be used.  Instead, we use dlopen on libOSMesa
(via bluegl), which then will map the correct GL methods for us,
and for the OSMesa functions, we also map those functions
from libOSMesa (instead of relying on compile-time linker).
2024-11-07 15:42:44 -08:00
Powei Feng 66abb75bc4 vk: remove incorrect assert 2024-10-28 15:54:09 -07:00
Ben Doherty 9e1ee2f290 Fix potential crash when a descriptor set is destroyed but not unbound (#8215) 2024-10-18 16:42:52 -04:00
Powei Feng 22bb67e0b0 Bump version to 1.55.1 2024-10-14 06:07:43 +00:00
Powei Feng 4ee33cd591 Release Filament 1.55.0 2024-10-14 06:07:34 +00:00
Evan Mezeske fd2f9555f1 Fix tiny typo in VulkanPlatformSwapChainImpl.cpp logging (#8196) 2024-10-11 23:09:32 +00:00
Mathias Agopian ccdb58e93c don't set ANGLE features in filament (#8192)
ANGLE features should be set by apps, the system or developers but it's
not a good idea to set them in a library as it might conflict with other
libs etc.

we did it because it improved performance, but that should be fixed at
the angle level instead.
2024-10-11 13:52:54 -07:00
Ben Doherty 1eb1df2cf2 Check for use-after-free for heap handles (#8182) 2024-10-11 20:28:18 +00:00
Mathias Agopian d784ce311f gles: add a flag to enable the validation of the nativewindow
We are seeing a cluster of crashes that could be due to using an
EGLSurface whose ANativeWindow has become invalid. This could happen if
we continued to use (i.e. draw with) an EGLSurface after 
SurfaceHolder::onSurfaceDestroyed() has returned.

This new flag enables an assertion that the native window is valid at
the time of makeCurrent(), which happens early in the frame.

BUG=[330392256]
2024-10-10 13:38:35 -07:00
Powei Feng 875b295967 vk: refactor texture default layout (#8183)
Previously, default layout is based on usage, but this actually
has two paths (Filament's TextureUsage and the computed
VkTextureUsage) that do not always agree.  We simplify so that
default layout is stored in the texture itself.

Also remove some unnecessary code that is no longer necessary.
In particular, we shouldn't be doing a flush and wait for the
transition to complete before updating a sampler descriptor.
We just need to make sure the layout before it is accessed is
correctly given in the update struct.
2024-10-10 19:31:20 +00:00
Powei Feng b1b2d7072d Revert "Release Filament 1.55.0" (#8191)
This reverts commit 4f5369cefa.

Reason: Initial attempt to release 1.55.0 failed due to a few
   small bugs.
2024-10-10 12:16:00 -07:00
Sungun Park 014e6bcbde Update hardware Handle (#8187)
In certain compilers, the assignment operators defined as default
doesn't automatically make a call to the parent's method if it's
user-defined.

Make this behavior explicit to avoid this edge case.

BUGS=[371980551]
2024-10-10 17:12:22 +00:00
Powei Feng 614dbb44d5 Make sure color attachment textures have copy-able usage set (#8184)
To ensure the source of readPixels() is properly copy-able, we
want the backing textures to be created with the right BLIT_SRC usage.
However, this was not documented in the API. We workaround the issue
to tag all color attachment textures as BLIT_SRC.

This workaround will be removed in the future. For now, violations of
this condition will elicit a warning being printed out.
2024-10-10 16:02:11 +00:00
Ben Doherty 40c0d59464 Metal: enhance assertions for buffer uploads (#8188) 2024-10-09 16:54:14 -07:00
mdagois 1b0ff3aadb vk: Transient attachment support (#8021) 2024-10-08 23:20:35 +00:00
Haneul Kim 164a25cac4 Update FreeFlightManipulator.h 2024-10-08 10:38:00 -07:00
Mathias Agopian 2bbcb6de4c fix potential shadow rendering bug
the problem stems from a mismatch between the shader code
and the cpu code. if the shader is configured to read the shadow
map, then the cpu must generate it, otherwise we can get stale data.

Wether the shader reads the shadow map depends on the shadow type.
For directional shadows, the shader needs the SRE variant + a
"shadow enabled" bit per cascade in the main UBO.
For punctual shadows, the shader only needs the SRE variant.

Because of all that, if the conditions are met on the CPU side for
the shader to access the shadow map, we must make sure to generate it,
but in the case the shadow map would be empty (e.g. no shadow receivers),
we need to initialize it (and we can skip some work in the case of VSM).

BUGS=[369908659]
2024-10-08 10:22:50 -07:00
Mathias Agopian 58aa74b4ae cleanup code so it's easier to understand
this should make it clearer how we set the per primitive variant
when SSR and shadowin is involved
2024-10-08 10:22:50 -07:00
Mathias Agopian 26b42a7685 fix typo when generating the SSR pass
this caused the HAS_SHADOWS flag to not be disabled, this didn't
actually cause a problem because shadowing and SSR share the same
SRE variant bit. But both should never be active together.
2024-10-08 10:22:50 -07:00
Mathias Agopian 99b55cb888 minor renaming to be more consistant 2024-10-08 10:22:50 -07:00
Powei Feng 4375ffb3e8 vk: remove unnecessary hiding of impl for DescriptorSetManager (#8170)
We remove one layer of indirection for clarity and very small bit
performance saving.
2024-10-07 19:06:38 +00:00
Mathias Agopian e9aeb9312b fix shadow multiplier mode
when shadow multiplier was used, the material used the wrong
variant (unlit).
2024-10-04 15:18:23 -07:00
Benjamin Doherty 4be172050d Fix Android build 2024-10-04 13:45:22 -07:00
Ben Doherty 3b9fc8f751 Remove textureUseAfterFreePoolSize (#8163) 2024-10-04 10:33:09 -07:00
Mathias Agopian 3bd4c45d6e workaround Mesa glDeleteBuffers() bug
Mesa always clears the generic binding if the buffer deleted
is bound to an indexed binding, even if it's not bound to the
generic binding.

BUGS=[371324321]
2024-10-04 10:17:19 -07:00
Ben Doherty 1809aa7b11 Fix OpenGL ES 2.0 descriptor set crash (#8176) 2024-10-03 17:23:20 -07:00
Powei Feng 739d4007e2 Fix imported texture path (#8175)
We need to also account for external image textures that are
imported in texture creation.
2024-10-03 15:16:56 -07:00
Powei Feng 3ed9e8f2c0 Initialize bool in DescriptorSet.h (#8173)
This class has a ( = default) constructor and hence should have
explicit initialization in its definition.
2024-10-02 13:45:05 -07:00
Powei Feng e77ae6ec2c Work around client descriptor set issues (#8171)
- We change GLDescriptorSet::Buffer default constructor to
  workaround a client's compiler set up issue.
- We removed the assert_invariant that checks that ubo/samplers
  are not changed after committed in DescriptorSet. This caused
  an existing client's build to crash.
2024-10-02 06:24:41 -07:00
Powei Feng 4f5369cefa Release Filament 1.55.0 2024-10-01 17:09:03 +00:00
Powei Feng 93d28bc16a Change std::memcpy to memcpy in SkinningBuffer (#8169) 2024-10-01 09:53:14 -07:00
Sungun Park e85c22c6f5 Fix incomplete use of MI for debugCombineArrayTexture (#8168)
`commit` call is required before `use`, which became a new norm for the
new descriptor set design.
2024-09-30 10:54:10 -07:00
2cade209b5 Add Descriptor Sets to Filament (#8165)
We add the concept of the descriptor set as a way to describe
shader resources into Filament. This is a comprehensive change
across Filament.

Info on descriptor sets is available here:
https://docs.vulkan.org/spec/latest/chapters/descriptorsets.html

Co-authored-by: Benjamin Doherty <bendoherty@google.com>
Co-authored-by: Mathias Agopian <mathias@google.com>
Co-authored-by: Sungun Park <sungunpark@google.com>
2024-09-27 23:20:20 -07:00
Powei Feng 3d4fc7852b github: add software rasterizer job for GL to presubmit (#8158)
We use Mesa's gallium swrast to render as the driver with
Filament's backend set to GL. We provide a few scripts to parse
the tests (as jsons) and run gltf_viewer to produce the rendering.
2024-09-26 16:06:40 -07:00
Powei Feng 7dc17980a3 addressed comment 2024-09-25 21:05:42 -07:00
Powei Feng 7da09a7f45 gl: add PlatformOSMesa as an offscreen context
For GL+Linux, PlatformGLX will try to open an X11 window
regardless of whether we are doing headless/offscreen rendering
or not.

Here we add an OSMesa platform, which will allow us to avoid
opening any window on Linux. This is particularly useful for
situation where a display is not available, like for CI.

One important detail is that even though we are displaying through
a window, we keep the SDL2 dependency in tact for gltf_viewer.
This is due to the fact that gltf_viewer is built upon
FilamentApp, which is heavily integrated with SDL2. This is mostly
ok since we won't be hitting any path for opening a window due to
gltf_viewer's existing support for headless mode.
2024-09-25 21:05:42 -07:00
Ben Doherty b97221b8de Improve setFrameScheduled threading (#8139) 2024-09-25 10:43:19 -07:00
79 changed files with 1778 additions and 769 deletions
@@ -0,0 +1,9 @@
name: 'ubuntu apt add deb-src'
runs:
using: "composite"
steps:
- name: "ubuntu apt add deb-src"
run: |
echo "deb-src http://archive.ubuntu.com/ubuntu jammy main restricted universe" | sudo tee /etc/apt/sources.list.d/my.list
sudo apt-get update
shell: bash
+15
View File
@@ -76,3 +76,18 @@ jobs:
- name: Run build script
run: |
cd build/web && printf "y" | ./build.sh presubmit
test-renderdiff:
name: test-renderdiff
runs-on: ubuntu-22.04-32core
steps:
- uses: actions/checkout@v4.1.6
- uses: ./.github/actions/ubuntu-apt-add-src
- name: Run script
run: |
source ./build/linux/ci-common.sh && bash test/renderdiff_tests.sh
- uses: actions/upload-artifact@v4
with:
name: presubmit-renderdiff-result
path: ./out/renderdiff_tests
+16
View File
@@ -45,6 +45,8 @@ option(FILAMENT_ENABLE_FEATURE_LEVEL_0 "Enable Feature Level 0" ON)
option(FILAMENT_ENABLE_MULTIVIEW "Enable multiview for Filament" OFF)
option(FILAMENT_SUPPORTS_OSMESA "Enable OSMesa (headless GL context) for Filament" OFF)
set(FILAMENT_NDK_VERSION "" CACHE STRING
"Android NDK version or version prefix to be used when building for Android."
)
@@ -73,6 +75,10 @@ set(FILAMENT_BACKEND_DEBUG_FLAG "" CACHE STRING
"A debug flag meant for enabling/disabling backend debugging paths"
)
set(FILAMENT_OSMESA_PATH "" CACHE STRING
"Path to the OSMesa header and lib"
)
# Enable exceptions by default in spirv-cross.
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS OFF)
@@ -132,12 +138,22 @@ else()
endif()
if (LINUX)
if (NOT FILAMENT_OSMESA_PATH STREQUAL "")
if (NOT EXISTS ${FILAMENT_OSMESA_PATH}/)
message(FATAL_ERROR "Cannot find specified OSMesa build directory: ${FILAMENT_OSMESA_PATH}")
endif()
set(FILAMENT_SUPPORTS_OSMESA TRUE)
endif()
if (FILAMENT_SUPPORTS_WAYLAND)
add_definitions(-DFILAMENT_SUPPORTS_WAYLAND)
set(FILAMENT_SUPPORTS_X11 FALSE)
elseif (FILAMENT_SUPPORTS_EGL_ON_LINUX)
add_definitions(-DFILAMENT_SUPPORTS_EGL_ON_LINUX)
set(FILAMENT_SUPPORTS_X11 FALSE)
elseif (FILAMENT_SUPPORTS_OSMESA)
set(FILAMENT_SUPPORTS_X11 FALSE)
add_definitions(-DFILAMENT_SUPPORTS_OSMESA)
else ()
if (FILAMENT_SUPPORTS_XCB)
add_definitions(-DFILAMENT_SUPPORTS_XCB)
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.55.0'
implementation 'com.google.android.filament:filament-android:1.55.1'
}
```
@@ -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.55.0'
pod 'Filament', '~> 1.55.1'
```
## Documentation
+3
View File
@@ -7,6 +7,9 @@ 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.55.1
## v1.55.0
- Add descriptor sets to describe shader resources. [⚠️ **New Material Version**]
@@ -520,13 +520,12 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderConfig(JNIEnv*,
jclass, jlong nativeBuilder, jlong commandBufferSizeMB, jlong perRenderPassArenaSizeMB,
jlong driverHandleArenaSizeMB, jlong minCommandBufferSizeMB, jlong perFrameCommandsSizeMB,
jlong jobSystemThreadCount,
jlong textureUseAfterFreePoolSize, jboolean disableParallelShaderCompile,
jlong jobSystemThreadCount, jboolean disableParallelShaderCompile,
jint stereoscopicType, jlong stereoscopicEyeCount,
jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge,
jboolean disableHandleUseAfterFreeCheck,
jint preferredShaderLanguage,
jboolean forceGLES2Context) {
jboolean forceGLES2Context, jboolean assertNativeWindowIsValid) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
Engine::Config config = {
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
@@ -535,7 +534,6 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
.minCommandBufferSizeMB = (uint32_t) minCommandBufferSizeMB,
.perFrameCommandsSizeMB = (uint32_t) perFrameCommandsSizeMB,
.jobSystemThreadCount = (uint32_t) jobSystemThreadCount,
.textureUseAfterFreePoolSize = (uint32_t) textureUseAfterFreePoolSize,
.disableParallelShaderCompile = (bool) disableParallelShaderCompile,
.stereoscopicType = (Engine::StereoscopicType) stereoscopicType,
.stereoscopicEyeCount = (uint8_t) stereoscopicEyeCount,
@@ -544,6 +542,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
.disableHandleUseAfterFreeCheck = (bool) disableHandleUseAfterFreeCheck,
.preferredShaderLanguage = (Engine::Config::ShaderLanguage) preferredShaderLanguage,
.forceGLES2Context = (bool) forceGLES2Context,
.assertNativeWindowIsValid = (bool) assertNativeWindowIsValid,
};
builder->config(&config);
}
@@ -224,13 +224,12 @@ public class Engine {
nSetBuilderConfig(mNativeBuilder, config.commandBufferSizeMB,
config.perRenderPassArenaSizeMB, config.driverHandleArenaSizeMB,
config.minCommandBufferSizeMB, config.perFrameCommandsSizeMB,
config.jobSystemThreadCount,
config.textureUseAfterFreePoolSize, config.disableParallelShaderCompile,
config.jobSystemThreadCount, config.disableParallelShaderCompile,
config.stereoscopicType.ordinal(), config.stereoscopicEyeCount,
config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge,
config.disableHandleUseAfterFreeCheck,
config.preferredShaderLanguage.ordinal(),
config.forceGLES2Context);
config.forceGLES2Context, config.assertNativeWindowIsValid);
return this;
}
@@ -419,12 +418,12 @@ public class Engine {
*/
public long stereoscopicEyeCount = 2;
/*
/**
* @Deprecated This value is no longer used.
*/
public long resourceAllocatorCacheSizeMB = 64;
/*
/**
* This value determines how many frames texture entries are kept for in the cache. This
* is a soft limit, meaning some texture older than this are allowed to stay in the cache.
* Typically only one texture is evicted per frame.
@@ -432,12 +431,12 @@ public class Engine {
*/
public long resourceAllocatorCacheMaxAge = 1;
/*
/**
* Disable backend handles use-after-free checks.
*/
public boolean disableHandleUseAfterFreeCheck = false;
/*
/**
* Sets a preferred shader language for Filament to use.
*
* The Metal backend supports two shader languages: MSL (Metal Shading Language) and
@@ -459,12 +458,19 @@ public class Engine {
};
public ShaderLanguage preferredShaderLanguage = ShaderLanguage.DEFAULT;
/*
/**
* When the OpenGL ES backend is used, setting this value to true will force a GLES2.0
* context if supported by the Platform, or if not, will have the backend pretend
* it's a GLES2 context. Ignored on other backends.
*/
public boolean forceGLES2Context = false;
/**
* Assert the native window associated to a SwapChain is valid when calling makeCurrent().
* This is only supported for:
* - PlatformEGLAndroid
*/
public boolean assertNativeWindowIsValid = false;
}
private Engine(long nativeEngine, Config config) {
@@ -1406,12 +1412,11 @@ public class Engine {
private static native void nSetBuilderConfig(long nativeBuilder, long commandBufferSizeMB,
long perRenderPassArenaSizeMB, long driverHandleArenaSizeMB,
long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount,
long textureUseAfterFreePoolSize, boolean disableParallelShaderCompile,
int stereoscopicType, long stereoscopicEyeCount,
boolean disableParallelShaderCompile, int stereoscopicType, long stereoscopicEyeCount,
long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge,
boolean disableHandleUseAfterFreeCheck,
int preferredShaderLanguage,
boolean forceGLES2Context);
boolean forceGLES2Context, boolean assertNativeWindowIsValid);
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);
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.55.0
VERSION_NAME=1.55.1
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+9 -1
View File
@@ -64,6 +64,9 @@ function print_help {
echo " enabling debug paths in the backend from the build script. For example, make a"
echo " systrace-enabled build without directly changing #defines. Remember to add -f when"
echo " changing this option."
echo " -X osmesa_path"
echo " Indicates a path to a completed OSMesa build. OSMesa is used to create an offscreen GL"
echo " context for software rasterization"
echo " -S type"
echo " Enable stereoscopic rendering where type is one of [instanced|multiview]. This is only"
echo " meant for building the samples."
@@ -180,6 +183,8 @@ BACKEND_DEBUG_FLAG_OPTION=""
STEREOSCOPIC_OPTION=""
OSMESA_OPTION=""
IOS_BUILD_SIMULATOR=false
BUILD_UNIVERSAL_LIBRARIES=false
@@ -240,6 +245,7 @@ function build_desktop_target {
${ASAN_UBSAN_OPTION} \
${BACKEND_DEBUG_FLAG_OPTION} \
${STEREOSCOPIC_OPTION} \
${OSMESA_OPTION} \
${architectures} \
../..
ln -sf "out/cmake-${lc_target}/compile_commands.json" \
@@ -796,7 +802,7 @@ function check_debug_release_build {
pushd "$(dirname "$0")" > /dev/null
while getopts ":hacCfgijmp:q:uvslwedk:bx:S:" opt; do
while getopts ":hacCfgijmp:q:uvslwedk:bx:S:X:" opt; do
case ${opt} in
h)
print_help
@@ -950,6 +956,8 @@ while getopts ":hacCfgijmp:q:uvslwedk:bx:S:" opt; do
exit 1
esac
;;
X) OSMESA_OPTION="-DFILAMENT_OSMESA_PATH=${OPTARG}"
;;
\?)
echo "Invalid option: -${OPTARG}" >&2
echo ""
+10
View File
@@ -119,6 +119,8 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
list(APPEND SRCS src/opengl/platforms/PlatformGLX.cpp)
elseif (FILAMENT_SUPPORTS_EGL_ON_LINUX)
list(APPEND SRCS src/opengl/platforms/PlatformEGLHeadless.cpp)
elseif (FILAMENT_SUPPORTS_OSMESA)
list(APPEND SRCS src/opengl/platforms/PlatformOSMesa.cpp)
endif()
elseif (WIN32)
list(APPEND SRCS src/opengl/platforms/PlatformWGL.cpp)
@@ -366,6 +368,11 @@ set(LINUX_LINKER_OPTIMIZATION_FLAGS
-Wl,--exclude-libs,bluegl
)
if (LINUX AND FILAMENT_SUPPORTS_OSMESA)
set(OSMESA_COMPILE_FLAGS
-I${FILAMENT_OSMESA_PATH}/include/GL)
endif()
if (MSVC)
set(FILAMENT_WARNINGS /W3)
else()
@@ -386,6 +393,7 @@ endif()
target_compile_options(${TARGET} PRIVATE
${FILAMENT_WARNINGS}
${OSMESA_COMPILE_FLAGS}
$<$<CONFIG:Release>:${OPTIMIZATION_FLAGS}>
$<$<AND:$<PLATFORM_ID:Darwin>,$<CONFIG:Release>>:${DARWIN_OPTIMIZATION_FLAGS}>
)
@@ -395,6 +403,7 @@ if (FILAMENT_SUPPORTS_METAL)
endif()
target_link_libraries(${TARGET} PRIVATE
${OSMESA_LINKER_FLAGS}
$<$<AND:$<PLATFORM_ID:Linux>,$<CONFIG:Release>>:${LINUX_LINKER_OPTIMIZATION_FLAGS}>
)
@@ -429,6 +438,7 @@ if (APPLE OR LINUX)
test/test_StencilBuffer.cpp
test/test_Scissor.cpp
test/test_MipLevels.cpp
test/test_Handles.cpp
)
set(BACKEND_TEST_LIBS
backend
@@ -765,7 +765,8 @@ enum class TextureUsage : uint16_t {
BLIT_SRC = 0x0040, //!< Texture can be used the source of a blit()
BLIT_DST = 0x0080, //!< Texture can be used the destination of a blit()
PROTECTED = 0x0100, //!< Texture can be used for protected content
DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage
DEFAULT = UPLOADABLE | SAMPLEABLE, //!< Default texture usage
ALL_ATTACHMENTS = COLOR_ATTACHMENT | DEPTH_ATTACHMENT | STENCIL_ATTACHMENT | SUBPASS_INPUT, //!< Mask of all attachments
};
inline const char* stringify(TextureUsage usage) {
+13 -2
View File
@@ -23,6 +23,7 @@
#include <utils/debug.h>
#include <type_traits> // FIXME: STL headers are not allowed in public headers
#include <utility>
#include <stdint.h>
@@ -106,8 +107,18 @@ struct Handle : public HandleBase {
Handle(Handle const& rhs) noexcept = default;
Handle(Handle&& rhs) noexcept = default;
Handle& operator=(Handle const& rhs) noexcept = default;
Handle& operator=(Handle&& rhs) noexcept = default;
// Explicitly redefine copy/move assignment operators rather than just using default here.
// Because it doesn't make a call to the parent's method automatically during the std::move
// function call(https://en.cppreference.com/w/cpp/algorithm/move) in certain compilers like
// NDK 25.1.8937393 and below (see b/371980551)
Handle& operator=(Handle const& rhs) noexcept {
HandleBase::operator=(rhs);
return *this;
}
Handle& operator=(Handle&& rhs) noexcept {
HandleBase::operator=(std::move(rhs));
return *this;
}
explicit Handle(HandleId id) noexcept : HandleBase(id) { }
+7 -7
View File
@@ -68,13 +68,6 @@ public:
*/
size_t handleArenaSize = 0;
/**
* This number of most-recently destroyed textures will be tracked for use-after-free.
* Throws an exception when a texture is freed but still bound to a SamplerGroup and used in
* a draw call. 0 disables completely. Currently only respected by the Metal backend.
*/
size_t textureUseAfterFreePoolSize = 0;
size_t metalUploadBufferSizeBytes = 512 * 1024;
/**
@@ -98,6 +91,13 @@ public:
* Sets the technique for stereoscopic rendering.
*/
StereoscopicType stereoscopicType = StereoscopicType::NONE;
/**
* Assert the native window associated to a SwapChain is valid when calling makeCurrent().
* This is only supported for:
* - PlatformEGLAndroid
*/
bool assertNativeWindowIsValid = false;
};
Platform() noexcept;
@@ -89,6 +89,11 @@ protected:
*/
AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept override;
protected:
bool makeCurrent(ContextType type,
SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept override;
private:
struct InitializeJvmForPerformanceManagerIfNeeded {
InitializeJvmForPerformanceManagerIfNeeded();
@@ -102,6 +107,10 @@ private:
using clock = std::chrono::high_resolution_clock;
clock::time_point mStartTimeOfActualWork;
void* mNativeWindowLib = nullptr;
int32_t (*ANativeWindow_getBuffersDefaultDataSpace)(ANativeWindow* window) = nullptr;
bool mAssertNativeWindowIsValid = false;
};
} // namespace filament::backend
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2024 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_OPENGL_OPENGL_PLATFORM_OSMESA_H
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H
#include <stdint.h>
#include "bluegl/BlueGL.h"
#include <osmesa.h>
#include <backend/platforms/OpenGLPlatform.h>
#include <backend/DriverEnums.h>
namespace filament::backend {
/**
* A concrete implementation of OpenGLPlatform that uses OSMesa, which is an offscreen
* context that can be used in conjunction with Mesa for software rasterization.
* See https://docs.mesa3d.org/osmesa.html for more information.
*/
class PlatformOSMesa : public OpenGLPlatform {
protected:
// --------------------------------------------------------------------------------------------
// Platform Interface
Driver* createDriver(void* sharedGLContext, const DriverConfig& driverConfig) noexcept override;
int getOSVersion() const noexcept final override { return 0; }
// --------------------------------------------------------------------------------------------
// OpenGLPlatform Interface
void terminate() 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;
bool makeCurrent(ContextType type, SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept override;
void commit(SwapChain* swapChain) noexcept override;
private:
OSMesaContext mContext;
void* mOsMesaApi = nullptr;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H
@@ -139,7 +139,8 @@ DECL_DRIVER_API_N(beginFrame,
DECL_DRIVER_API_N(setFrameScheduledCallback,
backend::SwapChainHandle, sch,
backend::CallbackHandler*, handler,
backend::FrameScheduledCallback&&, callback)
backend::FrameScheduledCallback&&, callback,
uint64_t, flags)
DECL_DRIVER_API_N(setFrameCompletedCallback,
backend::SwapChainHandle, sch,
@@ -169,7 +169,7 @@ public:
auto [p, tag] = handleToPointer(handle.getId());
if (isPoolHandle(handle.getId())) {
// check for use after free
// check for pool handle use-after-free
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
auto const pNode = static_cast<typename Allocator::Node*>(p);
@@ -179,6 +179,22 @@ public:
<< "use-after-free of Handle with id=" << handle.getId()
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
}
} else {
// check for heap handle use-after-free
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
uint8_t const index = (handle.getId() & HANDLE_INDEX_MASK);
// if we've already handed out this handle index before, it's definitely a
// use-after-free, otherwise it's probably just a corrupted handle
if (index < mId) {
FILAMENT_CHECK_POSTCONDITION(p != nullptr)
<< "use-after-free of heap Handle with id=" << handle.getId()
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
} else {
FILAMENT_CHECK_POSTCONDITION(p != nullptr)
<< "corrupted heap Handle with id=" << handle.getId()
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
}
}
}
return static_cast<Dp>(p);
@@ -186,14 +202,18 @@ public:
template<typename B>
bool is_valid(Handle<B>& handle) {
if (handle && isPoolHandle(handle.getId())) {
auto [p, tag] = handleToPointer(handle.getId());
if (!handle) {
// null handles are invalid
return false;
}
auto [p, tag] = handleToPointer(handle.getId());
if (isPoolHandle(handle.getId())) {
uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
auto const pNode = static_cast<typename Allocator::Node*>(p);
uint8_t const expectedAge = pNode[-1].age;
return expectedAge == age;
}
return true;
return p != nullptr;
}
template<typename Dp, typename B>
+6
View File
@@ -41,6 +41,10 @@
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
#include "backend/platforms/PlatformEGLHeadless.h"
#endif
#elif defined(FILAMENT_SUPPORTS_OSMESA)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
#include "backend/platforms/PlatformOSMesa.h"
#endif
#endif
#elif defined(WIN32)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
@@ -124,6 +128,8 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
return new PlatformGLX();
#elif defined(FILAMENT_SUPPORTS_EGL_ON_LINUX)
return new PlatformEGLHeadless();
#elif defined(FILAMENT_SUPPORTS_OSMESA)
return new PlatformOSMesa();
#else
return nullptr;
#endif
+8 -4
View File
@@ -174,8 +174,10 @@ public:
* Update the buffer with data inside src. Potentially allocates a new buffer allocation to hold
* the bytes which will be released when the current frame is finished.
*/
void copyIntoBuffer(void* src, size_t size, size_t byteOffset);
void copyIntoBufferUnsynchronized(void* src, size_t size, size_t byteOffset);
using TagResolver = utils::Invocable<const char*(void)>;
void copyIntoBuffer(void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag);
void copyIntoBufferUnsynchronized(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag);
/**
* Denotes that this buffer is used for a draw call ensuring that its allocation remains valid
@@ -214,8 +216,10 @@ private:
BUMP_ALLOCATOR,
};
void uploadWithPoolBuffer(void* src, size_t size, size_t byteOffset) const;
void uploadWithBumpAllocator(void* src, size_t size, size_t byteOffset) const;
void uploadWithPoolBuffer(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const;
void uploadWithBumpAllocator(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const;
UploadStrategy mUploadStrategy;
TrackedMetalBuffer mBuffer;
+28 -10
View File
@@ -66,15 +66,20 @@ MetalBuffer::~MetalBuffer() {
}
}
void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
void MetalBuffer::copyIntoBuffer(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
if (size <= 0) {
return;
}
FILAMENT_CHECK_PRECONDITION(src)
<< "copyIntoBuffer called with a null src, tag=" << getHandleTag();
FILAMENT_CHECK_PRECONDITION(size + byteOffset <= mBufferSize)
<< "Attempting to copy " << size << " bytes into a buffer of size " << mBufferSize
<< " at offset " << byteOffset;
<< " at offset " << byteOffset << ", tag=" << getHandleTag();
// The copy blit requires that byteOffset be a multiple of 4.
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3)) << "byteOffset must be a multiple of 4";
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3))
<< "byteOffset must be a multiple of 4, tag=" << getHandleTag();
// If we have a cpu buffer, we can directly copy into it.
if (mCpuBuffer) {
@@ -84,17 +89,18 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
switch (mUploadStrategy) {
case UploadStrategy::BUMP_ALLOCATOR:
uploadWithBumpAllocator(src, size, byteOffset);
uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag));
break;
case UploadStrategy::POOL:
uploadWithPoolBuffer(src, size, byteOffset);
uploadWithPoolBuffer(src, size, byteOffset, std::move(getHandleTag));
break;
}
}
void MetalBuffer::copyIntoBufferUnsynchronized(void* src, size_t size, size_t byteOffset) {
void MetalBuffer::copyIntoBufferUnsynchronized(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
// TODO: implement the unsynchronized version
copyIntoBuffer(src, size, byteOffset);
copyIntoBuffer(src, size, byteOffset, std::move(getHandleTag));
}
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw() noexcept {
@@ -200,9 +206,13 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncod
}
}
void MetalBuffer::uploadWithPoolBuffer(void* src, size_t size, size_t byteOffset) const {
void MetalBuffer::uploadWithPoolBuffer(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const {
MetalBufferPool* bufferPool = mContext.bufferPool;
const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size);
FILAMENT_CHECK_POSTCONDITION(staging)
<< "uploadWithPoolbuffer unable to acquire staging buffer of size " << size
<< ", tag=" << getHandleTag();
memcpy(staging->buffer.get().contents, src, size);
// Encode a blit from the staging buffer into the private GPU buffer.
@@ -220,10 +230,18 @@ void MetalBuffer::uploadWithPoolBuffer(void* src, size_t size, size_t byteOffset
}];
}
void MetalBuffer::uploadWithBumpAllocator(void* src, size_t size, size_t byteOffset) const {
void MetalBuffer::uploadWithBumpAllocator(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const {
MetalBumpAllocator& allocator = *mContext.bumpAllocator;
auto [buffer, offset] = allocator.allocateStagingArea(size);
memcpy(static_cast<char*>(buffer.contents) + offset, src, size);
FILAMENT_CHECK_POSTCONDITION(buffer)
<< "uploadWithBumpAllocator unable to acquire staging area of size " << size
<< ", tag=" << getHandleTag();
void* const contents = buffer.contents;
FILAMENT_CHECK_POSTCONDITION(contents)
<< "uploadWithBumpAllocator unable to acquire pointer to staging area, size " << size
<< ", tag=" << getHandleTag();
memcpy(static_cast<char*>(contents) + offset, src, size);
// Encode a blit from the staging buffer into the private GPU buffer.
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
-11
View File
@@ -118,9 +118,6 @@ private:
};
struct MetalContext {
explicit MetalContext(size_t metalFreedTextureListSize)
: texturesToDestroy(metalFreedTextureListSize) {}
MetalDriver* driver;
id<MTLDevice> device = nullptr;
id<MTLCommandQueue> commandQueue = nullptr;
@@ -187,14 +184,6 @@ struct MetalContext {
// Keeps track of all alive textures.
tsl::robin_set<MetalTexture*> textures;
// This circular buffer implements delayed destruction for Metal texture handles. It keeps a
// handle to a fixed number of the most recently destroyed texture handles. When we're asked to
// destroy a texture handle, we free its texture memory, but keep the MetalTexture object alive,
// marking it as "terminated". If we later are asked to use that texture, we can check its
// terminated status and throw an Objective-C error instead of crashing, which is helpful for
// debugging use-after-free issues in release builds.
utils::FixedCircularBuffer<Handle<HwTexture>> texturesToDestroy;
MetalBufferPool* bufferPool;
MetalBumpAllocator* bumpAllocator;
+32 -29
View File
@@ -19,6 +19,8 @@
#include "CommandStreamDispatcher.h"
#include "metal/MetalDriver.h"
#include <filament/SwapChain.h>
#include "MetalBlitter.h"
#include "MetalBufferPool.h"
#include "MetalContext.h"
@@ -38,6 +40,7 @@
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/sstream.h>
#include <utils/Invocable.h>
#include <algorithm>
@@ -105,13 +108,13 @@ Dispatcher MetalDriver::getDispatcher() const noexcept {
return ConcreteDispatcher<MetalDriver>::make();
}
MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept
: mPlatform(*platform),
mContext(new MetalContext(driverConfig.textureUseAfterFreePoolSize)),
mHandleAllocator("Handles",
driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck),
mStereoscopicType(driverConfig.stereoscopicType) {
MetalDriver::MetalDriver(
MetalPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept
: mPlatform(*platform),
mContext(new MetalContext),
mHandleAllocator(
"Handles", driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck),
mStereoscopicType(driverConfig.stereoscopicType) {
mContext->driver = this;
TrackedMetalBuffer::setPlatform(platform);
@@ -256,10 +259,14 @@ void MetalDriver::beginFrame(int64_t monotonic_clock_ns,
}
}
void MetalDriver::setFrameScheduledCallback(
Handle<HwSwapChain> sch, CallbackHandler* handler, FrameScheduledCallback&& callback) {
void MetalDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
FrameScheduledCallback&& callback, uint64_t flags) {
// Turn off the CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER flag if a custom handler is provided.
if (handler) {
flags &= ~SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER;
}
auto* swapChain = handle_cast<MetalSwapChain>(sch);
swapChain->setFrameScheduledCallback(handler, std::move(callback));
swapChain->setFrameScheduledCallback(handler, std::move(callback), flags);
}
void MetalDriver::setFrameCompletedCallback(
@@ -802,15 +809,7 @@ void MetalDriver::destroyTexture(Handle<HwTexture> th) {
auto* metalTexture = handle_cast<MetalTexture>(th);
mContext->textures.erase(metalTexture);
// Free memory from the texture and mark it as freed.
metalTexture->terminate();
// Add this texture handle to our texturesToDestroy queue to be destroyed later.
if (auto handleToFree = mContext->texturesToDestroy.push(th)) {
// If texturesToDestroy is full, then .push evicts the oldest texture handle in the
// queue (or simply th, if use-after-free detection is disabled).
destruct_handle<MetalTexture>(handleToFree.value());
}
destruct_handle<MetalTexture>(th);
}
void MetalDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
@@ -860,12 +859,6 @@ void MetalDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
}
void MetalDriver::terminate() {
// Terminate any outstanding MetalTextures.
while (!mContext->texturesToDestroy.empty()) {
Handle<HwTexture> toDestroy = mContext->texturesToDestroy.pop();
destruct_handle<MetalTexture>(toDestroy);
}
// finish() will flush the pending command buffer and will ensure all GPU work has finished.
// This must be done before calling bufferPool->reset() to ensure no buffers are in flight.
finish();
@@ -1089,24 +1082,34 @@ size_t MetalDriver::getMaxUniformBufferSize() {
void MetalDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& data,
uint32_t byteOffset) {
FILAMENT_CHECK_PRECONDITION(data.buffer)
<< "updateIndexBuffer called with a null buffer.";
auto* ib = handle_cast<MetalIndexBuffer>(ibh);
ib->buffer.copyIntoBuffer(data.buffer, data.size, byteOffset);
ib->buffer.copyIntoBuffer(data.buffer, data.size, byteOffset,
[&]() { return mHandleAllocator.getHandleTag(ibh.getId()).c_str_safe(); });
scheduleDestroy(std::move(data));
}
void MetalDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& data,
uint32_t byteOffset) {
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
<< "updateBufferObject must be called outside of a render pass.";
<< "updateBufferObject must be called outside of a render pass. tag="
<< mHandleAllocator.getHandleTag(boh.getId()).c_str_safe();
FILAMENT_CHECK_PRECONDITION(data.buffer)
<< "updateBufferObject called with a null buffer. tag="
<< mHandleAllocator.getHandleTag(boh.getId()).c_str_safe();
auto* bo = handle_cast<MetalBufferObject>(boh);
bo->updateBuffer(data.buffer, data.size, byteOffset);
bo->updateBuffer(data.buffer, data.size, byteOffset,
[&]() { return mHandleAllocator.getHandleTag(boh.getId()).c_str_safe(); });
scheduleDestroy(std::move(data));
}
void MetalDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> boh,
BufferDescriptor&& data, uint32_t byteOffset) {
auto* bo = handle_cast<MetalBufferObject>(boh);
bo->updateBufferUnsynchronized(data.buffer, data.size, byteOffset);
bo->updateBufferUnsynchronized(data.buffer, data.size, byteOffset,
[&]() { return mHandleAllocator.getHandleTag(boh.getId()).c_str_safe(); });
scheduleDestroy(std::move(data));
}
+9 -25
View File
@@ -74,7 +74,8 @@ public:
void releaseDrawable();
void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback);
void setFrameScheduledCallback(
CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags);
void setFrameCompletedCallback(
CallbackHandler* handler, utils::Invocable<void(void)>&& callback);
@@ -123,6 +124,7 @@ private:
struct {
CallbackHandler* handler = nullptr;
std::shared_ptr<FrameScheduledCallback> callback = nullptr;
uint64_t flags = 0;
} frameScheduled;
struct {
@@ -133,11 +135,15 @@ private:
class MetalBufferObject : public HwBufferObject {
public:
using TagResolver = MetalBuffer::TagResolver;
MetalBufferObject(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage,
uint32_t byteCount);
void updateBuffer(void* data, size_t size, uint32_t byteOffset);
void updateBufferUnsynchronized(void* data, size_t size, uint32_t byteOffset);
void updateBuffer(void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag);
void updateBufferUnsynchronized(
void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag);
MetalBuffer* getBuffer() { return &buffer; }
private:
@@ -265,26 +271,6 @@ public:
MTLPixelFormat devicePixelFormat;
// Frees memory associated with this texture and marks it as "terminated".
// Used to track "use after free" scenario.
void terminate() noexcept;
bool isTerminated() const noexcept { return terminated; }
inline void checkUseAfterFree(const char* samplerGroupDebugName, size_t textureIndex) const {
if (UTILS_LIKELY(!isTerminated())) {
return;
}
NSString* reason =
[NSString stringWithFormat:
@"Filament Metal texture use after free, sampler group = "
@"%s, texture index = %zu",
samplerGroupDebugName, textureIndex];
NSException* useAfterFreeException =
[NSException exceptionWithName:@"MetalTextureUseAfterFree"
reason:reason
userInfo:nil];
[useAfterFreeException raise];
}
private:
void loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice,
PixelBufferDescriptor const& data) noexcept;
@@ -301,8 +287,6 @@ private:
// Filament swizzling only affects texture reads, so this should not be used when the texture is
// bound as a render target attachment.
id<MTLTexture> swizzledTextureView = nil;
bool terminated = false;
};
class MetalRenderTarget : public HwRenderTarget {
+33 -25
View File
@@ -229,9 +229,10 @@ void MetalSwapChain::ensureDepthStencilTexture() {
}
void MetalSwapChain::setFrameScheduledCallback(
CallbackHandler* handler, FrameScheduledCallback&& callback) {
CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) {
frameScheduled.handler = handler;
frameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
frameScheduled.flags = flags;
}
void MetalSwapChain::setFrameCompletedCallback(
@@ -260,10 +261,10 @@ public:
PresentDrawableData& operator=(const PresentDrawableData&) = delete;
static PresentDrawableData* create(id<CAMetalDrawable> drawable,
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver) {
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags) {
assert_invariant(drawableMutex);
assert_invariant(driver);
return new PresentDrawableData(drawable, drawableMutex, driver);
return new PresentDrawableData(drawable, drawableMutex, driver, flags);
}
static void maybePresentAndDestroyAsync(PresentDrawableData* that, bool shouldPresent) {
@@ -271,16 +272,23 @@ public:
[that->mDrawable present];
}
// mDrawable is acquired on the driver thread. Typically, we would release this object on
// the same thread, but after receiving consistent crash reports from within
// [CAMetalDrawable dealloc], we suspect this object requires releasing on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{ cleanupAndDestroy(that); });
if (that->mFlags & SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER) {
cleanupAndDestroy(that);
} else {
// mDrawable is acquired on the driver thread. Typically, we would release this object
// on the same thread, but after receiving consistent crash reports from within
// [CAMetalDrawable dealloc], we suspect this object requires releasing on the main
// thread.
dispatch_async(dispatch_get_main_queue(), ^{
cleanupAndDestroy(that);
});
}
}
private:
PresentDrawableData(id<CAMetalDrawable> drawable, std::shared_ptr<std::mutex> drawableMutex,
MetalDriver* driver)
: mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver) {}
MetalDriver* driver, uint64_t flags)
: mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver), mFlags(flags) {}
static void cleanupAndDestroy(PresentDrawableData *that) {
if (that->mDrawable) {
@@ -295,6 +303,7 @@ private:
id<CAMetalDrawable> mDrawable;
std::shared_ptr<std::mutex> mDrawableMutex;
MetalDriver* mDriver = nullptr;
uint64_t mFlags = 0;
};
void presentDrawable(bool presentFrame, void* user) {
@@ -311,8 +320,8 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
struct Callback {
Callback(std::shared_ptr<FrameScheduledCallback> callback, id<CAMetalDrawable> drawable,
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver)
: f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver)) {}
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags)
: f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver, flags)) {}
std::shared_ptr<FrameScheduledCallback> f;
// PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later.
std::unique_ptr<PresentDrawableData> data;
@@ -327,14 +336,19 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
// This callback pointer will be captured by the block. Even if the scheduled handler is never
// called, the unique_ptr will still ensure we don't leak memory.
uint64_t const flags = frameScheduled.flags;
__block auto callback = std::make_unique<Callback>(
frameScheduled.callback, drawable, layerDrawableMutex, context.driver);
frameScheduled.callback, drawable, layerDrawableMutex, context.driver, flags);
backend::CallbackHandler* handler = frameScheduled.handler;
MetalDriver* driver = context.driver;
[getPendingCommandBuffer(&context) addScheduledHandler:^(id<MTLCommandBuffer> cb) {
Callback* user = callback.release();
driver->scheduleCallback(handler, user, &Callback::func);
if (flags & SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER) {
Callback::func(user);
} else {
driver->scheduleCallback(handler, user, &Callback::func);
}
}];
}
@@ -369,12 +383,14 @@ MetalBufferObject::MetalBufferObject(MetalContext& context, BufferObjectBinding
BufferUsage usage, uint32_t byteCount)
: HwBufferObject(byteCount), buffer(context, bindingType, usage, byteCount) {}
void MetalBufferObject::updateBuffer(void* data, size_t size, uint32_t byteOffset) {
buffer.copyIntoBuffer(data, size, byteOffset);
void MetalBufferObject::updateBuffer(
void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag) {
buffer.copyIntoBuffer(data, size, byteOffset, std::move(getHandleTag));
}
void MetalBufferObject::updateBufferUnsynchronized(void* data, size_t size, uint32_t byteOffset) {
buffer.copyIntoBufferUnsynchronized(data, size, byteOffset);
void MetalBufferObject::updateBufferUnsynchronized(
void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag) {
buffer.copyIntoBufferUnsynchronized(data, size, byteOffset, std::move(getHandleTag));
}
MetalVertexBufferInfo::MetalVertexBufferInfo(MetalContext& context, uint8_t bufferCount,
@@ -634,14 +650,6 @@ MetalTexture::MetalTexture(MetalContext& context, TextureFormat format, uint32_t
texture = externalImage->getMtlTexture();
}
void MetalTexture::terminate() noexcept {
texture = nil;
swizzledTextureView = nil;
msaaSidecar = nil;
externalImage = nullptr;
terminated = true;
}
id<MTLTexture> MetalTexture::getMtlTextureForRead() const noexcept {
return swizzledTextureView ? swizzledTextureView : texture;
}
+1 -1
View File
@@ -54,7 +54,7 @@ void NoopDriver::beginFrame(int64_t monotonic_clock_ns,
}
void NoopDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
CallbackHandler* handler, FrameScheduledCallback&& callback) {
CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) {
}
+2 -2
View File
@@ -3467,8 +3467,8 @@ void OpenGLDriver::beginFrame(
}
}
void OpenGLDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
CallbackHandler* handler, FrameScheduledCallback&& callback) {
void OpenGLDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
FrameScheduledCallback&& callback, uint64_t flags) {
DEBUG_MARKER()
}
@@ -553,6 +553,8 @@ void PlatformEGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept {
if (swapChain) {
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;
@@ -25,12 +25,14 @@
#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/ostream.h>
#include <utils/Panic.h>
#include <utils/Log.h>
#include <EGL/egl.h>
@@ -42,7 +44,9 @@
#include <chrono>
#include <new>
#include <string_view>
#include <dlfcn.h>
#include <unistd.h>
#include <stddef.h>
@@ -80,8 +84,6 @@ UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSANDROIDPROC eglGetFrameTimestampsANDROID =
}
using namespace glext;
using EGLStream = Platform::Stream;
// ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() {
@@ -101,38 +103,73 @@ PlatformEGLAndroid::PlatformEGLAndroid() noexcept
mExternalStreamManager(ExternalStreamManagerAndroid::create()),
mInitializeJvmForPerformanceManagerIfNeeded(),
mPerformanceHintManager() {
char scratch[PROP_VALUE_MAX + 1];
int length = __system_property_get("ro.build.version.release", scratch);
int const androidVersion = length >= 0 ? atoi(scratch) : 1;
if (!androidVersion) {
mOSVersion = 1000; // if androidVersion is 0, it means "future"
} else {
length = __system_property_get("ro.build.version.sdk", scratch);
mOSVersion = length >= 0 ? atoi(scratch) : 1;
mOSVersion = android_get_device_api_level();
if (mOSVersion < 0) {
mOSVersion = __ANDROID_API_FUTURE__;
}
// This disables an ANGLE optimization on ARM, which turns out to be more costly for us
// see b/229017581
// We need to do this before we create the GL context.
// An alternative solution is use a system property:
// __system_property_set(
// "debug.angle.feature_overrides_disabled",
// "preferSubmitAtFBOBoundary");
// but that would outlive this process, so the environment variable is better.
// We also make sure to not update the variable if it already exists.
// There is no harm setting this if we're not on ANGLE or ARM.
setenv("ANGLE_FEATURE_OVERRIDES_DISABLED", "preferSubmitAtFBOBoundary", false);
mNativeWindowLib = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW);
if (mNativeWindowLib) {
ANativeWindow_getBuffersDefaultDataSpace =
(int32_t(*)(ANativeWindow*))dlsym(mNativeWindowLib,
"ANativeWindow_getBuffersDefaultDataSpace");
}
}
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept = default;
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
if (mNativeWindowLib) {
dlclose(mNativeWindowLib);
}
}
void PlatformEGLAndroid::terminate() noexcept {
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
PlatformEGL::terminate();
}
static constexpr const std::string_view kNativeWindowInvalidMsg =
"ANativeWindow is invalid. It probably has been destroyed. EGL surface = ";
bool PlatformEGLAndroid::makeCurrent(ContextType type,
SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept {
// fast & safe path
if (UTILS_LIKELY(!mAssertNativeWindowIsValid)) {
return PlatformEGL::makeCurrent(type, drawSwapChain, readSwapChain);
}
SwapChainEGL const* const dsc = static_cast<SwapChainEGL const*>(drawSwapChain);
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 monotonic_clock_ns,
int64_t refreshIntervalNs,
@@ -189,6 +226,8 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
"eglGetFrameTimestampsANDROID");
}
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
return driver;
}
@@ -0,0 +1,149 @@
/*
* Copyright (C) 2024 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 <backend/platforms/PlatformOSMesa.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#include <dlfcn.h>
#include <memory>
namespace filament::backend {
using namespace backend;
namespace {
using BackingType = GLfloat;
#define BACKING_GL_TYPE GL_FLOAT
struct OSMesaSwapchain {
OSMesaSwapchain(uint32_t width, uint32_t height)
: width(width),
height(height),
buffer(new uint8_t[width * height * 4 * sizeof(BackingType)]) {}
uint32_t width = 0;
uint32_t height = 0;
std::unique_ptr<uint8_t[]> buffer;
};
struct OSMesaAPI {
private:
using CreateContextFunc = OSMesaContext (*)(GLenum format, OSMesaContext);
using DestroyContextFunc = GLboolean (*)(OSMesaContext);
using MakeCurrentFunc = GLboolean (*)(OSMesaContext ctx, void* buffer, GLenum type,
GLsizei width, GLsizei height);
using GetProcAddressFunc = OSMESAproc (*)(const char* funcName);
public:
CreateContextFunc OSMesaCreateContext;
DestroyContextFunc OSMesaDestroyContext;
MakeCurrentFunc OSMesaMakeCurrent;
GetProcAddressFunc OSMesaGetProcAddress;
OSMesaAPI() {
constexpr char const* libraryNames[] = {"libOSMesa.so", "libosmesa.so"};
for (char const* libName: libraryNames) {
mLib = dlopen(libName, RTLD_GLOBAL | RTLD_NOW);
if (mLib) {
break;
}
}
FILAMENT_CHECK_PRECONDITION(mLib)
<< "Unable to dlopen libOSMesa to create a software GL context";
OSMesaGetProcAddress = (GetProcAddressFunc) dlsym(mLib, "OSMesaGetProcAddress");
OSMesaCreateContext = (CreateContextFunc) OSMesaGetProcAddress("OSMesaCreateContext");
OSMesaDestroyContext =
(DestroyContextFunc) OSMesaGetProcAddress("OSMesaDestroyContext");
OSMesaMakeCurrent = (MakeCurrentFunc) OSMesaGetProcAddress("OSMesaMakeCurrent");
}
~OSMesaAPI() {
dlclose(mLib);
}
private:
void* mLib = nullptr;
};
}// anonymous namespace
Driver* PlatformOSMesa::createDriver(void* const sharedGLContext,
const DriverConfig& driverConfig) noexcept {
OSMesaAPI* api = new OSMesaAPI();
mOsMesaApi = api;
FILAMENT_CHECK_PRECONDITION(sharedGLContext == nullptr)
<< "shared GL context is not supported with PlatformOSMesa";
mContext = api->OSMesaCreateContext(GL_RGBA, NULL);
// We need to do a no-op makecurrent here so that the context will be in a correct state before
// any GL calls.
auto chain = createSwapChain(1, 1, 0);
makeCurrent(ContextType::UNPROTECTED, chain, nullptr);
destroySwapChain(chain);
int result = bluegl::bind();
FILAMENT_CHECK_POSTCONDITION(!result) << "Unable to load OpenGL entry points.";
return OpenGLPlatform::createDefaultDriver(this, sharedGLContext, driverConfig);
}
void PlatformOSMesa::terminate() noexcept {
OSMesaAPI* api = (OSMesaAPI*) mOsMesaApi;
api->OSMesaDestroyContext(mContext);
delete api;
mOsMesaApi = nullptr;
bluegl::unbind();
}
Platform::SwapChain* PlatformOSMesa::createSwapChain(void* nativeWindow, uint64_t flags) noexcept {
FILAMENT_CHECK_POSTCONDITION(false) << "Cannot create non-headless swapchain";
return (SwapChain*) nativeWindow;
}
Platform::SwapChain* PlatformOSMesa::createSwapChain(uint32_t width, uint32_t height,
uint64_t flags) noexcept {
OSMesaSwapchain* swapchain = new OSMesaSwapchain(width, height);
return (SwapChain*) swapchain;
}
void PlatformOSMesa::destroySwapChain(Platform::SwapChain* swapChain) noexcept {
OSMesaSwapchain* impl = (OSMesaSwapchain*) swapChain;
delete impl;
}
bool PlatformOSMesa::makeCurrent(ContextType type, SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept {
OSMesaAPI* api = (OSMesaAPI*) mOsMesaApi;
OSMesaSwapchain* impl = (OSMesaSwapchain*) drawSwapChain;
auto result = api->OSMesaMakeCurrent(mContext, (BackingType*) impl->buffer.get(),
BACKING_GL_TYPE, impl->width, impl->height);
FILAMENT_CHECK_POSTCONDITION(result == GL_TRUE) << "OSMesaMakeCurrent failed!";
return true;
}
void PlatformOSMesa::commit(Platform::SwapChain* swapChain) noexcept {
// No-op since we are not scanning out to a display.
}
} // namespace filament::backend
@@ -66,10 +66,10 @@ inline void blitFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect, V
1, blitRegions, filter);
if (oldSrcLayout == VulkanLayout::UNDEFINED) {
oldSrcLayout = imgutil::getDefaultLayout(src.texture->usage);
oldSrcLayout = src.texture->getDefaultLayout();
}
if (oldDstLayout == VulkanLayout::UNDEFINED) {
oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage);
oldDstLayout = dst.texture->getDefaultLayout();
}
src.texture->transitionLayout(commands, srcRange, oldSrcLayout);
dst.texture->transitionLayout(commands, dstRange, oldDstLayout);
@@ -109,10 +109,10 @@ inline void resolveFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect
1, resolveRegions);
if (oldSrcLayout == VulkanLayout::UNDEFINED) {
oldSrcLayout = imgutil::getDefaultLayout(src.texture->usage);
oldSrcLayout = src.texture->getDefaultLayout();
}
if (oldDstLayout == VulkanLayout::UNDEFINED) {
oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage);
oldDstLayout = dst.texture->getDefaultLayout();
}
src.texture->transitionLayout(commands, srcRange, oldSrcLayout);
dst.texture->transitionLayout(commands, dstRange, oldDstLayout);
@@ -144,6 +144,10 @@ public:
return mPhysicalDeviceFeatures.shaderClipDistance == VK_TRUE;
}
inline bool isLazilyAllocatedMemorySupported() const noexcept {
return mLazilyAllocatedMemorySupported;
}
private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
VkPhysicalDeviceProperties mPhysicalDeviceProperties = {};
@@ -151,6 +155,7 @@ private:
bool mDebugMarkersSupported = false;
bool mDebugUtilsSupported = false;
bool mMultiviewEnabled = false;
bool mLazilyAllocatedMemorySupported = false;
VkFormatList mDepthStencilFormats;
VkFormatList mBlittableDepthStencilFormats;
+13 -47
View File
@@ -88,28 +88,6 @@ VmaAllocator createAllocator(VkInstance instance, VkPhysicalDevice physicalDevic
return allocator;
}
VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator, VulkanStagePool& stagePool) {
VulkanTexture* emptyTexture = new VulkanTexture(device, physicalDevice, context, allocator,
commands, handleAllocator, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1,
TextureUsage::DEFAULT | TextureUsage::COLOR_ATTACHMENT | TextureUsage::SUBPASS_INPUT,
stagePool, true /* heap allocated */);
uint32_t black = 0;
PixelBufferDescriptor pbd(&black, 4, PixelDataFormat::RGBA, PixelDataType::UBYTE);
emptyTexture->updateImage(pbd, 1, 1, 1, 0, 0, 0, 0);
return emptyTexture;
}
VulkanBufferObject* createEmptyBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanCommands* commands) {
VulkanBufferObject* obj =
new VulkanBufferObject(allocator, stagePool, 1, BufferObjectBinding::UNIFORM);
uint8_t byte = 0;
obj->buffer.loadFromCpu(commands->get().buffer(), &byte, 0, 1);
return obj;
}
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location,
@@ -255,13 +233,6 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
#endif
mTimestamps = std::make_unique<VulkanTimestamps>(mPlatform->getDevice());
mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(),
mContext, mAllocator, &mCommands, &mResourceAllocator, mStagePool);
mEmptyBufferObject = createEmptyBufferObject(mAllocator, mStagePool, &mCommands);
mDescriptorSetManager.setPlaceHolders(mSamplerCache.getSampler({}), mEmptyTexture,
mEmptyBufferObject);
}
VulkanDriver::~VulkanDriver() noexcept = default;
@@ -326,9 +297,6 @@ void VulkanDriver::terminate() {
// to those commands are no longer referenced.
finish(0);
delete mEmptyBufferObject;
delete mEmptyTexture;
// Command buffers should come first since it might have commands depending on resources that
// are about to be destroyed.
mCommands.terminate();
@@ -399,8 +367,8 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
FVK_SYSTRACE_END();
}
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
CallbackHandler* handler, FrameScheduledCallback&& callback) {
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
FrameScheduledCallback&& callback, uint64_t flags) {
}
void VulkanDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
@@ -436,14 +404,6 @@ void VulkanDriver::updateDescriptorSetTexture(
SamplerParams params) {
VulkanDescriptorSet* set = mResourceAllocator.handle_cast<VulkanDescriptorSet*>(dsh);
VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(th);
// We need to make sure the initial layout transition has been completed before we can write
// the sampler descriptor. We flush and wait until the transition has been completed.
if (!texture->transitionReady()) {
mCommands.flush();
mCommands.wait();
}
VkSampler const vksampler = mSamplerCache.getSampler(params);
mDescriptorSetManager.updateSampler(set, binding, texture, vksampler);
}
@@ -1315,12 +1275,14 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
// Create the VkRenderPass or fetch it from cache.
VulkanFboCache::RenderPassKey rpkey = {
.initialDepthLayout = currentDepthLayout,
.depthFormat = depth.getFormat(),
.clear = clearVal,
.discardStart = discardStart,
.discardEnd = discardEndVal,
.initialDepthLayout = currentDepthLayout,
.samples = rt->getSamples(),
.needsResolveMask = 0,
.usesLazilyAllocatedMemory = 0,
.subpassMask = uint8_t(params.subpassMask),
.viewCount = renderTargetLayerCount,
};
@@ -1329,8 +1291,14 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
if (info.texture) {
assert_invariant(info.layerCount == renderTargetLayerCount);
rpkey.colorFormat[i] = info.getFormat();
if (rpkey.samples > 1 && info.texture->samples == 1) {
rpkey.needsResolveMask |= (1 << i);
if (rpkey.samples > 1) {
const VulkanTexture* sidecar = info.texture->getSidecar();
if (sidecar && sidecar->isTransientAttachment()) {
rpkey.usesLazilyAllocatedMemory |= (1 << i);
}
if (info.texture->samples == 1) {
rpkey.needsResolveMask |= (1 << i);
}
}
} else {
rpkey.colorFormat[i] = VK_FORMAT_UNDEFINED;
@@ -1382,8 +1350,6 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
VulkanTexture* texture = attachment.texture;
if (texture->samples == 1) {
mRenderPassFboInfo.hasColorResolve = true;
auto const& range = attachment.getSubresourceRange();
attachment.texture->transitionLayout(&commands,
range, VulkanLayout::COLOR_ATTACHMENT);
@@ -115,10 +115,6 @@ private:
VulkanPlatform* mPlatform = nullptr;
std::unique_ptr<VulkanTimestamps> mTimestamps;
// Placeholder resources
VulkanTexture* mEmptyTexture;
VulkanBufferObject* mEmptyBufferObject;
VulkanSwapChain* mCurrentSwapChain = nullptr;
VulkanRenderTarget* mDefaultRenderTarget = nullptr;
VulkanRenderPass mCurrentRenderPass = {};
@@ -156,7 +152,6 @@ private:
struct {
using AttachmentArray = CappedArray<VulkanAttachment, MAX_RENDERTARGET_ATTACHMENT_TEXTURES>;
AttachmentArray attachments;
bool hasColorResolve = false;
} mRenderPassFboInfo = {};
bool const mIsSRGBSwapChainSupported;
@@ -41,6 +41,7 @@ bool VulkanFboCache::RenderPassEq::operator()(const RenderPassKey& k1,
if (k1.discardEnd != k2.discardEnd) return false;
if (k1.samples != k2.samples) return false;
if (k1.needsResolveMask != k2.needsResolveMask) return false;
if (k1.usesLazilyAllocatedMemory != k2.usesLazilyAllocatedMemory) return false;
if (k1.subpassMask != k2.subpassMask) return false;
if (k1.viewCount != k2.viewCount) return false;
return true;
@@ -254,7 +255,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
.format = config.colorFormat[i],
.samples = (VkSampleCountFlagBits) config.samples,
.loadOp = clear ? kClear : (discard ? kDontCare : kKeep),
.storeOp = kEnableStore,
.storeOp = (config.usesLazilyAllocatedMemory & (1 << i)) ? kDisableStore : kEnableStore,
.stencilLoadOp = kDontCare,
.stencilStoreOp = kDisableStore,
.initialLayout = imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT),
+4 -3
View File
@@ -42,18 +42,19 @@ public:
// RenderPassKey is a small POD representing the immutable state that is used to construct
// a VkRenderPass. It is hashed and used as a lookup key.
struct alignas(8) RenderPassKey {
VulkanLayout initialDepthLayout;
uint8_t padding[3] = {};
VkFormat colorFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]; // 32 bytes
VkFormat depthFormat; // 4 bytes
TargetBufferFlags clear; // 4 bytes
TargetBufferFlags discardStart; // 4 bytes
TargetBufferFlags discardEnd; // 4 bytes
VulkanLayout initialDepthLayout; // 1 byte
uint8_t samples; // 1 byte
uint8_t needsResolveMask; // 1 byte
uint8_t usesLazilyAllocatedMemory; // 1 byte
uint8_t subpassMask; // 1 byte
uint8_t viewCount; // 1 byte
uint8_t padding[2];
};
struct RenderPassVal {
VkRenderPass handle;
@@ -115,7 +115,6 @@ VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout const&
void VulkanDescriptorSet::acquire(VulkanTexture* texture) {
mResources.acquire(texture);
mTextures[mTextureCount++] = texture;
}
void VulkanDescriptorSet::acquire(VulkanBufferObject* bufferObject) {
@@ -283,11 +282,16 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
if (texture && texture->samples == 1) {
auto msTexture = texture->getSidecar();
if (UTILS_UNLIKELY(!msTexture)) {
// Clear all usage flags that are not related to attachments, so that we can
// use the transient usage flag.
const TextureUsage usage = texture->usage & TextureUsage::ALL_ATTACHMENTS;
assert_invariant(static_cast<uint16_t>(usage) != 0U);
// TODO: This should be allocated with the ResourceAllocator.
msTexture = new VulkanTexture(device, physicalDevice, context, allocator, commands,
handleAllocator,
texture->target, ((VulkanTexture const*) texture)->levels, texture->format,
samples, texture->width, texture->height, texture->depth, texture->usage,
samples, texture->width, texture->height, texture->depth, usage,
stagePool, true /* heap allocated */);
texture->setSidecar(msTexture);
}
+1 -16
View File
@@ -151,17 +151,9 @@ public:
void acquire(VulkanBufferObject* texture);
bool hasTexture(VulkanTexture* texture) {
return std::any_of(mTextures.begin(), mTextures.end(),
[texture](auto t) { return t == texture; });
}
// TODO: maybe change to fixed size for performance.
VkDescriptorSet const vkSet;
private:
std::array<VulkanTexture*, 16> mTextures = { nullptr };
uint8_t mTextureCount = 0;
VulkanAcquireOnlyResourceManager mResources;
OnRecycle mOnRecycleFn;
};
@@ -202,10 +194,6 @@ struct VulkanProgram : public HwProgram, VulkanResource {
inline VkShaderModule getFragmentShader() const { return mInfo->shaders[1]; }
inline utils::FixedCapacityVector<uint16_t> const& getBindingToSamplerIndex() const {
return mInfo->bindingToSamplerIndex;
}
// Get a list of the sampler binding indices so that we don't have to loop through all possible
// samplers.
inline BindingList const& getBindings() const { return mInfo->bindings; }
@@ -236,8 +224,7 @@ struct VulkanProgram : public HwProgram, VulkanResource {
private:
struct PipelineInfo {
explicit PipelineInfo(backend::Program const& program) noexcept
: bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff),
pushConstantDescription(program)
: pushConstantDescription(program)
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
, bindingToName(MAX_SAMPLER_COUNT, "")
#endif
@@ -245,8 +232,6 @@ private:
BindingList bindings;
// We store the samplerGroupIndex as the top 8-bit and the index within each group as the lower 8-bit.
utils::FixedCapacityVector<uint16_t> bindingToSamplerIndex;
VkShaderModule shaders[MAX_SHADER_MODULES] = { VK_NULL_HANDLE };
PushConstantDescription pushConstantDescription;
@@ -76,36 +76,6 @@ inline VkImageViewType getViewType(SamplerType target) {
}
}
inline VulkanLayout getDefaultLayout(TextureUsage usage) {
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
if (any(usage & TextureUsage::SAMPLEABLE)) {
return VulkanLayout::DEPTH_SAMPLER;
} else {
return VulkanLayout::DEPTH_ATTACHMENT;
}
}
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
return VulkanLayout::COLOR_ATTACHMENT;
}
// Finally, the layout for an immutable texture is optimal read-only.
return VulkanLayout::READ_ONLY;
}
inline VulkanLayout getDefaultLayout(VkImageUsageFlags vkusage) {
TextureUsage usage{};
if (vkusage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
usage = usage | TextureUsage::DEPTH_ATTACHMENT;
}
if (vkusage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) {
usage = usage | TextureUsage::COLOR_ATTACHMENT;
}
if (vkusage & VK_IMAGE_USAGE_SAMPLED_BIT) {
usage = usage | TextureUsage::SAMPLEABLE;
}
return getDefaultLayout(usage);
}
constexpr inline VkImageLayout getVkLayout(VulkanLayout layout) {
switch (layout) {
case VulkanLayout::UNDEFINED:
@@ -232,7 +232,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
VulkanAttachment const srcAttachment = srcTarget->getColor(0);
VkImageSubresourceRange const srcRange = srcAttachment.getSubresourceRange();
srcTexture->transitionLayout(cmdbuffer, {}, srcRange, VulkanLayout::TRANSFER_SRC);
srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
VkImageCopy const imageCopyRegion = {
.srcSubresource = {
@@ -268,7 +268,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
imgutil::getVkLayout(VulkanLayout::TRANSFER_DST), 1, &imageCopyRegion);
// Restore the source image layout.
srcTexture->transitionLayout(cmdbuffer, {}, srcRange, VulkanLayout::COLOR_ATTACHMENT);
srcTexture->transitionLayout(cmdbuffer, srcRange, srcTexture->getDefaultLayout());
vkEndCommandBuffer(cmdbuffer);
+94 -43
View File
@@ -113,23 +113,51 @@ VkComponentMapping composeSwizzle(VkComponentMapping const& prev, VkComponentMap
};
}
inline VulkanLayout getDefaultLayoutImpl(TextureUsage usage) {
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
if (any(usage & TextureUsage::SAMPLEABLE)) {
return VulkanLayout::DEPTH_SAMPLER;
} else {
return VulkanLayout::DEPTH_ATTACHMENT;
}
}
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
return VulkanLayout::COLOR_ATTACHMENT;
}
// Finally, the layout for an immutable texture is optimal read-only.
return VulkanLayout::READ_ONLY;
}
inline VulkanLayout getDefaultLayoutImpl(VkImageUsageFlags vkusage) {
TextureUsage usage{};
if (vkusage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
usage = usage | TextureUsage::DEPTH_ATTACHMENT;
}
if (vkusage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) {
usage = usage | TextureUsage::COLOR_ATTACHMENT;
}
if (vkusage & VK_IMAGE_USAGE_SAMPLED_BIT) {
usage = usage | TextureUsage::SAMPLEABLE;
}
return getDefaultLayoutImpl(usage);
}
} // anonymous namespace
VulkanTextureState::VulkanTextureState(
VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool,
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount)
: VulkanResource(VulkanResourceType::HEAP_ALLOCATED),
mVkFormat(format),
mViewType(viewType),
mFullViewRange {
filament::backend::getImageAspect(format), 0, levels, 0, layerCount
},
mStagePool(stagePool),
mDevice(device),
mAllocator(allocator),
mCommands(commands) {
}
VulkanTextureState::VulkanTextureState(VkDevice device, VmaAllocator allocator,
VulkanCommands* commands, VulkanStagePool& stagePool, VkFormat format,
VkImageViewType viewType, uint8_t levels, uint8_t layerCount, VulkanLayout defaultLayout)
: VulkanResource(VulkanResourceType::HEAP_ALLOCATED),
mVkFormat(format),
mViewType(viewType),
mFullViewRange{filament::backend::getImageAspect(format), 0, levels, 0, layerCount},
mDefaultLayout(defaultLayout),
mStagePool(stagePool),
mDevice(device),
mAllocator(allocator),
mCommands(commands),
mIsTransientAttachment(false) {}
VulkanTextureState* VulkanTexture::getSharedState() {
VulkanTextureState* state = mAllocator->handle_cast<VulkanTextureState*>(mState);
@@ -141,6 +169,7 @@ VulkanTextureState const* VulkanTexture::getSharedState() const {
return state;
}
// Constructor for internally passed VkImage
VulkanTexture::VulkanTexture(
VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
@@ -153,12 +182,14 @@ VulkanTexture::VulkanTexture(
mAllocator(handleAllocator),
mState(handleAllocator->initHandle<VulkanTextureState>(
device, allocator, commands, stagePool,
format, imgutil::getViewType(SamplerType::SAMPLER_2D), 1, 1)) {
format, imgutil::getViewType(SamplerType::SAMPLER_2D), 1, 1,
getDefaultLayoutImpl(tusage))) {
auto* const state = getSharedState();
state->mTextureImage = image;
mPrimaryViewRange = state->mFullViewRange;
}
// Constructor for user facing texture
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator, SamplerType target, uint8_t levels,
@@ -170,7 +201,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
mAllocator(handleAllocator),
mState(handleAllocator->initHandle<VulkanTextureState>(device, allocator, commands, stagePool,
backend::getVkFormat(tformat), imgutil::getViewType(target), levels,
getLayerCount(target, depth))) {
getLayerCount(target, depth), VulkanLayout::UNDEFINED)) {
auto* const state = getSharedState();
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
@@ -209,6 +240,19 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
// Determine if we can use the transient usage flag combined with lazily allocated memory.
const bool useTransientAttachment =
// Lazily allocated memory is available.
context.isLazilyAllocatedMemorySupported() &&
// Usage consists of attachment flags only.
none(tusage & ~TextureUsage::ALL_ATTACHMENTS) &&
// Usage contains at least one attachment flag.
any(tusage & TextureUsage::ALL_ATTACHMENTS);
state->mIsTransientAttachment = useTransientAttachment;
const VkImageUsageFlags transientFlag =
useTransientAttachment ? VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : 0U;
if (any(usage & TextureUsage::SAMPLEABLE)) {
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
@@ -224,19 +268,19 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | transientFlag;
if (any(usage & TextureUsage::SUBPASS_INPUT)) {
imageInfo.usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
}
}
if (any(usage & TextureUsage::STENCIL_ATTACHMENT)) {
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag;
}
if (any(usage & TextureUsage::UPLOADABLE)) {
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag;
// Depth resolves uses a custom shader and therefore needs to be sampleable.
if (samples > 1) {
@@ -285,8 +329,11 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VkMemoryRequirements memReqs = {};
vkGetImageMemoryRequirements(state->mDevice, state->mTextureImage, &memReqs);
const VkFlags requiredMemoryFlags =
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
(useTransientAttachment ? VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT : 0U);
uint32_t memoryTypeIndex
= context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
= context.selectMemoryType(memReqs.memoryTypeBits, requiredMemoryFlags);
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES)
<< "VulkanTexture: unable to find a memory type that meets requirements.";
@@ -309,18 +356,20 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanCommandBuffer& commandsBuf = state->mCommands->get();
commandsBuf.acquire(this);
transitionLayout(&commandsBuf, mPrimaryViewRange, imgutil::getDefaultLayout(imageInfo.usage));
auto const defaultLayout = state->mDefaultLayout = getDefaultLayoutImpl(imageInfo.usage);
transitionLayout(&commandsBuf, mPrimaryViewRange, defaultLayout);
}
// Constructor for creating a texture view
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, uint8_t baseLevel, uint8_t levelCount)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
VulkanResource(VulkanResourceType::TEXTURE),
mAllocator(handleAllocator)
{
VulkanResourceAllocator* handleAllocator, VulkanTexture const* src, uint8_t baseLevel,
uint8_t levelCount)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
VulkanResource(VulkanResourceType::TEXTURE),
mAllocator(handleAllocator) {
mState = src->mState;
auto* state = getSharedState();
@@ -330,14 +379,15 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
mPrimaryViewRange.levelCount = levelCount;
}
// Constructor for creating a texture view with swizzle
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, VkComponentMapping swizzle)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
VulkanResource(VulkanResourceType::TEXTURE),
mAllocator(handleAllocator) {
VulkanResourceAllocator* handleAllocator, VulkanTexture const* src,
VkComponentMapping swizzle)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
VulkanResource(VulkanResourceType::TEXTURE),
mAllocator(handleAllocator) {
mState = src->mState;
auto* state = getSharedState();
state->refs++;
@@ -440,7 +490,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
VkImageLayout const newVkLayout = imgutil::getVkLayout(newLayout);
if (nextLayout == VulkanLayout::UNDEFINED) {
nextLayout = imgutil::getDefaultLayout(this->usage);
nextLayout = getDefaultLayout();
}
transitionLayout(&commands, transitionRange, newLayout);
@@ -491,6 +541,11 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
transitionLayout(&commands, range, oldLayout);
}
VulkanLayout VulkanTexture::getDefaultLayout() const {
auto* const state = getSharedState();
return state->mDefaultLayout;
}
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
range.levelCount = 1;
range.layerCount = 1;
@@ -536,13 +591,11 @@ VkImageAspectFlags VulkanTexture::getImageAspect() const {
}
bool VulkanTexture::transitionLayout(VulkanCommandBuffer* commands,
const VkImageSubresourceRange& range, VulkanLayout newLayout) {
return transitionLayout(commands->buffer(), commands->fence, range, newLayout);
VkImageSubresourceRange const& range, VulkanLayout newLayout) {
return transitionLayout(commands->buffer(), range, newLayout);
}
bool VulkanTexture::transitionLayout(
VkCommandBuffer cmdbuf, std::shared_ptr<VulkanCmdFence> fence,
const VkImageSubresourceRange& range,
bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceRange const& range,
VulkanLayout newLayout) {
auto* const state = getSharedState();
VulkanLayout const oldLayout = getLayout(range.baseArrayLayer, range.baseMipLevel);
@@ -602,8 +655,6 @@ bool VulkanTexture::transitionLayout(
setLayout(range, newLayout);
if (hasTransitions) {
state->mTransitionFence = fence;
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
FVK_LOGD << "transition texture=" << state->mTextureImage << " (" << range.baseArrayLayer
<< "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << ","
+16 -14
View File
@@ -34,8 +34,8 @@ class VulkanResourceAllocator;
struct VulkanTextureState : public VulkanResource {
VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool,
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount);
VulkanStagePool& stagePool, VkFormat format, VkImageViewType viewType, uint8_t levels,
uint8_t layerCount, VulkanLayout defaultLayout);
struct ImageViewKey {
VkImageSubresourceRange range; // 4 * 5 bytes
@@ -67,8 +67,8 @@ struct VulkanTextureState : public VulkanResource {
VkFormat const mVkFormat;
VkImageViewType const mViewType;
VkImageSubresourceRange const mFullViewRange;
VkImage mTextureImage = VK_NULL_HANDLE;
VulkanLayout mDefaultLayout;
// Track the image layout of each subresource using a sparse range map.
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
@@ -78,7 +78,7 @@ struct VulkanTextureState : public VulkanResource {
VkDevice mDevice;
VmaAllocator mAllocator;
VulkanCommands* mCommands;
std::shared_ptr<VulkanCmdFence> mTransitionFence;
bool mIsTransientAttachment;
};
@@ -134,6 +134,10 @@ struct VulkanTexture : public HwTexture, VulkanResource {
return getLayout(mPrimaryViewRange.baseArrayLayer, mPrimaryViewRange.baseMipLevel);
}
// Returns the layout for the intended use of this texture (and not the expected layout at the
// time of the call.
VulkanLayout getDefaultLayout() const;
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
// target attachment. Unlike the primary image view, this always has type VK_IMAGE_VIEW_TYPE_2D
// and the identity swizzle.
@@ -170,11 +174,16 @@ struct VulkanTexture : public HwTexture, VulkanResource {
return state->mSidecarMSAA.get();
}
bool transitionLayout(VulkanCommandBuffer* commands, const VkImageSubresourceRange& range,
bool isTransientAttachment() const {
VulkanTextureState const* state = getSharedState();
return state->mIsTransientAttachment;
}
bool transitionLayout(VulkanCommandBuffer* commands, VkImageSubresourceRange const& range,
VulkanLayout newLayout);
bool transitionLayout(VkCommandBuffer cmdbuf, std::shared_ptr<VulkanCmdFence> fence,
VkImageSubresourceRange const& range, VulkanLayout newLayout);
bool transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceRange const& range,
VulkanLayout newLayout);
void attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
VkImageSubresourceRange const& range);
@@ -190,13 +199,6 @@ struct VulkanTexture : public HwTexture, VulkanResource {
// manually (outside of calls to transitionLayout).
void setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout);
bool transitionReady() {
VulkanTextureState* state = getSharedState();
auto res = !state->mTransitionFence || state->mTransitionFence->getStatus() == VK_SUCCESS;
state->mTransitionFence.reset();
return res;
}
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
void print() const;
#endif
@@ -193,64 +193,6 @@ private:
UnusedSetMap mUnused;
};
// This is an ever-expanding pool of sets where it
// 1. Keeps a list of smaller pools of different layout-dimensions.
// 2. Will add a pool if existing pool are not compatible with the requested layout o runs out.
class DescriptorInfinitePool {
private:
static constexpr uint16_t EXPECTED_SET_COUNT = 10;
static constexpr float SET_COUNT_GROWTH_FACTOR = 1.5;
public:
DescriptorInfinitePool(VkDevice device)
: mDevice(device) {}
VkDescriptorSet obtainSet(VulkanDescriptorSetLayout* layout) {
auto const vklayout = layout->getVkLayout();
DescriptorPool* sameTypePool = nullptr;
for (auto& pool: mPools) {
if (!pool->canAllocate(layout->count)) {
continue;
}
if (auto set = pool->obtainSet(vklayout); set != VK_NULL_HANDLE) {
return set;
}
if (!sameTypePool || sameTypePool->capacity() < pool->capacity()) {
sameTypePool = pool.get();
}
}
uint16_t capacity = EXPECTED_SET_COUNT;
if (sameTypePool) {
// Exponentially increase the size of the pool to ensure we don't hit this too often.
capacity = std::ceil(sameTypePool->capacity() * SET_COUNT_GROWTH_FACTOR);
}
// We need to increase the set of pools by one.
mPools.push_back(std::make_unique<DescriptorPool>(mDevice,
DescriptorCount::fromLayoutBitmask(layout->bitmask), capacity));
auto& pool = mPools.back();
auto ret = pool->obtainSet(vklayout);
assert_invariant(ret != VK_NULL_HANDLE && "failed to obtain a set?");
return ret;
}
void recycle(DescriptorCount const& count, VkDescriptorSetLayout vklayout,
VkDescriptorSet vkSet) {
for (auto& pool: mPools) {
if (!pool->canAllocate(count)) {
continue;
}
pool->recycle(vklayout, vkSet);
break;
}
}
private:
VkDevice mDevice;
std::vector<std::unique_ptr<DescriptorPool>> mPools;
};
template<typename Key>
struct Equal {
bool operator()(Key const& k1, Key const& k2) const {
@@ -318,7 +260,67 @@ inline VkDescriptorSetLayout createLayout(VkDevice device, BitmaskGroup const& b
return layout;
}
class DescriptorSetLayoutManager {
} // anonymous namespace
// This is an ever-expanding pool of sets where it
// 1. Keeps a list of smaller pools of different layout-dimensions.
// 2. Will add a pool if existing pool are not compatible with the requested layout o runs out.
class VulkanDescriptorSetManager::DescriptorInfinitePool {
private:
static constexpr uint16_t EXPECTED_SET_COUNT = 10;
static constexpr float SET_COUNT_GROWTH_FACTOR = 1.5;
public:
DescriptorInfinitePool(VkDevice device)
: mDevice(device) {}
VkDescriptorSet obtainSet(VulkanDescriptorSetLayout* layout) {
auto const vklayout = layout->getVkLayout();
DescriptorPool* sameTypePool = nullptr;
for (auto& pool: mPools) {
if (!pool->canAllocate(layout->count)) {
continue;
}
if (auto set = pool->obtainSet(vklayout); set != VK_NULL_HANDLE) {
return set;
}
if (!sameTypePool || sameTypePool->capacity() < pool->capacity()) {
sameTypePool = pool.get();
}
}
uint16_t capacity = EXPECTED_SET_COUNT;
if (sameTypePool) {
// Exponentially increase the size of the pool to ensure we don't hit this too often.
capacity = std::ceil(sameTypePool->capacity() * SET_COUNT_GROWTH_FACTOR);
}
// We need to increase the set of pools by one.
mPools.push_back(std::make_unique<DescriptorPool>(mDevice,
DescriptorCount::fromLayoutBitmask(layout->bitmask), capacity));
auto& pool = mPools.back();
auto ret = pool->obtainSet(vklayout);
assert_invariant(ret != VK_NULL_HANDLE && "failed to obtain a set?");
return ret;
}
void recycle(DescriptorCount const& count, VkDescriptorSetLayout vklayout,
VkDescriptorSet vkSet) {
for (auto& pool: mPools) {
if (!pool->canAllocate(count)) {
continue;
}
pool->recycle(vklayout, vkSet);
break;
}
}
private:
VkDevice mDevice;
std::vector<std::unique_ptr<DescriptorPool>> mPools;
};
class VulkanDescriptorSetManager::DescriptorSetLayoutManager {
public:
DescriptorSetLayoutManager(VkDevice device)
: mDevice(device) {}
@@ -345,329 +347,226 @@ private:
mVkLayouts;
};
} // anonymous namespace
class VulkanDescriptorSetManager::Impl {
class VulkanDescriptorSetManager::DescriptorSetHistory {
private:
struct DescriptorSetHistory {
private:
using TextureBundle = std::pair<VulkanTexture*, VkImageSubresourceRange>;
public:
DescriptorSetHistory()
: dynamicUboCount(0),
mResources(nullptr) {}
DescriptorSetHistory(UniformBufferBitmask const& dynamicUbo, uint8_t uniqueDynamicUboCount,
VulkanResourceAllocator* allocator, VulkanDescriptorSet* set)
: dynamicUboMask(dynamicUbo),
dynamicUboCount(uniqueDynamicUboCount),
mResources(allocator),
mSet(set),
mBound(false) {
assert_invariant(set);
// initial state is unbound.
mResources.acquire(mSet);
unbind();
}
~DescriptorSetHistory() {
if (mSet) {
mResources.clear();
}
}
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
mOffsets = std::move(offsets);
mBound = false;
}
void write(uint8_t binding) noexcept {
mBound = false;
}
// Ownership will be transfered to the commandbuffer.
void bind(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout, uint8_t index) noexcept {
VkCommandBuffer const cmdbuffer = commands->buffer();
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout,
index, 1, &mSet->vkSet, dynamicUboCount, mOffsets.data());
commands->acquire(mSet);
mResources.clear();
mBound = true;
}
void unbind() noexcept {
mResources.acquire(mSet);
mBound = false;
}
bool bound() const noexcept { return mBound; }
UniformBufferBitmask const dynamicUboMask;
uint8_t const dynamicUboCount;
private:
FixedSizeVulkanResourceManager<1> mResources;
VulkanDescriptorSet* mSet = nullptr;
backend::DescriptorSetOffsetArray mOffsets;
bool mBound = false;
};
using DescriptorSetHistoryArray =
std::array<DescriptorSetHistory*, UNIQUE_DESCRIPTOR_SET_COUNT>;
struct BoundInfo {
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
DescriptorSetMask setMask;
DescriptorSetHistoryArray boundSets;
bool operator==(BoundInfo const& info) const {
if (pipelineLayout != info.pipelineLayout || setMask != info.setMask) {
return false;
}
bool equal = true;
setMask.forEachSetBit([&](size_t i) {
if (boundSets[i] != info.boundSets[i]) {
equal = false;
}
});
return equal;
}
};
using TextureBundle = std::pair<VulkanTexture*, VkImageSubresourceRange>;
public:
Impl(VkDevice device, VulkanResourceAllocator* resourceAllocator)
: mDevice(device),
mResourceAllocator(resourceAllocator),
mLayoutManager(device),
mDescriptorPool(device) {}
DescriptorSetHistory()
: dynamicUboCount(0),
mResources(nullptr) {}
// bind() is not really binding the set but just stashing until we have all the info
// (pipelinelayout).
void bind(uint8_t setIndex, VulkanDescriptorSet* set,
backend::DescriptorSetOffsetArray&& offsets) {
auto& history = mHistory[set];
history.setOffsets(std::move(offsets));
DescriptorSetHistory(UniformBufferBitmask const& dynamicUbo, uint8_t uniqueDynamicUboCount,
VulkanResourceAllocator* allocator, VulkanDescriptorSet* set)
: dynamicUboMask(dynamicUbo),
dynamicUboCount(uniqueDynamicUboCount),
mResources(allocator),
mSet(set),
mBound(false) {
assert_invariant(set);
// initial state is unbound.
unbind();
}
auto lastHistory = mStashedSets[setIndex];
if (lastHistory) {
lastHistory->unbind();
~DescriptorSetHistory() {
if (mSet) {
mResources.clear();
}
mStashedSets[setIndex] = &history;
}
void commit(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout,
DescriptorSetMask const& setMask) {
DescriptorSetHistoryArray& updateSets = mStashedSets;
// setMask indicates the set of descriptor sets the driver wants to bind, curMask is the
// actual set of sets that *needs* to be bound.
DescriptorSetMask curMask = setMask;
setMask.forEachSetBit([&](size_t index) {
if (!updateSets[index] || updateSets[index]->bound()) {
curMask.unset(index);
}
});
BoundInfo nextInfo = {
pipelineLayout,
setMask,
updateSets,
};
if (curMask.none() && mLastBoundInfo == nextInfo) {
return;
}
curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) {
updateSets[index]->bind(commands, pipelineLayout, index);
});
mLastBoundInfo = nextInfo;
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
mOffsets = std::move(offsets);
mBound = false;
}
void updateBuffer(VulkanDescriptorSet* set, uint8_t binding, VulkanBufferObject* bufferObject,
VkDeviceSize offset, VkDeviceSize size) noexcept {
VkDescriptorBufferInfo const info = {
.buffer = bufferObject->buffer.getGpuBuffer(),
.offset = offset,
.range = size,
};
void write(uint8_t binding) noexcept { mBound = false; }
VkDescriptorType type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
auto& history = mHistory[set];
// Ownership will be transfered to the commandbuffer.
void bind(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout,
uint8_t index) noexcept {
VkCommandBuffer const cmdbuffer = commands->buffer();
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
1, &mSet->vkSet, dynamicUboCount, mOffsets.data());
if (history.dynamicUboMask.test(binding)) {
type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
}
VkWriteDescriptorSet const descriptorWrite = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
.dstSet = set->vkSet,
.dstBinding = binding,
.descriptorCount = 1,
.descriptorType = type,
.pBufferInfo = &info,
};
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
set->acquire(bufferObject);
history.write(binding);
commands->acquire(mSet);
mResources.clear();
mBound = true;
}
void updateSampler(VulkanDescriptorSet* set, uint8_t binding, VulkanTexture* texture,
VkSampler sampler) noexcept {
VkDescriptorImageInfo info{
.sampler = sampler,
};
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
VkImageViewType const expectedType = texture->getViewType();
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
expectedType == VK_IMAGE_VIEW_TYPE_2D) {
// If the sampler is part of a mipmapped depth texture, where one of the level *can* be
// an attachment, then the sampler for this texture has the same view properties as a
// view for an attachment. Therefore, we can use getAttachmentView to get a
// corresponding VkImageView.
info.imageView = texture->getAttachmentView(range);
} else {
info.imageView = texture->getViewForType(range, expectedType);
}
info.imageLayout = imgutil::getVkLayout(texture->getPrimaryImageLayout());
VkWriteDescriptorSet const descriptorWrite = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
.dstSet = set->vkSet,
.dstBinding = binding,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = &info,
};
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
set->acquire(texture);
mHistory[set].write(binding);
void unbind() noexcept {
mResources.acquire(mSet);
mBound = false;
}
void updateInputAttachment(VulkanDescriptorSet* set, VulkanAttachment attachment) noexcept {
// TOOD: fill-in this region
}
bool bound() const noexcept { return mBound; }
void setPlaceHolders(VkSampler sampler, VulkanTexture* texture,
VulkanBufferObject* bufferObject) noexcept {
mPlaceHolderBufferInfo = {
.buffer = bufferObject->buffer.getGpuBuffer(),
.offset = 0,
.range = 1,
};
mPlaceHolderImageInfo = {
.sampler = sampler,
.imageView = texture->getPrimaryImageView(),
.imageLayout = imgutil::getVkLayout(texture->getPrimaryImageLayout()),
};
}
void createSet(Handle<HwDescriptorSet> handle, VulkanDescriptorSetLayout* layout) {
auto const vkSet = mDescriptorPool.obtainSet(layout);
auto const& count = layout->count;
auto const vklayout = layout->getVkLayout();
VulkanDescriptorSet* set =
mResourceAllocator->construct<VulkanDescriptorSet>(handle, mResourceAllocator,
vkSet, [vkSet, count, vklayout, this](VulkanDescriptorSet* set) {
eraseSetFromHistory(set);
mDescriptorPool.recycle(count, vklayout, vkSet);
});
mHistory.emplace(
std::piecewise_construct,
std::forward_as_tuple(set),
std::forward_as_tuple(layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
mResourceAllocator, set));
}
void destroySet(Handle<HwDescriptorSet> handle) {
VulkanDescriptorSet* set = mResourceAllocator->handle_cast<VulkanDescriptorSet*>(handle);
eraseSetFromHistory(set);
}
void initVkLayout(VulkanDescriptorSetLayout* layout) {
layout->setVkLayout(mLayoutManager.getVkLayout(layout));
}
UniformBufferBitmask const dynamicUboMask;
uint8_t const dynamicUboCount;
private:
inline void eraseSetFromHistory(VulkanDescriptorSet* set) {
DescriptorSetHistory* history = &mHistory[set];
mHistory.erase(set);
FixedSizeVulkanResourceManager<1> mResources;
VulkanDescriptorSet* mSet = nullptr;
for (uint8_t i = 0; i < mStashedSets.size(); ++i) {
if (mStashedSets[i] == history) {
mStashedSets[i] = nullptr;
}
}
}
VkDevice mDevice;
VulkanResourceAllocator* mResourceAllocator;
DescriptorSetLayoutManager mLayoutManager;
DescriptorInfinitePool mDescriptorPool;
std::pair<VulkanAttachment, VkDescriptorImageInfo> mInputAttachment;
std::unordered_map<VulkanDescriptorSet*, DescriptorSetHistory> mHistory;
DescriptorSetHistoryArray mStashedSets = {};
BoundInfo mLastBoundInfo;
VkDescriptorBufferInfo mPlaceHolderBufferInfo;
VkDescriptorImageInfo mPlaceHolderImageInfo;
backend::DescriptorSetOffsetArray mOffsets;
bool mBound = false;
};
VulkanDescriptorSetManager::VulkanDescriptorSetManager(VkDevice device,
VulkanResourceAllocator* resourceAllocator)
: mImpl(new Impl(device, resourceAllocator)) {}
: mDevice(device),
mResourceAllocator(resourceAllocator),
mLayoutManager(std::make_unique<DescriptorSetLayoutManager>(device)),
mDescriptorPool(std::make_unique<DescriptorInfinitePool>(device)) {}
void VulkanDescriptorSetManager::terminate() noexcept {
assert_invariant(mImpl);
delete mImpl;
mImpl = nullptr;
}
VulkanDescriptorSetManager::~VulkanDescriptorSetManager() = default;
void VulkanDescriptorSetManager::updateBuffer(VulkanDescriptorSet* set, uint8_t binding,
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept {
mImpl->updateBuffer(set, binding, bufferObject, offset, size);
}
void VulkanDescriptorSetManager::updateSampler(VulkanDescriptorSet* set, uint8_t binding,
VulkanTexture* texture, VkSampler sampler) noexcept {
mImpl->updateSampler(set, binding, texture, sampler);
}
void VulkanDescriptorSetManager::updateInputAttachment(VulkanDescriptorSet* set,
VulkanAttachment attachment) noexcept {
mImpl->updateInputAttachment(set, attachment);
}
void VulkanDescriptorSetManager::setPlaceHolders(VkSampler sampler, VulkanTexture* texture,
VulkanBufferObject* bufferObject) noexcept {
mImpl->setPlaceHolders(sampler, texture, bufferObject);
void VulkanDescriptorSetManager::terminate() noexcept{
mLayoutManager.reset();
mDescriptorPool.reset();
mHistory.clear();
}
// bind() is not really binding the set but just stashing until we have all the info
// (pipelinelayout).
void VulkanDescriptorSetManager::bind(uint8_t setIndex, VulkanDescriptorSet* set,
backend::DescriptorSetOffsetArray&& offsets) {
return mImpl->bind(setIndex, set, std::move(offsets));
auto history = mHistory[set].get();
history->setOffsets(std::move(offsets));
auto lastHistory = mStashedSets[setIndex];
if (lastHistory) {
lastHistory->unbind();
}
mStashedSets[setIndex] = history;
}
void VulkanDescriptorSetManager::commit(VulkanCommandBuffer* commands,
VkPipelineLayout pipelineLayout, DescriptorSetMask const& setMask) {
mImpl->commit(commands, pipelineLayout, setMask);
DescriptorSetHistoryArray& updateSets = mStashedSets;
// setMask indicates the set of descriptor sets the driver wants to bind, curMask is the
// actual set of sets that *needs* to be bound.
DescriptorSetMask curMask = setMask;
setMask.forEachSetBit([&](size_t index) {
if (!updateSets[index] || updateSets[index]->bound()) {
curMask.unset(index);
}
});
BoundInfo nextInfo = {
pipelineLayout,
setMask,
updateSets,
};
if (curMask.none() && mLastBoundInfo == nextInfo) {
return;
}
curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) {
updateSets[index]->bind(commands, pipelineLayout, index);
});
mLastBoundInfo = nextInfo;
}
void VulkanDescriptorSetManager::updateBuffer(VulkanDescriptorSet* set, uint8_t binding,
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept {
VkDescriptorBufferInfo const info = {
.buffer = bufferObject->buffer.getGpuBuffer(),
.offset = offset,
.range = size,
};
VkDescriptorType type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
auto& history = mHistory[set];
if (history->dynamicUboMask.test(binding)) {
type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
}
VkWriteDescriptorSet const descriptorWrite = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
.dstSet = set->vkSet,
.dstBinding = binding,
.descriptorCount = 1,
.descriptorType = type,
.pBufferInfo = &info,
};
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
set->acquire(bufferObject);
history->write(binding);
}
void VulkanDescriptorSetManager::updateSampler(VulkanDescriptorSet* set, uint8_t binding,
VulkanTexture* texture, VkSampler sampler) noexcept {
VkDescriptorImageInfo info{
.sampler = sampler,
};
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
VkImageViewType const expectedType = texture->getViewType();
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
expectedType == VK_IMAGE_VIEW_TYPE_2D) {
// If the sampler is part of a mipmapped depth texture, where one of the level *can* be
// an attachment, then the sampler for this texture has the same view properties as a
// view for an attachment. Therefore, we can use getAttachmentView to get a
// corresponding VkImageView.
info.imageView = texture->getAttachmentView(range);
} else {
info.imageView = texture->getViewForType(range, expectedType);
}
info.imageLayout = imgutil::getVkLayout(texture->getDefaultLayout());
VkWriteDescriptorSet const descriptorWrite = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
.dstSet = set->vkSet,
.dstBinding = binding,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = &info,
};
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
set->acquire(texture);
mHistory[set]->write(binding);
}
void VulkanDescriptorSetManager::updateInputAttachment(VulkanDescriptorSet* set,
VulkanAttachment attachment) noexcept {
// TOOD: fill-in this region
}
void VulkanDescriptorSetManager::createSet(Handle<HwDescriptorSet> handle,
VulkanDescriptorSetLayout* layout) {
mImpl->createSet(handle, layout);
auto const vkSet = mDescriptorPool->obtainSet(layout);
auto const& count = layout->count;
auto const vklayout = layout->getVkLayout();
VulkanDescriptorSet* set = mResourceAllocator->construct<VulkanDescriptorSet>(handle,
mResourceAllocator, vkSet, [vkSet, count, vklayout, this](VulkanDescriptorSet* set) {
eraseSetFromHistory(set);
mDescriptorPool->recycle(count, vklayout, vkSet);
});
mHistory[set] = std::make_unique<DescriptorSetHistory>(layout->bitmask.dynamicUbo,
layout->count.dynamicUbo, mResourceAllocator, set);
}
void VulkanDescriptorSetManager::destroySet(Handle<HwDescriptorSet> handle) {
mImpl->destroySet(handle);
VulkanDescriptorSet* set = mResourceAllocator->handle_cast<VulkanDescriptorSet*>(handle);
eraseSetFromHistory(set);
}
void VulkanDescriptorSetManager::initVkLayout(VulkanDescriptorSetLayout* layout) {
mImpl->initVkLayout(layout);
layout->setVkLayout(mLayoutManager->getVkLayout(layout));
}
void VulkanDescriptorSetManager::eraseSetFromHistory(VulkanDescriptorSet* set) {
DescriptorSetHistory* history = mHistory[set].get();
mHistory.erase(set);
for (uint8_t i = 0; i < mStashedSets.size(); ++i) {
if (mStashedSets[i] == history) {
mStashedSets[i] = nullptr;
}
}
}
}// namespace filament::backend
} // namespace filament::backend
@@ -30,6 +30,8 @@
#include <bluevk/BlueVK.h>
#include <tsl/robin_map.h>
#include <memory>
namespace filament::backend {
// [GDSR]: Great-Descriptor-Set-Refactor: As of 03/20/24, the Filament frontend is planning to
@@ -45,6 +47,7 @@ public:
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator);
~VulkanDescriptorSetManager();
void terminate() noexcept;
@@ -71,8 +74,43 @@ public:
void initVkLayout(VulkanDescriptorSetLayout* layout);
private:
class Impl;
Impl* mImpl;
class DescriptorSetHistory;
class DescriptorSetLayoutManager;
class DescriptorInfinitePool;
void eraseSetFromHistory(VulkanDescriptorSet* set);
using DescriptorSetHistoryArray =
std::array<DescriptorSetHistory*, UNIQUE_DESCRIPTOR_SET_COUNT>;
struct BoundInfo {
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
DescriptorSetMask setMask;
DescriptorSetHistoryArray boundSets;
bool operator==(BoundInfo const& info) const {
if (pipelineLayout != info.pipelineLayout || setMask != info.setMask) {
return false;
}
bool equal = true;
setMask.forEachSetBit([&](size_t i) {
if (boundSets[i] != info.boundSets[i]) {
equal = false;
}
});
return equal;
}
};
VkDevice mDevice;
VulkanResourceAllocator* mResourceAllocator;
std::unique_ptr<DescriptorSetLayoutManager> mLayoutManager;
std::unique_ptr<DescriptorInfinitePool> mDescriptorPool;
std::pair<VulkanAttachment, VkDescriptorImageInfo> mInputAttachment;
std::unordered_map<VulkanDescriptorSet*, std::unique_ptr<DescriptorSetHistory>> mHistory;
DescriptorSetHistoryArray mStashedSets = {};
BoundInfo mLastBoundInfo;
};
}// namespace filament::backend
@@ -740,6 +740,20 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
context.mMultiviewEnabled = setContains(deviceExts, VK_KHR_MULTIVIEW_EXTENSION_NAME);
// Check the availability of lazily allocated memory
{
context.mLazilyAllocatedMemorySupported = false;
const uint32_t typeCount = context.mMemoryProperties.memoryTypeCount;
for(uint32_t i = 0; i < typeCount; ++i) {
const VkMemoryType type = context.mMemoryProperties.memoryTypes[i];
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
context.mLazilyAllocatedMemorySupported = true;
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
break;
}
}
}
#ifdef NDEBUG
// If we are in release build, we should not have turned on debug extensions
FILAMENT_CHECK_POSTCONDITION(!context.mDebugUtilsSupported && !context.mDebugMarkersSupported)
@@ -31,22 +31,22 @@ namespace {
std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& context,
VkDevice device, VkExtent2D extent, VkFormat format) {
bool const isDepth = isVkDepthFormat(format);
// Filament expects blit() to work with any texture, so we almost always set these usage flags.
// TODO: investigate performance implications of setting these flags.
VkImageUsageFlags const blittable
= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
VkImageCreateInfo imageInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = {extent.width, extent.height, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = blittable
| (isDepth ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
// Filament expects blit() to work with any texture, so we almost always set these usage flags
// (see copyFrame() and readPixels()).
VkImageUsageFlags const blittable =
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
VkImageCreateInfo imageInfo {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = {extent.width, extent.height, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = blittable | (isDepth ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
};
VkImage image;
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &image);
@@ -220,7 +220,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
.imageArrayLayers = 1,
.imageUsage
= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| VK_IMAGE_USAGE_TRANSFER_DST_BIT // Allows use as a blit destination.
| VK_IMAGE_USAGE_TRANSFER_DST_BIT // Allows use as a blit destination (for copyFrame)
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT,// Allows use as a blit source (for readPixels)
// TODO: Setting the preTransform to IDENTITY means we are letting the Android
@@ -239,7 +239,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
};
VkResult result = vkCreateSwapchainKHR(mDevice, &createInfo, VKALLOC, &mSwapchain);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkGetPhysicalDeviceSurfaceFormatsKHR error: " << static_cast<int32_t>(result);
<< "vkCreateSwapchainKHR error: " << static_cast<int32_t>(result);
mSwapChainBundle.colors = enumerate(vkGetSwapchainImagesKHR, mDevice, mSwapchain);
mSwapChainBundle.colorFormat = surfaceFormat.format;
+2 -2
View File
@@ -35,7 +35,7 @@ TEST_F(BackendTest, FrameScheduledCallback) {
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountA](PresentCallable callable) {
callable();
callbackCountA++;
});
}, 0);
// Render the first frame.
api.makeCurrent(swapChain, swapChain);
@@ -58,7 +58,7 @@ TEST_F(BackendTest, FrameScheduledCallback) {
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountB](PresentCallable callable) {
callable();
callbackCountB++;
});
}, 0);
// Render one final frame.
api.makeCurrent(swapChain, swapChain);
+115
View File
@@ -0,0 +1,115 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gtest/gtest.h>
#include <utils/Allocator.h>
#include <utils/compiler.h>
#include <private/backend/HandleAllocator.h>
#include "utils/Panic.h"
using namespace filament::backend;
// FIXME: consider making this constant non-private so we can use it in tests.
static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u;
static constexpr size_t POOL_SIZE_BYTES = 8 * 1024U * 1024U;
// NOTE: actual count may be lower due to alignment requirements
constexpr size_t const POOL_HANDLE_COUNT = POOL_SIZE_BYTES / (32 + 96 + 136); // 31775
// This must match HandleAllocatorGL, so its implementation is present on all platforms.
#define HandleAllocatorTest HandleAllocator<32, 96, 136> // ~4520 / pool / MiB
struct MyHandle {
};
struct Concrete : public MyHandle {
uint8_t data[32];
};
#if GTEST_HAS_EXCEPTIONS
#define EXPECT_THROW_IF_ENABLED EXPECT_THROW
#else
#define EXPECT_THROW_IF_ENABLED(args...)
#endif
TEST(HandlesTest, useAfterFreePool) {
HandleAllocatorTest allocator("Test Handles", POOL_SIZE_BYTES, false);
Handle<MyHandle> handleA = allocator.allocate<Concrete>();
allocator.deallocate(handleA);
Handle<MyHandle> handleB = allocator.allocate<Concrete>();
EXPECT_THROW_IF_ENABLED({
Concrete* actualHandleA = allocator.handle_cast<Concrete*>(handleA);
}, utils::PostconditionPanic);
}
TEST(HandlesTest, useAfterFreeHeap) {
HandleAllocatorTest allocator("Test Handles", POOL_SIZE_BYTES, false);
// Use up all the non-heap handles.
for (int i = 0; i < POOL_HANDLE_COUNT; i++) {
Handle<MyHandle> handle = allocator.allocate<Concrete>();
}
// This one is guaranteed to be a heap handle.
Handle<MyHandle> handleA = allocator.allocate<Concrete>();
EXPECT_TRUE(handleA.getId() & HANDLE_HEAP_FLAG);
allocator.deallocate(handleA);
Handle<MyHandle> handleB = allocator.allocate<Concrete>();
// This should assert:
EXPECT_THROW_IF_ENABLED({
Concrete* actualHandleA = allocator.handle_cast<Concrete*>(handleA);
}, utils::PostconditionPanic);
// simulate a "corrupted" handle
Handle<MyHandle> corruptedHandle { HANDLE_HEAP_FLAG | 0x123456 };
EXPECT_THROW_IF_ENABLED({
Concrete* actualHandleA = allocator.handle_cast<Concrete*>(corruptedHandle);
}, utils::PostconditionPanic);
}
TEST(HandlesTest, isValid) {
HandleAllocatorTest allocator("Test Handles", POOL_SIZE_BYTES, false);
Handle<MyHandle> poolHandle = allocator.allocate<Concrete>();
EXPECT_TRUE((poolHandle.getId() & HANDLE_HEAP_FLAG) == 0u);
// Use up all the non-heap handles.
for (int i = 0; i < POOL_HANDLE_COUNT; i++) {
Handle<MyHandle> handle = allocator.allocate<Concrete>();
}
// This one is guaranteed to be a heap handle.
Handle<MyHandle> heapHandle = allocator.allocate<Concrete>();
EXPECT_TRUE(heapHandle.getId() & HANDLE_HEAP_FLAG);
EXPECT_TRUE(allocator.is_valid(poolHandle));
EXPECT_TRUE(allocator.is_valid(heapHandle));
{
// null handles are invalid
Handle<MyHandle> handle;
EXPECT_FALSE(allocator.is_valid(handle));
}
{
Handle<MyHandle> handle { HANDLE_HEAP_FLAG | 0x123456 };
EXPECT_FALSE(allocator.is_valid(handle));
}
}
+7 -11
View File
@@ -288,16 +288,6 @@ public:
*/
uint32_t jobSystemThreadCount = 0;
/*
* Number of most-recently destroyed textures to track for use-after-free.
*
* This will cause the backend to throw an exception when a texture is freed but still bound
* to a SamplerGroup and used in a draw call. 0 disables completely.
*
* Currently only respected by the Metal backend.
*/
size_t textureUseAfterFreePoolSize = 0;
/**
* When uploading vertex or index data, the Filament Metal backend copies data
* into a shared staging area before transferring it to the GPU. This setting controls
@@ -390,8 +380,14 @@ public:
* it's a GLES2 context. Ignored on other backends.
*/
bool forceGLES2Context = false;
};
/**
* Assert the native window associated to a SwapChain is valid when calling makeCurrent().
* This is only supported for:
* - PlatformEGLAndroid
*/
bool assertNativeWindowIsValid = false;
};
#if UTILS_HAS_THREADING
using CreateCallback = void(void* UTILS_NULLABLE user, void* UTILS_NONNULL token);
+4 -1
View File
@@ -508,7 +508,7 @@ public:
*
* Framebuffer as seen on User buffer (PixelBufferDescriptor&)
* screen
*
*
* +--------------------+
* | | .stride .alignment
* | | ----------------------->-->
@@ -538,6 +538,9 @@ public:
* uploaded to it via setImage, the data returned from readPixels will be y-flipped with respect
* to the setImage call.
*
* Note: the texture that backs the COLOR attachment for `renderTarget` must have
* TextureUsage::BLIT_SRC as part of its usage.
*
* @remark
* readPixels() is intended for debugging and testing. It will impact performance significantly.
*
+14 -1
View File
@@ -254,6 +254,19 @@ public:
void* UTILS_NULLABLE getNativeWindow() const noexcept;
/**
* If this flag is passed to setFrameScheduledCallback, then the behavior of the default
* CallbackHandler (when nullptr is passed as the handler argument) is altered to call the
* callback on the Metal completion handler thread (as opposed to the main Filament thread).
* This flag also instructs the Metal backend to release the associated CAMetalDrawable on the
* completion handler thread.
*
* This flag has no effect if a custom CallbackHandler is passed.
*
* @see setFrameScheduledCallback
*/
static constexpr uint64_t CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER = 1;
/**
* FrameScheduledCallback is a callback function that notifies an application when Filament has
* completed processing a frame and that frame is ready to be scheduled for presentation.
@@ -298,7 +311,7 @@ public:
* @see PresentCallable
*/
void setFrameScheduledCallback(backend::CallbackHandler* UTILS_NULLABLE handler = nullptr,
FrameScheduledCallback&& callback = {});
FrameScheduledCallback&& callback = {}, uint64_t flags = 0);
/**
* Returns whether or not this SwapChain currently has a FrameScheduledCallback set.
+2 -1
View File
@@ -643,7 +643,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::ssr(FrameGraph& fg,
auto out = resources.getRenderPassInfo();
// Remove the HAS_SHADOWING RenderFlags, since it's irrelevant when rendering reflections
passBuilder.renderFlags(~RenderPass::HAS_SHADOWING, 0);
passBuilder.renderFlags(RenderPass::HAS_SHADOWING, 0);
// use our special SSR variant, it can only be applied to object that have
// the SCREEN_SPACE ReflectionMode.
@@ -3410,6 +3410,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
float(vp.width) / inputDesc.width,
float(vp.height) / inputDesc.height
});
mi->commit(driver);
mi->use(driver);
auto pipeline = material.getPipelineState(mEngine);
+11 -5
View File
@@ -655,12 +655,18 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
bool const hasSkinning = soaVisibility[i].skinning;
bool const hasSkinningOrMorphing = hasSkinning || hasMorphing;
// if we are already an SSR variant, the SRE bit is already set,
// there is no harm setting it again
// if we are already an SSR variant, the SRE bit is already set
static_assert(Variant::SPECIAL_SSR & Variant::SRE);
Variant renderableVariant = variant;
renderableVariant.setShadowReceiver(
Variant::isSSRVariant(variant) || (soaVisibility[i].receiveShadows & hasShadowing));
Variant renderableVariant{ variant };
// we can't have SSR and shadowing together by construction
bool const isSsrVariant = Variant::isSSRVariant(variant);
assert_invariant((isSsrVariant && !hasShadowing) || !isSsrVariant);
if (!isSsrVariant) {
// set the SRE variant, unless we're in SSR mode
renderableVariant.setShadowReceiver(soaVisibility[i].receiveShadows && hasShadowing);
}
renderableVariant.setSkinning(hasSkinningOrMorphing);
FRenderableManager::SkinningBindingInfo const& skinning = soaSkinning[i];
+48 -34
View File
@@ -261,10 +261,15 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
auto& passList = data.passList;
// Directional, cascaded shadow maps
auto const directionalShadowCastersRange = view.getVisibleDirectionalShadowCasters();
if (!directionalShadowCastersRange.empty()) {
// if the view has shadowing enabled, the SRE variant could be used, so we must
// generate the shadow maps
if (view.needsDirectionalShadowMaps()) {
auto const directionalShadowCastersRange = view.getVisibleDirectionalShadowCasters();
for (auto& shadowMap : getCascadedShadowMap()) {
// for the directional light, we already know if it has visible shadows.
// For the directional light, we already know if it has visible shadows.
// if hasVisibleShadows() is false, we're guaranteed the shader won't
// sample this shadow map (see PerViewUib::cascades and
// ShadowMapManager::updateCascadeShadowMaps)
if (shadowMap.hasVisibleShadows()) {
passList.push_back({
{}, &shadowMap, directionalShadowCastersRange,
@@ -274,8 +279,10 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
}
// Point lights and Spotlight shadow maps
auto const spotShadowCastersRange = view.getVisibleSpotShadowCasters();
if (!spotShadowCastersRange.empty()) {
// if the view has shadowing enabled, the SRE variant could be used, so we must
// generate the shadow maps
if (view.needsPointShadowMaps()) {
auto const spotShadowCastersRange = view.getVisibleSpotShadowCasters();
for (auto& shadowMap : getSpotShadowMaps()) {
assert_invariant(!shadowMap.isDirectionalShadow());
@@ -293,11 +300,11 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
break;
}
if (shadowMap.hasVisibleShadows()) {
passList.push_back({
{}, &shadowMap, spotShadowCastersRange,
VISIBLE_DYN_SHADOW_RENDERABLE });
}
// For spot/point lights, shadowMap.hasVisibleShadows() doesn't guarantee
// the shader won't access the shadow map, so we must generate it.
passList.push_back({
{}, &shadowMap, spotShadowCastersRange,
VISIBLE_DYN_SHADOW_RENDERABLE });
}
}
@@ -311,7 +318,6 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
scene, mainCameraInfo, userTime, passBuilder = passBuilder](
FrameGraphResources const&, auto const& data, DriverApi& driver) mutable {
// Note: we could almost parallel_for the loop below, the problem currently is
// that updatePrimitivesLod() updates temporary global state.
// prepareSpotShadowMap() also update the visibility of renderable. These two
@@ -321,7 +327,6 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
// Generate a RenderPass for each shadow map
for (auto const& entry : data.passList) {
ShadowMap const& shadowMap = *entry.shadowMap;
assert_invariant(shadowMap.hasVisibleShadows());
// Note: this loop can generate a lot of commands that come out of the
// "per frame command arena". The allocation persists until the
@@ -339,14 +344,18 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
// we should never be here
break;
case ShadowType::SPOT:
ShadowMapManager::cullSpotShadowMap(shadowMap, engine, view,
scene->getRenderableData(), entry.range,
scene->getLightData());
if (shadowMap.hasVisibleShadows()) {
ShadowMapManager::cullSpotShadowMap(shadowMap, engine, view,
scene->getRenderableData(), entry.range,
scene->getLightData());
}
break;
case ShadowType::POINT:
ShadowMapManager::cullPointShadowMap(shadowMap, view,
scene->getRenderableData(), entry.range,
scene->getLightData());
if (shadowMap.hasVisibleShadows()) {
ShadowMapManager::cullPointShadowMap(shadowMap, view,
scene->getRenderableData(), entry.range,
scene->getLightData());
}
break;
}
@@ -420,22 +429,21 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
auto const& passList = prepareShadowPass.getData().passList;
for (auto const& entry: passList) {
assert_invariant(entry.shadowMap->hasVisibleShadows());
const uint8_t layer = entry.shadowMap->getLayer();
const auto* options = entry.shadowMap->getShadowOptions();
const auto msaaSamples = textureRequirements.msaaSamples;
const bool blur = entry.shadowMap->hasVisibleShadows() &&
view.hasVSM() && options->vsm.blurWidth > 0.0f;
auto& shadowPass = fg.addPass<ShadowPassData>("Shadow Pass",
[&](FrameGraph::Builder& builder, auto& data) {
const bool blur = view.hasVSM() && options->vsm.blurWidth > 0.0f;
FrameGraphRenderPass::Descriptor renderTargetDesc{};
data.output = builder.createSubresource(prepareShadowPass->shadows,
"Shadowmap Layer", { .layer = layer });
if (view.hasVSM()) {
if (UTILS_UNLIKELY(view.hasVSM())) {
// Each shadow pass has its own sample count, but textures are created with
// a default count of 1 because we're using "magic resolve" (sample count is
// set on the render target).
@@ -447,13 +455,6 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
.format = TextureFormat::DEPTH16,
});
// Temporary (resolved) texture used to render the shadowmap when blurring
// is needed -- it'll be used as the source of the blur.
data.tempBlurSrc = builder.createTexture("Temporary Shadowmap", {
.width = textureRequirements.size, .height = textureRequirements.size,
.format = textureRequirements.format
});
depth = builder.write(depth,
FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
@@ -468,7 +469,15 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
renderTargetDesc.clearColor = vsmClearColor;
renderTargetDesc.samples = msaaSamples;
if (blur) {
if (UTILS_UNLIKELY(blur)) {
// Temporary (resolved) texture used to render the shadowmap when blurring
// is needed -- it'll be used as the source of the blur.
data.tempBlurSrc = builder.createTexture("Temporary Shadowmap", {
.width = textureRequirements.size,
.height = textureRequirements.size,
.format = textureRequirements.format
});
data.tempBlurSrc = builder.write(data.tempBlurSrc,
FrameGraphTexture::Usage::COLOR_ATTACHMENT);
@@ -510,15 +519,20 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
auto rt = resources.getRenderPassInfo(data.rt);
driver.beginRenderPass(rt.target, rt.params);
entry.shadowMap->bind(driver);
entry.executor.overrideScissor(entry.shadowMap->getScissor());
entry.executor.execute(engine, "Shadow Pass");
// if we know there are no visible shadows, we can skip rendering, but
// we need the render-pass to clear/initialize the shadow-map
// Note: this is always true for directional/cascade shadows.
if (entry.shadowMap->hasVisibleShadows()) {
entry.shadowMap->bind(driver);
entry.executor.overrideScissor(entry.shadowMap->getScissor());
entry.executor.execute(engine, "Shadow Pass");
}
driver.endRenderPass();
});
// now emit the blurring passes if needed
if (view.hasVSM()) {
if (UTILS_UNLIKELY(blur)) {
auto& ppm = engine.getPostProcessManager();
const float blurWidth = options->vsm.blurWidth;
+2 -2
View File
@@ -29,8 +29,8 @@ void* SwapChain::getNativeWindow() const noexcept {
}
void SwapChain::setFrameScheduledCallback(
backend::CallbackHandler* handler, FrameScheduledCallback&& callback) {
downcast(this)->setFrameScheduledCallback(handler, std::move(callback));
backend::CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) {
downcast(this)->setFrameScheduledCallback(handler, std::move(callback), flags);
}
bool SwapChain::isFrameScheduledCallbackSet() const noexcept {
+3 -3
View File
@@ -103,12 +103,12 @@ Engine* FEngine::create(Engine::Builder const& builder) {
}
DriverConfig const driverConfig{
.handleArenaSize = instance->getRequestedDriverHandleArenaSize(),
.textureUseAfterFreePoolSize = instance->getConfig().textureUseAfterFreePoolSize,
.metalUploadBufferSizeBytes = instance->getConfig().metalUploadBufferSizeBytes,
.disableParallelShaderCompile = instance->getConfig().disableParallelShaderCompile,
.disableHandleUseAfterFreeCheck = instance->getConfig().disableHandleUseAfterFreeCheck,
.forceGLES2Context = instance->getConfig().forceGLES2Context,
.stereoscopicType = instance->getConfig().stereoscopicType,
.stereoscopicType = instance->getConfig().stereoscopicType,
.assertNativeWindowIsValid = instance->getConfig().assertNativeWindowIsValid,
};
instance->mDriver = platform->createDriver(sharedContext, driverConfig);
@@ -701,12 +701,12 @@ int FEngine::loop() {
DriverConfig const driverConfig {
.handleArenaSize = getRequestedDriverHandleArenaSize(),
.textureUseAfterFreePoolSize = mConfig.textureUseAfterFreePoolSize,
.metalUploadBufferSizeBytes = mConfig.metalUploadBufferSizeBytes,
.disableParallelShaderCompile = mConfig.disableParallelShaderCompile,
.disableHandleUseAfterFreeCheck = mConfig.disableHandleUseAfterFreeCheck,
.forceGLES2Context = mConfig.forceGLES2Context,
.stereoscopicType = mConfig.stereoscopicType,
.assertNativeWindowIsValid = mConfig.assertNativeWindowIsValid,
};
mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig);
+12 -2
View File
@@ -156,8 +156,8 @@ RenderTarget* RenderTarget::Builder::build(Engine& engine) {
// ------------------------------------------------------------------------------------------------
FRenderTarget::FRenderTarget(FEngine& engine, const RenderTarget::Builder& builder)
: mSupportedColorAttachmentsCount(engine.getDriverApi().getMaxDrawBuffers()) {
: mSupportedColorAttachmentsCount(engine.getDriverApi().getMaxDrawBuffers()),
mSupportsReadPixels(false) {
std::copy(std::begin(builder.mImpl->mAttachments), std::end(builder.mImpl->mAttachments),
std::begin(mAttachments));
@@ -189,6 +189,16 @@ FRenderTarget::FRenderTarget(FEngine& engine, const RenderTarget::Builder& build
(TextureUsage::SAMPLEABLE | Texture::Usage::SUBPASS_INPUT))) {
mSampleableAttachmentsMask |= targetBufferBit;
}
// readPixels() only applies to the color attachment that binds at index 0.
if (i == 0 && any(attachment.texture->getUsage() & TextureUsage::COLOR_ATTACHMENT)) {
// TODO: the following will be changed to
// mSupportsReadPixels =
// any(attachment.texture->getUsage() & TextureUsage::BLIT_SRC);
// in a later filament version when clients have properly added the right usage.
mSupportsReadPixels = attachment.texture->hasBlitSrcUsage();
}
}
}
+5
View File
@@ -68,6 +68,10 @@ public:
bool hasSampleableDepth() const noexcept;
bool supportsReadPixels() const noexcept {
return mSupportsReadPixels;
}
private:
friend class RenderTarget;
static constexpr size_t ATTACHMENT_COUNT = MAX_SUPPORTED_COLOR_ATTACHMENTS_COUNT + 1u;
@@ -76,6 +80,7 @@ private:
backend::TargetBufferFlags mAttachmentMask = {};
backend::TargetBufferFlags mSampleableAttachmentsMask = {};
const uint8_t mSupportedColorAttachmentsCount;
bool mSupportsReadPixels = false;
};
FILAMENT_DOWNCAST(RenderTarget)
+18 -6
View File
@@ -455,6 +455,15 @@ void FRenderer::readPixels(uint32_t xoffset, uint32_t yoffset, uint32_t width, u
void FRenderer::readPixels(FRenderTarget* renderTarget,
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
backend::PixelBufferDescriptor&& buffer) {
// TODO: change the following to an assert when client call sites have addressed the issue.
if (!renderTarget->supportsReadPixels()) {
utils::slog.w << "readPixels() must be called with a renderTarget with COLOR0 created with "
"TextureUsage::BLIT_SRC. This precondition will be asserted in a later "
"release of Filament."
<< utils::io::endl;
}
RendererUtils::readPixels(mEngine.getDriverApi(), renderTarget->getHwHandle(),
xoffset, yoffset, width, height, std::move(buffer));
}
@@ -802,7 +811,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
passBuilder.renderFlags(renderFlags);
Variant variant;
variant.setDirectionalLighting(view.hasDirectionalLight());
variant.setDirectionalLighting(view.hasDirectionalLighting());
variant.setDynamicLighting(view.hasDynamicLighting());
variant.setFog(view.hasFog());
variant.setVsm(view.hasShadowing() && view.getShadowType() != ShadowType::PCF);
@@ -963,15 +972,18 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
});
const auto picking = picking_;
if (view.hasPicking()) {
struct PickingResolvePassData {
FrameGraphId<FrameGraphTexture> picking;
};
fg.addPass<PickingResolvePassData>("Picking Resolve Pass",
fg.addPass<PickingResolvePassData>(
"Picking Resolve Pass",
[&](FrameGraph::Builder& builder, auto& data) {
data.picking = builder.read(picking,
FrameGraphTexture::Usage::COLOR_ATTACHMENT);
// Note that BLIT_SRC is needed because this texture will be read later (via
// readPixels()).
data.picking =
builder.read(picking, FrameGraphTexture::Usage::COLOR_ATTACHMENT |
FrameGraphTexture::Usage::BLIT_SRC);
builder.declareRenderPass("Picking Resolve Target", {
.attachments = { .color = { data.picking }}
});
@@ -1197,7 +1209,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
// Debug: CSM visualisation
if (UTILS_UNLIKELY(engine.debug.shadowmap.visualize_cascades &&
view.hasShadowing() && view.hasDirectionalLight())) {
view.hasShadowing() && view.hasDirectionalLighting())) {
input = ppm.debugShadowCascades(fg, input, depth);
}
+3 -2
View File
@@ -70,9 +70,10 @@ void FSwapChain::terminate(FEngine& engine) noexcept {
}
void FSwapChain::setFrameScheduledCallback(
backend::CallbackHandler* handler, FrameScheduledCallback&& callback) {
backend::CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) {
mFrameScheduledCallbackIsSet = bool(callback);
mEngine.getDriverApi().setFrameScheduledCallback(mHwSwapChain, handler, std::move(callback));
mEngine.getDriverApi().setFrameScheduledCallback(
mHwSwapChain, handler, std::move(callback), flags);
}
bool FSwapChain::isFrameScheduledCallbackSet() const noexcept {
+1 -1
View File
@@ -79,7 +79,7 @@ public:
}
void setFrameScheduledCallback(
backend::CallbackHandler* handler, FrameScheduledCallback&& callback);
backend::CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags);
bool isFrameScheduledCallbackSet() const noexcept;
+11
View File
@@ -79,6 +79,7 @@ struct Texture::BuilderDetails {
Sampler mTarget = Sampler::SAMPLER_2D;
InternalFormat mFormat = InternalFormat::RGBA8;
Usage mUsage = Usage::NONE;
bool mHasBlitSrc = false;
bool mTextureIsSwizzled = false;
std::array<Swizzle, 4> mSwizzle = {
Swizzle::CHANNEL_0, Swizzle::CHANNEL_1,
@@ -183,6 +184,15 @@ Texture* Texture::Builder::build(Engine& engine) {
}
}
// TODO: remove in a future filament release.
// Clients might not have known that textures that are read need to have BLIT_SRC as usages. For
// now, we workaround the issue by making sure any color attachment can be the source of a copy
// for readPixels().
mImpl->mHasBlitSrc = any(mImpl->mUsage & TextureUsage::BLIT_SRC);
if (!mImpl->mHasBlitSrc && any(mImpl->mUsage & TextureUsage::COLOR_ATTACHMENT)) {
mImpl->mUsage |= TextureUsage::BLIT_SRC;
}
const bool sampleable = bool(mImpl->mUsage & TextureUsage::SAMPLEABLE);
const bool swizzled = mImpl->mTextureIsSwizzled;
const bool imported = mImpl->mImportedId;
@@ -232,6 +242,7 @@ FTexture::FTexture(FEngine& engine, const Builder& builder) {
mLevelCount = builder->mLevels;
mSwizzle = builder->mSwizzle;
mTextureIsSwizzled = builder->mTextureIsSwizzled;
mHasBlitSrc = builder->mHasBlitSrc;
bool const isImported = builder->mImportedId != 0;
if (mTarget == SamplerType::SAMPLER_EXTERNAL && !isImported) {
+12 -1
View File
@@ -123,6 +123,11 @@ public:
bool textureHandleCanMutate() const noexcept;
void updateLodRange(uint8_t level) noexcept;
// TODO: remove in a future filament release. See below for description.
inline bool hasBlitSrcUsage() const noexcept {
return mHasBlitSrc;
}
private:
friend class Texture;
struct LodRange {
@@ -163,7 +168,13 @@ private:
bool mTextureIsSwizzled;
Usage mUsage = Usage::DEFAULT;
// there is 7 bytes of padding here
// TODO: remove in a future filament release.
// Indicates whether the user has set the TextureUsage::BLIT_SRC usage. This will be used to
// temporarily validate whether this texture can be used for readPixels.
bool mHasBlitSrc = false;
// there is 5 bytes of padding here
FStream* mStream = nullptr; // only needed for streaming textures
};
+1 -1
View File
@@ -584,7 +584,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren
// we also know if we have a directional light
FLightManager::Instance const directionalLight =
lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
mHasDirectionalLight = directionalLight.isValid();
mHasDirectionalLighting = directionalLight.isValid();
// As soon as prepareVisibleLight finishes, we can kick-off the froxelization
if (hasDynamicLighting()) {
+10 -2
View File
@@ -175,9 +175,17 @@ public:
utils::JobSystem::Job* getFroxelizerSync() const noexcept { return mFroxelizerSync; }
void setFroxelizerSync(utils::JobSystem::Job* sync) noexcept { mFroxelizerSync = sync; }
bool hasDirectionalLight() const noexcept { return mHasDirectionalLight; }
// ultimately decides to use the DIR variant
bool hasDirectionalLighting() const noexcept { return mHasDirectionalLighting; }
// ultimately decides to use the DYN variant
bool hasDynamicLighting() const noexcept { return mHasDynamicLighting; }
// ultimately decides to use the SRE variant
bool hasShadowing() const noexcept { return mHasShadowing; }
bool needsDirectionalShadowMaps() const noexcept { return mHasShadowing && mHasDirectionalLighting; }
bool needsPointShadowMaps() const noexcept { return mHasShadowing && mHasDynamicLighting; }
bool needsShadowMap() const noexcept { return mNeedsShadowMap; }
bool hasFog() const noexcept { return mFogOptions.enabled && mFogOptions.density > 0.0f; }
bool hasVSM() const noexcept { return mShadowType == ShadowType::VSM; }
@@ -573,7 +581,7 @@ private:
Range mVisibleDirectionalShadowCasters;
Range mSpotLightShadowCasters;
uint32_t mRenderableUBOSize = 0;
mutable bool mHasDirectionalLight = false;
mutable bool mHasDirectionalLighting = false;
mutable bool mHasDynamicLighting = false;
mutable bool mHasShadowing = false;
mutable bool mNeedsShadowMap = false;
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.55.0"
spec.version = "1.55.1"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.55.0/filament-v1.55.0-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.55.1/filament-v1.55.1-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
+2
View File
@@ -36,6 +36,8 @@ elseif (APPLE AND NOT IOS)
elseif(LINUX)
if(FILAMENT_SUPPORTS_EGL_ON_LINUX)
set(SRCS ${SRCS} src/BlueGLLinuxEGL.cpp)
elseif(FILAMENT_SUPPORTS_OSMESA)
set(SRCS ${SRCS} src/BlueGLLinuxOSMesa.cpp)
else()
set(SRCS ${SRCS} src/BlueGLLinux.cpp)
endif()
+59
View File
@@ -0,0 +1,59 @@
/*
* Copyright (C) 2024 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 <GL/gl.h>
#include <dlfcn.h>
#include <string.h>
namespace bluegl {
namespace {
using ProcAddressFunc = void*(*)(char const* funcName);
}
struct Driver {
ProcAddressFunc OSMesaGetProcAddress;
void* library;
} g_driver = {nullptr, nullptr};
bool initBinder() {
constexpr char const* libraryNames[] = {"libOSMesa.so", "libosmesa.so"};
for (char const* name : libraryNames) {
g_driver.library = dlopen(name, RTLD_GLOBAL | RTLD_NOW);
if (g_driver.library) {
break;
}
}
if (!g_driver.library) {
return false;
}
g_driver.OSMesaGetProcAddress = (ProcAddressFunc)
dlsym(g_driver.library, "OSMesaGetProcAddress");
return g_driver.OSMesaGetProcAddress;
}
void* loadFunction(const char* name) {
return (void*) g_driver.OSMesaGetProcAddress(name);
}
void shutdownBinder() {
dlclose(g_driver.library);
memset(&g_driver, 0, sizeof(g_driver));
}
} // namespace bluegl
@@ -226,6 +226,8 @@ public:
void jumpToBookmark(const Bookmark& bookmark) override {
Base::mEye = bookmark.flight.position;
mTargetEuler.x = bookmark.flight.pitch;
mTargetEuler.y = bookmark.flight.yaw;
updateTarget(bookmark.flight.pitch, bookmark.flight.yaw);
}
+17
View File
@@ -0,0 +1,17 @@
# Rendering Difference Test
We created a few scripts to run `gltf_viewer` and produce headless renderings.
This is mainly useful for continuous integration where GPUs are generally not available on cloud
machines. To perform software rasterization, these scripts are centered around [Mesa]'s software
rasterizers, but nothing bars us from using another rasterizer like [SwiftShader]. Additionally,
we should be able to use GPUs where available (though this is more of a future work).
The script `run.py` contains the core logic for taking input parameters (such as the test
description file) and then running gltf_viewer to produce the results.
In the `test` directory is a list of test descriptions that are specified in json. Please see
`sample.json` to parse the structure.
[Mesa]: https://docs.mesa3d.org
[SwiftShader]: https://github.com/google/swiftshader
+142
View File
@@ -0,0 +1,142 @@
# Copyright (C) 2024 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.
from utils import execute, ArgParseImpl
import glob
from itertools import chain
import json
import sys
import os
from os import path
def _is_list_of_strings(field):
return isinstance(field, list) and\
all(isinstance(item, str) for item in field)
def _is_string(s):
return isinstance(s, str)
def _is_dict(s):
return isinstance(s, dict)
class RenderingConfig():
def __init__(self, data):
assert 'name' in data
assert _is_string(data['name'])
self.name = data['name']
assert 'rendering' in data
assert _is_dict(data['rendering'])
self.rendering = data['rendering']
class PresetConfig(RenderingConfig):
def __init__(self, data, existing_models):
RenderingConfig.__init__(self, data)
models = data.get('models')
if models:
assert _is_list_of_strings(models)
assert all(m in existing_models for m in models)
self.models = models
class TestConfig(RenderingConfig):
def __init__(self, data, existing_models, presets):
RenderingConfig.__init__(self, data)
description = data.get('description')
if description:
assert _is_string(description)
self.description = description
apply_presets = data.get('apply_presets')
rendering = {}
preset_models = []
if apply_presets:
given_presets = {p.name: p for p in presets}
assert all((name in given_presets) for name in apply_presets)
for preset in apply_presets:
rendering.update(given_presets[preset].rendering)
preset_models += given_presets[preset].models
assert 'rendering' in data
rendering.update(data['rendering'])
self.rendering = rendering
models = data.get('models')
self.models = preset_models
if models:
assert _is_list_of_strings(models)
assert all(m in existing_models for m in models)
self.models = set(models + self.models)
def to_filament_format(self):
json_out = {
'name': self.name,
'base': self.rendering
}
return json.dumps(json_out)
class RenderTestConfig():
def __init__(self, data):
assert 'name' in data
name = data['name']
assert _is_string(name)
self.name = name
assert 'backends' in data
backends = data['backends']
assert _is_list_of_strings(backends)
self.backends = backends
assert 'model_search_paths' in data
model_search_paths = data.get('model_search_paths')
assert _is_list_of_strings(model_search_paths)
assert all(path.isdir(p) for p in model_search_paths)
model_paths = list(
chain(*(glob.glob(f'{d}/**/*.glb', recursive=True) for d in model_search_paths)))
# This flatten the output for glob.glob
self.models = {path.splitext(path.basename(model))[0]: model for model in model_paths}
preset_data = data.get('presets')
presets = []
if preset_data:
presets = [PresetConfig(p, self.models) for p in preset_data]
assert 'tests' in data
self.tests = [TestConfig(t, self.models, presets) for t in data['tests']]
test_names = list([t.name for t in self.tests])
# We cannot have duplicate test names
assert len(test_names) == len(set(test_names))
def _remove_comments_from_json_txt(json_txt):
res = []
for line in json_txt.split('\n'):
if '//' in line:
line = line.split('//')[0]
res.append(line)
return '\n'.join(res)
def parse_test_config_from_path(config_path):
with open(config_path, 'r') as f:
json_txt = json.loads(_remove_comments_from_json_txt(f.read()))
return RenderTestConfig(json_txt)
if __name__ == "__main__":
parser = ArgParseImpl()
parser.add_argument('--test', help='Configuration of the test', required=True)
args, _ = parser.parse_known_args(sys.argv[1:])
test = parse_test_config_from_path(args.test)
+54
View File
@@ -0,0 +1,54 @@
# Copyright (C) 2024 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.
from utils import execute, ArgParseImpl
from parse_test_json import parse_test_config_from_path
import sys
import os
def run_test(gltf_viewer, pixel_test, output_dir, opengl_lib=None, vk_icd=None):
assert os.path.isdir(output_dir)
assert os.access(gltf_viewer, os.X_OK)
for test in pixel_test.tests:
test_json_path = f'{output_dir}/{test.name}_simplified.json'
with open(test_json_path, 'w') as f:
f.write(f'[{test.to_filament_format()}]')
for backend in pixel_test.backends:
env = None
if backend == 'opengl' and opengl_lib and os.path.isdir(opengl_lib):
env = {'LD_LIBRARY_PATH': opengl_lib}
for model in test.models:
model_path = pixel_test.models[model]
out_name = f'{test.name}_{model}_{backend}'
execute(f'{gltf_viewer} -a {backend} --batch={test_json_path} -e {model_path} --headless',
env=env, capture_output=False)
execute(f'mv -f {test.name}0.ppm {output_dir}/{out_name}.ppm', capture_output=False)
execute(f'mv -f {test.name}0.json {output_dir}/{test.name}.json', capture_output=False)
if __name__ == "__main__":
parser = ArgParseImpl()
parser.add_argument('--test', help='Configuration of the test', required=True)
parser.add_argument('--gltf_viewer', help='Path to the gltf_viewer', required=True)
parser.add_argument('--output_dir', help='Output Directory', required=True)
parser.add_argument('--opengl_lib', help='Path to the folder containing OpenGL driver lib (for LD_LIBRARY_PATH)')
parser.add_argument('--vk_icd', help='Path to VK ICD file')
args, _ = parser.parse_known_args(sys.argv[1:])
test = parse_test_config_from_path(args.test)
run_test(args.gltf_viewer, test, args.output_dir, opengl_lib=args.opengl_lib, vk_icd=args.vk_icd)
+34
View File
@@ -0,0 +1,34 @@
{
"name": "PresubmitPixelTests",
"backends": ["opengl"],
"model_search_paths": ["third_party/models"],
"presets": [
{
"name": "Standard",
"models": ["lucy", "DamagedHelmet"],
"rendering": {
"viewer.cameraFocusDistance": 0,
"view.postProcessingEnabled": true
}
}
],
"tests": [
{
"name": "BloomFlare",
"description": "Testing bloom and flare",
"apply_presets": ["Standard"],
"rendering": {
"view.bloom.enabled": true,
"view.bloom.lensFlare": true
}
},
{
"name": "MSAA",
"description": "Testing Multi-sample Anti-aliasing",
"apply_presets": ["Standard"],
"rendering": {
"view.msaa.enabled": true
}
}
]
}
+34
View File
@@ -0,0 +1,34 @@
{
"name": "SampleTest" , // [required]
"backends": ["opengl"], // [required] Specifies the backend that will be used to render
// this test
"model_search_paths": ["third_party/models"], // [optional] Base iirectory from which we will glob for
// .glb files and try to match against names in the 'models'
// field.
"presets": [ // [optional] A list of preset configurations that tests can
// inherit from.
{
"name": "Standard", // [required]
"models": ["lucy", "DamagedHelmet"], // [optional] Base name for the gltf file used in the test. For
// example, source files are lucy.glb and DamagedHelmet.gltf
"rendering": { // [required] Rendering settings used in the test. The json format
"viewer.cameraFocusDistance": 0, // is taken from AutomationSpec in libs/viewer
"view.postProcessingEnabled": true
}
}
],
"tests": [ // [required] List of test configurations
{
"name": "BloomFlare", // [required]
"description": "Testing bloom and flare", // [optional]
"apply_presets": ["Standard"], // [optional] Applies the preset in order. Item must be in
// 'presets' field in the top-level struct.
"model": [], // [optional] List of models used in this test. The list is
// *appended* onto the lists provided by the presets.
"rendering": { // [required] See description in 'presets'
"view.bloom.enabled": true,
"view.bloom.lensFlare": true
}
}
]
}
+68
View File
@@ -0,0 +1,68 @@
# Copyright (C) 2024 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.
import subprocess
import os
import argparse
import sys
def execute(cmd,
cwd=None,
capture_output=True,
stdin=None,
env=None,
raise_errors=False):
in_env = os.environ
in_env.update(env if env else {})
home = os.environ['HOME']
if f'{home}/bin' not in in_env['PATH']:
in_env['PATH'] = in_env['PATH'] + f':{home}/bin'
stdout = subprocess.PIPE if capture_output else sys.stdout
stderr = subprocess.PIPE if capture_output else sys.stdout
output = ''
err_output = ''
return_code = -1
kwargs = {
'cwd': cwd,
'env': in_env,
'stdout': stdout,
'stderr': stderr,
'stdin': stdin,
'universal_newlines': True
}
if capture_output:
process = subprocess.Popen(cmd.split(' '), **kwargs)
output, err_output = process.communicate()
return_code = process.returncode
else:
return_code = subprocess.call(cmd.split(' '), **kwargs)
if return_code:
# Error
if raise_errors:
raise subprocess.CalledProcessError(return_code, cmd)
if output:
if type(output) != str:
try:
output = output.decode('utf-8').strip()
except UnicodeDecodeError as e:
print('cannot decode ', output, file=sys.stderr)
return return_code, (output if return_code == 0 else err_output)
class ArgParseImpl(argparse.ArgumentParser):
def error(self, message):
sys.stderr.write('error: %s\n' % message)
self.print_help()
sys.exit(1)
+45
View File
@@ -0,0 +1,45 @@
# Copyright (C) 2024 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.
#!/usr/bin/bash
OUTPUT_DIR="$(pwd)/out/renderdiff_tests"
RENDERDIFF_TEST_DIR="$(pwd)/test/renderdiff"
TEST_UTILS_DIR="$(pwd)/test/utils"
MESA_DIR="$(pwd)/mesa/out/"
MESA_LIB_DIR="${MESA_DIR}/lib/x86_64-linux-gnu"
function prepare_mesa() {
if [ ! -d ${MESA_LIB_DIR} ]; then
rm -rf mesa
bash ${TEST_UTILS_DIR}/get_mesa.sh
fi
}
# Following steps are taken:
# - Get and build mesa
# - Build gltf_viewer
# - Run the python script that runs the test
# - Zip up the result
set -e && set -x && prepare_mesa && \
mkdir -p ${OUTPUT_DIR} && \
./build.sh -X ${MESA_DIR} -p desktop debug gltf_viewer && \
python3 ${RENDERDIFF_TEST_DIR}/run.py \
--gltf_viewer="$(pwd)/out/cmake-debug/samples/gltf_viewer" \
--test=${RENDERDIFF_TEST_DIR}/tests/presubmit.json \
--output_dir=${OUTPUT_DIR} \
--opengl_lib=${MESA_LIB_DIR}
unset MESA_LIB_DIR
+38
View File
@@ -0,0 +1,38 @@
# Copyright (C) 2024 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.
#!/usr/bin/bash
set -e
set -x
sudo apt-get -y build-dep mesa
git clone https://gitlab.freedesktop.org/mesa/mesa.git
pushd .
cd mesa
git checkout mesa-23.2.1
mkdir -p out
# -Dosmesa=true => builds OSMesa, which is an offscreen GL context
# -Dgallium-drivers=swrast => builds GL software rasterizer
# -Dvulkan-drivers=swrast => builds VK software rasterizer
meson setup builddir/ -Dprefix="$(pwd)/out" -Dosmesa=true -Dglx=xlib -Dgallium-drivers=swrast -Dvulkan-drivers=swrast
meson install -C builddir/
popd
+4
View File
@@ -46,6 +46,10 @@
#include "SDL_config_linux_wayland.h"
#elif defined(FILAMENT_SUPPORTS_X11)
#include "SDL_config_linux_x11.h"
#elif defined(FILAMENT_SUPPORTS_OSMESA)
// We still want the same defines and libs as x11 but we won't actually
// open any windows.
#include "SDL_config_linux_x11.h"
#endif
#else
/* This is a minimal configuration just to get SDL running on new platforms */
+1 -1
View File
@@ -103,7 +103,7 @@ if (LINUX)
if (FILAMENT_SUPPORTS_WAYLAND)
file(GLOB SRCS_LINUX_WAYLAND ${SRC_DIR}/video/wayland/*.c)
list(APPEND SRCS ${SRCS_LINUX_WAYLAND})
elseif(FILAMENT_SUPPORTS_X11)
elseif(FILAMENT_SUPPORTS_X11 OR FILAMENT_SUPPORTS_OSMESA)
file(GLOB SRCS_LINUX_X11 ${SRC_DIR}/video/x11/*.c)
list(APPEND SRCS ${SRCS_LINUX_X11})
endif()
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "filament",
"version": "1.55.0",
"version": "1.55.1",
"description": "Real-time physically based rendering engine",
"main": "filament.js",
"module": "filament.js",