Compare commits
116 Commits
pf/test-of
...
ma/descrip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69d5ac2107 | ||
|
|
c01d2c09b6 | ||
|
|
f37d8b2fc2 | ||
|
|
6397fa3fe1 | ||
|
|
37d1baf0e8 | ||
|
|
1596a5ec71 | ||
|
|
22e62b92a6 | ||
|
|
8f979dacaf | ||
|
|
684d441ba7 | ||
|
|
e3cb67045b | ||
|
|
f4cd9f712a | ||
|
|
37c615e249 | ||
|
|
99b3587dd3 | ||
|
|
8e30b1bfc9 | ||
|
|
c0b1daf339 | ||
|
|
efb94da111 | ||
|
|
c538bded6f | ||
|
|
f6d9db14c3 | ||
|
|
91ce3a9375 | ||
|
|
8999b21187 | ||
|
|
3aeee1630a | ||
|
|
78aa1c4b10 | ||
|
|
c2b3632725 | ||
|
|
15d77ed1d7 | ||
|
|
8c91b1baf5 | ||
|
|
1b4afbab51 | ||
|
|
c677607353 | ||
|
|
cdb539b3cf | ||
|
|
6a64cdb059 | ||
|
|
cf0cb66b28 | ||
|
|
16bed4de00 | ||
|
|
3a2333d709 | ||
|
|
6790a1238b | ||
|
|
2b620e65fd | ||
|
|
2aa51db614 | ||
|
|
2bbbb7f4d1 | ||
|
|
339e8da976 | ||
|
|
c36dd955f4 | ||
|
|
950be941eb | ||
|
|
3857e3789c | ||
|
|
fad5d57053 | ||
|
|
e95f0ed6a0 | ||
|
|
73b0be799c | ||
|
|
2202b5ab8c | ||
|
|
639b933fd6 | ||
|
|
6653c6c08b | ||
|
|
777f664b1b | ||
|
|
6de6a2123d | ||
|
|
d161ef24f5 | ||
|
|
c3ea1e45f9 | ||
|
|
283d240409 | ||
|
|
0e0f3a5518 | ||
|
|
ba5413622f | ||
|
|
ba8d429fcb | ||
|
|
f11e5cb081 | ||
|
|
c84f5d2a7f | ||
|
|
a7317e7a99 | ||
|
|
be4391950d | ||
|
|
6f20cf4b02 | ||
|
|
c6c20df474 | ||
|
|
9674a5ae3c | ||
|
|
485c05789b | ||
|
|
b058794dd1 | ||
|
|
be22e9305d | ||
|
|
4ae7aa6a1b | ||
|
|
bbb45218ac | ||
|
|
02bdddf50e | ||
|
|
21d93ce49d | ||
|
|
cab5fc561c | ||
|
|
c780804fd6 | ||
|
|
b6d6f8f6e4 | ||
|
|
f098d08c48 | ||
|
|
5c08f34e8c | ||
|
|
a37d03101e | ||
|
|
c0819b781a | ||
|
|
d9bf68ed06 | ||
|
|
32ac10161b | ||
|
|
d073fb5342 | ||
|
|
8fe7c1ff30 | ||
|
|
3b12e54573 | ||
|
|
a1f146ebea | ||
|
|
9401d6be9e | ||
|
|
111be6ef6d | ||
|
|
bd0ca7a9bd | ||
|
|
8797f45bd0 | ||
|
|
f04a25028e | ||
|
|
bb1f58e97c | ||
|
|
189e36d539 | ||
|
|
cba819be8f | ||
|
|
41e1e7e54b | ||
|
|
d8580d93ad | ||
|
|
fef40a8c5d | ||
|
|
d299b1f00e | ||
|
|
daa2790c4e | ||
|
|
d2cb53e39d | ||
|
|
315c8d75bc | ||
|
|
c19785b72f | ||
|
|
38b3074835 | ||
|
|
929ad27606 | ||
|
|
c12bcbb21e | ||
|
|
07f07e0700 | ||
|
|
7e0181b2cf | ||
|
|
b4162eb313 | ||
|
|
a0dc7104b6 | ||
|
|
80f151ccf7 | ||
|
|
26bb99c64e | ||
|
|
1612cdb353 | ||
|
|
e75882e4d4 | ||
|
|
2e763149e7 | ||
|
|
e40a4c69a3 | ||
|
|
9a1ee1ff67 | ||
|
|
9fb692b2f6 | ||
|
|
ac7c679783 | ||
|
|
38a3c52629 | ||
|
|
29ecfd5fda | ||
|
|
f11a04722b |
17
.github/actions/android-continuous/action.yml
vendored
Normal file
17
.github/actions/android-continuous/action.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
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,32 +8,35 @@ on:
|
||||
- rc/**
|
||||
|
||||
jobs:
|
||||
build-android:
|
||||
name: build-android
|
||||
build-android-armv7:
|
||||
name: build-android-armv7
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/setup-java@v3
|
||||
- name: Run Android Continuous
|
||||
uses: ./.github/actions/android-continuous
|
||||
with:
|
||||
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
|
||||
build-abi: armeabi-v7a
|
||||
|
||||
build-android-armv8a:
|
||||
name: build-android-armv8a
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run Android Continuous
|
||||
uses: ./.github/actions/android-continuous
|
||||
with:
|
||||
name: filament-android
|
||||
path: out/filament-android-release.aar
|
||||
- uses: actions/upload-artifact@v1.0.0
|
||||
build-abi: arm64-v8a
|
||||
|
||||
build-android-x86_64:
|
||||
name: build-android-x86_64
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run Android Continuous
|
||||
uses: ./.github/actions/android-continuous
|
||||
with:
|
||||
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
|
||||
build-abi: x86_64
|
||||
|
||||
4
.github/workflows/presubmit.yml
vendored
4
.github/workflows/presubmit.yml
vendored
@@ -49,8 +49,10 @@ 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
|
||||
cd build/android && printf "y" | ./build.sh presubmit arm64-v8a
|
||||
|
||||
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
|
||||
cd build/android && printf "y" | ./build.sh release armeabi-v7a,arm64-v8a,x86,x86_64
|
||||
cd ../..
|
||||
mv out/filament-android-release.aar out/filament-${TAG}-android.aar
|
||||
mv out/filamat-android-release.aar out/filamat-${TAG}-android.aar
|
||||
|
||||
@@ -7,3 +7,5 @@ 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.0'
|
||||
implementation 'com.google.android.filament:filament-android:1.54.2'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,19 +51,9 @@ 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.0'
|
||||
pod 'Filament', '~> 1.54.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,6 +7,13 @@ 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
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.54.0
|
||||
VERSION_NAME=1.54.2
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -60,13 +60,7 @@ 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=
|
||||
@@ -74,5 +68,19 @@ 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
|
||||
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION} ./build.sh -p android $ANDROID_ABIS -c $BUILD_SAMPLES $GENERATE_ARCHIVES $BUILD_DEBUG $BUILD_RELEASE
|
||||
build_android $2
|
||||
|
||||
@@ -61,6 +61,7 @@ set(SRCS
|
||||
src/Engine.cpp
|
||||
src/Exposure.cpp
|
||||
src/Fence.cpp
|
||||
src/FilamentBuilder.cpp
|
||||
src/FrameInfo.cpp
|
||||
src/FrameSkipper.cpp
|
||||
src/Froxelizer.cpp
|
||||
@@ -75,8 +76,6 @@ set(SRCS
|
||||
src/MaterialInstance.cpp
|
||||
src/MaterialParser.cpp
|
||||
src/MorphTargetBuffer.cpp
|
||||
src/PerViewUniforms.cpp
|
||||
src/PerShadowMapUniforms.cpp
|
||||
src/PostProcessManager.cpp
|
||||
src/RenderPass.cpp
|
||||
src/RenderPrimitive.cpp
|
||||
@@ -125,6 +124,12 @@ 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
|
||||
@@ -152,19 +157,16 @@ 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
|
||||
@@ -192,6 +194,14 @@ 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
|
||||
@@ -209,7 +219,6 @@ 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,6 +12,7 @@ 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
|
||||
@@ -69,9 +70,13 @@ 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
|
||||
@@ -495,21 +500,38 @@ 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()
|
||||
|
||||
if (NOT IOS AND NOT WEBGL)
|
||||
# ==================================================================================================
|
||||
# Metal utils tests
|
||||
|
||||
add_executable(compute_test
|
||||
test/ComputeTest.cpp
|
||||
test/Arguments.cpp
|
||||
test/test_ComputeBasic.cpp
|
||||
)
|
||||
if (APPLE AND NOT IOS)
|
||||
|
||||
target_link_libraries(compute_test PRIVATE
|
||||
add_executable(metal_utils_test test/MetalTest.mm)
|
||||
|
||||
target_link_libraries(metal_utils_test PRIVATE
|
||||
backend
|
||||
getopt
|
||||
gtest
|
||||
)
|
||||
|
||||
set_target_properties(compute_test PROPERTIES FOLDER Tests)
|
||||
set_target_properties(metal_utils_test PROPERTIES FOLDER Tests)
|
||||
|
||||
endif()
|
||||
|
||||
101
filament/backend/include/backend/DescriptorSetOffsetArray.h
Normal file
101
filament/backend/include/backend/DescriptorSetOffsetArray.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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,13 +19,16 @@
|
||||
#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>
|
||||
@@ -97,6 +100,8 @@ 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
|
||||
@@ -191,6 +196,61 @@ 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
|
||||
*/
|
||||
@@ -270,15 +330,6 @@ 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);
|
||||
|
||||
/**
|
||||
@@ -368,6 +419,18 @@ 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
|
||||
@@ -696,6 +759,23 @@ 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,
|
||||
@@ -887,6 +967,9 @@ 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;
|
||||
@@ -895,6 +978,9 @@ 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;
|
||||
@@ -902,6 +988,12 @@ 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);
|
||||
}
|
||||
@@ -1069,32 +1161,6 @@ 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.
|
||||
@@ -1259,6 +1325,8 @@ 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,6 +41,8 @@ struct HwTexture;
|
||||
struct HwTimerQuery;
|
||||
struct HwVertexBufferInfo;
|
||||
struct HwVertexBuffer;
|
||||
struct HwDescriptorSetLayout;
|
||||
struct HwDescriptorSet;
|
||||
|
||||
/*
|
||||
* A handle to a backend resource. HandleBase is for internal use only.
|
||||
@@ -130,19 +132,21 @@ 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 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>;
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
|
||||
@@ -22,15 +22,23 @@
|
||||
|
||||
#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,9 +24,11 @@
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#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 <array>
|
||||
#include <unordered_map>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -40,29 +42,36 @@ public:
|
||||
static constexpr size_t UNIFORM_BINDING_COUNT = CONFIG_UNIFORM_BINDING_COUNT;
|
||||
static constexpr size_t SAMPLER_BINDING_COUNT = CONFIG_SAMPLER_BINDING_COUNT;
|
||||
|
||||
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 Descriptor {
|
||||
utils::CString name;
|
||||
backend::DescriptorType type;
|
||||
backend::descriptor_binding_t binding;
|
||||
};
|
||||
|
||||
struct SamplerGroupData {
|
||||
utils::FixedCapacityVector<Sampler> samplers;
|
||||
ShaderStageFlags stageFlags = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS;
|
||||
struct SpecializationConstant {
|
||||
using Type = std::variant<int32_t, float, bool>;
|
||||
uint32_t id; // id set in glsl
|
||||
Type value; // value and type
|
||||
};
|
||||
|
||||
struct Uniform {
|
||||
struct Uniform { // For ES2 support
|
||||
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 UniformBlockInfo = std::array<utils::CString, UNIFORM_BINDING_COUNT>;
|
||||
using UniformInfo = utils::FixedCapacityVector<Uniform>;
|
||||
using SamplerGroupInfo = std::array<SamplerGroupData, SAMPLER_BINDING_COUNT>;
|
||||
using DescriptorBindingsInfo = utils::FixedCapacityVector<Descriptor>;
|
||||
using DescriptorSetInfo = std::array<DescriptorBindingsInfo, MAX_DESCRIPTOR_SET_COUNT>;
|
||||
using SpecializationConstantsInfo = utils::FixedCapacityVector<SpecializationConstant>;
|
||||
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;
|
||||
@@ -79,43 +88,19 @@ 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);
|
||||
|
||||
// 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;
|
||||
// Descriptor binding (set, binding, type -> shader name) info
|
||||
Program& descriptorBindings(backend::descriptor_set_t set,
|
||||
DescriptorBindingsInfo descriptorBindings) 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;
|
||||
Program& specializationConstants(SpecializationConstantsInfo specConstants) noexcept;
|
||||
|
||||
struct PushConstant {
|
||||
utils::CString name;
|
||||
@@ -129,33 +114,40 @@ 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; }
|
||||
|
||||
utils::FixedCapacityVector<SpecializationConstant> const& getSpecializationConstants() const noexcept {
|
||||
uint64_t getCacheId() const noexcept { return mCacheId; }
|
||||
|
||||
bool isMultiview() const noexcept { return mMultiview; }
|
||||
|
||||
CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; }
|
||||
|
||||
SpecializationConstantsInfo const& getSpecializationConstants() const noexcept {
|
||||
return mSpecializationConstants;
|
||||
}
|
||||
utils::FixedCapacityVector<SpecializationConstant>& getSpecializationConstants() noexcept {
|
||||
|
||||
SpecializationConstantsInfo& 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)];
|
||||
@@ -165,27 +157,29 @@ public:
|
||||
return mPushConstants[static_cast<uint8_t>(stage)];
|
||||
}
|
||||
|
||||
uint64_t getCacheId() const noexcept { return mCacheId; }
|
||||
auto const& getBindingUniformInfo() const { return mBindingUniformsInfo; }
|
||||
auto& getBindingUniformInfo() { return mBindingUniformsInfo; }
|
||||
|
||||
bool isMultiview() const noexcept { return mMultiview; }
|
||||
|
||||
CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; }
|
||||
auto const& getAttributes() const { return mAttributes; }
|
||||
auto& getAttributes() { return mAttributes; }
|
||||
|
||||
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{};
|
||||
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)> mLogger;
|
||||
utils::FixedCapacityVector<SpecializationConstant> mSpecializationConstants;
|
||||
std::array<utils::FixedCapacityVector<PushConstant>, SHADER_TYPE_COUNT> mPushConstants;
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> mAttributes;
|
||||
std::array<UniformInfo, Program::UNIFORM_BINDING_COUNT> mBindingUniformInfo;
|
||||
CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH;
|
||||
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)> mLogger;
|
||||
SpecializationConstantsInfo mSpecializationConstants;
|
||||
std::array<utils::FixedCapacityVector<PushConstant>, SHADER_TYPE_COUNT> mPushConstants;
|
||||
DescriptorSetInfo mDescriptorBindings;
|
||||
|
||||
// For ES2 support only
|
||||
AttributesInfo mAttributes;
|
||||
BindingUniformsInfo mBindingUniformsInfo;
|
||||
|
||||
// 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,6 +18,7 @@
|
||||
#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,6 +162,10 @@ 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
|
||||
* -----------------------
|
||||
@@ -196,20 +200,33 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, createTexture,
|
||||
uint32_t, depth,
|
||||
backend::TextureUsage, usage)
|
||||
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
@@ -221,9 +238,6 @@ 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,
|
||||
@@ -257,25 +271,53 @@ 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(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)
|
||||
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)
|
||||
|
||||
/*
|
||||
* Synchronous APIs
|
||||
@@ -342,15 +384,6 @@ 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,
|
||||
@@ -365,10 +398,12 @@ 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,
|
||||
@@ -415,37 +450,16 @@ 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,
|
||||
uint32_t, len = 0)
|
||||
const char*, string)
|
||||
|
||||
DECL_DRIVER_API_N(pushGroupMarker,
|
||||
const char*, string,
|
||||
uint32_t, len = 0)
|
||||
const char*, string)
|
||||
|
||||
DECL_DRIVER_API_0(popGroupMarker)
|
||||
|
||||
|
||||
@@ -18,6 +18,17 @@
|
||||
#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,11 +20,12 @@
|
||||
#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>
|
||||
|
||||
@@ -37,7 +38,7 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define HandleAllocatorGL HandleAllocator<32, 64, 136> // ~4520 / pool / MiB
|
||||
#define HandleAllocatorGL HandleAllocator<32, 96, 136> // ~4520 / pool / MiB
|
||||
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB
|
||||
#define HandleAllocatorMTL HandleAllocator<32, 64, 552> // ~1660 / pool / MiB
|
||||
|
||||
@@ -173,8 +174,10 @@ 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;
|
||||
FILAMENT_CHECK_POSTCONDITION(expectedAge == age) <<
|
||||
"use-after-free of Handle with id=" << handle.getId();
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,6 +204,29 @@ 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>
|
||||
@@ -318,12 +344,24 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
// 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);
|
||||
|
||||
static bool isPoolHandle(HandleBase::HandleId id) noexcept {
|
||||
return (id & HANDLE_HEAP_FLAG) == 0u;
|
||||
@@ -338,7 +376,7 @@ private:
|
||||
// a non-pool handle.
|
||||
if (UTILS_LIKELY(isPoolHandle(id))) {
|
||||
char* const base = (char*)mHandleArena.getArea().begin();
|
||||
uint32_t const tag = id & HANDLE_TAG_MASK;
|
||||
uint32_t const tag = id & HANDLE_AGE_MASK;
|
||||
size_t const offset = (id & HANDLE_INDEX_MASK) * Allocator::getAlignment();
|
||||
return { static_cast<void*>(base + offset), tag };
|
||||
}
|
||||
@@ -353,7 +391,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_TAG_MASK;
|
||||
id |= tag & HANDLE_AGE_MASK;
|
||||
assert_invariant((id & HANDLE_HEAP_FLAG) == 0);
|
||||
return id;
|
||||
}
|
||||
@@ -363,6 +401,7 @@ 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__) && !defined(IOS)
|
||||
#if !defined(WIN32) && !defined(__EMSCRIPTEN__)
|
||||
# include <sys/mman.h>
|
||||
# include <unistd.h>
|
||||
# define HAS_MMAP 1
|
||||
|
||||
@@ -20,11 +20,16 @@
|
||||
#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>
|
||||
@@ -74,8 +79,8 @@ CommandStream::CommandStream(Driver& driver, CircularBuffer& buffer) noexcept
|
||||
}
|
||||
|
||||
void CommandStream::execute(void* buffer) {
|
||||
SYSTRACE_CALL();
|
||||
SYSTRACE_CONTEXT();
|
||||
// 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.
|
||||
|
||||
Profiler profiler;
|
||||
|
||||
@@ -100,6 +105,7 @@ 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,6 +101,14 @@ 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,6 +80,9 @@ 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,7 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "backend/Program.h"
|
||||
#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>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -52,41 +63,24 @@ Program& Program::shaderLanguage(ShaderLanguage shaderLanguage) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Program& Program::descriptorBindings(backend::descriptor_set_t set,
|
||||
DescriptorBindingsInfo descriptorBindings) noexcept {
|
||||
mDescriptorBindings[set] = std::move(descriptorBindings);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Program& Program::uniforms(uint32_t index, UniformInfo const& uniforms) noexcept {
|
||||
assert_invariant(index < UNIFORM_BINDING_COUNT);
|
||||
mBindingUniformInfo[index] = uniforms;
|
||||
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));
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
Program& Program::attributes(
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes) noexcept {
|
||||
Program& Program::attributes(AttributesInfo attributes) noexcept {
|
||||
mAttributes = std::move(attributes);
|
||||
return *this;
|
||||
}
|
||||
|
||||
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 {
|
||||
Program& Program::specializationConstants(SpecializationConstantsInfo specConstants) noexcept {
|
||||
mSpecializationConstants = std::move(specConstants);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -160,6 +160,8 @@ 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;
|
||||
|
||||
@@ -180,7 +182,7 @@ public:
|
||||
* is no device allocation.
|
||||
*
|
||||
*/
|
||||
id<MTLBuffer> getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) noexcept;
|
||||
id<MTLBuffer> getGpuBufferForDraw() noexcept;
|
||||
|
||||
void* getCpuBuffer() const noexcept { return mCpuBuffer; }
|
||||
|
||||
|
||||
@@ -40,12 +40,15 @@ 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.
|
||||
{
|
||||
@@ -53,8 +56,8 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
|
||||
mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate],
|
||||
TrackedMetalBuffer::Type::GENERIC };
|
||||
}
|
||||
FILAMENT_CHECK_POSTCONDITION(mBuffer)
|
||||
<< "Could not allocate Metal buffer of size " << size << ".";
|
||||
// mBuffer might fail to be allocated. Clients can check for this by calling
|
||||
// wasAllocationSuccessful().
|
||||
}
|
||||
|
||||
MetalBuffer::~MetalBuffer() {
|
||||
@@ -94,7 +97,7 @@ void MetalBuffer::copyIntoBufferUnsynchronized(void* src, size_t size, size_t by
|
||||
copyIntoBuffer(src, size, byteOffset);
|
||||
}
|
||||
|
||||
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) noexcept {
|
||||
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw() noexcept {
|
||||
// If there's a CPU buffer, then we return nil here, as the CPU-side buffer will be bound
|
||||
// separately.
|
||||
if (mCpuBuffer) {
|
||||
@@ -137,7 +140,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(cmdBuffer);
|
||||
id<MTLBuffer> gpuBuffer = buffer->getGpuBufferForDraw();
|
||||
if (!gpuBuffer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "MetalShaderCompiler.h"
|
||||
#include "MetalState.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <CoreVideo/CVMetalTextureCache.h>
|
||||
#include <Metal/Metal.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
@@ -46,13 +48,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
|
||||
|
||||
@@ -68,6 +70,53 @@ 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) {}
|
||||
@@ -76,8 +125,12 @@ struct MetalContext {
|
||||
id<MTLDevice> device = nullptr;
|
||||
id<MTLCommandQueue> commandQueue = nullptr;
|
||||
|
||||
id<MTLCommandBuffer> pendingCommandBuffer = nullptr;
|
||||
id<MTLRenderCommandEncoder> currentRenderPassEncoder = 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;
|
||||
|
||||
std::atomic<bool> memorylessLimitsReached = false;
|
||||
|
||||
@@ -108,8 +161,6 @@ 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;
|
||||
@@ -125,13 +176,15 @@ struct MetalContext {
|
||||
|
||||
std::array<MetalPushConstantBuffer, Program::SHADER_TYPE_COUNT> currentPushConstants;
|
||||
|
||||
MetalSamplerGroup* samplerBindings[Program::SAMPLER_BINDING_COUNT] = {};
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
// Keeps track of all alive textures.
|
||||
tsl::robin_set<MetalTexture*> textures;
|
||||
|
||||
// This circular buffer implements delayed destruction for Metal texture handles. It keeps a
|
||||
@@ -154,6 +207,7 @@ 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,9 +101,14 @@ 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) {
|
||||
@@ -125,6 +130,7 @@ void submitPendingCommands(MetalContext* context) {
|
||||
assert_invariant(context->pendingCommandBuffer.status != MTLCommandBufferStatusCommitted);
|
||||
[context->pendingCommandBuffer commit];
|
||||
context->pendingCommandBuffer = nil;
|
||||
context->pendingCommandBufferId++;
|
||||
}
|
||||
|
||||
id<MTLTexture> getOrCreateEmptyTexture(MetalContext* context) {
|
||||
@@ -167,7 +173,6 @@ 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,6 +32,7 @@
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
@@ -57,11 +58,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;
|
||||
@@ -73,10 +74,23 @@ 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;
|
||||
std::mutex mTickOpsLock;
|
||||
|
||||
// Tasks regularly executed on the driver thread after a command buffer has completed
|
||||
struct DeferredTask {
|
||||
DeferredTask(uint64_t commandBufferId, utils::Invocable<void()>&& fn) noexcept
|
||||
: commandBufferId(commandBufferId), fn(std::move(fn)) {}
|
||||
uint64_t commandBufferId; // after this command buffer completes
|
||||
utils::Invocable<void()> fn; // execute this task
|
||||
};
|
||||
void executeAfterCurrentCommandBufferCompletes(utils::Invocable<void()>&& fn) noexcept;
|
||||
void executeDeferredOps() noexcept;
|
||||
std::deque<DeferredTask> mDeferredTasks;
|
||||
|
||||
/*
|
||||
* Driver interface
|
||||
@@ -137,7 +151,6 @@ 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,100 +32,75 @@ struct MetalContext;
|
||||
* texture.
|
||||
*/
|
||||
class MetalExternalImage {
|
||||
|
||||
public:
|
||||
MetalExternalImage() = default;
|
||||
|
||||
MetalExternalImage(MetalContext& context,
|
||||
TextureSwizzle r = TextureSwizzle::CHANNEL_0,
|
||||
TextureSwizzle g = TextureSwizzle::CHANNEL_1,
|
||||
TextureSwizzle b = TextureSwizzle::CHANNEL_2,
|
||||
TextureSwizzle a = TextureSwizzle::CHANNEL_3) noexcept;
|
||||
MetalExternalImage(MetalExternalImage&&);
|
||||
MetalExternalImage& operator=(MetalExternalImage&&);
|
||||
~MetalExternalImage() noexcept;
|
||||
|
||||
MetalExternalImage(const MetalExternalImage&) = delete;
|
||||
MetalExternalImage& operator=(const MetalExternalImage&) = delete;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* While the texture is used for rendering, this MetalExternalImage must be kept alive.
|
||||
*/
|
||||
bool isValid() const noexcept;
|
||||
id<MTLTexture> getMtlTexture() const noexcept;
|
||||
|
||||
bool isValid() const noexcept {
|
||||
return mImage != nil || mRgbTexture != nullptr;
|
||||
}
|
||||
|
||||
NSUInteger getWidth() const noexcept;
|
||||
NSUInteger getHeight() const noexcept;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Create an external image with the passed-in CVPixelBuffer.
|
||||
*
|
||||
* Calling set with a YCbCr image will encode a compute pass to convert the image from YCbCr to
|
||||
* RGB.
|
||||
* 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.
|
||||
*/
|
||||
void set(CVPixelBufferRef image) noexcept;
|
||||
static MetalExternalImage createFromImage(MetalContext& context, CVPixelBufferRef image);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
void set(CVPixelBufferRef image, size_t plane) noexcept;
|
||||
static MetalExternalImage createFromImagePlane(
|
||||
MetalContext& context, CVPixelBufferRef image, uint32_t plane);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
static void assertWritableImage(CVPixelBufferRef image);
|
||||
|
||||
/**
|
||||
* Free resources. Should be called at least once when no further calls to set will occur.
|
||||
*/
|
||||
static void shutdown(MetalContext& context) noexcept;
|
||||
|
||||
static void assertWritableImage(CVPixelBufferRef image);
|
||||
|
||||
private:
|
||||
MetalExternalImage(CVPixelBufferRef image, CVMetalTextureRef texture) noexcept
|
||||
: mImage(image), mTexture(texture) {}
|
||||
explicit MetalExternalImage(id<MTLTexture> texture) noexcept : mRgbTexture(texture) {}
|
||||
|
||||
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 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);
|
||||
|
||||
static constexpr size_t Y_PLANE = 0;
|
||||
static constexpr size_t CBCR_PLANE = 1;
|
||||
|
||||
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.
|
||||
// TODO: this could probably be a union.
|
||||
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,10 +34,6 @@
|
||||
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>
|
||||
@@ -71,18 +67,30 @@ ycbcrToRgb(texture2d<half, access::read> inYTexture [[texture(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::getWidth() const noexcept {
|
||||
if (mImage) {
|
||||
return CVPixelBufferGetWidth(mImage);
|
||||
}
|
||||
if (mRgbTexture) {
|
||||
return mRgbTexture.width;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MetalExternalImage::set(CVPixelBufferRef image) noexcept {
|
||||
unset();
|
||||
NSUInteger MetalExternalImage::getHeight() const noexcept {
|
||||
if (mImage) {
|
||||
return CVPixelBufferGetHeight(mImage);
|
||||
}
|
||||
if (mRgbTexture) {
|
||||
return mRgbTexture.height;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
MetalExternalImage MetalExternalImage::createFromImage(
|
||||
MetalContext& context, CVPixelBufferRef image) {
|
||||
if (!image) {
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
OSType formatType = CVPixelBufferGetPixelFormatType(image);
|
||||
@@ -96,30 +104,29 @@ void MetalExternalImage::set(CVPixelBufferRef image) noexcept {
|
||||
<< ".";
|
||||
|
||||
if (planeCount == 0) {
|
||||
mImage = image;
|
||||
mTexture = createTextureFromImage(image, MTLPixelFormatBGRA8Unorm, 0);
|
||||
mTextureView = createSwizzledTextureView(mTexture);
|
||||
mWidth = CVPixelBufferGetWidth(image);
|
||||
mHeight = CVPixelBufferGetHeight(image);
|
||||
CVMetalTextureRef texture =
|
||||
createTextureFromImage(context.textureCache, image, MTLPixelFormatBGRA8Unorm, 0);
|
||||
return { CVPixelBufferRetain(image), texture };
|
||||
}
|
||||
|
||||
if (planeCount == 2) {
|
||||
CVMetalTextureRef yPlane = createTextureFromImage(image, MTLPixelFormatR8Unorm, Y_PLANE);
|
||||
CVMetalTextureRef cbcrPlane = createTextureFromImage(image, MTLPixelFormatRG8Unorm,
|
||||
CBCR_PLANE);
|
||||
CVPixelBufferRetain(image);
|
||||
|
||||
CVMetalTextureRef yPlane =
|
||||
createTextureFromImage(context.textureCache, image, MTLPixelFormatR8Unorm, Y_PLANE);
|
||||
CVMetalTextureRef cbcrPlane =
|
||||
createTextureFromImage(context.textureCache, image, MTLPixelFormatRG8Unorm, CBCR_PLANE);
|
||||
|
||||
// Get the size of luminance plane.
|
||||
mWidth = CVPixelBufferGetWidthOfPlane(image, Y_PLANE);
|
||||
mHeight = CVPixelBufferGetHeightOfPlane(image, Y_PLANE);
|
||||
NSUInteger width = CVPixelBufferGetWidthOfPlane(image, Y_PLANE);
|
||||
NSUInteger height = CVPixelBufferGetHeightOfPlane(image, Y_PLANE);
|
||||
|
||||
id<MTLTexture> rgbTexture = createRgbTexture(mWidth, mHeight);
|
||||
id<MTLCommandBuffer> commandBuffer = encodeColorConversionPass(
|
||||
id<MTLTexture> rgbTexture = createRgbTexture(context.device, width, height);
|
||||
id<MTLCommandBuffer> commandBuffer = encodeColorConversionPass(context,
|
||||
CVMetalTextureGetTexture(yPlane),
|
||||
CVMetalTextureGetTexture(cbcrPlane),
|
||||
rgbTexture);
|
||||
|
||||
mRgbTexture = createSwizzledTextureView(rgbTexture);
|
||||
|
||||
[commandBuffer addCompletedHandler:^(id <MTLCommandBuffer> o) {
|
||||
CVBufferRelease(yPlane);
|
||||
CVBufferRelease(cbcrPlane);
|
||||
@@ -127,70 +134,83 @@ void MetalExternalImage::set(CVPixelBufferRef image) noexcept {
|
||||
}];
|
||||
|
||||
[commandBuffer commit];
|
||||
return MetalExternalImage { rgbTexture };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void MetalExternalImage::set(CVPixelBufferRef image, size_t plane) noexcept {
|
||||
unset();
|
||||
|
||||
MetalExternalImage MetalExternalImage::createFromImagePlane(
|
||||
MetalContext& context, CVPixelBufferRef image, uint32_t plane) {
|
||||
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.";
|
||||
|
||||
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;
|
||||
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;
|
||||
};
|
||||
|
||||
const MTLPixelFormat format = getPlaneFormat(plane);
|
||||
assert_invariant(format != MTLPixelFormatInvalid);
|
||||
mTexture = createTextureFromImage(image, format, plane);
|
||||
mTextureView = createSwizzledTextureView(mTexture);
|
||||
CVMetalTextureRef mTexture = createTextureFromImage(context.textureCache, image, format, plane);
|
||||
return { CVPixelBufferRetain(image), mTexture };
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalExternalImage::getMetalTextureForDraw() const noexcept {
|
||||
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 {
|
||||
if (mRgbTexture) {
|
||||
return mRgbTexture;
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (mTexture) {
|
||||
return CVMetalTextureGetTexture(mTexture);
|
||||
}
|
||||
if (tracker.trackResource((__bridge void*) commandBuffer, mTexture, cvBufferDeleter)) {
|
||||
CVBufferRetain(mTexture);
|
||||
}
|
||||
|
||||
assert_invariant(mTextureView);
|
||||
return mTextureView;
|
||||
return nil;
|
||||
}
|
||||
|
||||
CVMetalTextureRef MetalExternalImage::createTextureFromImage(CVPixelBufferRef image,
|
||||
MTLPixelFormat format, size_t plane) {
|
||||
CVMetalTextureRef MetalExternalImage::createTextureFromImage(CVMetalTextureCacheRef textureCache,
|
||||
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,
|
||||
mContext.textureCache, image, nullptr, format, width, height, plane, &texture);
|
||||
CVReturn result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache,
|
||||
image, nullptr, format, width, height, plane, &texture);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == kCVReturnSuccess)
|
||||
<< "Could not create a CVMetalTexture from CVPixelBuffer.";
|
||||
|
||||
@@ -201,58 +221,19 @@ void MetalExternalImage::shutdown(MetalContext& context) noexcept {
|
||||
context.externalImageComputePipelineState = nil;
|
||||
}
|
||||
|
||||
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) {
|
||||
id<MTLTexture> MetalExternalImage::createRgbTexture(
|
||||
id<MTLDevice> device, size_t width, size_t height) {
|
||||
MTLTextureDescriptor *descriptor =
|
||||
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
|
||||
width:width
|
||||
height:height
|
||||
mipmapped:NO];
|
||||
descriptor.usage = MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead;
|
||||
return [mContext.device newTextureWithDescriptor:descriptor];
|
||||
return [device newTextureWithDescriptor:descriptor];
|
||||
}
|
||||
|
||||
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) {
|
||||
void MetalExternalImage::ensureComputePipelineState(MetalContext& context) {
|
||||
if (context.externalImageComputePipelineState != nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -260,29 +241,28 @@ void MetalExternalImage::ensureComputePipelineState() {
|
||||
|
||||
NSString* objcSource = [NSString stringWithCString:kernel
|
||||
encoding:NSUTF8StringEncoding];
|
||||
id<MTLLibrary> library = [mContext.device newLibraryWithSource:objcSource
|
||||
options:nil
|
||||
error:&error];
|
||||
id<MTLLibrary> library = [context.device newLibraryWithSource:objcSource
|
||||
options:nil
|
||||
error:&error];
|
||||
NSERROR_CHECK("Unable to compile Metal shading library.");
|
||||
|
||||
id<MTLFunction> kernelFunction = [library newFunctionWithName:@"ycbcrToRgb"];
|
||||
|
||||
mContext.externalImageComputePipelineState =
|
||||
[mContext.device newComputePipelineStateWithFunction:kernelFunction
|
||||
error:&error];
|
||||
context.externalImageComputePipelineState =
|
||||
[context.device newComputePipelineStateWithFunction:kernelFunction error:&error];
|
||||
NSERROR_CHECK("Unable to create Metal compute pipeline state.");
|
||||
}
|
||||
|
||||
id<MTLCommandBuffer> MetalExternalImage::encodeColorConversionPass(id<MTLTexture> inYPlane,
|
||||
id<MTLTexture> inCbCrTexture, id<MTLTexture> outTexture) {
|
||||
ensureComputePipelineState();
|
||||
id<MTLCommandBuffer> MetalExternalImage::encodeColorConversionPass(MetalContext& context,
|
||||
id<MTLTexture> inYPlane, id<MTLTexture> inCbCrTexture, id<MTLTexture> outTexture) {
|
||||
ensureComputePipelineState(context);
|
||||
|
||||
id<MTLCommandBuffer> commandBuffer = [mContext.commandQueue commandBuffer];
|
||||
id<MTLCommandBuffer> commandBuffer = [context.commandQueue commandBuffer];
|
||||
commandBuffer.label = @"YCbCr to RGB conversion";
|
||||
|
||||
id<MTLComputeCommandEncoder> computeEncoder = [commandBuffer computeCommandEncoder];
|
||||
|
||||
[computeEncoder setComputePipelineState:mContext.externalImageComputePipelineState];
|
||||
[computeEncoder setComputePipelineState:context.externalImageComputePipelineState];
|
||||
[computeEncoder setTexture:inYPlane atIndex:0];
|
||||
[computeEncoder setTexture:inCbCrTexture atIndex:1];
|
||||
[computeEncoder setTexture:outTexture atIndex:2];
|
||||
@@ -300,5 +280,11 @@ id<MTLCommandBuffer> MetalExternalImage::encodeColorConversionPass(id<MTLTexture
|
||||
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,6 +44,7 @@
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
@@ -84,6 +85,8 @@ public:
|
||||
NSUInteger getSurfaceWidth() const;
|
||||
NSUInteger getSurfaceHeight() const;
|
||||
|
||||
bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; }
|
||||
|
||||
private:
|
||||
|
||||
enum class SwapChainType {
|
||||
@@ -93,7 +96,6 @@ 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();
|
||||
@@ -138,12 +140,6 @@ 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;
|
||||
};
|
||||
@@ -200,12 +196,10 @@ 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;
|
||||
@@ -227,43 +221,42 @@ 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,
|
||||
TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a)
|
||||
noexcept;
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
~MetalTexture();
|
||||
// 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;
|
||||
|
||||
// Returns an id<MTLTexture> suitable for reading in a shader, taking into account swizzle and
|
||||
// LOD clamping.
|
||||
id<MTLTexture> getMtlTextureForRead() noexcept;
|
||||
// Returns an id<MTLTexture> suitable for reading in a shader, taking into account swizzle.
|
||||
id<MTLTexture> getMtlTextureForRead() const noexcept;
|
||||
|
||||
// Returns the id<MTLTexture> for attaching to a render pass.
|
||||
id<MTLTexture> getMtlTextureForWrite() noexcept {
|
||||
id<MTLTexture> getMtlTextureForWrite() const 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
|
||||
@@ -302,97 +295,16 @@ 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:
|
||||
|
||||
@@ -547,6 +459,61 @@ 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,7 +74,6 @@ 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) {
|
||||
@@ -100,17 +99,15 @@ 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(context),
|
||||
externalImage(MetalExternalImage::createFromImage(context, pixelBuffer)),
|
||||
type(SwapChainType::CVPIXELBUFFERREF) {
|
||||
assert_invariant(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
|
||||
MetalExternalImage::assertWritableImage(pixelBuffer);
|
||||
externalImage.set(pixelBuffer);
|
||||
assert_invariant(externalImage.isValid());
|
||||
}
|
||||
|
||||
@@ -121,7 +118,6 @@ MTLPixelFormat MetalSwapChain::decideDepthStencilFormat(uint64_t flags) {
|
||||
}
|
||||
|
||||
MetalSwapChain::~MetalSwapChain() {
|
||||
externalImage.set(nullptr);
|
||||
}
|
||||
|
||||
NSUInteger MetalSwapChain::getSurfaceWidth() const {
|
||||
@@ -171,7 +167,7 @@ id<MTLTexture> MetalSwapChain::acquireDrawable() {
|
||||
}
|
||||
|
||||
if (isPixelBuffer()) {
|
||||
return externalImage.getMetalTextureForDraw();
|
||||
return externalImage.getMtlTexture();
|
||||
}
|
||||
|
||||
assert_invariant(isCaMetalLayer());
|
||||
@@ -257,10 +253,6 @@ void MetalSwapChain::present() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD
|
||||
#define FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD 1
|
||||
#endif
|
||||
|
||||
class PresentDrawableData {
|
||||
public:
|
||||
PresentDrawableData() = delete;
|
||||
@@ -279,14 +271,10 @@ 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:
|
||||
@@ -482,11 +470,6 @@ 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);
|
||||
}
|
||||
@@ -506,10 +489,9 @@ 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, 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) {
|
||||
TextureUsage usage) noexcept
|
||||
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context) {
|
||||
assert_invariant(target != SamplerType::SAMPLER_EXTERNAL);
|
||||
|
||||
devicePixelFormat = decidePixelFormat(&context, format);
|
||||
FILAMENT_CHECK_POSTCONDITION(devicePixelFormat != MTLPixelFormatInvalid)
|
||||
@@ -595,16 +577,28 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
<< ", levels = " << int(levels) << ", MTLPixelFormat = " << int(devicePixelFormat)
|
||||
<< ", width = " << width << ", height = " << height << ", depth = " << depth
|
||||
<< "). Out of memory?";
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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) {
|
||||
// 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.
|
||||
@@ -618,44 +612,38 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
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),
|
||||
externalImage(context) {
|
||||
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context) {
|
||||
texture = metalTexture;
|
||||
setLodRange(0, levels - 1);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void MetalTexture::terminate() noexcept {
|
||||
texture = nil;
|
||||
swizzledTextureView = nil;
|
||||
lodTextureView = nil;
|
||||
msaaSidecar = nil;
|
||||
externalImage.set(nullptr);
|
||||
externalImage = nullptr;
|
||||
terminated = true;
|
||||
}
|
||||
|
||||
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;
|
||||
id<MTLTexture> MetalTexture::getMtlTextureForRead() const noexcept {
|
||||
return swizzledTextureView ? swizzledTextureView : texture;
|
||||
}
|
||||
|
||||
MTLPixelFormat MetalTexture::decidePixelFormat(MetalContext* context, TextureFormat format) {
|
||||
@@ -774,15 +762,12 @@ 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,
|
||||
@@ -906,98 +891,6 @@ 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) :
|
||||
@@ -1343,5 +1236,189 @@ 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,32 +33,28 @@
|
||||
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
|
||||
// 17-25 Uniform buffers 9 Program::UNIFORM_BINDING_COUNT
|
||||
// 26 Push constants 1
|
||||
// 27-30 Sampler groups (argument buffers) 4 Program::SAMPLER_BINDING_COUNT
|
||||
// 20 Push constants 1
|
||||
// 21-24 Descriptor sets (argument buffers) 4 limited by MAX_DESCRIPTOR_SET_COUNT
|
||||
// 25 Dynamic offset buffer 1
|
||||
//
|
||||
// Total 31
|
||||
// Total 23
|
||||
|
||||
// Compute Bindings
|
||||
// ----------------------
|
||||
// Bindings Buffer name Count
|
||||
// ------------------------------------------------------
|
||||
// 0-3 SSBO buffers 4 MAX_SSBO_COUNT
|
||||
// 17-25 Uniform buffers 9 Program::UNIFORM_BINDING_COUNT
|
||||
// 26 Push constants 1
|
||||
// 27-30 Sampler groups (argument buffers) 4 Program::SAMPLER_BINDING_COUNT
|
||||
// 20 Push constants 1
|
||||
// 21-24 Descriptor sets (argument buffers) 4 limited by MAX_DESCRIPTOR_SET_COUNT
|
||||
// 25 Dynamic offset buffer 1
|
||||
//
|
||||
// Total 18
|
||||
// Total 10
|
||||
|
||||
// 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.
|
||||
@@ -71,10 +67,11 @@ 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 UNIFORM_BUFFER_BINDING_START = 17u;
|
||||
static constexpr uint32_t SSBO_BINDING_START = 0u;
|
||||
static constexpr uint32_t SAMPLER_GROUP_BINDING_START = 27u;
|
||||
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;
|
||||
|
||||
// Forward declarations necessary here, definitions at end of file.
|
||||
inline bool operator==(const MTLViewport& lhs, const MTLViewport& rhs);
|
||||
@@ -387,14 +384,17 @@ using DepthClampStateTracker = StateTracker<MTLDepthClipMode>;
|
||||
// Argument encoder
|
||||
|
||||
struct ArgumentEncoderState {
|
||||
NSUInteger bufferCount;
|
||||
utils::FixedCapacityVector<MTLTextureType> textureTypes;
|
||||
|
||||
explicit ArgumentEncoderState(utils::FixedCapacityVector<MTLTextureType>&& types)
|
||||
: textureTypes(std::move(types)) {}
|
||||
explicit ArgumentEncoderState(
|
||||
NSUInteger bufferCount, utils::FixedCapacityVector<MTLTextureType>&& types)
|
||||
: bufferCount(bufferCount), textureTypes(std::move(types)) {}
|
||||
|
||||
bool operator==(const ArgumentEncoderState& rhs) const noexcept {
|
||||
return std::equal(textureTypes.begin(), textureTypes.end(), rhs.textureTypes.begin(),
|
||||
rhs.textureTypes.end());
|
||||
rhs.textureTypes.end()) &&
|
||||
bufferCount == rhs.bufferCount;
|
||||
}
|
||||
|
||||
bool operator!=(const ArgumentEncoderState& rhs) const noexcept {
|
||||
@@ -416,6 +416,30 @@ 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,28 +166,40 @@ 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& count = textureTypes.size();
|
||||
assert_invariant(count > 0);
|
||||
const auto& textureCount = textureTypes.size();
|
||||
const auto& bufferCount = state.bufferCount;
|
||||
assert_invariant(textureCount > 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:(count * 2)];
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
[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++) {
|
||||
MTLArgumentDescriptor* textureArgument = [MTLArgumentDescriptor argumentDescriptor];
|
||||
textureArgument.index = i * 2 + 0;
|
||||
textureArgument.index = i++;
|
||||
textureArgument.dataType = MTLDataTypeTexture;
|
||||
textureArgument.textureType = textureTypes[i];
|
||||
textureArgument.access = MTLArgumentAccessReadOnly;
|
||||
[arguments addObject:textureArgument];
|
||||
|
||||
MTLArgumentDescriptor* samplerArgument = [MTLArgumentDescriptor argumentDescriptor];
|
||||
samplerArgument.index = i * 2 + 1;
|
||||
samplerArgument.index = i++;
|
||||
samplerArgument.dataType = MTLDataTypeSampler;
|
||||
textureArgument.access = MTLArgumentAccessReadOnly;
|
||||
[arguments addObject:samplerArgument];
|
||||
@@ -196,5 +208,64 @@ 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,9 +99,6 @@ void NoopDriver::destroyProgram(Handle<HwProgram> ph) {
|
||||
void NoopDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
|
||||
}
|
||||
|
||||
void NoopDriver::destroySamplerGroup(Handle<HwSamplerGroup> sbh) {
|
||||
}
|
||||
|
||||
void NoopDriver::destroySwapChain(Handle<HwSwapChain> sch) {
|
||||
}
|
||||
|
||||
@@ -111,6 +108,12 @@ 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 {};
|
||||
}
|
||||
@@ -248,9 +251,6 @@ 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,11 +276,6 @@ 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) {
|
||||
@@ -303,27 +298,14 @@ 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, uint32_t len) {
|
||||
void NoopDriver::insertEventMarker(char const* string) {
|
||||
}
|
||||
|
||||
void NoopDriver::pushGroupMarker(char const* string, uint32_t len) {
|
||||
void NoopDriver::pushGroupMarker(char const* string) {
|
||||
}
|
||||
|
||||
void NoopDriver::popGroupMarker(int) {
|
||||
@@ -392,4 +374,28 @@ 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
|
||||
|
||||
89
filament/backend/src/opengl/BindingMap.h
Normal file
89
filament/backend/src/opengl/BindingMap.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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
|
||||
361
filament/backend/src/opengl/GLDescriptorSet.cpp
Normal file
361
filament/backend/src/opengl/GLDescriptorSet.cpp
Normal file
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* 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
|
||||
171
filament/backend/src/opengl/GLDescriptorSet.h
Normal file
171
filament/backend/src/opengl/GLDescriptorSet.h
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* 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
|
||||
52
filament/backend/src/opengl/GLDescriptorSetLayout.h
Normal file
52
filament/backend/src/opengl/GLDescriptorSetLayout.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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,12 +21,32 @@
|
||||
|
||||
#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 {
|
||||
@@ -44,8 +64,14 @@ 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;
|
||||
};
|
||||
|
||||
|
||||
@@ -60,10 +60,19 @@ public:
|
||||
struct RenderPrimitive {
|
||||
static_assert(MAX_VERTEX_ATTRIBUTE_COUNT <= 16);
|
||||
|
||||
GLuint vao[2] = {}; // 4
|
||||
GLuint vao[2] = {}; // 8
|
||||
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
|
||||
@@ -76,16 +85,11 @@ public:
|
||||
// See OpenGLContext::bindVertexArray()
|
||||
uint8_t nameVersion = 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
|
||||
// Size in bytes of indices in the index buffer (1 or 2)
|
||||
uint8_t indicesShift = 0; // 1
|
||||
|
||||
GLenum getIndicesType() const noexcept {
|
||||
return indicesSize == 4 ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
|
||||
return indicesType;
|
||||
}
|
||||
} gl;
|
||||
|
||||
@@ -474,12 +478,6 @@ 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;
|
||||
@@ -506,9 +504,6 @@ 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;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "OpenGLDriver.h"
|
||||
|
||||
#include "CommandStreamDispatcher.h"
|
||||
#include "GLTexture.h"
|
||||
#include "GLUtils.h"
|
||||
#include "OpenGLContext.h"
|
||||
#include "OpenGLDriverFactory.h"
|
||||
@@ -28,19 +29,21 @@
|
||||
|
||||
#include <backend/BufferDescriptor.h>
|
||||
#include <backend/CallbackHandler.h>
|
||||
#include <backend/DescriptorSetOffsetArray.h>
|
||||
#include <backend/DriverApiForward.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
#include <backend/PipelineState.h>
|
||||
#include <backend/Platform.h>
|
||||
#include <backend/Program.h>
|
||||
#include <backend/SamplerDescriptor.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include "private/backend/Dispatcher.h"
|
||||
#include "private/backend/DriverApi.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
@@ -59,7 +62,9 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
@@ -132,17 +137,16 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
|
||||
// this is useful for development, but too verbose even for debug builds
|
||||
// For reference on a 64-bits machine in Release mode:
|
||||
// GLIndexBuffer : 8 moderate
|
||||
// GLSamplerGroup : 16 few
|
||||
// GLSwapChain : 16 few
|
||||
// GLTimerQuery : 16 few
|
||||
// GLFence : 24 few
|
||||
// GLRenderPrimitive : 32 many
|
||||
// GLBufferObject : 32 many
|
||||
// -- less than or equal 32 bytes
|
||||
// OpenGLProgram : 56 moderate
|
||||
// GLTexture : 64 moderate
|
||||
// -- less than or equal 64 bytes
|
||||
// GLVertexBuffer : 76 moderate
|
||||
// OpenGLProgram : 96 moderate
|
||||
// -- less than or equal 96 bytes
|
||||
// GLStream : 104 few
|
||||
// GLRenderTarget : 112 few
|
||||
// GLVertexBufferInfo : 132 moderate
|
||||
@@ -154,7 +158,6 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
|
||||
<< "\nGLVertexBuffer: " << sizeof(GLVertexBuffer)
|
||||
<< "\nGLVertexBufferInfo: " << sizeof(GLVertexBufferInfo)
|
||||
<< "\nGLIndexBuffer: " << sizeof(GLIndexBuffer)
|
||||
<< "\nGLSamplerGroup: " << sizeof(GLSamplerGroup)
|
||||
<< "\nGLRenderPrimitive: " << sizeof(GLRenderPrimitive)
|
||||
<< "\nGLTexture: " << sizeof(GLTexture)
|
||||
<< "\nGLTimerQuery: " << sizeof(GLTimerQuery)
|
||||
@@ -249,8 +252,6 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
|
||||
mDriverConfig(driverConfig),
|
||||
mCurrentPushConstants(new(std::nothrow) PushConstantBundle{}) {
|
||||
|
||||
std::fill(mSamplerBindings.begin(), mSamplerBindings.end(), nullptr);
|
||||
|
||||
// set a reasonable default value for our stream array
|
||||
mTexturesWithStreamsAttached.reserve(8);
|
||||
mStreamsWithPendingAcquiredImage.reserve(8);
|
||||
@@ -381,18 +382,28 @@ void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept {
|
||||
}
|
||||
|
||||
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
// set-up textures and samplers in the proper TMUs (as specified in setSamplers)
|
||||
if (UTILS_UNLIKELY(mBoundProgram == p)) {
|
||||
// program didn't change, don't do anything.
|
||||
return true;
|
||||
}
|
||||
|
||||
// compile/link the program if needed and call glUseProgram
|
||||
bool const success = p->use(this, mContext);
|
||||
assert_invariant(success == p->isValid());
|
||||
|
||||
if (success) {
|
||||
// TODO: we could even improve this if the program could tell us which of the descriptors
|
||||
// bindings actually changed. In practice, it is likely that set 0 or 1 might not
|
||||
// change often.
|
||||
decltype(mInvalidDescriptorSetBindings) changed;
|
||||
changed.setValue((1 << MAX_DESCRIPTOR_SET_COUNT) - 1);
|
||||
mInvalidDescriptorSetBindings |= changed;
|
||||
|
||||
mBoundProgram = p;
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(mContext.isES2() && success)) {
|
||||
for (uint32_t i = 0; i < Program::UNIFORM_BINDING_COUNT; i++) {
|
||||
auto [id, buffer, age] = mContext.getEs2UniformBinding(i);
|
||||
if (buffer) {
|
||||
p->updateUniforms(i, id, buffer, age);
|
||||
}
|
||||
}
|
||||
// Set the output colorspace for this program (linear or rec709). This in only relevant
|
||||
// Set the output colorspace for this program (linear or rec709). This is only relevant
|
||||
// when mPlatform.isSRGBSwapChainSupported() is false (no need to check though).
|
||||
p->setRec709ColorSpace(mRec709OutputColorspace);
|
||||
}
|
||||
@@ -532,15 +543,23 @@ Handle<HwProgram> OpenGLDriver::createProgramS() noexcept {
|
||||
return initHandle<OpenGLProgram>();
|
||||
}
|
||||
|
||||
Handle<HwSamplerGroup> OpenGLDriver::createSamplerGroupS() noexcept {
|
||||
return initHandle<GLSamplerGroup>();
|
||||
}
|
||||
|
||||
Handle<HwTexture> OpenGLDriver::createTextureS() noexcept {
|
||||
return initHandle<GLTexture>();
|
||||
}
|
||||
|
||||
Handle<HwTexture> OpenGLDriver::createTextureSwizzledS() noexcept {
|
||||
Handle<HwTexture> OpenGLDriver::createTextureViewS() noexcept {
|
||||
return initHandle<GLTexture>();
|
||||
}
|
||||
|
||||
Handle<HwTexture> OpenGLDriver::createTextureViewSwizzleS() noexcept {
|
||||
return initHandle<GLTexture>();
|
||||
}
|
||||
|
||||
Handle<HwTexture> OpenGLDriver::createTextureExternalImageS() noexcept {
|
||||
return initHandle<GLTexture>();
|
||||
}
|
||||
|
||||
Handle<HwTexture> OpenGLDriver::createTextureExternalImagePlaneS() noexcept {
|
||||
return initHandle<GLTexture>();
|
||||
}
|
||||
|
||||
@@ -572,6 +591,14 @@ Handle<HwTimerQuery> OpenGLDriver::createTimerQueryS() noexcept {
|
||||
return initHandle<GLTimerQuery>();
|
||||
}
|
||||
|
||||
Handle<HwDescriptorSetLayout> OpenGLDriver::createDescriptorSetLayoutS() noexcept {
|
||||
return initHandle<GLDescriptorSetLayout>();
|
||||
}
|
||||
|
||||
Handle<HwDescriptorSet> OpenGLDriver::createDescriptorSetS() noexcept {
|
||||
return initHandle<GLDescriptorSet>();
|
||||
}
|
||||
|
||||
void OpenGLDriver::createVertexBufferInfoR(
|
||||
Handle<HwVertexBufferInfo> vbih,
|
||||
uint8_t bufferCount,
|
||||
@@ -644,7 +671,8 @@ void OpenGLDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
|
||||
|
||||
GLVertexBuffer* const vb = handle_cast<GLVertexBuffer*>(vbh);
|
||||
GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
|
||||
rp->gl.indicesSize = (ib->elementSize == 4u) ? 4u : 2u;
|
||||
rp->gl.indicesShift = (ib->elementSize == 4u) ? 2u : 1u;
|
||||
rp->gl.indicesType = (ib->elementSize == 4u) ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
|
||||
rp->gl.vertexBufferWithObjects = vbh;
|
||||
rp->type = pt;
|
||||
rp->vbih = vb->vbih;
|
||||
@@ -698,13 +726,6 @@ void OpenGLDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void OpenGLDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t size,
|
||||
utils::FixedSizeString<32> debugName) {
|
||||
DEBUG_MARKER()
|
||||
|
||||
construct<GLSamplerGroup>(sbh, size);
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void OpenGLDriver::textureStorage(OpenGLDriver::GLTexture* t,
|
||||
uint32_t width, uint32_t height, uint32_t depth, bool useProtectedMemory) noexcept {
|
||||
@@ -900,32 +921,129 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void OpenGLDriver::createTextureSwizzledR(Handle<HwTexture> th,
|
||||
SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
|
||||
uint32_t w, uint32_t h, uint32_t depth, TextureUsage usage,
|
||||
TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) {
|
||||
void OpenGLDriver::createTextureViewR(Handle<HwTexture> th,
|
||||
Handle<HwTexture> srch, uint8_t baseLevel, uint8_t levelCount) {
|
||||
DEBUG_MARKER()
|
||||
GLTexture const* const src = handle_cast<GLTexture const*>(srch);
|
||||
|
||||
assert_invariant(uint8_t(usage) & uint8_t(TextureUsage::SAMPLEABLE));
|
||||
FILAMENT_CHECK_PRECONDITION(any(src->usage & TextureUsage::SAMPLEABLE))
|
||||
<< "TextureView can only be created on a SAMPLEABLE texture";
|
||||
|
||||
createTextureR(th, target, levels, format, samples, w, h, depth, usage);
|
||||
FILAMENT_CHECK_PRECONDITION(!src->gl.imported)
|
||||
<< "TextureView can't be created on imported textures";
|
||||
|
||||
// WebGL does not support swizzling. We assert for this in the Texture builder,
|
||||
// so it is probably fine to silently ignore the swizzle state here.
|
||||
#if !defined(__EMSCRIPTEN__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2)
|
||||
if (!mContext.isES2()) {
|
||||
// the texture is still bound and active from createTextureR
|
||||
GLTexture* t = handle_cast<GLTexture*>(th);
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_R, (GLint)getSwizzleChannel(r));
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_G, (GLint)getSwizzleChannel(g));
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_B, (GLint)getSwizzleChannel(b));
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_A, (GLint)getSwizzleChannel(a));
|
||||
if (!src->ref) {
|
||||
// lazily create the ref handle, because most textures will never get a texture view
|
||||
src->ref = initHandle<GLTextureRef>();
|
||||
}
|
||||
#endif
|
||||
|
||||
GLTexture* t = construct<GLTexture>(th,
|
||||
src->target,
|
||||
src->levels,
|
||||
src->samples,
|
||||
src->width, src->height, src->depth,
|
||||
src->format,
|
||||
src->usage);
|
||||
|
||||
t->gl = src->gl;
|
||||
t->gl.sidecarRenderBufferMS = 0;
|
||||
t->gl.sidecarSamples = 1;
|
||||
|
||||
auto srcBaseLevel = src->gl.baseLevel;
|
||||
auto srcMaxLevel = src->gl.maxLevel;
|
||||
if (srcBaseLevel > srcMaxLevel) {
|
||||
srcBaseLevel = 0;
|
||||
srcMaxLevel = 127;
|
||||
}
|
||||
t->gl.baseLevel = (int8_t)std::min(127, srcBaseLevel + baseLevel);
|
||||
t->gl.maxLevel = (int8_t)std::min(127, srcBaseLevel + baseLevel + levelCount - 1);
|
||||
|
||||
// increase reference count to this texture handle
|
||||
t->ref = src->ref;
|
||||
GLTextureRef* ref = handle_cast<GLTextureRef*>(t->ref);
|
||||
assert_invariant(ref);
|
||||
ref->count++;
|
||||
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void OpenGLDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwTexture> srch,
|
||||
backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b,
|
||||
backend::TextureSwizzle a) {
|
||||
|
||||
DEBUG_MARKER()
|
||||
GLTexture const* const src = handle_cast<GLTexture const*>(srch);
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(any(src->usage & TextureUsage::SAMPLEABLE))
|
||||
<< "TextureView can only be created on a SAMPLEABLE texture";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!src->gl.imported)
|
||||
<< "TextureView can't be created on imported textures";
|
||||
|
||||
if (!src->ref) {
|
||||
// lazily create the ref handle, because most textures will never get a texture view
|
||||
src->ref = initHandle<GLTextureRef>();
|
||||
}
|
||||
|
||||
GLTexture* t = construct<GLTexture>(th,
|
||||
src->target,
|
||||
src->levels,
|
||||
src->samples,
|
||||
src->width, src->height, src->depth,
|
||||
src->format,
|
||||
src->usage);
|
||||
|
||||
t->gl = src->gl;
|
||||
t->gl.baseLevel = src->gl.baseLevel;
|
||||
t->gl.maxLevel = src->gl.maxLevel;
|
||||
t->gl.sidecarRenderBufferMS = 0;
|
||||
t->gl.sidecarSamples = 1;
|
||||
|
||||
auto getChannel = [&swizzle = src->gl.swizzle](TextureSwizzle ch) {
|
||||
switch (ch) {
|
||||
case TextureSwizzle::SUBSTITUTE_ZERO:
|
||||
case TextureSwizzle::SUBSTITUTE_ONE:
|
||||
return ch;
|
||||
case TextureSwizzle::CHANNEL_0:
|
||||
return swizzle[0];
|
||||
case TextureSwizzle::CHANNEL_1:
|
||||
return swizzle[1];
|
||||
case TextureSwizzle::CHANNEL_2:
|
||||
return swizzle[2];
|
||||
case TextureSwizzle::CHANNEL_3:
|
||||
return swizzle[3];
|
||||
}
|
||||
};
|
||||
|
||||
t->gl.swizzle = {
|
||||
getChannel(r),
|
||||
getChannel(g),
|
||||
getChannel(b),
|
||||
getChannel(a),
|
||||
};
|
||||
|
||||
// increase reference count to this texture handle
|
||||
t->ref = src->ref;
|
||||
GLTextureRef* ref = handle_cast<GLTextureRef*>(t->ref);
|
||||
assert_invariant(ref);
|
||||
ref->count++;
|
||||
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void OpenGLDriver::createTextureExternalImageR(Handle<HwTexture> th, backend::TextureFormat format,
|
||||
uint32_t width, uint32_t height, backend::TextureUsage usage, void* image) {
|
||||
createTextureR(th, SamplerType::SAMPLER_EXTERNAL, 1, format, 1, width, height, 1, usage);
|
||||
setExternalImage(th, image);
|
||||
}
|
||||
|
||||
void OpenGLDriver::createTextureExternalImagePlaneR(Handle<HwTexture> th,
|
||||
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
|
||||
void* image, uint32_t plane) {
|
||||
createTextureR(th, SamplerType::SAMPLER_EXTERNAL, 1, format, 1, width, height, 1, usage);
|
||||
setExternalImagePlane(th, image, plane);
|
||||
}
|
||||
|
||||
void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
|
||||
SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
|
||||
uint32_t w, uint32_t h, uint32_t depth, TextureUsage usage) {
|
||||
@@ -1330,14 +1448,6 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
|
||||
rt->gl.resolve |= resolveFlags;
|
||||
|
||||
if (any(t->usage & TextureUsage::SAMPLEABLE)) {
|
||||
// In a sense, drawing to a texture level is similar to calling setTextureData on it; in
|
||||
// both cases, we update the base/max LOD to give shaders access to levels as they become
|
||||
// available. Note that this can only expand the LOD range (never shrink it), and that
|
||||
// users can override this range by calling setMinMaxLevels().
|
||||
updateTextureLodRange(t, (int8_t)binfo.level);
|
||||
}
|
||||
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER)
|
||||
}
|
||||
@@ -1578,6 +1688,19 @@ void OpenGLDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
||||
mContext.createTimerQuery(tq);
|
||||
}
|
||||
|
||||
void OpenGLDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
|
||||
DescriptorSetLayout&& info) {
|
||||
DEBUG_MARKER()
|
||||
construct<GLDescriptorSetLayout>(dslh, std::move(info));
|
||||
}
|
||||
|
||||
void OpenGLDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
||||
Handle<HwDescriptorSetLayout> dslh) {
|
||||
DEBUG_MARKER()
|
||||
GLDescriptorSetLayout const* dsl = handle_cast<GLDescriptorSetLayout*>(dslh);
|
||||
construct<GLDescriptorSet>(dsh, mContext, dslh, dsl);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Destroying driver objects
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -1655,35 +1778,41 @@ void OpenGLDriver::destroyProgram(Handle<HwProgram> ph) {
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::destroySamplerGroup(Handle<HwSamplerGroup> sbh) {
|
||||
DEBUG_MARKER()
|
||||
if (sbh) {
|
||||
GLSamplerGroup* sb = handle_cast<GLSamplerGroup*>(sbh);
|
||||
for (auto& binding : mSamplerBindings) {
|
||||
if (binding == sb) {
|
||||
binding = nullptr;
|
||||
}
|
||||
}
|
||||
destruct(sbh, sb);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::destroyTexture(Handle<HwTexture> th) {
|
||||
DEBUG_MARKER()
|
||||
|
||||
if (th) {
|
||||
auto& gl = mContext;
|
||||
GLTexture* t = handle_cast<GLTexture*>(th);
|
||||
|
||||
if (UTILS_LIKELY(!t->gl.imported)) {
|
||||
if (UTILS_LIKELY(t->usage & TextureUsage::SAMPLEABLE)) {
|
||||
gl.unbindTexture(t->gl.target, t->gl.id);
|
||||
if (UTILS_UNLIKELY(t->hwStream)) {
|
||||
detachStream(t);
|
||||
// drop a reference
|
||||
uint16_t count = 0;
|
||||
if (UTILS_UNLIKELY(t->ref)) {
|
||||
// the common case is that we don't have a ref handle
|
||||
GLTextureRef* const ref = handle_cast<GLTextureRef*>(t->ref);
|
||||
count = --(ref->count);
|
||||
if (count == 0) {
|
||||
destruct(t->ref, ref);
|
||||
}
|
||||
}
|
||||
if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) {
|
||||
mPlatform.destroyExternalImage(t->externalTexture);
|
||||
if (count == 0) {
|
||||
// if this was the last reference, we destroy the refcount as well as
|
||||
// the GL texture name itself.
|
||||
gl.unbindTexture(t->gl.target, t->gl.id);
|
||||
if (UTILS_UNLIKELY(t->hwStream)) {
|
||||
detachStream(t);
|
||||
}
|
||||
if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) {
|
||||
mPlatform.destroyExternalImage(t->externalTexture);
|
||||
} else {
|
||||
glDeleteTextures(1, &t->gl.id);
|
||||
}
|
||||
} else {
|
||||
glDeleteTextures(1, &t->gl.id);
|
||||
// The Handle<HwTexture> is always destroyed. For extra precaution we also
|
||||
// check that the GLTexture has a trivial destructor.
|
||||
static_assert(std::is_trivially_destructible_v<GLTexture>);
|
||||
}
|
||||
} else {
|
||||
assert_invariant(t->gl.target == GL_RENDERBUFFER);
|
||||
@@ -1788,6 +1917,28 @@ void OpenGLDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> dslh) {
|
||||
DEBUG_MARKER()
|
||||
if (dslh) {
|
||||
GLDescriptorSetLayout* dsl = handle_cast<GLDescriptorSetLayout*>(dslh);
|
||||
destruct(dslh, dsl);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
|
||||
DEBUG_MARKER()
|
||||
if (dsh) {
|
||||
// unbind the descriptor-set, to avoid use-after-free
|
||||
for (auto& bound : mBoundDescriptorSets) {
|
||||
if (bound.dsh == dsh) {
|
||||
bound = {};
|
||||
}
|
||||
}
|
||||
GLDescriptorSet* ds = handle_cast<GLDescriptorSet*>(dsh);
|
||||
destruct(dsh, ds);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Synchronous APIs
|
||||
// These are called on the application's thread
|
||||
@@ -2222,6 +2373,10 @@ void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain>
|
||||
// Updating driver objects
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void OpenGLDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
|
||||
mHandleAllocator.associateTagToHandle(handleId, std::move(tag));
|
||||
}
|
||||
|
||||
void OpenGLDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh,
|
||||
uint32_t index, Handle<HwBufferObject> boh) {
|
||||
DEBUG_MARKER()
|
||||
@@ -2360,122 +2515,6 @@ void OpenGLDriver::resetBufferObject(Handle<HwBufferObject> boh) {
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
|
||||
BufferDescriptor&& data) {
|
||||
DEBUG_MARKER()
|
||||
|
||||
OpenGLContext const& context = getContext();
|
||||
|
||||
#if defined(GL_EXT_texture_filter_anisotropic)
|
||||
const bool anisotropyWorkaround =
|
||||
context.ext.EXT_texture_filter_anisotropic &&
|
||||
context.bugs.texture_filter_anisotropic_broken_on_sampler;
|
||||
#endif
|
||||
|
||||
GLSamplerGroup* const sb = handle_cast<GLSamplerGroup *>(sbh);
|
||||
assert_invariant(sb->textureUnitEntries.size() == data.size / sizeof(SamplerDescriptor));
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
bool const es2 = context.isES2();
|
||||
#endif
|
||||
|
||||
auto const* const pSamplers = (SamplerDescriptor const*)data.buffer;
|
||||
for (size_t i = 0, c = sb->textureUnitEntries.size(); i < c; i++) {
|
||||
GLuint samplerId = 0u;
|
||||
Handle<HwTexture> th = pSamplers[i].t;
|
||||
if (UTILS_LIKELY(th)) {
|
||||
GLTexture const* const t = handle_cast<const GLTexture*>(th);
|
||||
assert_invariant(t);
|
||||
|
||||
if (UTILS_UNLIKELY(es2)
|
||||
#if defined(GL_EXT_texture_filter_anisotropic)
|
||||
|| UTILS_UNLIKELY(anisotropyWorkaround)
|
||||
#endif
|
||||
) {
|
||||
// We must set texture parameters on the texture itself.
|
||||
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
|
||||
}
|
||||
|
||||
SamplerParams params = pSamplers[i].s;
|
||||
if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) {
|
||||
// From OES_EGL_image_external spec:
|
||||
// "The default s and t wrap modes are CLAMP_TO_EDGE, and it is an INVALID_ENUM
|
||||
// error to set the wrap mode to any other value."
|
||||
params.wrapS = SamplerWrapMode::CLAMP_TO_EDGE;
|
||||
params.wrapT = SamplerWrapMode::CLAMP_TO_EDGE;
|
||||
params.wrapR = SamplerWrapMode::CLAMP_TO_EDGE;
|
||||
}
|
||||
// GLES3.x specification forbids depth textures to be filtered.
|
||||
if (UTILS_UNLIKELY(isDepthFormat(t->format)
|
||||
&& params.compareMode == SamplerCompareMode::NONE
|
||||
&& params.filterMag != SamplerMagFilter::NEAREST
|
||||
&& params.filterMin != SamplerMinFilter::NEAREST
|
||||
&& params.filterMin != SamplerMinFilter::NEAREST_MIPMAP_NEAREST)) {
|
||||
params.filterMag = SamplerMagFilter::NEAREST;
|
||||
params.filterMin = SamplerMinFilter::NEAREST;
|
||||
#ifndef NDEBUG
|
||||
slog.w << "HwSamplerGroup specifies a filtered depth texture, which is not allowed."
|
||||
<< io::endl;
|
||||
#endif
|
||||
}
|
||||
#if defined(GL_EXT_texture_filter_anisotropic)
|
||||
if (UTILS_UNLIKELY(anisotropyWorkaround)) {
|
||||
// Driver claims to support anisotropic filtering, but it fails when set on
|
||||
// the sampler, we have to set it on the texture instead.
|
||||
// The texture is already bound here.
|
||||
GLfloat const anisotropy = float(1u << params.anisotropyLog2);
|
||||
glTexParameterf(t->gl.target, GL_TEXTURE_MAX_ANISOTROPY_EXT,
|
||||
std::min(context.gets.max_anisotropy, anisotropy));
|
||||
}
|
||||
#endif
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (UTILS_LIKELY(!es2)) {
|
||||
samplerId = mContext.getSampler(params);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
// in ES2 the sampler parameters need to be set on the texture itself
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MIN_FILTER,
|
||||
(GLint)getTextureFilter(params.filterMin));
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MAG_FILTER,
|
||||
(GLint)getTextureFilter(params.filterMag));
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_WRAP_S,
|
||||
(GLint)getWrapMode(params.wrapS));
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_WRAP_T,
|
||||
(GLint)getWrapMode(params.wrapT));
|
||||
}
|
||||
} else {
|
||||
// this happens if the program doesn't use all samplers of a sampler group,
|
||||
// which is not an error.
|
||||
}
|
||||
|
||||
sb->textureUnitEntries[i] = { th, samplerId };
|
||||
}
|
||||
scheduleDestroy(std::move(data));
|
||||
}
|
||||
|
||||
void OpenGLDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint32_t maxLevel) {
|
||||
DEBUG_MARKER()
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
auto& gl = mContext;
|
||||
if (!gl.isES2()) {
|
||||
GLTexture* t = handle_cast<GLTexture*>(th);
|
||||
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
|
||||
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
|
||||
|
||||
// Must fit within int8_t.
|
||||
assert_invariant(minLevel <= 0x7f && maxLevel <= 0x7f);
|
||||
|
||||
t->gl.baseLevel = (int8_t)minLevel;
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel);
|
||||
|
||||
t->gl.maxLevel = (int8_t)maxLevel; // NOTE: according to the GL spec, the default value of this 1000
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::update3DImage(Handle<HwTexture> th,
|
||||
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
|
||||
uint32_t width, uint32_t height, uint32_t depth,
|
||||
@@ -2505,16 +2544,6 @@ void OpenGLDriver::generateMipmaps(Handle<HwTexture> th) {
|
||||
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
|
||||
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
|
||||
|
||||
t->gl.baseLevel = 0;
|
||||
t->gl.maxLevel = static_cast<int8_t>(t->levels - 1);
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (!gl.isES2()) {
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel);
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel);
|
||||
}
|
||||
#endif
|
||||
|
||||
glGenerateMipmap(t->gl.target);
|
||||
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
@@ -2624,21 +2653,6 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (!gl.isES2()) {
|
||||
// Update the base/max LOD, so we don't access undefined LOD. this allows the app to
|
||||
// specify levels as they become available.
|
||||
if (int8_t(level) < t->gl.baseLevel) {
|
||||
t->gl.baseLevel = int8_t(level);
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel);
|
||||
}
|
||||
if (int8_t(level) > t->gl.maxLevel) {
|
||||
t->gl.maxLevel = int8_t(level);
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
scheduleDestroy(std::move(p));
|
||||
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
@@ -2725,21 +2739,6 @@ void OpenGLDriver::setCompressedTextureData(GLTexture* t, uint32_t level,
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (!gl.isES2()) {
|
||||
// Update the base/max LOD, so we don't access undefined LOD. this allows the app to
|
||||
// specify levels as they become available.
|
||||
if (int8_t(level) < t->gl.baseLevel) {
|
||||
t->gl.baseLevel = int8_t(level);
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel);
|
||||
}
|
||||
if (int8_t(level) > t->gl.maxLevel) {
|
||||
t->gl.maxLevel = int8_t(level);
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
scheduleDestroy(std::move(p));
|
||||
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
@@ -3134,81 +3133,23 @@ void OpenGLDriver::setScissor(Viewport const& scissor) noexcept {
|
||||
// Setting rendering state
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void OpenGLDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> ubh) {
|
||||
DEBUG_MARKER()
|
||||
GLBufferObject* ub = handle_cast<GLBufferObject *>(ubh);
|
||||
assert_invariant(ub->bindingType == BufferObjectBinding::UNIFORM);
|
||||
bindBufferRange(BufferObjectBinding::UNIFORM, index, ubh, 0, ub->byteCount);
|
||||
}
|
||||
|
||||
void OpenGLDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index,
|
||||
Handle<HwBufferObject> ubh, uint32_t offset, uint32_t size) {
|
||||
DEBUG_MARKER()
|
||||
auto& gl = mContext;
|
||||
|
||||
assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE ||
|
||||
bindingType == BufferObjectBinding::UNIFORM);
|
||||
|
||||
GLBufferObject* ub = handle_cast<GLBufferObject *>(ubh);
|
||||
|
||||
assert_invariant(offset + size <= ub->byteCount);
|
||||
|
||||
if (UTILS_UNLIKELY(ub->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
|
||||
gl.setEs2UniformBinding(index,
|
||||
ub->gl.id,
|
||||
static_cast<uint8_t const*>(ub->gl.buffer) + offset,
|
||||
ub->age);
|
||||
} else {
|
||||
GLenum const target = GLUtils::getBufferBindingType(bindingType);
|
||||
|
||||
assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE ||
|
||||
ub->gl.binding == target);
|
||||
|
||||
gl.bindBufferRange(target, GLuint(index), ub->gl.id, offset, size);
|
||||
}
|
||||
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void OpenGLDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
|
||||
DEBUG_MARKER()
|
||||
auto& gl = mContext;
|
||||
|
||||
if (UTILS_UNLIKELY(bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
|
||||
gl.setEs2UniformBinding(index, 0, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
GLenum const target = GLUtils::getBufferBindingType(bindingType);
|
||||
gl.bindBufferRange(target, GLuint(index), 0, 0, 0);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void OpenGLDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
||||
DEBUG_MARKER()
|
||||
assert_invariant(index < Program::SAMPLER_BINDING_COUNT);
|
||||
GLSamplerGroup* sb = handle_cast<GLSamplerGroup *>(sbh);
|
||||
mSamplerBindings[index] = sb;
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void OpenGLDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
void OpenGLDriver::insertEventMarker(char const* string) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
auto& gl = mContext;
|
||||
if (gl.ext.EXT_debug_marker) {
|
||||
glInsertEventMarkerEXT(GLsizei(len ? len : strlen(string)), string);
|
||||
glInsertEventMarkerEXT(GLsizei(strlen(string)), string);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) {
|
||||
void OpenGLDriver::pushGroupMarker(char const* string) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
#if DEBUG_GROUP_MARKER_LEVEL & DEBUG_GROUP_MARKER_OPENGL
|
||||
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
|
||||
glPushGroupMarkerEXT(GLsizei(len ? len : strlen(string)), string);
|
||||
glPushGroupMarkerEXT(GLsizei(strlen(string)), string);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -3557,6 +3498,26 @@ void OpenGLDriver::endFrame(UTILS_UNUSED uint32_t frameId) {
|
||||
insertEventMarker("endFrame");
|
||||
}
|
||||
|
||||
void OpenGLDriver::updateDescriptorSetBuffer(
|
||||
backend::DescriptorSetHandle dsh,
|
||||
backend::descriptor_binding_t binding,
|
||||
backend::BufferObjectHandle boh,
|
||||
uint32_t offset, uint32_t size) {
|
||||
GLDescriptorSet* ds = handle_cast<GLDescriptorSet*>(dsh);
|
||||
GLBufferObject* bo = boh ? handle_cast<GLBufferObject*>(boh) : nullptr;
|
||||
ds->update(mContext, binding, bo, offset, size);
|
||||
}
|
||||
|
||||
void OpenGLDriver::updateDescriptorSetTexture(
|
||||
backend::DescriptorSetHandle dsh,
|
||||
backend::descriptor_binding_t binding,
|
||||
backend::TextureHandle th,
|
||||
SamplerParams params) {
|
||||
GLDescriptorSet* ds = handle_cast<GLDescriptorSet*>(dsh);
|
||||
GLTexture* t = th ? handle_cast<GLTexture*>(th) : nullptr;
|
||||
ds->update(mContext, binding, t, params);
|
||||
}
|
||||
|
||||
void OpenGLDriver::flush(int) {
|
||||
DEBUG_MARKER()
|
||||
auto& gl = mContext;
|
||||
@@ -3801,15 +3762,6 @@ void OpenGLDriver::blit(
|
||||
gl.unbindFramebuffer(GL_DRAW_FRAMEBUFFER);
|
||||
gl.unbindFramebuffer(GL_READ_FRAMEBUFFER);
|
||||
glDeleteFramebuffers(2, fbo);
|
||||
|
||||
if (any(d->usage & TextureUsage::SAMPLEABLE)) {
|
||||
// In a sense, blitting to a texture level is similar to calling setTextureData on it; in
|
||||
// both cases, we update the base/max LOD to give shaders access to levels as they become
|
||||
// available. Note that this can only expand the LOD range (never shrink it), and that
|
||||
// users can override this range by calling setMinMaxLevels().
|
||||
updateTextureLodRange(d, int8_t(dstLevel));
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3880,29 +3832,6 @@ void OpenGLDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::updateTextureLodRange(GLTexture* texture, int8_t targetLevel) noexcept {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
auto& gl = mContext;
|
||||
if (!gl.isES2()) {
|
||||
if (texture && any(texture->usage & TextureUsage::SAMPLEABLE)) {
|
||||
if (targetLevel < texture->gl.baseLevel || targetLevel > texture->gl.maxLevel) {
|
||||
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, texture);
|
||||
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
|
||||
if (targetLevel < texture->gl.baseLevel) {
|
||||
texture->gl.baseLevel = targetLevel;
|
||||
glTexParameteri(texture->gl.target, GL_TEXTURE_BASE_LEVEL, targetLevel);
|
||||
}
|
||||
if (targetLevel > texture->gl.maxLevel) {
|
||||
texture->gl.maxLevel = targetLevel;
|
||||
glTexParameteri(texture->gl.target, GL_TEXTURE_MAX_LEVEL, targetLevel);
|
||||
}
|
||||
}
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::bindPipeline(PipelineState const& state) {
|
||||
DEBUG_MARKER()
|
||||
auto& gl = mContext;
|
||||
@@ -3912,6 +3841,8 @@ void OpenGLDriver::bindPipeline(PipelineState const& state) {
|
||||
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(state.program);
|
||||
mValidProgram = useProgram(p);
|
||||
(*mCurrentPushConstants) = p->getPushConstants();
|
||||
mCurrentSetLayout = state.pipelineLayout.setLayout;
|
||||
// TODO: we should validate that the pipeline layout matches the program's
|
||||
}
|
||||
|
||||
void OpenGLDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
@@ -3935,18 +3866,82 @@ void OpenGLDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
mBoundRenderPrimitive = rp;
|
||||
}
|
||||
|
||||
void OpenGLDriver::bindDescriptorSet(
|
||||
backend::DescriptorSetHandle dsh,
|
||||
backend::descriptor_set_t set,
|
||||
backend::DescriptorSetOffsetArray&& offsets) {
|
||||
// handle_cast<> here also serves to validate the handle (it actually cannot return nullptr)
|
||||
GLDescriptorSet const* const ds = handle_cast<GLDescriptorSet*>(dsh);
|
||||
if (ds) {
|
||||
assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT);
|
||||
if (mBoundDescriptorSets[set].dsh != dsh) {
|
||||
// if the descriptor itself changed, we mark this descriptor binding
|
||||
// invalid -- it will be re-bound at the next draw.
|
||||
mInvalidDescriptorSetBindings.set(set, true);
|
||||
} else if (!offsets.empty()) {
|
||||
// if we reset offsets, we mark the offsets invalid so these descriptors only can
|
||||
// be re-bound at the next draw.
|
||||
mInvalidDescriptorSetBindingOffsets.set(set, true);
|
||||
}
|
||||
|
||||
// `offsets` data's lifetime will end when this function returns. We have to make a copy.
|
||||
// (the data is allocated inside the CommandStream)
|
||||
mBoundDescriptorSets[set].dsh = dsh;
|
||||
std::copy_n(offsets.data(), ds->getDynamicBufferCount(),
|
||||
mBoundDescriptorSets[set].offsets.data());
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::updateDescriptors(utils::bitset8 invalidDescriptorSets) noexcept {
|
||||
assert_invariant(mBoundProgram);
|
||||
auto const offsetOnly = mInvalidDescriptorSetBindingOffsets & ~mInvalidDescriptorSetBindings;
|
||||
invalidDescriptorSets.forEachSetBit([this, offsetOnly,
|
||||
&boundDescriptorSets = mBoundDescriptorSets,
|
||||
&context = mContext,
|
||||
&boundProgram = *mBoundProgram](size_t set) {
|
||||
assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT);
|
||||
auto const& entry = boundDescriptorSets[set];
|
||||
if (entry.dsh) {
|
||||
GLDescriptorSet* const ds = handle_cast<GLDescriptorSet*>(entry.dsh);
|
||||
#ifndef NDEBUG
|
||||
if (UTILS_UNLIKELY(!offsetOnly[set])) {
|
||||
// validate that this descriptor-set layout matches the layout set in the pipeline
|
||||
// we don't need to do the check if only the offset is changing
|
||||
ds->validate(mHandleAllocator, mCurrentSetLayout[set]);
|
||||
}
|
||||
#endif
|
||||
ds->bind(context, mHandleAllocator, boundProgram,
|
||||
set, entry.offsets.data(), offsetOnly[set]);
|
||||
}
|
||||
});
|
||||
mInvalidDescriptorSetBindings.clear();
|
||||
mInvalidDescriptorSetBindingOffsets.clear();
|
||||
}
|
||||
|
||||
void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
DEBUG_MARKER()
|
||||
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
|
||||
if (UTILS_UNLIKELY(!rp || !mValidProgram)) {
|
||||
assert_invariant(!mContext.isES2());
|
||||
assert_invariant(mBoundRenderPrimitive);
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
if (UTILS_UNLIKELY(!mValidProgram)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
assert_invariant(mBoundProgram);
|
||||
assert_invariant(mValidProgram);
|
||||
|
||||
// When the program changes, we might have to rebind all or some descriptors
|
||||
auto const invalidDescriptorSets =
|
||||
mInvalidDescriptorSetBindings | mInvalidDescriptorSetBindingOffsets;
|
||||
if (UTILS_UNLIKELY(invalidDescriptorSets.any())) {
|
||||
updateDescriptors(invalidDescriptorSets);
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
assert_invariant(!mContext.isES2());
|
||||
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
|
||||
glDrawElementsInstanced(GLenum(rp->type), (GLsizei)indexCount,
|
||||
rp->gl.getIndicesType(),
|
||||
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize),
|
||||
reinterpret_cast<const void*>(indexOffset << rp->gl.indicesShift),
|
||||
(GLsizei)instanceCount);
|
||||
#endif
|
||||
|
||||
@@ -3957,19 +3952,30 @@ void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
||||
#endif
|
||||
}
|
||||
|
||||
// This is the ES2 version of draw2().
|
||||
void OpenGLDriver::draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
DEBUG_MARKER()
|
||||
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
|
||||
if (UTILS_UNLIKELY(!rp || !mValidProgram)) {
|
||||
assert_invariant(mContext.isES2());
|
||||
assert_invariant(mBoundRenderPrimitive);
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
if (UTILS_UNLIKELY(!mValidProgram)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
assert_invariant(mBoundProgram);
|
||||
assert_invariant(mValidProgram);
|
||||
|
||||
assert_invariant(mContext.isES2());
|
||||
// When the program changes, we might have to rebind all or some descriptors
|
||||
auto const invalidDescriptorSets =
|
||||
mInvalidDescriptorSetBindings | mInvalidDescriptorSetBindingOffsets;
|
||||
if (UTILS_UNLIKELY(invalidDescriptorSets.any())) {
|
||||
updateDescriptors(invalidDescriptorSets);
|
||||
}
|
||||
|
||||
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
|
||||
assert_invariant(instanceCount == 1);
|
||||
|
||||
glDrawElements(GLenum(rp->type), (GLsizei)indexCount, rp->gl.getIndicesType(),
|
||||
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize));
|
||||
|
||||
reinterpret_cast<const void*>(indexOffset << rp->gl.indicesShift));
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "OpenGLContext.h"
|
||||
#include "OpenGLTimerQuery.h"
|
||||
#include "GLBufferObject.h"
|
||||
#include "GLDescriptorSet.h"
|
||||
#include "GLDescriptorSetLayout.h"
|
||||
#include "GLTexture.h"
|
||||
#include "ShaderCompilerService.h"
|
||||
|
||||
@@ -36,6 +38,7 @@
|
||||
#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>
|
||||
@@ -52,6 +55,7 @@
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -123,16 +127,6 @@ 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;
|
||||
@@ -145,6 +139,10 @@ 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 {
|
||||
@@ -317,10 +315,6 @@ 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;
|
||||
@@ -333,8 +327,16 @@ 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;
|
||||
@@ -346,9 +348,6 @@ 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;
|
||||
|
||||
@@ -359,8 +358,6 @@ 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;
|
||||
@@ -384,6 +381,7 @@ private:
|
||||
bool mRec709OutputColorspace = false;
|
||||
|
||||
PushConstantBundle* mCurrentPushConstants = nullptr;
|
||||
PipelineLayout::SetLayout mCurrentSetLayout;
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "OpenGLProgram.h"
|
||||
|
||||
#include "GLUtils.h"
|
||||
#include "GLTexture.h"
|
||||
#include "OpenGLDriver.h"
|
||||
#include "ShaderCompilerService.h"
|
||||
|
||||
@@ -24,6 +25,7 @@
|
||||
#include <backend/Program.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
@@ -32,9 +34,10 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <new>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <new>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -46,9 +49,8 @@ using namespace utils;
|
||||
using namespace backend;
|
||||
|
||||
struct OpenGLProgram::LazyInitializationData {
|
||||
Program::UniformBlockInfo uniformBlockInfo;
|
||||
Program::SamplerGroupInfo samplerGroupInfo;
|
||||
std::array<Program::UniformInfo, Program::UNIFORM_BINDING_COUNT> bindingUniformInfo;
|
||||
Program::DescriptorSetInfo descriptorBindings;
|
||||
Program::BindingUniformsInfo bindingUniformInfo;
|
||||
utils::FixedCapacityVector<Program::PushConstant> vertexPushConstants;
|
||||
utils::FixedCapacityVector<Program::PushConstant> fragmentPushConstants;
|
||||
};
|
||||
@@ -57,16 +59,14 @@ struct OpenGLProgram::LazyInitializationData {
|
||||
OpenGLProgram::OpenGLProgram() noexcept = default;
|
||||
|
||||
OpenGLProgram::OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept
|
||||
: HwProgram(std::move(program.getName())) {
|
||||
: HwProgram(std::move(program.getName())), mRec709Location(-1) {
|
||||
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,36 +124,86 @@ 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 (!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);
|
||||
}
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
}
|
||||
} else
|
||||
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;
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if (context.isES2()) {
|
||||
// ES2 initialization of (fake) UBOs
|
||||
UniformsRecord* const uniformsRecords = new(std::nothrow) UniformsRecord[Program::UNIFORM_BINDING_COUNT];
|
||||
UTILS_NOUNROLL
|
||||
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 (auto&& [index, name, uniforms] : lazyInitializationData.bindingUniformInfo) {
|
||||
uniformsRecords[index].locations.reserve(uniforms.size());
|
||||
uniformsRecords[index].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[binding].locations[j] = loc;
|
||||
if (UTILS_UNLIKELY(binding == 0)) {
|
||||
uniformsRecords[index].locations[j] = loc;
|
||||
if (UTILS_UNLIKELY(index == 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.
|
||||
@@ -165,51 +215,11 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
|
||||
}
|
||||
}
|
||||
}
|
||||
uniformsRecords[binding].uniforms = std::move(uniforms);
|
||||
uniformsRecords[index].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;
|
||||
|
||||
@@ -226,41 +236,8 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
void OpenGLProgram::updateUniforms(
|
||||
uint32_t index, GLuint id, void const* buffer, uint16_t age) const noexcept {
|
||||
assert_invariant(mUniformsRecords);
|
||||
assert_invariant(buffer);
|
||||
|
||||
|
||||
@@ -19,17 +19,20 @@
|
||||
|
||||
#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>
|
||||
@@ -69,31 +72,24 @@ 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;
|
||||
}
|
||||
|
||||
// For ES2 only
|
||||
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) noexcept;
|
||||
void setRec709ColorSpace(bool rec709) const noexcept;
|
||||
GLuint getBufferBinding(descriptor_set_t set, descriptor_binding_t binding) const noexcept {
|
||||
return mBindingMap.get(set, binding);
|
||||
}
|
||||
|
||||
struct {
|
||||
GLuint program = 0;
|
||||
} gl; // 4 bytes
|
||||
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 setRec709ColorSpace(bool rec709) const noexcept;
|
||||
|
||||
PushConstantBundle getPushConstants() {
|
||||
auto fragBegin = mPushConstants.begin() + mPushConstantFragmentStageOffset;
|
||||
@@ -112,22 +108,15 @@ private:
|
||||
void initializeProgramState(OpenGLContext& context, GLuint program,
|
||||
LazyInitializationData& lazyInitializationData) noexcept;
|
||||
|
||||
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
|
||||
BindingMap mBindingMap; // 8 bytes + out-of-line 256 bytes
|
||||
|
||||
ShaderCompilerService::program_token_t mToken{}; // 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
|
||||
// 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
|
||||
|
||||
// only needed for ES2
|
||||
GLint mRec709Location = -1; // 4 bytes
|
||||
|
||||
using LocationInfo = utils::FixedCapacityVector<GLint>;
|
||||
struct UniformsRecord {
|
||||
Program::UniformInfo uniforms;
|
||||
@@ -135,15 +124,20 @@ private:
|
||||
mutable GLuint id = 0;
|
||||
mutable uint16_t age = std::numeric_limits<uint16_t>::max();
|
||||
};
|
||||
UniformsRecord const* mUniformsRecords = nullptr; // 8 bytes
|
||||
UniformsRecord const* mUniformsRecords = nullptr;
|
||||
GLint mRec709Location : 24; // 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
|
||||
// Push constant array offset for fragment stage constants.
|
||||
GLint mPushConstantFragmentStageOffset : 8; // 1 byte
|
||||
|
||||
public:
|
||||
struct {
|
||||
GLuint program = 0;
|
||||
} gl; // 4 bytes
|
||||
};
|
||||
|
||||
// if OpenGLProgram is larger tha 64 bytes, it'll fall in a larger Handle bucket.
|
||||
static_assert(sizeof(OpenGLProgram) <= 64); // currently 64 bytes
|
||||
// if OpenGLProgram is larger than 96 bytes, it'll fall in a larger Handle bucket.
|
||||
static_assert(sizeof(OpenGLProgram) <= 96); // currently 96 bytes
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "VulkanBlitter.h"
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanContext.h"
|
||||
#include "VulkanFboCache.h"
|
||||
#include "VulkanHandles.h"
|
||||
@@ -33,9 +34,10 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, VkFilter filter,
|
||||
inline void blitFast(VulkanCommandBuffer* commands, 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()
|
||||
@@ -49,8 +51,8 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
VulkanLayout oldSrcLayout = src.getLayout();
|
||||
VulkanLayout oldDstLayout = dst.getLayout();
|
||||
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, VulkanLayout::TRANSFER_DST);
|
||||
src.texture->transitionLayout(commands, srcRange, VulkanLayout::TRANSFER_SRC);
|
||||
dst.texture->transitionLayout(commands, dstRange, VulkanLayout::TRANSFER_DST);
|
||||
|
||||
const VkImageBlit blitRegions[1] = {{
|
||||
.srcSubresource = { aspect, src.level, src.layer, 1 },
|
||||
@@ -58,7 +60,7 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
.dstSubresource = { aspect, dst.level, dst.layer, 1 },
|
||||
.dstOffsets = { dstRect[0], dstRect[1] },
|
||||
}};
|
||||
vkCmdBlitImage(cmdbuffer,
|
||||
vkCmdBlitImage(cmdbuf,
|
||||
src.getImage(), imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
dst.getImage(), imgutil::getVkLayout(VulkanLayout::TRANSFER_DST),
|
||||
1, blitRegions, filter);
|
||||
@@ -69,12 +71,13 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
if (oldDstLayout == VulkanLayout::UNDEFINED) {
|
||||
oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage);
|
||||
}
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, oldSrcLayout);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, oldDstLayout);
|
||||
src.texture->transitionLayout(commands, srcRange, oldSrcLayout);
|
||||
dst.texture->transitionLayout(commands, dstRange, oldDstLayout);
|
||||
}
|
||||
|
||||
inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
inline void resolveFast(VulkanCommandBuffer* commands, 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()
|
||||
@@ -88,8 +91,8 @@ inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspe
|
||||
VulkanLayout oldSrcLayout = src.getLayout();
|
||||
VulkanLayout oldDstLayout = dst.getLayout();
|
||||
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, VulkanLayout::TRANSFER_DST);
|
||||
src.texture->transitionLayout(commands, srcRange, VulkanLayout::TRANSFER_SRC);
|
||||
dst.texture->transitionLayout(commands, dstRange, VulkanLayout::TRANSFER_DST);
|
||||
|
||||
assert_invariant(
|
||||
aspect != VK_IMAGE_ASPECT_DEPTH_BIT && "Resolve with depth is not yet supported.");
|
||||
@@ -111,8 +114,8 @@ inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspe
|
||||
if (oldDstLayout == VulkanLayout::UNDEFINED) {
|
||||
oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage);
|
||||
}
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, oldSrcLayout);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, oldDstLayout);
|
||||
src.texture->transitionLayout(commands, srcRange, oldSrcLayout);
|
||||
dst.texture->transitionLayout(commands, dstRange, oldDstLayout);
|
||||
}
|
||||
|
||||
struct BlitterUniforms {
|
||||
@@ -149,10 +152,9 @@ 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(cmdbuffer, aspect, src, dst);
|
||||
resolveFast(&commands, aspect, src, dst);
|
||||
}
|
||||
|
||||
void VulkanBlitter::blit(VkFilter filter,
|
||||
@@ -175,10 +177,9 @@ 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(cmdbuffer, aspect, filter, src, dst, srcRectPair, dstRectPair);
|
||||
blitFast(&commands, aspect, filter, src, dst, srcRectPair, dstRectPair);
|
||||
}
|
||||
|
||||
void VulkanBlitter::terminate() noexcept {
|
||||
|
||||
@@ -297,11 +297,11 @@ bool VulkanCommands::flush() {
|
||||
#endif
|
||||
|
||||
auto& cmdfence = currentbuf->fence;
|
||||
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();
|
||||
UTILS_UNUSED_IN_RELEASE VkResult result = VK_SUCCESS;
|
||||
{
|
||||
auto scope = cmdfence->setValue(VK_NOT_READY);
|
||||
result = vkQueueSubmit(mQueue, 1, &submitInfo, cmdfence->getFence());
|
||||
}
|
||||
|
||||
#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->fence;
|
||||
fences[count++] = wrapper->fence->getFence();
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
@@ -361,12 +361,13 @@ void VulkanCommands::gc() {
|
||||
if (wrapper->buffer() == VK_NULL_HANDLE) {
|
||||
continue;
|
||||
}
|
||||
VkResult const result = vkGetFenceStatus(mDevice, wrapper->fence->fence);
|
||||
auto const vkfence = wrapper->fence->getFence();
|
||||
VkResult const result = vkGetFenceStatus(mDevice, vkfence);
|
||||
if (result != VK_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
fences[count++] = wrapper->fence->fence;
|
||||
wrapper->fence->status.store(VK_SUCCESS);
|
||||
fences[count++] = vkfence;
|
||||
wrapper->fence->setValue(VK_SUCCESS);
|
||||
wrapper->reset();
|
||||
mAvailableBufferCount++;
|
||||
}
|
||||
@@ -383,9 +384,9 @@ void VulkanCommands::updateFences() {
|
||||
if (wrapper->buffer() != VK_NULL_HANDLE) {
|
||||
VulkanCmdFence* fence = wrapper->fence.get();
|
||||
if (fence) {
|
||||
VkResult status = vkGetFenceStatus(mDevice, fence->fence);
|
||||
VkResult status = vkGetFenceStatus(mDevice, fence->getFence());
|
||||
// This is either VK_SUCCESS, VK_NOT_READY, or VK_ERROR_DEVICE_LOST.
|
||||
fence->status.store(status, std::memory_order_relaxed);
|
||||
fence->setValue(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +61,40 @@ 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;
|
||||
|
||||
@@ -90,9 +90,9 @@ VmaAllocator createAllocator(VkInstance instance, VkPhysicalDevice physicalDevic
|
||||
|
||||
VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanStagePool& stagePool) {
|
||||
VulkanResourceAllocator* handleAllocator, VulkanStagePool& stagePool) {
|
||||
VulkanTexture* emptyTexture = new VulkanTexture(device, physicalDevice, context, allocator,
|
||||
commands, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1,
|
||||
commands, handleAllocator, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1,
|
||||
TextureUsage::DEFAULT | TextureUsage::COLOR_ATTACHMENT | TextureUsage::SUBPASS_INPUT,
|
||||
stagePool, true /* heap allocated */);
|
||||
uint32_t black = 0;
|
||||
@@ -149,6 +149,7 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFla
|
||||
}
|
||||
#endif // FVK_EANBLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
|
||||
|
||||
}// anonymous namespace
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
@@ -256,15 +257,11 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
mTimestamps = std::make_unique<VulkanTimestamps>(mPlatform->getDevice());
|
||||
|
||||
mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(),
|
||||
mContext, mAllocator, &mCommands, mStagePool);
|
||||
mContext, mAllocator, &mCommands, &mResourceAllocator, 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;
|
||||
@@ -387,7 +384,6 @@ void VulkanDriver::collectGarbage() {
|
||||
mStagePool.gc();
|
||||
mFramebufferCache.gc();
|
||||
mPipelineCache.gc();
|
||||
mDescriptorSetManager.gc();
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
|
||||
mResourceAllocator.print();
|
||||
@@ -422,6 +418,36 @@ 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");
|
||||
@@ -440,12 +466,6 @@ 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) {
|
||||
@@ -528,23 +548,56 @@ 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, target, levels,
|
||||
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator,
|
||||
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);
|
||||
//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);
|
||||
auto vktexture = mResourceAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
|
||||
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, target, levels,
|
||||
format, samples, w, h, depth, usage, mStagePool, false /*heap allocated */, swizzleMap);
|
||||
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator,
|
||||
src, baseLevel, levelCount);
|
||||
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,
|
||||
@@ -571,7 +624,6 @@ void VulkanDriver::destroyProgram(Handle<HwProgram> ph) {
|
||||
return;
|
||||
}
|
||||
auto vkprogram = mResourceAllocator.handle_cast<VulkanProgram*>(ph);
|
||||
mDescriptorSetManager.clearProgram(vkprogram);
|
||||
mResourceManager.release(vkprogram);
|
||||
}
|
||||
|
||||
@@ -641,9 +693,10 @@ 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, width, height,
|
||||
samples, colorTargets, depthStencil, mStagePool, layerCount);
|
||||
auto renderTarget = mResourceAllocator.construct<VulkanRenderTarget>(rth,
|
||||
mPlatform->getDevice(), mPlatform->getPhysicalDevice(), mContext, mAllocator,
|
||||
&mCommands, &mResourceAllocator, width, height, samples, colorTargets, depthStencil,
|
||||
mStagePool, layerCount);
|
||||
mResourceManager.acquire(renderTarget);
|
||||
}
|
||||
|
||||
@@ -671,7 +724,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, mStagePool, nativeWindow, flags);
|
||||
mAllocator, &mCommands, &mResourceAllocator, mStagePool, nativeWindow, flags);
|
||||
mResourceManager.acquire(swapChain);
|
||||
}
|
||||
|
||||
@@ -684,7 +737,8 @@ 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, mStagePool, nullptr, flags, VkExtent2D{width, height});
|
||||
mAllocator, &mCommands, &mResourceAllocator, mStagePool,
|
||||
nullptr, flags, VkExtent2D{width, height});
|
||||
mResourceManager.acquire(swapChain);
|
||||
}
|
||||
|
||||
@@ -692,6 +746,22 @@ 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>();
|
||||
}
|
||||
@@ -712,7 +782,19 @@ Handle<HwTexture> VulkanDriver::createTextureS() noexcept {
|
||||
return mResourceAllocator.allocHandle<VulkanTexture>();
|
||||
}
|
||||
|
||||
Handle<HwTexture> VulkanDriver::createTextureSwizzledS() noexcept {
|
||||
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 {
|
||||
return mResourceAllocator.allocHandle<VulkanTexture>();
|
||||
}
|
||||
|
||||
@@ -720,10 +802,6 @@ 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>();
|
||||
}
|
||||
@@ -762,21 +840,12 @@ Handle<HwTimerQuery> VulkanDriver::createTimerQueryS() noexcept {
|
||||
return tqh;
|
||||
}
|
||||
|
||||
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);
|
||||
Handle<HwDescriptorSetLayout> VulkanDriver::createDescriptorSetLayoutS() noexcept {
|
||||
return mResourceAllocator.allocHandle<VulkanDescriptorSetLayout>();
|
||||
}
|
||||
|
||||
Handle<HwDescriptorSet> VulkanDriver::createDescriptorSetS() noexcept {
|
||||
return mResourceAllocator.allocHandle<VulkanDescriptorSet>();
|
||||
}
|
||||
|
||||
void VulkanDriver::destroySwapChain(Handle<HwSwapChain> sch) {
|
||||
@@ -801,6 +870,17 @@ 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 {};
|
||||
}
|
||||
@@ -838,8 +918,7 @@ 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.
|
||||
std::unique_lock<utils::Mutex> lock(cmdfence->mutex);
|
||||
if (cmdfence->status.load() == VK_SUCCESS) {
|
||||
if (cmdfence->getStatus() == VK_SUCCESS) {
|
||||
return FenceStatus::CONDITION_SATISFIED;
|
||||
}
|
||||
|
||||
@@ -890,7 +969,9 @@ bool VulkanDriver::isRenderTargetFormatSupported(TextureFormat format) {
|
||||
}
|
||||
|
||||
bool VulkanDriver::isFrameBufferFetchSupported() {
|
||||
return true;
|
||||
// TODO: we must fix this before landing descriptor set change. Otherwise, the scuba tests will fail.
|
||||
//return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isFrameBufferFetchMultiSampleSupported() {
|
||||
@@ -1078,10 +1159,6 @@ 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) {
|
||||
@@ -1167,23 +1244,6 @@ 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,
|
||||
@@ -1198,7 +1258,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
FVK_SYSTRACE_START("beginRenderPass");
|
||||
|
||||
VulkanRenderTarget* const rt = mResourceAllocator.handle_cast<VulkanRenderTarget*>(rth);
|
||||
const VkExtent2D extent = rt->getExtent();
|
||||
VkExtent2D const 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
|
||||
@@ -1228,36 +1288,6 @@ 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;
|
||||
@@ -1270,11 +1300,9 @@ 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.
|
||||
if (currentDepthLayout == VulkanLayout::DEPTH_SAMPLER) {
|
||||
depth.texture->transitionLayout(cmdbuffer, depth.getSubresourceRange(),
|
||||
VulkanLayout::DEPTH_ATTACHMENT);
|
||||
currentDepthLayout = VulkanLayout::DEPTH_ATTACHMENT;
|
||||
}
|
||||
depth.texture->transitionLayout(&commands, depth.getSubresourceRange(),
|
||||
VulkanLayout::DEPTH_ATTACHMENT);
|
||||
currentDepthLayout = VulkanLayout::DEPTH_ATTACHMENT;
|
||||
}
|
||||
|
||||
uint8_t const renderTargetLayerCount = rt->getLayerCount();
|
||||
@@ -1292,7 +1320,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
.viewCount = renderTargetLayerCount,
|
||||
};
|
||||
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
const VulkanAttachment& info = rt->getColor(i);
|
||||
VulkanAttachment const& info = rt->getColor(i);
|
||||
if (info.texture) {
|
||||
assert_invariant(info.layerCount == renderTargetLayerCount);
|
||||
rpkey.initialColorLayoutMask |= 1 << i;
|
||||
@@ -1300,11 +1328,6 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
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;
|
||||
}
|
||||
@@ -1323,28 +1346,39 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
};
|
||||
auto& renderPassAttachments = mRenderPassFboInfo.attachments;
|
||||
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
if (!rt->getColor(i).texture) {
|
||||
VulkanAttachment& attachment = rt->getColor(i);
|
||||
if (!attachment.texture) {
|
||||
fbkey.color[i] = VK_NULL_HANDLE;
|
||||
fbkey.resolve[i] = VK_NULL_HANDLE;
|
||||
} else if (fbkey.samples == 1) {
|
||||
auto& colorAttachment = rt->getColor(i);
|
||||
renderPassAttachments.insert(colorAttachment);
|
||||
fbkey.color[i] = colorAttachment.getImageView();
|
||||
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();
|
||||
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 = colorAttachment.texture;
|
||||
VulkanTexture* texture = attachment.texture;
|
||||
if (texture->samples == 1) {
|
||||
mRenderPassFboInfo.hasColorResolve = true;
|
||||
|
||||
renderPassAttachments.insert(colorAttachment);
|
||||
fbkey.resolve[i] = colorAttachment.getImageView();
|
||||
auto const& range = attachment.getSubresourceRange();
|
||||
attachment.texture->transitionLayout(&commands,
|
||||
range, VulkanLayout::COLOR_ATTACHMENT);
|
||||
renderPassAttachments.insert(attachment);
|
||||
fbkey.resolve[i] = attachment.getImageView();
|
||||
assert_invariant(fbkey.resolve[i]);
|
||||
}
|
||||
assert_invariant(fbkey.color[i]);
|
||||
@@ -1462,57 +1496,27 @@ 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. 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.
|
||||
// pipeline barrier between framebuffer writes and shader reads.
|
||||
if (!rt->isSwapChain()) {
|
||||
for (auto const& attachment: mRenderPassFboInfo.attachments) {
|
||||
bool const isDepth = attachment.isDepth();
|
||||
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);
|
||||
for (auto& attachment: mRenderPassFboInfo.attachments) {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mRenderPassFboInfo.clear();
|
||||
mDescriptorSetManager.clearState();
|
||||
mCurrentRenderPass.renderTarget = nullptr;
|
||||
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
|
||||
FVK_SYSTRACE_END();
|
||||
@@ -1571,33 +1575,6 @@ 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");
|
||||
@@ -1606,13 +1583,13 @@ void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
value);
|
||||
}
|
||||
|
||||
void VulkanDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
void VulkanDriver::insertEventMarker(char const* string) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
mCommands.insertEventMarker(string, len);
|
||||
mCommands.insertEventMarker(string, strlen(string));
|
||||
#endif
|
||||
}
|
||||
|
||||
void VulkanDriver::pushGroupMarker(char const* string, uint32_t) {
|
||||
void VulkanDriver::pushGroupMarker(char const* string) {
|
||||
// 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);
|
||||
@@ -1798,8 +1775,8 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
*mResourceAllocator.handle_cast<VulkanVertexBufferInfo*>(pipelineState.vertexBufferInfo);
|
||||
|
||||
Handle<HwProgram> programHandle = pipelineState.program;
|
||||
RasterState rasterState = pipelineState.rasterState;
|
||||
PolygonOffset depthOffset = pipelineState.polygonOffset;
|
||||
RasterState const& rasterState = pipelineState.rasterState;
|
||||
PolygonOffset const& depthOffset = pipelineState.polygonOffset;
|
||||
|
||||
auto* program = mResourceAllocator.handle_cast<VulkanProgram*>(programHandle);
|
||||
commands->acquire(program);
|
||||
@@ -1821,7 +1798,6 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
.colorWriteMask = (VkColorComponentFlags) (rasterState.colorWrite ? 0xf : 0x0),
|
||||
.rasterizationSamples = rt->getSamples(),
|
||||
.depthClamp = rasterState.depthClamp,
|
||||
.reserved = 0,
|
||||
.colorTargetCount = rt->getColorTargetCount(mCurrentRenderPass),
|
||||
.colorBlendOp = rasterState.blendEquationRGB,
|
||||
.alphaBlendOp = rasterState.blendEquationAlpha,
|
||||
@@ -1844,59 +1820,26 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
mPipelineCache.bindPrimitiveTopology(topology);
|
||||
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, vbi.getAttributeCount());
|
||||
|
||||
// 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.
|
||||
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);
|
||||
|
||||
auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex();
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
auto const& bindingToName = program->getBindingToName();
|
||||
#endif
|
||||
constexpr uint8_t descriptorSetMaskTable[4] = {0x1, 0x3, 0x7, 0xF};
|
||||
|
||||
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);
|
||||
@@ -1939,6 +1882,14 @@ 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");
|
||||
@@ -1946,8 +1897,8 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
||||
VulkanCommandBuffer& commands = mCommands.get();
|
||||
VkCommandBuffer cmdbuffer = commands.buffer();
|
||||
|
||||
// Bind "dynamic" UBOs if they need to change.
|
||||
mDescriptorSetManager.dynamicBind(&commands, {});
|
||||
mDescriptorSetManager.commit(&commands, mBoundPipeline.pipelineLayout,
|
||||
mBoundPipeline.descriptorSetMask);
|
||||
|
||||
// Finally, make the actual draw call. TODO: support subranges
|
||||
const uint32_t firstIndex = indexOffset;
|
||||
@@ -1995,7 +1946,7 @@ void VulkanDriver::scissor(Viewport scissorBox) {
|
||||
.extent = { uint32_t(r - l), uint32_t(t - b) }
|
||||
};
|
||||
|
||||
const VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
|
||||
VulkanRenderTarget const* rt = mCurrentRenderPass.renderTarget;
|
||||
rt->transformClientRectToPlatform(&scissor);
|
||||
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
|
||||
}
|
||||
@@ -2038,6 +1989,10 @@ 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,12 +159,11 @@ 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;
|
||||
|
||||
@@ -16,13 +16,11 @@
|
||||
|
||||
#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>
|
||||
|
||||
@@ -54,54 +52,13 @@ void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) {
|
||||
}
|
||||
|
||||
template<typename Bitmask>
|
||||
static constexpr Bitmask fromStageFlags(ShaderStageFlags2 flags, uint8_t binding) {
|
||||
Bitmask ret = 0;
|
||||
if (flags & ShaderStageFlags2::VERTEX) {
|
||||
ret |= (getVertexStage<Bitmask>() << binding);
|
||||
inline void fromStageFlags(backend::ShaderStageFlags stage, descriptor_binding_t binding,
|
||||
Bitmask& mask) {
|
||||
if ((bool) (stage & ShaderStageFlags::VERTEX)) {
|
||||
mask.set(binding + getVertexStageShift<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);
|
||||
if ((bool) (stage & ShaderStageFlags::FRAGMENT)) {
|
||||
mask.set(binding + getFragmentStageShift<Bitmask>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,22 +80,113 @@ 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,
|
||||
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)) {}
|
||||
DescriptorSetLayout const& layout)
|
||||
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT), mDevice(device),
|
||||
vklayout(createDescriptorSetLayout(device, getLayoutCreateInfo(layout))),
|
||||
bitmask(fromBackendLayout(layout)),
|
||||
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 }) {
|
||||
@@ -190,24 +238,11 @@ 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);
|
||||
@@ -224,12 +259,6 @@ 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,
|
||||
@@ -257,40 +286,6 @@ 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;
|
||||
@@ -321,6 +316,7 @@ 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)
|
||||
@@ -354,6 +350,7 @@ 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 */);
|
||||
@@ -383,7 +380,8 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
|
||||
VulkanTexture* msTexture = depthTexture->getSidecar();
|
||||
if (UTILS_UNLIKELY(!msTexture)) {
|
||||
msTexture = new VulkanTexture(device, physicalDevice, context, allocator,
|
||||
commands, depthTexture->target, msLevel, depthTexture->format, samples,
|
||||
commands, handleAllocator,
|
||||
depthTexture->target, msLevel, depthTexture->format, samples,
|
||||
depthTexture->width, depthTexture->height, depthTexture->depth, depthTexture->usage,
|
||||
stagePool, true /* heap allocated */);
|
||||
depthTexture->setSidecar(msTexture);
|
||||
@@ -535,15 +533,7 @@ 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.
|
||||
if (!mFence) {
|
||||
return false;
|
||||
}
|
||||
VkResult status = mFence->status.load(std::memory_order_relaxed);
|
||||
if (status != VK_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return mFence && mFence->getStatus() == VK_SUCCESS;
|
||||
}
|
||||
|
||||
VulkanTimerQuery::~VulkanTimerQuery() = default;
|
||||
@@ -559,35 +549,4 @@ 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,39 +29,52 @@
|
||||
#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 {
|
||||
|
||||
using namespace descset;
|
||||
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
|
||||
|
||||
class VulkanTimestamps;
|
||||
struct VulkanBufferObject;
|
||||
|
||||
struct VulkanDescriptorSetLayout : public VulkanResource {
|
||||
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = 3;
|
||||
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>;
|
||||
|
||||
// The bitmask representation of a set layout.
|
||||
struct Bitmask {
|
||||
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
|
||||
// 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
|
||||
|
||||
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.
|
||||
@@ -71,6 +84,10 @@ struct VulkanDescriptorSetLayout : public VulkanResource {
|
||||
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;
|
||||
@@ -78,10 +95,10 @@ struct VulkanDescriptorSetLayout : public VulkanResource {
|
||||
|
||||
static inline Count fromLayoutBitmask(Bitmask const& mask) {
|
||||
return {
|
||||
.ubo = countBits(collapseStages(mask.ubo)),
|
||||
.dynamicUbo = countBits(collapseStages(mask.dynamicUbo)),
|
||||
.sampler = countBits(collapseStages(mask.sampler)),
|
||||
.inputAttachment = countBits(collapseStages(mask.inputAttachment)),
|
||||
.ubo = collapsedCount(mask.ubo),
|
||||
.dynamicUbo = collapsedCount(mask.dynamicUbo),
|
||||
.sampler = collapsedCount(mask.sampler),
|
||||
.inputAttachment = collapsedCount(mask.inputAttachment),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,70 +114,27 @@ struct VulkanDescriptorSetLayout : public VulkanResource {
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(Bitmask) % 8 == 0);
|
||||
|
||||
explicit VulkanDescriptorSetLayout(VkDevice device, VkDescriptorSetLayoutCreateInfo const& info,
|
||||
Bitmask const& bitmask);
|
||||
VulkanDescriptorSetLayout(VkDevice device, DescriptorSetLayout const& layout);
|
||||
|
||||
~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),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
using VulkanDescriptorSetLayoutList = std::array<Handle<VulkanDescriptorSetLayout>,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||
struct VulkanDescriptorSet : public VulkanResource {
|
||||
struct VulkanDescriptorSet : public VulkanResource, HwDescriptorSet {
|
||||
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() {
|
||||
@@ -169,17 +143,25 @@ 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>;
|
||||
|
||||
@@ -224,16 +206,6 @@ 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();
|
||||
}
|
||||
@@ -273,10 +245,6 @@ 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
|
||||
@@ -302,7 +270,9 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
|
||||
// Creates an offscreen render target.
|
||||
VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VulkanContext const& context, VmaAllocator allocator,
|
||||
VulkanCommands* commands, uint32_t width, uint32_t height,
|
||||
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);
|
||||
|
||||
@@ -484,6 +454,7 @@ private:
|
||||
utils::Mutex mFenceMutex;
|
||||
};
|
||||
|
||||
|
||||
inline constexpr VkBufferUsageFlagBits getBufferObjectUsage(
|
||||
BufferObjectBinding bindingType) noexcept {
|
||||
switch(bindingType) {
|
||||
|
||||
@@ -131,14 +131,18 @@ getVkTransition(const VulkanLayoutTransition& transition) {
|
||||
|
||||
}// anonymous namespace
|
||||
|
||||
void transitionLayout(VkCommandBuffer cmdbuffer,
|
||||
bool transitionLayout(VkCommandBuffer cmdbuffer,
|
||||
VulkanLayoutTransition transition) {
|
||||
if (transition.oldLayout == transition.newLayout) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
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,
|
||||
@@ -152,6 +156,7 @@ void transitionLayout(VkCommandBuffer cmdbuffer,
|
||||
.subresourceRange = transition.subresources,
|
||||
};
|
||||
vkCmdPipelineBarrier(cmdbuffer, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
return true;
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -135,7 +135,9 @@ constexpr inline VkImageLayout getVkLayout(VulkanLayout layout) {
|
||||
}
|
||||
}
|
||||
|
||||
void transitionLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition);
|
||||
// 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);
|
||||
|
||||
} // 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 = 1;
|
||||
colorBlendState.attachmentCount = mPipelineRequirements.rasterState.colorTargetCount;
|
||||
colorBlendState.pAttachments = colorBlendAttachments;
|
||||
|
||||
// If we reach this point, we need to create and stash a brand new pipeline object.
|
||||
@@ -210,8 +210,8 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
pipelineCreateInfo.pDynamicState = &dynamicState;
|
||||
|
||||
// Filament assumes consistent blend state across all color attachments.
|
||||
colorBlendState.attachmentCount = mPipelineRequirements.rasterState.colorTargetCount;
|
||||
for (auto& target : colorBlendAttachments) {
|
||||
for (uint8_t i = 0; i < colorBlendState.attachmentCount; ++i) {
|
||||
auto& target = colorBlendAttachments[i];
|
||||
target.blendEnable = mPipelineRequirements.rasterState.blendEnable;
|
||||
target.srcColorBlendFactor = mPipelineRequirements.rasterState.srcColorBlendFactor;
|
||||
target.dstColorBlendFactor = mPipelineRequirements.rasterState.dstColorBlendFactor;
|
||||
|
||||
@@ -91,8 +91,7 @@ public:
|
||||
VkBlendFactor dstAlphaBlendFactor : 5;
|
||||
VkColorComponentFlags colorWriteMask : 4;
|
||||
uint8_t rasterizationSamples : 4;// offset = 4 bytes
|
||||
uint8_t depthClamp : 1;
|
||||
uint8_t reserved : 3;
|
||||
uint8_t depthClamp : 4;
|
||||
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,6 +105,10 @@ 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,12 +26,14 @@ using namespace utils;
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context,
|
||||
VmaAllocator allocator, VulkanCommands* commands, VulkanStagePool& stagePool,
|
||||
VmaAllocator allocator, VulkanCommands* commands, VulkanResourceAllocator* handleAllocator,
|
||||
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),
|
||||
@@ -62,12 +64,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, color,
|
||||
bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height,
|
||||
mColors.push_back(std::make_unique<VulkanTexture>(device, mAllocator, mCommands, mHandleAllocator,
|
||||
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, bundle.depth,
|
||||
bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height,
|
||||
mDepth = std::make_unique<VulkanTexture>(device, mAllocator, mCommands, mHandleAllocator,
|
||||
bundle.depth, bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height,
|
||||
TextureUsage::DEPTH_ATTACHMENT, mStagePool, true /* heap allocated */);
|
||||
|
||||
mExtent = bundle.extent;
|
||||
@@ -75,7 +77,7 @@ void VulkanSwapChain::update() {
|
||||
|
||||
void VulkanSwapChain::present() {
|
||||
if (!mHeadless && mTransitionSwapChainImageLayoutForPresent) {
|
||||
VkCommandBuffer const cmdbuf = mCommands->get().buffer();
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkImageSubresourceRange const subresources{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
@@ -83,7 +85,7 @@ void VulkanSwapChain::present() {
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
};
|
||||
mColors[mCurrentSwapIndex]->transitionLayout(cmdbuf, subresources, VulkanLayout::PRESENT);
|
||||
mColors[mCurrentSwapIndex]->transitionLayout(&commands, subresources, VulkanLayout::PRESENT);
|
||||
}
|
||||
|
||||
mCommands->flush();
|
||||
|
||||
@@ -36,11 +36,13 @@ 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, VulkanStagePool& stagePool,
|
||||
VulkanCommands* commands, VulkanResourceAllocator* handleAllocator,
|
||||
VulkanStagePool& stagePool,
|
||||
void* nativeWindow, uint64_t flags, VkExtent2D extent = {0, 0});
|
||||
|
||||
~VulkanSwapChain();
|
||||
@@ -80,6 +82,7 @@ private:
|
||||
VulkanPlatform* mPlatform;
|
||||
VulkanCommands* mCommands;
|
||||
VmaAllocator mAllocator;
|
||||
VulkanResourceAllocator* const mHandleAllocator;
|
||||
VulkanStagePool& mStagePool;
|
||||
bool const mHeadless;
|
||||
bool const mFlushAndWaitOnResize;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanResourceAllocator.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
@@ -28,50 +29,138 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
|
||||
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,
|
||||
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),
|
||||
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) {}
|
||||
: 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;
|
||||
}
|
||||
|
||||
VulkanTexture::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, VkComponentMapping swizzle)
|
||||
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)
|
||||
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage),
|
||||
VulkanResource(
|
||||
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
|
||||
mVkFormat(backend::getVkFormat(tformat)),
|
||||
mViewType(imgutil::getViewType(target)),
|
||||
mSwizzle(swizzle),
|
||||
mStagePool(stagePool),
|
||||
mDevice(device),
|
||||
mAllocator(allocator),
|
||||
mCommands(commands) {
|
||||
mAllocator(handleAllocator),
|
||||
mState(handleAllocator->initHandle<VulkanTextureState>(device, allocator, commands, stagePool,
|
||||
backend::getVkFormat(tformat), imgutil::getViewType(target), levels,
|
||||
getLayerCount(target, depth))) {
|
||||
auto* const state = getSharedState();
|
||||
|
||||
// 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 = mVkFormat,
|
||||
.format = state->mVkFormat,
|
||||
.extent = {w, h, depth},
|
||||
.mipLevels = levels,
|
||||
.arrayLayers = 1,
|
||||
@@ -97,13 +186,11 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
imageInfo.extent.depth = 1;
|
||||
}
|
||||
|
||||
// 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::BLIT_SRC)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
}
|
||||
if (any(usage & TextureUsage::BLIT_DST)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
|
||||
if (any(usage & TextureUsage::SAMPLEABLE)) {
|
||||
@@ -111,9 +198,9 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
// Validate that the format is actually sampleable.
|
||||
VkFormatProperties props;
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, mVkFormat, &props);
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, state->mVkFormat, &props);
|
||||
if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
|
||||
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << mVkFormat << " is not "
|
||||
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << state->mVkFormat << " is not "
|
||||
"sampleable with optimal tiling." << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
@@ -121,7 +208,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 | blittable;
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
if (any(usage & TextureUsage::SUBPASS_INPUT)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
||||
}
|
||||
@@ -130,10 +217,9 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
}
|
||||
if (any(usage & TextureUsage::UPLOADABLE)) {
|
||||
imageInfo.usage |= blittable;
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
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.
|
||||
@@ -147,7 +233,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(mVkFormat)
|
||||
samples = reduceSampleCount(samples, isVkDepthFormat(state->mVkFormat)
|
||||
? limits.sampledImageDepthSampleCounts
|
||||
: limits.sampledImageColorSampleCounts);
|
||||
}
|
||||
@@ -161,12 +247,12 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
this->samples = samples;
|
||||
imageInfo.samples = (VkSampleCountFlagBits) samples;
|
||||
|
||||
VkResult error = vkCreateImage(mDevice, &imageInfo, VKALLOC, &mTextureImage);
|
||||
VkResult error = vkCreateImage(state->mDevice, &imageInfo, VKALLOC, &state->mTextureImage);
|
||||
if (error || FVK_ENABLED(FVK_DEBUG_TEXTURE)) {
|
||||
FVK_LOGD << "vkCreateImage: "
|
||||
<< "image = " << mTextureImage << ", "
|
||||
<< "image = " << state->mTextureImage << ", "
|
||||
<< "result = " << error << ", "
|
||||
<< "handle = " << utils::io::hex << mTextureImage << utils::io::dec << ", "
|
||||
<< "handle = " << utils::io::hex << state->mTextureImage << utils::io::dec << ", "
|
||||
<< "extent = " << w << "x" << h << "x"<< depth << ", "
|
||||
<< "mipLevels = " << int(levels) << ", "
|
||||
<< "TextureUsage = " << static_cast<int>(usage) << ", "
|
||||
@@ -175,13 +261,13 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
<< "type = " << imageInfo.imageType << ", "
|
||||
<< "flags = " << imageInfo.flags << ", "
|
||||
<< "target = " << static_cast<int>(target) <<", "
|
||||
<< "format = " << mVkFormat << utils::io::endl;
|
||||
<< "format = " << state->mVkFormat << utils::io::endl;
|
||||
}
|
||||
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to create image.";
|
||||
|
||||
// Allocate memory for the VkImage and bind it.
|
||||
VkMemoryRequirements memReqs = {};
|
||||
vkGetImageMemoryRequirements(mDevice, mTextureImage, &memReqs);
|
||||
vkGetImageMemoryRequirements(state->mDevice, state->mTextureImage, &memReqs);
|
||||
|
||||
uint32_t memoryTypeIndex
|
||||
= context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
@@ -194,58 +280,67 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
.allocationSize = memReqs.size,
|
||||
.memoryTypeIndex = memoryTypeIndex,
|
||||
};
|
||||
error = vkAllocateMemory(mDevice, &allocInfo, nullptr, &mTextureImageMemory);
|
||||
error = vkAllocateMemory(state->mDevice, &allocInfo, nullptr, &state->mTextureImageMemory);
|
||||
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to allocate image memory.";
|
||||
error = vkBindImageMemory(mDevice, mTextureImage, mTextureImageMemory, 0);
|
||||
error = vkBindImageMemory(state->mDevice, state->mTextureImage, state->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 = mFullViewRange;
|
||||
mPrimaryViewRange = state->mFullViewRange;
|
||||
|
||||
// Go ahead and create the primary image view.
|
||||
getImageView(mPrimaryViewRange, mViewType, mSwizzle);
|
||||
getImageView(mPrimaryViewRange, state->mViewType, mSwizzle);
|
||||
|
||||
// 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));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
VulkanTexture::~VulkanTexture() {
|
||||
if (mTextureImageMemory != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(mDevice, mTextureImage, VKALLOC);
|
||||
vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC);
|
||||
}
|
||||
for (auto entry : mCachedImageViews) {
|
||||
vkDestroyImageView(mDevice, entry.second, VKALLOC);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +349,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;
|
||||
|
||||
@@ -267,7 +362,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(mVkFormat);
|
||||
const VkFormat deviceFormat = getVkFormatLinear(state->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.");
|
||||
@@ -279,14 +374,14 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
|
||||
// Otherwise, use vkCmdCopyBufferToImage.
|
||||
void* mapped = nullptr;
|
||||
VulkanStage const* stage = mStagePool.acquireStage(hostData->size);
|
||||
VulkanStage const* stage = state->mStagePool.acquireStage(hostData->size);
|
||||
assert_invariant(stage->memory);
|
||||
vmaMapMemory(mAllocator, stage->memory, &mapped);
|
||||
vmaMapMemory(state->mAllocator, stage->memory, &mapped);
|
||||
memcpy(mapped, hostData->buffer, hostData->size);
|
||||
vmaUnmapMemory(mAllocator, stage->memory);
|
||||
vmaFlushAllocation(mAllocator, stage->memory, 0, hostData->size);
|
||||
vmaUnmapMemory(state->mAllocator, stage->memory);
|
||||
vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData->size);
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VulkanCommandBuffer& commands = state->mCommands->get();
|
||||
VkCommandBuffer const cmdbuf = commands.buffer();
|
||||
commands.acquire(this);
|
||||
|
||||
@@ -332,24 +427,25 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
nextLayout = imgutil::getDefaultLayout(this->usage);
|
||||
}
|
||||
|
||||
transitionLayout(cmdbuf, transitionRange, newLayout);
|
||||
transitionLayout(&commands, transitionRange, newLayout);
|
||||
|
||||
vkCmdCopyBufferToImage(cmdbuf, stage->buffer, mTextureImage, newVkLayout, 1, ©Region);
|
||||
vkCmdCopyBufferToImage(cmdbuf, stage->buffer, state->mTextureImage, newVkLayout, 1, ©Region);
|
||||
|
||||
transitionLayout(cmdbuf, transitionRange, nextLayout);
|
||||
transitionLayout(&commands, 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
|
||||
= mStagePool.acquireImage(hostData.format, hostData.type, width, height);
|
||||
vmaMapMemory(mAllocator, stage->memory, &mapped);
|
||||
= state->mStagePool.acquireImage(hostData.format, hostData.type, width, height);
|
||||
vmaMapMemory(state->mAllocator, stage->memory, &mapped);
|
||||
memcpy(mapped, hostData.buffer, hostData.size);
|
||||
vmaUnmapMemory(mAllocator, stage->memory);
|
||||
vmaFlushAllocation(mAllocator, stage->memory, 0, hostData.size);
|
||||
vmaUnmapMemory(state->mAllocator, stage->memory);
|
||||
vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData.size);
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VulkanCommandBuffer& commands = state->mCommands->get();
|
||||
VkCommandBuffer const cmdbuf = commands.buffer();
|
||||
commands.acquire(this);
|
||||
|
||||
@@ -371,19 +467,12 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
|
||||
|
||||
VulkanLayout const newLayout = VulkanLayout::TRANSFER_DST;
|
||||
VulkanLayout const oldLayout = getLayout(layer, miplevel);
|
||||
transitionLayout(cmdbuf, range, newLayout);
|
||||
transitionLayout(&commands, range, newLayout);
|
||||
|
||||
vkCmdBlitImage(cmdbuf, stage->image, imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
mTextureImage, imgutil::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST);
|
||||
state->mTextureImage, imgutil::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST);
|
||||
|
||||
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);
|
||||
transitionLayout(&commands, range, oldLayout);
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
|
||||
@@ -402,35 +491,44 @@ VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range,
|
||||
|
||||
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
|
||||
VkComponentMapping swizzle) {
|
||||
ImageViewKey const key {range, viewType, swizzle};
|
||||
auto iter = mCachedImageViews.find(key);
|
||||
if (iter != mCachedImageViews.end()) {
|
||||
auto* const state = getSharedState();
|
||||
VulkanTextureState::ImageViewKey const key{ range, viewType, swizzle };
|
||||
auto iter = state->mCachedImageViews.find(key);
|
||||
if (iter != state->mCachedImageViews.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
VkImageViewCreateInfo viewInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.image = mTextureImage,
|
||||
.image = state->mTextureImage,
|
||||
.viewType = viewType,
|
||||
.format = mVkFormat,
|
||||
.format = state->mVkFormat,
|
||||
.components = swizzle,
|
||||
.subresourceRange = range,
|
||||
};
|
||||
VkImageView imageView;
|
||||
vkCreateImageView(mDevice, &viewInfo, VKALLOC, &imageView);
|
||||
mCachedImageViews.emplace(key, imageView);
|
||||
vkCreateImageView(state->mDevice, &viewInfo, VKALLOC, &imageView);
|
||||
state->mCachedImageViews.emplace(key, imageView);
|
||||
return imageView;
|
||||
}
|
||||
|
||||
VkImageAspectFlags VulkanTexture::getImageAspect() const {
|
||||
// Helper function in VulkanUtility
|
||||
return filament::backend::getImageAspect(mVkFormat);
|
||||
auto* const state = getSharedState();
|
||||
return filament::backend::getImageAspect(state->mVkFormat);
|
||||
}
|
||||
|
||||
void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubresourceRange& range,
|
||||
VulkanLayout newLayout) {
|
||||
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,
|
||||
VulkanLayout newLayout) {
|
||||
auto* const state = getSharedState();
|
||||
VulkanLayout const oldLayout = getLayout(range.baseArrayLayer, range.baseMipLevel);
|
||||
|
||||
uint32_t const firstLayer = range.baseArrayLayer;
|
||||
@@ -441,7 +539,7 @@ void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubres
|
||||
// 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 sophiscated range finding.
|
||||
// TODO: transition by multiple slices with more sophisticated range finding.
|
||||
bool transitionSliceBySlice = false;
|
||||
for (uint32_t i = firstLayer; i < lastLayer; ++i) {
|
||||
for (uint32_t j = firstLevel; j < lastLevel; ++j) {
|
||||
@@ -452,50 +550,82 @@ void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubres
|
||||
}
|
||||
}
|
||||
|
||||
#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
|
||||
|
||||
bool hasTransitions = false;
|
||||
if (transitionSliceBySlice) {
|
||||
for (uint32_t i = firstLayer; i < lastLayer; ++i) {
|
||||
for (uint32_t j = firstLevel; j < lastLevel; ++j) {
|
||||
VulkanLayout const layout = getLayout(i, j);
|
||||
imgutil::transitionLayout(cmdbuf, {
|
||||
.image = mTextureImage,
|
||||
.oldLayout = layout,
|
||||
.newLayout = newLayout,
|
||||
.subresources = {
|
||||
.aspectMask = range.aspectMask,
|
||||
.baseMipLevel = j,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = i,
|
||||
.layerCount = 1,
|
||||
},
|
||||
});
|
||||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imgutil::transitionLayout(cmdbuf, {
|
||||
.image = mTextureImage,
|
||||
} else if (newLayout != oldLayout) {
|
||||
hasTransitions = imgutil::transitionLayout(cmdbuf, {
|
||||
.image = state->mTextureImage,
|
||||
.oldLayout = oldLayout,
|
||||
.newLayout = newLayout,
|
||||
.subresources = range,
|
||||
});
|
||||
}
|
||||
|
||||
setLayout(range, newLayout);
|
||||
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;
|
||||
}
|
||||
|
||||
void VulkanTexture::setLayout(const VkImageSubresourceRange& range, VulkanLayout 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();
|
||||
uint32_t const firstLayer = range.baseArrayLayer;
|
||||
uint32_t const lastLayer = firstLayer + range.layerCount;
|
||||
uint32_t const firstLevel = range.baseMipLevel;
|
||||
@@ -508,28 +638,30 @@ void VulkanTexture::setLayout(const VkImageSubresourceRange& range, VulkanLayout
|
||||
for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) {
|
||||
uint32_t const first = (layer << 16) | firstLevel;
|
||||
uint32_t const last = (layer << 16) | lastLevel;
|
||||
mSubresourceLayouts.clear(first, last);
|
||||
state->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;
|
||||
mSubresourceLayouts.add(first, last, newLayout);
|
||||
state->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 (!mSubresourceLayouts.has(key)) {
|
||||
if (!state->mSubresourceLayouts.has(key)) {
|
||||
return VulkanLayout::UNDEFINED;
|
||||
}
|
||||
return mSubresourceLayouts.get(key);
|
||||
return state->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;
|
||||
@@ -542,16 +674,16 @@ void VulkanTexture::print() const {
|
||||
layer < (mPrimaryViewRange.baseArrayLayer + mPrimaryViewRange.layerCount) &&
|
||||
level >= mPrimaryViewRange.baseMipLevel &&
|
||||
level < (mPrimaryViewRange.baseMipLevel + mPrimaryViewRange.levelCount);
|
||||
FVK_LOGD << "[" << mTextureImage << "]: (" << layer << "," << level
|
||||
FVK_LOGD << "[" << state->mTextureImage << "]: (" << layer << "," << level
|
||||
<< ")=" << getLayout(layer, level)
|
||||
<< " primary=" << primary
|
||||
<< utils::io::endl;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto view: mCachedImageViews) {
|
||||
for (auto view: state->mCachedImageViews) {
|
||||
auto& range = view.first.range;
|
||||
FVK_LOGD << "[" << mTextureImage << ", imageView=" << view.second << "]=>"
|
||||
FVK_LOGD << "[" << state->mTextureImage << ", imageView=" << view.second << "]=>"
|
||||
<< " (" << range.baseArrayLayer << "," << range.baseMipLevel << ")"
|
||||
<< " count=(" << range.layerCount << "," << range.levelCount << ")"
|
||||
<< " aspect=" << range.aspectMask << " viewType=" << view.first.type
|
||||
|
||||
@@ -30,88 +30,12 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
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 = {});
|
||||
class VulkanResourceAllocator;
|
||||
|
||||
// Specialized constructor for internally created textures (e.g. from a swap chain)
|
||||
// The texture will never destroy the given VkImage, but it does manages its subresources.
|
||||
VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands, VkImage image,
|
||||
VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage,
|
||||
VulkanStagePool& stagePool, bool heapAllocated = false);
|
||||
|
||||
~VulkanTexture();
|
||||
|
||||
// Uploads data into a subregion of a 2D or 3D texture.
|
||||
void updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel);
|
||||
|
||||
// Returns the primary image view, which is used for shader sampling.
|
||||
VkImageView getPrimaryImageView() {
|
||||
return getImageView(mPrimaryViewRange, mViewType, mSwizzle);
|
||||
}
|
||||
|
||||
VkImageViewType getViewType() const { return mViewType; }
|
||||
|
||||
// Sets the min/max range of miplevels in the primary image view.
|
||||
void setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel);
|
||||
|
||||
VkImageSubresourceRange getPrimaryViewRange() const { return mPrimaryViewRange; }
|
||||
|
||||
VkImageSubresourceRange getFullViewRange() const { return mFullViewRange; }
|
||||
|
||||
VulkanLayout getPrimaryImageLayout() const {
|
||||
return getLayout(mPrimaryViewRange.baseArrayLayer, mPrimaryViewRange.baseMipLevel);
|
||||
}
|
||||
|
||||
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
|
||||
// target attachment. Unlike the primary image view, this always has type VK_IMAGE_VIEW_TYPE_2D
|
||||
// and the identity swizzle.
|
||||
VkImageView getAttachmentView(VkImageSubresourceRange range);
|
||||
|
||||
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
|
||||
// target attachment when rendering with multiview.
|
||||
VkImageView getMultiviewAttachmentView(VkImageSubresourceRange range);
|
||||
|
||||
// This is a workaround for the first few frames where we're waiting for the texture to actually
|
||||
// be uploaded. In that case, we bind the sampler to an empty texture, but the corresponding
|
||||
// imageView needs to be of the right type. Hence, we provide an option to indicate the
|
||||
// view type. Swizzle option does not matter in this case.
|
||||
VkImageView getViewForType(VkImageSubresourceRange const& range, VkImageViewType type);
|
||||
|
||||
VkFormat getVkFormat() const { return mVkFormat; }
|
||||
VkImage getVkImage() const { return mTextureImage; }
|
||||
|
||||
VulkanLayout getLayout(uint32_t layer, uint32_t level) const;
|
||||
|
||||
void setSidecar(VulkanTexture* sidecar) {
|
||||
mSidecarMSAA.reset(sidecar);
|
||||
}
|
||||
|
||||
VulkanTexture* getSidecar() const {
|
||||
return mSidecarMSAA.get();
|
||||
}
|
||||
|
||||
void transitionLayout(VkCommandBuffer commands, const VkImageSubresourceRange& range,
|
||||
VulkanLayout newLayout);
|
||||
|
||||
// Returns the preferred data plane of interest for all image views.
|
||||
// For now this always returns either DEPTH or COLOR.
|
||||
VkImageAspectFlags getImageAspect() const;
|
||||
|
||||
// For implicit transition like the end of a render pass, we need to be able to set the layout
|
||||
// manually (outside of calls to transitionLayout).
|
||||
void setLayout(const VkImageSubresourceRange& range, VulkanLayout newLayout);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
void print() const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
struct VulkanTextureState : public VulkanResource {
|
||||
VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanStagePool& stagePool,
|
||||
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount);
|
||||
|
||||
struct ImageViewKey {
|
||||
VkImageSubresourceRange range; // 4 * 5 bytes
|
||||
@@ -134,6 +58,150 @@ private:
|
||||
|
||||
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,
|
||||
@@ -142,28 +210,15 @@ private:
|
||||
void updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint32_t miplevel);
|
||||
|
||||
// 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;
|
||||
VulkanResourceAllocator* const mAllocator;
|
||||
|
||||
// Track the image layout of each subresource using a sparse range map.
|
||||
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
|
||||
|
||||
VkImageSubresourceRange mFullViewRange;
|
||||
Handle<VulkanTextureState> mState;
|
||||
|
||||
// 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;
|
||||
|
||||
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
|
||||
VulkanStagePool& mStagePool;
|
||||
VkDevice mDevice;
|
||||
VmaAllocator mAllocator;
|
||||
VulkanCommands* mCommands;
|
||||
VkComponentMapping mSwizzle {};
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -577,7 +577,7 @@ uint32_t getComponentCount(VkFormat format) {
|
||||
return {};
|
||||
}
|
||||
|
||||
VkComponentMapping getSwizzleMap(TextureSwizzle swizzle[4]) {
|
||||
VkComponentMapping getSwizzleMap(TextureSwizzle const swizzle[4]) {
|
||||
VkComponentMapping map;
|
||||
VkComponentSwizzle* dst = &map.r;
|
||||
for (int i = 0; i < 4; ++i, ++dst) {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
@@ -38,7 +39,7 @@ VkCullModeFlags getCullMode(CullingMode mode);
|
||||
VkFrontFace getFrontFace(bool inverseFrontFaces);
|
||||
PixelDataType getComponentType(VkFormat format);
|
||||
uint32_t getComponentCount(VkFormat format);
|
||||
VkComponentMapping getSwizzleMap(TextureSwizzle swizzle[4]);
|
||||
VkComponentMapping getSwizzleMap(TextureSwizzle const swizzle[4]);
|
||||
VkShaderStageFlags getShaderStageFlags(ShaderStageFlags stageFlags);
|
||||
|
||||
bool equivalent(const VkRect2D& a, const VkRect2D& b);
|
||||
@@ -405,12 +406,13 @@ constexpr VkFormat ALL_VK_FORMATS[] = {
|
||||
VK_FORMAT_R16G16_S10_5_NV,
|
||||
};
|
||||
|
||||
// An Array that will be fixed capacity, but the "size" (as in user added elements) is variable.
|
||||
// Note that this class is movable.
|
||||
// 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.
|
||||
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;
|
||||
@@ -448,6 +450,20 @@ 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);
|
||||
@@ -512,125 +528,28 @@ private:
|
||||
uint32_t mInd = 0;
|
||||
};
|
||||
|
||||
// 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;
|
||||
using UniformBufferBitmask = utils::bitset64;
|
||||
using SamplerBitmask = utils::bitset64;
|
||||
|
||||
// We only have at most one input attachment, so this bitmask exists only to make the code more
|
||||
// general.
|
||||
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));
|
||||
using InputAttachmentBitmask = utils::bitset64;
|
||||
|
||||
template<typename Bitmask>
|
||||
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;
|
||||
}
|
||||
static constexpr uint8_t getVertexStageShift() noexcept {
|
||||
// We assume the bottom half of bits are for vertex stages.
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename Bitmask>
|
||||
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;
|
||||
}
|
||||
static constexpr uint8_t getFragmentStageShift() noexcept {
|
||||
// We assume the top half of bits are for fragment stages.
|
||||
return sizeof(Bitmask) * 4;
|
||||
}
|
||||
|
||||
typedef enum ShaderStageFlags2 : uint8_t {
|
||||
NONE = 0,
|
||||
VERTEX = 0x1,
|
||||
FRAGMENT = 0x2,
|
||||
} ShaderStageFlags2;
|
||||
// We have at most 4 descriptor sets. This is to indicate which ones are active.
|
||||
using DescriptorSetMask = utils::bitset8;
|
||||
|
||||
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,12 +30,8 @@
|
||||
#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.
|
||||
@@ -45,60 +41,32 @@ class VulkanDescriptorSetManager {
|
||||
public:
|
||||
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT =
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT;
|
||||
using GetPipelineLayoutFunction = std::function<VkPipelineLayout(
|
||||
VulkanDescriptorSetLayoutList const&, VulkanProgram* program)>;
|
||||
|
||||
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
||||
|
||||
VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator);
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
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,
|
||||
void updateBuffer(VulkanDescriptorSet* set, uint8_t binding,
|
||||
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept;
|
||||
|
||||
void updateSampler(Handle<VulkanDescriptorSet> set, uint8_t binding,
|
||||
void updateSampler(VulkanDescriptorSet* set, uint8_t binding,
|
||||
VulkanTexture* texture, VkSampler sampler) noexcept;
|
||||
|
||||
void updateInputAttachment(Handle<VulkanDescriptorSet> set, VulkanAttachment attachment) noexcept;
|
||||
void updateInputAttachment(VulkanDescriptorSet* set, VulkanAttachment attachment) noexcept;
|
||||
|
||||
void clearBuffer(uint32_t bindingIndex);
|
||||
void bind(uint8_t setIndex, VulkanDescriptorSet* set, backend::DescriptorSetOffsetArray&& offsets);
|
||||
|
||||
void commit(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout,
|
||||
DescriptorSetMask const& setMask);
|
||||
|
||||
void setPlaceHolders(VkSampler sampler, VulkanTexture* texture,
|
||||
VulkanBufferObject* bufferObject) noexcept;
|
||||
|
||||
void clearState() noexcept;
|
||||
void createSet(Handle<HwDescriptorSet> handle, VulkanDescriptorSetLayout* layout);
|
||||
|
||||
// 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) {}
|
||||
void destroySet(Handle<HwDescriptorSet> handle);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
@@ -108,4 +76,3 @@ private:
|
||||
}// namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
|
||||
|
||||
|
||||
@@ -21,19 +21,20 @@
|
||||
namespace filament::backend {
|
||||
|
||||
VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
VulkanDescriptorSetLayoutList const& descriptorSetLayouts, VulkanProgram* program) {
|
||||
DescriptorSetLayoutArray const& descriptorSetLayouts, VulkanProgram* program) {
|
||||
PipelineLayoutKey key = {};
|
||||
uint8_t descSetLayoutCount = 0;
|
||||
key.descSetLayouts = descriptorSetLayouts;
|
||||
for (auto layoutHandle: descriptorSetLayouts) {
|
||||
if (layoutHandle) {
|
||||
auto layout = mAllocator->handle_cast<VulkanDescriptorSetLayout*>(layoutHandle);
|
||||
key.descSetLayouts[descSetLayoutCount++] = layout->vklayout;
|
||||
if (layoutHandle == VK_NULL_HANDLE) {
|
||||
break;
|
||||
}
|
||||
descSetLayoutCount++;
|
||||
}
|
||||
|
||||
// build the push constant layout key
|
||||
uint32_t pushConstantRangeCount = program->getPushConstantRangeCount();
|
||||
auto const& pushConstantRanges = program->getPushConstantRanges();
|
||||
uint32_t const 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) {
|
||||
@@ -52,8 +53,8 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
}
|
||||
}
|
||||
|
||||
if (PipelineLayoutMap::iterator iter = mPipelineLayouts.find(key); iter != mPipelineLayouts.end()) {
|
||||
PipelineLayoutCacheEntry& entry = iter.value();
|
||||
if (auto iter = mPipelineLayouts.find(key); iter != mPipelineLayouts.end()) {
|
||||
PipelineLayoutCacheEntry& entry = iter->second;
|
||||
entry.lastUsed = mTimestamp++;
|
||||
return entry.handle;
|
||||
}
|
||||
|
||||
@@ -22,41 +22,40 @@
|
||||
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
#include <unordered_map>
|
||||
|
||||
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;// We have one set of push constant per shader stage (fragment, vertex, etc).
|
||||
uint8_t size;
|
||||
uint8_t stage = 0;// We have one set of push constant per shader stage (fragment, vertex, etc).
|
||||
uint8_t size = 0;
|
||||
// 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 {
|
||||
using DescriptorSetLayoutArray = std::array<VkDescriptorSetLayout,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
DescriptorSetLayoutArray descSetLayouts = {}; // 8 * 3
|
||||
PushConstantKey pushConstant[Program::SHADER_TYPE_COUNT] = {}; // 2 * 3
|
||||
DescriptorSetLayoutArray descSetLayouts = {}; // 8 * 4
|
||||
PushConstantKey pushConstant[Program::SHADER_TYPE_COUNT] = {}; // 2 * 3
|
||||
uint16_t padding = 0;
|
||||
};
|
||||
static_assert(sizeof(PipelineLayoutKey) == 32);
|
||||
static_assert(sizeof(PipelineLayoutKey) == 40);
|
||||
|
||||
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(VulkanDescriptorSetLayoutList const& descriptorSetLayouts,
|
||||
VkPipelineLayout getLayout(DescriptorSetLayoutArray const& descriptorSetLayouts,
|
||||
VulkanProgram* program);
|
||||
|
||||
private:
|
||||
@@ -73,15 +72,14 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
using PipelineLayoutMap = tsl::robin_map<PipelineLayoutKey, PipelineLayoutCacheEntry,
|
||||
using PipelineLayoutMap = std::unordered_map<PipelineLayoutKey, PipelineLayoutCacheEntry,
|
||||
PipelineLayoutKeyHashFn, PipelineLayoutKeyEqual>;
|
||||
|
||||
VkDevice mDevice;
|
||||
VulkanResourceAllocator* mAllocator;
|
||||
Timestamp mTimestamp;
|
||||
PipelineLayoutMap mPipelineLayouts;
|
||||
};
|
||||
|
||||
}
|
||||
} // filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
|
||||
|
||||
@@ -112,9 +112,10 @@ void BackendTest::fullViewport(Viewport& viewport) {
|
||||
}
|
||||
|
||||
void BackendTest::renderTriangle(
|
||||
filament::backend::Handle<filament::backend::HwRenderTarget> renderTarget,
|
||||
filament::backend::Handle<filament::backend::HwSwapChain> swapChain,
|
||||
filament::backend::Handle<filament::backend::HwProgram> program) {
|
||||
PipelineLayout const& pipelineLayout,
|
||||
Handle<filament::backend::HwRenderTarget> renderTarget,
|
||||
Handle<filament::backend::HwSwapChain> swapChain,
|
||||
Handle<filament::backend::HwProgram> program) {
|
||||
RenderPassParams params = {};
|
||||
fullViewport(params);
|
||||
params.flags.clear = TargetBufferFlags::COLOR;
|
||||
@@ -123,11 +124,15 @@ void BackendTest::renderTriangle(
|
||||
params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
params.viewport.height = 512;
|
||||
params.viewport.width = 512;
|
||||
renderTriangle(renderTarget, swapChain, program, params);
|
||||
renderTriangle(pipelineLayout, renderTarget, swapChain, program, params);
|
||||
}
|
||||
|
||||
void BackendTest::renderTriangle(Handle<HwRenderTarget> renderTarget,
|
||||
Handle<HwSwapChain> swapChain, Handle<HwProgram> program, const RenderPassParams& params) {
|
||||
void BackendTest::renderTriangle(
|
||||
PipelineLayout const& pipelineLayout,
|
||||
Handle<HwRenderTarget> renderTarget,
|
||||
Handle<HwSwapChain> swapChain,
|
||||
Handle<HwProgram> program,
|
||||
const RenderPassParams& params) {
|
||||
auto& api = getDriverApi();
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
@@ -138,6 +143,7 @@ void BackendTest::renderTriangle(Handle<HwRenderTarget> renderTarget,
|
||||
|
||||
PipelineState state;
|
||||
state.program = program;
|
||||
state.pipelineLayout = pipelineLayout;
|
||||
state.rasterState.colorWrite = true;
|
||||
state.rasterState.depthWrite = false;
|
||||
state.rasterState.depthFunc = RasterState::DepthFunc::A;
|
||||
|
||||
@@ -51,10 +51,15 @@ protected:
|
||||
static void fullViewport(filament::backend::RenderPassParams& params);
|
||||
static void fullViewport(filament::backend::Viewport& viewport);
|
||||
|
||||
void renderTriangle(filament::backend::Handle<filament::backend::HwRenderTarget> renderTarget,
|
||||
void renderTriangle(
|
||||
filament::backend::PipelineLayout const& pipelineLayout,
|
||||
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::Handle<filament::backend::HwRenderTarget> renderTarget,
|
||||
|
||||
void renderTriangle(
|
||||
filament::backend::PipelineLayout const& pipelineLayout,
|
||||
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);
|
||||
|
||||
159
filament/backend/test/MetalTest.mm
Normal file
159
filament/backend/test/MetalTest.mm
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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,12 +73,13 @@ void ShaderGenerator::shutdown() {
|
||||
FinalizeProcess();
|
||||
}
|
||||
|
||||
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)) {
|
||||
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)) {
|
||||
switch (backend) {
|
||||
case Backend::OPENGL:
|
||||
mShaderLanguage = filament::backend::ShaderLanguage::ESSL3;
|
||||
@@ -95,9 +96,8 @@ ShaderGenerator::ShaderGenerator(std::string vertex, std::string fragment,
|
||||
}
|
||||
}
|
||||
|
||||
ShaderGenerator::Blob ShaderGenerator::transpileShader(
|
||||
ShaderStage stage, std::string shader, Backend backend, bool isMobile,
|
||||
const filament::SamplerInterfaceBlock* sib) noexcept {
|
||||
ShaderGenerator::Blob ShaderGenerator::transpileShader(ShaderStage stage, std::string shader,
|
||||
Backend backend, bool isMobile, const filamat::DescriptorSets& descriptorSets) noexcept {
|
||||
TProgram program;
|
||||
const EShLanguage language = stage == ShaderStage::VERTEX ? EShLangVertex : EShLangFragment;
|
||||
TShader tShader(language);
|
||||
@@ -161,9 +161,8 @@ ShaderGenerator::Blob ShaderGenerator::transpileShader(
|
||||
return { result.c_str(), result.c_str() + result.length() + 1 };
|
||||
} else if (backend == Backend::METAL) {
|
||||
const auto sm = isMobile ? ShaderModel::MOBILE : ShaderModel::DESKTOP;
|
||||
filamat::SibVector sibs = filamat::SibVector::with_capacity(1);
|
||||
if (sib) { sibs.emplace_back(0, sib); }
|
||||
filamat::GLSLPostProcessor::spirvToMsl(&spirv, &result, sm, false, sibs, nullptr);
|
||||
filamat::GLSLPostProcessor::spirvToMsl(
|
||||
&spirv, &result, stage, sm, false, descriptorSets, 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,6 +22,7 @@
|
||||
#include "private/filament/SamplerInterfaceBlock.h"
|
||||
#include "private/backend/DriverApi.h"
|
||||
#include "backend/Program.h"
|
||||
#include "../src/GLSLPostProcessor.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
@@ -40,7 +41,7 @@ public:
|
||||
* @param fragment The fragment shader, written in GLSL 450 core.
|
||||
*/
|
||||
ShaderGenerator(std::string vertex, std::string fragment, Backend backend, bool isMobile,
|
||||
const filament::SamplerInterfaceBlock* sib = nullptr) noexcept;
|
||||
filamat::DescriptorSets&& descriptorSets = {}) noexcept;
|
||||
|
||||
ShaderGenerator(const ShaderGenerator& rhs) = delete;
|
||||
ShaderGenerator& operator=(const ShaderGenerator& rhs) = delete;
|
||||
@@ -52,7 +53,7 @@ private:
|
||||
|
||||
using Blob = std::vector<char>;
|
||||
static Blob transpileShader(ShaderStage stage, std::string shader, Backend backend,
|
||||
bool isMobile, const filament::SamplerInterfaceBlock* sib = nullptr) noexcept;
|
||||
bool isMobile, const filamat::DescriptorSets& descriptorSets) noexcept;
|
||||
|
||||
Backend mBackend;
|
||||
|
||||
|
||||
@@ -53,8 +53,7 @@ test::NativeView getNativeView() {
|
||||
nativeView.width = static_cast<size_t>(drawableSize.width);
|
||||
nativeView.height = static_cast<size_t>(drawableSize.height);
|
||||
|
||||
test::runTests();
|
||||
// exit(runTests());
|
||||
exit(test::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;
|
||||
uniform Params { highp vec4 color; highp vec4 scale; } params;
|
||||
layout(binding = 0, set = 1) uniform Params { highp vec4 color; highp vec4 scale; } params;
|
||||
void main() {
|
||||
gl_Position = vec4((mesh_position.xy + 0.5) * params.scale.xy, params.scale.z, 1.0);
|
||||
#if defined(TARGET_VULKAN_ENVIRONMENT)
|
||||
@@ -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;
|
||||
uniform Params { highp vec4 color; highp vec4 scale; } params;
|
||||
layout(binding = 0, set = 1) uniform Params { highp vec4 color; highp vec4 scale; } params;
|
||||
void main() {
|
||||
fragColor = params.color;
|
||||
})";
|
||||
@@ -348,12 +348,26 @@ TEST_F(BackendTest, ColorResolve) {
|
||||
// Create a program.
|
||||
ProgramHandle program;
|
||||
{
|
||||
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
|
||||
filamat::DescriptorSets descriptors;
|
||||
descriptors[1] = { { "Params",
|
||||
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0 },
|
||||
{} } };
|
||||
ShaderGenerator shaderGen(
|
||||
triangleVs, triangleFs, sBackend, sIsMobilePlatform, std::move(descriptors));
|
||||
Program prog = shaderGen.getProgram(api);
|
||||
prog.uniformBlockBindings({{"params", 1}});
|
||||
prog.descriptorBindings(1, {{ "Params", DescriptorType::UNIFORM_BUFFER, 0 }});
|
||||
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);
|
||||
|
||||
@@ -388,6 +402,7 @@ 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;
|
||||
@@ -401,10 +416,12 @@ 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);
|
||||
@@ -428,6 +445,8 @@ 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;
|
||||
|
||||
uniform Params {
|
||||
layout(binding = 0, set = 1) 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;
|
||||
|
||||
uniform Params {
|
||||
layout(binding = 0, set = 1) uniform Params {
|
||||
highp vec4 padding[4]; // offset of 64 bytes
|
||||
|
||||
highp vec4 color;
|
||||
@@ -89,20 +89,36 @@ 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();
|
||||
getDriverApi().makeCurrent(swapChain, swapChain);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program.
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
|
||||
Program p = shaderGen.getProgram(getDriverApi());
|
||||
auto program = getDriverApi().createProgram(std::move(p));
|
||||
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));
|
||||
|
||||
auto defaultRenderTarget = getDriverApi().createDefaultRenderTarget(0);
|
||||
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);
|
||||
|
||||
// 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(getDriverApi(), largeBuffers);
|
||||
TrianglePrimitive triangle(api, largeBuffers);
|
||||
|
||||
RenderPassParams params = {};
|
||||
fullViewport(params);
|
||||
@@ -113,6 +129,7 @@ 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;
|
||||
@@ -121,11 +138,13 @@ 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 = getDriverApi().createBufferObject(sizeof(MaterialParams) + 64,
|
||||
auto ubuffer = api.createBufferObject(sizeof(MaterialParams) + 64,
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::STATIC);
|
||||
getDriverApi().bindUniformBuffer(0, ubuffer);
|
||||
|
||||
getDriverApi().startCapture(0);
|
||||
api.updateDescriptorSetBuffer(descriptorSet, 0, ubuffer, 0, sizeof(MaterialParams) + 64);
|
||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
||||
|
||||
api.startCapture(0);
|
||||
|
||||
// Upload uniforms.
|
||||
{
|
||||
@@ -139,11 +158,11 @@ TEST_F(BackendTest, VertexBufferUpdate) {
|
||||
delete sp;
|
||||
};
|
||||
BufferDescriptor bd(tmp, sizeof(MaterialParams), cb);
|
||||
getDriverApi().updateBufferObject(ubuffer, std::move(bd), 64);
|
||||
api.updateBufferObject(ubuffer, std::move(bd), 64);
|
||||
}
|
||||
|
||||
getDriverApi().makeCurrent(swapChain, swapChain);
|
||||
getDriverApi().beginFrame(0, 0, 0);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
|
||||
// Draw 10 triangles, updating the vertex buffer / index buffer each time.
|
||||
size_t triangleIndex = 0;
|
||||
@@ -171,22 +190,25 @@ TEST_F(BackendTest, VertexBufferUpdate) {
|
||||
params.flags.discardStart = TargetBufferFlags::NONE;
|
||||
}
|
||||
|
||||
getDriverApi().beginRenderPass(defaultRenderTarget, params);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
getDriverApi().endRenderPass();
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
triangleIndex++;
|
||||
}
|
||||
|
||||
getDriverApi().flush();
|
||||
getDriverApi().commit(swapChain);
|
||||
getDriverApi().endFrame(0);
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
getDriverApi().stopCapture(0);
|
||||
api.stopCapture(0);
|
||||
|
||||
getDriverApi().destroyProgram(program);
|
||||
getDriverApi().destroySwapChain(swapChain);
|
||||
getDriverApi().destroyRenderTarget(defaultRenderTarget);
|
||||
api.destroyProgram(program);
|
||||
api.destroySwapChain(swapChain);
|
||||
api.destroyDescriptorSet(descriptorSet);
|
||||
api.destroyDescriptorSetLayout(descriptorSetLayout);
|
||||
api.destroyBufferObject(ubuffer);
|
||||
api.destroyRenderTarget(defaultRenderTarget);
|
||||
}
|
||||
|
||||
executeCommands();
|
||||
@@ -195,27 +217,45 @@ 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();
|
||||
getDriverApi().makeCurrent(swapChain, swapChain);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program.
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
|
||||
Program p = shaderGen.getProgram(getDriverApi());
|
||||
p.uniformBlockBindings({{"params", 1}});
|
||||
auto program = getDriverApi().createProgram(std::move(p));
|
||||
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);
|
||||
|
||||
|
||||
// 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 = getDriverApi().createBufferObject(sizeof(MaterialParams) + 64,
|
||||
auto ubuffer = api.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, {});
|
||||
|
||||
// Create a render target.
|
||||
auto colorTexture = getDriverApi().createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
auto colorTexture = api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT);
|
||||
auto renderTarget = getDriverApi().createRenderTarget(
|
||||
auto renderTarget = api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR0, 512, 512, 1, 0, {{colorTexture}}, {}, {});
|
||||
|
||||
// Upload uniforms for the first triangle.
|
||||
@@ -230,7 +270,7 @@ TEST_F(BackendTest, BufferObjectUpdateWithOffset) {
|
||||
delete sp;
|
||||
};
|
||||
BufferDescriptor bd(tmp, sizeof(MaterialParams), cb);
|
||||
getDriverApi().updateBufferObject(ubuffer, std::move(bd), 64);
|
||||
api.updateBufferObject(ubuffer, std::move(bd), 64);
|
||||
}
|
||||
|
||||
RenderPassParams params = {};
|
||||
@@ -240,7 +280,8 @@ TEST_F(BackendTest, BufferObjectUpdateWithOffset) {
|
||||
params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
params.viewport.height = 512;
|
||||
params.viewport.width = 512;
|
||||
renderTriangle(renderTarget, swapChain, program, params);
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
|
||||
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.
|
||||
@@ -255,29 +296,32 @@ TEST_F(BackendTest, BufferObjectUpdateWithOffset) {
|
||||
delete sp;
|
||||
};
|
||||
BufferDescriptor bd((char*)tmp + offsetof(MaterialParams, color.b), sizeof(float) * 4, cb);
|
||||
getDriverApi().updateBufferObject(ubuffer, std::move(bd), 64 + offsetof(MaterialParams, color.b));
|
||||
api.updateBufferObject(ubuffer, std::move(bd), 64 + offsetof(MaterialParams, color.b));
|
||||
}
|
||||
|
||||
params.flags.clear = TargetBufferFlags::NONE;
|
||||
params.flags.discardStart = TargetBufferFlags::NONE;
|
||||
renderTriangle(renderTarget, swapChain, program, params);
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
|
||||
renderTarget, swapChain, program, params);
|
||||
|
||||
static const uint32_t expectedHash = 91322442;
|
||||
readPixelsAndAssertHash(
|
||||
"BufferObjectUpdateWithOffset", 512, 512, renderTarget, expectedHash, true);
|
||||
|
||||
getDriverApi().flush();
|
||||
getDriverApi().commit(swapChain);
|
||||
getDriverApi().endFrame(0);
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
getDriverApi().destroyProgram(program);
|
||||
getDriverApi().destroySwapChain(swapChain);
|
||||
getDriverApi().destroyBufferObject(ubuffer);
|
||||
getDriverApi().destroyRenderTarget(renderTarget);
|
||||
getDriverApi().destroyTexture(colorTexture);
|
||||
api.destroyProgram(program);
|
||||
api.destroySwapChain(swapChain);
|
||||
api.destroyDescriptorSet(descriptorSet);
|
||||
api.destroyDescriptorSetLayout(descriptorSetLayout);
|
||||
api.destroyBufferObject(ubuffer);
|
||||
api.destroyRenderTarget(renderTarget);
|
||||
api.destroyTexture(colorTexture);
|
||||
|
||||
// This ensures all driver commands have finished before exiting the test.
|
||||
getDriverApi().finish();
|
||||
api.finish();
|
||||
|
||||
executeCommands();
|
||||
|
||||
|
||||
@@ -155,8 +155,9 @@ kernel void main0(device Output_data& output_data [[buffer(0)]],
|
||||
|
||||
driver.dispatchCompute(ph, { groupCount, 1, 1 });
|
||||
|
||||
driver.unbindBuffer(BufferObjectBinding::SHADER_STORAGE, 0);
|
||||
driver.unbindBuffer(BufferObjectBinding::SHADER_STORAGE, 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);
|
||||
|
||||
float* const user = (float*)malloc(size);
|
||||
driver.readBufferSubData(output_data, 0, size, { user, size });
|
||||
|
||||
@@ -19,12 +19,17 @@
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.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>
|
||||
@@ -37,27 +42,28 @@ using namespace image;
|
||||
// Shaders
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static std::string fullscreenVs = R"(#version 450 core
|
||||
static std::string const 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 fullscreenFs = R"(#version 450 core
|
||||
static std::string const 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(location = 0, set = 1) uniform sampler2D test_tex;
|
||||
layout(binding = 0, set = 1) uniform sampler2D test_tex;
|
||||
|
||||
uniform Params {
|
||||
layout(binding = 1, set = 1) 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;
|
||||
@@ -106,12 +112,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);
|
||||
@@ -134,19 +140,37 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
// Create a program.
|
||||
ProgramHandle program;
|
||||
{
|
||||
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);
|
||||
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
|
||||
SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH, false };
|
||||
filamat::DescriptorSets descriptors;
|
||||
descriptors[1] = {
|
||||
{ "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 },
|
||||
samplerInfo },
|
||||
{ "Params", { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, 1 }, {} }
|
||||
};
|
||||
ShaderGenerator shaderGen(fullscreenVs, fullscreenFs, sBackend, sIsMobilePlatform,
|
||||
std::move(descriptors));
|
||||
Program prog = shaderGen.getProgram(api);
|
||||
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}});
|
||||
prog.descriptorBindings(1, {
|
||||
{ "test_tex", DescriptorType::SAMPLER, 0 },
|
||||
{ "Params", DescriptorType::UNIFORM_BUFFER, 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.
|
||||
@@ -154,6 +178,10 @@ 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++) {
|
||||
@@ -189,20 +217,10 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
state.rasterState.depthWrite = false;
|
||||
state.rasterState.depthFunc = RasterState::DepthFunc::A;
|
||||
state.program = program;
|
||||
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);
|
||||
state.pipelineLayout.setLayout[1] = { descriptorSetLayout };
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
api.bindSamplers(0, sgroup);
|
||||
api.bindUniformBuffer(0, ubuffer);
|
||||
|
||||
// Downsample passes
|
||||
params.flags.discardStart = TargetBufferFlags::ALL;
|
||||
@@ -211,7 +229,16 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
const uint32_t sourceLevel = targetLevel - 1;
|
||||
params.viewport.width = kTexWidth >> targetLevel;
|
||||
params.viewport.height = kTexHeight >> targetLevel;
|
||||
getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel);
|
||||
|
||||
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, {});
|
||||
|
||||
uploadUniforms(getDriverApi(), ubuffer, {
|
||||
.fbWidth = float(params.viewport.width),
|
||||
.fbHeight = float(params.viewport.height),
|
||||
@@ -220,6 +247,8 @@ 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
|
||||
@@ -230,7 +259,16 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
const uint32_t sourceLevel = targetLevel + 1;
|
||||
params.viewport.width = kTexWidth >> targetLevel;
|
||||
params.viewport.height = kTexHeight >> targetLevel;
|
||||
getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel);
|
||||
|
||||
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, {});
|
||||
|
||||
uploadUniforms(getDriverApi(), ubuffer, {
|
||||
.fbWidth = float(params.viewport.width),
|
||||
.fbHeight = float(params.viewport.height),
|
||||
@@ -239,10 +277,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
|
||||
@@ -259,10 +297,14 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
getDriver().purge();
|
||||
}
|
||||
|
||||
api.destroyDescriptorSetLayout(descriptorSetLayout);
|
||||
api.destroyProgram(program);
|
||||
api.destroySwapChain(swapChain);
|
||||
api.destroyTexture(texture);
|
||||
for (auto rt : renderTargets) api.destroyRenderTarget(rt);
|
||||
api.destroyBufferObject(ubuffer);
|
||||
for (auto rt : renderTargets) {
|
||||
api.destroyRenderTarget(rt);
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t expected = 0x70695aa1;
|
||||
|
||||
@@ -20,14 +20,18 @@
|
||||
#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;
|
||||
|
||||
@@ -297,20 +301,31 @@ TEST_F(BackendTest, UpdateImage2D) {
|
||||
auto defaultRenderTarget = api.createDefaultRenderTarget(0);
|
||||
|
||||
// Create a program.
|
||||
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();
|
||||
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
|
||||
SamplerType::SAMPLER_2D, getSamplerFormat(t.textureFormat), Precision::HIGH, false };
|
||||
filamat::DescriptorSets descriptors;
|
||||
descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 },
|
||||
samplerInfo } };
|
||||
|
||||
std::string const fragment = stringReplace("{samplerType}",
|
||||
getSamplerTypeName(t.textureFormat), fragmentTemplate);
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
|
||||
ShaderGenerator shaderGen(
|
||||
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
|
||||
|
||||
Program prog = shaderGen.getProgram(api);
|
||||
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
|
||||
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
|
||||
|
||||
ProgramHandle const program = api.createProgram(std::move(prog));
|
||||
|
||||
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
|
||||
{{
|
||||
DescriptorType::SAMPLER,
|
||||
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
|
||||
DescriptorFlags::NONE, 0
|
||||
}}});
|
||||
|
||||
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
|
||||
|
||||
// Create a Texture.
|
||||
auto usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
|
||||
Handle<HwTexture> const texture = api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
@@ -331,23 +346,22 @@ TEST_F(BackendTest, UpdateImage2D) {
|
||||
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 512, t.bufferPadding));
|
||||
}
|
||||
|
||||
SamplerGroup samplers(1);
|
||||
samplers.setSampler(0, { texture, {
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
|
||||
.filterMag = SamplerMagFilter::NEAREST,
|
||||
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST } });
|
||||
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
|
||||
|
||||
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(defaultRenderTarget, swapChain, program);
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
|
||||
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);
|
||||
@@ -374,18 +388,27 @@ TEST_F(BackendTest, UpdateImageSRGB) {
|
||||
auto defaultRenderTarget = api.createDefaultRenderTarget(0);
|
||||
|
||||
// Create a program.
|
||||
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();
|
||||
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
|
||||
SamplerType::SAMPLER_2D, getSamplerFormat(textureFormat), Precision::HIGH, false };
|
||||
filamat::DescriptorSets descriptors;
|
||||
descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 },
|
||||
samplerInfo } };
|
||||
|
||||
std::string const fragment = stringReplace("{samplerType}",
|
||||
getSamplerTypeName(textureFormat), fragmentTemplate);
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
|
||||
ShaderGenerator shaderGen(
|
||||
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
|
||||
Program prog = shaderGen.getProgram(api);
|
||||
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
|
||||
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
|
||||
ProgramHandle const program = api.createProgram(std::move(prog));
|
||||
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
|
||||
{{
|
||||
DescriptorType::SAMPLER,
|
||||
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
|
||||
DescriptorFlags::NONE, 0
|
||||
}}});
|
||||
|
||||
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
|
||||
|
||||
// Create a texture.
|
||||
Handle<HwTexture> const texture = api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
@@ -417,17 +440,15 @@ TEST_F(BackendTest, UpdateImageSRGB) {
|
||||
api.beginFrame(0, 0, 0);
|
||||
|
||||
// Update samplers.
|
||||
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.updateDescriptorSetTexture(descriptorSet, 0, texture, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
|
||||
api.bindSamplers(0, sgroup);
|
||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
||||
|
||||
renderTriangle(defaultRenderTarget, swapChain, program);
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
|
||||
defaultRenderTarget, swapChain, program);
|
||||
|
||||
static const uint32_t expectedHash = 359858623;
|
||||
readPixelsAndAssertHash("UpdateImageSRGB", 512, 512, defaultRenderTarget, expectedHash);
|
||||
@@ -436,7 +457,8 @@ TEST_F(BackendTest, UpdateImageSRGB) {
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.destroySamplerGroup(sgroup);
|
||||
api.destroyDescriptorSet(descriptorSet);
|
||||
api.destroyDescriptorSetLayout(descriptorSetLayout);
|
||||
api.destroyProgram(program);
|
||||
api.destroySwapChain(swapChain);
|
||||
api.destroyRenderTarget(defaultRenderTarget);
|
||||
@@ -462,18 +484,26 @@ TEST_F(BackendTest, UpdateImageMipLevel) {
|
||||
auto defaultRenderTarget = api.createDefaultRenderTarget(0);
|
||||
|
||||
// Create a program.
|
||||
SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder()
|
||||
.name("Test")
|
||||
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
|
||||
.add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} )
|
||||
.build();
|
||||
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
|
||||
SamplerType::SAMPLER_2D, getSamplerFormat(textureFormat), Precision::HIGH, false };
|
||||
filamat::DescriptorSets descriptors;
|
||||
descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 },
|
||||
samplerInfo } };
|
||||
std::string const fragment = stringReplace("{samplerType}",
|
||||
getSamplerTypeName(textureFormat), fragmentUpdateImageMip);
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
|
||||
ShaderGenerator shaderGen(
|
||||
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
|
||||
Program prog = shaderGen.getProgram(api);
|
||||
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
|
||||
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
|
||||
ProgramHandle const program = api.createProgram(std::move(prog));
|
||||
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
|
||||
{{
|
||||
DescriptorType::SAMPLER,
|
||||
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
|
||||
DescriptorFlags::NONE, 0
|
||||
}}});
|
||||
|
||||
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
|
||||
|
||||
// Create a texture with 3 mip levels.
|
||||
// Base level: 1024
|
||||
@@ -489,17 +519,15 @@ TEST_F(BackendTest, UpdateImageMipLevel) {
|
||||
api.beginFrame(0, 0, 0);
|
||||
|
||||
// Update samplers.
|
||||
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.updateDescriptorSetTexture(descriptorSet, 0, texture, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
|
||||
api.bindSamplers(0, sgroup);
|
||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
||||
|
||||
renderTriangle(defaultRenderTarget, swapChain, program);
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
|
||||
defaultRenderTarget, swapChain, program);
|
||||
|
||||
static const uint32_t expectedHash = 3644679986;
|
||||
readPixelsAndAssertHash("UpdateImageMipLevel", 512, 512, defaultRenderTarget, expectedHash);
|
||||
@@ -508,7 +536,8 @@ TEST_F(BackendTest, UpdateImageMipLevel) {
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.destroySamplerGroup(sgroup);
|
||||
api.destroyDescriptorSet(descriptorSet);
|
||||
api.destroyDescriptorSetLayout(descriptorSetLayout);
|
||||
api.destroyProgram(program);
|
||||
api.destroySwapChain(swapChain);
|
||||
api.destroyRenderTarget(defaultRenderTarget);
|
||||
@@ -536,18 +565,26 @@ TEST_F(BackendTest, UpdateImage3D) {
|
||||
auto defaultRenderTarget = api.createDefaultRenderTarget(0);
|
||||
|
||||
// Create a program.
|
||||
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();
|
||||
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
|
||||
SamplerType::SAMPLER_2D_ARRAY, getSamplerFormat(textureFormat), Precision::HIGH, false };
|
||||
filamat::DescriptorSets descriptors;
|
||||
descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 },
|
||||
samplerInfo } };
|
||||
std::string fragment = stringReplace("{samplerType}",
|
||||
getSamplerTypeName(samplerType), fragmentUpdateImage3DTemplate);
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
|
||||
ShaderGenerator shaderGen(
|
||||
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
|
||||
Program prog = shaderGen.getProgram(api);
|
||||
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
|
||||
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.descriptorBindings(1, {{ "test_tex", DescriptorType::SAMPLER, 0 }});
|
||||
ProgramHandle const program = api.createProgram(std::move(prog));
|
||||
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
|
||||
{{
|
||||
DescriptorType::SAMPLER,
|
||||
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
|
||||
DescriptorFlags::NONE, 0
|
||||
}}});
|
||||
|
||||
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
|
||||
|
||||
// Create a texture.
|
||||
Handle<HwTexture> texture = api.createTexture(samplerType, 1,
|
||||
@@ -573,17 +610,15 @@ TEST_F(BackendTest, UpdateImage3D) {
|
||||
api.beginFrame(0, 0, 0);
|
||||
|
||||
// Update samplers.
|
||||
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.updateDescriptorSetTexture(descriptorSet, 0, texture, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
|
||||
api.bindSamplers(0, sgroup);
|
||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
||||
|
||||
renderTriangle(defaultRenderTarget, swapChain, program);
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
|
||||
defaultRenderTarget, swapChain, program);
|
||||
|
||||
static const uint32_t expectedHash = 3644679986;
|
||||
readPixelsAndAssertHash("UpdateImage3D", 512, 512, defaultRenderTarget, expectedHash);
|
||||
@@ -592,7 +627,8 @@ TEST_F(BackendTest, UpdateImage3D) {
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.destroySamplerGroup(sgroup);
|
||||
api.destroyDescriptorSet(descriptorSet);
|
||||
api.destroyDescriptorSetLayout(descriptorSetLayout);
|
||||
api.destroyProgram(program);
|
||||
api.destroySwapChain(swapChain);
|
||||
api.destroyRenderTarget(defaultRenderTarget);
|
||||
|
||||
@@ -21,7 +21,11 @@
|
||||
#include "TrianglePrimitive.h"
|
||||
#include "BackendTestUtils.h"
|
||||
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -71,7 +75,7 @@ namespace test {
|
||||
using namespace filament;
|
||||
using namespace filament::backend;
|
||||
|
||||
TEST_F(BackendTest, SetMinMaxLevel) {
|
||||
TEST_F(BackendTest, TextureViewLod) {
|
||||
auto& api = getDriverApi();
|
||||
api.startCapture(0);
|
||||
|
||||
@@ -87,26 +91,35 @@ TEST_F(BackendTest, SetMinMaxLevel) {
|
||||
{
|
||||
ShaderGenerator shaderGen(vertex, whiteFragment, sBackend, sIsMobilePlatform);
|
||||
Program p = shaderGen.getProgram(api);
|
||||
Program::Sampler sampler{utils::CString("backend_test_sib_tex"), 0};
|
||||
p.setSamplerGroup(0, ShaderStageFlags::FRAGMENT, &sampler, 1);
|
||||
p.descriptorBindings(0, {{"backend_test_sib_tex", DescriptorType::SAMPLER, 0}});
|
||||
whiteProgram = api.createProgram(std::move(p));
|
||||
}
|
||||
|
||||
// Create a program that samples a texture.
|
||||
Handle<HwProgram> textureProgram;
|
||||
{
|
||||
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);
|
||||
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "backend_test", "sib_tex", 0,
|
||||
SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH, false };
|
||||
filamat::DescriptorSets descriptors;
|
||||
descriptors[1] = { { "backend_test_sib_tex",
|
||||
{ DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 }, samplerInfo } };
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
|
||||
Program p = shaderGen.getProgram(api);
|
||||
Program::Sampler sampler{utils::CString("backend_test_sib_tex"), 0};
|
||||
p.setSamplerGroup(0, ShaderStageFlags::FRAGMENT, &sampler, 1);
|
||||
p.descriptorBindings(0, {{"backend_test_sib_tex", DescriptorType::SAMPLER, 0}});
|
||||
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)
|
||||
@@ -150,7 +163,7 @@ TEST_F(BackendTest, SetMinMaxLevel) {
|
||||
// Level 1: 64x64 (green) <-- base
|
||||
// Level 2: 32x32 (blue) <--- white triangle rendered
|
||||
// Level 3: 16x16 (yellow) <-- max
|
||||
api.setMinMaxLevels(texture, 1, 3);
|
||||
auto texture13 = api.createTextureView(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.
|
||||
@@ -183,20 +196,17 @@ TEST_F(BackendTest, SetMinMaxLevel) {
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
api.updateDescriptorSetTexture(descriptorSet[0], 0, texture13, {
|
||||
.filterMag = SamplerMagFilter::NEAREST,
|
||||
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
|
||||
|
||||
api.bindDescriptorSet(descriptorSet[0], 0, {});
|
||||
|
||||
// Render a triangle to the screen, sampling from mip level 1.
|
||||
// Because the min level is 1, the result color should be the white triangle drawn in the
|
||||
@@ -208,7 +218,13 @@ TEST_F(BackendTest, SetMinMaxLevel) {
|
||||
|
||||
// Adjust the base mip to 2.
|
||||
// Note that this is done without another call to updateSamplerGroup.
|
||||
api.setMinMaxLevels(texture, 2, 3);
|
||||
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, {});
|
||||
|
||||
// Render a second, smaller, triangle, again sampling from mip level 1.
|
||||
// This triangle should be yellow striped.
|
||||
@@ -233,7 +249,12 @@ TEST_F(BackendTest, SetMinMaxLevel) {
|
||||
// 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,10 +19,14 @@
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <CoreVideo/CoreVideo.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -61,29 +65,39 @@ using namespace filament::backend;
|
||||
|
||||
// Rendering an external image without setting any data should not crash.
|
||||
TEST_F(BackendTest, RenderExternalImageWithoutSet) {
|
||||
TrianglePrimitive triangle(getDriverApi());
|
||||
auto& api = getDriverApi();
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
|
||||
auto swapChain = createSwapChain();
|
||||
|
||||
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);
|
||||
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));
|
||||
|
||||
// Create a program that samples a texture.
|
||||
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));
|
||||
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
|
||||
}}});
|
||||
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = getDriverApi().createDefaultRenderTarget(0);
|
||||
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
|
||||
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = api.createDefaultRenderTarget(0);
|
||||
|
||||
// Create a texture that will be backed by an external image.
|
||||
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
|
||||
const NativeView& view = getNativeView();
|
||||
backend::Handle<HwTexture> texture = getDriverApi().createTexture(
|
||||
backend::Handle<HwTexture> texture = api.createTexture(
|
||||
SamplerType::SAMPLER_EXTERNAL, // target
|
||||
1, // levels
|
||||
TextureFormat::RGBA8, // format
|
||||
@@ -102,63 +116,74 @@ 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;
|
||||
|
||||
getDriverApi().startCapture(0);
|
||||
getDriverApi().makeCurrent(swapChain, swapChain);
|
||||
getDriverApi().beginFrame(0, 0, 0);
|
||||
api.startCapture(0);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
|
||||
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);
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {});
|
||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
||||
|
||||
// Render a triangle.
|
||||
getDriverApi().beginRenderPass(defaultRenderTarget, params);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
getDriverApi().endRenderPass();
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
getDriverApi().flush();
|
||||
getDriverApi().commit(swapChain);
|
||||
getDriverApi().endFrame(0);
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
getDriverApi().stopCapture(0);
|
||||
api.stopCapture(0);
|
||||
|
||||
// Delete our resources.
|
||||
getDriverApi().destroyTexture(texture);
|
||||
getDriverApi().destroySamplerGroup(samplerGroup);
|
||||
api.destroyDescriptorSet(descriptorSet);
|
||||
api.destroyDescriptorSetLayout(descriptorSetLayout);
|
||||
api.destroyTexture(texture);
|
||||
|
||||
// Destroy frame resources.
|
||||
getDriverApi().destroyProgram(program);
|
||||
getDriverApi().destroyRenderTarget(defaultRenderTarget);
|
||||
api.destroyProgram(program);
|
||||
api.destroyRenderTarget(defaultRenderTarget);
|
||||
|
||||
api.finish();
|
||||
|
||||
executeCommands();
|
||||
}
|
||||
|
||||
TEST_F(BackendTest, RenderExternalImage) {
|
||||
TrianglePrimitive triangle(getDriverApi());
|
||||
auto& api = getDriverApi();
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
|
||||
auto swapChain = createSwapChain();
|
||||
|
||||
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);
|
||||
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));
|
||||
|
||||
// Create a program that samples a texture.
|
||||
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));
|
||||
Program p = shaderGen.getProgram(api);
|
||||
p.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
|
||||
auto program = api.createProgram(std::move(p));
|
||||
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = getDriverApi().createDefaultRenderTarget(0);
|
||||
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);
|
||||
|
||||
// require users to create two Filament textures and have two material parameters
|
||||
// add a "plane" parameter to setExternalImage
|
||||
@@ -166,15 +191,6 @@ 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];
|
||||
@@ -209,8 +225,9 @@ TEST_F(BackendTest, RenderExternalImage) {
|
||||
}
|
||||
}
|
||||
|
||||
getDriverApi().setupExternalImage(pixBuffer);
|
||||
getDriverApi().setExternalImage(texture, pixBuffer);
|
||||
api.setupExternalImage(pixBuffer);
|
||||
backend::Handle<HwTexture> texture =
|
||||
api.createTextureExternalImage(TextureFormat::RGBA8, 1024, 1024, usage, pixBuffer);
|
||||
|
||||
// We're now free to release the buffer.
|
||||
CVBufferRelease(pixBuffer);
|
||||
@@ -224,40 +241,43 @@ 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;
|
||||
|
||||
getDriverApi().startCapture(0);
|
||||
getDriverApi().makeCurrent(swapChain, swapChain);
|
||||
getDriverApi().beginFrame(0, 0, 0);
|
||||
api.startCapture(0);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
|
||||
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);
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {});
|
||||
|
||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
||||
|
||||
// Render a triangle.
|
||||
getDriverApi().beginRenderPass(defaultRenderTarget, params);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
getDriverApi().endRenderPass();
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
getDriverApi().flush();
|
||||
getDriverApi().commit(swapChain);
|
||||
getDriverApi().endFrame(0);
|
||||
readPixelsAndAssertHash("RenderExternalImage", 512, 512, defaultRenderTarget, 267229901, true);
|
||||
|
||||
getDriverApi().stopCapture(0);
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.stopCapture(0);
|
||||
|
||||
// Delete our resources.
|
||||
getDriverApi().destroyTexture(texture);
|
||||
getDriverApi().destroySamplerGroup(samplerGroup);
|
||||
api.destroyDescriptorSet(descriptorSet);
|
||||
api.destroyDescriptorSetLayout(descriptorSetLayout);
|
||||
api.destroyTexture(texture);
|
||||
|
||||
// Destroy frame resources.
|
||||
getDriverApi().destroyProgram(program);
|
||||
getDriverApi().destroyRenderTarget(defaultRenderTarget);
|
||||
api.destroyProgram(program);
|
||||
api.destroyRenderTarget(defaultRenderTarget);
|
||||
|
||||
api.finish();
|
||||
|
||||
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 `setMinMaxLevels` driver API, then the Vulkan backend will not bind those particular
|
||||
the `createTextureView` 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> {
|
||||
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
|
||||
friend struct BuilderDetails;
|
||||
public:
|
||||
Builder() noexcept;
|
||||
@@ -78,6 +78,21 @@ 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.
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/PrivateImplementation.h>
|
||||
#include <utils/CString.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -54,6 +55,23 @@ 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> {
|
||||
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
|
||||
friend struct BuilderDetails;
|
||||
public:
|
||||
Builder() noexcept;
|
||||
@@ -83,6 +83,21 @@ 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.
|
||||
|
||||
@@ -38,7 +38,7 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI {
|
||||
struct BuilderDetails;
|
||||
|
||||
public:
|
||||
class Builder : public BuilderBase<BuilderDetails> {
|
||||
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
|
||||
friend struct BuilderDetails;
|
||||
|
||||
public:
|
||||
@@ -70,6 +70,21 @@ public:
|
||||
*/
|
||||
Builder& localTransforms(math::mat4f const* UTILS_NULLABLE localTransforms) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this InstanceBuffer 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 InstanceBuffer
|
||||
* @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 InstanceBuffer object and returns a pointer to it.
|
||||
*/
|
||||
|
||||
@@ -39,7 +39,7 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI {
|
||||
struct BuilderDetails;
|
||||
|
||||
public:
|
||||
class Builder : public BuilderBase<BuilderDetails> {
|
||||
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
|
||||
friend struct BuilderDetails;
|
||||
public:
|
||||
Builder() noexcept;
|
||||
@@ -63,6 +63,21 @@ public:
|
||||
*/
|
||||
Builder& count(size_t count) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this MorphTargetBuffer 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 MorphTargetBuffer
|
||||
* @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 MorphTargetBuffer object and returns a pointer to it.
|
||||
*
|
||||
|
||||
@@ -597,21 +597,6 @@ public:
|
||||
friend class FEngine;
|
||||
friend class FRenderPrimitive;
|
||||
friend class FRenderableManager;
|
||||
struct Entry {
|
||||
VertexBuffer* UTILS_NULLABLE vertices = nullptr;
|
||||
IndexBuffer* UTILS_NULLABLE indices = nullptr;
|
||||
size_t offset = 0;
|
||||
size_t count = 0;
|
||||
MaterialInstance const* UTILS_NULLABLE materialInstance = nullptr;
|
||||
PrimitiveType type = PrimitiveType::TRIANGLES;
|
||||
uint16_t blendOrder = 0;
|
||||
bool globalBlendOrderEnabled = false;
|
||||
struct {
|
||||
MorphTargetBuffer* UTILS_NULLABLE buffer = nullptr;
|
||||
size_t offset = 0;
|
||||
size_t count = 0;
|
||||
} morphing;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,7 +39,7 @@ class UTILS_PUBLIC SkinningBuffer : public FilamentAPI {
|
||||
struct BuilderDetails;
|
||||
|
||||
public:
|
||||
class Builder : public BuilderBase<BuilderDetails> {
|
||||
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
|
||||
friend struct BuilderDetails;
|
||||
public:
|
||||
Builder() noexcept;
|
||||
@@ -69,6 +69,21 @@ public:
|
||||
*/
|
||||
Builder& initialize(bool initialize = true) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this SkinningBuffer 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 SkinningBuffer
|
||||
* @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 SkinningBuffer object and returns a pointer to it.
|
||||
*
|
||||
|
||||
@@ -94,7 +94,7 @@ public:
|
||||
*
|
||||
* To create a NATIVE stream, call the <pre>stream</pre> method on the builder.
|
||||
*/
|
||||
class Builder : public BuilderBase<BuilderDetails> {
|
||||
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
|
||||
friend struct BuilderDetails;
|
||||
public:
|
||||
Builder() noexcept;
|
||||
@@ -136,6 +136,21 @@ public:
|
||||
*/
|
||||
Builder& height(uint32_t height) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this Stream 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 Stream
|
||||
* @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 Stream object and returns a pointer to it.
|
||||
*
|
||||
|
||||
@@ -112,7 +112,7 @@ public:
|
||||
|
||||
|
||||
//! Use Builder to construct a Texture object instance
|
||||
class Builder : public BuilderBase<BuilderDetails> {
|
||||
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
|
||||
friend struct BuilderDetails;
|
||||
public:
|
||||
Builder() noexcept;
|
||||
@@ -202,6 +202,21 @@ public:
|
||||
*/
|
||||
Builder& swizzle(Swizzle r, Swizzle g, Swizzle b, Swizzle a) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this Texture 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 Texture
|
||||
* @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 Texture object and returns a pointer to it.
|
||||
*
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
using AttributeType = backend::ElementType;
|
||||
using BufferDescriptor = backend::BufferDescriptor;
|
||||
|
||||
class Builder : public BuilderBase<BuilderDetails> {
|
||||
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
|
||||
friend struct BuilderDetails;
|
||||
public:
|
||||
Builder() noexcept;
|
||||
@@ -158,6 +158,21 @@ public:
|
||||
*/
|
||||
Builder& advancedSkinning(bool enabled) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this VertexBuffer 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 VertexBuffer
|
||||
* @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 VertexBuffer object and returns a pointer to it.
|
||||
*
|
||||
|
||||
@@ -570,6 +570,13 @@ public:
|
||||
*/
|
||||
void setShadowType(ShadowType shadow) noexcept;
|
||||
|
||||
/**
|
||||
* Returns the shadow mapping technique used by this View.
|
||||
*
|
||||
* @return value set by setShadowType().
|
||||
*/
|
||||
ShadowType getShadowType() const noexcept;
|
||||
|
||||
/**
|
||||
* Sets VSM shadowing options that apply across the entire View.
|
||||
*
|
||||
|
||||
31
filament/src/FilamentBuilder.cpp
Normal file
31
filament/src/FilamentBuilder.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 <filament/FilamentAPI.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace filament {
|
||||
|
||||
void builderMakeName(utils::CString& outName, const char* name, size_t len) noexcept {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
size_t const length = std::min(len, size_t { 128u });
|
||||
outName = utils::CString(name, length);
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
@@ -31,8 +31,7 @@ using namespace utils;
|
||||
using namespace backend;
|
||||
|
||||
FrameSkipper::FrameSkipper(size_t latency) noexcept
|
||||
: mLast(std::max(latency, MAX_FRAME_LATENCY) - 1) {
|
||||
assert_invariant(latency <= MAX_FRAME_LATENCY);
|
||||
: mLast(std::clamp(latency, size_t(1), MAX_FRAME_LATENCY) - 1) {
|
||||
}
|
||||
|
||||
FrameSkipper::~FrameSkipper() noexcept = default;
|
||||
|
||||
@@ -32,7 +32,18 @@ namespace filament {
|
||||
* outrun the GPU.
|
||||
*/
|
||||
class FrameSkipper {
|
||||
static constexpr size_t MAX_FRAME_LATENCY = 3;
|
||||
/*
|
||||
* The maximum frame latency acceptable on ANDROID is 2 because higher latencies will be
|
||||
* throttled anyway in BufferQueueProducer::dequeueBuffer(), because ANDROID is generally
|
||||
* triple-buffered no more; that case is actually pretty bad because the GL thread can block
|
||||
* anywhere (usually inside the first draw command that touches the swapchain).
|
||||
*
|
||||
* A frame latency of 1 has the benefit of reducing render latency,
|
||||
* but the drawback of preventing CPU / GPU overlap.
|
||||
*
|
||||
* Generally a frame latency of 2 is the best compromise.
|
||||
*/
|
||||
static constexpr size_t MAX_FRAME_LATENCY = 2;
|
||||
public:
|
||||
/*
|
||||
* The latency parameter defines how many unfinished frames we want to accept before we start
|
||||
|
||||
@@ -24,18 +24,28 @@
|
||||
|
||||
#include <filament/MaterialChunkType.h>
|
||||
|
||||
#include <private/filament/SamplerBindingsInfo.h>
|
||||
#include <private/filament/SamplerInterfaceBlock.h>
|
||||
#include <private/filament/BufferInterfaceBlock.h>
|
||||
#include <private/filament/SubpassInfo.h>
|
||||
#include <private/filament/Variant.h>
|
||||
#include <private/filament/ConstantInfo.h>
|
||||
#include <private/filament/PushConstantInfo.h>
|
||||
#include <private/filament/EngineEnums.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
|
||||
using namespace utils;
|
||||
using namespace filament::backend;
|
||||
@@ -62,12 +72,13 @@ constexpr std::pair<ChunkType, ChunkType> shaderLanguageToTags(ShaderLanguage la
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
MaterialParser::MaterialParserDetails::MaterialParserDetails(
|
||||
const utils::FixedCapacityVector<ShaderLanguage>& preferredLanguages, const void* data,
|
||||
utils::FixedCapacityVector<ShaderLanguage> preferredLanguages, const void* data,
|
||||
size_t size)
|
||||
: mManagedBuffer(data, size),
|
||||
mChunkContainer(mManagedBuffer.data(), mManagedBuffer.size()),
|
||||
mPreferredLanguages(preferredLanguages),
|
||||
mMaterialChunk(mChunkContainer) {}
|
||||
mPreferredLanguages(std::move(preferredLanguages)),
|
||||
mMaterialChunk(mChunkContainer) {
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
UTILS_NOINLINE
|
||||
@@ -82,11 +93,29 @@ bool MaterialParser::MaterialParserDetails::getFromSimpleChunk(
|
||||
return false;
|
||||
}
|
||||
|
||||
MaterialParser::MaterialParserDetails::ManagedBuffer::ManagedBuffer(const void* start, size_t size)
|
||||
: mStart(malloc(size)), mSize(size) {
|
||||
memcpy(mStart, start, size);
|
||||
}
|
||||
|
||||
MaterialParser::MaterialParserDetails::ManagedBuffer::~ManagedBuffer() noexcept {
|
||||
free(mStart);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
template<typename T>
|
||||
bool MaterialParser::get(typename T::Container* container) const noexcept {
|
||||
auto [start, end] = mImpl.mChunkContainer.getChunkRange(T::tag);
|
||||
if (start == end) return false;
|
||||
filaflat::Unflattener unflattener{ start, end };
|
||||
return T::unflatten(unflattener, container);
|
||||
}
|
||||
|
||||
MaterialParser::MaterialParser(utils::FixedCapacityVector<ShaderLanguage> preferredLanguages,
|
||||
const void* data, size_t size)
|
||||
: mImpl(preferredLanguages, data, size) {}
|
||||
: mImpl(std::move(preferredLanguages), data, size) {
|
||||
}
|
||||
|
||||
ChunkContainer& MaterialParser::getChunkContainer() noexcept {
|
||||
return mImpl.mChunkContainer;
|
||||
@@ -158,25 +187,16 @@ bool MaterialParser::getCacheId(uint64_t* cacheId) const noexcept {
|
||||
return unflattener.read(cacheId);
|
||||
}
|
||||
|
||||
bool MaterialParser::getUIB(BufferInterfaceBlock* uib) const noexcept {
|
||||
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialUib);
|
||||
if (start == end) return false;
|
||||
Unflattener unflattener(start, end);
|
||||
return ChunkUniformInterfaceBlock::unflatten(unflattener, uib);
|
||||
bool MaterialParser::getUIB(BufferInterfaceBlock* container) const noexcept {
|
||||
return get<ChunkUniformInterfaceBlock>(container);
|
||||
}
|
||||
|
||||
bool MaterialParser::getSIB(SamplerInterfaceBlock* sib) const noexcept {
|
||||
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialSib);
|
||||
if (start == end) return false;
|
||||
Unflattener unflattener(start, end);
|
||||
return ChunkSamplerInterfaceBlock::unflatten(unflattener, sib);
|
||||
bool MaterialParser::getSIB(SamplerInterfaceBlock* container) const noexcept {
|
||||
return get<ChunkSamplerInterfaceBlock>(container);
|
||||
}
|
||||
|
||||
bool MaterialParser::getSubpasses(SubpassInfo* subpass) const noexcept {
|
||||
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialSubpass);
|
||||
if (start == end) return false;
|
||||
Unflattener unflattener(start, end);
|
||||
return ChunkSubpassInterfaceBlock::unflatten(unflattener, subpass);
|
||||
bool MaterialParser::getSubpasses(SubpassInfo* container) const noexcept {
|
||||
return get<ChunkSubpassInterfaceBlock>(container);
|
||||
}
|
||||
|
||||
bool MaterialParser::getShaderModels(uint32_t* value) const noexcept {
|
||||
@@ -187,43 +207,24 @@ bool MaterialParser::getMaterialProperties(uint64_t* value) const noexcept {
|
||||
return mImpl.getFromSimpleChunk(ChunkType::MaterialProperties, value);
|
||||
}
|
||||
|
||||
bool MaterialParser::getUniformBlockBindings(
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>>* value) const noexcept {
|
||||
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialUniformBindings);
|
||||
if (start == end) return false;
|
||||
Unflattener unflattener(start, end);
|
||||
return ChunkUniformBlockBindings::unflatten(unflattener, value);
|
||||
}
|
||||
|
||||
bool MaterialParser::getBindingUniformInfo(BindingUniformInfoContainer* container) const noexcept {
|
||||
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialBindingUniformInfo);
|
||||
if (start == end) return false;
|
||||
Unflattener unflattener(start, end);
|
||||
return ChunkBindingUniformInfo::unflatten(unflattener, container);
|
||||
return get<ChunkBindingUniformInfo>(container);
|
||||
}
|
||||
|
||||
bool MaterialParser::getAttributeInfo(AttributeInfoContainer* container) const noexcept {
|
||||
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialAttributeInfo);
|
||||
if (start == end) return false;
|
||||
Unflattener unflattener(start, end);
|
||||
return ChunkAttributeInfo::unflatten(unflattener, container);
|
||||
return get<ChunkAttributeInfo>(container);
|
||||
}
|
||||
|
||||
bool MaterialParser::getSamplerBlockBindings(
|
||||
SamplerGroupBindingInfoList* pSamplerGroupInfoList,
|
||||
SamplerBindingToNameMap* pSamplerBindingToNameMap) const noexcept {
|
||||
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialSamplerBindings);
|
||||
if (start == end) return false;
|
||||
Unflattener unflattener(start, end);
|
||||
return ChunkSamplerBlockBindings::unflatten(unflattener,
|
||||
pSamplerGroupInfoList, pSamplerBindingToNameMap);
|
||||
bool MaterialParser::getDescriptorBindings(DescriptorBindingsContainer* container) const noexcept {
|
||||
return get<ChunkDescriptorBindingsInfo>(container);
|
||||
}
|
||||
|
||||
bool MaterialParser::getConstants(utils::FixedCapacityVector<MaterialConstant>* value) const noexcept {
|
||||
auto [start, end] = mImpl.mChunkContainer.getChunkRange(filamat::MaterialConstants);
|
||||
if (start == end) return false;
|
||||
Unflattener unflattener(start, end);
|
||||
return ChunkMaterialConstants::unflatten(unflattener, value);
|
||||
bool MaterialParser::getDescriptorSetLayout(DescriptorSetLayoutContainer* container) const noexcept {
|
||||
return get<ChunkDescriptorSetLayoutInfo>(container);
|
||||
}
|
||||
|
||||
bool MaterialParser::getConstants(utils::FixedCapacityVector<MaterialConstant>* container) const noexcept {
|
||||
return get<ChunkMaterialConstants>(container);
|
||||
}
|
||||
|
||||
bool MaterialParser::getPushConstants(utils::CString* structVarName,
|
||||
@@ -466,7 +467,9 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
|
||||
}
|
||||
|
||||
for (uint64_t i = 0; i < numFields; i++) {
|
||||
static_assert(sizeof(backend::descriptor_binding_t) == sizeof(uint8_t));
|
||||
CString fieldName;
|
||||
uint8_t fieldBinding = 0;
|
||||
uint8_t fieldType = 0;
|
||||
uint8_t fieldFormat = 0;
|
||||
uint8_t fieldPrecision = 0;
|
||||
@@ -476,6 +479,10 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!unflattener.read(&fieldBinding)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!unflattener.read(&fieldType)) {
|
||||
return false;
|
||||
}
|
||||
@@ -492,7 +499,9 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
|
||||
return false;
|
||||
}
|
||||
|
||||
builder.add({ fieldName.data(), fieldName.size() }, SamplerInterfaceBlock::Type(fieldType),
|
||||
builder.add({ fieldName.data(), fieldName.size() },
|
||||
SamplerInterfaceBlock::Binding(fieldBinding),
|
||||
SamplerInterfaceBlock::Type(fieldType),
|
||||
SamplerInterfaceBlock::Format(fieldFormat),
|
||||
SamplerInterfaceBlock::Precision(fieldPrecision),
|
||||
fieldMultisample);
|
||||
@@ -557,28 +566,6 @@ bool ChunkSubpassInterfaceBlock::unflatten(Unflattener& unflattener,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChunkUniformBlockBindings::unflatten(filaflat::Unflattener& unflattener,
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>>* uniformBlockBindings) {
|
||||
uint8_t count;
|
||||
if (!unflattener.read(&count)) {
|
||||
return false;
|
||||
}
|
||||
uniformBlockBindings->reserve(count);
|
||||
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
CString name;
|
||||
uint8_t binding;
|
||||
if (!unflattener.read(&name)) {
|
||||
return false;
|
||||
}
|
||||
if (!unflattener.read(&binding)) {
|
||||
return false;
|
||||
}
|
||||
uniformBlockBindings->emplace_back(std::move(name), binding);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChunkBindingUniformInfo::unflatten(filaflat::Unflattener& unflattener,
|
||||
MaterialParser::BindingUniformInfoContainer* bindingUniformInfo) {
|
||||
uint8_t bindingPointCount;
|
||||
@@ -591,6 +578,10 @@ bool ChunkBindingUniformInfo::unflatten(filaflat::Unflattener& unflattener,
|
||||
if (!unflattener.read(&index)) {
|
||||
return false;
|
||||
}
|
||||
utils::CString uboName;
|
||||
if (!unflattener.read(&uboName)) {
|
||||
return false;
|
||||
}
|
||||
uint8_t uniformCount;
|
||||
if (!unflattener.read(&uniformCount)) {
|
||||
return false;
|
||||
@@ -616,7 +607,7 @@ bool ChunkBindingUniformInfo::unflatten(filaflat::Unflattener& unflattener,
|
||||
}
|
||||
uniforms.push_back({ name, offset, size, UniformType(type) });
|
||||
}
|
||||
bindingUniformInfo->emplace_back(UniformBindingPoints(index), std::move(uniforms));
|
||||
bindingUniformInfo->emplace_back(index, std::move(uboName), std::move(uniforms));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -646,52 +637,94 @@ bool ChunkAttributeInfo::unflatten(filaflat::Unflattener& unflattener,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChunkSamplerBlockBindings::unflatten(Unflattener& unflattener,
|
||||
SamplerGroupBindingInfoList* pSamplerGroupBindingInfoList,
|
||||
SamplerBindingToNameMap* pSamplerBindingToNameMap) {
|
||||
assert_invariant(pSamplerGroupBindingInfoList && pSamplerBindingToNameMap);
|
||||
SamplerGroupBindingInfoList& samplerGroupBindingInfoList = *pSamplerGroupBindingInfoList;
|
||||
SamplerBindingToNameMap& samplerBindingToNameMap = *pSamplerBindingToNameMap;
|
||||
bool ChunkDescriptorBindingsInfo::unflatten(filaflat::Unflattener& unflattener,
|
||||
MaterialParser::DescriptorBindingsContainer* container) {
|
||||
|
||||
uint8_t count;
|
||||
if (!unflattener.read(&count)) {
|
||||
return false;
|
||||
}
|
||||
assert_invariant(count == utils::Enum::count<SamplerBindingPoints>());
|
||||
|
||||
UTILS_NOUNROLL
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (!unflattener.read(&samplerGroupBindingInfoList[i].bindingOffset)) {
|
||||
return false;
|
||||
}
|
||||
if (!unflattener.read((uint8_t *)&samplerGroupBindingInfoList[i].shaderStageFlags)) {
|
||||
return false;
|
||||
}
|
||||
if (!unflattener.read(&samplerGroupBindingInfoList[i].count)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!unflattener.read(&count)) {
|
||||
uint8_t setCount;
|
||||
if (!unflattener.read(&setCount)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
samplerBindingToNameMap.reserve(count);
|
||||
samplerBindingToNameMap.resize(count);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
uint8_t binding;
|
||||
if (!unflattener.read(&binding)) {
|
||||
for (size_t j = 0; j < setCount; j++) {
|
||||
static_assert(sizeof(DescriptorSetBindingPoints) == sizeof(uint8_t));
|
||||
|
||||
DescriptorSetBindingPoints set;
|
||||
if (!unflattener.read(reinterpret_cast<uint8_t*>(&set))) {
|
||||
return false;
|
||||
}
|
||||
assert_invariant(binding < backend::MAX_SAMPLER_COUNT);
|
||||
if (!unflattener.read(&samplerBindingToNameMap[binding])) {
|
||||
|
||||
uint8_t descriptorCount;
|
||||
if (!unflattener.read(&descriptorCount)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& descriptors = (*container)[+set];
|
||||
descriptors.reserve(descriptorCount);
|
||||
for (size_t i = 0; i < descriptorCount; i++) {
|
||||
utils::CString name;
|
||||
if (!unflattener.read(&name)) {
|
||||
return false;
|
||||
}
|
||||
uint8_t type;
|
||||
if (!unflattener.read(&type)) {
|
||||
return false;
|
||||
}
|
||||
uint8_t binding;
|
||||
if (!unflattener.read(&binding)) {
|
||||
return false;
|
||||
}
|
||||
descriptors.push_back({
|
||||
std::move(name),
|
||||
backend::DescriptorType(type),
|
||||
backend::descriptor_binding_t(binding)});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChunkDescriptorSetLayoutInfo::unflatten(filaflat::Unflattener& unflattener,
|
||||
MaterialParser::DescriptorSetLayoutContainer* container) {
|
||||
for (size_t j = 0; j < 2; j++) {
|
||||
uint8_t descriptorCount;
|
||||
if (!unflattener.read(&descriptorCount)) {
|
||||
return false;
|
||||
}
|
||||
auto& descriptors = (*container)[j].bindings;
|
||||
descriptors.reserve(descriptorCount);
|
||||
for (size_t i = 0; i < descriptorCount; i++) {
|
||||
uint8_t type;
|
||||
if (!unflattener.read(&type)) {
|
||||
return false;
|
||||
}
|
||||
uint8_t stageFlags;
|
||||
if (!unflattener.read(&stageFlags)) {
|
||||
return false;
|
||||
}
|
||||
uint8_t binding;
|
||||
if (!unflattener.read(&binding)) {
|
||||
return false;
|
||||
}
|
||||
uint8_t flags;
|
||||
if (!unflattener.read(&flags)) {
|
||||
return false;
|
||||
}
|
||||
uint16_t count;
|
||||
if (!unflattener.read(&count)) {
|
||||
return false;
|
||||
}
|
||||
descriptors.push_back({
|
||||
backend::DescriptorType(type),
|
||||
backend::ShaderStageFlags(stageFlags),
|
||||
backend::descriptor_binding_t(binding),
|
||||
backend::DescriptorFlags(flags),
|
||||
count,
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChunkMaterialConstants::unflatten(filaflat::Unflattener& unflattener,
|
||||
utils::FixedCapacityVector<MaterialConstant>* materialConstants) {
|
||||
assert_invariant(materialConstants);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user