Compare commits
37
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3fa1c7035 | ||
|
|
66abb75bc4 | ||
|
|
9e1ee2f290 | ||
|
|
22bb67e0b0 | ||
|
|
4ee33cd591 | ||
|
|
fd2f9555f1 | ||
|
|
ccdb58e93c | ||
|
|
1eb1df2cf2 | ||
|
|
d784ce311f | ||
|
|
875b295967 | ||
|
|
b1b2d7072d | ||
|
|
014e6bcbde | ||
|
|
614dbb44d5 | ||
|
|
40c0d59464 | ||
|
|
1b0ff3aadb | ||
|
|
164a25cac4 | ||
|
|
2bbcb6de4c | ||
|
|
58aa74b4ae | ||
|
|
26b42a7685 | ||
|
|
99b55cb888 | ||
|
|
4375ffb3e8 | ||
|
|
e9aeb9312b | ||
|
|
4be172050d | ||
|
|
3b9fc8f751 | ||
|
|
3bd4c45d6e | ||
|
|
1809aa7b11 | ||
|
|
739d4007e2 | ||
|
|
3ed9e8f2c0 | ||
|
|
e77ae6ec2c | ||
|
|
4f5369cefa | ||
|
|
93d28bc16a | ||
|
|
e85c22c6f5 | ||
|
|
2cade209b5 | ||
|
|
3d4fc7852b | ||
|
|
7dc17980a3 | ||
|
|
7da09a7f45 | ||
|
|
b97221b8de |
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) { }
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 << ","
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -79,7 +79,7 @@ public:
|
||||
}
|
||||
|
||||
void setFrameScheduledCallback(
|
||||
backend::CallbackHandler* handler, FrameScheduledCallback&& callback);
|
||||
backend::CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags);
|
||||
|
||||
bool isFrameScheduledCallbackSet() const noexcept;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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)
|
||||
Executable
+45
@@ -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
|
||||
Executable
+38
@@ -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
@@ -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
@@ -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,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",
|
||||
|
||||
Reference in New Issue
Block a user