Compare commits

..

116 Commits

Author SHA1 Message Date
Benjamin Doherty
69d5ac2107 Check for invalid texture inside MaterialInstance 2024-09-12 12:11:01 -07:00
Powei Feng
c01d2c09b6 vk: reduce map look-up in descriptor set hot loop
DescriptorSetManager's commit() is the inner most of the
rendering loop. We want to avoid any hash map look up here.
We just store a pointer to the history object where appropriate.
2024-09-12 08:48:25 -07:00
Powei Feng
f37d8b2fc2 vk: refactor descriptor bitmask to use bitset
Using bitset enables simpler counting of bits and the use of the
handy 'forEachSetBit'.

Also cleaned up various unused code.
2024-09-12 08:48:25 -07:00
Sungun Park
6397fa3fe1 Fix a crash for color grading (#8118)
Set postprocess descriptor set for color grading
2024-09-11 18:51:15 -07:00
Benjamin Doherty
37d1baf0e8 Metal debug logging improvements 2024-09-10 13:36:45 -07:00
Powei Feng
1596a5ec71 vk: Fix descriptor set bitmask padding
The sizing of each field in Bitmask has changed, and the
description was no longer accurate.  This caused msan to fail
because the mask is also used as a key to cache.
2024-09-10 12:13:24 -07:00
Mathias Agopian
22e62b92a6 disable compute test to fix build
this test is no important for now, since we don't have a finished
compute api.

the test breaks because it still use the old non-descriptor based API.
2024-09-09 23:09:01 -07:00
Mathias Agopian
8f979dacaf Merge branch 'main' into ma/descriptor-sets 2024-09-09 22:58:33 -07:00
Mathias Agopian
684d441ba7 fix FrameSkipper (#8110)
FrameSkipper was recently broken because of a typo resulting in 
the frame latency being always 3 instead of the default of 2.

This change also makes the maximum latency 2 instead of 3, because on
ANDROID, 3 can cause CPU throttling at seemingly random places on the
GL thread.
2024-09-09 22:19:25 -07:00
Benjamin Doherty
e3cb67045b Remove some more magic numbers 2024-09-09 17:01:53 -07:00
Benjamin Doherty
f4cd9f712a GLSLPostProcessor clean up, remove magic numbers 2024-09-09 16:51:11 -07:00
Sungun Park
37c615e249 Support multi-layered render target (#8108)
Clients can create a multi-layered render target that consists of array
textures, and use it as a custom render target.

A new sample app "hellostereo" demonstrates how to use this feature.
2024-09-09 20:27:58 +00:00
Benjamin Doherty
99b3587dd3 Update comment 2024-09-09 12:28:35 -07:00
Benjamin Doherty
8e30b1bfc9 Rename toString 2024-09-09 12:26:53 -07:00
Benjamin Doherty
c0b1daf339 MetalDescriptorSet move implementation into MetalHandles.mm 2024-09-09 12:23:45 -07:00
Benjamin Doherty
efb94da111 Remove deprecated driver APIS 2024-09-09 12:17:27 -07:00
Benjamin Doherty
c538bded6f Get rid of unused MetalSamplerGroup and related code 2024-09-09 12:17:27 -07:00
Benjamin Doherty
f6d9db14c3 Better DEBUG_LOG 2024-09-09 12:17:27 -07:00
Benjamin Doherty
91ce3a9375 Smaller empty buffer 2024-09-09 12:17:27 -07:00
Powei Feng
8999b21187 Revert "reenable the SimplificationPass in spirv-opt"
This reverts commit 30387af61c.

Causes breakage on Pixel 8pro for 1P apps.
2024-09-09 10:21:53 -07:00
Mathias Agopian
3aeee1630a Merge branch 'main' into ma/descriptor-sets 2024-09-06 18:49:04 -07:00
Powei Feng
78aa1c4b10 vk: use std::unordered_map in PipelineLayoutCache (#7990)
This is to address a weird map.find() miss that only happens on
ARM in release mode.

BUG=365159519

Co-authored-by: Sungun Park <sungunpark@google.com>
2024-09-06 22:10:01 +00:00
Zaven Muradyan
c2b3632725 Expose getShadowType on View. (#8106)
While this method exists on FView, it is missing from View and thus not accessible for filament users. This adds a pass-through method on View.
2024-09-05 15:21:26 -07:00
Benjamin Doherty
15d77ed1d7 Metal: respect the shader stage for each descriptor 2024-09-05 11:38:10 -07:00
Mathias Agopian
8c91b1baf5 fix debug markers when implemented with systrace
when debug markers are implemented with systrace, begin/end can is not
guaranteed to happen in the same C++ scope, so we can't really use 
other scoped systraces.
2024-09-04 22:58:58 -07:00
Sungun Park
1b4afbab51 Release Filament 1.54.2 2024-09-04 18:55:52 +00:00
Powei Feng
c677607353 github: Split Android CI to per ABI builds
- Pulled the android continuous workflow into an action
 - Split the android continuous build into 3 ABIs armv7, armv8a,
   and x86_64 (note that x86 is not present).
 - Remove the upload artifacts step from previous workflow.
   This was meant for letting client try a tip-of-tree Android
   build.  We can revisit this later. (Also removed mention
   in README.md)
 - Split the android continous into debug build and a release
   build commands, enabling deletion of intermediate output
   directory.
2024-09-04 11:22:43 -07:00
Powei Feng
cdb539b3cf Make builderMakeName public
This call is used in the BuilderNameMixin template definition,
which is a public API.
2024-09-04 10:04:20 -07:00
Mathias Agopian
6a64cdb059 fix build due to a bad merge 2024-09-03 12:14:10 -07:00
Mathias Agopian
cf0cb66b28 Merge branch 'main' into ma/descriptor-sets 2024-09-03 11:25:40 -07:00
Ben Doherty
16bed4de00 Enable mmap on iOS (#8100) 2024-09-03 09:58:13 -07:00
Mathias Agopian
3a2333d709 Merge branch 'main' into ma/descriptor-sets 2024-08-30 16:31:23 -07:00
Mathias Agopian
6790a1238b fix webgl build 2024-08-30 16:28:14 -07:00
Mathias Agopian
2b620e65fd fix potential framegraph textures use-after free
make sure to unset all textures in the per-view sampler group after
they are used, because the resource could be destroyed after the
pass is finished

- unset the fog and ibl_specular after the color pass
- move that cleanup a bit earlier
- in the case of screen-space reflection the structure pass is
  set, but might not be used in the color pass, so we also need to
  unset it after the SSR pass and before any other passes.
2024-08-30 16:24:25 -07:00
Mathias Agopian
2aa51db614 add texture tagging in the FrameGraph
Also tag user texture "FTexture" if user doesn't provide a tag, this
is so that we can distinguish them from internal textures that might
not be tagged.
2024-08-30 16:24:25 -07:00
Mathias Agopian
2bbbb7f4d1 Update filament/src/details/Renderer.cpp
Co-authored-by: Powei Feng <powei@google.com>
2024-08-30 16:22:49 -07:00
Mathias Agopian
339e8da976 repair the "no buffer padding case"
with a previous change we were too aggressive in falling back to 
"no buffer padding", we need to do this only in the case where
"as subpass" would have been used if supported by the h/w.
2024-08-30 16:22:49 -07:00
Mathias Agopian
c36dd955f4 make pushGroupMarker and insertEventMarker null-terminated strings
This is because we had this assumption on vk and mtl backends already
and also because SYSTRACE works this way and we didn't have non-null
terminated strings. This makes things more consistant and less bug 
prone.
2024-08-30 15:29:09 -07:00
Mathias Agopian
950be941eb disabling ResourceAllocator led to incorrect assert
this is because the "disposer" is now separate from the 
ResourceAllocator, so even if we don't use the resource allocator we
need to register the handle with the disposer.
2024-08-30 15:28:54 -07:00
Mathias Agopian
3857e3789c a handle with a tag would sometime return "no tag"
this is because the key used to retrieve the tag was not "truncated"
like it was when inserting the tag in the hash-map.
2024-08-30 15:28:37 -07:00
Powei Feng
fad5d57053 vk: properly compose swizzle 2024-08-30 15:17:03 -07:00
Powei Feng
e95f0ed6a0 vk: implement createTextureViewSwizzled() 2024-08-30 15:17:03 -07:00
Mathias Agopian
73b0be799c Merge branch 'main' into ma/descriptor-sets 2024-08-30 15:13:43 -07:00
Mathias Agopian
2202b5ab8c Add support for depth clamp and use it for shadows
vk, metal and desktop gl all support depth clamp, GLES/android also does
with ANGLE. Add support for it in the backends.

use depth clamp to improve directional shadow quality; this allows
to render everything that's behind the camera at the same "zero" depth,
so we can reduce the depth range we need.

Fixes #6293
2024-08-30 10:56:31 -07:00
Sungun Park
639b933fd6 Rename customRenderTarget (#8093)
The currentRenderTarget is a misnomer as it's a custom RT. Rename it
properly.
2024-08-29 11:47:24 -07:00
Mathias Agopian
6653c6c08b fix a typo in RenderPassBuilder::renderFlags() 2024-08-28 21:51:03 -07:00
Maximilien Dagois
777f664b1b Refactored the handling of usage flags in the VulkanTexture constructor 2024-08-28 12:58:31 -07:00
Mathias Agopian
6de6a2123d implement createTextureViewSwizzled() in the GL backend 2024-08-28 12:53:50 -07:00
Ben Doherty
d161ef24f5 Update createTextureView API to include swizzling, implement on Metal (#8077) 2024-08-28 11:52:49 -07:00
Mathias Agopian
c3ea1e45f9 Merge branch 'main' into ma/descriptor-sets 2024-08-28 11:45:02 -07:00
Powei Feng
283d240409 Re-enable DebugRegistry::setProperty for release build (#8086)
`DebugRegistry::setProperty` is no longer just applicable to debug
builds.

This change was previously added in 6c0bd36 but then reverted
in a7317e7. We re-enable it and separate it from the shadow
changes in this commit.
2024-08-28 06:17:26 +00:00
Sungun Park
0e0f3a5518 Release Filament 1.54.1 2024-08-27 23:30:26 +00:00
Powei Feng
ba5413622f Decouple subpass from buffer padding (#8085)
Coupling subpass on/off with buffer padding has unintended
consequences when we need to turn subpass off for tests. We
uncouple them in this change.
2024-08-27 22:33:31 +00:00
Ben Doherty
ba8d429fcb Metal: annotate memory allocation failures with buffer tag (#8082) 2024-08-27 10:49:35 -07:00
Mathias Agopian
f11e5cb081 remove uses of the blackboard
the framegraph blackboard tends to create a lot of problems hard to 
debug, so we are now more explicit. here we remove usages of the
blackboard in RenderUtils.cpp.
2024-08-27 09:55:00 -07:00
Mathias Agopian
c84f5d2a7f fix screen-space reflection when sub-pass colorgrading is used
the problem was that in that case, SSR was using the colorgraded
color buffer as history.
2024-08-27 09:55:00 -07:00
Sungun Park
a7317e7a99 Revert two depth relevant changes (#8083)
This reverts commits
- b70aa43727 "depth clamp cannot work with VSM"
- 6c0bd360b3 "Add support for depth clamp and use it for shadows"
2024-08-26 23:30:15 +00:00
Mathias Agopian
be4391950d fix gltfio ubershaders
default parameters were not initialized which could cause them to
be incorrectly evaluated in the shader. this is actually a pretty
crazy bug that has been around since forever and which we were
"lucky" to not run into sooner.

this was exposed with the specular extension that was added recently.
2024-08-26 10:24:42 -07:00
Ben Doherty
6f20cf4b02 Support tagging driver handles with a name (#8038) 2024-08-24 09:17:05 -07:00
Mathias Agopian
c6c20df474 Merge branch 'main' into ma/descriptor-sets 2024-08-23 23:04:57 -07:00
Mathias Agopian
9674a5ae3c add support for subresources in "blit"
This is needed for debugging, but is a pretty low-hanging fruit.
2024-08-23 22:37:01 -07:00
Balaji M
485c05789b return statement moved to separate line
Co-authored-by: Powei Feng <powei@google.com>
2024-08-23 11:02:19 -07:00
Balaji M
b058794dd1 decoding meshoptimized gltf just once 2024-08-23 11:02:19 -07:00
Powei Feng
be22e9305d matc: initialize CustomVariable (#8076)
The default values were not provided via default construction.
Caused matc to crash on Linux.
2024-08-23 16:09:56 +00:00
Sungun Park
4ae7aa6a1b Fix compatibility warning for multiview (#8075)
Verify the compatibility between a compiled material and the engine's
setting only when the engine is set up for stereo.

Default materials are always compiled with either 'instanced' or
'multiview. Therefore Filament will emit warnings unintentionally if the
engine is set up for stereo. This commit fixes it.
2024-08-22 20:39:04 +00:00
Ben Doherty
bbb45218ac Fix, assertion when an external image is used but not yet set (#8073) 2024-08-21 15:47:07 -07:00
Mathias Agopian
02bdddf50e fix a too aggressive assert 2024-08-21 15:19:56 -07:00
Powei Feng
21d93ce49d Fix missing duplication of descriptor set in material instance (#8068) 2024-08-21 12:07:24 -07:00
Powei Feng
cab5fc561c GLDescriptorSet: less restrictive size assert (#8070)
For some platform, the compilation fails because the size of the
struct is not exactly 32 bits (but is always less).
2024-08-21 11:51:59 -07:00
Mathias Agopian
c780804fd6 Merge branch 'main' into ma/descriptor-sets 2024-08-21 10:27:31 -07:00
Mathias Agopian
b6d6f8f6e4 Merge branch 'main' into ma/descriptor-sets 2024-08-20 12:08:32 -07:00
Mathias Agopian
f098d08c48 Merge branch 'main' into ma/descriptor-sets 2024-08-19 17:13:44 -07:00
Mathias Agopian
5c08f34e8c Merge branch 'main' into ma/descriptor-sets 2024-08-19 16:40:36 -07:00
Mathias Agopian
a37d03101e Merge branch 'main' into ma/descriptor-sets 2024-08-19 11:47:19 -07:00
Mathias Agopian
c0819b781a Merge branch 'main' into ma/descriptor-sets 2024-08-16 16:03:54 -07:00
Powei Feng
d9bf68ed06 vk: fix missing read-after-write barrier (#8046)
We relied on layout transition to emit a barrier, but sometimes
it's skipped. In that case, we explicitly emit a barrier for the
transition from attachment -> sampler.

Without this, we'll hit read-after-write sync validation error
and produce various artifacts (e.g a fade to white effect) on
different platforms.
2024-08-16 15:59:38 -07:00
Powei Feng
32ac10161b Move morphing buffer offset out of pipeline check (#8047)
The push constant call should not be tied to the caching of the
pipeline.
2024-08-16 13:17:37 -07:00
Benjamin Doherty
d073fb5342 Implement external images / descriptor sets for Metal 2024-08-16 13:08:24 -07:00
Mathias Agopian
8fe7c1ff30 Merge branch 'main' into ma/descriptor-sets 2024-08-14 22:47:16 -07:00
Mathias Agopian
3b12e54573 Merge branch 'main' into ma/descriptor-sets 2024-08-12 15:09:26 -07:00
Mathias Agopian
a1f146ebea Merge branch 'main' into ma/descriptor-sets 2024-08-12 15:05:43 -07:00
Mathias Agopian
9401d6be9e fix warning about missing case
this is to allow -Werror to work.
2024-08-11 23:53:16 -07:00
Mathias Agopian
111be6ef6d Merge branch 'main' into ma/descriptor-sets 2024-08-09 15:25:58 -07:00
Mathias Agopian
bd0ca7a9bd Merge branch 'main' into ma/descriptor-sets 2024-08-08 22:48:25 -07:00
Mathias Agopian
8797f45bd0 Merge branch 'main' into ma/descriptor-sets 2024-08-08 13:27:20 -07:00
Mathias Agopian
f04a25028e Merge branch 'main' into ma/descriptor-sets 2024-08-01 22:19:16 -07:00
Powei Feng
bb1f58e97c Fix broken merge for vk (#8007) 2024-07-31 10:40:09 +08:00
Powei Feng
189e36d539 Merge branch 'main' into ma/descriptor-sets 2024-07-31 10:11:51 +08:00
Mathias Agopian
cba819be8f Merge branch 'main' into ma/descriptor-sets 2024-07-25 17:25:08 -07:00
Mathias Agopian
41e1e7e54b Merge branch 'main' into ma/descriptor-sets 2024-07-24 15:38:12 -07:00
Ben Doherty
d8580d93ad New external image API (#7981) 2024-07-23 13:06:48 -06:00
Mathias Agopian
fef40a8c5d Merge branch 'main' into ma/descriptor-sets 2024-07-19 10:09:28 -07:00
Mathias Agopian
d299b1f00e Merge branch 'main' into ma/descriptor-sets 2024-06-28 13:20:28 -07:00
Mathias Agopian
daa2790c4e Merge branch 'main' into ma/descriptor-sets 2024-06-28 12:54:46 -07:00
Mathias Agopian
d2cb53e39d add minimal support for Texture Views in the backends
Texture Views are currently limited to the min/max lod, and it's not
allowed to have several views of the same texture in a single shader.
Only SAMPLEABLE textures can have a texture view.

Additionally, setMinMaxLevels() is removed as well as all automatic
min/max lods adjustments. This is all handled by filament itself
from now on, using Texture Views.
2024-06-28 12:23:58 -07:00
Mathias Agopian
315c8d75bc vk: repair transmission + MSAA
That code was changed as part as what looked like a refactoring
unrelated to the descriptor-set change itself.
Reverting that thunk fixes the problem, however the revert doesn't 
have the layout transitions that were added.

This PR reverts back to the old behavior, but adds the layout 
transitions.

It looks like the refactored code didn't handle "auto resolve" anymore.
2024-06-28 12:12:27 -07:00
Ben Doherty
c19785b72f Merge branch 'main' into ma/descriptor-sets 2024-06-27 17:48:34 -07:00
Benjamin Doherty
38b3074835 Fix validation error, bind descriptor sets at bind time 2024-06-27 17:42:24 -07:00
Benjamin Doherty
929ad27606 Pass the correct stage flags 2024-06-27 14:44:59 -07:00
Benjamin Doherty
c12bcbb21e Bind the actual resource at updateDescriptorSet time 2024-06-27 14:44:57 -07:00
Benjamin Doherty
07f07e0700 Remove log spew 2024-06-27 14:35:20 -07:00
Benjamin Doherty
7e0181b2cf Merge remote-tracking branch 'origin/main' into ma/descriptor-sets 2024-06-25 18:19:39 -07:00
Mathias Agopian
b4162eb313 fix windows build 2024-06-24 22:57:09 -07:00
Mathias Agopian
a0dc7104b6 fix backend tests
The pipeline layout wasn't set properly
2024-06-24 22:08:02 -07:00
Mathias Agopian
80f151ccf7 Merge branch 'main' into ma/descriptor-sets 2024-06-24 16:32:18 -07:00
Benjamin Doherty
26bb99c64e Pass descriptor set info to spirvToMsl more concicely 2024-06-24 15:19:09 -07:00
Benjamin Doherty
1612cdb353 WIP: Implement Metal descriptor sets 2024-06-21 17:00:47 -07:00
Benjamin Doherty
e75882e4d4 Update MetalArgumentBuffer to support buffers 2024-06-21 16:42:20 -07:00
Benjamin Doherty
2e763149e7 MetalArgumentBuffer support buffers 2024-06-21 16:42:19 -07:00
Mathias Agopian
e40a4c69a3 Merge branch 'main' into ma/descriptor-sets 2024-06-21 09:50:29 -07:00
Mathias Agopian
9a1ee1ff67 fix bad merge 2024-06-18 14:11:04 -07:00
Mathias Agopian
9fb692b2f6 Merge branch 'main' into ma/descriptor-sets 2024-06-18 13:39:02 -07:00
Ben Doherty
ac7c679783 Merge branch 'main' into ma/descriptor-sets 2024-06-18 13:21:36 -07:00
Mathias Agopian
38a3c52629 vk: build new descriptor set framework 2024-06-17 23:19:24 -07:00
Mathias Agopian
29ecfd5fda generate the proper per-view descriptor-set layout in the shader
the per-view descriptor-set layout depends on both the material
and the variant.
2024-06-17 23:19:24 -07:00
Mathias Agopian
f11a04722b new descriptor set api 2024-06-17 23:19:24 -07:00
208 changed files with 9218 additions and 5674 deletions

View File

@@ -0,0 +1,17 @@
name: 'Android Continuous'
inputs:
build-abi:
description: 'The target platform ABI'
required: true
default: 'armeabi-v7a'
runs:
using: "composite"
steps:
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Run build script
run: |
cd build/android && printf "y" | ./build.sh continuous ${{ inputs.build-abi }}
shell: bash

View File

@@ -8,32 +8,35 @@ on:
- rc/**
jobs:
build-android:
name: build-android
build-android-armv7:
name: build-android-armv7
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- uses: actions/setup-java@v3
- name: Run Android Continuous
uses: ./.github/actions/android-continuous
with:
distribution: 'temurin'
java-version: '17'
- name: Run build script
run: |
cd build/android && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v1.0.0
build-abi: armeabi-v7a
build-android-armv8a:
name: build-android-armv8a
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- name: Run Android Continuous
uses: ./.github/actions/android-continuous
with:
name: filament-android
path: out/filament-android-release.aar
- uses: actions/upload-artifact@v1.0.0
build-abi: arm64-v8a
build-android-x86_64:
name: build-android-x86_64
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- name: Run Android Continuous
uses: ./.github/actions/android-continuous
with:
name: filamat-android-full
path: out/filamat-android-release.aar
- uses: actions/upload-artifact@v1.0.0
with:
name: gltfio-android-release
path: out/gltfio-android-release.aar
- uses: actions/upload-artifact@v1.0.0
with:
name: filament-utils-android-release
path: out/filament-utils-android-release.aar
build-abi: x86_64

View File

@@ -49,8 +49,10 @@ jobs:
distribution: 'temurin'
java-version: '17'
- name: Run build script
# Only build 1 64 bit target during presubmit to cut down build times during presubmit
# Continuous builds will build everything
run: |
cd build/android && printf "y" | ./build.sh presubmit
cd build/android && printf "y" | ./build.sh presubmit arm64-v8a
build-ios:
name: build-iOS

View File

@@ -120,7 +120,7 @@ jobs:
env:
TAG: ${{ steps.git_ref.outputs.tag }}
run: |
cd build/android && printf "y" | ./build.sh release
cd build/android && printf "y" | ./build.sh release armeabi-v7a,arm64-v8a,x86,x86_64
cd ../..
mv out/filament-android-release.aar out/filament-${TAG}-android.aar
mv out/filamat-android-release.aar out/filamat-${TAG}-android.aar

View File

@@ -7,3 +7,5 @@ for next branch cut* header.
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- Add support for multi-layered render target with array textures.

View File

@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.54.0'
implementation 'com.google.android.filament:filament-android:1.54.2'
}
```
@@ -51,19 +51,9 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.54.0'
pod 'Filament', '~> 1.54.2'
```
### Snapshots
If you prefer to live on the edge, you can download a continuous build by following the following
steps:
1. Find the [commit](https://github.com/google/filament/commits/main) you're interested in.
2. Click the green check mark under the commit message.
3. Click on the _Details_ link for the platform you're interested in.
4. On the top left click _Summary_, then in the _Artifacts_ section choose the desired artifact.
## Documentation
- [Filament](https://google.github.io/filament/Filament.html), an in-depth explanation of

View File

@@ -7,6 +7,13 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.54.3
## v1.54.2
- Add a `name` API to Filament objects for debugging handle use-after-free assertions
## v1.54.1

View File

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

View File

@@ -60,13 +60,7 @@ if [[ ! -d "${ANDROID_HOME}/ndk/$FILAMENT_NDK_VERSION" ]]; then
yes | ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --licenses
${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager "ndk;$FILAMENT_NDK_VERSION"
fi
# Only build 1 64 bit target during presubmit to cut down build times during presubmit
# Continuous builds will build everything
ANDROID_ABIS=
if [[ "$TARGET" == "presubmit" ]]; then
ANDROID_ABIS="-q arm64-v8a"
fi
# Build the Android sample-gltf-viewer APK during release.
BUILD_SAMPLES=
@@ -74,5 +68,19 @@ if [[ "$TARGET" == "release" ]]; then
BUILD_SAMPLES="-k sample-gltf-viewer"
fi
function build_android() {
local ABI=$1
# Do the following in two steps so that we do not run out of space
if [[ -n "${BUILD_DEBUG}" ]]; then
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION} ./build.sh -p android -q ${ABI} -c ${BUILD_SAMPLES} ${GENERATE_ARCHIVES} ${BUILD_DEBUG}
rm -rf out/cmake-android-debug-*
fi
if [[ -n "${BUILD_RELEASE}" ]]; then
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION} ./build.sh -p android -q ${ABI} -c ${BUILD_SAMPLES} ${GENERATE_ARCHIVES} ${BUILD_RELEASE}
rm -rf out/cmake-android-release-*
fi
}
pushd `dirname $0`/../.. > /dev/null
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION} ./build.sh -p android $ANDROID_ABIS -c $BUILD_SAMPLES $GENERATE_ARCHIVES $BUILD_DEBUG $BUILD_RELEASE
build_android $2

View File

@@ -61,6 +61,7 @@ set(SRCS
src/Engine.cpp
src/Exposure.cpp
src/Fence.cpp
src/FilamentBuilder.cpp
src/FrameInfo.cpp
src/FrameSkipper.cpp
src/Froxelizer.cpp
@@ -75,8 +76,6 @@ set(SRCS
src/MaterialInstance.cpp
src/MaterialParser.cpp
src/MorphTargetBuffer.cpp
src/PerViewUniforms.cpp
src/PerShadowMapUniforms.cpp
src/PostProcessManager.cpp
src/RenderPass.cpp
src/RenderPrimitive.cpp
@@ -125,6 +124,12 @@ set(SRCS
src/details/Texture.cpp
src/details/VertexBuffer.cpp
src/details/View.cpp
src/ds/ColorPassDescriptorSet.cpp
src/ds/DescriptorSet.cpp
src/ds/DescriptorSetLayout.cpp
src/ds/PostProcessDescriptorSet.cpp
src/ds/ShadowMapDescriptorSet.cpp
src/ds/SsrPassDescriptorSet.cpp
src/fg/Blackboard.cpp
src/fg/DependencyGraph.cpp
src/fg/FrameGraph.cpp
@@ -152,19 +157,16 @@ set(PRIVATE_HDRS
src/HwVertexBufferInfoFactory.h
src/Intersections.h
src/MaterialParser.h
src/PerViewUniforms.h
src/PerShadowMapUniforms.h
src/PIDController.h
src/PostProcessManager.h
src/RendererUtils.h
src/RenderPass.h
src/RenderPrimitive.h
src/RendererUtils.h
src/ResourceAllocator.h
src/ResourceList.h
src/ShadowMap.h
src/ShadowMapManager.h
src/SharedHandle.h
src/TypedUniformBuffer.h
src/UniformBuffer.h
src/components/CameraManager.h
src/components/LightManager.h
@@ -192,6 +194,14 @@ set(PRIVATE_HDRS
src/details/Texture.h
src/details/VertexBuffer.h
src/details/View.h
src/downcast.h
src/ds/ColorPassDescriptorSet.h
src/ds/DescriptorSetLayout.h
src/ds/PostProcessDescriptorSet.h
src/ds/ShadowMapDescriptorSet.h
src/ds/SsrPassDescriptorSet.h
src/ds/TypedBuffer.h
src/ds/TypedUniformBuffer.h
src/fg/Blackboard.h
src/fg/FrameGraph.h
src/fg/FrameGraphId.h
@@ -209,7 +219,6 @@ set(PRIVATE_HDRS
src/materials/fsr/ffx_a.h
src/materials/fsr/ffx_fsr1.h
src/materials/fsr/ffx_fsr1_mobile.fs
src/downcast.h
)
set(MATERIAL_SRCS

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -162,6 +162,10 @@ DECL_DRIVER_API_0(finish)
// reset state tracking, if the driver does any state tracking (e.g. GL)
DECL_DRIVER_API_0(resetState)
DECL_DRIVER_API_N(setDebugTag,
backend::HandleBase::HandleId, handleId,
utils::CString, tag)
/*
* Creating driver objects
* -----------------------
@@ -196,20 +200,33 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, createTexture,
uint32_t, depth,
backend::TextureUsage, usage)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureSwizzled,
backend::SamplerType, target,
uint8_t, levels,
backend::TextureFormat, format,
uint8_t, samples,
uint32_t, width,
uint32_t, height,
uint32_t, depth,
backend::TextureUsage, usage,
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureView,
backend::TextureHandle, texture,
uint8_t, baseLevel,
uint8_t, levelCount)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureViewSwizzle,
backend::TextureHandle, texture,
backend::TextureSwizzle, r,
backend::TextureSwizzle, g,
backend::TextureSwizzle, b,
backend::TextureSwizzle, a)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureExternalImage,
backend::TextureFormat, format,
uint32_t, width,
uint32_t, height,
backend::TextureUsage, usage,
void*, image)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureExternalImagePlane,
backend::TextureFormat, format,
uint32_t, width,
uint32_t, height,
backend::TextureUsage, usage,
void*, image,
uint32_t, plane)
DECL_DRIVER_API_R_N(backend::TextureHandle, importTexture,
intptr_t, id,
backend::SamplerType, target,
@@ -221,9 +238,6 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, importTexture,
uint32_t, depth,
backend::TextureUsage, usage)
DECL_DRIVER_API_R_N(backend::SamplerGroupHandle, createSamplerGroup,
uint32_t, size, utils::FixedSizeString<32>, debugName)
DECL_DRIVER_API_R_N(backend::RenderPrimitiveHandle, createRenderPrimitive,
backend::VertexBufferHandle, vbh,
backend::IndexBufferHandle, ibh,
@@ -257,25 +271,53 @@ DECL_DRIVER_API_R_N(backend::SwapChainHandle, createSwapChainHeadless,
DECL_DRIVER_API_R_0(backend::TimerQueryHandle, createTimerQuery)
DECL_DRIVER_API_R_N(backend::DescriptorSetLayoutHandle, createDescriptorSetLayout,
backend::DescriptorSetLayout&&, info)
DECL_DRIVER_API_R_N(backend::DescriptorSetHandle, createDescriptorSet,
backend::DescriptorSetLayoutHandle, dslh)
DECL_DRIVER_API_N(updateDescriptorSetBuffer,
backend::DescriptorSetHandle, dsh,
backend::descriptor_binding_t, binding,
backend::BufferObjectHandle, boh,
uint32_t, offset,
uint32_t, size
)
DECL_DRIVER_API_N(updateDescriptorSetTexture,
backend::DescriptorSetHandle, dsh,
backend::descriptor_binding_t, binding,
backend::TextureHandle, th,
SamplerParams, params
)
DECL_DRIVER_API_N(bindDescriptorSet,
backend::DescriptorSetHandle, dsh,
backend::descriptor_set_t, set,
backend::DescriptorSetOffsetArray&&, offsets
)
/*
* Destroying driver objects
* -------------------------
*/
DECL_DRIVER_API_N(destroyVertexBuffer, backend::VertexBufferHandle, vbh)
DECL_DRIVER_API_N(destroyVertexBufferInfo,backend::VertexBufferInfoHandle, vbih)
DECL_DRIVER_API_N(destroyIndexBuffer, backend::IndexBufferHandle, ibh)
DECL_DRIVER_API_N(destroyBufferObject, backend::BufferObjectHandle, ibh)
DECL_DRIVER_API_N(destroyRenderPrimitive, backend::RenderPrimitiveHandle, rph)
DECL_DRIVER_API_N(destroyProgram, backend::ProgramHandle, ph)
DECL_DRIVER_API_N(destroySamplerGroup, backend::SamplerGroupHandle, sbh)
DECL_DRIVER_API_N(destroyTexture, backend::TextureHandle, th)
DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth)
DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch)
DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh)
DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh)
DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh)
DECL_DRIVER_API_N(destroyVertexBuffer, backend::VertexBufferHandle, vbh)
DECL_DRIVER_API_N(destroyVertexBufferInfo, backend::VertexBufferInfoHandle, vbih)
DECL_DRIVER_API_N(destroyIndexBuffer, backend::IndexBufferHandle, ibh)
DECL_DRIVER_API_N(destroyBufferObject, backend::BufferObjectHandle, ibh)
DECL_DRIVER_API_N(destroyRenderPrimitive, backend::RenderPrimitiveHandle, rph)
DECL_DRIVER_API_N(destroyProgram, backend::ProgramHandle, ph)
DECL_DRIVER_API_N(destroyTexture, backend::TextureHandle, th)
DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth)
DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch)
DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh)
DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh)
DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh)
DECL_DRIVER_API_N(destroyDescriptorSetLayout, backend::DescriptorSetLayoutHandle, dslh)
DECL_DRIVER_API_N(destroyDescriptorSet, backend::DescriptorSetHandle, dsh)
/*
* Synchronous APIs
@@ -342,15 +384,6 @@ DECL_DRIVER_API_N(updateBufferObjectUnsynchronized,
DECL_DRIVER_API_N(resetBufferObject,
backend::BufferObjectHandle, ibh)
DECL_DRIVER_API_N(updateSamplerGroup,
backend::SamplerGroupHandle, ubh,
backend::BufferDescriptor&&, data)
DECL_DRIVER_API_N(setMinMaxLevels,
backend::TextureHandle, th,
uint32_t, minLevel,
uint32_t, maxLevel)
DECL_DRIVER_API_N(update3DImage,
backend::TextureHandle, th,
uint32_t, level,
@@ -365,10 +398,12 @@ DECL_DRIVER_API_N(update3DImage,
DECL_DRIVER_API_N(generateMipmaps,
backend::TextureHandle, th)
// Deprecated
DECL_DRIVER_API_N(setExternalImage,
backend::TextureHandle, th,
void*, image)
// Deprecated
DECL_DRIVER_API_N(setExternalImagePlane,
backend::TextureHandle, th,
void*, image,
@@ -415,37 +450,16 @@ DECL_DRIVER_API_N(commit,
* -----------------------
*/
DECL_DRIVER_API_N(bindUniformBuffer,
uint32_t, index,
backend::BufferObjectHandle, ubh)
DECL_DRIVER_API_N(bindBufferRange,
BufferObjectBinding, bindingType,
uint32_t, index,
backend::BufferObjectHandle, ubh,
uint32_t, offset,
uint32_t, size)
DECL_DRIVER_API_N(unbindBuffer,
BufferObjectBinding, bindingType,
uint32_t, index)
DECL_DRIVER_API_N(bindSamplers,
uint32_t, index,
backend::SamplerGroupHandle, sbh)
DECL_DRIVER_API_N(setPushConstant,
backend::ShaderStage, stage,
uint8_t, index,
backend::PushConstantVariant, value)
DECL_DRIVER_API_N(insertEventMarker,
const char*, string,
uint32_t, len = 0)
const char*, string)
DECL_DRIVER_API_N(pushGroupMarker,
const char*, string,
uint32_t, len = 0)
const char*, string)
DECL_DRIVER_API_0(popGroupMarker)

View File

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

View File

@@ -20,11 +20,12 @@
#include <backend/Handle.h>
#include <utils/Allocator.h>
#include <utils/CString.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <utils/Panic.h>
#include <tsl/robin_map.h>
@@ -37,7 +38,7 @@
#include <stddef.h>
#include <stdint.h>
#define HandleAllocatorGL HandleAllocator<32, 64, 136> // ~4520 / pool / MiB
#define HandleAllocatorGL HandleAllocator<32, 96, 136> // ~4520 / pool / MiB
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB
#define HandleAllocatorMTL HandleAllocator<32, 64, 552> // ~1660 / pool / MiB
@@ -173,8 +174,10 @@ public:
uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
auto const pNode = static_cast<typename Allocator::Node*>(p);
uint8_t const expectedAge = pNode[-1].age;
FILAMENT_CHECK_POSTCONDITION(expectedAge == age) <<
"use-after-free of Handle with id=" << handle.getId();
// getHandleTag() is only called if the check fails.
FILAMENT_CHECK_POSTCONDITION(expectedAge == age)
<< "use-after-free of Handle with id=" << handle.getId()
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
}
}
@@ -201,6 +204,29 @@ public:
return handle_cast<Dp>(const_cast<Handle<B>&>(handle));
}
void associateTagToHandle(HandleBase::HandleId id, utils::CString&& tag) noexcept {
// TODO: for now, only pool handles check for use-after-free, so we only keep tags for
// those
if (isPoolHandle(id)) {
// Truncate the age to get the debug tag
uint32_t const key = id & ~(HANDLE_DEBUG_TAG_MASK ^ HANDLE_AGE_MASK);
// This line is the costly part. In the future, we could potentially use a custom
// allocator.
mDebugTags[key] = std::move(tag);
}
}
utils::CString getHandleTag(HandleBase::HandleId id) const noexcept {
if (!isPoolHandle(id)) {
return "(no tag)";
}
uint32_t const key = id & ~(HANDLE_DEBUG_TAG_MASK ^ HANDLE_AGE_MASK);
if (auto pos = mDebugTags.find(key); pos != mDebugTags.end()) {
return pos->second;
}
return "(no tag)";
}
private:
template<typename D>
@@ -318,12 +344,24 @@ private:
}
}
// we handle a 4 bits age per address
static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u; // pool vs heap handle
static constexpr uint32_t HANDLE_AGE_MASK = 0x78000000u; // handle's age
static constexpr uint32_t HANDLE_INDEX_MASK = 0x07FFFFFFu; // handle index
static constexpr uint32_t HANDLE_TAG_MASK = HANDLE_AGE_MASK;
static constexpr uint32_t HANDLE_AGE_SHIFT = 27;
// number if bits allotted to the handle's age (currently 4 max)
static constexpr uint32_t HANDLE_AGE_BIT_COUNT = 4;
// number if bits allotted to the handle's debug tag (HANDLE_AGE_BIT_COUNT max)
static constexpr uint32_t HANDLE_DEBUG_TAG_BIT_COUNT = 2;
// bit shift for both the age and debug tag
static constexpr uint32_t HANDLE_AGE_SHIFT = 27;
// mask for the heap (vs pool) flag
static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u;
// mask for the age
static constexpr uint32_t HANDLE_AGE_MASK =
((1 << HANDLE_AGE_BIT_COUNT) - 1) << HANDLE_AGE_SHIFT;
// mask for the debug tag
static constexpr uint32_t HANDLE_DEBUG_TAG_MASK =
((1 << HANDLE_DEBUG_TAG_BIT_COUNT) - 1) << HANDLE_AGE_SHIFT;
// mask for the index
static constexpr uint32_t HANDLE_INDEX_MASK = 0x07FFFFFFu;
static_assert(HANDLE_DEBUG_TAG_BIT_COUNT <= HANDLE_AGE_BIT_COUNT);
static bool isPoolHandle(HandleBase::HandleId id) noexcept {
return (id & HANDLE_HEAP_FLAG) == 0u;
@@ -338,7 +376,7 @@ private:
// a non-pool handle.
if (UTILS_LIKELY(isPoolHandle(id))) {
char* const base = (char*)mHandleArena.getArea().begin();
uint32_t const tag = id & HANDLE_TAG_MASK;
uint32_t const tag = id & HANDLE_AGE_MASK;
size_t const offset = (id & HANDLE_INDEX_MASK) * Allocator::getAlignment();
return { static_cast<void*>(base + offset), tag };
}
@@ -353,7 +391,7 @@ private:
size_t const offset = (char*)p - base;
assert_invariant((offset % Allocator::getAlignment()) == 0);
auto id = HandleBase::HandleId(offset / Allocator::getAlignment());
id |= tag & HANDLE_TAG_MASK;
id |= tag & HANDLE_AGE_MASK;
assert_invariant((id & HANDLE_HEAP_FLAG) == 0);
return id;
}
@@ -363,6 +401,7 @@ private:
// Below is only used when running out of space in the HandleArena
mutable utils::Mutex mLock;
tsl::robin_map<HandleBase::HandleId, void*> mOverflowMap;
tsl::robin_map<HandleBase::HandleId, utils::CString> mDebugTags;
HandleBase::HandleId mId = 0;
bool mUseAfterFreeCheckDisabled = false;
};

View File

@@ -24,7 +24,7 @@
#include <utils/debug.h>
#include <utils/ostream.h>
#if !defined(WIN32) && !defined(__EMSCRIPTEN__) && !defined(IOS)
#if !defined(WIN32) && !defined(__EMSCRIPTEN__)
# include <sys/mman.h>
# include <unistd.h>
# define HAS_MMAP 1

View File

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

View File

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

View File

@@ -80,6 +80,9 @@ HandleAllocator<P0, P1, P2>::HandleAllocator(const char* name, size_t size,
bool disableUseAfterFreeCheck) noexcept
: mHandleArena(name, size, disableUseAfterFreeCheck),
mUseAfterFreeCheckDisabled(disableUseAfterFreeCheck) {
// Reserve initial space for debug tags. This prevents excessive calls to malloc when the first
// few tags are set.
mDebugTags.reserve(512);
}
template <size_t P0, size_t P1, size_t P2>

View File

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

View File

@@ -160,6 +160,8 @@ public:
size_t size, bool forceGpuBuffer = false);
~MetalBuffer();
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer || mCpuBuffer; }
MetalBuffer(const MetalBuffer& rhs) = delete;
MetalBuffer& operator=(const MetalBuffer& rhs) = delete;
@@ -180,7 +182,7 @@ public:
* is no device allocation.
*
*/
id<MTLBuffer> getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) noexcept;
id<MTLBuffer> getGpuBufferForDraw() noexcept;
void* getCpuBuffer() const noexcept { return mCpuBuffer; }

View File

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

View File

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

View File

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

View File

@@ -32,6 +32,7 @@
#include <functional>
#include <mutex>
#include <vector>
#include <deque>
namespace filament {
namespace backend {
@@ -57,11 +58,11 @@ class MetalDriver final : public DriverBase {
public:
static Driver* create(MetalPlatform* platform, const Platform::DriverConfig& driverConfig);
void runAtNextTick(const std::function<void()>& fn) noexcept;
private:
friend class MetalSwapChain;
friend struct MetalDescriptorSet;
MetalPlatform& mPlatform;
MetalContext* mContext;
@@ -73,10 +74,23 @@ private:
/*
* Tasks run regularly on the driver thread.
* Not thread-safe; tasks are run from the driver thead and must be enqueued from the driver
* thread.
*/
void runAtNextTick(const std::function<void()>& fn) noexcept;
void executeTickOps() noexcept;
std::vector<std::function<void()>> mTickOps;
std::mutex mTickOpsLock;
// Tasks regularly executed on the driver thread after a command buffer has completed
struct DeferredTask {
DeferredTask(uint64_t commandBufferId, utils::Invocable<void()>&& fn) noexcept
: commandBufferId(commandBufferId), fn(std::move(fn)) {}
uint64_t commandBufferId; // after this command buffer completes
utils::Invocable<void()> fn; // execute this task
};
void executeAfterCurrentCommandBufferCompletes(utils::Invocable<void()>&& fn) noexcept;
void executeDeferredOps() noexcept;
std::deque<DeferredTask> mDeferredTasks;
/*
* Driver interface
@@ -137,7 +151,6 @@ private:
inline void setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph, PrimitiveType pt,
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh);
void finalizeSamplerGroup(MetalSamplerGroup* sg);
void enumerateBoundBuffers(BufferObjectBinding bindingType,
const std::function<void(const BufferState&, MetalBuffer*, uint32_t)>& f);

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -60,10 +60,19 @@ public:
struct RenderPrimitive {
static_assert(MAX_VERTEX_ATTRIBUTE_COUNT <= 16);
GLuint vao[2] = {}; // 4
GLuint vao[2] = {}; // 8
GLuint elementArray = 0; // 4
GLenum indicesType = 0; // 4
// The optional 32-bit handle to a GLVertexBuffer is necessary only if the referenced
// VertexBuffer supports buffer objects. If this is zero, then the VBO handles array is
// immutable.
Handle<HwVertexBuffer> vertexBufferWithObjects; // 4
mutable utils::bitset<uint16_t> vertexAttribArray; // 2
uint8_t reserved[2] = {}; // 2
// if this differs from vertexBufferWithObjects->bufferObjectsVersion, this VAO needs to
// be updated (see OpenGLDriver::updateVertexArrayObject())
uint8_t vertexBufferVersion = 0; // 1
@@ -76,16 +85,11 @@ public:
// See OpenGLContext::bindVertexArray()
uint8_t nameVersion = 0; // 1
// Size in bytes of indices in the index buffer
uint8_t indicesSize = 0; // 1
// The optional 32-bit handle to a GLVertexBuffer is necessary only if the referenced
// VertexBuffer supports buffer objects. If this is zero, then the VBO handles array is
// immutable.
Handle<HwVertexBuffer> vertexBufferWithObjects; // 4
// Size in bytes of indices in the index buffer (1 or 2)
uint8_t indicesShift = 0; // 1
GLenum getIndicesType() const noexcept {
return indicesSize == 4 ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
return indicesType;
}
} gl;
@@ -474,12 +478,6 @@ public:
void unbindEverything() noexcept;
void synchronizeStateAndCache(size_t index) noexcept;
void setEs2UniformBinding(size_t index, GLuint id, void const* data, uint16_t age) noexcept {
mUniformBindings[index] = { id, data, age };
}
auto getEs2UniformBinding(size_t index) const noexcept {
return mUniformBindings[index];
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLuint getSamplerSlow(SamplerParams sp) const noexcept;
@@ -506,9 +504,6 @@ private:
std::vector<std::function<void(OpenGLContext&)>> mDestroyWithNormalContext;
RenderPrimitive mDefaultVAO;
std::optional<GLuint> mDefaultFbo[2];
std::array<
std::tuple<GLuint, void const*, uint16_t>,
CONFIG_UNIFORM_BINDING_COUNT> mUniformBindings = {};
mutable tsl::robin_map<SamplerParams, GLuint,
SamplerParams::Hasher, SamplerParams::EqualTo> mSamplerMap;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -159,12 +159,11 @@ private:
VulkanReadPixels mReadPixels;
VulkanDescriptorSetManager mDescriptorSetManager;
VulkanDescriptorSetManager::GetPipelineLayoutFunction mGetPipelineFunction;
// This is necessary for us to write to push constants after binding a pipeline.
struct BoundPipeline {
VulkanProgram* program;
VkPipelineLayout pipelineLayout;
DescriptorSetMask descriptorSetMask;
};
BoundPipeline mBoundPipeline = {};
RenderPassFboBundle mRenderPassFboInfo;

View File

@@ -16,13 +16,11 @@
#include "VulkanHandles.h"
#include "VulkanDriver.h"
#include "VulkanConstants.h"
#include "VulkanDriver.h"
#include "VulkanMemory.h"
#include "VulkanResourceAllocator.h"
#include "VulkanUtility.h"
#include "spirv/VulkanSpirvUtils.h"
#include "utils/Log.h"
#include <backend/platforms/VulkanPlatform.h>
@@ -54,54 +52,13 @@ void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) {
}
template<typename Bitmask>
static constexpr Bitmask fromStageFlags(ShaderStageFlags2 flags, uint8_t binding) {
Bitmask ret = 0;
if (flags & ShaderStageFlags2::VERTEX) {
ret |= (getVertexStage<Bitmask>() << binding);
inline void fromStageFlags(backend::ShaderStageFlags stage, descriptor_binding_t binding,
Bitmask& mask) {
if ((bool) (stage & ShaderStageFlags::VERTEX)) {
mask.set(binding + getVertexStageShift<Bitmask>());
}
if (flags & ShaderStageFlags2::FRAGMENT) {
ret |= (getFragmentStage<Bitmask>() << binding);
}
return ret;
}
constexpr decltype(VulkanProgram::MAX_SHADER_MODULES) MAX_SHADER_MODULES =
VulkanProgram::MAX_SHADER_MODULES;
using LayoutDescriptionList = VulkanProgram::LayoutDescriptionList;
template<typename Bitmask>
void addDescriptors(Bitmask mask,
utils::FixedCapacityVector<DescriptorSetLayoutBinding>& outputList) {
constexpr uint8_t MODULE_OFFSET = (sizeof(Bitmask) * 8) / MAX_SHADER_MODULES;
for (uint8_t i = 0; i < MODULE_OFFSET; ++i) {
bool const hasVertex = (mask & (1ULL << i)) != 0;
bool const hasFragment = (mask & (1ULL << (MODULE_OFFSET + i))) != 0;
if (!hasVertex && !hasFragment) {
continue;
}
DescriptorSetLayoutBinding binding{
.binding = i,
.flags = DescriptorFlags::NONE,
.count = 0,// This is always 0 for now as we pass the size of the UBOs in the Driver API
// instead.
};
if (hasVertex) {
binding.stageFlags = ShaderStageFlags2::VERTEX;
}
if (hasFragment) {
binding.stageFlags = static_cast<ShaderStageFlags2>(
binding.stageFlags | ShaderStageFlags2::FRAGMENT);
}
if constexpr (std::is_same_v<Bitmask, UniformBufferBitmask>) {
binding.type = DescriptorType::UNIFORM_BUFFER;
} else if constexpr (std::is_same_v<Bitmask, SamplerBitmask>) {
binding.type = DescriptorType::SAMPLER;
} else if constexpr (std::is_same_v<Bitmask, InputAttachmentBitmask>) {
binding.type = DescriptorType::INPUT_ATTACHMENT;
}
outputList.push_back(binding);
if ((bool) (stage & ShaderStageFlags::FRAGMENT)) {
mask.set(binding + getFragmentStageShift<Bitmask>());
}
}
@@ -123,22 +80,113 @@ inline VkShaderStageFlags getVkStage(backend::ShaderStage stage) {
}
}
inline VkDescriptorSetLayoutCreateInfo getLayoutCreateInfo(DescriptorSetLayout const& layout) {
// Note that the following *needs* to be static so that VkDescriptorSetLayoutCreateInfo will not
// refer to stack memory.
static VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
uint32_t count = 0;
for (auto const& binding: layout.bindings) {
VkShaderStageFlags stages = 0;
VkDescriptorType type;
if ((binding.stageFlags & ShaderStageFlags::VERTEX) != ShaderStageFlags::NONE) {
stages |= VK_SHADER_STAGE_VERTEX_BIT;
}
if ((binding.stageFlags & ShaderStageFlags::FRAGMENT) != ShaderStageFlags::NONE) {
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
}
assert_invariant(stages != 0);
switch (binding.type) {
case DescriptorType::UNIFORM_BUFFER: {
type = (binding.flags & DescriptorFlags::DYNAMIC_OFFSET) != DescriptorFlags::NONE
? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC
: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
break;
}
case DescriptorType::SAMPLER: {
type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
break;
}
case DescriptorType::INPUT_ATTACHMENT: {
type = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
break;
}
case DescriptorType::SHADER_STORAGE_BUFFER:
PANIC_POSTCONDITION("Shader storage is not supported.");
break;
}
toBind[count++] = {
.binding = binding.binding,
.descriptorType = type,
.descriptorCount = 1,
.stageFlags = stages,
};
}
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
VkDescriptorSetLayoutCreateInfo dlinfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.pNext = nullptr,
.bindingCount = count,
.pBindings = toBind,
};
return dlinfo;
}
using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask;
BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
BitmaskGroup mask;
for (auto const& binding: layout.bindings) {
switch (binding.type) {
case DescriptorType::UNIFORM_BUFFER: {
if ((binding.flags & DescriptorFlags::DYNAMIC_OFFSET) != DescriptorFlags::NONE) {
fromStageFlags(binding.stageFlags, binding.binding, mask.dynamicUbo);
} else {
fromStageFlags(binding.stageFlags, binding.binding, mask.ubo);
}
break;
}
case DescriptorType::SAMPLER: {
fromStageFlags(binding.stageFlags, binding.binding, mask.sampler);
break;
}
case DescriptorType::INPUT_ATTACHMENT: {
fromStageFlags(binding.stageFlags, binding.binding, mask.inputAttachment);
break;
}
case DescriptorType::SHADER_STORAGE_BUFFER:
PANIC_POSTCONDITION("Shader storage is not supported");
break;
}
}
return mask;
}
} // anonymous namespace
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(VkDevice device,
VkDescriptorSetLayoutCreateInfo const& info, Bitmask const& bitmask)
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT),
mDevice(device),
vklayout(createDescriptorSetLayout(device, info)),
bitmask(bitmask),
bindings(getBindings(bitmask)),
count(Count::fromLayoutBitmask(bitmask)) {}
DescriptorSetLayout const& layout)
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT), mDevice(device),
vklayout(createDescriptorSetLayout(device, getLayoutCreateInfo(layout))),
bitmask(fromBackendLayout(layout)),
count(Count::fromLayoutBitmask(bitmask)) {
}
VulkanDescriptorSetLayout::~VulkanDescriptorSetLayout() {
vkDestroyDescriptorSetLayout(mDevice, vklayout, VKALLOC);
}
void VulkanDescriptorSet::acquire(VulkanTexture* texture) {
mResources.acquire(texture);
mTextures[mTextureCount++] = texture;
}
void VulkanDescriptorSet::acquire(VulkanBufferObject* bufferObject) {
mResources.acquire(bufferObject);
}
PushConstantDescription::PushConstantDescription(backend::Program const& program) noexcept {
mRangeCount = 0;
for (auto stage : { ShaderStage::VERTEX, ShaderStage::FRAGMENT, ShaderStage::COMPUTE }) {
@@ -190,24 +238,11 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
mInfo(new(std::nothrow) PipelineInfo(builder)),
mDevice(device) {
constexpr uint8_t UBO_MODULE_OFFSET = (sizeof(UniformBufferBitmask) * 8) / MAX_SHADER_MODULES;
constexpr uint8_t SAMPLER_MODULE_OFFSET = (sizeof(SamplerBitmask) * 8) / MAX_SHADER_MODULES;
constexpr uint8_t INPUT_ATTACHMENT_MODULE_OFFSET =
(sizeof(InputAttachmentBitmask) * 8) / MAX_SHADER_MODULES;
Program::ShaderSource const& blobs = builder.getShadersSource();
auto& modules = mInfo->shaders;
auto const& specializationConstants = builder.getSpecializationConstants();
std::vector<uint32_t> shader;
// TODO: this will be moved out of the shader as the descriptor set layout will be provided by
// Filament instead of parsed from the shaders. See [GDSR] in VulkanDescriptorSetManager.h
UniformBufferBitmask uboMask = 0;
SamplerBitmask samplerMask = 0;
InputAttachmentBitmask inputAttachmentMask = 0;
static_assert(static_cast<ShaderStage>(0) == ShaderStage::VERTEX &&
static_cast<ShaderStage>(1) == ShaderStage::FRAGMENT &&
MAX_SHADER_MODULES == 2);
@@ -224,12 +259,6 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
dataSize = shader.size() * 4;
}
auto const [ubo, sampler, inputAttachment] = getProgramBindings(blob);
uboMask |= (static_cast<UniformBufferBitmask>(ubo) << (UBO_MODULE_OFFSET * i));
samplerMask |= (static_cast<SamplerBitmask>(sampler) << (SAMPLER_MODULE_OFFSET * i));
inputAttachmentMask |= (static_cast<InputAttachmentBitmask>(inputAttachment)
<< (INPUT_ATTACHMENT_MODULE_OFFSET * i));
VkShaderModule& module = modules[i];
VkShaderModuleCreateInfo moduleInfo = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
@@ -257,40 +286,6 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
#endif
}
LayoutDescriptionList& layouts = mInfo->layouts;
layouts[0].bindings = utils::FixedCapacityVector<DescriptorSetLayoutBinding>::with_capacity(
countBits(collapseStages(uboMask)));
layouts[1].bindings = utils::FixedCapacityVector<DescriptorSetLayoutBinding>::with_capacity(
countBits(collapseStages(samplerMask)));
layouts[2].bindings = utils::FixedCapacityVector<DescriptorSetLayoutBinding>::with_capacity(
countBits(collapseStages(inputAttachmentMask)));
addDescriptors(uboMask, layouts[0].bindings);
addDescriptors(samplerMask, layouts[1].bindings);
addDescriptors(inputAttachmentMask, layouts[2].bindings);
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
auto& bindingToName = mInfo->bindingToName;
#endif
auto& groupInfo = builder.getSamplerGroupInfo();
auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex;
auto& bindings = mInfo->bindings;
for (uint8_t groupInd = 0; groupInd < Program::SAMPLER_BINDING_COUNT; groupInd++) {
auto const& group = groupInfo[groupInd];
auto const& samplers = group.samplers;
for (size_t i = 0; i < samplers.size(); ++i) {
uint32_t const binding = samplers[i].binding;
bindingToSamplerIndex[binding] = (groupInd << 8) | (0xff & i);
assert_invariant(bindings.find(binding) == bindings.end());
bindings.insert(binding);
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
bindingToName[binding] = samplers[i].name.c_str();
#endif
}
}
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "Created VulkanProgram " << builder << ", shaders = (" << modules[0]
<< ", " << modules[1] << ")" << utils::io::endl;
@@ -321,6 +316,7 @@ void VulkanRenderTarget::bindToSwapChain(VulkanSwapChain& swapChain) {
VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
uint32_t width, uint32_t height, uint8_t samples,
VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount)
@@ -354,6 +350,7 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
if (UTILS_UNLIKELY(!msTexture)) {
// TODO: This should be allocated with the ResourceAllocator.
msTexture = new VulkanTexture(device, physicalDevice, context, allocator, commands,
handleAllocator,
texture->target, ((VulkanTexture const*) texture)->levels, texture->format,
samples, texture->width, texture->height, texture->depth, texture->usage,
stagePool, true /* heap allocated */);
@@ -383,7 +380,8 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
VulkanTexture* msTexture = depthTexture->getSidecar();
if (UTILS_UNLIKELY(!msTexture)) {
msTexture = new VulkanTexture(device, physicalDevice, context, allocator,
commands, depthTexture->target, msLevel, depthTexture->format, samples,
commands, handleAllocator,
depthTexture->target, msLevel, depthTexture->format, samples,
depthTexture->width, depthTexture->height, depthTexture->depth, depthTexture->usage,
stagePool, true /* heap allocated */);
depthTexture->setSidecar(msTexture);
@@ -535,15 +533,7 @@ bool VulkanTimerQuery::isCompleted() noexcept {
// timestamp has at least been written into a processed command buffer.
// This fence indicates that the corresponding buffer has been completed.
if (!mFence) {
return false;
}
VkResult status = mFence->status.load(std::memory_order_relaxed);
if (status != VK_SUCCESS) {
return false;
}
return true;
return mFence && mFence->getStatus() == VK_SUCCESS;
}
VulkanTimerQuery::~VulkanTimerQuery() = default;
@@ -559,35 +549,4 @@ VulkanRenderPrimitive::VulkanRenderPrimitive(VulkanResourceAllocator* resourceAl
mResources.acquire(indexBuffer);
}
using Bitmask = VulkanDescriptorSetLayout::Bitmask;
Bitmask Bitmask::fromBackendLayout(descset::DescriptorSetLayout const& layout) {
Bitmask mask;
for (auto const& binding: layout.bindings) {
switch (binding.type) {
case descset::DescriptorType::UNIFORM_BUFFER: {
if (binding.flags == descset::DescriptorFlags::DYNAMIC_OFFSET) {
mask.dynamicUbo |= fromStageFlags<UniformBufferBitmask>(binding.stageFlags,
binding.binding);
} else {
mask.ubo |= fromStageFlags<UniformBufferBitmask>(binding.stageFlags,
binding.binding);
}
break;
}
case descset::DescriptorType::SAMPLER: {
mask.sampler |= fromStageFlags<SamplerBitmask>(binding.stageFlags, binding.binding);
break;
}
case descset::DescriptorType::INPUT_ATTACHMENT: {
mask.inputAttachment |=
fromStageFlags<InputAttachmentBitmask>(binding.stageFlags, binding.binding);
break;
}
}
}
return mask;
}
} // namespace filament::backend

View File

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

View File

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

View File

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

View File

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

View File

@@ -91,8 +91,7 @@ public:
VkBlendFactor dstAlphaBlendFactor : 5;
VkColorComponentFlags colorWriteMask : 4;
uint8_t rasterizationSamples : 4;// offset = 4 bytes
uint8_t depthClamp : 1;
uint8_t reserved : 3;
uint8_t depthClamp : 4;
uint8_t colorTargetCount; // offset = 5 bytes
BlendEquation colorBlendOp : 4; // offset = 6 bytes
BlendEquation alphaBlendOp : 4;

View File

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

View File

@@ -105,6 +105,10 @@ public:
mHandleAllocatorImpl.deallocate(handle, obj);
}
inline void associateHandle(HandleBase::HandleId id, utils::CString&& tag) noexcept {
mHandleAllocatorImpl.associateTagToHandle(id, std::move(tag));
}
private:
AllocatorImpl mHandleAllocatorImpl;

View File

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

View File

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

View File

@@ -15,6 +15,7 @@
*/
#include "VulkanMemory.h"
#include "VulkanResourceAllocator.h"
#include "VulkanTexture.h"
#include "VulkanUtility.h"
@@ -28,50 +29,138 @@ using namespace bluevk;
namespace filament::backend {
VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
namespace {
inline uint8_t getLayerCount(SamplerType const target, uint32_t const depth) {
switch (target) {
case SamplerType::SAMPLER_2D:
case SamplerType::SAMPLER_3D:
case SamplerType::SAMPLER_EXTERNAL:
return 1;
case SamplerType::SAMPLER_CUBEMAP:
return 6;
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
return depth * 6;
case SamplerType::SAMPLER_2D_ARRAY:
return depth;
}
}
VkComponentMapping composeSwizzle(VkComponentMapping const& prev, VkComponentMapping const& next) {
static constexpr VkComponentSwizzle IDENTITY[] = {
VK_COMPONENT_SWIZZLE_R,
VK_COMPONENT_SWIZZLE_G,
VK_COMPONENT_SWIZZLE_B,
VK_COMPONENT_SWIZZLE_A,
};
auto const compose = [](VkComponentSwizzle out, VkComponentMapping const& prev,
uint8_t channelIndex) {
// We need to first change all identities to its equivalent channel.
if (out == VK_COMPONENT_SWIZZLE_IDENTITY) {
out = IDENTITY[channelIndex];
}
switch (out) {
case VK_COMPONENT_SWIZZLE_R:
out = prev.r;
break;
case VK_COMPONENT_SWIZZLE_G:
out = prev.g;
break;
case VK_COMPONENT_SWIZZLE_B:
out = prev.b;
break;
case VK_COMPONENT_SWIZZLE_A:
out = prev.a;
break;
case VK_COMPONENT_SWIZZLE_IDENTITY:
case VK_COMPONENT_SWIZZLE_ZERO:
case VK_COMPONENT_SWIZZLE_ONE:
return out;
// Below is not exposed in Vulkan's API, but needs to be there for compilation.
case VK_COMPONENT_SWIZZLE_MAX_ENUM:
break;
}
// If the result correctly corresponds to the identity, just return identity.
if (IDENTITY[channelIndex] == out) {
return VK_COMPONENT_SWIZZLE_IDENTITY;
}
return out;
};
// Note that the channel index corresponds to the VkComponentMapping struct layout.
return {
compose(next.r, prev, 0),
compose(next.g, prev, 1),
compose(next.b, prev, 2),
compose(next.a, prev, 3),
};
}
} // anonymous namespace
VulkanTextureState::VulkanTextureState(
VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool,
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount)
: VulkanResource(VulkanResourceType::HEAP_ALLOCATED),
mVkFormat(format),
mViewType(viewType),
mFullViewRange {
filament::backend::getImageAspect(format), 0, levels, 0, layerCount
},
mStagePool(stagePool),
mDevice(device),
mAllocator(allocator),
mCommands(commands) {
}
VulkanTextureState* VulkanTexture::getSharedState() {
VulkanTextureState* state = mAllocator->handle_cast<VulkanTextureState*>(mState);
return state;
}
VulkanTextureState const* VulkanTexture::getSharedState() const {
VulkanTextureState const* state = mAllocator->handle_cast<VulkanTextureState const*>(mState);
return state;
}
VulkanTexture::VulkanTexture(
VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VkImage image, VkFormat format, uint8_t samples, uint32_t width, uint32_t height,
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated)
: HwTexture(SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED,
tusage),
VulkanResource(
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
mVkFormat(format),
mViewType(imgutil::getViewType(target)),
mSwizzle({}),
mTextureImage(image),
mFullViewRange{
.aspectMask = getImageAspect(),
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
mPrimaryViewRange(mFullViewRange),
mStagePool(stagePool),
mDevice(device),
mAllocator(allocator),
mCommands(commands) {}
: HwTexture(SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED,
tusage),
VulkanResource(
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
mAllocator(handleAllocator),
mState(handleAllocator->initHandle<VulkanTextureState>(
device, allocator, commands, stagePool,
format, imgutil::getViewType(SamplerType::SAMPLER_2D), 1, 1)) {
auto* const state = getSharedState();
state->mTextureImage = image;
mPrimaryViewRange = state->mFullViewRange;
}
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
SamplerType target, uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w,
uint32_t h, uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool,
bool heapAllocated, VkComponentMapping swizzle)
VulkanResourceAllocator* handleAllocator, SamplerType target, uint8_t levels,
TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated)
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage),
VulkanResource(
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
mVkFormat(backend::getVkFormat(tformat)),
mViewType(imgutil::getViewType(target)),
mSwizzle(swizzle),
mStagePool(stagePool),
mDevice(device),
mAllocator(allocator),
mCommands(commands) {
mAllocator(handleAllocator),
mState(handleAllocator->initHandle<VulkanTextureState>(device, allocator, commands, stagePool,
backend::getVkFormat(tformat), imgutil::getViewType(target), levels,
getLayerCount(target, depth))) {
auto* const state = getSharedState();
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
VkImageCreateInfo imageInfo{.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,
.format = mVkFormat,
.format = state->mVkFormat,
.extent = {w, h, depth},
.mipLevels = levels,
.arrayLayers = 1,
@@ -97,13 +186,11 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.extent.depth = 1;
}
// Filament expects blit() to work with any texture, so we almost always set these usage flags.
// TODO: investigate performance implications of setting these flags.
constexpr VkImageUsageFlags blittable = VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
if (any(usage & (TextureUsage::BLIT_DST | TextureUsage::BLIT_SRC))) {
imageInfo.usage |= blittable;
if (any(usage & TextureUsage::BLIT_SRC)) {
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
if (any(usage & TextureUsage::BLIT_DST)) {
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
if (any(usage & TextureUsage::SAMPLEABLE)) {
@@ -111,9 +198,9 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
// Validate that the format is actually sampleable.
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(physicalDevice, mVkFormat, &props);
vkGetPhysicalDeviceFormatProperties(physicalDevice, state->mVkFormat, &props);
if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << mVkFormat << " is not "
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << state->mVkFormat << " is not "
"sampleable with optimal tiling." << utils::io::endl;
}
#endif
@@ -121,7 +208,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | blittable;
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
if (any(usage & TextureUsage::SUBPASS_INPUT)) {
imageInfo.usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
}
@@ -130,10 +217,9 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
}
if (any(usage & TextureUsage::UPLOADABLE)) {
imageInfo.usage |= blittable;
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
imageInfo.usage |= blittable;
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
// Depth resolves uses a custom shader and therefore needs to be sampleable.
@@ -147,7 +233,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
// any kind of attachment (color or depth).
const auto& limits = context.getPhysicalDeviceLimits();
if (imageInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) {
samples = reduceSampleCount(samples, isVkDepthFormat(mVkFormat)
samples = reduceSampleCount(samples, isVkDepthFormat(state->mVkFormat)
? limits.sampledImageDepthSampleCounts
: limits.sampledImageColorSampleCounts);
}
@@ -161,12 +247,12 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
this->samples = samples;
imageInfo.samples = (VkSampleCountFlagBits) samples;
VkResult error = vkCreateImage(mDevice, &imageInfo, VKALLOC, &mTextureImage);
VkResult error = vkCreateImage(state->mDevice, &imageInfo, VKALLOC, &state->mTextureImage);
if (error || FVK_ENABLED(FVK_DEBUG_TEXTURE)) {
FVK_LOGD << "vkCreateImage: "
<< "image = " << mTextureImage << ", "
<< "image = " << state->mTextureImage << ", "
<< "result = " << error << ", "
<< "handle = " << utils::io::hex << mTextureImage << utils::io::dec << ", "
<< "handle = " << utils::io::hex << state->mTextureImage << utils::io::dec << ", "
<< "extent = " << w << "x" << h << "x"<< depth << ", "
<< "mipLevels = " << int(levels) << ", "
<< "TextureUsage = " << static_cast<int>(usage) << ", "
@@ -175,13 +261,13 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
<< "type = " << imageInfo.imageType << ", "
<< "flags = " << imageInfo.flags << ", "
<< "target = " << static_cast<int>(target) <<", "
<< "format = " << mVkFormat << utils::io::endl;
<< "format = " << state->mVkFormat << utils::io::endl;
}
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to create image.";
// Allocate memory for the VkImage and bind it.
VkMemoryRequirements memReqs = {};
vkGetImageMemoryRequirements(mDevice, mTextureImage, &memReqs);
vkGetImageMemoryRequirements(state->mDevice, state->mTextureImage, &memReqs);
uint32_t memoryTypeIndex
= context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
@@ -194,58 +280,67 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
.allocationSize = memReqs.size,
.memoryTypeIndex = memoryTypeIndex,
};
error = vkAllocateMemory(mDevice, &allocInfo, nullptr, &mTextureImageMemory);
error = vkAllocateMemory(state->mDevice, &allocInfo, nullptr, &state->mTextureImageMemory);
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to allocate image memory.";
error = vkBindImageMemory(mDevice, mTextureImage, mTextureImageMemory, 0);
error = vkBindImageMemory(state->mDevice, state->mTextureImage, state->mTextureImageMemory, 0);
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to bind image.";
uint32_t layerCount = 0;
if (target == SamplerType::SAMPLER_CUBEMAP) {
layerCount = 6;
} else if (target == SamplerType::SAMPLER_CUBEMAP_ARRAY) {
layerCount = depth * 6;
} else if (target == SamplerType::SAMPLER_2D_ARRAY) {
layerCount = depth;
} else if (target == SamplerType::SAMPLER_3D) {
layerCount = 1;
} else {
layerCount = 1;
}
mFullViewRange = {
.aspectMask = getImageAspect(),
.baseMipLevel = 0,
.levelCount = levels,
.baseArrayLayer = 0,
.layerCount = layerCount,
};
// Spec out the "primary" VkImageView that shaders use to sample from the image.
mPrimaryViewRange = mFullViewRange;
mPrimaryViewRange = state->mFullViewRange;
// Go ahead and create the primary image view.
getImageView(mPrimaryViewRange, mViewType, mSwizzle);
getImageView(mPrimaryViewRange, state->mViewType, mSwizzle);
// Transition the layout of each image slice that might be used as a render target.
// We do not transition images that are merely SAMPLEABLE, this is deferred until upload time
// because we do not know how many layers and levels will actually be used.
if (imageInfo.usage
& (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) {
VulkanCommandBuffer& commands = mCommands->get();
VkCommandBuffer const cmdbuf = commands.buffer();
commands.acquire(this);
transitionLayout(cmdbuf, mFullViewRange, imgutil::getDefaultLayout(imageInfo.usage));
}
VulkanCommandBuffer& commandsBuf = state->mCommands->get();
commandsBuf.acquire(this);
transitionLayout(&commandsBuf, mPrimaryViewRange, imgutil::getDefaultLayout(imageInfo.usage));
}
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, uint8_t baseLevel, uint8_t levelCount)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
VulkanResource(VulkanResourceType::TEXTURE),
mAllocator(handleAllocator)
{
mState = src->mState;
auto* state = getSharedState();
state->refs++;
mPrimaryViewRange = src->mPrimaryViewRange;
mPrimaryViewRange.baseMipLevel = src->mPrimaryViewRange.baseMipLevel + baseLevel;
mPrimaryViewRange.levelCount = levelCount;
}
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, VkComponentMapping swizzle)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
VulkanResource(VulkanResourceType::TEXTURE),
mAllocator(handleAllocator) {
mState = src->mState;
auto* state = getSharedState();
state->refs++;
mPrimaryViewRange = src->mPrimaryViewRange;
mSwizzle = composeSwizzle(src->mSwizzle, swizzle);
}
VulkanTexture::~VulkanTexture() {
if (mTextureImageMemory != VK_NULL_HANDLE) {
vkDestroyImage(mDevice, mTextureImage, VKALLOC);
vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC);
}
for (auto entry : mCachedImageViews) {
vkDestroyImageView(mDevice, entry.second, VKALLOC);
auto* const state = getSharedState();
state->refs--;
if (state->refs == 0) {
if (state->mTextureImageMemory != VK_NULL_HANDLE) {
vkDestroyImage(state->mDevice, state->mTextureImage, VKALLOC);
vkFreeMemory(state->mDevice, state->mTextureImageMemory, VKALLOC);
}
for (auto entry: state->mCachedImageViews) {
vkDestroyImageView(state->mDevice, entry.second, VKALLOC);
}
mAllocator->destruct<VulkanTextureState>(mState);
}
}
@@ -254,7 +349,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
assert_invariant(width <= this->width && height <= this->height);
assert_invariant(depth <= this->depth * ((target == SamplerType::SAMPLER_CUBEMAP ||
target == SamplerType::SAMPLER_CUBEMAP_ARRAY) ? 6 : 1));
auto* const state = getSharedState();
const PixelBufferDescriptor* hostData = &data;
PixelBufferDescriptor reshapedData;
@@ -267,7 +362,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
// If format conversion is both required and supported, use vkCmdBlitImage.
const VkFormat hostFormat = backend::getVkFormat(hostData->format, hostData->type);
const VkFormat deviceFormat = getVkFormatLinear(mVkFormat);
const VkFormat deviceFormat = getVkFormatLinear(state->mVkFormat);
if (hostFormat != deviceFormat && hostFormat != VK_FORMAT_UNDEFINED) {
assert_invariant(xoffset == 0 && yoffset == 0 && zoffset == 0 &&
"Offsets not yet supported when format conversion is required.");
@@ -279,14 +374,14 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
// Otherwise, use vkCmdCopyBufferToImage.
void* mapped = nullptr;
VulkanStage const* stage = mStagePool.acquireStage(hostData->size);
VulkanStage const* stage = state->mStagePool.acquireStage(hostData->size);
assert_invariant(stage->memory);
vmaMapMemory(mAllocator, stage->memory, &mapped);
vmaMapMemory(state->mAllocator, stage->memory, &mapped);
memcpy(mapped, hostData->buffer, hostData->size);
vmaUnmapMemory(mAllocator, stage->memory);
vmaFlushAllocation(mAllocator, stage->memory, 0, hostData->size);
vmaUnmapMemory(state->mAllocator, stage->memory);
vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData->size);
VulkanCommandBuffer& commands = mCommands->get();
VulkanCommandBuffer& commands = state->mCommands->get();
VkCommandBuffer const cmdbuf = commands.buffer();
commands.acquire(this);
@@ -332,24 +427,25 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
nextLayout = imgutil::getDefaultLayout(this->usage);
}
transitionLayout(cmdbuf, transitionRange, newLayout);
transitionLayout(&commands, transitionRange, newLayout);
vkCmdCopyBufferToImage(cmdbuf, stage->buffer, mTextureImage, newVkLayout, 1, &copyRegion);
vkCmdCopyBufferToImage(cmdbuf, stage->buffer, state->mTextureImage, newVkLayout, 1, &copyRegion);
transitionLayout(cmdbuf, transitionRange, nextLayout);
transitionLayout(&commands, transitionRange, nextLayout);
}
void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width,
uint32_t height, uint32_t depth, uint32_t miplevel) {
auto* const state = getSharedState();
void* mapped = nullptr;
VulkanStageImage const* stage
= mStagePool.acquireImage(hostData.format, hostData.type, width, height);
vmaMapMemory(mAllocator, stage->memory, &mapped);
= state->mStagePool.acquireImage(hostData.format, hostData.type, width, height);
vmaMapMemory(state->mAllocator, stage->memory, &mapped);
memcpy(mapped, hostData.buffer, hostData.size);
vmaUnmapMemory(mAllocator, stage->memory);
vmaFlushAllocation(mAllocator, stage->memory, 0, hostData.size);
vmaUnmapMemory(state->mAllocator, stage->memory);
vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData.size);
VulkanCommandBuffer& commands = mCommands->get();
VulkanCommandBuffer& commands = state->mCommands->get();
VkCommandBuffer const cmdbuf = commands.buffer();
commands.acquire(this);
@@ -371,19 +467,12 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
VulkanLayout const newLayout = VulkanLayout::TRANSFER_DST;
VulkanLayout const oldLayout = getLayout(layer, miplevel);
transitionLayout(cmdbuf, range, newLayout);
transitionLayout(&commands, range, newLayout);
vkCmdBlitImage(cmdbuf, stage->image, imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC),
mTextureImage, imgutil::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST);
state->mTextureImage, imgutil::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST);
transitionLayout(cmdbuf, range, oldLayout);
}
void VulkanTexture::setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel) {
maxMiplevel = filament::math::min(int(maxMiplevel), int(this->levels - 1));
mPrimaryViewRange.baseMipLevel = minMiplevel;
mPrimaryViewRange.levelCount = maxMiplevel - minMiplevel + 1;
getImageView(mPrimaryViewRange, mViewType, mSwizzle);
transitionLayout(&commands, range, oldLayout);
}
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
@@ -402,35 +491,44 @@ VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range,
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
VkComponentMapping swizzle) {
ImageViewKey const key {range, viewType, swizzle};
auto iter = mCachedImageViews.find(key);
if (iter != mCachedImageViews.end()) {
auto* const state = getSharedState();
VulkanTextureState::ImageViewKey const key{ range, viewType, swizzle };
auto iter = state->mCachedImageViews.find(key);
if (iter != state->mCachedImageViews.end()) {
return iter->second;
}
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.image = mTextureImage,
.image = state->mTextureImage,
.viewType = viewType,
.format = mVkFormat,
.format = state->mVkFormat,
.components = swizzle,
.subresourceRange = range,
};
VkImageView imageView;
vkCreateImageView(mDevice, &viewInfo, VKALLOC, &imageView);
mCachedImageViews.emplace(key, imageView);
vkCreateImageView(state->mDevice, &viewInfo, VKALLOC, &imageView);
state->mCachedImageViews.emplace(key, imageView);
return imageView;
}
VkImageAspectFlags VulkanTexture::getImageAspect() const {
// Helper function in VulkanUtility
return filament::backend::getImageAspect(mVkFormat);
auto* const state = getSharedState();
return filament::backend::getImageAspect(state->mVkFormat);
}
void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubresourceRange& range,
VulkanLayout newLayout) {
bool VulkanTexture::transitionLayout(VulkanCommandBuffer* commands,
const VkImageSubresourceRange& range, VulkanLayout newLayout) {
return transitionLayout(commands->buffer(), commands->fence, range, newLayout);
}
bool VulkanTexture::transitionLayout(
VkCommandBuffer cmdbuf, std::shared_ptr<VulkanCmdFence> fence,
const VkImageSubresourceRange& range,
VulkanLayout newLayout) {
auto* const state = getSharedState();
VulkanLayout const oldLayout = getLayout(range.baseArrayLayer, range.baseMipLevel);
uint32_t const firstLayer = range.baseArrayLayer;
@@ -441,7 +539,7 @@ void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubres
// If we are transitioning more than one layer/level (slice), we need to know whether they are
// all of the same layer. If not, we need to transition slice-by-slice. Otherwise it would
// trigger the validation layer saying that the `oldLayout` provided is incorrect.
// TODO: transition by multiple slices with more sophiscated range finding.
// TODO: transition by multiple slices with more sophisticated range finding.
bool transitionSliceBySlice = false;
for (uint32_t i = firstLayer; i < lastLayer; ++i) {
for (uint32_t j = firstLevel; j < lastLevel; ++j) {
@@ -452,50 +550,82 @@ void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubres
}
}
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
FVK_LOGD << "transition texture=" << mTextureImage
<< " (" << range.baseArrayLayer
<< "," << range.baseMipLevel << ")"
<< " count=(" << range.layerCount
<< "," << range.levelCount << ")"
<< " from=" << oldLayout << " to=" << newLayout
<< " format=" << mVkFormat
<< " depth=" << isVkDepthFormat(mVkFormat)
<< " slice-by-slice=" << transitionSliceBySlice
<< utils::io::endl;
#endif
bool hasTransitions = false;
if (transitionSliceBySlice) {
for (uint32_t i = firstLayer; i < lastLayer; ++i) {
for (uint32_t j = firstLevel; j < lastLevel; ++j) {
VulkanLayout const layout = getLayout(i, j);
imgutil::transitionLayout(cmdbuf, {
.image = mTextureImage,
.oldLayout = layout,
.newLayout = newLayout,
.subresources = {
.aspectMask = range.aspectMask,
.baseMipLevel = j,
.levelCount = 1,
.baseArrayLayer = i,
.layerCount = 1,
},
});
if (layout == newLayout) {
continue;
}
hasTransitions = hasTransitions || imgutil::transitionLayout(cmdbuf, {
.image = state->mTextureImage,
.oldLayout = layout,
.newLayout = newLayout,
.subresources = {
.aspectMask = range.aspectMask,
.baseMipLevel = j,
.levelCount = 1,
.baseArrayLayer = i,
.layerCount = 1,
},
});
}
}
} else {
imgutil::transitionLayout(cmdbuf, {
.image = mTextureImage,
} else if (newLayout != oldLayout) {
hasTransitions = imgutil::transitionLayout(cmdbuf, {
.image = state->mTextureImage,
.oldLayout = oldLayout,
.newLayout = newLayout,
.subresources = range,
});
}
setLayout(range, newLayout);
if (hasTransitions) {
state->mTransitionFence = fence;
setLayout(range, newLayout);
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
FVK_LOGD << "transition texture=" << state->mTextureImage << " (" << range.baseArrayLayer
<< "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << ","
<< range.levelCount << ")" << " from=" << oldLayout << " to=" << newLayout
<< " format=" << state->mVkFormat << " depth=" << isVkDepthFormat(state->mVkFormat)
<< " slice-by-slice=" << transitionSliceBySlice << utils::io::endl;
#endif
} else {
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
FVK_LOGD << "transition texture=" << state->mTextureImage << " (" << range.baseArrayLayer
<< "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << ","
<< range.levelCount << ")" << " to=" << newLayout
<< " is skipped because of no change in layout" << utils::io::endl;
#endif
}
return hasTransitions;
}
void VulkanTexture::setLayout(const VkImageSubresourceRange& range, VulkanLayout newLayout) {
void VulkanTexture::attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
VkImageSubresourceRange const& range) {
VkCommandBuffer const cmdbuf = commands->buffer();
auto* const state = getSharedState();
VkImageLayout const layout
= imgutil::getVkLayout(getLayout(range.baseArrayLayer, range.baseMipLevel));
VkImageMemoryBarrier barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = layout,
.newLayout = layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = state->mTextureImage,
.subresourceRange = range,
};
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}
void VulkanTexture::setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout) {
auto* const state = getSharedState();
uint32_t const firstLayer = range.baseArrayLayer;
uint32_t const lastLayer = firstLayer + range.layerCount;
uint32_t const firstLevel = range.baseMipLevel;
@@ -508,28 +638,30 @@ void VulkanTexture::setLayout(const VkImageSubresourceRange& range, VulkanLayout
for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) {
uint32_t const first = (layer << 16) | firstLevel;
uint32_t const last = (layer << 16) | lastLevel;
mSubresourceLayouts.clear(first, last);
state->mSubresourceLayouts.clear(first, last);
}
} else {
for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) {
uint32_t const first = (layer << 16) | firstLevel;
uint32_t const last = (layer << 16) | lastLevel;
mSubresourceLayouts.add(first, last, newLayout);
state->mSubresourceLayouts.add(first, last, newLayout);
}
}
}
VulkanLayout VulkanTexture::getLayout(uint32_t layer, uint32_t level) const {
assert_invariant(level <= 0xffff && layer <= 0xffff);
auto* const state = getSharedState();
const uint32_t key = (layer << 16) | level;
if (!mSubresourceLayouts.has(key)) {
if (!state->mSubresourceLayouts.has(key)) {
return VulkanLayout::UNDEFINED;
}
return mSubresourceLayouts.get(key);
return state->mSubresourceLayouts.get(key);
}
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
void VulkanTexture::print() const {
auto* const state = getSharedState();
uint32_t const firstLayer = 0;
uint32_t const lastLayer = firstLayer + mFullViewRange.layerCount;
uint32_t const firstLevel = 0;
@@ -542,16 +674,16 @@ void VulkanTexture::print() const {
layer < (mPrimaryViewRange.baseArrayLayer + mPrimaryViewRange.layerCount) &&
level >= mPrimaryViewRange.baseMipLevel &&
level < (mPrimaryViewRange.baseMipLevel + mPrimaryViewRange.levelCount);
FVK_LOGD << "[" << mTextureImage << "]: (" << layer << "," << level
FVK_LOGD << "[" << state->mTextureImage << "]: (" << layer << "," << level
<< ")=" << getLayout(layer, level)
<< " primary=" << primary
<< utils::io::endl;
}
}
for (auto view: mCachedImageViews) {
for (auto view: state->mCachedImageViews) {
auto& range = view.first.range;
FVK_LOGD << "[" << mTextureImage << ", imageView=" << view.second << "]=>"
FVK_LOGD << "[" << state->mTextureImage << ", imageView=" << view.second << "]=>"
<< " (" << range.baseArrayLayer << "," << range.baseMipLevel << ")"
<< " count=(" << range.layerCount << "," << range.levelCount << ")"
<< " aspect=" << range.aspectMask << " viewType=" << view.first.type

View File

@@ -30,88 +30,12 @@
namespace filament::backend {
struct VulkanTexture : public HwTexture, VulkanResource {
// Standard constructor for user-facing textures.
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
VmaAllocator allocator, VulkanCommands* commands, SamplerType target, uint8_t levels,
TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated = false,
VkComponentMapping swizzle = {});
class VulkanResourceAllocator;
// Specialized constructor for internally created textures (e.g. from a swap chain)
// The texture will never destroy the given VkImage, but it does manages its subresources.
VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands, VkImage image,
VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage,
VulkanStagePool& stagePool, bool heapAllocated = false);
~VulkanTexture();
// Uploads data into a subregion of a 2D or 3D texture.
void updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel);
// Returns the primary image view, which is used for shader sampling.
VkImageView getPrimaryImageView() {
return getImageView(mPrimaryViewRange, mViewType, mSwizzle);
}
VkImageViewType getViewType() const { return mViewType; }
// Sets the min/max range of miplevels in the primary image view.
void setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel);
VkImageSubresourceRange getPrimaryViewRange() const { return mPrimaryViewRange; }
VkImageSubresourceRange getFullViewRange() const { return mFullViewRange; }
VulkanLayout getPrimaryImageLayout() const {
return getLayout(mPrimaryViewRange.baseArrayLayer, mPrimaryViewRange.baseMipLevel);
}
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
// target attachment. Unlike the primary image view, this always has type VK_IMAGE_VIEW_TYPE_2D
// and the identity swizzle.
VkImageView getAttachmentView(VkImageSubresourceRange range);
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
// target attachment when rendering with multiview.
VkImageView getMultiviewAttachmentView(VkImageSubresourceRange range);
// This is a workaround for the first few frames where we're waiting for the texture to actually
// be uploaded. In that case, we bind the sampler to an empty texture, but the corresponding
// imageView needs to be of the right type. Hence, we provide an option to indicate the
// view type. Swizzle option does not matter in this case.
VkImageView getViewForType(VkImageSubresourceRange const& range, VkImageViewType type);
VkFormat getVkFormat() const { return mVkFormat; }
VkImage getVkImage() const { return mTextureImage; }
VulkanLayout getLayout(uint32_t layer, uint32_t level) const;
void setSidecar(VulkanTexture* sidecar) {
mSidecarMSAA.reset(sidecar);
}
VulkanTexture* getSidecar() const {
return mSidecarMSAA.get();
}
void transitionLayout(VkCommandBuffer commands, const VkImageSubresourceRange& range,
VulkanLayout newLayout);
// Returns the preferred data plane of interest for all image views.
// For now this always returns either DEPTH or COLOR.
VkImageAspectFlags getImageAspect() const;
// For implicit transition like the end of a render pass, we need to be able to set the layout
// manually (outside of calls to transitionLayout).
void setLayout(const VkImageSubresourceRange& range, VulkanLayout newLayout);
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
void print() const;
#endif
private:
struct VulkanTextureState : public VulkanResource {
VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool,
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount);
struct ImageViewKey {
VkImageSubresourceRange range; // 4 * 5 bytes
@@ -134,6 +58,150 @@ private:
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
uint32_t refs = 1;
// The texture with the sidecar owns the sidecar.
std::unique_ptr<VulkanTexture> mSidecarMSAA;
VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE;
VkFormat const mVkFormat;
VkImageViewType const mViewType;
VkImageSubresourceRange const mFullViewRange;
VkImage mTextureImage = VK_NULL_HANDLE;
// Track the image layout of each subresource using a sparse range map.
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
VulkanStagePool& mStagePool;
VkDevice mDevice;
VmaAllocator mAllocator;
VulkanCommands* mCommands;
std::shared_ptr<VulkanCmdFence> mTransitionFence;
};
struct VulkanTexture : public HwTexture, VulkanResource {
// Standard constructor for user-facing textures.
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
SamplerType target, uint8_t levels,
TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated = false);
// Specialized constructor for internally created textures (e.g. from a swap chain)
// The texture will never destroy the given VkImage, but it does manages its subresources.
VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VkImage image,
VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage,
VulkanStagePool& stagePool, bool heapAllocated = false);
// Constructor for creating a texture view for wrt specific mip range
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, uint8_t baseLevel, uint8_t levelCount);
// Constructor for creating a texture view for swizzle.
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, VkComponentMapping swizzle);
~VulkanTexture();
// Uploads data into a subregion of a 2D or 3D texture.
void updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel);
// Returns the primary image view, which is used for shader sampling.
VkImageView getPrimaryImageView() {
VulkanTextureState* state = getSharedState();
return getImageView(mPrimaryViewRange, state->mViewType, mSwizzle);
}
VkImageViewType getViewType() const {
VulkanTextureState const* state = getSharedState();
return state->mViewType;
}
VkImageSubresourceRange getPrimaryViewRange() const { return mPrimaryViewRange; }
VulkanLayout getPrimaryImageLayout() const {
return getLayout(mPrimaryViewRange.baseArrayLayer, mPrimaryViewRange.baseMipLevel);
}
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
// target attachment. Unlike the primary image view, this always has type VK_IMAGE_VIEW_TYPE_2D
// and the identity swizzle.
VkImageView getAttachmentView(VkImageSubresourceRange range);
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
// target attachment when rendering with multiview.
VkImageView getMultiviewAttachmentView(VkImageSubresourceRange range);
// This is a workaround for the first few frames where we're waiting for the texture to actually
// be uploaded. In that case, we bind the sampler to an empty texture, but the corresponding
// imageView needs to be of the right type. Hence, we provide an option to indicate the
// view type. Swizzle option does not matter in this case.
VkImageView getViewForType(VkImageSubresourceRange const& range, VkImageViewType type);
VkFormat getVkFormat() const {
VulkanTextureState const* state = getSharedState();
return state->mVkFormat;
}
VkImage getVkImage() const {
VulkanTextureState const* state = getSharedState();
return state->mTextureImage;
}
VulkanLayout getLayout(uint32_t layer, uint32_t level) const;
void setSidecar(VulkanTexture* sidecar) {
VulkanTextureState* state = getSharedState();
state->mSidecarMSAA.reset(sidecar);
}
VulkanTexture* getSidecar() const {
VulkanTextureState const* state = getSharedState();
return state->mSidecarMSAA.get();
}
bool transitionLayout(VulkanCommandBuffer* commands, const VkImageSubresourceRange& range,
VulkanLayout newLayout);
bool transitionLayout(VkCommandBuffer cmdbuf, std::shared_ptr<VulkanCmdFence> fence,
VkImageSubresourceRange const& range, VulkanLayout newLayout);
void attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
VkImageSubresourceRange const& range);
// Returns the preferred data plane of interest for all image views.
// For now this always returns either DEPTH or COLOR.
VkImageAspectFlags getImageAspect() const;
// For implicit transition like the end of a render pass, we need to be able to set the layout
// manually (outside of calls to transitionLayout).
void setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout);
bool transitionReady() {
VulkanTextureState* state = getSharedState();
auto res = !state->mTransitionFence || state->mTransitionFence->getStatus() == VK_SUCCESS;
state->mTransitionFence.reset();
return res;
}
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
void print() const;
#endif
private:
VulkanTextureState* getSharedState();
VulkanTextureState const* getSharedState() const;
// Gets or creates a cached VkImageView for a range of miplevels, array layers, viewType, and
// swizzle (or not).
VkImageView getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
@@ -142,28 +210,15 @@ private:
void updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width, uint32_t height,
uint32_t depth, uint32_t miplevel);
// The texture with the sidecar owns the sidecar.
std::unique_ptr<VulkanTexture> mSidecarMSAA;
const VkFormat mVkFormat;
const VkImageViewType mViewType;
const VkComponentMapping mSwizzle;
VkImage mTextureImage = VK_NULL_HANDLE;
VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE;
VulkanResourceAllocator* const mAllocator;
// Track the image layout of each subresource using a sparse range map.
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
VkImageSubresourceRange mFullViewRange;
Handle<VulkanTextureState> mState;
// Track the range of subresources that define the "primary" image view, which is the special
// image view that gets bound to an actual texture sampler.
VkImageSubresourceRange mPrimaryViewRange;
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
VulkanStagePool& mStagePool;
VkDevice mDevice;
VmaAllocator mAllocator;
VulkanCommands* mCommands;
VkComponentMapping mSwizzle {};
};
} // namespace filament::backend

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -54,7 +54,7 @@ public:
using BufferDescriptor = backend::BufferDescriptor;
using BindingType = backend::BufferObjectBinding;
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
@@ -78,6 +78,21 @@ public:
*/
Builder& bindingType(BindingType bindingType) noexcept;
/**
* Associate an optional name with this BufferObject for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this BufferObject
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the BufferObject and returns a pointer to it. After creation, the buffer
* object is uninitialized. Use BufferObject::setBuffer() to initialize it.

View File

@@ -19,6 +19,7 @@
#include <utils/compiler.h>
#include <utils/PrivateImplementation.h>
#include <utils/CString.h>
#include <stddef.h>
@@ -54,6 +55,23 @@ public:
template<typename T>
using BuilderBase = utils::PrivateImplementation<T>;
// This needs to be public because it is used in the following template.
UTILS_PUBLIC void builderMakeName(utils::CString& outName, const char* name, size_t len) noexcept;
template <typename Builder>
class UTILS_PUBLIC BuilderNameMixin {
public:
Builder& name(const char* name, size_t len) noexcept {
builderMakeName(mName, name, len);
return static_cast<Builder&>(*this);
}
utils::CString const& getName() const noexcept { return mName; }
private:
utils::CString mName;
};
} // namespace filament
#endif // TNT_FILAMENT_FILAMENTAPI_H

View File

@@ -59,7 +59,7 @@ public:
UINT = uint8_t(backend::ElementType::UINT), //!< 32-bit indices
};
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
@@ -83,6 +83,21 @@ public:
*/
Builder& bufferType(IndexType indexType) noexcept;
/**
* Associate an optional name with this IndexBuffer for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this IndexBuffer
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the IndexBuffer object and returns a pointer to it. After creation, the index
* buffer is uninitialized. Use IndexBuffer::setBuffer() to initialize the IndexBuffer.

View File

@@ -38,7 +38,7 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI {
struct BuilderDetails;
public:
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
@@ -70,6 +70,21 @@ public:
*/
Builder& localTransforms(math::mat4f const* UTILS_NULLABLE localTransforms) noexcept;
/**
* Associate an optional name with this InstanceBuffer for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this InstanceBuffer
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the InstanceBuffer object and returns a pointer to it.
*/

