Compare commits
1 Commits
ma/descrip
...
pf/test-ro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69fc1f302c |
17
.github/actions/android-continuous/action.yml
vendored
17
.github/actions/android-continuous/action.yml
vendored
@@ -1,17 +0,0 @@
|
||||
name: 'Android Continuous'
|
||||
inputs:
|
||||
build-abi:
|
||||
description: 'The target platform ABI'
|
||||
required: true
|
||||
default: 'armeabi-v7a'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/android && printf "y" | ./build.sh continuous ${{ inputs.build-abi }}
|
||||
shell: bash
|
||||
47
.github/workflows/android-continuous.yml
vendored
47
.github/workflows/android-continuous.yml
vendored
@@ -8,35 +8,32 @@ on:
|
||||
- rc/**
|
||||
|
||||
jobs:
|
||||
build-android-armv7:
|
||||
name: build-android-armv7
|
||||
build-android:
|
||||
name: build-android
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run Android Continuous
|
||||
uses: ./.github/actions/android-continuous
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
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
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/android && printf "y" | ./build.sh continuous
|
||||
- uses: actions/upload-artifact@v1.0.0
|
||||
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
|
||||
name: filament-android
|
||||
path: out/filament-android-release.aar
|
||||
- uses: actions/upload-artifact@v1.0.0
|
||||
with:
|
||||
build-abi: x86_64
|
||||
name: filamat-android-full
|
||||
path: out/filamat-android-release.aar
|
||||
- uses: actions/upload-artifact@v1.0.0
|
||||
with:
|
||||
name: gltfio-android-release
|
||||
path: out/gltfio-android-release.aar
|
||||
- uses: actions/upload-artifact@v1.0.0
|
||||
with:
|
||||
name: filament-utils-android-release
|
||||
path: out/filament-utils-android-release.aar
|
||||
|
||||
4
.github/workflows/presubmit.yml
vendored
4
.github/workflows/presubmit.yml
vendored
@@ -49,10 +49,8 @@ jobs:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Run build script
|
||||
# Only build 1 64 bit target during presubmit to cut down build times during presubmit
|
||||
# Continuous builds will build everything
|
||||
run: |
|
||||
cd build/android && printf "y" | ./build.sh presubmit arm64-v8a
|
||||
cd build/android && printf "y" | ./build.sh presubmit
|
||||
|
||||
build-ios:
|
||||
name: build-iOS
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -120,7 +120,7 @@ jobs:
|
||||
env:
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
run: |
|
||||
cd build/android && printf "y" | ./build.sh release armeabi-v7a,arm64-v8a,x86,x86_64
|
||||
cd build/android && printf "y" | ./build.sh release
|
||||
cd ../..
|
||||
mv out/filament-android-release.aar out/filament-${TAG}-android.aar
|
||||
mv out/filamat-android-release.aar out/filamat-${TAG}-android.aar
|
||||
|
||||
@@ -7,5 +7,3 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- Add support for multi-layered render target with array textures.
|
||||
14
README.md
14
README.md
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.54.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.53.2'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,9 +51,19 @@ 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.54.2'
|
||||
pod 'Filament', '~> 1.53.2'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
If you prefer to live on the edge, you can download a continuous build by following the following
|
||||
steps:
|
||||
|
||||
1. Find the [commit](https://github.com/google/filament/commits/main) you're interested in.
|
||||
2. Click the green check mark under the commit message.
|
||||
3. Click on the _Details_ link for the platform you're interested in.
|
||||
4. On the top left click _Summary_, then in the _Artifacts_ section choose the desired artifact.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Filament](https://google.github.io/filament/Filament.html), an in-depth explanation of
|
||||
|
||||
@@ -7,29 +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.54.3
|
||||
|
||||
|
||||
## v1.54.2
|
||||
|
||||
- Add a `name` API to Filament objects for debugging handle use-after-free assertions
|
||||
|
||||
## v1.54.1
|
||||
|
||||
|
||||
## v1.54.0
|
||||
|
||||
- materials: add a new `stereoscopicType` material parameter. [⚠️ **New Material Version**]
|
||||
- Fix a crash when compiling shaders on IMG devices
|
||||
|
||||
## v1.53.5
|
||||
|
||||
- engine: Fix bug causing certain sampler parameters to not be applied correctly in GLES 2.0 and on
|
||||
certain GLES 3.0 drivers.
|
||||
|
||||
## v1.53.4
|
||||
|
||||
|
||||
## v1.53.3
|
||||
|
||||
- Add drag and drop support for IBL files for desktop gltf_viewer.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.54.2
|
||||
VERSION_NAME=1.53.2
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -60,7 +60,13 @@ if [[ ! -d "${ANDROID_HOME}/ndk/$FILAMENT_NDK_VERSION" ]]; then
|
||||
yes | ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --licenses
|
||||
${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager "ndk;$FILAMENT_NDK_VERSION"
|
||||
fi
|
||||
|
||||
# Only build 1 64 bit target during presubmit to cut down build times during presubmit
|
||||
# Continuous builds will build everything
|
||||
ANDROID_ABIS=
|
||||
if [[ "$TARGET" == "presubmit" ]]; then
|
||||
ANDROID_ABIS="-q arm64-v8a"
|
||||
fi
|
||||
|
||||
# Build the Android sample-gltf-viewer APK during release.
|
||||
BUILD_SAMPLES=
|
||||
@@ -68,19 +74,5 @@ if [[ "$TARGET" == "release" ]]; then
|
||||
BUILD_SAMPLES="-k sample-gltf-viewer"
|
||||
fi
|
||||
|
||||
function build_android() {
|
||||
local ABI=$1
|
||||
|
||||
# Do the following in two steps so that we do not run out of space
|
||||
if [[ -n "${BUILD_DEBUG}" ]]; then
|
||||
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION} ./build.sh -p android -q ${ABI} -c ${BUILD_SAMPLES} ${GENERATE_ARCHIVES} ${BUILD_DEBUG}
|
||||
rm -rf out/cmake-android-debug-*
|
||||
fi
|
||||
if [[ -n "${BUILD_RELEASE}" ]]; then
|
||||
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION} ./build.sh -p android -q ${ABI} -c ${BUILD_SAMPLES} ${GENERATE_ARCHIVES} ${BUILD_RELEASE}
|
||||
rm -rf out/cmake-android-release-*
|
||||
fi
|
||||
}
|
||||
|
||||
pushd `dirname $0`/../.. > /dev/null
|
||||
build_android $2
|
||||
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION} ./build.sh -p android $ANDROID_ABIS -c $BUILD_SAMPLES $GENERATE_ARCHIVES $BUILD_DEBUG $BUILD_RELEASE
|
||||
|
||||
@@ -1308,12 +1308,7 @@ Description
|
||||
declare a variable called `eyeDirection` you can access it in the fragment shader using
|
||||
`variable_eyeDirection`. In the vertex shader, the interpolant name is simply a member of
|
||||
the `MaterialVertexInputs` structure (`material.eyeDirection` in your example). Each
|
||||
interpolant is of type `float4` (`vec4`) in the shaders. By default the precision of the
|
||||
interpolant is `highp` in *both* the vertex and fragment shaders.
|
||||
An alternate syntax can be used to specify both the name and precision of the interpolant.
|
||||
In this case the specified precision is used as-is in both fragment and vertex stages, in
|
||||
particular if `default` is specified the default precision is used is the fragment shader
|
||||
(`mediump`) and in the vertex shader (`highp`).
|
||||
interpolant is of type `float4` (`vec4`) in the shaders.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
||||
material {
|
||||
@@ -1325,11 +1320,7 @@ material {
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
eyeDirection,
|
||||
{
|
||||
name : eyeColor,
|
||||
precision : medium
|
||||
}
|
||||
eyeDirection
|
||||
],
|
||||
vertexDomain : device,
|
||||
depthWrite : false,
|
||||
|
||||
@@ -61,7 +61,6 @@ set(SRCS
|
||||
src/Engine.cpp
|
||||
src/Exposure.cpp
|
||||
src/Fence.cpp
|
||||
src/FilamentBuilder.cpp
|
||||
src/FrameInfo.cpp
|
||||
src/FrameSkipper.cpp
|
||||
src/Froxelizer.cpp
|
||||
@@ -76,6 +75,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
|
||||
@@ -124,12 +125,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
|
||||
@@ -157,16 +152,19 @@ set(PRIVATE_HDRS
|
||||
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
|
||||
@@ -194,14 +192,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
|
||||
@@ -219,6 +209,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
|
||||
|
||||
@@ -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,38 +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_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()
|
||||
|
||||
@@ -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
|
||||
@@ -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,61 +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;
|
||||
uint16_t count;
|
||||
};
|
||||
|
||||
struct DescriptorSetLayout {
|
||||
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bitmask for selecting render buffers
|
||||
*/
|
||||
@@ -330,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);
|
||||
|
||||
/**
|
||||
@@ -419,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
|
||||
@@ -759,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,
|
||||
@@ -967,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;
|
||||
@@ -978,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;
|
||||
@@ -988,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);
|
||||
}
|
||||
@@ -1150,7 +1058,7 @@ struct RasterState {
|
||||
bool inverseFrontFaces : 1; // 31
|
||||
|
||||
//! padding, must be 0
|
||||
bool depthClamp : 1; // 32
|
||||
uint8_t padding : 1; // 32
|
||||
};
|
||||
uint32_t u = 0;
|
||||
};
|
||||
@@ -1161,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.
|
||||
@@ -1310,7 +1244,7 @@ enum class Workaround : uint16_t {
|
||||
ADRENO_UNIFORM_ARRAY_CRASH,
|
||||
// Workaround a Metal pipeline compilation error with the message:
|
||||
// "Could not statically determine the target of a texture". See light_indirect.fs
|
||||
METAL_STATIC_TEXTURE_TARGET_ERROR,
|
||||
A8X_STATIC_TEXTURE_TARGET_ERROR,
|
||||
// Adreno drivers sometimes aren't able to blit into a layer of a texture array.
|
||||
DISABLE_BLIT_INTO_TEXTURE_ARRAY,
|
||||
// Multiple workarounds needed for PowerVR GPUs
|
||||
@@ -1325,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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -162,10 +162,6 @@ DECL_DRIVER_API_0(finish)
|
||||
// reset state tracking, if the driver does any state tracking (e.g. GL)
|
||||
DECL_DRIVER_API_0(resetState)
|
||||
|
||||
DECL_DRIVER_API_N(setDebugTag,
|
||||
backend::HandleBase::HandleId, handleId,
|
||||
utils::CString, tag)
|
||||
|
||||
/*
|
||||
* Creating driver objects
|
||||
* -----------------------
|
||||
@@ -200,33 +196,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 +221,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 +257,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
|
||||
@@ -347,7 +305,6 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isDepthStencilBlitSupported, backend::TextureFormat, format)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedTexturesSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthClampSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
|
||||
@@ -384,6 +341,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 +364,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,16 +414,37 @@ 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,
|
||||
backend::PushConstantVariant, value)
|
||||
|
||||
DECL_DRIVER_API_N(insertEventMarker,
|
||||
const char*, string)
|
||||
const char*, string,
|
||||
uint32_t, len = 0)
|
||||
|
||||
DECL_DRIVER_API_N(pushGroupMarker,
|
||||
const char*, string)
|
||||
const char*, string,
|
||||
uint32_t, len = 0)
|
||||
|
||||
DECL_DRIVER_API_0(popGroupMarker)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,12 +20,11 @@
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <utils/Allocator.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
@@ -38,9 +37,9 @@
|
||||
#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
|
||||
#define HandleAllocatorMTL HandleAllocator<32, 48, 552> // ~1660 / pool / MiB
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -174,10 +173,8 @@ public:
|
||||
uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
|
||||
auto const pNode = static_cast<typename Allocator::Node*>(p);
|
||||
uint8_t const expectedAge = pNode[-1].age;
|
||||
// getHandleTag() is only called if the check fails.
|
||||
FILAMENT_CHECK_POSTCONDITION(expectedAge == age)
|
||||
<< "use-after-free of Handle with id=" << handle.getId()
|
||||
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
|
||||
FILAMENT_CHECK_POSTCONDITION(expectedAge == age) <<
|
||||
"use-after-free of Handle with id=" << handle.getId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,29 +201,6 @@ public:
|
||||
return handle_cast<Dp>(const_cast<Handle<B>&>(handle));
|
||||
}
|
||||
|
||||
void associateTagToHandle(HandleBase::HandleId id, utils::CString&& tag) noexcept {
|
||||
// TODO: for now, only pool handles check for use-after-free, so we only keep tags for
|
||||
// those
|
||||
if (isPoolHandle(id)) {
|
||||
// Truncate the age to get the debug tag
|
||||
uint32_t const key = id & ~(HANDLE_DEBUG_TAG_MASK ^ HANDLE_AGE_MASK);
|
||||
// This line is the costly part. In the future, we could potentially use a custom
|
||||
// allocator.
|
||||
mDebugTags[key] = std::move(tag);
|
||||
}
|
||||
}
|
||||
|
||||
utils::CString getHandleTag(HandleBase::HandleId id) const noexcept {
|
||||
if (!isPoolHandle(id)) {
|
||||
return "(no tag)";
|
||||
}
|
||||
uint32_t const key = id & ~(HANDLE_DEBUG_TAG_MASK ^ HANDLE_AGE_MASK);
|
||||
if (auto pos = mDebugTags.find(key); pos != mDebugTags.end()) {
|
||||
return pos->second;
|
||||
}
|
||||
return "(no tag)";
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<typename D>
|
||||
@@ -344,24 +318,12 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
// number if bits allotted to the handle's age (currently 4 max)
|
||||
static constexpr uint32_t HANDLE_AGE_BIT_COUNT = 4;
|
||||
// number if bits allotted to the handle's debug tag (HANDLE_AGE_BIT_COUNT max)
|
||||
static constexpr uint32_t HANDLE_DEBUG_TAG_BIT_COUNT = 2;
|
||||
// bit shift for both the age and debug tag
|
||||
static constexpr uint32_t HANDLE_AGE_SHIFT = 27;
|
||||
// mask for the heap (vs pool) flag
|
||||
static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u;
|
||||
// mask for the age
|
||||
static constexpr uint32_t HANDLE_AGE_MASK =
|
||||
((1 << HANDLE_AGE_BIT_COUNT) - 1) << HANDLE_AGE_SHIFT;
|
||||
// mask for the debug tag
|
||||
static constexpr uint32_t HANDLE_DEBUG_TAG_MASK =
|
||||
((1 << HANDLE_DEBUG_TAG_BIT_COUNT) - 1) << HANDLE_AGE_SHIFT;
|
||||
// mask for the index
|
||||
static constexpr uint32_t HANDLE_INDEX_MASK = 0x07FFFFFFu;
|
||||
|
||||
static_assert(HANDLE_DEBUG_TAG_BIT_COUNT <= HANDLE_AGE_BIT_COUNT);
|
||||
// we handle a 4 bits age per address
|
||||
static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u; // pool vs heap handle
|
||||
static constexpr uint32_t HANDLE_AGE_MASK = 0x78000000u; // handle's age
|
||||
static constexpr uint32_t HANDLE_INDEX_MASK = 0x07FFFFFFu; // handle index
|
||||
static constexpr uint32_t HANDLE_TAG_MASK = HANDLE_AGE_MASK;
|
||||
static constexpr uint32_t HANDLE_AGE_SHIFT = 27;
|
||||
|
||||
static bool isPoolHandle(HandleBase::HandleId id) noexcept {
|
||||
return (id & HANDLE_HEAP_FLAG) == 0u;
|
||||
@@ -376,7 +338,7 @@ private:
|
||||
// a non-pool handle.
|
||||
if (UTILS_LIKELY(isPoolHandle(id))) {
|
||||
char* const base = (char*)mHandleArena.getArea().begin();
|
||||
uint32_t const tag = id & HANDLE_AGE_MASK;
|
||||
uint32_t const tag = id & HANDLE_TAG_MASK;
|
||||
size_t const offset = (id & HANDLE_INDEX_MASK) * Allocator::getAlignment();
|
||||
return { static_cast<void*>(base + offset), tag };
|
||||
}
|
||||
@@ -391,7 +353,7 @@ private:
|
||||
size_t const offset = (char*)p - base;
|
||||
assert_invariant((offset % Allocator::getAlignment()) == 0);
|
||||
auto id = HandleBase::HandleId(offset / Allocator::getAlignment());
|
||||
id |= tag & HANDLE_AGE_MASK;
|
||||
id |= tag & HANDLE_TAG_MASK;
|
||||
assert_invariant((id & HANDLE_HEAP_FLAG) == 0);
|
||||
return id;
|
||||
}
|
||||
@@ -401,7 +363,6 @@ private:
|
||||
// Below is only used when running out of space in the HandleArena
|
||||
mutable utils::Mutex mLock;
|
||||
tsl::robin_map<HandleBase::HandleId, void*> mOverflowMap;
|
||||
tsl::robin_map<HandleBase::HandleId, utils::CString> mDebugTags;
|
||||
HandleBase::HandleId mId = 0;
|
||||
bool mUseAfterFreeCheckDisabled = false;
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#if !defined(WIN32) && !defined(__EMSCRIPTEN__)
|
||||
#if !defined(WIN32) && !defined(__EMSCRIPTEN__) && !defined(IOS)
|
||||
# include <sys/mman.h>
|
||||
# include <unistd.h>
|
||||
# define HAS_MMAP 1
|
||||
|
||||
@@ -101,8 +101,9 @@ void CommandBufferQueue::flush() noexcept {
|
||||
size_t const used = std::distance(
|
||||
static_cast<char const*>(begin), static_cast<char const*>(end));
|
||||
|
||||
|
||||
std::unique_lock<utils::Mutex> lock(mLock);
|
||||
mCommandBuffersToExecute.push_back({ begin, end });
|
||||
mCondition.notify_one();
|
||||
|
||||
// circular buffer is too small, we corrupted the stream
|
||||
FILAMENT_CHECK_POSTCONDITION(used <= mFreeSpace) <<
|
||||
@@ -111,13 +112,11 @@ void CommandBufferQueue::flush() noexcept {
|
||||
"Space used at this time: " << used <<
|
||||
" bytes, overflow: " << used - mFreeSpace << " bytes";
|
||||
|
||||
mFreeSpace -= used;
|
||||
mCommandBuffersToExecute.push_back({ begin, end });
|
||||
mCondition.notify_one();
|
||||
|
||||
// wait until there is enough space in the buffer
|
||||
mFreeSpace -= used;
|
||||
if (UTILS_UNLIKELY(mFreeSpace < requiredSize)) {
|
||||
|
||||
|
||||
#ifndef NDEBUG
|
||||
size_t const totalUsed = circularBuffer.size() - mFreeSpace;
|
||||
slog.d << "CommandStream used too much space (will block): "
|
||||
@@ -154,10 +153,8 @@ std::vector<CommandBufferQueue::Range> CommandBufferQueue::waitForCommands() con
|
||||
}
|
||||
|
||||
void CommandBufferQueue::releaseBuffer(CommandBufferQueue::Range const& buffer) {
|
||||
size_t const used = std::distance(
|
||||
static_cast<char const*>(buffer.begin), static_cast<char const*>(buffer.end));
|
||||
std::lock_guard<utils::Mutex> const lock(mLock);
|
||||
mFreeSpace += used;
|
||||
mFreeSpace += uintptr_t(buffer.end) - uintptr_t(buffer.begin);
|
||||
mCondition.notify_one();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -80,9 +80,6 @@ HandleAllocator<P0, P1, P2>::HandleAllocator(const char* name, size_t size,
|
||||
bool disableUseAfterFreeCheck) noexcept
|
||||
: mHandleArena(name, size, disableUseAfterFreeCheck),
|
||||
mUseAfterFreeCheckDisabled(disableUseAfterFreeCheck) {
|
||||
// Reserve initial space for debug tags. This prevents excessive calls to malloc when the first
|
||||
// few tags are set.
|
||||
mDebugTags.reserve(512);
|
||||
}
|
||||
|
||||
template <size_t P0, size_t P1, size_t P2>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -160,8 +160,6 @@ public:
|
||||
size_t size, bool forceGpuBuffer = false);
|
||||
~MetalBuffer();
|
||||
|
||||
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer || mCpuBuffer; }
|
||||
|
||||
MetalBuffer(const MetalBuffer& rhs) = delete;
|
||||
MetalBuffer& operator=(const MetalBuffer& rhs) = delete;
|
||||
|
||||
@@ -182,7 +180,7 @@ public:
|
||||
* is no device allocation.
|
||||
*
|
||||
*/
|
||||
id<MTLBuffer> getGpuBufferForDraw() noexcept;
|
||||
id<MTLBuffer> getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) noexcept;
|
||||
|
||||
void* getCpuBuffer() const noexcept { return mCpuBuffer; }
|
||||
|
||||
|
||||
@@ -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.
|
||||
{
|
||||
@@ -56,8 +53,8 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
|
||||
mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate],
|
||||
TrackedMetalBuffer::Type::GENERIC };
|
||||
}
|
||||
// mBuffer might fail to be allocated. Clients can check for this by calling
|
||||
// wasAllocationSuccessful().
|
||||
FILAMENT_CHECK_POSTCONDITION(mBuffer)
|
||||
<< "Could not allocate Metal buffer of size " << size << ".";
|
||||
}
|
||||
|
||||
MetalBuffer::~MetalBuffer() {
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -146,7 +93,7 @@ struct MetalContext {
|
||||
} highestSupportedGpuFamily;
|
||||
|
||||
struct {
|
||||
bool staticTextureTargetError;
|
||||
bool a8xStaticTextureTargetError;
|
||||
} bugs;
|
||||
|
||||
// sampleCountLookup[requestedSamples] gives a <= sample count supported by the device.
|
||||
@@ -161,9 +108,10 @@ 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;
|
||||
Handle<HwRenderPrimitive> currentRenderPrimitive;
|
||||
|
||||
// State caches.
|
||||
@@ -176,15 +124,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 +153,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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
@@ -58,11 +57,11 @@ class MetalDriver final : public DriverBase {
|
||||
|
||||
public:
|
||||
static Driver* create(MetalPlatform* platform, const Platform::DriverConfig& driverConfig);
|
||||
void runAtNextTick(const std::function<void()>& fn) noexcept;
|
||||
|
||||
private:
|
||||
|
||||
friend class MetalSwapChain;
|
||||
friend struct MetalDescriptorSet;
|
||||
|
||||
MetalPlatform& mPlatform;
|
||||
MetalContext* mContext;
|
||||
@@ -74,23 +73,10 @@ private:
|
||||
|
||||
/*
|
||||
* Tasks run regularly on the driver thread.
|
||||
* Not thread-safe; tasks are run from the driver thead and must be enqueued from the driver
|
||||
* thread.
|
||||
*/
|
||||
void runAtNextTick(const std::function<void()>& fn) noexcept;
|
||||
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;
|
||||
std::mutex mTickOpsLock;
|
||||
|
||||
/*
|
||||
* Driver interface
|
||||
@@ -151,6 +137,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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<id<MTLBuffer>, Program::SHADER_TYPE_COUNT> cachedBuffer = { nil };
|
||||
};
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
|
||||
|
||||
@@ -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());
|
||||
@@ -253,6 +257,10 @@ void MetalSwapChain::present() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD
|
||||
#define FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD 1
|
||||
#endif
|
||||
|
||||
class PresentDrawableData {
|
||||
public:
|
||||
PresentDrawableData() = delete;
|
||||
@@ -271,10 +279,14 @@ public:
|
||||
[that->mDrawable present];
|
||||
}
|
||||
|
||||
#if FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD == 1
|
||||
// mDrawable is acquired on the driver thread. Typically, we would release this object on
|
||||
// the same thread, but after receiving consistent crash reports from within
|
||||
// [CAMetalDrawable dealloc], we suspect this object requires releasing on the main thread.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{ cleanupAndDestroy(that); });
|
||||
#else
|
||||
that->mDriver->runAtNextTick([that]() { cleanupAndDestroy(that); });
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -470,6 +482,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 +506,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 +595,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 +618,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 +774,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 +906,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,189 +1343,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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
buffer = [context.device newBufferWithLength:encoder.encodedLength
|
||||
options:MTLResourceStorageModeShared];
|
||||
[encoder setArgumentBuffer:buffer 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;
|
||||
}
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
|
||||
@@ -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);
|
||||
@@ -379,22 +382,18 @@ using SamplerStateCache = StateCache<SamplerState, id<MTLSamplerState>, SamplerS
|
||||
|
||||
using CullModeStateTracker = StateTracker<MTLCullMode>;
|
||||
using WindingStateTracker = StateTracker<MTLWinding>;
|
||||
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 +415,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<__unsafe_unretained id<MTLBuffer>, N> mBuffers = { nil };
|
||||
std::array<NSUInteger, N> mOffsets = { 0 };
|
||||
utils::bitset8 mDirtyBuffers;
|
||||
utils::bitset8 mDirtyOffsets;
|
||||
};
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {};
|
||||
}
|
||||
@@ -205,10 +202,6 @@ bool NoopDriver::isProtectedTexturesSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NoopDriver::isDepthClampSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NoopDriver::isWorkaroundNeeded(Workaround) {
|
||||
return false;
|
||||
}
|
||||
@@ -251,6 +244,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 +272,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,14 +299,27 @@ 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) {
|
||||
}
|
||||
|
||||
void NoopDriver::insertEventMarker(char const* string) {
|
||||
void NoopDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
}
|
||||
|
||||
void NoopDriver::pushGroupMarker(char const* string) {
|
||||
void NoopDriver::pushGroupMarker(char const* string, uint32_t len) {
|
||||
}
|
||||
|
||||
void NoopDriver::popGroupMarker(int) {
|
||||
@@ -374,28 +388,4 @@ 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) {
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -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
|
||||
@@ -1,361 +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())) {
|
||||
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
|
||||
@@ -1,171 +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 {
|
||||
Buffer() = default;
|
||||
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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -676,7 +676,6 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||
#endif
|
||||
ext->EXT_depth_clamp = exts.has("GL_EXT_depth_clamp"sv);
|
||||
ext->EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
|
||||
@@ -747,7 +746,6 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor)
|
||||
ext->EXT_color_buffer_half_float = true; // Assumes core profile.
|
||||
ext->EXT_clip_cull_distance = true;
|
||||
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||
ext->EXT_depth_clamp = true;
|
||||
ext->EXT_discard_framebuffer = false;
|
||||
ext->EXT_disjoint_timer_query = true;
|
||||
ext->EXT_multisampled_render_to_texture = false;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -224,9 +220,8 @@ public:
|
||||
bool EXT_color_buffer_float;
|
||||
bool EXT_color_buffer_half_float;
|
||||
bool EXT_debug_marker;
|
||||
bool EXT_depth_clamp;
|
||||
bool EXT_discard_framebuffer;
|
||||
bool EXT_disjoint_timer_query;
|
||||
bool EXT_discard_framebuffer;
|
||||
bool EXT_multisampled_render_to_texture2;
|
||||
bool EXT_multisampled_render_to_texture;
|
||||
bool EXT_protected_textures;
|
||||
@@ -244,10 +239,10 @@ public:
|
||||
bool KHR_parallel_shader_compile;
|
||||
bool KHR_texture_compression_astc_hdr;
|
||||
bool KHR_texture_compression_astc_ldr;
|
||||
bool OES_EGL_image_external_essl3;
|
||||
bool OES_depth24;
|
||||
bool OES_depth_texture;
|
||||
bool OES_depth24;
|
||||
bool OES_packed_depth_stencil;
|
||||
bool OES_EGL_image_external_essl3;
|
||||
bool OES_rgb8_rgba8;
|
||||
bool OES_standard_derivatives;
|
||||
bool OES_texture_npot;
|
||||
@@ -478,6 +473,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;
|
||||
@@ -504,6 +505,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;
|
||||
|
||||
@@ -623,7 +627,6 @@ constexpr size_t OpenGLContext::getIndexForCap(GLenum cap) noexcept { //NOLINT
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
case GL_PROGRAM_POINT_SIZE: index = 10; break;
|
||||
#endif
|
||||
case GL_DEPTH_CLAMP: index = 11; break;
|
||||
default: break;
|
||||
}
|
||||
assert_invariant(index < state.enables.caps.size());
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
@@ -85,7 +81,7 @@ public:
|
||||
const Platform::DriverConfig& driverConfig) noexcept;
|
||||
|
||||
class DebugMarker {
|
||||
UTILS_UNUSED OpenGLDriver& driver;
|
||||
OpenGLDriver& driver;
|
||||
public:
|
||||
DebugMarker(OpenGLDriver& driver, const char* string) noexcept;
|
||||
~DebugMarker() noexcept;
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -143,25 +143,21 @@ void TimerQueryNativeFactory::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQ
|
||||
|
||||
driver.runEveryNowAndThen([&context = mContext, weak]() -> bool {
|
||||
auto state = weak.lock();
|
||||
if (!state) {
|
||||
// The timer query state has been destroyed on the way, very likely due to the IBL
|
||||
// prefilter context destruction. We still return true to get this element removed from
|
||||
// the query list.
|
||||
return true;
|
||||
if (state) {
|
||||
GLuint available = 0;
|
||||
context.procs.getQueryObjectuiv(state->gl.query, GL_QUERY_RESULT_AVAILABLE, &available);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
if (!available) {
|
||||
// we need to try this one again later
|
||||
return false;
|
||||
}
|
||||
GLuint64 elapsedTime = 0;
|
||||
// we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query
|
||||
context.procs.getQueryObjectui64v(state->gl.query, GL_QUERY_RESULT, &elapsedTime);
|
||||
state->elapsed.store((int64_t)elapsedTime, std::memory_order_relaxed);
|
||||
} else {
|
||||
state->elapsed.store(int64_t(TimerQueryResult::ERROR), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
GLuint available = 0;
|
||||
context.procs.getQueryObjectuiv(state->gl.query, GL_QUERY_RESULT_AVAILABLE, &available);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
if (!available) {
|
||||
// we need to try this one again later
|
||||
return false;
|
||||
}
|
||||
GLuint64 elapsedTime = 0;
|
||||
// we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query
|
||||
context.procs.getQueryObjectui64v(state->gl.query, GL_QUERY_RESULT, &elapsedTime);
|
||||
state->elapsed.store((int64_t)elapsedTime, std::memory_order_relaxed);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,28 +26,16 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace utils;
|
||||
@@ -488,12 +476,6 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
// check status of program linking and shader compilation, logs error and free all resources
|
||||
// in case of error.
|
||||
bool const success = checkProgramStatus(token);
|
||||
|
||||
// Unless we have matdbg, we panic if a program is invalid. Otherwise, we'd get a UB.
|
||||
// The compilation error has been logged to log.e by this point.
|
||||
FILAMENT_CHECK_POSTCONDITION(FILAMENT_ENABLE_MATDBG || success)
|
||||
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
|
||||
|
||||
if (UTILS_LIKELY(success)) {
|
||||
program = token->gl.program;
|
||||
// no need to keep the shaders around
|
||||
@@ -597,31 +579,24 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
version = "#version 310 es\n";
|
||||
}
|
||||
|
||||
std::array<std::string_view, 5> sources = {
|
||||
version,
|
||||
prolog,
|
||||
specializationConstantString,
|
||||
packingFunctions,
|
||||
{ body.data(), body.size() - 1 } // null-terminated
|
||||
const std::array<const char*, 5> sources = {
|
||||
version.data(),
|
||||
prolog.data(),
|
||||
specializationConstantString.c_str(),
|
||||
packingFunctions.data(),
|
||||
body.data()
|
||||
};
|
||||
|
||||
// Some of the sources may be zero-length. Remove them as to avoid passing lengths of
|
||||
// zero to glShaderSource(). glShaderSource should work with lengths of zero, but some
|
||||
// drivers instead interpret zero as a sentinel for a null-terminated string.
|
||||
auto partitionPoint = std::stable_partition(
|
||||
sources.begin(), sources.end(), [](std::string_view s) { return !s.empty(); });
|
||||
size_t count = std::distance(sources.begin(), partitionPoint);
|
||||
|
||||
std::array<const char*, 5> shaderStrings;
|
||||
std::array<GLint, 5> lengths;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
shaderStrings[i] = sources[i].data();
|
||||
lengths[i] = sources[i].size();
|
||||
}
|
||||
const std::array<GLint, 5> lengths = {
|
||||
(GLint)version.length(),
|
||||
(GLint)prolog.length(),
|
||||
(GLint)specializationConstantString.length(),
|
||||
(GLint)packingFunctions.length(),
|
||||
(GLint)body.length() - 1 // null terminated
|
||||
};
|
||||
|
||||
GLuint const shaderId = glCreateShader(glShaderType);
|
||||
glShaderSource(shaderId, count, shaderStrings.data(), lengths.data());
|
||||
|
||||
glShaderSource(shaderId, sources.size(), sources.data(), lengths.data());
|
||||
glCompileShader(shaderId);
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
@@ -201,12 +201,6 @@ using namespace glext;
|
||||
# define GL_CLIP_DISTANCE1 0x3001
|
||||
#endif
|
||||
|
||||
#if defined(GL_EXT_depth_clamp)
|
||||
# define GL_DEPTH_CLAMP GL_DEPTH_CLAMP_EXT
|
||||
#else
|
||||
# define GL_DEPTH_CLAMP 0x864F
|
||||
#endif
|
||||
|
||||
#if defined(GL_KHR_debug)
|
||||
# define GL_DEBUG_OUTPUT GL_DEBUG_OUTPUT_KHR
|
||||
# define GL_DEBUG_OUTPUT_SYNCHRONOUS GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -59,11 +59,7 @@ VkExtent2D VulkanAttachment::getExtent2D() const {
|
||||
|
||||
VkImageView VulkanAttachment::getImageView() {
|
||||
assert_invariant(texture);
|
||||
VkImageSubresourceRange range = getSubresourceRange();
|
||||
if (range.layerCount > 1) {
|
||||
return texture->getMultiviewAttachmentView(range);
|
||||
}
|
||||
return texture->getAttachmentView(range);
|
||||
return texture->getAttachmentView(getSubresourceRange());
|
||||
}
|
||||
|
||||
bool VulkanAttachment::isDepth() const {
|
||||
@@ -77,7 +73,7 @@ VkImageSubresourceRange VulkanAttachment::getSubresourceRange() const {
|
||||
.baseMipLevel = uint32_t(level),
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = uint32_t(layer),
|
||||
.layerCount = layerCount,
|
||||
.layerCount = 1,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -43,8 +43,6 @@ struct VulkanCommandBuffer;
|
||||
struct VulkanAttachment {
|
||||
VulkanTexture* texture = nullptr;
|
||||
uint8_t level = 0;
|
||||
uint8_t baseViewIndex = 0;
|
||||
uint8_t layerCount = 1;
|
||||
uint16_t layer = 0;
|
||||
|
||||
bool isDepth() const;
|
||||
@@ -125,10 +123,6 @@ public:
|
||||
return mPhysicalDeviceFeatures.imageCubeArray == VK_TRUE;
|
||||
}
|
||||
|
||||
inline bool isDepthClampSupported() const noexcept {
|
||||
return mPhysicalDeviceFeatures.depthClamp == VK_TRUE;
|
||||
}
|
||||
|
||||
inline bool isDebugMarkersSupported() const noexcept {
|
||||
return mDebugMarkersSupported;
|
||||
}
|
||||
|
||||
@@ -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,8 +595,6 @@ 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,
|
||||
};
|
||||
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = colorTargets[i].getExtent2D();
|
||||
@@ -663,8 +609,6 @@ 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,
|
||||
};
|
||||
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = depthStencil[0].getExtent2D();
|
||||
@@ -677,8 +621,6 @@ 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,
|
||||
};
|
||||
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = depthStencil[1].getExtent2D();
|
||||
@@ -693,10 +635,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);
|
||||
mResourceManager.acquire(renderTarget);
|
||||
}
|
||||
|
||||
@@ -724,7 +665,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);
|
||||
}
|
||||
|
||||
@@ -737,8 +678,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);
|
||||
}
|
||||
|
||||
@@ -746,22 +686,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, mPlatform->getDevice(), info);
|
||||
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 +706,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 +714,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 +756,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 +795,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 +832,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 +884,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() {
|
||||
@@ -1025,10 +938,6 @@ bool VulkanDriver::isProtectedTexturesSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isDepthClampSupported() {
|
||||
return mContext.isDepthClampSupported();
|
||||
}
|
||||
|
||||
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU: {
|
||||
@@ -1159,6 +1068,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 +1157,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 +1188,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
|
||||
@@ -1288,6 +1218,36 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
// the non-sampling case.
|
||||
VulkanCommandBuffer& commands = mCommands.get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
|
||||
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();
|
||||
|
||||
TargetBufferFlags clearVal = params.flags.clear;
|
||||
@@ -1300,13 +1260,13 @@ 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,
|
||||
@@ -1317,17 +1277,20 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
.discardEnd = discardEndVal,
|
||||
.samples = rt->getSamples(),
|
||||
.subpassMask = uint8_t(params.subpassMask),
|
||||
.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;
|
||||
}
|
||||
@@ -1346,39 +1309,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();
|
||||
attachment.texture->transitionLayout(&commands,
|
||||
range, VulkanLayout::COLOR_ATTACHMENT);
|
||||
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]);
|
||||
@@ -1496,27 +1448,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.clear();
|
||||
mDescriptorSetManager.clearState();
|
||||
mCurrentRenderPass.renderTarget = nullptr;
|
||||
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
|
||||
FVK_SYSTRACE_END();
|
||||
@@ -1575,6 +1557,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");
|
||||
@@ -1583,13 +1592,13 @@ void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
value);
|
||||
}
|
||||
|
||||
void VulkanDriver::insertEventMarker(char const* string) {
|
||||
void VulkanDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
mCommands.insertEventMarker(string, strlen(string));
|
||||
mCommands.insertEventMarker(string, len);
|
||||
#endif
|
||||
}
|
||||
|
||||
void VulkanDriver::pushGroupMarker(char const* string) {
|
||||
void VulkanDriver::pushGroupMarker(char const* string, uint32_t) {
|
||||
// Turns out all the markers are 0-terminated, so we can just pass it without len.
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
mCommands.pushGroupMarker(string);
|
||||
@@ -1775,8 +1784,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);
|
||||
@@ -1797,7 +1806,6 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
.dstAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionDstAlpha),
|
||||
.colorWriteMask = (VkColorComponentFlags) (rasterState.colorWrite ? 0xf : 0x0),
|
||||
.rasterizationSamples = rt->getSamples(),
|
||||
.depthClamp = rasterState.depthClamp,
|
||||
.colorTargetCount = rt->getColorTargetCount(mCurrentRenderPass),
|
||||
.colorBlendOp = rasterState.blendEquationRGB,
|
||||
.alphaBlendOp = rasterState.blendEquationAlpha,
|
||||
@@ -1820,26 +1828,59 @@ 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->vklayout;
|
||||
});
|
||||
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);
|
||||
@@ -1882,14 +1923,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");
|
||||
@@ -1897,8 +1930,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;
|
||||
@@ -1946,7 +1979,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);
|
||||
}
|
||||
@@ -1989,10 +2022,6 @@ void VulkanDriver::debugCommandBegin(CommandStream* cmds, bool synchronous, cons
|
||||
void VulkanDriver::resetState(int) {
|
||||
}
|
||||
|
||||
void VulkanDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
|
||||
mResourceAllocator.associateHandle(handleId, std::move(tag));
|
||||
}
|
||||
|
||||
// explicit instantiation of the Dispatcher
|
||||
template class ConcreteDispatcher<VulkanDriver>;
|
||||
|
||||
|
||||
@@ -159,11 +159,12 @@ private:
|
||||
VulkanReadPixels mReadPixels;
|
||||
VulkanDescriptorSetManager mDescriptorSetManager;
|
||||
|
||||
VulkanDescriptorSetManager::GetPipelineLayoutFunction mGetPipelineFunction;
|
||||
|
||||
// This is necessary for us to write to push constants after binding a pipeline.
|
||||
struct BoundPipeline {
|
||||
VulkanProgram* program;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
DescriptorSetMask descriptorSetMask;
|
||||
};
|
||||
BoundPipeline mBoundPipeline = {};
|
||||
RenderPassFboBundle mRenderPassFboInfo;
|
||||
|
||||
@@ -43,7 +43,6 @@ bool VulkanFboCache::RenderPassEq::operator()(const RenderPassKey& k1,
|
||||
if (k1.samples != k2.samples) return false;
|
||||
if (k1.needsResolveMask != k2.needsResolveMask) return false;
|
||||
if (k1.subpassMask != k2.subpassMask) return false;
|
||||
if (k1.viewCount != k2.viewCount) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -187,25 +186,6 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
.pDependencies = dependencies
|
||||
};
|
||||
|
||||
VkRenderPassMultiviewCreateInfo multiviewCreateInfo = {};
|
||||
uint32_t const subpassViewMask = (1 << config.viewCount) - 1;
|
||||
// Prepare a view mask array for the maximum number of subpasses. All subpasses have all views
|
||||
// activated.
|
||||
uint32_t const viewMasks[2] = {subpassViewMask, subpassViewMask};
|
||||
if (config.viewCount > 1) {
|
||||
// Fill the multiview create info.
|
||||
multiviewCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO;
|
||||
multiviewCreateInfo.pNext = nullptr;
|
||||
multiviewCreateInfo.subpassCount = hasSubpasses ? 2u : 1u;
|
||||
multiviewCreateInfo.pViewMasks = viewMasks;
|
||||
multiviewCreateInfo.dependencyCount = 0;
|
||||
multiviewCreateInfo.pViewOffsets = nullptr;
|
||||
multiviewCreateInfo.correlationMaskCount = 1;
|
||||
multiviewCreateInfo.pCorrelationMasks = &subpassViewMask;
|
||||
|
||||
renderPassInfo.pNext = &multiviewCreateInfo;
|
||||
}
|
||||
|
||||
int attachmentIndex = 0;
|
||||
|
||||
// Populate the Color Attachments.
|
||||
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
uint8_t samples; // 1 byte
|
||||
uint8_t needsResolveMask; // 1 byte
|
||||
uint8_t subpassMask; // 1 byte
|
||||
uint8_t viewCount; // 1 byte
|
||||
uint8_t padding2; // 1 byte
|
||||
};
|
||||
struct RenderPassVal {
|
||||
VkRenderPass handle;
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include "VulkanDriver.h"
|
||||
#include "VulkanConstants.h"
|
||||
#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>
|
||||
|
||||
@@ -52,13 +54,54 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,113 +123,22 @@ inline VkShaderStageFlags getVkStage(backend::ShaderStage stage) {
|
||||
}
|
||||
}
|
||||
|
||||
inline VkDescriptorSetLayoutCreateInfo getLayoutCreateInfo(DescriptorSetLayout const& layout) {
|
||||
// Note that the following *needs* to be static so that VkDescriptorSetLayoutCreateInfo will not
|
||||
// refer to stack memory.
|
||||
static VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
|
||||
uint32_t count = 0;
|
||||
|
||||
for (auto const& binding: layout.bindings) {
|
||||
VkShaderStageFlags stages = 0;
|
||||
VkDescriptorType type;
|
||||
|
||||
if ((binding.stageFlags & ShaderStageFlags::VERTEX) != ShaderStageFlags::NONE) {
|
||||
stages |= VK_SHADER_STAGE_VERTEX_BIT;
|
||||
}
|
||||
if ((binding.stageFlags & ShaderStageFlags::FRAGMENT) != ShaderStageFlags::NONE) {
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
}
|
||||
assert_invariant(stages != 0);
|
||||
|
||||
switch (binding.type) {
|
||||
case DescriptorType::UNIFORM_BUFFER: {
|
||||
type = (binding.flags & DescriptorFlags::DYNAMIC_OFFSET) != DescriptorFlags::NONE
|
||||
? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC
|
||||
: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
break;
|
||||
}
|
||||
case DescriptorType::SAMPLER: {
|
||||
type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
break;
|
||||
}
|
||||
case DescriptorType::INPUT_ATTACHMENT: {
|
||||
type = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
|
||||
break;
|
||||
}
|
||||
case DescriptorType::SHADER_STORAGE_BUFFER:
|
||||
PANIC_POSTCONDITION("Shader storage is not supported.");
|
||||
break;
|
||||
}
|
||||
toBind[count++] = {
|
||||
.binding = binding.binding,
|
||||
.descriptorType = type,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = stages,
|
||||
};
|
||||
}
|
||||
|
||||
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
|
||||
VkDescriptorSetLayoutCreateInfo dlinfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.bindingCount = count,
|
||||
.pBindings = toBind,
|
||||
};
|
||||
return dlinfo;
|
||||
}
|
||||
|
||||
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(VkDevice device,
|
||||
DescriptorSetLayout const& layout)
|
||||
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT), mDevice(device),
|
||||
vklayout(createDescriptorSetLayout(device, getLayoutCreateInfo(layout))),
|
||||
bitmask(fromBackendLayout(layout)),
|
||||
count(Count::fromLayoutBitmask(bitmask)) {
|
||||
}
|
||||
VkDescriptorSetLayoutCreateInfo const& info, Bitmask const& bitmask)
|
||||
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT),
|
||||
mDevice(device),
|
||||
vklayout(createDescriptorSetLayout(device, info)),
|
||||
bitmask(bitmask),
|
||||
bindings(getBindings(bitmask)),
|
||||
count(Count::fromLayoutBitmask(bitmask)) {}
|
||||
|
||||
VulkanDescriptorSetLayout::~VulkanDescriptorSetLayout() {
|
||||
vkDestroyDescriptorSetLayout(mDevice, vklayout, VKALLOC);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSet::acquire(VulkanTexture* texture) {
|
||||
mResources.acquire(texture);
|
||||
mTextures[mTextureCount++] = texture;
|
||||
}
|
||||
|
||||
void VulkanDescriptorSet::acquire(VulkanBufferObject* bufferObject) {
|
||||
mResources.acquire(bufferObject);
|
||||
}
|
||||
|
||||
PushConstantDescription::PushConstantDescription(backend::Program const& program) noexcept {
|
||||
mRangeCount = 0;
|
||||
for (auto stage : { ShaderStage::VERTEX, ShaderStage::FRAGMENT, ShaderStage::COMPUTE }) {
|
||||
@@ -238,11 +190,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);
|
||||
@@ -259,6 +224,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,
|
||||
@@ -286,6 +257,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;
|
||||
@@ -316,15 +321,13 @@ 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)
|
||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool)
|
||||
: HwRenderTarget(width, height),
|
||||
VulkanResource(VulkanResourceType::RENDER_TARGET),
|
||||
mOffscreen(true),
|
||||
mSamples(samples),
|
||||
mLayerCount(layerCount) {
|
||||
mSamples(samples) {
|
||||
for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) {
|
||||
mColor[index] = color[index];
|
||||
}
|
||||
@@ -350,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 */);
|
||||
@@ -380,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);
|
||||
@@ -533,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;
|
||||
@@ -549,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
|
||||
|
||||
@@ -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 << getVertexStageShift<Bitmask>()) - 1ULL;
|
||||
constexpr uint64_t FRAGMENT_MASK = (1ULL << getFragmentStageShift<Bitmask>()) - 1ULL;
|
||||
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,27 +97,70 @@ struct VulkanDescriptorSetLayout : public VulkanResource, HwDescriptorSetLayout
|
||||
}
|
||||
};
|
||||
|
||||
VulkanDescriptorSetLayout(VkDevice device, DescriptorSetLayout const& layout);
|
||||
static_assert(sizeof(Bitmask) % 8 == 0);
|
||||
|
||||
explicit VulkanDescriptorSetLayout(VkDevice device, VkDescriptorSetLayoutCreateInfo const& info,
|
||||
Bitmask const& bitmask);
|
||||
|
||||
~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:
|
||||
|
||||
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(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() {
|
||||
@@ -143,25 +169,17 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
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>;
|
||||
|
||||
@@ -206,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();
|
||||
}
|
||||
@@ -245,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
|
||||
@@ -270,11 +302,9 @@ 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);
|
||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool);
|
||||
|
||||
// Creates a special "default" render target (i.e. associated with the swap chain)
|
||||
explicit VulkanRenderTarget();
|
||||
@@ -289,7 +319,6 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
|
||||
VulkanAttachment& getMsaaDepth();
|
||||
uint8_t getColorTargetCount(const VulkanRenderPass& pass) const;
|
||||
uint8_t getSamples() const { return mSamples; }
|
||||
uint8_t getLayerCount() const { return mLayerCount; }
|
||||
bool hasDepth() const { return mDepth.texture; }
|
||||
bool isSwapChain() const { return !mOffscreen; }
|
||||
void bindToSwapChain(VulkanSwapChain& surf);
|
||||
@@ -301,7 +330,6 @@ private:
|
||||
VulkanAttachment mMsaaDepthAttachment = {};
|
||||
const bool mOffscreen : 1;
|
||||
uint8_t mSamples : 7;
|
||||
uint8_t mLayerCount = 1;
|
||||
};
|
||||
|
||||
struct VulkanBufferObject;
|
||||
@@ -454,7 +482,6 @@ private:
|
||||
utils::Mutex mFenceMutex;
|
||||
};
|
||||
|
||||
|
||||
inline constexpr VkBufferUsageFlagBits getBufferObjectUsage(
|
||||
BufferObjectBinding bindingType) noexcept {
|
||||
switch(bindingType) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -184,7 +184,6 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
vkRaster.polygonMode = VK_POLYGON_MODE_FILL;
|
||||
vkRaster.cullMode = raster.cullMode;
|
||||
vkRaster.frontFace = raster.frontFace;
|
||||
vkRaster.depthClampEnable = raster.depthClamp;
|
||||
vkRaster.depthBiasEnable = raster.depthBiasEnable;
|
||||
vkRaster.depthBiasConstantFactor = raster.depthBiasConstantFactor;
|
||||
vkRaster.depthBiasClamp = 0.0f;
|
||||
@@ -210,8 +209,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;
|
||||
|
||||
@@ -90,8 +90,7 @@ public:
|
||||
VkBlendFactor srcAlphaBlendFactor : 5;
|
||||
VkBlendFactor dstAlphaBlendFactor : 5;
|
||||
VkColorComponentFlags colorWriteMask : 4;
|
||||
uint8_t rasterizationSamples : 4;// offset = 4 bytes
|
||||
uint8_t depthClamp : 4;
|
||||
uint8_t rasterizationSamples; // offset = 4 bytes
|
||||
uint8_t colorTargetCount; // offset = 5 bytes
|
||||
BlendEquation colorBlendOp : 4; // offset = 6 bytes
|
||||
BlendEquation alphaBlendOp : 4;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -105,10 +105,6 @@ public:
|
||||
mHandleAllocatorImpl.deallocate(handle, obj);
|
||||
}
|
||||
|
||||
inline void associateHandle(HandleBase::HandleId id, utils::CString&& tag) noexcept {
|
||||
mHandleAllocatorImpl.associateTagToHandle(id, std::move(tag));
|
||||
}
|
||||
|
||||
private:
|
||||
AllocatorImpl mHandleAllocatorImpl;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanResourceAllocator.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
@@ -29,138 +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;
|
||||
};
|
||||
|
||||
// Note that the channel index corresponds to the VkComponentMapping struct layout.
|
||||
return {
|
||||
compose(next.r, prev, 0),
|
||||
compose(next.g, prev, 1),
|
||||
compose(next.b, prev, 2),
|
||||
compose(next.a, prev, 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,
|
||||
@@ -186,11 +97,13 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
imageInfo.extent.depth = 1;
|
||||
}
|
||||
|
||||
if (any(usage & TextureUsage::BLIT_SRC)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
}
|
||||
if (any(usage & TextureUsage::BLIT_DST)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
// Filament expects blit() to work with any texture, so we almost always set these usage flags.
|
||||
// TODO: investigate performance implications of setting these flags.
|
||||
constexpr VkImageUsageFlags blittable = VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
|
||||
if (any(usage & (TextureUsage::BLIT_DST | TextureUsage::BLIT_SRC))) {
|
||||
imageInfo.usage |= blittable;
|
||||
}
|
||||
|
||||
if (any(usage & TextureUsage::SAMPLEABLE)) {
|
||||
@@ -198,9 +111,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
|
||||
@@ -208,7 +121,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
}
|
||||
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | blittable;
|
||||
if (any(usage & TextureUsage::SUBPASS_INPUT)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
||||
}
|
||||
@@ -217,9 +130,10 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
}
|
||||
if (any(usage & TextureUsage::UPLOADABLE)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
imageInfo.usage |= blittable;
|
||||
}
|
||||
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
|
||||
imageInfo.usage |= blittable;
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
|
||||
// Depth resolves uses a custom shader and therefore needs to be sampleable.
|
||||
@@ -233,7 +147,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);
|
||||
}
|
||||
@@ -247,12 +161,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) << ", "
|
||||
@@ -261,13 +175,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);
|
||||
@@ -280,67 +194,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,7 +254,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;
|
||||
|
||||
@@ -362,7 +267,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.");
|
||||
@@ -374,14 +279,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);
|
||||
|
||||
@@ -427,25 +332,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, ©Region);
|
||||
vkCmdCopyBufferToImage(cmdbuf, stage->buffer, mTextureImage, newVkLayout, 1, ©Region);
|
||||
|
||||
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);
|
||||
|
||||
@@ -467,68 +371,63 @@ 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) {
|
||||
// Attachments should only have one mipmap level and one layer.
|
||||
range.levelCount = 1;
|
||||
range.layerCount = 1;
|
||||
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D, {});
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getMultiviewAttachmentView(VkImageSubresourceRange range) {
|
||||
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D_ARRAY, {});
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range, VkImageViewType type) {
|
||||
return getImageView(range, type, mSwizzle);
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
|
||||
VkComponentMapping swizzle) {
|
||||
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;
|
||||
@@ -539,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) {
|
||||
@@ -550,82 +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,
|
||||
});
|
||||
}
|
||||
|
||||
if (hasTransitions) {
|
||||
state->mTransitionFence = fence;
|
||||
setLayout(range, newLayout);
|
||||
|
||||
#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;
|
||||
setLayout(range, newLayout);
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -638,30 +505,28 @@ 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 + mFullViewRange.layerCount;
|
||||
uint32_t const firstLevel = 0;
|
||||
@@ -674,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
|
||||
|
||||
@@ -30,12 +30,84 @@
|
||||
|
||||
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);
|
||||
|
||||
// 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,150 +130,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);
|
||||
|
||||
// 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,
|
||||
@@ -210,15 +138,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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,32 +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>();
|
||||
}
|
||||
|
||||
// TODO: To be completed after [GDSR]
|
||||
void destroySet(Handle<VulkanDescriptorSet> set) {}
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
@@ -76,3 +108,4 @@ private:
|
||||
}// namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -325,7 +325,6 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
// We could simply enable all supported features, but since that may have performance
|
||||
// consequences let's just enable the features we need.
|
||||
VkPhysicalDeviceFeatures enabledFeatures{
|
||||
.depthClamp = features.depthClamp,
|
||||
.samplerAnisotropy = features.samplerAnisotropy,
|
||||
.textureCompressionETC2 = features.textureCompressionETC2,
|
||||
.textureCompressionBC = features.textureCompressionBC,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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()) };
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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**
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
using BufferDescriptor = backend::BufferDescriptor;
|
||||
using BindingType = backend::BufferObjectBinding;
|
||||
|
||||
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
|
||||
class Builder : public BuilderBase<BuilderDetails> {
|
||||
friend struct BuilderDetails;
|
||||
public:
|
||||
Builder() noexcept;
|
||||
@@ -78,21 +78,6 @@ public:
|
||||
*/
|
||||
Builder& bindingType(BindingType bindingType) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this BufferObject for debugging purposes.
|
||||
*
|
||||
* name will show in error messages and should be kept as short as possible. The name is
|
||||
* truncated to a maximum of 128 characters.
|
||||
*
|
||||
* The name string is copied during this method so clients may free its memory after
|
||||
* the function returns.
|
||||
*
|
||||
* @param name A string to identify this BufferObject
|
||||
* @param len Length of name, should be less than or equal to 128
|
||||
* @return This Builder, for chaining calls.
|
||||
*/
|
||||
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
|
||||
|
||||
/**
|
||||
* Creates the BufferObject and returns a pointer to it. After creation, the buffer
|
||||
* object is uninitialized. Use BufferObject::setBuffer() to initialize it.
|
||||
|
||||
@@ -896,29 +896,6 @@ public:
|
||||
/** Tells whether an InstanceBuffer object is valid */
|
||||
bool isValid(const InstanceBuffer* UTILS_NULLABLE p) const;
|
||||
|
||||
/**
|
||||
* Retrieve the count of each resource tracked by Engine.
|
||||
* This is intended for debugging.
|
||||
* @{
|
||||
*/
|
||||
size_t getBufferObjectCount() const noexcept;
|
||||
size_t getViewCount() const noexcept;
|
||||
size_t getSceneCount() const noexcept;
|
||||
size_t getSwapChainCount() const noexcept;
|
||||
size_t getStreamCount() const noexcept;
|
||||
size_t getIndexBufferCount() const noexcept;
|
||||
size_t getSkinningBufferCount() const noexcept;
|
||||
size_t getMorphTargetBufferCount() const noexcept;
|
||||
size_t getInstanceBufferCount() const noexcept;
|
||||
size_t getVertexBufferCount() const noexcept;
|
||||
size_t getIndirectLightCount() const noexcept;
|
||||
size_t getMaterialCount() const noexcept;
|
||||
size_t getTextureCount() const noexcept;
|
||||
size_t getSkyboxeCount() const noexcept;
|
||||
size_t getColorGradingCount() const noexcept;
|
||||
size_t getRenderTargetCount() const noexcept;
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) and blocks until
|
||||
* all commands to this point are executed. Note that does guarantee that the
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/PrivateImplementation.h>
|
||||
#include <utils/CString.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -55,23 +54,6 @@ public:
|
||||
template<typename T>
|
||||
using BuilderBase = utils::PrivateImplementation<T>;
|
||||
|
||||
// This needs to be public because it is used in the following template.
|
||||
UTILS_PUBLIC void builderMakeName(utils::CString& outName, const char* name, size_t len) noexcept;
|
||||
|
||||
template <typename Builder>
|
||||
class UTILS_PUBLIC BuilderNameMixin {
|
||||
public:
|
||||
Builder& name(const char* name, size_t len) noexcept {
|
||||
builderMakeName(mName, name, len);
|
||||
return static_cast<Builder&>(*this);
|
||||
}
|
||||
|
||||
utils::CString const& getName() const noexcept { return mName; }
|
||||
|
||||
private:
|
||||
utils::CString mName;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
#endif // TNT_FILAMENT_FILAMENTAPI_H
|
||||
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
UINT = uint8_t(backend::ElementType::UINT), //!< 32-bit indices
|
||||
};
|
||||
|
||||
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
|
||||
class Builder : public BuilderBase<BuilderDetails> {
|
||||
friend struct BuilderDetails;
|
||||
public:
|
||||
Builder() noexcept;
|
||||
@@ -83,21 +83,6 @@ public:
|
||||
*/
|
||||
Builder& bufferType(IndexType indexType) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this IndexBuffer for debugging purposes.
|
||||
*
|
||||
* name will show in error messages and should be kept as short as possible. The name is
|
||||
* truncated to a maximum of 128 characters.
|
||||
*
|
||||
* The name string is copied during this method so clients may free its memory after
|
||||
* the function returns.
|
||||
*
|
||||
* @param name A string to identify this IndexBuffer
|
||||
* @param len Length of name, should be less than or equal to 128
|
||||
* @return This Builder, for chaining calls.
|
||||
*/
|
||||
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
|
||||
|
||||
/**
|
||||
* Creates the IndexBuffer object and returns a pointer to it. After creation, the index
|
||||
* buffer is uninitialized. Use IndexBuffer::setBuffer() to initialize the IndexBuffer.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user