Compare commits

..

1 Commits

Author SHA1 Message Date
Benjamin Doherty
d3be6a99e3 Test commit 2024-09-16 12:09:26 -07:00
211 changed files with 5683 additions and 9536 deletions

View File

@@ -8,15 +8,35 @@ on:
- rc/**
jobs:
build-android:
name: build-android
# We intentially use a larger runner here to enable larger disk space
# (standard linux runner will fail on disk space and faster build time).
runs-on: ubuntu-22.04-32core
build-android-armv7:
name: build-android-armv7
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- name: Run Android Continuous
uses: ./.github/actions/android-continuous
with:
build-abi: armeabi-v7a,arm64-v8a,x86_64
build-abi: armeabi-v7a
build-android-armv8a:
name: build-android-armv8a
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- name: Run Android Continuous
uses: ./.github/actions/android-continuous
with:
build-abi: arm64-v8a
build-android-x86_64:
name: build-android-x86_64
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- name: Run Android Continuous
uses: ./.github/actions/android-continuous
with:
build-abi: x86_64

View File

@@ -10,14 +10,14 @@ on:
jobs:
build-ios:
name: build-ios
runs-on: macos-14-xlarge
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/ios && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v1.0.0
with:
name: filament-ios
path: out/filament-release-ios.tgz

View File

@@ -10,14 +10,14 @@ on:
jobs:
build-linux:
name: build-linux
runs-on: ubuntu-22.04-16core
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/linux && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v1.0.0
with:
name: filament-linux
path: out/filament-release-linux.tgz

View File

@@ -10,14 +10,14 @@ on:
jobs:
build-mac:
name: build-mac
runs-on: macos-14-xlarge
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/mac && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v1.0.0
with:
name: filament-mac
path: out/filament-release-darwin.tgz

View File

@@ -15,7 +15,7 @@ jobs:
strategy:
matrix:
os: [macos-14-xlarge, ubuntu-22.04-16core]
os: [macos-14, ubuntu-22.04]
steps:
- uses: actions/checkout@v4.1.6
@@ -29,7 +29,7 @@ jobs:
build-windows:
name: build-windows
runs-on: win-2019-16core
runs-on: windows-2019
steps:
- uses: actions/checkout@v4.1.6
@@ -40,7 +40,7 @@ jobs:
build-android:
name: build-android
runs-on: ubuntu-22.04-16core
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
@@ -56,7 +56,7 @@ jobs:
build-ios:
name: build-iOS
runs-on: macos-14-xlarge
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
@@ -69,7 +69,7 @@ jobs:
build-web:
name: build-web
runs-on: ubuntu-22.04-16core
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6

View File

@@ -31,7 +31,7 @@ jobs:
strategy:
matrix:
os: [macos-14-xlarge, ubuntu-22.04-32core]
os: [macos-14, ubuntu-22.04]
steps:
- name: Decide Git ref
@@ -65,7 +65,7 @@ jobs:
build-web:
name: build-web
runs-on: ubuntu-22.04-16core
runs-on: macos-14
if: github.event_name == 'release' || github.event.inputs.platform == 'web'
steps:
@@ -98,7 +98,7 @@ jobs:
build-android:
name: build-android
runs-on: ubuntu-22.04-16core
runs-on: macos-14
if: github.event_name == 'release' || github.event.inputs.platform == 'android'
steps:
@@ -152,7 +152,7 @@ jobs:
build-ios:
name: build-ios
runs-on: macos-14-xlarge
runs-on: macos-14
if: github.event_name == 'release' || github.event.inputs.platform == 'ios'
steps:
@@ -185,7 +185,7 @@ jobs:
build-windows:
name: build-windows
runs-on: windows-2019-32core
runs-on: windows-2019
if: github.event_name == 'release' || github.event.inputs.platform == 'windows'
steps:
@@ -205,7 +205,6 @@ jobs:
TAG: ${{ steps.git_ref.outputs.tag }}
run: |
build\windows\build-github.bat release
echo on
move out\filament-windows.tgz out\filament-%TAG%-windows.tgz
shell: cmd
- uses: actions/github-script@v6

View File

@@ -10,14 +10,14 @@ on:
jobs:
build-web:
name: build-web
runs-on: ubuntu-22.04-16core
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/web && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v1.0.0
with:
name: filament-web
path: out/filament-release-web.tgz

View File

@@ -10,7 +10,7 @@ on:
jobs:
build-windows:
name: build-windows
runs-on: windows-2019-32core
runs-on: windows-2019
steps:
- uses: actions/checkout@v4.1.6
@@ -18,7 +18,7 @@ jobs:
run: |
build\windows\build-github.bat continuous
shell: cmd
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v1.0.0
with:
name: filament-windows
path: out/filament-windows.tgz

View File

@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.55.0'
implementation 'com.google.android.filament:filament-android:1.54.3'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.55.0'
pod 'Filament', '~> 1.54.3'
```
## Documentation

View File

@@ -7,16 +7,6 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.55.0
- Add descriptor sets to describe shader resources. [⚠️ **New Material Version**]
## v1.54.5
## v1.54.4
- Add support for multi-layered render target with array textures.
## v1.54.3

View File

@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.55.0
VERSION_NAME=1.54.3
POM_DESCRIPTION=Real-time physically based rendering engine for Android.

View File

@@ -1,6 +1,7 @@
#!/bin/bash
if [ `uname` == "Linux" ];then
source `dirname $0`/../linux/ci-common.sh
curl -OL https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-linux.zip
unzip -q ninja-linux.zip
elif [ `uname` == "Darwin" ];then
curl -OL https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-mac.zip
unzip -q ninja-mac.zip
@@ -12,6 +13,9 @@ fi
chmod +x ninja
export PATH="$PWD:$PATH"
# FIXME: kokoro machines have node and npm but currently they are symlinked to non-existent files
# npm install -g typescript
# Install emscripten.
curl -L https://github.com/emscripten-core/emsdk/archive/refs/tags/3.1.15.zip > emsdk.zip
unzip emsdk.zip ; mv emsdk-* emsdk ; cd emsdk

View File

@@ -115,23 +115,17 @@ cmake ..\.. ^
-DFILAMENT_SUPPORTS_VULKAN=ON ^
|| exit /b
set build_flags=-j %NUMBER_OF_PROCESSORS%
@echo on
:: we've upgraded the windows machines, so the following are no longer accurate as of 09/19/24, but
:: keeping around the comment for record.
:: Attempt to fix "error C1060: compiler is out of heap space" seen on CI.
:: Some resource libraries require significant heap space to compile, so first compile them serially.
:: cmake --build . --target filagui --config %config% %build_flags% || exit /b
:: cmake --build . --target uberarchive --config %config% %build_flags% || exit /b
:: cmake --build . --target gltf-demo-resources --config %config% %build_flags% || exit /b
:: cmake --build . --target filamentapp-resources --config %config% %build_flags% || exit /b
:: cmake --build . --target sample-resources --config %config% %build_flags% || exit /b
:: cmake --build . --target suzanne-resources --config %config% %build_flags% || exit /b
@echo on
cmake --build . --target filagui --config %config% || exit /b
cmake --build . --target uberarchive --config %config% || exit /b
cmake --build . --target gltf-demo-resources --config %config% || exit /b
cmake --build . --target filamentapp-resources --config %config% || exit /b
cmake --build . --target sample-resources --config %config% || exit /b
cmake --build . --target suzanne-resources --config %config% || exit /b
cmake --build . %INSTALL% --config %config% %build_flags% -- /m || exit /b
cmake --build . %INSTALL% --config %config% -- /m || exit /b
@echo off
echo Disk info after building variant: %variant%

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -66,7 +66,6 @@ set(SRCS
src/FrameSkipper.cpp
src/Froxelizer.cpp
src/Frustum.cpp
src/HwDescriptorSetLayoutFactory.cpp
src/HwRenderPrimitiveFactory.cpp
src/HwVertexBufferInfoFactory.cpp
src/IndexBuffer.cpp
@@ -77,6 +76,8 @@ set(SRCS
src/MaterialInstance.cpp
src/MaterialParser.cpp
src/MorphTargetBuffer.cpp
src/PerViewUniforms.cpp
src/PerShadowMapUniforms.cpp
src/PostProcessManager.cpp
src/RenderPass.cpp
src/RenderPrimitive.cpp
@@ -125,12 +126,6 @@ set(SRCS
src/details/Texture.cpp
src/details/VertexBuffer.cpp
src/details/View.cpp
src/ds/ColorPassDescriptorSet.cpp
src/ds/DescriptorSet.cpp
src/ds/DescriptorSetLayout.cpp
src/ds/PostProcessDescriptorSet.cpp
src/ds/ShadowMapDescriptorSet.cpp
src/ds/SsrPassDescriptorSet.cpp
src/fg/Blackboard.cpp
src/fg/DependencyGraph.cpp
src/fg/FrameGraph.cpp
@@ -154,21 +149,23 @@ set(PRIVATE_HDRS
src/FrameInfo.h
src/FrameSkipper.h
src/Froxelizer.h
src/HwDescriptorSetLayoutFactory.h
src/HwRenderPrimitiveFactory.h
src/HwVertexBufferInfoFactory.h
src/Intersections.h
src/MaterialParser.h
src/PerViewUniforms.h
src/PerShadowMapUniforms.h
src/PIDController.h
src/PostProcessManager.h
src/RendererUtils.h
src/RenderPass.h
src/RenderPrimitive.h
src/RendererUtils.h
src/ResourceAllocator.h
src/ResourceList.h
src/ShadowMap.h
src/ShadowMapManager.h
src/SharedHandle.h
src/TypedUniformBuffer.h
src/UniformBuffer.h
src/components/CameraManager.h
src/components/LightManager.h
@@ -196,14 +193,6 @@ set(PRIVATE_HDRS
src/details/Texture.h
src/details/VertexBuffer.h
src/details/View.h
src/downcast.h
src/ds/ColorPassDescriptorSet.h
src/ds/DescriptorSetLayout.h
src/ds/PostProcessDescriptorSet.h
src/ds/ShadowMapDescriptorSet.h
src/ds/SsrPassDescriptorSet.h
src/ds/TypedBuffer.h
src/ds/TypedUniformBuffer.h
src/fg/Blackboard.h
src/fg/FrameGraph.h
src/fg/FrameGraphId.h
@@ -221,6 +210,7 @@ set(PRIVATE_HDRS
src/materials/fsr/ffx_a.h
src/materials/fsr/ffx_fsr1.h
src/materials/fsr/ffx_fsr1_mobile.fs
src/downcast.h
)
set(MATERIAL_SRCS

View File

@@ -12,7 +12,6 @@ set(PUBLIC_HDRS
include/backend/AcquiredImage.h
include/backend/BufferDescriptor.h
include/backend/CallbackHandler.h
include/backend/DescriptorSetOffsetArray.h
include/backend/DriverApiForward.h
include/backend/DriverEnums.h
include/backend/Handle.h
@@ -70,13 +69,9 @@ set(PRIVATE_HDRS
if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
list(APPEND SRCS
include/backend/platforms/OpenGLPlatform.h
src/opengl/BindingMap.h
src/opengl/gl_headers.cpp
src/opengl/gl_headers.h
src/opengl/GLBufferObject.h
src/opengl/GLDescriptorSet.cpp
src/opengl/GLDescriptorSet.h
src/opengl/GLDescriptorSetLayout.h
src/opengl/GLTexture.h
src/opengl/GLUtils.cpp
src/opengl/GLUtils.h
@@ -500,40 +495,21 @@ endif()
# ==================================================================================================
# Compute tests
#
#if (NOT IOS AND NOT WEBGL)
#
#add_executable(compute_test
# test/ComputeTest.cpp
# test/Arguments.cpp
# test/test_ComputeBasic.cpp
# )
#
#target_link_libraries(compute_test PRIVATE
# backend
# getopt
# gtest
# )
#
#set_target_properties(compute_test PROPERTIES FOLDER Tests)
#
#endif()
# ==================================================================================================
# Metal utils tests
if (NOT IOS AND NOT WEBGL)
if (APPLE AND NOT IOS)
add_executable(compute_test
test/ComputeTest.cpp
test/Arguments.cpp
test/test_ComputeBasic.cpp
)
add_executable(metal_utils_test test/MetalTest.mm)
target_compile_options(metal_utils_test PRIVATE "-fobjc-arc")
target_link_libraries(metal_utils_test PRIVATE
target_link_libraries(compute_test PRIVATE
backend
getopt
gtest
)
set_target_properties(metal_utils_test PROPERTIES FOLDER Tests)
set_target_properties(compute_test PROPERTIES FOLDER Tests)
endif()

View File

@@ -1,101 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_COMMANDSTREAMVECTOR_H
#define TNT_FILAMENT_BACKEND_COMMANDSTREAMVECTOR_H
#include <backend/DriverApiForward.h>
#include <initializer_list>
#include <memory>
#include <stddef.h>
#include <stdint.h>
namespace filament::backend {
void* allocateFromCommandStream(DriverApi& driver, size_t size, size_t alignment) noexcept;
class DescriptorSetOffsetArray {
public:
using value_type = uint32_t;
using reference = value_type&;
using const_reference = value_type const&;
using size_type = uint32_t;
using difference_type = int32_t;
using pointer = value_type*;
using const_pointer = value_type const*;
using iterator = pointer;
using const_iterator = const_pointer;
DescriptorSetOffsetArray() noexcept = default;
~DescriptorSetOffsetArray() noexcept = default;
DescriptorSetOffsetArray(size_type size, DriverApi& driver) noexcept {
mOffsets = (value_type *)allocateFromCommandStream(driver,
size * sizeof(value_type), alignof(value_type));
std::uninitialized_fill_n(mOffsets, size, 0);
}
DescriptorSetOffsetArray(std::initializer_list<uint32_t> list, DriverApi& driver) noexcept {
mOffsets = (value_type *)allocateFromCommandStream(driver,
list.size() * sizeof(value_type), alignof(value_type));
std::uninitialized_copy(list.begin(), list.end(), mOffsets);
}
DescriptorSetOffsetArray(DescriptorSetOffsetArray const&) = delete;
DescriptorSetOffsetArray& operator=(DescriptorSetOffsetArray const&) = delete;
DescriptorSetOffsetArray(DescriptorSetOffsetArray&& rhs) noexcept
: mOffsets(rhs.mOffsets) {
rhs.mOffsets = nullptr;
}
DescriptorSetOffsetArray& operator=(DescriptorSetOffsetArray&& rhs) noexcept {
if (this != &rhs) {
mOffsets = rhs.mOffsets;
rhs.mOffsets = nullptr;
}
return *this;
}
bool empty() const noexcept { return mOffsets == nullptr; }
value_type* data() noexcept { return mOffsets; }
const value_type* data() const noexcept { return mOffsets; }
reference operator[](size_type n) noexcept {
return *(data() + n);
}
const_reference operator[](size_type n) const noexcept {
return *(data() + n);
}
void clear() noexcept {
mOffsets = nullptr;
}
private:
value_type *mOffsets = nullptr;
};
} // namespace filament::backend
#endif //TNT_FILAMENT_BACKEND_COMMANDSTREAMVECTOR_H

View File

@@ -19,16 +19,13 @@
#ifndef TNT_FILAMENT_BACKEND_DRIVERENUMS_H
#define TNT_FILAMENT_BACKEND_DRIVERENUMS_H
#include <utils/BitmaskEnum.h>
#include <utils/unwindows.h> // Because we define ERROR in the FenceStatus enum.
#include <backend/Platform.h>
#include <backend/PresentCallable.h>
#include <utils/BitmaskEnum.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <math/vec4.h>
@@ -100,8 +97,6 @@ static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guarantee
static constexpr size_t MAX_SAMPLER_COUNT = 62; // Maximum needed at feature level 3.
static constexpr size_t MAX_VERTEX_BUFFER_COUNT = 16; // Max number of bound buffer objects.
static constexpr size_t MAX_SSBO_COUNT = 4; // This is guaranteed by OpenGL ES.
static constexpr size_t MAX_DESCRIPTOR_SET_COUNT = 4; // This is guaranteed by Vulkan.
static constexpr size_t MAX_DESCRIPTOR_COUNT = 64; // per set
static constexpr size_t MAX_PUSH_CONSTANT_COUNT = 32; // Vulkan 1.1 spec allows for 128-byte
// of push constant (we assume 4-byte
@@ -196,70 +191,6 @@ static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguag
}
}
enum class ShaderStage : uint8_t {
VERTEX = 0,
FRAGMENT = 1,
COMPUTE = 2
};
static constexpr size_t PIPELINE_STAGE_COUNT = 2;
enum class ShaderStageFlags : uint8_t {
NONE = 0,
VERTEX = 0x1,
FRAGMENT = 0x2,
COMPUTE = 0x4,
ALL_SHADER_STAGE_FLAGS = VERTEX | FRAGMENT | COMPUTE
};
static inline constexpr bool hasShaderType(ShaderStageFlags flags, ShaderStage type) noexcept {
switch (type) {
case ShaderStage::VERTEX:
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::VERTEX));
case ShaderStage::FRAGMENT:
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::FRAGMENT));
case ShaderStage::COMPUTE:
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::COMPUTE));
}
}
enum class DescriptorType : uint8_t {
UNIFORM_BUFFER,
SHADER_STORAGE_BUFFER,
SAMPLER,
INPUT_ATTACHMENT,
};
enum class DescriptorFlags : uint8_t {
NONE = 0x00,
DYNAMIC_OFFSET = 0x01
};
using descriptor_set_t = uint8_t;
using descriptor_binding_t = uint8_t;
struct DescriptorSetLayoutBinding {
DescriptorType type;
ShaderStageFlags stageFlags;
descriptor_binding_t binding;
DescriptorFlags flags = DescriptorFlags::NONE;
uint16_t count = 0;
friend inline bool operator==(
DescriptorSetLayoutBinding const& lhs,
DescriptorSetLayoutBinding const& rhs) noexcept {
return lhs.type == rhs.type &&
lhs.flags == rhs.flags &&
lhs.count == rhs.count &&
lhs.stageFlags == rhs.stageFlags;
}
};
struct DescriptorSetLayout {
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
};
/**
* Bitmask for selecting render buffers
*/
@@ -339,6 +270,15 @@ enum class FenceStatus : int8_t {
TIMEOUT_EXPIRED = 1, //!< wait()'s timeout expired. The Fence condition is not satisfied.
};
/**
* Status codes for sync objects
*/
enum class SyncStatus : int8_t {
ERROR = -1, //!< An error occurred. The Sync is not signaled.
SIGNALED = 0, //!< The Sync is signaled.
NOT_SIGNALED = 1, //!< The Sync is not signaled yet
};
static constexpr uint64_t FENCE_WAIT_FOR_EVER = uint64_t(-1);
/**
@@ -428,18 +368,6 @@ enum class SamplerType : uint8_t {
SAMPLER_CUBEMAP_ARRAY, //!< Cube map array texture (feature level 2)
};
inline const char* stringify(SamplerType samplerType) {
switch (samplerType) {
case SamplerType::SAMPLER_2D: return "SAMPLER_2D";
case SamplerType::SAMPLER_2D_ARRAY: return "SAMPLER_2D_ARRAY";
case SamplerType::SAMPLER_CUBEMAP: return "SAMPLER_CUBEMAP";
case SamplerType::SAMPLER_EXTERNAL: return "SAMPLER_EXTERNAL";
case SamplerType::SAMPLER_3D: return "SAMPLER_3D";
case SamplerType::SAMPLER_CUBEMAP_ARRAY: return "SAMPLER_CUBEMAP_ARRAY";
}
return "UNKNOWN";
}
//! Subpass type
enum class SubpassType : uint8_t {
SUBPASS_INPUT
@@ -768,23 +696,6 @@ enum class TextureUsage : uint16_t {
DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage
};
inline const char* stringify(TextureUsage usage) {
switch (usage) {
case TextureUsage::NONE: return "NONE";
case TextureUsage::COLOR_ATTACHMENT: return "COLOR_ATTACHMENT";
case TextureUsage::DEPTH_ATTACHMENT: return "DEPTH_ATTACHMENT";
case TextureUsage::STENCIL_ATTACHMENT: return "STENCIL_ATTACHMENT";
case TextureUsage::UPLOADABLE: return "UPLOADABLE";
case TextureUsage::SAMPLEABLE: return "SAMPLEABLE";
case TextureUsage::SUBPASS_INPUT: return "SUBPASS_INPUT";
case TextureUsage::BLIT_SRC: return "BLIT_SRC";
case TextureUsage::BLIT_DST: return "BLIT_DST";
case TextureUsage::PROTECTED: return "PROTECTED";
case TextureUsage::DEFAULT: return "DEFAULT";
default: return "UNKNOWN";
}
}
//! Texture swizzle
enum class TextureSwizzle : uint8_t {
SUBSTITUTE_ZERO,
@@ -976,9 +887,6 @@ struct SamplerParams { // NOLINT
struct EqualTo {
bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept {
assert_invariant(lhs.padding0 == 0);
assert_invariant(lhs.padding1 == 0);
assert_invariant(lhs.padding2 == 0);
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs == *pRhs;
@@ -987,9 +895,6 @@ struct SamplerParams { // NOLINT
struct LessThan {
bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept {
assert_invariant(lhs.padding0 == 0);
assert_invariant(lhs.padding1 == 0);
assert_invariant(lhs.padding2 == 0);
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs == *pRhs;
@@ -997,12 +902,6 @@ struct SamplerParams { // NOLINT
};
private:
friend inline bool operator == (SamplerParams lhs, SamplerParams rhs) noexcept {
return SamplerParams::EqualTo{}(lhs, rhs);
}
friend inline bool operator != (SamplerParams lhs, SamplerParams rhs) noexcept {
return !SamplerParams::EqualTo{}(lhs, rhs);
}
friend inline bool operator < (SamplerParams lhs, SamplerParams rhs) noexcept {
return SamplerParams::LessThan{}(lhs, rhs);
}
@@ -1170,6 +1069,32 @@ struct RasterState {
* \privatesection
*/
enum class ShaderStage : uint8_t {
VERTEX = 0,
FRAGMENT = 1,
COMPUTE = 2
};
static constexpr size_t PIPELINE_STAGE_COUNT = 2;
enum class ShaderStageFlags : uint8_t {
NONE = 0,
VERTEX = 0x1,
FRAGMENT = 0x2,
COMPUTE = 0x4,
ALL_SHADER_STAGE_FLAGS = VERTEX | FRAGMENT | COMPUTE
};
static inline constexpr bool hasShaderType(ShaderStageFlags flags, ShaderStage type) noexcept {
switch (type) {
case ShaderStage::VERTEX:
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::VERTEX));
case ShaderStage::FRAGMENT:
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::FRAGMENT));
case ShaderStage::COMPUTE:
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::COMPUTE));
}
}
/**
* Selects which buffers to clear at the beginning of the render pass, as well as which buffers
* can be discarded at the beginning and end of the render pass.
@@ -1334,8 +1259,6 @@ template<> struct utils::EnableBitMaskOperators<filament::backend::ShaderStageFl
: public std::true_type {};
template<> struct utils::EnableBitMaskOperators<filament::backend::TargetBufferFlags>
: public std::true_type {};
template<> struct utils::EnableBitMaskOperators<filament::backend::DescriptorFlags>
: public std::true_type {};
template<> struct utils::EnableBitMaskOperators<filament::backend::TextureUsage>
: public std::true_type {};
template<> struct utils::EnableBitMaskOperators<filament::backend::StencilFace>

View File

@@ -41,8 +41,6 @@ struct HwTexture;
struct HwTimerQuery;
struct HwVertexBufferInfo;
struct HwVertexBuffer;
struct HwDescriptorSetLayout;
struct HwDescriptorSet;
/*
* A handle to a backend resource. HandleBase is for internal use only.
@@ -132,21 +130,19 @@ private:
// Types used by the command stream
// (we use this renaming because the macro-system doesn't deal well with "<" and ">")
using BufferObjectHandle = Handle<HwBufferObject>;
using FenceHandle = Handle<HwFence>;
using IndexBufferHandle = Handle<HwIndexBuffer>;
using ProgramHandle = Handle<HwProgram>;
using RenderPrimitiveHandle = Handle<HwRenderPrimitive>;
using RenderTargetHandle = Handle<HwRenderTarget>;
using SamplerGroupHandle = Handle<HwSamplerGroup>;
using StreamHandle = Handle<HwStream>;
using SwapChainHandle = Handle<HwSwapChain>;
using TextureHandle = Handle<HwTexture>;
using TimerQueryHandle = Handle<HwTimerQuery>;
using VertexBufferHandle = Handle<HwVertexBuffer>;
using VertexBufferInfoHandle = Handle<HwVertexBufferInfo>;
using DescriptorSetLayoutHandle = Handle<HwDescriptorSetLayout>;
using DescriptorSetHandle = Handle<HwDescriptorSet>;
using BufferObjectHandle = Handle<HwBufferObject>;
using FenceHandle = Handle<HwFence>;
using IndexBufferHandle = Handle<HwIndexBuffer>;
using ProgramHandle = Handle<HwProgram>;
using RenderPrimitiveHandle = Handle<HwRenderPrimitive>;
using RenderTargetHandle = Handle<HwRenderTarget>;
using SamplerGroupHandle = Handle<HwSamplerGroup>;
using StreamHandle = Handle<HwStream>;
using SwapChainHandle = Handle<HwSwapChain>;
using TextureHandle = Handle<HwTexture>;
using TimerQueryHandle = Handle<HwTimerQuery>;
using VertexBufferHandle = Handle<HwVertexBuffer>;
using VertexBufferInfoHandle = Handle<HwVertexBufferInfo>;
} // namespace filament::backend

View File

@@ -22,23 +22,15 @@
#include <utils/ostream.h>
#include <array>
#include <stdint.h>
namespace filament::backend {
//! \privatesection
struct PipelineLayout {
using SetLayout = std::array<Handle<HwDescriptorSetLayout>, MAX_DESCRIPTOR_SET_COUNT>;
SetLayout setLayout; // 16
};
struct PipelineState {
Handle<HwProgram> program; // 4
Handle<HwVertexBufferInfo> vertexBufferInfo; // 4
PipelineLayout pipelineLayout; // 16
RasterState rasterState; // 4
StencilState stencilState; // 12
PolygonOffset polygonOffset; // 8

View File

@@ -24,11 +24,9 @@
#include <backend/DriverEnums.h>
#include <array>
#include <unordered_map>
#include <tuple>
#include <utility>
#include <variant>
#include <array> // FIXME: STL headers are not allowed in public headers
#include <utility> // FIXME: STL headers are not allowed in public headers
#include <variant> // FIXME: STL headers are not allowed in public headers
#include <stddef.h>
#include <stdint.h>
@@ -42,36 +40,29 @@ public:
static constexpr size_t UNIFORM_BINDING_COUNT = CONFIG_UNIFORM_BINDING_COUNT;
static constexpr size_t SAMPLER_BINDING_COUNT = CONFIG_SAMPLER_BINDING_COUNT;
struct Descriptor {
utils::CString name;
backend::DescriptorType type;
backend::descriptor_binding_t binding;
struct Sampler {
utils::CString name = {}; // name of the sampler in the shader
uint32_t binding = 0; // binding point of the sampler in the shader
};
struct SpecializationConstant {
using Type = std::variant<int32_t, float, bool>;
uint32_t id; // id set in glsl
Type value; // value and type
struct SamplerGroupData {
utils::FixedCapacityVector<Sampler> samplers;
ShaderStageFlags stageFlags = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS;
};
struct Uniform { // For ES2 support
struct Uniform {
utils::CString name; // full qualified name of the uniform field
uint16_t offset; // offset in 'uint32_t' into the uniform buffer
uint8_t size; // >1 for arrays
UniformType type; // uniform type
};
using DescriptorBindingsInfo = utils::FixedCapacityVector<Descriptor>;
using DescriptorSetInfo = std::array<DescriptorBindingsInfo, MAX_DESCRIPTOR_SET_COUNT>;
using SpecializationConstantsInfo = utils::FixedCapacityVector<SpecializationConstant>;
using UniformBlockInfo = std::array<utils::CString, UNIFORM_BINDING_COUNT>;
using UniformInfo = utils::FixedCapacityVector<Uniform>;
using SamplerGroupInfo = std::array<SamplerGroupData, SAMPLER_BINDING_COUNT>;
using ShaderBlob = utils::FixedCapacityVector<uint8_t>;
using ShaderSource = std::array<ShaderBlob, SHADER_TYPE_COUNT>;
using AttributesInfo = utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>>;
using UniformInfo = utils::FixedCapacityVector<Uniform>;
using BindingUniformsInfo = utils::FixedCapacityVector<
std::tuple<uint8_t, utils::CString, Program::UniformInfo>>;
Program() noexcept;
Program(const Program& rhs) = delete;
@@ -88,19 +79,43 @@ public:
Program& diagnostics(utils::CString const& name,
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)>&& logger);
// Sets one of the program's shader (e.g. vertex, fragment)
// sets one of the program's shader (e.g. vertex, fragment)
// string-based shaders are null terminated, consequently the size parameter must include the
// null terminating character.
Program& shader(ShaderStage shader, void const* data, size_t size);
// Sets the language of the shader sources provided with shader() (defaults to ESSL3)
// sets the language of the shader sources provided with shader() (defaults to ESSL3)
Program& shaderLanguage(ShaderLanguage shaderLanguage);
// Descriptor binding (set, binding, type -> shader name) info
Program& descriptorBindings(backend::descriptor_set_t set,
DescriptorBindingsInfo descriptorBindings) noexcept;
// Note: This is only needed for GLES3.0 backends, because the layout(binding=) syntax is
// not permitted in glsl. The backend needs a way to associate a uniform block
// to a binding point.
Program& uniformBlockBindings(
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& uniformBlockBindings) noexcept;
Program& specializationConstants(SpecializationConstantsInfo specConstants) noexcept;
// Note: This is only needed for GLES2.0, this is used to emulate UBO. This function tells
// the program everything it needs to know about the uniforms at a given binding
Program& uniforms(uint32_t index, UniformInfo const& uniforms) noexcept;
// Note: This is only needed for GLES2.0.
Program& attributes(
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes) noexcept;
// sets the 'bindingPoint' sampler group descriptor for this program.
// 'samplers' can be destroyed after this call.
// This effectively associates a set of (BindingPoints, index) to a texture unit in the shader.
// Or more precisely, what layout(binding=) is set to in GLSL.
Program& setSamplerGroup(size_t bindingPoint, ShaderStageFlags stageFlags,
Sampler const* samplers, size_t count) noexcept;
struct SpecializationConstant {
using Type = std::variant<int32_t, float, bool>;
uint32_t id; // id set in glsl
Type value; // value and type
};
Program& specializationConstants(
utils::FixedCapacityVector<SpecializationConstant> specConstants) noexcept;
struct PushConstant {
utils::CString name;
@@ -114,40 +129,33 @@ public:
Program& multiview(bool multiview) noexcept;
// For ES2 support only...
Program& uniforms(uint32_t index, utils::CString name, UniformInfo uniforms) noexcept;
Program& attributes(AttributesInfo attributes) noexcept;
//
// Getters for program construction...
//
ShaderSource const& getShadersSource() const noexcept { return mShadersSource; }
ShaderSource& getShadersSource() noexcept { return mShadersSource; }
UniformBlockInfo const& getUniformBlockBindings() const noexcept { return mUniformBlocks; }
UniformBlockInfo& getUniformBlockBindings() noexcept { return mUniformBlocks; }
SamplerGroupInfo const& getSamplerGroupInfo() const { return mSamplerGroups; }
SamplerGroupInfo& getSamplerGroupInfo() { return mSamplerGroups; }
auto const& getBindingUniformInfo() const { return mBindingUniformInfo; }
auto& getBindingUniformInfo() { return mBindingUniformInfo; }
auto const& getAttributes() const { return mAttributes; }
auto& getAttributes() { return mAttributes; }
utils::CString const& getName() const noexcept { return mName; }
utils::CString& getName() noexcept { return mName; }
auto const& getShaderLanguage() const { return mShaderLanguage; }
uint64_t getCacheId() const noexcept { return mCacheId; }
bool isMultiview() const noexcept { return mMultiview; }
CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; }
SpecializationConstantsInfo const& getSpecializationConstants() const noexcept {
utils::FixedCapacityVector<SpecializationConstant> const& getSpecializationConstants() const noexcept {
return mSpecializationConstants;
}
SpecializationConstantsInfo& getSpecializationConstants() noexcept {
utils::FixedCapacityVector<SpecializationConstant>& getSpecializationConstants() noexcept {
return mSpecializationConstants;
}
DescriptorSetInfo& getDescriptorBindings() noexcept {
return mDescriptorBindings;
}
utils::FixedCapacityVector<PushConstant> const& getPushConstants(
ShaderStage stage) const noexcept {
return mPushConstants[static_cast<uint8_t>(stage)];
@@ -157,29 +165,27 @@ public:
return mPushConstants[static_cast<uint8_t>(stage)];
}
auto const& getBindingUniformInfo() const { return mBindingUniformsInfo; }
auto& getBindingUniformInfo() { return mBindingUniformsInfo; }
uint64_t getCacheId() const noexcept { return mCacheId; }
auto const& getAttributes() const { return mAttributes; }
auto& getAttributes() { return mAttributes; }
bool isMultiview() const noexcept { return mMultiview; }
CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; }
private:
friend utils::io::ostream& operator<<(utils::io::ostream& out, const Program& builder);
UniformBlockInfo mUniformBlocks = {};
SamplerGroupInfo mSamplerGroups = {};
ShaderSource mShadersSource;
ShaderLanguage mShaderLanguage = ShaderLanguage::ESSL3;
utils::CString mName;
uint64_t mCacheId{};
CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH;
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)> mLogger;
SpecializationConstantsInfo mSpecializationConstants;
utils::FixedCapacityVector<SpecializationConstant> mSpecializationConstants;
std::array<utils::FixedCapacityVector<PushConstant>, SHADER_TYPE_COUNT> mPushConstants;
DescriptorSetInfo mDescriptorBindings;
// For ES2 support only
AttributesInfo mAttributes;
BindingUniformsInfo mBindingUniformsInfo;
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> mAttributes;
std::array<UniformInfo, Program::UNIFORM_BINDING_COUNT> mBindingUniformInfo;
CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH;
// Indicates the current engine was initialized with multiview stereo, and the variant for this
// program contains STE flag. This will be referred later for the OpenGL shader compiler to
// determine whether shader code replacement for the num_views should be performed.

View File

@@ -30,6 +30,10 @@ namespace filament::backend {
struct TargetBufferInfo {
// note: the parameters of this constructor are not in the order of this structure's fields
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level, uint16_t layer, uint8_t baseViewIndex) noexcept
: handle(handle), baseViewIndex(baseViewIndex), level(level), layer(layer) {
}
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level, uint16_t layer) noexcept
: handle(handle), level(level), layer(layer) {
}
@@ -47,15 +51,14 @@ struct TargetBufferInfo {
// texture to be used as render target
Handle<HwTexture> handle;
// Starting layer index for multiview. This value is only used when the `layerCount` for the
// render target is greater than 1.
uint8_t baseViewIndex = 0;
// level to be used
uint8_t level = 0;
// - For cubemap textures, this indicates the face of the cubemap. See TextureCubemapFace for
// the face->layer mapping)
// - For 2d array, cubemap array, and 3d textures, this indicates an index of a single layer of
// them.
// - For multiview textures (i.e., layerCount for the RenderTarget is greater than 1), this
// indicates a starting layer index of the current 2d array texture for multiview.
// For cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping
uint16_t layer = 0;
};
@@ -100,7 +103,7 @@ public:
// this is here for backward compatibility
MRT(Handle<HwTexture> handle, uint8_t level, uint16_t layer) noexcept
: mInfos{{ handle, level, layer }} {
: mInfos{{ handle, level, layer, 0 }} {
}
};

View File

@@ -18,7 +18,6 @@
#define TNT_FILAMENT_BACKEND_PRIVATE_DRIVER_H
#include <backend/CallbackHandler.h>
#include <backend/DescriptorSetOffsetArray.h>
#include <backend/DriverApiForward.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>

View File

@@ -200,33 +200,20 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, createTexture,
uint32_t, depth,
backend::TextureUsage, usage)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureView,
backend::TextureHandle, texture,
uint8_t, baseLevel,
uint8_t, levelCount)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureViewSwizzle,
backend::TextureHandle, texture,
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureSwizzled,
backend::SamplerType, target,
uint8_t, levels,
backend::TextureFormat, format,
uint8_t, samples,
uint32_t, width,
uint32_t, height,
uint32_t, depth,
backend::TextureUsage, usage,
backend::TextureSwizzle, r,
backend::TextureSwizzle, g,
backend::TextureSwizzle, b,
backend::TextureSwizzle, a)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureExternalImage,
backend::TextureFormat, format,
uint32_t, width,
uint32_t, height,
backend::TextureUsage, usage,
void*, image)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureExternalImagePlane,
backend::TextureFormat, format,
uint32_t, width,
uint32_t, height,
backend::TextureUsage, usage,
void*, image,
uint32_t, plane)
DECL_DRIVER_API_R_N(backend::TextureHandle, importTexture,
intptr_t, id,
backend::SamplerType, target,
@@ -238,6 +225,9 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, importTexture,
uint32_t, depth,
backend::TextureUsage, usage)
DECL_DRIVER_API_R_N(backend::SamplerGroupHandle, createSamplerGroup,
uint32_t, size, utils::FixedSizeString<32>, debugName)
DECL_DRIVER_API_R_N(backend::RenderPrimitiveHandle, createRenderPrimitive,
backend::VertexBufferHandle, vbh,
backend::IndexBufferHandle, ibh,
@@ -271,53 +261,25 @@ DECL_DRIVER_API_R_N(backend::SwapChainHandle, createSwapChainHeadless,
DECL_DRIVER_API_R_0(backend::TimerQueryHandle, createTimerQuery)
DECL_DRIVER_API_R_N(backend::DescriptorSetLayoutHandle, createDescriptorSetLayout,
backend::DescriptorSetLayout&&, info)
DECL_DRIVER_API_R_N(backend::DescriptorSetHandle, createDescriptorSet,
backend::DescriptorSetLayoutHandle, dslh)
DECL_DRIVER_API_N(updateDescriptorSetBuffer,
backend::DescriptorSetHandle, dsh,
backend::descriptor_binding_t, binding,
backend::BufferObjectHandle, boh,
uint32_t, offset,
uint32_t, size
)
DECL_DRIVER_API_N(updateDescriptorSetTexture,
backend::DescriptorSetHandle, dsh,
backend::descriptor_binding_t, binding,
backend::TextureHandle, th,
SamplerParams, params
)
DECL_DRIVER_API_N(bindDescriptorSet,
backend::DescriptorSetHandle, dsh,
backend::descriptor_set_t, set,
backend::DescriptorSetOffsetArray&&, offsets
)
/*
* Destroying driver objects
* -------------------------
*/
DECL_DRIVER_API_N(destroyVertexBuffer, backend::VertexBufferHandle, vbh)
DECL_DRIVER_API_N(destroyVertexBufferInfo, backend::VertexBufferInfoHandle, vbih)
DECL_DRIVER_API_N(destroyIndexBuffer, backend::IndexBufferHandle, ibh)
DECL_DRIVER_API_N(destroyBufferObject, backend::BufferObjectHandle, ibh)
DECL_DRIVER_API_N(destroyRenderPrimitive, backend::RenderPrimitiveHandle, rph)
DECL_DRIVER_API_N(destroyProgram, backend::ProgramHandle, ph)
DECL_DRIVER_API_N(destroyTexture, backend::TextureHandle, th)
DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth)
DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch)
DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh)
DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh)
DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh)
DECL_DRIVER_API_N(destroyDescriptorSetLayout, backend::DescriptorSetLayoutHandle, dslh)
DECL_DRIVER_API_N(destroyDescriptorSet, backend::DescriptorSetHandle, dsh)
DECL_DRIVER_API_N(destroyVertexBuffer, backend::VertexBufferHandle, vbh)
DECL_DRIVER_API_N(destroyVertexBufferInfo,backend::VertexBufferInfoHandle, vbih)
DECL_DRIVER_API_N(destroyIndexBuffer, backend::IndexBufferHandle, ibh)
DECL_DRIVER_API_N(destroyBufferObject, backend::BufferObjectHandle, ibh)
DECL_DRIVER_API_N(destroyRenderPrimitive, backend::RenderPrimitiveHandle, rph)
DECL_DRIVER_API_N(destroyProgram, backend::ProgramHandle, ph)
DECL_DRIVER_API_N(destroySamplerGroup, backend::SamplerGroupHandle, sbh)
DECL_DRIVER_API_N(destroyTexture, backend::TextureHandle, th)
DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth)
DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch)
DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh)
DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh)
DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh)
/*
* Synchronous APIs
@@ -384,6 +346,15 @@ DECL_DRIVER_API_N(updateBufferObjectUnsynchronized,
DECL_DRIVER_API_N(resetBufferObject,
backend::BufferObjectHandle, ibh)
DECL_DRIVER_API_N(updateSamplerGroup,
backend::SamplerGroupHandle, ubh,
backend::BufferDescriptor&&, data)
DECL_DRIVER_API_N(setMinMaxLevels,
backend::TextureHandle, th,
uint32_t, minLevel,
uint32_t, maxLevel)
DECL_DRIVER_API_N(update3DImage,
backend::TextureHandle, th,
uint32_t, level,
@@ -398,12 +369,10 @@ DECL_DRIVER_API_N(update3DImage,
DECL_DRIVER_API_N(generateMipmaps,
backend::TextureHandle, th)
// Deprecated
DECL_DRIVER_API_N(setExternalImage,
backend::TextureHandle, th,
void*, image)
// Deprecated
DECL_DRIVER_API_N(setExternalImagePlane,
backend::TextureHandle, th,
void*, image,
@@ -450,6 +419,25 @@ DECL_DRIVER_API_N(commit,
* -----------------------
*/
DECL_DRIVER_API_N(bindUniformBuffer,
uint32_t, index,
backend::BufferObjectHandle, ubh)
DECL_DRIVER_API_N(bindBufferRange,
BufferObjectBinding, bindingType,
uint32_t, index,
backend::BufferObjectHandle, ubh,
uint32_t, offset,
uint32_t, size)
DECL_DRIVER_API_N(unbindBuffer,
BufferObjectBinding, bindingType,
uint32_t, index)
DECL_DRIVER_API_N(bindSamplers,
uint32_t, index,
backend::SamplerGroupHandle, sbh)
DECL_DRIVER_API_N(setPushConstant,
backend::ShaderStage, stage,
uint8_t, index,

View File

@@ -18,17 +18,6 @@
#define TNT_FILAMENT_BACKEND_PRIVATE_DRIVERAPI_H
#include "backend/DriverApiForward.h"
#include "private/backend/CommandStream.h"
#include <stddef.h>
namespace filament::backend {
inline void* allocateFromCommandStream(DriverApi& driver, size_t size, size_t alignment) noexcept {
return driver.allocate(size, alignment);
}
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_PRIVATE_DRIVERAPI_H

View File

@@ -38,7 +38,7 @@
#include <stddef.h>
#include <stdint.h>
#define HandleAllocatorGL HandleAllocator<32, 96, 136> // ~4520 / pool / MiB
#define HandleAllocatorGL HandleAllocator<32, 64, 136> // ~4520 / pool / MiB
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB
#define HandleAllocatorMTL HandleAllocator<32, 64, 552> // ~1660 / pool / MiB

View File

@@ -20,16 +20,11 @@
#include <utils/CallStack.h>
#endif
#include <utils/compiler.h>
#include <utils/Log.h>
#include <utils/ostream.h>
#include <utils/Profiler.h>
#include <utils/Systrace.h>
#include <cstddef>
#include <functional>
#include <string>
#include <utility>
#ifdef __ANDROID__
#include <sys/system_properties.h>
@@ -79,8 +74,8 @@ CommandStream::CommandStream(Driver& driver, CircularBuffer& buffer) noexcept
}
void CommandStream::execute(void* buffer) {
// NOTE: we can't use SYSTRACE_CALL() or similar here because, execute() below, also
// uses systrace BEGIN/END and the END is not guaranteed to be happening in this scope.
SYSTRACE_CALL();
SYSTRACE_CONTEXT();
Profiler profiler;
@@ -105,7 +100,6 @@ void CommandStream::execute(void* buffer) {
// we want to remove all this when tracing is completely disabled
profiler.stop();
UTILS_UNUSED Profiler::Counters const counters = profiler.readCounters();
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("GLThread (I)", counters.getInstructions());
SYSTRACE_VALUE32("GLThread (C)", counters.getCpuCycles());
SYSTRACE_VALUE32("GLThread (CPI x10)", counters.getCPI() * 10);

View File

@@ -101,14 +101,6 @@ struct HwProgram : public HwBase {
HwProgram() noexcept = default;
};
struct HwDescriptorSetLayout : public HwBase {
HwDescriptorSetLayout() noexcept = default;
};
struct HwDescriptorSet : public HwBase {
HwDescriptorSet() noexcept = default;
};
struct HwSamplerGroup : public HwBase {
HwSamplerGroup() noexcept = default;
};

View File

@@ -14,18 +14,7 @@
* limitations under the License.
*/
#include <backend/Program.h>
#include <backend/DriverEnums.h>
#include <utils/debug.h>
#include <utils/CString.h>
#include <utils/ostream.h>
#include <utils/Invocable.h>
#include <utility>
#include <stddef.h>
#include <stdint.h>
#include "backend/Program.h"
namespace filament::backend {
@@ -63,24 +52,41 @@ Program& Program::shaderLanguage(ShaderLanguage shaderLanguage) {
return *this;
}
Program& Program::descriptorBindings(backend::descriptor_set_t set,
DescriptorBindingsInfo descriptorBindings) noexcept {
mDescriptorBindings[set] = std::move(descriptorBindings);
Program& Program::uniformBlockBindings(
FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& uniformBlockBindings) noexcept {
for (auto const& item : uniformBlockBindings) {
assert_invariant(item.second < UNIFORM_BINDING_COUNT);
mUniformBlocks[item.second] = item.first;
}
return *this;
}
Program& Program::uniforms(uint32_t index, utils::CString name, UniformInfo uniforms) noexcept {
mBindingUniformsInfo.reserve(mBindingUniformsInfo.capacity() + 1);
mBindingUniformsInfo.emplace_back(index, std::move(name), std::move(uniforms));
Program& Program::uniforms(uint32_t index, UniformInfo const& uniforms) noexcept {
assert_invariant(index < UNIFORM_BINDING_COUNT);
mBindingUniformInfo[index] = uniforms;
return *this;
}
Program& Program::attributes(AttributesInfo attributes) noexcept {
Program& Program::attributes(
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes) noexcept {
mAttributes = std::move(attributes);
return *this;
}
Program& Program::specializationConstants(SpecializationConstantsInfo specConstants) noexcept {
Program& Program::setSamplerGroup(size_t bindingPoint, ShaderStageFlags stageFlags,
const Program::Sampler* samplers, size_t count) noexcept {
auto& groupData = mSamplerGroups[bindingPoint];
groupData.stageFlags = stageFlags;
auto& samplerList = groupData.samplers;
samplerList.reserve(count);
samplerList.resize(count);
std::copy_n(samplers, count, samplerList.data());
return *this;
}
Program& Program::specializationConstants(
FixedCapacityVector<SpecializationConstant> specConstants) noexcept {
mSpecializationConstants = std::move(specConstants);
return *this;
}

View File

@@ -73,11 +73,10 @@ public:
enum class Type {
NONE = 0,
GENERIC = 1,
RING = 2, // deprecated
RING = 2,
STAGING = 3,
DESCRIPTOR_SET = 4,
};
static constexpr size_t TypeCount = 4;
static constexpr size_t TypeCount = 3;
static constexpr auto toIndex(Type t) {
assert_invariant(t != Type::NONE);
@@ -89,8 +88,6 @@ public:
return 1;
case Type::STAGING:
return 2;
case Type::DESCRIPTOR_SET:
return 3;
}
}
@@ -185,7 +182,7 @@ public:
* is no device allocation.
*
*/
id<MTLBuffer> getGpuBufferForDraw() noexcept;
id<MTLBuffer> getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) noexcept;
void* getCpuBuffer() const noexcept { return mCpuBuffer; }

View File

@@ -40,15 +40,12 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
// If the buffer is less than 4K in size and is updated frequently, we don't use an explicit
// buffer. Instead, we use immediate command encoder methods like setVertexBytes:length:atIndex:.
// This won't work for SSBOs, since they are read/write.
/*
if (size <= 4 * 1024 && bindingType != BufferObjectBinding::SHADER_STORAGE &&
usage == BufferUsage::DYNAMIC && !forceGpuBuffer) {
mBuffer = nil;
mCpuBuffer = malloc(size);
return;
}
*/
// Otherwise, we allocate a private GPU buffer.
{
@@ -97,7 +94,7 @@ void MetalBuffer::copyIntoBufferUnsynchronized(void* src, size_t size, size_t by
copyIntoBuffer(src, size, byteOffset);
}
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw() noexcept {
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) noexcept {
// If there's a CPU buffer, then we return nil here, as the CPU-side buffer will be bound
// separately.
if (mCpuBuffer) {
@@ -140,7 +137,7 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncod
}
// getGpuBufferForDraw() might return nil, which means there isn't a device allocation for
// this buffer. In this case, we'll bind the buffer below with the CPU-side memory.
id<MTLBuffer> gpuBuffer = buffer->getGpuBufferForDraw();
id<MTLBuffer> gpuBuffer = buffer->getGpuBufferForDraw(cmdBuffer);
if (!gpuBuffer) {
continue;
}

View File

@@ -21,8 +21,6 @@
#include "MetalShaderCompiler.h"
#include "MetalState.h"
#include <backend/DriverEnums.h>
#include <CoreVideo/CVMetalTextureCache.h>
#include <Metal/Metal.h>
#include <QuartzCore/QuartzCore.h>
@@ -48,13 +46,13 @@ class MetalBlitter;
class MetalBufferPool;
class MetalBumpAllocator;
class MetalRenderTarget;
class MetalSamplerGroup;
class MetalSwapChain;
class MetalTexture;
class MetalTimerQueryInterface;
struct MetalUniformBuffer;
struct MetalIndexBuffer;
struct MetalVertexBuffer;
struct MetalDescriptorSet;
constexpr static uint8_t MAX_SAMPLE_COUNT = 8; // Metal devices support at most 8 MSAA samples
@@ -70,53 +68,6 @@ private:
bool mDirty = false;
};
class MetalDynamicOffsets {
public:
void setOffsets(uint32_t set, const uint32_t* offsets, uint32_t count) {
assert(set < MAX_DESCRIPTOR_SET_COUNT);
auto getStartIndexForSet = [&](uint32_t s) {
uint32_t startIndex = 0;
for (uint32_t i = 0; i < s; i++) {
startIndex += mCounts[i];
}
return startIndex;
};
const bool resizeNecessary = mCounts[set] != count;
if (UTILS_UNLIKELY(resizeNecessary)) {
int delta = count - mCounts[set];
auto thisSetStart = mOffsets.begin() + getStartIndexForSet(set);
if (delta > 0) {
mOffsets.insert(thisSetStart, delta, 0);
} else {
mOffsets.erase(thisSetStart, thisSetStart - delta);
}
mCounts[set] = count;
}
if (resizeNecessary ||
!std::equal(
offsets, offsets + count, mOffsets.begin() + getStartIndexForSet(set))) {
std::copy(offsets, offsets + count, mOffsets.begin() + getStartIndexForSet(set));
mDirty = true;
}
}
bool isDirty() const { return mDirty; }
void setDirty(bool dirty) { mDirty = dirty; }
std::pair<uint32_t, const uint32_t*> getOffsets() const {
return { mOffsets.size(), mOffsets.data() };
}
private:
std::array<uint32_t, MAX_DESCRIPTOR_SET_COUNT> mCounts = { 0 };
std::vector<uint32_t> mOffsets;
bool mDirty = false;
};
struct MetalContext {
explicit MetalContext(size_t metalFreedTextureListSize)
: texturesToDestroy(metalFreedTextureListSize) {}
@@ -125,12 +76,8 @@ struct MetalContext {
id<MTLDevice> device = nullptr;
id<MTLCommandQueue> commandQueue = nullptr;
// The ID of pendingCommandBuffer (or the next command buffer, if pendingCommandBuffer is nil).
uint64_t pendingCommandBufferId = 1;
// read from driver thread, set from completion handlers
std::atomic<uint64_t> latestCompletedCommandBufferId = 0;
id<MTLCommandBuffer> pendingCommandBuffer = nil;
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nil;
id<MTLCommandBuffer> pendingCommandBuffer = nullptr;
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nullptr;
std::atomic<bool> memorylessLimitsReached = false;
@@ -161,6 +108,8 @@ struct MetalContext {
// State trackers.
PipelineStateTracker pipelineState;
DepthStencilStateTracker depthStencilState;
std::array<BufferState, Program::UNIFORM_BINDING_COUNT> uniformState;
std::array<BufferState, MAX_SSBO_COUNT> ssboState;
CullModeStateTracker cullModeState;
WindingStateTracker windingState;
DepthClampStateTracker depthClampState;
@@ -176,15 +125,13 @@ struct MetalContext {
std::array<MetalPushConstantBuffer, Program::SHADER_TYPE_COUNT> currentPushConstants;
// Keeps track of descriptor sets we've finalized for the current render pass.
tsl::robin_set<MetalDescriptorSet*> finalizedDescriptorSets;
std::array<MetalDescriptorSet*, MAX_DESCRIPTOR_SET_COUNT> currentDescriptorSets = {};
MetalBufferBindings<MAX_DESCRIPTOR_SET_COUNT, ShaderStage::VERTEX> vertexDescriptorBindings;
MetalBufferBindings<MAX_DESCRIPTOR_SET_COUNT, ShaderStage::FRAGMENT> fragmentDescriptorBindings;
MetalBufferBindings<MAX_DESCRIPTOR_SET_COUNT, ShaderStage::COMPUTE> computeDescriptorBindings;
MetalDynamicOffsets dynamicOffsets;
MetalSamplerGroup* samplerBindings[Program::SAMPLER_BINDING_COUNT] = {};
// Keeps track of all alive textures.
// Keeps track of sampler groups we've finalized for the current render pass.
tsl::robin_set<MetalSamplerGroup*> finalizedSamplerGroups;
// Keeps track of all alive sampler groups, textures.
tsl::robin_set<MetalSamplerGroup*> samplerGroups;
tsl::robin_set<MetalTexture*> textures;
// This circular buffer implements delayed destruction for Metal texture handles. It keeps a
@@ -207,7 +154,6 @@ struct MetalContext {
// Empty texture used to prevent GPU errors when a sampler has been bound without a texture.
id<MTLTexture> emptyTexture = nil;
id<MTLBuffer> emptyBuffer = nil;
MetalBlitter* blitter = nullptr;

View File

@@ -101,14 +101,9 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
context->pendingCommandBuffer = [context->commandQueue commandBuffer];
// It's safe for this block to capture the context variable. MetalDriver::terminate will ensure
// all frames and their completion handlers finish before context is deallocated.
uint64_t thisCommandBufferId = context->pendingCommandBufferId;
[context->pendingCommandBuffer addCompletedHandler:^(id <MTLCommandBuffer> buffer) {
context->resourceTracker.clearResources((__bridge void*) buffer);
// Command buffers should complete in order, so latestCompletedCommandBufferId will only
// ever increase.
context->latestCompletedCommandBufferId = thisCommandBufferId;
auto errorCode = (MTLCommandBufferError)buffer.error.code;
if (@available(macOS 11.0, *)) {
if (errorCode == MTLCommandBufferErrorMemoryless) {
@@ -130,7 +125,6 @@ void submitPendingCommands(MetalContext* context) {
assert_invariant(context->pendingCommandBuffer.status != MTLCommandBufferStatusCommitted);
[context->pendingCommandBuffer commit];
context->pendingCommandBuffer = nil;
context->pendingCommandBufferId++;
}
id<MTLTexture> getOrCreateEmptyTexture(MetalContext* context) {
@@ -173,6 +167,7 @@ void MetalPushConstantBuffer::setPushConstant(PushConstantVariant value, uint8_t
void MetalPushConstantBuffer::setBytes(id<MTLCommandEncoder> encoder, ShaderStage stage) {
constexpr size_t PUSH_CONSTANT_SIZE_BYTES = 4;
constexpr size_t PUSH_CONSTANT_BUFFER_INDEX = 26;
static char buffer[MAX_PUSH_CONSTANT_COUNT * PUSH_CONSTANT_SIZE_BYTES];
assert_invariant(mPushConstants.size() <= MAX_PUSH_CONSTANT_COUNT);

View File

@@ -32,7 +32,6 @@
#include <functional>
#include <mutex>
#include <vector>
#include <deque>
namespace filament {
namespace backend {
@@ -62,7 +61,6 @@ public:
private:
friend class MetalSwapChain;
friend struct MetalDescriptorSet;
MetalPlatform& mPlatform;
MetalContext* mContext;
@@ -81,17 +79,6 @@ private:
void executeTickOps() noexcept;
std::vector<std::function<void()>> mTickOps;
// Tasks regularly executed on the driver thread after a command buffer has completed
struct DeferredTask {
DeferredTask(uint64_t commandBufferId, utils::Invocable<void()>&& fn) noexcept
: commandBufferId(commandBufferId), fn(std::move(fn)) {}
uint64_t commandBufferId; // after this command buffer completes
utils::Invocable<void()> fn; // execute this task
};
void executeAfterCurrentCommandBufferCompletes(utils::Invocable<void()>&& fn) noexcept;
void executeDeferredOps() noexcept;
std::deque<DeferredTask> mDeferredTasks;
/*
* Driver interface
*/
@@ -151,6 +138,7 @@ private:
inline void setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph, PrimitiveType pt,
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh);
void finalizeSamplerGroup(MetalSamplerGroup* sg);
void enumerateBoundBuffers(BufferObjectBinding bindingType,
const std::function<void(const BufferState&, MetalBuffer*, uint32_t)>& f);

File diff suppressed because it is too large Load Diff

View File

@@ -32,75 +32,100 @@ struct MetalContext;
* texture.
*/
class MetalExternalImage {
public:
MetalExternalImage() = default;
MetalExternalImage(MetalExternalImage&&);
MetalExternalImage& operator=(MetalExternalImage&&);
~MetalExternalImage() noexcept;
MetalExternalImage(const MetalExternalImage&) = delete;
MetalExternalImage& operator=(const MetalExternalImage&) = delete;
MetalExternalImage(MetalContext& context,
TextureSwizzle r = TextureSwizzle::CHANNEL_0,
TextureSwizzle g = TextureSwizzle::CHANNEL_1,
TextureSwizzle b = TextureSwizzle::CHANNEL_2,
TextureSwizzle a = TextureSwizzle::CHANNEL_3) noexcept;
/**
* While the texture is used for rendering, this MetalExternalImage must be kept alive.
* @return true, if this MetalExternalImage is holding a live external image. Returns false
* until set has been called with a valid CVPixelBuffer. The image can be cleared via
* set(nullptr), and isValid will return false again.
*/
id<MTLTexture> getMtlTexture() const noexcept;
bool isValid() const noexcept {
return mImage != nil || mRgbTexture != nullptr;
}
NSUInteger getWidth() const noexcept;
NSUInteger getHeight() const noexcept;
bool isValid() const noexcept;
/**
* Create an external image with the passed-in CVPixelBuffer.
* Set this external image to the passed-in CVPixelBuffer. Future calls to
* getMetalTextureForDraw will return a texture backed by this CVPixelBuffer. Previous
* CVPixelBuffers and related resources will be released when all GPU work using them has
* finished.
*
* Ownership is taken of the CVPixelBuffer, which will be released when the returned
* MetalExternalImage is destroyed (or, in the case of a YCbCr image, after the conversion has
* completed).
*
* Calling set with a YCbCr image will encode a compute pass to convert the image from
* YCbCr to RGB.
* Calling set with a YCbCr image will encode a compute pass to convert the image from YCbCr to
* RGB.
*/
static MetalExternalImage createFromImage(MetalContext& context, CVPixelBufferRef image);
void set(CVPixelBufferRef image) noexcept;
/**
* Create an external image with a specific plane of the passed-in CVPixelBuffer.
*
* Ownership is taken of the CVPixelBuffer, which will be released when the returned
* MetalExternalImage is destroyed.
* Set this external image to a specific plane of the passed-in CVPixelBuffer. Future calls to
* getMetalTextureForDraw will return a texture backed by a single plane of this CVPixelBuffer.
* Previous CVPixelBuffers and related resources will be released when all GPU work using them
* has finished.
*/
static MetalExternalImage createFromImagePlane(
MetalContext& context, CVPixelBufferRef image, uint32_t plane);
void set(CVPixelBufferRef image, size_t plane) noexcept;
static void assertWritableImage(CVPixelBufferRef image);
/**
* Returns the width of the external image, or 0 if one is not set. For YCbCr images, returns
* the width of the luminance plane.
*/
size_t getWidth() const noexcept { return mWidth; }
/**
* Returns the height of the external image, or 0 if one is not set. For YCbCr images, returns
* the height of the luminance plane.
*/
size_t getHeight() const noexcept { return mHeight; }
/**
* Get a Metal texture used to draw this image and denote that it is used for the current frame.
* For future frames that use this external image, getMetalTextureForDraw must be called again.
*/
id<MTLTexture> getMetalTextureForDraw() const noexcept;
/**
* Free resources. Should be called at least once when no further calls to set will occur.
*/
static void shutdown(MetalContext& context) noexcept;
private:
MetalExternalImage(CVPixelBufferRef image, CVMetalTextureRef texture) noexcept
: mImage(image), mTexture(texture) {}
explicit MetalExternalImage(id<MTLTexture> texture) noexcept : mRgbTexture(texture) {}
static void assertWritableImage(CVPixelBufferRef image);
static id<MTLTexture> createRgbTexture(id<MTLDevice> device, size_t width, size_t height);
static CVMetalTextureRef createTextureFromImage(CVMetalTextureCacheRef textureCache,
CVPixelBufferRef image, MTLPixelFormat format, size_t plane);
static void ensureComputePipelineState(MetalContext& context);
static id<MTLCommandBuffer> encodeColorConversionPass(MetalContext& context,
id<MTLTexture> inYPlane, id<MTLTexture> inCbCrTexture, id<MTLTexture> outTexture);
private:
void unset();
CVMetalTextureRef createTextureFromImage(CVPixelBufferRef image, MTLPixelFormat format,
size_t plane);
id<MTLTexture> createRgbTexture(size_t width, size_t height);
id<MTLTexture> createSwizzledTextureView(id<MTLTexture> texture) const;
id<MTLTexture> createSwizzledTextureView(CVMetalTextureRef texture) const;
void ensureComputePipelineState();
id<MTLCommandBuffer> encodeColorConversionPass(id<MTLTexture> inYPlane, id<MTLTexture>
inCbCrTexture, id<MTLTexture> outTexture);
static constexpr size_t Y_PLANE = 0;
static constexpr size_t CBCR_PLANE = 1;
// TODO: this could probably be a union.
MetalContext& mContext;
// If the external image has a single plane, mImage and mTexture hold references to the image
// and created Metal texture, respectively.
// mTextureView is a view of mTexture with any swizzling applied.
CVPixelBufferRef mImage = nullptr;
CVMetalTextureRef mTexture = nullptr;
id<MTLTexture> mTextureView = nullptr;
size_t mWidth = 0;
size_t mHeight = 0;
// If the external image is in the YCbCr format, this holds the result of the converted RGB
// texture.
id<MTLTexture> mRgbTexture = nil;
struct {
TextureSwizzle r, g, b, a;
} mSwizzle;
};
} // namespace backend

View File

@@ -34,6 +34,10 @@
namespace filament {
namespace backend {
static const auto cvBufferDeleter = [](const void* buffer) {
CVBufferRelease((CVMetalTextureRef) buffer);
};
static const char* kernel = R"(
#include <metal_stdlib>
#include <simd/simd.h>
@@ -67,30 +71,18 @@ ycbcrToRgb(texture2d<half, access::read> inYTexture [[texture(0)]],
}
)";
NSUInteger MetalExternalImage::getWidth() const noexcept {
if (mImage) {
return CVPixelBufferGetWidth(mImage);
}
if (mRgbTexture) {
return mRgbTexture.width;
}
return 0;
MetalExternalImage::MetalExternalImage(MetalContext& context, TextureSwizzle r, TextureSwizzle g,
TextureSwizzle b, TextureSwizzle a) noexcept : mContext(context), mSwizzle{r, g, b, a} { }
bool MetalExternalImage::isValid() const noexcept {
return mRgbTexture != nil || mImage != nullptr;
}
NSUInteger MetalExternalImage::getHeight() const noexcept {
if (mImage) {
return CVPixelBufferGetHeight(mImage);
}
if (mRgbTexture) {
return mRgbTexture.height;
}
return 0;
}
void MetalExternalImage::set(CVPixelBufferRef image) noexcept {
unset();
MetalExternalImage MetalExternalImage::createFromImage(
MetalContext& context, CVPixelBufferRef image) {
if (!image) {
return {};
return;
}
OSType formatType = CVPixelBufferGetPixelFormatType(image);
@@ -104,29 +96,30 @@ MetalExternalImage MetalExternalImage::createFromImage(
<< ".";
if (planeCount == 0) {
CVMetalTextureRef texture =
createTextureFromImage(context.textureCache, image, MTLPixelFormatBGRA8Unorm, 0);
return { CVPixelBufferRetain(image), texture };
mImage = image;
mTexture = createTextureFromImage(image, MTLPixelFormatBGRA8Unorm, 0);
mTextureView = createSwizzledTextureView(mTexture);
mWidth = CVPixelBufferGetWidth(image);
mHeight = CVPixelBufferGetHeight(image);
}
if (planeCount == 2) {
CVPixelBufferRetain(image);
CVMetalTextureRef yPlane =
createTextureFromImage(context.textureCache, image, MTLPixelFormatR8Unorm, Y_PLANE);
CVMetalTextureRef cbcrPlane =
createTextureFromImage(context.textureCache, image, MTLPixelFormatRG8Unorm, CBCR_PLANE);
CVMetalTextureRef yPlane = createTextureFromImage(image, MTLPixelFormatR8Unorm, Y_PLANE);
CVMetalTextureRef cbcrPlane = createTextureFromImage(image, MTLPixelFormatRG8Unorm,
CBCR_PLANE);
// Get the size of luminance plane.
NSUInteger width = CVPixelBufferGetWidthOfPlane(image, Y_PLANE);
NSUInteger height = CVPixelBufferGetHeightOfPlane(image, Y_PLANE);
mWidth = CVPixelBufferGetWidthOfPlane(image, Y_PLANE);
mHeight = CVPixelBufferGetHeightOfPlane(image, Y_PLANE);
id<MTLTexture> rgbTexture = createRgbTexture(context.device, width, height);
id<MTLCommandBuffer> commandBuffer = encodeColorConversionPass(context,
id<MTLTexture> rgbTexture = createRgbTexture(mWidth, mHeight);
id<MTLCommandBuffer> commandBuffer = encodeColorConversionPass(
CVMetalTextureGetTexture(yPlane),
CVMetalTextureGetTexture(cbcrPlane),
rgbTexture);
mRgbTexture = createSwizzledTextureView(rgbTexture);
[commandBuffer addCompletedHandler:^(id <MTLCommandBuffer> o) {
CVBufferRelease(yPlane);
CVBufferRelease(cbcrPlane);
@@ -134,83 +127,70 @@ MetalExternalImage MetalExternalImage::createFromImage(
}];
[commandBuffer commit];
return MetalExternalImage { rgbTexture };
}
return {};
}
MetalExternalImage MetalExternalImage::createFromImagePlane(
MetalContext& context, CVPixelBufferRef image, uint32_t plane) {
void MetalExternalImage::set(CVPixelBufferRef image, size_t plane) noexcept {
unset();
if (!image) {
return {};
return;
}
const OSType formatType = CVPixelBufferGetPixelFormatType(image);
FILAMENT_CHECK_POSTCONDITION(formatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
<< "Metal planar external images must be in the 420f format.";
FILAMENT_CHECK_POSTCONDITION(plane == 0 || plane == 1)
<< "Metal planar external images must be created from planes 0 or 1.";
auto getPlaneFormat = [](size_t plane) {
// Right now Metal only supports kCVPixelFormatType_420YpCbCr8BiPlanarFullRange planar
// external images, so we can make the following assumptions about the format of each plane.
if (plane == 0) {
return MTLPixelFormatR8Unorm; // luminance
}
if (plane == 1) {
return MTLPixelFormatRG8Unorm; // CbCr
}
return MTLPixelFormatInvalid;
mImage = image;
auto getPlaneFormat = [] (size_t plane) {
// Right now Metal only supports kCVPixelFormatType_420YpCbCr8BiPlanarFullRange planar
// external images, so we can make the following assumptions about the format of each plane.
if (plane == 0) {
return MTLPixelFormatR8Unorm; // luminance
}
if (plane == 1) {
// CbCr
return MTLPixelFormatRG8Unorm; // CbCr
}
return MTLPixelFormatInvalid;
};
const MTLPixelFormat format = getPlaneFormat(plane);
assert_invariant(format != MTLPixelFormatInvalid);
CVMetalTextureRef mTexture = createTextureFromImage(context.textureCache, image, format, plane);
return { CVPixelBufferRetain(image), mTexture };
mTexture = createTextureFromImage(image, format, plane);
mTextureView = createSwizzledTextureView(mTexture);
}
MetalExternalImage::MetalExternalImage(MetalExternalImage&& rhs) {
std::swap(mImage, rhs.mImage);
std::swap(mTexture, rhs.mTexture);
std::swap(mRgbTexture, rhs.mRgbTexture);
}
MetalExternalImage& MetalExternalImage::operator=(MetalExternalImage&& rhs) {
CVPixelBufferRelease(mImage);
CVBufferRelease(mTexture);
mImage = nullptr;
mTexture = nullptr;
mRgbTexture = nullptr;
std::swap(mImage, rhs.mImage);
std::swap(mTexture, rhs.mTexture);
std::swap(mRgbTexture, rhs.mRgbTexture);
return *this;
}
MetalExternalImage::~MetalExternalImage() noexcept {
CVPixelBufferRelease(mImage);
CVBufferRelease(mTexture);
}
id<MTLTexture> MetalExternalImage::getMtlTexture() const noexcept {
id<MTLTexture> MetalExternalImage::getMetalTextureForDraw() const noexcept {
if (mRgbTexture) {
return mRgbTexture;
}
if (mTexture) {
return CVMetalTextureGetTexture(mTexture);
// Retain the image and Metal texture until the GPU has finished with this frame. This does
// not need to be done for the RGB texture, because it is an Objective-C object whose
// lifetime is automatically managed by Metal.
auto& tracker = mContext.resourceTracker;
auto commandBuffer = getPendingCommandBuffer(&mContext);
if (tracker.trackResource((__bridge void*) commandBuffer, mImage, cvBufferDeleter)) {
CVPixelBufferRetain(mImage);
}
return nil;
if (tracker.trackResource((__bridge void*) commandBuffer, mTexture, cvBufferDeleter)) {
CVBufferRetain(mTexture);
}
assert_invariant(mTextureView);
return mTextureView;
}
CVMetalTextureRef MetalExternalImage::createTextureFromImage(CVMetalTextureCacheRef textureCache,
CVPixelBufferRef image, MTLPixelFormat format, size_t plane) {
CVMetalTextureRef MetalExternalImage::createTextureFromImage(CVPixelBufferRef image,
MTLPixelFormat format, size_t plane) {
const size_t width = CVPixelBufferGetWidthOfPlane(image, plane);
const size_t height = CVPixelBufferGetHeightOfPlane(image, plane);
CVMetalTextureRef texture;
CVReturn result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache,
image, nullptr, format, width, height, plane, &texture);
CVReturn result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
mContext.textureCache, image, nullptr, format, width, height, plane, &texture);
FILAMENT_CHECK_POSTCONDITION(result == kCVReturnSuccess)
<< "Could not create a CVMetalTexture from CVPixelBuffer.";
@@ -221,19 +201,58 @@ void MetalExternalImage::shutdown(MetalContext& context) noexcept {
context.externalImageComputePipelineState = nil;
}
id<MTLTexture> MetalExternalImage::createRgbTexture(
id<MTLDevice> device, size_t width, size_t height) {
void MetalExternalImage::assertWritableImage(CVPixelBufferRef image) {
OSType formatType = CVPixelBufferGetPixelFormatType(image);
FILAMENT_CHECK_PRECONDITION(formatType == kCVPixelFormatType_32BGRA)
<< "Metal SwapChain images must be in the 32BGRA format.";
}
void MetalExternalImage::unset() {
CVPixelBufferRelease(mImage);
CVBufferRelease(mTexture);
mImage = nullptr;
mTexture = nullptr;
mTextureView = nil;
mRgbTexture = nil;
mWidth = 0;
mHeight = 0;
}
id<MTLTexture> MetalExternalImage::createRgbTexture(size_t width, size_t height) {
MTLTextureDescriptor *descriptor =
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
width:width
height:height
mipmapped:NO];
descriptor.usage = MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead;
return [device newTextureWithDescriptor:descriptor];
return [mContext.device newTextureWithDescriptor:descriptor];
}
void MetalExternalImage::ensureComputePipelineState(MetalContext& context) {
if (context.externalImageComputePipelineState != nil) {
id<MTLTexture> MetalExternalImage::createSwizzledTextureView(id<MTLTexture> texture) const {
const bool isDefaultSwizzle =
mSwizzle.r == TextureSwizzle::CHANNEL_0 &&
mSwizzle.g == TextureSwizzle::CHANNEL_1 &&
mSwizzle.b == TextureSwizzle::CHANNEL_2 &&
mSwizzle.a == TextureSwizzle::CHANNEL_3;
if (!isDefaultSwizzle && mContext.supportsTextureSwizzling) {
// Even though we've already checked supportsTextureSwizzling, we still need to guard these
// calls with @availability, otherwise the API usage will generate compiler warnings.
if (@available(iOS 13, *)) {
texture = createTextureViewWithSwizzle(texture,
getSwizzleChannels(mSwizzle.r, mSwizzle.g, mSwizzle.b, mSwizzle.a));
}
}
return texture;
}
id<MTLTexture> MetalExternalImage::createSwizzledTextureView(CVMetalTextureRef ref) const {
id<MTLTexture> texture = CVMetalTextureGetTexture(ref);
return createSwizzledTextureView(texture);
}
void MetalExternalImage::ensureComputePipelineState() {
if (mContext.externalImageComputePipelineState != nil) {
return;
}
@@ -241,28 +260,29 @@ void MetalExternalImage::ensureComputePipelineState(MetalContext& context) {
NSString* objcSource = [NSString stringWithCString:kernel
encoding:NSUTF8StringEncoding];
id<MTLLibrary> library = [context.device newLibraryWithSource:objcSource
options:nil
error:&error];
id<MTLLibrary> library = [mContext.device newLibraryWithSource:objcSource
options:nil
error:&error];
NSERROR_CHECK("Unable to compile Metal shading library.");
id<MTLFunction> kernelFunction = [library newFunctionWithName:@"ycbcrToRgb"];
context.externalImageComputePipelineState =
[context.device newComputePipelineStateWithFunction:kernelFunction error:&error];
mContext.externalImageComputePipelineState =
[mContext.device newComputePipelineStateWithFunction:kernelFunction
error:&error];
NSERROR_CHECK("Unable to create Metal compute pipeline state.");
}
id<MTLCommandBuffer> MetalExternalImage::encodeColorConversionPass(MetalContext& context,
id<MTLTexture> inYPlane, id<MTLTexture> inCbCrTexture, id<MTLTexture> outTexture) {
ensureComputePipelineState(context);
id<MTLCommandBuffer> MetalExternalImage::encodeColorConversionPass(id<MTLTexture> inYPlane,
id<MTLTexture> inCbCrTexture, id<MTLTexture> outTexture) {
ensureComputePipelineState();
id<MTLCommandBuffer> commandBuffer = [context.commandQueue commandBuffer];
id<MTLCommandBuffer> commandBuffer = [mContext.commandQueue commandBuffer];
commandBuffer.label = @"YCbCr to RGB conversion";
id<MTLComputeCommandEncoder> computeEncoder = [commandBuffer computeCommandEncoder];
[computeEncoder setComputePipelineState:context.externalImageComputePipelineState];
[computeEncoder setComputePipelineState:mContext.externalImageComputePipelineState];
[computeEncoder setTexture:inYPlane atIndex:0];
[computeEncoder setTexture:inCbCrTexture atIndex:1];
[computeEncoder setTexture:outTexture atIndex:2];
@@ -280,11 +300,5 @@ id<MTLCommandBuffer> MetalExternalImage::encodeColorConversionPass(MetalContext&
return commandBuffer;
}
void MetalExternalImage::assertWritableImage(CVPixelBufferRef image) {
OSType formatType = CVPixelBufferGetPixelFormatType(image);
FILAMENT_CHECK_PRECONDITION(formatType == kCVPixelFormatType_32BGRA)
<< "Metal SwapChain images must be in the 32BGRA format.";
}
} // namespace backend
} // namespace filament

View File

@@ -44,7 +44,6 @@
#include <condition_variable>
#include <memory>
#include <type_traits>
#include <vector>
namespace filament {
namespace backend {
@@ -85,8 +84,6 @@ public:
NSUInteger getSurfaceWidth() const;
NSUInteger getSurfaceHeight() const;
bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; }
private:
enum class SwapChainType {
@@ -96,6 +93,7 @@ private:
};
bool isCaMetalLayer() const { return type == SwapChainType::CAMETALLAYER; }
bool isHeadless() const { return type == SwapChainType::HEADLESS; }
bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; }
void scheduleFrameScheduledCallback();
void scheduleFrameCompletedCallback();
@@ -140,6 +138,12 @@ public:
void updateBufferUnsynchronized(void* data, size_t size, uint32_t byteOffset);
MetalBuffer* getBuffer() { return &buffer; }
// Tracks which uniform/ssbo buffers this buffer object is bound into.
static_assert(Program::UNIFORM_BINDING_COUNT <= 32);
static_assert(MAX_SSBO_COUNT <= 32);
utils::bitset32 boundUniformBuffers;
utils::bitset32 boundSsbos;
private:
MetalBuffer buffer;
};
@@ -196,10 +200,12 @@ public:
MetalProgram(MetalContext& context, Program&& program) noexcept;
const MetalShaderCompiler::MetalFunctionBundle& getFunctions();
const Program::SamplerGroupInfo& getSamplerGroupInfo() { return samplerGroupInfo; }
private:
void initialize();
Program::SamplerGroupInfo samplerGroupInfo;
MetalContext& mContext;
MetalShaderCompiler::MetalFunctionBundle mFunctionBundle;
MetalShaderCompiler::program_token_t mToken;
@@ -221,42 +227,43 @@ struct PixelBufferShape {
class MetalTexture : public HwTexture {
public:
MetalTexture(MetalContext& context, 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
MetalTexture(MetalContext& context, MetalTexture const* src, uint8_t baseLevel,
uint8_t levelCount) noexcept;
MetalTexture(MetalContext& context, MetalTexture const* src, TextureSwizzle r, TextureSwizzle g,
TextureSwizzle b, TextureSwizzle a) noexcept;
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage,
TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a)
noexcept;
// Constructor for importing an id<MTLTexture> outside of Filament.
MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format,
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage,
id<MTLTexture> texture) noexcept;
// Constructors for importing external images.
MetalTexture(MetalContext& context, TextureFormat format, uint32_t width, uint32_t height,
TextureUsage usage, CVPixelBufferRef image) noexcept;
MetalTexture(MetalContext& context, TextureFormat format, uint32_t width, uint32_t height,
TextureUsage usage, CVPixelBufferRef image, uint32_t plane) noexcept;
~MetalTexture();
// Returns an id<MTLTexture> suitable for reading in a shader, taking into account swizzle.
id<MTLTexture> getMtlTextureForRead() const noexcept;
// Returns an id<MTLTexture> suitable for reading in a shader, taking into account swizzle and
// LOD clamping.
id<MTLTexture> getMtlTextureForRead() noexcept;
// Returns the id<MTLTexture> for attaching to a render pass.
id<MTLTexture> getMtlTextureForWrite() const noexcept {
id<MTLTexture> getMtlTextureForWrite() noexcept {
return texture;
}
std::shared_ptr<MetalExternalImage> getExternalImage() const noexcept { return externalImage; }
void loadImage(uint32_t level, MTLRegion region, PixelBufferDescriptor& p) noexcept;
void generateMipmaps() noexcept;
// A texture starts out with none of its mip levels (also referred to as LODs) available for
// reading. 4 actions update the range of LODs available:
// - calling loadImage
// - calling generateMipmaps
// - using the texture as a render target attachment
// - calling setMinMaxLevels
// A texture's available mips are consistent throughout a render pass.
void setLodRange(uint16_t minLevel, uint16_t maxLevel);
void extendLodRangeTo(uint16_t level);
static MTLPixelFormat decidePixelFormat(MetalContext* context, TextureFormat format);
MetalContext& context;
MetalExternalImage externalImage;
// A "sidecar" texture used to implement automatic MSAA resolve.
// This is created by MetalRenderTarget and stored here so it can be used with multiple
@@ -295,16 +302,97 @@ private:
id<MTLTexture> texture = nil;
std::shared_ptr<MetalExternalImage> externalImage;
// If non-nil, a swizzled texture view to use instead of "texture".
// Filament swizzling only affects texture reads, so this should not be used when the texture is
// bound as a render target attachment.
id<MTLTexture> swizzledTextureView = nil;
id<MTLTexture> lodTextureView = nil;
uint16_t minLod = std::numeric_limits<uint16_t>::max();
uint16_t maxLod = 0;
bool terminated = false;
};
class MetalSamplerGroup : public HwSamplerGroup {
public:
explicit MetalSamplerGroup(size_t size, utils::FixedSizeString<32> name) noexcept
: size(size),
debugName(name),
textureHandles(size, Handle<HwTexture>()),
textures(size, nil),
samplers(size, nil) {}
inline void setTextureHandle(size_t index, Handle<HwTexture> th) {
assert_invariant(!finalized);
textureHandles[index] = th;
}
// This method is only used for debugging, to ensure all texture handles are alive.
const auto& getTextureHandles() const {
return textureHandles;
}
// Encode a MTLTexture into this SamplerGroup at the given index.
inline void setFinalizedTexture(size_t index, id<MTLTexture> t) {
assert_invariant(!finalized);
textures[index] = t;
}
// Encode a MTLSamplerState into this SamplerGroup at the given index.
inline void setFinalizedSampler(size_t index, id<MTLSamplerState> s) {
assert_invariant(!finalized);
samplers[index] = s;
}
// A SamplerGroup is "finalized" when all of its textures have been set and is ready for use in
// a draw call.
// Once a SamplerGroup is finalized, it must be reset or mutated to be written into again.
void finalize();
bool isFinalized() const noexcept { return finalized; }
// Both of these methods "unfinalize" a SamplerGroup, allowing it to be updated via calls to
// setFinalizedTexture or setFinalizedSampler. The difference is that when reset is called, all
// the samplers/textures must be rebound. The MTLArgumentEncoder must be specified, in case
// the texture types have changed.
// Mutate re-encodes the current set of samplers/textures into the new argument
// buffer.
void reset(id<MTLCommandBuffer> cmdBuffer, id<MTLArgumentEncoder> e, id<MTLDevice> device);
void mutate(id<MTLCommandBuffer> cmdBuffer);
id<MTLBuffer> getArgumentBuffer() const {
assert_invariant(finalized);
return argBuffer->getCurrentAllocation().first;
}
NSUInteger getArgumentBufferOffset() const {
return argBuffer->getCurrentAllocation().second;
}
inline std::pair<Handle<HwTexture>, id<MTLTexture>> getFinalizedTexture(size_t index) {
return {textureHandles[index], textures[index]};
}
// Calls the Metal useResource:usage:stages: method for all the textures in this SamplerGroup.
void useResources(id<MTLRenderCommandEncoder> renderPassEncoder);
size_t size;
utils::FixedSizeString<32> debugName;
public:
// These vectors are kept in sync with one another.
utils::FixedCapacityVector<Handle<HwTexture>> textureHandles;
utils::FixedCapacityVector<id<MTLTexture>> textures;
utils::FixedCapacityVector<id<MTLSamplerState>> samplers;
id<MTLArgumentEncoder> encoder;
std::unique_ptr<MetalRingBuffer> argBuffer = nullptr;
bool finalized = false;
};
class MetalRenderTarget : public HwRenderTarget {
public:
@@ -459,61 +547,6 @@ struct MetalTimerQuery : public HwTimerQuery {
std::shared_ptr<Status> status;
};
class MetalDescriptorSetLayout : public HwDescriptorSetLayout {
public:
MetalDescriptorSetLayout(DescriptorSetLayout&& layout) noexcept;
const auto& getBindings() const noexcept { return mLayout.bindings; }
size_t getDynamicOffsetCount() const noexcept { return mDynamicOffsetCount; }
/**
* Get an argument encoder for this descriptor set and shader stage.
* textureTypes should only include the textures present in the corresponding shader stage.
*/
id<MTLArgumentEncoder> getArgumentEncoder(id<MTLDevice> device, ShaderStage stage,
utils::FixedCapacityVector<MTLTextureType> const& textureTypes);
private:
id<MTLArgumentEncoder> getArgumentEncoderSlow(id<MTLDevice> device, ShaderStage stage,
utils::FixedCapacityVector<MTLTextureType> const& textureTypes);
DescriptorSetLayout mLayout;
size_t mDynamicOffsetCount = 0;
std::array<id<MTLArgumentEncoder>, Program::SHADER_TYPE_COUNT> mCachedArgumentEncoder = { nil };
std::array<utils::FixedCapacityVector<MTLTextureType>, Program::SHADER_TYPE_COUNT>
mCachedTextureTypes;
};
struct MetalDescriptorSet : public HwDescriptorSet {
MetalDescriptorSet(MetalDescriptorSetLayout* layout) noexcept;
void finalize(MetalDriver* driver);
id<MTLBuffer> finalizeAndGetBuffer(MetalDriver* driver, ShaderStage stage);
MetalDescriptorSetLayout* layout;
struct BufferBinding {
id<MTLBuffer> buffer;
uint32_t offset;
uint32_t size;
};
struct TextureBinding {
id<MTLTexture> texture;
SamplerParams sampler;
};
tsl::robin_map<descriptor_binding_t, BufferBinding> buffers;
tsl::robin_map<descriptor_binding_t, TextureBinding> textures;
std::vector<id<MTLResource>> vertexResources;
std::vector<id<MTLResource>> fragmentResources;
std::vector<std::shared_ptr<MetalExternalImage>> externalImages;
std::array<TrackedMetalBuffer, Program::SHADER_TYPE_COUNT> cachedBuffer = { nil };
};
} // namespace backend
} // namespace filament

View File

@@ -74,6 +74,7 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, CAMetalLayer* nativeWindow
depthStencilFormat(decideDepthStencilFormat(flags)),
layer(nativeWindow),
layerDrawableMutex(std::make_shared<std::mutex>()),
externalImage(context),
type(SwapChainType::CAMETALLAYER) {
if (!(flags & SwapChain::CONFIG_TRANSPARENT) && !nativeWindow.opaque) {
@@ -99,15 +100,17 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, int32_t width, int32_t hei
depthStencilFormat(decideDepthStencilFormat(flags)),
headlessWidth(width),
headlessHeight(height),
externalImage(context),
type(SwapChainType::HEADLESS) {}
MetalSwapChain::MetalSwapChain(MetalContext& context, CVPixelBufferRef pixelBuffer, uint64_t flags)
: context(context),
depthStencilFormat(decideDepthStencilFormat(flags)),
externalImage(MetalExternalImage::createFromImage(context, pixelBuffer)),
externalImage(context),
type(SwapChainType::CVPIXELBUFFERREF) {
assert_invariant(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
MetalExternalImage::assertWritableImage(pixelBuffer);
externalImage.set(pixelBuffer);
assert_invariant(externalImage.isValid());
}
@@ -118,6 +121,7 @@ MTLPixelFormat MetalSwapChain::decideDepthStencilFormat(uint64_t flags) {
}
MetalSwapChain::~MetalSwapChain() {
externalImage.set(nullptr);
}
NSUInteger MetalSwapChain::getSurfaceWidth() const {
@@ -167,7 +171,7 @@ id<MTLTexture> MetalSwapChain::acquireDrawable() {
}
if (isPixelBuffer()) {
return externalImage.getMtlTexture();
return externalImage.getMetalTextureForDraw();
}
assert_invariant(isCaMetalLayer());
@@ -470,6 +474,11 @@ void MetalRenderPrimitive::setBuffers(MetalVertexBufferInfo const* const vbi,
MetalProgram::MetalProgram(MetalContext& context, Program&& program) noexcept
: HwProgram(program.getName()), mContext(context) {
// Save this program's SamplerGroupInfo, it's used during draw calls to bind sampler groups to
// the appropriate stage(s).
samplerGroupInfo = program.getSamplerGroupInfo();
mToken = context.shaderCompiler->createProgram(program.getName(), std::move(program));
assert_invariant(mToken);
}
@@ -489,9 +498,10 @@ void MetalProgram::initialize() {
MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
TextureUsage usage) noexcept
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context) {
assert_invariant(target != SamplerType::SAMPLER_EXTERNAL);
TextureUsage usage, TextureSwizzle r, TextureSwizzle g, TextureSwizzle b,
TextureSwizzle a) noexcept
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context),
externalImage(context, r, g, b, a) {
devicePixelFormat = decidePixelFormat(&context, format);
FILAMENT_CHECK_POSTCONDITION(devicePixelFormat != MTLPixelFormatInvalid)
@@ -577,28 +587,16 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
<< ", levels = " << int(levels) << ", MTLPixelFormat = " << int(devicePixelFormat)
<< ", width = " << width << ", height = " << height << ", depth = " << depth
<< "). Out of memory?";
}
MetalTexture::MetalTexture(MetalContext& context, MetalTexture const* src, uint8_t baseLevel,
uint8_t levelCount) noexcept
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
context(context),
devicePixelFormat(src->devicePixelFormat),
externalImage(src->externalImage) {
texture = createTextureViewWithLodRange(
src->getMtlTextureForRead(), baseLevel, baseLevel + levelCount - 1);
}
MetalTexture::MetalTexture(MetalContext& context, MetalTexture const* src, TextureSwizzle r,
TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) noexcept
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
context(context),
devicePixelFormat(src->devicePixelFormat),
externalImage(src->externalImage) {
texture = src->getMtlTextureForRead();
if (context.supportsTextureSwizzling) {
// If swizzling is set, set up a swizzled texture view that we'll use when sampling this texture.
const bool isDefaultSwizzle =
r == TextureSwizzle::CHANNEL_0 &&
g == TextureSwizzle::CHANNEL_1 &&
b == TextureSwizzle::CHANNEL_2 &&
a == TextureSwizzle::CHANNEL_3;
// If texture is nil, then it must be a SAMPLER_EXTERNAL texture.
// Swizzling for external textures is handled inside MetalExternalImage.
if (!isDefaultSwizzle && texture && context.supportsTextureSwizzling) {
// Even though we've already checked context.supportsTextureSwizzling, we still need to
// guard these calls with @availability, otherwise the API usage will generate compiler
// warnings.
@@ -612,38 +610,44 @@ MetalTexture::MetalTexture(MetalContext& context, MetalTexture const* src, Textu
MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format,
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage,
id<MTLTexture> metalTexture) noexcept
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context) {
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context),
externalImage(context) {
texture = metalTexture;
}
MetalTexture::MetalTexture(MetalContext& context, TextureFormat format, uint32_t width,
uint32_t height, TextureUsage usage, CVPixelBufferRef image) noexcept
: HwTexture(SamplerType::SAMPLER_EXTERNAL, 1, 1, width, height, 1, format, usage),
context(context),
externalImage(std::make_shared<MetalExternalImage>(
MetalExternalImage::createFromImage(context, image))) {
texture = externalImage->getMtlTexture();
}
MetalTexture::MetalTexture(MetalContext& context, TextureFormat format, uint32_t width,
uint32_t height, TextureUsage usage, CVPixelBufferRef image, uint32_t plane) noexcept
: HwTexture(SamplerType::SAMPLER_EXTERNAL, 1, 1, width, height, 1, format, usage),
context(context),
externalImage(std::make_shared<MetalExternalImage>(
MetalExternalImage::createFromImagePlane(context, image, plane))) {
texture = externalImage->getMtlTexture();
setLodRange(0, levels - 1);
}
void MetalTexture::terminate() noexcept {
texture = nil;
swizzledTextureView = nil;
lodTextureView = nil;
msaaSidecar = nil;
externalImage = nullptr;
externalImage.set(nullptr);
terminated = true;
}
id<MTLTexture> MetalTexture::getMtlTextureForRead() const noexcept {
return swizzledTextureView ? swizzledTextureView : texture;
MetalTexture::~MetalTexture() {
externalImage.set(nullptr);
}
id<MTLTexture> MetalTexture::getMtlTextureForRead() noexcept {
if (lodTextureView) {
return lodTextureView;
}
// The texture's swizzle remains constant throughout its lifetime, however its LOD range can
// change. We'll cache the LOD view, and set lodTextureView to nil if minLod or maxLod is
// updated.
id<MTLTexture> t = swizzledTextureView ? swizzledTextureView : texture;
if (!t) {
return nil;
}
if (UTILS_UNLIKELY(minLod > maxLod)) {
// If the texture does not have any available LODs, provide a view of only level 0.
// Filament should prevent this from ever occurring.
lodTextureView = createTextureViewWithLodRange(t, 0, 0);
return lodTextureView;
}
lodTextureView = createTextureViewWithLodRange(t, minLod, maxLod);
return lodTextureView;
}
MTLPixelFormat MetalTexture::decidePixelFormat(MetalContext* context, TextureFormat format) {
@@ -762,12 +766,15 @@ void MetalTexture::loadImage(uint32_t level, MTLRegion region, PixelBufferDescri
assert_invariant(false);
}
}
extendLodRangeTo(level);
}
void MetalTexture::generateMipmaps() noexcept {
id <MTLBlitCommandEncoder> blitEncoder = [getPendingCommandBuffer(&context) blitCommandEncoder];
[blitEncoder generateMipmapsForTexture:texture];
[blitEncoder endEncoding];
setLodRange(0, texture.mipmapLevelCount - 1);
}
void MetalTexture::loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice,
@@ -891,6 +898,98 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
context.blitter->blit(getPendingCommandBuffer(&context), args, "Texture upload blit");
}
void MetalTexture::extendLodRangeTo(uint16_t level) {
assert_invariant(!isInRenderPass(&context));
minLod = std::min(minLod, level);
maxLod = std::max(maxLod, level);
lodTextureView = nil;
}
void MetalTexture::setLodRange(uint16_t min, uint16_t max) {
assert_invariant(!isInRenderPass(&context));
assert_invariant(min <= max);
minLod = min;
maxLod = max;
lodTextureView = nil;
}
void MetalSamplerGroup::finalize() {
assert_invariant(encoder);
// TODO: we should be able to encode textures and samplers inside setFinalizedTexture and
// setFinalizedSampler as they become available, but Metal doesn't seem to like this; the arg
// buffer gets encoded incorrectly. This warrants more investigation.
auto [buffer, offset] = argBuffer->getCurrentAllocation();
[encoder setArgumentBuffer:buffer offset:offset];
// Encode all textures and samplers.
for (size_t s = 0; s < size; s++) {
[encoder setTexture:textures[s] atIndex:(s * 2 + 0)];
[encoder setSamplerState:samplers[s] atIndex:(s * 2 + 1)];
}
finalized = true;
}
void MetalSamplerGroup::reset(id<MTLCommandBuffer> cmdBuffer, id<MTLArgumentEncoder> e,
id<MTLDevice> device) {
encoder = e;
// The number of slots in the ring buffer we use to manage argument buffer allocations.
// This number was chosen to avoid running out of slots and having to allocate a "fallback"
// buffer when SamplerGroups are updated multiple times a frame. This value can reduced after
// auditing Filament's calls to updateSamplerGroup, which should be as few times as possible.
// For example, the bloom downsample pass should be refactored to maintain two separate
// MaterialInstances instead of "ping ponging" between two texture bindings, which causes a
// single SamplerGroup to be updated many times a frame.
static constexpr auto METAL_ARGUMENT_BUFFER_SLOTS = 32;
MTLSizeAndAlign argBufferLayout;
argBufferLayout.size = encoder.encodedLength;
argBufferLayout.align = encoder.alignment;
// Chances are, even though the MTLArgumentEncoder might change, the required size and alignment
// probably won't. So we can re-use the previous ring buffer.
if (UTILS_UNLIKELY(!argBuffer || !argBuffer->canAccomodateLayout(argBufferLayout))) {
argBuffer = std::make_unique<MetalRingBuffer>(device, MTLResourceStorageModeShared,
argBufferLayout, METAL_ARGUMENT_BUFFER_SLOTS);
} else {
argBuffer->createNewAllocation(cmdBuffer);
}
// Clear all textures and samplers.
assert_invariant(textureHandles.size() == textures.size());
assert_invariant(textures.size() == samplers.size());
for (size_t s = 0; s < textureHandles.size(); s++) {
textureHandles[s] = {};
textures[s] = nil;
samplers[s] = nil;
}
finalized = false;
}
void MetalSamplerGroup::mutate(id<MTLCommandBuffer> cmdBuffer) {
assert_invariant(finalized); // only makes sense to mutate if this sampler group is finalized
assert_invariant(argBuffer);
argBuffer->createNewAllocation(cmdBuffer);
finalized = false;
}
void MetalSamplerGroup::useResources(id<MTLRenderCommandEncoder> renderPassEncoder) {
assert_invariant(finalized);
if (@available(iOS 13, *)) {
// TODO: pass only the appropriate stages to useResources.
[renderPassEncoder useResources:textures.data()
count:textures.size()
usage:MTLResourceUsageRead | MTLResourceUsageSample
stages:MTLRenderStageFragment | MTLRenderStageVertex];
} else {
[renderPassEncoder useResources:textures.data()
count:textures.size()
usage:MTLResourceUsageRead | MTLResourceUsageSample];
}
}
MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height,
uint8_t samples, Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
Attachment depthAttachment, Attachment stencilAttachment) :
@@ -1236,193 +1335,5 @@ FenceStatus MetalFence::wait(uint64_t timeoutNs) {
return FenceStatus::ERROR;
}
MetalDescriptorSetLayout::MetalDescriptorSetLayout(DescriptorSetLayout&& l) noexcept
: mLayout(std::move(l)) {
size_t dynamicBindings = 0;
for (const auto& binding : mLayout.bindings) {
if (any(binding.flags & DescriptorFlags::DYNAMIC_OFFSET)) {
dynamicBindings++;
}
}
mDynamicOffsetCount = dynamicBindings;
}
id<MTLArgumentEncoder> MetalDescriptorSetLayout::getArgumentEncoder(id<MTLDevice> device, ShaderStage stage,
utils::FixedCapacityVector<MTLTextureType> const& textureTypes) {
auto const index = static_cast<size_t>(stage);
assert_invariant(index < mCachedArgumentEncoder.size());
if (mCachedArgumentEncoder[index] &&
std::equal(
textureTypes.begin(), textureTypes.end(), mCachedTextureTypes[index].begin())) {
return mCachedArgumentEncoder[index];
}
mCachedArgumentEncoder[index] = getArgumentEncoderSlow(device, stage, textureTypes);
mCachedTextureTypes[index] = textureTypes;
return mCachedArgumentEncoder[index];
}
id<MTLArgumentEncoder> MetalDescriptorSetLayout::getArgumentEncoderSlow(id<MTLDevice> device,
ShaderStage stage, utils::FixedCapacityVector<MTLTextureType> const& textureTypes) {
auto const& bindings = getBindings();
NSMutableArray<MTLArgumentDescriptor*>* arguments = [NSMutableArray new];
// Important! The bindings must be sorted by binding number. This has already been done inside
// createDescriptorSetLayout.
size_t textureIndex = 0;
for (auto const& binding : bindings) {
if (!hasShaderType(binding.stageFlags, stage)) {
continue;
}
switch (binding.type) {
case DescriptorType::UNIFORM_BUFFER:
case DescriptorType::SHADER_STORAGE_BUFFER: {
MTLArgumentDescriptor* bufferArgument = [MTLArgumentDescriptor argumentDescriptor];
bufferArgument.index = binding.binding * 2;
bufferArgument.dataType = MTLDataTypePointer;
bufferArgument.access = MTLArgumentAccessReadOnly;
[arguments addObject:bufferArgument];
break;
}
case DescriptorType::SAMPLER: {
MTLArgumentDescriptor* textureArgument = [MTLArgumentDescriptor argumentDescriptor];
textureArgument.index = binding.binding * 2;
textureArgument.dataType = MTLDataTypeTexture;
MTLTextureType textureType = MTLTextureType2D;
if (textureIndex < textureTypes.size()) {
textureType = textureTypes[textureIndex++];
}
textureArgument.textureType = textureType;
textureArgument.access = MTLArgumentAccessReadOnly;
[arguments addObject:textureArgument];
MTLArgumentDescriptor* samplerArgument = [MTLArgumentDescriptor argumentDescriptor];
samplerArgument.index = binding.binding * 2 + 1;
samplerArgument.dataType = MTLDataTypeSampler;
textureArgument.access = MTLArgumentAccessReadOnly;
[arguments addObject:samplerArgument];
break;
}
case DescriptorType::INPUT_ATTACHMENT:
// TODO: support INPUT_ATTACHMENT
assert_invariant(false);
break;
}
}
return [device newArgumentEncoderWithArguments:arguments];
}
MetalDescriptorSet::MetalDescriptorSet(MetalDescriptorSetLayout* layout) noexcept
: layout(layout) {}
void MetalDescriptorSet::finalize(MetalDriver* driver) {
[driver->mContext->currentRenderPassEncoder useResource:driver->mContext->emptyBuffer
usage:MTLResourceUsageRead];
[driver->mContext->currentRenderPassEncoder
useResource:getOrCreateEmptyTexture(driver->mContext)
usage:MTLResourceUsageRead];
if (@available(iOS 13.0, *)) {
[driver->mContext->currentRenderPassEncoder useResources:vertexResources.data()
count:vertexResources.size()
usage:MTLResourceUsageRead
stages:MTLRenderStageVertex];
[driver->mContext->currentRenderPassEncoder useResources:fragmentResources.data()
count:fragmentResources.size()
usage:MTLResourceUsageRead
stages:MTLRenderStageFragment];
} else {
[driver->mContext->currentRenderPassEncoder useResources:vertexResources.data()
count:vertexResources.size()
usage:MTLResourceUsageRead];
[driver->mContext->currentRenderPassEncoder useResources:fragmentResources.data()
count:fragmentResources.size()
usage:MTLResourceUsageRead];
}
}
id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, ShaderStage stage) {
auto const index = static_cast<size_t>(stage);
assert_invariant(index < cachedBuffer.size());
auto& buffer = cachedBuffer[index];
if (buffer) {
return buffer.get();
}
// Map all the texture bindings to their respective texture types.
auto const& bindings = layout->getBindings();
auto textureTypes = utils::FixedCapacityVector<MTLTextureType>::with_capacity(bindings.size());
for (auto const& binding : bindings) {
if (!hasShaderType(binding.stageFlags, stage)) {
continue;
}
MTLTextureType textureType = MTLTextureType2D;
if (auto found = textures.find(binding.binding); found != textures.end()) {
auto const& textureBinding = textures[binding.binding];
textureType = textureBinding.texture.textureType;
}
textureTypes.push_back(textureType);
}
MetalContext const& context = *driver->mContext;
id<MTLArgumentEncoder> encoder =
layout->getArgumentEncoder(context.device, stage, textureTypes);
{
ScopedAllocationTimer timer("descriptor_set");
buffer = { [context.device newBufferWithLength:encoder.encodedLength
options:MTLResourceStorageModeShared],
TrackedMetalBuffer::Type::DESCRIPTOR_SET };
}
[encoder setArgumentBuffer:buffer.get() offset:0];
for (auto const& binding : bindings) {
if (!hasShaderType(binding.stageFlags, stage)) {
continue;
}
switch (binding.type) {
case DescriptorType::UNIFORM_BUFFER:
case DescriptorType::SHADER_STORAGE_BUFFER: {
auto found = buffers.find(binding.binding);
if (found == buffers.end()) {
[encoder setBuffer:driver->mContext->emptyBuffer
offset:0
atIndex:binding.binding * 2];
continue;
}
auto const& bufferBinding = buffers[binding.binding];
[encoder setBuffer:bufferBinding.buffer
offset:bufferBinding.offset
atIndex:binding.binding * 2];
break;
}
case DescriptorType::SAMPLER: {
auto found = textures.find(binding.binding);
if (found == textures.end()) {
[encoder setTexture:driver->mContext->emptyTexture atIndex:binding.binding * 2];
id<MTLSamplerState> sampler =
driver->mContext->samplerStateCache.getOrCreateState({});
[encoder setSamplerState:sampler atIndex:binding.binding * 2 + 1];
continue;
}
auto const& textureBinding = textures[binding.binding];
[encoder setTexture:textureBinding.texture atIndex:binding.binding * 2];
SamplerState samplerState { .samplerParams = textureBinding.sampler };
id<MTLSamplerState> sampler =
driver->mContext->samplerStateCache.getOrCreateState(samplerState);
[encoder setSamplerState:sampler atIndex:binding.binding * 2 + 1];
break;
}
case DescriptorType::INPUT_ATTACHMENT:
assert_invariant(false);
break;
}
}
return buffer.get();
}
} // namespace backend
} // namespace filament

View File

@@ -33,28 +33,32 @@
namespace filament {
namespace backend {
inline bool operator==(const SamplerParams& lhs, const SamplerParams& rhs) {
return SamplerParams::EqualTo{}(lhs, rhs);
}
// Rasterization Bindings
// ----------------------
// Bindings Buffer name Count
// ------------------------------------------------------
// 0 Zero buffer (placeholder vertex buffer) 1
// 1-16 Filament vertex buffers 16 limited by MAX_VERTEX_BUFFER_COUNT
// 20 Push constants 1
// 21-24 Descriptor sets (argument buffers) 4 limited by MAX_DESCRIPTOR_SET_COUNT
// 25 Dynamic offset buffer 1
// 17-25 Uniform buffers 9 Program::UNIFORM_BINDING_COUNT
// 26 Push constants 1
// 27-30 Sampler groups (argument buffers) 4 Program::SAMPLER_BINDING_COUNT
//
// Total 23
// Total 31
// Compute Bindings
// ----------------------
// Bindings Buffer name Count
// ------------------------------------------------------
// 0-3 SSBO buffers 4 MAX_SSBO_COUNT
// 20 Push constants 1
// 21-24 Descriptor sets (argument buffers) 4 limited by MAX_DESCRIPTOR_SET_COUNT
// 25 Dynamic offset buffer 1
// 17-25 Uniform buffers 9 Program::UNIFORM_BINDING_COUNT
// 26 Push constants 1
// 27-30 Sampler groups (argument buffers) 4 Program::SAMPLER_BINDING_COUNT
//
// Total 10
// Total 18
// The total number of vertex buffer "slots" that the Metal backend can bind.
// + 1 to account for the zero buffer, a placeholder buffer used internally by the Metal backend.
@@ -67,11 +71,10 @@ static constexpr uint32_t ZERO_VERTEX_BUFFER_BINDING = 0u;
static constexpr uint32_t USER_VERTEX_BUFFER_BINDING_START = 1u;
// These constants must match the equivalent in CodeGenerator.h.
static constexpr uint32_t PUSH_CONSTANT_BUFFER_INDEX = 20u;
static constexpr uint32_t DESCRIPTOR_SET_BINDING_START = 21u;
static constexpr uint32_t DYNAMIC_OFFSET_BINDING = 25u;
static constexpr uint32_t UNIFORM_BUFFER_BINDING_START = 17u;
static constexpr uint32_t SSBO_BINDING_START = 0u;
static constexpr uint32_t SAMPLER_GROUP_BINDING_START = 27u;
// Forward declarations necessary here, definitions at end of file.
inline bool operator==(const MTLViewport& lhs, const MTLViewport& rhs);
@@ -384,17 +387,14 @@ using DepthClampStateTracker = StateTracker<MTLDepthClipMode>;
// Argument encoder
struct ArgumentEncoderState {
NSUInteger bufferCount;
utils::FixedCapacityVector<MTLTextureType> textureTypes;
explicit ArgumentEncoderState(
NSUInteger bufferCount, utils::FixedCapacityVector<MTLTextureType>&& types)
: bufferCount(bufferCount), textureTypes(std::move(types)) {}
explicit ArgumentEncoderState(utils::FixedCapacityVector<MTLTextureType>&& types)
: textureTypes(std::move(types)) {}
bool operator==(const ArgumentEncoderState& rhs) const noexcept {
return std::equal(textureTypes.begin(), textureTypes.end(), rhs.textureTypes.begin(),
rhs.textureTypes.end()) &&
bufferCount == rhs.bufferCount;
rhs.textureTypes.end());
}
bool operator!=(const ArgumentEncoderState& rhs) const noexcept {
@@ -416,30 +416,6 @@ struct ArgumentEncoderCreator {
using ArgumentEncoderCache = StateCache<ArgumentEncoderState, id<MTLArgumentEncoder>,
ArgumentEncoderCreator, ArgumentEncoderHasher>;
template <NSUInteger N, ShaderStage stage>
class MetalBufferBindings {
public:
MetalBufferBindings() { invalidate(); }
void invalidate() {
mDirtyBuffers.reset();
mDirtyOffsets.reset();
for (int i = 0; i < int(N); i++) {
mDirtyBuffers.set(i, true);
mDirtyOffsets.set(i, true);
}
}
void setBuffer(const id<MTLBuffer> buffer, NSUInteger offset, NSUInteger index);
void bindBuffers(id<MTLCommandEncoder> encoder, NSUInteger startIndex);
private:
static_assert(N <= 8);
std::array<__weak id<MTLBuffer>, N> mBuffers = { nil };
std::array<NSUInteger, N> mOffsets = { 0 };
utils::bitset8 mDirtyBuffers;
utils::bitset8 mDirtyOffsets;
};
} // namespace backend
} // namespace filament

View File

@@ -166,40 +166,28 @@ id<MTLSamplerState> SamplerStateCreator::operator()(id<MTLDevice> device,
id<MTLArgumentEncoder> ArgumentEncoderCreator::operator()(id<MTLDevice> device,
const ArgumentEncoderState &state) noexcept {
const auto& textureTypes = state.textureTypes;
const auto& textureCount = textureTypes.size();
const auto& bufferCount = state.bufferCount;
assert_invariant(textureCount > 0);
const auto& count = textureTypes.size();
assert_invariant(count > 0);
// Metal has separate data types for textures versus samplers, so the argument buffer layout
// alternates between texture and sampler, i.e.:
// buffer0
// buffer1
// textureA
// samplerA
// textureB
// samplerB
// etc
NSMutableArray<MTLArgumentDescriptor*>* arguments =
[NSMutableArray arrayWithCapacity:(bufferCount + textureCount * 2)];
size_t i = 0;
for (size_t j = 0; j < bufferCount; j++) {
MTLArgumentDescriptor* bufferArgument = [MTLArgumentDescriptor argumentDescriptor];
bufferArgument.index = i++;
bufferArgument.dataType = MTLDataTypePointer;
bufferArgument.access = MTLArgumentAccessReadOnly;
[arguments addObject:bufferArgument];
}
for (size_t j = 0; j < textureCount; j++) {
[NSMutableArray arrayWithCapacity:(count * 2)];
for (size_t i = 0; i < count; i++) {
MTLArgumentDescriptor* textureArgument = [MTLArgumentDescriptor argumentDescriptor];
textureArgument.index = i++;
textureArgument.index = i * 2 + 0;
textureArgument.dataType = MTLDataTypeTexture;
textureArgument.textureType = textureTypes[i];
textureArgument.access = MTLArgumentAccessReadOnly;
[arguments addObject:textureArgument];
MTLArgumentDescriptor* samplerArgument = [MTLArgumentDescriptor argumentDescriptor];
samplerArgument.index = i++;
samplerArgument.index = i * 2 + 1;
samplerArgument.dataType = MTLDataTypeSampler;
textureArgument.access = MTLArgumentAccessReadOnly;
[arguments addObject:samplerArgument];
@@ -208,64 +196,5 @@ id<MTLArgumentEncoder> ArgumentEncoderCreator::operator()(id<MTLDevice> device,
return [device newArgumentEncoderWithArguments:arguments];
}
template <NSUInteger N, ShaderStage stage>
void MetalBufferBindings<N, stage>::setBuffer(const id<MTLBuffer> buffer, NSUInteger offset, NSUInteger index) {
assert_invariant(offset + 1 <= N);
if (mBuffers[index] != buffer) {
mBuffers[index] = buffer;
mDirtyBuffers.set(index);
}
if (mOffsets[index] != offset) {
mOffsets[index] = offset;
mDirtyOffsets.set(index);
}
}
template <NSUInteger N, ShaderStage stage>
void MetalBufferBindings<N, stage>::bindBuffers(
id<MTLCommandEncoder> encoder, NSUInteger startIndex) {
if (mDirtyBuffers.none() && mDirtyOffsets.none()) {
return;
}
utils::bitset8 onlyOffsetDirty = mDirtyOffsets & ~mDirtyBuffers;
onlyOffsetDirty.forEachSetBit([&](size_t i) {
if constexpr (stage == ShaderStage::VERTEX) {
[(id<MTLRenderCommandEncoder>)encoder setVertexBufferOffset:mOffsets[i]
atIndex:i + startIndex];
} else if constexpr (stage == ShaderStage::FRAGMENT) {
[(id<MTLRenderCommandEncoder>)encoder setFragmentBufferOffset:mOffsets[i]
atIndex:i + startIndex];
} else if constexpr (stage == ShaderStage::COMPUTE) {
[(id<MTLComputeCommandEncoder>)encoder setBufferOffset:mOffsets[i]
atIndex:i + startIndex];
}
});
mDirtyOffsets.reset();
mDirtyBuffers.forEachSetBit([&](size_t i) {
if constexpr (stage == ShaderStage::VERTEX) {
[(id<MTLRenderCommandEncoder>)encoder setVertexBuffer:mBuffers[i]
offset:mOffsets[i]
atIndex:i + startIndex];
} else if constexpr (stage == ShaderStage::FRAGMENT) {
[(id<MTLRenderCommandEncoder>)encoder setFragmentBuffer:mBuffers[i]
offset:mOffsets[i]
atIndex:i + startIndex];
} else if constexpr (stage == ShaderStage::COMPUTE) {
[(id<MTLComputeCommandEncoder>)encoder setBuffer:mBuffers[i]
offset:mOffsets[i]
atIndex:i + startIndex];
}
});
mDirtyBuffers.reset();
}
template class MetalBufferBindings<MAX_DESCRIPTOR_SET_COUNT, ShaderStage::VERTEX>;
template class MetalBufferBindings<MAX_DESCRIPTOR_SET_COUNT, ShaderStage::FRAGMENT>;
template class MetalBufferBindings<MAX_DESCRIPTOR_SET_COUNT, ShaderStage::COMPUTE>;
} // namespace backend
} // namespace filament

View File

@@ -99,6 +99,9 @@ void NoopDriver::destroyProgram(Handle<HwProgram> ph) {
void NoopDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
}
void NoopDriver::destroySamplerGroup(Handle<HwSamplerGroup> sbh) {
}
void NoopDriver::destroySwapChain(Handle<HwSwapChain> sch) {
}
@@ -108,12 +111,6 @@ void NoopDriver::destroyStream(Handle<HwStream> sh) {
void NoopDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
}
void NoopDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> tqh) {
}
void NoopDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) {
}
Handle<HwStream> NoopDriver::createStreamNative(void* nativeStream) {
return {};
}
@@ -251,6 +248,9 @@ void NoopDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t inde
Handle<HwBufferObject> boh) {
}
void NoopDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint32_t maxLevel) {
}
void NoopDriver::update3DImage(Handle<HwTexture> th,
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
uint32_t width, uint32_t height, uint32_t depth,
@@ -276,6 +276,11 @@ void NoopDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
void NoopDriver::generateMipmaps(Handle<HwTexture> th) { }
void NoopDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
BufferDescriptor&& data) {
scheduleDestroy(std::move(data));
}
void NoopDriver::compilePrograms(CompilerPriorityQueue priority,
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
if (callback) {
@@ -298,6 +303,19 @@ void NoopDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> re
void NoopDriver::commit(Handle<HwSwapChain> sch) {
}
void NoopDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> ubh) {
}
void NoopDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index,
Handle<HwBufferObject> ubh, uint32_t offset, uint32_t size) {
}
void NoopDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
}
void NoopDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
}
void NoopDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
backend::PushConstantVariant value) {
}
@@ -374,27 +392,6 @@ void NoopDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
void NoopDriver::resetState(int) {
}
void NoopDriver::updateDescriptorSetBuffer(
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
backend::BufferObjectHandle boh,
uint32_t offset,
uint32_t size) {
}
void NoopDriver::updateDescriptorSetTexture(
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
backend::TextureHandle th,
SamplerParams params) {
}
void NoopDriver::bindDescriptorSet(
backend::DescriptorSetHandle dsh,
backend::descriptor_set_t set,
backend::DescriptorSetOffsetArray&& offsets) {
}
void NoopDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_OPENGL_BINDINGMAP_H
#define TNT_FILAMENT_BACKEND_OPENGL_BINDINGMAP_H
#include <backend/DriverEnums.h>
#include "gl_headers.h"
#include <utils/bitset.h>
#include <utils/debug.h>
#include <new>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
namespace filament::backend {
class BindingMap {
struct CompressedBinding {
// this is in fact a GLuint, but we only want 8-bits
uint8_t binding : 7;
uint8_t sampler : 1;
};
CompressedBinding (*mStorage)[MAX_DESCRIPTOR_COUNT];
utils::bitset64 mActiveDescriptors[MAX_DESCRIPTOR_SET_COUNT];
public:
BindingMap() noexcept
: mStorage(new (std::nothrow) CompressedBinding[MAX_DESCRIPTOR_SET_COUNT][MAX_DESCRIPTOR_COUNT]) {
#ifndef NDEBUG
memset(mStorage, 0xFF, sizeof(CompressedBinding[MAX_DESCRIPTOR_SET_COUNT][MAX_DESCRIPTOR_COUNT]));
#endif
}
~BindingMap() noexcept {
delete [] mStorage;
}
BindingMap(BindingMap const&) noexcept = delete;
BindingMap(BindingMap&&) noexcept = delete;
BindingMap& operator=(BindingMap const&) noexcept = delete;
BindingMap& operator=(BindingMap&&) noexcept = delete;
struct Binding {
GLuint binding;
DescriptorType type;
};
void insert(descriptor_set_t set, descriptor_binding_t binding, Binding entry) noexcept {
assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT);
assert_invariant(binding < MAX_DESCRIPTOR_COUNT);
assert_invariant(entry.binding < 128); // we reserve 1 bit for the type right now
mStorage[set][binding] = { (uint8_t)entry.binding, entry.type == DescriptorType::SAMPLER };
mActiveDescriptors[set].set(binding);
}
GLuint get(descriptor_set_t set, descriptor_binding_t binding) const noexcept {
assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT);
assert_invariant(binding < MAX_DESCRIPTOR_COUNT);
return mStorage[set][binding].binding;
}
utils::bitset64 getActiveDescriptors(descriptor_set_t set) const noexcept {
return mActiveDescriptors[set];
}
};
} // namespace filament::backend
#endif //TNT_FILAMENT_BACKEND_OPENGL_BINDINGMAP_H

View File

@@ -1,363 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "GLDescriptorSet.h"
#include "GLBufferObject.h"
#include "GLDescriptorSetLayout.h"
#include "GLTexture.h"
#include "GLUtils.h"
#include "OpenGLDriver.h"
#include "OpenGLContext.h"
#include "OpenGLProgram.h"
#include "gl_headers.h"
#include <private/backend/HandleAllocator.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <utils/BitmaskEnum.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/bitset.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <algorithm>
#include <type_traits>
#include <utility>
#include <variant>
#include <stddef.h>
#include <stdint.h>
namespace filament::backend {
GLDescriptorSet::GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle dslh,
GLDescriptorSetLayout const* layout) noexcept
: descriptors(layout->maxDescriptorBinding + 1),
dslh(std::move(dslh)) {
// We have allocated enough storage for all descriptors. Now allocate the empty descriptor
// themselves.
for (auto const& entry : layout->bindings) {
size_t const index = entry.binding;
// now we'll initialize the alternative for each way we can handle this descriptor.
auto& desc = descriptors[index].desc;
switch (entry.type) {
case DescriptorType::UNIFORM_BUFFER: {
// A uniform buffer can have dynamic offsets or not and have special handling for
// ES2 (where we need to emulate it). That's four alternatives.
bool const dynamicOffset = any(entry.flags & DescriptorFlags::DYNAMIC_OFFSET);
dynamicBuffers.set(index, dynamicOffset);
if (UTILS_UNLIKELY(gl.isES2())) {
if (dynamicOffset) {
dynamicBufferCount++;
}
desc.emplace<BufferGLES2>(dynamicOffset);
} else {
auto const type = GLUtils::getBufferBindingType(BufferObjectBinding::UNIFORM);
if (dynamicOffset) {
dynamicBufferCount++;
desc.emplace<DynamicBuffer>(type);
} else {
desc.emplace<Buffer>(type);
}
}
break;
}
case DescriptorType::SHADER_STORAGE_BUFFER: {
// shader storage buffers are not supported on ES2, So that's two alternatives.
bool const dynamicOffset = any(entry.flags & DescriptorFlags::DYNAMIC_OFFSET);
dynamicBuffers.set(index, dynamicOffset);
auto const type = GLUtils::getBufferBindingType(BufferObjectBinding::SHADER_STORAGE);
if (dynamicOffset) {
dynamicBufferCount++;
desc.emplace<DynamicBuffer>(type);
} else {
desc.emplace<Buffer>(type);
}
break;
}
case DescriptorType::SAMPLER:
if (UTILS_UNLIKELY(gl.isES2())) {
desc.emplace<SamplerGLES2>();
} else {
const bool anisotropyWorkaround =
gl.ext.EXT_texture_filter_anisotropic &&
gl.bugs.texture_filter_anisotropic_broken_on_sampler;
if (anisotropyWorkaround) {
desc.emplace<SamplerWithAnisotropyWorkaround>();
} else {
desc.emplace<Sampler>();
}
}
break;
case DescriptorType::INPUT_ATTACHMENT:
break;
}
}
}
void GLDescriptorSet::update(OpenGLContext&,
descriptor_binding_t binding, GLBufferObject* bo, size_t offset, size_t size) noexcept {
assert_invariant(binding < descriptors.size());
std::visit([=](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Buffer> || std::is_same_v<T, DynamicBuffer>) {
assert_invariant(arg.target != 0);
arg.id = bo ? bo->gl.id : 0;
arg.offset = uint32_t(offset);
arg.size = uint32_t(size);
assert_invariant(arg.id || (!arg.size && !offset));
} else if constexpr (std::is_same_v<T, BufferGLES2>) {
arg.bo = bo;
arg.offset = uint32_t(offset);
} else {
// API usage error. User asked to update the wrong type of descriptor.
PANIC_PRECONDITION("descriptor %d is not a buffer", +binding);
}
}, descriptors[binding].desc);
}
void GLDescriptorSet::update(OpenGLContext& gl,
descriptor_binding_t binding, GLTexture* t, SamplerParams params) noexcept {
assert_invariant(binding < descriptors.size());
std::visit([=, &gl](auto&& arg) mutable {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Sampler> ||
std::is_same_v<T, SamplerWithAnisotropyWorkaround> ||
std::is_same_v<T, SamplerGLES2>) {
if (UTILS_UNLIKELY(t && t->target == SamplerType::SAMPLER_EXTERNAL)) {
// From OES_EGL_image_external spec:
// "The default s and t wrap modes are CLAMP_TO_EDGE, and it is an INVALID_ENUM
// error to set the wrap mode to any other value."
params.wrapS = SamplerWrapMode::CLAMP_TO_EDGE;
params.wrapT = SamplerWrapMode::CLAMP_TO_EDGE;
params.wrapR = SamplerWrapMode::CLAMP_TO_EDGE;
}
// GLES3.x specification forbids depth textures to be filtered.
if (t && isDepthFormat(t->format)
&& params.compareMode == SamplerCompareMode::NONE) {
params.filterMag = SamplerMagFilter::NEAREST;
switch (params.filterMin) {
case SamplerMinFilter::LINEAR:
params.filterMin = SamplerMinFilter::NEAREST;
break;
case SamplerMinFilter::LINEAR_MIPMAP_NEAREST:
case SamplerMinFilter::NEAREST_MIPMAP_LINEAR:
case SamplerMinFilter::LINEAR_MIPMAP_LINEAR:
params.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST;
break;
default:
break;
}
}
arg.target = t ? t->gl.target : 0;
arg.id = t ? t->gl.id : 0;
if constexpr (std::is_same_v<T, Sampler> ||
std::is_same_v<T, SamplerWithAnisotropyWorkaround>) {
if constexpr (std::is_same_v<T, SamplerWithAnisotropyWorkaround>) {
arg.anisotropy = float(1u << params.anisotropyLog2);
}
if (t) {
arg.ref = t->ref;
arg.baseLevel = t->gl.baseLevel;
arg.maxLevel = t->gl.maxLevel;
arg.swizzle = t->gl.swizzle;
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
arg.sampler = gl.getSampler(params);
#else
(void)gl;
#endif
} else {
arg.params = params;
}
} else {
// API usage error. User asked to update the wrong type of descriptor.
PANIC_PRECONDITION("descriptor %d is not a texture", +binding);
}
}, descriptors[binding].desc);
}
template<typename T>
void GLDescriptorSet::updateTextureView(OpenGLContext& gl,
HandleAllocatorGL& handleAllocator, GLuint unit, T const& desc) noexcept {
// The common case is that we don't have a ref handle (we only have one if
// the texture ever had a View on it).
assert_invariant(desc.ref);
GLTextureRef* const ref = handleAllocator.handle_cast<GLTextureRef*>(desc.ref);
if (UTILS_UNLIKELY((desc.baseLevel != ref->baseLevel || desc.maxLevel != ref->maxLevel))) {
// If we have views, then it's still uncommon that we'll switch often
// handle the case where we reset to the original texture
GLint baseLevel = GLint(desc.baseLevel); // NOLINT(*-signed-char-misuse)
GLint maxLevel = GLint(desc.maxLevel); // NOLINT(*-signed-char-misuse)
if (baseLevel > maxLevel) {
baseLevel = 0;
maxLevel = 1000; // per OpenGL spec
}
// that is very unfortunate that we have to call activeTexture here
gl.activeTexture(unit);
glTexParameteri(desc.target, GL_TEXTURE_BASE_LEVEL, baseLevel);
glTexParameteri(desc.target, GL_TEXTURE_MAX_LEVEL, maxLevel);
ref->baseLevel = desc.baseLevel;
ref->maxLevel = desc.maxLevel;
}
if (UTILS_UNLIKELY(desc.swizzle != ref->swizzle)) {
using namespace GLUtils;
gl.activeTexture(unit);
#if !defined(__EMSCRIPTEN__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2)
glTexParameteri(desc.target, GL_TEXTURE_SWIZZLE_R, (GLint)getSwizzleChannel(desc.swizzle[0]));
glTexParameteri(desc.target, GL_TEXTURE_SWIZZLE_G, (GLint)getSwizzleChannel(desc.swizzle[1]));
glTexParameteri(desc.target, GL_TEXTURE_SWIZZLE_B, (GLint)getSwizzleChannel(desc.swizzle[2]));
glTexParameteri(desc.target, GL_TEXTURE_SWIZZLE_A, (GLint)getSwizzleChannel(desc.swizzle[3]));
#endif
ref->swizzle = desc.swizzle;
}
}
void GLDescriptorSet::bind(
OpenGLContext& gl,
HandleAllocatorGL& handleAllocator,
OpenGLProgram const& p,
descriptor_set_t set, uint32_t const* offsets, bool offsetsOnly) const noexcept {
// TODO: check that offsets is sized correctly
size_t dynamicOffsetIndex = 0;
utils::bitset64 activeDescriptorBindings = p.getActiveDescriptors(set);
if (offsetsOnly) {
activeDescriptorBindings &= dynamicBuffers;
}
// loop only over the active indices for this program
activeDescriptorBindings.forEachSetBit(
[this,&gl, &handleAllocator, &p, set, offsets, &dynamicOffsetIndex]
(size_t binding) {
// This would fail here if we're trying to set a descriptor that doesn't exist in the
// program. In other words, a mismatch between the program's layout and this descriptor-set.
assert_invariant(binding < descriptors.size());
auto const& entry = descriptors[binding];
std::visit(
[&gl, &handleAllocator, &p, &dynamicOffsetIndex, set, binding, offsets]
(auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Buffer>) {
GLuint const bindingPoint = p.getBufferBinding(set, binding);
GLintptr const offset = arg.offset;
assert_invariant(arg.id || (!arg.size && !offset));
gl.bindBufferRange(arg.target, bindingPoint, arg.id, offset, arg.size);
} else if constexpr (std::is_same_v<T, DynamicBuffer>) {
GLuint const bindingPoint = p.getBufferBinding(set, binding);
GLintptr const offset = arg.offset + offsets[dynamicOffsetIndex++];
assert_invariant(arg.id || (!arg.size && !offset));
gl.bindBufferRange(arg.target, bindingPoint, arg.id, offset, arg.size);
} else if constexpr (std::is_same_v<T, BufferGLES2>) {
GLuint const bindingPoint = p.getBufferBinding(set, binding);
GLintptr offset = arg.offset;
if (arg.dynamicOffset) {
offset += offsets[dynamicOffsetIndex++];
}
if (arg.bo) {
auto buffer = static_cast<char const*>(arg.bo->gl.buffer) + offset;
p.updateUniforms(bindingPoint, arg.bo->gl.id, buffer, arg.bo->age);
}
} else if constexpr (std::is_same_v<T, Sampler>) {
GLuint const unit = p.getTextureUnit(set, binding);
if (arg.target) {
gl.bindTexture(unit, arg.target, arg.id);
gl.bindSampler(unit, arg.sampler);
if (UTILS_UNLIKELY(arg.ref)) {
updateTextureView(gl, handleAllocator, unit, arg);
}
} else {
gl.unbindTextureUnit(unit);
}
} else if constexpr (std::is_same_v<T, SamplerWithAnisotropyWorkaround>) {
GLuint const unit = p.getTextureUnit(set, binding);
if (arg.target) {
gl.bindTexture(unit, arg.target, arg.id);
gl.bindSampler(unit, arg.sampler);
if (UTILS_UNLIKELY(arg.ref)) {
updateTextureView(gl, handleAllocator, unit, arg);
}
#if defined(GL_EXT_texture_filter_anisotropic)
// Driver claims to support anisotropic filtering, but it fails when set on
// the sampler, we have to set it on the texture instead.
glTexParameterf(arg.target, GL_TEXTURE_MAX_ANISOTROPY_EXT,
std::min(gl.gets.max_anisotropy, float(arg.anisotropy)));
#endif
} else {
gl.unbindTextureUnit(unit);
}
} else if constexpr (std::is_same_v<T, SamplerGLES2>) {
// in ES2 the sampler parameters need to be set on the texture itself
GLuint const unit = p.getTextureUnit(set, binding);
if (arg.target) {
gl.bindTexture(unit, arg.target, arg.id);
SamplerParams const params = arg.params;
glTexParameteri(arg.target, GL_TEXTURE_MIN_FILTER,
(GLint)GLUtils::getTextureFilter(params.filterMin));
glTexParameteri(arg.target, GL_TEXTURE_MAG_FILTER,
(GLint)GLUtils::getTextureFilter(params.filterMag));
glTexParameteri(arg.target, GL_TEXTURE_WRAP_S,
(GLint)GLUtils::getWrapMode(params.wrapS));
glTexParameteri(arg.target, GL_TEXTURE_WRAP_T,
(GLint)GLUtils::getWrapMode(params.wrapT));
#if defined(GL_EXT_texture_filter_anisotropic)
glTexParameterf(arg.target, GL_TEXTURE_MAX_ANISOTROPY_EXT,
std::min(gl.gets.max_anisotropy, arg.anisotropy));
#endif
} else {
gl.unbindTextureUnit(unit);
}
}
}, entry.desc);
});
CHECK_GL_ERROR(utils::slog.e)
}
void GLDescriptorSet::validate(HandleAllocatorGL& allocator,
DescriptorSetLayoutHandle pipelineLayout) const {
if (UTILS_UNLIKELY(dslh != pipelineLayout)) {
auto* const dsl = allocator.handle_cast < GLDescriptorSetLayout const * > (dslh);
auto* const cur = allocator.handle_cast < GLDescriptorSetLayout const * > (pipelineLayout);
UTILS_UNUSED_IN_RELEASE
bool const pipelineLayoutMatchesDescriptorSetLayout = std::equal(
dsl->bindings.begin(), dsl->bindings.end(),
cur->bindings.begin(),
[](DescriptorSetLayoutBinding const& lhs,
DescriptorSetLayoutBinding const& rhs) {
return lhs.type == rhs.type &&
lhs.stageFlags == rhs.stageFlags &&
lhs.binding == rhs.binding &&
lhs.flags == rhs.flags &&
lhs.count == rhs.count;
});
assert_invariant(pipelineLayoutMatchesDescriptorSetLayout);
}
}
} // namespace filament::backend

View File

@@ -1,175 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_OPENGL_GLDESCRIPTORSET_H
#define TNT_FILAMENT_BACKEND_OPENGL_GLDESCRIPTORSET_H
#include "DriverBase.h"
#include "gl_headers.h"
#include <private/backend/HandleAllocator.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <utils/bitset.h>
#include <utils/FixedCapacityVector.h>
#include <math/half.h>
#include <array>
#include <variant>
#include <stddef.h>
#include <stdint.h>
namespace filament::backend {
struct GLBufferObject;
struct GLTexture;
struct GLTextureRef;
struct GLDescriptorSetLayout;
class OpenGLProgram;
class OpenGLContext;
class OpenGLDriver;
struct GLDescriptorSet : public HwDescriptorSet {
using HwDescriptorSet::HwDescriptorSet;
GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle dslh,
GLDescriptorSetLayout const* layout) noexcept;
// update a buffer descriptor in the set
void update(OpenGLContext& gl,
descriptor_binding_t binding, GLBufferObject* bo, size_t offset, size_t size) noexcept;
// update a sampler descriptor in the set
void update(OpenGLContext& gl,
descriptor_binding_t binding, GLTexture* t, SamplerParams params) noexcept;
// conceptually bind the set to the command buffer
void bind(
OpenGLContext& gl,
HandleAllocatorGL& handleAllocator,
OpenGLProgram const& p,
descriptor_set_t set, uint32_t const* offsets, bool offsetsOnly) const noexcept;
uint32_t getDynamicBufferCount() const noexcept {
return dynamicBufferCount;
}
void validate(HandleAllocatorGL& allocator, DescriptorSetLayoutHandle pipelineLayout) const;
private:
// a Buffer Descriptor such as SSBO or UBO with static offset
struct Buffer {
// Workaround: we cannot define the following as Buffer() = default because one of our
// clients has their compiler set up where such declaration (possibly coupled with explicit)
// will be considered a deleted constructor.
Buffer() {}
explicit Buffer(GLenum target) noexcept : target(target) {}
GLenum target; // 4
GLuint id = 0; // 4
uint32_t offset = 0; // 4
uint32_t size = 0; // 4
};
// a Buffer Descriptor such as SSBO or UBO with dynamic offset
struct DynamicBuffer {
DynamicBuffer() = default;
explicit DynamicBuffer(GLenum target) noexcept : target(target) { }
GLenum target; // 4
GLuint id = 0; // 4
uint32_t offset = 0; // 4
uint32_t size = 0; // 4
};
// a UBO descriptor for ES2
struct BufferGLES2 {
BufferGLES2() = default;
explicit BufferGLES2(bool dynamicOffset) noexcept : dynamicOffset(dynamicOffset) { }
GLBufferObject const* bo = nullptr; // 8
uint32_t offset = 0; // 4
bool dynamicOffset = false; // 4
};
// A sampler descriptor
struct Sampler {
GLenum target = 0; // 4
GLuint id = 0; // 4
GLuint sampler = 0; // 4
Handle<GLTextureRef> ref; // 4
int8_t baseLevel = 0x7f; // 1
int8_t maxLevel = -1; // 1
std::array<TextureSwizzle, 4> swizzle{ // 4
TextureSwizzle::CHANNEL_0,
TextureSwizzle::CHANNEL_1,
TextureSwizzle::CHANNEL_2,
TextureSwizzle::CHANNEL_3
};
};
struct SamplerWithAnisotropyWorkaround {
GLenum target = 0; // 4
GLuint id = 0; // 4
GLuint sampler = 0; // 4
Handle<GLTextureRef> ref; // 4
math::half anisotropy = 1.0f; // 2
int8_t baseLevel = 0x7f; // 1
int8_t maxLevel = -1; // 1
std::array<TextureSwizzle, 4> swizzle{ // 4
TextureSwizzle::CHANNEL_0,
TextureSwizzle::CHANNEL_1,
TextureSwizzle::CHANNEL_2,
TextureSwizzle::CHANNEL_3
};
};
// A sampler descriptor for ES2
struct SamplerGLES2 {
GLenum target = 0; // 4
GLuint id = 0; // 4
SamplerParams params{}; // 4
float anisotropy = 1.0f; // 4
};
struct Descriptor {
std::variant<
Buffer,
DynamicBuffer,
BufferGLES2,
Sampler,
SamplerWithAnisotropyWorkaround,
SamplerGLES2> desc;
};
static_assert(sizeof(Descriptor) <= 32);
template<typename T>
static void updateTextureView(OpenGLContext& gl,
HandleAllocatorGL& handleAllocator, GLuint unit, T const& desc) noexcept;
utils::FixedCapacityVector<Descriptor> descriptors; // 16
utils::bitset64 dynamicBuffers; // 8
DescriptorSetLayoutHandle dslh; // 4
uint8_t dynamicBufferCount = 0; // 1
};
static_assert(sizeof(GLDescriptorSet) <= 32);
} // namespace filament::backend
#endif //TNT_FILAMENT_BACKEND_OPENGL_GLDESCRIPTORSET_H

View File

@@ -1,52 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_OPENGL_GLDESCRIPTORSETLAYOUT_H
#define TNT_FILAMENT_BACKEND_OPENGL_GLDESCRIPTORSETLAYOUT_H
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <algorithm>
#include <utility>
#include <stdint.h>
namespace filament::backend {
struct GLDescriptorSetLayout : public HwDescriptorSetLayout, public DescriptorSetLayout {
using HwDescriptorSetLayout::HwDescriptorSetLayout;
explicit GLDescriptorSetLayout(DescriptorSetLayout&& layout) noexcept
: DescriptorSetLayout(std::move(layout)) {
std::sort(bindings.begin(), bindings.end(),
[](auto&& lhs, auto&& rhs){
return lhs.binding < rhs.binding;
});
auto p = std::max_element(bindings.cbegin(), bindings.cend(),
[](auto const& lhs, auto const& rhs) {
return lhs.binding < rhs.binding;
});
maxDescriptorBinding = p->binding;
}
uint8_t maxDescriptorBinding = 0;
};
} // namespace filament::backend
#endif //TNT_FILAMENT_BACKEND_OPENGL_GLDESCRIPTORSETLAYOUT_H

View File

@@ -21,32 +21,12 @@
#include "gl_headers.h"
#include <backend/Handle.h>
#include <backend/DriverEnums.h>
#include <backend/platforms/OpenGLPlatform.h>
#include <array>
#include <stdint.h>
namespace filament::backend {
struct GLTextureRef {
GLTextureRef() = default;
// view reference counter
uint16_t count = 1;
// current per-view values of the texture (in GL we can only have a single View active at
// a time, and this tracks that state). It's used to avoid unnecessarily change state.
int8_t baseLevel = 127;
int8_t maxLevel = -1;
std::array<TextureSwizzle, 4> swizzle{
TextureSwizzle::CHANNEL_0,
TextureSwizzle::CHANNEL_1,
TextureSwizzle::CHANNEL_2,
TextureSwizzle::CHANNEL_3
};
};
struct GLTexture : public HwTexture {
using HwTexture::HwTexture;
struct GL {
@@ -64,14 +44,8 @@ struct GLTexture : public HwTexture {
bool imported : 1;
uint8_t sidecarSamples : 4;
uint8_t reserved1 : 3;
std::array<TextureSwizzle, 4> swizzle{
TextureSwizzle::CHANNEL_0,
TextureSwizzle::CHANNEL_1,
TextureSwizzle::CHANNEL_2,
TextureSwizzle::CHANNEL_3
};
} gl;
mutable Handle<GLTextureRef> ref;
OpenGLPlatform::ExternalTexture* externalTexture = nullptr;
};

View File

@@ -552,14 +552,6 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
} else if (strstr(renderer, "AMD") ||
strstr(renderer, "ATI")) {
// AMD/ATI GPU
} else if (strstr(vendor, "Mesa")) {
// Seen on
// [Mesa],
// [llvmpipe (LLVM 17.0.6, 256 bits)],
// [4.5 (Core Profile) Mesa 24.0.6-1],
// [4.50]
// not known which version are affected
bugs->rebind_buffer_after_deletion = true;
} else if (strstr(renderer, "Mozilla")) {
bugs->disable_invalidate_framebuffer = true;
}
@@ -937,19 +929,15 @@ void OpenGLContext::unbindSampler(GLuint sampler) noexcept {
}
}
void OpenGLContext::deleteBuffer(GLuint buffer, GLenum target) noexcept {
glDeleteBuffers(1, &buffer);
void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept {
glDeleteBuffers(n, buffers);
// bindings of bound buffers are reset to 0
size_t const targetIndex = getIndexForBufferTarget(target);
auto& genericBinding = state.buffers.genericBinding[targetIndex];
if (genericBinding == buffer) {
genericBinding = 0;
}
if (UTILS_UNLIKELY(bugs.rebind_buffer_after_deletion)) {
if (genericBinding) {
glBindBuffer(target, genericBinding);
const size_t targetIndex = getIndexForBufferTarget(target);
auto& genericBuffer = state.buffers.genericBinding[targetIndex];
UTILS_NOUNROLL
for (GLsizei i = 0; i < n; ++i) {
if (genericBuffer == buffers[i]) {
genericBuffer = 0;
}
}
@@ -958,13 +946,16 @@ void OpenGLContext::deleteBuffer(GLuint buffer, GLenum target) noexcept {
(target != GL_UNIFORM_BUFFER && target != GL_TRANSFORM_FEEDBACK_BUFFER));
if (target == GL_UNIFORM_BUFFER || target == GL_TRANSFORM_FEEDBACK_BUFFER) {
auto& indexedBinding = state.buffers.targets[targetIndex];
UTILS_NOUNROLL
for (auto& entry: indexedBinding.buffers) {
if (entry.name == buffer) {
entry.name = 0;
entry.offset = 0;
entry.size = 0;
auto& indexedBuffer = state.buffers.targets[targetIndex];
UTILS_NOUNROLL // clang generates >1 KiB of code!!
for (GLsizei i = 0; i < n; ++i) {
UTILS_NOUNROLL
for (auto& buffer : indexedBuffer.buffers) {
if (buffer.name == buffers[i]) {
buffer.name = 0;
buffer.offset = 0;
buffer.size = 0;
}
}
}
}

View File

@@ -60,19 +60,10 @@ public:
struct RenderPrimitive {
static_assert(MAX_VERTEX_ATTRIBUTE_COUNT <= 16);
GLuint vao[2] = {}; // 8
GLuint vao[2] = {}; // 4
GLuint elementArray = 0; // 4
GLenum indicesType = 0; // 4
// The optional 32-bit handle to a GLVertexBuffer is necessary only if the referenced
// VertexBuffer supports buffer objects. If this is zero, then the VBO handles array is
// immutable.
Handle<HwVertexBuffer> vertexBufferWithObjects; // 4
mutable utils::bitset<uint16_t> vertexAttribArray; // 2
uint8_t reserved[2] = {}; // 2
// if this differs from vertexBufferWithObjects->bufferObjectsVersion, this VAO needs to
// be updated (see OpenGLDriver::updateVertexArrayObject())
uint8_t vertexBufferVersion = 0; // 1
@@ -85,11 +76,16 @@ public:
// See OpenGLContext::bindVertexArray()
uint8_t nameVersion = 0; // 1
// Size in bytes of indices in the index buffer (1 or 2)
uint8_t indicesShift = 0; // 1
// Size in bytes of indices in the index buffer
uint8_t indicesSize = 0; // 1
// The optional 32-bit handle to a GLVertexBuffer is necessary only if the referenced
// VertexBuffer supports buffer objects. If this is zero, then the VBO handles array is
// immutable.
Handle<HwVertexBuffer> vertexBufferWithObjects; // 4
GLenum getIndicesType() const noexcept {
return indicesType;
return indicesSize == 4 ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
}
} gl;
@@ -190,7 +186,7 @@ public:
inline void viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept;
inline void depthRange(GLclampf near, GLclampf far) noexcept;
void deleteBuffer(GLuint buffer, GLenum target) noexcept;
void deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept;
void deleteVertexArray(GLuint vao) noexcept;
void destroyWithContext(size_t index, std::function<void(OpenGLContext&)> const& closure) noexcept;
@@ -316,15 +312,10 @@ public:
// a glFinish. So we must delay the destruction until we know the GPU is finished.
bool delay_fbo_destruction;
// Mesa sometimes clears the generic buffer binding when *another* buffer is destroyed,
// if that other buffer is bound on an *indexed* buffer binding.
bool rebind_buffer_after_deletion;
// Force feature level 0. Typically used for low end ES3 devices with significant driver
// bugs or performance issues.
bool force_feature_level0;
} bugs = {};
// state getters -- as needed.
@@ -483,6 +474,12 @@ public:
void unbindEverything() noexcept;
void synchronizeStateAndCache(size_t index) noexcept;
void setEs2UniformBinding(size_t index, GLuint id, void const* data, uint16_t age) noexcept {
mUniformBindings[index] = { id, data, age };
}
auto getEs2UniformBinding(size_t index) const noexcept {
return mUniformBindings[index];
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLuint getSamplerSlow(SamplerParams sp) const noexcept;
@@ -509,6 +506,9 @@ private:
std::vector<std::function<void(OpenGLContext&)>> mDestroyWithNormalContext;
RenderPrimitive mDefaultVAO;
std::optional<GLuint> mDefaultFbo[2];
std::array<
std::tuple<GLuint, void const*, uint16_t>,
CONFIG_UNIFORM_BINDING_COUNT> mUniformBindings = {};
mutable tsl::robin_map<SamplerParams, GLuint,
SamplerParams::Hasher, SamplerParams::EqualTo> mSamplerMap;
@@ -559,9 +559,6 @@ private:
{ bugs.delay_fbo_destruction,
"delay_fbo_destruction",
""},
{ bugs.rebind_buffer_after_deletion,
"rebind_buffer_after_deletion",
""},
{ bugs.force_feature_level0,
"force_feature_level0",
""},

View File

@@ -17,7 +17,6 @@
#include "OpenGLDriver.h"
#include "CommandStreamDispatcher.h"
#include "GLTexture.h"
#include "GLUtils.h"
#include "OpenGLContext.h"
#include "OpenGLDriverFactory.h"
@@ -29,21 +28,19 @@
#include <backend/BufferDescriptor.h>
#include <backend/CallbackHandler.h>
#include <backend/DescriptorSetOffsetArray.h>
#include <backend/DriverApiForward.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <backend/PipelineState.h>
#include <backend/Platform.h>
#include <backend/Program.h>
#include <backend/SamplerDescriptor.h>
#include <backend/TargetBufferInfo.h>
#include "private/backend/Dispatcher.h"
#include "private/backend/DriverApi.h"
#include <type_traits>
#include <utils/BitmaskEnum.h>
#include <utils/FixedCapacityVector.h>
#include <utils/CString.h>
#include <utils/Log.h>
#include <utils/Panic.h>
@@ -62,9 +59,7 @@
#include <memory>
#include <mutex>
#include <new>
#include <type_traits>
#include <utility>
#include <variant>
#include <stdint.h>
#include <stdlib.h>
@@ -137,16 +132,17 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
// this is useful for development, but too verbose even for debug builds
// For reference on a 64-bits machine in Release mode:
// GLIndexBuffer : 8 moderate
// GLSamplerGroup : 16 few
// GLSwapChain : 16 few
// GLTimerQuery : 16 few
// GLFence : 24 few
// GLRenderPrimitive : 32 many
// GLBufferObject : 32 many
// -- less than or equal 32 bytes
// OpenGLProgram : 56 moderate
// GLTexture : 64 moderate
// -- less than or equal 64 bytes
// GLVertexBuffer : 76 moderate
// OpenGLProgram : 96 moderate
// -- less than or equal 96 bytes
// GLStream : 104 few
// GLRenderTarget : 112 few
// GLVertexBufferInfo : 132 moderate
@@ -158,6 +154,7 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
<< "\nGLVertexBuffer: " << sizeof(GLVertexBuffer)
<< "\nGLVertexBufferInfo: " << sizeof(GLVertexBufferInfo)
<< "\nGLIndexBuffer: " << sizeof(GLIndexBuffer)
<< "\nGLSamplerGroup: " << sizeof(GLSamplerGroup)
<< "\nGLRenderPrimitive: " << sizeof(GLRenderPrimitive)
<< "\nGLTexture: " << sizeof(GLTexture)
<< "\nGLTimerQuery: " << sizeof(GLTimerQuery)
@@ -252,6 +249,8 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
mDriverConfig(driverConfig),
mCurrentPushConstants(new(std::nothrow) PushConstantBundle{}) {
std::fill(mSamplerBindings.begin(), mSamplerBindings.end(), nullptr);
// set a reasonable default value for our stream array
mTexturesWithStreamsAttached.reserve(8);
mStreamsWithPendingAcquiredImage.reserve(8);
@@ -382,28 +381,18 @@ void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept {
}
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
if (UTILS_UNLIKELY(mBoundProgram == p)) {
// program didn't change, don't do anything.
return true;
}
// compile/link the program if needed and call glUseProgram
// set-up textures and samplers in the proper TMUs (as specified in setSamplers)
bool const success = p->use(this, mContext);
assert_invariant(success == p->isValid());
if (success) {
// TODO: we could even improve this if the program could tell us which of the descriptors
// bindings actually changed. In practice, it is likely that set 0 or 1 might not
// change often.
decltype(mInvalidDescriptorSetBindings) changed;
changed.setValue((1 << MAX_DESCRIPTOR_SET_COUNT) - 1);
mInvalidDescriptorSetBindings |= changed;
mBoundProgram = p;
}
if (UTILS_UNLIKELY(mContext.isES2() && success)) {
// Set the output colorspace for this program (linear or rec709). This is only relevant
for (uint32_t i = 0; i < Program::UNIFORM_BINDING_COUNT; i++) {
auto [id, buffer, age] = mContext.getEs2UniformBinding(i);
if (buffer) {
p->updateUniforms(i, id, buffer, age);
}
}
// Set the output colorspace for this program (linear or rec709). This in only relevant
// when mPlatform.isSRGBSwapChainSupported() is false (no need to check though).
p->setRec709ColorSpace(mRec709OutputColorspace);
}
@@ -543,23 +532,15 @@ Handle<HwProgram> OpenGLDriver::createProgramS() noexcept {
return initHandle<OpenGLProgram>();
}
Handle<HwSamplerGroup> OpenGLDriver::createSamplerGroupS() noexcept {
return initHandle<GLSamplerGroup>();
}
Handle<HwTexture> OpenGLDriver::createTextureS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::createTextureViewS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::createTextureViewSwizzleS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::createTextureExternalImageS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::createTextureExternalImagePlaneS() noexcept {
Handle<HwTexture> OpenGLDriver::createTextureSwizzledS() noexcept {
return initHandle<GLTexture>();
}
@@ -591,14 +572,6 @@ Handle<HwTimerQuery> OpenGLDriver::createTimerQueryS() noexcept {
return initHandle<GLTimerQuery>();
}
Handle<HwDescriptorSetLayout> OpenGLDriver::createDescriptorSetLayoutS() noexcept {
return initHandle<GLDescriptorSetLayout>();
}
Handle<HwDescriptorSet> OpenGLDriver::createDescriptorSetS() noexcept {
return initHandle<GLDescriptorSet>();
}
void OpenGLDriver::createVertexBufferInfoR(
Handle<HwVertexBufferInfo> vbih,
uint8_t bufferCount,
@@ -671,8 +644,7 @@ void OpenGLDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
GLVertexBuffer* const vb = handle_cast<GLVertexBuffer*>(vbh);
GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
rp->gl.indicesShift = (ib->elementSize == 4u) ? 2u : 1u;
rp->gl.indicesType = (ib->elementSize == 4u) ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
rp->gl.indicesSize = (ib->elementSize == 4u) ? 4u : 2u;
rp->gl.vertexBufferWithObjects = vbh;
rp->type = pt;
rp->vbih = vb->vbih;
@@ -726,6 +698,13 @@ void OpenGLDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t size,
utils::FixedSizeString<32> debugName) {
DEBUG_MARKER()
construct<GLSamplerGroup>(sbh, size);
}
UTILS_NOINLINE
void OpenGLDriver::textureStorage(OpenGLDriver::GLTexture* t,
uint32_t width, uint32_t height, uint32_t depth, bool useProtectedMemory) noexcept {
@@ -921,129 +900,32 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::createTextureViewR(Handle<HwTexture> th,
Handle<HwTexture> srch, uint8_t baseLevel, uint8_t levelCount) {
void OpenGLDriver::createTextureSwizzledR(Handle<HwTexture> th,
SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
uint32_t w, uint32_t h, uint32_t depth, TextureUsage usage,
TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) {
DEBUG_MARKER()
GLTexture const* const src = handle_cast<GLTexture const*>(srch);
FILAMENT_CHECK_PRECONDITION(any(src->usage & TextureUsage::SAMPLEABLE))
<< "TextureView can only be created on a SAMPLEABLE texture";
assert_invariant(uint8_t(usage) & uint8_t(TextureUsage::SAMPLEABLE));
FILAMENT_CHECK_PRECONDITION(!src->gl.imported)
<< "TextureView can't be created on imported textures";
createTextureR(th, target, levels, format, samples, w, h, depth, usage);
if (!src->ref) {
// lazily create the ref handle, because most textures will never get a texture view
src->ref = initHandle<GLTextureRef>();
// WebGL does not support swizzling. We assert for this in the Texture builder,
// so it is probably fine to silently ignore the swizzle state here.
#if !defined(__EMSCRIPTEN__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2)
if (!mContext.isES2()) {
// the texture is still bound and active from createTextureR
GLTexture* t = handle_cast<GLTexture*>(th);
glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_R, (GLint)getSwizzleChannel(r));
glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_G, (GLint)getSwizzleChannel(g));
glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_B, (GLint)getSwizzleChannel(b));
glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_A, (GLint)getSwizzleChannel(a));
}
GLTexture* t = construct<GLTexture>(th,
src->target,
src->levels,
src->samples,
src->width, src->height, src->depth,
src->format,
src->usage);
t->gl = src->gl;
t->gl.sidecarRenderBufferMS = 0;
t->gl.sidecarSamples = 1;
auto srcBaseLevel = src->gl.baseLevel;
auto srcMaxLevel = src->gl.maxLevel;
if (srcBaseLevel > srcMaxLevel) {
srcBaseLevel = 0;
srcMaxLevel = 127;
}
t->gl.baseLevel = (int8_t)std::min(127, srcBaseLevel + baseLevel);
t->gl.maxLevel = (int8_t)std::min(127, srcBaseLevel + baseLevel + levelCount - 1);
// increase reference count to this texture handle
t->ref = src->ref;
GLTextureRef* ref = handle_cast<GLTextureRef*>(t->ref);
assert_invariant(ref);
ref->count++;
#endif
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwTexture> srch,
backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b,
backend::TextureSwizzle a) {
DEBUG_MARKER()
GLTexture const* const src = handle_cast<GLTexture const*>(srch);
FILAMENT_CHECK_PRECONDITION(any(src->usage & TextureUsage::SAMPLEABLE))
<< "TextureView can only be created on a SAMPLEABLE texture";
FILAMENT_CHECK_PRECONDITION(!src->gl.imported)
<< "TextureView can't be created on imported textures";
if (!src->ref) {
// lazily create the ref handle, because most textures will never get a texture view
src->ref = initHandle<GLTextureRef>();
}
GLTexture* t = construct<GLTexture>(th,
src->target,
src->levels,
src->samples,
src->width, src->height, src->depth,
src->format,
src->usage);
t->gl = src->gl;
t->gl.baseLevel = src->gl.baseLevel;
t->gl.maxLevel = src->gl.maxLevel;
t->gl.sidecarRenderBufferMS = 0;
t->gl.sidecarSamples = 1;
auto getChannel = [&swizzle = src->gl.swizzle](TextureSwizzle ch) {
switch (ch) {
case TextureSwizzle::SUBSTITUTE_ZERO:
case TextureSwizzle::SUBSTITUTE_ONE:
return ch;
case TextureSwizzle::CHANNEL_0:
return swizzle[0];
case TextureSwizzle::CHANNEL_1:
return swizzle[1];
case TextureSwizzle::CHANNEL_2:
return swizzle[2];
case TextureSwizzle::CHANNEL_3:
return swizzle[3];
}
};
t->gl.swizzle = {
getChannel(r),
getChannel(g),
getChannel(b),
getChannel(a),
};
// increase reference count to this texture handle
t->ref = src->ref;
GLTextureRef* ref = handle_cast<GLTextureRef*>(t->ref);
assert_invariant(ref);
ref->count++;
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::createTextureExternalImageR(Handle<HwTexture> th, backend::TextureFormat format,
uint32_t width, uint32_t height, backend::TextureUsage usage, void* image) {
createTextureR(th, SamplerType::SAMPLER_EXTERNAL, 1, format, 1, width, height, 1, usage);
setExternalImage(th, image);
}
void OpenGLDriver::createTextureExternalImagePlaneR(Handle<HwTexture> th,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
void* image, uint32_t plane) {
createTextureR(th, SamplerType::SAMPLER_EXTERNAL, 1, format, 1, width, height, 1, usage);
setExternalImagePlane(th, image, plane);
}
void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
uint32_t w, uint32_t h, uint32_t depth, TextureUsage usage) {
@@ -1266,7 +1148,6 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
if (any(t->usage & TextureUsage::SAMPLEABLE)) {
switch (t->target) {
case SamplerType::SAMPLER_2D:
case SamplerType::SAMPLER_3D:
case SamplerType::SAMPLER_2D_ARRAY:
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
// this could be GL_TEXTURE_2D_MULTISAMPLE or GL_TEXTURE_2D_ARRAY
@@ -1277,9 +1158,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
target = getCubemapTarget(binfo.layer);
// note: cubemaps can't be multi-sampled
break;
case SamplerType::SAMPLER_EXTERNAL:
// This is an error. We have asserted in debug build.
target = t->gl.target;
default:
break;
}
}
@@ -1297,7 +1176,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
if (rt->gl.samples <= 1 ||
(rt->gl.samples > 1 && t->samples > 1 && gl.features.multisample_texture)) {
// On GL3.2 / GLES3.1 and above multisample is handled when creating the texture.
// on GL3.2 / GLES3.1 and above multisample is handled when creating the texture.
// If multisampled textures are not supported and we end-up here, things should
// still work, albeit without MSAA.
gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
@@ -1321,7 +1200,6 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
GL_RENDERBUFFER, t->gl.id);
}
break;
case GL_TEXTURE_3D:
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_CUBE_MAP_ARRAY:
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
@@ -1331,7 +1209,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
if (layerCount > 1) {
// if layerCount > 1, it means we use the multiview extension.
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, attachment,
t->gl.id, 0, binfo.layer, layerCount);
t->gl.id, 0, binfo.baseViewIndex, layerCount);
} else
#endif // !defined(__EMSCRIPTEN__) && !defined(IOS)
{
@@ -1435,7 +1313,6 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
GL_RENDERBUFFER, t->gl.id);
}
break;
case GL_TEXTURE_3D:
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_CUBE_MAP_ARRAY:
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
@@ -1453,6 +1330,14 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
rt->gl.resolve |= resolveFlags;
if (any(t->usage & TextureUsage::SAMPLEABLE)) {
// In a sense, drawing to a texture level is similar to calling setTextureData on it; in
// both cases, we update the base/max LOD to give shaders access to levels as they become
// available. Note that this can only expand the LOD range (never shrink it), and that
// users can override this range by calling setMinMaxLevels().
updateTextureLodRange(t, (int8_t)binfo.level);
}
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER)
}
@@ -1693,19 +1578,6 @@ void OpenGLDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
mContext.createTimerQuery(tq);
}
void OpenGLDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
DescriptorSetLayout&& info) {
DEBUG_MARKER()
construct<GLDescriptorSetLayout>(dslh, std::move(info));
}
void OpenGLDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
Handle<HwDescriptorSetLayout> dslh) {
DEBUG_MARKER()
GLDescriptorSetLayout const* dsl = handle_cast<GLDescriptorSetLayout*>(dslh);
construct<GLDescriptorSet>(dsh, mContext, dslh, dsl);
}
// ------------------------------------------------------------------------------------------------
// Destroying driver objects
// ------------------------------------------------------------------------------------------------
@@ -1732,7 +1604,7 @@ void OpenGLDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
if (ibh) {
auto& gl = mContext;
GLIndexBuffer const* ib = handle_cast<const GLIndexBuffer*>(ibh);
gl.deleteBuffer(ib->gl.buffer, GL_ELEMENT_ARRAY_BUFFER);
gl.deleteBuffers(1, &ib->gl.buffer, GL_ELEMENT_ARRAY_BUFFER);
destruct(ibh, ib);
}
}
@@ -1745,7 +1617,7 @@ void OpenGLDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
free(bo->gl.buffer);
} else {
gl.deleteBuffer(bo->gl.id, bo->gl.binding);
gl.deleteBuffers(1, &bo->gl.id, bo->gl.binding);
}
destruct(boh, bo);
}
@@ -1783,41 +1655,35 @@ void OpenGLDriver::destroyProgram(Handle<HwProgram> ph) {
}
}
void OpenGLDriver::destroySamplerGroup(Handle<HwSamplerGroup> sbh) {
DEBUG_MARKER()
if (sbh) {
GLSamplerGroup* sb = handle_cast<GLSamplerGroup*>(sbh);
for (auto& binding : mSamplerBindings) {
if (binding == sb) {
binding = nullptr;
}
}
destruct(sbh, sb);
}
}
void OpenGLDriver::destroyTexture(Handle<HwTexture> th) {
DEBUG_MARKER()
if (th) {
auto& gl = mContext;
GLTexture* t = handle_cast<GLTexture*>(th);
if (UTILS_LIKELY(!t->gl.imported)) {
if (UTILS_LIKELY(t->usage & TextureUsage::SAMPLEABLE)) {
// drop a reference
uint16_t count = 0;
if (UTILS_UNLIKELY(t->ref)) {
// the common case is that we don't have a ref handle
GLTextureRef* const ref = handle_cast<GLTextureRef*>(t->ref);
count = --(ref->count);
if (count == 0) {
destruct(t->ref, ref);
}
gl.unbindTexture(t->gl.target, t->gl.id);
if (UTILS_UNLIKELY(t->hwStream)) {
detachStream(t);
}
if (count == 0) {
// if this was the last reference, we destroy the refcount as well as
// the GL texture name itself.
gl.unbindTexture(t->gl.target, t->gl.id);
if (UTILS_UNLIKELY(t->hwStream)) {
detachStream(t);
}
if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) {
mPlatform.destroyExternalImage(t->externalTexture);
} else {
glDeleteTextures(1, &t->gl.id);
}
if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) {
mPlatform.destroyExternalImage(t->externalTexture);
} else {
// The Handle<HwTexture> is always destroyed. For extra precaution we also
// check that the GLTexture has a trivial destructor.
static_assert(std::is_trivially_destructible_v<GLTexture>);
glDeleteTextures(1, &t->gl.id);
}
} else {
assert_invariant(t->gl.target == GL_RENDERBUFFER);
@@ -1922,28 +1788,6 @@ void OpenGLDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
}
}
void OpenGLDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> dslh) {
DEBUG_MARKER()
if (dslh) {
GLDescriptorSetLayout* dsl = handle_cast<GLDescriptorSetLayout*>(dslh);
destruct(dslh, dsl);
}
}
void OpenGLDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
DEBUG_MARKER()
if (dsh) {
// unbind the descriptor-set, to avoid use-after-free
for (auto& bound : mBoundDescriptorSets) {
if (bound.dsh == dsh) {
bound = {};
}
}
GLDescriptorSet* ds = handle_cast<GLDescriptorSet*>(dsh);
destruct(dsh, ds);
}
}
// ------------------------------------------------------------------------------------------------
// Synchronous APIs
// These are called on the application's thread
@@ -2520,6 +2364,122 @@ void OpenGLDriver::resetBufferObject(Handle<HwBufferObject> boh) {
}
}
void OpenGLDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
BufferDescriptor&& data) {
DEBUG_MARKER()
OpenGLContext const& context = getContext();
#if defined(GL_EXT_texture_filter_anisotropic)
const bool anisotropyWorkaround =
context.ext.EXT_texture_filter_anisotropic &&
context.bugs.texture_filter_anisotropic_broken_on_sampler;
#endif
GLSamplerGroup* const sb = handle_cast<GLSamplerGroup *>(sbh);
assert_invariant(sb->textureUnitEntries.size() == data.size / sizeof(SamplerDescriptor));
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
bool const es2 = context.isES2();
#endif
auto const* const pSamplers = (SamplerDescriptor const*)data.buffer;
for (size_t i = 0, c = sb->textureUnitEntries.size(); i < c; i++) {
GLuint samplerId = 0u;
Handle<HwTexture> th = pSamplers[i].t;
if (UTILS_LIKELY(th)) {
GLTexture const* const t = handle_cast<const GLTexture*>(th);
assert_invariant(t);
if (UTILS_UNLIKELY(es2)
#if defined(GL_EXT_texture_filter_anisotropic)
|| UTILS_UNLIKELY(anisotropyWorkaround)
#endif
) {
// We must set texture parameters on the texture itself.
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
}
SamplerParams params = pSamplers[i].s;
if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) {
// From OES_EGL_image_external spec:
// "The default s and t wrap modes are CLAMP_TO_EDGE, and it is an INVALID_ENUM
// error to set the wrap mode to any other value."
params.wrapS = SamplerWrapMode::CLAMP_TO_EDGE;
params.wrapT = SamplerWrapMode::CLAMP_TO_EDGE;
params.wrapR = SamplerWrapMode::CLAMP_TO_EDGE;
}
// GLES3.x specification forbids depth textures to be filtered.
if (UTILS_UNLIKELY(isDepthFormat(t->format)
&& params.compareMode == SamplerCompareMode::NONE
&& params.filterMag != SamplerMagFilter::NEAREST
&& params.filterMin != SamplerMinFilter::NEAREST
&& params.filterMin != SamplerMinFilter::NEAREST_MIPMAP_NEAREST)) {
params.filterMag = SamplerMagFilter::NEAREST;
params.filterMin = SamplerMinFilter::NEAREST;
#ifndef NDEBUG
slog.w << "HwSamplerGroup specifies a filtered depth texture, which is not allowed."
<< io::endl;
#endif
}
#if defined(GL_EXT_texture_filter_anisotropic)
if (UTILS_UNLIKELY(anisotropyWorkaround)) {
// Driver claims to support anisotropic filtering, but it fails when set on
// the sampler, we have to set it on the texture instead.
// The texture is already bound here.
GLfloat const anisotropy = float(1u << params.anisotropyLog2);
glTexParameterf(t->gl.target, GL_TEXTURE_MAX_ANISOTROPY_EXT,
std::min(context.gets.max_anisotropy, anisotropy));
}
#endif
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (UTILS_LIKELY(!es2)) {
samplerId = mContext.getSampler(params);
} else
#endif
{
// in ES2 the sampler parameters need to be set on the texture itself
glTexParameteri(t->gl.target, GL_TEXTURE_MIN_FILTER,
(GLint)getTextureFilter(params.filterMin));
glTexParameteri(t->gl.target, GL_TEXTURE_MAG_FILTER,
(GLint)getTextureFilter(params.filterMag));
glTexParameteri(t->gl.target, GL_TEXTURE_WRAP_S,
(GLint)getWrapMode(params.wrapS));
glTexParameteri(t->gl.target, GL_TEXTURE_WRAP_T,
(GLint)getWrapMode(params.wrapT));
}
} else {
// this happens if the program doesn't use all samplers of a sampler group,
// which is not an error.
}
sb->textureUnitEntries[i] = { th, samplerId };
}
scheduleDestroy(std::move(data));
}
void OpenGLDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint32_t maxLevel) {
DEBUG_MARKER()
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
auto& gl = mContext;
if (!gl.isES2()) {
GLTexture* t = handle_cast<GLTexture*>(th);
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
// Must fit within int8_t.
assert_invariant(minLevel <= 0x7f && maxLevel <= 0x7f);
t->gl.baseLevel = (int8_t)minLevel;
glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel);
t->gl.maxLevel = (int8_t)maxLevel; // NOTE: according to the GL spec, the default value of this 1000
glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel);
}
#endif
}
void OpenGLDriver::update3DImage(Handle<HwTexture> th,
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
uint32_t width, uint32_t height, uint32_t depth,
@@ -2549,6 +2509,16 @@ void OpenGLDriver::generateMipmaps(Handle<HwTexture> th) {
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
t->gl.baseLevel = 0;
t->gl.maxLevel = static_cast<int8_t>(t->levels - 1);
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (!gl.isES2()) {
glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel);
glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel);
}
#endif
glGenerateMipmap(t->gl.target);
CHECK_GL_ERROR(utils::slog.e)
@@ -2658,6 +2628,21 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
}
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (!gl.isES2()) {
// Update the base/max LOD, so we don't access undefined LOD. this allows the app to
// specify levels as they become available.
if (int8_t(level) < t->gl.baseLevel) {
t->gl.baseLevel = int8_t(level);
glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel);
}
if (int8_t(level) > t->gl.maxLevel) {
t->gl.maxLevel = int8_t(level);
glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel);
}
}
#endif
scheduleDestroy(std::move(p));
CHECK_GL_ERROR(utils::slog.e)
@@ -2744,6 +2729,21 @@ void OpenGLDriver::setCompressedTextureData(GLTexture* t, uint32_t level,
}
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (!gl.isES2()) {
// Update the base/max LOD, so we don't access undefined LOD. this allows the app to
// specify levels as they become available.
if (int8_t(level) < t->gl.baseLevel) {
t->gl.baseLevel = int8_t(level);
glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel);
}
if (int8_t(level) > t->gl.maxLevel) {
t->gl.maxLevel = int8_t(level);
glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel);
}
}
#endif
scheduleDestroy(std::move(p));
CHECK_GL_ERROR(utils::slog.e)
@@ -2914,9 +2914,6 @@ void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
GLuint const fbo = gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER)
// each render-pass starts with a disabled scissor
gl.disable(GL_SCISSOR_TEST);
if (gl.ext.EXT_discard_framebuffer
&& !gl.bugs.disable_invalidate_framebuffer) {
AttachmentArray attachments; // NOLINT
@@ -2929,6 +2926,7 @@ void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
// It's important to clear the framebuffer before drawing, as it resets
// the fb to a known state (resets fb compression and possibly other things).
// So we use glClear instead of glInvalidateFramebuffer
gl.disable(GL_SCISSOR_TEST);
clearWithRasterPipe(discardFlags & ~clearFlags, { 0.0f }, 0.0f, 0);
}
@@ -2944,6 +2942,7 @@ void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
}
if (any(clearFlags)) {
gl.disable(GL_SCISSOR_TEST);
clearWithRasterPipe(clearFlags,
params.clearColor, (GLfloat)params.clearDepth, (GLint)params.clearStencil);
}
@@ -2962,6 +2961,7 @@ void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
#ifndef NDEBUG
// clear the discarded (but not the cleared ones) buffers in debug builds
gl.disable(GL_SCISSOR_TEST);
clearWithRasterPipe(discardFlags & ~clearFlags,
{ 1, 0, 0, 1 }, 1.0, 0);
#endif
@@ -3138,6 +3138,64 @@ void OpenGLDriver::setScissor(Viewport const& scissor) noexcept {
// Setting rendering state
// ------------------------------------------------------------------------------------------------
void OpenGLDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> ubh) {
DEBUG_MARKER()
GLBufferObject* ub = handle_cast<GLBufferObject *>(ubh);
assert_invariant(ub->bindingType == BufferObjectBinding::UNIFORM);
bindBufferRange(BufferObjectBinding::UNIFORM, index, ubh, 0, ub->byteCount);
}
void OpenGLDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index,
Handle<HwBufferObject> ubh, uint32_t offset, uint32_t size) {
DEBUG_MARKER()
auto& gl = mContext;
assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE ||
bindingType == BufferObjectBinding::UNIFORM);
GLBufferObject* ub = handle_cast<GLBufferObject *>(ubh);
assert_invariant(offset + size <= ub->byteCount);
if (UTILS_UNLIKELY(ub->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
gl.setEs2UniformBinding(index,
ub->gl.id,
static_cast<uint8_t const*>(ub->gl.buffer) + offset,
ub->age);
} else {
GLenum const target = GLUtils::getBufferBindingType(bindingType);
assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE ||
ub->gl.binding == target);
gl.bindBufferRange(target, GLuint(index), ub->gl.id, offset, size);
}
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
DEBUG_MARKER()
auto& gl = mContext;
if (UTILS_UNLIKELY(bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
gl.setEs2UniformBinding(index, 0, nullptr, 0);
return;
}
GLenum const target = GLUtils::getBufferBindingType(bindingType);
gl.bindBufferRange(target, GLuint(index), 0, 0, 0);
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
DEBUG_MARKER()
assert_invariant(index < Program::SAMPLER_BINDING_COUNT);
GLSamplerGroup* sb = handle_cast<GLSamplerGroup *>(sbh);
mSamplerBindings[index] = sb;
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::insertEventMarker(char const* string) {
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_debug_marker
@@ -3503,26 +3561,6 @@ void OpenGLDriver::endFrame(UTILS_UNUSED uint32_t frameId) {
insertEventMarker("endFrame");
}
void OpenGLDriver::updateDescriptorSetBuffer(
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
backend::BufferObjectHandle boh,
uint32_t offset, uint32_t size) {
GLDescriptorSet* ds = handle_cast<GLDescriptorSet*>(dsh);
GLBufferObject* bo = boh ? handle_cast<GLBufferObject*>(boh) : nullptr;
ds->update(mContext, binding, bo, offset, size);
}
void OpenGLDriver::updateDescriptorSetTexture(
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
backend::TextureHandle th,
SamplerParams params) {
GLDescriptorSet* ds = handle_cast<GLDescriptorSet*>(dsh);
GLTexture* t = th ? handle_cast<GLTexture*>(th) : nullptr;
ds->update(mContext, binding, t, params);
}
void OpenGLDriver::flush(int) {
DEBUG_MARKER()
auto& gl = mContext;
@@ -3767,6 +3805,15 @@ void OpenGLDriver::blit(
gl.unbindFramebuffer(GL_DRAW_FRAMEBUFFER);
gl.unbindFramebuffer(GL_READ_FRAMEBUFFER);
glDeleteFramebuffers(2, fbo);
if (any(d->usage & TextureUsage::SAMPLEABLE)) {
// In a sense, blitting to a texture level is similar to calling setTextureData on it; in
// both cases, we update the base/max LOD to give shaders access to levels as they become
// available. Note that this can only expand the LOD range (never shrink it), and that
// users can override this range by calling setMinMaxLevels().
updateTextureLodRange(d, int8_t(dstLevel));
}
#endif
}
@@ -3837,6 +3884,29 @@ void OpenGLDriver::blitDEPRECATED(TargetBufferFlags buffers,
#endif
}
void OpenGLDriver::updateTextureLodRange(GLTexture* texture, int8_t targetLevel) noexcept {
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
auto& gl = mContext;
if (!gl.isES2()) {
if (texture && any(texture->usage & TextureUsage::SAMPLEABLE)) {
if (targetLevel < texture->gl.baseLevel || targetLevel > texture->gl.maxLevel) {
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, texture);
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
if (targetLevel < texture->gl.baseLevel) {
texture->gl.baseLevel = targetLevel;
glTexParameteri(texture->gl.target, GL_TEXTURE_BASE_LEVEL, targetLevel);
}
if (targetLevel > texture->gl.maxLevel) {
texture->gl.maxLevel = targetLevel;
glTexParameteri(texture->gl.target, GL_TEXTURE_MAX_LEVEL, targetLevel);
}
}
CHECK_GL_ERROR(utils::slog.e)
}
}
#endif
}
void OpenGLDriver::bindPipeline(PipelineState const& state) {
DEBUG_MARKER()
auto& gl = mContext;
@@ -3846,8 +3916,6 @@ void OpenGLDriver::bindPipeline(PipelineState const& state) {
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(state.program);
mValidProgram = useProgram(p);
(*mCurrentPushConstants) = p->getPushConstants();
mCurrentSetLayout = state.pipelineLayout.setLayout;
// TODO: we should validate that the pipeline layout matches the program's
}
void OpenGLDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
@@ -3871,83 +3939,18 @@ void OpenGLDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
mBoundRenderPrimitive = rp;
}
void OpenGLDriver::bindDescriptorSet(
backend::DescriptorSetHandle dsh,
backend::descriptor_set_t set,
backend::DescriptorSetOffsetArray&& offsets) {
// handle_cast<> here also serves to validate the handle (it actually cannot return nullptr)
GLDescriptorSet const* const ds = handle_cast<GLDescriptorSet*>(dsh);
if (ds) {
assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT);
if (mBoundDescriptorSets[set].dsh != dsh) {
// if the descriptor itself changed, we mark this descriptor binding
// invalid -- it will be re-bound at the next draw.
mInvalidDescriptorSetBindings.set(set, true);
} else if (!offsets.empty()) {
// if we reset offsets, we mark the offsets invalid so these descriptors only can
// be re-bound at the next draw.
mInvalidDescriptorSetBindingOffsets.set(set, true);
}
// `offsets` data's lifetime will end when this function returns. We have to make a copy.
// (the data is allocated inside the CommandStream)
mBoundDescriptorSets[set].dsh = dsh;
assert_invariant(offsets.data() != nullptr || ds->getDynamicBufferCount() == 0);
std::copy_n(offsets.data(), ds->getDynamicBufferCount(),
mBoundDescriptorSets[set].offsets.data());
}
}
void OpenGLDriver::updateDescriptors(utils::bitset8 invalidDescriptorSets) noexcept {
assert_invariant(mBoundProgram);
auto const offsetOnly = mInvalidDescriptorSetBindingOffsets & ~mInvalidDescriptorSetBindings;
invalidDescriptorSets.forEachSetBit([this, offsetOnly,
&boundDescriptorSets = mBoundDescriptorSets,
&context = mContext,
&boundProgram = *mBoundProgram](size_t set) {
assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT);
auto const& entry = boundDescriptorSets[set];
if (entry.dsh) {
GLDescriptorSet* const ds = handle_cast<GLDescriptorSet*>(entry.dsh);
#ifndef NDEBUG
if (UTILS_UNLIKELY(!offsetOnly[set])) {
// validate that this descriptor-set layout matches the layout set in the pipeline
// we don't need to do the check if only the offset is changing
ds->validate(mHandleAllocator, mCurrentSetLayout[set]);
}
#endif
ds->bind(context, mHandleAllocator, boundProgram,
set, entry.offsets.data(), offsetOnly[set]);
}
});
mInvalidDescriptorSetBindings.clear();
mInvalidDescriptorSetBindingOffsets.clear();
}
void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
DEBUG_MARKER()
assert_invariant(!mContext.isES2());
assert_invariant(mBoundRenderPrimitive);
#if FILAMENT_ENABLE_MATDBG
if (UTILS_UNLIKELY(!mValidProgram)) {
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
if (UTILS_UNLIKELY(!rp || !mValidProgram)) {
return;
}
#endif
assert_invariant(mBoundProgram);
assert_invariant(mValidProgram);
// When the program changes, we might have to rebind all or some descriptors
auto const invalidDescriptorSets =
mInvalidDescriptorSetBindings | mInvalidDescriptorSetBindingOffsets;
if (UTILS_UNLIKELY(invalidDescriptorSets.any())) {
updateDescriptors(invalidDescriptorSets);
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
assert_invariant(!mContext.isES2());
glDrawElementsInstanced(GLenum(rp->type), (GLsizei)indexCount,
rp->gl.getIndicesType(),
reinterpret_cast<const void*>(indexOffset << rp->gl.indicesShift),
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize),
(GLsizei)instanceCount);
#endif
@@ -3958,30 +3961,19 @@ void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
#endif
}
// This is the ES2 version of draw2().
void OpenGLDriver::draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
DEBUG_MARKER()
assert_invariant(mContext.isES2());
assert_invariant(mBoundRenderPrimitive);
#if FILAMENT_ENABLE_MATDBG
if (UTILS_UNLIKELY(!mValidProgram)) {
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
if (UTILS_UNLIKELY(!rp || !mValidProgram)) {
return;
}
#endif
assert_invariant(mBoundProgram);
assert_invariant(mValidProgram);
// When the program changes, we might have to rebind all or some descriptors
auto const invalidDescriptorSets =
mInvalidDescriptorSetBindings | mInvalidDescriptorSetBindingOffsets;
if (UTILS_UNLIKELY(invalidDescriptorSets.any())) {
updateDescriptors(invalidDescriptorSets);
}
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
assert_invariant(mContext.isES2());
assert_invariant(instanceCount == 1);
glDrawElements(GLenum(rp->type), (GLsizei)indexCount, rp->gl.getIndicesType(),
reinterpret_cast<const void*>(indexOffset << rp->gl.indicesShift));
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize));
#if FILAMENT_ENABLE_MATDBG
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)

View File

@@ -21,8 +21,6 @@
#include "OpenGLContext.h"
#include "OpenGLTimerQuery.h"
#include "GLBufferObject.h"
#include "GLDescriptorSet.h"
#include "GLDescriptorSetLayout.h"
#include "GLTexture.h"
#include "ShaderCompilerService.h"
@@ -38,7 +36,6 @@
#include "private/backend/Driver.h"
#include "private/backend/HandleAllocator.h"
#include <utils/bitset.h>
#include <utils/FixedCapacityVector.h>
#include <utils/compiler.h>
#include <utils/debug.h>
@@ -55,7 +52,6 @@
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
#include <stddef.h>
@@ -127,6 +123,16 @@ public:
} gl;
};
struct GLSamplerGroup : public HwSamplerGroup {
using HwSamplerGroup::HwSamplerGroup;
struct Entry {
Handle<HwTexture> th;
GLuint sampler = 0u;
};
utils::FixedCapacityVector<Entry> textureUnitEntries;
explicit GLSamplerGroup(size_t size) noexcept : textureUnitEntries(size) { }
};
struct GLRenderPrimitive : public HwRenderPrimitive {
using HwRenderPrimitive::HwRenderPrimitive;
OpenGLContext::RenderPrimitive gl;
@@ -139,10 +145,6 @@ public:
using GLTimerQuery = filament::backend::GLTimerQuery;
using GLDescriptorSetLayout = filament::backend::GLDescriptorSetLayout;
using GLDescriptorSet = filament::backend::GLDescriptorSet;
struct GLStream : public HwStream {
using HwStream::HwStream;
struct Info {
@@ -315,6 +317,10 @@ private:
void resolvePass(ResolveAction action, GLRenderTarget const* rt,
TargetBufferFlags discardFlags) noexcept;
const std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT>& getSamplerBindings() const {
return mSamplerBindings;
}
using AttachmentArray = std::array<GLenum, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + 2>;
static GLsizei getAttachments(AttachmentArray& attachments, TargetBufferFlags buffers,
bool isDefaultFramebuffer) noexcept;
@@ -327,16 +333,8 @@ private:
GLboolean mRenderPassStencilWrite{};
GLRenderPrimitive const* mBoundRenderPrimitive = nullptr;
OpenGLProgram* mBoundProgram = nullptr;
bool mValidProgram = false;
utils::bitset8 mInvalidDescriptorSetBindings;
utils::bitset8 mInvalidDescriptorSetBindingOffsets;
void updateDescriptors(utils::bitset8 invalidDescriptorSets) noexcept;
struct {
backend::DescriptorSetHandle dsh;
std::array<uint32_t, CONFIG_UNIFORM_BINDING_COUNT> offsets;
} mBoundDescriptorSets[MAX_DESCRIPTOR_SET_COUNT];
void clearWithRasterPipe(TargetBufferFlags clearFlags,
math::float4 const& linearColor, GLfloat depth, GLint stencil) noexcept;
@@ -348,6 +346,9 @@ private:
// ES2 only. Uniform buffer emulation binding points
GLuint mLastAssignedEmulatedUboId = 0;
// sampler buffer binding points (nullptr if not used)
std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT> mSamplerBindings = {}; // 4 pointers
// this must be accessed from the driver thread only
std::vector<GLTexture*> mTexturesWithStreamsAttached;
@@ -358,6 +359,8 @@ private:
void detachStream(GLTexture* t) noexcept;
void replaceStream(GLTexture* t, GLStream* stream) noexcept;
void updateTextureLodRange(GLTexture* texture, int8_t targetLevel) noexcept;
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
// tasks executed on the main thread after the fence signaled
void whenGpuCommandsComplete(const std::function<void()>& fn) noexcept;
@@ -381,7 +384,6 @@ private:
bool mRec709OutputColorspace = false;
PushConstantBundle* mCurrentPushConstants = nullptr;
PipelineLayout::SetLayout mCurrentSetLayout;
};
// ------------------------------------------------------------------------------------------------

View File

@@ -17,7 +17,6 @@
#include "OpenGLProgram.h"
#include "GLUtils.h"
#include "GLTexture.h"
#include "OpenGLDriver.h"
#include "ShaderCompilerService.h"
@@ -25,7 +24,6 @@
#include <backend/Program.h>
#include <backend/Handle.h>
#include <utils/BitmaskEnum.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
@@ -34,10 +32,9 @@
#include <algorithm>
#include <array>
#include <algorithm>
#include <new>
#include <string_view>
#include <utility>
#include <new>
#include <stddef.h>
#include <stdint.h>
@@ -49,8 +46,9 @@ using namespace utils;
using namespace backend;
struct OpenGLProgram::LazyInitializationData {
Program::DescriptorSetInfo descriptorBindings;
Program::BindingUniformsInfo bindingUniformInfo;
Program::UniformBlockInfo uniformBlockInfo;
Program::SamplerGroupInfo samplerGroupInfo;
std::array<Program::UniformInfo, Program::UNIFORM_BINDING_COUNT> bindingUniformInfo;
utils::FixedCapacityVector<Program::PushConstant> vertexPushConstants;
utils::FixedCapacityVector<Program::PushConstant> fragmentPushConstants;
};
@@ -59,14 +57,16 @@ struct OpenGLProgram::LazyInitializationData {
OpenGLProgram::OpenGLProgram() noexcept = default;
OpenGLProgram::OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept
: HwProgram(std::move(program.getName())), mRec709Location(-1) {
: HwProgram(std::move(program.getName())) {
auto* const lazyInitializationData = new(std::nothrow) LazyInitializationData();
lazyInitializationData->samplerGroupInfo = std::move(program.getSamplerGroupInfo());
if (UTILS_UNLIKELY(gld.getContext().isES2())) {
lazyInitializationData->bindingUniformInfo = std::move(program.getBindingUniformInfo());
} else {
lazyInitializationData->uniformBlockInfo = std::move(program.getUniformBlockBindings());
}
lazyInitializationData->vertexPushConstants = std::move(program.getPushConstants(ShaderStage::VERTEX));
lazyInitializationData->fragmentPushConstants = std::move(program.getPushConstants(ShaderStage::FRAGMENT));
lazyInitializationData->descriptorBindings = std::move(program.getDescriptorBindings());
ShaderCompilerService& compiler = gld.getShaderCompilerService();
mToken = compiler.createProgram(name, std::move(program));
@@ -124,86 +124,36 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
SYSTRACE_CALL();
// from the pipeline layout we compute a mapping from {set, binding} to {binding}
// for both buffers and textures
for (auto&& entry: lazyInitializationData.descriptorBindings) {
std::sort(entry.begin(), entry.end(),
[](Program::Descriptor const& lhs, Program::Descriptor const& rhs) {
return lhs.binding < rhs.binding;
});
}
GLuint tmu = 0;
GLuint binding = 0;
// needed for samplers
context.useProgram(program);
UTILS_NOUNROLL
for (backend::descriptor_set_t set = 0; set < MAX_DESCRIPTOR_SET_COUNT; set++) {
for (Program::Descriptor const& entry: lazyInitializationData.descriptorBindings[set]) {
switch (entry.type) {
case DescriptorType::UNIFORM_BUFFER:
case DescriptorType::SHADER_STORAGE_BUFFER: {
if (!entry.name.empty()) {
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (UTILS_LIKELY(!context.isES2())) {
GLuint const index = glGetUniformBlockIndex(program,
entry.name.c_str());
if (index != GL_INVALID_INDEX) {
// this can fail if the program doesn't use this descriptor
glUniformBlockBinding(program, index, binding);
mBindingMap.insert(set, entry.binding,
{ binding, entry.type });
++binding;
}
} else
#endif
{
auto pos = std::find_if(lazyInitializationData.bindingUniformInfo.begin(),
lazyInitializationData.bindingUniformInfo.end(),
[&name = entry.name](const auto& item) {
return std::get<1>(item) == name;
});
if (pos != lazyInitializationData.bindingUniformInfo.end()) {
binding = std::get<0>(*pos);
mBindingMap.insert(set, entry.binding, { binding, entry.type });
}
}
}
break;
if (!context.isES2()) {
// Note: This is only needed, because the layout(binding=) syntax is not permitted in glsl
// (ES3.0 and GL4.1). The backend needs a way to associate a uniform block to a binding point.
UTILS_NOUNROLL
for (GLuint binding = 0, n = lazyInitializationData.uniformBlockInfo.size();
binding < n; binding++) {
auto const& name = lazyInitializationData.uniformBlockInfo[binding];
if (!name.empty()) {
GLuint const index = glGetUniformBlockIndex(program, name.c_str());
if (index != GL_INVALID_INDEX) {
glUniformBlockBinding(program, index, binding);
}
case DescriptorType::SAMPLER: {
if (!entry.name.empty()) {
GLint const loc = glGetUniformLocation(program, entry.name.c_str());
if (loc >= 0) {
// this can fail if the program doesn't use this descriptor
mBindingMap.insert(set, entry.binding, { tmu, entry.type });
glUniform1i(loc, GLint(tmu));
++tmu;
}
}
break;
}
case DescriptorType::INPUT_ATTACHMENT:
break;
CHECK_GL_ERROR(utils::slog.e)
}
CHECK_GL_ERROR(utils::slog.e)
}
}
if (context.isES2()) {
} else
#endif
{
// ES2 initialization of (fake) UBOs
UniformsRecord* const uniformsRecords = new(std::nothrow) UniformsRecord[Program::UNIFORM_BINDING_COUNT];
UTILS_NOUNROLL
for (auto&& [index, name, uniforms] : lazyInitializationData.bindingUniformInfo) {
uniformsRecords[index].locations.reserve(uniforms.size());
uniformsRecords[index].locations.resize(uniforms.size());
for (GLuint binding = 0, n = Program::UNIFORM_BINDING_COUNT; binding < n; binding++) {
Program::UniformInfo& uniforms = lazyInitializationData.bindingUniformInfo[binding];
uniformsRecords[binding].locations.reserve(uniforms.size());
uniformsRecords[binding].locations.resize(uniforms.size());
for (size_t j = 0, c = uniforms.size(); j < c; j++) {
GLint const loc = glGetUniformLocation(program, uniforms[j].name.c_str());
uniformsRecords[index].locations[j] = loc;
if (UTILS_UNLIKELY(index == 0)) {
uniformsRecords[binding].locations[j] = loc;
if (UTILS_UNLIKELY(binding == 0)) {
// This is a bit of a gross hack here, we stash the location of
// "frameUniforms.rec709", which obviously the backend shouldn't know about,
// which is used for emulating the "rec709" colorspace in the shader.
@@ -215,11 +165,51 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
}
}
}
uniformsRecords[index].uniforms = std::move(uniforms);
uniformsRecords[binding].uniforms = std::move(uniforms);
}
mUniformsRecords = uniformsRecords;
}
uint8_t usedBindingCount = 0;
uint8_t tmu = 0;
UTILS_NOUNROLL
for (size_t i = 0, c = lazyInitializationData.samplerGroupInfo.size(); i < c; i++) {
auto const& samplers = lazyInitializationData.samplerGroupInfo[i].samplers;
if (samplers.empty()) {
// this binding point doesn't have any samplers, skip it.
continue;
}
// keep this in the loop, so we skip it in the rare case a program doesn't have
// sampler. The context cache will prevent repeated calls to GL.
context.useProgram(program);
bool atLeastOneSamplerUsed = false;
UTILS_NOUNROLL
for (const Program::Sampler& sampler: samplers) {
// find its location and associate a TMU to it
GLint const loc = glGetUniformLocation(program, sampler.name.c_str());
if (loc >= 0) {
// this can fail if the program doesn't use this sampler
glUniform1i(loc, tmu);
atLeastOneSamplerUsed = true;
}
tmu++;
}
// if this program doesn't use any sampler from this HwSamplerGroup, just cancel the
// whole group.
if (atLeastOneSamplerUsed) {
// Cache the sampler uniform locations for each interface block
mUsedSamplerBindingPoints[usedBindingCount] = i;
usedBindingCount++;
} else {
tmu -= samplers.size();
}
}
mUsedBindingsCount = usedBindingCount;
auto& vertexConstants = lazyInitializationData.vertexPushConstants;
auto& fragmentConstants = lazyInitializationData.fragmentPushConstants;
@@ -236,8 +226,41 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
}
}
void OpenGLProgram::updateUniforms(
uint32_t index, GLuint id, void const* buffer, uint16_t age) const noexcept {
void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept {
using GLTexture = OpenGLDriver::GLTexture;
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
bool const es2 = gld->getContext().isES2();
#endif
// cache a few member variable locally, outside the loop
auto const& UTILS_RESTRICT samplerBindings = gld->getSamplerBindings();
auto const& UTILS_RESTRICT usedBindingPoints = mUsedSamplerBindingPoints;
for (uint8_t i = 0, tmu = 0, n = mUsedBindingsCount; i < n; i++) {
size_t const binding = usedBindingPoints[i];
assert_invariant(binding < Program::SAMPLER_BINDING_COUNT);
auto const * const sb = samplerBindings[binding];
assert_invariant(sb);
if (!sb) continue; // should never happen, this would be a user error.
for (uint8_t j = 0, m = sb->textureUnitEntries.size(); j < m; ++j, ++tmu) { // "<=" on purpose here
Handle<HwTexture> th = sb->textureUnitEntries[j].th;
if (th) { // program may not use all samplers of sampler group
GLTexture const* const t = gld->handle_cast<GLTexture const*>(th);
gld->bindTexture(tmu, t);
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (UTILS_LIKELY(!es2)) {
GLuint const s = sb->textureUnitEntries[j].sampler;
gld->bindSampler(tmu, s);
}
#endif
}
}
}
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLProgram::updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) noexcept {
assert_invariant(mUniformsRecords);
assert_invariant(buffer);

View File

@@ -19,20 +19,17 @@
#include "DriverBase.h"
#include "BindingMap.h"
#include "OpenGLContext.h"
#include "ShaderCompilerService.h"
#include <private/backend/Driver.h>
#include <backend/DriverEnums.h>
#include <backend/Program.h>
#include <utils/bitset.h>
#include <utils/compiler.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Slice.h>
#include <array>
#include <limits>
#include <stddef.h>
@@ -72,25 +69,32 @@ public:
}
context.useProgram(gl.program);
if (UTILS_UNLIKELY(mUsedBindingsCount)) {
// We rely on GL state tracking to avoid unnecessary glBindTexture / glBindSampler
// calls.
// we need to do this if:
// - the content of mSamplerBindings has changed
// - the content of any bound sampler buffer has changed
// ... since last time we used this program
// Turns out the former might be relatively cheap to check, the latter requires
// a bit less. Compared to what updateSamplers() actually does, which is
// pretty little, I'm not sure if we'll get ahead.
updateSamplers(gld);
}
return true;
}
GLuint getBufferBinding(descriptor_set_t set, descriptor_binding_t binding) const noexcept {
return mBindingMap.get(set, binding);
}
GLuint getTextureUnit(descriptor_set_t set, descriptor_binding_t binding) const noexcept {
return mBindingMap.get(set, binding);
}
utils::bitset64 getActiveDescriptors(descriptor_set_t set) const noexcept {
return mBindingMap.getActiveDescriptors(set);
}
// For ES2 only
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) const noexcept;
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) noexcept;
void setRec709ColorSpace(bool rec709) const noexcept;
struct {
GLuint program = 0;
} gl; // 4 bytes
PushConstantBundle getPushConstants() {
auto fragBegin = mPushConstants.begin() + mPushConstantFragmentStageOffset;
return {
@@ -108,15 +112,22 @@ private:
void initializeProgramState(OpenGLContext& context, GLuint program,
LazyInitializationData& lazyInitializationData) noexcept;
BindingMap mBindingMap; // 8 bytes + out-of-line 256 bytes
void updateSamplers(OpenGLDriver* gld) const noexcept;
// number of bindings actually used by this program
std::array<uint8_t, Program::SAMPLER_BINDING_COUNT> mUsedSamplerBindingPoints; // 4 bytes
ShaderCompilerService::program_token_t mToken{}; // 16 bytes
// Note that this can be replaced with a raw pointer and an uint8_t (for size) to reduce the
// size of the container to 9 bytes if there is a need in the future.
utils::FixedCapacityVector<std::pair<GLint, ConstantType>> mPushConstants;// 16 bytes
uint8_t mUsedBindingsCount = 0u; // 1 byte
UTILS_UNUSED uint8_t padding[2] = {}; // 2 byte
// Push constant array offset for fragment stage constants.
uint8_t mPushConstantFragmentStageOffset = 0u; // 1 byte
// only needed for ES2
GLint mRec709Location = -1; // 4 bytes
using LocationInfo = utils::FixedCapacityVector<GLint>;
struct UniformsRecord {
Program::UniformInfo uniforms;
@@ -124,20 +135,15 @@ private:
mutable GLuint id = 0;
mutable uint16_t age = std::numeric_limits<uint16_t>::max();
};
UniformsRecord const* mUniformsRecords = nullptr;
GLint mRec709Location : 24; // 4 bytes
UniformsRecord const* mUniformsRecords = nullptr; // 8 bytes
// Push constant array offset for fragment stage constants.
GLint mPushConstantFragmentStageOffset : 8; // 1 byte
public:
struct {
GLuint program = 0;
} gl; // 4 bytes
// Note that this can be replaced with a raw pointer and an uint8_t (for size) to reduce the
// size of the container to 9 bytes if there is a need in the future.
utils::FixedCapacityVector<std::pair<GLint, ConstantType>> mPushConstants;// 16 bytes
};
// if OpenGLProgram is larger than 96 bytes, it'll fall in a larger Handle bucket.
static_assert(sizeof(OpenGLProgram) <= 96); // currently 96 bytes
// if OpenGLProgram is larger tha 64 bytes, it'll fall in a larger Handle bucket.
static_assert(sizeof(OpenGLProgram) <= 64); // currently 64 bytes
} // namespace filament::backend

View File

@@ -155,6 +155,10 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
ext.egl.KHR_no_config_context = extensions.has("EGL_KHR_no_config_context");
ext.egl.KHR_surfaceless_context = extensions.has("EGL_KHR_surfaceless_context");
ext.egl.EXT_protected_content = extensions.has("EGL_EXT_protected_content");
if (ext.egl.KHR_create_context) {
// KHR_create_context implies KHR_surfaceless_context for ES3.x contexts
ext.egl.KHR_surfaceless_context = true;
}
eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC) eglGetProcAddress("eglCreateSyncKHR");
eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress("eglDestroySyncKHR");

View File

@@ -410,6 +410,7 @@ io::ostream& operator<<(io::ostream& out, const RasterState& rs) {
io::ostream& operator<<(io::ostream& out, const TargetBufferInfo& tbi) {
return out << "TargetBufferInfo{"
<< "handle=" << tbi.handle
<< ", baseViewIndex=" << tbi.baseViewIndex
<< ", level=" << tbi.level
<< ", layer=" << tbi.layer << "}";
}

View File

@@ -15,7 +15,6 @@
*/
#include "VulkanBlitter.h"
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include "VulkanFboCache.h"
#include "VulkanHandles.h"
@@ -34,10 +33,9 @@ namespace filament::backend {
namespace {
inline void blitFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect, VkFilter filter,
inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, VkFilter filter,
VulkanAttachment src, VulkanAttachment dst,
const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
VkCommandBuffer const cmdbuf = commands->buffer();
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
FVK_LOGD << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
<< " layout=" << src.getLayout()
@@ -51,8 +49,8 @@ inline void blitFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect, V
VulkanLayout oldSrcLayout = src.getLayout();
VulkanLayout oldDstLayout = dst.getLayout();
src.texture->transitionLayout(commands, srcRange, VulkanLayout::TRANSFER_SRC);
dst.texture->transitionLayout(commands, dstRange, VulkanLayout::TRANSFER_DST);
src.texture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
dst.texture->transitionLayout(cmdbuffer, dstRange, VulkanLayout::TRANSFER_DST);
const VkImageBlit blitRegions[1] = {{
.srcSubresource = { aspect, src.level, src.layer, 1 },
@@ -60,7 +58,7 @@ inline void blitFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect, V
.dstSubresource = { aspect, dst.level, dst.layer, 1 },
.dstOffsets = { dstRect[0], dstRect[1] },
}};
vkCmdBlitImage(cmdbuf,
vkCmdBlitImage(cmdbuffer,
src.getImage(), imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC),
dst.getImage(), imgutil::getVkLayout(VulkanLayout::TRANSFER_DST),
1, blitRegions, filter);
@@ -71,13 +69,12 @@ inline void blitFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect, V
if (oldDstLayout == VulkanLayout::UNDEFINED) {
oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage);
}
src.texture->transitionLayout(commands, srcRange, oldSrcLayout);
dst.texture->transitionLayout(commands, dstRange, oldDstLayout);
src.texture->transitionLayout(cmdbuffer, srcRange, oldSrcLayout);
dst.texture->transitionLayout(cmdbuffer, dstRange, oldDstLayout);
}
inline void resolveFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect,
inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
VulkanAttachment src, VulkanAttachment dst) {
VkCommandBuffer const cmdbuffer = commands->buffer();
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
FVK_LOGD << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
<< " layout=" << src.getLayout()
@@ -91,8 +88,8 @@ inline void resolveFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect
VulkanLayout oldSrcLayout = src.getLayout();
VulkanLayout oldDstLayout = dst.getLayout();
src.texture->transitionLayout(commands, srcRange, VulkanLayout::TRANSFER_SRC);
dst.texture->transitionLayout(commands, dstRange, VulkanLayout::TRANSFER_DST);
src.texture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
dst.texture->transitionLayout(cmdbuffer, dstRange, VulkanLayout::TRANSFER_DST);
assert_invariant(
aspect != VK_IMAGE_ASPECT_DEPTH_BIT && "Resolve with depth is not yet supported.");
@@ -114,8 +111,8 @@ inline void resolveFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect
if (oldDstLayout == VulkanLayout::UNDEFINED) {
oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage);
}
src.texture->transitionLayout(commands, srcRange, oldSrcLayout);
dst.texture->transitionLayout(commands, dstRange, oldDstLayout);
src.texture->transitionLayout(cmdbuffer, srcRange, oldSrcLayout);
dst.texture->transitionLayout(cmdbuffer, dstRange, oldDstLayout);
}
struct BlitterUniforms {
@@ -152,9 +149,10 @@ void VulkanBlitter::resolve(VulkanAttachment dst, VulkanAttachment src) {
#endif
VulkanCommandBuffer& commands = mCommands->get();
VkCommandBuffer const cmdbuffer = commands.buffer();
commands.acquire(src.texture);
commands.acquire(dst.texture);
resolveFast(&commands, aspect, src, dst);
resolveFast(cmdbuffer, aspect, src, dst);
}
void VulkanBlitter::blit(VkFilter filter,
@@ -177,9 +175,10 @@ void VulkanBlitter::blit(VkFilter filter,
// src and dst should have the same aspect here
VkImageAspectFlags const aspect = src.texture->getImageAspect();
VulkanCommandBuffer& commands = mCommands->get();
VkCommandBuffer const cmdbuffer = commands.buffer();
commands.acquire(src.texture);
commands.acquire(dst.texture);
blitFast(&commands, aspect, filter, src, dst, srcRectPair, dstRectPair);
blitFast(cmdbuffer, aspect, filter, src, dst, srcRectPair, dstRectPair);
}
void VulkanBlitter::terminate() noexcept {

View File

@@ -297,11 +297,11 @@ bool VulkanCommands::flush() {
#endif
auto& cmdfence = currentbuf->fence;
UTILS_UNUSED_IN_RELEASE VkResult result = VK_SUCCESS;
{
auto scope = cmdfence->setValue(VK_NOT_READY);
result = vkQueueSubmit(mQueue, 1, &submitInfo, cmdfence->getFence());
}
std::unique_lock<utils::Mutex> lock(cmdfence->mutex);
cmdfence->status.store(VK_NOT_READY);
UTILS_UNUSED_IN_RELEASE VkResult result = vkQueueSubmit(mQueue, 1, &submitInfo, cmdfence->fence);
cmdfence->condition.notify_all();
lock.unlock();
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
if (result != VK_SUCCESS) {
@@ -340,7 +340,7 @@ void VulkanCommands::wait() {
auto wrapper = mStorage[i].get();
if (wrapper->buffer() != VK_NULL_HANDLE
&& mCurrentCommandBufferIndex != static_cast<int8_t>(i)) {
fences[count++] = wrapper->fence->getFence();
fences[count++] = wrapper->fence->fence;
}
}
if (count > 0) {
@@ -361,13 +361,12 @@ void VulkanCommands::gc() {
if (wrapper->buffer() == VK_NULL_HANDLE) {
continue;
}
auto const vkfence = wrapper->fence->getFence();
VkResult const result = vkGetFenceStatus(mDevice, vkfence);
VkResult const result = vkGetFenceStatus(mDevice, wrapper->fence->fence);
if (result != VK_SUCCESS) {
continue;
}
fences[count++] = vkfence;
wrapper->fence->setValue(VK_SUCCESS);
fences[count++] = wrapper->fence->fence;
wrapper->fence->status.store(VK_SUCCESS);
wrapper->reset();
mAvailableBufferCount++;
}
@@ -384,9 +383,9 @@ void VulkanCommands::updateFences() {
if (wrapper->buffer() != VK_NULL_HANDLE) {
VulkanCmdFence* fence = wrapper->fence.get();
if (fence) {
VkResult status = vkGetFenceStatus(mDevice, fence->getFence());
VkResult status = vkGetFenceStatus(mDevice, fence->fence);
// This is either VK_SUCCESS, VK_NOT_READY, or VK_ERROR_DEVICE_LOST.
fence->setValue(status);
fence->status.store(status, std::memory_order_relaxed);
}
}
}

View File

@@ -61,40 +61,8 @@ private:
// Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences.
struct VulkanCmdFence {
struct SetValueScope {
public:
~SetValueScope() {
mHolder->mutex.unlock();
mHolder->condition.notify_all();
}
private:
SetValueScope(VulkanCmdFence* fenceHolder, VkResult result) :
mHolder(fenceHolder) {
mHolder->mutex.lock();
mHolder->status.store(result);
}
VulkanCmdFence* mHolder;
friend struct VulkanCmdFence;
};
VulkanCmdFence(VkFence ifence);
~VulkanCmdFence() = default;
SetValueScope setValue(VkResult value) {
return {this, value};
}
VkFence& getFence() {
return fence;
}
VkResult getStatus() {
std::unique_lock<utils::Mutex> lock(mutex);
return status.load(std::memory_order_acquire);
}
private:
VkFence fence;
utils::Condition condition;
utils::Mutex mutex;

View File

@@ -43,6 +43,7 @@ struct VulkanCommandBuffer;
struct VulkanAttachment {
VulkanTexture* texture = nullptr;
uint8_t level = 0;
uint8_t baseViewIndex = 0;
uint8_t layerCount = 1;
uint16_t layer = 0;

View File

@@ -90,9 +90,9 @@ VmaAllocator createAllocator(VkInstance instance, VkPhysicalDevice physicalDevic
VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator, VulkanStagePool& stagePool) {
VulkanStagePool& stagePool) {
VulkanTexture* emptyTexture = new VulkanTexture(device, physicalDevice, context, allocator,
commands, handleAllocator, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1,
commands, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1,
TextureUsage::DEFAULT | TextureUsage::COLOR_ATTACHMENT | TextureUsage::SUBPASS_INPUT,
stagePool, true /* heap allocated */);
uint32_t black = 0;
@@ -149,7 +149,6 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFla
}
#endif // FVK_EANBLED(FVK_DEBUG_DEBUG_UTILS)
}// anonymous namespace
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
@@ -257,11 +256,15 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
mTimestamps = std::make_unique<VulkanTimestamps>(mPlatform->getDevice());
mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(),
mContext, mAllocator, &mCommands, &mResourceAllocator, mStagePool);
mContext, mAllocator, &mCommands, mStagePool);
mEmptyBufferObject = createEmptyBufferObject(mAllocator, mStagePool, &mCommands);
mDescriptorSetManager.setPlaceHolders(mSamplerCache.getSampler({}), mEmptyTexture,
mEmptyBufferObject);
mGetPipelineFunction = [this](VulkanDescriptorSetLayoutList const& layouts, VulkanProgram* program) {
return mPipelineLayoutCache.getLayout(layouts, program);
};
}
VulkanDriver::~VulkanDriver() noexcept = default;
@@ -384,6 +387,7 @@ void VulkanDriver::collectGarbage() {
mStagePool.gc();
mFramebufferCache.gc();
mPipelineCache.gc();
mDescriptorSetManager.gc();
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
mResourceAllocator.print();
@@ -418,36 +422,6 @@ void VulkanDriver::endFrame(uint32_t frameId) {
FVK_SYSTRACE_END();
}
void VulkanDriver::updateDescriptorSetBuffer(
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
backend::BufferObjectHandle boh,
uint32_t offset,
uint32_t size) {
VulkanDescriptorSet* set = mResourceAllocator.handle_cast<VulkanDescriptorSet*>(dsh);
VulkanBufferObject* obj = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
mDescriptorSetManager.updateBuffer(set, binding, obj, offset, size);
}
void VulkanDriver::updateDescriptorSetTexture(
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
backend::TextureHandle th,
SamplerParams params) {
VulkanDescriptorSet* set = mResourceAllocator.handle_cast<VulkanDescriptorSet*>(dsh);
VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(th);
// We need to make sure the initial layout transition has been completed before we can write
// the sampler descriptor. We flush and wait until the transition has been completed.
if (!texture->transitionReady()) {
mCommands.flush();
mCommands.wait();
}
VkSampler const vksampler = mSamplerCache.getSampler(params);
mDescriptorSetManager.updateSampler(set, binding, texture, vksampler);
}
void VulkanDriver::flush(int) {
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("flush");
@@ -466,6 +440,12 @@ void VulkanDriver::finish(int dummy) {
FVK_SYSTRACE_END();
}
void VulkanDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t count,
utils::FixedSizeString<32> debugName) {
auto sg = mResourceAllocator.construct<VulkanSamplerGroup>(sbh, count);
mResourceManager.acquire(sg);
}
void VulkanDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
PrimitiveType pt) {
@@ -548,56 +528,23 @@ void VulkanDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage usage) {
auto vktexture = mResourceAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator,
target, levels,
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, target, levels,
format, samples, w, h, depth, usage, mStagePool);
mResourceManager.acquire(vktexture);
}
//void VulkanDriver::createTextureSwizzledR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
// TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
// TextureUsage usage,
// TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) {
// TextureSwizzle swizzleArray[] = {r, g, b, a};
// const VkComponentMapping swizzleMap = getSwizzleMap(swizzleArray);
// auto vktexture = mResourceAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
// mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator,
// target, levels, format, samples, w, h, depth, usage, mStagePool,
// false /*heap allocated */, swizzleMap);
// mResourceManager.acquire(vktexture);
//}
void VulkanDriver::createTextureViewR(Handle<HwTexture> th, Handle<HwTexture> srch,
uint8_t baseLevel, uint8_t levelCount) {
VulkanTexture const* src = mResourceAllocator.handle_cast<VulkanTexture const*>(srch);
void VulkanDriver::createTextureSwizzledR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage usage,
TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) {
TextureSwizzle swizzleArray[] = {r, g, b, a};
const VkComponentMapping swizzleMap = getSwizzleMap(swizzleArray);
auto vktexture = mResourceAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator,
src, baseLevel, levelCount);
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, target, levels,
format, samples, w, h, depth, usage, mStagePool, false /*heap allocated */, swizzleMap);
mResourceManager.acquire(vktexture);
}
void VulkanDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwTexture> srch,
backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b,
backend::TextureSwizzle a) {
TextureSwizzle const swizzleArray[] = {r, g, b, a};
VkComponentMapping const swizzle = getSwizzleMap(swizzleArray);
VulkanTexture const* src = mResourceAllocator.handle_cast<VulkanTexture const*>(srch);
auto vktexture = mResourceAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator,
src, swizzle);
mResourceManager.acquire(vktexture);
}
void VulkanDriver::createTextureExternalImageR(Handle<HwTexture> th, backend::TextureFormat format,
uint32_t width, uint32_t height, backend::TextureUsage usage, void* image) {
}
void VulkanDriver::createTextureExternalImagePlaneR(Handle<HwTexture> th,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
void* image, uint32_t plane) {
}
void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
@@ -624,6 +571,7 @@ void VulkanDriver::destroyProgram(Handle<HwProgram> ph) {
return;
}
auto vkprogram = mResourceAllocator.handle_cast<VulkanProgram*>(ph);
mDescriptorSetManager.clearProgram(vkprogram);
mResourceManager.release(vkprogram);
}
@@ -647,6 +595,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
colorTargets[i] = {
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(color[i].handle),
.level = color[i].level,
.baseViewIndex = color[i].baseViewIndex,
.layerCount = layerCount,
.layer = color[i].layer,
};
@@ -662,6 +611,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
depthStencil[0] = {
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(depth.handle),
.level = depth.level,
.baseViewIndex = depth.baseViewIndex,
.layerCount = layerCount,
.layer = depth.layer,
};
@@ -675,6 +625,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
depthStencil[1] = {
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(stencil.handle),
.level = stencil.level,
.baseViewIndex = stencil.baseViewIndex,
.layerCount = layerCount,
.layer = stencil.layer,
};
@@ -690,10 +641,9 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
assert_invariant(tmin == tmax);
assert_invariant(tmin.x >= width && tmin.y >= height);
auto renderTarget = mResourceAllocator.construct<VulkanRenderTarget>(rth,
mPlatform->getDevice(), mPlatform->getPhysicalDevice(), mContext, mAllocator,
&mCommands, &mResourceAllocator, width, height, samples, colorTargets, depthStencil,
mStagePool, layerCount);
auto renderTarget = mResourceAllocator.construct<VulkanRenderTarget>(rth, mPlatform->getDevice(),
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, width, height,
samples, colorTargets, depthStencil, mStagePool, layerCount);
mResourceManager.acquire(renderTarget);
}
@@ -721,7 +671,7 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
}
auto swapChain = mResourceAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext,
mAllocator, &mCommands, &mResourceAllocator, mStagePool, nativeWindow, flags);
mAllocator, &mCommands, mStagePool, nativeWindow, flags);
mResourceManager.acquire(swapChain);
}
@@ -734,8 +684,7 @@ void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t wi
}
assert_invariant(width > 0 && height > 0 && "Vulkan requires non-zero swap chain dimensions.");
auto swapChain = mResourceAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext,
mAllocator, &mCommands, &mResourceAllocator, mStagePool,
nullptr, flags, VkExtent2D{width, height});
mAllocator, &mCommands, mStagePool, nullptr, flags, VkExtent2D{width, height});
mResourceManager.acquire(swapChain);
}
@@ -743,25 +692,6 @@ void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
// nothing to do, timer query was constructed in createTimerQueryS
}
void VulkanDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
backend::DescriptorSetLayout&& info) {
VulkanDescriptorSetLayout* layout = mResourceAllocator.construct<VulkanDescriptorSetLayout>(
dslh, info);
// This will create a VkDescriptorSetLayout (which is cached) for this object.
mDescriptorSetManager.initVkLayout(layout);
mResourceManager.acquire(layout);
}
void VulkanDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
Handle<HwDescriptorSetLayout> dslh) {
auto layout = mResourceAllocator.handle_cast<VulkanDescriptorSetLayout*>(dslh);
mDescriptorSetManager.createSet(dsh, layout);
auto set = mResourceAllocator.handle_cast<VulkanDescriptorSet*>(dsh);
mResourceManager.acquire(set);
}
Handle<HwVertexBufferInfo> VulkanDriver::createVertexBufferInfoS() noexcept {
return mResourceAllocator.allocHandle<VulkanVertexBufferInfo>();
}
@@ -782,19 +712,7 @@ Handle<HwTexture> VulkanDriver::createTextureS() noexcept {
return mResourceAllocator.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureViewS() noexcept {
return mResourceAllocator.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureViewSwizzleS() noexcept {
return mResourceAllocator.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureExternalImageS() noexcept {
return mResourceAllocator.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureExternalImagePlaneS() noexcept {
Handle<HwTexture> VulkanDriver::createTextureSwizzledS() noexcept {
return mResourceAllocator.allocHandle<VulkanTexture>();
}
@@ -802,6 +720,10 @@ Handle<HwTexture> VulkanDriver::importTextureS() noexcept {
return mResourceAllocator.allocHandle<VulkanTexture>();
}
Handle<HwSamplerGroup> VulkanDriver::createSamplerGroupS() noexcept {
return mResourceAllocator.allocHandle<VulkanSamplerGroup>();
}
Handle<HwRenderPrimitive> VulkanDriver::createRenderPrimitiveS() noexcept {
return mResourceAllocator.allocHandle<VulkanRenderPrimitive>();
}
@@ -840,12 +762,21 @@ Handle<HwTimerQuery> VulkanDriver::createTimerQueryS() noexcept {
return tqh;
}
Handle<HwDescriptorSetLayout> VulkanDriver::createDescriptorSetLayoutS() noexcept {
return mResourceAllocator.allocHandle<VulkanDescriptorSetLayout>();
}
Handle<HwDescriptorSet> VulkanDriver::createDescriptorSetS() noexcept {
return mResourceAllocator.allocHandle<VulkanDescriptorSet>();
void VulkanDriver::destroySamplerGroup(Handle<HwSamplerGroup> sbh) {
if (!sbh) {
return;
}
// Unlike most of the other "Hw" handles, the sampler buffer is an abstract concept and does
// not map to any Vulkan objects. To handle destruction, the only thing we need to do is
// ensure that the next draw call doesn't try to access a zombie sampler buffer. Therefore,
// simply replace all weak references with null.
auto* hwsb = mResourceAllocator.handle_cast<VulkanSamplerGroup*>(sbh);
for (auto& binding : mSamplerBindings) {
if (binding == hwsb) {
binding = nullptr;
}
}
mResourceManager.release(hwsb);
}
void VulkanDriver::destroySwapChain(Handle<HwSwapChain> sch) {
@@ -870,17 +801,6 @@ void VulkanDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
mThreadSafeResourceManager.release(vtq);
}
void VulkanDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> dslh) {
VulkanDescriptorSetLayout* layout = mResourceAllocator.handle_cast<VulkanDescriptorSetLayout*>(dslh);
mResourceManager.release(layout);
}
void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
mDescriptorSetManager.destroySet(dsh);
VulkanDescriptorSet* set = mResourceAllocator.handle_cast<VulkanDescriptorSet*>(dsh);
mResourceManager.release(set);
}
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream) {
return {};
}
@@ -918,7 +838,8 @@ FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> fh) {
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted".
// When this fence gets submitted, its status changes to VK_NOT_READY.
if (cmdfence->getStatus() == VK_SUCCESS) {
std::unique_lock<utils::Mutex> lock(cmdfence->mutex);
if (cmdfence->status.load() == VK_SUCCESS) {
return FenceStatus::CONDITION_SATISFIED;
}
@@ -969,9 +890,7 @@ bool VulkanDriver::isRenderTargetFormatSupported(TextureFormat format) {
}
bool VulkanDriver::isFrameBufferFetchSupported() {
// TODO: we must fix this before landing descriptor set change. Otherwise, the scuba tests will fail.
//return true;
return false;
return true;
}
bool VulkanDriver::isFrameBufferFetchMultiSampleSupported() {
@@ -1159,6 +1078,10 @@ void VulkanDriver::resetBufferObject(Handle<HwBufferObject> boh) {
// This is only useful if updateBufferObjectUnsynchronized() is implemented unsynchronizedly.
}
void VulkanDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint32_t maxLevel) {
mResourceAllocator.handle_cast<VulkanTexture*>(th)->setPrimaryRange(minLevel, maxLevel);
}
void VulkanDriver::update3DImage(Handle<HwTexture> th, uint32_t level, uint32_t xoffset,
uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth,
PixelBufferDescriptor&& data) {
@@ -1244,6 +1167,23 @@ void VulkanDriver::generateMipmaps(Handle<HwTexture> th) {
srcw = dstw;
srch = dsth;
} while ((srcw > 1 || srch > 1) && level < t->levels);
t->setPrimaryRange(0, t->levels - 1);
}
void VulkanDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
BufferDescriptor&& data) {
auto* sb = mResourceAllocator.handle_cast<VulkanSamplerGroup*>(sbh);
// FIXME: we shouldn't be using SamplerGroup here, instead the backend should create
// a descriptor or any internal data-structure that represents the textures/samplers.
// It's preferable to do as much work as possible here.
// Here, we emulate the older backend API by re-creating a SamplerGroup from the
// passed data.
SamplerGroup samplerGroup(data.size / sizeof(SamplerDescriptor));
memcpy(samplerGroup.data(), data.buffer, data.size);
*sb->sb = std::move(samplerGroup);
scheduleDestroy(std::move(data));
}
void VulkanDriver::compilePrograms(CompilerPriorityQueue priority,
@@ -1258,7 +1198,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
FVK_SYSTRACE_START("beginRenderPass");
VulkanRenderTarget* const rt = mResourceAllocator.handle_cast<VulkanRenderTarget*>(rth);
VkExtent2D const extent = rt->getExtent();
const VkExtent2D extent = rt->getExtent();
assert_invariant(rt == mDefaultRenderTarget || extent.width > 0 && extent.height > 0);
// Filament has the expectation that the contents of the swap chain are not preserved on the
@@ -1289,10 +1229,34 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
VulkanCommandBuffer& commands = mCommands.get();
VkCommandBuffer const cmdbuffer = commands.buffer();
// Scissor is reset with each render pass
// This also takes care of VUID-vkCmdDrawIndexed-None-07832.
VkRect2D const scissor{ .offset = { 0, 0 }, .extent = extent };
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
UTILS_NOUNROLL
for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::SAMPLER_BINDING_COUNT;
samplerGroupIdx++) {
VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupIdx];
if (!vksb) {
continue;
}
SamplerGroup* sb = vksb->sb.get();
for (size_t i = 0; i < sb->getSize(); i++) {
SamplerDescriptor const* boundSampler = sb->data() + i;
if (UTILS_LIKELY(boundSampler->t)) {
VulkanTexture* texture
= mResourceAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
if (!any(texture->usage & TextureUsage::DEPTH_ATTACHMENT)) {
continue;
}
if (texture->getPrimaryImageLayout() == VulkanLayout::DEPTH_SAMPLER) {
continue;
}
commands.acquire(texture);
// Transition the primary view, which is the sampler's view into the right layout.
texture->transitionLayout(cmdbuffer, texture->getPrimaryViewRange(),
VulkanLayout::DEPTH_SAMPLER);
break;
}
}
}
VulkanLayout currentDepthLayout = depth.getLayout();
@@ -1306,15 +1270,18 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
// If the depth attachment texture was previously sampled, then we need to manually
// transition it to an attachment. This is necessary to also set up a barrier between the
// previous read and the potentially coming write.
depth.texture->transitionLayout(&commands, depth.getSubresourceRange(),
VulkanLayout::DEPTH_ATTACHMENT);
currentDepthLayout = VulkanLayout::DEPTH_ATTACHMENT;
if (currentDepthLayout == VulkanLayout::DEPTH_SAMPLER) {
depth.texture->transitionLayout(cmdbuffer, depth.getSubresourceRange(),
VulkanLayout::DEPTH_ATTACHMENT);
currentDepthLayout = VulkanLayout::DEPTH_ATTACHMENT;
}
}
uint8_t const renderTargetLayerCount = rt->getLayerCount();
// Create the VkRenderPass or fetch it from cache.
VulkanFboCache::RenderPassKey rpkey = {
.initialColorLayoutMask = 0,
.initialDepthLayout = currentDepthLayout,
.depthFormat = depth.getFormat(),
.clear = clearVal,
@@ -1325,13 +1292,19 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
.viewCount = renderTargetLayerCount,
};
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
VulkanAttachment const& info = rt->getColor(i);
const VulkanAttachment& info = rt->getColor(i);
if (info.texture) {
assert_invariant(info.layerCount == renderTargetLayerCount);
rpkey.initialColorLayoutMask |= 1 << i;
rpkey.colorFormat[i] = info.getFormat();
if (rpkey.samples > 1 && info.texture->samples == 1) {
rpkey.needsResolveMask |= (1 << i);
}
if (info.texture->getPrimaryImageLayout() != VulkanLayout::COLOR_ATTACHMENT) {
((VulkanTexture*) info.texture)
->transitionLayout(cmdbuffer, info.getSubresourceRange(),
VulkanLayout::COLOR_ATTACHMENT);
}
} else {
rpkey.colorFormat[i] = VK_FORMAT_UNDEFINED;
}
@@ -1350,45 +1323,28 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
};
auto& renderPassAttachments = mRenderPassFboInfo.attachments;
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
VulkanAttachment& attachment = rt->getColor(i);
if (!attachment.texture) {
if (!rt->getColor(i).texture) {
fbkey.color[i] = VK_NULL_HANDLE;
fbkey.resolve[i] = VK_NULL_HANDLE;
continue;
}
if (fbkey.samples == 1) {
auto const& range = attachment.getSubresourceRange();
auto tex = attachment.texture;
if (tex->getLayout(range.baseMipLevel, range.baseArrayLayer) !=
VulkanLayout::COLOR_ATTACHMENT &&
!tex->transitionLayout(&commands, range, VulkanLayout::COLOR_ATTACHMENT)) {
// If the layout transition did not emit a barrier, we do it manually here.
tex->samplerToAttachmentBarrier(&commands, range);
}
renderPassAttachments.insert(attachment);
fbkey.color[i] = attachment.getImageView();
} else if (fbkey.samples == 1) {
auto& colorAttachment = rt->getColor(i);
renderPassAttachments.insert(colorAttachment);
fbkey.color[i] = colorAttachment.getImageView();
fbkey.resolve[i] = VK_NULL_HANDLE;
assert_invariant(fbkey.color[i]);
} else {
auto& msaaColorAttachment = rt->getMsaaColor(i);
auto const& msaaRange = attachment.getSubresourceRange();
msaaColorAttachment.texture->transitionLayout(&commands,
msaaRange, VulkanLayout::COLOR_ATTACHMENT);
renderPassAttachments.insert(msaaColorAttachment);
auto& colorAttachment = rt->getColor(i);
fbkey.color[i] = msaaColorAttachment.getImageView();
VulkanTexture* texture = attachment.texture;
VulkanTexture* texture = colorAttachment.texture;
if (texture->samples == 1) {
mRenderPassFboInfo.hasColorResolve = true;
auto const& range = attachment.getSubresourceRange();
attachment.texture->transitionLayout(&commands,
range, VulkanLayout::COLOR_ATTACHMENT);
renderPassAttachments.insert(attachment);
fbkey.resolve[i] = attachment.getImageView();
renderPassAttachments.insert(colorAttachment);
fbkey.resolve[i] = colorAttachment.getImageView();
assert_invariant(fbkey.resolve[i]);
}
assert_invariant(fbkey.color[i]);
@@ -1506,27 +1462,57 @@ void VulkanDriver::endRenderPass(int) {
assert_invariant(rt);
// Since we might soon be sampling from the render target that we just wrote to, we need a
// pipeline barrier between framebuffer writes and shader reads.
// pipeline barrier between framebuffer writes and shader reads. This is a memory barrier rather
// than an image barrier. If we were to use image barriers here, we would potentially need to
// issue several of them when considering MRT. This would be very complex to set up and would
// require more state tracking, so we've chosen to use a memory barrier for simplicity and
// correctness.
if (!rt->isSwapChain()) {
for (auto& attachment: mRenderPassFboInfo.attachments) {
auto const& range = attachment.getSubresourceRange();
for (auto const& attachment: mRenderPassFboInfo.attachments) {
bool const isDepth = attachment.isDepth();
auto texture = attachment.texture;
if (isDepth) {
texture->setLayout(range, VulkanFboCache::FINAL_DEPTH_ATTACHMENT_LAYOUT);
if (!texture->transitionLayout(&commands, range, VulkanLayout::DEPTH_SAMPLER)) {
texture->attachmentToSamplerBarrier(&commands, range);
}
} else {
texture->setLayout(range, VulkanFboCache::FINAL_COLOR_ATTACHMENT_LAYOUT);
if (!texture->transitionLayout(&commands, range, VulkanLayout::READ_WRITE)) {
texture->attachmentToSamplerBarrier(&commands, range);
}
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
// This is a workaround around a validation issue (might not be an actual driver issue).
if (mRenderPassFboInfo.hasColorResolve && !isDepth) {
srcStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
}
VkPipelineStageFlags dstStageMask =
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
VkAccessFlags srcAccess = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkAccessFlags dstAccess = VK_ACCESS_SHADER_READ_BIT;
VulkanLayout layout = VulkanFboCache::FINAL_COLOR_ATTACHMENT_LAYOUT;
if (isDepth) {
srcAccess = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
dstAccess = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
layout = VulkanFboCache::FINAL_DEPTH_ATTACHMENT_LAYOUT;
}
auto const vkLayout = imgutil::getVkLayout(layout);
auto const& range = attachment.getSubresourceRange();
VkImageMemoryBarrier barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = srcAccess,
.dstAccessMask = dstAccess,
.oldLayout = vkLayout,
.newLayout = vkLayout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = attachment.getImage(),
.subresourceRange = range,
};
attachment.texture->setLayout(range, layout);
vkCmdPipelineBarrier(cmdbuffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr,
1, &barrier);
}
}
mRenderPassFboInfo = {};
mRenderPassFboInfo.clear();
mDescriptorSetManager.clearState();
mCurrentRenderPass.renderTarget = nullptr;
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
FVK_SYSTRACE_END();
@@ -1585,6 +1571,33 @@ void VulkanDriver::commit(Handle<HwSwapChain> sch) {
FVK_SYSTRACE_END();
}
void VulkanDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> boh) {
auto* bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
VkDeviceSize const offset = 0;
VkDeviceSize const size = VK_WHOLE_SIZE;
mDescriptorSetManager.updateBuffer({}, (uint32_t) index, bo, offset, size);
}
void VulkanDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index,
Handle<HwBufferObject> boh, uint32_t offset, uint32_t size) {
assert_invariant(bindingType == BufferObjectBinding::UNIFORM);
// TODO: implement BufferObjectBinding::SHADER_STORAGE case
auto* bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
mDescriptorSetManager.updateBuffer({}, (uint32_t) index, bo, offset, size);
}
void VulkanDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
mDescriptorSetManager.clearBuffer((uint32_t) index);
}
void VulkanDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
auto* hwsb = mResourceAllocator.handle_cast<VulkanSamplerGroup*>(sbh);
mSamplerBindings[index] = hwsb;
}
void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
backend::PushConstantVariant value) {
assert_invariant(mBoundPipeline.program && "Expect a program when writing to push constants");
@@ -1785,8 +1798,8 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
*mResourceAllocator.handle_cast<VulkanVertexBufferInfo*>(pipelineState.vertexBufferInfo);
Handle<HwProgram> programHandle = pipelineState.program;
RasterState const& rasterState = pipelineState.rasterState;
PolygonOffset const& depthOffset = pipelineState.polygonOffset;
RasterState rasterState = pipelineState.rasterState;
PolygonOffset depthOffset = pipelineState.polygonOffset;
auto* program = mResourceAllocator.handle_cast<VulkanProgram*>(programHandle);
commands->acquire(program);
@@ -1830,31 +1843,69 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
mPipelineCache.bindPrimitiveTopology(topology);
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, vbi.getAttributeCount());
auto& setLayouts = pipelineState.pipelineLayout.setLayout;
VulkanDescriptorSetLayout::DescriptorSetLayoutArray layoutList;
uint8_t layoutCount = 0;
std::transform(setLayouts.begin(), setLayouts.end(), layoutList.begin(),
[&](Handle<HwDescriptorSetLayout> handle) -> VkDescriptorSetLayout {
if (!handle) {
return VK_NULL_HANDLE;
}
auto layout = mResourceAllocator.handle_cast<VulkanDescriptorSetLayout*>(handle);
layoutCount++;
return layout->getVkLayout();
});
auto pipelineLayout = mPipelineLayoutCache.getLayout(layoutList, program);
// Query the program for the mapping from (SamplerGroupBinding,Offset) to (SamplerBinding),
// where "SamplerBinding" is the integer in the GLSL, and SamplerGroupBinding is the abstract
// Filament concept used to form groups of samplers.
constexpr uint8_t descriptorSetMaskTable[4] = {0x1, 0x3, 0x7, 0xF};
auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex();
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
auto const& bindingToName = program->getBindingToName();
#endif
for (auto binding: program->getBindings()) {
uint16_t const indexPair = bindingToSamplerIndex[binding];
if (indexPair == 0xffff) {
continue;
}
uint16_t const samplerGroupInd = (indexPair >> 8) & 0xff;
uint16_t const samplerInd = (indexPair & 0xff);
VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupInd];
if (!vksb) {
continue;
}
SamplerDescriptor const* boundSampler = ((SamplerDescriptor*) vksb->sb->data()) + samplerInd;
if (UTILS_UNLIKELY(!boundSampler->t)) {
continue;
}
VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
// TODO: can this uninitialized check be checked in a higher layer?
// This fallback path is very flaky because the dummy texture might not have
// matching characteristics. (e.g. if the missing texture is a 3D texture)
if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) {
#if FVK_ENABLED(FVK_DEBUG_TEXTURE) && FVK_ENABLED_DEBUG_SAMPLER_NAME
FVK_LOGW << "Uninitialized texture bound to '" << bindingToName[binding] << "'";
FVK_LOGW << " in material '" << program->name.c_str() << "'";
FVK_LOGW << " at binding point " << +binding << utils::io::endl;
#endif
texture = mEmptyTexture;
}
VkSampler const vksampler = mSamplerCache.getSampler(boundSampler->s);
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
VulkanDriver::DebugUtils::setName(VK_OBJECT_TYPE_SAMPLER,
reinterpret_cast<uint64_t>(vksampler), bindingToName[binding].c_str());
#endif
mDescriptorSetManager.updateSampler({}, binding, texture, vksampler);
}
auto const pipelineLayout = mDescriptorSetManager.bind(commands, program, mGetPipelineFunction);
mBoundPipeline = {
.program = program,
.pipelineLayout = pipelineLayout,
.descriptorSetMask = DescriptorSetMask(descriptorSetMaskTable[layoutCount]),
};
mPipelineCache.bindLayout(pipelineLayout);
mPipelineCache.bindPipeline(commands);
// Since we don't statically define scissor as part of the pipeline, we need to call scissor at
// least once. Context: VUID-vkCmdDrawIndexed-None-07832.
auto const& extent = rt->getExtent();
scissor({0, 0, extent.width, extent.height});
FVK_SYSTRACE_END();
}
@@ -1887,14 +1938,6 @@ void VulkanDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
FVK_SYSTRACE_END();
}
void VulkanDriver::bindDescriptorSet(
backend::DescriptorSetHandle dsh,
backend::descriptor_set_t setIndex,
backend::DescriptorSetOffsetArray&& offsets) {
VulkanDescriptorSet* set = mResourceAllocator.handle_cast<VulkanDescriptorSet*>(dsh);
mDescriptorSetManager.bind(setIndex, set, std::move(offsets));
}
void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("draw2");
@@ -1902,8 +1945,8 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
VulkanCommandBuffer& commands = mCommands.get();
VkCommandBuffer cmdbuffer = commands.buffer();
mDescriptorSetManager.commit(&commands, mBoundPipeline.pipelineLayout,
mBoundPipeline.descriptorSetMask);
// Bind "dynamic" UBOs if they need to change.
mDescriptorSetManager.dynamicBind(&commands, {});
// Finally, make the actual draw call. TODO: support subranges
const uint32_t firstIndex = indexOffset;
@@ -1933,9 +1976,6 @@ void VulkanDriver::scissor(Viewport scissorBox) {
VulkanCommandBuffer& commands = mCommands.get();
VkCommandBuffer cmdbuffer = commands.buffer();
// TODO: it's a common case that scissor() is called with (0, 0, maxint, maxint)
// we should maybe have a fast path for this and avoid vkCmdSetScissor() if possible
// Set scissoring.
// clamp left-bottom to 0,0 and avoid overflows
constexpr int32_t maxvali = std::numeric_limits<int32_t>::max();
@@ -1954,7 +1994,7 @@ void VulkanDriver::scissor(Viewport scissorBox) {
.extent = { uint32_t(r - l), uint32_t(t - b) }
};
VulkanRenderTarget const* rt = mCurrentRenderPass.renderTarget;
const VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
rt->transformClientRectToPlatform(&scissor);
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
}

View File

@@ -47,6 +47,21 @@ struct VulkanSamplerGroup;
constexpr uint8_t MAX_RENDERTARGET_ATTACHMENT_TEXTURES =
MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT * 2 + 1;
// We need to store information about a render pass to enable better barriers at the end of a
// renderpass.
struct RenderPassFboBundle {
using AttachmentArray =
CappedArray<VulkanAttachment, MAX_RENDERTARGET_ATTACHMENT_TEXTURES>;
AttachmentArray attachments;
bool hasColorResolve = false;
void clear() {
attachments.clear();
hasColorResolve = false;
}
};
class VulkanDriver final : public DriverBase {
public:
static Driver* create(VulkanPlatform* platform, VulkanContext const& context,
@@ -144,20 +159,15 @@ private:
VulkanReadPixels mReadPixels;
VulkanDescriptorSetManager mDescriptorSetManager;
VulkanDescriptorSetManager::GetPipelineLayoutFunction mGetPipelineFunction;
// This is necessary for us to write to push constants after binding a pipeline.
struct {
struct BoundPipeline {
VulkanProgram* program;
VkPipelineLayout pipelineLayout;
DescriptorSetMask descriptorSetMask;
} mBoundPipeline = {};
// We need to store information about a render pass to enable better barriers at the end of a
// renderpass.
struct {
using AttachmentArray = CappedArray<VulkanAttachment, MAX_RENDERTARGET_ATTACHMENT_TEXTURES>;
AttachmentArray attachments;
bool hasColorResolve = false;
} mRenderPassFboInfo = {};
};
BoundPipeline mBoundPipeline = {};
RenderPassFboBundle mRenderPassFboInfo;
bool const mIsSRGBSwapChainSupported;
backend::StereoscopicType const mStereoscopicType;

View File

@@ -31,6 +31,7 @@ namespace filament::backend {
bool VulkanFboCache::RenderPassEq::operator()(const RenderPassKey& k1,
const RenderPassKey& k2) const {
if (k1.initialColorLayoutMask != k2.initialColorLayoutMask) return false;
if (k1.initialDepthLayout != k2.initialDepthLayout) return false;
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (k1.colorFormat[i] != k2.colorFormat[i]) return false;
@@ -257,7 +258,9 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
.storeOp = kEnableStore,
.stencilLoadOp = kDontCare,
.stencilStoreOp = kDisableStore,
.initialLayout = imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT),
.initialLayout = ((!discard && config.initialColorLayoutMask & (1 << i)) || clear)
? imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT)
: imgutil::getVkLayout(VulkanLayout::UNDEFINED),
.finalLayout = imgutil::getVkLayout(FINAL_COLOR_ATTACHMENT_LAYOUT),
};
}

View File

@@ -42,8 +42,19 @@ public:
// RenderPassKey is a small POD representing the immutable state that is used to construct
// a VkRenderPass. It is hashed and used as a lookup key.
struct alignas(8) RenderPassKey {
VulkanLayout initialDepthLayout;
uint8_t padding[3] = {};
// For each target, we need to know three image layouts: the layout BEFORE the pass, the
// layout DURING the pass, and the layout AFTER the pass. Here are the rules:
// - For depth, we explicitly specify all three layouts.
// - Color targets have their initial image layout specified with a bitmask.
// - For each color target, the pre-existing layout is either UNDEFINED (0) or GENERAL (1).
// - The render pass and final images layout for color buffers is always
// VulkanLayout::COLOR_ATTACHMENT.
uint8_t initialColorLayoutMask;
// Note that if VulkanLayout grows beyond 16, we'd need to up this.
VulkanLayout initialDepthLayout : 8;
uint8_t padding0;
uint8_t padding1;
VkFormat colorFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]; // 32 bytes
VkFormat depthFormat; // 4 bytes
@@ -60,6 +71,7 @@ public:
uint32_t timestamp;
};
static_assert(0 == MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT % 8);
static_assert(sizeof(RenderPassKey::initialColorLayoutMask) == MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT / 8);
static_assert(sizeof(TargetBufferFlags) == 4, "TargetBufferFlags has unexpected size.");
static_assert(sizeof(VkFormat) == 4, "VkFormat has unexpected size.");
static_assert(sizeof(RenderPassKey) == 56, "RenderPassKey has unexpected size.");

View File

@@ -17,14 +17,11 @@
#include "VulkanHandles.h"
#include "VulkanConstants.h"
// TODO: remove this by moving DebugUtils out of VulkanDriver
#include "VulkanDriver.h"
#include "VulkanMemory.h"
#include "VulkanResourceAllocator.h"
#include "VulkanUtility.h"
#include "spirv/VulkanSpirvUtils.h"
#include "utils/Log.h"
#include <backend/platforms/VulkanPlatform.h>
@@ -56,14 +53,62 @@ void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) {
}
template<typename Bitmask>
inline void fromStageFlags(backend::ShaderStageFlags stage, descriptor_binding_t binding,
Bitmask& mask) {
if ((bool) (stage & ShaderStageFlags::VERTEX)) {
mask.set(binding + getVertexStageShift<Bitmask>());
static constexpr Bitmask fromStageFlags(ShaderStageFlags2 flags, uint8_t binding) {
Bitmask ret = 0;
if (flags & ShaderStageFlags2::VERTEX) {
ret |= (getVertexStage<Bitmask>() << binding);
}
if ((bool) (stage & ShaderStageFlags::FRAGMENT)) {
mask.set(binding + getFragmentStageShift<Bitmask>());
if (flags & ShaderStageFlags2::FRAGMENT) {
ret |= (getFragmentStage<Bitmask>() << binding);
}
return ret;
}
constexpr decltype(VulkanProgram::MAX_SHADER_MODULES) MAX_SHADER_MODULES =
VulkanProgram::MAX_SHADER_MODULES;
using LayoutDescriptionList = VulkanProgram::LayoutDescriptionList;
template<typename Bitmask>
void addDescriptors(Bitmask mask,
utils::FixedCapacityVector<DescriptorSetLayoutBinding>& outputList) {
constexpr uint8_t MODULE_OFFSET = (sizeof(Bitmask) * 8) / MAX_SHADER_MODULES;
for (uint8_t i = 0; i < MODULE_OFFSET; ++i) {
bool const hasVertex = (mask & (1ULL << i)) != 0;
bool const hasFragment = (mask & (1ULL << (MODULE_OFFSET + i))) != 0;
if (!hasVertex && !hasFragment) {
continue;
}
DescriptorSetLayoutBinding binding{
.binding = i,
.flags = DescriptorFlags::NONE,
.count = 0,// This is always 0 for now as we pass the size of the UBOs in the Driver API
// instead.
};
if (hasVertex) {
binding.stageFlags = ShaderStageFlags2::VERTEX;
}
if (hasFragment) {
binding.stageFlags = static_cast<ShaderStageFlags2>(
binding.stageFlags | ShaderStageFlags2::FRAGMENT);
}
if constexpr (std::is_same_v<Bitmask, UniformBufferBitmask>) {
binding.type = DescriptorType::UNIFORM_BUFFER;
} else if constexpr (std::is_same_v<Bitmask, SamplerBitmask>) {
binding.type = DescriptorType::SAMPLER;
} else if constexpr (std::is_same_v<Bitmask, InputAttachmentBitmask>) {
binding.type = DescriptorType::INPUT_ATTACHMENT;
}
outputList.push_back(binding);
}
}
inline VkDescriptorSetLayout createDescriptorSetLayout(VkDevice device,
VkDescriptorSetLayoutCreateInfo const& info) {
VkDescriptorSetLayout layout;
vkCreateDescriptorSetLayout(device, &info, VKALLOC, &layout);
return layout;
}
inline VkShaderStageFlags getVkStage(backend::ShaderStage stage) {
@@ -77,49 +122,20 @@ inline VkShaderStageFlags getVkStage(backend::ShaderStage stage) {
}
}
using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask;
BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
BitmaskGroup mask;
for (auto const& binding: layout.bindings) {
switch (binding.type) {
case DescriptorType::UNIFORM_BUFFER: {
if ((binding.flags & DescriptorFlags::DYNAMIC_OFFSET) != DescriptorFlags::NONE) {
fromStageFlags(binding.stageFlags, binding.binding, mask.dynamicUbo);
} else {
fromStageFlags(binding.stageFlags, binding.binding, mask.ubo);
}
break;
}
case DescriptorType::SAMPLER: {
fromStageFlags(binding.stageFlags, binding.binding, mask.sampler);
break;
}
case DescriptorType::INPUT_ATTACHMENT: {
fromStageFlags(binding.stageFlags, binding.binding, mask.inputAttachment);
break;
}
case DescriptorType::SHADER_STORAGE_BUFFER:
PANIC_POSTCONDITION("Shader storage is not supported");
break;
}
}
return mask;
}
} // anonymous namespace
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout const& layout)
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(VkDevice device,
VkDescriptorSetLayoutCreateInfo const& info, Bitmask const& bitmask)
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT),
bitmask(fromBackendLayout(layout)),
mDevice(device),
vklayout(createDescriptorSetLayout(device, info)),
bitmask(bitmask),
bindings(getBindings(bitmask)),
count(Count::fromLayoutBitmask(bitmask)) {}
void VulkanDescriptorSet::acquire(VulkanTexture* texture) {
mResources.acquire(texture);
mTextures[mTextureCount++] = texture;
}
void VulkanDescriptorSet::acquire(VulkanBufferObject* bufferObject) {
mResources.acquire(bufferObject);
VulkanDescriptorSetLayout::~VulkanDescriptorSetLayout() {
vkDestroyDescriptorSetLayout(mDevice, vklayout, VKALLOC);
}
PushConstantDescription::PushConstantDescription(backend::Program const& program) noexcept {
@@ -173,11 +189,24 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
mInfo(new(std::nothrow) PipelineInfo(builder)),
mDevice(device) {
constexpr uint8_t UBO_MODULE_OFFSET = (sizeof(UniformBufferBitmask) * 8) / MAX_SHADER_MODULES;
constexpr uint8_t SAMPLER_MODULE_OFFSET = (sizeof(SamplerBitmask) * 8) / MAX_SHADER_MODULES;
constexpr uint8_t INPUT_ATTACHMENT_MODULE_OFFSET =
(sizeof(InputAttachmentBitmask) * 8) / MAX_SHADER_MODULES;
Program::ShaderSource const& blobs = builder.getShadersSource();
auto& modules = mInfo->shaders;
auto const& specializationConstants = builder.getSpecializationConstants();
std::vector<uint32_t> shader;
// TODO: this will be moved out of the shader as the descriptor set layout will be provided by
// Filament instead of parsed from the shaders. See [GDSR] in VulkanDescriptorSetManager.h
UniformBufferBitmask uboMask = 0;
SamplerBitmask samplerMask = 0;
InputAttachmentBitmask inputAttachmentMask = 0;
static_assert(static_cast<ShaderStage>(0) == ShaderStage::VERTEX &&
static_cast<ShaderStage>(1) == ShaderStage::FRAGMENT &&
MAX_SHADER_MODULES == 2);
@@ -194,6 +223,12 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
dataSize = shader.size() * 4;
}
auto const [ubo, sampler, inputAttachment] = getProgramBindings(blob);
uboMask |= (static_cast<UniformBufferBitmask>(ubo) << (UBO_MODULE_OFFSET * i));
samplerMask |= (static_cast<SamplerBitmask>(sampler) << (SAMPLER_MODULE_OFFSET * i));
inputAttachmentMask |= (static_cast<InputAttachmentBitmask>(inputAttachment)
<< (INPUT_ATTACHMENT_MODULE_OFFSET * i));
VkShaderModule& module = modules[i];
VkShaderModuleCreateInfo moduleInfo = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
@@ -221,6 +256,40 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
#endif
}
LayoutDescriptionList& layouts = mInfo->layouts;
layouts[0].bindings = utils::FixedCapacityVector<DescriptorSetLayoutBinding>::with_capacity(
countBits(collapseStages(uboMask)));
layouts[1].bindings = utils::FixedCapacityVector<DescriptorSetLayoutBinding>::with_capacity(
countBits(collapseStages(samplerMask)));
layouts[2].bindings = utils::FixedCapacityVector<DescriptorSetLayoutBinding>::with_capacity(
countBits(collapseStages(inputAttachmentMask)));
addDescriptors(uboMask, layouts[0].bindings);
addDescriptors(samplerMask, layouts[1].bindings);
addDescriptors(inputAttachmentMask, layouts[2].bindings);
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
auto& bindingToName = mInfo->bindingToName;
#endif
auto& groupInfo = builder.getSamplerGroupInfo();
auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex;
auto& bindings = mInfo->bindings;
for (uint8_t groupInd = 0; groupInd < Program::SAMPLER_BINDING_COUNT; groupInd++) {
auto const& group = groupInfo[groupInd];
auto const& samplers = group.samplers;
for (size_t i = 0; i < samplers.size(); ++i) {
uint32_t const binding = samplers[i].binding;
bindingToSamplerIndex[binding] = (groupInd << 8) | (0xff & i);
assert_invariant(bindings.find(binding) == bindings.end());
bindings.insert(binding);
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
bindingToName[binding] = samplers[i].name.c_str();
#endif
}
}
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "Created VulkanProgram " << builder << ", shaders = (" << modules[0]
<< ", " << modules[1] << ")" << utils::io::endl;
@@ -251,7 +320,6 @@ void VulkanRenderTarget::bindToSwapChain(VulkanSwapChain& swapChain) {
VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
uint32_t width, uint32_t height, uint8_t samples,
VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount)
@@ -285,7 +353,6 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
if (UTILS_UNLIKELY(!msTexture)) {
// TODO: This should be allocated with the ResourceAllocator.
msTexture = new VulkanTexture(device, physicalDevice, context, allocator, commands,
handleAllocator,
texture->target, ((VulkanTexture const*) texture)->levels, texture->format,
samples, texture->width, texture->height, texture->depth, texture->usage,
stagePool, true /* heap allocated */);
@@ -315,8 +382,7 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
VulkanTexture* msTexture = depthTexture->getSidecar();
if (UTILS_UNLIKELY(!msTexture)) {
msTexture = new VulkanTexture(device, physicalDevice, context, allocator,
commands, handleAllocator,
depthTexture->target, msLevel, depthTexture->format, samples,
commands, depthTexture->target, msLevel, depthTexture->format, samples,
depthTexture->width, depthTexture->height, depthTexture->depth, depthTexture->usage,
stagePool, true /* heap allocated */);
depthTexture->setSidecar(msTexture);
@@ -468,7 +534,15 @@ bool VulkanTimerQuery::isCompleted() noexcept {
// timestamp has at least been written into a processed command buffer.
// This fence indicates that the corresponding buffer has been completed.
return mFence && mFence->getStatus() == VK_SUCCESS;
if (!mFence) {
return false;
}
VkResult status = mFence->status.load(std::memory_order_relaxed);
if (status != VK_SUCCESS) {
return false;
}
return true;
}
VulkanTimerQuery::~VulkanTimerQuery() = default;
@@ -484,4 +558,35 @@ VulkanRenderPrimitive::VulkanRenderPrimitive(VulkanResourceAllocator* resourceAl
mResources.acquire(indexBuffer);
}
using Bitmask = VulkanDescriptorSetLayout::Bitmask;
Bitmask Bitmask::fromBackendLayout(descset::DescriptorSetLayout const& layout) {
Bitmask mask;
for (auto const& binding: layout.bindings) {
switch (binding.type) {
case descset::DescriptorType::UNIFORM_BUFFER: {
if (binding.flags == descset::DescriptorFlags::DYNAMIC_OFFSET) {
mask.dynamicUbo |= fromStageFlags<UniformBufferBitmask>(binding.stageFlags,
binding.binding);
} else {
mask.ubo |= fromStageFlags<UniformBufferBitmask>(binding.stageFlags,
binding.binding);
}
break;
}
case descset::DescriptorType::SAMPLER: {
mask.sampler |= fromStageFlags<SamplerBitmask>(binding.stageFlags, binding.binding);
break;
}
case descset::DescriptorType::INPUT_ATTACHMENT: {
mask.inputAttachment |=
fromStageFlags<InputAttachmentBitmask>(binding.stageFlags, binding.binding);
break;
}
}
}
return mask;
}
} // namespace filament::backend

View File

@@ -29,52 +29,39 @@
#include <private/backend/SamplerGroup.h>
#include <backend/Program.h>
#include <utils/bitset.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Mutex.h>
#include <utils/StructureOfArrays.h>
namespace filament::backend {
namespace {
// Counts the total number of descriptors for both vertex and fragment stages.
template<typename Bitmask>
inline uint8_t collapsedCount(Bitmask const& mask) {
static_assert(sizeof(mask) <= 64);
constexpr uint64_t VERTEX_MASK = (1ULL << getFragmentStageShift<Bitmask>()) - 1ULL;
constexpr uint64_t FRAGMENT_MASK = (VERTEX_MASK << getFragmentStageShift<Bitmask>());
uint64_t val = mask.getValue();
val = ((val & VERTEX_MASK) >> getVertexStageShift<Bitmask>()) |
((val & FRAGMENT_MASK) >> getFragmentStageShift<Bitmask>());
return (uint8_t) Bitmask(val).count();
}
} // anonymous namespace
using namespace descset;
class VulkanTimestamps;
struct VulkanBufferObject;
struct VulkanDescriptorSetLayout : public VulkanResource, HwDescriptorSetLayout {
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = 4;
static constexpr uint8_t MAX_BINDINGS = 25;
using DescriptorSetLayoutArray = std::array<VkDescriptorSetLayout,
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
struct VulkanDescriptorSetLayout : public VulkanResource {
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = 3;
// The bitmask representation of a set layout.
struct Bitmask {
// TODO: better utiltize the space below and use bitset instead.
UniformBufferBitmask ubo; // 8 bytes
UniformBufferBitmask dynamicUbo; // 8 bytes
SamplerBitmask sampler; // 8 bytes
InputAttachmentBitmask inputAttachment; // 8 bytes
UniformBufferBitmask ubo = 0; // 4 bytes
UniformBufferBitmask dynamicUbo = 0; // 4 bytes
SamplerBitmask sampler = 0; // 8 bytes
InputAttachmentBitmask inputAttachment = 0; // 1 bytes
// Because we're using this struct as hash key, must make it's 8-bytes aligned, with no
// unaccounted bytes.
uint8_t padding0 = 0; // 1 bytes
uint16_t padding1 = 0;// 2 bytes
uint32_t padding2 = 0;// 4 bytes
bool operator==(Bitmask const& right) const {
return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler &&
inputAttachment == right.inputAttachment;
}
static Bitmask fromBackendLayout(descset::DescriptorSetLayout const& layout);
};
static_assert(sizeof(Bitmask) == 32);
// This is a convenience struct to quickly check layout compatibility in terms of descriptor set
// pools.
@@ -84,10 +71,6 @@ struct VulkanDescriptorSetLayout : public VulkanResource, HwDescriptorSetLayout
uint32_t sampler = 0;
uint32_t inputAttachment = 0;
inline uint32_t total() const {
return ubo + dynamicUbo + sampler + inputAttachment;
}
bool operator==(Count const& right) const noexcept {
return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler &&
inputAttachment == right.inputAttachment;
@@ -95,10 +78,10 @@ struct VulkanDescriptorSetLayout : public VulkanResource, HwDescriptorSetLayout
static inline Count fromLayoutBitmask(Bitmask const& mask) {
return {
.ubo = collapsedCount(mask.ubo),
.dynamicUbo = collapsedCount(mask.dynamicUbo),
.sampler = collapsedCount(mask.sampler),
.inputAttachment = collapsedCount(mask.inputAttachment),
.ubo = countBits(collapseStages(mask.ubo)),
.dynamicUbo = countBits(collapseStages(mask.dynamicUbo)),
.sampler = countBits(collapseStages(mask.sampler)),
.inputAttachment = countBits(collapseStages(mask.inputAttachment)),
};
}
@@ -114,58 +97,89 @@ struct VulkanDescriptorSetLayout : public VulkanResource, HwDescriptorSetLayout
}
};
VulkanDescriptorSetLayout(DescriptorSetLayout const& layout);
static_assert(sizeof(Bitmask) % 8 == 0);
~VulkanDescriptorSetLayout() = default;
explicit VulkanDescriptorSetLayout(VkDevice device, VkDescriptorSetLayoutCreateInfo const& info,
Bitmask const& bitmask);
VkDescriptorSetLayout getVkLayout() const { return mVkLayout; }
void setVkLayout(VkDescriptorSetLayout vklayout) { mVkLayout = vklayout; }
~VulkanDescriptorSetLayout();
VkDevice const mDevice;
VkDescriptorSetLayout const vklayout;
Bitmask const bitmask;
// This is a convenience struct so that we don't have to iterate through all the bits of the
// bitmask (which correspondings to binding indices).
struct _Bindings {
utils::FixedCapacityVector<uint8_t> const ubo;
utils::FixedCapacityVector<uint8_t> const dynamicUbo;
utils::FixedCapacityVector<uint8_t> const sampler;
utils::FixedCapacityVector<uint8_t> const inputAttachment;
} bindings;
Count const count;
private:
VkDescriptorSetLayout mVkLayout = VK_NULL_HANDLE;
template <typename MaskType>
utils::FixedCapacityVector<uint8_t> bits(MaskType mask) {
utils::FixedCapacityVector<uint8_t> ret =
utils::FixedCapacityVector<uint8_t>::with_capacity(countBits(mask));
for (uint8_t i = 0; i < sizeof(mask) * 4; ++i) {
if (mask & (1 << i)) {
ret.push_back(i);
}
}
return ret;
}
_Bindings getBindings(Bitmask const& bitmask) {
auto const uboCollapsed = collapseStages(bitmask.ubo);
auto const dynamicUboCollapsed = collapseStages(bitmask.dynamicUbo);
auto const samplerCollapsed = collapseStages(bitmask.sampler);
auto const inputAttachmentCollapsed = collapseStages(bitmask.inputAttachment);
return {
bits(uboCollapsed),
bits(dynamicUboCollapsed),
bits(samplerCollapsed),
bits(inputAttachmentCollapsed),
};
}
};
struct VulkanDescriptorSet : public VulkanResource, HwDescriptorSet {
using VulkanDescriptorSetLayoutList = std::array<Handle<VulkanDescriptorSetLayout>,
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
struct VulkanDescriptorSet : public VulkanResource {
public:
// Because we need to recycle descriptor sets not used, we allow for a callback that the "Pool"
// can use to repackage the vk handle.
using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
using OnRecycle = std::function<void()>;
VulkanDescriptorSet(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet,
OnRecycle&& onRecycleFn)
VulkanDescriptorSet(VulkanResourceAllocator* allocator,
VkDescriptorSet rawSet, OnRecycle&& onRecycleFn)
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET),
resources(allocator),
vkSet(rawSet),
mResources(allocator),
mOnRecycleFn(std::move(onRecycleFn)) {}
~VulkanDescriptorSet() {
if (mOnRecycleFn) {
mOnRecycleFn(this);
mOnRecycleFn();
}
}
void acquire(VulkanTexture* texture);
void acquire(VulkanBufferObject* texture);
bool hasTexture(VulkanTexture* texture) {
return std::any_of(mTextures.begin(), mTextures.end(),
[texture](auto t) { return t == texture; });
}
// TODO: maybe change to fixed size for performance.
VulkanAcquireOnlyResourceManager resources;
VkDescriptorSet const vkSet;
private:
std::array<VulkanTexture*, 16> mTextures = { nullptr };
uint8_t mTextureCount = 0;
VulkanAcquireOnlyResourceManager mResources;
OnRecycle mOnRecycleFn;
};
using VulkanDescriptorSetList = std::array<Handle<VulkanDescriptorSet>,
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
using PushConstantNameArray = utils::FixedCapacityVector<char const*>;
using PushConstantNameByStage = std::array<PushConstantNameArray, Program::SHADER_TYPE_COUNT>;
@@ -210,6 +224,16 @@ struct VulkanProgram : public HwProgram, VulkanResource {
// samplers.
inline BindingList const& getBindings() const { return mInfo->bindings; }
// TODO: this is currently not used. This will replace getLayoutDescriptionList below.
// inline descset::DescriptorSetLayout const& getLayoutDescription() const {
// return mInfo->layout;
// }
// In the usual case, we would have just one layout per program. But in the current setup, we
// have a set/layout for each descriptor type. This will be changed in the future.
using LayoutDescriptionList = std::array<descset::DescriptorSetLayout,
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
inline LayoutDescriptionList const& getLayoutDescriptionList() const { return mInfo->layouts; }
inline uint32_t getPushConstantRangeCount() const {
return mInfo->pushConstantDescription.getVkRangeCount();
}
@@ -249,6 +273,10 @@ private:
utils::FixedCapacityVector<uint16_t> bindingToSamplerIndex;
VkShaderModule shaders[MAX_SHADER_MODULES] = { VK_NULL_HANDLE };
// TODO: Use this instead of `layouts` after Filament-side Descriptor Set API is in place.
// descset::DescriptorSetLayout layout;
LayoutDescriptionList layouts;
PushConstantDescription pushConstantDescription;
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
@@ -274,9 +302,7 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
// Creates an offscreen render target.
VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator,
VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
uint32_t width, uint32_t height,
VulkanCommands* commands, uint32_t width, uint32_t height,
uint8_t samples, VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount);
@@ -458,7 +484,6 @@ private:
utils::Mutex mFenceMutex;
};
inline constexpr VkBufferUsageFlagBits getBufferObjectUsage(
BufferObjectBinding bindingType) noexcept {
switch(bindingType) {

View File

@@ -131,18 +131,14 @@ getVkTransition(const VulkanLayoutTransition& transition) {
}// anonymous namespace
bool transitionLayout(VkCommandBuffer cmdbuffer,
void transitionLayout(VkCommandBuffer cmdbuffer,
VulkanLayoutTransition transition) {
if (transition.oldLayout == transition.newLayout) {
return false;
return;
}
auto [srcAccessMask, dstAccessMask, srcStage, dstStage, oldLayout, newLayout]
= getVkTransition(transition);
if (oldLayout == newLayout) {
return false;
}
assert_invariant(transition.image != VK_NULL_HANDLE && "No image for transition");
VkImageMemoryBarrier barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
@@ -156,7 +152,6 @@ bool transitionLayout(VkCommandBuffer cmdbuffer,
.subresourceRange = transition.subresources,
};
vkCmdPipelineBarrier(cmdbuffer, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
return true;
}
}// namespace filament::backend

View File

@@ -135,9 +135,7 @@ constexpr inline VkImageLayout getVkLayout(VulkanLayout layout) {
}
}
// Returns true if a transition has been added to the command buffer, false otherwis (where there is
// no transition necessary).
bool transitionLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition);
void transitionLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition);
} // namespace imgutil

View File

@@ -98,7 +98,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
VkPipelineColorBlendStateCreateInfo colorBlendState;
colorBlendState = VkPipelineColorBlendStateCreateInfo{};
colorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlendState.attachmentCount = mPipelineRequirements.rasterState.colorTargetCount;
colorBlendState.attachmentCount = 1;
colorBlendState.pAttachments = colorBlendAttachments;
// If we reach this point, we need to create and stash a brand new pipeline object.
@@ -210,8 +210,8 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
pipelineCreateInfo.pDynamicState = &dynamicState;
// Filament assumes consistent blend state across all color attachments.
for (uint8_t i = 0; i < colorBlendState.attachmentCount; ++i) {
auto& target = colorBlendAttachments[i];
colorBlendState.attachmentCount = mPipelineRequirements.rasterState.colorTargetCount;
for (auto& target : colorBlendAttachments) {
target.blendEnable = mPipelineRequirements.rasterState.blendEnable;
target.srcColorBlendFactor = mPipelineRequirements.rasterState.srcColorBlendFactor;
target.dstColorBlendFactor = mPipelineRequirements.rasterState.dstColorBlendFactor;

View File

@@ -232,7 +232,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
VulkanAttachment const srcAttachment = srcTarget->getColor(0);
VkImageSubresourceRange const srcRange = srcAttachment.getSubresourceRange();
srcTexture->transitionLayout(cmdbuffer, {}, srcRange, VulkanLayout::TRANSFER_SRC);
srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
VkImageCopy const imageCopyRegion = {
.srcSubresource = {
@@ -268,7 +268,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
imgutil::getVkLayout(VulkanLayout::TRANSFER_DST), 1, &imageCopyRegion);
// Restore the source image layout.
srcTexture->transitionLayout(cmdbuffer, {}, srcRange, VulkanLayout::COLOR_ATTACHMENT);
srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::COLOR_ATTACHMENT);
vkEndCommandBuffer(cmdbuffer);

View File

@@ -26,14 +26,12 @@ using namespace utils;
namespace filament::backend {
VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context,
VmaAllocator allocator, VulkanCommands* commands, VulkanResourceAllocator* handleAllocator,
VulkanStagePool& stagePool,
VmaAllocator allocator, VulkanCommands* commands, VulkanStagePool& stagePool,
void* nativeWindow, uint64_t flags, VkExtent2D extent)
: VulkanResource(VulkanResourceType::SWAP_CHAIN),
mPlatform(platform),
mCommands(commands),
mAllocator(allocator),
mHandleAllocator(handleAllocator),
mStagePool(stagePool),
mHeadless(extent.width != 0 && extent.height != 0 && !nativeWindow),
mFlushAndWaitOnResize(platform->getCustomization().flushAndWaitOnWindowResize),
@@ -64,12 +62,12 @@ void VulkanSwapChain::update() {
VkDevice const device = mPlatform->getDevice();
for (auto const color: bundle.colors) {
mColors.push_back(std::make_unique<VulkanTexture>(device, mAllocator, mCommands, mHandleAllocator,
color, bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height,
mColors.push_back(std::make_unique<VulkanTexture>(device, mAllocator, mCommands, color,
bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height,
TextureUsage::COLOR_ATTACHMENT, mStagePool, true /* heap allocated */));
}
mDepth = std::make_unique<VulkanTexture>(device, mAllocator, mCommands, mHandleAllocator,
bundle.depth, bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height,
mDepth = std::make_unique<VulkanTexture>(device, mAllocator, mCommands, bundle.depth,
bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height,
TextureUsage::DEPTH_ATTACHMENT, mStagePool, true /* heap allocated */);
mExtent = bundle.extent;
@@ -77,7 +75,7 @@ void VulkanSwapChain::update() {
void VulkanSwapChain::present() {
if (!mHeadless && mTransitionSwapChainImageLayoutForPresent) {
VulkanCommandBuffer& commands = mCommands->get();
VkCommandBuffer const cmdbuf = mCommands->get().buffer();
VkImageSubresourceRange const subresources{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
@@ -85,7 +83,7 @@ void VulkanSwapChain::present() {
.baseArrayLayer = 0,
.layerCount = 1,
};
mColors[mCurrentSwapIndex]->transitionLayout(&commands, subresources, VulkanLayout::PRESENT);
mColors[mCurrentSwapIndex]->transitionLayout(cmdbuf, subresources, VulkanLayout::PRESENT);
}
mCommands->flush();

View File

@@ -36,13 +36,11 @@ namespace filament::backend {
struct VulkanHeadlessSwapChain;
struct VulkanSurfaceSwapChain;
class VulkanResourceAllocator;
// A wrapper around the platform implementation of swapchain.
struct VulkanSwapChain : public HwSwapChain, VulkanResource {
VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context, VmaAllocator allocator,
VulkanCommands* commands, VulkanResourceAllocator* handleAllocator,
VulkanStagePool& stagePool,
VulkanCommands* commands, VulkanStagePool& stagePool,
void* nativeWindow, uint64_t flags, VkExtent2D extent = {0, 0});
~VulkanSwapChain();
@@ -82,7 +80,6 @@ private:
VulkanPlatform* mPlatform;
VulkanCommands* mCommands;
VmaAllocator mAllocator;
VulkanResourceAllocator* const mHandleAllocator;
VulkanStagePool& mStagePool;
bool const mHeadless;
bool const mFlushAndWaitOnResize;

View File

@@ -15,7 +15,6 @@
*/
#include "VulkanMemory.h"
#include "VulkanResourceAllocator.h"
#include "VulkanTexture.h"
#include "VulkanUtility.h"
@@ -29,154 +28,50 @@ using namespace bluevk;
namespace filament::backend {
namespace {
inline uint8_t getLayerCount(SamplerType const target, uint32_t const depth) {
switch (target) {
case SamplerType::SAMPLER_2D:
case SamplerType::SAMPLER_3D:
case SamplerType::SAMPLER_EXTERNAL:
return 1;
case SamplerType::SAMPLER_CUBEMAP:
return 6;
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
return depth * 6;
case SamplerType::SAMPLER_2D_ARRAY:
return depth;
}
}
VkComponentMapping composeSwizzle(VkComponentMapping const& prev, VkComponentMapping const& next) {
static constexpr VkComponentSwizzle IDENTITY[] = {
VK_COMPONENT_SWIZZLE_R,
VK_COMPONENT_SWIZZLE_G,
VK_COMPONENT_SWIZZLE_B,
VK_COMPONENT_SWIZZLE_A,
};
auto const compose = [](VkComponentSwizzle out, VkComponentMapping const& prev,
uint8_t channelIndex) {
// We need to first change all identities to its equivalent channel.
if (out == VK_COMPONENT_SWIZZLE_IDENTITY) {
out = IDENTITY[channelIndex];
}
switch (out) {
case VK_COMPONENT_SWIZZLE_R:
out = prev.r;
break;
case VK_COMPONENT_SWIZZLE_G:
out = prev.g;
break;
case VK_COMPONENT_SWIZZLE_B:
out = prev.b;
break;
case VK_COMPONENT_SWIZZLE_A:
out = prev.a;
break;
case VK_COMPONENT_SWIZZLE_IDENTITY:
case VK_COMPONENT_SWIZZLE_ZERO:
case VK_COMPONENT_SWIZZLE_ONE:
return out;
// Below is not exposed in Vulkan's API, but needs to be there for compilation.
case VK_COMPONENT_SWIZZLE_MAX_ENUM:
break;
}
// If the result correctly corresponds to the identity, just return identity.
if (IDENTITY[channelIndex] == out) {
return VK_COMPONENT_SWIZZLE_IDENTITY;
}
return out;
};
auto const identityToChannel = [](VkComponentSwizzle val, uint8_t channelIndex) {
if (val != VK_COMPONENT_SWIZZLE_IDENTITY) {
return val;
}
return IDENTITY[channelIndex];
};
// We make sure all all identities are mapped into respective channels so that actual channel
// mapping will be passed onto the output.
VkComponentMapping const prevExplicit = {
identityToChannel(prev.r, 0),
identityToChannel(prev.g, 1),
identityToChannel(prev.b, 2),
identityToChannel(prev.a, 3),
};
// Note that the channel index corresponds to the VkComponentMapping struct layout.
return {
compose(next.r, prevExplicit, 0),
compose(next.g, prevExplicit, 1),
compose(next.b, prevExplicit, 2),
compose(next.a, prevExplicit, 3),
};
}
} // anonymous namespace
VulkanTextureState::VulkanTextureState(
VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool,
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount)
: VulkanResource(VulkanResourceType::HEAP_ALLOCATED),
mVkFormat(format),
mViewType(viewType),
mFullViewRange {
filament::backend::getImageAspect(format), 0, levels, 0, layerCount
},
mStagePool(stagePool),
mDevice(device),
mAllocator(allocator),
mCommands(commands) {
}
VulkanTextureState* VulkanTexture::getSharedState() {
VulkanTextureState* state = mAllocator->handle_cast<VulkanTextureState*>(mState);
return state;
}
VulkanTextureState const* VulkanTexture::getSharedState() const {
VulkanTextureState const* state = mAllocator->handle_cast<VulkanTextureState const*>(mState);
return state;
}
VulkanTexture::VulkanTexture(
VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VkImage image, VkFormat format, uint8_t samples, uint32_t width, uint32_t height,
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated)
: HwTexture(SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED,
tusage),
VulkanResource(
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
mAllocator(handleAllocator),
mState(handleAllocator->initHandle<VulkanTextureState>(
device, allocator, commands, stagePool,
format, imgutil::getViewType(SamplerType::SAMPLER_2D), 1, 1)) {
auto* const state = getSharedState();
state->mTextureImage = image;
mPrimaryViewRange = state->mFullViewRange;
}
: HwTexture(SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED,
tusage),
VulkanResource(
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
mVkFormat(format),
mViewType(imgutil::getViewType(target)),
mSwizzle({}),
mTextureImage(image),
mFullViewRange{
.aspectMask = getImageAspect(),
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
mPrimaryViewRange(mFullViewRange),
mStagePool(stagePool),
mDevice(device),
mAllocator(allocator),
mCommands(commands) {}
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator, SamplerType target, uint8_t levels,
TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated)
SamplerType target, uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w,
uint32_t h, uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool,
bool heapAllocated, VkComponentMapping swizzle)
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage),
VulkanResource(
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
mAllocator(handleAllocator),
mState(handleAllocator->initHandle<VulkanTextureState>(device, allocator, commands, stagePool,
backend::getVkFormat(tformat), imgutil::getViewType(target), levels,
getLayerCount(target, depth))) {
auto* const state = getSharedState();
mVkFormat(backend::getVkFormat(tformat)),
mViewType(imgutil::getViewType(target)),
mSwizzle(swizzle),
mStagePool(stagePool),
mDevice(device),
mAllocator(allocator),
mCommands(commands) {
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
VkImageCreateInfo imageInfo{.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,
.format = state->mVkFormat,
.format = mVkFormat,
.extent = {w, h, depth},
.mipLevels = levels,
.arrayLayers = 1,
@@ -214,9 +109,9 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
// Validate that the format is actually sampleable.
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(physicalDevice, state->mVkFormat, &props);
vkGetPhysicalDeviceFormatProperties(physicalDevice, mVkFormat, &props);
if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << state->mVkFormat << " is not "
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << mVkFormat << " is not "
"sampleable with optimal tiling." << utils::io::endl;
}
#endif
@@ -249,7 +144,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
// any kind of attachment (color or depth).
const auto& limits = context.getPhysicalDeviceLimits();
if (imageInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) {
samples = reduceSampleCount(samples, isVkDepthFormat(state->mVkFormat)
samples = reduceSampleCount(samples, isVkDepthFormat(mVkFormat)
? limits.sampledImageDepthSampleCounts
: limits.sampledImageColorSampleCounts);
}
@@ -263,12 +158,12 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
this->samples = samples;
imageInfo.samples = (VkSampleCountFlagBits) samples;
VkResult error = vkCreateImage(state->mDevice, &imageInfo, VKALLOC, &state->mTextureImage);
VkResult error = vkCreateImage(mDevice, &imageInfo, VKALLOC, &mTextureImage);
if (error || FVK_ENABLED(FVK_DEBUG_TEXTURE)) {
FVK_LOGD << "vkCreateImage: "
<< "image = " << state->mTextureImage << ", "
<< "image = " << mTextureImage << ", "
<< "result = " << error << ", "
<< "handle = " << utils::io::hex << state->mTextureImage << utils::io::dec << ", "
<< "handle = " << utils::io::hex << mTextureImage << utils::io::dec << ", "
<< "extent = " << w << "x" << h << "x"<< depth << ", "
<< "mipLevels = " << int(levels) << ", "
<< "TextureUsage = " << static_cast<int>(usage) << ", "
@@ -277,13 +172,13 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
<< "type = " << imageInfo.imageType << ", "
<< "flags = " << imageInfo.flags << ", "
<< "target = " << static_cast<int>(target) <<", "
<< "format = " << state->mVkFormat << utils::io::endl;
<< "format = " << mVkFormat << utils::io::endl;
}
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to create image.";
// Allocate memory for the VkImage and bind it.
VkMemoryRequirements memReqs = {};
vkGetImageMemoryRequirements(state->mDevice, state->mTextureImage, &memReqs);
vkGetImageMemoryRequirements(mDevice, mTextureImage, &memReqs);
uint32_t memoryTypeIndex
= context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
@@ -296,67 +191,58 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
.allocationSize = memReqs.size,
.memoryTypeIndex = memoryTypeIndex,
};
error = vkAllocateMemory(state->mDevice, &allocInfo, nullptr, &state->mTextureImageMemory);
error = vkAllocateMemory(mDevice, &allocInfo, nullptr, &mTextureImageMemory);
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to allocate image memory.";
error = vkBindImageMemory(state->mDevice, state->mTextureImage, state->mTextureImageMemory, 0);
error = vkBindImageMemory(mDevice, mTextureImage, mTextureImageMemory, 0);
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to bind image.";
uint32_t layerCount = 0;
if (target == SamplerType::SAMPLER_CUBEMAP) {
layerCount = 6;
} else if (target == SamplerType::SAMPLER_CUBEMAP_ARRAY) {
layerCount = depth * 6;
} else if (target == SamplerType::SAMPLER_2D_ARRAY) {
layerCount = depth;
} else if (target == SamplerType::SAMPLER_3D) {
layerCount = 1;
} else {
layerCount = 1;
}
mFullViewRange = {
.aspectMask = getImageAspect(),
.baseMipLevel = 0,
.levelCount = levels,
.baseArrayLayer = 0,
.layerCount = layerCount,
};
// Spec out the "primary" VkImageView that shaders use to sample from the image.
mPrimaryViewRange = state->mFullViewRange;
mPrimaryViewRange = mFullViewRange;
// Go ahead and create the primary image view.
getImageView(mPrimaryViewRange, state->mViewType, mSwizzle);
getImageView(mPrimaryViewRange, mViewType, mSwizzle);
VulkanCommandBuffer& commandsBuf = state->mCommands->get();
commandsBuf.acquire(this);
transitionLayout(&commandsBuf, mPrimaryViewRange, imgutil::getDefaultLayout(imageInfo.usage));
}
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, uint8_t baseLevel, uint8_t levelCount)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
VulkanResource(VulkanResourceType::TEXTURE),
mAllocator(handleAllocator)
{
mState = src->mState;
auto* state = getSharedState();
state->refs++;
mPrimaryViewRange = src->mPrimaryViewRange;
mPrimaryViewRange.baseMipLevel = src->mPrimaryViewRange.baseMipLevel + baseLevel;
mPrimaryViewRange.levelCount = levelCount;
}
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, VkComponentMapping swizzle)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
VulkanResource(VulkanResourceType::TEXTURE),
mAllocator(handleAllocator) {
mState = src->mState;
auto* state = getSharedState();
state->refs++;
mPrimaryViewRange = src->mPrimaryViewRange;
mSwizzle = composeSwizzle(src->mSwizzle, swizzle);
// Transition the layout of each image slice that might be used as a render target.
// We do not transition images that are merely SAMPLEABLE, this is deferred until upload time
// because we do not know how many layers and levels will actually be used.
if (imageInfo.usage
& (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) {
VulkanCommandBuffer& commands = mCommands->get();
VkCommandBuffer const cmdbuf = commands.buffer();
commands.acquire(this);
transitionLayout(cmdbuf, mFullViewRange, imgutil::getDefaultLayout(imageInfo.usage));
}
}
VulkanTexture::~VulkanTexture() {
auto* const state = getSharedState();
state->refs--;
if (state->refs == 0) {
if (state->mTextureImageMemory != VK_NULL_HANDLE) {
vkDestroyImage(state->mDevice, state->mTextureImage, VKALLOC);
vkFreeMemory(state->mDevice, state->mTextureImageMemory, VKALLOC);
}
for (auto entry: state->mCachedImageViews) {
vkDestroyImageView(state->mDevice, entry.second, VKALLOC);
}
mAllocator->destruct<VulkanTextureState>(mState);
if (mTextureImageMemory != VK_NULL_HANDLE) {
vkDestroyImage(mDevice, mTextureImage, VKALLOC);
vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC);
}
for (auto entry : mCachedImageViews) {
vkDestroyImageView(mDevice, entry.second, VKALLOC);
}
}
@@ -365,7 +251,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
assert_invariant(width <= this->width && height <= this->height);
assert_invariant(depth <= this->depth * ((target == SamplerType::SAMPLER_CUBEMAP ||
target == SamplerType::SAMPLER_CUBEMAP_ARRAY) ? 6 : 1));
auto* const state = getSharedState();
const PixelBufferDescriptor* hostData = &data;
PixelBufferDescriptor reshapedData;
@@ -378,7 +264,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
// If format conversion is both required and supported, use vkCmdBlitImage.
const VkFormat hostFormat = backend::getVkFormat(hostData->format, hostData->type);
const VkFormat deviceFormat = getVkFormatLinear(state->mVkFormat);
const VkFormat deviceFormat = getVkFormatLinear(mVkFormat);
if (hostFormat != deviceFormat && hostFormat != VK_FORMAT_UNDEFINED) {
assert_invariant(xoffset == 0 && yoffset == 0 && zoffset == 0 &&
"Offsets not yet supported when format conversion is required.");
@@ -390,14 +276,14 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
// Otherwise, use vkCmdCopyBufferToImage.
void* mapped = nullptr;
VulkanStage const* stage = state->mStagePool.acquireStage(hostData->size);
VulkanStage const* stage = mStagePool.acquireStage(hostData->size);
assert_invariant(stage->memory);
vmaMapMemory(state->mAllocator, stage->memory, &mapped);
vmaMapMemory(mAllocator, stage->memory, &mapped);
memcpy(mapped, hostData->buffer, hostData->size);
vmaUnmapMemory(state->mAllocator, stage->memory);
vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData->size);
vmaUnmapMemory(mAllocator, stage->memory);
vmaFlushAllocation(mAllocator, stage->memory, 0, hostData->size);
VulkanCommandBuffer& commands = state->mCommands->get();
VulkanCommandBuffer& commands = mCommands->get();
VkCommandBuffer const cmdbuf = commands.buffer();
commands.acquire(this);
@@ -443,25 +329,24 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
nextLayout = imgutil::getDefaultLayout(this->usage);
}
transitionLayout(&commands, transitionRange, newLayout);
transitionLayout(cmdbuf, transitionRange, newLayout);
vkCmdCopyBufferToImage(cmdbuf, stage->buffer, state->mTextureImage, newVkLayout, 1, &copyRegion);
vkCmdCopyBufferToImage(cmdbuf, stage->buffer, mTextureImage, newVkLayout, 1, &copyRegion);
transitionLayout(&commands, transitionRange, nextLayout);
transitionLayout(cmdbuf, transitionRange, nextLayout);
}
void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width,
uint32_t height, uint32_t depth, uint32_t miplevel) {
auto* const state = getSharedState();
void* mapped = nullptr;
VulkanStageImage const* stage
= state->mStagePool.acquireImage(hostData.format, hostData.type, width, height);
vmaMapMemory(state->mAllocator, stage->memory, &mapped);
= mStagePool.acquireImage(hostData.format, hostData.type, width, height);
vmaMapMemory(mAllocator, stage->memory, &mapped);
memcpy(mapped, hostData.buffer, hostData.size);
vmaUnmapMemory(state->mAllocator, stage->memory);
vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData.size);
vmaUnmapMemory(mAllocator, stage->memory);
vmaFlushAllocation(mAllocator, stage->memory, 0, hostData.size);
VulkanCommandBuffer& commands = state->mCommands->get();
VulkanCommandBuffer& commands = mCommands->get();
VkCommandBuffer const cmdbuf = commands.buffer();
commands.acquire(this);
@@ -483,12 +368,19 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
VulkanLayout const newLayout = VulkanLayout::TRANSFER_DST;
VulkanLayout const oldLayout = getLayout(layer, miplevel);
transitionLayout(&commands, range, newLayout);
transitionLayout(cmdbuf, range, newLayout);
vkCmdBlitImage(cmdbuf, stage->image, imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC),
state->mTextureImage, imgutil::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST);
mTextureImage, imgutil::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST);
transitionLayout(&commands, range, oldLayout);
transitionLayout(cmdbuf, range, oldLayout);
}
void VulkanTexture::setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel) {
maxMiplevel = filament::math::min(int(maxMiplevel), int(this->levels - 1));
mPrimaryViewRange.baseMipLevel = minMiplevel;
mPrimaryViewRange.levelCount = maxMiplevel - minMiplevel + 1;
getImageView(mPrimaryViewRange, mViewType, mSwizzle);
}
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
@@ -507,44 +399,35 @@ VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range,
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
VkComponentMapping swizzle) {
auto* const state = getSharedState();
VulkanTextureState::ImageViewKey const key{ range, viewType, swizzle };
auto iter = state->mCachedImageViews.find(key);
if (iter != state->mCachedImageViews.end()) {
ImageViewKey const key {range, viewType, swizzle};
auto iter = mCachedImageViews.find(key);
if (iter != mCachedImageViews.end()) {
return iter->second;
}
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.image = state->mTextureImage,
.image = mTextureImage,
.viewType = viewType,
.format = state->mVkFormat,
.format = mVkFormat,
.components = swizzle,
.subresourceRange = range,
};
VkImageView imageView;
vkCreateImageView(state->mDevice, &viewInfo, VKALLOC, &imageView);
state->mCachedImageViews.emplace(key, imageView);
vkCreateImageView(mDevice, &viewInfo, VKALLOC, &imageView);
mCachedImageViews.emplace(key, imageView);
return imageView;
}
VkImageAspectFlags VulkanTexture::getImageAspect() const {
// Helper function in VulkanUtility
auto* const state = getSharedState();
return filament::backend::getImageAspect(state->mVkFormat);
return filament::backend::getImageAspect(mVkFormat);
}
bool VulkanTexture::transitionLayout(VulkanCommandBuffer* commands,
const VkImageSubresourceRange& range, VulkanLayout newLayout) {
return transitionLayout(commands->buffer(), commands->fence, range, newLayout);
}
bool VulkanTexture::transitionLayout(
VkCommandBuffer cmdbuf, std::shared_ptr<VulkanCmdFence> fence,
const VkImageSubresourceRange& range,
void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubresourceRange& range,
VulkanLayout newLayout) {
auto* const state = getSharedState();
VulkanLayout const oldLayout = getLayout(range.baseArrayLayer, range.baseMipLevel);
uint32_t const firstLayer = range.baseArrayLayer;
@@ -555,7 +438,7 @@ bool VulkanTexture::transitionLayout(
// If we are transitioning more than one layer/level (slice), we need to know whether they are
// all of the same layer. If not, we need to transition slice-by-slice. Otherwise it would
// trigger the validation layer saying that the `oldLayout` provided is incorrect.
// TODO: transition by multiple slices with more sophisticated range finding.
// TODO: transition by multiple slices with more sophiscated range finding.
bool transitionSliceBySlice = false;
for (uint32_t i = firstLayer; i < lastLayer; ++i) {
for (uint32_t j = firstLevel; j < lastLevel; ++j) {
@@ -566,109 +449,50 @@ bool VulkanTexture::transitionLayout(
}
}
bool hasTransitions = false;
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
FVK_LOGD << "transition texture=" << mTextureImage
<< " (" << range.baseArrayLayer
<< "," << range.baseMipLevel << ")"
<< " count=(" << range.layerCount
<< "," << range.levelCount << ")"
<< " from=" << oldLayout << " to=" << newLayout
<< " format=" << mVkFormat
<< " depth=" << isVkDepthFormat(mVkFormat)
<< " slice-by-slice=" << transitionSliceBySlice
<< utils::io::endl;
#endif
if (transitionSliceBySlice) {
for (uint32_t i = firstLayer; i < lastLayer; ++i) {
for (uint32_t j = firstLevel; j < lastLevel; ++j) {
VulkanLayout const layout = getLayout(i, j);
if (layout == newLayout) {
continue;
}
hasTransitions = hasTransitions || imgutil::transitionLayout(cmdbuf, {
.image = state->mTextureImage,
.oldLayout = layout,
.newLayout = newLayout,
.subresources = {
.aspectMask = range.aspectMask,
.baseMipLevel = j,
.levelCount = 1,
.baseArrayLayer = i,
.layerCount = 1,
},
});
imgutil::transitionLayout(cmdbuf, {
.image = mTextureImage,
.oldLayout = layout,
.newLayout = newLayout,
.subresources = {
.aspectMask = range.aspectMask,
.baseMipLevel = j,
.levelCount = 1,
.baseArrayLayer = i,
.layerCount = 1,
},
});
}
}
} else if (newLayout != oldLayout) {
hasTransitions = imgutil::transitionLayout(cmdbuf, {
.image = state->mTextureImage,
} else {
imgutil::transitionLayout(cmdbuf, {
.image = mTextureImage,
.oldLayout = oldLayout,
.newLayout = newLayout,
.subresources = range,
});
}
// Even if we didn't carry out the transition, we should assume that the new layout is defined
// through this call.
setLayout(range, newLayout);
if (hasTransitions) {
state->mTransitionFence = fence;
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
FVK_LOGD << "transition texture=" << state->mTextureImage << " (" << range.baseArrayLayer
<< "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << ","
<< range.levelCount << ")" << " from=" << oldLayout << " to=" << newLayout
<< " format=" << state->mVkFormat << " depth=" << isVkDepthFormat(state->mVkFormat)
<< " slice-by-slice=" << transitionSliceBySlice << utils::io::endl;
#endif
} else {
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
FVK_LOGD << "transition texture=" << state->mTextureImage << " (" << range.baseArrayLayer
<< "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << ","
<< range.levelCount << ")" << " to=" << newLayout
<< " is skipped because of no change in layout" << utils::io::endl;
#endif
}
return hasTransitions;
}
void VulkanTexture::samplerToAttachmentBarrier(VulkanCommandBuffer* commands,
VkImageSubresourceRange const& range) {
VkCommandBuffer const cmdbuf = commands->buffer();
auto* const state = getSharedState();
VkImageLayout const layout =
imgutil::getVkLayout(getLayout(range.baseArrayLayer, range.baseMipLevel));
VkImageMemoryBarrier barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_SHADER_READ_BIT,
.dstAccessMask =
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.oldLayout = layout,
.newLayout = layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = state->mTextureImage,
.subresourceRange = range,
};
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, 0, nullptr, 0, nullptr, 1, &barrier);
}
void VulkanTexture::attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
VkImageSubresourceRange const& range) {
VkCommandBuffer const cmdbuf = commands->buffer();
auto* const state = getSharedState();
VkImageLayout const layout
= imgutil::getVkLayout(getLayout(range.baseArrayLayer, range.baseMipLevel));
VkImageMemoryBarrier barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = layout,
.newLayout = layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = state->mTextureImage,
.subresourceRange = range,
};
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}
void VulkanTexture::setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout) {
auto* const state = getSharedState();
void VulkanTexture::setLayout(const VkImageSubresourceRange& range, VulkanLayout newLayout) {
uint32_t const firstLayer = range.baseArrayLayer;
uint32_t const lastLayer = firstLayer + range.layerCount;
uint32_t const firstLevel = range.baseMipLevel;
@@ -681,34 +505,32 @@ void VulkanTexture::setLayout(VkImageSubresourceRange const& range, VulkanLayout
for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) {
uint32_t const first = (layer << 16) | firstLevel;
uint32_t const last = (layer << 16) | lastLevel;
state->mSubresourceLayouts.clear(first, last);
mSubresourceLayouts.clear(first, last);
}
} else {
for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) {
uint32_t const first = (layer << 16) | firstLevel;
uint32_t const last = (layer << 16) | lastLevel;
state->mSubresourceLayouts.add(first, last, newLayout);
mSubresourceLayouts.add(first, last, newLayout);
}
}
}
VulkanLayout VulkanTexture::getLayout(uint32_t layer, uint32_t level) const {
assert_invariant(level <= 0xffff && layer <= 0xffff);
auto* const state = getSharedState();
const uint32_t key = (layer << 16) | level;
if (!state->mSubresourceLayouts.has(key)) {
if (!mSubresourceLayouts.has(key)) {
return VulkanLayout::UNDEFINED;
}
return state->mSubresourceLayouts.get(key);
return mSubresourceLayouts.get(key);
}
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
void VulkanTexture::print() const {
auto* const state = getSharedState();
uint32_t const firstLayer = 0;
uint32_t const lastLayer = firstLayer + state->mFullViewRange.layerCount;
uint32_t const lastLayer = firstLayer + mFullViewRange.layerCount;
uint32_t const firstLevel = 0;
uint32_t const lastLevel = firstLevel + state->mFullViewRange.levelCount;
uint32_t const lastLevel = firstLevel + mFullViewRange.levelCount;
for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) {
for (uint32_t level = firstLevel; level < lastLevel; ++level) {
@@ -717,16 +539,16 @@ void VulkanTexture::print() const {
layer < (mPrimaryViewRange.baseArrayLayer + mPrimaryViewRange.layerCount) &&
level >= mPrimaryViewRange.baseMipLevel &&
level < (mPrimaryViewRange.baseMipLevel + mPrimaryViewRange.levelCount);
FVK_LOGD << "[" << state->mTextureImage << "]: (" << layer << "," << level
FVK_LOGD << "[" << mTextureImage << "]: (" << layer << "," << level
<< ")=" << getLayout(layer, level)
<< " primary=" << primary
<< utils::io::endl;
}
}
for (auto view: state->mCachedImageViews) {
for (auto view: mCachedImageViews) {
auto& range = view.first.range;
FVK_LOGD << "[" << state->mTextureImage << ", imageView=" << view.second << "]=>"
FVK_LOGD << "[" << mTextureImage << ", imageView=" << view.second << "]=>"
<< " (" << range.baseArrayLayer << "," << range.baseMipLevel << ")"
<< " count=(" << range.layerCount << "," << range.levelCount << ")"
<< " aspect=" << range.aspectMask << " viewType=" << view.first.type

View File

@@ -30,12 +30,88 @@
namespace filament::backend {
class VulkanResourceAllocator;
struct VulkanTexture : public HwTexture, VulkanResource {
// Standard constructor for user-facing textures.
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
VmaAllocator allocator, 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, bool heapAllocated = false,
VkComponentMapping swizzle = {});
struct VulkanTextureState : public VulkanResource {
VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool,
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount);
// 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, VulkanCommands* commands, VkImage image,
VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage,
VulkanStagePool& stagePool, bool heapAllocated = false);
~VulkanTexture();
// Uploads data into a subregion of a 2D or 3D texture.
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, mViewType, mSwizzle);
}
VkImageViewType getViewType() const { return mViewType; }
// Sets the min/max range of miplevels in the primary image view.
void setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel);
VkImageSubresourceRange getPrimaryViewRange() const { return mPrimaryViewRange; }
VkImageSubresourceRange getFullViewRange() const { return mFullViewRange; }
VulkanLayout getPrimaryImageLayout() const {
return getLayout(mPrimaryViewRange.baseArrayLayer, mPrimaryViewRange.baseMipLevel);
}
// 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 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);
VkFormat getVkFormat() const { return mVkFormat; }
VkImage getVkImage() const { return mTextureImage; }
VulkanLayout getLayout(uint32_t layer, uint32_t level) const;
void setSidecar(VulkanTexture* sidecar) {
mSidecarMSAA.reset(sidecar);
}
VulkanTexture* getSidecar() const {
return mSidecarMSAA.get();
}
void transitionLayout(VkCommandBuffer commands, const VkImageSubresourceRange& range,
VulkanLayout newLayout);
// Returns the preferred data plane of interest for all image views.
// For now this always returns either DEPTH or COLOR.
VkImageAspectFlags getImageAspect() const;
// For implicit transition like the end of a render pass, we need to be able to set the layout
// manually (outside of calls to transitionLayout).
void setLayout(const VkImageSubresourceRange& range, VulkanLayout newLayout);
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
void print() const;
#endif
private:
struct ImageViewKey {
VkImageSubresourceRange range; // 4 * 5 bytes
@@ -58,153 +134,6 @@ struct VulkanTextureState : public VulkanResource {
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
uint32_t refs = 1;
// The texture with the sidecar owns the sidecar.
std::unique_ptr<VulkanTexture> mSidecarMSAA;
VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE;
VkFormat const mVkFormat;
VkImageViewType const mViewType;
VkImageSubresourceRange const mFullViewRange;
VkImage mTextureImage = VK_NULL_HANDLE;
// Track the image layout of each subresource using a sparse range map.
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
VulkanStagePool& mStagePool;
VkDevice mDevice;
VmaAllocator mAllocator;
VulkanCommands* mCommands;
std::shared_ptr<VulkanCmdFence> mTransitionFence;
};
struct VulkanTexture : public HwTexture, VulkanResource {
// Standard constructor for user-facing textures.
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
SamplerType target, uint8_t levels,
TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated = false);
// 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, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VkImage image,
VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage,
VulkanStagePool& stagePool, bool heapAllocated = false);
// Constructor for creating a texture view for wrt specific mip range
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, uint8_t baseLevel, uint8_t levelCount);
// Constructor for creating a texture view for swizzle.
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, VkComponentMapping swizzle);
~VulkanTexture();
// Uploads data into a subregion of a 2D or 3D texture.
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() {
VulkanTextureState* state = getSharedState();
return getImageView(mPrimaryViewRange, state->mViewType, mSwizzle);
}
VkImageViewType getViewType() const {
VulkanTextureState const* state = getSharedState();
return state->mViewType;
}
VkImageSubresourceRange getPrimaryViewRange() const { return mPrimaryViewRange; }
VulkanLayout getPrimaryImageLayout() const {
return getLayout(mPrimaryViewRange.baseArrayLayer, mPrimaryViewRange.baseMipLevel);
}
// 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 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);
VkFormat getVkFormat() const {
VulkanTextureState const* state = getSharedState();
return state->mVkFormat;
}
VkImage getVkImage() const {
VulkanTextureState const* state = getSharedState();
return state->mTextureImage;
}
VulkanLayout getLayout(uint32_t layer, uint32_t level) const;
void setSidecar(VulkanTexture* sidecar) {
VulkanTextureState* state = getSharedState();
state->mSidecarMSAA.reset(sidecar);
}
VulkanTexture* getSidecar() const {
VulkanTextureState const* state = getSharedState();
return state->mSidecarMSAA.get();
}
bool transitionLayout(VulkanCommandBuffer* commands, const VkImageSubresourceRange& range,
VulkanLayout newLayout);
bool transitionLayout(VkCommandBuffer cmdbuf, std::shared_ptr<VulkanCmdFence> fence,
VkImageSubresourceRange const& range, VulkanLayout newLayout);
void attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
VkImageSubresourceRange const& range);
void samplerToAttachmentBarrier(VulkanCommandBuffer* commands,
VkImageSubresourceRange const& range);
// Returns the preferred data plane of interest for all image views.
// For now this always returns either DEPTH or COLOR.
VkImageAspectFlags getImageAspect() const;
// For implicit transition like the end of a render pass, we need to be able to set the layout
// manually (outside of calls to transitionLayout).
void setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout);
bool transitionReady() {
VulkanTextureState* state = getSharedState();
auto res = !state->mTransitionFence || state->mTransitionFence->getStatus() == VK_SUCCESS;
state->mTransitionFence.reset();
return res;
}
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
void print() const;
#endif
private:
VulkanTextureState* getSharedState();
VulkanTextureState const* getSharedState() const;
// Gets or creates a cached VkImageView for a range of miplevels, array layers, viewType, and
// swizzle (or not).
VkImageView getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
@@ -213,15 +142,28 @@ private:
void updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width, uint32_t height,
uint32_t depth, uint32_t miplevel);
VulkanResourceAllocator* const mAllocator;
// The texture with the sidecar owns the sidecar.
std::unique_ptr<VulkanTexture> mSidecarMSAA;
const VkFormat mVkFormat;
const VkImageViewType mViewType;
const VkComponentMapping mSwizzle;
VkImage mTextureImage = VK_NULL_HANDLE;
VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE;
Handle<VulkanTextureState> mState;
// Track the image layout of each subresource using a sparse range map.
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
VkImageSubresourceRange mFullViewRange;
// Track the range of subresources that define the "primary" image view, which is the special
// image view that gets bound to an actual texture sampler.
VkImageSubresourceRange mPrimaryViewRange;
VkComponentMapping mSwizzle {};
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
VulkanStagePool& mStagePool;
VkDevice mDevice;
VmaAllocator mAllocator;
VulkanCommands* mCommands;
};
} // namespace filament::backend

View File

@@ -577,7 +577,7 @@ uint32_t getComponentCount(VkFormat format) {
return {};
}
VkComponentMapping getSwizzleMap(TextureSwizzle const swizzle[4]) {
VkComponentMapping getSwizzleMap(TextureSwizzle swizzle[4]) {
VkComponentMapping map;
VkComponentSwizzle* dst = &map.r;
for (int i = 0; i < 4; ++i, ++dst) {

View File

@@ -19,7 +19,6 @@
#include <backend/DriverEnums.h>
#include <utils/bitset.h>
#include <utils/FixedCapacityVector.h>
#include <bluevk/BlueVK.h>
@@ -39,7 +38,7 @@ VkCullModeFlags getCullMode(CullingMode mode);
VkFrontFace getFrontFace(bool inverseFrontFaces);
PixelDataType getComponentType(VkFormat format);
uint32_t getComponentCount(VkFormat format);
VkComponentMapping getSwizzleMap(TextureSwizzle const swizzle[4]);
VkComponentMapping getSwizzleMap(TextureSwizzle swizzle[4]);
VkShaderStageFlags getShaderStageFlags(ShaderStageFlags stageFlags);
bool equivalent(const VkRect2D& a, const VkRect2D& b);
@@ -406,13 +405,12 @@ constexpr VkFormat ALL_VK_FORMATS[] = {
VK_FORMAT_R16G16_S10_5_NV,
};
// An Array that will be statically fixed in capacity, but the "size" (as in user added elements) is
// variable. Note that this class is movable.
// An Array that will be fixed capacity, but the "size" (as in user added elements) is variable.
// Note that this class is movable.
template<typename T, uint16_t CAPACITY>
class CappedArray {
private:
using FixedSizeArray = std::array<T, CAPACITY>;
public:
using const_iterator = typename FixedSizeArray::const_iterator;
using iterator = typename FixedSizeArray::iterator;
@@ -450,20 +448,6 @@ public:
return mArray.cend();
}
inline iterator begin() {
if (mInd == 0) {
return mArray.end();
}
return mArray.begin();
}
inline iterator end() {
if (mInd > 0 && mInd < CAPACITY) {
return mArray.begin() + mInd;
}
return mArray.end();
}
inline T back() {
assert_invariant(mInd > 0);
return *(mArray.begin() + mInd);
@@ -528,28 +512,125 @@ private:
uint32_t mInd = 0;
};
using UniformBufferBitmask = utils::bitset64;
using SamplerBitmask = utils::bitset64;
// TODO: ok to remove once Filament-side API is complete
namespace descset {
// Used to describe the descriptor binding in shader stages. We assume that the binding index does
// not exceed 31. We also assume that we have two shader stages - vertex and fragment. The below
// types and struct are used across VulkanDescriptorSet and VulkanProgram.
using UniformBufferBitmask = uint32_t;
using SamplerBitmask = uint64_t;
// We only have at most one input attachment, so this bitmask exists only to make the code more
// general.
using InputAttachmentBitmask = utils::bitset64;
using InputAttachmentBitmask = uint8_t;
constexpr UniformBufferBitmask UBO_VERTEX_STAGE = 0x1;
constexpr UniformBufferBitmask UBO_FRAGMENT_STAGE = (0x1ULL << (sizeof(UniformBufferBitmask) * 4));
constexpr SamplerBitmask SAMPLER_VERTEX_STAGE = 0x1;
constexpr SamplerBitmask SAMPLER_FRAGMENT_STAGE = (0x1ULL << (sizeof(SamplerBitmask) * 4));
constexpr InputAttachmentBitmask INPUT_ATTACHMENT_VERTEX_STAGE = 0x1;
constexpr InputAttachmentBitmask INPUT_ATTACHMENT_FRAGMENT_STAGE =
(0x1ULL << (sizeof(InputAttachmentBitmask) * 4));
template<typename Bitmask>
static constexpr uint8_t getVertexStageShift() noexcept {
// We assume the bottom half of bits are for vertex stages.
return 0;
static constexpr Bitmask getVertexStage() noexcept {
if constexpr (std::is_same_v<Bitmask, UniformBufferBitmask>) {
return UBO_VERTEX_STAGE;
}
if constexpr (std::is_same_v<Bitmask, SamplerBitmask>) {
return SAMPLER_VERTEX_STAGE;
}
if constexpr (std::is_same_v<Bitmask, InputAttachmentBitmask>) {
return INPUT_ATTACHMENT_VERTEX_STAGE;
}
}
template<typename Bitmask>
static constexpr uint8_t getFragmentStageShift() noexcept {
// We assume the top half of bits are for fragment stages.
return sizeof(Bitmask) * 4;
static constexpr Bitmask getFragmentStage() noexcept {
if constexpr (std::is_same_v<Bitmask, UniformBufferBitmask>) {
return UBO_FRAGMENT_STAGE;
}
if constexpr (std::is_same_v<Bitmask, SamplerBitmask>) {
return SAMPLER_FRAGMENT_STAGE;
}
if constexpr (std::is_same_v<Bitmask, InputAttachmentBitmask>) {
return INPUT_ATTACHMENT_FRAGMENT_STAGE;
}
}
// We have at most 4 descriptor sets. This is to indicate which ones are active.
using DescriptorSetMask = utils::bitset8;
typedef enum ShaderStageFlags2 : uint8_t {
NONE = 0,
VERTEX = 0x1,
FRAGMENT = 0x2,
} ShaderStageFlags2;
enum class DescriptorType : uint8_t {
UNIFORM_BUFFER,
SAMPLER,
INPUT_ATTACHMENT,
};
enum class DescriptorFlags : uint8_t {
NONE = 0x00,
DYNAMIC_OFFSET = 0x01
};
struct DescriptorSetLayoutBinding {
DescriptorType type;
ShaderStageFlags2 stageFlags;
uint8_t binding;
DescriptorFlags flags;
uint16_t count;
};
struct DescriptorSetLayout {
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
};
} // namespace descset
namespace {
// Use constexpr to statically generate a bit count table for 8-bit numbers.
struct _BitCountHelper {
constexpr _BitCountHelper() : data{} {
for (uint16_t i = 0; i < 256; ++i) {
data[i] = 0;
for (auto j = i; j > 0; j /= 2) {
if (j & 1) {
data[i]++;
}
}
}
}
template<typename MaskType>
constexpr uint8_t count(MaskType num) {
uint8_t count = 0;
for (uint8_t i = 0; i < sizeof(MaskType) * 8; i+=8) {
count += data[(num >> i) & 0xFF];
}
return count;
}
private:
uint8_t data[256];
};
} // namespace anonymous
template<typename MaskType>
inline uint8_t countBits(MaskType num) {
static _BitCountHelper BitCounter = {};
return BitCounter.count(num);
}
// This is useful for counting the total number of descriptors for both vertex and fragment stages.
template<typename MaskType>
inline MaskType collapseStages(MaskType mask) {
constexpr uint8_t NBITS_DIV_2 = sizeof(MaskType) * 4;
// First zero out the top-half and then or the bottom-half against the original top-half.
return ((mask << NBITS_DIV_2) >> NBITS_DIV_2) | (mask >> NBITS_DIV_2);
}
} // namespace filament::backend

View File

@@ -30,8 +30,12 @@
#include <bluevk/BlueVK.h>
#include <tsl/robin_map.h>
#include <functional>
namespace filament::backend {
using namespace descset;
// [GDSR]: Great-Descriptor-Set-Refactor: As of 03/20/24, the Filament frontend is planning to
// introduce descriptor set. This PR will arrive before that change is complete. As such, some of
// the methods introduced here will be obsolete, and certain logic will be generalized.
@@ -41,34 +45,60 @@ class VulkanDescriptorSetManager {
public:
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT =
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT;
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
using GetPipelineLayoutFunction = std::function<VkPipelineLayout(
VulkanDescriptorSetLayoutList const&, VulkanProgram* program)>;
VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator);
void terminate() noexcept;
void updateBuffer(VulkanDescriptorSet* set, uint8_t binding,
void gc();
// TODO: Obsolete after [GDSR].
// This will write/update/bind all of the descriptor set. After [GDSR], the binding for
// descriptor sets will not depend on the layout described in the program.
VkPipelineLayout bind(VulkanCommandBuffer* commands, VulkanProgram* program,
GetPipelineLayoutFunction& getPipelineLayoutFn);
// TODO: Obsolete after [GDSR].
// This is to "dynamically" bind UBOs that might have offsets changed between pipeline binding
// and the draw call. We do this because UBOs for primitives that are part of the same
// renderable can be stored within one buffer. This can be a no-op if there were no range
// changes between the pipeline bind and the draw call. We will re-use applicable states
// provided within the bind() call, including the UBO descriptor set layout. TODO: make it a
// proper dynamic binding when Filament-side descriptor changes are completed.
void dynamicBind(VulkanCommandBuffer* commands, Handle<VulkanDescriptorSetLayout> uboLayout);
// TODO: Obsolete after [GDSR].
// Since we use program pointer as cache key, we need to clear the cache when it's freed.
void clearProgram(VulkanProgram* program) noexcept;
Handle<VulkanDescriptorSetLayout> createLayout(descset::DescriptorSetLayout const& layout);
void destroyLayout(Handle<VulkanDescriptorSetLayout> layout);
void updateBuffer(Handle<VulkanDescriptorSet> set, uint8_t binding,
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept;
void updateSampler(VulkanDescriptorSet* set, uint8_t binding,
void updateSampler(Handle<VulkanDescriptorSet> set, uint8_t binding,
VulkanTexture* texture, VkSampler sampler) noexcept;
void updateInputAttachment(VulkanDescriptorSet* set, VulkanAttachment attachment) noexcept;
void updateInputAttachment(Handle<VulkanDescriptorSet> set, VulkanAttachment attachment) noexcept;
void bind(uint8_t setIndex, VulkanDescriptorSet* set, backend::DescriptorSetOffsetArray&& offsets);
void commit(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout,
DescriptorSetMask const& setMask);
void clearBuffer(uint32_t bindingIndex);
void setPlaceHolders(VkSampler sampler, VulkanTexture* texture,
VulkanBufferObject* bufferObject) noexcept;
void createSet(Handle<HwDescriptorSet> handle, VulkanDescriptorSetLayout* layout);
void clearState() noexcept;
void destroySet(Handle<HwDescriptorSet> handle);
// TODO: To be completed after [GDSR]
Handle<VulkanDescriptorSet> createSet(Handle<VulkanDescriptorSetLayout> layout) {
return Handle<VulkanDescriptorSet>();
}
void initVkLayout(VulkanDescriptorSetLayout* layout);
// TODO: To be completed after [GDSR]
void destroySet(Handle<VulkanDescriptorSet> set) {}
private:
class Impl;
@@ -78,3 +108,4 @@ private:
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H

View File

@@ -21,20 +21,19 @@
namespace filament::backend {
VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
DescriptorSetLayoutArray const& descriptorSetLayouts, VulkanProgram* program) {
VulkanDescriptorSetLayoutList const& descriptorSetLayouts, VulkanProgram* program) {
PipelineLayoutKey key = {};
uint8_t descSetLayoutCount = 0;
key.descSetLayouts = descriptorSetLayouts;
for (auto layoutHandle: descriptorSetLayouts) {
if (layoutHandle == VK_NULL_HANDLE) {
break;
if (layoutHandle) {
auto layout = mAllocator->handle_cast<VulkanDescriptorSetLayout*>(layoutHandle);
key.descSetLayouts[descSetLayoutCount++] = layout->vklayout;
}
descSetLayoutCount++;
}
// build the push constant layout key
uint32_t const pushConstantRangeCount = program->getPushConstantRangeCount();
auto const& pushConstantRanges = program->getPushConstantRanges();
uint32_t pushConstantRangeCount = program->getPushConstantRangeCount();
auto const& pushConstantRanges = program->getPushConstantRanges();
if (pushConstantRangeCount > 0) {
assert_invariant(pushConstantRangeCount <= Program::SHADER_TYPE_COUNT);
for (uint8_t i = 0; i < pushConstantRangeCount; ++i) {
@@ -53,8 +52,8 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
}
}
if (auto iter = mPipelineLayouts.find(key); iter != mPipelineLayouts.end()) {
PipelineLayoutCacheEntry& entry = iter->second;
if (PipelineLayoutMap::iterator iter = mPipelineLayouts.find(key); iter != mPipelineLayouts.end()) {
PipelineLayoutCacheEntry& entry = iter.value();
entry.lastUsed = mTimestamp++;
return entry.handle;
}

View File

@@ -22,40 +22,41 @@
#include <utils/Hash.h>
#include <unordered_map>
#include <tsl/robin_map.h>
namespace filament::backend {
class VulkanPipelineLayoutCache {
public:
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
VulkanPipelineLayoutCache(VkDevice device, VulkanResourceAllocator* allocator)
: mDevice(device),
mAllocator(allocator),
mTimestamp(0) {}
void terminate() noexcept;
struct PushConstantKey {
uint8_t stage = 0;// We have one set of push constant per shader stage (fragment, vertex, etc).
uint8_t size = 0;
uint8_t stage;// We have one set of push constant per shader stage (fragment, vertex, etc).
uint8_t size;
// Note that there is also an offset parameter for push constants, but
// we always assume our update range will have the offset 0.
};
struct PipelineLayoutKey {
DescriptorSetLayoutArray descSetLayouts = {}; // 8 * 4
PushConstantKey pushConstant[Program::SHADER_TYPE_COUNT] = {}; // 2 * 3
using DescriptorSetLayoutArray = std::array<VkDescriptorSetLayout,
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
DescriptorSetLayoutArray descSetLayouts = {}; // 8 * 3
PushConstantKey pushConstant[Program::SHADER_TYPE_COUNT] = {}; // 2 * 3
uint16_t padding = 0;
};
static_assert(sizeof(PipelineLayoutKey) == 40);
static_assert(sizeof(PipelineLayoutKey) == 32);
VulkanPipelineLayoutCache(VulkanPipelineLayoutCache const&) = delete;
VulkanPipelineLayoutCache& operator=(VulkanPipelineLayoutCache const&) = delete;
// A pipeline layout depends on the descriptor set layout and the push constant ranges, which
// are described in the program.
VkPipelineLayout getLayout(DescriptorSetLayoutArray const& descriptorSetLayouts,
VkPipelineLayout getLayout(VulkanDescriptorSetLayoutList const& descriptorSetLayouts,
VulkanProgram* program);
private:
@@ -72,14 +73,15 @@ private:
}
};
using PipelineLayoutMap = std::unordered_map<PipelineLayoutKey, PipelineLayoutCacheEntry,
using PipelineLayoutMap = tsl::robin_map<PipelineLayoutKey, PipelineLayoutCacheEntry,
PipelineLayoutKeyHashFn, PipelineLayoutKeyEqual>;
VkDevice mDevice;
VulkanResourceAllocator* mAllocator;
Timestamp mTimestamp;
PipelineLayoutMap mPipelineLayouts;
};
} // filament::backend
}
#endif // TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H

View File

@@ -112,10 +112,9 @@ void BackendTest::fullViewport(Viewport& viewport) {
}
void BackendTest::renderTriangle(
PipelineLayout const& pipelineLayout,
Handle<filament::backend::HwRenderTarget> renderTarget,
Handle<filament::backend::HwSwapChain> swapChain,
Handle<filament::backend::HwProgram> program) {
filament::backend::Handle<filament::backend::HwRenderTarget> renderTarget,
filament::backend::Handle<filament::backend::HwSwapChain> swapChain,
filament::backend::Handle<filament::backend::HwProgram> program) {
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
@@ -124,15 +123,11 @@ void BackendTest::renderTriangle(
params.flags.discardEnd = TargetBufferFlags::NONE;
params.viewport.height = 512;
params.viewport.width = 512;
renderTriangle(pipelineLayout, renderTarget, swapChain, program, params);
renderTriangle(renderTarget, swapChain, program, params);
}
void BackendTest::renderTriangle(
PipelineLayout const& pipelineLayout,
Handle<HwRenderTarget> renderTarget,
Handle<HwSwapChain> swapChain,
Handle<HwProgram> program,
const RenderPassParams& params) {
void BackendTest::renderTriangle(Handle<HwRenderTarget> renderTarget,
Handle<HwSwapChain> swapChain, Handle<HwProgram> program, const RenderPassParams& params) {
auto& api = getDriverApi();
TrianglePrimitive triangle(api);
@@ -143,7 +138,6 @@ void BackendTest::renderTriangle(
PipelineState state;
state.program = program;
state.pipelineLayout = pipelineLayout;
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;

View File

@@ -51,15 +51,10 @@ protected:
static void fullViewport(filament::backend::RenderPassParams& params);
static void fullViewport(filament::backend::Viewport& viewport);
void renderTriangle(
filament::backend::PipelineLayout const& pipelineLayout,
filament::backend::Handle<filament::backend::HwRenderTarget> renderTarget,
void renderTriangle(filament::backend::Handle<filament::backend::HwRenderTarget> renderTarget,
filament::backend::Handle<filament::backend::HwSwapChain> swapChain,
filament::backend::Handle<filament::backend::HwProgram> program);
void renderTriangle(
filament::backend::PipelineLayout const& pipelineLayout,
filament::backend::Handle<filament::backend::HwRenderTarget> renderTarget,
void renderTriangle(filament::backend::Handle<filament::backend::HwRenderTarget> renderTarget,
filament::backend::Handle<filament::backend::HwSwapChain> swapChain,
filament::backend::Handle<filament::backend::HwProgram> program,
const filament::backend::RenderPassParams& params);

View File

@@ -1,159 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gtest/gtest.h>
#include "../src/metal/MetalContext.h"
namespace test {
TEST(MetalDynamicOffsets, none) {
filament::backend::MetalDynamicOffsets dynamicOffsets;
const auto [count, offsets] = dynamicOffsets.getOffsets();
EXPECT_EQ(count, 0u);
}
TEST(MetalDynamicOffsets, basic) {
filament::backend::MetalDynamicOffsets dynamicOffsets;
{
const auto [count, offsets] = dynamicOffsets.getOffsets();
EXPECT_EQ(count, 0u);
}
{
uint32_t o[2] = { 1, 2 };
dynamicOffsets.setOffsets(0, o, 2);
const auto [count, offsets] = dynamicOffsets.getOffsets();
EXPECT_EQ(count, 2);
EXPECT_EQ(offsets[0], 1);
EXPECT_EQ(offsets[1], 2);
}
{
uint32_t o[3] = { 3, 4, 5 };
dynamicOffsets.setOffsets(1, o, 3);
const auto [count, offsets] = dynamicOffsets.getOffsets();
EXPECT_EQ(count, 5);
EXPECT_EQ(offsets[0], 1);
EXPECT_EQ(offsets[1], 2);
EXPECT_EQ(offsets[2], 3);
EXPECT_EQ(offsets[3], 4);
EXPECT_EQ(offsets[4], 5);
}
// skip descriptor set index 2
{
uint32_t o[1] = { 6 };
dynamicOffsets.setOffsets(3, o, 1);
const auto [count, offsets] = dynamicOffsets.getOffsets();
EXPECT_EQ(count, 6);
EXPECT_EQ(offsets[0], 1);
EXPECT_EQ(offsets[1], 2);
EXPECT_EQ(offsets[2], 3);
EXPECT_EQ(offsets[3], 4);
EXPECT_EQ(offsets[4], 5);
EXPECT_EQ(offsets[5], 6);
}
}
TEST(MetalDynamicOffsets, outOfOrder) {
filament::backend::MetalDynamicOffsets dynamicOffsets;
uint32_t o1[2] = { 2, 3 };
dynamicOffsets.setOffsets(1, o1, 2);
uint32_t o2[2] = { 0, 1 };
dynamicOffsets.setOffsets(0, o2, 2);
uint32_t o3[2] = { 4, 5 };
dynamicOffsets.setOffsets(2, o3, 2);
const auto [count, offsets] = dynamicOffsets.getOffsets();
EXPECT_EQ(count, 6);
EXPECT_EQ(offsets[0], 0);
EXPECT_EQ(offsets[1], 1);
EXPECT_EQ(offsets[2], 2);
EXPECT_EQ(offsets[3], 3);
EXPECT_EQ(offsets[4], 4);
EXPECT_EQ(offsets[5], 5);
};
TEST(MetalDynamicOffsets, removal) {
filament::backend::MetalDynamicOffsets dynamicOffsets;
uint32_t o1[2] = { 2, 3 };
dynamicOffsets.setOffsets(1, o1, 2);
uint32_t o2[2] = { 0, 1 };
dynamicOffsets.setOffsets(0, o2, 2);
uint32_t o3[2] = { 4, 5 };
dynamicOffsets.setOffsets(2, o3, 2);
dynamicOffsets.setOffsets(1, nullptr, 0);
const auto [count, offsets] = dynamicOffsets.getOffsets();
EXPECT_EQ(count, 4);
EXPECT_EQ(offsets[0], 0);
EXPECT_EQ(offsets[1], 1);
EXPECT_EQ(offsets[2], 4);
EXPECT_EQ(offsets[3], 5);
};
TEST(MetalDynamicOffsets, resize) {
filament::backend::MetalDynamicOffsets dynamicOffsets;
uint32_t o1[2] = { 2, 3 };
dynamicOffsets.setOffsets(1, o1, 2);
uint32_t o2[2] = { 0, 1 };
dynamicOffsets.setOffsets(0, o2, 2);
uint32_t o3[2] = { 6, 7 };
dynamicOffsets.setOffsets(2, o3, 2);
uint32_t o4[4] = { 2, 3, 4, 5 };
dynamicOffsets.setOffsets(1, o4, 4);
const auto [count, offsets] = dynamicOffsets.getOffsets();
EXPECT_EQ(count, 8);
EXPECT_EQ(offsets[0], 0);
EXPECT_EQ(offsets[1], 1);
EXPECT_EQ(offsets[2], 2);
EXPECT_EQ(offsets[3], 3);
EXPECT_EQ(offsets[4], 4);
EXPECT_EQ(offsets[5], 5);
EXPECT_EQ(offsets[6], 6);
EXPECT_EQ(offsets[7], 7);
};
TEST(MetalDynamicOffsets, dirty) {
filament::backend::MetalDynamicOffsets dynamicOffsets;
EXPECT_FALSE(dynamicOffsets.isDirty());
uint32_t o1[2] = { 2, 3 };
dynamicOffsets.setOffsets(1, o1, 2);
EXPECT_TRUE(dynamicOffsets.isDirty());
dynamicOffsets.setDirty(false);
EXPECT_FALSE(dynamicOffsets.isDirty());
// Setting the same offsets should not mark the offsets as dirty
dynamicOffsets.setOffsets(1, o1, 2);
EXPECT_FALSE(dynamicOffsets.isDirty());
// Resizing the offsets should mark the offsets as dirty
uint32_t o2[3] = { 4, 5, 6 };
dynamicOffsets.setOffsets(1, o2, 3);
EXPECT_TRUE(dynamicOffsets.isDirty());
};
};
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -73,13 +73,12 @@ void ShaderGenerator::shutdown() {
FinalizeProcess();
}
ShaderGenerator::ShaderGenerator(std::string vertex, std::string fragment, Backend backend,
bool isMobile, filamat::DescriptorSets&& descriptorSets) noexcept
: mBackend(backend),
mVertexBlob(transpileShader(
ShaderStage::VERTEX, std::move(vertex), backend, isMobile, descriptorSets)),
mFragmentBlob(transpileShader(
ShaderStage::FRAGMENT, std::move(fragment), backend, isMobile, descriptorSets)) {
ShaderGenerator::ShaderGenerator(std::string vertex, std::string fragment,
Backend backend, bool isMobile, const filament::SamplerInterfaceBlock* sib) noexcept
: mBackend(backend),
mVertexBlob(transpileShader(ShaderStage::VERTEX, std::move(vertex), backend, isMobile, sib)),
mFragmentBlob(transpileShader(ShaderStage::FRAGMENT, std::move(fragment), backend,
isMobile, sib)) {
switch (backend) {
case Backend::OPENGL:
mShaderLanguage = filament::backend::ShaderLanguage::ESSL3;
@@ -96,8 +95,9 @@ ShaderGenerator::ShaderGenerator(std::string vertex, std::string fragment, Backe
}
}
ShaderGenerator::Blob ShaderGenerator::transpileShader(ShaderStage stage, std::string shader,
Backend backend, bool isMobile, const filamat::DescriptorSets& descriptorSets) noexcept {
ShaderGenerator::Blob ShaderGenerator::transpileShader(
ShaderStage stage, std::string shader, Backend backend, bool isMobile,
const filament::SamplerInterfaceBlock* sib) noexcept {
TProgram program;
const EShLanguage language = stage == ShaderStage::VERTEX ? EShLangVertex : EShLangFragment;
TShader tShader(language);
@@ -161,8 +161,9 @@ ShaderGenerator::Blob ShaderGenerator::transpileShader(ShaderStage stage, std::s
return { result.c_str(), result.c_str() + result.length() + 1 };
} else if (backend == Backend::METAL) {
const auto sm = isMobile ? ShaderModel::MOBILE : ShaderModel::DESKTOP;
filamat::GLSLPostProcessor::spirvToMsl(
&spirv, &result, stage, sm, false, descriptorSets, nullptr);
filamat::SibVector sibs = filamat::SibVector::with_capacity(1);
if (sib) { sibs.emplace_back(0, sib); }
filamat::GLSLPostProcessor::spirvToMsl(&spirv, &result, sm, false, sibs, nullptr);
return { result.c_str(), result.c_str() + result.length() + 1 };
} else if (backend == Backend::VULKAN) {
return { (uint8_t*)spirv.data(), (uint8_t*)(spirv.data() + spirv.size()) };

View File

@@ -22,7 +22,6 @@
#include "private/filament/SamplerInterfaceBlock.h"
#include "private/backend/DriverApi.h"
#include "backend/Program.h"
#include "../src/GLSLPostProcessor.h"
#include <string>
@@ -41,7 +40,7 @@ public:
* @param fragment The fragment shader, written in GLSL 450 core.
*/
ShaderGenerator(std::string vertex, std::string fragment, Backend backend, bool isMobile,
filamat::DescriptorSets&& descriptorSets = {}) noexcept;
const filament::SamplerInterfaceBlock* sib = nullptr) noexcept;
ShaderGenerator(const ShaderGenerator& rhs) = delete;
ShaderGenerator& operator=(const ShaderGenerator& rhs) = delete;
@@ -53,7 +52,7 @@ private:
using Blob = std::vector<char>;
static Blob transpileShader(ShaderStage stage, std::string shader, Backend backend,
bool isMobile, const filamat::DescriptorSets& descriptorSets) noexcept;
bool isMobile, const filament::SamplerInterfaceBlock* sib = nullptr) noexcept;
Backend mBackend;

View File

@@ -53,7 +53,8 @@ test::NativeView getNativeView() {
nativeView.width = static_cast<size_t>(drawableSize.width);
nativeView.height = static_cast<size_t>(drawableSize.height);
exit(test::runTests());
test::runTests();
// exit(runTests());
}
- (NSView*)createView {

View File

@@ -38,7 +38,7 @@ using namespace utils;
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;
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)
@@ -50,7 +50,7 @@ void main() {
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;
uniform Params { highp vec4 color; highp vec4 scale; } params;
void main() {
fragColor = params.color;
})";
@@ -348,26 +348,12 @@ TEST_F(BackendTest, ColorResolve) {
// Create a program.
ProgramHandle program;
{
filamat::DescriptorSets descriptors;
descriptors[1] = { { "Params",
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0 },
{} } };
ShaderGenerator shaderGen(
triangleVs, triangleFs, sBackend, sIsMobilePlatform, std::move(descriptors));
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
Program prog = shaderGen.getProgram(api);
prog.descriptorBindings(1, {{ "Params", DescriptorType::UNIFORM_BUFFER, 0 }});
prog.uniformBlockBindings({{"params", 1}});
program = api.createProgram(std::move(prog));
}
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
{{
DescriptorType::UNIFORM_BUFFER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}});
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
// Create a VertexBuffer, IndexBuffer, and RenderPrimitive.
TrianglePrimitive const triangle(api);
@@ -402,7 +388,6 @@ TEST_F(BackendTest, ColorResolve) {
PipelineState state = {};
state.program = program;
state.pipelineLayout.setLayout[1] = { descriptorSetLayout };
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
@@ -416,12 +401,10 @@ TEST_F(BackendTest, ColorResolve) {
.scale = float4(1, 1, 0.5, 0),
});
api.updateDescriptorSetBuffer(descriptorSet, 0, ubuffer, 0, sizeof(MaterialParams));
api.bindDescriptorSet(descriptorSet, 1, {});
// FIXME: on Metal this triangle is not drawn. Can't understand why.
api.beginFrame(0, 0, 0);
api.beginRenderPass(srcRenderTarget, params);
api.bindUniformBuffer(0, ubuffer);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
api.endFrame(0);
@@ -445,8 +428,6 @@ TEST_F(BackendTest, ColorResolve) {
EXPECT_TRUE(sparams.pixelHashResult == expected);
// Cleanup.
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyBufferObject(ubuffer);
api.destroyProgram(program);
api.destroyTexture(srcColorTexture);

View File

@@ -31,7 +31,7 @@ layout(location = 0) in vec4 mesh_position;
layout(location = 0) out uvec4 indices;
layout(binding = 0, set = 1) uniform Params {
uniform Params {
highp vec4 padding[4]; // offset of 64 bytes
highp vec4 color;
@@ -51,7 +51,7 @@ std::string fragment (R"(#version 450 core
layout(location = 0) out vec4 fragColor;
layout(binding = 0, set = 1) uniform Params {
uniform Params {
highp vec4 padding[4]; // offset of 64 bytes
highp vec4 color;
@@ -89,36 +89,20 @@ TEST_F(BackendTest, VertexBufferUpdate) {
// The test is executed within this block scope to force destructors to run before
// executeCommands().
{
auto& api = getDriverApi();
// Create a platform-specific SwapChain and make it current.
auto swapChain = createSwapChain();
api.makeCurrent(swapChain, swapChain);
getDriverApi().makeCurrent(swapChain, swapChain);
// Create a program.
filamat::DescriptorSets descriptors;
descriptors[1] = { { "Params",
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, 0 }, {} } };
ShaderGenerator shaderGen(
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
Program p = shaderGen.getProgram(api);
p.descriptorBindings(1, {{ "Params", DescriptorType::UNIFORM_BUFFER, 0 }});
auto program = api.createProgram(std::move(p));
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(getDriverApi());
auto program = getDriverApi().createProgram(std::move(p));
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
{{
DescriptorType::UNIFORM_BUFFER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}});
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
auto defaultRenderTarget = api.createDefaultRenderTarget(0);
auto defaultRenderTarget = getDriverApi().createDefaultRenderTarget(0);
// To test large buffers (which exercise a different code path) create an extra large
// buffer. Only the first 3 vertices will be used.
TrianglePrimitive triangle(api, largeBuffers);
TrianglePrimitive triangle(getDriverApi(), largeBuffers);
RenderPassParams params = {};
fullViewport(params);
@@ -129,7 +113,6 @@ TEST_F(BackendTest, VertexBufferUpdate) {
PipelineState state;
state.program = program;
state.pipelineLayout.setLayout[1] = { descriptorSetLayout };
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
@@ -138,13 +121,11 @@ TEST_F(BackendTest, 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 = api.createBufferObject(sizeof(MaterialParams) + 64,
auto ubuffer = getDriverApi().createBufferObject(sizeof(MaterialParams) + 64,
BufferObjectBinding::UNIFORM, BufferUsage::STATIC);
getDriverApi().bindUniformBuffer(0, ubuffer);
api.updateDescriptorSetBuffer(descriptorSet, 0, ubuffer, 0, sizeof(MaterialParams) + 64);
api.bindDescriptorSet(descriptorSet, 1, {});
api.startCapture(0);
getDriverApi().startCapture(0);
// Upload uniforms.
{
@@ -158,11 +139,11 @@ TEST_F(BackendTest, VertexBufferUpdate) {
delete sp;
};
BufferDescriptor bd(tmp, sizeof(MaterialParams), cb);
api.updateBufferObject(ubuffer, std::move(bd), 64);
getDriverApi().updateBufferObject(ubuffer, std::move(bd), 64);
}
api.makeCurrent(swapChain, swapChain);
api.beginFrame(0, 0, 0);
getDriverApi().makeCurrent(swapChain, swapChain);
getDriverApi().beginFrame(0, 0, 0);
// Draw 10 triangles, updating the vertex buffer / index buffer each time.
size_t triangleIndex = 0;
@@ -190,25 +171,22 @@ TEST_F(BackendTest, VertexBufferUpdate) {
params.flags.discardStart = TargetBufferFlags::NONE;
}
api.beginRenderPass(defaultRenderTarget, params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
getDriverApi().beginRenderPass(defaultRenderTarget, params);
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
getDriverApi().endRenderPass();
triangleIndex++;
}
api.flush();
api.commit(swapChain);
api.endFrame(0);
getDriverApi().flush();
getDriverApi().commit(swapChain);
getDriverApi().endFrame(0);
api.stopCapture(0);
getDriverApi().stopCapture(0);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyBufferObject(ubuffer);
api.destroyRenderTarget(defaultRenderTarget);
getDriverApi().destroyProgram(program);
getDriverApi().destroySwapChain(swapChain);
getDriverApi().destroyRenderTarget(defaultRenderTarget);
}
executeCommands();
@@ -217,45 +195,27 @@ TEST_F(BackendTest, VertexBufferUpdate) {
// This test renders two triangles in two separate draw calls. Between the draw calls, a uniform
// buffer object is partially updated.
TEST_F(BackendTest, BufferObjectUpdateWithOffset) {
auto& api = getDriverApi();
// Create a platform-specific SwapChain and make it current.
auto swapChain = createSwapChain();
api.makeCurrent(swapChain, swapChain);
getDriverApi().makeCurrent(swapChain, swapChain);
// Create a program.
filamat::DescriptorSets descriptors;
descriptors[1] = { { "Params",
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, 0 }, {} } };
ShaderGenerator shaderGen(
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
Program p = shaderGen.getProgram(api);
p.descriptorBindings(1, {{ "Params", DescriptorType::UNIFORM_BUFFER, 0 }});
auto program = api.createProgram(std::move(p));
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
{{
DescriptorType::UNIFORM_BUFFER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}});
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(getDriverApi());
p.uniformBlockBindings({{"params", 1}});
auto program = getDriverApi().createProgram(std::move(p));
// 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 = api.createBufferObject(sizeof(MaterialParams) + 64,
auto ubuffer = getDriverApi().createBufferObject(sizeof(MaterialParams) + 64,
BufferObjectBinding::UNIFORM, BufferUsage::STATIC);
api.updateDescriptorSetBuffer(descriptorSet, 0, ubuffer, 0, sizeof(MaterialParams) + 64);
api.bindDescriptorSet(descriptorSet, 1, {});
getDriverApi().bindUniformBuffer(0, ubuffer);
// Create a render target.
auto colorTexture = api.createTexture(SamplerType::SAMPLER_2D, 1,
auto colorTexture = getDriverApi().createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT);
auto renderTarget = api.createRenderTarget(
auto renderTarget = getDriverApi().createRenderTarget(
TargetBufferFlags::COLOR0, 512, 512, 1, 0, {{colorTexture}}, {}, {});
// Upload uniforms for the first triangle.
@@ -270,7 +230,7 @@ TEST_F(BackendTest, BufferObjectUpdateWithOffset) {
delete sp;
};
BufferDescriptor bd(tmp, sizeof(MaterialParams), cb);
api.updateBufferObject(ubuffer, std::move(bd), 64);
getDriverApi().updateBufferObject(ubuffer, std::move(bd), 64);
}
RenderPassParams params = {};
@@ -280,8 +240,7 @@ TEST_F(BackendTest, BufferObjectUpdateWithOffset) {
params.flags.discardEnd = TargetBufferFlags::NONE;
params.viewport.height = 512;
params.viewport.width = 512;
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
renderTarget, swapChain, program, params);
renderTriangle(renderTarget, swapChain, program, 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.
@@ -296,32 +255,29 @@ TEST_F(BackendTest, BufferObjectUpdateWithOffset) {
delete sp;
};
BufferDescriptor bd((char*)tmp + offsetof(MaterialParams, color.b), sizeof(float) * 4, cb);
api.updateBufferObject(ubuffer, std::move(bd), 64 + offsetof(MaterialParams, color.b));
getDriverApi().updateBufferObject(ubuffer, std::move(bd), 64 + offsetof(MaterialParams, color.b));
}
params.flags.clear = TargetBufferFlags::NONE;
params.flags.discardStart = TargetBufferFlags::NONE;
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
renderTarget, swapChain, program, params);
renderTriangle(renderTarget, swapChain, program, params);
static const uint32_t expectedHash = 91322442;
readPixelsAndAssertHash(
"BufferObjectUpdateWithOffset", 512, 512, renderTarget, expectedHash, true);
api.flush();
api.commit(swapChain);
api.endFrame(0);
getDriverApi().flush();
getDriverApi().commit(swapChain);
getDriverApi().endFrame(0);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyBufferObject(ubuffer);
api.destroyRenderTarget(renderTarget);
api.destroyTexture(colorTexture);
getDriverApi().destroyProgram(program);
getDriverApi().destroySwapChain(swapChain);
getDriverApi().destroyBufferObject(ubuffer);
getDriverApi().destroyRenderTarget(renderTarget);
getDriverApi().destroyTexture(colorTexture);
// This ensures all driver commands have finished before exiting the test.
api.finish();
getDriverApi().finish();
executeCommands();

View File

@@ -155,9 +155,8 @@ kernel void main0(device Output_data& output_data [[buffer(0)]],
driver.dispatchCompute(ph, { groupCount, 1, 1 });
// FIXME: we need a way to unbind the buffer in order to read from them
// driver.unbindBuffer(BufferObjectBinding::SHADER_STORAGE, 0);
// driver.unbindBuffer(BufferObjectBinding::SHADER_STORAGE, 1);
driver.unbindBuffer(BufferObjectBinding::SHADER_STORAGE, 0);
driver.unbindBuffer(BufferObjectBinding::SHADER_STORAGE, 1);
float* const user = (float*)malloc(size);
driver.readBufferSubData(output_data, 0, size, { user, size });

View File

@@ -19,17 +19,12 @@
#include "ShaderGenerator.h"
#include "TrianglePrimitive.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include "private/backend/SamplerGroup.h"
#include <utils/Hash.h>
#include <utils/Log.h>
#include <fstream>
#include <string>
#include <stddef.h>
#include <stdint.h>
#ifndef IOS
#include <imageio/ImageEncoder.h>
@@ -42,28 +37,27 @@ using namespace image;
// Shaders
////////////////////////////////////////////////////////////////////////////////////////////////////
static std::string const fullscreenVs = R"(#version 450 core
static std::string fullscreenVs = R"(#version 450 core
layout(location = 0) in vec4 mesh_position;
void main() {
// Hack: move and scale triangle so that it covers entire viewport.
gl_Position = vec4((mesh_position.xy + 0.5) * 5.0, 0.0, 1.0);
})";
static std::string const fullscreenFs = R"(#version 450 core
static std::string fullscreenFs = R"(#version 450 core
precision mediump int; precision highp float;
layout(location = 0) out vec4 fragColor;
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
// This parameter is ignored for other backends.
layout(binding = 0, set = 1) uniform sampler2D test_tex;
layout(location = 0, set = 1) uniform sampler2D test_tex;
layout(binding = 1, set = 1) uniform Params {
uniform Params {
highp float fbWidth;
highp float fbHeight;
highp float sourceLevel;
highp float unused;
} params;
void main() {
vec2 fbsize = vec2(params.fbWidth, params.fbHeight);
vec2 uv = (gl_FragCoord.xy + 0.5) / fbsize;
@@ -112,12 +106,12 @@ static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt) {
int w = kTexWidth, h = kTexHeight;
const uint32_t* texels = (uint32_t*) buffer;
sPixelHashResult = utils::hash::murmur3(texels, size / 4, 0);
#ifndef IOS
#ifndef IOS
LinearImage image(w, h, 4);
image = toLinearWithAlpha<uint8_t>(w, h, w * 4, (uint8_t*) buffer);
std::ofstream pngstrm("feedback.png", std::ios::binary | std::ios::trunc);
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", "feedback.png");
#endif
#endif
free(buffer);
};
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb);
@@ -140,37 +134,19 @@ TEST_F(BackendTest, FeedbackLoops) {
// Create a program.
ProgramHandle program;
{
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));
SamplerInterfaceBlock const sib = filament::SamplerInterfaceBlock::Builder()
.name("Test")
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} )
.build();
ShaderGenerator shaderGen(fullscreenVs, fullscreenFs, sBackend, sIsMobilePlatform, &sib);
Program prog = shaderGen.getProgram(api);
prog.descriptorBindings(1, {
{ "test_tex", DescriptorType::SAMPLER, 0 },
{ "Params", DescriptorType::UNIFORM_BUFFER, 1 }
});
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
prog.uniformBlockBindings({{"params", 1}});
program = api.createProgram(std::move(prog));
}
DescriptorSetLayoutHandle descriptorSetLayout = 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
}}});
TrianglePrimitive const triangle(getDriverApi());
// Create a texture.
@@ -178,10 +154,6 @@ TEST_F(BackendTest, FeedbackLoops) {
Handle<HwTexture> const texture = api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage);
// Create ubo
auto ubuffer = api.createBufferObject(sizeof(MaterialParams),
BufferObjectBinding::UNIFORM, BufferUsage::STATIC);
// Create a RenderTarget for each miplevel.
Handle<HwRenderTarget> renderTargets[kNumLevels];
for (uint8_t level = 0; level < kNumLevels; level++) {
@@ -217,10 +189,20 @@ TEST_F(BackendTest, FeedbackLoops) {
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
state.program = program;
state.pipelineLayout.setLayout[1] = { descriptorSetLayout };
SamplerGroup samplers(1);
SamplerParams sparams = {};
sparams.filterMag = SamplerMagFilter::LINEAR;
sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
samplers.setSampler(0, { texture, sparams });
auto sgroup =
api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test"));
api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api));
auto ubuffer = api.createBufferObject(sizeof(MaterialParams),
BufferObjectBinding::UNIFORM, BufferUsage::STATIC);
api.makeCurrent(swapChain, swapChain);
api.beginFrame(0, 0, 0);
api.bindSamplers(0, sgroup);
api.bindUniformBuffer(0, ubuffer);
// Downsample passes
params.flags.discardStart = TargetBufferFlags::ALL;
@@ -229,16 +211,7 @@ TEST_F(BackendTest, FeedbackLoops) {
const uint32_t sourceLevel = targetLevel - 1;
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
auto textureView = api.createTextureView(texture, sourceLevel, 1);
DescriptorSetHandle descriptorSet = 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, {});
getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel);
uploadUniforms(getDriverApi(), ubuffer, {
.fbWidth = float(params.viewport.width),
.fbHeight = float(params.viewport.height),
@@ -247,8 +220,6 @@ TEST_F(BackendTest, FeedbackLoops) {
api.beginRenderPass(renderTargets[targetLevel], params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
api.destroyTexture(textureView);
api.destroyDescriptorSet(descriptorSet);
}
// Upsample passes
@@ -259,16 +230,7 @@ TEST_F(BackendTest, FeedbackLoops) {
const uint32_t sourceLevel = targetLevel + 1;
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
auto textureView = api.createTextureView(texture, sourceLevel, 1);
DescriptorSetHandle descriptorSet = 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, {});
getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel);
uploadUniforms(getDriverApi(), ubuffer, {
.fbWidth = float(params.viewport.width),
.fbHeight = float(params.viewport.height),
@@ -277,10 +239,10 @@ TEST_F(BackendTest, FeedbackLoops) {
api.beginRenderPass(renderTargets[targetLevel], params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
api.destroyTexture(textureView);
api.destroyDescriptorSet(descriptorSet);
}
getDriverApi().setMinMaxLevels(texture, 0, 0x7f);
// Read back the render target corresponding to the base level.
//
// NOTE: Calling glReadPixels on any miplevel other than the base level
@@ -297,14 +259,10 @@ TEST_F(BackendTest, FeedbackLoops) {
getDriver().purge();
}
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyTexture(texture);
api.destroyBufferObject(ubuffer);
for (auto rt : renderTargets) {
api.destroyRenderTarget(rt);
}
for (auto rt : renderTargets) api.destroyRenderTarget(rt);
}
const uint32_t expected = 0x70695aa1;

View File

@@ -20,18 +20,14 @@
#include "TrianglePrimitive.h"
#include "BackendTestUtils.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include "private/filament/SamplerInterfaceBlock.h"
#include "private/backend/SamplerGroup.h"
#include <math/half.h>
#include <fstream>
#include <vector>
#include <stddef.h>
#include <stdint.h>
using namespace filament;
using namespace filament::backend;
@@ -301,31 +297,20 @@ TEST_F(BackendTest, UpdateImage2D) {
auto defaultRenderTarget = api.createDefaultRenderTarget(0);
// 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 } };
SamplerInterfaceBlock const sib = filament::SamplerInterfaceBlock::Builder()
.name("Test")
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_2D, getSamplerFormat(t.textureFormat), Precision::HIGH }} )
.build();
std::string const fragment = stringReplace("{samplerType}",
getSamplerTypeName(t.textureFormat), fragmentTemplate);
ShaderGenerator shaderGen(
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
Program prog = shaderGen.getProgram(api);
prog.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[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);
// Create a Texture.
auto usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
Handle<HwTexture> const texture = api.createTexture(SamplerType::SAMPLER_2D, 1,
@@ -346,22 +331,23 @@ TEST_F(BackendTest, UpdateImage2D) {
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 512, t.bufferPadding));
}
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
SamplerGroup samplers(1);
samplers.setSampler(0, { texture, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST } });
api.bindDescriptorSet(descriptorSet, 1, {});
auto sgroup = api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test"));
api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api));
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
defaultRenderTarget, swapChain, program);
api.bindSamplers(0, sgroup);
renderTriangle(defaultRenderTarget, swapChain, program);
readPixelsAndAssertHash(t.name, 512, 512, defaultRenderTarget, expectedHash);
api.commit(swapChain);
api.endFrame(0);
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyRenderTarget(defaultRenderTarget);
@@ -388,27 +374,18 @@ TEST_F(BackendTest, UpdateImageSRGB) {
auto defaultRenderTarget = 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 } };
SamplerInterfaceBlock const sib = filament::SamplerInterfaceBlock::Builder()
.name("Test")
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} )
.build();
std::string const fragment = stringReplace("{samplerType}",
getSamplerTypeName(textureFormat), fragmentTemplate);
ShaderGenerator shaderGen(
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
Program prog = shaderGen.getProgram(api);
prog.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[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);
// Create a texture.
Handle<HwTexture> const texture = api.createTexture(SamplerType::SAMPLER_2D, 1,
@@ -440,15 +417,17 @@ TEST_F(BackendTest, UpdateImageSRGB) {
api.beginFrame(0, 0, 0);
// Update samplers.
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
SamplerGroup samplers(1);
SamplerParams sparams = {};
sparams.filterMag = SamplerMagFilter::LINEAR;
sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
samplers.setSampler(0, { texture, sparams });
auto sgroup = api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test"));
api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api));
api.bindDescriptorSet(descriptorSet, 1, {});
api.bindSamplers(0, sgroup);
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
defaultRenderTarget, swapChain, program);
renderTriangle(defaultRenderTarget, swapChain, program);
static const uint32_t expectedHash = 359858623;
readPixelsAndAssertHash("UpdateImageSRGB", 512, 512, defaultRenderTarget, expectedHash);
@@ -457,8 +436,7 @@ TEST_F(BackendTest, UpdateImageSRGB) {
api.commit(swapChain);
api.endFrame(0);
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroySamplerGroup(sgroup);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyRenderTarget(defaultRenderTarget);
@@ -484,26 +462,18 @@ TEST_F(BackendTest, UpdateImageMipLevel) {
auto defaultRenderTarget = 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 } };
SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder()
.name("Test")
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} )
.build();
std::string const fragment = stringReplace("{samplerType}",
getSamplerTypeName(textureFormat), fragmentUpdateImageMip);
ShaderGenerator shaderGen(
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
Program prog = shaderGen.getProgram(api);
prog.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[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);
// Create a texture with 3 mip levels.
// Base level: 1024
@@ -519,15 +489,17 @@ TEST_F(BackendTest, UpdateImageMipLevel) {
api.beginFrame(0, 0, 0);
// Update samplers.
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
SamplerGroup samplers(1);
SamplerParams sparams = {};
sparams.filterMag = SamplerMagFilter::LINEAR;
sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
samplers.setSampler(0, { texture, sparams });
auto sgroup = api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test"));
api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api));
api.bindDescriptorSet(descriptorSet, 1, {});
api.bindSamplers(0, sgroup);
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
defaultRenderTarget, swapChain, program);
renderTriangle(defaultRenderTarget, swapChain, program);
static const uint32_t expectedHash = 3644679986;
readPixelsAndAssertHash("UpdateImageMipLevel", 512, 512, defaultRenderTarget, expectedHash);
@@ -536,8 +508,7 @@ TEST_F(BackendTest, UpdateImageMipLevel) {
api.commit(swapChain);
api.endFrame(0);
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroySamplerGroup(sgroup);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyRenderTarget(defaultRenderTarget);
@@ -565,26 +536,18 @@ TEST_F(BackendTest, UpdateImage3D) {
auto defaultRenderTarget = 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 } };
SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder()
.name("Test")
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_2D_ARRAY, SamplerFormat::FLOAT, Precision::HIGH }} )
.build();
std::string fragment = stringReplace("{samplerType}",
getSamplerTypeName(samplerType), fragmentUpdateImage3DTemplate);
ShaderGenerator shaderGen(
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
Program prog = shaderGen.getProgram(api);
prog.descriptorBindings(1, {{ "test_tex", DescriptorType::SAMPLER, 0 }});
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[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);
// Create a texture.
Handle<HwTexture> texture = api.createTexture(samplerType, 1,
@@ -610,15 +573,17 @@ TEST_F(BackendTest, UpdateImage3D) {
api.beginFrame(0, 0, 0);
// Update samplers.
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
SamplerGroup samplers(1);
SamplerParams sparams = {};
sparams.filterMag = SamplerMagFilter::LINEAR;
sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
samplers.setSampler(0, { texture, sparams});
auto sgroup = api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test"));
api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api));
api.bindDescriptorSet(descriptorSet, 1, {});
api.bindSamplers(0, sgroup);
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
defaultRenderTarget, swapChain, program);
renderTriangle(defaultRenderTarget, swapChain, program);
static const uint32_t expectedHash = 3644679986;
readPixelsAndAssertHash("UpdateImage3D", 512, 512, defaultRenderTarget, expectedHash);
@@ -627,8 +592,7 @@ TEST_F(BackendTest, UpdateImage3D) {
api.commit(swapChain);
api.endFrame(0);
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroySamplerGroup(sgroup);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyRenderTarget(defaultRenderTarget);

View File

@@ -21,11 +21,7 @@
#include "TrianglePrimitive.h"
#include "BackendTestUtils.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <stddef.h>
#include <stdint.h>
#include "private/backend/SamplerGroup.h"
namespace {
@@ -75,7 +71,7 @@ namespace test {
using namespace filament;
using namespace filament::backend;
TEST_F(BackendTest, TextureViewLod) {
TEST_F(BackendTest, SetMinMaxLevel) {
auto& api = getDriverApi();
api.startCapture(0);
@@ -91,35 +87,26 @@ TEST_F(BackendTest, TextureViewLod) {
{
ShaderGenerator shaderGen(vertex, whiteFragment, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(api);
p.descriptorBindings(0, {{"backend_test_sib_tex", DescriptorType::SAMPLER, 0}});
Program::Sampler sampler{utils::CString("backend_test_sib_tex"), 0};
p.setSamplerGroup(0, ShaderStageFlags::FRAGMENT, &sampler, 1);
whiteProgram = api.createProgram(std::move(p));
}
// 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));
SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder()
.name("backend_test_sib")
.stageFlags(backend::ShaderStageFlags::FRAGMENT)
.add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} )
.build();
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
Program p = shaderGen.getProgram(api);
p.descriptorBindings(0, {{"backend_test_sib_tex", DescriptorType::SAMPLER, 0}});
Program::Sampler sampler{utils::CString("backend_test_sib_tex"), 0};
p.setSamplerGroup(0, ShaderStageFlags::FRAGMENT, &sampler, 1);
textureProgram = api.createProgram(std::move(p));
}
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
{{
DescriptorType::SAMPLER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}});
DescriptorSetHandle descriptorSet[2];
descriptorSet[0] = api.createDescriptorSet(descriptorSetLayout);
descriptorSet[1] = api.createDescriptorSet(descriptorSetLayout);
// Create a texture that has 4 mip levels. Each level is a different color.
// Level 0: 128x128 (red)
// Level 1: 64x64 (green)
@@ -163,7 +150,7 @@ TEST_F(BackendTest, TextureViewLod) {
// Level 1: 64x64 (green) <-- base
// Level 2: 32x32 (blue) <--- white triangle rendered
// Level 3: 16x16 (yellow) <-- max
auto texture13 = api.createTextureView(texture, 1, 3);
api.setMinMaxLevels(texture, 1, 3);
// Render a white triangle into level 2.
// We specify mip level 2, because minMaxLevels has no effect when rendering into a texture.
@@ -196,17 +183,20 @@ TEST_F(BackendTest, TextureViewLod) {
PipelineState state;
state.program = textureProgram;
state.pipelineLayout.setLayout = { descriptorSetLayout };
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = SamplerCompareFunc::A;
state.rasterState.culling = CullingMode::NONE;
api.updateDescriptorSetTexture(descriptorSet[0], 0, texture13, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
api.bindDescriptorSet(descriptorSet[0], 0, {});
SamplerGroup samplers(1);
SamplerParams samplerParams {};
samplerParams.filterMag = SamplerMagFilter::NEAREST;
samplerParams.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST;
samplers.setSampler(0, { texture, samplerParams });
backend::Handle<HwSamplerGroup> samplerGroup =
api.createSamplerGroup(1, utils::FixedSizeString<32>("Test"));
api.updateSamplerGroup(samplerGroup, samplers.toBufferDescriptor(api));
api.bindSamplers(0, samplerGroup);
// 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
@@ -218,13 +208,7 @@ TEST_F(BackendTest, TextureViewLod) {
// Adjust the base mip to 2.
// Note that this is done without another call to updateSamplerGroup.
auto texture22 = api.createTextureView(texture, 2, 2);
api.updateDescriptorSetTexture(descriptorSet[1], 0, texture22, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
api.bindDescriptorSet(descriptorSet[1], 0, {});
api.setMinMaxLevels(texture, 2, 3);
// Render a second, smaller, triangle, again sampling from mip level 1.
// This triangle should be yellow striped.
@@ -249,12 +233,7 @@ TEST_F(BackendTest, TextureViewLod) {
// Cleanup.
api.destroySwapChain(swapChain);
api.destroyRenderTarget(renderTarget);
api.destroyDescriptorSet(descriptorSet[0]);
api.destroyDescriptorSet(descriptorSet[1]);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyTexture(texture);
api.destroyTexture(texture13);
api.destroyTexture(texture22);
api.destroyProgram(whiteProgram);
api.destroyProgram(textureProgram);
}

View File

@@ -19,14 +19,10 @@
#include "ShaderGenerator.h"
#include "TrianglePrimitive.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include "private/backend/SamplerGroup.h"
#include <CoreVideo/CoreVideo.h>
#include <stddef.h>
#include <stdint.h>
namespace {
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -65,39 +61,29 @@ using namespace filament::backend;
// Rendering an external image without setting any data should not crash.
TEST_F(BackendTest, RenderExternalImageWithoutSet) {
auto& api = getDriverApi();
TrianglePrimitive triangle(api);
TrianglePrimitive triangle(getDriverApi());
auto swapChain = 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));
SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder()
.name("Test")
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, Precision::HIGH }} )
.build();
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
// Create a program that samples a texture.
Program p = shaderGen.getProgram(api);
p.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
backend::Handle<HwProgram> program = api.createProgram(std::move(p));
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
{{
DescriptorType::SAMPLER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}});
Program p = shaderGen.getProgram(getDriverApi());
Program::Sampler sampler { utils::CString("test_tex"), 0 };
p.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, &sampler, 1);
backend::Handle<HwProgram> program = getDriverApi().createProgram(std::move(p));
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
backend::Handle<HwRenderTarget> defaultRenderTarget = api.createDefaultRenderTarget(0);
backend::Handle<HwRenderTarget> defaultRenderTarget = getDriverApi().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 = api.createTexture(
backend::Handle<HwTexture> texture = getDriverApi().createTexture(
SamplerType::SAMPLER_EXTERNAL, // target
1, // levels
TextureFormat::RGBA8, // format
@@ -116,74 +102,63 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
PipelineState state;
state.program = program;
state.pipelineLayout.setLayout[1] = { descriptorSetLayout };
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
state.rasterState.culling = CullingMode::NONE;
api.startCapture(0);
api.makeCurrent(swapChain, swapChain);
api.beginFrame(0, 0, 0);
getDriverApi().startCapture(0);
getDriverApi().makeCurrent(swapChain, swapChain);
getDriverApi().beginFrame(0, 0, 0);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {});
api.bindDescriptorSet(descriptorSet, 1, {});
SamplerGroup samplers(1);
samplers.setSampler(0, { texture, {} });
backend::Handle<HwSamplerGroup> samplerGroup =
getDriverApi().createSamplerGroup(1, utils::FixedSizeString<32>("Test"));
getDriverApi().updateSamplerGroup(samplerGroup, samplers.toBufferDescriptor(getDriverApi()));
getDriverApi().bindSamplers(0, samplerGroup);
// Render a triangle.
api.beginRenderPass(defaultRenderTarget, params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
getDriverApi().beginRenderPass(defaultRenderTarget, params);
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
getDriverApi().endRenderPass();
api.flush();
api.commit(swapChain);
api.endFrame(0);
getDriverApi().flush();
getDriverApi().commit(swapChain);
getDriverApi().endFrame(0);
api.stopCapture(0);
getDriverApi().stopCapture(0);
// Delete our resources.
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyTexture(texture);
getDriverApi().destroyTexture(texture);
getDriverApi().destroySamplerGroup(samplerGroup);
// Destroy frame resources.
api.destroyProgram(program);
api.destroyRenderTarget(defaultRenderTarget);
api.finish();
getDriverApi().destroyProgram(program);
getDriverApi().destroyRenderTarget(defaultRenderTarget);
executeCommands();
}
TEST_F(BackendTest, RenderExternalImage) {
auto& api = getDriverApi();
TrianglePrimitive triangle(api);
TrianglePrimitive triangle(getDriverApi());
auto swapChain = 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));
SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder()
.name("Test")
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, Precision::HIGH }} )
.build();
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
// Create a program that samples a texture.
Program p = shaderGen.getProgram(api);
p.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
auto program = api.createProgram(std::move(p));
Program p = shaderGen.getProgram(getDriverApi());
Program::Sampler sampler { utils::CString("test_tex"), 0 };
p.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, &sampler, 1);
auto program = getDriverApi().createProgram(std::move(p));
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
{{
DescriptorType::SAMPLER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}});
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
backend::Handle<HwRenderTarget> defaultRenderTarget = api.createDefaultRenderTarget(0);
backend::Handle<HwRenderTarget> defaultRenderTarget = getDriverApi().createDefaultRenderTarget(0);
// require users to create two Filament textures and have two material parameters
// add a "plane" parameter to setExternalImage
@@ -191,6 +166,15 @@ TEST_F(BackendTest, RenderExternalImage) {
// 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 = getDriverApi().createTexture(
SamplerType::SAMPLER_EXTERNAL, // target
1, // levels
TextureFormat::RGBA8, // format
1, // samples
view.width, // width
view.height, // height
1, // depth
usage); // usage
// Create an external image.
CFStringRef keys[4];
@@ -225,9 +209,8 @@ TEST_F(BackendTest, RenderExternalImage) {
}
}
api.setupExternalImage(pixBuffer);
backend::Handle<HwTexture> texture =
api.createTextureExternalImage(TextureFormat::RGBA8, 1024, 1024, usage, pixBuffer);
getDriverApi().setupExternalImage(pixBuffer);
getDriverApi().setExternalImage(texture, pixBuffer);
// We're now free to release the buffer.
CVBufferRelease(pixBuffer);
@@ -241,43 +224,40 @@ TEST_F(BackendTest, RenderExternalImage) {
PipelineState state;
state.program = program;
state.pipelineLayout.setLayout[1] = { descriptorSetLayout };
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
state.rasterState.culling = CullingMode::NONE;
api.startCapture(0);
api.makeCurrent(swapChain, swapChain);
api.beginFrame(0, 0, 0);
getDriverApi().startCapture(0);
getDriverApi().makeCurrent(swapChain, swapChain);
getDriverApi().beginFrame(0, 0, 0);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {});
api.bindDescriptorSet(descriptorSet, 1, {});
SamplerGroup samplers(1);
samplers.setSampler(0, { texture, {} });
backend::Handle<HwSamplerGroup> samplerGroup =
getDriverApi().createSamplerGroup(1, utils::FixedSizeString<32>("Test"));
getDriverApi().updateSamplerGroup(samplerGroup, samplers.toBufferDescriptor(getDriverApi()));
getDriverApi().bindSamplers(0, samplerGroup);
// Render a triangle.
api.beginRenderPass(defaultRenderTarget, params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
getDriverApi().beginRenderPass(defaultRenderTarget, params);
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
getDriverApi().endRenderPass();
readPixelsAndAssertHash("RenderExternalImage", 512, 512, defaultRenderTarget, 267229901, true);
getDriverApi().flush();
getDriverApi().commit(swapChain);
getDriverApi().endFrame(0);
api.flush();
api.commit(swapChain);
api.endFrame(0);
api.stopCapture(0);
getDriverApi().stopCapture(0);
// Delete our resources.
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyTexture(texture);
getDriverApi().destroyTexture(texture);
getDriverApi().destroySamplerGroup(samplerGroup);
// Destroy frame resources.
api.destroyProgram(program);
api.destroyRenderTarget(defaultRenderTarget);
api.finish();
getDriverApi().destroyProgram(program);
getDriverApi().destroyRenderTarget(defaultRenderTarget);
executeCommands();
}

View File

@@ -182,7 +182,7 @@ updated several times; each time it undergoes these two transitions.
If a shader samples from a texture whose mipmaps are only partially loaded, we might see validation
warnings about how some subresources are in an UNDEFINED layout. However if we are properly using
the `createTextureView` driver API, then the Vulkan backend will not bind those particular
the `setMinMaxLevels` driver API, then the Vulkan backend will not bind those particular
subresources, so validation should not complain.
(2) **Writeable Color Textures**

View File

@@ -118,7 +118,7 @@ public:
Builder& mipLevel(AttachmentPoint attachment, uint8_t level) noexcept;
/**
* Sets the face for cubemap textures at the given attachment point.
* Sets the cubemap face for a given attachment point.
*
* @param attachment The attachment point.
* @param face The associated cubemap face.
@@ -127,12 +127,7 @@ public:
Builder& face(AttachmentPoint attachment, CubemapFace face) noexcept;
/**
* Sets an index of a single layer for 2d array, cubemap array, and 3d textures at the given
* attachment point.
*
* For cubemap array textures, layer is translated into an array index and face according to
* - index: layer / 6
* - face: layer % 6
* Sets the layer for a given attachment point (for 3D textures).
*
* @param attachment The attachment point.
* @param layer The associated cubemap layer.
@@ -140,19 +135,6 @@ public:
*/
Builder& layer(AttachmentPoint attachment, uint32_t layer) noexcept;
/**
* Sets the starting index of the 2d array textures for multiview at the given attachment
* point.
*
* This requires COLOR and DEPTH attachments (if set) to be of 2D array textures.
*
* @param attachment The attachment point.
* @param layerCount The number of layers used for multiview, starting from baseLayer.
* @param baseLayer The starting index of the 2d array texture.
* @return A reference to this Builder for chaining calls.
*/
Builder& multiview(AttachmentPoint attachment, uint8_t layerCount, uint8_t baseLayer = 0) noexcept;
/**
* Creates the RenderTarget object and returns a pointer to it.
*

View File

@@ -597,6 +597,21 @@ public:
friend class FEngine;
friend class FRenderPrimitive;
friend class FRenderableManager;
struct Entry {
VertexBuffer* UTILS_NULLABLE vertices = nullptr;
IndexBuffer* UTILS_NULLABLE indices = nullptr;
size_t offset = 0;
size_t count = 0;
MaterialInstance const* UTILS_NULLABLE materialInstance = nullptr;
PrimitiveType type = PrimitiveType::TRIANGLES;
uint16_t blendOrder = 0;
bool globalBlendOrderEnabled = false;
struct {
MorphTargetBuffer* UTILS_NULLABLE buffer = nullptr;
size_t offset = 0;
size_t count = 0;
} morphing;
};
};
/**

View File

@@ -570,13 +570,6 @@ public:
*/
void setShadowType(ShadowType shadow) noexcept;
/**
* Returns the shadow mapping technique used by this View.
*
* @return value set by setShadowType().
*/
ShadowType getShadowType() const noexcept;
/**
* Sets VSM shadowing options that apply across the entire View.
*

View File

@@ -56,12 +56,7 @@ class Bimap {
}
};
using ForwardMap = tsl::robin_map<
KeyDelegate, Value,
KeyHasherDelegate,
std::equal_to<KeyDelegate>,
std::allocator<std::pair<KeyDelegate, Value>>,
true>;
using ForwardMap = tsl::robin_map<KeyDelegate, Value, KeyHasherDelegate>;
using BackwardMap = tsl::robin_map<Value, KeyDelegate, ValueHash>;
Allocator mAllocator;

View File

@@ -31,7 +31,8 @@ using namespace utils;
using namespace backend;
FrameSkipper::FrameSkipper(size_t latency) noexcept
: mLast(std::clamp(latency, size_t(1), MAX_FRAME_LATENCY) - 1) {
: mLast(std::max(latency, MAX_FRAME_LATENCY) - 1) {
assert_invariant(latency <= MAX_FRAME_LATENCY);
}
FrameSkipper::~FrameSkipper() noexcept = default;

View File

@@ -32,18 +32,7 @@ namespace filament {
* outrun the GPU.
*/
class FrameSkipper {
/*
* The maximum frame latency acceptable on ANDROID is 2 because higher latencies will be
* throttled anyway in BufferQueueProducer::dequeueBuffer(), because ANDROID is generally
* triple-buffered no more; that case is actually pretty bad because the GL thread can block
* anywhere (usually inside the first draw command that touches the swapchain).
*
* A frame latency of 1 has the benefit of reducing render latency,
* but the drawback of preventing CPU / GPU overlap.
*
* Generally a frame latency of 2 is the best compromise.
*/
static constexpr size_t MAX_FRAME_LATENCY = 2;
static constexpr size_t MAX_FRAME_LATENCY = 3;
public:
/*
* The latency parameter defines how many unfinished frames we want to accept before we start

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