Compare 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 FengandSungun Park 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 AgopianandPowei Feng 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 MandPowei Feng 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
Mathias Agopian 5e7106b521 custom variables can now have their precision specified
e.g.:

    variables : [
        {
            name : vertex,
            precision : medium,
         }
    ],
2024-08-22 13:08:48 -07:00
Mathias Agopian 30387af61c reenable the SimplificationPass in spirv-opt
Issues with it were fixed (https://github.com/KhronosGroup/SPIRV-Cross/pull/2163),
so it should be safe to reenable. The invalid glsl code seems to be resolved.
2024-08-22 13:08:23 -07: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 1c2ffc9ed4 new screenshot debug option in gltf_viewer
screenshots are saved in the current directory as 
"screenshotXX.ppm" with XX increasing after each
screenshot.
2024-08-20 22:41:49 -07:00
Sungun Park 574518ea74 Cleanup color pass for multiview (#8062) 2024-08-20 21:44:17 +00:00
Ben Doherty d5a274fa25 Add better glShaderSource fix for IMG devices (#8061) 2024-08-20 12:28:13 -07:00
Mathias Agopian b6d6f8f6e4 Merge branch 'main' into ma/descriptor-sets 2024-08-20 12:08:32 -07:00
Mathias Agopian 3351db1178 improve FrameSkipper when disregarding skipping
It is legal for the app to draw a frame even when FrameSkipper 
detects that a frame should be skipped. In that case we used to
overwrite the most recent fence, but this wasn't ideal. We now
proceed as if the fence had signaled, i.e. we destroy it and
move all the fences up, creating a new one at the end of the 
delayed array.
2024-08-20 12:07:59 -07:00
Mathias Agopian b70aa43727 depth clamp cannot work with VSM
This is because the shadowmap doesn't store depth values so it doesn't 
work to "flatten" all casters behind de camera to the near plane.
The bulk of shadowing works but the filtering/edges don't always.

Disable depth-clamping when VSM is enabled.
2024-08-20 12:04:01 -07:00
Sungun Park 1f9a11802d Minor tweak (#8053)
Move the common part outside the loop.
2024-08-20 17:13:55 +00:00
Sungun Park 89835a7a67 Fix a bug for calculating distance (#8058)
This is a missing part from the change
22d99bac3d
2024-08-20 16:28:29 +00:00
Powei Feng 28ef805e5d Release Filament 1.54.0 2024-08-20 08:55:10 -07:00
Mathias Agopian f098d08c48 Merge branch 'main' into ma/descriptor-sets 2024-08-19 17:13:44 -07:00
Mathias Agopian 6c0bd360b3 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-19 17:13:30 -07:00
Mathias Agopian 063affb612 more improvement of csm display 2024-08-19 17:13:03 -07:00
Mathias Agopian 9c857f64ae improve split-view mode
- side panel doesn't overlap with content anymore
2024-08-19 17:13:03 -07:00
Mathias Agopian 5c08f34e8c Merge branch 'main' into ma/descriptor-sets 2024-08-19 16:40:36 -07:00
Mathias Agopian 5966b5dd8f fix shadow cascade computations
shadow cascades where not calculated properly because part of the 
calculation took the cascade near/far into account, while another
part didn't. This resulted in cascades being too large. It didn't
create wrong shadows, but reduced (and in some case canceled) the
usefulness of the cascade.

We fix the problem by always  using the projection matrix only for
describing the cascade's frustum, as opposed to just passing the
near/far plane distances.

Now the calculation of each cascade is completely self contained and
identical.


We also improve the orientation of the light frustum:
We can rotate the light frustum around the light direction axis, so
it aligns with the view direction, this generally result in smaller
light frustums. This cannot be used in stable mode.
2024-08-19 16:30:16 -07:00
Mathias Agopian a37d03101e Merge branch 'main' into ma/descriptor-sets 2024-08-19 11:47:19 -07:00
Mathias Agopian 1795c40591 fix typo when calculation shadowmap frustum
min() and lowest() are different!
2024-08-19 00:03:44 -07:00
Mathias Agopian c0819b781a Merge branch 'main' into ma/descriptor-sets 2024-08-16 16:03:54 -07:00
Mathias Agopian 26f4239d8c fix a few issues with shadowing
A recent change broken the optional "depth clamp" as well as the 
computation of the far plane of the light frustum. There was also
a case where DEBUG builds could assert.

- The far plane was no longer being "optimized" (i.e. moved as close
as possible), which resulted in less optimal use of the shadow texture.
the far plane can be moved as close as the farthest visible shadow 
caster.

- After the camera/light frustums intersection we now see of the 2D
bounds seen from the light are empty and if so we bail, which prevents
an assertion later.

- finally, the "DEPTH_CLAMP" option is also updated for the new code
structure.
2024-08-16 16:00:37 -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 ad29b9c70a fix several issue with the debug datasource in View
- the last View created was always overriding previous View's datasource
- because of lazy registering of the data source it was possible that
  the registering lambda was called after the view was destroyed, leading
  to crashes
- all view would share the same PID parameters and these would be
  initialized to default value instead of the user provided value. so
  debug build would behave differently.

With this change we improve things:
- now only the first view gets to publish its data source. it's still
  not ideal, but works for our use case with gltf_Viewer
- the view can now unregister itself when it's destroyed
- only the view that successfully registered uses the debug PID values
  and publishes its data source.
- the normal parameters are used until we query the datasource (from
  imgui), so by default the behavior is now identical to release builds


This fixes a crash in gltf_viewer when opening the Debug panel.
2024-08-16 09:38:54 -07:00
Mathias Agopian 8fe7c1ff30 Merge branch 'main' into ma/descriptor-sets 2024-08-14 22:47:16 -07:00
Sungun Park 1c817026f2 Remove unused code (#8043) 2024-08-14 15:41:10 +00:00
Powei Feng 44a954b559 Fix misnumbered version in RELEASE_NOTES 2024-08-13 16:28:33 -07:00
Powei Feng cdd0147a17 Release Filament 1.53.5 2024-08-13 15:59:08 -07:00
Sungun Park 9de29a475e Add warning for the incompatibility of stereo type (#8031)
Print a warning in case the sterescopic type in a compiled package is
different than what's in the engine's setting. The application may
proceed, but it could end up visual glitches when enabling stereoscopic
rendering.

This requires the stereoscopic type to be written into the package,
which needs a material version bump.
2024-08-13 01:31:10 +00:00
Mathias Agopian 3b12e54573 Merge branch 'main' into ma/descriptor-sets 2024-08-12 15:09:26 -07:00
Mathias Agopian 7f97363d9c fix debug build 2024-08-12 15:09:06 -07:00
Mathias Agopian a1f146ebea Merge branch 'main' into ma/descriptor-sets 2024-08-12 15:05:43 -07:00
Mathias Agopian d88b4c1bc3 Add a way to retrieve the count of all resources
This is intended for debugging.

BUGS=[358111049]
2024-08-12 14:50:43 -07:00
Mathias Agopian 3badf9db18 read all valid pending timer queries each frame
This reduces the latency of the timer query result; with the previous
code the latency could only increase, but there is no reason to wait
a whole frame for reading the next available result.

We just loop over them until we find one that has not signaled; instead
of doing one per frame.
2024-08-12 14:50:20 -07:00
Mathias Agopian 929f793cf3 clean-up RenderPass a bit
make mCommandBegin and mCommandEnd const* const, this allows to make
sort() And resize() static, and instanceify() almost static..
2024-08-12 14:49:57 -07:00
Mathias Agopian 06bffaa650 fix a couple precondition checks logs 2024-08-12 14:49:44 -07:00
Ben Doherty 2e2f111435 Re-enable missing packing/unpacking functions in ESSL, fix IMG shader compilation (#8030) 2024-08-12 10:23:52 -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 06f9626429 improve GL backend debug markers
we now have two levels of debug markers. Those that come from the "user"
(i.e. filament itself) are now always enabled and generate both 
systrace and gl markers. the 2nd level is internal and always
disabled by default. Of enabled at compile time it'll emit markers for
each driver API method.
2024-08-09 15:25:33 -07:00
Mathias Agopian bd0ca7a9bd Merge branch 'main' into ma/descriptor-sets 2024-08-08 22:48:25 -07:00
Mathias Agopian fa0e20a699 always validate Engine::config 2024-08-08 22:46:57 -07:00
Mathias Agopian 8797f45bd0 Merge branch 'main' into ma/descriptor-sets 2024-08-08 13:27:20 -07:00
Mathias Agopian 59593830e5 Fix skipFrame incorrectly asserts in some cases
BUGS=[357992376]
2024-08-08 13:24:38 -07:00
Mathias Agopian 112f9d742c assert when a program compilation fails
The current behavior is to get a UB, which usually is a crash. So this
is better.
2024-08-08 13:24:08 -07:00
Ben Doherty 8ba20eb03c Metal: fix static texture target on more devices (#8022) 2024-08-08 13:04:42 -07:00
Ben Doherty 1c0370d5d7 Remove flaky assertion in ShadowMap (#8023) 2024-08-08 13:04:30 -07:00
Benjamin Doherty 56f714633d Release Filament 1.53.4 2024-08-08 12:12:27 -07:00
Benjamin Doherty f0bc338c80 Revert: inject the missing packing/unpacking function in ESSL 3.0 2024-08-07 16:18:02 -07:00
Benjamin Doherty 9750ddb9db Add type_traits header 2024-08-05 10:59:46 -07:00
Mathias Agopian ec2ee9db7a minor cleanups in CommanmdBufferQueue
- use the same code on both ends of updating the free space. i.e.
  both side compute the "used" space in exactly the same way.
  the math was the same before, but the code was different which
  could be confusing.

- assert for overflow before queuing the buffer. It wouldn't matter
  anyways, because it's done with the condition lock held, so the
  consumer would never have a chance to deuque it. still, less 
  confusing.
2024-08-05 10:06:44 -07:00
Mathias Agopian 1ce1e335a0 gl backend: add a couple missing systrace events 2024-08-02 23:21:34 -07:00
Sungun Park 5fe356e446 Fix ResourceAllocator compile error on Windows (#8011)
This commit 730bc99025 introduced a new
dependency on ResourceAllocator because of the new field
`std::unique_ptr<ResourceAllocator> mResourceAllocator{};` in
details/Renderer.h

This requires cpp files including details/Renderer.h to include
ResourceAllocator.h as well.

This compile issue only happens on the Windows compiler, Visual Studio.
2024-08-02 14:54:02 -07:00
Mathias Agopian f04a25028e Merge branch 'main' into ma/descriptor-sets 2024-08-01 22:19:16 -07:00
Eliza Velasquez 14ddadd2f6 Fix ES2/anisotropy bug in updateSamplerGroup
This function attempts to set texture parameters in these two cases, but the
texture is not guaranteed to be bound. Perhaps it once was, but the assumption
broke at some point.
2024-08-01 16:44:52 -07:00
Mathias Agopian a2f5b635d1 don't mix seq_cst with other memory orders
this change shouldn't have any impact on ARM, however, according
to cppreference it's not safe to mix seq_cst with other memory
orders:

"as soon as atomic operations that are not tagged memory_order_seq_cst 
enter the picture, the sequential consistency guarantee for the program 
is lost"
2024-07-31 10:11:01 -07:00
Mathias Agopian 324dcd3c86 early exit in fog to improve performance 2024-07-31 10:10:48 -07:00
Sungun Park 4125802644 Release Filament 1.53.3 2024-07-31 16:22:54 +00:00
Powei Feng bb1f58e97c Fix broken merge for vk (#8007) 2024-07-31 10:40:09 +08:00
Balaji M 2d21fcbe55 [gltfio] Initialize mCgltfBuffersLoaded to fix crash (#7999)
Value of mCgltfBuffersLoaded is sometimes retained across creation of FAssetLoader which skips loading the buffer in AssetLoaderExtended#createPrimitive leading to null pointer crash
2024-07-31 02:12:04 +00:00
Powei Feng 189e36d539 Merge branch 'main' into ma/descriptor-sets 2024-07-31 10:11:51 +08:00
Sungun Park 3f4dac6248 Fix a crash for IBL resource loading (#8001)
This fixes a crash introduced by a8ace2891d

The refactored FrameInfoManager can cause a crash when IBL resource loading
happens because now the getLastFrameInfo() references an invalid value via the
`front` method. Return the default FrameInfo to resolve this.

Also fix a null pointer reference bug for OpenGLTimer::State, which
happenes when the renderer for IBLPrefilterContext is destroyed.
2024-07-29 22:25:36 +00:00
Ben Doherty 40ae416715 Metal: redistribute pool sizes (#8000) 2024-07-29 13:14:50 -06:00
Grant CommodoreandPowei Feng b54a2046cb Multiview support for vulkan (#7892)
Adds multiview support for vulkan. This is done by adding a layerCount to the renderTarget, which is used to determine if multiview is available and being used in the current renderpass.

FIXES=[332425392]

Co-authored-by: Powei Feng <powei@google.com>
2024-07-26 07:41:00 +00:00
Mathias Agopian 8add6ae1ac fix a potential crash is AssetLoader
if it can't find a name for a node, it will revert to the config's
defaultNodeName, however, if that is nullptr also, a crash will occur.
so we provide a last-resort hardcoded name in that case.
2024-07-25 22:44:34 -07:00
Mathias Agopian cba819be8f Merge branch 'main' into ma/descriptor-sets 2024-07-25 17:25:08 -07:00
Mathias Agopian e7c96cd124 better handle skipped frames and cache eviction
Add Renderer::skipFrame() which should be called when intentionally 
skipping frames, for instance because the screen content hasn't changed,
allowing Filament to performance needed periodic tasks, such as
cache garbage collection and callback dispatches.

We also improve the ResourceAllocator cache eviction policy:
- the cache is aggressively purged when skipping a frame
- we aggressively evict entries older than 3 frames

The default Config is now set to a more agressive setting.
2024-07-25 17:23:04 -07:00
Mathias Agopian 730bc99025 Move the ResourceAllocator from Engine to Renderer
The ResourceAllocator used to be global and owned by the Engine, this
was causing some issues when using several Renderers because each
one could cause the eviction of cache data for another.

We now have a ResourceAllocator per Renderer, which makes more sense
because most resources are allocated by the FrameGraph.

We also introduce a ResourceAllocatorDisposer class, which is used
for checking in and out a texture from the cache, and destroy the
texture when it's checked-out. That objet is still global.
2024-07-25 17:23:04 -07:00
Powei Feng 7441e878bb gltfio: enable escaped unicode for node name (#7989)
Fixes #7846
2024-07-25 09:06:56 +00:00
Grant CommodoreandPowei Feng ef4c4a76fc Only sets the layercount if the view has stereo and the stereoscopic type is multiview (#7987)
Co-authored-by: Powei Feng <powei@google.com>
2024-07-25 07:31:51 +00:00
Mathias Agopian 41e1e7e54b Merge branch 'main' into ma/descriptor-sets 2024-07-24 15:38:12 -07:00
Mathias Agopian be4f287b07 More improvements to the JobSystem (#7988)
* improve parallel_for a bit

We get about 40% performance increase. The gain comes from not having
to copy the JobData structure each time we create a job, by using a
new emplaceJob() method, we can create the structure directly into
its destination.

* avoid calling wakeAll() when possible

wakeAll() is very expensive and not always needed when a job finishes
because there may not be anyone waiting on that job.

We now maintain a waiter count per job, and use that to determine if
we need to notify or not.

And now that the JobSystem overhead is lower, we can decrease the size
of the jobs, which improves the load balancing.

* mActiveJobs fixes

some comments claimed mActiveJobs needed to be modified before or after
accessing the WorkQueue; this couldn't be correct because there were no
guaranteed global ordering with the workQueue.
2024-07-24 15:34:36 -07:00
Mathias Agopian d3a35de386 fix a crash with bloom when screen dimension smaller than 16px 2024-07-24 11:49:38 -07:00
Ben Doherty d8580d93ad New external image API (#7981) 2024-07-23 13:06:48 -06:00
Sungun Park 9057c47a56 Release Filament 1.53.2 2024-07-23 01:23:57 +00:00
Mathias Agopian 4a5081388d RenderPass batch size was too small
The batch size was calculated incorrectly, which in theory could lead
to command buffer overflows (albeit unlikely).
2024-07-22 13:53:05 -07:00
Mathias Agopian a60fe41681 performance improvements to JobSystem
- reduce the number of calls to notify_one() and notify_all().
  notify_one() is not only called when running a new job, and
  notify_all() only when a job finishes.

- don't hold the condition lock while calling notify_*(), as it is not
  strictly needed, and because notify_*() can be very slow, there can
  be a lot of contention on this lock as a result; blocking the whole
  jobsystem thread pool.

- add a new version of run() that takes an opaque thread id that can
  be retrieved from a job's execute function; this is especially
  intended to be used by parallel_for(); it's just a more efficient
  version of run() that avoids a hashmap lookup.


Overall these change yield a significant performance boost:
- running + waiting a job: +200%
- running many jobs: +150%
- running many jobs in parallel: +50%
2024-07-22 11:12:51 -07:00
Mathias Agopian fef40a8c5d Merge branch 'main' into ma/descriptor-sets 2024-07-19 10:09:28 -07:00
Mathias Agopian a8ace2891d improve frame timing info
- we use a circular buffer for the frame history so that 
  we don't have to copy the data when insert a new entry.
  This also allows us to keep a reference to an entry, which
  doesn't get invalidated when an entry is added/removed.

- we now store the gpu frame time in the correct slot (instead of
  always the latest). It didn't matter before because the API wasn't
  public and we only needed some recent frame time.

- a new public API now returns the frame history, which now contains 
  more data; in particular the main and backend thread's begin/end
  frame time.


BUGS=[321110544]
2024-07-18 21:14:44 -07:00
mdagoisandPowei Feng 669ffc85c0 Minor typo fix and slight optimization (#7978)
Co-authored-by: Powei Feng <powei@google.com>
2024-07-19 03:07:54 +00:00
mdagoisandPowei Feng d957dd8082 Fix to barriers in VulkanBuffer (#7979)
Co-authored-by: Powei Feng <powei@google.com>
2024-07-19 10:45:10 +08:00
Ben Doherty 8eda62c957 Update imgui to v1.90.9 (#7977) 2024-07-18 16:38:25 -07:00
Sungun Park 90f079ac2c Add an extra check for renderStandaloneView (#7975)
renderStandaloneView must be called outside of beginFrame() /
endFrame(). This extra check ensures this behavior.
2024-07-18 06:31:02 -07:00
Balaji M 3728f06603 utility::loadCgltfBuffers is done once instead of doing for each primitive (#7969) 2024-07-12 23:22:25 +00:00
Adam Wychowaniec 39cfce641c Fix precondition check when creating static geometry. 2024-07-08 11:53:24 -07:00
Mathias Agopian 3fbf3f7111 fix a couple small bugs when mapping the circular buffer
- in case of failure we were munmap'ing the wrong size for the
  guard page (in practice this never happened)
- the post-condition check was incorrect; it checked for nullptr instead
  of MAP_FAILED. this also never happened in practice.

Also made a couple of small improvements:

- in case the special circular buffer mapping fails, log a message
  as warning instead of debug.

- immediately memset (i.e. populate) the pages for the circular buffer
  since they will all be accessed rather quickly.
2024-07-08 10:48:44 -07:00
Reinar ef9bbece2a Fix feature level detection for VulkanDriver 2024-07-02 15:09:25 -07:00
Mathias Agopian eb8fa9ecdf shader compilation could fail on ES 3.0 devices
the failure could happen if the shader didn't have any #extension
strings, which was likely to happen on release builds (e.g. on 
emulator).
2024-07-01 11:25:12 -07:00
Mathias Agopian d299b1f00e Merge branch 'main' into ma/descriptor-sets 2024-06-28 13:20:28 -07:00
Ben Doherty e1bfe7ca81 Metal: add a more detailed CHECK_POSTCONDITION when creating a texture (#7943) 2024-06-28 13:11:00 -07:00
Ben Doherty ca4c7ac739 Update cgltf to 1.14 (#7945) 2024-06-28 13:10:41 -07:00
Ben Doherty 9676bc94e8 Fix MetalBumpAllocator (#7951) 2024-06-28 13:09:01 -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 f7a5111106 switch to new morphing API
- remove deprecated morphing APIs
- repair gltfio, samples and tests

The new API doesn't allow a MorphTargetBuffer per RenderPrimitive,
instead the MorphTargetBuffer is specified per Renderable.

gltfio separates RenderPrimitives from Renderables, in particular all
RenderPrimitives are created before their Renderable; this was
problematic for this change because all primitives must share
a single MorphTargetBuffer living in the Renderable.

To fix this, we're no longer initializing the morphing paramters
at RenderPrimitive creation, instead we store a reference to the
BufferSlot in the Primtive structure, so that later, when the Renderable
is created we can finally retrieve the BufferSlot and initialize its
morphing paramters, which are not available. The "morphing parameters"
are now expanded to contain the MorphTargetBuffer as before (except now
it's always the same for all the primitives of a Rendrable), as well
as the offset within the buffer and the vertex count.
2024-06-28 12:14:57 -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
Sungun Park 56ac7ab902 IBL drag & drop support for desktop gltf_viewer (#7947)
Users now can drag and drop IBL files for desktop gltf_viewer.

It attempts to load gltf files first then tries to load IBL files.
2024-06-27 23:14:13 +00: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
Ben Doherty 44b4de904e Add saveRawVariants flag to matc (#7949) 2024-06-27 14:36:03 -07:00
Benjamin Doherty 07f07e0700 Remove log spew 2024-06-27 14:35:20 -07:00
Tibor Hencz a2fe9f745c Add ability to disable panning in the OrbitManipulator (#7928) 2024-06-27 00:05:14 +00: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
966 changed files with 52989 additions and 58994 deletions
@@ -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
+25 -22
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
+3 -1
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
+1 -1
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
+2
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.
+2 -12
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.53.1'
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.53.1'
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
+27
View File
@@ -7,6 +7,33 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.54.3
## v1.54.2
- Add a `name` API to Filament objects for debugging handle use-after-free assertions
## v1.54.1
## v1.54.0
- materials: add a new `stereoscopicType` material parameter. [⚠️ **New Material Version**]
- Fix a crash when compiling shaders on IMG devices
## v1.53.5
- engine: Fix bug causing certain sampler parameters to not be applied correctly in GLES 2.0 and on
certain GLES 3.0 drivers.
## v1.53.4
## v1.53.3
- Add drag and drop support for IBL files for desktop gltf_viewer.
## v1.53.2
@@ -253,16 +253,10 @@ Java_com_google_android_filament_RenderableManager_nBuilderMorphingStandard(JNIE
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_RenderableManager_nBuilderSetMorphTargetBufferAt(JNIEnv*, jclass,
jlong nativeBuilder, int level, int primitiveIndex, jlong nativeMorphTargetBuffer,
int offset, int count) {
Java_com_google_android_filament_RenderableManager_nBuilderSetMorphTargetBufferOffsetAt(JNIEnv*, jclass,
jlong nativeBuilder, int level, int primitiveIndex, int offset) {
RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder;
if (nativeMorphTargetBuffer) {
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
builder->morphing(level, primitiveIndex, morphTargetBuffer, offset, count);
} else {
builder->morphing(level, primitiveIndex, offset, count);
}
builder->morphing(level, primitiveIndex, offset);
}
extern "C" JNIEXPORT void JNICALL
@@ -334,18 +328,12 @@ Java_com_google_android_filament_RenderableManager_nSetMorphWeights(JNIEnv* env,
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferAt(JNIEnv*,
Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferOffsetAt(JNIEnv*,
jclass, jlong nativeRenderableManager, jint i, int level, jint primitiveIndex,
jlong nativeMorphTargetBuffer, jint offset, jint count) {
jlong, jint offset) {
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
if (nativeMorphTargetBuffer) {
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level,
(size_t) primitiveIndex, morphTargetBuffer, (size_t) offset, (size_t) count);
} else {
rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level,
(size_t) primitiveIndex, (size_t) offset, (size_t) count);
}
rm->setMorphTargetBufferOffsetAt((RenderableManager::Instance) i, (uint8_t) level,
(size_t) primitiveIndex, (size_t) offset);
}
extern "C" JNIEXPORT jint JNICALL
@@ -28,6 +28,14 @@
using namespace filament;
using namespace backend;
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Renderer_nSkipFrame(JNIEnv *, jclass, jlong nativeRenderer,
jlong vsyncSteadyClockTimeNano) {
Renderer *renderer = (Renderer *) nativeRenderer;
renderer->skipFrame(uint64_t(vsyncSteadyClockTimeNano));
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Renderer_nBeginFrame(JNIEnv *, jclass, jlong nativeRenderer,
jlong nativeSwapChain, jlong frameTimeNanos) {
@@ -531,3 +531,12 @@ Java_com_google_android_filament_View_nGetFogEntity(JNIEnv *env, jclass clazz,
View *view = (View *) nativeView;
return (jint)view->getFogEntity().getId();
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nClearFrameHistory(JNIEnv *env, jclass clazz,
jlong nativeView, jlong nativeEngine) {
View *view = (View *) nativeView;
Engine *engine = (Engine *) nativeEngine;
view->clearFrameHistory(*engine);
}
@@ -425,9 +425,12 @@ public class Engine {
public long resourceAllocatorCacheSizeMB = 64;
/*
* This value determines for how many frames are texture entries kept in the cache.
* This value determines how many frames texture entries are kept for in the cache. This
* is a soft limit, meaning some texture older than this are allowed to stay in the cache.
* Typically only one texture is evicted per frame.
* The default is 1.
*/
public long resourceAllocatorCacheMaxAge = 2;
public long resourceAllocatorCacheMaxAge = 1;
/*
* Disable backend handles use-after-free checks.
@@ -74,7 +74,7 @@ public class MorphTargetBuffer {
*
* @exception IllegalStateException if the MorphTargetBuffer could not be created
*
* @see #setMorphTargetBufferAt
* @see #setMorphTargetBufferOffsetAt
*/
@NonNull
public MorphTargetBuffer build(@NonNull Engine engine) {
@@ -569,40 +569,13 @@ public class RenderableManager {
*
* @param level the level of detail (lod), only 0 can be specified
* @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor
* @param morphTargetBuffer specifies the morph target buffer
* @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices)
* @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3)
*/
@NonNull
public Builder morphing(@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@IntRange(from = 0) int offset,
@IntRange(from = 0) int count) {
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex, 0, offset, count);
return this;
}
/** @deprecated */
@Deprecated
@NonNull
public Builder morphing(@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@NonNull MorphTargetBuffer morphTargetBuffer,
@IntRange(from = 0) int offset,
@IntRange(from = 0) int count) {
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex,
morphTargetBuffer.getNativeObject(), offset, count);
return this;
}
/** @deprecated */
@Deprecated
@NonNull
public Builder morphing(@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@NonNull MorphTargetBuffer morphTargetBuffer) {
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex,
morphTargetBuffer.getNativeObject(), 0, morphTargetBuffer.getVertexCount());
@IntRange(from = 0) int offset) {
nBuilderSetMorphTargetBufferOffsetAt(mNativeBuilder, level, primitiveIndex, offset);
return this;
}
@@ -705,34 +678,11 @@ public class RenderableManager {
*
* @see Builder#morphing
*/
public void setMorphTargetBufferAt(@EntityInstance int i,
public void setMorphTargetBufferOffsetAt(@EntityInstance int i,
@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@IntRange(from = 0) int offset,
@IntRange(from = 0) int count) {
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex, 0, offset, count);
}
/** @deprecated */
@Deprecated
public void setMorphTargetBufferAt(@EntityInstance int i,
@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@NonNull MorphTargetBuffer morphTargetBuffer,
@IntRange(from = 0) int offset,
@IntRange(from = 0) int count) {
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex,
morphTargetBuffer.getNativeObject(), offset, count);
}
/** @deprecated */
@Deprecated
public void setMorphTargetBufferAt(@EntityInstance int i,
@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@NonNull MorphTargetBuffer morphTargetBuffer) {
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex,
morphTargetBuffer.getNativeObject(), 0, morphTargetBuffer.getVertexCount());
@IntRange(from = 0) int offset) {
nSetMorphTargetBufferOffsetAt(mNativeObject, i, level, primitiveIndex, 0, offset);
}
/**
@@ -1033,7 +983,7 @@ public class RenderableManager {
private static native void nBuilderSkinningBuffer(long nativeBuilder, long nativeSkinningBuffer, int boneCount, int offset);
private static native void nBuilderMorphing(long nativeBuilder, int targetCount);
private static native void nBuilderMorphingStandard(long nativeBuilder, long nativeMorphTargetBuffer);
private static native void nBuilderSetMorphTargetBufferAt(long nativeBuilder, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count);
private static native void nBuilderSetMorphTargetBufferOffsetAt(long nativeBuilder, int level, int primitiveIndex, int offset);
private static native void nBuilderEnableSkinningBuffers(long nativeBuilder, boolean enabled);
private static native void nBuilderFog(long nativeBuilder, boolean enabled);
private static native void nBuilderLightChannel(long nativeRenderableManager, int channel, boolean enable);
@@ -1043,7 +993,7 @@ public class RenderableManager {
private static native int nSetBonesAsMatrices(long nativeObject, int i, Buffer matrices, int remaining, int boneCount, int offset);
private static native int nSetBonesAsQuaternions(long nativeObject, int i, Buffer quaternions, int remaining, int boneCount, int offset);
private static native void nSetMorphWeights(long nativeObject, int instance, float[] weights, int offset);
private static native void nSetMorphTargetBufferAt(long nativeObject, int i, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count);
private static native void nSetMorphTargetBufferOffsetAt(long nativeObject, int i, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset);
private static native int nGetMorphTargetCount(long nativeObject, int i);
private static native void nSetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float cx, float cy, float cz, float ex, float ey, float ez);
private static native void nSetLayerMask(long nativeRenderableManager, int i, int select, int value);
@@ -297,6 +297,20 @@ public class Renderer {
nSetVsyncTime(getNativeObject(), steadyClockTimeNano);
}
/**
* Call skipFrame when momentarily skipping frames, for instance if the content of the
* scene doesn't change.
*
* @param vsyncSteadyClockTimeNano The time in nanoseconds when the frame started being rendered,
* in the {@link System#nanoTime()} timebase. Divide this value by 1000000 to
* convert it to the {@link android.os.SystemClock#uptimeMillis()}
* time base. This typically comes from
* {@link android.view.Choreographer.FrameCallback}.
*/
public void skipFrame(long vsyncSteadyClockTimeNano) {
nSkipFrame(getNativeObject(), vsyncSteadyClockTimeNano);
}
/**
* Sets up a frame for this <code>Renderer</code>.
* <p><code>beginFrame</code> manages frame pacing, and returns whether or not a frame should be
@@ -716,6 +730,7 @@ public class Renderer {
private static native void nSetPresentationTime(long nativeObject, long monotonicClockNanos);
private static native void nSetVsyncTime(long nativeObject, long steadyClockTimeNano);
private static native void nSkipFrame(long nativeObject, long vsyncSteadyClockTimeNano);
private static native boolean nBeginFrame(long nativeRenderer, long nativeSwapChain, long frameTimeNanos);
private static native void nEndFrame(long nativeRenderer);
private static native void nRender(long nativeRenderer, long nativeView);
@@ -1233,6 +1233,18 @@ public class View {
return nGetFogEntity(getNativeObject());
}
/**
* When certain temporal features are used (e.g.: TAA or Screen-space reflections), the view
* keeps a history of previous frame renders associated with the Renderer the view was last
* used with. When switching Renderer, it may be necessary to clear that history by calling
* this method. Similarly, if the whole content of the screen change, like when a cut-scene
* starts, clearing the history might be needed to avoid artifacts due to the previous frame
* being very different.
*/
public void clearFrameHistory(Engine engine) {
nClearFrameHistory(getNativeObject(), engine.getNativeObject());
}
public long getNativeObject() {
if (mNativeObject == 0) {
throw new IllegalStateException("Calling method on destroyed View");
@@ -1294,7 +1306,7 @@ public class View {
private static native void nSetMaterialGlobal(long nativeView, int index, float x, float y, float z, float w);
private static native void nGetMaterialGlobal(long nativeView, int index, float[] out);
private static native int nGetFogEntity(long nativeView);
private static native void nClearFrameHistory(long nativeView, long nativeEngine);
/**
* List of available ambient occlusion techniques.
@@ -125,6 +125,11 @@ extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBu
builder->groundPlane(a, b, c, d);
}
extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderPanning(JNIEnv*, jclass, jlong nativeBuilder, jboolean enabled) {
Builder* builder = (Builder*) nativeBuilder;
builder->panning(enabled);
}
extern "C" JNIEXPORT long Java_com_google_android_filament_utils_Manipulator_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder, jint mode) {
Builder* builder = (Builder*) nativeBuilder;
return (jlong) builder->build((Mode) mode);
@@ -274,6 +274,17 @@ public class Manipulator {
return this;
}
/**
* Sets whether panning is enabled in the manipulator.
*
* @return this <code>Builder</code> object for chaining calls
*/
@NonNull
public Builder panning(Boolean enabled) {
nBuilderPanning(mNativeBuilder, enabled);
return this;
}
/**
* Creates and returns the <code>Manipulator</code> object.
*
@@ -483,6 +494,7 @@ public class Manipulator {
private static native void nBuilderFlightPanSpeed(long nativeBuilder, float x, float y);
private static native void nBuilderFlightMoveDamping(long nativeBuilder, float damping);
private static native void nBuilderGroundPlane(long nativeBuilder, float a, float b, float c, float d);
private static native void nBuilderPanning(long nativeBuilder, Boolean enabled);
private static native long nBuilderBuild(long nativeBuilder, int mode);
private static native void nDestroyManipulator(long nativeManip);
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.53.1
VERSION_NAME=1.54.2
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+15 -7
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
+11 -2
View File
@@ -1308,7 +1308,12 @@ Description
declare a variable called `eyeDirection` you can access it in the fragment shader using
`variable_eyeDirection`. In the vertex shader, the interpolant name is simply a member of
the `MaterialVertexInputs` structure (`material.eyeDirection` in your example). Each
interpolant is of type `float4` (`vec4`) in the shaders.
interpolant is of type `float4` (`vec4`) in the shaders. By default the precision of the
interpolant is `highp` in *both* the vertex and fragment shaders.
An alternate syntax can be used to specify both the name and precision of the interpolant.
In this case the specified precision is used as-is in both fragment and vertex stages, in
particular if `default` is specified the default precision is used is the fragment shader
(`mediump`) and in the vertex shader (`highp`).
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
@@ -1320,7 +1325,11 @@ material {
}
],
variables : [
eyeDirection
eyeDirection,
{
name : eyeColor,
precision : medium
}
],
vertexDomain : device,
depthWrite : false,
+16 -7
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
+30 -8
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()
@@ -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
+106 -38
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);
}
@@ -1058,7 +1150,7 @@ struct RasterState {
bool inverseFrontFaces : 1; // 31
//! padding, must be 0
uint8_t padding : 1; // 32
bool depthClamp : 1; // 32
};
uint32_t u = 0;
};
@@ -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.
@@ -1244,7 +1310,7 @@ enum class Workaround : uint16_t {
ADRENO_UNIFORM_ARRAY_CRASH,
// Workaround a Metal pipeline compilation error with the message:
// "Could not statically determine the target of a texture". See light_indirect.fs
A8X_STATIC_TEXTURE_TARGET_ERROR,
METAL_STATIC_TEXTURE_TARGET_ERROR,
// Adreno drivers sometimes aren't able to blit into a layer of a texture array.
DISABLE_BLIT_INTO_TEXTURE_ARRAY,
// Multiple workarounds needed for PowerVR GPUs
@@ -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>
+17 -13
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
@@ -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
+62 -68
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.
@@ -18,6 +18,7 @@
#define TNT_FILAMENT_BACKEND_PRIVATE_DRIVER_H
#include <backend/CallbackHandler.h>
#include <backend/DescriptorSetOffsetArray.h>
#include <backend/DriverApiForward.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
@@ -162,6 +162,10 @@ DECL_DRIVER_API_0(finish)
// reset state tracking, if the driver does any state tracking (e.g. GL)
DECL_DRIVER_API_0(resetState)
DECL_DRIVER_API_N(setDebugTag,
backend::HandleBase::HandleId, handleId,
utils::CString, tag)
/*
* Creating driver objects
* -----------------------
@@ -196,20 +200,33 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, createTexture,
uint32_t, depth,
backend::TextureUsage, usage)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureSwizzled,
backend::SamplerType, target,
uint8_t, levels,
backend::TextureFormat, format,
uint8_t, samples,
uint32_t, width,
uint32_t, height,
uint32_t, depth,
backend::TextureUsage, usage,
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureView,
backend::TextureHandle, texture,
uint8_t, baseLevel,
uint8_t, levelCount)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureViewSwizzle,
backend::TextureHandle, texture,
backend::TextureSwizzle, r,
backend::TextureSwizzle, g,
backend::TextureSwizzle, b,
backend::TextureSwizzle, a)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureExternalImage,
backend::TextureFormat, format,
uint32_t, width,
uint32_t, height,
backend::TextureUsage, usage,
void*, image)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureExternalImagePlane,
backend::TextureFormat, format,
uint32_t, width,
uint32_t, height,
backend::TextureUsage, usage,
void*, image,
uint32_t, plane)
DECL_DRIVER_API_R_N(backend::TextureHandle, importTexture,
intptr_t, id,
backend::SamplerType, target,
@@ -221,9 +238,6 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, importTexture,
uint32_t, depth,
backend::TextureUsage, usage)
DECL_DRIVER_API_R_N(backend::SamplerGroupHandle, createSamplerGroup,
uint32_t, size, utils::FixedSizeString<32>, debugName)
DECL_DRIVER_API_R_N(backend::RenderPrimitiveHandle, createRenderPrimitive,
backend::VertexBufferHandle, vbh,
backend::IndexBufferHandle, ibh,
@@ -257,25 +271,53 @@ DECL_DRIVER_API_R_N(backend::SwapChainHandle, createSwapChainHeadless,
DECL_DRIVER_API_R_0(backend::TimerQueryHandle, createTimerQuery)
DECL_DRIVER_API_R_N(backend::DescriptorSetLayoutHandle, createDescriptorSetLayout,
backend::DescriptorSetLayout&&, info)
DECL_DRIVER_API_R_N(backend::DescriptorSetHandle, createDescriptorSet,
backend::DescriptorSetLayoutHandle, dslh)
DECL_DRIVER_API_N(updateDescriptorSetBuffer,
backend::DescriptorSetHandle, dsh,
backend::descriptor_binding_t, binding,
backend::BufferObjectHandle, boh,
uint32_t, offset,
uint32_t, size
)
DECL_DRIVER_API_N(updateDescriptorSetTexture,
backend::DescriptorSetHandle, dsh,
backend::descriptor_binding_t, binding,
backend::TextureHandle, th,
SamplerParams, params
)
DECL_DRIVER_API_N(bindDescriptorSet,
backend::DescriptorSetHandle, dsh,
backend::descriptor_set_t, set,
backend::DescriptorSetOffsetArray&&, offsets
)
/*
* Destroying driver objects
* -------------------------
*/
DECL_DRIVER_API_N(destroyVertexBuffer, backend::VertexBufferHandle, vbh)
DECL_DRIVER_API_N(destroyVertexBufferInfo,backend::VertexBufferInfoHandle, vbih)
DECL_DRIVER_API_N(destroyIndexBuffer, backend::IndexBufferHandle, ibh)
DECL_DRIVER_API_N(destroyBufferObject, backend::BufferObjectHandle, ibh)
DECL_DRIVER_API_N(destroyRenderPrimitive, backend::RenderPrimitiveHandle, rph)
DECL_DRIVER_API_N(destroyProgram, backend::ProgramHandle, ph)
DECL_DRIVER_API_N(destroySamplerGroup, backend::SamplerGroupHandle, sbh)
DECL_DRIVER_API_N(destroyTexture, backend::TextureHandle, th)
DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth)
DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch)
DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh)
DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh)
DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh)
DECL_DRIVER_API_N(destroyVertexBuffer, backend::VertexBufferHandle, vbh)
DECL_DRIVER_API_N(destroyVertexBufferInfo, backend::VertexBufferInfoHandle, vbih)
DECL_DRIVER_API_N(destroyIndexBuffer, backend::IndexBufferHandle, ibh)
DECL_DRIVER_API_N(destroyBufferObject, backend::BufferObjectHandle, ibh)
DECL_DRIVER_API_N(destroyRenderPrimitive, backend::RenderPrimitiveHandle, rph)
DECL_DRIVER_API_N(destroyProgram, backend::ProgramHandle, ph)
DECL_DRIVER_API_N(destroyTexture, backend::TextureHandle, th)
DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth)
DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch)
DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh)
DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh)
DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh)
DECL_DRIVER_API_N(destroyDescriptorSetLayout, backend::DescriptorSetLayoutHandle, dslh)
DECL_DRIVER_API_N(destroyDescriptorSet, backend::DescriptorSetHandle, dsh)
/*
* Synchronous APIs
@@ -305,6 +347,7 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isDepthStencilBlitSupported, backend::TextureFormat, format)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedTexturesSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthClampSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
@@ -341,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,
@@ -364,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,
@@ -414,37 +450,16 @@ DECL_DRIVER_API_N(commit,
* -----------------------
*/
DECL_DRIVER_API_N(bindUniformBuffer,
uint32_t, index,
backend::BufferObjectHandle, ubh)
DECL_DRIVER_API_N(bindBufferRange,
BufferObjectBinding, bindingType,
uint32_t, index,
backend::BufferObjectHandle, ubh,
uint32_t, offset,
uint32_t, size)
DECL_DRIVER_API_N(unbindBuffer,
BufferObjectBinding, bindingType,
uint32_t, index)
DECL_DRIVER_API_N(bindSamplers,
uint32_t, index,
backend::SamplerGroupHandle, sbh)
DECL_DRIVER_API_N(setPushConstant,
backend::ShaderStage, stage,
uint8_t, index,
backend::PushConstantVariant, value)
DECL_DRIVER_API_N(insertEventMarker,
const char*, string,
uint32_t, len = 0)
const char*, string)
DECL_DRIVER_API_N(pushGroupMarker,
const char*, string,
uint32_t, len = 0)
const char*, string)
DECL_DRIVER_API_0(popGroupMarker)
@@ -18,6 +18,17 @@
#define TNT_FILAMENT_BACKEND_PRIVATE_DRIVERAPI_H
#include "backend/DriverApiForward.h"
#include "private/backend/CommandStream.h"
#include <stddef.h>
namespace filament::backend {
inline void* allocateFromCommandStream(DriverApi& driver, size_t size, size_t alignment) noexcept {
return driver.allocate(size, alignment);
}
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_PRIVATE_DRIVERAPI_H
@@ -20,11 +20,12 @@
#include <backend/Handle.h>
#include <utils/Allocator.h>
#include <utils/CString.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <utils/Panic.h>
#include <tsl/robin_map.h>
@@ -37,9 +38,9 @@
#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, 48, 552> // ~1660 / pool / MiB
#define HandleAllocatorMTL HandleAllocator<32, 64, 552> // ~1660 / pool / MiB
namespace filament::backend {
@@ -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;
};
+10 -7
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
@@ -32,10 +32,11 @@
# define HAS_MMAP 0
#endif
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace utils;
@@ -81,6 +82,9 @@ void* CircularBuffer::alloc(size_t size) noexcept {
// map the circular buffer once...
vaddr = mmap(reserve_vaddr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if (vaddr != MAP_FAILED) {
// populate the address space with pages (because this is a circular buffer,
// all the pages will be allocated eventually, might as well do it now)
memset(vaddr, 0, size);
// and map the circular buffer again, behind the previous copy...
vaddr_shadow = mmap((char*)vaddr + size, size,
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
@@ -101,7 +105,7 @@ void* CircularBuffer::alloc(size_t size) noexcept {
if (UTILS_UNLIKELY(mAshmemFd < 0)) {
// ashmem failed
if (vaddr_guard != MAP_FAILED) {
munmap(vaddr_guard, size);
munmap(vaddr_guard, BLOCK_SIZE);
}
if (vaddr_shadow != MAP_FAILED) {
@@ -119,12 +123,11 @@ void* CircularBuffer::alloc(size_t size) noexcept {
data = mmap(nullptr, size * 2 + BLOCK_SIZE,
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
FILAMENT_CHECK_POSTCONDITION(data) <<
FILAMENT_CHECK_POSTCONDITION(data != MAP_FAILED) <<
"couldn't allocate " << (size * 2 / 1024) <<
" KiB of virtual address space for the command buffer";
slog.d << "WARNING: Using soft CircularBuffer (" << (size * 2 / 1024) << " KiB)"
<< io::endl;
slog.w << "Using 'soft' CircularBuffer (" << (size * 2 / 1024) << " KiB)" << io::endl;
// guard page at the end
void* guard = (void*)(uintptr_t(data) + size * 2);
+8 -5
View File
@@ -101,9 +101,8 @@ void CommandBufferQueue::flush() noexcept {
size_t const used = std::distance(
static_cast<char const*>(begin), static_cast<char const*>(end));
std::unique_lock<utils::Mutex> lock(mLock);
mCommandBuffersToExecute.push_back({ begin, end });
mCondition.notify_one();
// circular buffer is too small, we corrupted the stream
FILAMENT_CHECK_POSTCONDITION(used <= mFreeSpace) <<
@@ -112,10 +111,12 @@ void CommandBufferQueue::flush() noexcept {
"Space used at this time: " << used <<
" bytes, overflow: " << used - mFreeSpace << " bytes";
// wait until there is enough space in the buffer
mFreeSpace -= used;
if (UTILS_UNLIKELY(mFreeSpace < requiredSize)) {
mCommandBuffersToExecute.push_back({ begin, end });
mCondition.notify_one();
// wait until there is enough space in the buffer
if (UTILS_UNLIKELY(mFreeSpace < requiredSize)) {
#ifndef NDEBUG
size_t const totalUsed = circularBuffer.size() - mFreeSpace;
@@ -153,8 +154,10 @@ std::vector<CommandBufferQueue::Range> CommandBufferQueue::waitForCommands() con
}
void CommandBufferQueue::releaseBuffer(CommandBufferQueue::Range const& buffer) {
size_t const used = std::distance(
static_cast<char const*>(buffer.begin), static_cast<char const*>(buffer.end));
std::lock_guard<utils::Mutex> const lock(mLock);
mFreeSpace += uintptr_t(buffer.end) - uintptr_t(buffer.begin);
mFreeSpace += used;
mCondition.notify_one();
}
+8 -2
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);
+8
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;
};
+3
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>
+20 -26
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;
}
+3 -1
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; }
+7 -4
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;
}
@@ -145,6 +145,7 @@ std::pair<id<MTLBuffer>, size_t> MetalBumpAllocator::allocateStagingArea(size_t
// We're finished with the current allocation.
mCurrentUploadBuffer = { [mDevice newBufferWithLength:mCapacity options:MTLStorageModeShared],
TrackedMetalBuffer::Type::STAGING };
mHead = size;
return { mCurrentUploadBuffer.get(), 0 };
}
+67 -12
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;
@@ -93,7 +146,7 @@ struct MetalContext {
} highestSupportedGpuFamily;
struct {
bool a8xStaticTextureTargetError;
bool staticTextureTargetError;
} bugs;
// sampleCountLookup[requestedSamples] gives a <= sample count supported by the device.
@@ -108,10 +161,9 @@ struct MetalContext {
// State trackers.
PipelineStateTracker pipelineState;
DepthStencilStateTracker depthStencilState;
std::array<BufferState, Program::UNIFORM_BINDING_COUNT> uniformState;
std::array<BufferState, MAX_SSBO_COUNT> ssboState;
CullModeStateTracker cullModeState;
WindingStateTracker windingState;
DepthClampStateTracker depthClampState;
Handle<HwRenderPrimitive> currentRenderPrimitive;
// State caches.
@@ -124,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
@@ -153,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;
+7 -2
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);
+16 -3
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
+41 -66
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
+105 -119
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
+78 -111
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
+238 -161
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)
@@ -556,8 +538,6 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
descriptor.usage = getMetalTextureUsage(usage);
descriptor.storageMode = MTLStorageModePrivate;
texture = [context.device newTextureWithDescriptor:descriptor];
FILAMENT_CHECK_POSTCONDITION(texture != nil)
<< "Could not create Metal texture. Out of memory?";
break;
case SamplerType::SAMPLER_CUBEMAP:
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
@@ -572,8 +552,6 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
descriptor.usage = getMetalTextureUsage(usage);
descriptor.storageMode = MTLStorageModePrivate;
texture = [context.device newTextureWithDescriptor:descriptor];
FILAMENT_CHECK_POSTCONDITION(texture != nil)
<< "Could not create Metal texture. Out of memory?";
break;
case SamplerType::SAMPLER_3D:
descriptor = [MTLTextureDescriptor new];
@@ -586,8 +564,6 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
descriptor.usage = getMetalTextureUsage(usage);
descriptor.storageMode = MTLStorageModePrivate;
texture = [context.device newTextureWithDescriptor:descriptor];
FILAMENT_CHECK_POSTCONDITION(texture != nil)
<< "Could not create Metal texture. Out of memory?";
break;
case SamplerType::SAMPLER_EXTERNAL:
// If we're using external textures (CVPixelBufferRefs), we don't need to make any
@@ -596,15 +572,33 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
break;
}
// 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) {
FILAMENT_CHECK_POSTCONDITION(target == SamplerType::SAMPLER_EXTERNAL || texture != nil)
<< "Could not create Metal texture (SamplerType = " << int(target)
<< ", levels = " << int(levels) << ", MTLPixelFormat = " << int(devicePixelFormat)
<< ", width = " << width << ", height = " << height << ", depth = " << depth
<< "). Out of memory?";
}
MetalTexture::MetalTexture(MetalContext& context, MetalTexture const* src, uint8_t baseLevel,
uint8_t levelCount) noexcept
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
context(context),
devicePixelFormat(src->devicePixelFormat),
externalImage(src->externalImage) {
texture = createTextureViewWithLodRange(
src->getMtlTextureForRead(), baseLevel, baseLevel + levelCount - 1);
}
MetalTexture::MetalTexture(MetalContext& context, MetalTexture const* src, TextureSwizzle r,
TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) noexcept
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
context(context),
devicePixelFormat(src->devicePixelFormat),
externalImage(src->externalImage) {
texture = src->getMtlTextureForRead();
if (context.supportsTextureSwizzling) {
// 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
+43 -18
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);
@@ -382,18 +379,22 @@ using SamplerStateCache = StateCache<SamplerState, id<MTLSamplerState>, SamplerS
using CullModeStateTracker = StateTracker<MTLCullMode>;
using WindingStateTracker = StateTracker<MTLWinding>;
using DepthClampStateTracker = StateTracker<MTLDepthClipMode>;
// Argument encoder
struct ArgumentEncoderState {
NSUInteger bufferCount;
utils::FixedCapacityVector<MTLTextureType> textureTypes;
explicit ArgumentEncoderState(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 {
@@ -415,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
+77 -6
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
+36 -26
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 {};
}
@@ -202,6 +205,10 @@ bool NoopDriver::isProtectedTexturesSupported() {
return true;
}
bool NoopDriver::isDepthClampSupported() {
return false;
}
bool NoopDriver::isWorkaroundNeeded(Workaround) {
return false;
}
@@ -244,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,
@@ -272,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) {
@@ -299,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) {
@@ -388,4 +374,28 @@ void NoopDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
void NoopDriver::resetState(int) {
}
void NoopDriver::updateDescriptorSetBuffer(
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
backend::BufferObjectHandle boh,
uint32_t offset,
uint32_t size) {
}
void NoopDriver::updateDescriptorSetTexture(
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
backend::TextureHandle th,
SamplerParams params) {
}
void NoopDriver::bindDescriptorSet(
backend::DescriptorSetHandle dsh,
backend::descriptor_set_t set,
backend::DescriptorSetOffsetArray&& offsets) {
}
void NoopDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
}
} // namespace filament
+89
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
@@ -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
@@ -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
@@ -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
+27 -1
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;
};
@@ -676,6 +676,7 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
#ifndef __EMSCRIPTEN__
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
#endif
ext->EXT_depth_clamp = exts.has("GL_EXT_depth_clamp"sv);
ext->EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
#ifndef __EMSCRIPTEN__
ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
@@ -746,6 +747,7 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor)
ext->EXT_color_buffer_half_float = true; // Assumes core profile.
ext->EXT_clip_cull_distance = true;
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
ext->EXT_depth_clamp = true;
ext->EXT_discard_framebuffer = false;
ext->EXT_disjoint_timer_query = true;
ext->EXT_multisampled_render_to_texture = false;
+19 -22
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;
@@ -220,8 +224,9 @@ public:
bool EXT_color_buffer_float;
bool EXT_color_buffer_half_float;
bool EXT_debug_marker;
bool EXT_disjoint_timer_query;
bool EXT_depth_clamp;
bool EXT_discard_framebuffer;
bool EXT_disjoint_timer_query;
bool EXT_multisampled_render_to_texture2;
bool EXT_multisampled_render_to_texture;
bool EXT_protected_textures;
@@ -239,10 +244,10 @@ public:
bool KHR_parallel_shader_compile;
bool KHR_texture_compression_astc_hdr;
bool KHR_texture_compression_astc_ldr;
bool OES_depth_texture;
bool OES_depth24;
bool OES_packed_depth_stencil;
bool OES_EGL_image_external_essl3;
bool OES_depth24;
bool OES_depth_texture;
bool OES_packed_depth_stencil;
bool OES_rgb8_rgba8;
bool OES_standard_derivatives;
bool OES_texture_npot;
@@ -473,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;
@@ -505,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;
@@ -627,6 +623,7 @@ constexpr size_t OpenGLContext::getIndexForCap(GLenum cap) noexcept { //NOLINT
#ifdef BACKEND_OPENGL_VERSION_GL
case GL_PROGRAM_POINT_SIZE: index = 10; break;
#endif
case GL_DEPTH_CLAMP: index = 11; break;
default: break;
}
assert_invariant(index < state.enables.caps.size());
File diff suppressed because it is too large Load Diff
+18 -20
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>
@@ -81,7 +85,7 @@ public:
const Platform::DriverConfig& driverConfig) noexcept;
class DebugMarker {
OpenGLDriver& driver;
UTILS_UNUSED OpenGLDriver& driver;
public:
DebugMarker(OpenGLDriver& driver, const char* string) noexcept;
~DebugMarker() noexcept;
@@ -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;
};
// ------------------------------------------------------------------------------------------------
+84 -107
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);
+33 -39
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
@@ -143,21 +143,25 @@ void TimerQueryNativeFactory::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQ
driver.runEveryNowAndThen([&context = mContext, weak]() -> bool {
auto state = weak.lock();
if (state) {
GLuint available = 0;
context.procs.getQueryObjectuiv(state->gl.query, GL_QUERY_RESULT_AVAILABLE, &available);
CHECK_GL_ERROR(utils::slog.e)
if (!available) {
// we need to try this one again later
return false;
}
GLuint64 elapsedTime = 0;
// we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query
context.procs.getQueryObjectui64v(state->gl.query, GL_QUERY_RESULT, &elapsedTime);
state->elapsed.store((int64_t)elapsedTime, std::memory_order_relaxed);
} else {
state->elapsed.store(int64_t(TimerQueryResult::ERROR), std::memory_order_relaxed);
if (!state) {
// The timer query state has been destroyed on the way, very likely due to the IBL
// prefilter context destruction. We still return true to get this element removed from
// the query list.
return true;
}
GLuint available = 0;
context.procs.getQueryObjectuiv(state->gl.query, GL_QUERY_RESULT_AVAILABLE, &available);
CHECK_GL_ERROR(utils::slog.e)
if (!available) {
// we need to try this one again later
return false;
}
GLuint64 elapsedTime = 0;
// we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query
context.procs.getQueryObjectui64v(state->gl.query, GL_QUERY_RESULT, &elapsedTime);
state->elapsed.store((int64_t)elapsedTime, std::memory_order_relaxed);
return true;
});
}
@@ -26,16 +26,28 @@
#include <utils/compiler.h>
#include <utils/CString.h>
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <utils/JobSystem.h>
#include <utils/Log.h>
#include <utils/ostream.h>
#include <utils/Panic.h>
#include <utils/Systrace.h>
#include <array>
#include <cctype>
#include <chrono>
#include <mutex>
#include <memory>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <variant>
#include <stddef.h>
#include <stdint.h>
namespace filament::backend {
using namespace utils;
@@ -476,6 +488,12 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
// check status of program linking and shader compilation, logs error and free all resources
// in case of error.
bool const success = checkProgramStatus(token);
// Unless we have matdbg, we panic if a program is invalid. Otherwise, we'd get a UB.
// The compilation error has been logged to log.e by this point.
FILAMENT_CHECK_POSTCONDITION(FILAMENT_ENABLE_MATDBG || success)
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
if (UTILS_LIKELY(success)) {
program = token->gl.program;
// no need to keep the shaders around
@@ -579,24 +597,31 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
version = "#version 310 es\n";
}
const std::array<const char*, 5> sources = {
version.data(),
prolog.data(),
specializationConstantString.c_str(),
packingFunctions.data(),
body.data()
std::array<std::string_view, 5> sources = {
version,
prolog,
specializationConstantString,
packingFunctions,
{ body.data(), body.size() - 1 } // null-terminated
};
const std::array<GLint, 5> lengths = {
(GLint)version.length(),
(GLint)prolog.length(),
(GLint)specializationConstantString.length(),
(GLint)packingFunctions.length(),
(GLint)body.length() - 1 // null terminated
};
// Some of the sources may be zero-length. Remove them as to avoid passing lengths of
// zero to glShaderSource(). glShaderSource should work with lengths of zero, but some
// drivers instead interpret zero as a sentinel for a null-terminated string.
auto partitionPoint = std::stable_partition(
sources.begin(), sources.end(), [](std::string_view s) { return !s.empty(); });
size_t count = std::distance(sources.begin(), partitionPoint);
std::array<const char*, 5> shaderStrings;
std::array<GLint, 5> lengths;
for (size_t i = 0; i < count; i++) {
shaderStrings[i] = sources[i].data();
lengths[i] = sources[i].size();
}
GLuint const shaderId = glCreateShader(glShaderType);
glShaderSource(shaderId, sources.size(), sources.data(), lengths.data());
glShaderSource(shaderId, count, shaderStrings.data(), lengths.data());
glCompileShader(shaderId);
#ifndef NDEBUG
@@ -785,25 +810,24 @@ mediump vec4 unpackSnorm4x8(highp uint v) {
// - extensions
// - everything else
std::array<std::string_view, 3> ShaderCompilerService::splitShaderSource(std::string_view source) noexcept {
auto start = source.find("#version");
assert_invariant(start != std::string_view::npos);
auto version_start = source.find("#version");
assert_invariant(version_start != std::string_view::npos);
auto version_eol = source.find('\n', start) + 1;
auto version_eol = source.find('\n', version_start) + 1;
assert_invariant(version_eol != std::string_view::npos);
auto pos = source.rfind("\n#extension");
if (pos == std::string_view::npos) {
pos = version_eol;
auto prolog_start = version_eol;
auto prolog_eol = source.rfind("\n#extension"); // last #extension line
if (prolog_eol == std::string_view::npos) {
prolog_eol = prolog_start;
} else {
++pos;
prolog_eol = source.find('\n', prolog_eol + 1) + 1;
}
auto body_start = prolog_eol;
auto eol = source.find('\n', pos) + 1;
assert_invariant(eol != std::string_view::npos);
std::string_view const version = source.substr(start, version_eol - start);
std::string_view const prolog = source.substr(version_eol, eol - version_eol);
std::string_view const body = source.substr(eol, source.length() - eol);
std::string_view const version = source.substr(version_start, version_eol - version_start);
std::string_view const prolog = source.substr(prolog_start, prolog_eol - prolog_start);
std::string_view const body = source.substr(body_start, source.length() - body_start);
return { version, prolog, body };
}
+6
View File
@@ -201,6 +201,12 @@ using namespace glext;
# define GL_CLIP_DISTANCE1 0x3001
#endif
#if defined(GL_EXT_depth_clamp)
# define GL_DEPTH_CLAMP GL_DEPTH_CLAMP_EXT
#else
# define GL_DEPTH_CLAMP 0x864F
#endif
#if defined(GL_KHR_debug)
# define GL_DEBUG_OUTPUT GL_DEBUG_OUTPUT_KHR
# define GL_DEBUG_OUTPUT_SYNCHRONOUS GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR
+16 -15
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 {
@@ -28,6 +28,7 @@ VulkanBuffer::VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool,
: mAllocator(allocator),
mStagePool(stagePool),
mUsage(usage),
mUpdatedOffset(0),
mUpdatedBytes(0) {
// for now make sure that only 1 bit is set in usage
// (because loadFromCpu() assumes that somewhat)
@@ -80,6 +81,7 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = mGpuBuffer,
.offset = mUpdatedOffset,
.size = mUpdatedBytes,
};
vkCmdPipelineBarrier(cmdbuf, srcStage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1,
@@ -93,6 +95,7 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
};
vkCmdCopyBuffer(cmdbuf, stage->buffer, mGpuBuffer, 1, &region);
mUpdatedOffset = byteOffset;
mUpdatedBytes = numBytes;
// Firstly, ensure that the copy finishes before the next draw call.
@@ -42,6 +42,7 @@ private:
VmaAllocation mGpuMemory = VK_NULL_HANDLE;
VkBuffer mGpuBuffer = VK_NULL_HANDLE;
VkBufferUsageFlags mUsage = {};
uint32_t mUpdatedOffset = 0;
uint32_t mUpdatedBytes = 0;
};
+12 -11
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);
}
}
}
@@ -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;
@@ -59,7 +59,11 @@ VkExtent2D VulkanAttachment::getExtent2D() const {
VkImageView VulkanAttachment::getImageView() {
assert_invariant(texture);
return texture->getAttachmentView(getSubresourceRange());
VkImageSubresourceRange range = getSubresourceRange();
if (range.layerCount > 1) {
return texture->getMultiviewAttachmentView(range);
}
return texture->getAttachmentView(range);
}
bool VulkanAttachment::isDepth() const {
@@ -73,7 +77,7 @@ VkImageSubresourceRange VulkanAttachment::getSubresourceRange() const {
.baseMipLevel = uint32_t(level),
.levelCount = 1,
.baseArrayLayer = uint32_t(layer),
.layerCount = 1,
.layerCount = layerCount,
};
}
@@ -43,6 +43,8 @@ struct VulkanCommandBuffer;
struct VulkanAttachment {
VulkanTexture* texture = nullptr;
uint8_t level = 0;
uint8_t baseViewIndex = 0;
uint8_t layerCount = 1;
uint16_t layer = 0;
bool isDepth() const;
@@ -123,6 +125,10 @@ public:
return mPhysicalDeviceFeatures.imageCubeArray == VK_TRUE;
}
inline bool isDepthClampSupported() const noexcept {
return mPhysicalDeviceFeatures.depthClamp == VK_TRUE;
}
inline bool isDebugMarkersSupported() const noexcept {
return mDebugMarkersSupported;
}
+227 -256
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);
}
@@ -595,6 +647,8 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
colorTargets[i] = {
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(color[i].handle),
.level = color[i].level,
.baseViewIndex = color[i].baseViewIndex,
.layerCount = layerCount,
.layer = color[i].layer,
};
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = colorTargets[i].getExtent2D();
@@ -609,6 +663,8 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
depthStencil[0] = {
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(depth.handle),
.level = depth.level,
.baseViewIndex = depth.baseViewIndex,
.layerCount = layerCount,
.layer = depth.layer,
};
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = depthStencil[0].getExtent2D();
@@ -621,6 +677,8 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
depthStencil[1] = {
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(stencil.handle),
.level = stencil.level,
.baseViewIndex = stencil.baseViewIndex,
.layerCount = layerCount,
.layer = stencil.layer,
};
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = depthStencil[1].getExtent2D();
@@ -635,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);
auto renderTarget = mResourceAllocator.construct<VulkanRenderTarget>(rth,
mPlatform->getDevice(), mPlatform->getPhysicalDevice(), mContext, mAllocator,
&mCommands, &mResourceAllocator, width, height, samples, colorTargets, depthStencil,
mStagePool, layerCount);
mResourceManager.acquire(renderTarget);
}
@@ -665,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);
}
@@ -678,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);
}
@@ -686,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>();
}
@@ -706,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>();
}
@@ -714,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>();
}
@@ -756,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) {
@@ -795,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 {};
}
@@ -832,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;
}
@@ -884,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() {
@@ -938,6 +1025,10 @@ bool VulkanDriver::isProtectedTexturesSupported() {
return false;
}
bool VulkanDriver::isDepthClampSupported() {
return mContext.isDepthClampSupported();
}
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU: {
@@ -986,15 +1077,15 @@ FeatureLevel VulkanDriver::getFeatureLevel() {
// If the max sampler counts do not meet FL2 standards, then this is an FL1 device.
const auto& fl2 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_2];
if (fl2.MAX_VERTEX_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers ||
fl2.MAX_FRAGMENT_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers) {
if (limits.maxPerStageDescriptorSamplers < fl2.MAX_VERTEX_SAMPLER_COUNT ||
limits.maxPerStageDescriptorSamplers < fl2.MAX_FRAGMENT_SAMPLER_COUNT) {
return FeatureLevel::FEATURE_LEVEL_1;
}
// If the max sampler counts do not meet FL3 standards, then this is an FL2 device.
const auto& fl3 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_3];
if (fl3.MAX_VERTEX_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers ||
fl3.MAX_FRAGMENT_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers) {
if (limits.maxPerStageDescriptorSamplers < fl3.MAX_VERTEX_SAMPLER_COUNT||
limits.maxPerStageDescriptorSamplers < fl3.MAX_FRAGMENT_SAMPLER_COUNT) {
return FeatureLevel::FEATURE_LEVEL_2;
}
@@ -1068,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) {
@@ -1157,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,
@@ -1188,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
@@ -1218,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;
@@ -1260,13 +1300,13 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
// If the depth attachment texture was previously sampled, then we need to manually
// transition it to an attachment. This is necessary to also set up a barrier between the
// previous read and the potentially coming write.
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();
// Create the VkRenderPass or fetch it from cache.
VulkanFboCache::RenderPassKey rpkey = {
.initialColorLayoutMask = 0,
@@ -1277,20 +1317,17 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
.discardEnd = discardEndVal,
.samples = rt->getSamples(),
.subpassMask = uint8_t(params.subpassMask),
.viewCount = renderTargetLayerCount,
};
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
const VulkanAttachment& info = rt->getColor(i);
VulkanAttachment const& info = rt->getColor(i);
if (info.texture) {
assert_invariant(info.layerCount == renderTargetLayerCount);
rpkey.initialColorLayoutMask |= 1 << i;
rpkey.colorFormat[i] = info.getFormat();
if (rpkey.samples > 1 && info.texture->samples == 1) {
rpkey.needsResolveMask |= (1 << i);
}
if (info.texture->getPrimaryImageLayout() != VulkanLayout::COLOR_ATTACHMENT) {
((VulkanTexture*) info.texture)
->transitionLayout(cmdbuffer, info.getSubresourceRange(),
VulkanLayout::COLOR_ATTACHMENT);
}
} else {
rpkey.colorFormat[i] = VK_FORMAT_UNDEFINED;
}
@@ -1309,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]);
@@ -1448,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();
@@ -1557,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");
@@ -1592,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);
@@ -1784,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);
@@ -1806,6 +1797,7 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
.dstAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionDstAlpha),
.colorWriteMask = (VkColorComponentFlags) (rasterState.colorWrite ? 0xf : 0x0),
.rasterizationSamples = rt->getSamples(),
.depthClamp = rasterState.depthClamp,
.colorTargetCount = rt->getColorTargetCount(mCurrentRenderPass),
.colorBlendOp = rasterState.blendEquationRGB,
.alphaBlendOp = rasterState.blendEquationAlpha,
@@ -1828,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);
@@ -1923,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");
@@ -1930,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;
@@ -1979,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);
}
@@ -2022,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>;
+1 -2
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;
@@ -43,6 +43,7 @@ bool VulkanFboCache::RenderPassEq::operator()(const RenderPassKey& k1,
if (k1.samples != k2.samples) return false;
if (k1.needsResolveMask != k2.needsResolveMask) return false;
if (k1.subpassMask != k2.subpassMask) return false;
if (k1.viewCount != k2.viewCount) return false;
return true;
}
@@ -186,6 +187,25 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
.pDependencies = dependencies
};
VkRenderPassMultiviewCreateInfo multiviewCreateInfo = {};
uint32_t const subpassViewMask = (1 << config.viewCount) - 1;
// Prepare a view mask array for the maximum number of subpasses. All subpasses have all views
// activated.
uint32_t const viewMasks[2] = {subpassViewMask, subpassViewMask};
if (config.viewCount > 1) {
// Fill the multiview create info.
multiviewCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO;
multiviewCreateInfo.pNext = nullptr;
multiviewCreateInfo.subpassCount = hasSubpasses ? 2u : 1u;
multiviewCreateInfo.pViewMasks = viewMasks;
multiviewCreateInfo.dependencyCount = 0;
multiviewCreateInfo.pViewOffsets = nullptr;
multiviewCreateInfo.correlationMaskCount = 1;
multiviewCreateInfo.pCorrelationMasks = &subpassViewMask;
renderPassInfo.pNext = &multiviewCreateInfo;
}
int attachmentIndex = 0;
// Populate the Color Attachments.
+1 -1
View File
@@ -64,7 +64,7 @@ public:
uint8_t samples; // 1 byte
uint8_t needsResolveMask; // 1 byte
uint8_t subpassMask; // 1 byte
uint8_t padding2; // 1 byte
uint8_t viewCount; // 1 byte
};
struct RenderPassVal {
VkRenderPass handle;
+114 -154
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,13 +316,15 @@ 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)
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount)
: HwRenderTarget(width, height),
VulkanResource(VulkanResourceType::RENDER_TARGET),
mOffscreen(true),
mSamples(samples) {
mSamples(samples),
mLayerCount(layerCount) {
for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) {
mColor[index] = color[index];
}
@@ -353,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 */);
@@ -382,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);
@@ -534,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;
@@ -558,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
+60 -87
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,9 +270,11 @@ 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);
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount);
// Creates a special "default" render target (i.e. associated with the swap chain)
explicit VulkanRenderTarget();
@@ -319,6 +289,7 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
VulkanAttachment& getMsaaDepth();
uint8_t getColorTargetCount(const VulkanRenderPass& pass) const;
uint8_t getSamples() const { return mSamples; }
uint8_t getLayerCount() const { return mLayerCount; }
bool hasDepth() const { return mDepth.texture; }
bool isSwapChain() const { return !mOffscreen; }
void bindToSwapChain(VulkanSwapChain& surf);
@@ -330,6 +301,7 @@ private:
VulkanAttachment mMsaaDepthAttachment = {};
const bool mOffscreen : 1;
uint8_t mSamples : 7;
uint8_t mLayerCount = 1;
};
struct VulkanBufferObject;
@@ -482,6 +454,7 @@ private:
utils::Mutex mFenceMutex;
};
inline constexpr VkBufferUsageFlagBits getBufferObjectUsage(
BufferObjectBinding bindingType) noexcept {
switch(bindingType) {
@@ -131,14 +131,18 @@ getVkTransition(const VulkanLayoutTransition& transition) {
}// anonymous namespace
void transitionLayout(VkCommandBuffer cmdbuffer,
bool transitionLayout(VkCommandBuffer cmdbuffer,
VulkanLayoutTransition transition) {
if (transition.oldLayout == transition.newLayout) {
return;
return false;
}
auto [srcAccessMask, dstAccessMask, srcStage, dstStage, oldLayout, newLayout]
= getVkTransition(transition);
if (oldLayout == newLayout) {
return false;
}
assert_invariant(transition.image != VK_NULL_HANDLE && "No image for transition");
VkImageMemoryBarrier barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
@@ -152,6 +156,7 @@ void transitionLayout(VkCommandBuffer cmdbuffer,
.subresourceRange = transition.subresources,
};
vkCmdPipelineBarrier(cmdbuffer, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
return true;
}
}// namespace filament::backend
@@ -135,7 +135,9 @@ constexpr inline VkImageLayout getVkLayout(VulkanLayout layout) {
}
}
void transitionLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition);
// Returns true if a transition has been added to the command buffer, false otherwis (where there is
// no transition necessary).
bool transitionLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition);
} // namespace imgutil
@@ -98,7 +98,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
VkPipelineColorBlendStateCreateInfo colorBlendState;
colorBlendState = VkPipelineColorBlendStateCreateInfo{};
colorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlendState.attachmentCount = 1;
colorBlendState.attachmentCount = mPipelineRequirements.rasterState.colorTargetCount;
colorBlendState.pAttachments = colorBlendAttachments;
// If we reach this point, we need to create and stash a brand new pipeline object.
@@ -184,6 +184,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
vkRaster.polygonMode = VK_POLYGON_MODE_FILL;
vkRaster.cullMode = raster.cullMode;
vkRaster.frontFace = raster.frontFace;
vkRaster.depthClampEnable = raster.depthClamp;
vkRaster.depthBiasEnable = raster.depthBiasEnable;
vkRaster.depthBiasConstantFactor = raster.depthBiasConstantFactor;
vkRaster.depthBiasClamp = 0.0f;
@@ -209,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;
@@ -90,7 +90,8 @@ public:
VkBlendFactor srcAlphaBlendFactor : 5;
VkBlendFactor dstAlphaBlendFactor : 5;
VkColorComponentFlags colorWriteMask : 4;
uint8_t rasterizationSamples; // offset = 4 bytes
uint8_t rasterizationSamples : 4;// offset = 4 bytes
uint8_t depthClamp : 4;
uint8_t colorTargetCount; // offset = 5 bytes
BlendEquation colorBlendOp : 4; // offset = 6 bytes
BlendEquation alphaBlendOp : 4;
@@ -232,7 +232,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
VulkanAttachment const srcAttachment = srcTarget->getColor(0);
VkImageSubresourceRange const srcRange = srcAttachment.getSubresourceRange();
srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
srcTexture->transitionLayout(cmdbuffer, {}, srcRange, VulkanLayout::TRANSFER_SRC);
VkImageCopy const imageCopyRegion = {
.srcSubresource = {
@@ -268,7 +268,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
imgutil::getVkLayout(VulkanLayout::TRANSFER_DST), 1, &imageCopyRegion);
// Restore the source image layout.
srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::COLOR_ATTACHMENT);
srcTexture->transitionLayout(cmdbuffer, {}, srcRange, VulkanLayout::COLOR_ATTACHMENT);
vkEndCommandBuffer(cmdbuffer);
@@ -105,6 +105,10 @@ public:
mHandleAllocatorImpl.deallocate(handle, obj);
}
inline void associateHandle(HandleBase::HandleId id, utils::CString&& tag) noexcept {
mHandleAllocatorImpl.associateTagToHandle(id, std::move(tag));
}
private:
AllocatorImpl mHandleAllocatorImpl;
@@ -26,12 +26,14 @@ using namespace utils;
namespace filament::backend {
VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context,
VmaAllocator allocator, VulkanCommands* commands, VulkanStagePool& stagePool,
VmaAllocator allocator, VulkanCommands* commands, VulkanResourceAllocator* handleAllocator,
VulkanStagePool& stagePool,
void* nativeWindow, uint64_t flags, VkExtent2D extent)
: VulkanResource(VulkanResourceType::SWAP_CHAIN),
mPlatform(platform),
mCommands(commands),
mAllocator(allocator),
mHandleAllocator(handleAllocator),
mStagePool(stagePool),
mHeadless(extent.width != 0 && extent.height != 0 && !nativeWindow),
mFlushAndWaitOnResize(platform->getCustomization().flushAndWaitOnWindowResize),
@@ -62,12 +64,12 @@ void VulkanSwapChain::update() {
VkDevice const device = mPlatform->getDevice();
for (auto const color: bundle.colors) {
mColors.push_back(std::make_unique<VulkanTexture>(device, mAllocator, mCommands, color,
bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height,
mColors.push_back(std::make_unique<VulkanTexture>(device, mAllocator, mCommands, mHandleAllocator,
color, bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height,
TextureUsage::COLOR_ATTACHMENT, mStagePool, true /* heap allocated */));
}
mDepth = std::make_unique<VulkanTexture>(device, mAllocator, mCommands, bundle.depth,
bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height,
mDepth = std::make_unique<VulkanTexture>(device, mAllocator, mCommands, mHandleAllocator,
bundle.depth, bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height,
TextureUsage::DEPTH_ATTACHMENT, mStagePool, true /* heap allocated */);
mExtent = bundle.extent;
@@ -75,7 +77,7 @@ void VulkanSwapChain::update() {
void VulkanSwapChain::present() {
if (!mHeadless && mTransitionSwapChainImageLayoutForPresent) {
VkCommandBuffer const cmdbuf = mCommands->get().buffer();
VulkanCommandBuffer& commands = mCommands->get();
VkImageSubresourceRange const subresources{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
@@ -83,7 +85,7 @@ void VulkanSwapChain::present() {
.baseArrayLayer = 0,
.layerCount = 1,
};
mColors[mCurrentSwapIndex]->transitionLayout(cmdbuf, subresources, VulkanLayout::PRESENT);
mColors[mCurrentSwapIndex]->transitionLayout(&commands, subresources, VulkanLayout::PRESENT);
}
mCommands->flush();
@@ -36,11 +36,13 @@ namespace filament::backend {
struct VulkanHeadlessSwapChain;
struct VulkanSurfaceSwapChain;
class VulkanResourceAllocator;
// A wrapper around the platform implementation of swapchain.
struct VulkanSwapChain : public HwSwapChain, VulkanResource {
VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context, VmaAllocator allocator,
VulkanCommands* commands, VulkanStagePool& stagePool,
VulkanCommands* commands, VulkanResourceAllocator* handleAllocator,
VulkanStagePool& stagePool,
void* nativeWindow, uint64_t flags, VkExtent2D extent = {0, 0});
~VulkanSwapChain();
@@ -80,6 +82,7 @@ private:
VulkanPlatform* mPlatform;
VulkanCommands* mCommands;
VmaAllocator mAllocator;
VulkanResourceAllocator* const mHandleAllocator;
VulkanStagePool& mStagePool;
bool const mHeadless;
bool const mFlushAndWaitOnResize;
+301 -166
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,63 +467,68 @@ 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) {
// Attachments should only have one mipmap level and one layer.
range.levelCount = 1;
range.layerCount = 1;
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D, {});
}
VkImageView VulkanTexture::getMultiviewAttachmentView(VkImageSubresourceRange range) {
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D_ARRAY, {});
}
VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range, VkImageViewType type) {
return getImageView(range, type, mSwizzle);
}
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
VkComponentMapping swizzle) {
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;
@@ -438,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) {
@@ -449,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;
@@ -505,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;
@@ -539,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
+152 -93
View File
@@ -30,84 +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);
// 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
@@ -130,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,
@@ -138,28 +210,15 @@ private:
void updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width, uint32_t height,
uint32_t depth, uint32_t miplevel);
// The texture with the sidecar owns the sidecar.
std::unique_ptr<VulkanTexture> mSidecarMSAA;
const VkFormat mVkFormat;
const VkImageViewType mViewType;
const VkComponentMapping mSwizzle;
VkImage mTextureImage = VK_NULL_HANDLE;
VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE;
VulkanResourceAllocator* const mAllocator;
// Track the image layout of each subresource using a sparse range map.
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
VkImageSubresourceRange mFullViewRange;
Handle<VulkanTextureState> mState;
// Track the range of subresources that define the "primary" image view, which is the special
// image view that gets bound to an actual texture sampler.
VkImageSubresourceRange mPrimaryViewRange;
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
VulkanStagePool& mStagePool;
VkDevice mDevice;
VmaAllocator mAllocator;
VulkanCommands* mCommands;
VkComponentMapping mSwizzle {};
};
} // namespace filament::backend
@@ -577,7 +577,7 @@ uint32_t getComponentCount(VkFormat format) {
return {};
}
VkComponentMapping getSwizzleMap(TextureSwizzle swizzle[4]) {
VkComponentMapping getSwizzleMap(TextureSwizzle const swizzle[4]) {
VkComponentMapping map;
VkComponentSwizzle* dst = &map.r;
for (int i = 0; i < 4; ++i, ++dst) {
+30 -111
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
File diff suppressed because it is too large Load Diff
@@ -30,12 +30,8 @@
#include <bluevk/BlueVK.h>
#include <tsl/robin_map.h>
#include <functional>
namespace filament::backend {
using namespace descset;
// [GDSR]: Great-Descriptor-Set-Refactor: As of 03/20/24, the Filament frontend is planning to
// introduce descriptor set. This PR will arrive before that change is complete. As such, some of
// the methods introduced here will be obsolete, and certain logic will be generalized.
@@ -45,60 +41,32 @@ class VulkanDescriptorSetManager {
public:
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT =
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT;
using GetPipelineLayoutFunction = std::function<VkPipelineLayout(
VulkanDescriptorSetLayoutList const&, VulkanProgram* program)>;
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator);
void terminate() noexcept;
void gc();
// TODO: Obsolete after [GDSR].
// This will write/update/bind all of the descriptor set. After [GDSR], the binding for
// descriptor sets will not depend on the layout described in the program.
VkPipelineLayout bind(VulkanCommandBuffer* commands, VulkanProgram* program,
GetPipelineLayoutFunction& getPipelineLayoutFn);
// TODO: Obsolete after [GDSR].
// This is to "dynamically" bind UBOs that might have offsets changed between pipeline binding
// and the draw call. We do this because UBOs for primitives that are part of the same
// renderable can be stored within one buffer. This can be a no-op if there were no range
// changes between the pipeline bind and the draw call. We will re-use applicable states
// provided within the bind() call, including the UBO descriptor set layout. TODO: make it a
// proper dynamic binding when Filament-side descriptor changes are completed.
void dynamicBind(VulkanCommandBuffer* commands, Handle<VulkanDescriptorSetLayout> uboLayout);
// TODO: Obsolete after [GDSR].
// Since we use program pointer as cache key, we need to clear the cache when it's freed.
void clearProgram(VulkanProgram* program) noexcept;
Handle<VulkanDescriptorSetLayout> createLayout(descset::DescriptorSetLayout const& layout);
void destroyLayout(Handle<VulkanDescriptorSetLayout> layout);
void updateBuffer(Handle<VulkanDescriptorSet> set, uint8_t binding,
void updateBuffer(VulkanDescriptorSet* set, uint8_t binding,
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept;
void updateSampler(Handle<VulkanDescriptorSet> set, uint8_t binding,
void updateSampler(VulkanDescriptorSet* set, uint8_t binding,
VulkanTexture* texture, VkSampler sampler) noexcept;
void updateInputAttachment(Handle<VulkanDescriptorSet> set, VulkanAttachment attachment) noexcept;
void updateInputAttachment(VulkanDescriptorSet* set, VulkanAttachment attachment) noexcept;
void clearBuffer(uint32_t bindingIndex);
void bind(uint8_t setIndex, VulkanDescriptorSet* set, backend::DescriptorSetOffsetArray&& offsets);
void commit(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout,
DescriptorSetMask const& setMask);
void setPlaceHolders(VkSampler sampler, VulkanTexture* texture,
VulkanBufferObject* bufferObject) noexcept;
void clearState() noexcept;
void createSet(Handle<HwDescriptorSet> handle, VulkanDescriptorSetLayout* layout);
// TODO: To be completed after [GDSR]
Handle<VulkanDescriptorSet> createSet(Handle<VulkanDescriptorSetLayout> layout) {
return Handle<VulkanDescriptorSet>();
}
// TODO: To be completed after [GDSR]
void destroySet(Handle<VulkanDescriptorSet> set) {}
void destroySet(Handle<HwDescriptorSet> handle);
private:
class Impl;
@@ -108,4 +76,3 @@ private:
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
@@ -21,23 +21,24 @@
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& pushCostantRanges = 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) {
auto const& range = pushCostantRanges[i];
auto const& range = pushConstantRanges[i];
auto& pushConstant = key.pushConstant[i];
if (range.stageFlags & VK_SHADER_STAGE_VERTEX_BIT) {
pushConstant.stage = static_cast<uint8_t>(ShaderStage::VERTEX);
@@ -52,9 +53,8 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
}
}
auto iter = mPipelineLayouts.find(key);
if (iter != mPipelineLayouts.end()) {
PipelineLayoutCacheEntry& entry = mPipelineLayouts[key];
if (auto iter = mPipelineLayouts.find(key); iter != mPipelineLayouts.end()) {
PipelineLayoutCacheEntry& entry = iter->second;
entry.lastUsed = mTimestamp++;
return entry.handle;
}
@@ -65,7 +65,7 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
.setLayoutCount = (uint32_t) descSetLayoutCount,
.pSetLayouts = key.descSetLayouts.data(),
.pushConstantRangeCount = pushConstantRangeCount,
.pPushConstantRanges = pushCostantRanges,
.pPushConstantRanges = pushConstantRanges,
};
VkPipelineLayout layout;
@@ -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
@@ -325,6 +325,7 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
// We could simply enable all supported features, but since that may have performance
// consequences let's just enable the features we need.
VkPhysicalDeviceFeatures enabledFeatures{
.depthClamp = features.depthClamp,
.samplerAnisotropy = features.samplerAnisotropy,
.textureCompressionETC2 = features.textureCompressionETC2,
.textureCompressionBC = features.textureCompressionBC,
+12 -6
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;
+7 -2
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);
+159
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();
}
+11 -12
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()) };
+3 -2
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;

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