Compare commits

...
Author SHA1 Message Date
bridgewaterrobbie 3f04387651 Fix test 2025-04-15 17:44:40 -04:00
bridgewaterrobbie 7b13964363 Test adding a label to descriptor sets for WebGPU 2025-04-15 17:29:49 -04:00
bridgewaterrobbie 44b6c63fed Handle WebGPUDescriptorSetLayout creation 2025-04-15 13:51:14 -04:00
Syed Idris Shah 6908f1b8af Move adapter and device creation to the begining 2025-04-15 13:35:50 -04:00
Andy Hovingh ad1b36d2b3 fix: DEBUG_COMMAND_STREAM could not be compiled as true due to lack of BufferObjectStreamDescriptor insertion operator 2025-04-14 16:50:53 -05:00
Sungun Park 315c2c273f Cleanup shader compiler (#8613)
There's no functional difference.
2025-04-14 18:27:52 +00:00
Juan Caldas 005d835dbe wgpu: Add Vertex and Index Buffer (#8616)
* implement vertex and index buffers
2025-04-14 10:36:14 -04:00
Jeremy Nelson cade94ab2c webgpu: surface window size for swapchain/surface 2025-04-11 17:30:08 -05:00
Max Rebuschatis 5cd2f5626c Fix bug in FMaterialInstance::setParameterImpl() where mTextureParameters
was not updated when a texture was reassigned if !texture->textureHandleCanMutate().

This could cause a crash in FMaterialInstance::commit() since the old texture
binding was still in mTextureParameters but may have been destroyed.
2025-04-11 12:51:46 -07:00
Jeremy Nelson 94bbcbf1c3 wgpu: combine swapchain classes 2025-04-10 09:20:12 -05:00
Andy Hovingh 52c998d2b6 webgpu: use handle allocator with swapchain 2025-04-10 07:37:53 -05:00
rafadevai 527d831c15 VK: Reuse renderpasses after swapchain resize (#8601)
When a swapchain is resized the FBO cache gets resets
which includes the framebuffer and renderpass objects.
This causes pipelines to be recreated instead of being
reused from the previous frames, causing stalls.

In the case of renderpasses there's no need to clear these
objects, since the state of the renderpass is the same, only
their refcount should be adjusted, fixing the issue mentioned
above.
2025-04-10 07:08:20 +00:00
Syed Idris Shah 2790f2e64c Cleanup the dead code from VulkanPlatformApple.mm
__APPLE__ is defined for both macos and ios platform.
#elif code will never be excuted.
2025-04-09 14:16:11 -04:00
Benjamin Doherty 757640e850 Release Filament 1.59.1 2025-04-09 10:20:51 -07:00
Ben Doherty 9d9abca33a Validate textures have non-zero dimensions (#8607) 2025-04-09 10:18:17 -07:00
Syed Idris Shah bea7a7c73f Implement basic webgpu driver backend to render a background color
Add priliminay implementations of CommandBuffer, RenderColor Attachment,
RenderPass encoders and web gpu queue. This is sufficient to render
background color on a surface using webgpu api's.
2025-04-08 16:28:26 -04:00
Syed Idris Shah dd62f98784 Treat native Window as CAMetalLayer
Attach MetalLayer to Native View same as vulkan and metal backend.
This change assumes native window is already a CAMetalLayer. This is no different
for IOS and Mac.
2025-04-08 16:28:26 -04:00
Syed Idris Shah 395a3eda9b Introduce basic HandleAllocator for webgpu
Implement stub implementations for different handles. This is a
place holder patch. These handles will be enhanced and used by webgpu
driver for backend implementations.
2025-04-08 16:28:26 -04:00
Syed Idris Shah 9d18cc35e0 Fix webgpu android build after dawn update 2025-04-08 14:58:59 -04:00
Andy Hovingh 2e37ebe70c webgpu: compile WGSL shaders into Android materials when needed 2025-04-08 13:13:21 -05:00
Matthew Hoffman 63ef412be1 Shared shader helpers for backend tests. (#8562) 2025-04-08 12:45:59 -05:00
rafadevai f0996296c1 VK: Remove explicitImageReadyWait callback (#8603)
This function was created to make sure the swapchain
image was available before submitting the work to the GPU.

So the order of the process looked like

| Acquire | Begin | Record | Wait | Present | End |

After this change the expectation is that when the
swapchain acquires a new image it will also wait until
is actually available from the platform.

| Acquire | Begin | Record | Present | End |

In the case the image is not available, the expectation
is that the acquire will wait until it is and then start
processing the frame.
2025-04-08 06:09:49 +00:00
Powei Feng a60d570b3b vk: preparing for Ycbcr conversion (#8596)
- Move utils functions to the right files
 - Add conversion from vk formats to filament
 - Refactor how to enable multiview and external sampler in
   VulkanPlatform
 - Make VuklanDescriptorSet and VuklanDescriptorSetLayout more
   general to allow for mutability
 - Reorder vulkan files in CMakeLists.txt to be sorted in
   alphabetical order.
2025-04-08 05:50:37 +00:00
Powei Feng 3fdb9f311a github: add code-correctness presubmit (#8595) 2025-04-04 15:17:07 -07:00
Andy Hovingh 70f2cda9b6 remove unnecessary third party includes from dawn (Filament already brings them in) 2025-04-03 20:22:28 -04:00
Andy Hovingh c3e4894d49 fix Mac OS 15.4 compile errors: minor changes to reflect webgpu and tint API changes 2025-04-03 20:22:28 -04:00
Andy Hovingh a128d9143a fix Mac OS 15.4 compile errors: ignore clang nullability-extension warnings/errors only for Macs 2025-04-03 20:22:28 -04:00
Andy Hovingh 2cf10999f4 fix Mac OS 15.4 compile errors: upgrade spirv-tools dependency to match dawn (6add4e478f8802d3bbd100120e5ffc6f725ec9fe) 2025-04-03 20:22:28 -04:00
Andy Hovingh e0e44da185 fix Mac OS 15.4 compile errors: upgrade spirv-headers dependency to match dawn (8e82b7cfeca98baae9a01a53511483da7194f854) 2025-04-03 20:22:28 -04:00
Andy Hovingh ce62e0ef06 fix Mac OS 15.4 compile errors: upgrade glslang dependency to match dawn (e57f993cff981c8c3ffd38967e030f04d13781a9) 2025-04-03 20:22:28 -04:00
Andy Hovingh 826b885d0e fix Mac OS 15.4 compile errors: upgrade dawn dependency (chromium/7106 tag/version) 2025-04-03 20:22:28 -04:00
Andy Hovingh 5b9a24edf0 fix Mac OS 15.4 compile errors: upgrade abseil dependency (version Abseil LTS 20250127.1) 2025-04-03 20:22:28 -04:00
Andy Hovingh b651f63e02 update release notes 2025-04-03 20:22:28 -04:00
Andy Hovingh c48273df3c fix Mac OS 15.4 compile errors: invoke printSurfaceConfiguration to avoid an unused error 2025-04-03 20:22:28 -04:00
Andy Hovingh 8397a15914 fix Mac OS 15.4 compile errors: upgrade libpng dependency (v1.6.47) 2025-04-03 20:22:28 -04:00
Andy Hovingh d76567b4a8 fix Mac OS 15.4 compile errors: upgrade libz dependency (v1.3.1) 2025-04-03 20:22:28 -04:00
Konrad Piascik ce62b169cb Fix Windows webGPU build 2025-04-03 14:12:17 -04:00
Powei Feng 9244f8ee21 vk: add YcbcrConversion to VulkanTexture (#8588)
- Refactor VulkanTextureState and VulkanTexture so that most
  state fields are consts.
- Add a field for VkYcbcrConversion.
- Clear the image view cache when YcbcrConversion changes
- Make sure that getAttachmentView and getView are better defined
  and called.
2025-04-03 17:30:40 +00:00
bridgewaterrobbie 3c7a871e7e Avoid running the inline pass without merge return on WebGPU 2025-04-02 17:52:27 -04:00
Jeremy Nelson 31d698e9ca remove resize and refactor GetCurrentTexture
remove resize and refactor GetCurrentTexture

remove resize and refactor GetCurrentTexture
2025-04-02 15:35:50 -05:00
Andy Hovingh 769b0db858 webgpu: select color format, present mode, and composite alpha mode from surface capabilities 2025-04-02 15:35:50 -05:00
Jeremy Nelson b40064b1ab Changed class name to WebGPUSwapChain and comments for handle allocator 2025-04-02 15:35:50 -05:00
Jeremy Nelson 889a62d617 Create WebGPUSurface Class 2025-04-02 15:35:50 -05:00
Mathias Agopian 784b3ce565 Fix subpasses with custom render targets
When Color grading is using the optimized subpass mode, it could fail
if it was the last pass. This is because in that case it renders
directly into the custom rendertarget which doesn't have MRTs (which
is needed). The fix is to issue an extra blit.

FIXES=[406343577]
2025-04-01 15:23:34 -07:00
Powei Feng 4f77547ab3 vk: add ycbcr conversion cache (#8557)
Introduce a cache for Ycbcr conversion, which is needed for
external textures on Android.
2025-04-01 22:12:59 +00:00
Nick Fisher 46c89499d0 Dispose debug.server/debug.fgViewerServer when Engine destroyed (#8563) 2025-04-01 20:52:42 +00:00
Sungun Park 57c782685b Add missing header (#8586) 2025-04-01 11:11:19 -07:00
bridgewaterrobbieandPowei Feng bc799c78f6 Update libs/filamat/src/GLSLPostProcessor.cpp per suggestion
Co-authored-by: Powei Feng <powei@google.com>
2025-03-31 19:39:05 -04:00
bridgewaterrobbieandPowei Feng 86e688a2a3 Update libs/filamat/src/GLSLPostProcessor.cpp per suggestion
Co-authored-by: Powei Feng <powei@google.com>
2025-03-31 19:39:05 -04:00
bridgewaterrobbie 3dead231d4 Output failing processed spir-v on failure to read with tint
Also add a build option to ignore such issues if needed during bringup
2025-03-31 19:39:05 -04:00
bridgewaterrobbie fd382ce219 Split and rebind combined image samplers for WebGPU API target 2025-03-31 19:39:05 -04:00
bridgewaterrobbie 46358572b7 Allow non uniform derivitives 2025-03-31 19:39:05 -04:00
bridgewaterrobbie 546b986107 Hack a workaround for OpConstantLoad issue in Tint to work with most spec constants 2025-03-31 19:39:05 -04:00
bridgewaterrobbie 5245141c46 Workaround Tint issues with texelFetchOffset in dofMedian.mat 2025-03-31 19:39:05 -04:00
bridgewaterrobbie 62b182fe04 Update SPIR-V tools to commit ada1771a9f7a125573aa94fe551fdc44b45769bd to pull in splitter pass 2025-03-31 19:39:05 -04:00
9407 changed files with 354134 additions and 2043038 deletions
+34
View File
@@ -19,6 +19,8 @@ jobs:
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- name: Run build script
run: |
WORKFLOW_OS=`echo \`uname\` | sed "s/Darwin/mac/" | tr [:upper:] [:lower:]`
@@ -44,6 +46,8 @@ jobs:
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
@@ -60,6 +64,8 @@ jobs:
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- name: Run build script
run: |
cd build/ios && printf "y" | ./build.sh presubmit
@@ -122,3 +128,31 @@ jobs:
run: source ./build/linux/ci-common.sh && ./build.sh -W debug test_filamat filament
- name: Run test
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
code-correcteness:
name: code-correctness
runs-on: 'macos-14-xlarge'
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install prerequisites
run: |
pip install pyyaml
brew install llvm
sudo ln -s "$(brew --prefix llvm)/bin/clang-tidy" "/usr/local/bin/clang-tidy"
- name: Run build script
# We need to build before clang-tidy can run analysis
run: |
# This will build for all three desktop backends on mac
./build.sh -p desktop debug gltf_viewer
- name: Run test
run: |
bash test/code-correctness/test.sh
+3
View File
@@ -334,6 +334,9 @@ if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} /W0 /Zc:__cplusplus")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} -fstrict-aliasing -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations")
if (APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-nullability-extension")
endif()
endif()
if (FILAMENT_USE_EXTERNAL_GLES3)
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.59.0'
implementation 'com.google.android.filament:filament-android:1.59.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.59.0'
pod 'Filament', '~> 1.59.1'
```
## Documentation
+4
View File
@@ -7,6 +7,10 @@ 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.59.2
- Fix build/compile errors when upgrading to MacOS 15.4
## v1.59.1
@@ -142,6 +142,12 @@ abstract class MaterialCompiler extends TaskWithBinary {
if (!exclude_vulkan) {
matcArgs += ['-a', 'vulkan']
}
def include_webgpu = providers
.gradleProperty("com.google.android.filament.include-webgpu")
.forUseAtConfigurationTime().present
if (include_webgpu) {
matcArgs += ['-a', 'webgpu', '--variant-filter=skinning,stereo']
}
def mat_no_opt = providers
.gradleProperty("com.google.android.filament.matnopt")
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.59.0
VERSION_NAME=1.59.1
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+38 -26
View File
@@ -177,29 +177,6 @@ endif()
if (FILAMENT_SUPPORTS_VULKAN)
list(APPEND SRCS
include/backend/platforms/VulkanPlatform.h
src/vulkan/VulkanDescriptorSetCache.cpp
src/vulkan/VulkanDescriptorSetCache.h
src/vulkan/VulkanDescriptorSetLayoutCache.cpp
src/vulkan/VulkanDescriptorSetLayoutCache.h
src/vulkan/VulkanPipelineLayoutCache.cpp
src/vulkan/VulkanPipelineLayoutCache.h
src/vulkan/memory/ResourceManager.cpp
src/vulkan/memory/ResourceManager.h
src/vulkan/memory/ResourcePointer.h
src/vulkan/memory/Resource.cpp
src/vulkan/memory/Resource.h
src/vulkan/platform/VulkanPlatform.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
src/vulkan/utils/Conversion.cpp
src/vulkan/utils/Conversion.h
src/vulkan/utils/Definitions.h
src/vulkan/utils/Helper.h
src/vulkan/utils/Image.h
src/vulkan/utils/Image.cpp
src/vulkan/utils/Spirv.h
src/vulkan/utils/Spirv.cpp
src/vulkan/utils/StaticVector.h
src/vulkan/VulkanAsyncHandles.h
src/vulkan/VulkanBlitter.cpp
src/vulkan/VulkanBlitter.h
@@ -210,6 +187,10 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanConstants.h
src/vulkan/VulkanContext.cpp
src/vulkan/VulkanContext.h
src/vulkan/VulkanDescriptorSetCache.cpp
src/vulkan/VulkanDescriptorSetCache.h
src/vulkan/VulkanDescriptorSetLayoutCache.cpp
src/vulkan/VulkanDescriptorSetLayoutCache.h
src/vulkan/VulkanDriver.cpp
src/vulkan/VulkanDriver.h
src/vulkan/VulkanDriverFactory.h
@@ -217,22 +198,43 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanFboCache.h
src/vulkan/VulkanHandles.cpp
src/vulkan/VulkanHandles.h
src/vulkan/VulkanMemory.h
src/vulkan/VulkanMemory.cpp
src/vulkan/VulkanMemory.h
src/vulkan/VulkanPipelineCache.cpp
src/vulkan/VulkanPipelineCache.h
src/vulkan/VulkanPipelineLayoutCache.cpp
src/vulkan/VulkanPipelineLayoutCache.h
src/vulkan/VulkanQueryManager.cpp
src/vulkan/VulkanQueryManager.h
src/vulkan/VulkanReadPixels.cpp
src/vulkan/VulkanReadPixels.h
src/vulkan/VulkanSamplerCache.cpp
src/vulkan/VulkanSamplerCache.h
src/vulkan/VulkanStagePool.cpp
src/vulkan/VulkanStagePool.h
src/vulkan/VulkanSwapChain.cpp
src/vulkan/VulkanSwapChain.h
src/vulkan/VulkanReadPixels.cpp
src/vulkan/VulkanReadPixels.h
src/vulkan/VulkanTexture.cpp
src/vulkan/VulkanTexture.h
src/vulkan/VulkanYcbcrConversionCache.cpp
src/vulkan/VulkanYcbcrConversionCache.h
src/vulkan/memory/Resource.cpp
src/vulkan/memory/Resource.h
src/vulkan/memory/ResourceManager.cpp
src/vulkan/memory/ResourceManager.h
src/vulkan/memory/ResourcePointer.h
src/vulkan/platform/VulkanPlatform.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
src/vulkan/utils/Conversion.cpp
src/vulkan/utils/Conversion.h
src/vulkan/utils/Definitions.h
src/vulkan/utils/Helper.h
src/vulkan/utils/Image.cpp
src/vulkan/utils/Image.h
src/vulkan/utils/Spirv.cpp
src/vulkan/utils/Spirv.h
src/vulkan/utils/StaticVector.h
)
if (LINUX OR WIN32)
list(APPEND SRCS src/vulkan/platform/VulkanPlatformLinuxWindows.cpp)
@@ -253,6 +255,10 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPUConstants.h
src/webgpu/WebGPUDriver.cpp
src/webgpu/WebGPUDriver.h
src/webgpu/WebGPUHandles.cpp
src/webgpu/WebGPUHandles.h
src/webgpu/WebGPUSwapChain.cpp
src/webgpu/WebGPUSwapChain.h
)
if (WIN32)
list(APPEND SRCS src/webgpu/platform/WebGPUPlatformWindows.cpp)
@@ -302,6 +308,11 @@ target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR})
# add this subproject to the Filament folder
set_target_properties(${TARGET} PROPERTIES FOLDER Filament)
# we need to export the headers properly for backend.lib on windows
if (WIN32 AND FILAMENT_SUPPORTS_WEBGPU)
target_compile_definitions(${TARGET} PRIVATE "WGPU_IMPLEMENTATION")
endif()
# ==================================================================================================
# Expose a header-only target to minimize dependencies.
# ==================================================================================================
@@ -497,6 +508,7 @@ if (APPLE OR LINUX)
test/ImageExpectations.cpp
test/Lifetimes.cpp
test/Shader.cpp
test/SharedShaders.cpp
test/test_FeedbackLoops.cpp
test/test_Blit.cpp
test/test_MissingRequiredAttributes.cpp
@@ -55,4 +55,9 @@ public:
} // namespace filament::backend
#if !defined(NDEBUG)
utils::io::ostream& operator<<(utils::io::ostream& out,
const filament::backend::BufferObjectStreamDescriptor& b);
#endif
#endif // TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
@@ -1139,6 +1139,7 @@ struct ExternalSamplerDatum {
static_assert(sizeof(ExternalSamplerDatum) == 12);
struct DescriptorSetLayout {
std::string label;
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
// TODO: uncomment when needed
@@ -104,10 +104,6 @@ public:
// Semaphore to be signaled once the image is available.
VkSemaphore imageReadySemaphore = VK_NULL_HANDLE;
// A function called right before vkQueueSubmit. After this call, the image must be
// available. This pointer can be null if imageReadySemaphore is not VK_NULL_HANDLE.
std::function<void(SwapChainPtr handle)> explicitImageReadyWait = nullptr;
};
VulkanPlatform();
@@ -361,8 +357,7 @@ public:
VkImageUsageFlags usage);
virtual VkSampler createExternalSampler(SamplerYcbcrConversion chroma,
SamplerParams sampler,
uint32_t internalFormat);
SamplerParams sampler, uint32_t internalFormat);
virtual VkImageView createExternalImageView(SamplerYcbcrConversion chroma,
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
@@ -38,6 +38,12 @@ public:
[[nodiscard]] wgpu::Instance& getInstance() noexcept { return mInstance; }
// TODO consider that this functionality is not WebGPU-specific, and thus could be
// placed in a generic place and even reused across backends. Alternatively,
// a 3rd party library could be considered. However, this was a simple and
// quick change and works for now.
// gets the size (height and width) of the surface/window
[[nodiscard]] wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const;
// either returns a valid surface or panics
[[nodiscard]] wgpu::Surface createSurface(void* nativeWindow, uint64_t flags);
// either returns a valid adapter or panics
@@ -54,31 +54,27 @@ using namespace utils;
// ------------------------------------------------------------------------------------------------
static void logCompilationError(utils::io::ostream& out,
ShaderStage shaderType, const char* name,
static inline std::string to_string(bool b) noexcept { return b ? "true" : "false"; }
static inline std::string to_string(int i) noexcept { return std::to_string(i); }
static inline std::string to_string(float f) noexcept { return "float(" + std::to_string(f) + ")"; }
static void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
GLuint shaderId, CString const& sourceCode) noexcept;
static void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept;
static void logProgramLinkError(utils::io::ostream& out,
const char* name, GLuint program) noexcept;
static inline std::string to_string(bool b) noexcept {
return b ? "true" : "false";
}
static inline std::string to_string(int i) noexcept {
return std::to_string(i);
}
static inline std::string to_string(float f) noexcept {
return "float(" + std::to_string(f) + ")";
}
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, char* source,
size_t len) noexcept;
static void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount, char* source,
size_t len) noexcept;
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
static std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept;
// ------------------------------------------------------------------------------------------------
struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
struct ProgramData {
GLuint program{};
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
shaders_t shaders{};
};
~OpenGLProgramToken() override;
@@ -90,10 +86,10 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
ShaderCompilerService& compiler;
utils::CString const& name;
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes;
std::array<utils::CString, Program::SHADER_TYPE_COUNT> shaderSourceCode;
shaders_source_t shaderSourceCode;
void* user = nullptr;
struct {
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
shaders_t shaders{};
GLuint program = 0;
} gl; // 12 bytes
@@ -140,11 +136,12 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() = default;
void ShaderCompilerService::setUserData(const program_token_t& token, void* user) noexcept {
/* static */ void ShaderCompilerService::setUserData(const program_token_t& token,
void* user) noexcept {
token->user = user;
}
void* ShaderCompilerService::getUserData(const program_token_t& token) noexcept {
/* static */ void* ShaderCompilerService::getUserData(const program_token_t& token) noexcept {
return token->user;
}
@@ -268,101 +265,111 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
token->handle = mCallbackManager.get();
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
if (mMode == Mode::THREAD_POOL) {
// queue a compile job
mCompilerThreadPool.queue(priorityQueue, token,
[this, &gl, program = std::move(program), token]() mutable {
// compile the shaders
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
compileShaders(gl,
std::move(program.getShadersSource()),
program.getSpecializationConstants(),
program.isMultiview(),
shaders,
token->shaderSourceCode);
switch (mMode) {
case Mode::THREAD_POOL: {
// queue a compile job
mCompilerThreadPool.queue(priorityQueue, token,
[this, &gl, program = std::move(program), token]() mutable {
// compile the shaders
shaders_t shaders{};
compileShaders(gl,
std::move(program.getShadersSource()),
program.getSpecializationConstants(),
program.isMultiview(),
shaders,
token->shaderSourceCode);
// link the program
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
// link the program
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
OpenGLProgramToken::ProgramData programData;
programData.shaders = shaders;
OpenGLProgramToken::ProgramData programData;
programData.shaders = shaders;
// We need to query the link status here to guarantee that the
// program is compiled and linked now (we don't want this to be
// deferred to later). We don't care about the result at this point.
GLint status = GL_FALSE;
glGetProgramiv(glProgram, GL_LINK_STATUS, &status);
programData.program = glProgram;
// We need to query the link status here to guarantee that the
// program is compiled and linked now (we don't want this to be
// deferred to later). We don't care about the result at this point.
GLint status = GL_FALSE;
glGetProgramiv(glProgram, GL_LINK_STATUS, &status);
programData.program = glProgram;
// we don't need to check for success here, it'll be done on the
// main thread side.
token->set(programData);
// we don't need to check for success here, it'll be done on the
// main thread side.
token->set(programData);
mCallbackManager.put(token->handle);
mCallbackManager.put(token->handle);
// caching must be the last thing we do
if (token->key && status == GL_TRUE) {
// Attempt to cache. This calls glGetProgramBinary.
mBlobCache.insert(mDriver.mPlatform, token->key, glProgram);
}
});
// caching must be the last thing we do
if (token->key && status == GL_TRUE) {
// Attempt to cache. This calls glGetProgramBinary.
mBlobCache.insert(mDriver.mPlatform, token->key, glProgram);
}
});
break;
}
} else {
// this cannot fail because we check compilation status after linking the program
// shaders[] is filled with id of shader stages present.
compileShaders(gl,
std::move(program.getShadersSource()),
program.getSpecializationConstants(),
program.isMultiview(),
token->gl.shaders,
token->shaderSourceCode);
case Mode::SYNCHRONOUS:
case Mode::ASYNCHRONOUS: {
// this cannot fail because we check compilation status after linking the program
// shaders[] is filled with id of shader stages present.
compileShaders(gl,
std::move(program.getShadersSource()),
program.getSpecializationConstants(),
program.isMultiview(),
token->gl.shaders,
token->shaderSourceCode);
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
assert_invariant(mMode != Mode::THREAD_POOL);
if (mMode == Mode::ASYNCHRONOUS) {
// don't attempt to link this program if all shaders are not done compiling
GLint status;
if (token->gl.program) {
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status);
if (status == GL_FALSE) {
return false;
}
} else {
for (auto shader: token->gl.shaders) {
if (shader) {
glGetShaderiv(shader, GL_COMPLETION_STATUS, &status);
if (status == GL_FALSE) {
return false;
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
assert_invariant(mMode != Mode::THREAD_POOL);
if (mMode == Mode::ASYNCHRONOUS) {
// don't attempt to link this program if all shaders are not done compiling
GLint status;
if (token->gl.program) {
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status);
if (status == GL_FALSE) {
return false;
}
} else {
for (auto shader: token->gl.shaders) {
if (shader) {
glGetShaderiv(shader, GL_COMPLETION_STATUS, &status);
if (status == GL_FALSE) {
return false;
}
}
}
}
}
}
if (!token->gl.program) {
// link the program, this also cannot fail because status is checked later.
token->gl.program = linkProgram(mDriver.getContext(),
token->gl.shaders, token->attributes);
if (mMode == Mode::ASYNCHRONOUS) {
// wait until the link finishes...
return false;
if (!token->gl.program) {
// link the program, this also cannot fail because status is checked later.
token->gl.program = linkProgram(mDriver.getContext(),
token->gl.shaders, token->attributes);
if (mMode == Mode::ASYNCHRONOUS) {
// wait until the link finishes...
return false;
}
}
}
assert_invariant(token->gl.program);
assert_invariant(token->gl.program);
mCallbackManager.put(token->handle);
mCallbackManager.put(token->handle);
if (token->key) {
// TODO: technically we don't have to cache right now. Is it advantageous to
// do this later, maybe depending on CPU usage?
// attempt to cache if we don't have a thread pool (otherwise it's done
// by the pool).
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
}
if (token->key) {
// TODO: technically we don't have to cache right now. Is it advantageous to
// do this later, maybe depending on CPU usage?
// attempt to cache if we don't have a thread pool (otherwise it's done
// by the pool).
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
}
return true;
});
return true;
});
break;
}
case Mode::UNDEFINED: {
assert_invariant(false);
}
}
return token;
@@ -377,12 +384,12 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
return program;
}
void ShaderCompilerService::terminate(program_token_t& token) {
/* static */ void ShaderCompilerService::terminate(program_token_t& token) {
assert_invariant(token);
token->canceled = true;
bool const canceled = token->compiler.cancelTickOp(token);
bool const isTickOpCanceled = token->compiler.cancelTickOp(token);
if (token->compiler.mMode == Mode::THREAD_POOL) {
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
@@ -395,7 +402,7 @@ void ShaderCompilerService::terminate(program_token_t& token) {
// order for future callbacks to be successfully called.
token->compiler.mCallbackManager.put(token->handle);
}
} else if (canceled) {
} else if (isTickOpCanceled) {
// Since the tick op was canceled, we need to .put the token here.
token->compiler.mCallbackManager.put(token->handle);
}
@@ -432,7 +439,8 @@ void ShaderCompilerService::notifyWhenAllProgramsAreReady(
// ------------------------------------------------------------------------------------------------
void ShaderCompilerService::getProgramFromCompilerPool(program_token_t& token) noexcept {
/* static */ void ShaderCompilerService::getProgramFromCompilerPool(
program_token_t& token) noexcept {
OpenGLProgramToken::ProgramData const& programData{ token->get() };
if (!token->canceled) {
token->gl.shaders = programData.shaders;
@@ -443,40 +451,53 @@ void ShaderCompilerService::getProgramFromCompilerPool(program_token_t& token) n
GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
SYSTRACE_CALL();
if (!token->gl.program) {
if (mMode == Mode::THREAD_POOL) {
// we need this program right now, remove it from the queue
auto job = mCompilerThreadPool.dequeue(token);
if (job) {
// if we were able to remove it, we execute the job now, otherwise it means
// it's being executed right now.
job();
switch (mMode) {
case Mode::THREAD_POOL: {
// we need this program right now, remove it from the queue
auto job = mCompilerThreadPool.dequeue(token);
if (job) {
// if we were able to remove it, we execute the job now, otherwise it means
// it's being executed right now.
job();
}
if (!token->canceled) {
token->compiler.cancelTickOp(token);
}
// Block until we get the program from the pool. Generally this wouldn't block
// because we just compiled the program above, when executing job.
ShaderCompilerService::getProgramFromCompilerPool(token);
break;
}
if (!token->canceled) {
case Mode::ASYNCHRONOUS: {
// we force the program link -- which might stall, either here or below in
// checkProgramStatus(), but we don't have a choice, we need to use the program now.
token->compiler.cancelTickOp(token);
token->gl.program =
linkProgram(mDriver.getContext(), token->gl.shaders, token->attributes);
assert_invariant(token->gl.program);
mCallbackManager.put(token->handle);
if (token->key) {
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
}
break;
}
// Block until we get the program from the pool. Generally this wouldn't block
// because we just compiled the program above, when executing job.
ShaderCompilerService::getProgramFromCompilerPool(token);
} else if (mMode == Mode::ASYNCHRONOUS) {
// we force the program link -- which might stall, either here or below in
// checkProgramStatus(), but we don't have a choice, we need to use the program now.
token->compiler.cancelTickOp(token);
token->gl.program = linkProgram(mDriver.getContext(),
token->gl.shaders, token->attributes);
assert_invariant(token->gl.program);
mCallbackManager.put(token->handle);
if (token->key) {
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
case Mode::SYNCHRONOUS: {
// if we don't have a program yet, block until we get it.
tick();
break;
}
case Mode::UNDEFINED: {
assert_invariant(false);
}
} else {
// if we don't have a program yet, block until we get it.
tick();
}
}
@@ -519,12 +540,12 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
* checked until after the program is linked.
* This always returns the GL shader IDs or zero a shader stage is not present.
*/
void ShaderCompilerService::compileShaders(OpenGLContext& context,
/* static */ void ShaderCompilerService::compileShaders(OpenGLContext& context,
Program::ShaderSource shadersSource,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
bool multiview,
std::array<GLuint, Program::SHADER_TYPE_COUNT>& outShaders,
UTILS_UNUSED_IN_RELEASE std::array<CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept {
shaders_t& outShaders,
UTILS_UNUSED_IN_RELEASE shaders_source_t& outShaderSourceCode) noexcept {
SYSTRACE_CALL();
@@ -635,209 +656,13 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
}
}
// If usages of the Google-style line directive are present, remove them, as some
// drivers don't allow the quotation marks. This source modification happens in-place.
void ShaderCompilerService::process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
char* source, size_t len) noexcept {
if (!context.ext.GOOGLE_cpp_style_line_directive) {
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) {
removeGoogleLineDirectives(source, len); // length is unaffected
}
}
}
// Look up the `source` to replace the number of eyes for multiview with the given number. This is
// necessary for OpenGL because OpenGL relies on the number specified in shader files to determine
// the number of views, which is assumed as a single digit, for multiview.
// This source modification happens in-place.
void ShaderCompilerService::process_OVR_multiview2(OpenGLContext& context,
int32_t eyeCount, char* source, size_t len) noexcept {
// We don't use regular expression in favor of performance.
if (context.ext.OVR_multiview2) {
const std::string_view shader{ source, len };
const std::string_view layout = "layout";
const std::string_view num_views = "num_views";
size_t found = 0;
while (true) {
found = shader.find(layout, found);
if (found == std::string_view::npos) {
break;
}
found = shader.find_first_not_of(' ', found + layout.size());
if (found == std::string_view::npos || shader[found] != '(') {
continue;
}
found = shader.find_first_not_of(' ', found + 1);
if (found == std::string_view::npos) {
continue;
}
if (shader.compare(found, num_views.size(), num_views) != 0) {
continue;
}
found = shader.find_first_not_of(' ', found + num_views.size());
if (found == std::string_view::npos || shader[found] != '=') {
continue;
}
found = shader.find_first_not_of(' ', found + 1);
if (found == std::string_view::npos) {
continue;
}
// We assume the value should be one-digit number.
assert_invariant(eyeCount < 10);
assert_invariant(!::isdigit(source[found + 1]));
source[found] = '0' + eyeCount;
break;
}
}
}
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
// macOS doesn't support GL_ARB_shading_language_packing
// Also GLES3.0 didn't have the full set of packing/unpacking functions
std::string_view ShaderCompilerService::process_ARB_shading_language_packing(OpenGLContext& context) noexcept {
using namespace std::literals;
#ifdef BACKEND_OPENGL_VERSION_GL
if (!context.isAtLeastGL<4, 2>() && !context.ext.ARB_shading_language_packing) {
return R"(
// these don't handle denormals, NaNs or inf
float u16tofp32(highp uint v) {
v <<= 16u;
highp uint s = v & 0x80000000u;
highp uint n = v & 0x7FFFFFFFu;
highp uint nz = (n == 0u) ? 0u : 0xFFFFFFFFu;
return uintBitsToFloat(s | ((((n >> 3u) + (0x70u << 23u))) & nz));
}
vec2 unpackHalf2x16(highp uint v) {
return vec2(u16tofp32(v&0xFFFFu), u16tofp32(v>>16u));
}
uint fp32tou16(float val) {
uint f32 = floatBitsToUint(val);
uint f16 = 0u;
uint sign = (f32 >> 16u) & 0x8000u;
int exponent = int((f32 >> 23u) & 0xFFu) - 127;
uint mantissa = f32 & 0x007FFFFFu;
if (exponent > 15) {
f16 = sign | (0x1Fu << 10u);
} else if (exponent > -15) {
exponent += 15;
mantissa >>= 13;
f16 = sign | uint(exponent << 10) | mantissa;
} else {
f16 = sign;
}
return f16;
}
highp uint packHalf2x16(vec2 v) {
highp uint x = fp32tou16(v.x);
highp uint y = fp32tou16(v.y);
return (y << 16u) | x;
}
highp uint packUnorm4x8(mediump vec4 v) {
v = round(clamp(v, 0.0, 1.0) * 255.0);
highp uint a = uint(v.x);
highp uint b = uint(v.y) << 8;
highp uint c = uint(v.z) << 16;
highp uint d = uint(v.w) << 24;
return (a|b|c|d);
}
highp uint packSnorm4x8(mediump vec4 v) {
v = round(clamp(v, -1.0, 1.0) * 127.0);
highp uint a = uint((int(v.x) & 0xff));
highp uint b = uint((int(v.y) & 0xff)) << 8;
highp uint c = uint((int(v.z) & 0xff)) << 16;
highp uint d = uint((int(v.w) & 0xff)) << 24;
return (a|b|c|d);
}
mediump vec4 unpackUnorm4x8(highp uint v) {
return vec4(float((v & 0x000000ffu) ),
float((v & 0x0000ff00u) >> 8),
float((v & 0x00ff0000u) >> 16),
float((v & 0xff000000u) >> 24)) / 255.0;
}
mediump vec4 unpackSnorm4x8(highp uint v) {
int a = int(((v ) & 0xffu) << 24u) >> 24 ;
int b = int(((v >> 8u) & 0xffu) << 24u) >> 24 ;
int c = int(((v >> 16u) & 0xffu) << 24u) >> 24 ;
int d = int(((v >> 24u) & 0xffu) << 24u) >> 24 ;
return clamp(vec4(float(a), float(b), float(c), float(d)) / 127.0, -1.0, 1.0);
}
)"sv;
}
#endif // BACKEND_OPENGL_VERSION_GL
#ifdef BACKEND_OPENGL_VERSION_GLES
if (!context.isES2() && !context.isAtLeastGLES<3, 1>()) {
return R"(
highp uint packUnorm4x8(mediump vec4 v) {
v = round(clamp(v, 0.0, 1.0) * 255.0);
highp uint a = uint(v.x);
highp uint b = uint(v.y) << 8;
highp uint c = uint(v.z) << 16;
highp uint d = uint(v.w) << 24;
return (a|b|c|d);
}
highp uint packSnorm4x8(mediump vec4 v) {
v = round(clamp(v, -1.0, 1.0) * 127.0);
highp uint a = uint((int(v.x) & 0xff));
highp uint b = uint((int(v.y) & 0xff)) << 8;
highp uint c = uint((int(v.z) & 0xff)) << 16;
highp uint d = uint((int(v.w) & 0xff)) << 24;
return (a|b|c|d);
}
mediump vec4 unpackUnorm4x8(highp uint v) {
return vec4(float((v & 0x000000ffu) ),
float((v & 0x0000ff00u) >> 8),
float((v & 0x00ff0000u) >> 16),
float((v & 0xff000000u) >> 24)) / 255.0;
}
mediump vec4 unpackSnorm4x8(highp uint v) {
int a = int(((v ) & 0xffu) << 24u) >> 24 ;
int b = int(((v >> 8u) & 0xffu) << 24u) >> 24 ;
int c = int(((v >> 16u) & 0xffu) << 24u) >> 24 ;
int d = int(((v >> 24u) & 0xffu) << 24u) >> 24 ;
return clamp(vec4(float(a), float(b), float(c), float(d)) / 127.0, -1.0, 1.0);
}
)"sv;
}
#endif // BACKEND_OPENGL_VERSION_GLES
return ""sv;
}
// split shader source code in three:
// - the version line
// - extensions
// - everything else
std::array<std::string_view, 3> ShaderCompilerService::splitShaderSource(std::string_view source) noexcept {
auto version_start = source.find("#version");
assert_invariant(version_start != std::string_view::npos);
auto version_eol = source.find('\n', version_start) + 1;
assert_invariant(version_eol != std::string_view::npos);
auto prolog_start = version_eol;
auto prolog_eol = source.rfind("\n#extension"); // last #extension line
if (prolog_eol == std::string_view::npos) {
prolog_eol = prolog_start;
} else {
prolog_eol = source.find('\n', prolog_eol + 1) + 1;
}
auto body_start = prolog_eol;
std::string_view const version = source.substr(version_start, version_eol - version_start);
std::string_view const prolog = source.substr(prolog_start, prolog_eol - prolog_start);
std::string_view const body = source.substr(body_start, source.length() - body_start);
return { version, prolog, body };
}
/*
* Create a program from the given shader IDs and links it. This cannot fail because errors
* are checked later. This always returns a valid GL program ID (which doesn't mean the
* program itself is valid).
*/
GLuint ShaderCompilerService::linkProgram(OpenGLContext& context,
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders,
/* static */ GLuint ShaderCompilerService::linkProgram(OpenGLContext& context,
shaders_t const& shaders,
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept {
SYSTRACE_CALL();
@@ -913,7 +738,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
* Checks a program link status and logs errors and frees resources on failure.
* Returns true on success.
*/
bool ShaderCompilerService::checkProgramStatus(program_token_t const& token) noexcept {
/* static */ bool ShaderCompilerService::checkProgramStatus(program_token_t const& token) noexcept {
SYSTRACE_CALL();
@@ -948,20 +773,24 @@ bool ShaderCompilerService::checkProgramStatus(program_token_t const& token) noe
return false;
}
// ------------------------------------------------------------------------------------------------
UTILS_NOINLINE
void logCompilationError(io::ostream& out, ShaderStage shaderType,
const char* name, GLuint shaderId,
UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
/* static */ void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
GLuint shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
auto to_string = [](ShaderStage type) -> const char* {
switch (type) {
case ShaderStage::VERTEX: return "vertex";
case ShaderStage::FRAGMENT: return "fragment";
case ShaderStage::COMPUTE: return "compute";
case ShaderStage::VERTEX:
return "vertex";
case ShaderStage::FRAGMENT:
return "fragment";
case ShaderStage::COMPUTE:
return "compute";
}
};
{ // scope for the temporary string storage
{// scope for the temporary string storage
GLint length = 0;
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &length);
@@ -969,8 +798,7 @@ void logCompilationError(io::ostream& out, ShaderStage shaderType,
glGetShaderInfoLog(shaderId, length, nullptr, infoLog.data());
out << "Compilation error in " << to_string(shaderType) << " shader \"" << name << "\":\n"
<< "\"" << infoLog.c_str() << "\""
<< io::endl;
<< "\"" << infoLog.c_str() << "\"" << io::endl;
}
#ifndef NDEBUG
@@ -996,7 +824,7 @@ void logCompilationError(io::ostream& out, ShaderStage shaderType,
}
UTILS_NOINLINE
void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept {
/* static */ void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept {
GLint length = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
@@ -1004,9 +832,204 @@ void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noe
glGetProgramInfoLog(program, length, nullptr, infoLog.data());
out << "Link error in \"" << name << "\":\n"
<< "\"" << infoLog.c_str() << "\""
<< io::endl;
<< "\"" << infoLog.c_str() << "\"" << io::endl;
}
// If usages of the Google-style line directive are present, remove them, as some
// drivers don't allow the quotation marks. This source modification happens in-place.
/* static */ void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, char* source,
size_t len) noexcept {
if (!context.ext.GOOGLE_cpp_style_line_directive) {
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) {
removeGoogleLineDirectives(source, len);// length is unaffected
}
}
}
// Look up the `source` to replace the number of eyes for multiview with the given number. This is
// necessary for OpenGL because OpenGL relies on the number specified in shader files to determine
// the number of views, which is assumed as a single digit, for multiview.
// This source modification happens in-place.
/* static */ void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount, char* source,
size_t len) noexcept {
// We don't use regular expression in favor of performance.
if (context.ext.OVR_multiview2) {
const std::string_view shader{ source, len };
const std::string_view layout = "layout";
const std::string_view num_views = "num_views";
size_t found = 0;
while (true) {
found = shader.find(layout, found);
if (found == std::string_view::npos) {
break;
}
found = shader.find_first_not_of(' ', found + layout.size());
if (found == std::string_view::npos || shader[found] != '(') {
continue;
}
found = shader.find_first_not_of(' ', found + 1);
if (found == std::string_view::npos) {
continue;
}
if (shader.compare(found, num_views.size(), num_views) != 0) {
continue;
}
found = shader.find_first_not_of(' ', found + num_views.size());
if (found == std::string_view::npos || shader[found] != '=') {
continue;
}
found = shader.find_first_not_of(' ', found + 1);
if (found == std::string_view::npos) {
continue;
}
// We assume the value should be one-digit number.
assert_invariant(eyeCount < 10);
assert_invariant(!::isdigit(source[found + 1]));
source[found] = '0' + eyeCount;
break;
}
}
}
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
// macOS doesn't support GL_ARB_shading_language_packing
// Also GLES3.0 didn't have the full set of packing/unpacking functions
/* static */ std::string_view process_ARB_shading_language_packing(
OpenGLContext& context) noexcept {
using namespace std::literals;
#ifdef BACKEND_OPENGL_VERSION_GL
if (!context.isAtLeastGL<4, 2>() && !context.ext.ARB_shading_language_packing) {
return R"(
// these don't handle denormals, NaNs or inf
float u16tofp32(highp uint v) {
v <<= 16u;
highp uint s = v & 0x80000000u;
highp uint n = v & 0x7FFFFFFFu;
highp uint nz = (n == 0u) ? 0u : 0xFFFFFFFFu;
return uintBitsToFloat(s | ((((n >> 3u) + (0x70u << 23u))) & nz));
}
vec2 unpackHalf2x16(highp uint v) {
return vec2(u16tofp32(v&0xFFFFu), u16tofp32(v>>16u));
}
uint fp32tou16(float val) {
uint f32 = floatBitsToUint(val);
uint f16 = 0u;
uint sign = (f32 >> 16u) & 0x8000u;
int exponent = int((f32 >> 23u) & 0xFFu) - 127;
uint mantissa = f32 & 0x007FFFFFu;
if (exponent > 15) {
f16 = sign | (0x1Fu << 10u);
} else if (exponent > -15) {
exponent += 15;
mantissa >>= 13;
f16 = sign | uint(exponent << 10) | mantissa;
} else {
f16 = sign;
}
return f16;
}
highp uint packHalf2x16(vec2 v) {
highp uint x = fp32tou16(v.x);
highp uint y = fp32tou16(v.y);
return (y << 16u) | x;
}
highp uint packUnorm4x8(mediump vec4 v) {
v = round(clamp(v, 0.0, 1.0) * 255.0);
highp uint a = uint(v.x);
highp uint b = uint(v.y) << 8;
highp uint c = uint(v.z) << 16;
highp uint d = uint(v.w) << 24;
return (a|b|c|d);
}
highp uint packSnorm4x8(mediump vec4 v) {
v = round(clamp(v, -1.0, 1.0) * 127.0);
highp uint a = uint((int(v.x) & 0xff));
highp uint b = uint((int(v.y) & 0xff)) << 8;
highp uint c = uint((int(v.z) & 0xff)) << 16;
highp uint d = uint((int(v.w) & 0xff)) << 24;
return (a|b|c|d);
}
mediump vec4 unpackUnorm4x8(highp uint v) {
return vec4(float((v & 0x000000ffu) ),
float((v & 0x0000ff00u) >> 8),
float((v & 0x00ff0000u) >> 16),
float((v & 0xff000000u) >> 24)) / 255.0;
}
mediump vec4 unpackSnorm4x8(highp uint v) {
int a = int(((v ) & 0xffu) << 24u) >> 24 ;
int b = int(((v >> 8u) & 0xffu) << 24u) >> 24 ;
int c = int(((v >> 16u) & 0xffu) << 24u) >> 24 ;
int d = int(((v >> 24u) & 0xffu) << 24u) >> 24 ;
return clamp(vec4(float(a), float(b), float(c), float(d)) / 127.0, -1.0, 1.0);
}
)"sv;
}
#endif// BACKEND_OPENGL_VERSION_GL
#ifdef BACKEND_OPENGL_VERSION_GLES
if (!context.isES2() && !context.isAtLeastGLES<3, 1>()) {
return R"(
highp uint packUnorm4x8(mediump vec4 v) {
v = round(clamp(v, 0.0, 1.0) * 255.0);
highp uint a = uint(v.x);
highp uint b = uint(v.y) << 8;
highp uint c = uint(v.z) << 16;
highp uint d = uint(v.w) << 24;
return (a|b|c|d);
}
highp uint packSnorm4x8(mediump vec4 v) {
v = round(clamp(v, -1.0, 1.0) * 127.0);
highp uint a = uint((int(v.x) & 0xff));
highp uint b = uint((int(v.y) & 0xff)) << 8;
highp uint c = uint((int(v.z) & 0xff)) << 16;
highp uint d = uint((int(v.w) & 0xff)) << 24;
return (a|b|c|d);
}
mediump vec4 unpackUnorm4x8(highp uint v) {
return vec4(float((v & 0x000000ffu) ),
float((v & 0x0000ff00u) >> 8),
float((v & 0x00ff0000u) >> 16),
float((v & 0xff000000u) >> 24)) / 255.0;
}
mediump vec4 unpackSnorm4x8(highp uint v) {
int a = int(((v ) & 0xffu) << 24u) >> 24 ;
int b = int(((v >> 8u) & 0xffu) << 24u) >> 24 ;
int c = int(((v >> 16u) & 0xffu) << 24u) >> 24 ;
int d = int(((v >> 24u) & 0xffu) << 24u) >> 24 ;
return clamp(vec4(float(a), float(b), float(c), float(d)) / 127.0, -1.0, 1.0);
}
)"sv;
}
#endif// BACKEND_OPENGL_VERSION_GLES
return ""sv;
}
// split shader source code in three:
// - the version line
// - extensions
// - everything else
/* static */ std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept {
auto version_start = source.find("#version");
assert_invariant(version_start != std::string_view::npos);
auto version_eol = source.find('\n', version_start) + 1;
assert_invariant(version_eol != std::string_view::npos);
auto prolog_start = version_eol;
auto prolog_eol = source.rfind("\n#extension");// last #extension line
if (prolog_eol == std::string_view::npos) {
prolog_eol = prolog_start;
} else {
prolog_eol = source.find('\n', prolog_eol + 1) + 1;
}
auto body_start = prolog_eol;
std::string_view const version = source.substr(version_start, version_eol - version_start);
std::string_view const prolog = source.substr(prolog_start, prolog_eol - prolog_start);
std::string_view const body = source.substr(body_start, source.length() - body_start);
return { version, prolog, body };
}
} // namespace filament::backend
@@ -57,6 +57,8 @@ class ShaderCompilerService {
public:
using program_token_t = std::shared_ptr<OpenGLProgramToken>;
using shaders_t = std::array<GLuint, Program::SHADER_TYPE_COUNT>;
using shaders_source_t = std::array<utils::CString, Program::SHADER_TYPE_COUNT>;
explicit ShaderCompilerService(OpenGLDriver& driver);
@@ -134,22 +136,9 @@ private:
OpenGLContext& context,
Program::ShaderSource shadersSource,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
bool multiview,
std::array<GLuint, Program::SHADER_TYPE_COUNT>& outShaders,
std::array<utils::CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept;
bool multiview, shaders_t& outShaders, shaders_source_t& outShaderSourceCode) noexcept;
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
char* source, size_t len) noexcept;
static void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount,
char* source, size_t len) noexcept;
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
static std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept;
static GLuint linkProgram(OpenGLContext& context,
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders,
static GLuint linkProgram(OpenGLContext& context, shaders_t const& shaders,
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept;
static bool checkProgramStatus(program_token_t const& token) noexcept;
+5
View File
@@ -15,6 +15,7 @@
*/
#include <backend/BufferDescriptor.h>
#include <backend/BufferObjectStreamDescriptor.h>
#include <backend/DescriptorSetOffsetArray.h>
#include <backend/DriverEnums.h>
#include <backend/PipelineState.h>
@@ -437,6 +438,10 @@ io::ostream& operator<<(io::ostream& out, BufferDescriptor const& b) {
<< ", user=" << b.getUser() << " }";
}
io::ostream& operator<<(io::ostream& out, const BufferObjectStreamDescriptor& b) {
return out << "BufferObjectStreamDescriptor{ streams(" << b.mStreams.size() << ")=... }";
}
io::ostream& operator<<(io::ostream& out, PixelBufferDescriptor const& b) {
BufferDescriptor const& base = static_cast<BufferDescriptor const&>(b);
return out << "PixelBufferDescriptor{ " << base
@@ -17,9 +17,6 @@
#include "VulkanBlitter.h"
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include "VulkanFboCache.h"
#include "VulkanHandles.h"
#include "VulkanSamplerCache.h"
#include "VulkanTexture.h"
#include "vulkan/utils/Image.h"
@@ -24,13 +24,6 @@
namespace filament::backend {
class VulkanBuffer;
class VulkanFboCache;
class VulkanPipelineCache;
class VulkanSamplerCache;
struct VulkanProgram;
class VulkanBlitter {
public:
VulkanBlitter(VkPhysicalDevice physicalDevice, VulkanCommands* commands) noexcept;
@@ -56,9 +56,9 @@ VkImageView VulkanAttachment::getImageView() {
assert_invariant(texture);
VkImageSubresourceRange range = getSubresourceRange();
if (range.layerCount > 1) {
return texture->getMultiviewAttachmentView(range);
return texture->getAttachmentView(range, VK_IMAGE_VIEW_TYPE_2D_ARRAY);
}
return texture->getAttachmentView(range);
return texture->getAttachmentView(range, VK_IMAGE_VIEW_TYPE_2D);
}
bool VulkanAttachment::isDepth() const {
@@ -68,11 +68,11 @@ bool VulkanAttachment::isDepth() const {
VkImageSubresourceRange VulkanAttachment::getSubresourceRange() const {
assert_invariant(texture);
return {
.aspectMask = texture->getImageAspect(),
.baseMipLevel = uint32_t(level),
.levelCount = 1,
.baseArrayLayer = uint32_t(layer),
.layerCount = layerCount,
.aspectMask = texture->getImageAspect(),
.baseMipLevel = uint32_t(level),
.levelCount = 1,
.baseArrayLayer = uint32_t(layer),
.layerCount = layerCount,
};
}
+14 -10
View File
@@ -17,7 +17,6 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANCONTEXT_H
#define TNT_FILAMENT_BACKEND_VULKANCONTEXT_H
#include "VulkanConstants.h"
#include "vulkan/utils/Image.h"
#include "vulkan/utils/Definitions.h"
@@ -30,8 +29,6 @@
#include <bluevk/BlueVK.h>
#include <memory>
VK_DEFINE_HANDLE(VmaAllocator)
VK_DEFINE_HANDLE(VmaPool)
@@ -73,13 +70,11 @@ struct VulkanRenderPass {
// context are stored in VulkanPlatform.
struct VulkanContext {
public:
inline uint32_t selectMemoryType(uint32_t flags, VkFlags reqs) const {
if ((reqs & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) {
assert_invariant(isProtectedMemorySupported() == true);
}
static uint32_t selectMemoryType(VkPhysicalDeviceMemoryProperties const& memoryProperties,
uint32_t flags, VkFlags reqs) {
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
if (flags & 1) {
if ((mMemoryProperties.memoryTypes[i].propertyFlags & reqs) == reqs) {
if ((memoryProperties.memoryTypes[i].propertyFlags & reqs) == reqs) {
return i;
}
}
@@ -88,6 +83,13 @@ public:
return (uint32_t) VK_MAX_MEMORY_TYPES;
}
inline uint32_t selectMemoryType(uint32_t flags, VkFlags reqs) const {
if ((reqs & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) {
assert_invariant(isProtectedMemorySupported());
}
return selectMemoryType(mMemoryProperties, flags, reqs);
}
inline fvkutils::VkFormatList const& getAttachmentDepthStencilFormats() const {
return mDepthStencilFormats;
}
@@ -121,7 +123,7 @@ public:
}
inline bool isMultiviewEnabled() const noexcept {
return mMultiviewEnabled;
return mPhysicalDeviceVk11Features.multiview == VK_TRUE;
}
inline bool isClipDistanceSupported() const noexcept {
@@ -145,6 +147,9 @@ private:
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
};
VkPhysicalDeviceVulkan11Features mPhysicalDeviceVk11Features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
};
VkPhysicalDeviceFeatures2 mPhysicalDeviceFeatures = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
};
@@ -157,7 +162,6 @@ private:
};
bool mDebugMarkersSupported = false;
bool mDebugUtilsSupported = false;
bool mMultiviewEnabled = false;
bool mLazilyAllocatedMemorySupported = false;
bool mProtectedMemorySupported = false;
@@ -260,7 +260,6 @@ VulkanDescriptorSetCache::~VulkanDescriptorSetCache() = default;
void VulkanDescriptorSetCache::terminate() noexcept{
mDescriptorPool.reset();
clearHistory();
}
// bind() is not really binding the set but just stashing until we have all the info
@@ -283,18 +282,25 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
fvkutils::DescriptorSetMask curMask = setMask;
auto& updateSets = mStashedSets;
auto& lastBoundSets = mLastBoundInfo.boundSets;
bool const pipelineLayoutIsSame = mLastBoundInfo.pipelineLayout == pipelineLayout;
setMask.forEachSetBit([&](size_t index) {
if (!updateSets[index] || updateSets[index] == lastBoundSets[index]) {
curMask.unset(index);
if (pipelineLayoutIsSame) {
auto& lastBoundSets = mLastBoundInfo.boundSets;
setMask.forEachSetBit([&](size_t index) {
if (!updateSets[index] || updateSets[index] == lastBoundSets[index]) {
curMask.unset(index);
}
});
if (curMask.none() &&
mLastBoundInfo.setMask == setMask && mLastBoundInfo.boundSets == updateSets) {
return;
}
});
if (curMask.none() &&
(mLastBoundInfo.pipelineLayout == pipelineLayout && mLastBoundInfo.setMask == setMask &&
mLastBoundInfo.boundSets == updateSets)) {
return;
} else {
setMask.forEachSetBit([&](size_t index) {
if (!updateSets[index]) {
curMask.unset(index);
}
});
}
curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) {
@@ -302,7 +308,7 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
auto set = updateSets[index];
VkCommandBuffer const cmdbuffer = commands->buffer();
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
1, &set->vkSet, set->uniqueDynamicUboCount, set->getOffsets()->data());
1, &set->getVkSet(), set->uniqueDynamicUboCount, set->getOffsets()->data());
commands->acquire(set);
});
@@ -330,8 +336,7 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
}
VkWriteDescriptorSet const descriptorWrite = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
.dstSet = set->vkSet,
.dstSet = set->getVkSet(),
.dstBinding = binding,
.descriptorCount = 1,
.descriptorType = type,
@@ -344,26 +349,25 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
VkSampler sampler) noexcept {
VkDescriptorImageInfo info{
.sampler = sampler,
};
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
VkImageSubresourceRange 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);
// an attachment, then the range for this view has exactly one level and one layer.
range.levelCount = 1;
range.layerCount = 1;
}
info.imageLayout = fvkutils::getVkLayout(texture->getDefaultLayout());
VkDescriptorImageInfo info{
.sampler = sampler,
.imageView = texture->getView(range),
.imageLayout = fvkutils::getVkLayout(texture->getDefaultLayout()),
};
VkWriteDescriptorSet const descriptorWrite = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
.dstSet = set->vkSet,
.dstSet = set->getVkSet(),
.dstBinding = binding,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
@@ -381,10 +385,10 @@ void VulkanDescriptorSetCache::updateInputAttachment(
fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet(
Handle<HwDescriptorSet> handle, fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
auto const vkSet = mDescriptorPool->obtainSet(layout);
auto const vkSet = getVkSet(layout);
auto const& count = layout->count;
auto const vklayout = layout->getVkLayout();
return fvkmemory::resource_ptr<VulkanDescriptorSet>::make(mResourceManager, handle, vkSet,
auto set = fvkmemory::resource_ptr<VulkanDescriptorSet>::make(mResourceManager, handle,
layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
[vkSet, count, vklayout, this](VulkanDescriptorSet*) {
// Note that mDescriptorPool could be gone due to terminate (when the backend shuts
@@ -393,10 +397,20 @@ fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet
mDescriptorPool->recycle(count, vklayout, vkSet);
}
});
set->setVkSet(vkSet);
return set;
}
void VulkanDescriptorSetCache::clearHistory() {
mStashedSets = {};
VkDescriptorSet VulkanDescriptorSetCache::getVkSet(
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
return mDescriptorPool->obtainSet(layout);
}
void VulkanDescriptorSetCache::manualRecyle(VulkanDescriptorSetLayout::Count const& count,
VkDescriptorSetLayout vklayout, VkDescriptorSet vkSet) {
mDescriptorPool->recycle(count, vklayout, vkSet);
}
void VulkanDescriptorSetCache::gc() { mStashedSets = {}; }
} // namespace filament::backend
@@ -41,6 +41,8 @@ public:
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT;
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
using DescriptorSetArray =
std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>, UNIQUE_DESCRIPTOR_SET_COUNT>;
VulkanDescriptorSetCache(VkDevice device, fvkmemory::ResourceManager* resourceManager);
~VulkanDescriptorSetCache();
@@ -68,14 +70,21 @@ public:
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
void clearHistory();
// This method is only meant to be used with external samplers (or internally within this
// class).
VkDescriptorSet getVkSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
// This method is only meant to be used with external samplers.
void manualRecyle(VulkanDescriptorSetLayout::Count const& count, VkDescriptorSetLayout vklayout,
VkDescriptorSet vkSet);
DescriptorSetArray const& getBoundSets() const { return mStashedSets; }
void gc();
private:
class DescriptorInfinitePool;
using DescriptorSetArray =
std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>, UNIQUE_DESCRIPTOR_SET_COUNT>;
VkDevice mDevice;
fvkmemory::ResourceManager* mResourceManager;
std::unique_ptr<DescriptorInfinitePool> mDescriptorPool;
@@ -18,6 +18,8 @@
#include "VulkanHandles.h"
#include <utils/Hash.h>
namespace filament::backend {
namespace {
@@ -58,6 +60,56 @@ uint32_t appendBindings(VkDescriptorSetLayoutBinding* toBind, VkDescriptorType t
return count;
}
uint32_t appendSamplerBindings(VkDescriptorSetLayoutBinding* toBind,
fvkutils::SamplerBitmask const& mask, fvkutils::SamplerBitmask const& external,
utils::FixedCapacityVector<VkSampler> const& immutableSamplers) {
using Bitmask = fvkutils::SamplerBitmask;
uint32_t count = 0;
Bitmask alreadySeen;
uint8_t immutableIndex = 0;
size_t const immutableSamplerCount = immutableSamplers.size();
mask.forEachSetBit([&](size_t index) {
VkShaderStageFlags stages = 0;
uint32_t binding = 0;
if (index < fvkutils::getFragmentStageShift<Bitmask>()) {
binding = (uint32_t) index;
stages |= VK_SHADER_STAGE_VERTEX_BIT;
auto fragIndex = index + fvkutils::getFragmentStageShift<Bitmask>();
if (mask.test(fragIndex)) {
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
alreadySeen.set(fragIndex);
}
} else if (!alreadySeen.test(index)) {
// We are in fragment stage bits
binding = (uint32_t) (index - fvkutils::getFragmentStageShift<Bitmask>());
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
}
if (stages) {
toBind[count++] = {
.binding = binding,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = 1,
.stageFlags = stages,
.pImmutableSamplers = external[index] && immutableSamplerCount > immutableIndex
? &immutableSamplers[immutableIndex++]
: nullptr,
};
}
});
return count;
}
uint64_t computeImmutableSamplerHash(utils::FixedCapacityVector<VkSampler> const& samplers) {
size_t const size = samplers.size();
if (size == 0) {
return 0;
} else if (size == 1) {
return (uint64_t) samplers[0];
}
return utils::hash::murmur3((uint32_t*) samplers.data(), samplers.size() * 2, 0);
}
} // anonymous namespace
VulkanDescriptorSetLayoutCache::VulkanDescriptorSetLayoutCache(VkDevice device,
@@ -73,37 +125,44 @@ void VulkanDescriptorSetLayoutCache::terminate() noexcept {
}
}
VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
VulkanDescriptorSetLayout::Bitmask const& bitmasks,
utils::FixedCapacityVector<VkSampler> immutableSamplers) {
LayoutKey key = {
.bitmask = bitmasks,
.immutableSamplerHash = computeImmutableSamplerHash(immutableSamplers),
};
if (auto itr = mVkLayouts.find(key); itr != mVkLayouts.end()) {
return itr->second;
}
VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
uint32_t count = 0;
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
bitmasks.dynamicUbo);
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmasks.ubo);
count += appendSamplerBindings(&toBind[count], bitmasks.sampler, bitmasks.externalSampler,
immutableSamplers);
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
bitmasks.inputAttachment);
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
VkDescriptorSetLayoutCreateInfo dlinfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = count,
.pBindings = toBind,
};
VkDescriptorSetLayout vklayout;
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &vklayout);
mVkLayouts[key] = vklayout;
return vklayout;
}
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> VulkanDescriptorSetLayoutCache::createLayout(
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info) {
auto layout = fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::make(mResourceManager, handle,
info);
VkDescriptorSetLayout vklayout = VK_NULL_HANDLE;
auto const& bitmasks = layout->bitmask;
if (auto itr = mVkLayouts.find(bitmasks); itr != mVkLayouts.end()) {
vklayout = itr->second;
} else {
VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
uint32_t count = 0;
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
bitmasks.dynamicUbo);
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmasks.ubo);
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
bitmasks.sampler);
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
bitmasks.inputAttachment);
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
VkDescriptorSetLayoutCreateInfo dlinfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.pNext = nullptr,
.bindingCount = count,
.pBindings = toBind,
};
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &vklayout);
mVkLayouts[bitmasks] = vklayout;
}
layout->setVkLayout(vklayout);
layout->setVkLayout(getVkLayout(layout->bitmask));
return layout;
}
@@ -26,12 +26,11 @@
#include <backend/TargetBufferInfo.h>
#include <utils/bitset.h>
#include <utils/FixedCapacityVector.h>
#include <bluevk/BlueVK.h>
#include <tsl/robin_map.h>
#include <memory>
namespace filament::backend {
class VulkanDescriptorSetLayoutCache {
@@ -41,21 +40,34 @@ public:
void terminate() noexcept;
// Just a wrapper around getVkLayout()
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> createLayout(
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info);
// This method is meant to be used with external samplers
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks,
utils::FixedCapacityVector<VkSampler> immutableSamplers = {});
private:
VkDevice mDevice;
fvkmemory::ResourceManager* mResourceManager;
using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask;
using BitmaskGroupHashFn = utils::hash::MurmurHashFn<BitmaskGroup>;
struct BitmaskGroupEqual {
bool operator()(BitmaskGroup const& k1, BitmaskGroup const& k2) const { return k1 == k2; }
struct LayoutKey {
// this describes the layout using bitset.
VulkanDescriptorSetLayout::Bitmask bitmask = {};
// number of immutable samplers can be arbitrary; so we hash them into 64-bit.
uint64_t immutableSamplerHash = 0;
};
static_assert(sizeof(LayoutKey) == 48);
using LayoutKeyHashFn = utils::hash::MurmurHashFn<LayoutKey>;
struct LayoutKeyEqual {
bool operator()(LayoutKey const& k1, LayoutKey const& k2) const {
return k1.bitmask == k2.bitmask && k1.immutableSamplerHash == k2.immutableSamplerHash;
}
};
tsl::robin_map<BitmaskGroup, VkDescriptorSetLayout, BitmaskGroupHashFn, BitmaskGroupEqual>
mVkLayouts;
tsl::robin_map<LayoutKey, VkDescriptorSetLayout, LayoutKeyHashFn, LayoutKeyEqual> mVkLayouts;
};
} // namespace filament::backend
+23 -12
View File
@@ -25,10 +25,12 @@
#include "VulkanHandles.h"
#include "VulkanMemory.h"
#include "VulkanTexture.h"
#include "vulkan/VulkanSamplerCache.h"
#include "vulkan/memory/ResourceManager.h"
#include "vulkan/memory/ResourcePointer.h"
#include "vulkan/utils/Conversion.h"
#include "vulkan/utils/Definitions.h"
#include "vulkan/vulkan_core.h"
#include <backend/DriverEnums.h>
#include <backend/platforms/VulkanPlatform.h>
@@ -212,6 +214,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
mPipelineCache(mPlatform->getDevice()),
mStagePool(mAllocator, &mCommands),
mFramebufferCache(mPlatform->getDevice()),
mYcbcrConversionCache(mPlatform->getDevice()),
mSamplerCache(mPlatform->getDevice()),
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
mReadPixels(mPlatform->getDevice()),
@@ -324,7 +327,7 @@ void VulkanDriver::terminate() {
mStagePool.terminate();
mPipelineCache.terminate();
mFramebufferCache.reset();
mFramebufferCache.terminate();
mSamplerCache.terminate();
mDescriptorSetLayoutCache.terminate();
mDescriptorSetCache.terminate();
@@ -363,7 +366,7 @@ void VulkanDriver::collectGarbage() {
FVK_SYSTRACE_SCOPE();
// Command buffers need to be submitted and completed before other resources can be gc'd.
mCommands.gc();
mDescriptorSetCache.clearHistory();
mDescriptorSetCache.gc();
mStagePool.gc();
mFramebufferCache.gc();
mPipelineCache.gc();
@@ -418,7 +421,11 @@ void VulkanDriver::updateDescriptorSetTexture(
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
VkSampler const vksampler = mSamplerCache.getSampler(params);
// TODO: YcbcrConversion?
VulkanSamplerCache::Params cacheParams = {
.sampler = params,
};
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
}
@@ -556,7 +563,7 @@ void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::S
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
Platform::ExternalImageHandleRef externalImage) {
FVK_SYSTRACE_SCOPE();
const auto& metadata = mPlatform->getExternalImageMetadata(externalImage);
auto const& metadata = mPlatform->getExternalImageMetadata(externalImage);
if (metadata.isProtected) {
usage |= backend::TextureUsage::PROTECTED;
}
@@ -566,7 +573,7 @@ void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::S
vkUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
if (any(usage & (TextureUsage::BLIT_DST & TextureUsage::UPLOADABLE))) {
if (any(usage & (TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE))) {
vkUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
@@ -582,13 +589,15 @@ void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::S
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex != VK_MAX_MEMORY_TYPES)
<< "failed to find a valid memory type for external image memory.";
const auto& data =
VkImage vkimg;
VkDeviceMemory deviceMemory;
std::tie(vkimg, deviceMemory) =
mPlatform->createExternalImageData(externalImage, metadata, memoryTypeIndex, vkUsage);
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mPlatform->getDevice(),
mAllocator, &mResourceManager, &mCommands, data.first, data.second, metadata.format,
metadata.samples, metadata.width, metadata.height, metadata.layerCount, usage,
mStagePool);
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mContext,
mPlatform->getDevice(), mAllocator, &mResourceManager, &mCommands, vkimg, deviceMemory,
metadata.format, VK_NULL_HANDLE, metadata.samples, metadata.width, metadata.height,
metadata.layerCount, usage, mStagePool);
texture.inc();
}
@@ -597,13 +606,14 @@ void VulkanDriver::createTextureExternalImageR(Handle<HwTexture> th, backend::Sa
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
void* externalImage) {
FVK_SYSTRACE_SCOPE();
assert_invariant(false && "Not supported in Vulkan backend");
// not supported in this backend
}
void VulkanDriver::createTextureExternalImagePlaneR(Handle<HwTexture> th,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
void* image, uint32_t plane) {
assert_invariant(false && "Not supported in Vulkan backend");
}
void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
@@ -611,6 +621,7 @@ void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage usage) {
// not supported in this backend
assert_invariant(false && "Not supported in Vulkan backend");
}
void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
@@ -1512,7 +1523,7 @@ void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
swapChain->acquire(resized);
if (resized) {
mFramebufferCache.reset();
mFramebufferCache.resetFramebuffers();
}
if (UTILS_LIKELY(mDefaultRenderTarget)) {
@@ -27,6 +27,7 @@
#include "VulkanSamplerCache.h"
#include "VulkanStagePool.h"
#include "VulkanQueryManager.h"
#include "VulkanYcbcrConversionCache.h"
#include "vulkan/VulkanDescriptorSetCache.h"
#include "vulkan/VulkanDescriptorSetLayoutCache.h"
#include "vulkan/VulkanPipelineLayoutCache.h"
@@ -135,6 +136,7 @@ private:
VulkanPipelineCache mPipelineCache;
VulkanStagePool mStagePool;
VulkanFboCache mFramebufferCache;
VulkanYcbcrConversionCache mYcbcrConversionCache;
VulkanSamplerCache mSamplerCache;
VulkanBlitter mBlitter;
VulkanReadPixels mReadPixels;
@@ -340,15 +340,21 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
return renderPass;
}
void VulkanFboCache::reset() noexcept {
for (auto pair : mFramebufferCache) {
void VulkanFboCache::resetFramebuffers() noexcept {
for (const auto& pair: mFramebufferCache) {
mRenderPassRefCount[pair.first.renderPass]--;
vkDestroyFramebuffer(mDevice, pair.second.handle, VKALLOC);
}
mFramebufferCache.clear();
for (auto pair : mRenderPassCache) {
}
void VulkanFboCache::terminate() noexcept {
resetFramebuffers();
for (const auto& pair: mRenderPassCache) {
vkDestroyRenderPass(mDevice, pair.second.handle, VKALLOC);
}
mRenderPassRefCount.clear();
mRenderPassCache.clear();
}
+4 -1
View File
@@ -106,8 +106,11 @@ public:
// Evicts old unused Vulkan objects. Call this once per frame.
void gc() noexcept;
// Frees all Framebuffer objects. Call this every time a the swapchain is resized
void resetFramebuffers() noexcept;
// Frees all Vulkan objects. Call this during shutdown before the device is destroyed.
void reset() noexcept;
void terminate() noexcept;
private:
VkDevice mDevice;
@@ -30,6 +30,7 @@
#include <backend/platforms/VulkanPlatform.h>
#include <utils/compiler.h> // UTILS_FALLTHROUGH
#include <utils/Panic.h> // ASSERT_POSTCONDITION
using namespace bluevk;
@@ -89,8 +90,9 @@ BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
}
break;
}
// TODO: properly handle external sampler
case DescriptorType::SAMPLER_EXTERNAL:
fromStageFlags(binding.stageFlags, binding.binding, mask.externalSampler);
UTILS_FALLTHROUGH;
case DescriptorType::SAMPLER: {
fromStageFlags(binding.stageFlags, binding.binding, mask.sampler);
break;
+24 -9
View File
@@ -66,18 +66,21 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
// The bitmask representation of a set layout.
struct Bitmask {
// TODO: better utiltize the space below and use bitset instead.
fvkutils::UniformBufferBitmask ubo; // 8 bytes
fvkutils::UniformBufferBitmask dynamicUbo; // 8 bytes
fvkutils::SamplerBitmask sampler; // 8 bytes
fvkutils::InputAttachmentBitmask inputAttachment; // 8 bytes
// This is a subset of the bitmask.sampler field.
fvkutils::SamplerBitmask externalSampler; // 8 bytes
bool operator==(Bitmask const& right) const {
return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler &&
inputAttachment == right.inputAttachment;
inputAttachment == right.inputAttachment &&
externalSampler == right.externalSampler;
}
};
static_assert(sizeof(Bitmask) == 32);
static_assert(sizeof(Bitmask) == 40);
// This is a convenience struct to quickly check layout compatibility in terms of descriptor set
// pools.
@@ -119,10 +122,16 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
VulkanDescriptorSetLayout(DescriptorSetLayout const& layout);
// Note that we don't destroy the vklayout. This is done by the layout cache.
~VulkanDescriptorSetLayout() = default;
VkDescriptorSetLayout getVkLayout() const { return mVkLayout; }
void setVkLayout(VkDescriptorSetLayout vklayout) { mVkLayout = vklayout; }
VkDescriptorSetLayout const& getVkLayout() const noexcept { return mVkLayout; }
// It is possible to have the layout switch out due to AHardwarebuffer (external image) format
// changes.
void setVkLayout(VkDescriptorSetLayout vklayout) noexcept { mVkLayout = vklayout; }
bool hasExternalSamplers() const noexcept { return bitmask.externalSampler.count() > 0; }
Bitmask const bitmask;
Count const count;
@@ -137,12 +146,11 @@ public:
// can use to repackage the vk handle.
using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
VulkanDescriptorSet(VkDescriptorSet rawSet,
VulkanDescriptorSet(
fvkutils::UniformBufferBitmask const& dynamicUboMask,
uint8_t uniqueDynamicUboCount,
OnRecycle&& onRecycleFn)
: vkSet(rawSet),
dynamicUboMask(dynamicUboMask),
: dynamicUboMask(dynamicUboMask),
uniqueDynamicUboCount(uniqueDynamicUboCount),
mOnRecycleFn(std::move(onRecycleFn)) {}
@@ -152,6 +160,13 @@ public:
}
}
VkDescriptorSet const& getVkSet() const noexcept {
return mVkSet;
}
// Note that the only case where you'd set it more than once is with external images/samplers.
void setVkSet(VkDescriptorSet vkset) noexcept { mVkSet = vkset; }
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
mOffsets = std::move(offsets);
}
@@ -163,11 +178,11 @@ public:
void acquire(fvkmemory::resource_ptr<VulkanTexture> texture);
void acquire(fvkmemory::resource_ptr<VulkanBufferObject> buffer);
VkDescriptorSet const vkSet;
fvkutils::UniformBufferBitmask const dynamicUboMask;
uint8_t const uniqueDynamicUboCount;
private:
VkDescriptorSet mVkSet = VK_NULL_HANDLE;
backend::DescriptorSetOffsetArray mOffsets;
std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources;
OnRecycle mOnRecycleFn;
@@ -18,24 +18,18 @@
#define TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
#include "VulkanCommands.h"
#include "VulkanMemory.h"
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
#include "backend/Program.h"
#include <bluevk/BlueVK.h>
#include <utils/bitset.h>
#include <utils/compiler.h>
#include <utils/Hash.h>
#include <list>
#include <tsl/robin_map.h>
#include <type_traits>
#include <vector>
#include <unordered_map>
namespace filament::backend {
@@ -14,7 +14,9 @@
* limitations under the License.
*/
#include "vulkan/VulkanSamplerCache.h"
#include "VulkanSamplerCache.h"
#include "VulkanConstants.h"
#include "vulkan/utils/Conversion.h"
#include <utils/Panic.h>
@@ -26,41 +28,48 @@ namespace filament::backend {
VulkanSamplerCache::VulkanSamplerCache(VkDevice device)
: mDevice(device) {}
VkSampler VulkanSamplerCache::getSampler(SamplerParams params) noexcept {
VkSampler VulkanSamplerCache::getSampler(Params params) noexcept {
auto iter = mCache.find(params);
if (UTILS_LIKELY(iter != mCache.end())) {
return iter->second;
}
VkSamplerCreateInfo samplerInfo {
VkSamplerYcbcrConversionInfo ycbcrConversion = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
.conversion = params.conversion,
};
auto const& samplerParams = params.sampler;
VkSamplerCreateInfo samplerInfo{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.magFilter = fvkutils::getFilter(params.filterMag),
.minFilter = fvkutils::getFilter(params.filterMin),
.mipmapMode = fvkutils::getMipmapMode(params.filterMin),
.addressModeU = fvkutils::getWrapMode(params.wrapS),
.addressModeV = fvkutils::getWrapMode(params.wrapT),
.addressModeW = fvkutils::getWrapMode(params.wrapR),
.anisotropyEnable = params.anisotropyLog2 == 0 ? VK_FALSE : VK_TRUE,
.maxAnisotropy = (float)(1u << params.anisotropyLog2),
.compareEnable = fvkutils::getCompareEnable(params.compareMode),
.compareOp = fvkutils::getCompareOp(params.compareFunc),
.pNext = params.conversion != VK_NULL_HANDLE ? &ycbcrConversion : VK_NULL_HANDLE,
.magFilter = fvkutils::getFilter(samplerParams.filterMag),
.minFilter = fvkutils::getFilter(samplerParams.filterMin),
.mipmapMode = fvkutils::getMipmapMode(samplerParams.filterMin),
.addressModeU = fvkutils::getWrapMode(samplerParams.wrapS),
.addressModeV = fvkutils::getWrapMode(samplerParams.wrapT),
.addressModeW = fvkutils::getWrapMode(samplerParams.wrapR),
.anisotropyEnable = samplerParams.anisotropyLog2 == 0 ? VK_FALSE : VK_TRUE,
.maxAnisotropy = (float) (1u << samplerParams.anisotropyLog2),
.compareEnable = fvkutils::getCompareEnable(samplerParams.compareMode),
.compareOp = fvkutils::getCompareOp(samplerParams.compareFunc),
.minLod = 0.0f,
.maxLod = fvkutils::getMaxLod(params.filterMin),
.maxLod = fvkutils::getMaxLod(samplerParams.filterMin),
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
.unnormalizedCoordinates = VK_FALSE
.unnormalizedCoordinates = VK_FALSE,
};
VkSampler sampler;
VkResult result = vkCreateSampler(mDevice, &samplerInfo, VKALLOC, &sampler);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create sampler."
<< " error=" << static_cast<int32_t>(result);
mCache.insert({params, sampler});
mCache.insert({ params, sampler });
return sampler;
}
void VulkanSamplerCache::terminate() noexcept {
for (auto pair : mCache) {
for (auto pair: mCache) {
vkDestroySampler(mDevice, pair.second, VKALLOC);
}
mCache.clear();
}
} // namespace filament::backend
}// namespace filament::backend
@@ -17,8 +17,11 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANSAMPLERCACHE_H
#define TNT_FILAMENT_BACKEND_VULKANSAMPLERCACHE_H
#include "VulkanContext.h"
#include <backend/DriverEnums.h>
#include <utils/Hash.h>
#include <bluevk/BlueVK.h>
#include <tsl/robin_map.h>
namespace filament::backend {
@@ -26,12 +29,28 @@ namespace filament::backend {
// Simple manager for VkSampler objects.
class VulkanSamplerCache {
public:
struct Params {
SamplerParams sampler = {};
uint32_t padding = 0;
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
};
static_assert(sizeof(Params) == 16);
explicit VulkanSamplerCache(VkDevice device);
VkSampler getSampler(SamplerParams params) noexcept;
VkSampler getSampler(Params params) noexcept;
void terminate() noexcept;
private:
VkDevice mDevice;
tsl::robin_map<SamplerParams, VkSampler, SamplerParams::Hasher, SamplerParams::EqualTo> mCache;
struct SamplerEqualTo {
bool operator()(Params lhs, Params rhs) const noexcept {
SamplerParams::EqualTo equal;
return equal(lhs.sampler, rhs.sampler) && lhs.conversion == rhs.conversion;
}
};
using SamplerHashFn = utils::hash::MurmurHashFn<Params>;
tsl::robin_map<Params, VkSampler, SamplerHashFn, SamplerEqualTo> mCache;
};
} // namespace filament::backend
@@ -32,6 +32,7 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
VulkanCommands* commands, VulkanStagePool& stagePool, void* nativeWindow, uint64_t flags,
VkExtent2D extent)
: mPlatform(platform),
mContext(context),
mResourceManager(resourceManager),
mCommands(commands),
mAllocator(allocator),
@@ -77,16 +78,16 @@ void VulkanSwapChain::update() {
}
for (auto const color: bundle.colors) {
auto colorTexture = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager,
device, mAllocator, mResourceManager, mCommands, color, VK_NULL_HANDLE,
bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height, bundle.layerCount, colorUsage,
mStagePool);
mContext, device, mAllocator, mResourceManager, mCommands, color, VK_NULL_HANDLE,
bundle.colorFormat, VK_NULL_HANDLE /*ycrcb */, 1, bundle.extent.width,
bundle.extent.height, bundle.layerCount, colorUsage, mStagePool);
mColors.push_back(colorTexture);
}
mDepth = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager, device,
mAllocator, mResourceManager, mCommands, bundle.depth, VK_NULL_HANDLE,
bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height, bundle.layerCount, depthUsage,
mStagePool);
mDepth = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager, mContext, device,
mAllocator, mResourceManager, mCommands, bundle.depth, VK_NULL_HANDLE,
bundle.depthFormat, VK_NULL_HANDLE /*ycrcb */, 1, bundle.extent.width,
bundle.extent.height, bundle.layerCount, depthUsage, mStagePool);
mExtent = bundle.extent;
mLayerCount = bundle.layerCount;
@@ -107,11 +108,6 @@ void VulkanSwapChain::present() {
mCommands->flush();
// call the image ready wait function
if (mExplicitImageReadyWait != nullptr) {
mExplicitImageReadyWait(swapChain);
}
// We only present if it is not headless. No-op for headless.
if (!mHeadless) {
VkSemaphore const finishedDrawing = mCommands->acquireFinishedSignal();
@@ -145,7 +141,6 @@ void VulkanSwapChain::acquire(bool& resized) {
VulkanPlatform::ImageSyncData imageSyncData;
VkResult const result = mPlatform->acquire(swapChain, &imageSyncData);
mCurrentSwapIndex = imageSyncData.imageIndex;
mExplicitImageReadyWait = imageSyncData.explicitImageReadyWait;
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
<< "Cannot acquire in swapchain. error=" << static_cast<int32_t>(result);
if (imageSyncData.imageReadySemaphore != VK_NULL_HANDLE) {
@@ -19,6 +19,7 @@
#include "DriverBase.h"
#include "VulkanConstants.h"
#include "VulkanContext.h"
#include "VulkanTexture.h"
#include "vulkan/memory/Resource.h"
@@ -28,7 +29,6 @@
#include <bluevk/BlueVK.h>
#include <utils/FixedCapacityVector.h>
#include <memory>
using namespace bluevk;
@@ -49,7 +49,9 @@ struct VulkanSwapChain : public HwSwapChain, fvkmemory::Resource {
void present();
void acquire(bool& reized);
// Acquire a new image from the swapchain. If the image is not available it would wait until it
// is.
void acquire(bool& resized);
fvkmemory::resource_ptr<VulkanTexture> getCurrentColor() const noexcept {
uint32_t const imageIndex = mCurrentSwapIndex;
@@ -83,6 +85,7 @@ private:
void update();
VulkanPlatform* mPlatform;
VulkanContext const& mContext;
fvkmemory::ResourceManager* mResourceManager;
VulkanCommands* mCommands;
VmaAllocator mAllocator;
@@ -98,7 +101,6 @@ private:
VkExtent2D mExtent;
uint32_t mLayerCount;
uint32_t mCurrentSwapIndex;
std::function<void(Platform::SwapChain* handle)> mExplicitImageReadyWait = nullptr;
bool mAcquired;
bool mIsFirstRenderPass;
};
+210 -169
View File
@@ -15,6 +15,7 @@
*/
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include "VulkanMemory.h"
#include "VulkanTexture.h"
#include "vulkan/memory/ResourcePointer.h"
@@ -54,7 +55,6 @@ VkComponentMapping composeSwizzle(VkComponentMapping const& prev, VkComponentMap
VK_COMPONENT_SWIZZLE_B,
VK_COMPONENT_SWIZZLE_A,
};
auto const compose = [](VkComponentMapping const& prev,
VkComponentMapping const& next) -> VkComponentMapping {
VkComponentSwizzle vals[4] = { next.r, next.g, next.b, next.a };
@@ -151,35 +151,152 @@ uint8_t getLayerCountFromDepth(uint32_t const depth) {
return getLayerCount(getSamplerTypeFromDepth(depth), depth);
}
VkImageUsageFlags getUsage(VulkanContext const& context, uint8_t samples,
VkPhysicalDevice physicalDevice, VkFormat vkFormat, TextureUsage tusage) {
VkImageUsageFlags usage = {};
if (any(tusage & TextureUsage::BLIT_SRC)) {
usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
if (any(tusage & TextureUsage::BLIT_DST)) {
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) &&
// Depth resolve cannot use transient attachment because it uses a custom shader.
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
// restriction.
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
// and above to be able to support stencil resolve (along with depth).
!(any(tusage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
const VkImageUsageFlags transientFlag =
useTransientAttachment ? VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : 0U;
if (any(tusage & TextureUsage::SAMPLEABLE)) {
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
if (physicalDevice != VK_NULL_HANDLE) {
// Validate that the format is actually sampleable.
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(physicalDevice, vkFormat, &props);
if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << mState->mVkFormat << " is not "
"sampleable with optimal tiling." << utils::io::endl;
}
}
#endif
usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
if (any(tusage & TextureUsage::COLOR_ATTACHMENT)) {
usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | transientFlag;
if (any(tusage & TextureUsage::SUBPASS_INPUT)) {
usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
}
}
if (any(tusage & TextureUsage::STENCIL_ATTACHMENT)) {
usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag;
}
if (any(tusage & TextureUsage::UPLOADABLE)) {
usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
if (any(tusage & TextureUsage::DEPTH_ATTACHMENT)) {
usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag;
// Depth resolves uses a custom shader and therefore needs to be sampleable.
if (samples > 1) {
usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
}
return usage;
}
} // anonymous namespace
VulkanTextureState::VulkanTextureState(VkDevice device, VmaAllocator allocator,
VulkanCommands* commands, VulkanStagePool& stagePool, VkFormat format,
VkImageViewType viewType, uint8_t levels, uint8_t layerCount, VulkanLayout defaultLayout,
VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands,
VmaAllocator allocator, VkDevice device, VkImage image, VkDeviceMemory deviceMemory,
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount,
VkSamplerYcbcrConversion ycbcrConversion, bool isExternalFormat, VkImageUsageFlags usage,
bool isProtected)
: mVkFormat(format),
mViewType(viewType),
mFullViewRange{fvkutils::getImageAspect(format), 0, levels, 0, layerCount},
mDefaultLayout(defaultLayout),
mIsProtected(isProtected),
mStagePool(stagePool),
mDevice(device),
mAllocator(allocator),
: mStagePool(stagePool),
mCommands(commands),
mIsTransientAttachment(false) {}
mAllocator(allocator),
mDevice(device),
mTextureImage(image),
mTextureImageMemory(deviceMemory),
mVkFormat(format),
mViewType(viewType),
mFullViewRange{ fvkutils::getImageAspect(format), 0, levels, 0, layerCount },
mYcbcr{ ycbcrConversion, isExternalFormat },
mDefaultLayout(getDefaultLayoutImpl(usage)),
mUsage(usage),
mIsProtected(isProtected) {}
// Constructor for internally passed VkImage
VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator,
VulkanTextureState::~VulkanTextureState() {
if (mTextureImageMemory != VK_NULL_HANDLE) {
vkDestroyImage(mDevice, mTextureImage, VKALLOC);
vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC);
}
clearCachedImageViews();
}
void VulkanTextureState::clearCachedImageViews() noexcept {
for (auto entry: mCachedImageViews) {
vkDestroyImageView(mDevice, entry.second, VKALLOC);
}
mCachedImageViews.clear();
}
VkImageView VulkanTextureState::getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
VkComponentMapping swizzle) {
ImageViewKey const key{ range, viewType, swizzle };
if (auto iter = mCachedImageViews.find(key); iter != mCachedImageViews.end()) {
return iter->second;
}
VkSamplerYcbcrConversionInfo conversionInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
.conversion = mYcbcr.conversion,
};
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = mYcbcr.conversion != VK_NULL_HANDLE ? &conversionInfo : nullptr,
.flags = 0,
.image = mTextureImage,
.viewType = viewType,
.format = mYcbcr.isExternalFormat ? VK_FORMAT_UNDEFINED : mVkFormat,
.components = swizzle,
.subresourceRange = range,
};
VkImageView imageView;
vkCreateImageView(mDevice, &viewInfo, VKALLOC, &imageView);
mCachedImageViews.emplace(key, imageView);
return imageView;
}
// Constructor for internally passed VkImage - including swapchain images and external images.
VulkanTexture::VulkanTexture(VulkanContext const& context, VkDevice device, VmaAllocator allocator,
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, VkImage image,
VkDeviceMemory memory, VkFormat format, uint8_t samples, uint32_t width,
uint32_t height, uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool)
: HwTexture(getSamplerTypeFromDepth(depth), 1, samples, width, height, depth, TextureFormat::UNUSED,
tusage),
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, device,
allocator, commands, stagePool, format, fvkutils::getViewType(SamplerType::SAMPLER_2D),
/*mipLevels=*/1, getLayerCountFromDepth(depth), getDefaultLayoutImpl(tusage), any(usage & TextureUsage::PROTECTED))) {
mState->mTextureImage = image;
mState->mTextureImageMemory = memory;
VkDeviceMemory memory, VkFormat format, VkSamplerYcbcrConversion conversion,
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage tusage,
VulkanStagePool& stagePool)
: HwTexture(getSamplerTypeFromDepth(depth), 1, samples, width, height, depth,
TextureFormat::UNUSED, tusage),
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, stagePool,
commands, allocator, device, image, memory, format,
fvkutils::getViewType(SamplerType::SAMPLER_2D),
/*mipLevels=*/1, getLayerCountFromDepth(depth), conversion,
/*isExternalFormat=*/false,
getUsage(context, samples, VK_NULL_HANDLE, format, tusage),
any(usage & TextureUsage::PROTECTED))) {
mPrimaryViewRange = mState->mFullViewRange;
}
@@ -189,21 +306,19 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, SamplerType target,
uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h,
uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool)
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage),
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, device,
allocator, commands, stagePool, fvkutils::getVkFormat(tformat),
fvkutils::getViewType(target), levels, getLayerCount(target, depth),
VulkanLayout::UNDEFINED, any(usage & TextureUsage::PROTECTED))) {
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage) {
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
VkFormat const vkFormat = fvkutils::getVkFormat(tformat);
bool const isProtected = any(tusage & TextureUsage::PROTECTED);
VkImageCreateInfo imageInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,
.format = mState->mVkFormat,
.format = vkFormat,
.extent = {w, h, depth},
.mipLevels = levels,
.arrayLayers = 1,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = 0,
.usage = getUsage(context, samples, physicalDevice, vkFormat, tusage),
};
if (target == SamplerType::SAMPLER_3D && any(tusage & TextureUsage::ALL_ATTACHMENTS)) {
if (context.isImageView2DOn3DImageSupported()) {
@@ -233,79 +348,17 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.arrayLayers = depth * 6;
imageInfo.extent.depth = 1;
}
if (any(usage & TextureUsage::PROTECTED)) {
if (isProtected) {
imageInfo.flags |= VK_IMAGE_CREATE_PROTECTED_BIT;
}
if (any(usage & TextureUsage::BLIT_SRC)) {
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
if (any(usage & TextureUsage::BLIT_DST)) {
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) &&
// Depth resolve cannot use transient attachment because it uses a custom shader.
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
// restriction.
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
// and above to be able to support stencil resolve (along with depth).
!(any(usage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
mState->mIsTransientAttachment = useTransientAttachment;
const VkImageUsageFlags transientFlag =
useTransientAttachment ? VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : 0U;
if (any(usage & TextureUsage::SAMPLEABLE)) {
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
// Validate that the format is actually sampleable.
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(physicalDevice, mState->mVkFormat, &props);
if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << mState->mVkFormat << " is not "
"sampleable with optimal tiling." << utils::io::endl;
}
#endif
imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
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 | 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 | transientFlag;
// Depth resolves uses a custom shader and therefore needs to be sampleable.
if (samples > 1) {
imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
}
// Constrain the sample count according to the sample count masks in VkPhysicalDeviceProperties.
// Note that VulkanRenderTarget holds a single MSAA count, so we play it safe if this is used as
// any kind of attachment (color or depth).
const auto& limits = context.getPhysicalDeviceLimits();
auto const& limits = context.getPhysicalDeviceLimits();
if (imageInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) {
samples = fvkutils::reduceSampleCount(samples,
fvkutils::isVkDepthFormat(mState->mVkFormat)
fvkutils::isVkDepthFormat(vkFormat)
? limits.sampledImageDepthSampleCounts
: limits.sampledImageColorSampleCounts);
}
@@ -319,33 +372,36 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
this->samples = samples;
imageInfo.samples = (VkSampleCountFlagBits) samples;
VkResult result = vkCreateImage(mState->mDevice, &imageInfo, VKALLOC, &mState->mTextureImage);
VkImage textureImage;
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &textureImage);
if (result != VK_SUCCESS || FVK_ENABLED(FVK_DEBUG_TEXTURE)) {
FVK_LOGD << "vkCreateImage: "
<< "image = " << mState->mTextureImage << ", "
<< "image = " << textureImage << ", "
<< "result = " << result << ", "
<< "handle = " << utils::io::hex << mState->mTextureImage << utils::io::dec << ", "
<< "handle = " << utils::io::hex << textureImage << utils::io::dec << ", "
<< "extent = " << w << "x" << h << "x"<< depth << ", "
<< "mipLevels = " << int(levels) << ", "
<< "TextureUsage = " << static_cast<int>(usage) << ", "
<< "TextureUsage = " << static_cast<int>(tusage) << ", "
<< "usage = " << imageInfo.usage << ", "
<< "samples = " << imageInfo.samples << ", "
<< "type = " << imageInfo.imageType << ", "
<< "flags = " << imageInfo.flags << ", "
<< "target = " << static_cast<int>(target) <<", "
<< "format = " << mState->mVkFormat << utils::io::endl;
<< "format = " << vkFormat << utils::io::endl;
}
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create image."
<< " error=" << static_cast<int32_t>(result);
// Allocate memory for the VkImage and bind it.
VkMemoryRequirements memReqs = {};
vkGetImageMemoryRequirements(mState->mDevice, mState->mTextureImage, &memReqs);
VkMemoryRequirements memReqs;
vkGetImageMemoryRequirements(device, textureImage, &memReqs);
const VkFlags requiredMemoryFlags =
bool const useTransientAttachment = imageInfo.usage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
VkFlags const requiredMemoryFlags =
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
(useTransientAttachment ? VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT : 0U) |
(mState->mIsProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT : 0U);
(isProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT : 0U);
uint32_t memoryTypeIndex
= context.selectMemoryType(memReqs.memoryTypeBits, requiredMemoryFlags);
@@ -357,21 +413,22 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
.allocationSize = memReqs.size,
.memoryTypeIndex = memoryTypeIndex,
};
result = vkAllocateMemory(mState->mDevice, &allocInfo, nullptr, &mState->mTextureImageMemory);
VkDeviceMemory textureImageMemory;
result = vkAllocateMemory(device, &allocInfo, nullptr, &textureImageMemory);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to allocate image memory."
<< " error=" << static_cast<int32_t>(result);
result = vkBindImageMemory(mState->mDevice, mState->mTextureImage, mState->mTextureImageMemory,
0);
result = vkBindImageMemory(device, textureImage, textureImageMemory, 0);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to bind image."
<< " error=" << static_cast<int32_t>(result);
mState = fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, stagePool,
commands, allocator, device, textureImage, textureImageMemory, vkFormat,
fvkutils::getViewType(target), levels, getLayerCount(target, depth),
VK_NULL_HANDLE /* ycbcrConversion */, false /*isExternalFormat*/, imageInfo.usage,
isProtected);
// Spec out the "primary" VkImageView that shaders use to sample from the image.
mPrimaryViewRange = mState->mFullViewRange;
// Go ahead and create the primary image view.
getImageView(mPrimaryViewRange, mState->mViewType, mSwizzle);
mState->mDefaultLayout = getDefaultLayoutImpl(imageInfo.usage);
}
// Constructor for creating a texture view
@@ -380,8 +437,8 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
fvkmemory::resource_ptr<VulkanTexture> src, uint8_t baseLevel,
uint8_t levelCount)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage) {
mState = src->mState;
src->format, src->usage),
mState(src->mState) {
mPrimaryViewRange = src->mPrimaryViewRange;
mPrimaryViewRange.baseMipLevel = src->mPrimaryViewRange.baseMipLevel + baseLevel;
mPrimaryViewRange.levelCount = levelCount;
@@ -392,21 +449,10 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
fvkmemory::resource_ptr<VulkanTexture> src, VkComponentMapping swizzle)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage) {
mState = src->mState;
mPrimaryViewRange = src->mPrimaryViewRange;
mSwizzle = composeSwizzle(src->mSwizzle, swizzle);
}
VulkanTextureState::~VulkanTextureState() {
if (mTextureImageMemory != VK_NULL_HANDLE) {
vkDestroyImage(mDevice, mTextureImage, VKALLOC);
vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC);
}
for (auto entry: mCachedImageViews) {
vkDestroyImageView(mDevice, entry.second, VKALLOC);
}
}
src->format, src->usage),
mState(src->mState),
mPrimaryViewRange(src->mPrimaryViewRange),
mSwizzle(composeSwizzle(src->mSwizzle, swizzle)) {}
void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel) {
@@ -513,20 +559,20 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
commands.acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
// TODO: support blit-based format conversion for 3D images and cubemaps.
const int layer = 0;
constexpr int layer = 0;
const VkOffset3D rect[2] { {0, 0, 0}, {int32_t(width), int32_t(height), 1} };
VkOffset3D const rect[2] { {0, 0, 0}, {int32_t(width), int32_t(height), 1} };
const VkImageAspectFlags aspect = getImageAspect();
VkImageAspectFlags const aspect = getImageAspect();
const VkImageBlit blitRegions[1] = {{
VkImageBlit const blitRegions[1] = {{
.srcSubresource = { aspect, 0, 0, 1 },
.srcOffsets = { rect[0], rect[1] },
.dstSubresource = { aspect, uint32_t(miplevel), layer, 1 },
.dstOffsets = { rect[0], rect[1] }
}};
const VkImageSubresourceRange range = { aspect, miplevel, 1, layer, 1 };
VkImageSubresourceRange const range = { aspect, miplevel, 1, layer, 1 };
VulkanLayout const newLayout = VulkanLayout::TRANSFER_DST;
VulkanLayout const oldLayout = getLayout(layer, miplevel);
@@ -542,41 +588,21 @@ VulkanLayout VulkanTexture::getDefaultLayout() const {
return mState->mDefaultLayout;
}
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
range.levelCount = 1;
range.layerCount = 1;
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D, {});
}
VkImageView VulkanTexture::getMultiviewAttachmentView(VkImageSubresourceRange range) {
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D_ARRAY, {});
}
VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range, VkImageViewType type) {
return getImageView(range, type, mSwizzle);
}
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
VkComponentMapping swizzle) {
VulkanTextureState::ImageViewKey const key{ range, viewType, swizzle };
auto iter = mState->mCachedImageViews.find(key);
if (iter != mState->mCachedImageViews.end()) {
return iter->second;
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange const& range, VkImageViewType type) {
assert_invariant(mState->mYcbcr.conversion == VK_NULL_HANDLE &&
"We are not yet supporting external image as attachments.");
if (type == VK_IMAGE_VIEW_TYPE_2D) {
VkImageSubresourceRange copy = range;
copy.levelCount = 1;
copy.layerCount = 1;
return mState->getImageView(copy, type, {});
} else {
return mState->getImageView(range, type, {});
}
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.image = mState->mTextureImage,
.viewType = viewType,
.format = mState->mVkFormat,
.components = swizzle,
.subresourceRange = range,
};
VkImageView imageView;
vkCreateImageView(mState->mDevice, &viewInfo, VKALLOC, &imageView);
mState->mCachedImageViews.emplace(key, imageView);
return imageView;
}
VkImageView VulkanTexture::getView(VkImageSubresourceRange const& range) {
return mState->getImageView(range, mState->mViewType, mSwizzle);
}
VkImageAspectFlags VulkanTexture::getImageAspect() const {
@@ -738,6 +764,21 @@ void VulkanTexture::setLayout(VkImageSubresourceRange const& range, VulkanLayout
}
}
void VulkanTexture::setYcbcrConversion(VkSamplerYcbcrConversion conversion, bool isExternalFormat) {
// Note that this comparison is valid because we only ever create VkSamplerYcbcrConversion from
// a cache. So for each set of parameters, there is exactly one conversion (similar to
// samplers).
VulkanTextureState::Ycbcr ycbcr = {
.conversion = conversion,
.isExternalFormat = isExternalFormat,
};
if (mState->mYcbcr != ycbcr) {
mState->mYcbcr = ycbcr;
mState->clearCachedImageViews();
}
}
VulkanLayout VulkanTexture::getLayout(uint32_t layer, uint32_t level) const {
assert_invariant(level <= 0xffff && layer <= 0xffff);
const uint32_t key = (layer << 16) | level;
+56 -37
View File
@@ -19,7 +19,10 @@
#include "DriverBase.h"
#include "VulkanBuffer.h"
#include "VulkanCommands.h"
#include "VulkanConstants.h"
#include "VulkanMemory.h"
#include "VulkanStagePool.h"
#include "vulkan/memory/Resource.h"
#include "vulkan/memory/ResourcePointer.h"
#include "vulkan/utils/Image.h"
@@ -31,10 +34,14 @@
namespace filament::backend {
struct VulkanTexture;
struct VulkanTextureState : public fvkmemory::Resource {
VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool, VkFormat format, VkImageViewType viewType, uint8_t levels,
uint8_t layerCount, VulkanLayout defaultLayout, bool isProtected);
VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands, VmaAllocator allocator,
VkDevice device, VkImage image, VkDeviceMemory deviceMemory, VkFormat format,
VkImageViewType viewType, uint8_t levels, uint8_t layerCount,
VkSamplerYcbcrConversion ycbcrConversion, bool isExternalFormat,
VkImageUsageFlags usage, bool isProtected);
~VulkanTextureState();
@@ -57,29 +64,50 @@ struct VulkanTextureState : public fvkmemory::Resource {
// No implicit padding allowed due to it being a hash key.
static_assert(sizeof(ImageViewKey) == 40);
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
VkImageView getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
VkComponentMapping swizzle);
private:
void clearCachedImageViews() noexcept;
VulkanStagePool& mStagePool;
VulkanCommands* const mCommands;
VmaAllocator const mAllocator;
VkDevice const mDevice;
// The texture with the sidecar owns the sidecar.
fvkmemory::resource_ptr<VulkanTexture> mSidecarMSAA;
VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE;
VkImage const mTextureImage;
VkDeviceMemory const mTextureImageMemory;
VkFormat const mVkFormat;
VkImageViewType const mViewType;
VkImageSubresourceRange const mFullViewRange;
VkImage mTextureImage = VK_NULL_HANDLE;
VulkanLayout mDefaultLayout;
bool mIsProtected = false;
// Note that this parameter is not constant due to the fact that AHB can force a change in the
// conversion matrix per-frame.
struct Ycbcr {
VkSamplerYcbcrConversion conversion;
bool isExternalFormat;
bool operator==(Ycbcr const& other) const {
return conversion == other.conversion && isExternalFormat == other.isExternalFormat;
}
bool operator!=(Ycbcr const& other) const {
return !((*this) == other);
}
} mYcbcr;
VulkanLayout const mDefaultLayout;
VkImageUsageFlags const mUsage;
bool const mIsProtected;
// Track the image layout of each subresource using a sparse range map.
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
VulkanStagePool& mStagePool;
VkDevice mDevice;
VmaAllocator mAllocator;
VulkanCommands* mCommands;
bool mIsTransientAttachment;
friend struct VulkanTexture;
};
struct VulkanTexture : public HwTexture, fvkmemory::Resource {
@@ -91,10 +119,10 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
VulkanStagePool& stagePool);
// Specialized constructor for internally created textures (e.g. from a swap chain)
// The texture will never destroy the given VkImage, but it does manages its subresources.
VulkanTexture(VkDevice device, VmaAllocator allocator,
VulkanTexture(VulkanContext const& context, VkDevice device, VmaAllocator allocator,
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, VkImage image,
VkDeviceMemory memory, VkFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
VkDeviceMemory memory, VkFormat format, VkSamplerYcbcrConversion conversion,
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
TextureUsage tusage, VulkanStagePool& stagePool);
// Constructor for creating a texture view for wrt specific mip range
@@ -113,11 +141,6 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
void updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel);
// Returns the primary image view, which is used for shader sampling.
VkImageView getPrimaryImageView() {
return getImageView(mPrimaryViewRange, mState->mViewType, mSwizzle);
}
VkImageViewType getViewType() const {
return mState->mViewType;
}
@@ -132,20 +155,11 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
// 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.
VkImageView getAttachmentView(VkImageSubresourceRange range);
// Gets or creates a cached VkImageView for a subresource that can be used as a render
// target attachment. Unlike the primary image view, this always the identity swizzle.
VkImageView getAttachmentView(VkImageSubresourceRange const& range, VkImageViewType type);
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
// target attachment when rendering with multiview.
VkImageView getMultiviewAttachmentView(VkImageSubresourceRange range);
// This is a workaround for the first few frames where we're waiting for the texture to actually
// be uploaded. In that case, we bind the sampler to an empty texture, but the corresponding
// imageView needs to be of the right type. Hence, we provide an option to indicate the
// view type. Swizzle option does not matter in this case.
VkImageView getViewForType(VkImageSubresourceRange const& range, VkImageViewType type);
VkImageView getView(VkImageSubresourceRange const& range);
VkFormat getVkFormat() const {
return mState->mVkFormat;
@@ -165,7 +179,7 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
}
bool isTransientAttachment() const {
return mState->mIsTransientAttachment;
return mState->mUsage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
}
bool getIsProtected() const {
@@ -192,6 +206,11 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
// manually (outside of calls to transitionLayout).
void setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout);
// This is used in the case of external images and external samplers. AHB might update the
// conversion per-frame. This implies that we need to invalidate the view cache when that
// happens.
void setYcbcrConversion(VkSamplerYcbcrConversion conversion, bool isExternal);
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
void print() const;
#endif
@@ -0,0 +1,84 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "VulkanYcbcrConversionCache.h"
#include "vulkan/VulkanConstants.h"
#include "vulkan/utils/Conversion.h"
#include <utils/Panic.h>
#if defined(__ANDROID__)
#include <vulkan/vulkan_android.h> // for VkExternalFormatANDROID
#endif
using namespace bluevk;
namespace filament::backend {
VulkanYcbcrConversionCache::VulkanYcbcrConversionCache(VkDevice device)
: mDevice(device) {}
VkSamplerYcbcrConversion VulkanYcbcrConversionCache::getConversion(
VulkanYcbcrConversionCache::Params params) noexcept {
auto iter = mCache.find(params);
if (UTILS_LIKELY(iter != mCache.end())) {
return iter->second;
}
auto const& chroma = params.conversion;
TextureSwizzle const swizzleArray[] = { chroma.r, chroma.g, chroma.b, chroma.a };
VkSamplerYcbcrConversionCreateInfo conversionInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
.format = fvkutils::getVkFormat(params.format),
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
.components = fvkutils::getSwizzleMap(swizzleArray),
.xChromaOffset = fvkutils::getChromaLocation(chroma.xChromaOffset),
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset),
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
};
// We could put this in the platform class, but that seems like a bit of an overkill
#if defined(__ANDROID__)
VkExternalFormatANDROID externalFormat = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
.externalFormat = params.externalFormat,
};
if (params.externalFormat) {
conversionInfo.pNext = &externalFormat;
conversionInfo.format = VK_FORMAT_UNDEFINED;
}
#endif
VkSamplerYcbcrConversion conversion;
VkResult result =
vkCreateSamplerYcbcrConversion(mDevice, &conversionInfo, nullptr, &conversion);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create Ycbcr Conversion."
<< " error=" << static_cast<int32_t>(result);
mCache.insert({ params, conversion });
return conversion;
}
void VulkanYcbcrConversionCache::terminate() noexcept {
for (auto& [param, conv]: mCache) {
vkDestroySamplerYcbcrConversion(mDevice, conv, VKALLOC);
}
mCache.clear();
}
} // namespace filament::backend
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
#define TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
#include <backend/DriverEnums.h>
#include <utils/Hash.h>
#include <bluevk/BlueVK.h>
#include <tsl/robin_map.h>
namespace filament::backend {
// Simple manager for VkSamplerYcbcrConversion objects.
class VulkanYcbcrConversionCache {
public:
struct Params {
SamplerYcbcrConversion conversion = {};
TextureFormat format = {};
uint16_t padding = 0;
uint64_t externalFormat = 0;
};
static_assert(sizeof(Params) == 16);
explicit VulkanYcbcrConversionCache(VkDevice device);
VkSamplerYcbcrConversion getConversion(Params params) noexcept;
void terminate() noexcept;
private:
VkDevice mDevice;
struct ConversionEqualTo {
bool operator()(Params lhs, Params rhs) const noexcept {
SamplerYcbcrConversion::EqualTo equal;
return equal(lhs.conversion, rhs.conversion) &&
lhs.externalFormat == rhs.externalFormat;
}
};
using ConversionHashFn = utils::hash::MurmurHashFn<Params>;
tsl::robin_map<Params, VkSamplerYcbcrConversion, ConversionHashFn, ConversionEqualTo> mCache;
};
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
@@ -211,6 +211,7 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
#endif
// MoltenVk is the only non-conformant implementation we're interested in.
#if defined(__APPLE__)
@@ -326,7 +327,9 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
}
VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
VkPhysicalDeviceFeatures2 const& features, uint32_t graphicsQueueFamilyIndex,
VkPhysicalDeviceFeatures2 const& features,
VkPhysicalDeviceVulkan11Features const& vk11Features,
uint32_t graphicsQueueFamilyIndex,
uint32_t protectedGraphicsQueueFamilyIndex, ExtensionSet const& deviceExtensions,
bool requestImageView2DOn3DImage) {
VkDevice device;
@@ -359,14 +362,28 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
// We could simply enable all supported features, but since that may have performance
// consequences let's just enable the features we need.
VkPhysicalDeviceFeatures enabledFeatures{
VkPhysicalDeviceFeatures enabledFeatures = {
.depthClamp = features.features.depthClamp,
.samplerAnisotropy = features.features.samplerAnisotropy,
.textureCompressionETC2 = features.features.textureCompressionETC2,
.textureCompressionBC = features.features.textureCompressionBC,
.shaderClipDistance = features.features.shaderClipDistance,
};
deviceCreateInfo.pEnabledFeatures = &enabledFeatures;
VkPhysicalDeviceFeatures2 enabledFeatures2 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.features = enabledFeatures,
};
chainStruct(&deviceCreateInfo, &enabledFeatures2);
VkPhysicalDeviceVulkan11Features enabledVk11Features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
.multiview = vk11Features.multiview,
#if defined(__ANDROID__)
.samplerYcbcrConversion = vk11Features.samplerYcbcrConversion,
#endif
};
chainStruct(&deviceCreateInfo, &enabledVk11Features);
deviceCreateInfo.enabledExtensionCount = (uint32_t) requestExtensions.size();
deviceCreateInfo.ppEnabledExtensionNames = requestExtensions.data();
@@ -383,7 +400,7 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
VkPhysicalDeviceMultiviewFeaturesKHR multiview = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR,
.multiview = VK_TRUE,
.multiview = vk11Features.multiview,
.multiviewGeometryShader = VK_FALSE,
.multiviewTessellationShader = VK_FALSE,
};
@@ -734,6 +751,7 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
};
chainStruct(&context.mPhysicalDeviceFeatures, &queryProtectedMemoryFeatures);
chainStruct(&context.mPhysicalDeviceFeatures, &context.mPhysicalDeviceVk11Features);
chainStruct(&context.mPhysicalDeviceProperties, &protectedMemoryProperties);
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
@@ -795,10 +813,10 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
}
if (mImpl->mDevice == VK_NULL_HANDLE) {
mImpl->mDevice =
createLogicalDevice(mImpl->mPhysicalDevice, context.mPhysicalDeviceFeatures,
mImpl->mGraphicsQueueFamilyIndex, mImpl->mProtectedGraphicsQueueFamilyIndex,
deviceExts, requestPortabilitySubsetImageView2DOn3DImage);
mImpl->mDevice = createLogicalDevice(mImpl->mPhysicalDevice,
context.mPhysicalDeviceFeatures, context.mPhysicalDeviceVk11Features,
mImpl->mGraphicsQueueFamilyIndex, mImpl->mProtectedGraphicsQueueFamilyIndex,
deviceExts, requestPortabilitySubsetImageView2DOn3DImage);
}
assert_invariant(mImpl->mDevice != VK_NULL_HANDLE);
@@ -826,12 +844,10 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
if (!mImpl->mSharedContext) {
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
context.mMultiviewEnabled = setContains(deviceExts, VK_KHR_MULTIVIEW_EXTENSION_NAME);
} else {
VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext;
context.mDebugUtilsSupported = scontext->debugUtilsSupported;
context.mDebugMarkersSupported = scontext->debugMarkersSupported;
context.mMultiviewEnabled = scontext->multiviewSupported;
}
// Check the availability of lazily allocated memory
@@ -295,9 +295,8 @@ VulkanPlatformAndroid::ImageData VulkanPlatformAndroid::createExternalImageData(
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device, SamplerYcbcrConversion chroma,
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
VkImageViewType viewType, VkComponentMapping swizzle){
VkExternalFormatANDROID externalFormat = {
VkExternalFormatANDROID externalFormat = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
.pNext = nullptr,
.externalFormat = internalFormat,
};
@@ -322,7 +321,6 @@ VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device, Sampler
VkSamplerYcbcrConversionInfo samplerYcbcrConversionInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
.pNext = nullptr,
.conversion = conversion,
};
@@ -350,7 +348,6 @@ VkSampler VulkanPlatform::createExternalSamplerImpl(
uint32_t internalFormat) {
VkExternalFormatANDROID externalFormat = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
.pNext = nullptr,
.externalFormat = internalFormat,
};
@@ -24,28 +24,12 @@
#include <bluevk/BlueVK.h>
// Platform specific includes and defines
#if defined(__APPLE__)
#include <Cocoa/Cocoa.h>
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
#include <Cocoa/Cocoa.h>
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
#ifndef VK_MVK_macos_surface
#error VK_MVK_macos_surface is not defined
#endif
#elif defined(FILAMENT_IOS)
// Metal is not available when building for the iOS simulator on Desktop.
#define METAL_AVAILABLE __has_include(<QuartzCore/CAMetalLayer.h>)
#if METAL_AVAILABLE
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
#endif
#ifndef VK_MVK_ios_surface
#error VK_MVK_ios_surface is not defined
#endif
#define METALVIEW_TAG 255
#else
#error Not a supported Apple + Vulkan platform
#ifndef VK_MVK_macos_surface
#error VK_MVK_macos_surface is not defined
#endif
using namespace bluevk;
@@ -54,11 +38,7 @@ namespace filament::backend {
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
ExtensionSet const ret = {
#if defined(__APPLE__)
VK_MVK_MACOS_SURFACE_EXTENSION_NAME, // TODO: replace with VK_EXT_metal_surface
#elif defined(FILAMENT_IOS) && defined(METAL_AVAILABLE)
VK_MVK_IOS_SURFACE_EXTENSION_NAME,
#endif
};
return ret;
}
@@ -90,36 +70,20 @@ VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
VkInstance instance, uint64_t flags) noexcept {
VkSurfaceKHR surface;
#if defined(__APPLE__)
NSView* nsview = (__bridge NSView*) nativeWindow;
FILAMENT_CHECK_POSTCONDITION(nsview) << "Unable to obtain Metal-backed NSView.";
NSView* nsview = (__bridge NSView*) nativeWindow;
FILAMENT_CHECK_POSTCONDITION(nsview) << "Unable to obtain Metal-backed NSView.";
// Create the VkSurface.
FILAMENT_CHECK_POSTCONDITION(vkCreateMacOSSurfaceMVK)
<< "Unable to load vkCreateMacOSSurfaceMVK.";
VkMacOSSurfaceCreateInfoMVK createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
createInfo.pView = (__bridge void*) nsview;
VkResult result = vkCreateMacOSSurfaceMVK((VkInstance) instance, &createInfo, VKALLOC,
(VkSurfaceKHR*) &surface);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkCreateMacOSSurfaceMVK. error=" << static_cast<int32_t>(result);
#elif defined(FILAMENT_IOS) && defined(METAL_AVAILABLE)
CAMetalLayer* metalLayer = (CAMetalLayer*) nativeWindow;
// Create the VkSurface.
FILAMENT_CHECK_POSTCONDITION(vkCreateIOSSurfaceMVK)
<< "Unable to load vkCreateIOSSurfaceMVK function.";
VkIOSSurfaceCreateInfoMVK createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
createInfo.pNext = NULL;
createInfo.flags = 0;
createInfo.pView = metalLayer;
VkResult result = vkCreateIOSSurfaceMVK((VkInstance) instance, &createInfo, VKALLOC,
FILAMENT_CHECK_POSTCONDITION(vkCreateMacOSSurfaceMVK)
<< "Unable to load vkCreateMacOSSurfaceMVK.";
VkMacOSSurfaceCreateInfoMVK createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
createInfo.pView = (__bridge void*) nsview;
VkResult result = vkCreateMacOSSurfaceMVK((VkInstance) instance, &createInfo, VKALLOC,
(VkSurfaceKHR*) &surface);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkCreateIOSSurfaceMVK failed. error=" << static_cast<int32_t>(result);
#endif
return std::make_tuple(surface, VkExtent2D{});
<< "vkCreateMacOSSurfaceMVK. error=" << static_cast<int32_t>(result);
return std::make_tuple(surface, VkExtent2D{});
}
} // namespace filament::backend
@@ -18,12 +18,12 @@
#define TNT_FILAMENT_BACKEND_VULKANSWAPCHAIN_IMPL_H
#include "vulkan/VulkanContext.h"
#include "vulkan/VulkanConstants.h"
#include <backend/platforms/VulkanPlatform.h>
#include <bluevk/BlueVK.h>
#include <tuple>
#include <unordered_map>
using namespace bluevk;
@@ -668,4 +668,111 @@ VkShaderStageFlags getShaderStageFlags(ShaderStageFlags stageFlags) {
return flags;
}
VkSamplerYcbcrModelConversion getYcbcrModelConversion(
SamplerYcbcrModelConversion model) {
switch (model) {
case SamplerYcbcrModelConversion::RGB_IDENTITY:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY;
case SamplerYcbcrModelConversion::YCBCR_IDENTITY:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY;
case SamplerYcbcrModelConversion::YCBCR_709:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709;
case SamplerYcbcrModelConversion::YCBCR_601:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601;
case SamplerYcbcrModelConversion::YCBCR_2020:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range) {
switch (range) {
case SamplerYcbcrRange::ITU_FULL:
return VK_SAMPLER_YCBCR_RANGE_ITU_FULL;
case SamplerYcbcrRange::ITU_NARROW:
return VK_SAMPLER_YCBCR_RANGE_ITU_NARROW;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
VkChromaLocation getChromaLocation(ChromaLocation loc) {
switch (loc) {
case ChromaLocation::COSITED_EVEN:
return VK_CHROMA_LOCATION_COSITED_EVEN;
case ChromaLocation::MIDPOINT:
return VK_CHROMA_LOCATION_MIDPOINT;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
SamplerYcbcrModelConversion getYcbcrModelConversionFilament(VkSamplerYcbcrModelConversion model) {
switch (model) {
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY:
return SamplerYcbcrModelConversion::RGB_IDENTITY;
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY:
return SamplerYcbcrModelConversion::YCBCR_IDENTITY;
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709:
return SamplerYcbcrModelConversion::YCBCR_709;
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601:
return SamplerYcbcrModelConversion::YCBCR_601;
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020:
return SamplerYcbcrModelConversion::YCBCR_2020;
default:
assert_invariant(false && "Unknown data type, conversion is not supported.");
return {};
}
}
SamplerYcbcrRange getYcbcrRangeFilament(VkSamplerYcbcrRange range) {
switch (range) {
case VK_SAMPLER_YCBCR_RANGE_ITU_FULL:
return SamplerYcbcrRange::ITU_FULL;
case VK_SAMPLER_YCBCR_RANGE_ITU_NARROW:
return SamplerYcbcrRange::ITU_NARROW;
default:
assert_invariant(false && "Unknown data type, conversion is not supported.");
return {};
}
}
ChromaLocation getChromaLocationFilament(VkChromaLocation loc) {
switch (loc) {
case VK_CHROMA_LOCATION_COSITED_EVEN:
return ChromaLocation::COSITED_EVEN;
case VK_CHROMA_LOCATION_MIDPOINT:
return ChromaLocation::MIDPOINT;
default:
assert_invariant(false && "Unknown data type, conversion is not supported.");
return {};
}
}
TextureSwizzle getSwizzleFilament(VkComponentSwizzle c, uint8_t rgbaIndex) {
switch (c) {
case VK_COMPONENT_SWIZZLE_ZERO:
return TextureSwizzle::SUBSTITUTE_ZERO;
case VK_COMPONENT_SWIZZLE_ONE:
return TextureSwizzle::SUBSTITUTE_ONE;
case VK_COMPONENT_SWIZZLE_IDENTITY:
return (TextureSwizzle) (((uint8_t) TextureSwizzle::CHANNEL_0) + rgbaIndex);
case VK_COMPONENT_SWIZZLE_R:
return TextureSwizzle::CHANNEL_0;
case VK_COMPONENT_SWIZZLE_G:
return TextureSwizzle::CHANNEL_1;
case VK_COMPONENT_SWIZZLE_B:
return TextureSwizzle::CHANNEL_2;
case VK_COMPONENT_SWIZZLE_A:
return TextureSwizzle::CHANNEL_3;
default:
assert_invariant(false && "Unknown data type, conversion is not supported.");
return {};
}
}
} // namespace filament::backend::fvkutils
@@ -69,6 +69,12 @@ VkSamplerYcbcrModelConversion getYcbcrModelConversion(SamplerYcbcrModelConversio
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range);
VkChromaLocation getChromaLocation(ChromaLocation loc);
// Ycbcr related functions
SamplerYcbcrModelConversion getYcbcrModelConversionFilament(VkSamplerYcbcrModelConversion model);
SamplerYcbcrRange getYcbcrRangeFilament(VkSamplerYcbcrRange range);
ChromaLocation getChromaLocationFilament(VkChromaLocation loc);
TextureSwizzle getSwizzleFilament(VkComponentSwizzle c, uint8_t rgbaIndex);
inline VkImageViewType getViewType(SamplerType target) {
switch (target) {
case SamplerType::SAMPLER_CUBEMAP:
@@ -348,6 +348,10 @@ using SamplerBitmask = utils::bitset64;
// general.
using InputAttachmentBitmask = utils::bitset64;
constexpr uint8_t MAX_DESCRIPTOR_SET_BITMASK_BITS =
std::max(std::max(sizeof(UniformBufferBitmask), sizeof(SamplerBitmask)),
sizeof(InputAttachmentBitmask)) * 8;
template<typename Bitmask>
static constexpr uint8_t getVertexStageShift() noexcept {
// We assume the bottom half of bits are for vertex stages.
@@ -193,49 +193,6 @@ uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask) {
return mostSignificantBit((sampleCount - 1) & mask);
}
VkSamplerYcbcrModelConversion getYcbcrModelConversion(
SamplerYcbcrModelConversion model) {
switch (model) {
case SamplerYcbcrModelConversion::RGB_IDENTITY:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY;
case SamplerYcbcrModelConversion::YCBCR_IDENTITY:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY;
case SamplerYcbcrModelConversion::YCBCR_709:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709;
case SamplerYcbcrModelConversion::YCBCR_601:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601;
case SamplerYcbcrModelConversion::YCBCR_2020:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range) {
switch (range) {
case SamplerYcbcrRange::ITU_FULL:
return VK_SAMPLER_YCBCR_RANGE_ITU_FULL;
case SamplerYcbcrRange::ITU_NARROW:
return VK_SAMPLER_YCBCR_RANGE_ITU_NARROW;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
VkChromaLocation getChromaLocation(ChromaLocation loc) {
switch (loc) {
case ChromaLocation::COSITED_EVEN:
return VK_CHROMA_LOCATION_COSITED_EVEN;
case ChromaLocation::MIDPOINT:
return VK_CHROMA_LOCATION_MIDPOINT;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
} // namespace filament::backend::fvkutils
bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) {
+117 -76
View File
@@ -16,6 +16,7 @@
#include "webgpu/WebGPUDriver.h"
#include "WebGPUSwapChain.h"
#include "webgpu/WebGPUConstants.h"
#include <backend/platforms/WebGPUPlatform.h>
@@ -35,6 +36,7 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <sstream>
#include <string_view>
#include <utility>
@@ -116,7 +118,6 @@ void printLimits(wgpu::Limits const& limits) {
printLimit("maxBufferSize", limits.maxBufferSize);
printLimit("maxVertexAttributes", limits.maxVertexAttributes);
printLimit("maxVertexBufferArrayStride", limits.maxVertexBufferArrayStride);
printLimit("maxInterStageShaderComponents", limits.maxInterStageShaderComponents);
printLimit("maxInterStageShaderVariables", limits.maxInterStageShaderVariables);
printLimit("maxColorAttachments", limits.maxColorAttachments);
printLimit("maxColorAttachmentBytesPerSample", limits.maxColorAttachmentBytesPerSample);
@@ -158,8 +159,6 @@ void printAdapterDetails(wgpu::Adapter const& adapter) {
FWGPU_LOGI << " vendor ID: " << adapterInfo.vendorID << utils::io::endl;
FWGPU_LOGI << " subgroup min size: " << adapterInfo.subgroupMinSize << utils::io::endl;
FWGPU_LOGI << " subgroup max size: " << adapterInfo.subgroupMaxSize << utils::io::endl;
FWGPU_LOGI << " compatibility mode: " << bool(adapterInfo.compatibilityMode)
<< utils::io::endl;
FWGPU_LOGI << " power preference: " << powerPreferenceStream.str() << utils::io::endl;
}
wgpu::SupportedFeatures supportedFeatures{};
@@ -175,55 +174,17 @@ void printAdapterDetails(wgpu::Adapter const& adapter) {
FWGPU_LOGI << " " << nameStream.str().data() << utils::io::endl;
});
}
wgpu::SupportedLimits supportedLimits{};
wgpu::Limits supportedLimits{};
if (!adapter.GetLimits(&supportedLimits)) {
FWGPU_LOGW << "Failed to get WebGPU adapter supported limits" << utils::io::endl;
} else {
FWGPU_LOGI << "WebGPU adapter supported limits:" << utils::io::endl;
printLimits(supportedLimits.limits);
printLimits(supportedLimits);
}
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabilities) {
std::stringstream usages_stream{};
usages_stream << capabilities.usages;
FWGPU_LOGI << "WebGPU surface capabilities:" << utils::io::endl;
FWGPU_LOGI << " surface usages: " << usages_stream.str().data() << utils::io::endl;
FWGPU_LOGI << " surface formats (" << capabilities.formatCount << "):" << utils::io::endl;
if (capabilities.formatCount > 0 && capabilities.formats != nullptr) {
std::for_each(capabilities.formats, capabilities.formats + capabilities.formatCount,
[](wgpu::TextureFormat const format) {
std::stringstream format_stream{};
format_stream << format;
FWGPU_LOGI << " " << format_stream.str().data() << utils::io::endl;
});
}
FWGPU_LOGI << " surface present modes (" << capabilities.presentModeCount
<< "):" << utils::io::endl;
if (capabilities.presentModeCount > 0 && capabilities.presentModes != nullptr) {
std::for_each(capabilities.presentModes,
capabilities.presentModes + capabilities.presentModeCount,
[](wgpu::PresentMode const presentMode) {
std::stringstream present_mode_stream{};
present_mode_stream << presentMode;
FWGPU_LOGI << " " << present_mode_stream.str().data() << utils::io::endl;
});
}
FWGPU_LOGI << " surface alpha modes (" << capabilities.alphaModeCount
<< "):" << utils::io::endl;
if (capabilities.alphaModeCount > 0 && capabilities.alphaModes != nullptr) {
std::for_each(capabilities.alphaModes,
capabilities.alphaModes + capabilities.alphaModeCount,
[](wgpu::CompositeAlphaMode const alphaMode) {
std::stringstream alpha_mode_stream{};
alpha_mode_stream << alphaMode;
FWGPU_LOGI << " " << alpha_mode_stream.str().data() << utils::io::endl;
});
}
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printDeviceDetails(wgpu::Device const& device) {
@@ -240,12 +201,12 @@ void printDeviceDetails(wgpu::Device const& device) {
FWGPU_LOGI << " " << nameStream.str().data() << utils::io::endl;
});
}
wgpu::SupportedLimits supportedLimits{};
wgpu::Limits supportedLimits{};
if (!device.GetLimits(&supportedLimits)) {
FWGPU_LOGW << "Failed to get WebGPU supported device limits" << utils::io::endl;
} else {
FWGPU_LOGI << "WebGPU device supported limits:" << utils::io::endl;
printLimits(supportedLimits.limits);
printLimits(supportedLimits);
}
}
#endif
@@ -267,6 +228,14 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfi
driverConfig.disableHeapHandleTags) {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printInstanceDetails(mPlatform.getInstance());
#endif
mAdapter = mPlatform.requestAdapter(nullptr);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printAdapterDetails(mAdapter);
#endif
mDevice = mPlatform.requestDevice(mAdapter);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printDeviceDetails(mDevice);
#endif
}
@@ -296,6 +265,7 @@ void WebGPUDriver::terminate() {
}
void WebGPUDriver::tick(int) {
mDevice.Tick();
}
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
@@ -325,15 +295,27 @@ void WebGPUDriver::finish(int) {
}
void WebGPUDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
if (rph) {
destructHandle<WGPURenderPrimitive>(rph);
}
}
void WebGPUDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vbih) {
if (vbih) {
destructHandle<WGPUVertexBufferInfo>(vbih);
}
}
void WebGPUDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
if (vbh) {
destructHandle<WGPUVertexBuffer>(vbh);
}
}
void WebGPUDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
if (ibh) {
destructHandle<WGPUIndexBuffer>(ibh);
}
}
void WebGPUDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
@@ -349,6 +331,10 @@ void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
}
void WebGPUDriver::destroySwapChain(Handle<HwSwapChain> sch) {
if (sch) {
destructHandle<WebGPUSwapChain>(sch);
}
mSwapChain = nullptr;
}
void WebGPUDriver::destroyStream(Handle<HwStream> sh) {
@@ -358,13 +344,16 @@ void WebGPUDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
}
void WebGPUDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> tqh) {
if (tqh) {
destructHandle<WebGPUDescriptorSetLayout>(tqh);
}
}
void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) {
}
Handle<HwSwapChain> WebGPUDriver::createSwapChainS() noexcept {
return Handle<HwSwapChain>((Handle<HwSwapChain>::HandleId) mNextFakeHandle++);
return allocHandle<WebGPUSwapChain>();
}
Handle<HwSwapChain> WebGPUDriver::createSwapChainHeadlessS() noexcept {
@@ -372,7 +361,7 @@ Handle<HwSwapChain> WebGPUDriver::createSwapChainHeadlessS() noexcept {
}
Handle<HwTexture> WebGPUDriver::createTextureS() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
return allocHandle<WGPUTexture>();
}
Handle<HwTexture> WebGPUDriver::importTextureS() noexcept {
@@ -400,15 +389,15 @@ Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept {
}
Handle<HwBufferObject> WebGPUDriver::createBufferObjectS() noexcept {
return Handle<HwBufferObject>((Handle<HwBufferObject>::HandleId) mNextFakeHandle++);
return allocHandle<WGPUBufferObject>();
}
Handle<HwRenderTarget> WebGPUDriver::createRenderTargetS() noexcept {
return Handle<HwRenderTarget>((Handle<HwRenderTarget>::HandleId) mNextFakeHandle++);
return allocHandle<WGPURenderTarget>();
}
Handle<HwVertexBuffer> WebGPUDriver::createVertexBufferS() noexcept {
return Handle<HwVertexBuffer>((Handle<HwVertexBuffer>::HandleId) mNextFakeHandle++);
return allocHandle<WGPUVertexBuffer>();
}
Handle<HwDescriptorSet> WebGPUDriver::createDescriptorSetS() noexcept {
@@ -416,11 +405,11 @@ Handle<HwDescriptorSet> WebGPUDriver::createDescriptorSetS() noexcept {
}
Handle<HwRenderPrimitive> WebGPUDriver::createRenderPrimitiveS() noexcept {
return Handle<HwRenderPrimitive>((Handle<HwRenderPrimitive>::HandleId) mNextFakeHandle++);
return allocHandle<WGPURenderPrimitive>();
}
Handle<HwVertexBufferInfo> WebGPUDriver::createVertexBufferInfoS() noexcept {
return Handle<HwVertexBufferInfo>((Handle<HwVertexBufferInfo>::HandleId) mNextFakeHandle++);
return allocHandle<WGPUVertexBufferInfo>();
}
Handle<HwTexture> WebGPUDriver::createTextureViewSwizzleS() noexcept {
@@ -428,12 +417,11 @@ Handle<HwTexture> WebGPUDriver::createTextureViewSwizzleS() noexcept {
}
Handle<HwRenderTarget> WebGPUDriver::createDefaultRenderTargetS() noexcept {
return Handle<HwRenderTarget>((Handle<HwRenderTarget>::HandleId) mNextFakeHandle++);
return allocHandle<WGPURenderTarget>();
}
Handle<HwDescriptorSetLayout> WebGPUDriver::createDescriptorSetLayoutS() noexcept {
return Handle<HwDescriptorSetLayout>(
(Handle<HwDescriptorSetLayout>::HandleId) mNextFakeHandle++);
return allocHandle<WebGPUDescriptorSetLayout>();
}
Handle<HwTexture> WebGPUDriver::createTextureExternalImageS() noexcept {
@@ -449,25 +437,15 @@ Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
}
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
mSurface= mPlatform.createSurface(nativeWindow, flags);
mAdapter = mPlatform.requestAdapter(mSurface);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printAdapterDetails(mAdapter);
wgpu::SurfaceCapabilities surfaceCapabilities{};
if (!mSurface.GetCapabilities(mAdapter, &surfaceCapabilities)) {
FWGPU_LOGW << "Failed to get WebGPU surface capabilities" << utils::io::endl;
} else {
printSurfaceCapabilitiesDetails(surfaceCapabilities);
}
#endif
mDevice = mPlatform.requestDevice(mAdapter);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printDeviceDetails(mDevice);
#endif
mNativeWindow = nativeWindow;
assert_invariant(!mSwapChain);
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
mQueue = mDevice.GetQueue();
// TODO configure the surface (maybe before or after creating the swapchain?
// how do we get the surface extent?)
// TODO actually create the swapchain
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
mSwapChain = constructHandle<WebGPUSwapChain>(sch, std::move(surface), surfaceSize, mAdapter,
mDevice, flags);
assert_invariant(mSwapChain);
FWGPU_LOGW << "WebGPU support is still essentially a no-op at this point in development (only "
"background components have been instantiated/selected, such as surface/screen, "
"graphics device/GPU, etc.), thus nothing is being drawn to the screen."
@@ -529,7 +507,11 @@ void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph, Handle<
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {}
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {}
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
assert_invariant(!mDefaultRenderTarget);
mDefaultRenderTarget = constructHandle<WGPURenderTarget>(rth);
assert_invariant(mDefaultRenderTarget);
}
void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> rth, TargetBufferFlags targets,
uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount, MRT color,
@@ -540,7 +522,9 @@ void WebGPUDriver::createFenceR(Handle<HwFence> fh, int) {}
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
void WebGPUDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
backend::DescriptorSetLayout&& info) {}
backend::DescriptorSetLayout&& info) {
constructHandle<WebGPUDescriptorSetLayout>(dslh, std::move(info), &mDevice);
}
void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
Handle<HwDescriptorSetLayout> dslh) {}
@@ -690,6 +674,10 @@ void WebGPUDriver::resetBufferObject(Handle<HwBufferObject> boh) {
void WebGPUDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
Handle<HwBufferObject> boh) {
auto* vertexBuffer = handleCast<WGPUVertexBuffer>(vbh);
auto* bufferObject = handleCast<WGPUBufferObject>(boh);
assert_invariant(index < vertexBuffer->buffers.size());
vertexBuffer->setBuffer(bufferObject, index);
}
void WebGPUDriver::update3DImage(Handle<HwTexture> th,
@@ -722,18 +710,71 @@ void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority,
}
void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassParams& params) {
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
.label = "command_encoder"
};
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
assert_invariant(mCommandEncoder);
// TODO: Remove this code once WebGPU pipeline is implemented
static float red = 1.0f;
if (red - 0.01 > 0) {
red -= 0.01;
} else {
red = 1.0f;
}
assert_invariant(mTextureView);
wgpu::RenderPassColorAttachment renderPassColorAttachment = {
.view = mTextureView,
// TODO: remove this code once WebGPU Pipeline is implemented with render targets, pipeline and buffers.
.depthSlice = wgpu::kDepthSliceUndefined,
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
.clearValue = wgpu::Color{red, 0 , 0 , 1},
};
wgpu::RenderPassDescriptor renderPassDescriptor = {
.colorAttachmentCount = 1,
.colorAttachments = &renderPassColorAttachment,
.depthStencilAttachment = nullptr,
.timestampWrites = nullptr,
};
mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor);
mRenderPassEncoder.SetViewport(params.viewport.left, params.viewport.bottom,
params.viewport.width, params.viewport.height, params.depthRange.near, params.depthRange.far);
}
void WebGPUDriver::endRenderPass(int) {
mRenderPassEncoder.End();
mRenderPassEncoder = nullptr;
wgpu::CommandBufferDescriptor commandBufferDescriptor {
.label = "command_buffer",
};
mCommandBuffer = mCommandEncoder.Finish(&commandBufferDescriptor);
assert_invariant(mCommandBuffer);
}
void WebGPUDriver::nextSubpass(int) {
}
void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
ASSERT_PRECONDITION_NON_FATAL(drawSch == readSch,
"WebGPU driver does not support distinct draw/read swap chains.");
auto* swapChain = handleCast<WebGPUSwapChain>(drawSch);
mSwapChain = swapChain;
assert_invariant(mSwapChain);
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
mTextureView = mSwapChain->getCurrentSurfaceTextureView(surfaceSize);
assert_invariant(mTextureView);
}
void WebGPUDriver::commit(Handle<HwSwapChain> sch) {
mCommandEncoder = nullptr;
mQueue.Submit(1, &mCommandBuffer);
mCommandBuffer = nullptr;
mTextureView = nullptr;
assert_invariant(mSwapChain);
mSwapChain->present();
}
void WebGPUDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
+26 -3
View File
@@ -17,6 +17,7 @@
#ifndef TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
#define TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
#include "WebGPUHandles.h"
#include <backend/platforms/WebGPUPlatform.h>
#include "DriverBase.h"
@@ -30,6 +31,7 @@
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <memory>
#ifndef FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB
# define FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB 8
@@ -37,6 +39,8 @@
namespace filament::backend {
class WebGPUSwapChain;
/**
* WebGPU backend (driver) implementation
*/
@@ -55,16 +59,20 @@ private:
// the platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
// handled in the WebGPUPlatform
WebGPUPlatform& mPlatform;
wgpu::Surface mSurface = nullptr;
wgpu::Adapter mAdapter = nullptr;
wgpu::Device mDevice = nullptr;
wgpu::Queue mQueue = nullptr;
void* mNativeWindow = nullptr;
WebGPUSwapChain* mSwapChain = nullptr;
uint64_t mNextFakeHandle = 1;
wgpu::CommandEncoder mCommandEncoder = nullptr;
wgpu::TextureView mTextureView = nullptr;
wgpu::RenderPassEncoder mRenderPassEncoder = nullptr;
wgpu::CommandBuffer mCommandBuffer = nullptr;
WGPURenderTarget* mDefaultRenderTarget = nullptr;
/*
* Driver interface
*/
template<typename T>
friend class ConcreteDispatcher;
@@ -91,6 +99,21 @@ private:
return mHandleAllocator.allocate<D>();
}
template<typename D, typename B, typename ... ARGS>
D* constructHandle(Handle<B>& handle, ARGS&& ... args) noexcept {
return mHandleAllocator.construct<D>(handle, std::forward<ARGS>(args)...);
}
template<typename D, typename B>
D* handleCast(Handle<B> handle) noexcept {
return mHandleAllocator.handle_cast<D*>(handle);
}
template<typename D, typename B>
void destructHandle(Handle<B>& handle) noexcept {
auto* p = mHandleAllocator.handle_cast<D*>(handle);
return mHandleAllocator.deallocate(handle, p);
}
};
}// namespace filament::backend
@@ -0,0 +1,153 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "WebGPUHandles.h"
#include <utility>
namespace {
wgpu::Buffer createIndexBuffer(wgpu::Device const& device, uint8_t elementSize, uint32_t indexCount) {
wgpu::BufferDescriptor descriptor{ .label = "index_buffer",
.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Index,
.size = elementSize * indexCount,
.mappedAtCreation = false };
return device.CreateBuffer(&descriptor);
}
} // namespace
namespace filament::backend {
WGPUIndexBuffer::WGPUIndexBuffer(wgpu::Device const& device, uint8_t elementSize,
uint32_t indexCount)
: buffer(createIndexBuffer(device, elementSize, indexCount)) {}
WGPUVertexBuffer::WGPUVertexBuffer(wgpu::Device const &device, uint32_t vextexCount, uint32_t bufferCount,
Handle<WGPUVertexBufferInfo> vbih)
: HwVertexBuffer(vextexCount),
vbih(vbih),
buffers(bufferCount) {
wgpu::BufferDescriptor descriptor {
.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Vertex,
.size = vextexCount * bufferCount,
.mappedAtCreation = false };
for (uint32_t i = 0; i < bufferCount; ++i) {
descriptor.label = ("vertex_buffer_" + std::to_string(i)).c_str();
buffers[i] = device.CreateBuffer(&descriptor);
}
}
// TODO: Empty function is a place holder for verxtex buffer updates and should be
// updated for that purpose.
void WGPUVertexBuffer::setBuffer(WGPUBufferObject* bufferObject, uint32_t index) {}
WGPUBufferObject::WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount)
: HwBufferObject(byteCount),
bufferObjectBinding(bindingType) {}
wgpu::ShaderStage WebGPUDescriptorSetLayout::filamentStageToWGPUStage(ShaderStageFlags fFlags) {
wgpu::ShaderStage ret = wgpu::ShaderStage::None;
if (any(ShaderStageFlags::VERTEX & fFlags)) {
ret |= wgpu::ShaderStage::Vertex;
}
if (any(ShaderStageFlags::FRAGMENT & fFlags)) {
ret |= wgpu::ShaderStage::Fragment;
}
if (any(ShaderStageFlags::COMPUTE & fFlags)) {
ret |= wgpu::ShaderStage::Compute;
}
return ret;
}
WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout,
wgpu::Device const* device) {
assert_invariant(device->Get());
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
// debugging. For now, hack an incrementing value.
static int layoutNum = 0;
uint samplerCount =
std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) {
return fEntry.type == DescriptorType::SAMPLER ||
fEntry.type == DescriptorType::SAMPLER_EXTERNAL;
});
std::vector<wgpu::BindGroupLayoutEntry> wEntries;
wEntries.reserve(layout.bindings.size() + samplerCount);
for (auto fEntry: layout.bindings) {
auto& wEntry = wEntries.emplace_back();
wEntry.visibility = filamentStageToWGPUStage(fEntry.stageFlags);
wEntry.binding = fEntry.binding * 2;
switch (fEntry.type) {
// TODO Metal treats these the same. Is this fine?
case DescriptorType::SAMPLER_EXTERNAL:
case DescriptorType::SAMPLER: {
// Sampler binding is 2n+1 due to split.
auto& samplerEntry = wEntries.emplace_back();
samplerEntry.binding = fEntry.binding * 2 + 1;
samplerEntry.visibility = wEntry.visibility;
// We are simply hoping that undefined and defaults suffices here.
samplerEntry.sampler.type = wgpu::SamplerBindingType::Undefined;
wEntry.texture.sampleType = wgpu::TextureSampleType::Undefined;
break;
}
case DescriptorType::UNIFORM_BUFFER: {
wEntry.buffer.hasDynamicOffset =
any(fEntry.flags & DescriptorFlags::DYNAMIC_OFFSET);
wEntry.buffer.type = wgpu::BufferBindingType::Uniform;
// TODO: Ideally we fill minBindingSize
break;
}
case DescriptorType::INPUT_ATTACHMENT: {
// TODO: support INPUT_ATTACHMENT. Metal does not currently.
PANIC_POSTCONDITION("Input Attachment is not supported");
break;
}
case DescriptorType::SHADER_STORAGE_BUFFER: {
// TODO: Vulkan does not support this, can we?
PANIC_POSTCONDITION("Shader storage is not supported");
break;
}
}
// Currently flags are only used to specify dynamic offset.
// UNUSED
// fEntry.count
}
wgpu::BindGroupLayoutDescriptor layoutDescriptor{
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
// debugging. For now, hack an incrementing value.
.label{ "layout_"+ layout.label + std::to_string(++layoutNum) },
.entryCount = wEntries.size(),
.entries = wEntries.data()
};
// TODO Do we need to defer this until we have more info on textures and samplers??
mLayout = device->CreateBindGroupLayout(&layoutDescriptor);
}
WebGPUDescriptorSetLayout::~WebGPUDescriptorSetLayout() {}
}// namespace filament::backend
+156
View File
@@ -0,0 +1,156 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
#define TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <utils/FixedCapacityVector.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
namespace filament::backend {
struct WGPUBufferObject;
// TODO: Currently WGPUVertexBufferInfo is not used by WebGPU for useful task.
// Update the struct when used by WebGPU driver.
struct WGPUVertexBufferInfo : public HwVertexBufferInfo {
WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
AttributeArray const& attributes)
: HwVertexBufferInfo(bufferCount, attributeCount),
attributes(attributes) {}
AttributeArray attributes;
};
struct WGPUVertexBuffer : public HwVertexBuffer {
WGPUVertexBuffer(wgpu::Device const &device, uint32_t vextexCount, uint32_t bufferCount,
Handle<WGPUVertexBufferInfo> vbih);
void setBuffer(WGPUBufferObject *bufferObject, uint32_t index);
Handle<WGPUVertexBufferInfo> vbih;
utils::FixedCapacityVector<wgpu::Buffer> buffers;
};
struct WGPUIndexBuffer : public HwIndexBuffer {
WGPUIndexBuffer(wgpu::Device const &device, uint8_t elementSize,
uint32_t indexCount);
wgpu::Buffer buffer;
};
// TODO: Currently WGPUVertexBufferInfo is not used by WebGPU for useful task.
// Update the struct when used by WebGPU driver.
struct WGPUBufferObject : HwBufferObject {
WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount);
wgpu::Buffer buffer;
const BufferObjectBinding bufferObjectBinding;
};
class WebGPUDescriptorSetLayout : public HwDescriptorSetLayout {
public:
WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, wgpu::Device const* device);
~WebGPUDescriptorSetLayout();
private:
// TODO: If this is useful elsewhere, remove it from this class
// Convert Filament Shader Stage Flags bitmask to webgpu equivilant
static wgpu::ShaderStage filamentStageToWGPUStage(ShaderStageFlags fFlags);
wgpu::BindGroupLayout mLayout;
};
// TODO: Currently WGPUTexture is not used by WebGPU for useful task.
// Update the struct when used by WebGPU driver.
struct WGPUTexture : public HwTexture {
WGPUTexture(SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage) noexcept;
// constructors for creating texture views
WGPUTexture(WGPUTexture const* src, uint8_t baseLevel, uint8_t levelCount) noexcept;
wgpu::Texture texture = nullptr;
};
struct WGPURenderPrimitive : public HwRenderPrimitive {
WGPURenderPrimitive();
void setBuffers(WGPUVertexBufferInfo const* const vbi,
WGPUVertexBuffer* vertexBuffer, WGPUIndexBuffer* indexBuffer);
WGPUVertexBuffer* vertexBuffer = nullptr;
WGPUIndexBuffer* indexBuffer = nullptr;
};
// TODO: Currently WGPURenderTarget is not used by WebGPU for useful task.
// Update the struct when used by WebGPU driver.
struct WGPURenderTarget : public HwRenderTarget {
class Attachment {
public:
friend struct WGPURenderTarget;
Attachment() = default;
Attachment(WGPUTexture* gpuTexture, uint8_t level = 0, uint16_t layer = 0)
: level(level),
layer(layer),
texture(gpuTexture->texture),
mWGPUTexture(gpuTexture) {}
uint8_t level = 0;
uint16_t layer = 0;
private:
wgpu::Texture texture = nullptr;
WGPUTexture* mWGPUTexture = nullptr;
};
WGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples,
Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]);
WGPURenderTarget()
: HwRenderTarget(0, 0),
defaultRenderTarget(true) {}
void setUpRenderPassAttachments(wgpu::RenderPassDescriptor* descriptor,
const RenderPassParams& params);
math::uint2 getAttachmentSize() noexcept;
bool isDefaultRenderTarget() const { return defaultRenderTarget; }
uint8_t getSamples() const { return samples; }
Attachment getDrawColorAttachment(size_t index);
Attachment getReadColorAttachment(size_t index);
private:
static wgpu::LoadOp getLoadAction(const RenderPassParams& params, TargetBufferFlags buffer);
static wgpu::LoadOp getStoreAction(const RenderPassParams& params, TargetBufferFlags buffer);
bool defaultRenderTarget = false;
uint8_t samples = 1;
Attachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
math::uint2 attachmentSize = {};
};
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
@@ -0,0 +1,276 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "webgpu/WebGPUSwapChain.h"
#include "webgpu/WebGPUConstants.h"
#include "backend/DriverEnums.h"
#include <utils/Panic.h>
#include <utils/ostream.h>
#include <dawn/webgpu_cpp_print.h>
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
#include <cstdint>
#include <sstream>
namespace {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabilities) {
std::stringstream usagesStream{};
usagesStream << capabilities.usages;
FWGPU_LOGI << "WebGPU surface capabilities:" << utils::io::endl;
FWGPU_LOGI << " surface usages: " << usagesStream.str().data() << utils::io::endl;
FWGPU_LOGI << " surface formats (" << capabilities.formatCount << "):" << utils::io::endl;
if (capabilities.formatCount > 0 && capabilities.formats != nullptr) {
std::for_each(capabilities.formats, capabilities.formats + capabilities.formatCount,
[](wgpu::TextureFormat const format) {
std::stringstream formatStream{};
formatStream << format;
FWGPU_LOGI << " " << formatStream.str().data() << utils::io::endl;
});
}
FWGPU_LOGI << " surface present modes (" << capabilities.presentModeCount
<< "):" << utils::io::endl;
if (capabilities.presentModeCount > 0 && capabilities.presentModes != nullptr) {
std::for_each(capabilities.presentModes,
capabilities.presentModes + capabilities.presentModeCount,
[](wgpu::PresentMode const presentMode) {
std::stringstream presentModeStream{};
presentModeStream << presentMode;
FWGPU_LOGI << " " << presentModeStream.str().data() << utils::io::endl;
});
}
FWGPU_LOGI << " surface alpha modes (" << capabilities.alphaModeCount
<< "):" << utils::io::endl;
if (capabilities.alphaModeCount > 0 && capabilities.alphaModes != nullptr) {
std::for_each(capabilities.alphaModes,
capabilities.alphaModes + capabilities.alphaModeCount,
[](wgpu::CompositeAlphaMode const alphaMode) {
std::stringstream alphaModeStream{};
alphaModeStream << alphaMode;
FWGPU_LOGI << " " << alphaModeStream.str().data() << utils::io::endl;
});
}
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config) {
std::stringstream formatStream{};
formatStream << config.format;
std::stringstream usageStream{};
usageStream << config.usage;
std::stringstream alphaModeStream{};
alphaModeStream << config.alphaMode;
std::stringstream presentModeStream{};
presentModeStream << config.presentMode;
FWGPU_LOGI << "WebGPU surface configuration:" << utils::io::endl;
FWGPU_LOGI << " surface format: " << formatStream.str() << utils::io::endl;
FWGPU_LOGI << " surface usage: " << usageStream.str() << utils::io::endl;
FWGPU_LOGI << " surface view formats (" << config.viewFormatCount << "):" << utils::io::endl;
if (config.viewFormatCount > 0 && config.viewFormats != nullptr) {
std::for_each(config.viewFormats, config.viewFormats + config.viewFormatCount,
[](wgpu::TextureFormat const viewFormat) {
std::stringstream viewFormatStream{};
viewFormatStream << viewFormat;
FWGPU_LOGI << " " << viewFormatStream.str().data() << utils::io::endl;
});
}
FWGPU_LOGI << " surface alpha mode: " << alphaModeStream.str() << utils::io::endl;
FWGPU_LOGI << " surface width: " << config.width << utils::io::endl;
FWGPU_LOGI << " surface height: " << config.height << utils::io::endl;
FWGPU_LOGI << " surface present mode: " << presentModeStream.str() << utils::io::endl;
}
#endif
wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount,
wgpu::TextureFormat const* availableFormats, bool useSRGBColorSpace) {
const std::array expectedColorFormats =
useSRGBColorSpace ?
std::array{
wgpu::TextureFormat::RGBA8UnormSrgb,
wgpu::TextureFormat::BGRA8UnormSrgb }
: std::array{
wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureFormat::BGRA8Unorm };
auto const firstFoundColorFormat = std::find_first_of(expectedColorFormats.begin(),
expectedColorFormats.end(), availableFormats, availableFormats + availableFormatsCount);
FILAMENT_CHECK_POSTCONDITION(firstFoundColorFormat != expectedColorFormats.end())
<< "Cannot find a suitable WebGPU swapchain "
<< (useSRGBColorSpace ? "sRGB" : "non-standard (e.g. linear) RGB") << " color format";
return *firstFoundColorFormat;
}
wgpu::PresentMode selectPresentMode(size_t availablePresentModesCount,
wgpu::PresentMode const* availablePresentModes) {
// Verify that our chosen present mode is supported. In practice all devices support the FIFO
// mode, but we check for it anyway for completeness. (and to avoid validation warnings)
const wgpu::PresentMode desiredPresentMode = wgpu::PresentMode::Fifo;
FILAMENT_CHECK_POSTCONDITION(
std::any_of(availablePresentModes, availablePresentModes + availablePresentModesCount,
[](const wgpu::PresentMode availablePresentMode) {
return availablePresentMode == desiredPresentMode;
}))
<< "Cannot find a suitable WebGPU swapchain present mode";
return desiredPresentMode;
}
wgpu::CompositeAlphaMode selectAlphaMode(size_t availableAlphaModesCount,
wgpu::CompositeAlphaMode const* availableAlphaModes) {
bool autoAvailable = false;
bool inheritAvailable = false;
bool opaqueAvailable = false;
bool premultipliedAvailable = false;
bool unpremultipliedAvailable = false;
std::for_each(availableAlphaModes, availableAlphaModes + availableAlphaModesCount,
[&](const wgpu::CompositeAlphaMode alphMode) {
switch (alphMode) {
// in practice, the surface capabilities would not list Auto,
// but for completeness and defensive programming we can leverage it
// if it explicitly comes back as available/supported
case wgpu::CompositeAlphaMode::Auto:
autoAvailable = true;
break;
case wgpu::CompositeAlphaMode::Opaque:
opaqueAvailable = true;
break;
case wgpu::CompositeAlphaMode::Premultiplied:
premultipliedAvailable = true;
break;
case wgpu::CompositeAlphaMode::Unpremultiplied:
unpremultipliedAvailable = true;
break;
case wgpu::CompositeAlphaMode::Inherit:
inheritAvailable = true;
break;
}
});
if (autoAvailable || inheritAvailable || opaqueAvailable) {
return wgpu::CompositeAlphaMode::Auto;
} else if (premultipliedAvailable) {
// In practice, we do not expect this to possibly happen, as opaque should be supported,
// which we select first. However, if the underlying system actually does not support
// opaque we allow it to be tested, but warn that this may not likely work as they expect
// (untested territory).
// We prefer premultiplied to unpremuliplied until that assumption should be adjusted.
FWGPU_LOGW << "Auto, Inherit, & Opaque composite alpha modes not supported. Filament has "
"historically used these. Premultiplied alpha composite mode for "
"transparency is being selected as a fallback, but may not work as expected."
<< utils::io::endl;
return wgpu::CompositeAlphaMode::Premultiplied;
} else {
FILAMENT_CHECK_POSTCONDITION(unpremultipliedAvailable)
<< "No available composite alpha modes? Unknown/unhandled composite alpha mode?";
// Again, we don't expect this in practice, but allow if for the same reason as premultiplied.
// We prefer premultiplied to unpremuliplied until that assumption should be adjusted.
FWGPU_LOGW << "Auto, Inherit, & Opaque composite alpha modes not supported. Filament has "
"historically used these. Unpremultiplied alpha composite mode for "
"transparency is being selected as a fallback "
"(premulitipled is not available either), but may not work as expected."
<< utils::io::endl;
return wgpu::CompositeAlphaMode::Unpremultiplied;
}
}
void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
wgpu::SurfaceCapabilities const& capabilities, wgpu::Extent2D const& surfaceSize,
bool useSRGBColorSpace) {
config.device = device;
config.usage = wgpu::TextureUsage::RenderAttachment;
config.width = surfaceSize.width;
config.height = surfaceSize.height;
config.format =
selectColorFormat(capabilities.formatCount, capabilities.formats, useSRGBColorSpace);
config.presentMode =
selectPresentMode(capabilities.presentModeCount, capabilities.presentModes);
config.alphaMode = selectAlphaMode(capabilities.alphaModeCount, capabilities.alphaModes);
}
}// namespace
namespace filament::backend {
WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize,
wgpu::Adapter& adapter, wgpu::Device& device, uint64_t flags)
: mSurface(surface) {
wgpu::SurfaceCapabilities capabilities = {};
if (!mSurface.GetCapabilities(adapter, &capabilities)) {
FWGPU_LOGW << "Failed to get WebGPU surface capabilities" << utils::io::endl;
} else {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printSurfaceCapabilitiesDetails(capabilities);
#endif
}
const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0;
initConfig(mConfig, device, capabilities, surfaceSize, useSRGBColorSpace);
mSurface.Configure(&mConfig);
}
WebGPUSwapChain::~WebGPUSwapChain() {
mSurface.Unconfigure();
}
void WebGPUSwapChain::setExtent(wgpu::Extent2D const& currentSurfaceSize) {
FILAMENT_CHECK_POSTCONDITION(currentSurfaceSize.width > 0 || currentSurfaceSize.height > 0)
<< "WebGPUSwapChain::setExtent: Invalid width " << currentSurfaceSize.width
<< " and/or height " << currentSurfaceSize.height << " requested.";
if (mConfig.width != currentSurfaceSize.width || mConfig.height != currentSurfaceSize.height) {
mConfig.width = currentSurfaceSize.width;
mConfig.height = currentSurfaceSize.height;
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printSurfaceConfiguration(mConfig);
#endif
FWGPU_LOGD << "Resizing to width " << mConfig.width << " height " << mConfig.height
<< utils::io::endl;
// TODO we may need to ensure no surface texture is flight when we do this. some
// synchronization may be necessary
mSurface.Configure(&mConfig);
}
}
wgpu::TextureView WebGPUSwapChain::getCurrentSurfaceTextureView(
wgpu::Extent2D const& currentSurfaceSize) {
setExtent(currentSurfaceSize);
wgpu::SurfaceTexture surfaceTexture;
mSurface.GetCurrentTexture(&surfaceTexture);
if (surfaceTexture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal) {
return nullptr;
}
// Create a view for this surface texture
// TODO: review these initiliazations as webgpu pipeline gets mature
wgpu::TextureViewDescriptor textureViewDescriptor = {
.label = "texture_view",
.format = surfaceTexture.texture.GetFormat(),
.dimension = wgpu::TextureViewDimension::e2D,
.baseMipLevel = 0,
.mipLevelCount = 1,
.baseArrayLayer = 0,
.arrayLayerCount = 1
};
return surfaceTexture.texture.CreateView(&textureViewDescriptor);
}
void WebGPUSwapChain::present() {
assert_invariant(mSurface);
mSurface.Present();
}
}// namespace filament::backend
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_WEBGPUSWAPCHAIN_H
#define TNT_FILAMENT_BACKEND_WEBGPUSWAPCHAIN_H
#include <webgpu/webgpu_cpp.h>
#include "DriverBase.h"
#include <backend/Platform.h>
#include <cstdint>
namespace filament::backend {
class WebGPUSwapChain final : public Platform::SwapChain, HwSwapChain {
public:
WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize,
wgpu::Adapter& adapter, wgpu::Device& device, uint64_t flags);
~WebGPUSwapChain();
wgpu::TextureView getCurrentSurfaceTextureView(wgpu::Extent2D const&);
void present();
private:
void setExtent(wgpu::Extent2D const&);
wgpu::Surface mSurface = {};
wgpu::SurfaceConfiguration mConfig = {};
};
} // namespace filament::backend
#endif //TNT_FILAMENT_BACKEND_WEBGPUSWAPCHAIN_H
@@ -18,6 +18,7 @@
#include <utils/Panic.h>
#include <android/native_window.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
@@ -28,6 +29,14 @@
namespace filament::backend {
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
ANativeWindow* window = static_cast<ANativeWindow*>(nativeWindow);
return wgpu::Extent2D{
.width = static_cast<uint32_t>(ANativeWindow_getWidth(window)),
.height = static_cast<uint32_t>(ANativeWindow_getHeight(window))
};
}
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) {
wgpu::SurfaceSourceAndroidNativeWindow surfaceSourceAndroidWindow{};
surfaceSourceAndroidWindow.window = nativeWindow;
@@ -24,21 +24,8 @@
#include <cstdint>
// Platform specific includes and defines
#if defined(__APPLE__)
#include <Cocoa/Cocoa.h>
#import <QuartzCore/CAMetalLayer.h>
#elif defined(FILAMENT_IOS)
// Metal is not available when building for the iOS simulator on Desktop.
#define METAL_AVAILABLE __has_include(<QuartzCore/CAMetalLayer.h>)
#if METAL_AVAILABLE
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
#endif
// is this needed?
#define METALVIEW_TAG 255
#else
#error Not a supported Apple + WebGPU platform
#endif
#include <Cocoa/Cocoa.h>
#import <QuartzCore/CAMetalLayer.h>
/**
* Apple (Mac OS and IOS) specific implementation aspects of the WebGPU backend
@@ -46,14 +33,19 @@
namespace filament::backend {
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
// Both IOS and MacOS expects CAMetalLayer.
CAMetalLayer* metalLayer = (__bridge CAMetalLayer*) nativeWindow;
return wgpu::Extent2D{
.width = static_cast<uint32_t>(metalLayer.drawableSize.width),
.height = static_cast<uint32_t>(metalLayer.drawableSize.height)
};
}
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) {
wgpu::Surface surface = nullptr;
#if defined(__APPLE__)
auto nsView = (__bridge NSView*) nativeWindow;
FILAMENT_CHECK_POSTCONDITION(nsView) << "Unable to obtain Metal-backed NSView.";
[nsView setWantsLayer:YES];
id metalLayer = [CAMetalLayer layer];
[nsView setLayer:metalLayer];
// Both IOS and MacOS expects CAMetalLayer.
CAMetalLayer* metalLayer = (__bridge CAMetalLayer*) nativeWindow;
wgpu::SurfaceSourceMetalLayer surfaceSourceMetalLayer{};
surfaceSourceMetalLayer.layer = (__bridge void*) metalLayer;
wgpu::SurfaceDescriptor surfaceDescriptor = {
@@ -62,19 +54,6 @@ wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags
};
surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface != nullptr) << "Unable to create Metal-backed surface.";
#elif defined(FILAMENT_IOS)
CAMetalLayer* metalLayer = (CAMetalLayer*) nativeWindow;
wgpu::SurfaceSourceMetalLayer surfaceSourceMetalLayer{};
surfaceSourceMetalLayer.layer = (__bridge void*) metalLayer;
wgpu::SurfaceDescriptor surfaceDescriptor = {
.nextInChain = &surfaceSourceMetalLayer,
.label = "metal_surface",
};
surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface != nullptr) << "Unable to create Metal-backed surface.";
#else
#error Not a supported Apple + WebGPU platform
#endif
return surface;
}
@@ -79,6 +79,74 @@
namespace filament::backend {
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
auto surfaceExtent = wgpu::Extent2D{};
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
wl* ptrval = reinterpret_cast<wl*>(nativeWindow);
surfaceExtent.width = ptrval->width;
surfaceExtent.height = ptrval->height;
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Unable to get window size for Linux Wayland-backed surface.";
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
if (g_x11.library == nullptr) {
g_x11.library = dlopen(LIBRARY_X11, RTLD_LOCAL | RTLD_NOW);
FILAMENT_CHECK_PRECONDITION(g_x11.library) << "Unable to open X11 library.";
#if defined(FILAMENT_SUPPORTS_XCB)
g_x11.xcbConnect = (XCB_CONNECT) dlsym(g_x11.library, "xcb_connect");
int screen = 0;
g_x11.connection = g_x11.xcbConnect(nullptr, &screen);
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
g_x11.openDisplay = (X11_OPEN_DISPLAY) dlsym(g_x11.library, "XOpenDisplay");
g_x11.display = g_x11.openDisplay(NULL);
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
#endif
}
#if defined(FILAMENT_SUPPORTS_XCB) || defined(FILAMENT_SUPPORTS_XLIB)
bool useXcb = false;
#endif
#if defined(FILAMENT_SUPPORTS_XCB)
#if defined(FILAMENT_SUPPORTS_XLIB)
useXcb = (SWAP_CHAIN_CONFIG_ENABLE_XCB) != 0;
#else
useXcb = true;
#endif
if (useXcb) {
const xcb_setup_t* setup = xcb_get_setup(g_x11.connection);
xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(setup);
xcb_screen_t* screen = screen_iter.data;
surfaceExtent.width = static_cast<uint32_t>(screen->width_in_pixels);
surfaceExtent.height = static_cast<uint32_t>(screen->height_in_pixels);
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Unable to get window surface size for Linux (or FreeBSD) "
"XCB-backed surface.";
}
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
if (!useXcb) {
int screenNumber = DefaultScreen(g_x11.display);
Screen* screen = ScreenOfDisplay(g_x11.display, screenNumber);
surfaceExtent.width = static_cast<uint32_t>(WidthOfScreen(screen));
surfaceExtent.height = static_cast<uint32_t>(HeightOfScreen(screen));
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Unable to get window surface size for Linux (or FreeBSD) "
"XLib-backed surface.";
}
#endif
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Cannot get window surface size for X11 surface for Linux (or FreeBSD) OS "
"(not built with support for XCB or XLIB?)";
#elif defined(__linux__)
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Cannot get window surface size for Linux (or FreeBSD) OS "
"(not built with support for Wayland or X11?)";
#else
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Not a supported (Linux) OS + WebGPU platform";
#endif
return surfaceExtent;
}
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t flags) {
wgpu::Surface surface = nullptr;
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
@@ -22,12 +22,24 @@
#include <cstdint>
#include <Windows.h>
/**
* Windows OS specific implementation aspects of the WebGPU backend
*/
namespace filament::backend {
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
HWND window = static_cast<HWND>(nativeWindow);
RECT windowRect;
GetWindowRect(window, &windowRect);
return wgpu::Extent2D{
.width = static_cast<uint32_t>(windowRect.right - windowRect.left),
.height = static_cast<uint32_t>(windowRect.bottom - windowRect.top)
};
}
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) {
// TODO verify this is necessary for Dawn implementation as well:
// On (at least) NVIDIA drivers, the Vulkan implementation (specifically the call to
@@ -41,12 +53,12 @@ wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags
wgpu::SurfaceSourceWindowsHWND surfaceSourceWin{};
surfaceSourceWin.hinstance = GetModuleHandle(nullptr);
surfaceSourceWin.hwnd = nativeWindow;
wgpu::SurfaceDescriptor surfaceDescriptor{
const wgpu::SurfaceDescriptor surfaceDescriptor{
.nextInChain = &surfaceSourceWin,
.label = "windows_surface"
};
wgpu::Surface surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface != nullptr) << "Unable to create Windows-backed surface.";
FILAMENT_CHECK_POSTCONDITION(surface.Get() != nullptr) << "Unable to create Windows-backed surface.";
return surface;
}
+26 -16
View File
@@ -23,30 +23,44 @@ namespace test {
using namespace filament::backend;
Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) {
utils::FixedCapacityVector<DescriptorSetLayoutBinding> kLayouts(config.uniformNames.size());
for (unsigned char i = 0; i < config.uniformNames.size(); ++i) {
kLayouts[i] =
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, i };
};
Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) : mCleanup(cleanup) {
utils::FixedCapacityVector<DescriptorSetLayoutBinding> kLayouts(config.uniforms.size());
for (unsigned char i = 0; i < config.uniforms.size(); ++i) {
kLayouts[i] = {
config.uniforms[i].type.value_or(DescriptorType::UNIFORM_BUFFER),
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, i };
}
// This assumes that the uniforms will all be in a single descriptor set at index 1.
// If there are shaders with uniforms in other sets then ShaderConfig will need to be expanded
// to accommodate that.
size_t kDescriptorSetIndex = 1;
filamat::DescriptorSets descriptors;
for (unsigned char i = 0; i < config.uniformNames.size(); ++i) {
descriptors[i + 1] = {{ config.uniformNames[i], kLayouts[i], {}}};
descriptors[kDescriptorSetIndex] = filamat::DescriptorSetInfo(config.uniforms.size());
for (unsigned char i = 0; i < config.uniforms.size(); ++i) {
descriptors[kDescriptorSetIndex][i] = {
config.uniforms[i].name, kLayouts[i], config.uniforms[i].samplerInfo };
}
ShaderGenerator shaderGen(
std::move(config.vertexShader), std::move(config.fragmentShader), BackendTest::sBackend,
BackendTest::sIsMobilePlatform, std::move(descriptors));
Program prog = shaderGen.getProgram(api);
for (unsigned char i = 0; i < config.uniformNames.size(); ++i) {
prog.descriptorBindings(1, {{ config.uniformNames[i], DescriptorType::UNIFORM_BUFFER, i }});
Program::DescriptorBindingsInfo bindingsInfo(config.uniforms.size());
for (unsigned char i = 0; i < config.uniforms.size(); ++i) {
bindingsInfo[i] = {
config.uniforms[i].name,
config.uniforms[i].type.value_or(DescriptorType::UNIFORM_BUFFER), i };
}
prog.descriptorBindings(1, bindingsInfo);
mProgram = cleanup.add(api.createProgram(std::move(prog)));
mDescriptorSetLayout = cleanup.add(
api.createDescriptorSetLayout(DescriptorSetLayout{ kLayouts }));
api.createDescriptorSetLayout(DescriptorSetLayout{ "kLayouts", kLayouts }));
}
mDescriptorSet = cleanup.add(api.createDescriptorSet(mDescriptorSetLayout));
filament::backend::DescriptorSetHandle Shader::createDescriptorSet(DriverApi& api) const {
return mCleanup.add(api.createDescriptorSet(mDescriptorSetLayout));
}
filament::backend::ProgramHandle Shader::getProgram() const {
@@ -57,8 +71,4 @@ filament::backend::DescriptorSetLayoutHandle Shader::getDescriptorSetLayout() co
return mDescriptorSetLayout;
}
filament::backend::DescriptorSetHandle Shader::getDescriptorSet() const {
return mDescriptorSet;
}
} // namespace test
+26 -6
View File
@@ -18,14 +18,22 @@
#define TNT_SHADER_H
#include "Lifetimes.h"
#include "private/filament/SamplerInterfaceBlock.h"
namespace test {
struct UniformConfig {
utils::CString name;
// If not specified this will be DescriptorType::UNIFORM_BUFFER
std::optional<filament::backend::DescriptorType> type;
std::optional<filament::SamplerInterfaceBlock::SamplerInfo> samplerInfo;
};
// All describing a shader that should be created.
struct ShaderConfig {
std::string vertexShader;
std::string fragmentShader;
std::vector<utils::CString> uniformNames;
std::vector<UniformConfig> uniforms;
};
// All values describing a uniform.
@@ -35,6 +43,7 @@ struct ResolvedUniformBindingConfig {
uint32_t byteOffset;
filament::backend::descriptor_set_t set;
filament::backend::descriptor_binding_t binding;
std::optional<filament::backend::DescriptorSetHandle> descriptorSet;
};
// An equivalent to ResolvedUniformBindingConfig with all fields optional.
@@ -46,6 +55,7 @@ struct UniformBindingConfig {
std::optional<uint32_t> byteOffset;
std::optional<filament::backend::descriptor_set_t> set;
std::optional<filament::backend::descriptor_binding_t> binding;
std::optional<filament::backend::DescriptorSetHandle> descriptorSet;
template<typename UniformType>
ResolvedUniformBindingConfig resolve();
@@ -79,12 +89,14 @@ public:
filament::backend::ProgramHandle getProgram() const;
filament::backend::DescriptorSetLayoutHandle getDescriptorSetLayout() const;
filament::backend::DescriptorSetHandle getDescriptorSet() const;
filament::backend::DescriptorSetHandle createDescriptorSet(
filament::backend::DriverApi& api) const;
protected:
Cleanup& mCleanup;
filament::backend::ProgramHandle mProgram;
filament::backend::DescriptorSetLayoutHandle mDescriptorSetLayout;
filament::backend::DescriptorSetHandle mDescriptorSet;
};
template<typename UniformType>
@@ -95,7 +107,8 @@ ResolvedUniformBindingConfig UniformBindingConfig::resolve() {
.bufferSize = bufferSize.value_or(resolvedDataSize),
.byteOffset = byteOffset.value_or(0),
.set = set.value_or(1),
.binding = binding.value_or(0)
.binding = binding.value_or(0),
.descriptorSet = descriptorSet
};
}
@@ -120,9 +133,16 @@ void Shader::bindUniform(filament::backend::DriverApi& api,
UniformBindingConfig config) const {
auto resolvedConfig = config.resolve<UniformType>();
api.updateDescriptorSetBuffer(getDescriptorSet(), resolvedConfig.binding, hwBuffer, 0,
filament::backend::DescriptorSetHandle descriptorSet;
if (resolvedConfig.descriptorSet.has_value()) {
descriptorSet = *resolvedConfig.descriptorSet;
} else {
descriptorSet = createDescriptorSet(api);
}
api.updateDescriptorSetBuffer(descriptorSet, resolvedConfig.binding, hwBuffer, 0,
resolvedConfig.bufferSize);
api.bindDescriptorSet(getDescriptorSet(), resolvedConfig.set, {});
api.bindDescriptorSet(descriptorSet, resolvedConfig.set, {});
}
template<typename UniformType>
+253
View File
@@ -0,0 +1,253 @@
/*
* Copyright (C) 2021 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 "SharedShaders.h"
#include "Shader.h"
#include "absl/strings/str_format.h"
#include "gtest/gtest.h"
namespace test {
using namespace filament::backend;
namespace {
// A shader stored in pieces so that uniform declarations can be injected.
struct ShaderText {
std::string mPrefix;
std::string mBody;
std::string withUniform(const std::string& uniformText) const {
return absl::StrFormat("%s\n%s\n%s", mPrefix.c_str(), uniformText.c_str(), mBody.c_str());
}
};
std::optional<ShaderText> GetGlslVertexShader(VertexShaderType type) {
switch (type) {
case VertexShaderType::Noop: {
return ShaderText{
R"(
#version 450 core
layout(location = 0) in vec4 mesh_position;
)", R"(
void main() {
gl_Position = vec4(mesh_position.xy, 0.0, 1.0);
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
gl_Position.y = -gl_Position.y;
#endif
})" };
}
case VertexShaderType::Simple: {
return ShaderText{
R"(
#version 450 core
layout(location = 0) in vec4 mesh_position;
)", R"(
void main() {
gl_Position = vec4(
mesh_position.xy * (params.scaleMinusOne.xy + 1.0) + params.offset.xy,
params.scaleMinusOne.z + 1.0,
1.0);
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
gl_Position.y = -gl_Position.y;
#endif
})" };
}
case VertexShaderType::Textured: {
return ShaderText{
R"(
#version 450 core
layout(location = 0) in vec4 mesh_position;
layout(location = 0) out vec2 uv;
)", R"(
void main() {
gl_Position = vec4(mesh_position.xy, 0.0, 1.0);
uv = (mesh_position.xy * 0.5 + 0.5);
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
gl_Position.y = -gl_Position.y;
#endif
})" };
}
default:
return std::nullopt;
}
}
std::optional<ShaderText> GetGlslFragmentShader(FragmentShaderType type) {
switch (type) {
case FragmentShaderType::White: {
return ShaderText{
R"(
#version 450 core
precision mediump int; precision highp float;
layout(location = 0) out vec4 fragColor;
)", R"(
void main() {
fragColor = vec4(1.0, 1.0, 1.0, 1.0);
})" };
}
case FragmentShaderType::SolidColored: {
return ShaderText{
R"(
#version 450 core
precision mediump int; precision highp float;
layout(location = 0) out vec4 fragColor;
)", R"(
void main() {
fragColor = params.color;
})" };
}
case FragmentShaderType::Textured: {
return ShaderText{
R"(
#version 450 core
precision mediump int; precision highp float;
layout(location = 0) out vec4 fragColor;
layout(location = 0) in vec2 uv;
)", R"(
void main() {
fragColor = texture(test_tex, uv);
})" };
}
default:
return std::nullopt;
}
}
std::optional<std::string> GetGlslUniform(ShaderUniformType type) {
switch (type) {
case ShaderUniformType::None: {
return "";
}
case ShaderUniformType::Simple: {
return R"(
layout(binding = 0, set = 1) uniform Params {
highp vec4 color;
// Use scaleMinusOne instead of scale so that a 0 initialized value is a good default
highp vec4 scaleMinusOne;
highp vec4 offset;
} params;
)";
}
case ShaderUniformType::SimpleWithPadding: {
return R"(
layout(binding = 0, set = 1) uniform Params {
highp vec4 padding[4]; // offset of 64 bytes
highp vec4 color;
// Use scaleMinusOne instead of scale so that a 0 initialized value is a good default
highp vec4 scaleMinusOne;
highp vec4 offset;
} params;
)";
}
case ShaderUniformType::Sampler: {
return R"(
layout(location = 0, set = 1) uniform sampler2D test_tex;
)";
}
default:
return std::nullopt;
}
}
std::vector<UniformConfig> GetUniformConfig(ShaderUniformType type) {
switch (type) {
case ShaderUniformType::None: {
return {};
}
case ShaderUniformType::Simple: {
return {{ "Params" }};
}
case ShaderUniformType::SimpleWithPadding: {
return {{ "Params" }};
}
case ShaderUniformType::Sampler: {
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo{
"backend_test", "test_tex", 0,
SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH, false };
return {{
"test_tex", DescriptorType::SAMPLER, samplerInfo
}};
}
default:
abort();
}
}
ShaderLanguage getShaderLanguage(const Backend& backend) {
switch (backend) {
case Backend::METAL:
return ShaderLanguage::MSL;
case Backend::WEBGPU:
return ShaderLanguage::WGSL;
case Backend::VULKAN:
case Backend::NOOP:
case Backend::OPENGL:
default: {
return ShaderLanguage::GLSL;
}
}
}
} // namespace
Shader SharedShaders::makeShader(filament::backend::DriverApi& api, Cleanup& cleanup,
ShaderRequest request) {
std::optional<ShaderText> vertex;
std::optional<ShaderText> fragment;
std::optional<std::string> uniform;
if (getShaderLanguage(BackendTest::sBackend) != ShaderLanguage::GLSL) {
// TODO: If any shaders need backend/shader language specific shaders rather than transpiled
// versions of the GLSL shader, check environment.
}
vertex = GetGlslVertexShader(request.mVertexType);
fragment = GetGlslFragmentShader(request.mFragmentType);
uniform = GetGlslUniform(request.mUniformType);
if (vertex.has_value() && fragment.has_value() && uniform.has_value()) {
return Shader(
api, cleanup, ShaderConfig{
vertex->withUniform(*uniform), fragment->withUniform(*uniform),
GetUniformConfig(request.mUniformType)}
);
}
abort();
}
std::string SharedShaders::getVertexShaderText(VertexShaderType vertex, ShaderUniformType uniform) {
std::optional<ShaderText> vertexText = GetGlslVertexShader(vertex);
std::optional<std::string> uniformText = GetGlslUniform(uniform);
if (!vertexText.has_value() || !uniformText.has_value()) {
abort();
}
return vertexText->withUniform(*uniformText);
}
std::string SharedShaders::getFragmentShaderText(FragmentShaderType fragment,
ShaderUniformType uniform) {
std::optional<ShaderText> fragmentText = GetGlslFragmentShader(fragment);
std::optional<std::string> uniformText = GetGlslUniform(uniform);
if (!fragmentText.has_value() || !uniformText.has_value()) {
abort();
}
return fragmentText->withUniform(*uniformText);
}
} // namespace test
+50
View File
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2021 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_SHAREDSHADERS_H
#define TNT_SHAREDSHADERS_H
#include "Shader.h"
#include "SharedShadersConstants.h"
#include "Lifetimes.h"
#include "PlatformRunner.h"
namespace test {
enum class ShaderLanguage : uint8_t {
GLSL,
MSL,
WGSL
};
struct ShaderRequest {
VertexShaderType mVertexType;
FragmentShaderType mFragmentType;
ShaderUniformType mUniformType;
};
class SharedShaders {
public:
static Shader makeShader(filament::backend::DriverApi& api, Cleanup& cleanup,
ShaderRequest request);
static std::string getVertexShaderText(VertexShaderType vertex, ShaderUniformType uniform);
static std::string getFragmentShaderText(FragmentShaderType fragment,
ShaderUniformType uniform);
};
} // namespace test
#endif //TNT_SHAREDSHADERS_H
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2021 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_SHAREDSHADERSCONSTANTS_H
#define TNT_SHAREDSHADERSCONSTANTS_H
#include "math/mathfwd.h"
enum class ShaderUniformType : uint8_t {
None,
Simple,
SimpleWithPadding,
Sampler,
};
struct SimpleMaterialParams {
filament::math::float4 color;
// 1.0 will be added to this value before use.
// The XY values are used to scale position inputs and the Z value is used to set the output
// position's Z value.
filament::math::float4 scaleMinusOne;
// Offset will be applied after scale
filament::math::float4 offset;
};
struct SimpleWithPaddingMaterialParams {
// The associated uniform structure in the shader will have 64 bytes of padding at the beginning
// So users of this struct will need to add 64 bytes to its size and offset all uniform writes.
filament::math::float4 color;
// 1.0 will be added to this value before use.
// The XY values are used to scale position inputs and the Z value is used to set the output
// position's Z value.
filament::math::float4 scaleMinusOne;
// Offset will be applied after scale
filament::math::float4 offset;
};
enum class VertexShaderType : uint8_t {
Noop,
Simple,
Textured
};
enum class FragmentShaderType : uint8_t {
White,
SolidColored,
Textured
};
#endif //TNT_SHAREDSHADERSCONSTANTS_H
+12 -40
View File
@@ -19,6 +19,7 @@
#include "ImageExpectations.h"
#include "Lifetimes.h"
#include "Shader.h"
#include "SharedShaders.h"
#include "TrianglePrimitive.h"
#include <utils/Hash.h>
@@ -40,48 +41,14 @@ using namespace filament::backend;
using namespace filament::math;
using namespace utils;
struct MaterialParams {
float4 color;
float4 scale;
};
class BlitTest : public BackendTest {
public:
BlitTest() : mCleanup(getDriverApi()) {}
protected:
Shader createShader();
Cleanup mCleanup;
};
static const char* const triangleVs = R"(#version 450 core
layout(location = 0) in vec4 mesh_position;
layout(binding = 0, set = 1) uniform Params { highp vec4 color; highp vec4 scale; } params;
void main() {
gl_Position = vec4((mesh_position.xy + 0.5) * params.scale.xy, params.scale.z, 1.0);
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
gl_Position.y = -gl_Position.y;
#endif
})";
static const char* const triangleFs = R"(#version 450 core
precision mediump int; precision highp float;
layout(location = 0) out vec4 fragColor;
layout(binding = 0, set = 1) uniform Params { highp vec4 color; highp vec4 scale; } params;
void main() {
fragColor = params.color;
})";
Shader BlitTest::createShader() {
return Shader(getDriverApi(), mCleanup, ShaderConfig{
.vertexShader = triangleVs,
.fragmentShader = triangleFs,
.uniformNames = { "Params" },
});
}
static uint32_t toUintColor(float4 color) {
color = saturate(color);
uint32_t r = color.r * 255.0f;
@@ -313,7 +280,11 @@ TEST_F(BlitTest, ColorResolve) {
constexpr auto kColorTexFormat = TextureFormat::RGBA8;
constexpr int kSampleCount = 4;
Shader shader = createShader();
Shader shader = SharedShaders::makeShader(api, mCleanup, ShaderRequest{
.mVertexType = VertexShaderType::Simple,
.mFragmentType = FragmentShaderType::SolidColored,
.mUniformType = ShaderUniformType::Simple,
});
// Create a VertexBuffer, IndexBuffer, and RenderPrimitive.
TrianglePrimitive const triangle(api);
@@ -356,14 +327,15 @@ TEST_F(BlitTest, ColorResolve) {
state.rasterState.depthFunc = RasterState::DepthFunc::A;
state.rasterState.culling = CullingMode::NONE;
auto ubuffer = mCleanup.add(api.createBufferObject(sizeof(MaterialParams),
auto ubuffer = mCleanup.add(api.createBufferObject(sizeof(SimpleMaterialParams),
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
// Draw red triangle into srcRenderTarget.
shader.uploadUniform(api, ubuffer, MaterialParams{
.color = float4(1, 0, 0, 1),
.scale = float4(1, 1, 0.5, 0),
shader.uploadUniform(api, ubuffer, SimpleMaterialParams{
.color = float4(1, 0, 0, 1),
.scaleMinusOne = float4(0, 0, -0.5, 0),
.offset = float4(0.5, 0.5, 0, 0),
});
shader.bindUniform<MaterialParams>(api, ubuffer);
shader.bindUniform<SimpleMaterialParams>(api, ubuffer);
// FIXME: on Metal this triangle is not drawn. Can't understand why.
{
+35 -83
View File
@@ -18,72 +18,18 @@
#include "Lifetimes.h"
#include "Shader.h"
#include "ShaderGenerator.h"
#include "SharedShaders.h"
#include "TrianglePrimitive.h"
namespace {
////////////////////////////////////////////////////////////////////////////////////////////////////
// Shaders
////////////////////////////////////////////////////////////////////////////////////////////////////
std::string vertex (R"(#version 450 core
layout(location = 0) in vec4 mesh_position;
layout(location = 0) out uvec4 indices;
layout(binding = 0, set = 1) uniform Params {
highp vec4 padding[4]; // offset of 64 bytes
highp vec4 color;
highp vec4 offset;
} params;
void main() {
gl_Position = vec4(mesh_position.xy + params.offset.xy, 0.0, 1.0);
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
gl_Position.y = -gl_Position.y;
#endif
}
)");
std::string fragment (R"(#version 450 core
layout(location = 0) out vec4 fragColor;
layout(binding = 0, set = 1) uniform Params {
highp vec4 padding[4]; // offset of 64 bytes
highp vec4 color;
highp vec4 offset;
} params;
void main() {
fragColor = vec4(params.color.rgb, 1.0f);
}
)");
}
namespace test {
using namespace filament;
using namespace filament::backend;
// In the shader, these MaterialParams are offset by 64 bytes into the uniform buffer to test buffer
// updates with offset.
struct MaterialParams {
math::float4 color;
math::float4 offset;
};
static_assert(sizeof(MaterialParams) == 8 * sizeof(float));
// Uniform config for writing MaterialParams to the shader uniform with 64 bytes of padding.
const UniformBindingConfig kBindingConfig = {
.dataSize = sizeof(MaterialParams),
.bufferSize = sizeof(MaterialParams) + 64,
.dataSize = sizeof(SimpleMaterialParams),
.bufferSize = sizeof(SimpleMaterialParams) + 64,
.byteOffset = 64
};
@@ -93,8 +39,10 @@ public:
protected:
Shader createShader() {
return Shader(getDriverApi(), mCleanup, ShaderConfig{
vertex, fragment, {"Params"}
return SharedShaders::makeShader(getDriverApi(), mCleanup, ShaderRequest{
.mVertexType = VertexShaderType::Simple,
.mFragmentType = FragmentShaderType::SolidColored,
.mUniformType = ShaderUniformType::SimpleWithPadding
});
}
@@ -129,7 +77,7 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = {0.f, 1.f, 0.f, 1.f};
params.clearColor = { 0.f, 1.f, 0.f, 1.f };
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
@@ -144,17 +92,18 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
// Create a uniform buffer.
// We use STATIC here, even though the buffer is updated, to force the Metal backend to use
// a GPU buffer, which is more interesting to test.
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(MaterialParams) + 64,
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(SimpleMaterialParams) + 64,
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
shader.bindUniform<MaterialParams>(api, ubuffer, kBindingConfig);
shader.bindUniform<SimpleMaterialParams>(api, ubuffer, kBindingConfig);
api.startCapture(0);
// Upload the uniform, but with an offset to accommodate the padding in the shader's
// uniform definition.
shader.uploadUniform(api, ubuffer, kBindingConfig, MaterialParams{
shader.uploadUniform(api, ubuffer, kBindingConfig, SimpleMaterialParams{
.color = { 1.0f, 1.0f, 1.0f, 1.0f },
.scaleMinusOne = { 0.0, 0.0, 0.0, 0.0 },
.offset = { 0.0f, 0.0f, 0.0f, 0.0f }
});
@@ -165,19 +114,21 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
size_t triangleIndex = 0;
for (float i = -1.0f; i < 1.0f; i += 0.2f) {
const float low = i, high = i + 0.2;
const filament::math::float2 v[3] {{low, low}, {high, low}, {low, high}};
const filament::math::float2 v[3]{{ low, low },
{ high, low },
{ low, high }};
triangle.updateVertices(v);
if (updateIndices) {
if (triangleIndex % 2 == 0) {
// Upload each index separately, to test offsets.
const TrianglePrimitive::index_type i[3] {0, 1, 2};
const TrianglePrimitive::index_type i[3]{ 0, 1, 2 };
triangle.updateIndices(i + 0, 1, 0);
triangle.updateIndices(i + 1, 1, 1);
triangle.updateIndices(i + 2, 1, 2);
} else {
// This effectively hides this triangle.
const TrianglePrimitive::index_type i[3] {0, 0, 0};
const TrianglePrimitive::index_type i[3]{ 0, 0, 0 };
triangle.updateIndices(i);
}
}
@@ -220,28 +171,29 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
// Create a uniform buffer.
// We use STATIC here, even though the buffer is updated, to force the Metal backend to use a
// GPU buffer, which is more interesting to test.
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(MaterialParams) + 64,
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(SimpleMaterialParams) + 64,
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
shader.bindUniform<MaterialParams>(api, ubuffer, kBindingConfig);
shader.bindUniform<SimpleMaterialParams>(api, ubuffer, kBindingConfig);
// Create a render target.
auto colorTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT));
auto renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR0, 512, 512, 1, 0, {{colorTexture}}, {}, {}));
TargetBufferFlags::COLOR0, 512, 512, 1, 0, {{ colorTexture }}, {}, {}));
// Upload uniforms for the first triangle.
// Upload the uniform, but with an offset to accommodate the padding in the shader's
// uniform definition.
shader.uploadUniform(api, ubuffer, kBindingConfig, MaterialParams{
shader.uploadUniform(api, ubuffer, kBindingConfig, SimpleMaterialParams{
.color = { 1.0f, 0.0f, 0.5f, 1.0f },
.scaleMinusOne = { 0.0f, 0.0f, 0.0f, 0.0f },
.offset = { 0.0f, 0.0f, 0.0f, 0.0f }
});
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = {0.f, 0.f, 1.f, 1.f};
params.clearColor = { 0.f, 0.f, 1.f, 1.f };
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
params.viewport.height = 512;
@@ -250,18 +202,18 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
renderTarget, swapChain, shader.getProgram(), params);
// Upload uniforms for the second triangle. To test partial buffer updates, we'll only update
// color.b, color.a, offset.x, and offset.y.
shader.uploadUniform(api, ubuffer, UniformBindingConfig{
.dataSize = sizeof(std::array<float, 4>),
.bufferSize = kBindingConfig.bufferSize,
.byteOffset = *kBindingConfig.byteOffset + offsetof(MaterialParams, color.b),
},
std::array<float, 4>{
// color.b, color.a
1.0f, 1.0f,
// offset.x, offset.y
0.5f, 0.5f }
);
// color.b, color.a, scaleMinusOne, offset.x, and offset.y.
const UniformBindingConfig partialBindingConfig = {
.dataSize = sizeof(float) * 8,
.bufferSize = sizeof(SimpleMaterialParams) + 64,
.byteOffset = 64 + offsetof(SimpleMaterialParams, color.b)
};
shader.uploadUniform(api, ubuffer, partialBindingConfig,
std::array<float, 8>{
1.0f, 1.0f, // color.b, color.a
0.0f, 0.0f, 0.0f, 0.0f, // scale
0.5f, 0.5f // offset.x, offset.y
});
params.flags.clear = TargetBufferFlags::NONE;
params.flags.discardStart = TargetBufferFlags::NONE;
+28 -55
View File
@@ -17,7 +17,7 @@
#include "BackendTest.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "Shader.h"
#include "TrianglePrimitive.h"
#include <backend/DriverEnums.h>
@@ -96,16 +96,6 @@ struct MaterialParams {
float unused;
};
static void uploadUniforms(DriverApi& dapi, Handle<HwBufferObject> ubh, MaterialParams params) {
MaterialParams* tmp = new MaterialParams(params);
auto cb = [](void* buffer, size_t size, void* user) {
MaterialParams* sp = (MaterialParams*) buffer;
delete sp;
};
BufferDescriptor bd(tmp, sizeof(MaterialParams), cb);
dapi.updateBufferObject(ubh, std::move(bd), 0);
}
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt) {
const size_t size = kTexWidth * kTexHeight * 4;
void* buffer = calloc(1, size);
@@ -140,38 +130,13 @@ TEST_F(BackendTest, FeedbackLoops) {
api.makeCurrent(swapChain, swapChain);
// Create a program.
ProgramHandle program;
{
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH, false };
filamat::DescriptorSets descriptors;
descriptors[1] = {
{ "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 },
samplerInfo },
{ "Params", { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, 1 }, {} }
};
ShaderGenerator shaderGen(fullscreenVs, fullscreenFs, sBackend, sIsMobilePlatform,
std::move(descriptors));
Program prog = shaderGen.getProgram(api);
prog.descriptorBindings(1, {
{ "test_tex", DescriptorType::SAMPLER, 0 },
{ "Params", DescriptorType::UNIFORM_BUFFER, 1 }
});
program = cleanup.add(api.createProgram(std::move(prog)));
}
DescriptorSetLayoutHandle descriptorSetLayout = cleanup.add(api.createDescriptorSetLayout({
{{
DescriptorType::SAMPLER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
},
{
DescriptorType::UNIFORM_BUFFER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 1,
DescriptorFlags::NONE, 0
}}}));
Shader shader = Shader(api, cleanup, ShaderConfig {
.vertexShader = fullscreenVs,
.fragmentShader = fullscreenFs,
.uniforms = {{"test_tex", DescriptorType::SAMPLER, samplerInfo}, {"Params"}}
});
TrianglePrimitive const triangle(getDriverApi());
@@ -218,8 +183,8 @@ TEST_F(BackendTest, FeedbackLoops) {
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
state.program = program;
state.pipelineLayout.setLayout[1] = { descriptorSetLayout };
state.program = shader.getProgram();
state.pipelineLayout.setLayout[1] = { shader.getDescriptorSetLayout() };
api.makeCurrent(swapChain, swapChain);
api.beginFrame(0, 0, 0);
@@ -233,20 +198,24 @@ TEST_F(BackendTest, FeedbackLoops) {
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
auto descriptorSet = shader.createDescriptorSet(api);
auto textureView = passCleanup.add(api.createTextureView(texture, sourceLevel, 1));
DescriptorSetHandle descriptorSet = passCleanup.add(api.createDescriptorSet(descriptorSetLayout));
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
api.updateDescriptorSetBuffer(descriptorSet, 1, ubuffer, 0, sizeof(MaterialParams));
api.bindDescriptorSet(descriptorSet, 1, {});
uploadUniforms(getDriverApi(), ubuffer, {
UniformBindingConfig uniformBinding{
.binding = 1,
.descriptorSet = descriptorSet
};
shader.bindUniform<MaterialParams>(api, ubuffer, uniformBinding);
shader.uploadUniform(api, ubuffer, uniformBinding, MaterialParams{
.fbWidth = float(params.viewport.width),
.fbHeight = float(params.viewport.height),
.sourceLevel = float(sourceLevel),
});
api.beginRenderPass(renderTargets[targetLevel], params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
@@ -262,20 +231,24 @@ TEST_F(BackendTest, FeedbackLoops) {
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
auto descriptorSet = shader.createDescriptorSet(api);
auto textureView = passCleanup.add(api.createTextureView(texture, sourceLevel, 1));
DescriptorSetHandle descriptorSet = passCleanup.add(api.createDescriptorSet(descriptorSetLayout));
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
api.updateDescriptorSetBuffer(descriptorSet, 1, ubuffer, 0, sizeof(MaterialParams));
api.bindDescriptorSet(descriptorSet, 1, {});
uploadUniforms(getDriverApi(), ubuffer, {
.fbWidth = float(params.viewport.width),
.fbHeight = float(params.viewport.height),
.sourceLevel = float(sourceLevel),
UniformBindingConfig uniformBinding{
.binding = 1,
.descriptorSet = descriptorSet
};
shader.bindUniform<MaterialParams>(api, ubuffer, uniformBinding);
shader.uploadUniform(api, ubuffer, uniformBinding, MaterialParams{
.fbWidth = float(params.viewport.width),
.fbHeight = float(params.viewport.height),
.sourceLevel = float(sourceLevel),
});
api.beginRenderPass(renderTargets[targetLevel], params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
+61 -133
View File
@@ -18,16 +18,14 @@
#include "BackendTestUtils.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "TrianglePrimitive.h"
#include "Shader.h"
#include "SharedShaders.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include "private/filament/SamplerInterfaceBlock.h"
#include <math/half.h>
#include <vector>
#include <stddef.h>
@@ -42,19 +40,6 @@ namespace {
// Shaders
////////////////////////////////////////////////////////////////////////////////////////////////////
std::string vertex (R"(#version 450 core
layout(location = 0) in vec4 mesh_position;
void main() {
gl_Position = vec4(mesh_position.xy, 0.0, 1.0);
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
gl_Position.y *= -1.0f;
#endif
}
)");
std::string fragmentTemplate (R"(#version 450 core
layout(location = 0) out vec4 fragColor;
@@ -119,6 +104,15 @@ namespace test {
template<typename componentType> inline componentType getMaxValue();
class LoadImageTest : public BackendTest {
public:
LoadImageTest() {
mVertexShader = SharedShaders::getVertexShaderText(VertexShaderType::Noop,
ShaderUniformType::None);
}
std::string mVertexShader;
};
inline std::string stringReplace(const std::string& find, const std::string& replace,
@@ -216,7 +210,7 @@ static SamplerFormat getSamplerFormat(TextureFormat textureFormat) {
}
}
TEST_F(BackendTest, UpdateImage2D) {
TEST_F(LoadImageTest, UpdateImage2D) {
// All of these test cases should result in the same rendered image, and thus the same hash.
static const uint32_t expectedHash = 3644679986;
@@ -306,28 +300,14 @@ TEST_F(BackendTest, UpdateImage2D) {
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, getSamplerFormat(t.textureFormat), Precision::HIGH, false };
filamat::DescriptorSets descriptors;
descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 },
samplerInfo } };
std::string const fragment = stringReplace("{samplerType}",
getSamplerTypeName(t.textureFormat), fragmentTemplate);
ShaderGenerator shaderGen(
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
Program prog = shaderGen.getProgram(api);
prog.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
ProgramHandle const program = cleanup.add(api.createProgram(std::move(prog)));
DescriptorSetLayoutHandle descriptorSetLayout = cleanup.add(api.createDescriptorSetLayout({
{{
DescriptorType::SAMPLER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}}));
DescriptorSetHandle descriptorSet = cleanup.add(api.createDescriptorSet(descriptorSetLayout));
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = mVertexShader,
.fragmentShader= fragment,
.uniforms = {{"test_tex", DescriptorType::SAMPLER, samplerInfo}}
});
// Create a Texture.
auto usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
@@ -349,14 +329,15 @@ TEST_F(BackendTest, UpdateImage2D) {
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 512, t.bufferPadding));
}
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
api.bindDescriptorSet(descriptorSet, 1, {});
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
defaultRenderTarget, swapChain, program);
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
defaultRenderTarget, swapChain, shader.getProgram());
readPixelsAndAssertHash(t.name, 512, 512, defaultRenderTarget, expectedHash);
@@ -370,8 +351,9 @@ TEST_F(BackendTest, UpdateImage2D) {
flushAndWait();
}
TEST_F(BackendTest, UpdateImageSRGB) {
TEST_F(LoadImageTest, UpdateImageSRGB) {
auto& api = getDriverApi();
Cleanup cleanup(api);
api.startCapture();
PixelDataFormat const pixelFormat = PixelDataFormat::RGBA;
@@ -379,36 +361,23 @@ TEST_F(BackendTest, UpdateImageSRGB) {
TextureFormat const textureFormat = TextureFormat::SRGB8_A8;
// Create a platform-specific SwapChain and make it current.
auto swapChain = createSwapChain();
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto defaultRenderTarget = api.createDefaultRenderTarget(0);
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, getSamplerFormat(textureFormat), Precision::HIGH, false };
filamat::DescriptorSets descriptors;
descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 },
samplerInfo } };
std::string const fragment = stringReplace("{samplerType}",
getSamplerTypeName(textureFormat), fragmentTemplate);
ShaderGenerator shaderGen(
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
Program prog = shaderGen.getProgram(api);
prog.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
ProgramHandle const program = api.createProgram(std::move(prog));
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
{{
DescriptorType::SAMPLER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}});
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = mVertexShader, .fragmentShader = fragment, .uniforms = {{
"text_tex", DescriptorType::SAMPLER, samplerInfo
}}});
// Create a texture.
Handle<HwTexture> const texture = api.createTexture(SamplerType::SAMPLER_2D, 1,
textureFormat, 1, 512, 512, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE);
Handle<HwTexture> const texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
textureFormat, 1, 512, 512, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE));
// Create image data.
size_t components; int bpp;
@@ -436,6 +405,7 @@ TEST_F(BackendTest, UpdateImageSRGB) {
api.beginFrame(0, 0, 0);
// Update samplers.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
@@ -443,8 +413,8 @@ TEST_F(BackendTest, UpdateImageSRGB) {
api.bindDescriptorSet(descriptorSet, 1, {});
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
defaultRenderTarget, swapChain, program);
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
defaultRenderTarget, swapChain, shader.getProgram());
static const uint32_t expectedHash = 359858623;
readPixelsAndAssertHash("UpdateImageSRGB", 512, 512, defaultRenderTarget, expectedHash);
@@ -453,21 +423,14 @@ TEST_F(BackendTest, UpdateImageSRGB) {
api.commit(swapChain);
api.endFrame(0);
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyRenderTarget(defaultRenderTarget);
// This ensures all driver commands have finished before exiting the test.
api.finish();
api.stopCapture();
flushAndWait();
}
TEST_F(BackendTest, UpdateImageMipLevel) {
TEST_F(LoadImageTest, UpdateImageMipLevel) {
auto& api = getDriverApi();
Cleanup cleanup(api);
api.startCapture();
PixelDataFormat pixelFormat = PixelDataFormat::RGBA;
@@ -475,38 +438,27 @@ TEST_F(BackendTest, UpdateImageMipLevel) {
TextureFormat textureFormat = TextureFormat::RGBA32F;
// Create a platform-specific SwapChain and make it current.
auto swapChain = createSwapChain();
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto defaultRenderTarget = api.createDefaultRenderTarget(0);
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, getSamplerFormat(textureFormat), Precision::HIGH, false };
filamat::DescriptorSets descriptors;
descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 },
samplerInfo } };
std::string const fragment = stringReplace("{samplerType}",
getSamplerTypeName(textureFormat), fragmentUpdateImageMip);
ShaderGenerator shaderGen(
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
Program prog = shaderGen.getProgram(api);
prog.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
ProgramHandle const program = api.createProgram(std::move(prog));
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
{{
DescriptorType::SAMPLER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}});
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
Shader shader(api, cleanup, ShaderConfig {
.vertexShader = mVertexShader,
.fragmentShader = fragment,
.uniforms = {{"test_tex", DescriptorType::SAMPLER, samplerInfo}}
});
// Create a texture with 3 mip levels.
// Base level: 1024
// Level 1: 512 <-- upload data and sample from this level
// Level 2: 256
Handle<HwTexture> texture = api.createTexture(SamplerType::SAMPLER_2D, 3,
textureFormat, 1, 1024, 1024, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE);
Handle<HwTexture> texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 3,
textureFormat, 1, 1024, 1024, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE));
// Create image data.
PixelBufferDescriptor descriptor = checkerboardPixelBuffer(pixelFormat, pixelType, 512);
@@ -515,6 +467,7 @@ TEST_F(BackendTest, UpdateImageMipLevel) {
api.beginFrame(0, 0, 0);
// Update samplers.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
@@ -522,8 +475,8 @@ TEST_F(BackendTest, UpdateImageMipLevel) {
api.bindDescriptorSet(descriptorSet, 1, {});
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
defaultRenderTarget, swapChain, program);
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
defaultRenderTarget, swapChain, shader.getProgram());
static const uint32_t expectedHash = 3644679986;
readPixelsAndAssertHash("UpdateImageMipLevel", 512, 512, defaultRenderTarget, expectedHash);
@@ -532,21 +485,14 @@ TEST_F(BackendTest, UpdateImageMipLevel) {
api.commit(swapChain);
api.endFrame(0);
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyRenderTarget(defaultRenderTarget);
// This ensures all driver commands have finished before exiting the test.
api.finish();
api.stopCapture();
flushAndWait();
}
TEST_F(BackendTest, UpdateImage3D) {
TEST_F(LoadImageTest, UpdateImage3D) {
auto& api = getDriverApi();
Cleanup cleanup(api);
api.startCapture();
PixelDataFormat pixelFormat = PixelDataFormat::RGBA;
@@ -556,35 +502,24 @@ TEST_F(BackendTest, UpdateImage3D) {
TextureUsage usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
// Create a platform-specific SwapChain and make it current.
auto swapChain = createSwapChain();
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto defaultRenderTarget = api.createDefaultRenderTarget(0);
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D_ARRAY, getSamplerFormat(textureFormat), Precision::HIGH, false };
filamat::DescriptorSets descriptors;
descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 },
samplerInfo } };
std::string fragment = stringReplace("{samplerType}",
getSamplerTypeName(samplerType), fragmentUpdateImage3DTemplate);
ShaderGenerator shaderGen(
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
Program prog = shaderGen.getProgram(api);
prog.descriptorBindings(1, {{ "test_tex", DescriptorType::SAMPLER, 0 }});
ProgramHandle const program = api.createProgram(std::move(prog));
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
{{
DescriptorType::SAMPLER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}});
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
Shader shader(api, cleanup, ShaderConfig {
.vertexShader = mVertexShader,
.fragmentShader = fragment,
.uniforms = {{"test_tex", DescriptorType::SAMPLER, samplerInfo}}
});
// Create a texture.
Handle<HwTexture> texture = api.createTexture(samplerType, 1,
textureFormat, 1, 512, 512, 4, usage);
Handle<HwTexture> texture = cleanup.add(api.createTexture(samplerType, 1,
textureFormat, 1, 512, 512, 4, usage));
// Create image data for all 4 layers.
size_t components; int bpp;
@@ -606,6 +541,7 @@ TEST_F(BackendTest, UpdateImage3D) {
api.beginFrame(0, 0, 0);
// Update samplers.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
@@ -613,8 +549,8 @@ TEST_F(BackendTest, UpdateImage3D) {
api.bindDescriptorSet(descriptorSet, 1, {});
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
defaultRenderTarget, swapChain, program);
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
defaultRenderTarget, swapChain, shader.getProgram());
static const uint32_t expectedHash = 3644679986;
readPixelsAndAssertHash("UpdateImage3D", 512, 512, defaultRenderTarget, expectedHash);
@@ -623,17 +559,9 @@ TEST_F(BackendTest, UpdateImage3D) {
api.commit(swapChain);
api.endFrame(0);
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyRenderTarget(defaultRenderTarget);
// This ensures all driver commands have finished before exiting the test.
api.finish();
api.stopCapture();
flushAndWait();
}
} // namespace test
+9 -21
View File
@@ -17,7 +17,8 @@
#include "BackendTest.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "Shader.h"
#include "SharedShaders.h"
#include "TrianglePrimitive.h"
namespace {
@@ -26,21 +27,6 @@ namespace {
// Shaders
////////////////////////////////////////////////////////////////////////////////////////////////////
std::string vertex (R"(#version 450 core
layout(location = 0) in vec4 mesh_position;
layout(location = 0) out uvec4 indices;
void main() {
gl_Position = vec4(mesh_position.xy, 0.0, 1.0);
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
gl_Position.y = -gl_Position.y;
#endif
}
)");
std::string fragment (R"(#version 450 core
layout(location = 0) out vec4 fragColor;
@@ -71,10 +57,12 @@ TEST_F(BackendTest, MRT) {
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a program.
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(api);
auto program = cleanup.add(api.createProgram(std::move(p)));
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = SharedShaders::getVertexShaderText(VertexShaderType::Noop,
ShaderUniformType::None),
.fragmentShader = fragment,
.uniforms = {}
});
TrianglePrimitive triangle(api);
@@ -122,7 +110,7 @@ TEST_F(BackendTest, MRT) {
params.flags.discardEnd = TargetBufferFlags::NONE;
PipelineState state;
state.program = program;
state.program = shader.getProgram();
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
+35 -67
View File
@@ -19,7 +19,8 @@
#include "BackendTestUtils.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "Shader.h"
#include "SharedShaders.h"
#include "TrianglePrimitive.h"
#include <backend/DriverEnums.h>
@@ -34,18 +35,7 @@ namespace {
// Shaders
////////////////////////////////////////////////////////////////////////////////////////////////////
std::string vertex (R"(#version 450 core
layout(location = 0) in vec4 mesh_position;
layout(location = 0) out vec2 uv;
void main() {
gl_Position = vec4(mesh_position.xy, 0.0, 1.0);
uv = (mesh_position.xy * 0.5 + 0.5);
}
)");
std::string fragment (R"(#version 450 core
std::string fragmentTexturedLod (R"(#version 450 core
layout(location = 0) out vec4 fragColor;
layout(location = 0) in vec2 uv;
@@ -57,18 +47,6 @@ void main() {
}
)");
std::string whiteFragment (R"(#version 450 core
layout(location = 0) out vec4 fragColor;
layout(location = 0) in vec2 uv;
layout(location = 0, set = 1) uniform sampler2D backend_test_sib_tex;
void main() {
fragColor = vec4(1.0);
}
)");
}
namespace test {
@@ -88,39 +66,25 @@ TEST_F(BackendTest, TextureViewLod) {
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a program that draws only white.
Handle<HwProgram> whiteProgram;
{
ShaderGenerator shaderGen(vertex, whiteFragment, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(api);
p.descriptorBindings(0, {{"backend_test_sib_tex", DescriptorType::SAMPLER, 0}});
whiteProgram = cleanup.add(api.createProgram(std::move(p)));
}
Shader whiteShader = SharedShaders::makeShader(api, cleanup, ShaderRequest {
.mVertexType = VertexShaderType::Textured,
.mFragmentType = FragmentShaderType::White,
.mUniformType = ShaderUniformType::Sampler
});
// Create a program that samples a texture.
Handle<HwProgram> textureProgram;
{
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "backend_test", "sib_tex", 0,
SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH, false };
filamat::DescriptorSets descriptors;
descriptors[1] = { { "backend_test_sib_tex",
{ DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 }, samplerInfo } };
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
Program p = shaderGen.getProgram(api);
p.descriptorBindings(0, {{"backend_test_sib_tex", DescriptorType::SAMPLER, 0}});
textureProgram = cleanup.add(api.createProgram(std::move(p)));
}
DescriptorSetLayoutHandle descriptorSetLayout = cleanup.add(api.createDescriptorSetLayout({
{{
DescriptorType::SAMPLER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}}));
DescriptorSetHandle descriptorSet[2];
descriptorSet[0] = cleanup.add(api.createDescriptorSet(descriptorSetLayout));
descriptorSet[1] = cleanup.add(api.createDescriptorSet(descriptorSetLayout));
std::string vertexShader = SharedShaders::getVertexShaderText(
VertexShaderType::Textured, ShaderUniformType::Sampler);
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo {
"backend_test", "sib_tex", 0,
SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH, false };
Shader texturedShader(api, cleanup, ShaderConfig {
.vertexShader = vertexShader,
.fragmentShader = fragmentTexturedLod,
.uniforms = {{
"backend_test_sib_tex", DescriptorType::SAMPLER, samplerInfo
}}
});
// Create a texture that has 4 mip levels. Each level is a different color.
// Level 0: 128x128 (red)
@@ -129,9 +93,10 @@ TEST_F(BackendTest, TextureViewLod) {
// Level 3: 16x16 (yellow)
const size_t kTextureSize = 128;
const size_t kMipLevels = 4;
Handle<HwTexture> texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, kMipLevels,
TextureFormat::RGBA8, 1, kTextureSize, kTextureSize, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::UPLOADABLE));
Handle<HwTexture> texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kMipLevels, TextureFormat::RGBA8, 1, kTextureSize, kTextureSize, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT
| TextureUsage::UPLOADABLE));
// Create image data.
auto pixelFormat = PixelDataFormat::RGBA;
@@ -179,7 +144,7 @@ TEST_F(BackendTest, TextureViewLod) {
params.flags.discardStart = TargetBufferFlags::NONE;
params.flags.discardEnd = TargetBufferFlags::NONE;
PipelineState ps = {};
ps.program = whiteProgram;
ps.program = whiteShader.getProgram();
ps.rasterState.colorWrite = true;
ps.rasterState.depthWrite = false;
api.beginRenderPass(renderTarget, params);
@@ -187,7 +152,8 @@ TEST_F(BackendTest, TextureViewLod) {
api.endRenderPass();
}
backend::Handle<HwRenderTarget> defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
backend::Handle<HwRenderTarget> defaultRenderTarget =
cleanup.add(api.createDefaultRenderTarget(0));
RenderPassParams params = {};
fullViewport(params);
@@ -197,18 +163,19 @@ TEST_F(BackendTest, TextureViewLod) {
params.flags.discardEnd = TargetBufferFlags::NONE;
PipelineState state;
state.program = textureProgram;
state.pipelineLayout.setLayout = { descriptorSetLayout };
state.program = texturedShader.getProgram();
state.pipelineLayout.setLayout = { texturedShader.getDescriptorSetLayout() };
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = SamplerCompareFunc::A;
state.rasterState.culling = CullingMode::NONE;
api.updateDescriptorSetTexture(descriptorSet[0], 0, texture13, {
DescriptorSetHandle descriptorSet13 = texturedShader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet13, 0, texture13, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
api.bindDescriptorSet(descriptorSet[0], 0, {});
api.bindDescriptorSet(descriptorSet13, 0, {});
// Render a triangle to the screen, sampling from mip level 1.
// Because the min level is 1, the result color should be the white triangle drawn in the
@@ -221,11 +188,12 @@ TEST_F(BackendTest, TextureViewLod) {
// Adjust the base mip to 2.
auto texture22 = cleanup.add(api.createTextureView(texture, 2, 2));
api.updateDescriptorSetTexture(descriptorSet[1], 0, texture22, {
DescriptorSetHandle descriptorSet22 = texturedShader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet22, 0, texture22, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
api.bindDescriptorSet(descriptorSet[1], 0, {});
api.bindDescriptorSet(descriptorSet22, 0, {});
// Render a second, smaller, triangle, again sampling from mip level 1.
// This triangle should be yellow striped.
@@ -17,7 +17,8 @@
#include "BackendTest.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "Shader.h"
#include "SharedShaders.h"
#include "TrianglePrimitive.h"
namespace {
@@ -26,7 +27,7 @@ namespace {
// Shaders
////////////////////////////////////////////////////////////////////////////////////////////////////
std::string vertex (R"(#version 450 core
std::string vertex(R"(#version 450 core
layout(location = 0) in vec4 mesh_position;
@@ -46,16 +47,6 @@ void main() {
}
)");
std::string fragment (R"(#version 450 core
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(1.0);
}
)");
}
namespace test {
@@ -78,9 +69,11 @@ TEST_F(BackendTest, MissingRequiredAttributes) {
api.makeCurrent(swapChain, swapChain);
// Create a program.
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(api);
auto program = cleanup.add(api.createProgram(std::move(p)));
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = vertex,
.fragmentShader = SharedShaders::getFragmentShaderText(FragmentShaderType::White,
ShaderUniformType::None),
});
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
@@ -89,12 +82,12 @@ TEST_F(BackendTest, MissingRequiredAttributes) {
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = {0.f, 1.f, 0.f, 1.f};
params.clearColor = { 0.f, 1.f, 0.f, 1.f };
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
PipelineState state;
state.program = program;
state.program = shader.getProgram();
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
+53 -63
View File
@@ -18,7 +18,8 @@
#include "BackendTestUtils.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "Shader.h"
#include "SharedShaders.h"
#include "TrianglePrimitive.h"
#include <utils/Hash.h>
@@ -30,6 +31,7 @@ using namespace filament;
using namespace filament::backend;
#ifndef FILAMENT_IOS
#include <imageio/ImageEncoder.h>
#include <image/ColorTransform.h>
@@ -42,20 +44,7 @@ namespace {
// Shaders
////////////////////////////////////////////////////////////////////////////////////////////////////
std::string vertex (R"(#version 450 core
layout(location = 0) in vec4 mesh_position;
void main() {
gl_Position = vec4(mesh_position.xy, 0.0, 1.0);
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
gl_Position.y = -gl_Position.y;
#endif
}
)");
std::string fragmentFloat (R"(#version 450 core
std::string fragmentFloat(R"(#version 450 core
layout(location = 0) out vec4 fragColor;
@@ -65,7 +54,7 @@ void main() {
)");
std::string fragmentUint (R"(#version 450 core
std::string fragmentUint(R"(#version 450 core
layout(location = 0) out uvec4 fragColor;
@@ -108,7 +97,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
size_t samples = 1;
// The size of the actual render target, taking mip level into account;
size_t getRenderTargetSize () const {
size_t getRenderTargetSize() const {
return std::max(size_t(1), renderTargetBaseSize >> mipLevel);
}
@@ -138,11 +127,11 @@ TEST_F(ReadPixelsTest, ReadPixels) {
}
void exportScreenshot(void* pixelData) const {
#ifndef FILAMENT_IOS
#ifndef FILAMENT_IOS
const size_t width = readRect.width, height = readRect.height;
LinearImage image(width, height, 4);
if (format == PixelDataFormat::RGBA && type == PixelDataType::UBYTE) {
image = toLinearWithAlpha<uint8_t>(width, height, width * 4, (uint8_t*) pixelData);
image = toLinearWithAlpha<uint8_t>(width, height, width * 4, (uint8_t*)pixelData);
}
if (format == PixelDataFormat::RGBA && type == PixelDataType::FLOAT) {
memcpy(image.getPixelRef(), pixelData, width * height * sizeof(math::float4));
@@ -151,13 +140,13 @@ TEST_F(ReadPixelsTest, ReadPixels) {
std::ofstream outputStream(png.c_str(), std::ios::binary | std::ios::trunc);
ImageEncoder::encode(outputStream, ImageEncoder::Format::PNG, image, "",
png.c_str());
#endif
#endif
}
void exportRawBytes(void* pixelData) const {
std::string out = std::string(testName) + ".raw";
std::ofstream outputStream(out.c_str(), std::ios::binary | std::ios::trunc);
outputStream.write((char*) pixelData, getBufferSizeBytes());
outputStream.write((char*)pixelData, getBufferSizeBytes());
outputStream.close();
}
@@ -240,22 +229,20 @@ TEST_F(ReadPixelsTest, ReadPixels) {
DriverApi& api = getDriverApi();
Cleanup cleanup(api);
// Create programs.
Handle<HwProgram> programFloat, programUint;
{
ShaderGenerator shaderGen(vertex, fragmentFloat, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(api);
programFloat = cleanup.add(api.createProgram(std::move(p)));
}
{
ShaderGenerator shaderGen(vertex, fragmentUint, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(api);
programUint = cleanup.add(api.createProgram(std::move(p)));
}
std::string vertexShader = SharedShaders::getVertexShaderText(VertexShaderType::Noop,
ShaderUniformType::None);
Shader floatShader(api, cleanup, ShaderConfig{
.vertexShader = vertexShader,
.fragmentShader = fragmentFloat,
.uniforms = {}
});
Shader uintShader(api, cleanup, ShaderConfig{
.vertexShader = vertexShader,
.fragmentShader = fragmentUint,
.uniforms = {}
});
for (const auto& t : testCases)
{
for (const auto& t: testCases) {
// Create a platform-specific SwapChain and make it current.
Handle<HwSwapChain> swapChain;
if (t.useDefaultRT) {
@@ -292,7 +279,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = {0.f, 0.f, 1.f, 1.f};
params.clearColor = { 0.f, 0.f, 1.f, 1.f };
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
params.viewport.height = t.getRenderTargetSize();
@@ -305,9 +292,9 @@ TEST_F(ReadPixelsTest, ReadPixels) {
api.beginRenderPass(renderTarget, params);
PipelineState state;
state.program = programFloat;
state.program = floatShader.getProgram();
if (isUnsignedIntFormat(t.textureFormat)) {
state.program = programUint;
state.program = uintShader.getProgram();
}
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
@@ -325,7 +312,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
Handle<HwRenderTarget> mipLevelOneRT = localCleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR, renderTargetBaseSize, renderTargetBaseSize, 1, 0,
{{ texture }}, {}, {}));
p.clearColor = {1.f, 0.f, 0.f, 1.f};
p.clearColor = { 1.f, 0.f, 0.f, 1.f };
api.beginRenderPass(mipLevelOneRT, p);
api.endRenderPass();
}
@@ -335,28 +322,28 @@ TEST_F(ReadPixelsTest, ReadPixels) {
PixelBufferDescriptor descriptor(buffer, t.getBufferSizeBytes(), t.format, t.type,
t.alignment, t.left, t.top, t.getPixelBufferStride(), [](void* buffer, size_t size,
void* user) {
const auto* test = (const TestCase*) user;
void* user) {
const auto* test = (const TestCase*)user;
assert_invariant(test);
test->exportScreenshot(buffer);
//test->exportRawBytes(buffer);
// Hash the contents of the buffer and check that they match.
uint32_t hash = utils::hash::murmur3((const uint32_t*) buffer, size / 4, 0);
uint32_t hash = utils::hash::murmur3((const uint32_t*)buffer, size / 4, 0);
ASSERT_EQ(test->hash, hash) << test->testName <<
" failed: hashes do not match." << std::endl;
" failed: hashes do not match." << std::endl;
free(buffer);
}, (void*) &t);
}, (void*)&t);
api.readPixels(renderTarget, t.readRect.x, t.readRect.y, t.readRect.width,
t.readRect.height, std::move(descriptor));
// Now render red over what was just rendered. This ensures that readPixels captures the
// state of rendering between render passes.
params.clearColor = {1.f, 0.f, 0.f, 1.f};
params.clearColor = { 1.f, 0.f, 0.f, 1.f };
api.beginRenderPass(renderTarget, params);
api.endRenderPass();
@@ -376,25 +363,27 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
Cleanup cleanup(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = cleanup.add(api.createSwapChainHeadless(renderTargetSize, renderTargetSize, 0));
auto swapChain = cleanup.add(
api.createSwapChainHeadless(renderTargetSize, renderTargetSize, 0));
api.makeCurrent(swapChain, swapChain);
// Create a program.
ShaderGenerator shaderGen(vertex, fragmentFloat, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(api);
auto program = cleanup.add(api.createProgram(std::move(p)));
Shader shader = SharedShaders::makeShader(api, cleanup, ShaderRequest{
.mVertexType = VertexShaderType::Noop,
.mFragmentType = FragmentShaderType::White,
.mUniformType = ShaderUniformType::None
});
// Create a Texture and RenderTarget to render into.
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
Handle<HwTexture> texture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, // target
1, // levels
TextureFormat::RGBA8, // format
1, // samples
renderTargetSize, // width
renderTargetSize, // height
1, // depth
usage)); // usage
SamplerType::SAMPLER_2D, // target
1, // levels
TextureFormat::RGBA8, // format
1, // samples
renderTargetSize, // width
renderTargetSize, // height
1, // depth
usage)); // usage
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR,
@@ -411,7 +400,7 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = {0.f, 0.f, 1.f, 1.f};
params.clearColor = { 0.f, 0.f, 1.f, 1.f };
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
params.viewport.height = renderTargetSize;
@@ -420,7 +409,7 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
void* buffer = calloc(1, renderTargetSize * renderTargetSize * 4);
PipelineState state;
state.program = program;
state.program = shader.getProgram();
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
@@ -444,11 +433,12 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
PixelBufferDescriptor descriptor(buffer, renderTargetSize * renderTargetSize * 4,
PixelDataFormat::RGBA, PixelDataType::UBYTE, 1, 0, 0, renderTargetSize,
[](void* buffer, size_t size, void* user) {
ReadPixelsTest* test = (ReadPixelsTest*) user;
ReadPixelsTest* test = (ReadPixelsTest*)user;
test->readPixelsFinished = true;
}, this);
api.readPixels(renderTarget, 0, 0, renderTargetSize, renderTargetSize, std::move(descriptor));
api.readPixels(renderTarget, 0, 0, renderTargetSize, renderTargetSize,
std::move(descriptor));
api.commit(swapChain);
api.endFrame(0);
@@ -17,7 +17,8 @@
#include "BackendTest.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "Shader.h"
#include "SharedShaders.h"
#include "TrianglePrimitive.h"
#include <backend/DriverEnums.h>
@@ -28,42 +29,19 @@
#include <stddef.h>
#include <stdint.h>
namespace {
////////////////////////////////////////////////////////////////////////////////////////////////////
// Shaders
////////////////////////////////////////////////////////////////////////////////////////////////////
std::string vertex (R"(#version 450 core
layout(location = 0) in vec4 mesh_position;
layout(location = 0) out vec2 uv;
void main() {
gl_Position = vec4(mesh_position.xy, 0.0, 1.0);
uv = (mesh_position.xy * 0.5 + 0.5);
}
)");
std::string fragment (R"(#version 450 core
layout(location = 0) out vec4 fragColor;
layout(location = 0) in vec2 uv;
layout(location = 0, set = 1) uniform sampler2D test_tex;
void main() {
fragColor = texture(test_tex, uv);
}
)");
}
namespace test {
using namespace filament;
using namespace filament::backend;
Shader createShader(DriverApi& api, Cleanup& cleanup, Backend backend) {
return SharedShaders::makeShader(api, cleanup, ShaderRequest{
.mVertexType = VertexShaderType::Textured,
.mFragmentType = FragmentShaderType::Textured,
.mUniformType = ShaderUniformType::Sampler
});
}
// Rendering an external image without setting any data should not crash.
TEST_F(BackendTest, RenderExternalImageWithoutSet) {
auto& api = getDriverApi();
@@ -73,57 +51,41 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
auto swapChain = cleanup.add(createSwapChain());
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, Precision::HIGH, false };
filamat::DescriptorSets descriptors;
descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 },
samplerInfo } };
ShaderGenerator shaderGen(
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
Shader shader = createShader(api, cleanup, sBackend);
// Create a program that samples a texture.
Program p = shaderGen.getProgram(api);
p.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
backend::Handle<HwProgram> program = cleanup.add(api.createProgram(std::move(p)));
DescriptorSetLayoutHandle descriptorSetLayout = cleanup.add(api.createDescriptorSetLayout({
{{
DescriptorType::SAMPLER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}}));
DescriptorSetHandle descriptorSet = cleanup.add(api.createDescriptorSet(descriptorSetLayout));
backend::Handle<HwRenderTarget> defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
backend::Handle<HwRenderTarget> defaultRenderTarget = cleanup.add(
api.createDefaultRenderTarget(0));
// Create a texture that will be backed by an external image.
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
const NativeView& view = getNativeView();
backend::Handle<HwTexture> texture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_EXTERNAL, // target
1, // levels
TextureFormat::RGBA8, // format
1, // samples
view.width, // width
view.height, // height
1, // depth
usage)); // usage
SamplerType::SAMPLER_EXTERNAL, // target
1, // levels
TextureFormat::RGBA8, // format
1, // samples
view.width, // width
view.height, // height
1, // depth
usage)); // usage
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = {0.f, 1.f, 0.f, 1.f};
params.clearColor = { 0.f, 1.f, 0.f, 1.f };
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
PipelineState state;
state.program = program;
state.pipelineLayout.setLayout[1] = { descriptorSetLayout };
state.program = shader.getProgram();
state.pipelineLayout.setLayout[1] = { shader.getDescriptorSetLayout() };
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
state.rasterState.culling = CullingMode::NONE;
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.startCapture(0);
api.makeCurrent(swapChain, swapChain);
api.beginFrame(0, 0, 0);
@@ -155,29 +117,11 @@ TEST_F(BackendTest, RenderExternalImage) {
auto swapChain = cleanup.add(createSwapChain());
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, Precision::HIGH, false };
filamat::DescriptorSets descriptors;
descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 },
samplerInfo } };
ShaderGenerator shaderGen(
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
Shader shader = createShader(api, cleanup, sBackend);
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
// Create a program that samples a texture.
Program p = shaderGen.getProgram(api);
p.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
auto program = cleanup.add(api.createProgram(std::move(p)));
DescriptorSetLayoutHandle descriptorSetLayout = cleanup.add(api.createDescriptorSetLayout({
{{
DescriptorType::SAMPLER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}}));
DescriptorSetHandle descriptorSet = cleanup.add(api.createDescriptorSet(descriptorSetLayout));
backend::Handle<HwRenderTarget> defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
backend::Handle<HwRenderTarget> defaultRenderTarget = cleanup.add(
api.createDefaultRenderTarget(0));
// require users to create two Filament textures and have two material parameters
// add a "plane" parameter to setExternalImage
@@ -198,10 +142,12 @@ TEST_F(BackendTest, RenderExternalImage) {
values[1] = values[0];
values[2] = values[0];
values[3] = values[0];
CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, (const void**) keys, (const void**) values, 4, nullptr, nullptr);
CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys,
(const void**)values, 4, nullptr, nullptr);
CVPixelBufferRef pixBuffer = nullptr;
CVReturn status =
CVPixelBufferCreate(kCFAllocatorDefault, 1024, 1024, kCVPixelFormatType_32BGRA, options, &pixBuffer);
CVPixelBufferCreate(kCFAllocatorDefault, 1024, 1024, kCVPixelFormatType_32BGRA, options,
&pixBuffer);
assert(status == kCVReturnSuccess);
// Fill image with checker-pattern.
@@ -210,7 +156,7 @@ TEST_F(BackendTest, RenderExternalImage) {
const uint32_t black = 0xFF000000;
CVReturn lockStatus = CVPixelBufferLockBaseAddress(pixBuffer, 0);
assert(lockStatus == kCVReturnSuccess);
uint32_t* pix = (uint32_t*) CVPixelBufferGetBaseAddressOfPlane(pixBuffer, 0);
uint32_t* pix = (uint32_t*)CVPixelBufferGetBaseAddressOfPlane(pixBuffer, 0);
assert(pix);
for (size_t r = 0; r < 1024; r++) {
for (size_t c = 0; c < 1024; c++) {
@@ -230,13 +176,13 @@ TEST_F(BackendTest, RenderExternalImage) {
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = {0.f, 1.f, 0.f, 1.f};
params.clearColor = { 0.f, 1.f, 0.f, 1.f };
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
PipelineState state;
state.program = program;
state.pipelineLayout.setLayout[1] = { descriptorSetLayout };
state.program = shader.getProgram();
state.pipelineLayout.setLayout[1] = { shader.getDescriptorSetLayout() };
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
@@ -247,7 +193,6 @@ TEST_F(BackendTest, RenderExternalImage) {
api.beginFrame(0, 0, 0);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {});
api.bindDescriptorSet(descriptorSet, 1, {});
// Render a triangle.
+14 -28
View File
@@ -17,7 +17,8 @@
#include "BackendTest.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "Shader.h"
#include "SharedShaders.h"
#include "TrianglePrimitive.h"
#include <utils/Hash.h>
@@ -27,23 +28,6 @@ namespace test {
using namespace filament;
using namespace filament::backend;
static const char* const triangleVs = R"(#version 450 core
layout(location = 0) in vec4 mesh_position;
void main() {
gl_Position = vec4(mesh_position.xy, 0.0, 1.0);
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
gl_Position.y = -gl_Position.y;
#endif
})";
static const char* const triangleFs = R"(#version 450 core
precision mediump int; precision highp float;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(1.0f);
})";
TEST_F(BackendTest, ScissorViewportRegion) {
auto& api = getDriverApi();
@@ -85,10 +69,11 @@ TEST_F(BackendTest, ScissorViewportRegion) {
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
api.makeCurrent(swapChain, swapChain);
// Create a program.
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(api);
ProgramHandle program = cleanup.add(api.createProgram(std::move(p)));
Shader shader = SharedShaders::makeShader(api, cleanup, ShaderRequest{
.mVertexType = VertexShaderType::Noop,
.mFragmentType = FragmentShaderType::White,
.mUniformType = ShaderUniformType::None,
});
// Create source color and depth textures.
Handle<HwTexture> srcTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, kNumLevels,
@@ -133,7 +118,7 @@ TEST_F(BackendTest, ScissorViewportRegion) {
params.flags.discardEnd = TargetBufferFlags::NONE;
PipelineState ps = {};
ps.program = program;
ps.program = shader.getProgram();
ps.rasterState.colorWrite = true;
ps.rasterState.depthWrite = false;
@@ -175,10 +160,11 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
api.makeCurrent(swapChain, swapChain);
// Create a program.
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(api);
ProgramHandle program = cleanup.add(api.createProgram(std::move(p)));
Shader shader = SharedShaders::makeShader(api, cleanup, ShaderRequest{
.mVertexType = VertexShaderType::Noop,
.mFragmentType = FragmentShaderType::White,
.mUniformType = ShaderUniformType::None,
});
// Create a source color textures.
Handle<HwTexture> srcTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
@@ -220,7 +206,7 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
params.flags.discardEnd = TargetBufferFlags::NONE;
PipelineState ps = {};
ps.program = program;
ps.program = shader.getProgram();
ps.rasterState.colorWrite = true;
ps.rasterState.depthWrite = false;
+26 -51
View File
@@ -17,43 +17,13 @@
#include "BackendTest.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "Shader.h"
#include "SharedShaders.h"
#include "TrianglePrimitive.h"
using namespace filament;
using namespace filament::backend;
namespace {
////////////////////////////////////////////////////////////////////////////////////////////////////
// Shaders
////////////////////////////////////////////////////////////////////////////////////////////////////
std::string vertex (R"(#version 450 core
layout(location = 0) in vec4 mesh_position;
void main() {
gl_Position = vec4(mesh_position.xy, 0.0, 1.0);
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
gl_Position.y = -gl_Position.y;
#endif
}
)");
std::string fragment (R"(#version 450 core
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(1.0);
}
)");
}
namespace test {
// 1. Clear the stencil buffer to all zeroes.
@@ -64,7 +34,7 @@ namespace test {
class BasicStencilBufferTest : public BackendTest {
public:
Handle<HwSwapChain> mSwapChain;
Handle <HwSwapChain> mSwapChain;
ProgramHandle mProgram;
Cleanup mCleanup;
@@ -77,21 +47,23 @@ public:
mSwapChain = mCleanup.add(createSwapChain());
api.makeCurrent(mSwapChain, mSwapChain);
// Create a program.
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(api);
mProgram = mCleanup.add(api.createProgram(std::move(p)));
Shader shader = SharedShaders::makeShader(api, mCleanup, ShaderRequest{
.mVertexType = VertexShaderType::Noop,
.mFragmentType = FragmentShaderType::White,
.mUniformType = ShaderUniformType::None
});
mProgram = shader.getProgram();
}
void RunTest(Handle<HwRenderTarget> renderTarget) {
void RunTest(Handle <HwRenderTarget> renderTarget) {
auto& api = getDriverApi();
// We'll be using a triangle as geometry.
TrianglePrimitive smallTriangle(api);
static filament::math::float2 vertices[3] = {
{ -0.5, -0.5 },
{ 0.5, -0.5 },
{ -0.5, 0.5 }
{ 0.5, -0.5 },
{ -0.5, 0.5 }
};
smallTriangle.updateVertices(vertices);
TrianglePrimitive triangle(api);
@@ -100,7 +72,7 @@ public:
// Render a small triangle only to the stencil buffer, increasing the stencil buffer to 1.
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::COLOR0 | TargetBufferFlags::STENCIL;
params.viewport = {0, 0, 512, 512};
params.viewport = { 0, 0, 512, 512 };
params.clearColor = math::float4(0.0f, 0.0f, 1.0f, 1.0f);
params.clearStencil = 0u;
params.flags.discardStart = TargetBufferFlags::ALL;
@@ -153,7 +125,7 @@ TEST_F(BasicStencilBufferTest, StencilBuffer) {
TextureFormat::STENCIL8, 1, 512, 512, 1, TextureUsage::STENCIL_ATTACHMENT));
auto renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR0 | TargetBufferFlags::STENCIL, 512, 512, 1, 0,
{{colorTexture}}, {}, {{stencilTexture}}));
{{ colorTexture }}, {}, {{ stencilTexture }}));
RunTest(renderTarget);
@@ -171,10 +143,11 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
auto colorTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT));
auto depthStencilTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::DEPTH24_STENCIL8, 1, 512, 512, 1, TextureUsage::STENCIL_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT));
TextureFormat::DEPTH24_STENCIL8, 1, 512, 512, 1,
TextureUsage::STENCIL_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT));
auto renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR0 | TargetBufferFlags::STENCIL, 512, 512, 1, 0,
{{colorTexture}}, {depthStencilTexture}, {{depthStencilTexture}}));
{{ colorTexture }}, { depthStencilTexture }, {{ depthStencilTexture }}));
RunTest(renderTarget);
@@ -194,15 +167,17 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
// Pass 1: Render a triangle into (an auto-created) MSAA color buffer using the stencil test.
// Performs an auto-resolve on the color.
auto colorTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE));
TextureFormat::RGBA8, 1, 512, 512, 1,
TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE));
auto depthStencilTextureMSAA = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::DEPTH24_STENCIL8, 4, 512, 512, 1, TextureUsage::STENCIL_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT));
TextureFormat::DEPTH24_STENCIL8, 4, 512, 512, 1,
TextureUsage::STENCIL_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT));
auto renderTarget0 = cleanup.add(api.createRenderTarget(
TargetBufferFlags::DEPTH_AND_STENCIL, 512, 512, 4, 0,
{{}}, {depthStencilTextureMSAA}, {depthStencilTextureMSAA}));
{{}}, { depthStencilTextureMSAA }, { depthStencilTextureMSAA }));
auto renderTarget1 = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH_AND_STENCIL, 512, 512, 4, 0,
{{colorTexture}}, {depthStencilTextureMSAA}, {depthStencilTextureMSAA}));
{{ colorTexture }}, { depthStencilTextureMSAA }, { depthStencilTextureMSAA }));
api.startCapture(0);
@@ -210,8 +185,8 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
TrianglePrimitive smallTriangle(api);
static filament::math::float2 vertices[3] = {
{ -0.5, -0.5 },
{ 0.5, -0.5 },
{ -0.5, 0.5 }
{ 0.5, -0.5 },
{ -0.5, 0.5 }
};
smallTriangle.updateVertices(vertices);
TrianglePrimitive triangle(api);
@@ -220,7 +195,7 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
// Render a small triangle only to the stencil buffer, increasing the stencil buffer to 1.
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::STENCIL;
params.viewport = {0, 0, 512, 512};
params.viewport = { 0, 0, 512, 512 };
params.clearStencil = 0u;
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
+8
View File
@@ -75,6 +75,14 @@ public:
utils::CString const& getName() const noexcept { return mName; }
utils::CString const& getNameOrDefault() const noexcept {
if (const auto& name = getName(); !name.empty()) {
return name;
}
static const utils::CString sDefaultName = "(none)";
return sDefaultName;
}
private:
utils::CString mName;
};
+11
View File
@@ -791,6 +791,17 @@ int FEngine::loop() {
}
}
#if FILAMENT_ENABLE_MATDBG
if(debug.server) {
delete debug.server;
}
#endif
#if FILAMENT_ENABLE_FGVIEWER
if(debug.fgviewerServer) {
delete debug.fgviewerServer;
}
#endif
// terminate() is a synchronous API
getDriverApi().terminate();
return 0;
+4 -2
View File
@@ -269,12 +269,14 @@ void FMaterialInstance::setParameterImpl(std::string_view const name,
if (texture && texture->textureHandleCanMutate()) {
mTextureParameters[binding] = { texture, sampler.getSamplerParams() };
} else {
// Ensure to erase the binding from mTextureParameters since it will not
// be updated.
mTextureParameters.erase(binding);
Handle<HwTexture> handle{};
if (texture) {
handle = texture->getHwHandleForSampling();
assert_invariant(handle == texture->getHwHandle());
} else {
mTextureParameters.erase(binding);
}
mDescriptorSet.setSampler(binding, handle, sampler.getSamplerParams());
}
+46 -27
View File
@@ -1364,6 +1364,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
xvp.left = xvp.bottom = 0;
svp = xvp;
}
if (scaled) {
mightNeedFinalBlit = false;
auto viewport = DEBUG_DYNAMIC_SCALING ? xvp : vp;
@@ -1385,34 +1386,59 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
SamplerMagFilter::NEAREST, SamplerMinFilter::NEAREST);
}
// We need to do special processing when rendering directly into the swap-chain, that is when
// the viewRenderTarget is the default render target (mRenderTarget) and we're rendering into
// it.
// * This is because the default render target is not multi-sampled, so we need an
// intermediate buffer when MSAA is enabled.
// * We also need an extra buffer for blending the result to the framebuffer if the view
// is translucent AND we've not already done it as part of upscaling.
// * And we can't use the default rendertarget if MRT is required (e.g. with color grading
// as a subpass)
// * And we also can't use the default rendertarget if frame history is needed (e.g. with
// screen-space reflections)
// * And we also can't use the default rendertarget with refractions, which need to reuse
// the rendertarget due to how the clear flags work.
// * We also need an extra blit if we haven't yet handled "xvp"
// TODO: in that specific scenario it would be better to just not use xvp
// The intermediate buffer is accomplished with a "fake" opaqueBlit (i.e. blit) operation.
if (UTILS_UNLIKELY((input == postProcessInput && viewRenderTarget == mRenderTargetHandle) &&
view.isStencilBufferEnabled())) {
// FIXME: I think this check is incomplete, if we're rendering into a custom rendertarget
// we need to check that it (not the swapchain) has a stencil buffer.
assert_invariant(mSwapChain);
FILAMENT_CHECK_PRECONDITION(mSwapChain->hasStencilBuffer())
<< "View has stencil buffer enabled, but SwapChain does not have "
"SwapChain::CONFIG_HAS_STENCIL_BUFFER flag set.";
}
/*
* Here we're ready to present the output of the framegraph, which is held in `input`.
* The presentation itself happens by forwarding `input` to the render target, which can
* either be the SwapChain's or the View's custom render target. This is done below with
* `forwardResource()`.
*
* There are however a few situations where `input` cannot be forwarded in this manner, and
* an intermediate buffer is needed instead.
*
* 1. Blending is needed (blendModeTranslucent)
* 2. Dimensions don't match, e.g.: because we have guard bands (xvp != svp)
* 3. MRT is needed (colorGradingConfig.asSubpass)
* 4. Frame history is needed (hasScreenSpaceRefraction)
* 5. Refraction is used because how clear flags work (ssReflectionsOptions.enabled)
* 6. `input` and the render target are not compatible:
* - MSAA doesn't match. Note: auto-resolve could work if the SwapChain was configured
* this way.
*
* One complication is that post-processing passes handle some of these issues automatically:
* - each post-processing pass will create an intermediate buffer, which takes care of
* - MSAA (because of auto-resolve)
* - MRT is needed
* - Frame history is needed
* - Refraction
* - the upscaling pass takes care of all the above plus blending and dimensions mismatch.
*
* TODO: we could make this work with custom render targets because we can access the texture
* for those and it could behave just like a regular resource. This would lift
* some (but not all) of those limitations. For instance we could use MRTs.
*/
const bool outputIsSwapChain =
(input == postProcessInput) && (viewRenderTarget == mRenderTargetHandle);
if (mightNeedFinalBlit) {
assert_invariant(!scaled);
// Determine if our `input` is in fact the output of the color pass, in which case
// many of the caveat above apply.
bool const inputIsColorPass = (input == postProcessInput);
if (blendModeTranslucent ||
xvp != svp ||
(outputIsSwapChain &&
(inputIsColorPass &&
(msaaSampleCount > 1 ||
colorGradingConfig.asSubpass ||
hasScreenSpaceRefraction ||
ssReflectionsOptions.enabled))) {
assert_invariant(!scaled);
input = ppm.blit(fg, blendModeTranslucent, input, xvp, {
.width = vp.width, .height = vp.height,
.format = colorGradingConfig.ldrFormat },
@@ -1420,13 +1446,6 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
}
}
if (UTILS_UNLIKELY(outputIsSwapChain && view.isStencilBufferEnabled())) {
assert_invariant(mSwapChain);
FILAMENT_CHECK_PRECONDITION(mSwapChain->hasStencilBuffer())
<< "View has stencil buffer enabled, but SwapChain does not have "
"SwapChain::CONFIG_HAS_STENCIL_BUFFER flag set.";
}
if (UTILS_UNLIKELY(engine.debug.shadowmap.display_shadow_texture)) {
auto shadowmap = blackboard.get<FrameGraphTexture>("shadowmap");
input = ppm.debugDisplayShadowTexture(fg, input, shadowmap,
+7 -1
View File
@@ -161,7 +161,13 @@ Texture::Builder& Texture::Builder::name(StaticString const& name) noexcept {
Texture* Texture::Builder::build(Engine& engine) {
if (mImpl->mTarget != SamplerType::SAMPLER_EXTERNAL) {
FILAMENT_CHECK_PRECONDITION(Texture::isTextureFormatSupported(engine, mImpl->mFormat))
<< "Texture format " << uint16_t(mImpl->mFormat) << " not supported on this platform";
<< "Texture format " << uint16_t(mImpl->mFormat)
<< " not supported on this platform, texture name="
<< getNameOrDefault().c_str_safe();
FILAMENT_CHECK_PRECONDITION(mImpl->mWidth > 0 && mImpl->mHeight > 0)
<< "Texture has invalid dimensions: (" << mImpl->mWidth << ", " << mImpl->mHeight
<< "), texture name=" << getNameOrDefault().c_str_safe();
}
const bool isProtectedTexturesSupported =
downcast(engine).getDriverApi().isProtectedTexturesSupported();
+5
View File
@@ -88,7 +88,12 @@ void minLeftMaxRight(inout vec4 a, inout vec4 b, inout vec4 c, inout vec4 d, ino
// - we can't use a function because offset must be a constant expression
// - we can't use line continuation because that's not supported on desktop
#ifdef TARGET_WEBGPU_ENVIRONMENT
//WebGPU can not handle texelFetchOffset without an error about "ConstOffset is only permitted...".
#define makeSample(ij, o) vec4(texelFetch(materialParams_dof, ij+o, 0).rgb, texelFetch(materialParams_alpha, ij+o, 0).r)
#else
#define makeSample(ij, o) vec4(texelFetchOffset(materialParams_dof, ij, 0, o).rgb, texelFetchOffset(materialParams_alpha, ij, 0, o).r)
#endif
vec4 median(ivec2 ij) {
vec4 v[9];
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.59.0"
spec.version = "1.59.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.59.0/filament-v1.59.0-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.59.1/filament-v1.59.1-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
+5 -5
View File
@@ -33,11 +33,11 @@ namespace filament::descriptor_sets {
using namespace backend;
static DescriptorSetLayout const postProcessDescriptorSetLayout{{
static DescriptorSetLayout const postProcessDescriptorSetLayout{"postProcess", {
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS },
}};
static DescriptorSetLayout const depthVariantDescriptorSetLayout{{
static DescriptorSetLayout const depthVariantDescriptorSetLayout{"depthVariant",{
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS },
}};
@@ -46,14 +46,14 @@ static DescriptorSetLayout const depthVariantDescriptorSetLayout{{
// dedicated SSR vertex shader), which uses perViewDescriptorSetLayout.
// This means that PerViewBindingPoints::SHADOWS must be in the layout even though it's not used
// by the SSR variant.
static DescriptorSetLayout const ssrVariantDescriptorSetLayout{{
static DescriptorSetLayout const ssrVariantDescriptorSetLayout{"ssrVariant", {
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS },
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SHADOWS },
{ DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::STRUCTURE },
{ DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSR },
}};
static DescriptorSetLayout perViewDescriptorSetLayout = {{
static DescriptorSetLayout perViewDescriptorSetLayout = {"perView", {
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS },
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SHADOWS },
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::LIGHTS },
@@ -68,7 +68,7 @@ static DescriptorSetLayout perViewDescriptorSetLayout = {{
{ DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FOG },
}};
static DescriptorSetLayout perRenderableDescriptorSetLayout = {{
static DescriptorSetLayout perRenderableDescriptorSetLayout = {"preRenderable",{
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::OBJECT_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET },
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::BONES_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET },
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::MORPHING_UNIFORMS },
+95 -21
View File
@@ -518,28 +518,89 @@ void GLSLPostProcessor::spirvToMsl(const SpirvBlob* spirv, std::string* outMsl,
}
}
void GLSLPostProcessor::rebindImageSamplerForWGSL(std::vector<uint32_t> &spirv) {
constexpr size_t HEADER_SIZE = 5;
size_t const dataSize = spirv.size();
uint32_t *data = spirv.data();
std::set<uint32_t> samplerTargetIDs;
auto pass = [&](uint32_t targetOp, std::function<void(uint32_t)> f) {
for (uint32_t cursor = HEADER_SIZE, cursorEnd = dataSize; cursor < cursorEnd;) {
uint32_t const firstWord = data[cursor];
uint32_t const wordCount = firstWord >> 16;
uint32_t const op = firstWord & 0x0000FFFF;
if (targetOp == op) {
f(cursor + 1);
}
cursor += wordCount;
}
};
//Parse through debug name info to determine which bindings are samplers and which are not.
// This is possible because the sampler splitting pass outputs sampler and texture pairs of the form:
// `uniform sampler2D var_x` => `uniform sampler var_sampler` and `uniform texture2D var_texture`;
// TODO: This works, but may limit what optimizations can be done and has the potential to collide with user
// variable names. Ideally, trace usage to determine binding type.
pass(spv::Op::OpName, [&](uint32_t pos) {
auto target = data[pos];
char *name = (char *) &data[pos + 1];
std::string_view view(name);
if (view.find("_sampler") != std::string_view::npos) {
samplerTargetIDs.insert(target);
}
});
// Write out the offset bindings
pass(spv::Op::OpDecorate, [&](uint32_t pos) {
uint32_t const type = data[pos + 1];
if (type == spv::Decoration::DecorationBinding) {
uint32_t const targetVar = data[pos];
if (samplerTargetIDs.find(targetVar) != samplerTargetIDs.end()) {
data[pos + 2] = data[pos + 2] * 2 + 1;
} else {
data[pos + 2] = data[pos + 2] * 2;
}
}
});
}
bool GLSLPostProcessor::spirvToWgsl(SpirvBlob *spirv, std::string *outWsl) {
#if FILAMENT_SUPPORTS_WEBGPU
// We need to remove dead code for our variant-filter workaround for WebGPU to work
// This is especially relevant for removing the push constants that morphing uses when not disabled
spv::spirvbin_t remapper(0);
remapper.remap(*spirv, spv::spirvbin_base_t::DCE_ALL);
//We need to run some opt-passes at all times to transpile to WGSL
auto optimizer = createEmptyOptimizer();
optimizer->RegisterPass(CreateSplitCombinedImageSamplerPass());
optimizeSpirv(optimizer, *spirv);
//Currently no options we want to use
const tint::spirv::reader::Options readerOpts{};
//After splitting the image samplers, we need to remap the bindings to separate them.
rebindImageSamplerForWGSL(*spirv);
//Allow non-uniform derivitives due to our nested shaders. See https://github.com/gpuweb/gpuweb/issues/3479
const tint::spirv::reader::Options readerOpts{true};
tint::wgsl::writer::Options writerOpts{};
tint::Program tintRead = tint::spirv::reader::Read(*spirv, readerOpts);
if (tintRead.Diagnostics().ContainsErrors()) {
//TODO remove this if block once combined image sampler conversion works.
if (tintRead.Diagnostics().Str().rfind("error: WGSL does not support combined image-samplers") != std::string::npos) {
slog.w << "This tint reader error is currently ignored during WebGPU bringup: " << tintRead.Diagnostics().Str() << io::endl;
}
else {
slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl;
return false;
}
//We know errors can potentially crop up, and want the ability to ignore them if needed for sample bringup
#ifndef FILAMENT_WEBGPU_IGNORE_TNT_READ_ERRORS
slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl;
spv_context context = spvContextCreate(SPV_ENV_VULKAN_1_1_SPIRV_1_4);
spv_text text = nullptr;
spv_diagnostic diagnostic = nullptr;
spv_result_t result = spvBinaryToText(
context,
spirv->data(),
spirv->size(),
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | SPV_BINARY_TO_TEXT_OPTION_COLOR,
&text,
&diagnostic);
slog.e << "Beginning SpirV-output dump with ret " << result << "\n\n" << text->str << "\n\nEndSPIRV\n" <<
io::endl;
spvTextDestroy(text);
slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl;
return false;
#endif
}
tint::Result<tint::wgsl::writer::Output> wgslOut = tint::wgsl::writer::Generate(tintRead,writerOpts);
@@ -547,7 +608,7 @@ bool GLSLPostProcessor::spirvToWgsl(SpirvBlob *spirv, std::string *outWsl) {
tint::SuccessType tintSuccess;
if (wgslOut != tintSuccess) {
slog.e << "Tint writer error: " << wgslOut.Failure().reason.Str() << io::endl;
slog.e << "Tint writer error: " << wgslOut.Failure().reason << io::endl;
return false;
}
*outWsl = wgslOut->wgsl;
@@ -887,10 +948,8 @@ bool GLSLPostProcessor::fullOptimization(const TShader& tShader,
return true;
}
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
MaterialBuilder::Optimization optimization, Config const& config) {
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createEmptyOptimizer() {
auto optimizer = std::make_shared<spvtools::Optimizer>(SPV_ENV_UNIVERSAL_1_3);
optimizer->SetMessageConsumer([](spv_message_level_t level,
const char* source, const spv_position_t& position, const char* message) {
if (!filterSpvOptimizerMessage(level)) {
@@ -899,6 +958,12 @@ std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
slog.e << stringifySpvOptimizerMessage(level, source, position, message)
<< io::endl;
});
return optimizer;
}
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
MaterialBuilder::Optimization optimization, Config const& config) {
auto optimizer = createEmptyOptimizer();
if (optimization == MaterialBuilder::Optimization::SIZE) {
// When optimizing for size, we don't run the SPIR-V through any size optimization passes
@@ -923,7 +988,7 @@ std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
return optimizer;
}
void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) const {
void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) {
if (!optimizer->Run(spirv.data(), spirv.size(), &spirv)) {
slog.e << "SPIR-V optimizer pass failed" << io::endl;
return;
@@ -955,6 +1020,9 @@ void GLSLPostProcessor::fixupClipDistance(
// - triggers a segfault with AMD OpenGL drivers on macOS
// - triggers a crash on some Adreno drivers (b/291140208, b/289401984, b/289393290)
// However Metal requires this pass in order to correctly generate half-precision MSL
// CreateMergeReturnPass() also creates issues with Tint conversion related to the
// bitwise "<<" Operator used in shaders/src/surface_light_directional.fs against
// a signed integer.
//
// CreateSimplificationPass() creates a lot of problems:
// - Adreno GPU show artifacts after running simplification passes (Vulkan)
@@ -965,6 +1033,11 @@ void GLSLPostProcessor::fixupClipDistance(
// However, the simplification passes below are necessary when targeting Metal, otherwise the
// result is mismatched half / float assignments in MSL.
// CreateInlineExhaustivePass() expects CreateMergeReturnPass() to be run beforehand
// (Throwing many warnings if this is not the case), but we don't consistently do so for the above
// reasons. While running it alone may have some value, we will disable it for the new WebGPU backend
// while minimizing other changes.
void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config const& config) {
auto RegisterPass = [&](spvtools::Optimizer::PassToken&& pass,
@@ -978,7 +1051,7 @@ void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config c
RegisterPass(CreateWrapOpKillPass());
RegisterPass(CreateDeadBranchElimPass());
RegisterPass(CreateMergeReturnPass(), MaterialBuilder::TargetApi::METAL);
RegisterPass(CreateInlineExhaustivePass());
RegisterPass(CreateInlineExhaustivePass(), MaterialBuilder::TargetApi::ALL & ~MaterialBuilder::TargetApi::WEBGPU);
RegisterPass(CreateAggressiveDCEPass());
RegisterPass(CreatePrivateToLocalPass());
RegisterPass(CreateLocalSingleBlockLoadStoreElimPass());
@@ -1022,7 +1095,8 @@ void GLSLPostProcessor::registerSizePasses(Optimizer& optimizer, Config const& c
RegisterPass(CreateWrapOpKillPass());
RegisterPass(CreateDeadBranchElimPass());
RegisterPass(CreateInlineExhaustivePass());
// Disable for WebGPU, see comment above registerPerformancePasses()
RegisterPass(CreateInlineExhaustivePass(), MaterialBuilder::TargetApi::ALL & ~MaterialBuilder::TargetApi::WEBGPU);
RegisterPass(CreateEliminateDeadFunctionsPass());
RegisterPass(CreatePrivateToLocalPass());
RegisterPass(CreateScalarReplacementPass(0));
+7 -1
View File
@@ -114,14 +114,20 @@ private:
* Retrieve an optimizer instance tuned for the given optimization level and shader configuration.
*/
using OptimizerPtr = std::shared_ptr<spvtools::Optimizer>;
static OptimizerPtr createOptimizer(
MaterialBuilder::Optimization optimization,
Config const& config);
static OptimizerPtr createEmptyOptimizer();
static void registerSizePasses(spvtools::Optimizer& optimizer, Config const& config);
static void registerPerformancePasses(spvtools::Optimizer& optimizer, Config const& config);
void optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) const;
static void optimizeSpirv(OptimizerPtr optimizer, SpirvBlob &spirv);
static void rebindImageSamplerForWGSL(std::vector<uint32_t>& spirv);
void fixupClipDistance(SpirvBlob& spirv, GLSLPostProcessor::Config const& config) const;
@@ -937,6 +937,15 @@ utils::io::sstream& CodeGenerator::generateSpecializationConstant(utils::io::sst
std::string const constantString = std::visit(SpecializationConstantFormatter(), value);
static const char* types[] = { "int", "float", "bool" };
// Spec constants aren't fully supported in Tint,
// workaround until https://issues.chromium.org/issues/42250586 is resolved
if (mTargetApi == TargetApi::WEBGPU) {
out << "layout (constant_id = " << id << ") const "
<< types[value.index()] << " " << name << "_hack = " << constantString << ";\n"
<< types[value.index()] << " " << name << " = " << name << "_hack;\n";
return out;
}
if (mTargetLanguage == MaterialBuilderBase::TargetLanguage::SPIRV) {
out << "layout (constant_id = " << id << ") const "
<< types[value.index()] << " " << name << " = " << constantString << ";\n";
+3 -1
View File
@@ -704,7 +704,9 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
::prepareNativeWindow(mWindow);
void* metalLayer = nullptr;
if (config.backend == filament::Engine::Backend::METAL || config.backend == filament::Engine::Backend::VULKAN) {
if (config.backend == filament::Engine::Backend::METAL || config.backend == filament::Engine::Backend::VULKAN
|| config.backend == filament::Engine::Backend::WEBGPU) {
metalLayer = setUpMetalLayer(nativeWindow);
// The swap chain on both native Metal and MoltenVK is a CAMetalLayer.
nativeSwapChain = metalLayer;
+2
View File
@@ -17,6 +17,8 @@
#ifndef TNT_UTILS_STATICSTRING_H
#define TNT_UTILS_STATICSTRING_H
#include <utils/compiler.h>
#include <type_traits>
#include <string_view>
+6
View File
@@ -205,7 +205,13 @@ void setup_window(Window& w, Engine* engine) {
void* nativeSwapChain = nativeWindow;
#if defined(__APPLE__)
void* metalLayer = nullptr;
#if defined(FILAMENT_SUPPORTS_WEBGPU)
if (kBackend == filament::Engine::Backend::METAL || kBackend == filament::Engine::Backend::VULKAN
|| kBackend == filament::Engine::Backend::WEBGPU) {
#else
if (kBackend == filament::Engine::Backend::METAL || kBackend == filament::Engine::Backend::VULKAN) {
#endif
metalLayer = setUpMetalLayer(nativeWindow);
// The swap chain on both native Metal and MoltenVK is a CAMetalLayer.
nativeSwapChain = metalLayer;
+109
View File
@@ -0,0 +1,109 @@
# Copyright (C) 2025 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import glob
import yaml
import hashlib
import concurrent.futures
import re
from utils import execute, ArgParseImpl
def get_line(file_name, offset):
with open(f'{file_name}', 'rb') as file:
bytes = file.read()[0:offset]
f_str = bytes.decode('utf-8')
return len(f_str.split('\n'))
return -1
def get_func_name(msg):
pattern = r"\'(.+)\'"
res = re.findall(pattern, msg)
if len(res) > 0:
return res[0].replace("'", '')
return msg
def run_tidy(files):
files_str = ' '.join(files)
hid = hashlib.md5(files_str.encode('utf-8')).hexdigest()
_, _ = execute(f'clang-tidy --export-fixes=/tmp/{hid}.yaml --quiet --checks=-*,bugprone-exception-escape {files_str}')
results = []
with open(f'/tmp/{hid}.yaml', 'r') as file:
data = yaml.safe_load(file)
for d in data['Diagnostics']:
if d['DiagnosticName'] != 'bugprone-exception-escape':
continue
msg = d['DiagnosticMessage']
fpath = msg['FilePath']
offset = msg['FileOffset']
line_num = get_line(fpath, offset)
results.append((msg['FilePath'].replace(f'{os.getcwd()}/', ''), line_num, get_func_name(msg['Message'])))
return results
def exception_escape_test():
files = glob.glob('filament/**/*.mm', recursive=True) + \
glob.glob('filament/**/*.cpp', recursive=True) + \
glob.glob('filament/**/*.h', recursive=True)
num_workers = 5 # Number of threads to spawn
part_len = len(files) // num_workers
workloads = []
for i in range(num_workers):
next = min(len(files), part_len)
workloads.append(files[0:next])
files = files[next:]
all_results = []
with concurrent.futures.ThreadPoolExecutor(max_workers=num_workers) as executor:
future_to_worker_id = {executor.submit(run_tidy, workloads[i]): i for i in range(num_workers)}
for future in concurrent.futures.as_completed(future_to_worker_id):
worker_id = future_to_worker_id[future]
try:
all_results.extend(future.result())
except Exception as exc:
print(f"Main: Worker {worker_id} generated an exception: {exc}")
test_name = 'code-correctness::exception-escape'
failure_str_lines = []
if len(all_results) > 0:
failure_str_lines.append(f'Number of failures: {len(all_results)}')
for fname, line_num, msg in all_results:
failure_str_lines.append(f'{fname}({line_num}): {msg}()')
return (len(all_results) == 0, failure_str_lines)
TESTS = [
(exception_escape_test,
'exception-escape',
'\'an exception may be thrown in a function which should not throw exceptions\'')
]
if __name__ == "__main__":
has_failures = False
for test_func, test_name, test_desc in TESTS:
result, res_strs = test_func()
ss = ' ' * 4
if result:
print(f'[{test_name}] PASSED')
else:
has_failures = True
print(f'[{test_name}] FAILED')
print(f'{ss}Description: \'{test_desc}\'')
for s in res_strs:
print(f'{ss}{s}')
if has_failures:
# TODO: Enable this when we've fixed all the exception-escape errors
#exit(1)
pass
+68
View File
@@ -0,0 +1,68 @@
# Copyright (C) 2024 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import subprocess
import os
import argparse
import sys
def execute(cmd,
cwd=None,
capture_output=True,
stdin=None,
env=None,
raise_errors=False):
in_env = os.environ
in_env.update(env if env else {})
home = os.environ['HOME']
if f'{home}/bin' not in in_env['PATH']:
in_env['PATH'] = in_env['PATH'] + f':{home}/bin'
stdout = subprocess.PIPE if capture_output else sys.stdout
stderr = subprocess.PIPE if capture_output else sys.stdout
output = ''
err_output = ''
return_code = -1
kwargs = {
'cwd': cwd,
'env': in_env,
'stdout': stdout,
'stderr': stderr,
'stdin': stdin,
'universal_newlines': True
}
if capture_output:
process = subprocess.Popen(cmd.split(' '), **kwargs)
output, err_output = process.communicate()
return_code = process.returncode
else:
return_code = subprocess.call(cmd.split(' '), **kwargs)
if return_code:
# Error
if raise_errors:
raise subprocess.CalledProcessError(return_code, cmd)
if output:
if type(output) != str:
try:
output = output.decode('utf-8').strip()
except UnicodeDecodeError as e:
print('cannot decode ', output, file=sys.stderr)
return return_code, (output if return_code == 0 else err_output)
class ArgParseImpl(argparse.ArgumentParser):
def error(self, message):
sys.stderr.write('error: %s\n' % message)
self.print_help()
sys.exit(1)
+28
View File
@@ -0,0 +1,28 @@
# Copyright (C) 2025 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/bash
CODE_CORRECTNESS_TEST_DIR="$(pwd)/test/code-correctness"
# Check if the clang-tidy command exists and is executable
if ! command -v clang-tidy > /dev/null 2>&1; then
# If command -v fails (returns a non-zero exit status), clang-tidy is not found
echo "Error: clang-tidy command not found." >&2
echo "Please install clang-tidy or ensure it is in your system's PATH." >&2
exit 1 # Exit the script with an error code (conventionally non-zero for failure)
fi
set -e && set -x && \
python3 ${CODE_CORRECTNESS_TEST_DIR}/src/run.py
+17
View File
@@ -0,0 +1,17 @@
# Bzlmod lockfile
MODULE.bazel.lock
# Ignore all bazel-* symlinks.
/bazel-*
# Ignore Bazel verbose explanations
--verbose_explanations
# Ignore CMake usual build directory
build
# Ignore Vim files
*.swp
# Ignore QtCreator Project file
CMakeLists.txt.user
# Ignore VS Code files
.vscode/*
# Ignore generated python artifacts
*.pyc
copts/__pycache__/
-374
View File
@@ -1,374 +0,0 @@
# Copyright 2018 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Flags specified here must not impact ABI. Code compiled with and without these
# opts will be linked together, and in some cases headers compiled with and
# without these options will be part of the same program.
import("//build/config/c++/c++.gni")
import("//build/config/nacl/config.gni")
import("//build/config/sanitizers/sanitizers.gni")
import("//build/toolchain/toolchain.gni")
import("//build_overrides/build.gni")
import("//third_party/abseil-cpp/absl.gni")
config("absl_component_build") {
defines = [ "ABSL_CONSUME_DLL" ]
}
assert(!is_nacl || is_nacl_saigo,
"base must not be built in most nacl toolchains")
component("absl") {
public_deps = [ ":absl_component_deps" ]
if (is_component_build) {
public_configs = [ ":absl_component_build" ]
if (is_win && is_clang) {
if (current_cpu == "x64") {
if (is_debug) {
sources = [ "symbols_x64_dbg.def" ]
} else {
if (is_asan) {
sources = [ "symbols_x64_rel_asan.def" ]
} else {
sources = [ "symbols_x64_rel.def" ]
}
}
}
if (current_cpu == "x86") {
if (is_debug) {
sources = [ "symbols_x86_dbg.def" ]
} else {
sources = [ "symbols_x86_rel.def" ]
}
}
if (current_cpu == "arm64") {
if (is_debug) {
sources = [ "symbols_arm64_dbg.def" ]
} else {
sources = [ "symbols_arm64_rel.def" ]
}
}
}
}
}
group("absl_component_deps") {
public_deps = [
"//third_party/abseil-cpp/absl/algorithm:container",
"//third_party/abseil-cpp/absl/base",
"//third_party/abseil-cpp/absl/base:config",
"//third_party/abseil-cpp/absl/base:core_headers",
"//third_party/abseil-cpp/absl/base:no_destructor",
"//third_party/abseil-cpp/absl/base:nullability",
"//third_party/abseil-cpp/absl/base:prefetch",
"//third_party/abseil-cpp/absl/cleanup",
"//third_party/abseil-cpp/absl/container:btree",
"//third_party/abseil-cpp/absl/container:fixed_array",
"//third_party/abseil-cpp/absl/container:flat_hash_map",
"//third_party/abseil-cpp/absl/container:flat_hash_set",
"//third_party/abseil-cpp/absl/container:inlined_vector",
"//third_party/abseil-cpp/absl/container:node_hash_map",
"//third_party/abseil-cpp/absl/container:node_hash_set",
"//third_party/abseil-cpp/absl/debugging:failure_signal_handler",
"//third_party/abseil-cpp/absl/debugging:stacktrace",
"//third_party/abseil-cpp/absl/debugging:symbolize",
"//third_party/abseil-cpp/absl/functional:any_invocable",
"//third_party/abseil-cpp/absl/functional:bind_front",
"//third_party/abseil-cpp/absl/functional:function_ref",
"//third_party/abseil-cpp/absl/functional:overload",
"//third_party/abseil-cpp/absl/hash",
"//third_party/abseil-cpp/absl/log:absl_check",
"//third_party/abseil-cpp/absl/log:absl_log",
"//third_party/abseil-cpp/absl/log:die_if_null",
"//third_party/abseil-cpp/absl/memory",
"//third_party/abseil-cpp/absl/meta:type_traits",
"//third_party/abseil-cpp/absl/numeric:bits",
"//third_party/abseil-cpp/absl/numeric:int128",
"//third_party/abseil-cpp/absl/random",
"//third_party/abseil-cpp/absl/random:distributions",
"//third_party/abseil-cpp/absl/status",
"//third_party/abseil-cpp/absl/status:statusor",
"//third_party/abseil-cpp/absl/strings",
"//third_party/abseil-cpp/absl/strings:cord",
"//third_party/abseil-cpp/absl/strings:has_ostream_operator",
"//third_party/abseil-cpp/absl/strings:str_format",
"//third_party/abseil-cpp/absl/strings:string_view",
"//third_party/abseil-cpp/absl/synchronization",
"//third_party/abseil-cpp/absl/time",
"//third_party/abseil-cpp/absl/types:optional",
"//third_party/abseil-cpp/absl/types:span",
"//third_party/abseil-cpp/absl/types:variant",
"//third_party/abseil-cpp/absl/utility",
]
if (is_component_build) {
public_deps += [ ":absl_full_deps" ]
}
# The following dependencies currently don't build with NaCl.
# TODO(https://crbug.com/1114625): Fix build errors and remove this section.
if (is_nacl) {
public_deps -=
[ "//third_party/abseil-cpp/absl/debugging:failure_signal_handler" ]
}
visibility = [ ":absl" ]
}
# Some third-party libraries use additional parts of absl that are banned in
# Chrome (e.g. flags generates static initializers that are undesirable in
# non-test binaries). These third-party libraries can depend on `absl_full`
# instead of `absl` to use those banned subsets of Abseil as well.
source_set("absl_full") {
public_deps = [ ":absl" ]
# In component builds, `absl` itself depends on `absl_full_deps` directly so
# that all of absl is built as a single shared library. This is to prevent
# globals from being duplicated between both `absl` and `absl_full` (e.g.
# inline variables), and the subtle breakage that could cause.
#
# Deps violations will still be enforced by the non-component build.
if (!is_component_build) {
public_deps += [ ":absl_full_deps" ]
}
if (build_with_chromium) {
visibility = [
"//third_party/fuzztest:*",
# GoogleTest doesn't actually need absl_full, but this makes gn check
# happier.
"//third_party/googletest:*",
]
}
}
group("absl_full_deps") {
public_deps = [
# Banned because each flag registration generates a static initializer, and
# the flag registration mechanism is problematic in component builds.
"//third_party/abseil-cpp/absl/flags:config",
"//third_party/abseil-cpp/absl/flags:flag",
"//third_party/abseil-cpp/absl/flags:parse",
"//third_party/abseil-cpp/absl/flags:reflection",
"//third_party/abseil-cpp/absl/flags:usage",
# Banned because the LOG and CHECK macros conflict with Chrome's version of
# those macros.
"//third_party/abseil-cpp/absl/log",
"//third_party/abseil-cpp/absl/log:check",
# TODO(danilchap): Investigate if these targets need to be banned in 3rd party library code.
"//third_party/abseil-cpp/absl/log:globals",
"//third_party/abseil-cpp/absl/log:initialize",
"//third_party/abseil-cpp/absl/random:bit_gen_ref",
]
visibility = [
":absl_component_deps",
":absl_full",
]
public_configs = [ "//third_party/abseil-cpp/absl/flags:absl_flags_config" ]
}
config("absl_include_config") {
include_dirs = [ "." ]
}
config("absl_define_config") {
defines = [ "ABSL_ALLOCATOR_NOTHROW=1" ]
if (is_win && !use_custom_libcxx) {
defines += [
# See crbug.com/1101367: Acknowledge extended alignment when using
# MSVC's standard library.
"_ENABLE_EXTENDED_ALIGNED_STORAGE",
]
}
}
config("absl_default_cflags_cc") {
cflags_cc = []
if (is_clang) {
cflags_cc += [
# TODO(crbug.com/588506): Explicitly enable conversion warnings.
"-Wbool-conversion",
"-Wconstant-conversion",
"-Wenum-conversion",
"-Wint-conversion",
"-Wliteral-conversion",
"-Wnon-literal-null-conversion",
"-Wnull-conversion",
"-Wobjc-literal-conversion",
"-Wstring-conversion",
]
if (!is_nacl) {
cflags_cc += [ "-Wbitfield-enum-conversion" ]
}
}
}
config("absl_public_cflags_cc") {
# Non-chromium users of abseil have more lax style guides and use additional features in
# abseil, Remove a few extra warnings for that use case.
if (!build_with_chromium && is_clang) {
cflags = [
# Allow the use of enable_if()
"-Wno-gcc-compat",
"-Wno-unreachable-code-break",
]
}
}
config("absl_test_cflags_cc") {
cflags_cc = []
if (is_clang || !is_win) {
cflags_cc += [
"-Wno-conversion-null",
"-Wno-missing-declarations",
"-Wno-unused-function",
"-Wno-unused-parameter",
"-Wno-unused-private-field",
]
}
if (is_win) {
cflags_cc += [
"/wd4101", # unreferenced local variable
]
}
}
config("absl_test_config") {
cflags_cc = []
if (is_win && !is_clang) {
cflags_cc += [ "/wd4996" ]
}
if (is_clang) {
cflags_cc += [
"-Wno-deprecated-declarations",
"-Wno-implicit-const-int-float-conversion",
"-Wno-unused-private-field",
"-Wno-unused-function",
]
}
}
if (absl_build_tests) {
import("//testing/test.gni")
if (!is_component_build) {
test("absl_tests") {
testonly = true
deps = [
"absl/algorithm:algorithm_test",
"absl/algorithm:container_test",
"absl/base:config_test",
"absl/base:no_destructor_test",
"absl/base:nullability_test",
"absl/base:prefetch_test",
"absl/cleanup:cleanup_test",
# TODO(mbonadei): Fix issue with EXPECT_DEATH and uncomment.
# "absl/container:btree_test",
"absl/container:common_policy_traits_test",
"absl/container:fixed_array_test",
"absl/container:flat_hash_map_test",
"absl/container:flat_hash_set_test",
"absl/container:hash_function_defaults_test",
"absl/container:inlined_vector_test",
"absl/container:node_slot_policy_test",
"absl/container:raw_hash_set_allocator_test",
"absl/container:raw_hash_set_test",
"absl/container:sample_element_size_test",
"absl/crc:crc32c_test",
"absl/crc:crc_cord_state_test",
"absl/crc:crc_memcpy_test",
"absl/crc:non_temporal_memcpy_test",
"absl/debugging:demangle_rust_test",
"absl/debugging:stacktrace_test",
"absl/flags:flag_test",
"absl/functional:any_invocable_test",
"absl/functional:function_ref_test",
"absl/functional:overload_test",
"absl/hash:hash_instantiated_test",
"absl/hash:hash_test",
"absl/hash:low_level_hash_test",
"absl/log:absl_check_test",
"absl/log:absl_log_basic_test",
"absl/log:die_if_null_test",
"absl/log:flags_test",
"absl/log:globals_test",
"absl/log:log_entry_test",
"absl/log:log_format_test",
"absl/log:log_macro_hygiene_test",
"absl/log:log_modifier_methods_test",
"absl/log:log_sink_test",
"absl/log:log_streamer_test",
"absl/log:scoped_mock_log_test",
"absl/log:stripping_test",
"absl/log:vlog_is_on_test",
"absl/log/internal:fnmatch_test",
"absl/log/internal:stderr_log_sink_test",
"absl/memory:memory_test",
"absl/meta:type_traits_test",
"absl/numeric:int128_test",
"absl/profiling:exponential_biased_test",
"absl/profiling:periodic_sampler_test",
"absl/status:status_matchers_test",
"absl/status:status_test",
"absl/status:statusor_test",
"absl/strings:ascii_test",
"absl/strings:char_formatting_test",
"absl/strings:charset_test",
"absl/strings:cord_buffer_test",
"absl/strings:cord_data_edge_test",
"absl/strings:cord_rep_btree_navigator_test",
"absl/strings:cord_rep_btree_reader_test",
"absl/strings:cord_rep_btree_test",
"absl/strings:cord_rep_crc_test",
"absl/strings:cordz_functions_test",
"absl/strings:cordz_info_statistics_test",
"absl/strings:cordz_info_test",
"absl/strings:cordz_test",
"absl/strings:cordz_update_scope_test",
"absl/strings:cordz_update_tracker_test",
"absl/strings:damerau_levenshtein_distance_test",
"absl/strings:has_absl_stringify_test",
"absl/strings:has_ostream_operator_test",
"absl/strings:match_test",
"absl/strings:str_format_arg_test",
"absl/strings:str_format_bind_test",
"absl/strings:str_format_checker_test",
"absl/strings:str_format_convert_test",
"absl/strings:str_format_extension_test",
"absl/strings:str_format_output_test",
"absl/strings:str_format_parser_test",
"absl/strings:str_format_test",
"absl/strings:str_replace_test",
"absl/strings:string_view_test",
"absl/synchronization:kernel_timeout_internal_test",
"absl/synchronization:waiter_test",
"absl/time:time_test",
"absl/types:optional_test",
"absl/types:span_test",
"absl/types:variant_test",
"absl/utility:if_constexpr_test",
"//third_party/googletest:gtest_main",
]
}
}
test("absl_hardening_tests") {
testonly = true
sources = [ "absl_hardening_test.cc" ]
deps = [
":absl",
"//base/test:run_all_unittests",
"//third_party/googletest:gtest",
]
}
}
+87 -15
View File
@@ -15,6 +15,7 @@ set(ABSL_INTERNAL_DLL_FILES
"base/internal/cycleclock.h"
"base/internal/cycleclock_config.h"
"base/internal/direct_mmap.h"
"base/internal/dynamic_annotations.h"
"base/internal/endian.h"
"base/internal/errno_saver.h"
"base/internal/fast_type_id.h"
@@ -27,6 +28,8 @@ set(ABSL_INTERNAL_DLL_FILES
"base/internal/low_level_scheduling.h"
"base/internal/nullability_impl.h"
"base/internal/per_thread_tls.h"
"base/internal/poison.cc"
"base/internal/poison.h"
"base/prefetch.h"
"base/internal/pretty_function.h"
"base/internal/raw_logging.cc"
@@ -46,6 +49,8 @@ set(ABSL_INTERNAL_DLL_FILES
"base/internal/thread_identity.h"
"base/internal/throw_delegate.cc"
"base/internal/throw_delegate.h"
"base/internal/tracing.cc"
"base/internal/tracing.h"
"base/internal/tsan_mutex_interface.h"
"base/internal/unaligned_access.h"
"base/internal/unscaledcycleclock.cc"
@@ -121,6 +126,9 @@ set(ABSL_INTERNAL_DLL_FILES
"debugging/symbolize.h"
"debugging/internal/address_is_readable.cc"
"debugging/internal/address_is_readable.h"
"debugging/internal/bounded_utf8_length_sequence.h"
"debugging/internal/decode_rust_punycode.cc"
"debugging/internal/decode_rust_punycode.h"
"debugging/internal/demangle.cc"
"debugging/internal/demangle.h"
"debugging/internal/demangle_rust.cc"
@@ -133,6 +141,8 @@ set(ABSL_INTERNAL_DLL_FILES
"debugging/internal/stack_consumption.h"
"debugging/internal/stacktrace_config.h"
"debugging/internal/symbolize.h"
"debugging/internal/utf8_for_code_point.cc"
"debugging/internal/utf8_for_code_point.h"
"debugging/internal/vdso_support.cc"
"debugging/internal/vdso_support.h"
"functional/any_invocable.h"
@@ -183,6 +193,8 @@ set(ABSL_INTERNAL_DLL_FILES
"log/internal/proto.cc"
"log/internal/strip.h"
"log/internal/structured.h"
"log/internal/structured_proto.cc"
"log/internal/structured_proto.h"
"log/internal/vlog_config.cc"
"log/internal/vlog_config.h"
"log/internal/voidify.h"
@@ -313,7 +325,6 @@ set(ABSL_INTERNAL_DLL_FILES
"strings/internal/string_constant.h"
"strings/internal/stringify_sink.h"
"strings/internal/stringify_sink.cc"
"strings/internal/has_absl_stringify.h"
"strings/has_absl_stringify.h"
"strings/has_ostream_operator.h"
"strings/match.cc"
@@ -439,9 +450,47 @@ set(ABSL_INTERNAL_DLL_FILES
"debugging/leak_check.cc"
)
if(NOT MSVC)
list(APPEND ABSL_INTERNAL_DLL_FILES
"flags/commandlineflag.cc"
"flags/commandlineflag.h"
"flags/config.h"
"flags/declare.h"
"flags/flag.h"
"flags/internal/commandlineflag.cc"
"flags/internal/commandlineflag.h"
"flags/internal/flag.cc"
"flags/internal/flag.h"
"flags/internal/parse.h"
"flags/internal/path_util.h"
"flags/internal/private_handle_accessor.cc"
"flags/internal/private_handle_accessor.h"
"flags/internal/program_name.cc"
"flags/internal/program_name.h"
"flags/internal/registry.h"
"flags/internal/sequence_lock.h"
"flags/internal/usage.cc"
"flags/internal/usage.h"
"flags/marshalling.cc"
"flags/marshalling.h"
"flags/parse.cc"
"flags/parse.h"
"flags/reflection.cc"
"flags/reflection.h"
"flags/usage.cc"
"flags/usage.h"
"flags/usage_config.cc"
"flags/usage_config.h"
"log/flags.cc"
"log/flags.h"
"log/internal/flags.h"
)
endif()
set(ABSL_INTERNAL_DLL_TARGETS
"absl_check"
"absl_log"
"absl_vlog_is_on"
"algorithm"
"algorithm_container"
"any"
@@ -507,6 +556,7 @@ set(ABSL_INTERNAL_DLL_TARGETS
"log_internal_check_op"
"log_internal_conditions"
"log_internal_config"
"log_internal_fnmatch"
"log_internal_format"
"log_internal_globals"
"log_internal_log_impl"
@@ -586,6 +636,7 @@ set(ABSL_INTERNAL_DLL_TARGETS
"strerror"
"strings"
"strings_internal"
"string_view"
"symbolize"
"synchronization"
"thread_pool"
@@ -596,8 +647,30 @@ set(ABSL_INTERNAL_DLL_TARGETS
"type_traits"
"utility"
"variant"
"vlog_config_internal"
"vlog_is_on"
)
if(NOT MSVC)
list(APPEND ABSL_INTERNAL_DLL_TARGETS
"flags"
"flags_commandlineflag"
"flags_commandlineflag_internal"
"flags_config"
"flags_internal"
"flags_marshalling"
"flags_parse"
"flags_path_util"
"flags_private_handle_accessor"
"flags_program_name"
"flags_reflection"
"flags_usage"
"flags_usage_internal"
"log_internal_flags"
"log_flags"
)
endif()
set(ABSL_INTERNAL_TEST_DLL_FILES
"hash/hash_testing.h"
"log/scoped_mock_log.cc"
@@ -674,12 +747,7 @@ function(absl_internal_dll_contains)
STRING(REGEX REPLACE "^absl::" "" _target ${ABSL_INTERNAL_DLL_TARGET})
list(FIND
ABSL_INTERNAL_DLL_TARGETS
"${_target}"
_index)
if (${_index} GREATER -1)
if (_target IN_LIST ABSL_INTERNAL_DLL_TARGETS)
set(${ABSL_INTERNAL_DLL_OUTPUT} 1 PARENT_SCOPE)
else()
set(${ABSL_INTERNAL_DLL_OUTPUT} 0 PARENT_SCOPE)
@@ -696,12 +764,7 @@ function(absl_internal_test_dll_contains)
STRING(REGEX REPLACE "^absl::" "" _target ${ABSL_INTERNAL_TEST_DLL_TARGET})
list(FIND
ABSL_INTERNAL_TEST_DLL_TARGETS
"${_target}"
_index)
if (${_index} GREATER -1)
if (_target IN_LIST ABSL_INTERNAL_TEST_DLL_TARGETS)
set(${ABSL_INTERNAL_TEST_DLL_OUTPUT} 1 PARENT_SCOPE)
else()
set(${ABSL_INTERNAL_TEST_DLL_OUTPUT} 0 PARENT_SCOPE)
@@ -753,7 +816,12 @@ function(absl_make_dll)
else()
set(_dll "abseil_dll")
set(_dll_files ${ABSL_INTERNAL_DLL_FILES})
set(_dll_libs "")
set(_dll_libs
Threads::Threads
# TODO(#1495): Use $<LINK_LIBRARY:FRAMEWORK,CoreFoundation> once our
# minimum CMake version >= 3.24
$<$<PLATFORM_ID:Darwin>:-Wl,-framework,CoreFoundation>
)
set(_dll_compile_definitions "")
set(_dll_includes "")
set(_dll_consume "ABSL_CONSUME_DLL")
@@ -770,8 +838,12 @@ function(absl_make_dll)
PRIVATE
${_dll_libs}
${ABSL_DEFAULT_LINKOPTS}
$<$<BOOL:${ANDROID}>:-llog>
)
set_target_properties(${_dll} PROPERTIES
LINKER_LANGUAGE "CXX"
SOVERSION ${ABSL_SOVERSION}
)
set_property(TARGET ${_dll} PROPERTY LINKER_LANGUAGE "CXX")
target_include_directories(
${_dll}
PUBLIC
+16 -9
View File
@@ -186,16 +186,16 @@ function(absl_cc_library)
endif()
endif()
endforeach()
set(skip_next_cflag OFF)
foreach(cflag ${ABSL_CC_LIB_COPTS})
if(skip_next_cflag)
set(skip_next_cflag OFF)
elseif(${cflag} MATCHES "^-Xarch_")
# Strip out the CMake-specific `SHELL:` prefix, which is used to construct
# a group of space-separated options.
# https://cmake.org/cmake/help/v3.30/command/target_compile_options.html#option-de-duplication
string(REGEX REPLACE "^SHELL:" "" cflag "${cflag}")
if(${cflag} MATCHES "^-Xarch_")
# An -Xarch_ flag implies that its successor only applies to the
# specified platform. Filter both of them out before the successor
# reaches the "^-m" filter.
set(skip_next_cflag ON)
elseif(${cflag} MATCHES "^(-Wno|/wd)")
# specified platform. Such option groups are each specified in a single
# `SHELL:`-prefixed string in the COPTS list, which we simply ignore.
elseif(${cflag} MATCHES "^(-Wno-|/wd)")
# These flags are needed to suppress warnings that might fire in our headers.
set(PC_CFLAGS "${PC_CFLAGS} ${cflag}")
elseif(${cflag} MATCHES "^(-W|/w[1234eo])")
@@ -258,6 +258,13 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n")
elseif(_build_type STREQUAL "static" OR _build_type STREQUAL "shared")
add_library(${_NAME} "")
target_sources(${_NAME} PRIVATE ${ABSL_CC_LIB_SRCS} ${ABSL_CC_LIB_HDRS})
if(APPLE)
set_target_properties(${_NAME} PROPERTIES
INSTALL_RPATH "@loader_path")
elseif(UNIX)
set_target_properties(${_NAME} PROPERTIES
INSTALL_RPATH "$ORIGIN")
endif()
target_link_libraries(${_NAME}
PUBLIC ${ABSL_CC_LIB_DEPS}
PRIVATE
@@ -306,7 +313,7 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n")
if(ABSL_ENABLE_INSTALL)
set_target_properties(${_NAME} PROPERTIES
OUTPUT_NAME "absl_${_NAME}"
SOVERSION 0
SOVERSION "${ABSL_SOVERSION}"
)
endif()
else()
+1 -1
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.16)
project(googletest-external NONE)
+2 -2
View File
@@ -39,7 +39,7 @@ section of your executable or of your library.<br>
Here is a short CMakeLists.txt example of an application project using Abseil.
```cmake
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.16)
project(my_app_project)
# Pick the C++ standard to compile with.
@@ -62,7 +62,7 @@ will control Abseil library targets) is set to at least that minimum. For
example:
```cmake
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.16)
project(my_lib_project)
# Leave C++ standard up to the root application, so set it only if this is the
@@ -15,7 +15,7 @@
# A simple CMakeLists.txt for testing cmake installation
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.16)
project(absl_cmake_testing CXX)
add_executable(simple simple.cc)
+5 -4
View File
@@ -22,7 +22,8 @@ set -euox pipefail
absl_dir=/abseil-cpp
absl_build_dir=/buildfs
googletest_builddir=/googletest_builddir
project_dir="${absl_dir}"/CMake/install_test_project
googletest_archive="googletest-${ABSL_GOOGLETEST_VERSION}.tar.gz"
project_dir="${absl_dir}/CMake/install_test_project"
project_build_dir=/buildfs/project-build
build_shared_libs="OFF"
@@ -33,9 +34,9 @@ fi
# Build and install GoogleTest
mkdir "${googletest_builddir}"
pushd "${googletest_builddir}"
curl -L "${ABSL_GOOGLETEST_DOWNLOAD_URL}" --output "${ABSL_GOOGLETEST_COMMIT}".zip
unzip "${ABSL_GOOGLETEST_COMMIT}".zip
pushd "googletest-${ABSL_GOOGLETEST_COMMIT}"
curl -L "${ABSL_GOOGLETEST_DOWNLOAD_URL}" --output "${googletest_archive}"
tar -xz -f "${googletest_archive}"
pushd "googletest-${ABSL_GOOGLETEST_VERSION}"
mkdir build
pushd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS="${build_shared_libs}" ..
+44 -54
View File
@@ -15,50 +15,16 @@
#
# https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md
# As of 2022-09-06, CMake 3.10 is the minimum supported version.
cmake_minimum_required(VERSION 3.10)
# Compiler id for Apple Clang is now AppleClang.
if (POLICY CMP0025)
cmake_policy(SET CMP0025 NEW)
endif (POLICY CMP0025)
# if command can use IN_LIST
if (POLICY CMP0057)
cmake_policy(SET CMP0057 NEW)
endif (POLICY CMP0057)
# Project version variables are the empty string if version is unspecified
if (POLICY CMP0048)
cmake_policy(SET CMP0048 NEW)
endif (POLICY CMP0048)
# Honor the GTest_ROOT variable if specified
if (POLICY CMP0074)
cmake_policy(SET CMP0074 NEW)
endif (POLICY CMP0074)
# option() honor variables
if (POLICY CMP0077)
cmake_policy(SET CMP0077 NEW)
endif (POLICY CMP0077)
# Allow the user to specify the MSVC runtime
if (POLICY CMP0091)
cmake_policy(SET CMP0091 NEW)
endif (POLICY CMP0091)
# try_compile() honors the CMAKE_CXX_STANDARD value
if (POLICY CMP0067)
cmake_policy(SET CMP0067 NEW)
endif (POLICY CMP0067)
# As of 2024-07-01, CMake 3.16 is the minimum supported version.
cmake_minimum_required(VERSION 3.16)
# Allow the user to specify the CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
if (POLICY CMP0141)
cmake_policy(SET CMP0141 NEW)
endif (POLICY CMP0141)
project(absl LANGUAGES CXX)
project(absl LANGUAGES CXX VERSION 20250127)
set(ABSL_SOVERSION "2501.0.0")
include(CTest)
# Output directory is correct by default for most build setups. However, when
@@ -75,12 +41,13 @@ else()
option(ABSL_ENABLE_INSTALL "Enable install rule" ON)
endif()
set(CMAKE_INSTALL_RPATH "$ORIGIN")
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH ON)
set(CMAKE_BUILD_RPATH_USE_ORIGIN ON)
option(ABSL_PROPAGATE_CXX_STD
"Use CMake C++ standard meta features (e.g. cxx_std_14) that propagate to targets that link to Abseil"
OFF) # TODO: Default to ON for CMake 3.8 and greater.
if(NOT ABSL_PROPAGATE_CXX_STD)
message(WARNING "A future Abseil release will default ABSL_PROPAGATE_CXX_STD to ON for CMake 3.8 and up. We recommend enabling this option to ensure your project still builds correctly.")
endif()
ON)
option(ABSL_USE_SYSTEM_INCLUDES
"Silence warnings in Abseil headers by marking them as SYSTEM includes"
@@ -91,6 +58,15 @@ list(APPEND CMAKE_MODULE_PATH
${CMAKE_CURRENT_LIST_DIR}/absl/copts
)
option(ABSL_MSVC_STATIC_RUNTIME
"Link static runtime libraries"
OFF)
if(ABSL_MSVC_STATIC_RUNTIME)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
else()
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
endif()
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
include(AbseilDll)
@@ -149,6 +125,14 @@ set(ABSL_LOCAL_GOOGLETEST_DIR "/usr/src/googletest" CACHE PATH
"If ABSL_USE_GOOGLETEST_HEAD is OFF and ABSL_GOOGLETEST_URL is not set, specifies the directory of a local GoogleTest checkout."
)
option(ABSL_BUILD_MONOLITHIC_SHARED_LIBS
"Build Abseil as a single shared library (always enabled for Windows)"
OFF
)
if(NOT BUILD_SHARED_LIBS AND ABSL_BUILD_MONOLITHIC_SHARED_LIBS)
message(WARNING "Not building a shared library because BUILD_SHARED_LIBS is not set. Ignoring ABSL_BUILD_MONOLITHIC_SHARED_LIBS.")
endif()
if((BUILD_TESTING AND ABSL_BUILD_TESTING) OR ABSL_BUILD_TEST_HELPERS)
if (ABSL_USE_EXTERNAL_GOOGLETEST)
if (ABSL_FIND_GOOGLETEST)
@@ -186,17 +170,7 @@ endif()
add_subdirectory(absl)
if(ABSL_ENABLE_INSTALL)
# absl:lts-remove-begin(system installation is supported for LTS releases)
# We don't support system-wide installation
list(APPEND SYSTEM_INSTALL_DIRS "/usr/local" "/usr" "/opt/" "/opt/local" "c:/Program Files/${PROJECT_NAME}")
if(NOT DEFINED CMAKE_INSTALL_PREFIX OR CMAKE_INSTALL_PREFIX IN_LIST SYSTEM_INSTALL_DIRS)
message(WARNING "\
The default and system-level install directories are unsupported except in LTS \
releases of Abseil. Please set CMAKE_INSTALL_PREFIX to install Abseil in your \
source or build tree directly.\
")
endif()
# absl:lts-remove-end
# install as a subdirectory only
install(EXPORT ${PROJECT_NAME}Targets
@@ -227,11 +201,13 @@ if(ABSL_ENABLE_INSTALL)
)
endif() # absl_VERSION
# Install the headers except for "options.h" which is installed separately.
install(DIRECTORY absl
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FILES_MATCHING
PATTERN "*.inc"
PATTERN "*.h"
PATTERN "options.h" EXCLUDE
PATTERN "copts" EXCLUDE
PATTERN "testdata" EXCLUDE
)
@@ -271,7 +247,21 @@ if(ABSL_ENABLE_INSTALL)
ABSL_INTERNAL_OPTIONS_H_PINNED
"${ABSL_INTERNAL_OPTIONS_H_CONTENTS}")
file(WRITE "${CMAKE_BINARY_DIR}/options-pinned.h" "${ABSL_INTERNAL_OPTIONS_H_PINNED}")
# If the file already exists, check if it matches the new contents.
# This avoids writing the file if it is already up-to-date when the CMake
# generation is triggered and triggering unnecessary rebuilds.
set(ABSL_INTERNAL_OPTIONS_H_PINNED_NEEDS_UPDATE TRUE)
if (EXISTS "${CMAKE_BINARY_DIR}/options-pinned.h")
file(READ "${CMAKE_BINARY_DIR}/options-pinned.h" ABSL_INTERNAL_OPTIONS_PINNED_H_CONTENTS)
if ("${ABSL_INTERNAL_OPTIONS_H_PINNED}" STREQUAL "${ABSL_INTERNAL_OPTIONS_PINNED_H_CONTENTS}")
set(ABSL_INTERNAL_OPTIONS_H_PINNED_NEEDS_UPDATE FALSE)
endif()
endif()
# If the file needs an update, generate it.
if (ABSL_INTERNAL_OPTIONS_H_PINNED_NEEDS_UPDATE)
file(GENERATE OUTPUT "${CMAKE_BINARY_DIR}/options-pinned.h" CONTENT "${ABSL_INTERNAL_OPTIONS_H_PINNED}")
endif()
install(FILES "${CMAKE_BINARY_DIR}/options-pinned.h"
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/absl/base
-6
View File
@@ -1,6 +0,0 @@
monorail: {
component: "Internals>Core"
}
buganizer_public: {
component_id: 1456128
}
+18 -14
View File
@@ -16,27 +16,31 @@
module(
name = "abseil-cpp",
version = "head",
version = "20250127.1",
compatibility_level = 1,
)
cc_configure = use_extension("@bazel_tools//tools/cpp:cc_configure.bzl", "cc_configure_extension")
cc_configure = use_extension("@rules_cc//cc:extensions.bzl",
"cc_configure_extension",
dev_dependency = True)
use_repo(cc_configure, "local_config_cc")
# Only direct dependencies need to be listed below.
# Please keep the versions in sync with the versions in the WORKSPACE file.
bazel_dep(name = "bazel_skylib",
version = "1.5.0")
bazel_dep(name = "rules_cc", version = "0.0.17")
bazel_dep(name = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "platforms", version = "0.0.10")
bazel_dep(name = "google_benchmark",
version = "1.8.3",
repo_name = "com_github_google_benchmark",
dev_dependency = True)
bazel_dep(
name = "google_benchmark",
version = "1.8.5",
dev_dependency = True,
)
bazel_dep(name = "googletest",
version = "1.14.0.bcr.1",
repo_name = "com_google_googletest")
bazel_dep(name = "platforms",
version = "0.0.8")
# Note: Googletest is NOT a dev_dependency. Some Abseil test utilities
# intended to be used by Abseil users depend on GoogleTest.
bazel_dep(
name = "googletest",
version = "1.15.2",
)
-5
View File
@@ -1,5 +0,0 @@
danilchap@chromium.org
mbonadei@chromium.org
# For .def files updates:
hans@chromium.org

Some files were not shown because too many files have changed in this diff Show More