View File

@@ -39,7 +39,7 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI {
struct BuilderDetails;
public:
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
@@ -63,6 +63,21 @@ public:
*/
Builder& count(size_t count) noexcept;
/**
* Associate an optional name with this MorphTargetBuffer for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this MorphTargetBuffer
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the MorphTargetBuffer object and returns a pointer to it.
*

View File

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

View File

@@ -39,7 +39,7 @@ class UTILS_PUBLIC SkinningBuffer : public FilamentAPI {
struct BuilderDetails;
public:
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
@@ -69,6 +69,21 @@ public:
*/
Builder& initialize(bool initialize = true) noexcept;
/**
* Associate an optional name with this SkinningBuffer for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this SkinningBuffer
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the SkinningBuffer object and returns a pointer to it.
*

View File

@@ -94,7 +94,7 @@ public:
*
* To create a NATIVE stream, call the <pre>stream</pre> method on the builder.
*/
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
@@ -136,6 +136,21 @@ public:
*/
Builder& height(uint32_t height) noexcept;
/**
* Associate an optional name with this Stream for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this Stream
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the Stream object and returns a pointer to it.
*

View File

@@ -112,7 +112,7 @@ public:
//! Use Builder to construct a Texture object instance
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
@@ -202,6 +202,21 @@ public:
*/
Builder& swizzle(Swizzle r, Swizzle g, Swizzle b, Swizzle a) noexcept;
/**
* Associate an optional name with this Texture for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this Texture
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the Texture object and returns a pointer to it.
*

View File

@@ -61,7 +61,7 @@ public:
using AttributeType = backend::ElementType;
using BufferDescriptor = backend::BufferDescriptor;
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
@@ -158,6 +158,21 @@ public:
*/
Builder& advancedSkinning(bool enabled) noexcept;
/**
* Associate an optional name with this VertexBuffer for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this VertexBuffer
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the VertexBuffer object and returns a pointer to it.
*

View File

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

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <filament/FilamentAPI.h>
#include <algorithm>
namespace filament {
void builderMakeName(utils::CString& outName, const char* name, size_t len) noexcept {
if (!name) {
return;
}
size_t const length = std::min(len, size_t { 128u });
outName = utils::CString(name, length);
}
} // namespace filament

View File

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

View File

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

View File

@@ -24,18 +24,28 @@
#include <filament/MaterialChunkType.h>
#include <private/filament/SamplerBindingsInfo.h>
#include <private/filament/SamplerInterfaceBlock.h>
#include <private/filament/BufferInterfaceBlock.h>
#include <private/filament/SubpassInfo.h>
#include <private/filament/Variant.h>
#include <private/filament/ConstantInfo.h>
#include <private/filament/PushConstantInfo.h>
#include <private/filament/EngineEnums.h>
#include <backend/DriverEnums.h>
#include <backend/Program.h>
#include <utils/compiler.h>
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <array>
#include <optional>
#include <tuple>
#include <utility>
#include <stdlib.h>
#include <optional>
#include <stdint.h>
using namespace utils;
using namespace filament::backend;
@@ -62,12 +72,13 @@ constexpr std::pair<ChunkType, ChunkType> shaderLanguageToTags(ShaderLanguage la
// ------------------------------------------------------------------------------------------------
MaterialParser::MaterialParserDetails::MaterialParserDetails(
const utils::FixedCapacityVector<ShaderLanguage>& preferredLanguages, const void* data,
utils::FixedCapacityVector<ShaderLanguage> preferredLanguages, const void* data,
size_t size)
: mManagedBuffer(data, size),
mChunkContainer(mManagedBuffer.data(), mManagedBuffer.size()),
mPreferredLanguages(preferredLanguages),
mMaterialChunk(mChunkContainer) {}
mPreferredLanguages(std::move(preferredLanguages)),
mMaterialChunk(mChunkContainer) {
}
template<typename T>
UTILS_NOINLINE
@@ -82,11 +93,29 @@ bool MaterialParser::MaterialParserDetails::getFromSimpleChunk(
return false;
}
MaterialParser::MaterialParserDetails::ManagedBuffer::ManagedBuffer(const void* start, size_t size)
: mStart(malloc(size)), mSize(size) {
memcpy(mStart, start, size);
}
MaterialParser::MaterialParserDetails::ManagedBuffer::~ManagedBuffer() noexcept {
free(mStart);
}
// ------------------------------------------------------------------------------------------------
template<typename T>
bool MaterialParser::get(typename T::Container* container) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(T::tag);
if (start == end) return false;
filaflat::Unflattener unflattener{ start, end };
return T::unflatten(unflattener, container);
}
MaterialParser::MaterialParser(utils::FixedCapacityVector<ShaderLanguage> preferredLanguages,
const void* data, size_t size)
: mImpl(preferredLanguages, data, size) {}
: mImpl(std::move(preferredLanguages), data, size) {
}
ChunkContainer& MaterialParser::getChunkContainer() noexcept {
return mImpl.mChunkContainer;
@@ -158,25 +187,16 @@ bool MaterialParser::getCacheId(uint64_t* cacheId) const noexcept {
return unflattener.read(cacheId);
}
bool MaterialParser::getUIB(BufferInterfaceBlock* uib) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialUib);
if (start == end) return false;
Unflattener unflattener(start, end);
return ChunkUniformInterfaceBlock::unflatten(unflattener, uib);
bool MaterialParser::getUIB(BufferInterfaceBlock* container) const noexcept {
return get<ChunkUniformInterfaceBlock>(container);
}
bool MaterialParser::getSIB(SamplerInterfaceBlock* sib) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialSib);
if (start == end) return false;
Unflattener unflattener(start, end);
return ChunkSamplerInterfaceBlock::unflatten(unflattener, sib);
bool MaterialParser::getSIB(SamplerInterfaceBlock* container) const noexcept {
return get<ChunkSamplerInterfaceBlock>(container);
}
bool MaterialParser::getSubpasses(SubpassInfo* subpass) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialSubpass);
if (start == end) return false;
Unflattener unflattener(start, end);
return ChunkSubpassInterfaceBlock::unflatten(unflattener, subpass);
bool MaterialParser::getSubpasses(SubpassInfo* container) const noexcept {
return get<ChunkSubpassInterfaceBlock>(container);
}
bool MaterialParser::getShaderModels(uint32_t* value) const noexcept {
@@ -187,43 +207,24 @@ bool MaterialParser::getMaterialProperties(uint64_t* value) const noexcept {
return mImpl.getFromSimpleChunk(ChunkType::MaterialProperties, value);
}
bool MaterialParser::getUniformBlockBindings(
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>>* value) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialUniformBindings);
if (start == end) return false;
Unflattener unflattener(start, end);
return ChunkUniformBlockBindings::unflatten(unflattener, value);
}
bool MaterialParser::getBindingUniformInfo(BindingUniformInfoContainer* container) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialBindingUniformInfo);
if (start == end) return false;
Unflattener unflattener(start, end);
return ChunkBindingUniformInfo::unflatten(unflattener, container);
return get<ChunkBindingUniformInfo>(container);
}
bool MaterialParser::getAttributeInfo(AttributeInfoContainer* container) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialAttributeInfo);
if (start == end) return false;
Unflattener unflattener(start, end);
return ChunkAttributeInfo::unflatten(unflattener, container);
return get<ChunkAttributeInfo>(container);
}
bool MaterialParser::getSamplerBlockBindings(
SamplerGroupBindingInfoList* pSamplerGroupInfoList,
SamplerBindingToNameMap* pSamplerBindingToNameMap) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialSamplerBindings);
if (start == end) return false;
Unflattener unflattener(start, end);
return ChunkSamplerBlockBindings::unflatten(unflattener,
pSamplerGroupInfoList, pSamplerBindingToNameMap);
bool MaterialParser::getDescriptorBindings(DescriptorBindingsContainer* container) const noexcept {
return get<ChunkDescriptorBindingsInfo>(container);
}
bool MaterialParser::getConstants(utils::FixedCapacityVector<MaterialConstant>* value) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(filamat::MaterialConstants);
if (start == end) return false;
Unflattener unflattener(start, end);
return ChunkMaterialConstants::unflatten(unflattener, value);
bool MaterialParser::getDescriptorSetLayout(DescriptorSetLayoutContainer* container) const noexcept {
return get<ChunkDescriptorSetLayoutInfo>(container);
}
bool MaterialParser::getConstants(utils::FixedCapacityVector<MaterialConstant>* container) const noexcept {
return get<ChunkMaterialConstants>(container);
}
bool MaterialParser::getPushConstants(utils::CString* structVarName,
@@ -466,7 +467,9 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
}
for (uint64_t i = 0; i < numFields; i++) {
static_assert(sizeof(backend::descriptor_binding_t) == sizeof(uint8_t));
CString fieldName;
uint8_t fieldBinding = 0;
uint8_t fieldType = 0;
uint8_t fieldFormat = 0;
uint8_t fieldPrecision = 0;
@@ -476,6 +479,10 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
return false;
}
if (!unflattener.read(&fieldBinding)) {
return false;
}
if (!unflattener.read(&fieldType)) {
return false;
}
@@ -492,7 +499,9 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
return false;
}
builder.add({ fieldName.data(), fieldName.size() }, SamplerInterfaceBlock::Type(fieldType),
builder.add({ fieldName.data(), fieldName.size() },
SamplerInterfaceBlock::Binding(fieldBinding),
SamplerInterfaceBlock::Type(fieldType),
SamplerInterfaceBlock::Format(fieldFormat),
SamplerInterfaceBlock::Precision(fieldPrecision),
fieldMultisample);
@@ -557,28 +566,6 @@ bool ChunkSubpassInterfaceBlock::unflatten(Unflattener& unflattener,
return true;
}
bool ChunkUniformBlockBindings::unflatten(filaflat::Unflattener& unflattener,
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>>* uniformBlockBindings) {
uint8_t count;
if (!unflattener.read(&count)) {
return false;
}
uniformBlockBindings->reserve(count);
for (uint8_t i = 0; i < count; i++) {
CString name;
uint8_t binding;
if (!unflattener.read(&name)) {
return false;
}
if (!unflattener.read(&binding)) {
return false;
}
uniformBlockBindings->emplace_back(std::move(name), binding);
}
return true;
}
bool ChunkBindingUniformInfo::unflatten(filaflat::Unflattener& unflattener,
MaterialParser::BindingUniformInfoContainer* bindingUniformInfo) {
uint8_t bindingPointCount;
@@ -591,6 +578,10 @@ bool ChunkBindingUniformInfo::unflatten(filaflat::Unflattener& unflattener,
if (!unflattener.read(&index)) {
return false;
}
utils::CString uboName;
if (!unflattener.read(&uboName)) {
return false;
}
uint8_t uniformCount;
if (!unflattener.read(&uniformCount)) {
return false;
@@ -616,7 +607,7 @@ bool ChunkBindingUniformInfo::unflatten(filaflat::Unflattener& unflattener,
}
uniforms.push_back({ name, offset, size, UniformType(type) });
}
bindingUniformInfo->emplace_back(UniformBindingPoints(index), std::move(uniforms));
bindingUniformInfo->emplace_back(index, std::move(uboName), std::move(uniforms));
}
return true;
}
@@ -646,52 +637,94 @@ bool ChunkAttributeInfo::unflatten(filaflat::Unflattener& unflattener,
return true;
}
bool ChunkSamplerBlockBindings::unflatten(Unflattener& unflattener,
SamplerGroupBindingInfoList* pSamplerGroupBindingInfoList,
SamplerBindingToNameMap* pSamplerBindingToNameMap) {
assert_invariant(pSamplerGroupBindingInfoList && pSamplerBindingToNameMap);
SamplerGroupBindingInfoList& samplerGroupBindingInfoList = *pSamplerGroupBindingInfoList;
SamplerBindingToNameMap& samplerBindingToNameMap = *pSamplerBindingToNameMap;
bool ChunkDescriptorBindingsInfo::unflatten(filaflat::Unflattener& unflattener,
MaterialParser::DescriptorBindingsContainer* container) {
uint8_t count;
if (!unflattener.read(&count)) {
return false;
}
assert_invariant(count == utils::Enum::count<SamplerBindingPoints>());
UTILS_NOUNROLL
for (size_t i = 0; i < count; i++) {
if (!unflattener.read(&samplerGroupBindingInfoList[i].bindingOffset)) {
return false;
}
if (!unflattener.read((uint8_t *)&samplerGroupBindingInfoList[i].shaderStageFlags)) {
return false;
}
if (!unflattener.read(&samplerGroupBindingInfoList[i].count)) {
return false;
}
}
if (!unflattener.read(&count)) {
uint8_t setCount;
if (!unflattener.read(&setCount)) {
return false;
}
samplerBindingToNameMap.reserve(count);
samplerBindingToNameMap.resize(count);
for (size_t i = 0; i < count; i++) {
uint8_t binding;
if (!unflattener.read(&binding)) {
for (size_t j = 0; j < setCount; j++) {
static_assert(sizeof(DescriptorSetBindingPoints) == sizeof(uint8_t));
DescriptorSetBindingPoints set;
if (!unflattener.read(reinterpret_cast<uint8_t*>(&set))) {
return false;
}
assert_invariant(binding < backend::MAX_SAMPLER_COUNT);
if (!unflattener.read(&samplerBindingToNameMap[binding])) {
uint8_t descriptorCount;
if (!unflattener.read(&descriptorCount)) {
return false;
}
auto& descriptors = (*container)[+set];
descriptors.reserve(descriptorCount);
for (size_t i = 0; i < descriptorCount; i++) {
utils::CString name;
if (!unflattener.read(&name)) {
return false;
}
uint8_t type;
if (!unflattener.read(&type)) {
return false;
}
uint8_t binding;
if (!unflattener.read(&binding)) {
return false;
}
descriptors.push_back({
std::move(name),
backend::DescriptorType(type),
backend::descriptor_binding_t(binding)});
}
}
return true;
}
bool ChunkDescriptorSetLayoutInfo::unflatten(filaflat::Unflattener& unflattener,
MaterialParser::DescriptorSetLayoutContainer* container) {
for (size_t j = 0; j < 2; j++) {
uint8_t descriptorCount;
if (!unflattener.read(&descriptorCount)) {
return false;
}
auto& descriptors = (*container)[j].bindings;
descriptors.reserve(descriptorCount);
for (size_t i = 0; i < descriptorCount; i++) {
uint8_t type;
if (!unflattener.read(&type)) {
return false;
}
uint8_t stageFlags;
if (!unflattener.read(&stageFlags)) {
return false;
}
uint8_t binding;
if (!unflattener.read(&binding)) {
return false;
}
uint8_t flags;
if (!unflattener.read(&flags)) {
return false;
}
uint16_t count;
if (!unflattener.read(&count)) {
return false;
}
descriptors.push_back({
backend::DescriptorType(type),
backend::ShaderStageFlags(stageFlags),
backend::descriptor_binding_t(binding),
backend::DescriptorFlags(flags),
count,
});
}
}
return true;
}
bool ChunkMaterialConstants::unflatten(filaflat::Unflattener& unflattener,
utils::FixedCapacityVector<MaterialConstant>* materialConstants) {
assert_invariant(materialConstants);

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