Compare commits
300 Commits
ebridgewat
...
v1.18.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdadb43e50 | ||
|
|
edaff60fbf | ||
|
|
32dab23bc6 | ||
|
|
362de7dd31 | ||
|
|
6b01fbb903 | ||
|
|
4934d9f7bc | ||
|
|
ed73955b00 | ||
|
|
3f1f2726c4 | ||
|
|
1a7bd7ea8d | ||
|
|
946ea43436 | ||
|
|
7f42385f5f | ||
|
|
8845ac2b75 | ||
|
|
da85001d4d | ||
|
|
8d15079937 | ||
|
|
adc542b5cd | ||
|
|
72feb044a3 | ||
|
|
c0ba260ddf | ||
|
|
2da215e8e7 | ||
|
|
4d0368b5f1 | ||
|
|
d11c78857d | ||
|
|
e829d90c4a | ||
|
|
a8006acd33 | ||
|
|
86ec502040 | ||
|
|
b19a73cc50 | ||
|
|
a99c695932 | ||
|
|
8cd720b53a | ||
|
|
04df79e58f | ||
|
|
a4b3717762 | ||
|
|
1035e442ee | ||
|
|
60d3638f15 | ||
|
|
3e7d3c9035 | ||
|
|
eb360be2ad | ||
|
|
485ac8704d | ||
|
|
dc74540423 | ||
|
|
96219c22db | ||
|
|
f3b7048775 | ||
|
|
aaed6fb376 | ||
|
|
35a5d3310f | ||
|
|
9da79a1d2d | ||
|
|
595b355d1b | ||
|
|
3a67d769f4 | ||
|
|
6fb536a937 | ||
|
|
bb460d78d8 | ||
|
|
838835a715 | ||
|
|
63acd53e23 | ||
|
|
fcd2d0457b | ||
|
|
58fc26461b | ||
|
|
fd82f6b04e | ||
|
|
cef3200533 | ||
|
|
634500c398 | ||
|
|
b3e294ac54 | ||
|
|
2bf7535ad0 | ||
|
|
3315f75de9 | ||
|
|
bbe7dbfa92 | ||
|
|
5697922a65 | ||
|
|
4da83df2b9 | ||
|
|
8f156d6588 | ||
|
|
cec0871c11 | ||
|
|
41a809368b | ||
|
|
ea53eb9290 | ||
|
|
05875057c9 | ||
|
|
44125926d1 | ||
|
|
60734349de | ||
|
|
fbfd5ec0ec | ||
|
|
a74a95cc65 | ||
|
|
bc0ea16ff0 | ||
|
|
b2dc8aa84c | ||
|
|
9987e8b6ab | ||
|
|
0d9bdcc008 | ||
|
|
b5c634045e | ||
|
|
1f05531d53 | ||
|
|
88f382f0e3 | ||
|
|
3e59925900 | ||
|
|
ea404f8d4f | ||
|
|
602a550d93 | ||
|
|
12abbe2d23 | ||
|
|
fb0ee97588 | ||
|
|
56ef48c9c3 | ||
|
|
47c3dd3dd1 | ||
|
|
c181648bfa | ||
|
|
0cf78b3abe | ||
|
|
22889a7ad9 | ||
|
|
a14451d0ac | ||
|
|
5dfdab10b7 | ||
|
|
d6f2e3b8e9 | ||
|
|
df30517743 | ||
|
|
8f80643c1a | ||
|
|
5aea9be2fb | ||
|
|
ad02e483d0 | ||
|
|
f463d53036 | ||
|
|
b8d4408524 | ||
|
|
fef70be848 | ||
|
|
bdb12d9b24 | ||
|
|
43ad283a83 | ||
|
|
2e4936afc4 | ||
|
|
891ffabd11 | ||
|
|
e2c19498b4 | ||
|
|
c32630b265 | ||
|
|
bf21e78d02 | ||
|
|
525d4e08a3 | ||
|
|
2e9bf6d694 | ||
|
|
e845f01d85 | ||
|
|
bef48be7b4 | ||
|
|
b54fdc9e6e | ||
|
|
cedbf2e30b | ||
|
|
592f8d1b0d | ||
|
|
29612a684e | ||
|
|
e6d5807399 | ||
|
|
fa2553251f | ||
|
|
7387718852 | ||
|
|
a503a6209a | ||
|
|
ce3e5f74e8 | ||
|
|
f37112358e | ||
|
|
f368b14621 | ||
|
|
6960b1148a | ||
|
|
3cc23aac25 | ||
|
|
11dc8740f2 | ||
|
|
4b797cff88 | ||
|
|
fe1c1736cd | ||
|
|
4058ef5f09 | ||
|
|
d25ca01624 | ||
|
|
d96f87dbbf | ||
|
|
8a2e31023f | ||
|
|
1ea8e171d9 | ||
|
|
e2be3dd0ac | ||
|
|
1c51164e7b | ||
|
|
f190f03530 | ||
|
|
055fc7cbc1 | ||
|
|
9073fc3dc3 | ||
|
|
2409dc9bc4 | ||
|
|
6586c8d70b | ||
|
|
ac0c94da69 | ||
|
|
d19d6a72b0 | ||
|
|
c81b5d98ef | ||
|
|
756866675f | ||
|
|
ebcd4925f7 | ||
|
|
13bed4fdf9 | ||
|
|
1dae5c6b6c | ||
|
|
8e6663e4b0 | ||
|
|
ba804444b8 | ||
|
|
58cfb85004 | ||
|
|
ab46481b45 | ||
|
|
4296782399 | ||
|
|
ef375a7103 | ||
|
|
fd258b7765 | ||
|
|
147de8d372 | ||
|
|
eb2a1928b6 | ||
|
|
35b033102f | ||
|
|
7bc65421a9 | ||
|
|
736514cf37 | ||
|
|
db0158dae8 | ||
|
|
e706695ed1 | ||
|
|
e8877ffe2d | ||
|
|
1fd5d9dae6 | ||
|
|
cd48089318 | ||
|
|
6379ab22c9 | ||
|
|
0bf02b75d5 | ||
|
|
c4259b5598 | ||
|
|
6b3c1179bc | ||
|
|
c1a0e61e8e | ||
|
|
fc06298ed4 | ||
|
|
4ca87b188c | ||
|
|
f1f60c3e0d | ||
|
|
76d21b56af | ||
|
|
0ab0e50a4f | ||
|
|
34f4c06a5c | ||
|
|
6de36f1e53 | ||
|
|
2a9a3b1ac2 | ||
|
|
84b73a3770 | ||
|
|
662a10e273 | ||
|
|
ecce02502e | ||
|
|
d17875aea1 | ||
|
|
b8897a68f9 | ||
|
|
84efd4871e | ||
|
|
85ea5a6b70 | ||
|
|
77891acb92 | ||
|
|
74fe102035 | ||
|
|
25cc554925 | ||
|
|
d787a521b5 | ||
|
|
46e52c71e1 | ||
|
|
1dad27a172 | ||
|
|
60d230b380 | ||
|
|
d7cb38e706 | ||
|
|
ce00cca6ee | ||
|
|
d627d57bad | ||
|
|
8ffc776f1c | ||
|
|
be032b52c1 | ||
|
|
4388e81e5f | ||
|
|
71a185d139 | ||
|
|
d2cf5985ac | ||
|
|
debcbb8e5c | ||
|
|
b9dd62c7d3 | ||
|
|
dc2b430f34 | ||
|
|
e5ef4e8868 | ||
|
|
c0d6cd3ac3 | ||
|
|
b63ab2dc19 | ||
|
|
5d8dad561c | ||
|
|
8933be1ae2 | ||
|
|
6b66b48b1d | ||
|
|
9c23eb6e33 | ||
|
|
baea54a3fc | ||
|
|
d9f800454c | ||
|
|
f9ee0de07a | ||
|
|
2786d0a9f7 | ||
|
|
491e8032e6 | ||
|
|
ef3f13f5d3 | ||
|
|
bcb5b2d790 | ||
|
|
02de3f2e2a | ||
|
|
0e7bb53c07 | ||
|
|
759109d478 | ||
|
|
54d5af6edf | ||
|
|
38fbe47ced | ||
|
|
f066c925ba | ||
|
|
994fdf4e1d | ||
|
|
50b50d65e3 | ||
|
|
0aaa985649 | ||
|
|
29564f8eae | ||
|
|
c15db68a5b | ||
|
|
3452fb3e56 | ||
|
|
35eb8e7be1 | ||
|
|
834b774128 | ||
|
|
5aa0eb9f9d | ||
|
|
d9a6e2e649 | ||
|
|
cb823b16a1 | ||
|
|
0bd41e877e | ||
|
|
ecc3e73967 | ||
|
|
464b4c24f9 | ||
|
|
32367516e8 | ||
|
|
1709a55606 | ||
|
|
58b4455979 | ||
|
|
ea1dede19c | ||
|
|
20ea3381fa | ||
|
|
7aa6fccd7c | ||
|
|
adbd54f4f8 | ||
|
|
9d54261f18 | ||
|
|
a97757c9ae | ||
|
|
7c79d9f89d | ||
|
|
bf0914f813 | ||
|
|
b96bc30fbd | ||
|
|
62b50eb8ba | ||
|
|
b4932e384a | ||
|
|
5e68dc5f8d | ||
|
|
f222f1b925 | ||
|
|
22e4a54782 | ||
|
|
1289922c5f | ||
|
|
2839c352b8 | ||
|
|
6a01cbc312 | ||
|
|
6193156556 | ||
|
|
fe23aa917d | ||
|
|
eb8a29a332 | ||
|
|
0626902530 | ||
|
|
042bfe2597 | ||
|
|
97133f3591 | ||
|
|
d06cc4390e | ||
|
|
6047d3235f | ||
|
|
2cda6e35bd | ||
|
|
8f8d51e17b | ||
|
|
6919e3b274 | ||
|
|
10bf944410 | ||
|
|
9a2f6fdb53 | ||
|
|
761977d385 | ||
|
|
21248f15b5 | ||
|
|
4f32817f6d | ||
|
|
cc9e05e711 | ||
|
|
419d68d4db | ||
|
|
8450232448 | ||
|
|
cc51726590 | ||
|
|
318e22af51 | ||
|
|
68ac87dc24 | ||
|
|
acb8f00075 | ||
|
|
06d9183aaa | ||
|
|
75af25419d | ||
|
|
f6b90d2a31 | ||
|
|
a3822f4af0 | ||
|
|
bcdad769ff | ||
|
|
be4fb4fdbb | ||
|
|
65394f6301 | ||
|
|
b0beee03bc | ||
|
|
fe1de41b8e | ||
|
|
a37b431e87 | ||
|
|
98107016b9 | ||
|
|
8bccfc2863 | ||
|
|
f54a0a3452 | ||
|
|
6778ab0624 | ||
|
|
269d636785 | ||
|
|
39862c91ce | ||
|
|
523f4026b4 | ||
|
|
a6bf162431 | ||
|
|
826e8d181c | ||
|
|
16dfadbba0 | ||
|
|
5cbb97551f | ||
|
|
defee767c3 | ||
|
|
9560318521 | ||
|
|
ef09feb048 | ||
|
|
39f323fe09 | ||
|
|
11b95304ea | ||
|
|
b7c30a7916 | ||
|
|
4cae48fc77 | ||
|
|
d1a93f0557 | ||
|
|
b93059fad7 |
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.17.0'
|
||||
implementation 'com.google.android.filament:filament-android:1.18.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -52,7 +52,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```
|
||||
pod 'Filament', '~> 1.17.0'
|
||||
pod 'Filament', '~> 1.18.0'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
This file contains one line summaries of commits that are worthy of mentioning in release notes.
|
||||
A new header is inserted each time a *tag* is created.
|
||||
|
||||
## v1.17.2 (currently main branch)
|
||||
## v1.18.0 (currently main branch)
|
||||
|
||||
## v1.17.1
|
||||
- engine: Add support separate samplers in fragment and vertex shaders [⚠️ **Material breakage**].
|
||||
- engine: Support legacy morphing mode with vertex attributes.
|
||||
- engine: Allow more flexible quality settings for the ColorGrading LUT.
|
||||
- engine: Improve screen-space reflections quality and allow reflections and refractions together.
|
||||
- Vulkan: Bug fixes and improvements.
|
||||
|
||||
## v1.17.0
|
||||
|
||||
|
||||
@@ -370,3 +370,10 @@ Java_com_google_android_filament_filamat_MaterialBuilder_nMaterialBuilderVariant
|
||||
auto builder = (MaterialBuilder*) nativeBuilder;
|
||||
builder->variantFilter((uint8_t) variantFilter);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_filamat_MaterialBuilder_nMaterialBuilderUseLegacyMorphing(JNIEnv*,
|
||||
jclass, jlong nativeBuilder) {
|
||||
auto builder = (MaterialBuilder*) nativeBuilder;
|
||||
builder->useLegacyMorphing();
|
||||
}
|
||||
|
||||
@@ -114,14 +114,14 @@ public class MaterialBuilder {
|
||||
BONE_INDICES, // indices of 4 bones (uvec4)
|
||||
BONE_WEIGHTS, // weights of the 4 bones (normalized float4)
|
||||
UNUSED, // reserved for future use
|
||||
CUSTOM0, // custom
|
||||
CUSTOM1, // custom
|
||||
CUSTOM2, // custom
|
||||
CUSTOM3, // custom
|
||||
CUSTOM4, // custom
|
||||
CUSTOM5, // custom
|
||||
CUSTOM6, // custom
|
||||
CUSTOM7 // custom
|
||||
CUSTOM0, // custom or MORPH_POSITION_0
|
||||
CUSTOM1, // custom or MORPH_POSITION_1
|
||||
CUSTOM2, // custom or MORPH_POSITION_2
|
||||
CUSTOM3, // custom or MORPH_POSITION_3
|
||||
CUSTOM4, // custom or MORPH_TANGENTS_0
|
||||
CUSTOM5, // custom or MORPH_TANGENTS_1
|
||||
CUSTOM6, // custom or MORPH_TANGENTS_2
|
||||
CUSTOM7 // custom or MORPH_TANGENTS_3
|
||||
}
|
||||
|
||||
public enum BlendingMode {
|
||||
@@ -459,6 +459,19 @@ public class MaterialBuilder {
|
||||
@NonNull
|
||||
public MaterialBuilder variantFilter(int variantFilter) {
|
||||
nMaterialBuilderVariantFilter(mNativeObject, variantFilter);
|
||||
useLegacyMorphing();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy morphing uses the data in the {@link VertexBuffer.VertexAttribute} slots
|
||||
* (<code>MORPH_POSITION_0</code>, etc) and is limited to 4 morph targets.
|
||||
*
|
||||
* @see RenderableManager.Builder#morphing()
|
||||
*/
|
||||
@NonNull
|
||||
public MaterialBuilder useLegacyMorphing() {
|
||||
nMaterialBuilderUseLegacyMorphing(mNativeObject);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -599,4 +612,5 @@ public class MaterialBuilder {
|
||||
private static native void nMaterialBuilderOptimization(long nativeBuilder, int optimization);
|
||||
private static native void nMaterialBuilderVariantFilter(long nativeBuilder,
|
||||
int variantFilter);
|
||||
private static native void nMaterialBuilderUseLegacyMorphing(long nativeBuilder);
|
||||
}
|
||||
|
||||
@@ -400,7 +400,21 @@ public class RenderableManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls if the renderable has vertex morphing targets, false by default.
|
||||
* Controls if the renderable has vertex morphing targets, false by default. This is
|
||||
* required to enable GPU morphing.
|
||||
*
|
||||
* <p>Filament supports two morphing modes: standard (default) and legacy.</p>
|
||||
*
|
||||
* <p>For standard morphing, A {@link MorphTargetBuffer} must be created and provided via
|
||||
* {@link RenderableManager#setMorphTargetBufferAt}. Standard morphing supports up to
|
||||
* <code>CONFIG_MAX_MORPH_TARGET_COUNT</code> morph targets.</p>
|
||||
*
|
||||
* For legacy morphing, the attached {@link VertexBuffer} must provide data in the
|
||||
* appropriate {@link VertexBuffer.VertexAttribute} slots (<code>MORPH_POSITION_0</code> etc).
|
||||
* Legacy morphing only supports up to 4 morph targets and will be deprecated in the future.
|
||||
* Legacy morphing must be enabled on the material definition: either via the
|
||||
* <code>legacyMorphing</code> material attribute or by calling
|
||||
* {@link MaterialBuilder::useLegacyMorphing}.
|
||||
*
|
||||
* <p>See also {@link RenderableManager#setMorphWeights}, which can be called on a per-frame basis
|
||||
* to advance the animation.</p>
|
||||
@@ -494,7 +508,8 @@ public class RenderableManager {
|
||||
/**
|
||||
* Updates the vertex morphing weights on a renderable, all zeroes by default.
|
||||
*
|
||||
* <p>The renderable must be built with morphing enabled.</p>
|
||||
* <p>The renderable must be built with morphing enabled. In legacy morphing mode, only the
|
||||
* first 4 weights are considered.</p>
|
||||
*
|
||||
* @see Builder#morphing
|
||||
*/
|
||||
|
||||
@@ -66,14 +66,14 @@ public class VertexBuffer {
|
||||
BONE_INDICES, // indices of 4 bones (uvec4)
|
||||
BONE_WEIGHTS, // weights of the 4 bones (normalized float4)
|
||||
UNUSED, // reserved for future use
|
||||
CUSTOM0, // custom
|
||||
CUSTOM1, // custom
|
||||
CUSTOM2, // custom
|
||||
CUSTOM3, // custom
|
||||
CUSTOM4, // custom
|
||||
CUSTOM5, // custom
|
||||
CUSTOM6, // custom
|
||||
CUSTOM7 // custom
|
||||
CUSTOM0, // custom or MORPH_POSITION_0
|
||||
CUSTOM1, // custom or MORPH_POSITION_1
|
||||
CUSTOM2, // custom or MORPH_POSITION_2
|
||||
CUSTOM3, // custom or MORPH_POSITION_3
|
||||
CUSTOM4, // custom or MORPH_TANGENTS_0
|
||||
CUSTOM5, // custom or MORPH_TANGENTS_1
|
||||
CUSTOM6, // custom or MORPH_TANGENTS_2
|
||||
CUSTOM7 // custom or MORPH_TANGENTS_3
|
||||
}
|
||||
|
||||
public enum AttributeType {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.17.0
|
||||
VERSION_NAME=1.18.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -1438,6 +1438,14 @@ This defines the following functions:</p>
|
||||
<li>CUSTOM5</li>
|
||||
<li>CUSTOM6</li>
|
||||
<li>CUSTOM7</li>
|
||||
<li>MORPH_POSITION_0</li>
|
||||
<li>MORPH_POSITION_1</li>
|
||||
<li>MORPH_POSITION_2</li>
|
||||
<li>MORPH_POSITION_3</li>
|
||||
<li>MORPH_TANGENTS_0</li>
|
||||
<li>MORPH_TANGENTS_1</li>
|
||||
<li>MORPH_TANGENTS_2</li>
|
||||
<li>MORPH_TANGENTS_3</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class='enumdoc'>
|
||||
|
||||
@@ -13,6 +13,7 @@ set(PUBLIC_HDRS
|
||||
include/backend/CallbackHandler.h
|
||||
include/backend/DriverEnums.h
|
||||
include/backend/Handle.h
|
||||
include/backend/ShaderStageFlags.h
|
||||
include/backend/PipelineState.h
|
||||
include/backend/PixelBufferDescriptor.h
|
||||
include/backend/Platform.h
|
||||
|
||||
@@ -49,7 +49,9 @@ static constexpr uint64_t SWAP_CHAIN_CONFIG_ENABLE_XCB = 0x4;
|
||||
static constexpr uint64_t SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER = 0x8;
|
||||
|
||||
static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_SAMPLER_COUNT = 16; // Matches the Adreno Vulkan driver.
|
||||
static constexpr size_t MAX_VERTEX_SAMPLER_COUNT = 16; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_FRAGMENT_SAMPLER_COUNT = 16; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_SAMPLER_COUNT = 32; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_VERTEX_BUFFER_COUNT = 16; // Max number of bound buffer objects.
|
||||
|
||||
static_assert(MAX_VERTEX_BUFFER_COUNT <= MAX_VERTEX_ATTRIBUTE_COUNT,
|
||||
|
||||
52
filament/backend/include/backend/ShaderStageFlags.h
Normal file
52
filament/backend/include/backend/ShaderStageFlags.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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_SHADERSTAGEFLAGS_H
|
||||
#define TNT_FILAMENT_BACKEND_SHADERSTAGEFLAGS_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct ShaderStageFlags {
|
||||
bool vertex : 1;
|
||||
bool fragment : 1;
|
||||
|
||||
inline bool hasShaderType(ShaderType type) const {
|
||||
return (vertex && type == ShaderType::VERTEX) ||
|
||||
(fragment && type == ShaderType::FRAGMENT);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr ShaderStageFlags ALL_SHADER_STAGE_FLAGS = { .vertex = true, .fragment = true };
|
||||
|
||||
inline utils::io::ostream& operator<<(utils::io::ostream& stream, ShaderStageFlags stageFlags) {
|
||||
stream << "{ ";
|
||||
if (stageFlags.vertex) {
|
||||
stream << "vertex";
|
||||
}
|
||||
if (stageFlags.fragment) {
|
||||
if (stageFlags.vertex)
|
||||
stream << " | ";
|
||||
stream << "fragment";
|
||||
}
|
||||
stream << " }";
|
||||
return stream;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_SHADERSTAGEFLAGS_H
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/ShaderStageFlags.h>
|
||||
|
||||
#include <private/filament/Variant.h>
|
||||
|
||||
@@ -47,7 +48,12 @@ public:
|
||||
bool strict = false; // if true, this sampler must always have a bound texture
|
||||
};
|
||||
|
||||
using SamplerGroupInfo = std::array<utils::FixedCapacityVector<Sampler>, BINDING_COUNT>;
|
||||
struct SamplerGroupData {
|
||||
utils::FixedCapacityVector<Sampler> samplers;
|
||||
ShaderStageFlags stageFlags = ALL_SHADER_STAGE_FLAGS;
|
||||
};
|
||||
|
||||
using SamplerGroupInfo = std::array<SamplerGroupData, BINDING_COUNT>;
|
||||
using UniformBlockInfo = std::array<utils::CString, BINDING_COUNT>;
|
||||
|
||||
Program() noexcept;
|
||||
@@ -75,7 +81,8 @@ public:
|
||||
// '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, Sampler const* samplers, size_t count) noexcept;
|
||||
Program& setSamplerGroup(size_t bindingPoint, ShaderStageFlags stageFlags,
|
||||
Sampler const* samplers, size_t count) noexcept;
|
||||
|
||||
Program& withVertexShader(void const* data, size_t size) {
|
||||
return shader(Shader::VERTEX, data, size);
|
||||
|
||||
@@ -45,9 +45,11 @@ Program& Program::setUniformBlock(size_t bindingPoint, utils::CString uniformBlo
|
||||
return *this;
|
||||
}
|
||||
|
||||
Program& Program::setSamplerGroup(size_t bindingPoint,
|
||||
Program& Program::setSamplerGroup(size_t bindingPoint, ShaderStageFlags stageFlags,
|
||||
const Program::Sampler* samplers, size_t count) noexcept {
|
||||
auto& samplerList = mSamplerGroups[bindingPoint];
|
||||
auto& groupData = mSamplerGroups[bindingPoint];
|
||||
groupData.stageFlags = stageFlags;
|
||||
auto& samplerList = groupData.samplers;
|
||||
samplerList.reserve(count);
|
||||
samplerList.resize(count);
|
||||
std::copy_n(samplers, count, samplerList.data());
|
||||
|
||||
@@ -119,7 +119,7 @@ private:
|
||||
mHandleAllocator.deallocate(handle, p);
|
||||
}
|
||||
|
||||
void enumerateSamplerGroups(const MetalProgram* program,
|
||||
void enumerateSamplerGroups(const MetalProgram* program, ShaderType shaderType,
|
||||
const std::function<void(const SamplerGroup::Sampler*, size_t)>& f);
|
||||
void enumerateBoundUniformBuffers(const std::function<void(const UniformBufferState&,
|
||||
MetalBuffer*, uint32_t)>& f);
|
||||
|
||||
@@ -1267,60 +1267,101 @@ void MetalDriver::draw(backend::PipelineState ps, Handle<HwRenderPrimitive> rph)
|
||||
// Enumerate all the sampler buffers for the program and check which textures and samplers need
|
||||
// to be bound.
|
||||
|
||||
id<MTLTexture> texturesToBind[SAMPLER_BINDING_COUNT] = {};
|
||||
id<MTLSamplerState> samplersToBind[SAMPLER_BINDING_COUNT] = {};
|
||||
|
||||
enumerateSamplerGroups(program, [this, &texturesToBind, &samplersToBind](
|
||||
const SamplerGroup::Sampler* sampler,
|
||||
uint8_t binding) {
|
||||
// We currently only support a max of SAMPLER_BINDING_COUNT samplers. Ignore any additional
|
||||
// samplers that may be bound.
|
||||
if (binding >= SAMPLER_BINDING_COUNT) {
|
||||
return;
|
||||
}
|
||||
auto getTextureToBind = [this](const SamplerGroup::Sampler* sampler) {
|
||||
const auto metalTexture = handle_const_cast<MetalTexture>(sampler->t);
|
||||
texturesToBind[binding] = metalTexture->swizzledTextureView ? metalTexture->swizzledTextureView
|
||||
: metalTexture->texture;
|
||||
|
||||
id<MTLTexture> textureToBind = metalTexture->swizzledTextureView ? metalTexture->swizzledTextureView
|
||||
: metalTexture->texture;
|
||||
if (metalTexture->externalImage.isValid()) {
|
||||
texturesToBind[binding] = metalTexture->externalImage.getMetalTextureForDraw();
|
||||
}
|
||||
|
||||
if (!texturesToBind[binding]) {
|
||||
utils::slog.w << "Warning: no texture bound at binding point " << (size_t) binding
|
||||
<< "." << utils::io::endl;
|
||||
texturesToBind[binding] = getOrCreateEmptyTexture(mContext);
|
||||
textureToBind = metalTexture->externalImage.getMetalTextureForDraw();
|
||||
}
|
||||
return textureToBind;
|
||||
};
|
||||
|
||||
auto getSamplerToBind = [this](const SamplerGroup::Sampler* sampler) {
|
||||
const auto metalTexture = handle_const_cast<MetalTexture>(sampler->t);
|
||||
SamplerState s {
|
||||
.samplerParams = sampler->s,
|
||||
.minLod = metalTexture->minLod,
|
||||
.maxLod = metalTexture->maxLod
|
||||
};
|
||||
id <MTLSamplerState> samplerState = mContext->samplerStateCache.getOrCreateState(s);
|
||||
samplersToBind[binding] = samplerState;
|
||||
return mContext->samplerStateCache.getOrCreateState(s);
|
||||
};
|
||||
|
||||
id<MTLTexture> texturesToBindVertex[backend::MAX_VERTEX_SAMPLER_COUNT] = {};
|
||||
id<MTLSamplerState> samplersToBindVertex[backend::MAX_VERTEX_SAMPLER_COUNT] = {};
|
||||
|
||||
enumerateSamplerGroups(program, ShaderType::VERTEX,
|
||||
[this, &getTextureToBind, &getSamplerToBind, &texturesToBindVertex, &samplersToBindVertex](
|
||||
const SamplerGroup::Sampler* sampler, uint8_t binding) {
|
||||
// We currently only support a max of MAX_VERTEX_SAMPLER_COUNT samplers. Ignore any additional
|
||||
// samplers that may be bound.
|
||||
if (binding >= backend::MAX_VERTEX_SAMPLER_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& textureToBind = texturesToBindVertex[binding];
|
||||
textureToBind = getTextureToBind(sampler);
|
||||
if (!textureToBind) {
|
||||
utils::slog.w << "Warning: no texture bound at binding point " << (size_t) binding
|
||||
<< " at the vertex shader." << utils::io::endl;
|
||||
textureToBind = getOrCreateEmptyTexture(mContext);
|
||||
}
|
||||
|
||||
auto& samplerToBind = samplersToBindVertex[binding];
|
||||
samplerToBind = getSamplerToBind(sampler);
|
||||
});
|
||||
|
||||
// Assign a default sampler to empty slots, in case Filament hasn't bound all samplers.
|
||||
// Metal requires all samplers referenced in shaders to be bound.
|
||||
for (auto& sampler : samplersToBind) {
|
||||
for (auto& sampler : samplersToBindVertex) {
|
||||
if (!sampler) {
|
||||
sampler = mContext->samplerStateCache.getOrCreateState({});
|
||||
}
|
||||
}
|
||||
|
||||
// Similar to uniforms, we can't tell which stage will use the textures / samplers, so bind
|
||||
// to both the vertex and fragment stages.
|
||||
NSRange vertexSamplerRange = NSMakeRange(0, backend::MAX_VERTEX_SAMPLER_COUNT);
|
||||
[mContext->currentRenderPassEncoder setVertexTextures:texturesToBindVertex
|
||||
withRange:vertexSamplerRange];
|
||||
[mContext->currentRenderPassEncoder setVertexSamplerStates:samplersToBindVertex
|
||||
withRange:vertexSamplerRange];
|
||||
|
||||
NSRange samplerRange = NSMakeRange(0, SAMPLER_BINDING_COUNT);
|
||||
[mContext->currentRenderPassEncoder setFragmentTextures:texturesToBind
|
||||
withRange:samplerRange];
|
||||
[mContext->currentRenderPassEncoder setVertexTextures:texturesToBind
|
||||
withRange:samplerRange];
|
||||
[mContext->currentRenderPassEncoder setFragmentSamplerStates:samplersToBind
|
||||
withRange:samplerRange];
|
||||
[mContext->currentRenderPassEncoder setVertexSamplerStates:samplersToBind
|
||||
withRange:samplerRange];
|
||||
id<MTLTexture> texturesToBindFragment[backend::MAX_FRAGMENT_SAMPLER_COUNT] = {};
|
||||
id<MTLSamplerState> samplersToBindFragment[backend::MAX_FRAGMENT_SAMPLER_COUNT] = {};
|
||||
|
||||
enumerateSamplerGroups(program, ShaderType::FRAGMENT,
|
||||
[this, &getTextureToBind, &getSamplerToBind, &texturesToBindFragment, &samplersToBindFragment](
|
||||
const SamplerGroup::Sampler* sampler, uint8_t binding) {
|
||||
// We currently only support a max of MAX_FRAGMENT_SAMPLER_COUNT samplers. Ignore any additional
|
||||
// samplers that may be bound.
|
||||
if (binding >= backend::MAX_FRAGMENT_SAMPLER_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& textureToBind = texturesToBindFragment[binding];
|
||||
textureToBind = getTextureToBind(sampler);
|
||||
if (!textureToBind) {
|
||||
utils::slog.w << "Warning: no texture bound at binding point " << (size_t) binding
|
||||
<< " at the fragment shader." << utils::io::endl;
|
||||
textureToBind = getOrCreateEmptyTexture(mContext);
|
||||
}
|
||||
|
||||
auto& samplerToBind = samplersToBindFragment[binding];
|
||||
samplerToBind = getSamplerToBind(sampler);
|
||||
});
|
||||
|
||||
// Assign a default sampler to empty slots, in case Filament hasn't bound all samplers.
|
||||
// Metal requires all samplers referenced in shaders to be bound.
|
||||
for (auto& sampler : samplersToBindFragment) {
|
||||
if (!sampler) {
|
||||
sampler = mContext->samplerStateCache.getOrCreateState({});
|
||||
}
|
||||
}
|
||||
|
||||
NSRange fragmentSamplerRange = NSMakeRange(0, backend::MAX_FRAGMENT_SAMPLER_COUNT);
|
||||
[mContext->currentRenderPassEncoder setFragmentTextures:texturesToBindFragment
|
||||
withRange:fragmentSamplerRange];
|
||||
[mContext->currentRenderPassEncoder setFragmentSamplerStates:samplersToBindFragment
|
||||
withRange:fragmentSamplerRange];
|
||||
|
||||
// Bind the vertex buffers.
|
||||
|
||||
@@ -1379,33 +1420,33 @@ void MetalDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
}
|
||||
|
||||
void MetalDriver::enumerateSamplerGroups(
|
||||
const MetalProgram* program,
|
||||
const MetalProgram* program, ShaderType shaderType,
|
||||
const std::function<void(const SamplerGroup::Sampler*, size_t)>& f) {
|
||||
for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < SAMPLER_GROUP_COUNT; samplerGroupIdx++) {
|
||||
const auto& samplerGroup = program->samplerGroupInfo[samplerGroupIdx];
|
||||
if (samplerGroup.empty()) {
|
||||
auto& samplerBlockInfo = (shaderType == ShaderType::VERTEX) ?
|
||||
program->vertexSamplerBlockInfo : program->fragmentSamplerBlockInfo;
|
||||
auto maxSamplerCount = (shaderType == ShaderType::VERTEX) ?
|
||||
MAX_VERTEX_SAMPLER_COUNT : MAX_FRAGMENT_SAMPLER_COUNT;
|
||||
for (size_t bindingIdx = 0; bindingIdx != maxSamplerCount; ++bindingIdx) {
|
||||
auto& blockInfo = samplerBlockInfo[bindingIdx];
|
||||
if (blockInfo.samplerGroup == UINT8_MAX) {
|
||||
continue;
|
||||
}
|
||||
const auto* metalSamplerGroup = mContext->samplerBindings[samplerGroupIdx];
|
||||
|
||||
const auto* metalSamplerGroup = mContext->samplerBindings[blockInfo.samplerGroup];
|
||||
if (!metalSamplerGroup) {
|
||||
// Do not emit warning here. For example this can arise when skinning is enabled
|
||||
// and the morphing texture is unused.
|
||||
continue;
|
||||
}
|
||||
|
||||
SamplerGroup* sb = metalSamplerGroup->sb.get();
|
||||
assert_invariant(sb->getSize() == samplerGroup.size());
|
||||
size_t samplerIdx = 0;
|
||||
for (const auto& sampler : samplerGroup) {
|
||||
size_t bindingPoint = sampler.binding;
|
||||
const SamplerGroup::Sampler* boundSampler = sb->getSamplers() + samplerIdx;
|
||||
samplerIdx++;
|
||||
const SamplerGroup::Sampler* boundSampler = sb->getSamplers() + blockInfo.sampler;
|
||||
|
||||
if (!boundSampler->t) {
|
||||
continue;
|
||||
}
|
||||
|
||||
f(boundSampler, bindingPoint);
|
||||
if (!boundSampler->t) {
|
||||
continue;
|
||||
}
|
||||
|
||||
f(boundSampler, bindingIdx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -159,7 +159,15 @@ struct MetalProgram : public HwProgram {
|
||||
|
||||
id<MTLFunction> vertexFunction;
|
||||
id<MTLFunction> fragmentFunction;
|
||||
Program::SamplerGroupInfo samplerGroupInfo;
|
||||
|
||||
struct SamplerBlockInfo {
|
||||
uint8_t samplerGroup = UINT8_MAX;
|
||||
uint8_t sampler = UINT8_MAX;
|
||||
};
|
||||
|
||||
std::array<SamplerBlockInfo, MAX_VERTEX_SAMPLER_COUNT> vertexSamplerBlockInfo;
|
||||
std::array<SamplerBlockInfo, MAX_FRAGMENT_SAMPLER_COUNT> fragmentSamplerBlockInfo;
|
||||
|
||||
bool isValid = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -332,7 +332,7 @@ void MetalRenderPrimitive::setBuffers(MetalVertexBuffer* vertexBuffer, MetalInde
|
||||
}
|
||||
|
||||
MetalProgram::MetalProgram(id<MTLDevice> device, const Program& program) noexcept
|
||||
: HwProgram(program.getName()), vertexFunction(nil), fragmentFunction(nil), samplerGroupInfo(),
|
||||
: HwProgram(program.getName()), vertexFunction(nil), fragmentFunction(nil),
|
||||
isValid(false) {
|
||||
|
||||
using MetalFunctionPtr = __strong id<MTLFunction>*;
|
||||
@@ -372,7 +372,45 @@ MetalProgram::MetalProgram(id<MTLDevice> device, const Program& program) noexcep
|
||||
// All stages of the program have compiled successfuly, this is a valid program.
|
||||
isValid = true;
|
||||
|
||||
samplerGroupInfo = program.getSamplerGroupInfo();
|
||||
// This calculates Metal resource binding indices. Filament's sampler bindings range from 0-31
|
||||
// across both vertex and fragment stages. However, Metal binding indices must be < 16, with a
|
||||
// separate binding "namespace" for each stage. So, we recompute binding indices for each shader
|
||||
// stage. This logic should match updateResourceBinding in GLSLPostProcessor.cpp
|
||||
// This is an example how binding indices each of shader stages is generated from sampler's
|
||||
// bindings. Below is sampler's bindings.
|
||||
// 0 shadowMap { fragment }
|
||||
// 1 structure { fragment }
|
||||
// 2 targets { vertex }
|
||||
// 3 color { vertex | fragment }
|
||||
// 4 depth { vertex | fragment }
|
||||
// Below is generated vertex sampler binding indices.
|
||||
// 0 targets
|
||||
// 1 color
|
||||
// 2 depth
|
||||
// Below is generated fragment sampler binding indices.
|
||||
// 0 shadowMap
|
||||
// 1 structure
|
||||
// 2 color
|
||||
// 3 depth
|
||||
auto& samplerGroupInfo = program.getSamplerGroupInfo();
|
||||
for (size_t shaderType = 0; shaderType != PIPELINE_STAGE_COUNT; ++shaderType) {
|
||||
size_t bindingIdx = 0;
|
||||
auto& samplerBlockInfo = (shaderType == ShaderType::VERTEX) ? vertexSamplerBlockInfo
|
||||
: fragmentSamplerBlockInfo;
|
||||
for (size_t samplerGroupIdx = 0; samplerGroupIdx != SAMPLER_GROUP_COUNT; ++samplerGroupIdx) {
|
||||
auto& groupData = samplerGroupInfo[samplerGroupIdx];
|
||||
auto stageFlags = groupData.stageFlags;
|
||||
if (!stageFlags.hasShaderType(static_cast<ShaderType>(shaderType))) {
|
||||
continue;
|
||||
}
|
||||
auto& samplers = groupData.samplers;
|
||||
for (size_t samplerIdx = 0, c = samplers.size(); samplerIdx != c; ++samplerIdx) {
|
||||
samplerBlockInfo[bindingIdx].samplerGroup = samplerGroupIdx;
|
||||
samplerBlockInfo[bindingIdx].sampler = samplerIdx;
|
||||
++bindingIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels,
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace filament {
|
||||
|
||||
class OpenGLContext {
|
||||
public:
|
||||
static constexpr const size_t MAX_TEXTURE_UNIT_COUNT = 16; // All mobile GPUs as of 2016
|
||||
static constexpr const size_t MAX_TEXTURE_UNIT_COUNT = backend::MAX_SAMPLER_COUNT;
|
||||
static constexpr const size_t MAX_BUFFER_BINDINGS = 32;
|
||||
typedef math::details::TVec4<GLint> vec4gli;
|
||||
typedef math::details::TVec2<GLclampf> vec2glf;
|
||||
|
||||
@@ -199,14 +199,15 @@ highp uint packHalf2x16(vec2 v) {
|
||||
#pragma nounroll
|
||||
for (size_t i = 0, c = samplerGroupInfo.size(); i < c; i++) {
|
||||
auto const& groupInfo = samplerGroupInfo[i];
|
||||
if (!groupInfo.empty()) {
|
||||
auto const& samplers = groupInfo.samplers;
|
||||
if (!samplers.empty()) {
|
||||
// Cache the sampler uniform locations for each interface block
|
||||
BlockInfo& info = mBlockInfos[numUsedBindings];
|
||||
info.binding = uint8_t(i);
|
||||
uint8_t count = 0;
|
||||
for (uint8_t j = 0, m = uint8_t(groupInfo.size()); j < m; ++j) {
|
||||
for (uint8_t j = 0, m = uint8_t(samplers.size()); j < m; ++j) {
|
||||
// find its location and associate a TMU to it
|
||||
GLint loc = glGetUniformLocation(program, groupInfo[j].name.c_str());
|
||||
GLint loc = glGetUniformLocation(program, samplers[j].name.c_str());
|
||||
if (loc >= 0) {
|
||||
glUniform1i(loc, tmu);
|
||||
indicesRun[tmu] = j;
|
||||
@@ -273,6 +274,7 @@ void OpenGLProgram::updateSamplers(OpenGLDriver* gld) noexcept {
|
||||
BlockInfo blockInfo = blockInfos[i];
|
||||
HwSamplerGroup const * const UTILS_RESTRICT hwsb = samplerBindings[blockInfo.binding];
|
||||
if (UTILS_UNLIKELY(!hwsb)) {
|
||||
tmu += blockInfo.count + 1;
|
||||
continue;
|
||||
}
|
||||
SamplerGroup const& UTILS_RESTRICT sb = *(hwsb->sb);
|
||||
|
||||
@@ -73,16 +73,19 @@ private:
|
||||
|
||||
struct BlockInfo {
|
||||
uint8_t binding : 3; // binding (i.e.: index in mSamplerBindings)
|
||||
uint8_t unused : 1; // padding / available
|
||||
uint8_t count : 4; // number of TMUs actually used minus 1
|
||||
uint8_t count : 5; // number of TMUs actually used minus 1
|
||||
|
||||
// if TEXTURE_UNIT_COUNT > 16, the count bitfield must be increased accordingly
|
||||
static_assert(TEXTURE_UNIT_COUNT <= 16, "TEXTURE_UNIT_COUNT must be <= 16");
|
||||
// if TEXTURE_UNIT_COUNT > 32, the count bitfield must be increased accordingly
|
||||
static_assert(TEXTURE_UNIT_COUNT <= 32, "TEXTURE_UNIT_COUNT must be <= 32");
|
||||
|
||||
// if SAMPLER_BINDING_COUNT > 8, the binding bitfield must be increased accordingly
|
||||
static_assert(backend::Program::BINDING_COUNT <= 8, "BINDING_COUNT must be <= 8");
|
||||
};
|
||||
|
||||
// This assert checks that the struct is the size we expect without any "hidden" padding bytes
|
||||
// inserted by the compiler.
|
||||
static_assert(sizeof(BlockInfo) == sizeof(uint8_t), "BlockInfo must be 8 bits");
|
||||
|
||||
uint8_t mUsedBindingsCount = 0;
|
||||
uint8_t mValidShaderSet = 0;
|
||||
bool mIsValid = false;
|
||||
|
||||
@@ -129,11 +129,9 @@ void VulkanBlitter::blitFast(VkImageAspectFlags aspect, VkFilter filter,
|
||||
|
||||
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
|
||||
|
||||
|
||||
VkImageLayout srcLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
if (src.texture) {
|
||||
srcLayout = mContext.getTextureLayout(src.texture->usage);
|
||||
}
|
||||
const VkImageLayout srcLayout = src.texture ?
|
||||
getDefaultImageLayout(src.texture->usage) :
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
transitionImageLayout(cmdbuffer, {
|
||||
src.image,
|
||||
@@ -172,7 +170,7 @@ void VulkanBlitter::blitFast(VkImageAspectFlags aspect, VkFilter filter,
|
||||
// Determine the desired texture layout for the destination while ensuring that the default
|
||||
// render target is supported, which has no associated texture.
|
||||
const VkImageLayout desiredLayout = dst.texture ?
|
||||
mContext.getTextureLayout(dst.texture->usage) :
|
||||
getDefaultImageLayout(dst.texture->usage) :
|
||||
mContext.currentSurface->getColor().layout;
|
||||
|
||||
transitionImageLayout(cmdbuffer, blitterTransitionHelper({
|
||||
@@ -267,7 +265,7 @@ void VulkanBlitter::blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter,
|
||||
// BEGIN RENDER PASS
|
||||
// -----------------
|
||||
|
||||
const VkImageLayout layout = mContext.getTextureLayout(TextureUsage::DEPTH_ATTACHMENT);
|
||||
const VkImageLayout layout = getDefaultImageLayout(TextureUsage::DEPTH_ATTACHMENT);
|
||||
|
||||
const VulkanFboCache::RenderPassKey rpkey = {
|
||||
.depthLayout = layout,
|
||||
@@ -324,7 +322,7 @@ void VulkanBlitter::blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter,
|
||||
// DRAW THE TRIANGLE
|
||||
// -----------------
|
||||
|
||||
mPipelineCache.bindProgramBundle(mDepthResolveProgram->bundle);
|
||||
mPipelineCache.bindProgram(*mDepthResolveProgram);
|
||||
mPipelineCache.bindPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP);
|
||||
|
||||
mContext.rasterState.depthStencil = {
|
||||
|
||||
@@ -50,6 +50,7 @@ struct VulkanCommandBuffer {
|
||||
uint32_t index = 0;
|
||||
};
|
||||
|
||||
// Allows classes to be notified after a new command buffer has been activated.
|
||||
class CommandBufferObserver {
|
||||
public:
|
||||
virtual void onCommandBuffer(const VulkanCommandBuffer& cmdbuffer) = 0;
|
||||
|
||||
@@ -333,25 +333,6 @@ VkFormat VulkanContext::findSupportedFormat(utils::Slice<VkFormat> candidates,
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
|
||||
VkImageLayout VulkanContext::getTextureLayout(TextureUsage usage) const {
|
||||
// Filament sometimes samples from depth while it is bound to the current render target, (e.g.
|
||||
// SSAO does this while depth writes are disabled) so let's keep it simple and use GENERAL for
|
||||
// all depth textures.
|
||||
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
|
||||
return VK_IMAGE_LAYOUT_GENERAL;
|
||||
}
|
||||
|
||||
// Filament sometimes samples from one miplevel while writing to another level in the same
|
||||
// texture (e.g. bloom does this). Moreover we'd like to avoid lots of expensive layout
|
||||
// transitions. So, keep it simple and use GENERAL for all color-attachable textures.
|
||||
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
|
||||
return VK_IMAGE_LAYOUT_GENERAL;
|
||||
}
|
||||
|
||||
// Finally, the layout for an immutable texture is optimal read-only.
|
||||
return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
}
|
||||
|
||||
void VulkanContext::createEmptyTexture(VulkanStagePool& stagePool) {
|
||||
emptyTexture = new VulkanTexture(*this, SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 1, 1, 1,
|
||||
@@ -359,7 +340,7 @@ void VulkanContext::createEmptyTexture(VulkanStagePool& stagePool) {
|
||||
TextureUsage::SUBPASS_INPUT, stagePool);
|
||||
uint32_t black = 0;
|
||||
PixelBufferDescriptor pbd(&black, 4, PixelDataFormat::RGBA, PixelDataType::UBYTE);
|
||||
emptyTexture->update2DImage(pbd, 1, 1, 0);
|
||||
emptyTexture->updateImage(pbd, 1, 1, 1, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -72,7 +72,6 @@ struct VulkanContext {
|
||||
uint32_t selectMemoryType(uint32_t flags, VkFlags reqs);
|
||||
VkFormat findSupportedFormat(utils::Slice<VkFormat> candidates, VkImageTiling tiling,
|
||||
VkFormatFeatureFlags features);
|
||||
VkImageLayout getTextureLayout(TextureUsage usage) const;
|
||||
void createEmptyTexture(VulkanStagePool& stagePool);
|
||||
|
||||
VkInstance instance;
|
||||
|
||||
@@ -865,8 +865,8 @@ void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescript
|
||||
void VulkanDriver::update2DImage(Handle<HwTexture> th,
|
||||
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
|
||||
PixelBufferDescriptor&& data) {
|
||||
assert_invariant(xoffset == 0 && yoffset == 0 && "Offsets not yet supported.");
|
||||
handle_cast<VulkanTexture*>(th)->update2DImage(data, width, height, level);
|
||||
handle_cast<VulkanTexture*>(th)->updateImage(data, width, height, 1,
|
||||
xoffset, yoffset, 0, level);
|
||||
scheduleDestroy(std::move(data));
|
||||
}
|
||||
|
||||
@@ -879,8 +879,8 @@ void VulkanDriver::update3DImage(
|
||||
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
|
||||
uint32_t width, uint32_t height, uint32_t depth,
|
||||
PixelBufferDescriptor&& data) {
|
||||
assert_invariant(xoffset == 0 && yoffset == 0 && zoffset == 0 && "Offsets not yet supported.");
|
||||
handle_cast<VulkanTexture*>(th)->update3DImage(data, width, height, depth, level);
|
||||
handle_cast<VulkanTexture*>(th)->updateImage(data, width, height, depth,
|
||||
xoffset, yoffset, zoffset, level);
|
||||
scheduleDestroy(std::move(data));
|
||||
}
|
||||
|
||||
@@ -1412,8 +1412,8 @@ void VulkanDriver::stopCapture(int) {
|
||||
void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
|
||||
uint32_t width, uint32_t height, PixelBufferDescriptor&& pbd) {
|
||||
const VkDevice device = mContext.device;
|
||||
const VulkanRenderTarget* srcTarget = handle_cast<VulkanRenderTarget*>(src);
|
||||
const VulkanTexture* srcTexture = srcTarget->getColor(mContext.currentSurface, 0).texture;
|
||||
VulkanRenderTarget* srcTarget = handle_cast<VulkanRenderTarget*>(src);
|
||||
VulkanTexture* srcTexture = srcTarget->getColor(mContext.currentSurface, 0).texture;
|
||||
const VkFormat srcFormat = srcTexture ? srcTexture->getVkFormat() :
|
||||
mContext.currentSurface->surfaceFormat.format;
|
||||
const bool swizzle = srcFormat == VK_FORMAT_B8G8R8A8_UNORM;
|
||||
@@ -1458,11 +1458,10 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
|
||||
|
||||
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
|
||||
|
||||
// TODO: staging should just use the GENERAL layout
|
||||
transitionImageLayout(cmdbuffer, {
|
||||
.image = stagingImage,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.subresources = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
@@ -1502,6 +1501,9 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
|
||||
|
||||
// Transition the source image layout (which might be the swap chain)
|
||||
|
||||
// Since ReadPixels is always issued after at least one render pass, we know that the color
|
||||
// attachment layout is COLOR_ATTACHMENT_OPTIMAL.
|
||||
|
||||
const VkImageSubresourceRange srcRange = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = srcAttachment.level,
|
||||
@@ -1510,11 +1512,10 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
|
||||
.layerCount = 1,
|
||||
};
|
||||
|
||||
// FIXME: the content of the source may be destroyed because of VK_IMAGE_LAYOUT_UNDEFINED
|
||||
VkImage srcImage = srcTarget->getColor(mContext.currentSurface, 0).image;
|
||||
VkImage srcImage = srcAttachment.image;
|
||||
transitionImageLayout(cmdbuffer, {
|
||||
.image = srcImage,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
.subresources = srcRange,
|
||||
.srcStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
||||
@@ -1523,33 +1524,22 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
|
||||
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
|
||||
});
|
||||
|
||||
// Perform the blit.
|
||||
// Perform the into the staging area. At this point we know that the src layout is
|
||||
// TRANSFER_SRC_OPTIMAL and the staging area is GENERAL.
|
||||
|
||||
vkCmdCopyImage(cmdbuffer, srcTarget->getColor(mContext.currentSurface, 0).image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, stagingImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, stagingImage, VK_IMAGE_LAYOUT_GENERAL,
|
||||
1, &imageCopyRegion);
|
||||
|
||||
// Restore the source image layout.
|
||||
// Restore the source image layout back to VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL.
|
||||
|
||||
if (srcTexture || mContext.currentSurface->presentQueue) {
|
||||
const VkImageLayout present = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
// FIXME: the content of image we just blitted into may be destroyed because of VK_IMAGE_LAYOUT_UNDEFINED
|
||||
transitionImageLayout(cmdbuffer, {
|
||||
.image = srcImage,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = srcTexture ? mContext.getTextureLayout(srcTexture->usage) : present,
|
||||
.subresources = srcRange,
|
||||
.srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
});
|
||||
if (UTILS_LIKELY(srcTexture)) {
|
||||
srcTexture->transitionLayout(cmdbuffer, srcRange, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
} else {
|
||||
// FIXME: the content of image we just blitted into may be destroyed because of VK_IMAGE_LAYOUT_UNDEFINED
|
||||
transitionImageLayout(cmdbuffer, {
|
||||
.image = srcImage,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
.subresources = srcRange,
|
||||
.srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
@@ -1558,30 +1548,6 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
|
||||
});
|
||||
}
|
||||
|
||||
// Transition the staging image layout to GENERAL.
|
||||
|
||||
// TODO: why is this not using transitionImageLayout() ?
|
||||
VkImageMemoryBarrier barrier = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = stagingImage,
|
||||
.subresourceRange = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
}
|
||||
};
|
||||
|
||||
vkCmdPipelineBarrier(cmdbuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
|
||||
// TODO: don't flush/wait here -- we should do this asynchronously
|
||||
|
||||
// Flush and wait.
|
||||
@@ -1770,7 +1736,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
}
|
||||
|
||||
// Push state changes to the VulkanPipelineCache instance. This is fast and does not make VK calls.
|
||||
mPipelineCache.bindProgramBundle(program->bundle);
|
||||
mPipelineCache.bindProgram(*program);
|
||||
mPipelineCache.bindRasterState(mContext.rasterState);
|
||||
mPipelineCache.bindPrimitiveTopology(prim.primitiveTopology);
|
||||
mPipelineCache.bindVertexArray(varray);
|
||||
@@ -1779,11 +1745,12 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
// where "SamplerBinding" is the integer in the GLSL, and SamplerGroupBinding is the abstract
|
||||
// Filament concept used to form groups of samplers.
|
||||
|
||||
VkDescriptorImageInfo samplers[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
|
||||
VkDescriptorImageInfo iInfo[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
|
||||
|
||||
for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::BINDING_COUNT; samplerGroupIdx++) {
|
||||
const auto& samplerGroup = program->samplerGroupInfo[samplerGroupIdx];
|
||||
if (samplerGroup.empty()) {
|
||||
const auto& samplers = samplerGroup.samplers;
|
||||
if (samplers.empty()) {
|
||||
continue;
|
||||
}
|
||||
VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupIdx];
|
||||
@@ -1791,9 +1758,9 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
continue;
|
||||
}
|
||||
SamplerGroup* sb = vksb->sb.get();
|
||||
assert_invariant(sb->getSize() == samplerGroup.size());
|
||||
assert_invariant(sb->getSize() == samplers.size());
|
||||
size_t samplerIdx = 0;
|
||||
for (const auto& sampler : samplerGroup) {
|
||||
for (const auto& sampler : samplers) {
|
||||
size_t bindingPoint = sampler.binding;
|
||||
const SamplerGroup::Sampler* boundSampler = sb->getSamplers() + samplerIdx;
|
||||
samplerIdx++;
|
||||
@@ -1821,19 +1788,19 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
const SamplerParams& samplerParams = boundSampler->s;
|
||||
VkSampler vksampler = mSamplerCache.getSampler(samplerParams);
|
||||
|
||||
samplers[bindingPoint] = {
|
||||
iInfo[bindingPoint] = {
|
||||
.sampler = vksampler,
|
||||
.imageView = texture->getPrimaryImageView(),
|
||||
.imageLayout = mContext.getTextureLayout(texture->usage)
|
||||
.imageLayout = getDefaultImageLayout(texture->usage)
|
||||
};
|
||||
|
||||
if (mContext.currentRenderPass.depthFeedback == texture) {
|
||||
samplers[bindingPoint].imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
|
||||
iInfo[bindingPoint].imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mPipelineCache.bindSamplers(samplers);
|
||||
mPipelineCache.bindSamplers(iInfo);
|
||||
|
||||
// Bind new descriptor sets if they need to change.
|
||||
// If descriptor set allocation failed, skip the draw call and bail. No need to emit an error
|
||||
@@ -1859,7 +1826,11 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
mPipelineCache.bindScissor(cmdbuffer, scissor);
|
||||
|
||||
// Bind a new pipeline if the pipeline state changed.
|
||||
mPipelineCache.bindPipeline(cmdbuffer);
|
||||
// If allocation failed, skip the draw call and bail. We do not emit an error since the
|
||||
// validation layer will already do so.
|
||||
if (!mPipelineCache.bindPipeline(cmdbuffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Next bind the vertex buffers and index buffer. One potential performance improvement is to
|
||||
// avoid rebinding these if they are already bound, but since we do not (yet) support subranges
|
||||
|
||||
@@ -138,16 +138,13 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
// In Vulkan, the subpass desc specifies the layout to transition to at the start of the render
|
||||
// pass, and the attachment description specifies the layout to transition to at the end.
|
||||
// However we use render passes to cause layout transitions only when drawing directly into the
|
||||
// swap chain. We keep our offscreen images in GENERAL layout, which is simple and prevents
|
||||
// thrashing the layout. Note that pipeline barriers are more powerful than render passes for
|
||||
// performing layout transitions, because they allow for per-miplevel transitions.
|
||||
// swap chain.
|
||||
const bool discard = any(config.discardStart & TargetBufferFlags::COLOR);
|
||||
struct { VkImageLayout subpass, initial, final; } colorLayouts[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT];
|
||||
if (isSwapChain) {
|
||||
colorLayouts[0].subpass = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
// It is legal to always use UNDEFINED for "initial", but we wish to avoid warnings
|
||||
// when the load op is LOAD.
|
||||
// Specifying UNDEFINED for "initial" can discard the existing data.
|
||||
colorLayouts[0].initial = discard ? VK_IMAGE_LAYOUT_UNDEFINED :
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ static VulkanAttachment createAttachment(VulkanContext& context, VulkanAttachmen
|
||||
.view = {},
|
||||
.memory = {},
|
||||
.texture = spec.texture,
|
||||
.layout = context.getTextureLayout(spec.texture->usage),
|
||||
.layout = spec.texture->getVkLayout(spec.layer, spec.level),
|
||||
.level = spec.level,
|
||||
.layer = spec.layer
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
* Copyright (C) 2022 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.
|
||||
@@ -41,6 +41,8 @@ VK_DEFINE_HANDLE(VmaPool)
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
|
||||
struct VulkanProgram;
|
||||
|
||||
// VulkanPipelineCache manages a cache of descriptor sets and pipelines.
|
||||
//
|
||||
// Please note the following limitations:
|
||||
@@ -63,6 +65,7 @@ public:
|
||||
|
||||
// Three descriptor set layouts: uniforms, combined image samplers, and input attachments.
|
||||
static constexpr uint32_t DESCRIPTOR_TYPE_COUNT = 3;
|
||||
static constexpr uint32_t INITIAL_DESCRIPTOR_SET_POOL_SIZE = 512;
|
||||
|
||||
// The VertexArray POD is an array of buffer targets and an array of attributes that refer to
|
||||
// those targets. It does not include any references to actual buffers, so you can think of it
|
||||
@@ -139,13 +142,14 @@ public:
|
||||
bool bindDescriptors(VkCommandBuffer cmdbuffer) noexcept;
|
||||
|
||||
// Creates a new pipeline if necessary and binds it using vkCmdBindPipeline.
|
||||
void bindPipeline(VkCommandBuffer cmdbuffer) noexcept;
|
||||
// Returns false if an error occurred.
|
||||
bool bindPipeline(VkCommandBuffer cmdbuffer) noexcept;
|
||||
|
||||
// Sets up a new scissor rect if it has been dirtied.
|
||||
// Sets up a new scissor rectangle if it has been dirtied.
|
||||
void bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept;
|
||||
|
||||
// Each of the following methods are fast and do not make Vulkan calls.
|
||||
void bindProgramBundle(const ProgramBundle& bundle) noexcept;
|
||||
void bindProgram(const VulkanProgram& program) noexcept;
|
||||
void bindRasterState(const RasterState& rasterState) noexcept;
|
||||
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept;
|
||||
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
|
||||
@@ -179,14 +183,33 @@ public:
|
||||
void onCommandBuffer(const VulkanCommandBuffer& cmdbuffer) override;
|
||||
|
||||
// Injects a dummy texture that can be used to clear out old descriptor sets.
|
||||
void setDummyTexture(VkImageView imageView) { mDummyImageView = imageView; }
|
||||
void setDummyTexture(VkImageView imageView) {
|
||||
mDummySamplerInfo.imageView = imageView;
|
||||
mDummyTargetInfo.imageView = imageView;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr uint32_t ALL_COMMAND_BUFFERS = (1 << VK_MAX_COMMAND_BUFFERS) - 1;
|
||||
|
||||
// PIPELINE LAYOUT CACHE KEY
|
||||
// -------------------------
|
||||
|
||||
// The cache key for pipeline layouts represents 32 samplers, each with 2 bits (one for each
|
||||
// shader stage).
|
||||
using PipelineLayoutKey = utils::bitset64;
|
||||
|
||||
struct PipelineLayoutKeyHashFn {
|
||||
size_t operator()(const PipelineLayoutKey& key) const;
|
||||
};
|
||||
|
||||
struct PipelineLayoutKeyEqual {
|
||||
bool operator()(const PipelineLayoutKey& k1, const PipelineLayoutKey& k2) const;
|
||||
};
|
||||
|
||||
// PIPELINE CACHE KEY
|
||||
// ------------------
|
||||
|
||||
// The pipeline key is a POD that represents all currently bound states that form the immutable
|
||||
// VkPipeline object. We apply a hash function to its contents only if has been mutated since
|
||||
// the previous call to getOrCreatePipeline.
|
||||
// VkPipeline object.
|
||||
struct PipelineKey {
|
||||
VkShaderModule shaders[SHADER_MODULE_COUNT]; // 16 bytes
|
||||
RasterState rasterState; // 124 bytes
|
||||
@@ -218,17 +241,36 @@ private:
|
||||
bool operator()(const PipelineKey& k1, const PipelineKey& k2) const;
|
||||
};
|
||||
|
||||
// The descriptor key is a POD that represents all currently bound states that go into the
|
||||
// descriptor set. We apply a hash function to its contents only if has been mutated since
|
||||
// the previous call to getOrCreateDescriptors.
|
||||
// DESCRIPTOR SET CACHE KEY
|
||||
// ------------------------
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
// Equivalent to VkDescriptorImageInfo but with explicit padding.
|
||||
struct UTILS_PACKED DescriptorImageInfo {
|
||||
DescriptorImageInfo& operator=(VkDescriptorImageInfo& that) {
|
||||
sampler = that.sampler;
|
||||
imageView = that.imageView;
|
||||
imageLayout = that.imageLayout;
|
||||
padding = 0;
|
||||
return *this;
|
||||
}
|
||||
operator VkDescriptorImageInfo() const { return { sampler, imageView, imageLayout }; }
|
||||
VkSampler sampler;
|
||||
VkImageView imageView;
|
||||
VkImageLayout imageLayout;
|
||||
uint32_t padding;
|
||||
};
|
||||
|
||||
// Represents all the Vulkan state that comprises a bound descriptor set.
|
||||
struct UTILS_PACKED DescriptorKey {
|
||||
VkBuffer uniformBuffers[UBUFFER_BINDING_COUNT];
|
||||
VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT];
|
||||
VkDescriptorImageInfo inputAttachments[TARGET_BINDING_COUNT];
|
||||
DescriptorImageInfo samplers[SAMPLER_BINDING_COUNT];
|
||||
DescriptorImageInfo inputAttachments[TARGET_BINDING_COUNT];
|
||||
VkDeviceSize uniformBufferOffsets[UBUFFER_BINDING_COUNT];
|
||||
VkDeviceSize uniformBufferSizes[UBUFFER_BINDING_COUNT];
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(std::is_trivially_copyable<DescriptorKey>::value, "DescriptorKey must be a POD.");
|
||||
@@ -239,84 +281,102 @@ private:
|
||||
bool operator()(const DescriptorKey& k1, const DescriptorKey& k2) const;
|
||||
};
|
||||
|
||||
// Represents a group of descriptor sets that are bound simultaneously.
|
||||
struct DescriptorBundle {
|
||||
VkDescriptorSet handles[DESCRIPTOR_TYPE_COUNT];
|
||||
utils::bitset32 commandBuffers;
|
||||
// CACHE ENTRY STRUCTS
|
||||
// -------------------
|
||||
|
||||
// The timestamp associated with a given cache entry represents time as a count of flush
|
||||
// events since the cache was constructed. If any cache entry was most recently used over
|
||||
// VK_MAX_PIPELINE_AGE flushes in the past, then we can be sure that it is no longer
|
||||
// being used by the GPU, and is therefore safe to destroy or reclaim.
|
||||
using Timestamp = uint64_t;
|
||||
Timestamp mCurrentTime = 0;
|
||||
|
||||
// The descriptor set cache entry is a group of descriptor sets that are bound simultaneously.
|
||||
struct DescriptorCacheEntry {
|
||||
std::array<VkDescriptorSet, DESCRIPTOR_TYPE_COUNT> handles;
|
||||
Timestamp lastUsed;
|
||||
PipelineLayoutKey pipelineLayout;
|
||||
};
|
||||
|
||||
struct PipelineVal {
|
||||
struct PipelineCacheEntry {
|
||||
VkPipeline handle;
|
||||
|
||||
// The "age" of a pipeline cache entry is the number of command buffer flush events that
|
||||
// have occurred since it was last used in a command buffer. This is used for LRU caching,
|
||||
// which is a crucial feature because VkPipeline construction is very slow.
|
||||
uint32_t age;
|
||||
Timestamp lastUsed;
|
||||
};
|
||||
|
||||
using PipelineMap = tsl::robin_map<PipelineKey, PipelineVal, PipelineHashFn, PipelineEqual>;
|
||||
using DescriptorMap = tsl::robin_map<DescriptorKey, DescriptorBundle, DescHashFn, DescEqual>;
|
||||
struct PipelineLayoutCacheEntry {
|
||||
VkPipelineLayout handle;
|
||||
Timestamp lastUsed;
|
||||
|
||||
struct CmdBufferState {
|
||||
PipelineVal* currentPipeline = nullptr;
|
||||
DescriptorBundle* currentDescriptorBundle = nullptr;
|
||||
VkRect2D scissor = {};
|
||||
std::array<VkDescriptorSetLayout, DESCRIPTOR_TYPE_COUNT> descriptorSetLayouts;
|
||||
|
||||
// Each pipeline layout has 3 arenas of unused descriptors (one for each binding type).
|
||||
//
|
||||
// The difference between the "arenas" and the "pool" are as follows.
|
||||
//
|
||||
// - The "pool" is a single, centralized factory for all descriptors (VkDescriptorPool).
|
||||
//
|
||||
// - Each "arena" is a set of unused (but alive) descriptors that can only be used with a
|
||||
// specific pipeline layout and a specific binding type. We manually manage each arena.
|
||||
// The arenas are created in an empty state, and they are gradually populated as new
|
||||
// descriptors are reclaimed over time. This is quite different from the pool, which is
|
||||
// given a fixed size when it is constructed.
|
||||
//
|
||||
std::array<std::vector<VkDescriptorSet>, DESCRIPTOR_TYPE_COUNT> descriptorSetArenas;
|
||||
};
|
||||
|
||||
// If bind is set to true, vkCmdBindDescriptorSets is required.
|
||||
// If overflow is set to true, a descriptor set allocation error has occurred.
|
||||
void getOrCreateDescriptors(VkDescriptorSet descriptors[DESCRIPTOR_TYPE_COUNT],
|
||||
bool* bind, bool* overflow) noexcept;
|
||||
// CACHE CONTAINERS
|
||||
// ----------------
|
||||
|
||||
// Returns true if any pipeline bindings have changed. (i.e., vkCmdBindPipeline is required)
|
||||
bool getOrCreatePipeline(VkPipeline* pipeline) noexcept;
|
||||
using PipelineLayoutMap = tsl::robin_map<PipelineLayoutKey , PipelineLayoutCacheEntry,
|
||||
PipelineLayoutKeyHashFn, PipelineLayoutKeyEqual>;
|
||||
using PipelineMap = tsl::robin_map<PipelineKey, PipelineCacheEntry,
|
||||
PipelineHashFn, PipelineEqual>;
|
||||
using DescriptorMap = tsl::robin_map<DescriptorKey, DescriptorCacheEntry,
|
||||
DescHashFn, DescEqual>;
|
||||
|
||||
void createLayoutsAndDescriptors() noexcept;
|
||||
PipelineLayoutMap mPipelineLayouts;
|
||||
PipelineMap mPipelines;
|
||||
DescriptorMap mDescriptorSets;
|
||||
|
||||
// These helpers all return unstable pointers that should not be stored.
|
||||
DescriptorCacheEntry* createDescriptorSets() noexcept;
|
||||
PipelineCacheEntry* createPipeline() noexcept;
|
||||
PipelineLayoutCacheEntry* getOrCreatePipelineLayout() noexcept;
|
||||
|
||||
// Misc helper methods.
|
||||
void destroyLayoutsAndDescriptors() noexcept;
|
||||
void markDirtyPipeline() noexcept { mDirtyPipeline.setValue(ALL_COMMAND_BUFFERS); }
|
||||
void markDirtyDescriptor() noexcept { mDirtyDescriptor.setValue(ALL_COMMAND_BUFFERS); }
|
||||
VkDescriptorPool createDescriptorPool(uint32_t size) const;
|
||||
void growDescriptorPool() noexcept;
|
||||
|
||||
// Immutable state.
|
||||
VkDevice mDevice = VK_NULL_HANDLE;
|
||||
VmaAllocator mAllocator = VK_NULL_HANDLE;
|
||||
const RasterState mDefaultRasterState;
|
||||
|
||||
// Current bindings are divided into two "keys" which are composed of a mix of actual values
|
||||
// (e.g., blending is OFF) and weak references to Vulkan objects (e.g., shader programs and
|
||||
// uniform buffers).
|
||||
PipelineKey mPipelineKey;
|
||||
DescriptorKey mDescriptorKey;
|
||||
// Current requirements for the pipeline layout, pipeline, and descriptor sets.
|
||||
PipelineLayoutKey mLayoutRequirements = {};
|
||||
PipelineKey mPipelineRequirements = {};
|
||||
DescriptorKey mDescriptorRequirements = {};
|
||||
|
||||
// Each command buffer has associated state, including the bindings set up by vkCmdBindPipeline
|
||||
// and vkCmdBindDescriptorSets.
|
||||
CmdBufferState mCmdBufferState[VK_MAX_COMMAND_BUFFERS];
|
||||
// Current bindings for the pipeline layout, pipeline, and descriptor sets.
|
||||
PipelineLayoutKey mBoundLayout = {};
|
||||
PipelineKey mBoundPipeline = {};
|
||||
DescriptorKey mBoundDescriptor = {};
|
||||
|
||||
// One dirty bit per command buffer, stored in a bitset to permit fast "set all dirty bits". If
|
||||
// a dirty flag is set for the current command buffer, then a new pipeline or descriptor set
|
||||
// needs to be retrieved from the cache or created.
|
||||
utils::bitset32 mDirtyPipeline;
|
||||
utils::bitset32 mDirtyDescriptor;
|
||||
|
||||
VkDescriptorSetLayout mDescriptorSetLayouts[DESCRIPTOR_TYPE_COUNT] = {};
|
||||
|
||||
std::vector<VkDescriptorSet> mDescriptorSetArena[DESCRIPTOR_TYPE_COUNT];
|
||||
|
||||
VkPipelineLayout mPipelineLayout = VK_NULL_HANDLE;
|
||||
PipelineMap mPipelines;
|
||||
DescriptorMap mDescriptorBundles;
|
||||
uint32_t mCmdBufferIndex = 0;
|
||||
// Current state for scissoring.
|
||||
VkRect2D mCurrentScissor = {};
|
||||
|
||||
// The descriptor set pool starts out with a decent number of descriptor sets. The cache can
|
||||
// grow the pool by re-creating it with a larger size. See growDescriptorPool().
|
||||
VkDescriptorPool mDescriptorPool;
|
||||
uint32_t mDescriptorPoolSize = 500;
|
||||
uint32_t mDescriptorPoolSize = INITIAL_DESCRIPTOR_SET_POOL_SIZE;
|
||||
|
||||
// After a growth event (i.e. when the VkDescriptorPool is replaced with a bigger version), all
|
||||
// currently used descriptors are moved into the "extinct" sets so that they can be safely
|
||||
// destroyed a few frames later.
|
||||
std::vector<VkDescriptorPool> mExtinctDescriptorPools;
|
||||
std::vector<DescriptorBundle> mExtinctDescriptorBundles;
|
||||
std::vector<DescriptorCacheEntry> mExtinctDescriptorBundles;
|
||||
|
||||
VkImageView mDummyImageView = VK_NULL_HANDLE;
|
||||
VkDescriptorBufferInfo mDummyBufferInfo = {};
|
||||
VkWriteDescriptorSet mDummyBufferWriteInfo = {};
|
||||
VkDescriptorImageInfo mDummySamplerInfo = {};
|
||||
|
||||
@@ -289,6 +289,7 @@ void VulkanSwapChain::makePresentable() {
|
||||
return;
|
||||
}
|
||||
VulkanAttachment& swapContext = color[currentSwapIndex];
|
||||
assert_invariant(swapContext.layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||
VkImageMemoryBarrier barrier {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
@@ -311,7 +312,7 @@ void VulkanSwapChain::makePresentable() {
|
||||
.oldLayout = firstRenderPass ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
#endif
|
||||
|
||||
.newLayout = swapContext.layout,
|
||||
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = swapContext.image,
|
||||
|
||||
@@ -38,6 +38,11 @@ VulkanTexture::VulkanTexture(VulkanContext& context, SamplerType target, uint8_t
|
||||
mVkFormat(tformat == TextureFormat::DEPTH24 ? context.finalDepthFormat :
|
||||
backend::getVkFormat(tformat)),
|
||||
|
||||
mAspect(any(usage & TextureUsage::DEPTH_ATTACHMENT) ? VK_IMAGE_ASPECT_DEPTH_BIT :
|
||||
VK_IMAGE_ASPECT_COLOR_BIT),
|
||||
|
||||
mViewType(getImageViewType(target)),
|
||||
|
||||
mSwizzle(swizzle), mContext(context), mStagePool(stagePool) {
|
||||
|
||||
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
|
||||
@@ -151,47 +156,33 @@ VulkanTexture::VulkanTexture(VulkanContext& context, SamplerType target, uint8_t
|
||||
error = vkBindImageMemory(context.device, mTextureImage, mTextureImageMemory, 0);
|
||||
ASSERT_POSTCONDITION(!error, "Unable to bind image.");
|
||||
|
||||
mAspect = any(usage & TextureUsage::DEPTH_ATTACHMENT) ? VK_IMAGE_ASPECT_DEPTH_BIT :
|
||||
VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
|
||||
// Spec out the "primary" VkImageView that shaders use to sample from the image.
|
||||
mPrimaryViewRange.aspectMask = mAspect;
|
||||
mPrimaryViewRange.baseMipLevel = 0;
|
||||
mPrimaryViewRange.levelCount = levels;
|
||||
mPrimaryViewRange.baseArrayLayer = 0;
|
||||
if (target == SamplerType::SAMPLER_CUBEMAP) {
|
||||
mViewType = VK_IMAGE_VIEW_TYPE_CUBE;
|
||||
mPrimaryViewRange.layerCount = 6;
|
||||
} else if (target == SamplerType::SAMPLER_2D_ARRAY) {
|
||||
mViewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
mPrimaryViewRange.layerCount = depth;
|
||||
} else if (target == SamplerType::SAMPLER_3D) {
|
||||
mViewType = VK_IMAGE_VIEW_TYPE_3D;
|
||||
mPrimaryViewRange.layerCount = 1;
|
||||
} else {
|
||||
mViewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
mPrimaryViewRange.layerCount = 1;
|
||||
}
|
||||
|
||||
// Go ahead and create the primary image view, no need to do it lazily.
|
||||
getImageView(mPrimaryViewRange);
|
||||
|
||||
// Transition the layout of each image slice.
|
||||
// TODO: The potentially redundant transition for SAMPLEABLE images.
|
||||
if (any(usage & (TextureUsage::COLOR_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT | TextureUsage::SAMPLEABLE))) {
|
||||
// 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 (any(usage & (TextureUsage::COLOR_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT))) {
|
||||
const uint32_t layers = mPrimaryViewRange.layerCount;
|
||||
transitionImageLayout(mContext.commands->get().cmdbuffer, textureTransitionHelper({
|
||||
.image = mTextureImage,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = mContext.getTextureLayout(usage),
|
||||
.subresources = {
|
||||
mAspect,
|
||||
0,
|
||||
levels,
|
||||
0,
|
||||
layers
|
||||
}
|
||||
}));
|
||||
VkImageSubresourceRange range = { mAspect, 0, levels, 0, layers };
|
||||
VkImageLayout layout = getDefaultImageLayout(usage);
|
||||
VkCommandBuffer commands = mContext.commands->get().cmdbuffer;
|
||||
transitionLayout(commands, range, layout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,13 +195,8 @@ VulkanTexture::~VulkanTexture() {
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanTexture::update2DImage(const PixelBufferDescriptor& data, uint32_t width,
|
||||
uint32_t height, int miplevel) {
|
||||
update3DImage(std::move(data), width, height, 1, miplevel);
|
||||
}
|
||||
|
||||
void VulkanTexture::update3DImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
|
||||
uint32_t depth, int miplevel) {
|
||||
void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel) {
|
||||
assert_invariant(width <= this->width && height <= this->height && depth <= this->depth);
|
||||
const PixelBufferDescriptor* hostData = &data;
|
||||
PixelBufferDescriptor reshapedData;
|
||||
@@ -222,60 +208,67 @@ void VulkanTexture::update3DImage(const PixelBufferDescriptor& data, uint32_t wi
|
||||
hostData = &reshapedData;
|
||||
}
|
||||
|
||||
// If format conversion is both required and supported, use vkCmdBlitImage. Otherwise, use
|
||||
// vkCmdCopyBufferToImage.
|
||||
|
||||
// If format conversion is both required and supported, use vkCmdBlitImage.
|
||||
const VkFormat hostFormat = backend::getVkFormat(hostData->format, hostData->type);
|
||||
const VkFormat deviceFormat = getVkFormatLinear(mVkFormat);
|
||||
|
||||
if (hostFormat != deviceFormat && hostFormat != VK_FORMAT_UNDEFINED) {
|
||||
updateWithBlitImage(*hostData, width, height, depth, miplevel);
|
||||
} else {
|
||||
updateWithCopyBuffer(*hostData, width, height, depth, miplevel);
|
||||
assert_invariant(xoffset == 0 && yoffset == 0 && zoffset == 0 &&
|
||||
"Offsets not yet supported when format conversion is required.");
|
||||
updateImageWithBlit(*hostData, width, height, depth, miplevel);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanTexture::updateWithCopyBuffer(const PixelBufferDescriptor& hostData, uint32_t width,
|
||||
uint32_t height, uint32_t depth, uint32_t miplevel) {
|
||||
// Otherwise, use vkCmdCopyBufferToImage.
|
||||
void* mapped = nullptr;
|
||||
VulkanStage const* stage = mStagePool.acquireStage(hostData.size);
|
||||
VulkanStage const* stage = mStagePool.acquireStage(hostData->size);
|
||||
vmaMapMemory(mContext.allocator, stage->memory, &mapped);
|
||||
memcpy(mapped, hostData.buffer, hostData.size);
|
||||
memcpy(mapped, hostData->buffer, hostData->size);
|
||||
vmaUnmapMemory(mContext.allocator, stage->memory);
|
||||
vmaFlushAllocation(mContext.allocator, stage->memory, 0, hostData.size);
|
||||
vmaFlushAllocation(mContext.allocator, stage->memory, 0, hostData->size);
|
||||
|
||||
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
|
||||
|
||||
// We can't blindly use LAYOUT_UNDEFINED because it may destroy the data, and because
|
||||
// we're potentially updating only a sub-region it would be a problem.
|
||||
VkImageLayout textureLayout = mContext.getTextureLayout(usage);
|
||||
transitionImageLayout(cmdbuffer, textureTransitionHelper({
|
||||
.image = mTextureImage,
|
||||
.oldLayout = textureLayout,
|
||||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.subresources = {
|
||||
mAspect,
|
||||
miplevel, 1,
|
||||
0,1
|
||||
}
|
||||
}));
|
||||
VkBufferImageCopy copyRegion = {
|
||||
.bufferOffset = {},
|
||||
.bufferRowLength = {},
|
||||
.bufferImageHeight = {},
|
||||
.imageSubresource = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.mipLevel = miplevel,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
},
|
||||
.imageOffset = { int32_t(xoffset), int32_t(yoffset), int32_t(zoffset) },
|
||||
.imageExtent = { width, height, depth }
|
||||
};
|
||||
|
||||
copyBufferToImage(cmdbuffer, stage->buffer, mTextureImage, width, height, depth,
|
||||
nullptr, miplevel);
|
||||
VkImageSubresourceRange transitionRange = {
|
||||
.aspectMask = mAspect,
|
||||
.baseMipLevel = miplevel,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
};
|
||||
|
||||
transitionImageLayout(cmdbuffer, textureTransitionHelper({
|
||||
.image = mTextureImage,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.newLayout = textureLayout,
|
||||
.subresources = {
|
||||
mAspect,
|
||||
miplevel, 1,
|
||||
0,1
|
||||
}
|
||||
}));
|
||||
// Vulkan specifies subregions for 3D textures differently than from 2D arrays.
|
||||
if (target == SamplerType::SAMPLER_2D_ARRAY) {
|
||||
copyRegion.imageOffset.z = 0;
|
||||
copyRegion.imageExtent.depth = 1;
|
||||
copyRegion.imageSubresource.baseArrayLayer = zoffset;
|
||||
copyRegion.imageSubresource.layerCount = depth;
|
||||
transitionRange.baseArrayLayer = zoffset;
|
||||
transitionRange.layerCount = depth;
|
||||
}
|
||||
|
||||
transitionLayout(cmdbuffer, transitionRange, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
|
||||
vkCmdCopyBufferToImage(cmdbuffer, stage->buffer, mTextureImage,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region);
|
||||
|
||||
transitionLayout(cmdbuffer, transitionRange, getDefaultImageLayout(usage));
|
||||
}
|
||||
|
||||
void VulkanTexture::updateWithBlitImage(const PixelBufferDescriptor& hostData, uint32_t width,
|
||||
void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width,
|
||||
uint32_t height, uint32_t depth, uint32_t miplevel) {
|
||||
void* mapped = nullptr;
|
||||
VulkanStageImage const* stage = mStagePool.acquireImage(
|
||||
@@ -299,25 +292,14 @@ void VulkanTexture::updateWithBlitImage(const PixelBufferDescriptor& hostData, u
|
||||
.dstOffsets = { rect[0], rect[1] }
|
||||
}};
|
||||
|
||||
// We can't blindly use LAYOUT_UNDEFINED because it may destroy the data, and because
|
||||
// we're potentially updating only a sub-region it would be a problem.
|
||||
VkImageLayout textureLayout = mContext.getTextureLayout(usage);
|
||||
transitionImageLayout(cmdbuffer, textureTransitionHelper({
|
||||
.image = mTextureImage,
|
||||
.oldLayout = textureLayout,
|
||||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.subresources = { mAspect, miplevel, 1, 0, 1 }
|
||||
}));
|
||||
const VkImageSubresourceRange range = { mAspect, miplevel, 1, 0, 1 };
|
||||
|
||||
transitionLayout(cmdbuffer, range, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
|
||||
vkCmdBlitImage(cmdbuffer, stage->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, mTextureImage,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, blitRegions, VK_FILTER_NEAREST);
|
||||
|
||||
transitionImageLayout(cmdbuffer, textureTransitionHelper({
|
||||
.image = mTextureImage,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.newLayout = textureLayout,
|
||||
.subresources = { mAspect, miplevel, 1, 0, 1 }
|
||||
}));
|
||||
transitionLayout(cmdbuffer, range, getDefaultImageLayout(usage));
|
||||
}
|
||||
|
||||
void VulkanTexture::updateCubeImage(const PixelBufferDescriptor& data,
|
||||
@@ -340,29 +322,30 @@ void VulkanTexture::updateCubeImage(const PixelBufferDescriptor& data,
|
||||
vmaUnmapMemory(mContext.allocator, stage->memory);
|
||||
vmaFlushAllocation(mContext.allocator, stage->memory, 0, numDstBytes);
|
||||
|
||||
|
||||
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
|
||||
const uint32_t width = std::max(1u, this->width >> miplevel);
|
||||
const uint32_t height = std::max(1u, this->height >> miplevel);
|
||||
|
||||
// We can use LAYOUT_UNDEFINED here because we're always replacing the whole data, so it
|
||||
// doesn't matter if the previous data is lost.
|
||||
transitionImageLayout(cmdbuffer, textureTransitionHelper({
|
||||
.image = mTextureImage,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.subresources = { mAspect, miplevel, 1, 0, 6 }
|
||||
}));
|
||||
const VkImageSubresourceRange range = { mAspect, miplevel, 1, 0, 6 };
|
||||
const VkImageLayout textureLayout = getDefaultImageLayout(usage);
|
||||
|
||||
copyBufferToImage(cmdbuffer, stage->buffer, mTextureImage, width, height, 1,
|
||||
&faceOffsets, miplevel);
|
||||
transitionLayout(cmdbuffer, range, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
|
||||
transitionImageLayout(cmdbuffer, textureTransitionHelper({
|
||||
.image = mTextureImage,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.newLayout = mContext.getTextureLayout(usage),
|
||||
.subresources = { mAspect, miplevel, 1, 0, 6 }
|
||||
}));
|
||||
VkBufferImageCopy regions[6] = {{}};
|
||||
VkExtent3D extent { width, height, 1 };
|
||||
for (size_t face = 0; face < 6; face++) {
|
||||
auto& region = regions[face];
|
||||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.imageSubresource.baseArrayLayer = face;
|
||||
region.imageSubresource.layerCount = 1;
|
||||
region.imageSubresource.mipLevel = miplevel;
|
||||
region.imageExtent = extent;
|
||||
region.bufferOffset = faceOffsets.offsets[face];
|
||||
}
|
||||
vkCmdCopyBufferToImage(cmdbuffer, stage->buffer, mTextureImage,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 6, regions);
|
||||
|
||||
transitionLayout(cmdbuffer, range, textureLayout);
|
||||
}
|
||||
|
||||
void VulkanTexture::setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel) {
|
||||
@@ -374,16 +357,13 @@ void VulkanTexture::setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel)
|
||||
|
||||
VkImageView VulkanTexture::getAttachmentView(int singleLevel, int singleLayer,
|
||||
VkImageAspectFlags aspect) {
|
||||
return getImageView({
|
||||
VkImageSubresourceRange range = {
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = uint32_t(singleLevel),
|
||||
.levelCount = uint32_t(1),
|
||||
.baseArrayLayer = uint32_t(singleLayer),
|
||||
.layerCount = uint32_t(1),
|
||||
}, true);
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, bool isAttachment) {
|
||||
};
|
||||
auto iter = mCachedImageViews.find(range);
|
||||
if (iter != mCachedImageViews.end()) {
|
||||
return iter->second;
|
||||
@@ -393,9 +373,9 @@ VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, bool isAt
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.image = mTextureImage,
|
||||
.viewType = isAttachment ? VK_IMAGE_VIEW_TYPE_2D : mViewType,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = mVkFormat,
|
||||
.components = isAttachment ? (VkComponentMapping{}) : mSwizzle,
|
||||
.components = VkComponentMapping{},
|
||||
.subresourceRange = range
|
||||
};
|
||||
VkImageView imageView;
|
||||
@@ -404,35 +384,75 @@ VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, bool isAt
|
||||
return imageView;
|
||||
}
|
||||
|
||||
void VulkanTexture::copyBufferToImage(VkCommandBuffer cmd, VkBuffer buffer, VkImage image,
|
||||
uint32_t width, uint32_t height, uint32_t depth, FaceOffsets const* faceOffsets, uint32_t miplevel) {
|
||||
VkExtent3D extent { width, height, depth };
|
||||
if (target == SamplerType::SAMPLER_CUBEMAP) {
|
||||
assert_invariant(faceOffsets);
|
||||
VkBufferImageCopy regions[6] = {{}};
|
||||
for (size_t face = 0; face < 6; face++) {
|
||||
auto& region = regions[face];
|
||||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.imageSubresource.baseArrayLayer = face;
|
||||
region.imageSubresource.layerCount = 1;
|
||||
region.imageSubresource.mipLevel = miplevel;
|
||||
region.imageExtent = extent;
|
||||
region.bufferOffset = faceOffsets->offsets[face];
|
||||
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range) {
|
||||
auto iter = mCachedImageViews.find(range);
|
||||
if (iter != mCachedImageViews.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
VkImageViewCreateInfo viewInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.image = mTextureImage,
|
||||
.viewType = mViewType,
|
||||
.format = mVkFormat,
|
||||
.components = mSwizzle,
|
||||
.subresourceRange = range
|
||||
};
|
||||
VkImageView imageView;
|
||||
vkCreateImageView(mContext.device, &viewInfo, VKALLOC, &imageView);
|
||||
mCachedImageViews.emplace(range, imageView);
|
||||
return imageView;
|
||||
}
|
||||
|
||||
void VulkanTexture::transitionLayout(VkCommandBuffer commands, const VkImageSubresourceRange& range,
|
||||
VkImageLayout newLayout) {
|
||||
// In debug builds, ensure that all subresources in the given range have the same layout.
|
||||
// It's easier to catch a mistake here than with validation, which waits until submission time.
|
||||
VkImageLayout oldLayout = getVkLayout(range.baseArrayLayer, range.baseMipLevel);
|
||||
#ifndef NDEBUG
|
||||
if (oldLayout != VK_IMAGE_LAYOUT_UNDEFINED) {
|
||||
for (uint32_t layer = 0; layer < range.layerCount; ++layer) {
|
||||
for (uint32_t level = 0; level < range.levelCount; ++level) {
|
||||
assert_invariant(getVkLayout(layer + range.baseArrayLayer,
|
||||
level + range.baseMipLevel) == oldLayout);
|
||||
}
|
||||
}
|
||||
vkCmdCopyBufferToImage(cmd, buffer, image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 6, regions);
|
||||
return;
|
||||
}
|
||||
VkBufferImageCopy region = {};
|
||||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.imageSubresource.mipLevel = miplevel;
|
||||
region.imageSubresource.layerCount = 1;
|
||||
region.imageExtent = extent;
|
||||
if (target == SamplerType::SAMPLER_2D_ARRAY) {
|
||||
region.imageExtent.depth = 1;
|
||||
region.imageSubresource.layerCount = depth;
|
||||
#endif
|
||||
|
||||
transitionImageLayout(commands, textureTransitionHelper({
|
||||
.image = mTextureImage,
|
||||
.oldLayout = oldLayout,
|
||||
.newLayout = newLayout,
|
||||
.subresources = range,
|
||||
}));
|
||||
|
||||
const uint32_t first_layer = range.baseArrayLayer;
|
||||
const uint32_t last_layer = first_layer + range.layerCount;
|
||||
const uint32_t first_level = range.baseMipLevel;
|
||||
const uint32_t last_level = first_level + range.levelCount;
|
||||
if (newLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
|
||||
for (uint32_t layer = first_layer; layer < last_layer; ++layer) {
|
||||
const uint32_t first = (layer << 16) | first_level;
|
||||
const uint32_t last = (layer << 16) | last_level;
|
||||
mSubresourceLayouts.clear(first, last);
|
||||
}
|
||||
} else {
|
||||
for (uint32_t layer = first_layer; layer < last_layer; ++layer) {
|
||||
const uint32_t first = (layer << 16) | first_level;
|
||||
const uint32_t last = (layer << 16) | last_level;
|
||||
mSubresourceLayouts.add(first, last, newLayout);
|
||||
}
|
||||
}
|
||||
vkCmdCopyBufferToImage(cmd, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||
}
|
||||
|
||||
VkImageLayout VulkanTexture::getVkLayout(uint32_t layer, uint32_t level) const {
|
||||
const uint32_t key = (layer << 16) | level;
|
||||
if (!mSubresourceLayouts.has(key)) {
|
||||
return VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
}
|
||||
return mSubresourceLayouts.get(key);
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "VulkanBuffer.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include <utils/RangeMap.h>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
|
||||
@@ -29,10 +31,12 @@ struct VulkanTexture : public HwTexture {
|
||||
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
||||
TextureUsage usage, VulkanStagePool& stagePool, VkComponentMapping swizzle = {});
|
||||
~VulkanTexture();
|
||||
void update2DImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
|
||||
int miplevel);
|
||||
void update3DImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
|
||||
uint32_t depth, int miplevel);
|
||||
|
||||
// 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);
|
||||
|
||||
// Uploads data into all 6 faces of a cubemap for a given miplevel.
|
||||
void updateCubeImage(const PixelBufferDescriptor& data, const FaceOffsets& faceOffsets,
|
||||
uint32_t miplevel);
|
||||
|
||||
@@ -49,35 +53,37 @@ struct VulkanTexture : public HwTexture {
|
||||
|
||||
VkFormat getVkFormat() const { return mVkFormat; }
|
||||
VkImage getVkImage() const { return mTextureImage; }
|
||||
VkImageLayout getVkLayout(uint32_t layer, uint32_t level) const;
|
||||
|
||||
void setSidecar(VulkanTexture* sidecar) { mSidecarMSAA = sidecar; }
|
||||
VulkanTexture* getSidecar() const { return mSidecarMSAA; }
|
||||
|
||||
void transitionLayout(VkCommandBuffer commands, const VkImageSubresourceRange& range,
|
||||
VkImageLayout newLayout);
|
||||
|
||||
private:
|
||||
// Gets or creates a cached VkImageView for a range of miplevels and array layers.
|
||||
// If isAttachment is true, this always returns a 2D image view without swizzle.
|
||||
VkImageView getImageView(VkImageSubresourceRange range, bool isAttachment = false);
|
||||
VkImageView getImageView(VkImageSubresourceRange range);
|
||||
|
||||
// Issues a copy from a VkBuffer to a specified miplevel in a VkImage. The given width and
|
||||
// height define a subregion within the miplevel.
|
||||
void copyBufferToImage(VkCommandBuffer cmdbuffer, VkBuffer buffer, VkImage image,
|
||||
uint32_t width, uint32_t height, uint32_t depth,
|
||||
FaceOffsets const* faceOffsets, uint32_t miplevel);
|
||||
|
||||
void updateWithCopyBuffer(const PixelBufferDescriptor& hostData, uint32_t width,
|
||||
uint32_t height, uint32_t depth, uint32_t miplevel);
|
||||
|
||||
void updateWithBlitImage(const PixelBufferDescriptor& hostData, uint32_t width,
|
||||
void updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width,
|
||||
uint32_t height, uint32_t depth, uint32_t miplevel);
|
||||
|
||||
VulkanTexture* mSidecarMSAA = nullptr;
|
||||
const VkFormat mVkFormat;
|
||||
const VkImageAspectFlags mAspect;
|
||||
const VkImageViewType mViewType;
|
||||
const VkComponentMapping mSwizzle;
|
||||
VkImageViewType mViewType;
|
||||
VkImage mTextureImage = VK_NULL_HANDLE;
|
||||
VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE;
|
||||
|
||||
// Track the image layout of each subresource using a sparse range map.
|
||||
utils::RangeMap<uint32_t, VkImageLayout> mSubresourceLayouts;
|
||||
|
||||
// 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::map<VkImageSubresourceRange, VkImageView> mCachedImageViews;
|
||||
VkImageAspectFlags mAspect;
|
||||
VulkanContext& mContext;
|
||||
VulkanStagePool& mStagePool;
|
||||
};
|
||||
|
||||
@@ -501,6 +501,45 @@ VkComponentMapping getSwizzleMap(TextureSwizzle swizzle[4]) {
|
||||
return map;
|
||||
}
|
||||
|
||||
VkImageViewType getImageViewType(SamplerType target) {
|
||||
switch (target) {
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
return VK_IMAGE_VIEW_TYPE_CUBE;
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
case SamplerType::SAMPLER_3D:
|
||||
return VK_IMAGE_VIEW_TYPE_3D;
|
||||
default:
|
||||
return VK_IMAGE_VIEW_TYPE_2D;
|
||||
}
|
||||
}
|
||||
|
||||
VkImageLayout getDefaultImageLayout(TextureUsage usage) {
|
||||
// Filament sometimes samples from depth while it is bound to the current render target, (e.g.
|
||||
// SSAO does this while depth writes are disabled) so let's keep it simple and use GENERAL for
|
||||
// all depth textures.
|
||||
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
|
||||
return VK_IMAGE_LAYOUT_GENERAL;
|
||||
}
|
||||
|
||||
// Filament sometimes samples from one miplevel while writing to another level in the same
|
||||
// texture (e.g. bloom does this). Moreover we'd like to avoid lots of expensive layout
|
||||
// transitions. So, keep it simple and use GENERAL for all color-attachable textures.
|
||||
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
|
||||
return VK_IMAGE_LAYOUT_GENERAL;
|
||||
}
|
||||
|
||||
// Finally, the layout for an immutable texture is optimal read-only.
|
||||
return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
}
|
||||
|
||||
VkShaderStageFlags getShaderStageFlags(ShaderStageFlags stageFlags) {
|
||||
VkShaderStageFlags flags = 0x0;
|
||||
if (stageFlags.vertex) flags |= VK_SHADER_STAGE_VERTEX_BIT;
|
||||
if (stageFlags.fragment) flags |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
return flags;
|
||||
}
|
||||
|
||||
void transitionImageLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition) {
|
||||
if (transition.oldLayout == transition.newLayout) {
|
||||
return;
|
||||
@@ -564,7 +603,7 @@ VulkanLayoutTransition textureTransitionHelper(VulkanLayoutTransition transition
|
||||
break;
|
||||
|
||||
// We support PRESENT as a target layout to allow blitting from the swap chain.
|
||||
// See also makeSwapChainPresentable().
|
||||
// See also SwapChain::makePresentable().
|
||||
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
||||
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
|
||||
transition.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define TNT_FILAMENT_DRIVER_VULKANUTILITY_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/ShaderStageFlags.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
@@ -47,6 +48,10 @@ VkCullModeFlags getCullMode(CullingMode mode);
|
||||
VkFrontFace getFrontFace(bool inverseFrontFaces);
|
||||
PixelDataType getComponentType(VkFormat format);
|
||||
VkComponentMapping getSwizzleMap(TextureSwizzle swizzle[4]);
|
||||
VkImageViewType getImageViewType(SamplerType target);
|
||||
VkImageLayout getDefaultImageLayout(TextureUsage usage);
|
||||
VkShaderStageFlags getShaderStageFlags(ShaderStageFlags stageFlags);
|
||||
|
||||
void transitionImageLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition);
|
||||
|
||||
// Helper function for populating barrier fields based on the desired image layout.
|
||||
|
||||
@@ -393,7 +393,7 @@ TEST_F(BackendTest, DepthMinify) {
|
||||
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
|
||||
Program prog = shaderGen.getProgram();
|
||||
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
|
||||
prog.setSamplerGroup(0, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.setSamplerGroup(0, ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.setUniformBlock(1, utils::CString("params"));
|
||||
program = api.createProgram(std::move(prog));
|
||||
}
|
||||
@@ -535,7 +535,7 @@ TEST_F(BackendTest, ColorResolve) {
|
||||
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
|
||||
Program prog = shaderGen.getProgram();
|
||||
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
|
||||
prog.setSamplerGroup(0, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.setSamplerGroup(0, ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.setUniformBlock(1, utils::CString("params"));
|
||||
program = api.createProgram(std::move(prog));
|
||||
}
|
||||
@@ -643,7 +643,7 @@ TEST_F(BackendTest, DepthResolve) {
|
||||
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
|
||||
Program prog = shaderGen.getProgram();
|
||||
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
|
||||
prog.setSamplerGroup(0, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.setSamplerGroup(0, ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.setUniformBlock(1, utils::CString("params"));
|
||||
program = api.createProgram(std::move(prog));
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
ShaderGenerator shaderGen(fullscreenVs, fullscreenFs, sBackend, sIsMobilePlatform);
|
||||
Program prog = shaderGen.getProgram();
|
||||
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
|
||||
prog.setSamplerGroup(0, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.setSamplerGroup(0, ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.setUniformBlock(1, utils::CString("params"));
|
||||
program = api.createProgram(std::move(prog));
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ TEST_F(BackendTest, UpdateImage2D) {
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
|
||||
Program prog = shaderGen.getProgram();
|
||||
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
|
||||
prog.setSamplerGroup(0, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.setSamplerGroup(0, ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
program = api.createProgram(std::move(prog));
|
||||
|
||||
// Create a Texture.
|
||||
@@ -468,7 +468,7 @@ TEST_F(BackendTest, UpdateImageSRGB) {
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
|
||||
Program prog = shaderGen.getProgram();
|
||||
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
|
||||
prog.setSamplerGroup(0, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.setSamplerGroup(0, ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
ProgramHandle program = api.createProgram(std::move(prog));
|
||||
|
||||
// Create a texture.
|
||||
@@ -554,7 +554,7 @@ TEST_F(BackendTest, UpdateImageMipLevel) {
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
|
||||
Program prog = shaderGen.getProgram();
|
||||
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
|
||||
prog.setSamplerGroup(0, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.setSamplerGroup(0, ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
ProgramHandle program = api.createProgram(std::move(prog));
|
||||
|
||||
// Create a texture with 3 mip levels.
|
||||
@@ -626,7 +626,7 @@ TEST_F(BackendTest, UpdateImage3D) {
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
|
||||
Program prog = shaderGen.getProgram();
|
||||
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
|
||||
prog.setSamplerGroup(0, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.setSamplerGroup(0, ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
ProgramHandle program = api.createProgram(std::move(prog));
|
||||
|
||||
// Create a texture.
|
||||
|
||||
@@ -68,7 +68,7 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
|
||||
// Create a program that samples a texture.
|
||||
Program p = shaderGen.getProgram();
|
||||
Program::Sampler sampler { utils::CString("tex"), 6 };
|
||||
p.setSamplerGroup(0, &sampler, 1);
|
||||
p.setSamplerGroup(0, ALL_SHADER_STAGE_FLAGS, &sampler, 1);
|
||||
backend::Handle<HwProgram> program = getDriverApi().createProgram(std::move(p));
|
||||
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = getDriverApi().createDefaultRenderTarget(0);
|
||||
@@ -142,7 +142,7 @@ TEST_F(BackendTest, RenderExternalImage) {
|
||||
// Create a program that samples a texture.
|
||||
Program p = shaderGen.getProgram();
|
||||
Program::Sampler sampler { utils::CString("tex"), 6 };
|
||||
p.setSamplerGroup(0, &sampler, 1);
|
||||
p.setSamplerGroup(0, ALL_SHADER_STAGE_FLAGS, &sampler, 1);
|
||||
auto program = getDriverApi().createProgram(std::move(p));
|
||||
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = getDriverApi().createDefaultRenderTarget(0);
|
||||
|
||||
@@ -300,7 +300,20 @@ public:
|
||||
Builder& skinning(size_t boneCount) noexcept; //!< \overload
|
||||
|
||||
/**
|
||||
* Controls if the renderable has vertex morphing targets, false by default.
|
||||
* Controls if the renderable has vertex morphing targets, false by default. This is
|
||||
* required to enable GPU morphing.
|
||||
*
|
||||
* Filament supports two morphing modes: standard (default) and legacy.
|
||||
*
|
||||
* For standard morphing, A MorphTargetBuffer must be created and provided via
|
||||
* RenderableManager::setMorphTargetBufferAt(). Standard morphing supports up to
|
||||
* \c CONFIG_MAX_MORPH_TARGET_COUNT morph targets.
|
||||
*
|
||||
* For legacy morphing, the attached VertexBuffer must provide data in the
|
||||
* appropriate VertexAttribute slots (\c MORPH_POSITION_0 etc). Legacy morphing only
|
||||
* supports up to 4 morph targets and will be deprecated in the future. Legacy morphing must
|
||||
* be enabled on the material definition: either via the legacyMorphing material attribute
|
||||
* or by calling filamat::MaterialBuilder::useLegacyMorphing().
|
||||
*
|
||||
* See also RenderableManager::setMorphWeights(), which can be called on a per-frame basis
|
||||
* to advance the animation.
|
||||
@@ -455,7 +468,8 @@ public:
|
||||
/**
|
||||
* Updates the vertex morphing weights on a renderable, all zeroes by default.
|
||||
*
|
||||
* The renderable must be built with morphing enabled, see Builder::morphing().
|
||||
* The renderable must be built with morphing enabled, see Builder::morphing(). In legacy
|
||||
* morphing mode, only the first 4 weights are considered.
|
||||
*/
|
||||
void setMorphWeights(Instance instance, float const* weights, size_t count) noexcept;
|
||||
|
||||
|
||||
@@ -261,6 +261,20 @@ void FEngine::init() {
|
||||
.package(MATERIALS_DEFAULTMATERIAL_DATA, MATERIALS_DEFAULTMATERIAL_SIZE)
|
||||
.build(*const_cast<FEngine*>(this)));
|
||||
|
||||
// create dummy textures we need throughout the engine
|
||||
|
||||
mDummyOneTexture = driverApi.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 1, 1, 1, TextureUsage::DEFAULT);
|
||||
|
||||
mDummyOneTextureArray = driverApi.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1,
|
||||
TextureFormat::RGBA8, 1, 1, 1, 1, TextureUsage::DEFAULT);
|
||||
|
||||
mDummyZeroTexture = driverApi.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 1, 1, 1, TextureUsage::DEFAULT);
|
||||
|
||||
// dummy textures must be initialized before this call
|
||||
mDummyMorphingSamplerGroup = FMorphTargetBuffer::createDummySampleGroup(*this);
|
||||
|
||||
mPostProcessManager.init();
|
||||
mLightManager.init(*this);
|
||||
mDFG = std::make_unique<DFG>(*this);
|
||||
@@ -344,6 +358,11 @@ void FEngine::shutdown() {
|
||||
}
|
||||
cleanupResourceList(mFences);
|
||||
|
||||
driver.destroySamplerGroup(mDummyMorphingSamplerGroup);
|
||||
driver.destroyTexture(mDummyOneTexture);
|
||||
driver.destroyTexture(mDummyOneTextureArray);
|
||||
driver.destroyTexture(mDummyZeroTexture);
|
||||
|
||||
/*
|
||||
* Shutdown the backend...
|
||||
*/
|
||||
|
||||
@@ -143,7 +143,7 @@ static void addSamplerGroup(Program& pb, uint8_t bindingPoint, SamplerInterfaceB
|
||||
const bool strict = (bindingPoint == filament::BindingPoints::PER_MATERIAL_INSTANCE);
|
||||
samplers[i] = { std::move(uniformName), binding, strict };
|
||||
}
|
||||
pb.setSamplerGroup(bindingPoint, samplers.data(), samplers.size());
|
||||
pb.setSamplerGroup(bindingPoint, sib.getStageFlags(), samplers.data(), samplers.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +389,8 @@ Handle<HwProgram> FMaterial::getSurfaceProgramSlow(Variant variant) const noexce
|
||||
addSamplerGroup(pb, BindingPoints::PER_VIEW, SibGenerator::getPerViewSib(variant), mSamplerBindings);
|
||||
addSamplerGroup(pb, BindingPoints::PER_MATERIAL_INSTANCE, mSamplerInterfaceBlock, mSamplerBindings);
|
||||
|
||||
// getSurfaceBindingIndexMap in GLSLPostProcessor.cpp also needs to update if sampler groups are added.
|
||||
|
||||
return createAndCacheProgram(std::move(pb), variant);
|
||||
}
|
||||
|
||||
@@ -401,6 +403,8 @@ Handle<HwProgram> FMaterial::getPostProcessProgramSlow(Variant variant)
|
||||
|
||||
addSamplerGroup(pb, BindingPoints::PER_MATERIAL_INSTANCE, mSamplerInterfaceBlock, mSamplerBindings);
|
||||
|
||||
// getPostProcessBindingIndexMap in GLSLPostProcessor.cpp also needs to update if sampler groups are added.
|
||||
|
||||
return createAndCacheProgram(std::move(pb), variant);
|
||||
}
|
||||
|
||||
|
||||
@@ -118,10 +118,19 @@ void FMorphTargetBuffer::terminate(FEngine& engine) {
|
||||
driver.destroyTexture(mTbHandle);
|
||||
}
|
||||
|
||||
Handle<HwSamplerGroup> FMorphTargetBuffer::createDummySampleGroup(FEngine& engine) noexcept {
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
Handle<HwSamplerGroup> sgh = driver.createSamplerGroup(PerRenderPrimitiveMorphingSib::SAMPLER_COUNT);
|
||||
backend::SamplerGroup group(PerRenderPrimitiveMorphingSib::SAMPLER_COUNT);
|
||||
group.setSampler(PerRenderPrimitiveMorphingSib::TARGETS, engine.getOneTextureArray(), {});
|
||||
driver.updateSamplerGroup(sgh, std::move(group.toCommandStream()));
|
||||
return sgh;
|
||||
}
|
||||
|
||||
void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t targetIndex, math::float3 const* positions, size_t count) {
|
||||
ASSERT_PRECONDITION(targetIndex < mCount, "targetIndex must be < count");
|
||||
|
||||
auto size = getSize(mVertexCount);
|
||||
const size_t size = getSize(mVertexCount);
|
||||
|
||||
ASSERT_PRECONDITION((int)sizeof(math::float3) * count <= size,
|
||||
"MorphTargetBuffer (size=%lu) overflow (size=%lu)",
|
||||
|
||||
@@ -253,15 +253,6 @@ void PostProcessManager::init() noexcept {
|
||||
registerPostProcessMaterial(info.name, info.data, info.size);
|
||||
}
|
||||
|
||||
mDummyOneTexture = driver.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 1, 1, 1, TextureUsage::DEFAULT);
|
||||
|
||||
mDummyOneTextureArray = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1,
|
||||
TextureFormat::RGBA8, 1, 1, 1, 1, TextureUsage::DEFAULT);
|
||||
|
||||
mDummyZeroTexture = driver.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 1, 1, 1, TextureUsage::DEFAULT);
|
||||
|
||||
mStarburstTexture = driver.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::R8, 1, 256, 1, 1, TextureUsage::DEFAULT);
|
||||
|
||||
@@ -277,17 +268,26 @@ void PostProcessManager::init() noexcept {
|
||||
float r = 0.5f + 0.5f * dist(gen);
|
||||
return uint8_t(r * 255.0f);
|
||||
});
|
||||
driver.update2DImage(mDummyOneTexture, 0, 0, 0, 1, 1, std::move(dataOne));
|
||||
driver.update3DImage(mDummyOneTextureArray, 0, 0, 0, 0, 1, 1, 1, std::move(dataOneArray));
|
||||
driver.update2DImage(mDummyZeroTexture, 0, 0, 0, 1, 1, std::move(dataZero));
|
||||
driver.update2DImage(mStarburstTexture, 0, 0, 0, 256, 1, std::move(dataStarburst));
|
||||
|
||||
driver.update2DImage(engine.getOneTexture(),
|
||||
0, 0, 0, 1, 1,
|
||||
std::move(dataOne));
|
||||
|
||||
driver.update3DImage(engine.getOneTextureArray(),
|
||||
0, 0, 0, 0, 1, 1, 1,
|
||||
std::move(dataOneArray));
|
||||
|
||||
driver.update2DImage(engine.getZeroTexture(),
|
||||
0, 0, 0, 1, 1,
|
||||
std::move(dataZero));
|
||||
|
||||
driver.update2DImage(mStarburstTexture,
|
||||
0, 0, 0, 256, 1,
|
||||
std::move(dataStarburst));
|
||||
}
|
||||
|
||||
void PostProcessManager::terminate(DriverApi& driver) noexcept {
|
||||
FEngine& engine = mEngine;
|
||||
driver.destroyTexture(mDummyOneTexture);
|
||||
driver.destroyTexture(mDummyOneTextureArray);
|
||||
driver.destroyTexture(mDummyZeroTexture);
|
||||
driver.destroyTexture(mStarburstTexture);
|
||||
auto first = mMaterialRegistry.begin();
|
||||
auto last = mMaterialRegistry.end();
|
||||
@@ -297,6 +297,18 @@ void PostProcessManager::terminate(DriverApi& driver) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
backend::Handle<backend::HwTexture> PostProcessManager::getOneTexture() const {
|
||||
return mEngine.getOneTexture();
|
||||
}
|
||||
|
||||
backend::Handle<backend::HwTexture> PostProcessManager::getZeroTexture() const {
|
||||
return mEngine.getZeroTexture();
|
||||
}
|
||||
|
||||
backend::Handle<backend::HwTexture> PostProcessManager::getOneTextureArray() const {
|
||||
return mEngine.getOneTextureArray();
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void PostProcessManager::commitAndRender(FrameGraphResources::RenderPassInfo const& out,
|
||||
PostProcessMaterial const& material, uint8_t variant, DriverApi& driver) const noexcept {
|
||||
|
||||
@@ -156,9 +156,9 @@ public:
|
||||
FrameGraphId<FrameGraphTexture> output, uint8_t dstLevel, uint8_t layer,
|
||||
bool reinhard, size_t kernelWidth, float sigma = 6.0f) noexcept;
|
||||
|
||||
backend::Handle<backend::HwTexture> getOneTexture() const { return mDummyOneTexture; }
|
||||
backend::Handle<backend::HwTexture> getZeroTexture() const { return mDummyZeroTexture; }
|
||||
backend::Handle<backend::HwTexture> getOneTextureArray() const { return mDummyOneTextureArray; }
|
||||
backend::Handle<backend::HwTexture> getOneTexture() const;
|
||||
backend::Handle<backend::HwTexture> getZeroTexture() const;
|
||||
backend::Handle<backend::HwTexture> getOneTextureArray() const;
|
||||
|
||||
math::float2 halton(size_t index) const noexcept {
|
||||
return mHaltonSamples[index & 0xFu];
|
||||
@@ -234,9 +234,6 @@ private:
|
||||
void registerPostProcessMaterial(utils::StaticString name, uint8_t const* data, int size);
|
||||
PostProcessMaterial& getPostProcessMaterial(utils::StaticString name) noexcept;
|
||||
|
||||
backend::Handle<backend::HwTexture> mDummyOneTexture;
|
||||
backend::Handle<backend::HwTexture> mDummyOneTextureArray;
|
||||
backend::Handle<backend::HwTexture> mDummyZeroTexture;
|
||||
backend::Handle<backend::HwTexture> mStarburstTexture;
|
||||
|
||||
std::uniform_real_distribution<float> mUniformDistribution{0.0f, 1.0f};
|
||||
|
||||
@@ -528,12 +528,13 @@ void RenderPass::Executor::execute(const char* name,
|
||||
engine.flush();
|
||||
|
||||
driver.beginRenderPass(renderTarget, params);
|
||||
recordDriverCommands(driver, mBegin, mEnd, mRenderableSoa);
|
||||
recordDriverCommands(engine, driver, mBegin, mEnd, mRenderableSoa);
|
||||
driver.endRenderPass();
|
||||
}
|
||||
|
||||
UTILS_NOINLINE // no need to be inlined
|
||||
void RenderPass::Executor::recordDriverCommands(backend::DriverApi& driver,
|
||||
void RenderPass::Executor::recordDriverCommands(FEngine& engine,
|
||||
backend::DriverApi& driver,
|
||||
const Command* first, const Command* last,
|
||||
FScene::RenderableSoa const& soa) const noexcept {
|
||||
SYSTRACE_CALL();
|
||||
@@ -589,6 +590,11 @@ void RenderPass::Executor::recordDriverCommands(backend::DriverApi& driver,
|
||||
skinning.handle,
|
||||
skinning.offset * sizeof(PerRenderableUibBone),
|
||||
CONFIG_MAX_BONE_COUNT * sizeof(PerRenderableUibBone));
|
||||
|
||||
if (!info.morphTargetBuffer) {
|
||||
driver.bindSamplers(BindingPoints::PER_RENDERABLE_MORPHING,
|
||||
engine.getDummyMorphingSamplerGroup());
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(info.morphWeightBuffer)) {
|
||||
@@ -598,7 +604,7 @@ void RenderPass::Executor::recordDriverCommands(backend::DriverApi& driver,
|
||||
info.morphWeightBuffer);
|
||||
|
||||
// When only skinning is enabled, morphTargetBuffer isn't created.
|
||||
if (UTILS_UNLIKELY(info.morphTargetBuffer)) {
|
||||
if (info.morphTargetBuffer) {
|
||||
driver.bindSamplers(BindingPoints::PER_RENDERABLE_MORPHING,
|
||||
info.morphTargetBuffer);
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ public:
|
||||
assert_invariant(e <= pass->end());
|
||||
}
|
||||
|
||||
void recordDriverCommands(backend::DriverApi& driver,
|
||||
void recordDriverCommands(FEngine& engine, backend::DriverApi& driver,
|
||||
const Command* first, const Command* last,
|
||||
FScene::RenderableSoa const& soa) const noexcept;
|
||||
|
||||
|
||||
@@ -332,10 +332,15 @@ public:
|
||||
return mRandomEngine;
|
||||
}
|
||||
|
||||
void pumpMessageQueues() {
|
||||
void pumpMessageQueues() const {
|
||||
getDriver().purge();
|
||||
}
|
||||
|
||||
backend::Handle<backend::HwTexture> getOneTexture() const { return mDummyOneTexture; }
|
||||
backend::Handle<backend::HwTexture> getZeroTexture() const { return mDummyZeroTexture; }
|
||||
backend::Handle<backend::HwTexture> getOneTextureArray() const { return mDummyOneTextureArray; }
|
||||
backend::Handle<backend::HwSamplerGroup> getDummyMorphingSamplerGroup() const { return mDummyMorphingSamplerGroup; }
|
||||
|
||||
private:
|
||||
FEngine(Backend backend, Platform* platform, void* sharedGLContext);
|
||||
void init();
|
||||
@@ -424,6 +429,11 @@ private:
|
||||
mutable filaflat::ShaderBuilder mFragmentShaderBuilder;
|
||||
FDebugRegistry mDebugRegistry;
|
||||
|
||||
backend::Handle<backend::HwTexture> mDummyOneTexture;
|
||||
backend::Handle<backend::HwTexture> mDummyOneTextureArray;
|
||||
backend::Handle<backend::HwTexture> mDummyZeroTexture;
|
||||
backend::Handle<backend::HwSamplerGroup> mDummyMorphingSamplerGroup;
|
||||
|
||||
std::thread::id mMainThreadId{};
|
||||
|
||||
public:
|
||||
|
||||
@@ -48,6 +48,8 @@ public:
|
||||
inline size_t getVertexCount() const noexcept { return mVertexCount; }
|
||||
inline size_t getCount() const noexcept { return mCount; }
|
||||
|
||||
static backend::Handle<backend::HwSamplerGroup> createDummySampleGroup(FEngine& engine) noexcept;
|
||||
|
||||
private:
|
||||
friend class FView;
|
||||
friend class RenderPass;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.17.0"
|
||||
spec.version = "1.18.0"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.17.0/filament-v1.17.0-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.18.0/filament-v1.18.0-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
namespace filament {
|
||||
|
||||
// update this when a new version of filament wouldn't work with older materials
|
||||
static constexpr size_t MATERIAL_VERSION = 17;
|
||||
static constexpr size_t MATERIAL_VERSION = 18;
|
||||
|
||||
/**
|
||||
* Supported shading models
|
||||
@@ -136,9 +136,21 @@ enum VertexAttribute : uint8_t {
|
||||
CUSTOM6 = 14,
|
||||
CUSTOM7 = 15,
|
||||
|
||||
// Aliases for legacy vertex morphing.
|
||||
// See RenderableManager::Builder::morphing().
|
||||
MORPH_POSITION_0 = CUSTOM0,
|
||||
MORPH_POSITION_1 = CUSTOM1,
|
||||
MORPH_POSITION_2 = CUSTOM2,
|
||||
MORPH_POSITION_3 = CUSTOM3,
|
||||
MORPH_TANGENTS_0 = CUSTOM4,
|
||||
MORPH_TANGENTS_1 = CUSTOM5,
|
||||
MORPH_TANGENTS_2 = CUSTOM6,
|
||||
MORPH_TANGENTS_3 = CUSTOM7,
|
||||
|
||||
// this is limited by driver::MAX_VERTEX_ATTRIBUTE_COUNT
|
||||
};
|
||||
|
||||
static constexpr size_t MAX_LEGACY_MORPH_TARGETS = 4;
|
||||
static constexpr size_t MAX_MORPH_TARGETS = 128; // this is limited by filament::CONFIG_MAX_MORPH_TARGET_COUNT
|
||||
static constexpr size_t MAX_CUSTOM_ATTRIBUTES = 8;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/ShaderStageFlags.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/CString.h>
|
||||
@@ -59,6 +60,8 @@ public:
|
||||
return name(utils::StaticString{ interfaceBlockName });
|
||||
}
|
||||
|
||||
Builder& stageFlags(backend::ShaderStageFlags stageFlags);
|
||||
|
||||
// Add a sampler
|
||||
Builder& add(utils::CString const& samplerName, Type type, Format format,
|
||||
Precision precision = Precision::MEDIUM,
|
||||
@@ -93,6 +96,7 @@ public:
|
||||
Precision precision;
|
||||
};
|
||||
utils::CString mName;
|
||||
backend::ShaderStageFlags mStageFlags = backend::ALL_SHADER_STAGE_FLAGS;
|
||||
std::vector<Entry> mEntries;
|
||||
};
|
||||
|
||||
@@ -115,6 +119,8 @@ public:
|
||||
// name of this sampler interface block
|
||||
const utils::CString& getName() const noexcept { return mName; }
|
||||
|
||||
backend::ShaderStageFlags getStageFlags() const noexcept { return mStageFlags; }
|
||||
|
||||
// size needed to store the samplers described by this interface block in a SamplerGroup
|
||||
size_t getSize() const noexcept { return mSize; }
|
||||
|
||||
@@ -138,6 +144,7 @@ private:
|
||||
explicit SamplerInterfaceBlock(Builder const& builder) noexcept;
|
||||
|
||||
utils::CString mName;
|
||||
backend::ShaderStageFlags mStageFlags; // It's needed to check if MAX_SAMPLER_COUNT is exceeded.
|
||||
std::vector<SamplerInfo> mSamplersInfoList;
|
||||
tsl::robin_map<const char*, uint32_t, utils::hashCStrings, utils::equalCStrings> mInfoMap;
|
||||
uint32_t mSize = 0; // size in Samplers (i.e.: count)
|
||||
|
||||
@@ -30,8 +30,6 @@ void SamplerBindingMap::populate(const SamplerInterfaceBlock* perMaterialSib,
|
||||
// The material variant currently only affects sampler formats (for VSM), not offsets.
|
||||
const Variant dummyVariant{};
|
||||
uint8_t offset = 0;
|
||||
size_t maxSamplerIndex = backend::MAX_SAMPLER_COUNT - 1;
|
||||
bool overflow = false;
|
||||
for (uint8_t blockIndex = 0; blockIndex < filament::BindingPoints::COUNT; blockIndex++) {
|
||||
mSamplerBlockOffsets[blockIndex] = offset;
|
||||
filament::SamplerInterfaceBlock const* sib;
|
||||
@@ -43,9 +41,6 @@ void SamplerBindingMap::populate(const SamplerInterfaceBlock* perMaterialSib,
|
||||
if (sib) {
|
||||
auto sibFields = sib->getSamplerInfoList();
|
||||
for (const auto& sInfo : sibFields) {
|
||||
if (offset > maxSamplerIndex) {
|
||||
overflow = true;
|
||||
}
|
||||
addSampler({
|
||||
.blockIndex = blockIndex,
|
||||
.localOffset = sInfo.offset,
|
||||
@@ -55,9 +50,37 @@ void SamplerBindingMap::populate(const SamplerInterfaceBlock* perMaterialSib,
|
||||
}
|
||||
}
|
||||
|
||||
auto isOverflow = [&perMaterialSib, &dummyVariant]() {
|
||||
size_t numVertexSampler = 0, numFragmentSampler = 0;
|
||||
for (uint8_t blockIndex = 0; blockIndex < filament::BindingPoints::COUNT; blockIndex++) {
|
||||
filament::SamplerInterfaceBlock const* sib;
|
||||
if (blockIndex == filament::BindingPoints::PER_MATERIAL_INSTANCE) {
|
||||
sib = perMaterialSib;
|
||||
} else {
|
||||
sib = filament::SibGenerator::getSib(blockIndex, dummyVariant);
|
||||
}
|
||||
if (sib) {
|
||||
// Shader stage flags is only needed to check if MAX_SAMPLER_COUNT is exceeded.
|
||||
// Somehow if we can get shader stage flags from Program then we can remove it in SamplerInterfaceBlock.
|
||||
const auto stageFlags = sib->getStageFlags();
|
||||
if (stageFlags.vertex) {
|
||||
numVertexSampler += sib->getSamplerInfoList().size();
|
||||
}
|
||||
if (stageFlags.fragment) {
|
||||
numFragmentSampler += sib->getSamplerInfoList().size();
|
||||
}
|
||||
if (numVertexSampler > backend::MAX_VERTEX_SAMPLER_COUNT ||
|
||||
numFragmentSampler > backend::MAX_FRAGMENT_SAMPLER_COUNT) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// If an overflow occurred, go back through and list all sampler names. This is helpful to
|
||||
// material authors who need to understand where the samplers are coming from.
|
||||
if (overflow) {
|
||||
if (isOverflow()) {
|
||||
utils::slog.e << "WARNING: Exceeded max sampler count of " << backend::MAX_SAMPLER_COUNT;
|
||||
if (materialName) {
|
||||
utils::slog.e << " (" << materialName << ")";
|
||||
@@ -74,7 +97,8 @@ void SamplerBindingMap::populate(const SamplerInterfaceBlock* perMaterialSib,
|
||||
if (sib) {
|
||||
auto sibFields = sib->getSamplerInfoList();
|
||||
for (auto sInfo : sibFields) {
|
||||
utils::slog.e << " " << (int) offset << " " << sInfo.name.c_str() << utils::io::endl;
|
||||
utils::slog.e << " " << (int) offset << " " << sInfo.name.c_str()
|
||||
<< " " << sib->getStageFlags() << utils::io::endl;
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,12 @@ SamplerInterfaceBlock::Builder::name(utils::StaticString const& interfaceBlockNa
|
||||
return *this;
|
||||
}
|
||||
|
||||
SamplerInterfaceBlock::Builder&
|
||||
SamplerInterfaceBlock::Builder::stageFlags(backend::ShaderStageFlags stageFlags) {
|
||||
mStageFlags = stageFlags;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add(
|
||||
utils::CString const& samplerName, Type type, Format format,
|
||||
Precision precision, bool multisample) noexcept {
|
||||
@@ -82,7 +88,7 @@ SamplerInterfaceBlock& SamplerInterfaceBlock::operator=(SamplerInterfaceBlock&&
|
||||
SamplerInterfaceBlock::~SamplerInterfaceBlock() noexcept = default;
|
||||
|
||||
SamplerInterfaceBlock::SamplerInterfaceBlock(Builder const& builder) noexcept
|
||||
: mName(builder.mName)
|
||||
: mName(builder.mName), mStageFlags(builder.mStageFlags)
|
||||
{
|
||||
auto& infoMap = mInfoMap;
|
||||
auto& samplersInfoList = mSamplersInfoList;
|
||||
|
||||
@@ -33,7 +33,8 @@ SamplerInterfaceBlock const& SibGenerator::getPerViewSib(Variant variant) noexce
|
||||
auto builder = SamplerInterfaceBlock::Builder();
|
||||
|
||||
builder
|
||||
.name("Light");
|
||||
.name("Light")
|
||||
.stageFlags({ .fragment = true });
|
||||
|
||||
if (hasVsm) {
|
||||
builder.add("shadowMap", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::HIGH);
|
||||
@@ -73,6 +74,7 @@ SamplerInterfaceBlock const& SibGenerator::getPerRenderPrimitiveMorphingSib(Vari
|
||||
|
||||
static SamplerInterfaceBlock sib = SamplerInterfaceBlock::Builder()
|
||||
.name("Morphing")
|
||||
.stageFlags({ .vertex = true })
|
||||
.add("targets", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::HIGH)
|
||||
.build();
|
||||
|
||||
|
||||
@@ -529,6 +529,12 @@ public:
|
||||
|
||||
MaterialBuilder& enableFramebufferFetch() noexcept;
|
||||
|
||||
/**
|
||||
* Legacy morphing uses the data in the VertexAttribute slots (\c MORPH_POSITION_0, etc) and is
|
||||
* limited to 4 morph targets. See filament::RenderableManager::Builder::morphing().
|
||||
*/
|
||||
MaterialBuilder& useLegacyMorphing() noexcept;
|
||||
|
||||
/**
|
||||
* Build the material. If you are using the Filament engine with this library, you should use
|
||||
* the job system provided by Engine.
|
||||
@@ -744,6 +750,8 @@ private:
|
||||
|
||||
bool mEnableFramebufferFetch = false;
|
||||
|
||||
bool mUseLegacyMorphing = false;
|
||||
|
||||
PreprocessorDefineList mDefines;
|
||||
};
|
||||
|
||||
|
||||
@@ -28,9 +28,12 @@
|
||||
|
||||
#include "sca/builtinResource.h"
|
||||
#include "sca/GLSLTools.h"
|
||||
#include "shaders/MaterialInfo.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <filament/MaterialEnums.h>
|
||||
#include <private/filament/Variant.h>
|
||||
#include <private/filament/SibGenerator.h>
|
||||
|
||||
using namespace glslang;
|
||||
using namespace spirv_cross;
|
||||
@@ -38,6 +41,53 @@ using namespace spvtools;
|
||||
|
||||
namespace filamat {
|
||||
|
||||
using BindingIndexMap = tsl::robin_map<std::string, uint16_t>;
|
||||
|
||||
|
||||
static void generateBindingIndexMap(const GLSLPostProcessor::Config& config,
|
||||
filament::SamplerInterfaceBlock const& sib, BindingIndexMap& map) {
|
||||
const auto stageFlags = sib.getStageFlags();
|
||||
if (!stageFlags.hasShaderType(config.shaderType)) {
|
||||
return;
|
||||
}
|
||||
const auto& infoList = sib.getSamplerInfoList();
|
||||
for (const auto& info : infoList) {
|
||||
auto uniformName = filament::SamplerInterfaceBlock::getUniformName(
|
||||
sib.getName().c_str(), info.name.c_str());
|
||||
map[uniformName.c_str()] = map.size();
|
||||
}
|
||||
}
|
||||
|
||||
static BindingIndexMap getSurfaceBindingIndexMap(const GLSLPostProcessor::Config& config) {
|
||||
BindingIndexMap map;
|
||||
// We assume material variant 0 here, which is sufficient for calculating the binding map.
|
||||
// The material variant currently only affects sampler formats (for VSM), not offsets.
|
||||
const filament::Variant dummyVariant{};
|
||||
generateBindingIndexMap(config,
|
||||
filament::SibGenerator::getPerRenderPrimitiveMorphingSib(dummyVariant), map);
|
||||
generateBindingIndexMap(config,
|
||||
filament::SibGenerator::getPerViewSib(dummyVariant), map);
|
||||
generateBindingIndexMap(config,
|
||||
config.materialInfo->sib, map);
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
static BindingIndexMap getPostProcessBindingIndexMap(const GLSLPostProcessor::Config& config) {
|
||||
BindingIndexMap map;
|
||||
generateBindingIndexMap(config, config.materialInfo->sib, map);
|
||||
return map;
|
||||
}
|
||||
|
||||
static BindingIndexMap getBindingIndexMap(const GLSLPostProcessor::Config& config) {
|
||||
switch (config.domain) {
|
||||
case filament::MaterialDomain::SURFACE:
|
||||
return getSurfaceBindingIndexMap(config);
|
||||
case filament::MaterialDomain::POST_PROCESS:
|
||||
return getPostProcessBindingIndexMap(config);
|
||||
}
|
||||
}
|
||||
|
||||
GLSLPostProcessor::GLSLPostProcessor(MaterialBuilder::Optimization optimization, uint32_t flags)
|
||||
: mOptimization(optimization),
|
||||
mPrintShaders(flags & PRINT_SHADERS),
|
||||
@@ -137,25 +187,29 @@ void GLSLPostProcessor::spirvToToMsl(const SpirvBlob *spirv, std::string *outMsl
|
||||
|
||||
auto executionModel = mslCompiler.get_execution_model();
|
||||
|
||||
auto duplicateResourceBinding = [executionModel, &mslCompiler](const auto& resource) {
|
||||
// The index will be used to remap based on BindingIndexMap and
|
||||
// the result becomes a [[buffer(index)]], [[texture(index)]] or [[sampler(index)]].
|
||||
auto updateResourceBinding = [executionModel, &mslCompiler]
|
||||
(const auto& resource, const BindingIndexMap* map = nullptr) {
|
||||
auto set = mslCompiler.get_decoration(resource.id, spv::DecorationDescriptorSet);
|
||||
auto binding = mslCompiler.get_decoration(resource.id, spv::DecorationBinding);
|
||||
MSLResourceBinding newBinding;
|
||||
newBinding.stage = executionModel;
|
||||
newBinding.desc_set = set;
|
||||
newBinding.binding = binding;
|
||||
newBinding.msl_texture = binding;
|
||||
newBinding.msl_sampler = binding;
|
||||
newBinding.msl_buffer = binding;
|
||||
newBinding.msl_texture =
|
||||
newBinding.msl_sampler =
|
||||
newBinding.msl_buffer = map ? map->at(mslCompiler.get_name(resource.id)) : binding;
|
||||
mslCompiler.add_msl_resource_binding(newBinding);
|
||||
};
|
||||
|
||||
auto resources = mslCompiler.get_shader_resources();
|
||||
auto bindingIndexMap = getBindingIndexMap(config);
|
||||
for (const auto& resource : resources.sampled_images) {
|
||||
duplicateResourceBinding(resource);
|
||||
updateResourceBinding(resource, &bindingIndexMap);
|
||||
}
|
||||
for (const auto& resource : resources.uniform_buffers) {
|
||||
duplicateResourceBinding(resource);
|
||||
updateResourceBinding(resource);
|
||||
}
|
||||
|
||||
*outMsl = mslCompiler.compile();
|
||||
|
||||
@@ -51,6 +51,7 @@ public:
|
||||
filament::backend::ShaderType shaderType;
|
||||
filament::backend::ShaderModel shaderModel;
|
||||
filament::MaterialDomain domain;
|
||||
const filamat::MaterialInfo* materialInfo;
|
||||
bool hasFramebufferFetch;
|
||||
struct {
|
||||
std::vector<std::pair<uint32_t, uint32_t>> subpassInputToColorLocation;
|
||||
|
||||
@@ -488,6 +488,7 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept {
|
||||
info.reflectionMode = mReflectionMode;
|
||||
info.quality = mShaderQuality;
|
||||
info.hasCustomSurfaceShading = mCustomSurfaceShading;
|
||||
info.useLegacyMorphing = mUseLegacyMorphing;
|
||||
}
|
||||
|
||||
bool MaterialBuilder::findProperties(filament::backend::ShaderType type,
|
||||
@@ -728,6 +729,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
|
||||
.shaderType = v.stage,
|
||||
.shaderModel = shaderModel,
|
||||
.domain = mMaterialDomain,
|
||||
.materialInfo = &info,
|
||||
.glsl = {},
|
||||
};
|
||||
|
||||
@@ -904,6 +906,11 @@ MaterialBuilder& MaterialBuilder::enableFramebufferFetch() noexcept {
|
||||
return *this;
|
||||
}
|
||||
|
||||
MaterialBuilder& MaterialBuilder::useLegacyMorphing() noexcept {
|
||||
mUseLegacyMorphing = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Package MaterialBuilder::build(JobSystem& jobSystem) noexcept {
|
||||
if (materialBuilderClients == 0) {
|
||||
utils::slog.e << "Error: MaterialBuilder::init() must be called before build()."
|
||||
@@ -930,7 +937,7 @@ Package MaterialBuilder::build(JobSystem& jobSystem) noexcept {
|
||||
}
|
||||
|
||||
// prepareToBuild must be called first, to populate mCodeGenPermutations.
|
||||
MaterialInfo info;
|
||||
MaterialInfo info {};
|
||||
prepareToBuild(info);
|
||||
|
||||
// Run checks, in order.
|
||||
@@ -951,6 +958,8 @@ Package MaterialBuilder::build(JobSystem& jobSystem) noexcept {
|
||||
writeSurfaceChunks(container);
|
||||
}
|
||||
|
||||
info.useLegacyMorphing = mUseLegacyMorphing;
|
||||
|
||||
// Generate all shaders and write the shader chunks.
|
||||
const auto variants = mMaterialDomain == MaterialDomain::SURFACE ?
|
||||
determineSurfaceVariants(mVariantFilter, isLit(), mShadowMultiplier) :
|
||||
|
||||
@@ -370,7 +370,11 @@ io::sstream& CodeGenerator::generateSamplers(
|
||||
// allows the sampler bindings to live in a separate "namespace" that starts at zero.
|
||||
// Note that the set specifier is not covered by the desktop GLSL spec, including
|
||||
// recent versions. It is only documented in the GL_KHR_vulkan_glsl extension.
|
||||
if (mTargetApi == TargetApi::VULKAN) {
|
||||
if (mTargetApi == TargetApi::VULKAN ||
|
||||
// For Metal, the sampler binding index must less than 16. But we generate sampler binding
|
||||
// index sequentially regardless binding shader stages, so it could be greater than 15.
|
||||
// To avoid this problem, we have to re-calculate resource binding indices each of shader stages.
|
||||
mTargetApi == TargetApi::METAL) {
|
||||
out << ", set = 1";
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ struct UTILS_PUBLIC MaterialInfo {
|
||||
bool multiBounceAOSet;
|
||||
bool specularAOSet;
|
||||
bool hasCustomSurfaceShading;
|
||||
bool useLegacyMorphing;
|
||||
filament::SpecularAmbientOcclusion specularAO;
|
||||
filament::RefractionMode refractionMode;
|
||||
filament::RefractionType refractionType;
|
||||
|
||||
@@ -187,6 +187,8 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel,
|
||||
|
||||
CodeGenerator::generateDefine(vs, "FLIP_UV_ATTRIBUTE", material.flipUV);
|
||||
|
||||
CodeGenerator::generateDefine(vs, "LEGACY_MORPHING", material.useLegacyMorphing);
|
||||
|
||||
const bool litVariants = lit || material.hasShadowMultiplier;
|
||||
|
||||
// note: even if the user vertex shader is empty, we can't use the "optimized" version if
|
||||
@@ -217,6 +219,16 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel,
|
||||
if (variant.hasSkinningOrMorphing()) {
|
||||
attributes.set(VertexAttribute::BONE_INDICES);
|
||||
attributes.set(VertexAttribute::BONE_WEIGHTS);
|
||||
if (material.useLegacyMorphing) {
|
||||
attributes.set(VertexAttribute::MORPH_POSITION_0);
|
||||
attributes.set(VertexAttribute::MORPH_POSITION_1);
|
||||
attributes.set(VertexAttribute::MORPH_POSITION_2);
|
||||
attributes.set(VertexAttribute::MORPH_POSITION_3);
|
||||
attributes.set(VertexAttribute::MORPH_TANGENTS_0);
|
||||
attributes.set(VertexAttribute::MORPH_TANGENTS_1);
|
||||
attributes.set(VertexAttribute::MORPH_TANGENTS_2);
|
||||
attributes.set(VertexAttribute::MORPH_TANGENTS_3);
|
||||
}
|
||||
}
|
||||
CodeGenerator::generateShaderInputs(vs, ShaderType::VERTEX, attributes, interpolation);
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ set(TEST_SRCS
|
||||
test/test_Entity.cpp
|
||||
test/test_FixedCapacityVector.cpp
|
||||
test/test_JobSystem.cpp
|
||||
test/test_RangeMap.cpp
|
||||
test/test_StructureOfArrays.cpp
|
||||
test/test_sstream.cpp
|
||||
test/test_string.cpp
|
||||
|
||||
@@ -25,10 +25,15 @@ template<typename T>
|
||||
struct Range {
|
||||
using value_type = T;
|
||||
T first = 0;
|
||||
T last = 0;
|
||||
T last = 0; // this actually refers to one past the last
|
||||
|
||||
size_t size() const noexcept { return last - first; }
|
||||
bool empty() const noexcept { return !size(); }
|
||||
bool contains(const T& t) const noexcept { return first <= t && t < last; }
|
||||
|
||||
bool overlaps(const Range<T>& that) const noexcept {
|
||||
return that.first < this->last && that.last > this->first;
|
||||
}
|
||||
|
||||
class const_iterator {
|
||||
friend struct Range;
|
||||
|
||||
311
libs/utils/include/utils/RangeMap.h
Normal file
311
libs/utils/include/utils/RangeMap.h
Normal file
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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_UTILS_RANGEMAP_H
|
||||
#define TNT_UTILS_RANGEMAP_H
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Range.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace utils {
|
||||
|
||||
/**
|
||||
* Sparse container for a series of ordered non-overlapping intervals.
|
||||
*
|
||||
* RangeMap has a low memory footprint if it contains fairly homogeneous data. Internally, the
|
||||
* intervals are automatically split and merged as elements are added or removed.
|
||||
*
|
||||
* Each interval maps to an instance of ValueType, which should support cheap equality checks
|
||||
* and copy assignment. (simple concrete types are ideal)
|
||||
*
|
||||
* KeyType should support operator< because intervals are internally sorted using std::map.
|
||||
*/
|
||||
template<typename KeyType, typename ValueType>
|
||||
class RangeMap {
|
||||
public:
|
||||
/**
|
||||
* Replaces all slots between first (inclusive) and last (exclusive).
|
||||
*/
|
||||
void add(KeyType first, KeyType last, const ValueType& value) noexcept {
|
||||
// First check if an existing range contains "first".
|
||||
Iterator iter = findRange(first);
|
||||
if (iter != end()) {
|
||||
Range<KeyType>& existing = getRange(iter);
|
||||
// Check if the existing range be extended.
|
||||
if (getValue(iter) == value) {
|
||||
if (existing.last < last) {
|
||||
wipe(existing.last, last);
|
||||
existing.last = last;
|
||||
mergeRight(iter);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Split the existing range into two ranges.
|
||||
if (last < existing.last && first > existing.first) {
|
||||
const KeyType tmp = existing.last;
|
||||
existing.last = first;
|
||||
insert(first, last, value);
|
||||
insert(last, tmp, getValue(iter));
|
||||
return;
|
||||
}
|
||||
// Clip the end of the existing range and potentially remove it.
|
||||
existing.last = first;
|
||||
clean(iter);
|
||||
// Check there is a range to the right that needs to be clipped.
|
||||
iter = findRange(last);
|
||||
if (iter != end()) {
|
||||
getRange(iter).first = last;
|
||||
clean(iter);
|
||||
}
|
||||
wipe(first, last);
|
||||
insert(first, last, value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if an existing range contains the end of the new range.
|
||||
KeyType back = last;
|
||||
iter = findRange(--back);
|
||||
if (iter == end()) {
|
||||
wipe(first, last);
|
||||
insert(first, last, value);
|
||||
return;
|
||||
}
|
||||
Range<KeyType>& existing = getRange(iter);
|
||||
|
||||
// Check if the existing range be extended.
|
||||
if (getValue(iter) == value) {
|
||||
if (existing.first > first) {
|
||||
wipe(first, existing.first);
|
||||
existing.first = first;
|
||||
mergeLeft(iter);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Clip the beginning of the existing range and potentially remove it.
|
||||
existing.first = last;
|
||||
clean(iter);
|
||||
wipe(first, last);
|
||||
insert(first, last, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for the "add" method that inserts a single element.
|
||||
*/
|
||||
void set(KeyType key, const ValueType& value) noexcept {
|
||||
KeyType begin = key;
|
||||
add(begin, ++key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a range exists that encompasses the given key.
|
||||
*/
|
||||
bool has(KeyType key) const noexcept {
|
||||
return findRange(key) != mMap.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the element at the given location, panics if no element exists.
|
||||
*/
|
||||
const ValueType& get(KeyType key) const {
|
||||
ConstIterator iter = findRange(key);
|
||||
ASSERT_PRECONDITION(iter != end(), "RangeMap: No element exists at the given key.");
|
||||
return getValue(iter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all elements between begin (inclusive) and end (exclusive).
|
||||
*/
|
||||
void clear(KeyType first, KeyType last) noexcept {
|
||||
// Check if an existing range contains "first".
|
||||
Iterator iter = findRange(first);
|
||||
if (iter != end()) {
|
||||
Range<KeyType>& existing = getRange(iter);
|
||||
// Split the existing range into two ranges.
|
||||
if (last < existing.last && first > existing.first) {
|
||||
const KeyType tmp = existing.last;
|
||||
existing.last = first;
|
||||
insert(last, tmp, getValue(iter));
|
||||
return;
|
||||
}
|
||||
// Clip the end of the existing range and potentially remove it.
|
||||
existing.last = first;
|
||||
clean(iter);
|
||||
wipe(first, last);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if an existing range contains the end of the new range.
|
||||
KeyType back = last;
|
||||
iter = findRange(--back);
|
||||
if (iter == end()) {
|
||||
wipe(first, last);
|
||||
return;
|
||||
}
|
||||
Range<KeyType>& existing = getRange(iter);
|
||||
|
||||
// Clip the beginning of the existing range and potentially remove it.
|
||||
existing.first = last;
|
||||
clean(iter);
|
||||
wipe(first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for the "clear" method that clears a single element.
|
||||
*/
|
||||
void reset(KeyType key) noexcept {
|
||||
KeyType begin = key;
|
||||
clear(begin, ++key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of internal interval objects (rarely used).
|
||||
*/
|
||||
size_t rangeCount() const noexcept { return mMap.size(); }
|
||||
|
||||
private:
|
||||
|
||||
using Map = std::map<KeyType, std::pair<Range<KeyType>, ValueType>>;
|
||||
using Iterator = typename Map::iterator;
|
||||
using ConstIterator = typename Map::const_iterator;
|
||||
|
||||
ConstIterator begin() const noexcept { return mMap.begin(); }
|
||||
ConstIterator end() const noexcept { return mMap.end(); }
|
||||
|
||||
Iterator begin() noexcept { return mMap.begin(); }
|
||||
Iterator end() noexcept { return mMap.end(); }
|
||||
|
||||
Range<KeyType>& getRange(Iterator iter) const { return iter->second.first; }
|
||||
ValueType& getValue(Iterator iter) const { return iter->second.second; }
|
||||
|
||||
const Range<KeyType>& getRange(ConstIterator iter) const { return iter->second.first; }
|
||||
const ValueType& getValue(ConstIterator iter) const { return iter->second.second; }
|
||||
|
||||
// Private helper that assumes there is no existing range that overlaps the given range.
|
||||
void insert(KeyType first, KeyType last, const ValueType& value) noexcept {
|
||||
assert_invariant(!has(first));
|
||||
assert_invariant(!has(last - 1));
|
||||
|
||||
// Check if there is an adjacent range to the left than can be extended.
|
||||
KeyType previous = first;
|
||||
if (Iterator iter = findRange(--previous); iter != end() && getValue(iter) == value) {
|
||||
getRange(iter).last = last;
|
||||
mergeRight(iter);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there is an adjacent range to the right than can be extended.
|
||||
if (Iterator iter = findRange(last); iter != end() && getValue(iter) == value) {
|
||||
getRange(iter).first = first;
|
||||
return;
|
||||
}
|
||||
|
||||
mMap[first] = {Range<KeyType> { first, last }, value};
|
||||
}
|
||||
|
||||
// Private helper that erases all intervals that are wholly contained within the given range.
|
||||
// Note that this is quite different from the public "clear" method.
|
||||
void wipe(KeyType first, KeyType last) noexcept {
|
||||
// Find the first range whose beginning is greater than or equal to "first".
|
||||
Iterator iter = mMap.lower_bound(first);
|
||||
while (iter != end() && getRange(iter).first < last) {
|
||||
KeyType existing_last = getRange(iter).last;
|
||||
if (existing_last > last) {
|
||||
break;
|
||||
}
|
||||
iter = mMap.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if there is range to the right that touches the given range.
|
||||
// If so, erases it, extends the given range rightwards, and returns true.
|
||||
bool mergeRight(Iterator iter) {
|
||||
Iterator next = iter;
|
||||
if (++next == end() || getValue(next) != getValue(iter)) {
|
||||
return false;
|
||||
}
|
||||
if (getRange(next).first != getRange(iter).last) {
|
||||
return false;
|
||||
}
|
||||
getRange(iter).last = getRange(next).last;
|
||||
mMap.erase(next);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks if there is range to the left that touches the given range.
|
||||
// If so, erases it, extends the given range leftwards, and returns true.
|
||||
bool mergeLeft(Iterator iter) {
|
||||
Iterator prev = iter;
|
||||
if (--prev == end() || getValue(prev) != getValue(iter)) {
|
||||
return false;
|
||||
}
|
||||
if (getRange(prev).last != getRange(iter).first) {
|
||||
return false;
|
||||
}
|
||||
getRange(iter).first = getRange(prev).first;
|
||||
mMap.erase(prev);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Erases the given range if it contains no elements.
|
||||
void clean(Iterator iter) {
|
||||
Range<KeyType>& range = getRange(iter);
|
||||
assert_invariant(range.first <= range.last);
|
||||
if (range.first == range.last) {
|
||||
mMap.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
// If the given key is encompassed by an existing range, returns an iterator for that range.
|
||||
// If no encompassing range exists, returns end().
|
||||
ConstIterator findRange(KeyType key) const noexcept {
|
||||
return findRangeT<ConstIterator>(*this, key);
|
||||
}
|
||||
|
||||
// If the given key is encompassed by an existing range, returns an iterator for that range.
|
||||
// If no encompassing range exists, returns end().
|
||||
Iterator findRange(KeyType key) noexcept {
|
||||
return findRangeT<Iterator>(*this, key);
|
||||
}
|
||||
|
||||
// This template method allows us to avoid code duplication for const and non-const variants of
|
||||
// findRange. C++17 has "std::as_const()" but that would not be helpful here, as we would still
|
||||
// need to convert a const iterator to a non-const iterator.
|
||||
template<typename IteratorType, typename SelfType>
|
||||
static IteratorType findRangeT(SelfType& instance, KeyType key) noexcept {
|
||||
// Find the first range whose beginning is greater than or equal to the given key.
|
||||
IteratorType iter = instance.mMap.lower_bound(key);
|
||||
if (iter != instance.end() && instance.getRange(iter).contains(key)) {
|
||||
return iter;
|
||||
}
|
||||
// If that was the first range, or if the map is empty, return false.
|
||||
if (iter == instance.begin()) {
|
||||
return instance.end();
|
||||
}
|
||||
// Check the range immediately previous to the one that was found.
|
||||
return instance.getRange(--iter).contains(key) ? iter : instance.end();
|
||||
}
|
||||
|
||||
// This maps from the start value of each range to the range itself.
|
||||
Map mMap;
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
|
||||
#endif // TNT_UTILS_RANGEMAP_H
|
||||
@@ -314,10 +314,12 @@ private:
|
||||
|
||||
using bitset8 = bitset<uint8_t>;
|
||||
using bitset32 = bitset<uint32_t>;
|
||||
using bitset64 = bitset<uint64_t>;
|
||||
using bitset256 = bitset<uint64_t, 4>;
|
||||
|
||||
static_assert(sizeof(bitset8) == sizeof(uint8_t), "bitset8 isn't 8 bits!");
|
||||
static_assert(sizeof(bitset32) == sizeof(uint32_t), "bitset32 isn't 32 bits!");
|
||||
static_assert(sizeof(bitset64) == sizeof(uint64_t), "bitset64 isn't 64 bits!");
|
||||
|
||||
} // namespace utils
|
||||
|
||||
|
||||
121
libs/utils/test/test_RangeMap.cpp
Normal file
121
libs/utils/test/test_RangeMap.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <utils/RangeMap.h>
|
||||
|
||||
using namespace utils;
|
||||
|
||||
TEST(RangeMapTest, Simple) {
|
||||
RangeMap<int, char> map;
|
||||
|
||||
map.add(45, 104, 'c');
|
||||
EXPECT_FALSE(map.has(104));
|
||||
EXPECT_EQ(map.get(45), 'c');
|
||||
EXPECT_EQ(map.get(103), 'c');
|
||||
EXPECT_EQ(map.rangeCount(), 1);
|
||||
|
||||
// Perform a no-op.
|
||||
map.set(103, 'c');
|
||||
EXPECT_EQ(map.rangeCount(), 1);
|
||||
|
||||
// Extend a neighboring interval.
|
||||
map.set(104, 'c');
|
||||
EXPECT_TRUE(map.has(104));
|
||||
EXPECT_EQ(map.get(104), 'c');
|
||||
EXPECT_EQ(map.rangeCount(), 1);
|
||||
|
||||
// Add a second interval.
|
||||
map.set(105, 'd');
|
||||
EXPECT_EQ(map.get(105), 'd');
|
||||
EXPECT_EQ(map.rangeCount(), 2);
|
||||
|
||||
// Extend an existing interval and wipe out the interval we just added.
|
||||
map.set(105, 'c');
|
||||
EXPECT_EQ(map.get(105), 'c');
|
||||
EXPECT_EQ(map.rangeCount(), 1);
|
||||
|
||||
// Clip the endpoint of an existing interval.
|
||||
map.set(105, 'd');
|
||||
EXPECT_EQ(map.get(104), 'c');
|
||||
EXPECT_EQ(map.get(105), 'd');
|
||||
EXPECT_EQ(map.rangeCount(), 2);
|
||||
|
||||
// Extend the second interval backwards while clipping the first one.
|
||||
map.set(104, 'd');
|
||||
EXPECT_EQ(map.get(104), 'd');
|
||||
EXPECT_EQ(map.get(103), 'c');
|
||||
EXPECT_EQ(map.rangeCount(), 2);
|
||||
|
||||
// Insert a range into the middle of the first range.
|
||||
map.add(46, 50, 'e');
|
||||
EXPECT_EQ(map.rangeCount(), 4);
|
||||
EXPECT_EQ(map.get(45), 'c');
|
||||
EXPECT_EQ(map.get(46), 'e');
|
||||
EXPECT_EQ(map.get(49), 'e');
|
||||
EXPECT_EQ(map.get(50), 'c');
|
||||
|
||||
// Extend the range that we just added backwards, wiping out its leftward neighbor.
|
||||
map.add(44, 50, 'e');
|
||||
EXPECT_EQ(map.rangeCount(), 3);
|
||||
|
||||
map.reset(45);
|
||||
EXPECT_FALSE(map.has(45));
|
||||
EXPECT_TRUE(map.has(46));
|
||||
|
||||
map.clear(44, 47);
|
||||
EXPECT_EQ(map.rangeCount(), 3);
|
||||
EXPECT_FALSE(map.has(46));
|
||||
EXPECT_TRUE(map.has(47));
|
||||
map.clear(47, 50);
|
||||
EXPECT_EQ(map.rangeCount(), 2);
|
||||
|
||||
// Clearing the middle of an existing interval should split it.
|
||||
map.reset(55);
|
||||
EXPECT_EQ(map.rangeCount(), 3);
|
||||
EXPECT_TRUE(map.has(54));
|
||||
EXPECT_FALSE(map.has(55));
|
||||
EXPECT_TRUE(map.has(56));
|
||||
|
||||
map.clear(-100, 200);
|
||||
map.add(0, 10, 'a');
|
||||
map.add(12, 15, 'a');
|
||||
map.add(20, 22, 'b');
|
||||
map.add(23, 26, 'c');
|
||||
EXPECT_EQ(map.rangeCount(), 4);
|
||||
|
||||
// Test a bunch of different ways that the first and second intervals can be merged.
|
||||
map.add(10, 12, 'a');
|
||||
EXPECT_EQ(map.rangeCount(), 3);
|
||||
map.clear(10, 12);
|
||||
EXPECT_EQ(map.rangeCount(), 4);
|
||||
map.add(9, 12, 'a');
|
||||
EXPECT_EQ(map.rangeCount(), 3);
|
||||
map.clear(10, 12);
|
||||
EXPECT_EQ(map.rangeCount(), 4);
|
||||
map.add(10, 13, 'a');
|
||||
EXPECT_EQ(map.rangeCount(), 3);
|
||||
map.add(10, 12, 'b');
|
||||
EXPECT_EQ(map.rangeCount(), 4);
|
||||
|
||||
map.add(0, 26, 'b');
|
||||
EXPECT_EQ(map.rangeCount(), 1);
|
||||
map.add(10, 12, 'c');
|
||||
EXPECT_EQ(map.rangeCount(), 3);
|
||||
map.add(9, 13, 'c');
|
||||
EXPECT_EQ(map.rangeCount(), 3);
|
||||
}
|
||||
@@ -997,7 +997,8 @@ void SimpleViewer::updateUserInterface() {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
for (int i = 0; i != mMorphWeights.size(); ++i) {
|
||||
ImGui::SliderFloat(mAsset->getMorphTargetNameAt(mCurrentMorphingEntity, i),
|
||||
const char* name = mAsset->getMorphTargetNameAt(mCurrentMorphingEntity, i);
|
||||
ImGui::SliderFloat(name ? name : "Unnamed target",
|
||||
&mMorphWeights[i], 0.0f, 1.0);
|
||||
}
|
||||
if (isAnimating) {
|
||||
|
||||
@@ -101,7 +101,14 @@ vec4 getPosition() {
|
||||
#if defined(HAS_SKINNING_OR_MORPHING)
|
||||
|
||||
if ((objectUniforms.flags & FILAMENT_OBJECT_MORPHING_ENABLED_BIT) != 0u) {
|
||||
#if defined(LEGACY_MORPHING)
|
||||
pos += morphingUniforms.weights[0] * mesh_custom0;
|
||||
pos += morphingUniforms.weights[1] * mesh_custom1;
|
||||
pos += morphingUniforms.weights[2] * mesh_custom2;
|
||||
pos += morphingUniforms.weights[3] * mesh_custom3;
|
||||
#else
|
||||
morphPosition(pos);
|
||||
#endif
|
||||
}
|
||||
|
||||
if ((objectUniforms.flags & FILAMENT_OBJECT_SKINNING_ENABLED_BIT) != 0u) {
|
||||
|
||||
@@ -36,8 +36,20 @@ void main() {
|
||||
|
||||
#if defined(HAS_SKINNING_OR_MORPHING)
|
||||
if ((objectUniforms.flags & FILAMENT_OBJECT_MORPHING_ENABLED_BIT) != 0u) {
|
||||
#if defined(LEGACY_MORPHING)
|
||||
vec3 normal0, normal1, normal2, normal3;
|
||||
toTangentFrame(mesh_custom4, normal0);
|
||||
toTangentFrame(mesh_custom5, normal1);
|
||||
toTangentFrame(mesh_custom6, normal2);
|
||||
toTangentFrame(mesh_custom7, normal3);
|
||||
material.worldNormal += morphingUniforms.weights[0].xyz * normal0;
|
||||
material.worldNormal += morphingUniforms.weights[1].xyz * normal1;
|
||||
material.worldNormal += morphingUniforms.weights[2].xyz * normal2;
|
||||
material.worldNormal += morphingUniforms.weights[3].xyz * normal3;
|
||||
#else
|
||||
morphNormal(material.worldNormal);
|
||||
material.worldNormal = normalize(material.worldNormal);
|
||||
#endif
|
||||
}
|
||||
|
||||
if ((objectUniforms.flags & FILAMENT_OBJECT_SKINNING_ENABLED_BIT) != 0u) {
|
||||
|
||||
@@ -590,6 +590,13 @@ static bool processFramebufferFetch(MaterialBuilder& builder, const JsonishValue
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool processLegacyMorphing(MaterialBuilder& builder, const JsonishValue& value) {
|
||||
if (value.toJsonBool()->getBool()) {
|
||||
builder.useLegacyMorphing();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool processCustomSurfaceShading(MaterialBuilder& builder, const JsonishValue& value) {
|
||||
builder.customSurfaceShading(value.toJsonBool()->getBool());
|
||||
return true;
|
||||
@@ -759,6 +766,7 @@ ParametersProcessor::ParametersProcessor() {
|
||||
mParameters["refractionMode"] = { &processRefractionMode, Type::STRING };
|
||||
mParameters["refractionType"] = { &processRefractionType, Type::STRING };
|
||||
mParameters["framebufferFetch"] = { &processFramebufferFetch, Type::BOOL };
|
||||
mParameters["legacyMorphing"] = { &processLegacyMorphing, Type::BOOL };
|
||||
mParameters["outputs"] = { &processOutputs, Type::ARRAY };
|
||||
mParameters["quality"] = { &processQuality, Type::STRING };
|
||||
mParameters["customSurfaceShading"] = { &processCustomSurfaceShading, Type::BOOL };
|
||||
|
||||
8
web/filament-js/filament.d.ts
vendored
8
web/filament-js/filament.d.ts
vendored
@@ -1001,6 +1001,14 @@ export enum VertexAttribute {
|
||||
CUSTOM5 = 13,
|
||||
CUSTOM6 = 14,
|
||||
CUSTOM7 = 15,
|
||||
MORPH_POSITION_0 = CUSTOM0,
|
||||
MORPH_POSITION_1 = CUSTOM1,
|
||||
MORPH_POSITION_2 = CUSTOM2,
|
||||
MORPH_POSITION_3 = CUSTOM3,
|
||||
MORPH_TANGENTS_0 = CUSTOM4,
|
||||
MORPH_TANGENTS_1 = CUSTOM5,
|
||||
MORPH_TANGENTS_2 = CUSTOM6,
|
||||
MORPH_TANGENTS_3 = CUSTOM7,
|
||||
}
|
||||
|
||||
export enum VertexBuffer$AttributeType {
|
||||
|
||||
@@ -61,7 +61,15 @@ enum_<VertexAttribute>("VertexAttribute")
|
||||
.value("CUSTOM4", CUSTOM4)
|
||||
.value("CUSTOM5", CUSTOM5)
|
||||
.value("CUSTOM6", CUSTOM6)
|
||||
.value("CUSTOM7", CUSTOM7);
|
||||
.value("CUSTOM7", CUSTOM7)
|
||||
.value("MORPH_POSITION_0", MORPH_POSITION_0)
|
||||
.value("MORPH_POSITION_1", MORPH_POSITION_1)
|
||||
.value("MORPH_POSITION_2", MORPH_POSITION_2)
|
||||
.value("MORPH_POSITION_3", MORPH_POSITION_3)
|
||||
.value("MORPH_TANGENTS_0", MORPH_TANGENTS_0)
|
||||
.value("MORPH_TANGENTS_1", MORPH_TANGENTS_1)
|
||||
.value("MORPH_TANGENTS_2", MORPH_TANGENTS_2)
|
||||
.value("MORPH_TANGENTS_3", MORPH_TANGENTS_3);
|
||||
|
||||
enum_<BufferObject::BindingType>("BufferObject$BindingType")
|
||||
.value("VERTEX", BufferObject::BindingType::VERTEX);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "filament",
|
||||
"version": "1.17.0",
|
||||
"version": "1.18.0",
|
||||
"description": "Real-time physically based rendering engine",
|
||||
"main": "filament.js",
|
||||
"module": "filament.js",
|
||||
|
||||
@@ -30,13 +30,18 @@
|
||||
Filament.Buffer = function(typedarray) {
|
||||
console.assert(typedarray.buffer instanceof ArrayBuffer);
|
||||
console.assert(typedarray.byteLength > 0);
|
||||
if (Filament.HEAPU32.buffer == typedarray.buffer) {
|
||||
typedarray = new Uint8Array(typedarray);
|
||||
}
|
||||
const ta = typedarray;
|
||||
|
||||
// The only reason we need to create a copy here is that emscripten might "grow" its entire heap
|
||||
// (i.e. destroy and recreate) during the allocation of the BufferDescriptor, which would cause
|
||||
// detachment if the source array happens to be view into the old emscripten heap.
|
||||
const ta = typedarray.slice();
|
||||
|
||||
const bd = new Filament.driver$BufferDescriptor(ta.byteLength);
|
||||
const uint8array = new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength);
|
||||
|
||||
// getBytes() returns a view into the emscripten heap, this just does a memcpy into it.
|
||||
bd.getBytes().set(uint8array);
|
||||
|
||||
return bd;
|
||||
};
|
||||
|
||||
@@ -49,10 +54,7 @@ Filament.Buffer = function(typedarray) {
|
||||
Filament.PixelBuffer = function(typedarray, format, datatype) {
|
||||
console.assert(typedarray.buffer instanceof ArrayBuffer);
|
||||
console.assert(typedarray.byteLength > 0);
|
||||
if (Filament.HEAPU32.buffer == typedarray.buffer) {
|
||||
typedarray = new Uint8Array(typedarray);
|
||||
}
|
||||
const ta = typedarray;
|
||||
const ta = typedarray.slice();
|
||||
const bd = new Filament.driver$PixelBufferDescriptor(ta.byteLength, format, datatype);
|
||||
const uint8array = new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength);
|
||||
bd.getBytes().set(uint8array);
|
||||
@@ -69,10 +71,7 @@ Filament.CompressedPixelBuffer = function(typedarray, cdatatype, faceSize) {
|
||||
console.assert(typedarray.buffer instanceof ArrayBuffer);
|
||||
console.assert(typedarray.byteLength > 0);
|
||||
faceSize = faceSize || typedarray.byteLength;
|
||||
if (Filament.HEAPU32.buffer == typedarray.buffer) {
|
||||
typedarray = new Uint8Array(typedarray);
|
||||
}
|
||||
const ta = typedarray;
|
||||
const ta = typedarray.slice();
|
||||
const bd = new Filament.driver$PixelBufferDescriptor(ta.byteLength, cdatatype, faceSize, true);
|
||||
const uint8array = new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength);
|
||||
bd.getBytes().set(uint8array);
|
||||
|
||||
Reference in New Issue
Block a user