Compare commits

...

300 Commits

Author SHA1 Message Date
Philip Rideout
cdadb43e50 WebGL: another fix for BufferDescriptor bindings.
The previous code would convert each element of the source data
into 8 bit-per-element, but we wnat to preserve the original format
that the user provides.

The new solution is to use `slice()` which is a robust way to clone
all the data in a typed array.

This fixes the new regression with Triangle that Ben caught.
2022-02-09 15:44:26 -08:00
Philip Rideout
edaff60fbf WebGL: remove buffer sharing optimization.
If emscripten grows the heap inside one of our BufferDescriptor binding
functions, then the old heap becomes "detached" and an error can
occur.

This fixes the issue seen with the Parquet demo that Ben caught.
2022-02-09 12:51:06 -08:00
Benjamin Doherty
32dab23bc6 Update RELEASE_NOTES for 1.18.0 2022-02-08 09:25:41 -08:00
Benjamin Doherty
362de7dd31 Merge branch 'rc/1.18.0' into release 2022-02-08 09:22:21 -08:00
Benjamin Doherty
6b01fbb903 Correct version to 1.18.0 2022-02-08 09:08:49 -08:00
Philip Rideout
4934d9f7bc Vulkan: fix leak when apps do not draw anything. 2022-02-07 14:27:06 -08:00
Ben Doherty
ed73955b00 Initialize useLegacyMorphing to fix MSAN warning (#5164) 2022-02-07 11:28:03 -08:00
Ben Doherty
3f1f2726c4 Add a MAX_LEGACY_MORPH_TARGETS definition (#5163) 2022-02-07 11:27:45 -08:00
Philip Rideout
1a7bd7ea8d Rewrite VulkanPipelineCache (without changing its API).
All three types of caches (descriptor sets, pipelines, and pipeline
layouts) are now managed in exactly the same way. They all use an LRU
eviction scheme that is based on a count of command buffer flush
events.

Vulkan objects can only be destroyed if there are no in-flight command
buffers that reference them, so an easy way to know when it is safe to
evict a given entry is to wait for "N" flushes after its last use, where
"N" is the number of command buffers in the command buffer ring.

Another big simplification is that there are no more dirty flags,
instead we store two sets of state vectors for each type of cache: the
"currently bound" state, and the "current requirements" state.
2022-02-03 17:37:40 -08:00
Ben Doherty
946ea43436 Fix sampler overflow check in SamplerBindingMap (#5143) 2022-02-02 11:04:48 -08:00
Philip Rideout
7f42385f5f VulkanPipelineCache: fix spurious SEGV.
Fixes #5142 by replacing unsafe pointers with map keys.

One of the differences between robin_map and unordered_map is the
following:

    pointers to keys or values in the map are invalidated in the same
    way as iterators to these keys-values

Therefore it is unsafe to track the pointer to a value that is stored
in a robin_map.
2022-02-01 16:34:34 -08:00
Philip Rideout
8845ac2b75 VulkanPipelineCache: code cleanup and minor fixes.
This is mostly just code cleanup. One actual bug was the fact that the
dummy sampler was re-created every time a new pipeline layout was
created.

It also felt strange to use `auto&` to refer to a C-style array. I
changed this into a `std::array` which is more consistent with other
fixed size arrays in this class.
2022-02-01 16:34:27 -08:00
Ben Doherty
da85001d4d Support legacy morphing (#5129)
Support legacy morphing (morphing with targets supplied via VertexAttributes) for older clients. This gives clients more time to transition over to the new MorphTargetBuffer API.
2022-01-27 16:08:46 -08:00
Mathias Agopian
8d15079937 update material version to 18 2022-01-26 14:32:46 -08:00
daemyung jang
adc542b5cd Bind samplers to specified shader stages (#5036)
Co-authored-by: Ben Doherty <bendoherty@google.com>
Co-authored-by: Mathias Agopian <mathias@google.com>
2022-01-26 14:31:30 -08:00
Philip Rideout
72feb044a3 Vulkan now supports offsets when uploading texture data.
This allows `MorphStressTest` to work on Vulkan.

However, `Horse` is still broken because it provides positions but not
tangents.  Separate fix for that is coming.

Partial fix for #5109.
2022-01-26 14:31:00 -08:00
Philip Rideout
c0ba260ddf Vulkan: clean up image layout management.
This fixes validation errors and makes a first pass at simplification.
VulkanTexture now tracks image layout using RangeMap, which paves the
way for further simplification.
2022-01-26 14:30:45 -08:00
Philip Rideout
2da215e8e7 RangeMap: improve naming convention, etc. 2022-01-26 14:30:23 -08:00
Philip Rideout
4d0368b5f1 RangeMap: improve the auto-merge functionality. 2022-01-26 14:30:09 -08:00
Philip Rideout
d11c78857d utils: introduce RangeMap container and unit test.
This will allow the Vulkan backend to efficiently track the subresource
image layouts for each texture.

This is a sparse container for a series of ordered non-overlapping
integer intervals, where each interval maps to a concrete value.
2022-01-26 14:29:45 -08:00
Mathias Agopian
e829d90c4a A morphing buffer must be bound when skinning is active
This is because we're using the same program variant for skinning
and morphing, in the skinning-only case, the buffer won't be accessed
in the shader, but it must be present.

fixes #5085
2022-01-26 14:20:25 -08:00
Mathias Agopian
a8006acd33 [GL backend] fix sampler binding bug
When a program had an unused SamplerInterfaceBlock, other samplers
could be bound to the wrong TMU

Fixes #5088
2022-01-26 14:20:12 -08:00
Benjamin Doherty
86ec502040 Bump version to 1.17.1 2022-01-24 12:52:22 -08:00
Benjamin Doherty
b19a73cc50 Merge branch 'rc/1.17.0' into release 2022-01-24 12:50:22 -08:00
Benjamin Doherty
a99c695932 Update RELEASE_NOTES for 1.17.0 2022-01-24 12:45:05 -08:00
Benjamin Doherty
8cd720b53a Bump version to 1.17.0 2022-01-18 13:54:40 -08:00
Benjamin Doherty
04df79e58f Merge branch 'rc/1.16.1' into release 2022-01-18 13:53:02 -08:00
Benjamin Doherty
a4b3717762 Update RELEASE_NOTES for 1.16.1 2022-01-18 13:48:36 -08:00
Benjamin Doherty
1035e442ee Bump version to 1.16.1 2022-01-10 10:47:46 -08:00
Benjamin Doherty
60d3638f15 Merge branch 'rc/1.16.0' into release 2022-01-10 10:44:36 -08:00
Benjamin Doherty
3e7d3c9035 Update RELEASE_NOTES for 1.16.0 2022-01-10 10:41:14 -08:00
jeanlemotan
eb360be2ad Fixed cubemap update 2022-01-10 10:34:49 -08:00
Benjamin Doherty
485ac8704d Bump version to 1.16.0 2022-01-04 11:55:19 -08:00
Benjamin Doherty
dc74540423 Merge branch 'rc/1.15.2' into release 2022-01-04 11:53:02 -08:00
Benjamin Doherty
96219c22db Update RELEASE_NOTES for 1.15.2 2022-01-04 11:49:50 -08:00
Romain Guy
f3b7048775 Add missing JNI impl (#4959) 2022-01-04 11:43:19 -08:00
Romain Guy
aaed6fb376 Fix rounding math 2022-01-04 11:43:13 -08:00
Romain Guy
35a5d3310f Fix preprocessor test 2021-12-13 13:53:20 -07:00
Benjamin Doherty
9da79a1d2d Bump version to 1.15.2 2021-12-13 11:08:28 -07:00
Benjamin Doherty
595b355d1b Merge branch 'rc/1.15.1' into release 2021-12-13 11:06:45 -07:00
Benjamin Doherty
3a67d769f4 Update RELEASE_NOTES for 1.15.1 2021-12-06 16:54:28 -08:00
Benjamin Doherty
6fb536a937 Bump version to 1.15.1 2021-12-06 11:17:37 -08:00
Benjamin Doherty
bb460d78d8 Merge branch 'rc/1.15.0' into release 2021-12-06 11:14:00 -08:00
Benjamin Doherty
838835a715 Update RELEASE_NOTES for 1.15.0 2021-12-02 11:52:04 -08:00
Romain Guy
63acd53e23 Use __ANDROID__ instead of ANDROID 2021-11-30 11:15:49 -08:00
Romain Guy
fcd2d0457b Workaround for a build system issue 2021-11-30 11:15:42 -08:00
Benjamin Doherty
58fc26461b Bump version to 1.15.0 2021-11-29 14:08:28 -08:00
Benjamin Doherty
fd82f6b04e Merge branch 'rc/1.14.2' into release 2021-11-29 14:07:03 -08:00
Benjamin Doherty
cef3200533 Add additional RELEASE_NOTES for 1.14.2 2021-11-29 10:21:56 -08:00
Ben Doherty
634500c398 Fix, avoid divide-by-zero inside makeBone (#4889) 2021-11-24 16:29:33 -08:00
Ben Doherty
b3e294ac54 Fix Metal depth comparison initialization (#4886) 2021-11-23 12:09:43 -08:00
Benjamin Doherty
2bf7535ad0 Update RELEASE_NOTES for 1.14.2 2021-11-22 10:16:00 -08:00
Benjamin Doherty
3315f75de9 Bump version to 1.14.2 2021-11-22 10:12:44 -08:00
Benjamin Doherty
bbe7dbfa92 Merge branch 'rc/1.14.1' into release 2021-11-22 10:11:06 -08:00
Benjamin Doherty
5697922a65 Update RELEASE_NOTES for 1.14.1 2021-11-17 12:04:26 -08:00
Ben Doherty
4da83df2b9 Fix material compilation error with device vertex domain (#4865)
A recent refactor was causing the following error when the vertex domain
was set to `device`:
```
ERROR: main.vs:23: 'material' : undeclared identifier
ERROR: main.vs:23: 'materialVertex' : no matching overloaded function found
```
2021-11-17 12:03:55 -08:00
Ben Doherty
8f156d6588 Android: re-enable VSM cascade fix (#4863) 2021-11-17 10:19:09 -08:00
Benjamin Doherty
cec0871c11 Bump version to 1.14.1 2021-11-15 10:09:45 -08:00
Benjamin Doherty
41a809368b Merge branch 'rc/1.14.0' into release 2021-11-15 10:07:56 -08:00
Romain Guy
ea53eb9290 Skip task incompatible with configuration caching (#4831) 2021-11-09 15:56:00 -08:00
Benjamin Doherty
05875057c9 Update RELEASE_NOTES for 1.14.0 2021-11-09 15:51:13 -08:00
Benjamin Doherty
44125926d1 Disable configuration-cache 2021-11-08 17:05:34 -08:00
Benjamin Doherty
60734349de Bump version to 1.14.0 2021-11-08 11:52:50 -08:00
Benjamin Doherty
fbfd5ec0ec Merge branch 'rc/1.13.0' into release 2021-11-08 11:50:19 -08:00
Ben Doherty
a74a95cc65 Call VirtualMachineEnv::JNI_OnLoad for non-Android Java builds (better fix) (#4779) 2021-11-04 13:28:17 -07:00
Benjamin Doherty
bc0ea16ff0 Update RELEASE_NOTES for 1.13.0 2021-11-02 13:15:53 -07:00
Mathias Agopian
b2dc8aa84c Fix typo that broke the directional shadowmap 2021-11-02 13:11:44 -07:00
Benjamin Doherty
9987e8b6ab Bump version to 1.13.0 2021-11-01 14:59:11 -07:00
Benjamin Doherty
0d9bdcc008 Merge branch 'rc/1.12.11' into release 2021-11-01 14:55:58 -07:00
Benjamin Doherty
b5c634045e Update RELEASE_NOTES for 1.12.11 2021-10-28 16:13:50 -07:00
Ben Doherty
1f05531d53 Call VirtualMachineEnv::JNI_OnLoad for non-Android Java builds (better fix) (#4779) 2021-10-28 16:02:23 -07:00
Benjamin Doherty
88f382f0e3 Revert "refactor colorgrading materials"
This reverts commit fb86a77cf8.
2021-10-28 15:56:50 -07:00
Benjamin Doherty
3e59925900 Remove problematic configuration-cache setting for release build 2021-10-28 15:56:26 -07:00
Benjamin Doherty
ea404f8d4f Remove problematic configuration-cache setting for release build 2021-10-26 10:09:51 -07:00
Benjamin Doherty
602a550d93 Bump version to 1.12.11 2021-10-25 12:30:37 -07:00
Benjamin Doherty
12abbe2d23 Merge branch 'rc/1.12.10' into release 2021-10-25 11:06:18 -07:00
Benjamin Doherty
fb0ee97588 Update RELEASE_NOTES for 1.12.10 2021-10-25 11:00:06 -07:00
Ben Doherty
56ef48c9c3 Fix, call VirtualMachineEnv::JNI_OnLoad for non-Android Java builds (#4749) 2021-10-25 10:39:24 -07:00
Benjamin Doherty
47c3dd3dd1 Revert "refactor colorgrading materials"
This reverts commit fb86a77cf8.
2021-10-25 10:37:52 -07:00
Benjamin Doherty
c181648bfa Bump version to 1.12.10 2021-10-20 12:15:51 -07:00
Benjamin Doherty
0cf78b3abe Merge branch 'rc/1.12.9' into release 2021-10-20 12:12:33 -07:00
Ben Doherty
22889a7ad9 Fix VirtualMachineEnv.cpp with older JNI versions (#4752) 2021-10-20 11:16:55 -07:00
Ben Doherty
a14451d0ac Fix, call VirtualMachineEnv::JNI_OnLoad for non-Android Java builds (#4749) 2021-10-20 10:41:34 -07:00
Benjamin Doherty
5dfdab10b7 Revert "refactor colorgrading materials"
This reverts commit fb86a77cf8.
2021-10-19 16:13:21 -07:00
Benjamin Doherty
d6f2e3b8e9 Update RELEASE_NOTES for 1.12.9 2021-10-14 18:22:20 -07:00
Benjamin Doherty
df30517743 Bump version to 1.12.9 2021-10-11 11:06:43 -07:00
Benjamin Doherty
8f80643c1a Merge branch 'rc/1.12.8' into release 2021-10-11 11:02:56 -07:00
Benjamin Doherty
5aea9be2fb Update RELEASE_NOTES for 1.12.8 2021-10-07 11:03:45 -07:00
Benjamin Doherty
ad02e483d0 Bump version to 1.12.8 2021-10-04 10:26:57 -07:00
Benjamin Doherty
f463d53036 Merge branch 'rc/1.12.7' into release 2021-10-04 10:21:42 -07:00
Benjamin Doherty
b8d4408524 Update RELEASE_NOTES for 1.12.7 2021-10-04 10:18:56 -07:00
Benjamin Doherty
fef70be848 Bump version to 1.12.7 2021-09-27 11:15:58 -07:00
Benjamin Doherty
bdb12d9b24 Merge branch 'rc/1.12.6' into release 2021-09-27 11:12:42 -07:00
Ben Doherty
43ad283a83 Fix, missing call to setTransparencyMode (#4674) 2021-09-24 13:32:49 -07:00
Benjamin Doherty
2e4936afc4 Revert "fix a race in jobsystem"
This reverts commit 2feb0ad325.
2021-09-24 11:24:44 -07:00
Benjamin Doherty
891ffabd11 Update RELEASE_NOTES for 1.12.6 2021-09-23 17:25:25 -07:00
Ben Doherty
e2c19498b4 Metal: don't call createTextureViewWithsSwizzle directly (#4662) 2021-09-23 11:28:04 -07:00
Ben Doherty
c32630b265 Fix MetalBlitter crash when shader contains warnings (#4663) 2021-09-23 11:27:56 -07:00
Alexey Pelykh
bf21e78d02 Podspec: include headers in nested directories (#4658) 2021-09-22 16:48:12 -07:00
Benjamin Doherty
525d4e08a3 Bump version to 1.12.6 2021-09-20 10:17:18 -07:00
Benjamin Doherty
2e9bf6d694 Merge branch 'rc/1.12.5' into release 2021-09-20 10:14:37 -07:00
Benjamin Doherty
e845f01d85 Update RELEASE_NOTES for 1.12.5 2021-09-20 10:10:56 -07:00
Benjamin Doherty
bef48be7b4 Bump version to 1.12.5 2021-09-13 10:47:09 -07:00
Benjamin Doherty
b54fdc9e6e Merge branch 'rc/1.12.4' into release 2021-09-13 10:43:06 -07:00
Benjamin Doherty
cedbf2e30b Update RELEASE_NOTES for 1.12.4 2021-09-13 10:40:55 -07:00
Benjamin Doherty
592f8d1b0d Bump version to 1.12.4 2021-09-08 11:05:44 -07:00
Benjamin Doherty
29612a684e Merge branch 'rc/1.12.3' into release 2021-09-08 11:03:53 -07:00
Benjamin Doherty
e6d5807399 Bump version to 1.12.3 2021-08-30 11:46:51 -07:00
Benjamin Doherty
fa2553251f Merge branch 'rc/1.12.2' into release 2021-08-30 11:44:00 -07:00
Benjamin Doherty
7387718852 Update RELEASE_NOTES for 1.12.2 2021-08-30 11:41:52 -07:00
Ben Doherty
a503a6209a Fix regression with clipSpaceTransform (#4552) 2021-08-26 14:14:58 -07:00
Ben Doherty
ce3e5f74e8 Fix Metal STREAM buffers (#4543) 2021-08-25 09:48:34 -07:00
Ben Doherty
f37112358e Fix missing bookkeeping in bindUniformBufferRange (#4538) 2021-08-24 09:51:40 -07:00
Benjamin Doherty
f368b14621 Bump version to 1.12.2 2021-08-23 12:41:33 -07:00
Benjamin Doherty
6960b1148a Merge branch 'rc/1.12.1' into release 2021-08-23 12:39:38 -07:00
Benjamin Doherty
3cc23aac25 Update RELEASE_NOTES for 1.12.1 2021-08-23 10:45:59 -07:00
Benjamin Doherty
11dc8740f2 Fix stack-use-after-scope error 2021-08-20 15:40:06 -07:00
Ben Doherty
4b797cff88 Windows: fix incorrect CRT flags with Visual Studio generators (#4516) 2021-08-20 10:56:41 -07:00
Ben Doherty
fe1c1736cd Fix, potential null dereferences in MetalBlitter (#4481) 2021-08-10 16:30:11 -07:00
Benjamin Doherty
4058ef5f09 Bump version to 1.12.1 2021-08-09 12:06:47 -07:00
Benjamin Doherty
d25ca01624 Merge branch 'rc/1.12.0' into release 2021-08-09 12:03:33 -07:00
Benjamin Doherty
d96f87dbbf Update RELEASE_NOTES for 1.12.0 2021-08-09 12:01:46 -07:00
Ben Doherty
8a2e31023f Attempt to fix TSAN failure in ColorGrading.cpp (#4447) 2021-08-05 11:54:07 -07:00
Benjamin Doherty
1ea8e171d9 Bump version to 1.12.0 2021-08-03 11:18:56 -07:00
Benjamin Doherty
e2be3dd0ac Merge branch 'rc/1.11.2' into release 2021-08-03 11:15:19 -07:00
Romain Guy
1c51164e7b Fix inverse tone mapping issues (#4437)
Bring color grading back into the Rec.709 color space to match
previous behaviors. This change also implements an exact inverse
tone map function for the "Filmic" operator.
2021-08-02 20:54:44 -07:00
Benjamin Doherty
f190f03530 Update RELEASE_NOTES for 1.11.2 2021-08-02 11:17:42 -07:00
Ben Doherty
055fc7cbc1 Attempt to fix Windows CI by turning off concurrent builds (#4395) 2021-07-30 14:09:07 -07:00
Ben Doherty
9073fc3dc3 Attempt to fix Windows CI by turning off concurrent builds (#4395) 2021-07-28 10:49:37 -07:00
Ben Doherty
2409dc9bc4 Expose Engine::flush (#4385) 2021-07-26 15:09:14 -07:00
Benjamin Doherty
6586c8d70b Bump version to 1.11.2 2021-07-26 12:50:29 -07:00
Benjamin Doherty
ac0c94da69 Merge branch 'rc/1.11.1' into release 2021-07-26 12:48:36 -07:00
Ben Doherty
d19d6a72b0 Expose Engine::flush (#4385) 2021-07-26 12:10:19 -07:00
Benjamin Doherty
c81b5d98ef Update RELEASE_NOTES for 1.11.1 2021-07-26 10:05:41 -07:00
Mathias Agopian
756866675f fix an issue where a sampler could fail to be updated
SamplerGroup was comparing texture handles to decide if a texture needed
to be updated, however, texture handles are (quickly) recycled and
therefore can't be used for that purpose. e.g. if a texture is destroyed,
its handle could be reused quickly by another texture, if that texture 
is now set on the SamplerGroup, it will ignore it, thinking it's not
different.
2021-07-21 14:46:44 -07:00
Benjamin Doherty
ebcd4925f7 Bump version to 1.11.1 2021-07-19 10:24:01 -07:00
Benjamin Doherty
13bed4fdf9 Merge branch 'rc/1.11.0' into release 2021-07-19 10:20:29 -07:00
Benjamin Doherty
1dae5c6b6c Update RELEASE_NOTES for 1.11.0 2021-07-19 10:18:07 -07:00
Benjamin Doherty
8e6663e4b0 Bump version to 1.11.0 2021-07-12 10:49:43 -07:00
Benjamin Doherty
ba804444b8 Merge branch 'rc/1.10.7' into release 2021-07-12 10:47:12 -07:00
Benjamin Doherty
58cfb85004 Update RELEASE_NOTES for 1.10.7 2021-07-12 10:45:24 -07:00
Mathias Agopian
ab46481b45 Fix colorgrading as subpass
We were inserting the colorgrading subpass command between the
refracted and blended objects, instead of after all of them.

Another bad side effect of this was to trigger the refraction pass for
no reason.
2021-07-12 10:35:01 -07:00
Mathias Agopian
4296782399 don't crash when refraction pass is empty 2021-07-12 09:53:37 -07:00
Benjamin Doherty
ef375a7103 Bump version to 1.10.7 2021-07-07 11:02:30 -07:00
Benjamin Doherty
fd258b7765 Merge branch 'rc/1.10.6' into release 2021-07-07 10:59:12 -07:00
Benjamin Doherty
147de8d372 Update RELEASE_NOTES for 1.10.6 2021-07-07 10:57:25 -07:00
Philip Rideout
eb2a1928b6 ShadowMapManager: fix MSAN use-of-uninitialized-value.
The operator!= in std::array compares SPLIT_COUNT elements, which
is potentially greater than cascadeCount, which was the number of
initialized elements in splitPercentages.
2021-07-07 10:51:18 -07:00
Benjamin Doherty
35b033102f Bump version to 1.10.6 2021-06-28 12:07:58 -07:00
Benjamin Doherty
7bc65421a9 Merge branch 'rc/1.10.5' into release 2021-06-28 12:04:31 -07:00
Benjamin Doherty
736514cf37 Update RELEASE_NOTES for 1.10.5 2021-06-28 12:04:27 -07:00
Mathias Agopian
db0158dae8 fix webgl build
we need to select opengl on webgl.
2021-06-28 08:27:46 -07:00
Benjamin Doherty
e706695ed1 Bump version to 1.10.5 2021-06-21 11:18:26 -07:00
Benjamin Doherty
e8877ffe2d Merge branch 'rc/1.10.4' into release 2021-06-21 11:16:10 -07:00
Benjamin Doherty
1fd5d9dae6 Update RELEASE_NOTES for 1.10.4 2021-06-21 11:14:35 -07:00
Ben Doherty
cd48089318 filament-utils-android: fix string literal conversions (#4137) 2021-06-15 16:19:32 -06:00
Benjamin Doherty
6379ab22c9 Bump version to 1.10.4 2021-06-14 11:06:42 -06:00
Benjamin Doherty
0bf02b75d5 Merge branch 'rc/1.10.3' into release 2021-06-14 11:04:25 -06:00
Benjamin Doherty
c4259b5598 Update RELEASE_NOTES for 1.10.3 2021-06-14 11:02:45 -06:00
Ben Doherty
6b3c1179bc Include sample-gltf-viewer with Android releases (#4099) 2021-06-08 10:41:48 -07:00
Philip Rideout
c1a0e61e8e Fix FixedCapacityVector destructor.
std::allocator::deallocate() expects the same value that was given
during allocate().

Interestingly, this bug did not manifest any issues (even with ASAN) on
some platforms.
2021-06-07 15:56:33 -07:00
Benjamin Doherty
fc06298ed4 Bump version to 1.10.3 2021-06-07 11:22:04 -07:00
Benjamin Doherty
4ca87b188c Merge branch 'rc/1.10.2' into release 2021-06-07 11:19:51 -07:00
Benjamin Doherty
f1f60c3e0d Turn off warnings as errors for spirv-tools 2021-06-07 11:18:56 -07:00
Benjamin Doherty
76d21b56af Update RELEASE_NOTES for 1.10.2 2021-06-07 11:18:09 -07:00
Benjamin Doherty
0ab0e50a4f Turn off warnings as errors for spirv-tools 2021-06-02 11:16:42 -07:00
Benjamin Doherty
34f4c06a5c Bump version to 1.10.2 2021-06-01 11:17:32 -07:00
Benjamin Doherty
6de36f1e53 Merge branch 'rc/1.10.1' into release 2021-06-01 11:15:49 -07:00
Benjamin Doherty
2a9a3b1ac2 Update RELEASE_NOTES for 1.10.1 2021-06-01 11:13:04 -07:00
Benjamin Doherty
84b73a3770 Bump version to 1.10.1 2021-05-24 10:52:02 -07:00
Benjamin Doherty
662a10e273 Merge branch 'rc/1.10.0' into release 2021-05-24 10:49:10 -07:00
Benjamin Doherty
ecce02502e Update RELEASE_NOTES for 1.10.0 2021-05-24 10:47:39 -07:00
Philip Rideout
d17875aea1 ImGuiHelper: fix support for custom images.
The texture binding in the material instance needs to be restored to the
glyph atlas when a custom image is not in use.
2021-05-21 12:49:37 -07:00
Philip Rideout
b8897a68f9 matc: detect missing end brace.
matc was failing to report certain kinds of syntax errors and would
read out-of-bounds memory.

This change casuses the flare material to fail.
2021-05-21 09:20:33 -07:00
Ben Doherty
84efd4871e API CHANGE: remove some Camera, Engine, and View deprecated APIs (#3965) 2021-05-19 12:02:13 -07:00
Benjamin Doherty
85ea5a6b70 Bump version to 1.10.0 2021-05-17 11:00:42 -07:00
Benjamin Doherty
77891acb92 Merge branch 'rc/1.9.25' into release 2021-05-17 10:57:30 -07:00
Benjamin Doherty
74fe102035 Update RELEASE_NOTES for 1.9.25 2021-05-17 10:54:38 -07:00
Philip Rideout
25cc554925 WASM: fix "Missing field" error for lensFlare. 2021-05-17 10:06:56 -07:00
Ben Doherty
d787a521b5 Fix DIST_DIR setting for Windows builds (#3945) 2021-05-12 11:16:30 -07:00
Ben Doherty
46e52c71e1 Fix DIST_DIR setting for Windows builds (#3945) 2021-05-12 11:15:49 -07:00
Philip Rideout
1dad27a172 Repair WebGL and fix potential INVALID_OPERATION.
We should take care not to call glVertexAttribPointer when there is
no bound ARRAY_BUFFER (i.e. when its binding is zero).

This fixes the black screen seen with some WebGL samples after
the recent memory leak fix related to the new BufferObject API.
2021-05-10 13:38:56 -07:00
Benjamin Doherty
60d230b380 Bump version to 1.9.25 2021-05-07 21:42:48 -07:00
Benjamin Doherty
d7cb38e706 Merge branch 'rc/1.9.24' into release 2021-05-07 21:39:17 -07:00
Benjamin Doherty
ce00cca6ee Update RELEASE_NOTES for 1.9.24 2021-05-07 21:36:49 -07:00
Philip Rideout
d627d57bad Second memory leak fix. (#3906)
Fixes #3888.
2021-05-06 14:25:46 -07:00
Philip Rideout
8ffc776f1c Fix horrible memory leak in the GL driver. (#3894)
This leak was introduced in the following PR on April 7.
https://github.com/google/filament/pull/3775

The guilty party has been contacted and properly admonished for his
transgression.

This was tested by adding the following code after applyAnimation in
gltf_viewer.cpp

        static int nframes = 0;
        if (!gpath.empty() && nframes++ > 100) {
            static int count = 0;
            printf("reloading %d\n", count++);
            nframes = 0;
            app.resourceLoader->asyncCancelLoad();
            app.resourceLoader->evictResourceData();
            app.viewer->removeAsset();
            app.assetLoader->destroyAsset(app.asset);
            loadAsset(gpath, app);
            loadResources(gpath, app);
        }
2021-05-04 18:22:08 -07:00
Benjamin Doherty
be032b52c1 Bump version to 1.9.24 2021-05-03 10:42:19 -07:00
Benjamin Doherty
4388e81e5f Merge branch 'rc/1.9.23' into release 2021-05-03 10:40:49 -07:00
Benjamin Doherty
71a185d139 Update RELEASE_NOTES for 1.9.23 2021-05-03 10:38:59 -07:00
Benjamin Doherty
d2cf5985ac Bump version to 1.9.23 2021-04-26 10:55:10 -07:00
Benjamin Doherty
debcbb8e5c Merge branch 'rc/1.9.22' into release 2021-04-26 10:52:43 -07:00
Benjamin Doherty
b9dd62c7d3 Update RELEASE_NOTES for 1.9.22 2021-04-26 10:47:11 -07:00
Benjamin Doherty
dc2b430f34 Bump version to 1.9.22 2021-04-26 10:39:16 -07:00
Benjamin Doherty
e5ef4e8868 Update RELEASE_NOTES for 1.9.21 2021-04-19 11:36:37 -07:00
Benjamin Doherty
c0d6cd3ac3 Merge branch 'rc/1.9.21' into release 2021-04-19 11:34:23 -07:00
Benjamin Doherty
b63ab2dc19 Update RELEASE_NOTES for 1.9.21 2021-04-19 11:32:31 -07:00
Benjamin Doherty
5d8dad561c Bump version to 1.9.21 2021-04-12 11:38:27 -07:00
Benjamin Doherty
8933be1ae2 Merge branch 'rc/1.9.20' into release 2021-04-12 11:36:17 -07:00
Benjamin Doherty
6b66b48b1d Update RELEASE_NOTES for 1.9.20 2021-04-12 11:35:45 -07:00
Benjamin Doherty
9c23eb6e33 Release Filament 1.9.20 2021-04-12 11:34:39 -07:00
Benjamin Doherty
baea54a3fc Update RELEASE_NOTES for 1.9.20 2021-04-12 09:20:31 -07:00
Benjamin Doherty
d9f800454c Bump version to 1.9.20 2021-04-05 11:14:17 -07:00
Benjamin Doherty
f9ee0de07a Merge branch 'rc/1.9.19' into release 2021-04-05 11:11:13 -07:00
Benjamin Doherty
2786d0a9f7 Update RELEASE_NOTES for 1.9.19 2021-04-05 11:10:50 -07:00
Benjamin Doherty
491e8032e6 Release Filament 1.9.19 2021-04-05 11:10:20 -07:00
Benjamin Doherty
ef3f13f5d3 Update RELEASE_NOTES for 1.9.19 2021-04-05 11:09:38 -07:00
Benjamin Doherty
bcb5b2d790 Implement Metal BufferObjects 2021-04-05 11:01:36 -07:00
Ben Doherty
02de3f2e2a Fix, regression with Metal buffers (#3715) 2021-03-29 14:21:34 -07:00
Benjamin Doherty
0e7bb53c07 Bump version to 1.9.19 2021-03-29 11:09:32 -07:00
Benjamin Doherty
759109d478 Merge branch 'rc/1.9.18' into release 2021-03-29 11:06:55 -07:00
Benjamin Doherty
54d5af6edf Update RELEASE_NOTES for 1.9.18 2021-03-29 11:04:57 -07:00
Philip Rideout
38fbe47ced RenderTarget: fix NPE when depth is not present.
This fixes a recent regression that would occur when a RenderTarget
does not have a depth attachment.
2021-03-29 10:32:22 -07:00
Benjamin Doherty
f066c925ba Bump version to 1.9.18 2021-03-22 10:16:34 -07:00
Benjamin Doherty
994fdf4e1d Merge branch 'rc/1.9.17' into release 2021-03-22 10:12:20 -07:00
Benjamin Doherty
50b50d65e3 Update RELEASE_NOTES for 1.9.17 2021-03-22 10:11:02 -07:00
Mathias Agopian
0aaa985649 Fix a hang in JobSystem
The hang was caused by a subtle race. When a job is completed, its 
thread must signal all the threads that might be waiting on this job.
The signaling code was attempting to signal only the minimum number
of threads -- this was important especially in the case where no threads
were waiting, then the call to notify() could be avoided.

Unfortunately, for performance reasons we're not calling notify() with
the condition lock held, this meant that between the time the number of 
waiting threads was latched and the time of the notify() call, more
threads could enter their condition variable wait(), and it would
then be possible for these threads to wake up, instead of the thread
we were trying to wake up (the one waiting on the job).

It would then get stuck forever.

This bug was introduced in 2df639133b


Also add some debugging code for this kind of failure (disabled)
2021-03-18 09:57:25 -07:00
Benjamin Doherty
29564f8eae Bump version to 1.9.17 2021-03-15 10:15:25 -07:00
Benjamin Doherty
c15db68a5b Merge branch 'rc/1.9.16' into release 2021-03-15 10:12:56 -07:00
Benjamin Doherty
3452fb3e56 Update RELEASE_NOTES for 1.9.16 2021-03-15 10:10:40 -07:00
Mathias Agopian
35eb8e7be1 Revert GL backend handle tracking
This wasn't very useful in the first place because we're recycling
handles very quickly. Additionally there was a race condition
which cause false positives.

This reverts commit bc6acd5c5a.
This reverts commit 3a15756c78.
2021-03-12 13:02:55 -08:00
Mathias Agopian
834b774128 Fixes some issues with imported rendertargets
We were not declaring the attachments it was using.

Fixes #3628
2021-03-12 08:26:16 -08:00
Benjamin Doherty
5aa0eb9f9d Bump version to 1.9.16 2021-03-08 10:09:14 -08:00
Benjamin Doherty
d9a6e2e649 Merge branch 'rc/1.9.15' into release 2021-03-08 10:07:00 -08:00
Benjamin Doherty
cb823b16a1 Update RELEASE_NOTES for 1.9.15 2021-03-08 10:05:19 -08:00
Ben Doherty
0bd41e877e Downgrade Linux GitHub Actions environment to fix error (#3588) 2021-03-01 11:21:11 -08:00
Benjamin Doherty
ecc3e73967 Bump version to 1.9.15 2021-03-01 11:13:44 -08:00
Benjamin Doherty
464b4c24f9 Merge branch 'rc/1.9.14' into release 2021-03-01 11:11:53 -08:00
Benjamin Doherty
32367516e8 Update RELEASE_NOTES for 1.9.14 2021-03-01 11:10:06 -08:00
Philip Rideout
1709a55606 filamat: Fix data race with SPIRV error registrations. 2021-02-26 10:34:36 -08:00
Benjamin Doherty
58b4455979 Bump version to 1.9.14 2021-02-22 10:32:56 -08:00
Benjamin Doherty
ea1dede19c Merge branch 'rc/1.9.13' into release 2021-02-22 10:29:57 -08:00
Benjamin Doherty
20ea3381fa Update RELEASE_NOTES for 1.9.13 2021-02-22 10:28:21 -08:00
Philip Rideout
7aa6fccd7c Modernize Python print syntax to appease external codebase. 2021-02-19 11:38:18 -08:00
Philip Rideout
adbd54f4f8 Change abs() to std::abs to appease external codebase. 2021-02-19 11:38:18 -08:00
Philip Rideout
9d54261f18 Move vk_mem_alloc to its own cpp.
This will improve parity between the GitHub repo and its sister codebase
within Google.
2021-02-19 11:38:18 -08:00
Philip Rideout
a97757c9ae Avoid uninitialized reads in computeVisibilityMasks.
I verified that this code is not enabled in our GitHub builds, and I
verified that the MSAN error goes away.
2021-02-19 11:38:18 -08:00
Philip Rideout
7c79d9f89d Remove some Windows line endings.
These line endings cause annoying diffs when comparing Filament's GitHub
source with its twin sister within Google.
2021-02-19 11:38:18 -08:00
Benjamin Doherty
bf0914f813 Bump version to 1.9.13 2021-02-16 10:48:24 -08:00
Benjamin Doherty
b96bc30fbd Merge branch 'rc/1.9.12' into release 2021-02-16 10:44:13 -08:00
Benjamin Doherty
62b50eb8ba Update RELEASE_NOTES for 1.9.12 2021-02-16 10:42:41 -08:00
Ben Doherty
b4932e384a matc: Use consistent params for semantic code analysis (#3524)
When running semantic analysis on a material, we were arbitrarily choosing the first code gen permutation to analyze. So, running matc with arguments --api metal versus --api all would run analysis on slightly different shader code. This causes bugs when flags passed to glslang differ during semantic analysis. This change updates all semantic analysis to always use the same shader code.
2021-02-08 14:06:47 -08:00
Benjamin Doherty
5e68dc5f8d Bump version to 1.9.12 2021-02-08 09:58:15 -08:00
Benjamin Doherty
f222f1b925 Merge branch 'rc/1.9.11' into release 2021-02-08 09:51:08 -08:00
Ben Doherty
22e4a54782 Update RELEASE_NOTES for 1.9.11 2021-02-08 09:50:23 -08:00
Benjamin Doherty
1289922c5f Release Filament 1.9.11 2021-02-08 09:48:50 -08:00
Ben Doherty
2839c352b8 Update RELEASE_NOTES for 1.9.11 2021-02-08 09:47:18 -08:00
Ben Doherty
6a01cbc312 Draft: fix TSAN issue by using lock in ColorGrading constructor (#3510)
This is a second attempt to fix Google3 TSAN failures seen inside of the ColorGrading constructor.

Related first attempt: #3462
2021-02-05 14:06:40 -08:00
Benjamin Doherty
6193156556 Bump version to 1.9.11 2021-02-01 11:49:12 -08:00
Benjamin Doherty
fe23aa917d Merge branch 'rc/1.9.10' into release 2021-02-01 11:45:47 -08:00
Benjamin Doherty
eb8a29a332 Update RELEASE_NOTES for 1.9.10 2021-02-01 11:45:17 -08:00
Ben Doherty
0626902530 Fix sporatic data race warning seen in Google3 (#3462) 2021-01-27 16:40:16 -08:00
Philip Rideout
042bfe2597 Partial fix for MSVC build. 2020-12-14 12:15:06 -08:00
Ben Doherty
97133f3591 Fix Windows iterator issue in Zip2Iterator and StructureOfArrays (#3322) 2020-12-14 12:14:53 -08:00
Philip Rideout
d06cc4390e filamat: minify struct fields.
This shrinks the arm64 so file by 24 KiB.
2020-12-14 11:19:43 -08:00
Philip Rideout
6047d3235f Remove VSM variant from Skybox material. 2020-12-14 11:19:31 -08:00
Philip Rideout
2cda6e35bd math: reduce template bloat for matrices
This does not change our API, it merely reduces the number of
non-inlined function instantiations appearing in non-optimized binaries.
2020-12-14 11:19:22 -08:00
Philip Rideout
8f8d51e17b Code review fixups for libibl_lite. 2020-12-14 11:19:16 -08:00
Philip Rideout
6919e3b274 libibl: use C callback for progress 2020-12-14 11:19:09 -08:00
Philip Rideout
10bf944410 Add libibl_lite. 2020-12-14 11:19:03 -08:00
Philip Rideout
9a2f6fdb53 Vulkan: change vkWaitForFences usage for SwiftShader.
When passing only 1 fence to vkWaitForFences, the `waitAll` argument
should not have any effect, but SwiftShader seems to skip the wait
when this argument is set to VK_FALSE.

More specifically, the failure to wait in `acquireWorkCommandBuffer`
causes the subsequent destruction of an in-use fence, which causes
a TSAN failure with Google's internal tests.

I am consulting with the SwiftShader team on a real fix, meanwhile
we can commit this easy workaround.

We have 5 usages of vkWaitForFences, one of which uses multiple fences
and should have used VK_TRUE anyway.
2020-11-20 09:37:54 -08:00
Philip Rideout
761977d385 Filament should always bind an IBL texture.
This prevents a SwiftShader crash and/or a slew of "no texture bound"
warnings that would appear when the client provides an IBL without
providing reflections texture, which should be a valid thing to do.

Note that it is okay to declare a sampler in GLSL that never gets bound,
as long as it is never sampled from. Since we always sample from the
IBL specular texture, we should always bind something to it.
2020-11-19 13:28:19 -08:00
Ben Doherty
21248f15b5 Fix, matc crash when building mobile materials (#3296) 2020-11-16 14:37:55 -08:00
Benjamin Doherty
4f32817f6d Bump version to 1.9.10 2020-11-16 12:37:49 -08:00
Benjamin Doherty
cc9e05e711 Merge branch 'rc/1.9.9' into release 2020-11-16 12:34:13 -08:00
Benjamin Doherty
419d68d4db Update RELEASE_NOTES for 1.9.9 2020-11-16 12:17:02 -08:00
Philip Rideout
8450232448 Improve the "unbound texture" warnings.
With Vulkan, this warning would sometimes be a false positive. It could
trigger for internal samplers like `ssao` and `structure`, even though
they were not declared in SPIR-V.

With OpenGL, this warning would never be a false positive because it has
the luxury of calling `glGetUniformLocation`.

This adds a private attribute to our samplers called `strict` that
indicates whether or not a sampler should always have a bound texture.
For now the only strict samplers are the custom ones declared in the
user's material.

At some point I think we should consider adding `spirv-reflect` to our
tree to help with problems like this.
2020-11-12 16:21:13 -08:00
Philip Rideout
cc51726590 Vulkan: improve robustness by providing 1x1 fallback. 2020-11-12 10:36:13 -08:00
Philip Rideout
318e22af51 Fix clear behavior with RenderTarget API.
This fixes a bug seen with client applications that use ClearOptions
instead of Skybox, and one or more offscreen RenderTarget objects.
These apps would see junk pixels because Filament would only clear the
first render target in the frame.

The fix is to factor some the flag-setting logic in `beginFrame()` into
a private method, and call this method from `render()` each time
the user-level RenderTarget has been changed.

I wrote a simple C++ demo to reproduce the issue and to verify that
this fix works.
2020-11-11 09:25:36 -08:00
Philip Rideout
68ac87dc24 NOOP backend should not care about GLSL vs SPIRV.
This fixes errors that would occur when using the NOOP backend with
materials that were built without OpenGL support.
2020-11-10 15:44:01 -08:00
Benjamin Doherty
acb8f00075 Bump version to 1.9.9 2020-11-09 09:32:55 -08:00
Benjamin Doherty
06d9183aaa Merge branch 'rc/1.9.8' into release 2020-11-09 09:28:58 -08:00
Benjamin Doherty
75af25419d Update RELEASE_NOTES for 1.9.8 2020-11-09 09:26:27 -08:00
Benjamin Doherty
f6b90d2a31 Bump version to 1.9.8 2020-11-09 09:21:36 -08:00
Philip Rideout
a3822f4af0 Fix FENCE_WAIT_FOR_EVER in Linux.
The number of infinite nanoseconds was negative because we asked
chrono for a signed integer, so "wait forever" really meant "do not
wait at all".
2020-11-02 16:12:53 -08:00
Benjamin Doherty
bcdad769ff Merge branch 'rc/1.9.7' into release 2020-11-02 11:03:57 -07:00
Benjamin Doherty
be4fb4fdbb Update RELEASE_NOTES for 1.9.7 2020-11-02 10:59:19 -07:00
Benjamin Doherty
65394f6301 Bump version to 1.9.7 2020-10-26 11:34:20 -06:00
Benjamin Doherty
b0beee03bc Merge branch 'rc/1.9.6' into release 2020-10-26 11:29:45 -06:00
Benjamin Doherty
fe1de41b8e Update RELEASE_NOTES for 1.9.6 2020-10-26 11:25:31 -06:00
Benjamin Doherty
a37b431e87 Bump version to 1.9.6 2020-10-19 11:55:13 -06:00
Benjamin Doherty
98107016b9 Merge branch 'rc/1.9.5' into release 2020-10-19 11:51:30 -06:00
Benjamin Doherty
8bccfc2863 Update RELEASE_NOTES for 1.9.5 2020-10-19 11:49:05 -06:00
Benjamin Doherty
f54a0a3452 Fix CocoaPod version 2020-10-13 15:15:02 -06:00
Benjamin Doherty
6778ab0624 Fix CocoaPod version 2020-10-13 15:09:59 -06:00
Benjamin Doherty
269d636785 Bump version to 1.9.5 2020-10-12 12:03:29 -06:00
Benjamin Doherty
39862c91ce Merge branch 'rc/1.9.4' into release 2020-10-12 11:56:24 -06:00
Benjamin Doherty
523f4026b4 Update RELEASE_NOTES for 1.9.4 2020-10-12 11:52:01 -06:00
Benjamin Doherty
a6bf162431 Bump version to 1.9.4 2020-10-07 16:06:23 -06:00
Benjamin Doherty
826e8d181c Merge branch 'rc/1.9.3' into release 2020-10-05 11:36:16 -06:00
Benjamin Doherty
16dfadbba0 Update RELEASE_NOTES for 1.9.3 2020-10-05 11:29:36 -06:00
Benjamin Doherty
5cbb97551f Bump version to 1.9.3 2020-09-28 11:40:31 -06:00
Benjamin Doherty
defee767c3 Merge branch 'rc/1.9.2' into release 2020-09-28 11:27:47 -06:00
Benjamin Doherty
9560318521 Update RELEASE_NOTES for 1.9.2 2020-09-28 11:26:21 -06:00
Benjamin Doherty
ef09feb048 Bump version to 1.9.2 2020-09-21 11:16:53 -06:00
Benjamin Doherty
39f323fe09 Merge branch 'rc/1.9.1' into release 2020-09-21 11:00:07 -06:00
Benjamin Doherty
11b95304ea Merge branch 'release' into rc/1.9.1 2020-09-21 10:59:52 -06:00
Benjamin Doherty
b7c30a7916 Update RELEASE_NOTES for 1.9.1 2020-09-21 10:59:06 -06:00
Benjamin Doherty
4cae48fc77 Bump version to 1.9.1 2020-09-14 11:51:36 -07:00
Benjamin Doherty
d1a93f0557 Update release notes for 1.9.0 2020-09-14 10:54:28 -07:00
Benjamin Doherty
b93059fad7 Bump version to 1.9.0 2020-09-08 10:47:23 -07:00
74 changed files with 1869 additions and 961 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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
*/

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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'>

View File

@@ -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

View File

@@ -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,

View 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

View File

@@ -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);

View File

@@ -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());

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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;
};

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 = {

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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 = {};

View File

@@ -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,

View File

@@ -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, &copyRegion);
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, &region);
}
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

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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.

View File

@@ -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));
}

View File

@@ -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));
}

View File

@@ -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.

View File

@@ -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);

View File

@@ -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;

View File

@@ -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...
*/

View File

@@ -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);
}

View File

@@ -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)",

View File

@@ -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 {

View File

@@ -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};

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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:

View File

@@ -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;

View File

@@ -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 = {

View File

@@ -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;

View File

@@ -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)

View File

@@ -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++;
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;
};

View File

@@ -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();

View File

@@ -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;

View File

@@ -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) :

View File

@@ -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";
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View 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

View File

@@ -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

View 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);
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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 };

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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",

View File

@@ -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);