Compare commits

..
Author SHA1 Message Date
Powei Feng 3e32e6cf2a backend-test: centralize read pixel logic 2024-05-16 15:08:56 -07:00
Ben Doherty 1d4f1fe71f Metal, fix callbacks being called only once (#7856) 2024-05-15 10:50:10 -07:00
Mathias Agopian c93aa4c90d minor libutils improvements 2024-05-14 13:22:38 -07:00
Mathias Agopian 499939ed3c backend: bindPipeline now takes const& 2024-05-14 13:21:38 -07:00
Mathias Agopian 6be97ee01d backend: zero-cost ES2 draw()
we use a different hook for the draw() call when on an ES2 context,
this eliminates completely the overhead of supporting ES2 for the draw
call. draw calls are expected to be the most common calls.
2024-05-14 13:21:24 -07:00
Powei Feng 7267696cbf vk: fix dynamic scissor validation error (#7853) 2024-05-14 19:08:50 +00:00
Powei Feng 85eb724a90 Reduce explicit swiftshader paths (#7848)
- Use custom ICD path to enable Swiftshader instead of
   specifying direct path to the lib.
   - Remove unused `swiftshader` directory in `build`
   - Remove swiftshader options in `build.sh` and cmakefiles
   - Change BUILD.md
 - Correctly handle XCB-only swapchain surface in VulkanPlatform
   for swiftshader.
 - Refactor `VulkanPlatform::ExtensionSet` so that `utils::CString`
   is used instead of string_view, so that we don't get into
   tricky lifetime issues with `const char*`
2024-05-14 17:40:54 +00:00
Mathias Agopian 6dd6db89d5 align android libraries to 16 KiB 2024-05-13 23:14:32 -07:00
Mathias Agopian c76f67c139 fix typo checking FILAMENT_ENABLE_MATDBG
this macro must be checked with #if not #ifdef
2024-05-13 23:14:13 -07:00
Sungun Park 6fc16bdcda Release Filament 1.51.8 2024-05-13 20:53:36 +00:00
Mathias Agopian 4f021583f1 backend tests were broken by a change in TargetBufferInfo
- a field was added, which broke the layout of the structure. We fix it
by adding constructors which will handle the old and new way of
initializing this structure.

- one of the test needed a hash update

- OpenGLContext wrongly asserted when trying to unbind texture 0
2024-05-10 14:39:22 -07:00
Powei Feng ef15a29c0c vk: fix missing lib for backend test 2024-05-10 13:00:00 -07:00
Benjamin Doherty a0472d3c9f Rename Metal log message 2024-05-10 11:25:34 -07:00
Ben Doherty 54a800a25d Metal: implement more accurate buffer tracking (#7839) 2024-05-10 11:14:41 -07:00
Powei Feng 7f8fbe586c gl: push constant small clean-up (#7841) 2024-05-10 10:23:32 -07:00
Powei Feng 6f2c45c76d Add push constants (#7817)
- Push constants is a small set of bytes that can be recorded
   directly on the command buffer.
 - Implemented it for the vulkan/gl backend.
2024-05-09 16:14:03 -07:00
Ryan ad60008b6a ryanmyers: Improve logging for Metallib function lookup failures (#7836)
If a .metallib was compiled with a target iOS version that's newer than
the current device, loading the .metallib may succeed, but finding main0
(or any other function in it) will fail. Currently, this causes a crash
due to an assert. Logging the error and returning
MetalFunctionBundle::error() makes the crash slightly easier to
diagnose.

(Note that in practice, this will probably be a useless "Compiler
encountered an internal error" message -- the GPU backend is crashing,
and the Metal stub library sees XPC_ERROR_CONNECTION_INTERRUPTED. It
retries up to 3 times (crashing each time) and then gives up.)
2024-05-09 10:49:41 -07:00
Mathias Agopian d87f9b621b add a simple tranformmanager unit test
this was to test issue #7827
2024-05-09 09:00:41 -07:00
Ben Doherty 1b38cda0d5 Add preferredShaderLanguage Java bindings (#7835) 2024-05-08 14:42:40 -07:00
Ben Doherty a1dea7b1fa Metal: log slow buffer allocation times (#7834) 2024-05-08 14:39:53 -07:00
Mathias Agopian 744708b5ca remove the single <sstream> usage we have
the STL's stream headers can bring in a lot of code, we don't use them.
2024-05-07 16:23:31 -07:00
Benjamin Doherty e901837317 Log excess buffer allocations for Metal 2024-05-07 15:39:48 -07:00
Powei Feng 17f71c9b81 Release Filament 1.51.7 2024-05-07 14:35:42 -07:00
Mathias AgopianandPowei Feng b056c126fe Add an Engine debug setting to force GLES 2.0 (#7829)
* Add an Engine debug setting to force GLES 2.0

This setting is only meaningful on GLES backends, it's otherwise
ignored. When set to true, the backend will try to force a ES2 context
if supported. If not supported by the platform,
the backend will pretend it's a ES2 context.

This setting is currently only taken into account by the EGL platform.

* Update filament/backend/include/backend/Platform.h

Co-authored-by: Powei Feng <powei@google.com>

---------

Co-authored-by: Powei Feng <powei@google.com>
2024-05-07 09:53:31 -07:00
Sungun Park 6ac36d1aab Fix a shader error (#7828)
gl_ViewID_OVR is of uint type. Explicitly convert it to int to fix the
error.
2024-05-06 21:03:04 +00:00
Ben Doherty a1752b363c Add preferredShaderLanguage option to Engine::Config (#7816) 2024-05-06 12:12:38 -07:00
Levente Koncz e855f4aafb Fix mixed up FBO targets in status check code inside OpenGLDriver::blit() 2024-05-06 09:17:15 -07:00
Ben Doherty 1c693e24cf Metal: track types of buffers (#7796) 2024-05-03 14:32:01 -07:00
Ben Doherty af92a1f21b Add getEyeIndex vertex API (#7822) 2024-05-03 14:22:24 -07:00
Powei Feng 10ff1ee3e4 Update filament to 1.51.6 for public pages (#7821)
Updated both /viewer and /remote
2024-05-03 20:49:53 +00:00
Vladyslav PopovychandVladyslav 5449f46350 Remove bitcode from iOS builds (#7802)
Co-authored-by: Vladyslav <vlad@arthouse.ar>
2024-05-03 12:25:00 -07:00
Mathias Agopian 2967888d26 repair instanced stereo in debug builds (#7819)
- it was broken by a wrong assertion
- also auto instancing + stereo instancing was
  always broken, but this makes it work
2024-05-03 09:36:09 -07:00
Powei Feng afe81d6889 vk: Use offset when uploading buffer data (#7818) 2024-05-02 11:17:32 -07:00
Mathias AgopianandBen Doherty 95134883d6 add the -a option to skinning and morphing samples (#7815)
* add the `-a` option to skinning and morphing samples

* Update samples/skinningtest.cpp

Co-authored-by: Ben Doherty <bendoherty@google.com>

* Update samples/helloskinningbuffer_morebones.cpp

Co-authored-by: Ben Doherty <bendoherty@google.com>

* Update samples/hellomorphing.cpp

Co-authored-by: Ben Doherty <bendoherty@google.com>

* Update samples/helloskinningbuffer.cpp

Co-authored-by: Ben Doherty <bendoherty@google.com>

---------

Co-authored-by: Ben Doherty <bendoherty@google.com>
2024-05-01 15:24:01 -07:00
Mathias Agopian 600c122761 fix ES2 support
- make sure we can build with ES2 headers only
- make sure to not use ES3 features when in ES2 mode
  (post processing was accidentally creating an R8 texture, 
  which is not supported)
2024-05-01 13:29:58 -07:00
Mathias Agopian 1cf5335964 Skybox doesn't need a commit() method
The material parameter changes are committed automatically in
Engine::prepare(), no need to do it explicitly for SkyBox.
2024-05-01 10:11:39 -07:00
Eliza Velasquez 4ac4ffd8d6 filagui: Split uiBlit material into two
Unfortunately, the external uniform was not optimized out as expected. The only
option is to split it into its own independent material.
2024-04-30 14:14:23 -07:00
Eliza Velasquez 6c40721695 Fix broken ImGui on web
I was unfortunately naive about the way that Filament handled external textures
on non-GLES platforms. This fix restricts the changes to Android (which is the
only place this change is required in the first place). Long story short, the
change broke WebGL. Desktop seems to be unaffected.
2024-04-30 14:14:23 -07:00
Benjamin Doherty 56fa078376 Release Filament 1.51.6 2024-04-29 16:49:28 -07:00
Ben Doherty 883507a26f Remove spirv-opt size optimizations for MSL (#7495) 2024-04-29 16:47:05 -07:00
Powei Feng d697d42c4a vk: finer-grain barrier at renderpass end (#7808)
- At the end of a renderpass we use a more fine-grained barrier
   for each of the attachments in the render target
 - Make sure that buffer update are barrier'd from previous reads
 - Remove previous Mali workaround barriers.  Seems to be fine
   without them on pixels + Mali.
2024-04-29 11:32:04 -07:00
Ben Doherty e7feee7d5b Switch setFrameScheduledCallback to use utils::Invocable (#7792) 2024-04-29 10:18:28 -07:00
Powei Feng 4c75e6e9a2 gltfio: enable extended implementation (#7776)
This change will enable proper flat-shading and MikkTSpace.

Caveats:
 - Only for disk-local glTF resources
 - iOS, Web, Android do not work as of now

Fixes #6358, #7444
2024-04-27 00:28:08 +00:00
Mathias Agopian 8ab8525359 fix a missing ubo upload in bloom set-up 2024-04-26 16:49:39 -07:00
Powei Feng 65c794881f vk: various clean up (#7805)
- use namespace for ImageUtility
 - use FILAMENT_BACKEND_DEBUG_FLAG
 - remove unused usage flag type
 - add const list of VKFormat for iteration
 - fix sampler name debug
2024-04-26 22:26:16 +00:00
Powei Feng 08d6d57ad0 vk: simplify VulkanAttachment (#7801) 2024-04-26 21:48:15 +00:00
Mathias Agopian 254cf15f0e a couple of significant improvements to instancing
The main goal of this change is to avoid having to select the 
"per renderable" UBO at execution time. Instead we want the
PrimitiveInfo structure to already know which UBO will be used.
Concretely, this means that this determination must be done when
the RenderPass is created.

When automatic instancing is used, the RenderPass creates a temporary
UBO to store the instance info. This UBO's life-time is dictated by both
the life-time of the RenderPass and the Executors that where created
from it. For this reason we introduce SharedHandle<> to correctly 
account for the owner's lifetime. This fixes a potential bugs where
that handle could have been destroyed and used later; in practice this
bug didn't happen however.

A couple other changes:

- RenderPass has a bunch of fields that were actually temporary, so we
removed those.

- The canonical "per-renderable" UBO was owned by View but accessed 
through Scene. This was confusing, it's now accessed through View.
2024-04-26 14:15:00 -07:00
Mathias Agopian 22d99bac3d simplify generateCommands()
It's been a while that generateCommands() can only generate either 
the depth or color pass but not both at once; we can simplify/de-dup
the code by leveraging that.
2024-04-26 14:15:00 -07:00
Mathias Agopian 420f06bef3 implement 16 and 8 bits clz, ctz and popcount
This is needed for utils::bitset8 and bitset16.
2024-04-26 14:13:48 -07:00
Powei Feng bf8bcef35c Add option to define a backend debug flag (#7798)
To allow easy enabling/disabling of debug options like vulkan
validation, android systrace, debug printing, and others, we
introduce an option to add a preprocessor flag so that a
backend can (optionally) use it to manage debug options.
2024-04-25 23:42:55 +00:00
Eliza Velasquez bfab9f9c32 engine: Add isPaused() 2024-04-25 16:11:14 -07:00
Powei Feng c8335fade7 vk: Fix unsupported depth blitting (#7789)
* vk: Fix unsupported depth blitting

On certain hardware (pixel 4 for example), blitting of depth texture
is not supported as an "optimalTilingFeature".  In these cases, we'd
would need to do a shader-based blit. We
 - Add the shader blit in PostProcessingManager
 - Add a driver API to check for support for blitting depthStencil
   attachments.
 - Fix some debugging ifdefs in vk backend.

The validation fixed is:
`[ VUID-vkCmdBlitImage-dstImage-02000 ] Object 0: handle = 0xb400007c300701d0, type = VK_OBJECT_TYPE_COMMAND_BUFFER; Object 1: handle = 0xf2039b0000000771, type = VK_OBJECT_TYPE_IMAGE; | MessageID = 0x86bc2a78 | In vkCmdBlitImage, VkFormatFeatureFlags (0x1c601) does not support required feature VK_FORMAT_FEATURE_2_BLIT_DST_BIT for format 126 used by VkImage 0xf2039b0000000771[] with tiling VK_IMAGE_TILING_OPTIMAL. The Vulkan spec states: The format features of dstImage must contain VK_FORMAT_FEATURE_BLIT_DST_BIT`
2024-04-24 18:27:57 +00:00
Mathias Agopian 99eac62b4e give proper move semantics to Handle<> 2024-04-23 15:38:29 -07:00
Mathias Agopian 6146d071ba StructureOfArray::push_back(&&) didn't do a move 2024-04-23 15:38:29 -07:00
Mathias Agopian d26c972f5b add missing includes in RenerableManager 2024-04-23 15:38:29 -07:00
Mathias Agopian 93dc218b83 fix PerShadowMapUniform includes 2024-04-23 15:38:29 -07:00
Ben Doherty b56379dc0f Add license information to matedit (#7790) 2024-04-23 14:05:53 -06:00
Mathias Agopian e4442a5eb2 optimize the inner rendering loop
This largely undoes a change I did recently where PrimitiveInfo has
a FRenderPrimitive* to save some space and keep a command at 64 bytes.

This wasn't a good idea because the inner rendering loop shouldn't 
to any dereference in the common case.

This change reorganises PrimitiveInfo such that it stores all the data
necessary to render a primitive in the common case. The less common
cases are when hybrid instancing, morphing or skinning are used; in 
those cases, a dereference into the renderable SOA is needed.

PrimitiveInfo currently has 16 bytes free, which we keep for futur use.
2024-04-23 10:22:33 -07:00
Ben Doherty 490e8cf0d0 Add option to preserve text shaders (#7786) 2024-04-23 10:38:23 -06:00
Mathias Agopian 0d25f79421 gl: simplify how we track bound textures
In opengl it's possible to bind several textures to the same texture
unit as long as they're a different target. Until now we were tracking
that state. In practice it's not very useful to bind several textures
to the same unit (it is a little bit when updating texture data, but
not when rendering). With the coming change to a descriptor set API, it
is better to have a 1-to-1 mapping between bound textures and texture
units.

So with this change, only a single texture can be bound to a texture 
unit. If another texture in bound to the same unit with a different
target, we first unbind the texture from the current target.

There is less state to track, and it allows us to 
"unbind a texture unit" (whereas before we'd have to iterate through 
all the possible targets for that unit and unbind all of them).
2024-04-22 16:03:24 -07:00
Mathias Agopian d0586743f6 add ADPF support for backend thread 2024-04-22 15:09:08 -07:00
Mathias Agopian 14a99cbc63 add the target refresh interval to the backend beginFrame 2024-04-22 15:09:08 -07:00
Benjamin Doherty 808b51c1cf matedit: fix use-after-free 2024-04-22 16:06:39 -06:00
Sungun Park 315726678e Release Filament 1.51.5 2024-04-22 20:47:55 +00:00
Ben Doherty bac8e58ee7 Support backends with multiple shader languages and precompiled Metal libraries (#7769) 2024-04-21 21:59:22 -04:00
Mathias AgopianandBen Doherty ec31a516f7 don't rely on FMaterialInstance having a default ctor (#7781)
* don't rely on FMaterialInstance having a default ctor

FMaterialInstance needed a default ctor because it is a field of
FMaterial but cannot be initialized before FMaterial itself is
initialized. So we had a defautl ctor and we'd finish the initialization
later. Conceptually the default material instance should have been 
new'ed and a pointer to it stored instead. 

That's basically what we do now, but to avoid the extra allocation,
we in-place new and delete the default material instance into an
aligned_storage inside FMaterial.

* Update filament/src/details/Material.h

Co-authored-by: Ben Doherty <bendoherty@google.com>

---------

Co-authored-by: Ben Doherty <bendoherty@google.com>
2024-04-19 21:38:30 +00:00
Mathias Agopian 7a3b762b53 cleanup details/Material.h
Should Fix #7774
2024-04-19 12:08:03 -07:00
Eliza Velasquez 67268cb76d filagui: Use material constants in uiBlit 2024-04-18 13:46:46 -07:00
Eliza Velasquez 0b34dc6608 filagui: Add support for external textures
We have an instance of a client trying to render a GL_TEXTURE_EXTERNAL_OES
texture with filagui, which currently fails.
2024-04-18 13:46:46 -07:00
Ben Doherty afe292986e Introduce new matedit tool (#7759) 2024-04-18 12:18:29 -04:00
Mathias Agopian 83acf0f9de fix hello triangle build 2024-04-18 00:38:26 -07:00
Mathias Agopian 69d9c8b72c fix a GL backend crash when shutting down
the gl backend did some of its cleanup in the its destructor,
including calling into OpenGL, however, the destructor is called from
the main thread, not the GL thread, so these calls would be no-ops at
best, and crashes in the worst case.
2024-04-16 15:08:45 -07:00
Mathias AgopianandPowei Feng 3bffd90357 don't crash if we don't have a Camera set on View (#7766)
* don't crash if we don't have a Camera set on View

- also add a method to query if a camera was set

* Update android/filament-android/src/main/java/com/google/android/filament/View.java

Co-authored-by: Powei Feng <powei@google.com>

---------

Co-authored-by: Powei Feng <powei@google.com>
2024-04-16 21:09:21 +00:00
Mathias AgopianandPowei Feng b21dfd5ea2 add a couple way of validating MaterialInstances (#7754)
* add a couple way of validating MaterialInstances

BUGS=[333907416]

* Update filament/include/filament/Engine.h

Co-authored-by: Powei Feng <powei@google.com>

* Update filament/include/filament/Engine.h

Co-authored-by: Powei Feng <powei@google.com>

---------

Co-authored-by: Powei Feng <powei@google.com>
2024-04-16 12:12:20 -07:00
Mathias Agopian 8af2d7512d cleanup and small improvements to JobSystem
- add missing includes
- wake-up one or several threads based on the number of jobs available
2024-04-16 12:11:15 -07:00
Mathias Agopian 785592293c don't use thread affinity for the thread-pool
using thread affinity naively on big.little architectures is very flaky,
for now it's better to simplify and not use it at all, let the kernel
figure things out.

BUGS=[333582569]
2024-04-16 12:11:15 -07:00
Maximilien Dagois 613b1fc246 Fixed a comment 2024-04-16 12:10:49 -07:00
Powei Feng c8098a4c15 gltfio: add Asset/Resource extended implementations (#7678)
* gltfio: add Asset/Resource extended implementations

 - Add gltfio/src/extended to implement an alternate loader for
   primitives. This is largely based on the implementation in
   AssetLoader/ResourceLoader
 - Able to correctly produce flat shading from gltf that only have
   vertex positions and indices.
 - This is not hooked into current code and should have no
   practical effect on gltfio.
2024-04-16 17:46:48 +00:00
Powei Feng d8eee061e8 vk: Fix clang-18 compiler warning (#7764)
Fixes #7762
2024-04-16 17:14:57 +00:00
Powei Feng 0ebe888344 Fix broken dynamic resolution (#7763)
Previous PR #7758 accidentally removed the setting of 'resolution'
for shaders other than 'blitLow'.  This PR undoes that.
2024-04-16 09:52:43 -07:00
Powei Feng c20496a461 Remove unused parameter in blitLow (#7758) 2024-04-15 16:19:54 -07:00
tomnomgoogleuk 8955a61df3 Set VkApplicationInfo.pEngineName to Filament (#7756)
Setting the pEngineName helps with the collection of metrics and is generally good practice.
2024-04-15 19:42:36 +00:00
Powei Feng 9e0e3262c3 Release Filament 1.51.4 2024-04-15 11:05:12 -07:00
Mathias Agopian d3d5281488 cleanup the Material constructor
We move some large blocks of code out of line.
2024-04-15 10:27:07 -07:00
Mathias Agopian 075f3d0869 fix another ibl-prefilter bug introduced recently
The top and bottom faces were swapped in the reflection map.

FIXES=[333421281]
2024-04-15 10:25:52 -07:00
Mathias Agopian 3ca9b9cd0f don't use thread affinity for the backend thread
Originally we did this because we wanted to run on a big core on 
android. However setting the thread affinity in this way is fragile,
we are not guaranteed to be on a big core, and we don't even know
if some thread is pinned to that core already; which was the case
with some GL drivers. This can also cause scheduling problems with
other threads.

We just remove this logic entirely for now, and we'll figure out 
something better later to run on a big core.

Fixes #7748
BUGS=[333949404]
2024-04-12 15:55:56 -07:00
Mathias Agopian eb33ce35ef make the Systrace class public
BUGS=[331834298]
2024-04-12 14:44:48 -07:00
Ben Doherty cddd22da28 Introduce a new precompiled Metal material chunk type (#7749)
This change introduces a new chunk type to material files for precompiled Metal libraries. Previously, SPIR-V was the only binary type, so there's also a couple of refactor commits present here. Nothing is changed in Filament or matc yet.

BUGS=[333547148]
2024-04-12 00:42:10 -04:00
Powei Feng 05ce5ead8d vk: Use new descriptor set caching (#7735)
- Use new descriptor set and layout caching
 - Remove descriptor set related code in VulkanPipelineCache
 -  fix leaks for descriptor sets/layouts

FIXES=248594812,325157400
2024-04-09 23:29:27 +00:00
Sungun Park 4bed88289f Release Filament 1.51.3 2024-04-08 20:49:02 +00:00
Ben Doherty 7a6138597e Metal re-apply: throw an NSException when attempting to draw with invalid program (#7741) 2024-04-05 17:50:07 -07:00
Ben Doherty 28da328815 Export utils::panic function (#7740) 2024-04-05 17:49:56 -07:00
Mathias Agopian de3c4b6d56 fix a possible leak when editing a material with matdbg
A MaterialParser could be leaked if several edits happened before they
were latched -- this was because the MaterialParser was stored as
a raw pointer instead of a unique_ptr<>, this was done as an attempt
to avoid to use a lock around accessing mPendingEdits.
2024-04-04 10:15:43 -07:00
Mathias Agopian 5d27240e0c clean-up lifetime of MaterialParser 2024-04-04 10:15:43 -07:00
Mathias Agopian 94fc37b8c2 fix missing headers 2024-04-04 10:15:43 -07:00
Powei Feng 532cb28ce9 vk: Introduce caches for descriptor sets/layouts (#7731)
Added
 - Cache for layouts
 - Pools for descriptor sets
 - Cache for descriptor set updates
 - Cache for pipeline layouts

Does not have effect on implementation.
2024-04-03 17:51:42 +00:00
Mathias Agopian b30d12aca5 split GLTexture and GLBufferObject to their own file 2024-04-02 23:34:30 -07:00
Mathias Agopian d891a6927f program is now 48 bytes instead of 56 2024-04-02 23:34:30 -07:00
Mathias Agopian 22b5715837 move mSamplerMap and ES2's mUniformBindings to OpenGLContext 2024-04-02 23:34:30 -07:00
Powei Feng 7eb08f11ea vk: add descriptor set/layout types to handles (#7728)
- Move VulkanDescriptorSet to VulkanHandles.h
 - Add VulkanDescriptorSetLayout to VulkanHandles.h
 - Add "input" descriptor set types to VulkanUtility.h. These are
   structs that will be defined in the backend API (shared across
   all backends) and eventually passed from the front-end to the
   vk backend.
 - Logic to parse descriptor set layout from the spirv-v shaders.
 - Move UsageFlags type to VulkanUtility.h
 - Just prep work. No effect to current implementations.
2024-04-02 20:59:24 +00:00
Mathias Agopian 588a05f77c Fix point/spot shadow rendering bug
We were calculating the shadow visibility of spot/point lights. The
visibility was calculated during the "execute" phase of the FrameGraph
but it was used/needed during the setup phase. The result was that
the visibility was always delayed by one frame (really it was stale 
data from the previous calculation).

We are now computing the shadow visibility earlier, during the
setup phase. This is also better because we can now skip culling
of these shadow maps entirely if we know they're not visible.

Fixes #7715
2024-04-01 17:09:34 -07:00
Benjamin Doherty 3ec2b47df7 Release Filament 1.51.2 2024-04-01 16:44:33 -07:00
Mathias Agopian 1c55ad49ee add support for custom blend functions
BUGS=[331610785]
2024-04-01 13:40:34 -07:00
Mathias Agopian 348e61fd76 remove unused code 2024-04-01 13:40:34 -07:00
Mathias Agopian 8ce6d54930 remove wrong noexcept in the backend
going forward we want to be able to throw exceptions from the backend
at the very least, we need to be consistant, currently we are
potentially throwing exceptions from `noexcept` places.

this changes makes it possible to throw exceptions from the backend,
during handle construction and conversion to pointers, which wasn't 
allowed before.

We still can't throw from dtors because it's generally a bad idea, 
better abort in that case.
2024-03-29 14:59:43 -07:00
Mathias Agopian 8db5e700d7 GL backend should only allocate renderbuffers when possible
There was several cases where the the gl backend would wrongly  use a 
renderbuffer instead of a texture.


BUGS=[329491941]
2024-03-29 14:59:29 -07:00
Mathias Agopian 0ec3596440 the user can now set a panic handler callback
BUGS=[331457244]
2024-03-29 12:25:06 -07:00
Ben Doherty ee4c03d0f1 Fix WebGL build with newer Emscripten (#7721) 2024-03-28 14:15:32 -07:00
Powei Feng f12e7f9fbf Release Filament 1.51.1 2024-03-28 11:47:52 -07:00
Nimrod Gileadi a068143953 Add semicolons to code snippet in AssetLoader.h (#7717) 2024-03-27 17:24:10 +00:00
Powei Feng 90d90094dc Bump material version to 51 in MaterialEnums.h 2024-03-26 14:37:13 -07:00
Sungun Park 54df4524eb Improve multiview shader replacement (#7706)
Replace the num_views for OpenGL multiviwe only when
- The engine is initialized with multiview stereo
- The variant for the material contains STE flag
- The program is for surface
- It's vertex shader (this is already in)
2024-03-26 21:36:22 +00:00
Powei Feng d70f2e1b81 vk: delete instead of ref-count EmptyTexture (#7711) 2024-03-26 13:35:50 -07:00
Powei Feng 2f359b73a4 Add missing include to Platform.h (#7709) 2024-03-26 19:51:57 +00:00
Sungun Park abb0cbc98e Add FILAMENT_ENABLE_MULTIVIEW option (#7707)
This allows the engine to include multiview shader code for default
materials.
2024-03-25 21:43:04 +00:00
Jacob Su 2763931b47 fix install android samples apk error. (#7230) 2024-03-22 23:05:01 -07:00
Sungun Park 0ead96b606 Replace value of num_views with engine's eye count (#7696)
For OpenGL multiview, it honors the qualifier `layout(num_views = X)`
specified in shader files to determine the number of views for
multiview.

We cannot recompile materials everytime the value changes. So replace
the value of num_views with the engine's eye count when shaders compile.
2024-03-22 21:45:58 +00:00
Ben Doherty 317c1bb7ea Metal: track buffer allocations (#7556) 2024-03-22 12:47:56 -07:00
Ben Doherty 9aaaad9271 Improve Skybox eyeDirection precision (#7685) 2024-03-22 12:03:06 -07:00
Mathias Agopian d2ce714e73 fix use-after-free disable option 2024-03-22 11:27:30 -07:00
Mathias Agopian 8398175d9c Add an option to disable use-after-free checks in the backend
BUGS=330403836
2024-03-22 10:24:29 -07:00
Sungun Park 26258a4718 Facilitate four views for multiview (#7694)
This change allows the combine function for multiview to be able to
combine more than two views side-by-side.
2024-03-22 17:09:08 +00:00
Mathias Agopian 3644e7f808 Fixes IBL prefilter has floor and ceiling flipped
FIXES=[330603077]
2024-03-21 13:28:24 -07:00
Powei Feng 4d774820d9 gl: Don't reference swapchain obj on Web
Fixes #7693
2024-03-21 13:27:45 -07:00
Eliza Velasquez aad45d9119 Fix typo 2024-03-21 19:59:47 +00:00
Eliza Velasquez 5389b37002 Incorporate feedback 2024-03-21 19:59:47 +00:00
Eliza Velasquez d5fe9e236f Allow rendering thread to pause
This PR adds a new `pause()` option to the `Engine` `Builder` and a new function
`setPaused()` to the `Engine`. While paused, the rendering thread will pause
indefinitely for commands as if none are available. As soon as the rendering
thread is unpaused, the commands are immediately executed.
2024-03-21 19:59:47 +00:00
Mathias Agopian 59890ac85a PlatformEGL::createSwapChain never returns a nullptr anymore
in case the swapchain creation fails, it will now return a swapchain
with an EGL_NO_SURFACE handle. this will avoid having to nullptr check
the pointer in various places and will revert to the previous behavior
on failure.

FIXES=[329659681]
2024-03-20 16:07:02 -07:00
Mathias Agopian 435969f565 attempt to detect buffer overflows in Texture::setImage()
We verify that the buffer given to setImage() is at least as large
as needed for the given region to transfer; at least based on the
size given.

This might help catch b/330407429.

BUGS=330407429
2024-03-20 16:06:43 -07:00
Ben Doherty e3db39105f iOS: fix gltf-viewer crash while destruction ResourceLoader (#7684) 2024-03-19 16:14:45 -07:00
Powei Feng dfa821d351 vk: fix broken gltf renderings (#7680)
This is due to color attachments being set to store=discard when
they are multi-sampled. It's unclear why that condition exists. For
now, removing it will fix the rendering issues with transparent
object + MSAA. We'll keep it as such until an issue surfaces.

Fixes #7674
2024-03-19 22:38:52 +00:00
Powei Feng 1ab223c965 vk: minor fixes (#7682)
- Add description to a few debug options
- Set correct usage flag for blit-src/dest images
- Correctly initialize a debug-only field in VulkanProgram
2024-03-19 13:59:52 -07:00
Mathias Agopian 29bb60cd94 fix typo that caused a wrong assertion 2024-03-19 13:13:12 -07:00
Mathias Agopian 93b15dac87 The type of geometry of a renderable can now be specified
- dynamic (default) no restriction apply
- static bounds: bounds and world transform can't be changed
- static: additionaly morphing/skinning and vertex/index buffers are
  immutable.

This will allow some optimizations in the future. Currently, we just
store the type but don't do anything with it.
2024-03-19 11:12:25 -07:00
Romain Guy a8fda9b4d0 Make PBR Neutral invertible (#7677)
Based on model-viewer's change at
https://github.com/google/model-viewer/pull/4716
2024-03-19 11:11:44 -07:00
262 changed files with 9921 additions and 4534 deletions
+13 -19
View File
@@ -73,7 +73,6 @@ The following CMake options are boolean options specific to Filament:
- `FILAMENT_SUPPORTS_VULKAN`: Include the Vulkan backend
- `FILAMENT_INSTALL_BACKEND_TEST`: Install the backend test library so it can be consumed on iOS
- `FILAMENT_USE_EXTERNAL_GLES3`: Experimental: Compile Filament against OpenGL ES 3
- `FILAMENT_USE_SWIFTSHADER`: Compile Filament against SwiftShader
- `FILAMENT_SKIP_SAMPLES`: Don't build sample apps
To turn an option on or off:
@@ -426,7 +425,7 @@ value is the desired roughness between 0 and 1.
## Generating C++ documentation
To generate the documentation you must first install `doxygen` and `graphviz`, then run the
To generate the documentation you must first install `doxygen` and `graphviz`, then run the
following commands:
```shell
@@ -436,32 +435,27 @@ doxygen docs/doxygen/filament.doxygen
Finally simply open `docs/html/index.html` in your web browser.
## SwiftShader
## Software Rasterization
To try out Filament's Vulkan support with SwiftShader, first build SwiftShader and set the
`SWIFTSHADER_LD_LIBRARY_PATH` variable to the folder that contains `libvk_swiftshader.dylib`:
We have tested swiftshader for running software rasterization on the Vulkan backend. To use this,
please first make sure that the [Vulkan SDK](https://www.lunarg.com/vulkan-sdk/) is installed on
your machine. If you are doing a manual installation of the SDK on Linux, you will have to source
`setup-env.sh` in the SDK's root folder to make sure the Vulkan loader is the first lib loaded.
### Swiftshader (Vulkan) [tested on macOS and Linux]
First, build SwiftShader
```shell
git clone https://github.com/google/swiftshader.git
cd swiftshader/build
cmake .. && make -j
export SWIFTSHADER_LD_LIBRARY_PATH=`pwd`
```
Next, go to your Filament repo and use the [easy build](#easy-build) script with `-t`.
## SwiftShader for CI
Continuous testing turnaround can be quite slow if you need to build SwiftShader from scratch, so we
provide an Ubuntu-based Docker image that has it already built. The Docker image also includes
everything necessary for building Filament. You can fetch and run the image as follows:
and then set `VK_ICD_FILENAMES` to the ICD json produced in the build. For example,
```shell
docker pull ghcr.io/filament-assets/swiftshader
docker run -it ghcr.io/filament-assets/swiftshader
export VK_ICD_FILENAMES=/Users/user/swiftshader/build/Darwin/vk_swiftshader_icd.json
```
To do more with the container, see the helper script at `build/swiftshader/test.sh`.
Build Filament as normal and use the vulkan backend.
If you are a team member, you can update the public image to the latest SwiftShader by
following the instructions at the top of `build/swiftshader/Dockerfile`.
+21 -37
View File
@@ -21,8 +21,6 @@ project(TNT)
# ==================================================================================================
option(FILAMENT_USE_EXTERNAL_GLES3 "Experimental: Compile Filament against OpenGL ES 3" OFF)
option(FILAMENT_USE_SWIFTSHADER "Compile Filament against SwiftShader" OFF)
option(FILAMENT_ENABLE_LTO "Enable link-time optimizations if supported by the compiler" OFF)
option(FILAMENT_SKIP_SAMPLES "Don't build samples" OFF)
@@ -71,6 +69,10 @@ set(FILAMENT_METAL_HANDLE_ARENA_SIZE_IN_MB "8" CACHE STRING
"Size of the Metal handle arena, default 8."
)
set(FILAMENT_BACKEND_DEBUG_FLAG "" CACHE STRING
"A debug flag meant for enabling/disabling backend debugging paths"
)
# Enable exceptions by default in spirv-cross.
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS OFF)
@@ -141,11 +143,6 @@ if (LINUX)
add_definitions(-DFILAMENT_SUPPORTS_XCB)
endif()
# Default Swiftshader build does not enable the xlib extension
if (FILAMENT_SUPPORTS_XLIB AND FILAMENT_USE_SWIFTSHADER)
set(FILAMENT_SUPPORTS_XLIB OFF)
endif()
if (FILAMENT_SUPPORTS_XLIB)
add_definitions(-DFILAMENT_SUPPORTS_XLIB)
endif()
@@ -169,15 +166,6 @@ if (NOT ANDROID AND NOT WEBGL AND NOT IOS AND NOT FILAMENT_LINUX_IS_MOBILE)
set(IS_HOST_PLATFORM TRUE)
endif()
if (IOS)
# Remove the headerpad_max_install_names linker flag on iOS. It causes warnings when linking
# executables with bitcode.
string(REPLACE "-Wl,-headerpad_max_install_names" "" CMAKE_C_LINK_FLAGS ${CMAKE_C_LINK_FLAGS})
string(REPLACE "-Wl,-headerpad_max_install_names" "" CMAKE_CXX_LINK_FLAGS ${CMAKE_CXX_LINK_FLAGS})
string(REPLACE "-Wl,-headerpad_max_install_names" "" CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS ${CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS})
string(REPLACE "-Wl,-headerpad_max_install_names" "" CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS ${CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS})
endif()
if (WIN32)
# Link statically against c/c++ lib to avoid missing redistriburable such as
# "VCRUNTIME140.dll not found. Try reinstalling the app.", but give users
@@ -332,10 +320,6 @@ if (FILAMENT_SUPPORTS_EGL_ON_LINUX)
set(EGL TRUE)
endif()
if (FILAMENT_USE_SWIFTSHADER)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DFILAMENT_USE_SWIFTSHADER")
endif()
if (WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_USE_MATH_DEFINES=1")
endif()
@@ -389,8 +373,9 @@ endif()
if (NOT MSVC AND NOT IOS)
# Omitting stack frame pointers prevents the generation of readable stack traces in crash reports on iOS
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fomit-frame-pointer")
endif()
# These aren't compatible with -fembed-bitcode (and seem to have no effect on Apple platforms anyway)
if (NOT MSVC)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffunction-sections -fdata-sections")
endif()
@@ -455,8 +440,13 @@ endif()
if (NOT WEBGL)
set(GC_SECTIONS "-Wl,--gc-sections")
endif()
set(B_SYMBOLIC_FUNCTIONS "-Wl,-Bsymbolic-functions")
if (ANDROID)
set(BINARY_ALIGNMENT "-Wl,-z,max-page-size=16384")
endif()
if (APPLE)
set(GC_SECTIONS "-Wl,-dead_strip")
set(B_SYMBOLIC_FUNCTIONS "")
@@ -470,7 +460,7 @@ if (APPLE)
endif()
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GC_SECTIONS}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GC_SECTIONS} ${B_SYMBOLIC_FUNCTIONS}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GC_SECTIONS} ${B_SYMBOLIC_FUNCTIONS} ${BINARY_ALIGNMENT}")
if (WEBGL_PTHREADS)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pthread")
@@ -548,6 +538,12 @@ if (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview")
set(FILAMENT_ENABLE_MULTIVIEW ON)
endif ()
# Define backend flag for debug only
if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT FILAMENT_BACKEND_DEBUG_FLAG STREQUAL "")
add_definitions(-DFILAMENT_BACKEND_DEBUG_FLAG=${FILAMENT_BACKEND_DEBUG_FLAG})
unset(FILAMENT_BACKEND_DEBUG_FLAG)
endif()
# ==================================================================================================
# Material compilation flags
# ==================================================================================================
@@ -676,21 +672,6 @@ else()
set(IMPORT_EXECUTABLES ${FILAMENT}/${IMPORT_EXECUTABLES_DIR}/ImportExecutables-${CMAKE_BUILD_TYPE}.cmake)
endif()
# ==================================================================================================
# Try to find Vulkan if the SDK is installed, otherwise fall back to the bundled version.
# This needs to stay in our top-level CMakeLists because it sets up variables that are used by the
# "bluevk" and "samples" targets.
# ==================================================================================================
if (FILAMENT_USE_SWIFTSHADER)
if (NOT FILAMENT_SUPPORTS_VULKAN)
message(ERROR "SwiftShader is only useful when Vulkan is enabled.")
endif()
find_library(SWIFTSHADER_VK NAMES vk_swiftshader HINTS "$ENV{SWIFTSHADER_LD_LIBRARY_PATH}")
message(STATUS "Found SwiftShader VK library in: ${SWIFTSHADER_VK}.")
add_definitions(-DFILAMENT_VKLIBRARY_PATH=\"${SWIFTSHADER_VK}\")
endif()
# ==================================================================================================
# Common Functions
# ==================================================================================================
@@ -819,6 +800,9 @@ if (IS_HOST_PLATFORM)
add_subdirectory(${TOOLS}/glslminifier)
add_subdirectory(${TOOLS}/matc)
add_subdirectory(${TOOLS}/matinfo)
if (NOT WIN32) # matedit not yet supported on Windows
add_subdirectory(${TOOLS}/matedit)
endif()
add_subdirectory(${TOOLS}/mipgen)
add_subdirectory(${TOOLS}/normal-blending)
add_subdirectory(${TOOLS}/resgen)
-2
View File
@@ -7,5 +7,3 @@ for next branch cut* header.
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- engine: Add experimental APIs `Engine::builder::paused()` and `Engine::setPaused()`
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.51.1'
implementation 'com.google.android.filament:filament-android:1.51.8'
}
```
@@ -51,7 +51,7 @@ 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.51.1'
pod 'Filament', '~> 1.51.8'
```
### Snapshots
+35
View File
@@ -7,6 +7,41 @@ 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.51.9
## v1.51.8
- filagui: Fix regression which broke WebGL
- Add a new Engine::Config setting to control preferred shader language
- Add `getEyeIndex` vertex API
- ios: Remove bitcode from iOS builds
## v1.51.7
- Add new matedit tool
- filagui: Support rendering `GL_TEXTURE_EXTERNAL_OES` textures.
- `setFrameScheduledCallback` now takes a `utils::Invocable`.
- engine: Add `isPaused()`
## v1.51.6
- Add new matedit tool
- filagui: Support rendering `GL_TEXTURE_EXTERNAL_OES` textures.
## v1.51.5
## v1.51.4
## v1.51.3
## v1.51.2
- engine: Add experimental APIs `Engine::builder::paused()` and `Engine::setPaused()`
## v1.51.1
+1
View File
@@ -38,6 +38,7 @@ set(FILAMAT_INCLUDE_DIRS
include_directories(${FILAMENT_DIR}/include)
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_SOURCE_DIR}/libfilamat-jni.map")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
add_library(filamat-jni SHARED src/main/cpp/MaterialBuilder.cpp)
target_include_directories(filamat-jni PRIVATE ${FILAMAT_INCLUDE_DIRS})
+1
View File
@@ -59,6 +59,7 @@ endif()
set(VERSION_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/libfilament-jni.map")
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${VERSION_SCRIPT}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
add_library(filament-jni SHARED
src/main/cpp/BufferObject.cpp
@@ -342,6 +342,21 @@ Java_com_google_android_filament_Engine_nIsValidMaterial(JNIEnv*, jclass,
return (jboolean)engine->isValid((Material*)nativeMaterial);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nIsValidMaterialInstance(JNIEnv*, jclass,
jlong nativeEngine, jlong nativeMaterial, jlong nativeMaterialInstance) {
Engine* engine = (Engine *)nativeEngine;
return (jboolean)engine->isValid((Material*)nativeMaterial,
(MaterialInstance*)nativeMaterialInstance);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nIsValidExpensiveMaterialInstance(JNIEnv*, jclass,
jlong nativeEngine, jlong nativeMaterialInstance) {
Engine* engine = (Engine *)nativeEngine;
return (jboolean)engine->isValidExpensive((MaterialInstance*)nativeMaterialInstance);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nIsValidSkybox(JNIEnv*, jclass,
jlong nativeEngine, jlong nativeSkybox) {
@@ -391,6 +406,13 @@ Java_com_google_android_filament_Engine_nFlush(JNIEnv*, jclass,
engine->flush();
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nIsPaused(JNIEnv*, jclass,
jlong nativeEngine) {
Engine* engine = (Engine*) nativeEngine;
return (jboolean)engine->isPaused();
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Engine_nSetPaused(JNIEnv*, jclass,
jlong nativeEngine, jboolean paused) {
@@ -495,7 +517,9 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
jlong textureUseAfterFreePoolSize, jboolean disableParallelShaderCompile,
jint stereoscopicType, jlong stereoscopicEyeCount,
jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge,
jboolean disableHandleUseAfterFreeCheck) {
jboolean disableHandleUseAfterFreeCheck,
jint preferredShaderLanguage,
jboolean forceGLES2Context) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
Engine::Config config = {
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
@@ -511,6 +535,8 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
.resourceAllocatorCacheSizeMB = (uint32_t) resourceAllocatorCacheSizeMB,
.resourceAllocatorCacheMaxAge = (uint8_t) resourceAllocatorCacheMaxAge,
.disableHandleUseAfterFreeCheck = (bool) disableHandleUseAfterFreeCheck,
.preferredShaderLanguage = (Engine::Config::ShaderLanguage) preferredShaderLanguage,
.forceGLES2Context = (bool) forceGLES2Context,
};
builder->config(&config);
}
@@ -104,6 +104,14 @@ Java_com_google_android_filament_RenderableManager_nBuilderGeometry__JIIJJIIII(J
(size_t) count);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_RenderableManager_nBuilderGeometryType(JNIEnv*, jclass,
jlong nativeBuilder, int type) {
RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder;
builder->geometryType((RenderableManager::Builder::GeometryType)type);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_RenderableManager_nBuilderMaterial(JNIEnv*, jclass,
@@ -49,6 +49,12 @@ Java_com_google_android_filament_View_nSetCamera(JNIEnv*, jclass,
view->setCamera(camera);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_View_nHasCamera(JNIEnv*, jclass, jlong nativeView) {
View* view = (View*) nativeView;
return (jboolean)view->hasCamera();
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nSetColorGrading(JNIEnv*, jclass,
jlong nativeView, jlong nativeColorGrading) {
@@ -225,7 +225,9 @@ public class Engine {
config.textureUseAfterFreePoolSize, config.disableParallelShaderCompile,
config.stereoscopicType.ordinal(), config.stereoscopicEyeCount,
config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge,
config.disableHandleUseAfterFreeCheck);
config.disableHandleUseAfterFreeCheck,
config.preferredShaderLanguage.ordinal(),
config.forceGLES2Context);
return this;
}
@@ -243,9 +245,11 @@ public class Engine {
/**
* Sets the initial paused state of the rendering thread.
*
* <p>Warning: This is an experimental API. See {@link Engine#setPaused(boolean)} for
* caveats.
*
* @param paused Whether to start the rendering thread paused.
* @return A reference to this Builder for chaining calls.
* @warning Experimental.
*/
public Builder paused(boolean paused) {
nSetBuilderPaused(mNativeBuilder, paused);
@@ -426,6 +430,35 @@ public class Engine {
* Disable backend handles use-after-free checks.
*/
public boolean disableHandleUseAfterFreeCheck = false;
/*
* Sets a preferred shader language for Filament to use.
*
* The Metal backend supports two shader languages: MSL (Metal Shading Language) and
* METAL_LIBRARY (precompiled .metallib). This option controls which shader language is
* used when materials contain both.
*
* By default, when preferredShaderLanguage is unset, Filament will prefer METAL_LIBRARY
* shaders if present within a material, falling back to MSL. Setting
* preferredShaderLanguage to ShaderLanguage::MSL will instead instruct Filament to check
* for the presence of MSL in a material first, falling back to METAL_LIBRARY if MSL is not
* present.
*
* When using a non-Metal backend, setting this has no effect.
*/
public enum ShaderLanguage {
DEFAULT,
MSL,
METAL_LIBRARY,
};
public ShaderLanguage preferredShaderLanguage = ShaderLanguage.DEFAULT;
/*
* When the OpenGL ES backend is used, setting this value to true will force a GLES2.0
* context if supported by the Platform, or if not, will have the backend pretend
* it's a GLES2 context. Ignored on other backends.
*/
public boolean forceGLES2Context = false;
}
private Engine(long nativeEngine, Config config) {
@@ -847,6 +880,24 @@ public class Engine {
return nIsValidMaterial(getNativeObject(), object.getNativeObject());
}
/**
* Returns whether the object is valid.
* @param object Object to check for validity
* @return returns true if the specified object is valid.
*/
public boolean isValidMaterialInstance(@NonNull Material ma, MaterialInstance mi) {
return nIsValidMaterialInstance(getNativeObject(), ma.getNativeObject(), mi.getNativeObject());
}
/**
* Returns whether the object is valid.
* @param object Object to check for validity
* @return returns true if the specified object is valid.
*/
public boolean isValidExpensiveMaterialInstance(@NonNull MaterialInstance object) {
return nIsValidExpensiveMaterialInstance(getNativeObject(), object.getNativeObject());
}
/**
* Returns whether the object is valid.
* @param object Object to check for validity
@@ -1207,9 +1258,29 @@ public class Engine {
nFlush(getNativeObject());
}
/**
* Get paused state of rendering thread.
*
* <p>Warning: This is an experimental API.
*
* @see #setPaused
*/
public boolean isPaused() {
return nIsPaused(getNativeObject());
}
/**
* Pause or resume the rendering thread.
* @warning Experimental.
*
* <p>Warning: This is an experimental API. In particular, note the following caveats.
*
* <ul><li>
* Buffer callbacks will never be called as long as the rendering thread is paused.
* Do not rely on a buffer callback to unpause the thread.
* </li><li>
* While the rendering thread is paused, rendering commands will continue to be queued until the
* buffer limit is reached. When the limit is reached, the program will abort.
* </li></ul>
*/
public void setPaused(boolean paused) {
nSetPaused(getNativeObject(), paused);
@@ -1280,6 +1351,8 @@ public class Engine {
private static native boolean nIsValidSkinningBuffer(long nativeEngine, long nativeSkinningBuffer);
private static native boolean nIsValidIndirectLight(long nativeEngine, long nativeIndirectLight);
private static native boolean nIsValidMaterial(long nativeEngine, long nativeMaterial);
private static native boolean nIsValidMaterialInstance(long nativeEngine, long nativeMaterial, long nativeMaterialInstance);
private static native boolean nIsValidExpensiveMaterialInstance(long nativeEngine, long nativeMaterialInstance);
private static native boolean nIsValidSkybox(long nativeEngine, long nativeSkybox);
private static native boolean nIsValidColorGrading(long nativeEngine, long nativeColorGrading);
private static native boolean nIsValidTexture(long nativeEngine, long nativeTexture);
@@ -1288,6 +1361,7 @@ public class Engine {
private static native void nDestroyEntity(long nativeEngine, int entity);
private static native void nFlushAndWait(long nativeEngine);
private static native void nFlush(long nativeEngine);
private static native boolean nIsPaused(long nativeEngine);
private static native void nSetPaused(long nativeEngine, boolean paused);
private static native long nGetTransformManager(long nativeEngine);
private static native long nGetLightManager(long nativeEngine);
@@ -1310,7 +1384,9 @@ public class Engine {
long textureUseAfterFreePoolSize, boolean disableParallelShaderCompile,
int stereoscopicType, long stereoscopicEyeCount,
long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge,
boolean disableHandleUseAfterFreeCheck);
boolean disableHandleUseAfterFreeCheck,
int preferredShaderLanguage,
boolean forceGLES2Context);
private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal);
private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
private static native void nSetBuilderPaused(long nativeBuilder, boolean paused);
@@ -175,6 +175,32 @@ public class RenderableManager {
return this;
}
/**
* Type of geometry for a Renderable
*/
public enum GeometryType {
/** dynamic gemoetry has no restriction */
DYNAMIC,
/** bounds and world space transform are immutable */
STATIC_BOUNDS,
/** skinning/morphing not allowed and Vertex/IndexBuffer immutables */
STATIC
}
/**
* Specify whether this renderable has static bounds. In this context his means that
* the renderable's bounding box cannot change and that the renderable's transform is
* assumed immutable. Changing the renderable's transform via the TransformManager
* can lead to corrupted graphics. Note that skinning and morphing are not forbidden.
* Disabled by default.
* @param enable whether this renderable has static bounds. false by default.
*/
@NonNull
public Builder geometryType(GeometryType type) {
nBuilderGeometryType(mNativeBuilder, type.ordinal());
return this;
}
/**
* Binds a material instance to the specified primitive.
*
@@ -964,6 +990,7 @@ public class RenderableManager {
private static native void nBuilderGeometry(long nativeBuilder, int index, int value, long nativeVertexBuffer, long nativeIndexBuffer);
private static native void nBuilderGeometry(long nativeBuilder, int index, int value, long nativeVertexBuffer, long nativeIndexBuffer, int offset, int count);
private static native void nBuilderGeometry(long nativeBuilder, int index, int value, long nativeVertexBuffer, long nativeIndexBuffer, int offset, int minIndex, int maxIndex, int count);
private static native void nBuilderGeometryType(long nativeBuilder, int type);
private static native void nBuilderMaterial(long nativeBuilder, int index, long nativeMaterialInstance);
private static native void nBuilderBlendOrder(long nativeBuilder, int index, int blendOrder);
private static native void nBuilderGlobalBlendOrderEnabled(long nativeBuilder, int index, boolean enabled);
@@ -241,6 +241,15 @@ public class View {
nSetCamera(getNativeObject(), camera == null ? 0 : camera.getNativeObject());
}
/**
* Query whether a camera is set.
* @return true if a camera is set, false otherwise
* @see #setCamera
*/
public boolean hasCamera() {
return nHasCamera(getNativeObject());
}
/**
* Gets this View's associated Camera, or null if none has been assigned.
*
@@ -1238,6 +1247,7 @@ public class View {
private static native void nSetName(long nativeView, String name);
private static native void nSetScene(long nativeView, long nativeScene);
private static native void nSetCamera(long nativeView, long nativeCamera);
private static native boolean nHasCamera(long nativeView);
private static native void nSetViewport(long nativeView, int left, int bottom, int width, int height);
private static native void nSetVisibleLayers(long nativeView, int select, int value);
private static native void nSetShadowingEnabled(long nativeView, boolean enabled);
@@ -31,6 +31,7 @@ set_target_properties(iblprefilter PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilament-iblprefilter.a)
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libfilament-utils-jni.map")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
add_library(filament-utils-jni SHARED
src/main/cpp/AutomationEngine.cpp
+4
View File
@@ -44,6 +44,7 @@ set_target_properties(uberarchive PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libuberarchive.a)
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.map")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
set(GLTFIO_SRCS
${GLTFIO_DIR}/include/gltfio/Animator.h
@@ -86,7 +87,10 @@ set(GLTFIO_SRCS
${GLTFIO_DIR}/src/Wireframe.cpp
${GLTFIO_DIR}/src/Wireframe.h
${GLTFIO_DIR}/src/downcast.h
${GLTFIO_DIR}/src/extended/AssetLoaderExtended.cpp
${GLTFIO_DIR}/src/extended/AssetLoaderExtended.h
${GLTFIO_DIR}/src/extended/ResourceLoaderExtended.cpp
${GLTFIO_DIR}/src/extended/ResourceLoaderExtended.h
${GLTFIO_DIR}/src/extended/TangentsJobExtended.cpp
${GLTFIO_DIR}/src/extended/TangentsJobExtended.h
${GLTFIO_DIR}/src/extended/TangentSpaceMeshWrapper.cpp
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.51.1
VERSION_NAME=1.51.8
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -321,7 +321,7 @@ class MainActivity : Activity() {
var flags = uiHelper.swapChainFlags
if (engine.activeFeatureLevel == Engine.FeatureLevel.FEATURE_LEVEL_0) {
if (SwapChain.isSRGBSwapChainSupported(engine)) {
flags = flags or SwapChain.CONFIG_SRGB_COLORSPACE
flags = flags or SwapChainFlags.CONFIG_SRGB_COLORSPACE
}
}
+23 -12
View File
@@ -44,8 +44,6 @@ function print_help {
echo " Exclude Vulkan support from the Android build."
echo " -s"
echo " Add iOS simulator support to the iOS build."
echo " -t"
echo " Enable SwiftShader support for Vulkan in desktop builds."
echo " -e"
echo " Enable EGL on Linux support for desktop builds."
echo " -l"
@@ -61,6 +59,11 @@ function print_help {
echo " -b"
echo " Enable Address and Undefined Behavior Sanitizers (asan/ubsan) for debugging."
echo " This is only for the desktop build."
echo " -x value"
echo " Define a preprocessor flag FILAMENT_BACKEND_DEBUG_FLAG with [value]. This is useful for"
echo " enabling debug paths in the backend from the build script. For example, make a"
echo " systrace-enabled build without directly changing #defines. Remember to add -f when"
echo " changing this option."
echo ""
echo "Build types:"
echo " release"
@@ -160,8 +163,6 @@ INSTALL_COMMAND=
VULKAN_ANDROID_OPTION="-DFILAMENT_SUPPORTS_VULKAN=ON"
VULKAN_ANDROID_GRADLE_OPTION=""
SWIFTSHADER_OPTION="-DFILAMENT_USE_SWIFTSHADER=OFF"
EGL_ON_LINUX_OPTION="-DFILAMENT_SUPPORTS_EGL_ON_LINUX=OFF"
MATDBG_OPTION="-DFILAMENT_ENABLE_MATDBG=OFF"
@@ -172,6 +173,8 @@ MATOPT_GRADLE_OPTION=""
ASAN_UBSAN_OPTION=""
BACKEND_DEBUG_FLAG_OPTION=""
IOS_BUILD_SIMULATOR=false
BUILD_UNIVERSAL_LIBRARIES=false
@@ -226,11 +229,11 @@ function build_desktop_target {
-DIMPORT_EXECUTABLES_DIR=out \
-DCMAKE_BUILD_TYPE="$1" \
-DCMAKE_INSTALL_PREFIX="../${lc_target}/filament" \
${SWIFTSHADER_OPTION} \
${EGL_ON_LINUX_OPTION} \
${MATDBG_OPTION} \
${MATOPT_OPTION} \
${ASAN_UBSAN_OPTION} \
${BACKEND_DEBUG_FLAG_OPTION} \
${architectures} \
../..
ln -sf "out/cmake-${lc_target}/compile_commands.json" \
@@ -289,6 +292,7 @@ function build_webgl_with_target {
-DCMAKE_BUILD_TYPE="$1" \
-DCMAKE_INSTALL_PREFIX="../webgl-${lc_target}/filament" \
-DWEBGL=1 \
${BACKEND_DEBUG_FLAG_OPTION} \
../..
ln -sf "out/cmake-webgl-${lc_target}/compile_commands.json" \
../../compile_commands.json
@@ -363,6 +367,7 @@ function build_android_target {
${MATDBG_OPTION} \
${MATOPT_OPTION} \
${VULKAN_ANDROID_OPTION} \
${BACKEND_DEBUG_FLAG_OPTION} \
../..
ln -sf "out/cmake-android-${lc_target}-${arch}/compile_commands.json" \
../../compile_commands.json
@@ -516,7 +521,7 @@ function build_android {
if [[ "${BUILD_ANDROID_SAMPLES}" == "true" ]]; then
for sample in ${ANDROID_SAMPLES}; do
echo "Installing out/${sample}-debug.apk"
cp samples/${sample}/build/outputs/apk/debug/${sample}-debug-unsigned.apk \
cp samples/${sample}/build/outputs/apk/debug/${sample}-debug.apk \
../out/${sample}-debug.apk
done
fi
@@ -597,6 +602,7 @@ function build_ios_target {
-DCMAKE_TOOLCHAIN_FILE=../../third_party/clang/iOS.cmake \
${MATDBG_OPTION} \
${MATOPT_OPTION} \
${BACKEND_DEBUG_FLAG_OPTION} \
../..
ln -sf "out/cmake-ios-${lc_target}-${arch}/compile_commands.json" \
../../compile_commands.json
@@ -730,6 +736,13 @@ function validate_build_command {
exit 1
fi
fi
# Make sure FILAMENT_BACKEND_DEBUG_FLAG is only meant for debug builds
if [[ "${ISSUE_DEBUG_BUILD}" != "true" ]] && [[ ! -z "${BACKEND_DEBUG_FLAG_OPTION}" ]]; then
echo "Error: cannot specify FILAMENT_BACKEND_DEBUG_FLAG in non-debug build"
exit 1
fi
set -e
}
@@ -776,7 +789,7 @@ function check_debug_release_build {
pushd "$(dirname "$0")" > /dev/null
while getopts ":hacCfgijmp:q:uvslwtedk:b" opt; do
while getopts ":hacCfgijmp:q:uvslwedk:bx:" opt; do
case ${opt} in
h)
print_help
@@ -840,7 +853,7 @@ while getopts ":hacCfgijmp:q:uvslwtedk:b" opt; do
echo "Platform must be one of [desktop|android|ios|webgl|all]"
echo ""
exit 1
;;
;;
esac
done
;;
@@ -895,10 +908,6 @@ while getopts ":hacCfgijmp:q:uvslwtedk:b" opt; do
IOS_BUILD_SIMULATOR=true
echo "iOS simulator support enabled."
;;
t)
SWIFTSHADER_OPTION="-DFILAMENT_USE_SWIFTSHADER=ON"
echo "SwiftShader support enabled."
;;
e)
EGL_ON_LINUX_OPTION="-DFILAMENT_SUPPORTS_EGL_ON_LINUX=ON -DFILAMENT_SKIP_SDL2=ON -DFILAMENT_SKIP_SAMPLES=ON"
echo "EGL on Linux support enabled; skipping SDL2."
@@ -918,6 +927,8 @@ while getopts ":hacCfgijmp:q:uvslwtedk:b" opt; do
b) ASAN_UBSAN_OPTION="-DFILAMENT_ENABLE_ASAN_UBSAN=ON"
echo "Enabled ASAN/UBSAN"
;;
x) BACKEND_DEBUG_FLAG_OPTION="-DFILAMENT_BACKEND_DEBUG_FLAG=${OPTARG}"
;;
\?)
echo "Invalid option: -${OPTARG}" >&2
echo ""
-53
View File
@@ -1,53 +0,0 @@
# Build the image:
# docker build --no-cache --tag ssfilament -f build/swiftshader/Dockerfile .
# docker tag ssfilament ghcr.io/filament-assets/swiftshader
#
# Publish the image:
# docker login ghcr.io --username <user> --password <token>
# docker push ghcr.io/filament-assets/swiftshader
#
# Run the image and mount the current directory:
# docker run -it -v `pwd`:/trees/filament -t ssfilament
FROM ubuntu:focal
WORKDIR /trees
ARG DEBIAN_FRONTEND=noninteractive
ENV SWIFTSHADER_LD_LIBRARY_PATH=/trees/swiftshader/build
ENV CXXFLAGS='-fno-builtin -Wno-pass-failed'
RUN apt-get update && \
apt-get --no-install-recommends install -y \
apt-transport-https \
apt-utils \
build-essential \
cmake \
ca-certificates \
git \
ninja-build \
python \
python3 \
xorg-dev \
clang-7 \
libc++-7-dev \
libc++abi-7-dev \
lldb
# Ensure that clang is used instead of gcc.
RUN set -eux ;\
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-7 100 ;\
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-7 100 ;\
update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100 ;\
update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100
# Get patch files from the local Filament tree.
COPY build/swiftshader/*.diff .
# Clone SwiftShader, apply patches, and build it.
RUN set -eux ;\
git clone https://swiftshader.googlesource.com/SwiftShader swiftshader ;\
cd swiftshader ;\
git checkout 139f5c3 ;\
git apply /trees/*.diff ;\
cd build ;\
cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release ;\
ninja
-56
View File
@@ -1,56 +0,0 @@
#!/usr/bin/env python3
from pathlib import Path
import os
spath = os.path.dirname(os.path.realpath(__file__))
path = Path(spath)
folder = "../../results/"
images = list(path.glob(folder + '*.png'))
images.sort()
gallery = open(path.absolute().joinpath(folder + 'index.html'), 'w')
gallery.write("""<html>
<head>
<script type="module" src="https://unpkg.com/img-comparison-slider@latest/dist/component/component.esm.js"></script>
<script nomodule="" src="https://unpkg.com/img-comparison-slider@latest/dist/component/component.js"></script>
<link rel="stylesheet" href="https://unpkg.com/img-comparison-slider@latest/dist/collection/styles/initial.css"/>
<style>
h2 {
font-weight: normal;
margin-top: 150px;
margin-bottom: 20px;
}
a {
text-decoration: none;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: blue;
}
a:hover {
font-weight: bold;
}
</style>
</head>
<body>
""")
tag = ''
for image in images:
group = image.stem.rstrip('0123456789')
before = f'https://filament-assets.github.io/golden/{group}/{image.name}'
after = image.name
gallery.write('\n')
gallery.write(f'<h2><a href="{image.stem}.json">{image.stem}.json</a></h2>\n')
gallery.write('<img-comparison-slider>\n')
gallery.write(f'<img slot="before" src="{before}" /> <img slot="after" src="{after}" />\n')
gallery.write('</img-comparison-slider>\n')
gallery.write("""</body>
</html>
""")
-62
View File
@@ -1,62 +0,0 @@
diff --git a/src/Vulkan/VkPipeline.cpp b/src/Vulkan/VkPipeline.cpp
index 86913ec72..3b35345af 100644
--- a/src/Vulkan/VkPipeline.cpp
+++ b/src/Vulkan/VkPipeline.cpp
@@ -71,7 +71,56 @@ std::vector<uint32_t> preprocessSpirv(
if(optimize)
{
// Full optimization list taken from spirv-opt.
- opt.RegisterPerformancePasses();
+
+ // We have removed CreateRedundancyEliminationPass because it segfaults when encountering:
+ // %389 = OpCompositeConstruct %7 %386 %387 %388 %86
+ // When inserting an entry into instruction_to_value_ (which is an unordered_map)
+ // This could perhaps be investigated further with help from asan.
+
+ using namespace spvtools;
+ opt.RegisterPass(CreateWrapOpKillPass())
+ .RegisterPass(CreateDeadBranchElimPass())
+ .RegisterPass(CreateMergeReturnPass())
+ .RegisterPass(CreateInlineExhaustivePass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreatePrivateToLocalPass())
+ .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
+ .RegisterPass(CreateLocalSingleStoreElimPass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateScalarReplacementPass())
+ .RegisterPass(CreateLocalAccessChainConvertPass())
+ .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
+ .RegisterPass(CreateLocalSingleStoreElimPass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateLocalMultiStoreElimPass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateCCPPass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateLoopUnrollPass(true))
+ .RegisterPass(CreateDeadBranchElimPass())
+ .RegisterPass(CreateRedundancyEliminationPass()) // workaround for SEGFAULT
+ .RegisterPass(CreateCombineAccessChainsPass())
+ .RegisterPass(CreateSimplificationPass())
+ .RegisterPass(CreateScalarReplacementPass())
+ .RegisterPass(CreateLocalAccessChainConvertPass())
+ .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
+ .RegisterPass(CreateLocalSingleStoreElimPass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateSSARewritePass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateVectorDCEPass())
+ .RegisterPass(CreateDeadInsertElimPass())
+ .RegisterPass(CreateDeadBranchElimPass())
+ .RegisterPass(CreateSimplificationPass())
+ .RegisterPass(CreateIfConversionPass())
+ .RegisterPass(CreateCopyPropagateArraysPass())
+ .RegisterPass(CreateReduceLoadSizePass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateBlockMergePass())
+ .RegisterPass(CreateRedundancyEliminationPass()) // workaround for SEGFAULT
+ .RegisterPass(CreateDeadBranchElimPass())
+ .RegisterPass(CreateBlockMergePass())
+ .RegisterPass(CreateSimplificationPass());
}
std::vector<uint32_t> optimized;
-127
View File
@@ -1,127 +0,0 @@
#!/bin/bash
set -e
function print_help {
local self_name=$(basename "$0")
echo "This script issues docker commands for testing Filament with SwiftShader."
echo "The usual sequence of commands is: fetch, start, build filament release, and run."
echo ""
echo "Usage:"
echo " $self_name [command]"
echo ""
echo "Commands:"
echo " build filament [debug | release]"
echo " Use the container to build Filament."
echo " build swiftshader [debug | release]"
echo " Use the container to do a clean rebuild of SwiftShader."
echo " (Note that the container already has SwiftShader built.)"
echo " fetch"
echo " Download the docker image from the central repository."
echo " help"
echo " Print this help message."
echo " logs"
echo " Print messages from the container's kernel ring buffer."
echo " This is useful for diagnosing OOM issues."
echo " run [lldb]"
echo " Launch a test inside the container, optionally via lldb."
echo " shell"
echo " Interact with a bash prompt in the container."
echo " start"
echo " Start a container from the image."
echo " stop"
echo " Stop the container."
echo ""
}
# Change the current working directory to the Filament root.
pushd "$(dirname "$0")/../.." > /dev/null
if [[ "$1" == "build" ]] && [[ "$2" == "filament" ]]; then
docker exec runner filament/build.sh -t $3 gltf_viewer
exit $?
fi
if [[ "$1" == "build" ]] && [[ "$2" == "swiftshader" ]]; then
BUILD_TYPE="$3"
BUILD_TYPE="$(tr '[:lower:]' '[:upper:]' <<< ${BUILD_TYPE:0:1})${BUILD_TYPE:1}"
docker exec --workdir /trees/swiftshader runner rm -rf build
docker exec --workdir /trees/swiftshader runner mkdir build
docker exec --workdir /trees/swiftshader/build runner cmake -GNinja -DCMAKE_BUILD_TYPE="$BUILD_TYPE" ..
docker exec --workdir /trees/swiftshader/build runner ninja
exit $?
fi
if [[ "$1" == "fetch" ]]; then
docker pull ghcr.io/filament-assets/swiftshader:latest
docker tag ghcr.io/filament-assets/swiftshader:latest ssfilament
exit $?
fi
if [[ "$1" == "help" ]]; then
print_help
exit 0
fi
if [[ "$1" == "logs" ]]; then
docker exec runner dmesg --human --read-clear
exit $?
fi
if [[ "$1" == "run" ]] && [[ "$2" == "lldb" ]]; then
docker exec -i --workdir /trees/filament/results runner \
lldb --batch -o run -o bt -- \
../out/cmake-release/samples/gltf_viewer \
--headless \
--batch ../libs/viewer/tests/basic.json \
--api vulkan
docker exec runner /trees/filament/build/swiftshader/gallery.py
exit $?
fi
if [[ "$1" == "run" ]]; then
docker exec --tty --workdir /trees/filament/results runner \
/usr/bin/catchsegv \
../out/cmake-release/samples/gltf_viewer \
--headless \
--batch ../libs/viewer/tests/basic.json \
--api vulkan
docker exec runner /trees/filament/build/swiftshader/gallery.py
exit $?
fi
if [[ "$1" == "shell" ]]; then
docker exec --interactive --tty runner /bin/bash
exit $?
fi
# Notes on options being passed to docker's run command:
#
# - The memory constraint seems to prevent an OOM signal in GitHub Actions.
# - The cap / security args allow use of lldb and creation of core dumps.
# - The privileged arg allows use of dmesg for examining OOM logs.
#
# Currently, a GitHub Actions VM has 2 CPUs, 7 GB RAM, and 14 GB of SSD disk space.
#
# Please be aware that Docker Desktop might impose additional resource constraints, and that those
# settings can only be controlled with its GUI. We recommend at least 7 GB of memory and 2 GB swap.
if [[ "$1" == "start" ]]; then
mkdir -p results
docker run --tty --rm --detach --privileged \
--memory 6.5g \
--name runner \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
--security-opt apparmor=unconfined \
--volume `pwd`:/trees/filament \
--workdir /trees \
ssfilament
exit $?
fi
if [[ "$1" == "stop" ]]; then
docker container rm runner --force
exit $?
fi
print_help
exit 1
+2 -2
View File
@@ -476,7 +476,7 @@ Energy conservation is one of the key components of a good BRDF for physically b
<p>
For the specular term, \(f_m\) is a mirror BRDF that can be modeled with the Fresnel law, noted \(F\) in the Cook-Torrance approximation of the microfacet model integration:
For the specular term, \(f_r\) is a mirror BRDF that can be modeled with the Fresnel law, noted \(F\) in the Cook-Torrance approximation of the microfacet model integration:
</p><p>
@@ -857,7 +857,7 @@ The full GLSL implementation of the standard model is shown in <a href="#listing
<span class="line"> <span class="hljs-comment">// perceptually linear roughness to roughness (see parameterization)</span></span>
<span class="line"> <span class="hljs-type">float</span> roughness = perceptualRoughness * perceptualRoughness;</span>
<span class="line"></span>
<span class="line"> <span class="hljs-type">float</span> D = D_GGX(NoH, a);</span>
<span class="line"> <span class="hljs-type">float</span> D = D_GGX(NoH, roughness);</span>
<span class="line"> <span class="hljs-type">vec3</span> F = F_Schlick(LoH, f0);</span>
<span class="line"> <span class="hljs-type">float</span> V = V_SmithGGXCorrelated(NoV, NoL, roughness);</span>
<span class="line"></span>
+141 -53
View File
File diff suppressed because one or more lines are too long
+33 -1
View File
@@ -1397,7 +1397,7 @@ Type
: `string`
Value
: Any of `opaque`, `transparent`, `fade`, `add`, `masked`, `multiply`, `screen`. Defaults to `opaque`.
: Any of `opaque`, `transparent`, `fade`, `add`, `masked`, `multiply`, `screen`, `custom`. Defaults to `opaque`.
Description
: Defines how/if the rendered object is blended with the content of the render target.
@@ -1420,6 +1420,7 @@ Description
of the material's output defines whether a fragment is discarded or not. Additionally,
ALPHA_TO_COVERAGE is enabled for non-translucent views. See the maskThreshold section for more
information.
- **Custom**: blending is enabled. But the blending function is user specified. See `blendFunction`.
!!! Note
When `blending` is set to `masked`, alpha to coverage is automatically enabled for the material.
@@ -1432,6 +1433,36 @@ material {
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### Blending and transparency: blendFunction
Type
: `object`
Fields
: `srcRGB`, `srcA`, `dstRGB`, `dstA`
Description
: - *srcRGB*: source function applied to the RGB channels
- *srcA*: source function applied to the alpha channel
- *srcRGB*: destination function applied to the RGB channels
- *srcRGB*: destination function applied to the alpha channel
The values possible for each functions are one of `zero`, `one`, `srcColor`, `oneMinusSrcColor`,
`dstColor`, `oneMinusDstColor`, `srcAlpha`, `oneMinusSrcAlpha`, `dstAlpha`,
`oneMinusDstAlpha`, `srcAlphaSaturate`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
blending : custom,
blendFunction :
{
srcRGB: one,
srcA: one,
dstRGB: oneMinusSrcColor,
dstA: oneMinusSrcAlpha
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### Blending and transparency: postLightingBlending
Type
@@ -2394,6 +2425,7 @@ The following APIs are only available from the vertex block:
**getWorldFromModelMatrix()** | float4x4 | Matrix that converts from model (object) space to world space
**getWorldFromModelNormalMatrix()** | float3x3 | Matrix that converts normals from model (object) space to world space
**getVertexIndex()** | int | Index of the current vertex
**getEyeIndex()** | int | Index of the eye being rendered, starting at 0
### Fragment only
File diff suppressed because one or more lines are too long
Binary file not shown.
+1 -1
View File
@@ -43,7 +43,7 @@ filament-viewer::part(canvas) {
</p>
</main>
<script src="https://unpkg.com/filament@1.44.0/filament.js"></script>
<script src="https://unpkg.com/filament@1.51.6/filament.js"></script>
<script src="https://unpkg.com/gltumble"></script>
<script src="filament-viewer.js" type="module"></script>
</body>
+2
View File
@@ -163,6 +163,7 @@ set(PRIVATE_HDRS
src/ResourceList.h
src/ShadowMap.h
src/ShadowMapManager.h
src/SharedHandle.h
src/TypedUniformBuffer.h
src/UniformBuffer.h
src/components/CameraManager.h
@@ -214,6 +215,7 @@ set(PRIVATE_HDRS
set(MATERIAL_SRCS
src/materials/antiAliasing/fxaa.mat
src/materials/antiAliasing/taa.mat
src/materials/blitDepth.mat
src/materials/blitLow.mat
src/materials/blitArray.mat
src/materials/bloom/bloomDownsample.mat
+12 -3
View File
@@ -66,11 +66,13 @@ set(PRIVATE_HDRS
# OpenGL / OpenGL ES Sources
# ==================================================================================================
if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMENT_USE_SWIFTSHADER)
if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
list(APPEND SRCS
include/backend/platforms/OpenGLPlatform.h
src/opengl/gl_headers.cpp
src/opengl/gl_headers.h
src/opengl/GLBufferObject.h
src/opengl/GLTexture.h
src/opengl/GLUtils.cpp
src/opengl/GLUtils.h
src/opengl/OpenGLBlobCache.cpp
@@ -166,8 +168,10 @@ endif()
if (FILAMENT_SUPPORTS_VULKAN)
list(APPEND SRCS
include/backend/platforms/VulkanPlatform.h
src/vulkan/caching/VulkanDescriptorSet.cpp
src/vulkan/caching/VulkanDescriptorSet.h
src/vulkan/caching/VulkanDescriptorSetManager.cpp
src/vulkan/caching/VulkanDescriptorSetManager.h
src/vulkan/caching/VulkanPipelineLayoutCache.cpp
src/vulkan/caching/VulkanPipelineLayoutCache.h
src/vulkan/platform/VulkanPlatform.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
@@ -413,6 +417,7 @@ if (APPLE OR LINUX)
test/test_MissingRequiredAttributes.cpp
test/test_ReadPixels.cpp
test/test_BufferUpdates.cpp
test/test_Callbacks.cpp
test/test_MRT.cpp
test/test_LoadImage.cpp
test/test_StencilBuffer.cpp
@@ -474,6 +479,10 @@ if (APPLE)
# linker from removing "unused" symbols.
target_link_libraries(backend_test_mac PRIVATE -force_load backend_test)
set_target_properties(backend_test_mac PROPERTIES FOLDER Tests)
# This is needed after XCode 15.3
set_target_properties(backend_test_mac PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
set_target_properties(backend_test_mac PROPERTIES INSTALL_RPATH /usr/local/lib)
endif()
endif()
+17 -9
View File
@@ -24,12 +24,14 @@
#include <backend/PresentCallable.h>
#include <utils/Invocable.h>
#include <utils/ostream.h>
#include <math/vec4.h>
#include <array> // FIXME: STL headers are not allowed in public headers
#include <type_traits> // FIXME: STL headers are not allowed in public headers
#include <variant> // FIXME: STL headers are not allowed in public headers
#include <stddef.h>
#include <stdint.h>
@@ -90,12 +92,15 @@ static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = SWAP_CHAIN_CON
*/
static constexpr uint64_t SWAP_CHAIN_CONFIG_PROTECTED_CONTENT = 0x40;
static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES.
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_PUSH_CONSTANT_COUNT = 32; // Vulkan 1.1 spec allows for 128-byte
// of push constant (we assume 4-byte
// types).
// Per feature level caps
// Use (int)FeatureLevel to index this array
static constexpr struct {
@@ -158,14 +163,16 @@ static constexpr const char* backendToString(Backend backend) {
}
/**
* Defines the shader language. Similar to the above backend enum, but the OpenGL backend can select
* between two shader languages: ESSL 1.0 and ESSL 3.0.
* Defines the shader language. Similar to the above backend enum, with some differences:
* - The OpenGL backend can select between two shader languages: ESSL 1.0 and ESSL 3.0.
* - The Metal backend can prefer precompiled Metal libraries, while falling back to MSL.
*/
enum class ShaderLanguage {
ESSL1 = 0,
ESSL3 = 1,
SPIRV = 2,
MSL = 3,
METAL_LIBRARY = 4,
};
static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguage) {
@@ -178,6 +185,8 @@ static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguag
return "SPIR-V";
case ShaderLanguage::MSL:
return "MSL";
case ShaderLanguage::METAL_LIBRARY:
return "Metal precompiled library";
}
}
@@ -327,7 +336,7 @@ enum class UniformType : uint8_t {
/**
* Supported constant parameter types
*/
enum class ConstantType : uint8_t {
enum class ConstantType : uint8_t {
INT,
FLOAT,
BOOL
@@ -682,7 +691,7 @@ enum class TextureUsage : uint16_t {
SUBPASS_INPUT = 0x0020, //!< Texture can be used as a subpass input
BLIT_SRC = 0x0040, //!< Texture can be used the source of a blit()
BLIT_DST = 0x0080, //!< Texture can be used the destination of a blit()
PROTECTED = 0x0100, //!< Texture can be used the destination of a blit()
PROTECTED = 0x0100, //!< Texture can be used for protected content
DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage
};
@@ -1214,13 +1223,15 @@ struct StencilState {
uint8_t padding = 0;
};
using PushConstantVariant = std::variant<int32_t, float, bool>;
static_assert(sizeof(StencilState::StencilOperations) == 5u,
"StencilOperations size not what was intended");
static_assert(sizeof(StencilState) == 12u,
"StencilState size not what was intended");
using FrameScheduledCallback = void(*)(PresentCallable callable, void* user);
using FrameScheduledCallback = utils::Invocable<void(backend::PresentCallable)>;
enum class Workaround : uint16_t {
// The EASU pass must split because shader compiler flattens early-exit branch
@@ -1237,9 +1248,6 @@ enum class Workaround : uint16_t {
DISABLE_BLIT_INTO_TEXTURE_ARRAY,
// Multiple workarounds needed for PowerVR GPUs
POWER_VR_SHADER_WORKAROUNDS,
// The driver has some threads pinned, and we can't easily know on which core, it can hurt
// performance more if we end-up pinned on the same one.
DISABLE_THREAD_AFFINITY
};
//! The type of technique for stereoscopic rendering
+15
View File
@@ -75,6 +75,19 @@ protected:
HandleBase(HandleBase const& rhs) noexcept = default;
HandleBase& operator=(HandleBase const& rhs) noexcept = default;
HandleBase(HandleBase&& rhs) noexcept
: object(rhs.object) {
rhs.object = nullid;
}
HandleBase& operator=(HandleBase&& rhs) noexcept {
if (this != &rhs) {
object = rhs.object;
rhs.object = nullid;
}
return *this;
}
private:
HandleId object;
};
@@ -89,8 +102,10 @@ struct Handle : public HandleBase {
Handle() noexcept = default;
Handle(Handle const& rhs) noexcept = default;
Handle(Handle&& rhs) noexcept = default;
Handle& operator=(Handle const& rhs) noexcept = default;
Handle& operator=(Handle&& rhs) noexcept = default;
explicit Handle(HandleId id) noexcept : HandleBase(id) { }
@@ -65,6 +65,12 @@ public:
* Disable backend handles use-after-free checks.
*/
bool disableHandleUseAfterFreeCheck = false;
/**
* Force GLES2 context if supported, or pretend the context is ES2. Only meaningful on
* GLES 3.x backends.
*/
bool forceGLES2Context = false;
};
Platform() noexcept;
@@ -48,7 +48,7 @@ namespace filament::backend {
* and optional user data:
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* swapChain->setFrameScheduledCallback(myFrameScheduledCallback, nullptr);
* swapChain->setFrameScheduledCallback(nullptr, myFrameScheduledCallback);
* if (renderer->beginFrame(swapChain)) {
* renderer->render(view);
* renderer->endFrame();
@@ -58,8 +58,6 @@ namespace filament::backend {
* @remark Only Filament's Metal backend supports PresentCallables and frame callbacks. Other
* backends ignore the callback (which will never be called) and proceed normally.
*
* @remark The SwapChain::FrameScheduledCallback is called on an arbitrary thread.
*
* Applications *must* call each PresentCallable they receive. Each PresentCallable represents a
* frame that is waiting to be presented. If an application fails to call a PresentCallable, a
* memory leak could occur. To "cancel" the presentation of a frame, pass false to the
@@ -84,6 +84,9 @@ public:
// 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)
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.
@@ -114,8 +117,18 @@ public:
Program& specializationConstants(
utils::FixedCapacityVector<SpecializationConstant> specConstants) noexcept;
struct PushConstant {
utils::CString name;
ConstantType type;
};
Program& pushConstants(ShaderStage stage,
utils::FixedCapacityVector<PushConstant> constants) noexcept;
Program& cacheId(uint64_t cacheId) noexcept;
Program& multiview(bool multiview) noexcept;
ShaderSource const& getShadersSource() const noexcept { return mShadersSource; }
ShaderSource& getShadersSource() noexcept { return mShadersSource; }
@@ -134,6 +147,8 @@ public:
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 {
return mSpecializationConstants;
}
@@ -141,8 +156,19 @@ public:
return mSpecializationConstants;
}
utils::FixedCapacityVector<PushConstant> const& getPushConstants(
ShaderStage stage) const noexcept {
return mPushConstants[static_cast<uint8_t>(stage)];
}
utils::FixedCapacityVector<PushConstant>& getPushConstants(ShaderStage stage) noexcept {
return mPushConstants[static_cast<uint8_t>(stage)];
}
uint64_t getCacheId() const noexcept { return mCacheId; }
bool isMultiview() const noexcept { return mMultiview; }
CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; }
private:
@@ -151,13 +177,20 @@ private:
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;
// 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.
// This variable could be promoted as a more generic variable later if other similar needs occur.
bool mMultiview = false;
};
} // namespace filament::backend
@@ -29,17 +29,36 @@ namespace filament::backend {
//! \privatesection
struct TargetBufferInfo {
// note: the parameters of this constructor are not in the order of this structure's fields
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level, uint16_t layer, uint8_t baseViewIndex) noexcept
: handle(handle), baseViewIndex(baseViewIndex), level(level), layer(layer) {
}
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level, uint16_t layer) noexcept
: handle(handle), level(level), layer(layer) {
}
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level) noexcept
: handle(handle), level(level) {
}
TargetBufferInfo(Handle<HwTexture> handle) noexcept // NOLINT(*-explicit-constructor)
: handle(handle) {
}
TargetBufferInfo() noexcept = default;
// texture to be used as render target
Handle<HwTexture> handle;
// starting layer index for multiview. This value is only used when the `layerCount` for the
// Starting layer index for multiview. This value is only used when the `layerCount` for the
// render target is greater than 1.
uint8_t baseViewIndex = 0;
// level to be used
uint8_t level = 0;
// for cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping
// For cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping
uint16_t layer = 0;
};
@@ -64,7 +83,7 @@ public:
MRT() noexcept = default;
MRT(TargetBufferInfo const& color) noexcept // NOLINT(hicpp-explicit-conversions)
MRT(TargetBufferInfo const& color) noexcept // NOLINT(hicpp-explicit-conversions, *-explicit-constructor)
: mInfos{ color } {
}
@@ -84,7 +103,7 @@ public:
// this is here for backward compatibility
MRT(Handle<HwTexture> handle, uint8_t level, uint16_t layer) noexcept
: mInfos{{ handle, 0, level, layer }} {
: mInfos{{ handle, level, layer, 0 }} {
}
};
@@ -140,6 +140,23 @@ public:
*/
virtual uint32_t getDefaultFramebufferObject() noexcept;
/**
* Called by the backend when a frame starts.
* @param steady_clock_ns vsync time point on the monotonic clock
* @param refreshIntervalNs refresh interval in nanosecond
* @param frameId a frame id
*/
virtual void beginFrame(
int64_t monotonic_clock_ns,
int64_t refreshIntervalNs,
uint32_t frameId) noexcept;
/**
* Called by the backend when a frame ends.
* @param frameId the frame id used in beginFrame
*/
virtual void endFrame(
uint32_t frameId) noexcept;
/**
* Type of contexts available
@@ -191,6 +208,12 @@ public:
utils::Invocable<void()> preContextChange,
utils::Invocable<void(size_t index)> postContextChange) noexcept;
/**
* Called by the backend just before calling commit()
* @see commit()
*/
virtual void preCommit() noexcept;
/**
* Called by the driver once the current frame finishes drawing. Typically, this should present
* the drawSwapChain. This is for example where `eglMakeCurrent()` would be called.
@@ -22,6 +22,10 @@
#include <backend/platforms/OpenGLPlatform.h>
#include <backend/platforms/PlatformEGL.h>
#include <utils/android/PerformanceHintManager.h>
#include <chrono>
#include <stddef.h>
#include <stdint.h>
@@ -58,6 +62,13 @@ protected:
void terminate() noexcept override;
void beginFrame(
int64_t monotonic_clock_ns,
int64_t refreshIntervalNs,
uint32_t frameId) noexcept override;
void preCommit() noexcept override;
/**
* Set the presentation time using `eglPresentationTimeANDROID`
* @param presentationTimeInNanosecond
@@ -81,6 +92,11 @@ protected:
private:
int mOSVersion;
ExternalStreamManagerAndroid& mExternalStreamManager;
utils::PerformanceHintManager mPerformanceHintManager;
utils::PerformanceHintManager::Session mPerformanceHintSession;
using clock = std::chrono::high_resolution_clock;
clock::time_point mStartTimeOfActualWork;
};
} // namespace filament::backend
@@ -23,9 +23,9 @@
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Hash.h>
#include <utils/PrivateImplementation.h>
#include <string_view>
#include <tuple>
#include <unordered_set>
@@ -47,6 +47,14 @@ struct VulkanPlatformPrivate;
class VulkanPlatform : public Platform, utils::PrivateImplementation<VulkanPlatformPrivate> {
public:
struct ExtensionHashFn {
std::size_t operator()(utils::CString const& s) const noexcept {
return std::hash<std::string>{}(s.data());
}
};
// Utility for managing device or instance extensions during initialization.
using ExtensionSet = std::unordered_set<utils::CString, ExtensionHashFn>;
/**
* A collection of handles to objects and metadata that comprises a Vulkan context. The client
* can instantiate this struct and pass to Engine::Builder::sharedContext if they wishes to
@@ -192,6 +200,13 @@ public:
virtual SwapChainPtr createSwapChain(void* nativeWindow, uint64_t flags = 0,
VkExtent2D extent = {0, 0});
/**
* Allows implementers to provide instance extensions that they'd like to include in the
* instance creation.
* @return A set of extensions to enable for the instance.
*/
virtual ExtensionSet getRequiredInstanceExtensions() { return {}; }
/**
* Destroy the swapchain.
* @param handle The handle returned by createSwapChain()
@@ -236,10 +251,9 @@ public:
VkQueue getGraphicsQueue() const noexcept;
private:
// Platform dependent helper methods
using ExtensionSet = std::unordered_set<std::string_view>;
static ExtensionSet getRequiredInstanceExtensions();
static ExtensionSet getSwapchainInstanceExtensions();
// Platform dependent helper methods
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
static SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
uint64_t flags) noexcept;
@@ -82,6 +82,7 @@ public:
void requestExit();
// suspend or unsuspend the queue.
bool isPaused() const noexcept;
void setPaused(bool paused);
bool isExitRequested() const;
@@ -134,7 +134,7 @@ struct CommandType<void (Driver::*)(ARGS...)> {
public:
template<typename M, typename D>
static inline void execute(M&& method, D&& driver, CommandBase* base, intptr_t* next) noexcept {
static inline void execute(M&& method, D&& driver, CommandBase* base, intptr_t* next) {
Command* self = static_cast<Command*>(base);
*next = align(sizeof(Command));
#if DEBUG_COMMAND_STREAM
@@ -168,7 +168,7 @@ struct CommandType<void (Driver::*)(ARGS...)> {
class CustomCommand : public CommandBase {
std::function<void()> mCommand;
static void execute(Driver&, CommandBase* base, intptr_t* next) noexcept;
static void execute(Driver&, CommandBase* base, intptr_t* next);
public:
inline CustomCommand(CustomCommand&& rhs) = default;
inline explicit CustomCommand(std::function<void()> cmd)
@@ -76,7 +76,7 @@ public:
// the fn function will execute a batch of driver commands
// this gives the driver a chance to wrap their execution in a meaningful manner
// the default implementation simply calls fn
virtual void execute(std::function<void(void)> const& fn) noexcept;
virtual void execute(std::function<void(void)> const& fn);
// This is called on debug build, or when enabled manually on the backend thread side.
virtual void debugCommandBegin(CommandStream* cmds,
@@ -133,18 +133,18 @@ DECL_DRIVER_API_0(tick)
DECL_DRIVER_API_N(beginFrame,
int64_t, monotonic_clock_ns,
int64_t, refreshIntervalNs,
uint32_t, frameId)
DECL_DRIVER_API_N(setFrameScheduledCallback,
backend::SwapChainHandle, sch,
backend::FrameScheduledCallback, callback,
void*, user)
backend::CallbackHandler*, handler,
backend::FrameScheduledCallback&&, callback)
DECL_DRIVER_API_N(setFrameCompletedCallback,
backend::SwapChainHandle, sch,
backend::CallbackHandler*, handler,
backend::CallbackHandler::Callback, callback,
void*, user)
utils::Invocable<void(void)>&&, callback)
DECL_DRIVER_API_N(setPresentationTime,
int64_t, monotonic_clock_ns)
@@ -303,6 +303,7 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedContentSupported)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isStereoSupported, backend::StereoscopicType, stereoscopicType)
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(uint8_t, getMaxDrawBuffers)
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
@@ -432,6 +433,11 @@ 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)
@@ -495,7 +501,7 @@ DECL_DRIVER_API_N(blit,
math::uint2, size)
DECL_DRIVER_API_N(bindPipeline,
backend::PipelineState, state)
backend::PipelineState const&, state)
DECL_DRIVER_API_N(bindRenderPrimitive,
backend::RenderPrimitiveHandle, rph)
@@ -65,7 +65,7 @@ public:
*
*/
template<typename D, typename ... ARGS>
Handle<D> allocateAndConstruct(ARGS&& ... args) noexcept {
Handle<D> allocateAndConstruct(ARGS&& ... args) {
Handle<D> h{ allocateHandle<D>() };
D* addr = handle_cast<D*>(h);
new(addr) D(std::forward<ARGS>(args)...);
@@ -97,7 +97,7 @@ public:
*/
template<typename D, typename B, typename ... ARGS>
typename std::enable_if_t<std::is_base_of_v<B, D>, D>*
destroyAndConstruct(Handle<B> const& handle, ARGS&& ... args) noexcept {
destroyAndConstruct(Handle<B> const& handle, ARGS&& ... args) {
assert_invariant(handle);
D* addr = handle_cast<D*>(const_cast<Handle<B>&>(handle));
assert_invariant(addr);
@@ -163,7 +163,7 @@ public:
inline typename std::enable_if_t<
std::is_pointer_v<Dp> &&
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
handle_cast(Handle<B>& handle) noexcept {
handle_cast(Handle<B>& handle) {
assert_invariant(handle);
auto [p, tag] = handleToPointer(handle.getId());
@@ -185,7 +185,7 @@ public:
inline typename std::enable_if_t<
std::is_pointer_v<Dp> &&
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
handle_cast(Handle<B> const& handle) noexcept {
handle_cast(Handle<B> const& handle) {
return handle_cast<Dp>(const_cast<Handle<B>&>(handle));
}
@@ -317,7 +317,7 @@ private:
return (id & HANDLE_HEAP_FLAG) == 0u;
}
HandleBase::HandleId allocateHandleSlow(size_t size) noexcept;
HandleBase::HandleId allocateHandleSlow(size_t size);
void deallocateHandleSlow(HandleBase::HandleId id, size_t size) noexcept;
// We inline this because it's just 4 instructions in the fast case
@@ -57,7 +57,13 @@ void CommandBufferQueue::requestExit() {
mCondition.notify_one();
}
bool CommandBufferQueue::isPaused() const noexcept {
std::lock_guard<utils::Mutex> const lock(mLock);
return mPaused;
}
void CommandBufferQueue::setPaused(bool paused) {
std::lock_guard<utils::Mutex> const lock(mLock);
if (paused) {
mPaused = true;
} else {
@@ -125,6 +131,9 @@ void CommandBufferQueue::flush() noexcept {
#endif
SYSTRACE_NAME("waiting: CircularBuffer::flush()");
ASSERT_POSTCONDITION(!mPaused,
"CommandStream is full, but since the rendering thread is paused, "
"the buffer cannot flush and we will deadlock. Instead, abort.");
mCondition.wait(lock, [this, requiredSize]() -> bool {
// TODO: on macOS, we need to call pumpEvents from time to time
return mFreeSpace >= requiredSize;
+1 -1
View File
@@ -149,7 +149,7 @@ void CommandType<void (Driver::*)(ARGS...)>::Command<METHOD>::log() noexcept {
// ------------------------------------------------------------------------------------------------
void CustomCommand::execute(Driver&, CommandBase* base, intptr_t* next) noexcept {
void CustomCommand::execute(Driver&, CommandBase* base, intptr_t* next) {
*next = CustomCommand::align(sizeof(CustomCommand));
static_cast<CustomCommand*>(base)->mCommand();
static_cast<CustomCommand*>(base)->~CustomCommand();
+1 -1
View File
@@ -214,7 +214,7 @@ size_t Driver::getElementTypeSize(ElementType type) noexcept {
Driver::~Driver() noexcept = default;
void Driver::execute(std::function<void(void)> const& fn) noexcept {
void Driver::execute(std::function<void(void)> const& fn) {
fn();
}
+1 -1
View File
@@ -107,7 +107,7 @@ void* HandleAllocator<P0, P1, P2>::handleToPointerSlow(HandleBase::HandleId id)
}
template <size_t P0, size_t P1, size_t P2>
HandleBase::HandleId HandleAllocator<P0, P1, P2>::allocateHandleSlow(size_t size) noexcept {
HandleBase::HandleId HandleAllocator<P0, P1, P2>::allocateHandleSlow(size_t size) {
void* p = ::malloc(size);
std::unique_lock lock(mLock);
+5 -6
View File
@@ -29,21 +29,21 @@
#include "backend/platforms/PlatformCocoaTouchGL.h"
#endif
#elif defined(__APPLE__)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
#include <backend/platforms/PlatformCocoaGL.h>
#endif
#elif defined(__linux__)
#if defined(FILAMENT_SUPPORTS_X11)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
#include "backend/platforms/PlatformGLX.h"
#endif
#elif defined(FILAMENT_SUPPORTS_EGL_ON_LINUX)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
#include "backend/platforms/PlatformEGLHeadless.h"
#endif
#endif
#elif defined(WIN32)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
#include "backend/platforms/PlatformWGL.h"
#endif
#elif defined(__EMSCRIPTEN__)
@@ -111,8 +111,7 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
}
assert_invariant(*backend == Backend::OPENGL);
#if defined(FILAMENT_SUPPORTS_OPENGL)
#if defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(FILAMENT_USE_SWIFTSHADER)
// Swiftshader OpenGLES support is deprecated and incomplete
#if defined(FILAMENT_USE_EXTERNAL_GLES3)
return nullptr;
#elif defined(__ANDROID__)
return new PlatformEGLAndroid();
+17 -1
View File
@@ -21,7 +21,7 @@ namespace filament::backend {
using namespace utils;
// We want these in the .cpp file, so they're not inlined (not worth it)
Program::Program() noexcept { // NOLINT(modernize-use-equals-default)
Program::Program() noexcept { // NOLINT(modernize-use-equals-default)
}
Program::Program(Program&& rhs) noexcept = default;
@@ -47,6 +47,11 @@ Program& Program::shader(ShaderStage shader, void const* data, size_t size) {
return *this;
}
Program& Program::shaderLanguage(ShaderLanguage shaderLanguage) {
mShaderLanguage = shaderLanguage;
return *this;
}
Program& Program::uniformBlockBindings(
FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& uniformBlockBindings) noexcept {
for (auto const& item : uniformBlockBindings) {
@@ -86,11 +91,22 @@ Program& Program::specializationConstants(
return *this;
}
Program& Program::pushConstants(ShaderStage stage,
utils::FixedCapacityVector<PushConstant> constants) noexcept {
mPushConstants[static_cast<uint8_t>(stage)] = std::move(constants);
return *this;
}
Program& Program::cacheId(uint64_t cacheId) noexcept {
mCacheId = cacheId;
return *this;
}
Program& Program::multiview(bool multiview) noexcept {
mMultiview = multiview;
return *this;
}
io::ostream& operator<<(io::ostream& out, const Program& builder) {
out << "Program{";
builder.mLogger(out);
+121 -37
View File
@@ -18,6 +18,7 @@
#define TNT_FILAMENT_DRIVER_METALBUFFER_H
#include "MetalContext.h"
#include "MetalPlatform.h"
#include <backend/DriverEnums.h>
@@ -28,47 +29,122 @@
#include <utility>
#include <memory>
#include <atomic>
#include <chrono>
namespace filament::backend {
class TrackedMetalBuffer {
class ScopedAllocationTimer {
public:
TrackedMetalBuffer() noexcept : mBuffer(nil) {}
TrackedMetalBuffer(id<MTLBuffer> buffer) noexcept : mBuffer(buffer) {
if (buffer) {
aliveBuffers++;
}
}
~TrackedMetalBuffer() {
if (mBuffer) {
aliveBuffers--;
ScopedAllocationTimer(const char* name) : mBeginning(clock_t::now()), mName(name) {}
~ScopedAllocationTimer() {
using namespace std::literals::chrono_literals;
static constexpr std::chrono::seconds LONG_TIME_THRESHOLD = 10s;
auto end = clock_t::now();
std::chrono::duration<double, std::micro> allocationTimeMicroseconds = end - mBeginning;
if (UTILS_UNLIKELY(allocationTimeMicroseconds > LONG_TIME_THRESHOLD)) {
if (platform && platform->hasDebugUpdateStatFunc()) {
char buffer[64];
snprintf(buffer, sizeof(buffer), "filament.metal.long_buffer_allocation_time.%s",
mName);
platform->debugUpdateStat(
buffer, static_cast<uint64_t>(allocationTimeMicroseconds.count()));
}
}
}
TrackedMetalBuffer(TrackedMetalBuffer&&) = delete;
TrackedMetalBuffer(TrackedMetalBuffer const&) = delete;
TrackedMetalBuffer& operator=(TrackedMetalBuffer const&) = delete;
TrackedMetalBuffer& operator=(TrackedMetalBuffer&& rhs) noexcept {
swap(rhs);
return *this;
}
id<MTLBuffer> get() const noexcept { return mBuffer; }
operator bool() const noexcept { return bool(mBuffer); }
static uint64_t getAliveBuffers() { return aliveBuffers; }
static void setPlatform(MetalPlatform* p) { platform = p; }
private:
void swap(TrackedMetalBuffer& other) noexcept {
id<MTLBuffer> temp = mBuffer;
mBuffer = other.mBuffer;
other.mBuffer = temp;
typedef std::chrono::steady_clock clock_t;
static MetalPlatform* platform;
std::chrono::time_point<clock_t> mBeginning;
const char* mName;
};
#ifndef FILAMENT_METAL_BUFFER_TRACKING
#define FILAMENT_METAL_BUFFER_TRACKING 0
#endif
class MetalBufferTracking {
public:
static constexpr size_t EXCESS_BUFFER_COUNT = 30000;
enum class Type {
NONE = 0,
GENERIC = 1,
RING = 2,
STAGING = 3,
};
static constexpr size_t TypeCount = 3;
static constexpr auto toIndex(Type t) {
assert_invariant(t != Type::NONE);
switch (t) {
case Type::NONE:
case Type::GENERIC:
return 0;
case Type::RING:
return 1;
case Type::STAGING:
return 2;
}
}
id<MTLBuffer> mBuffer;
#if FILAMENT_METAL_BUFFER_TRACKING
static void initialize() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
for (size_t i = 0; i < TypeCount; i++) {
aliveBuffers[i] = [NSHashTable weakObjectsHashTable];
}
});
}
static std::atomic<uint64_t> aliveBuffers;
static void setPlatform(MetalPlatform* p) { platform = p; }
static void track(id<MTLBuffer> buffer, Type type) {
assert_invariant(type != Type::NONE);
if (UTILS_UNLIKELY(getAliveBuffers() >= EXCESS_BUFFER_COUNT)) {
if (platform && platform->hasDebugUpdateStatFunc()) {
platform->debugUpdateStat("filament.metal.excess_buffers_allocated",
MetalBufferTracking::getAliveBuffers());
}
}
[aliveBuffers[toIndex(type)] addObject:buffer];
}
static uint64_t getAliveBuffers() {
uint64_t sum = 0;
for (size_t i = 1; i < TypeCount; i++) {
sum += getAliveBuffers(static_cast<Type>(i));
}
return sum;
}
static uint64_t getAliveBuffers(Type type) {
assert_invariant(type != Type::NONE);
NSHashTable* hashTable = aliveBuffers[toIndex(type)];
// Caution! We can't simply use hashTable.count here, which is inaccurate.
// See http://cocoamine.net/blog/2013/12/13/nsmaptable-and-zeroing-weak-references/
return hashTable.objectEnumerator.allObjects.count;
}
#else
static void initialize() {}
static void setPlatform(MetalPlatform* p) {}
static id<MTLBuffer> track(id<MTLBuffer> buffer, Type type) { return buffer; }
static uint64_t getAliveBuffers() { return 0; }
static uint64_t getAliveBuffers(Type type) { return 0; }
#endif
private:
#if FILAMENT_METAL_BUFFER_TRACKING
static std::array<NSHashTable<id<MTLBuffer>>*, TypeCount> aliveBuffers;
static MetalPlatform* platform;
#endif
};
class MetalBuffer {
@@ -122,7 +198,7 @@ public:
private:
TrackedMetalBuffer mBuffer;
id<MTLBuffer> mBuffer;
size_t mBufferSize = 0;
void* mCpuBuffer = nullptr;
MetalContext& mContext;
@@ -171,7 +247,11 @@ public:
mBufferOptions(options),
mSlotSizeBytes(computeSlotSize(layout)),
mSlotCount(slotCount) {
mBuffer = [device newBufferWithLength:mSlotSizeBytes * mSlotCount options:mBufferOptions];
{
ScopedAllocationTimer timer("ring");
mBuffer = [device newBufferWithLength:mSlotSizeBytes * mSlotCount options:mBufferOptions];
}
MetalBufferTracking::track(mBuffer, MetalBufferTracking::Type::RING);
assert_invariant(mBuffer);
}
@@ -189,9 +269,13 @@ public:
// If we already have an aux buffer, it will get freed here, unless it has been retained
// by a MTLCommandBuffer. In that case, it will be freed when the command buffer
// finishes executing.
mAuxBuffer = [mDevice newBufferWithLength:mSlotSizeBytes options:mBufferOptions];
{
ScopedAllocationTimer timer("ring");
mAuxBuffer = [mDevice newBufferWithLength:mSlotSizeBytes options:mBufferOptions];
}
MetalBufferTracking::track(mAuxBuffer, MetalBufferTracking::Type::RING);
assert_invariant(mAuxBuffer);
return {mAuxBuffer.get(), 0};
return { mAuxBuffer, 0 };
}
mCurrentSlot = (mCurrentSlot + 1) % mSlotCount;
mOccupiedSlots->fetch_add(1, std::memory_order_relaxed);
@@ -220,9 +304,9 @@ public:
*/
std::pair<id<MTLBuffer>, NSUInteger> getCurrentAllocation() const {
if (UTILS_UNLIKELY(mAuxBuffer)) {
return { mAuxBuffer.get(), 0 };
return { mAuxBuffer, 0 };
}
return { mBuffer.get(), mCurrentSlot * mSlotSizeBytes };
return { mBuffer, mCurrentSlot * mSlotSizeBytes };
}
bool canAccomodateLayout(MTLSizeAndAlign layout) const {
@@ -231,8 +315,8 @@ public:
private:
id<MTLDevice> mDevice;
TrackedMetalBuffer mBuffer;
TrackedMetalBuffer mAuxBuffer;
id<MTLBuffer> mBuffer;
id<MTLBuffer> mAuxBuffer;
MTLResourceOptions mBufferOptions;
+16 -6
View File
@@ -22,7 +22,13 @@
namespace filament {
namespace backend {
std::atomic<uint64_t> TrackedMetalBuffer::aliveBuffers = 0;
MetalPlatform* ScopedAllocationTimer::platform = nullptr;
#if FILAMENT_METAL_BUFFER_TRACKING
std::array<NSHashTable<id<MTLBuffer>>*, MetalBufferTracking::TypeCount>
MetalBufferTracking::aliveBuffers;
MetalPlatform* MetalBufferTracking::platform = nullptr;
#endif
MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage,
size_t size, bool forceGpuBuffer) : mBufferSize(size), mContext(context) {
@@ -37,7 +43,11 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
}
// Otherwise, we allocate a private GPU buffer.
mBuffer = [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate];
{
ScopedAllocationTimer timer("generic");
mBuffer = [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate];
}
MetalBufferTracking::track(mBuffer, MetalBufferTracking::Type::GENERIC);
ASSERT_POSTCONDITION(mBuffer, "Could not allocate Metal buffer of size %zu.", size);
}
@@ -64,7 +74,7 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
// Acquire a staging buffer to hold the contents of this update.
MetalBufferPool* bufferPool = mContext.bufferPool;
const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size);
memcpy(staging->buffer.get().contents, src, size);
memcpy(staging->buffer.contents, src, size);
// The blit below requires that byteOffset be a multiple of 4.
ASSERT_PRECONDITION(!(byteOffset & 0x3u), "byteOffset must be a multiple of 4");
@@ -73,9 +83,9 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
blitEncoder.label = @"Buffer upload blit";
[blitEncoder copyFromBuffer:staging->buffer.get()
[blitEncoder copyFromBuffer:staging->buffer
sourceOffset:0
toBuffer:mBuffer.get()
toBuffer:mBuffer
destinationOffset:byteOffset
size:size];
[blitEncoder endEncoding];
@@ -96,7 +106,7 @@ id<MTLBuffer> MetalBuffer::getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) n
return nil;
}
assert_invariant(mBuffer);
return mBuffer.get();
return mBuffer;
}
void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncoder> encoder,
+1 -1
View File
@@ -32,7 +32,7 @@ struct MetalContext;
// Immutable POD representing a shared CPU-GPU buffer.
struct MetalBufferPoolEntry {
TrackedMetalBuffer buffer;
id<MTLBuffer> buffer;
size_t capacity;
mutable uint64_t lastAccessed;
mutable uint32_t referenceCount;
@@ -42,8 +42,13 @@ MetalBufferPoolEntry const* MetalBufferPool::acquireBuffer(size_t numBytes) {
}
// We were not able to find a sufficiently large stage, so create a new one.
id<MTLBuffer> buffer = [mContext.device newBufferWithLength:numBytes
options:MTLResourceStorageModeShared];
id<MTLBuffer> buffer = nil;
{
ScopedAllocationTimer timer("staging");
buffer = [mContext.device newBufferWithLength:numBytes
options:MTLResourceStorageModeShared];
}
MetalBufferTracking::track(buffer, MetalBufferTracking::Type::STAGING);
ASSERT_POSTCONDITION(buffer, "Could not allocate Metal staging buffer of size %zu.", numBytes);
MetalBufferPoolEntry* stage = new MetalBufferPoolEntry {
.buffer = buffer,
+33 -10
View File
@@ -105,6 +105,10 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
driverConfig.disableHandleUseAfterFreeCheck) {
mContext->driver = this;
ScopedAllocationTimer::setPlatform(platform);
MetalBufferTracking::initialize();
MetalBufferTracking::setPlatform(platform);
mContext->device = mPlatform.createDevice();
assert_invariant(mContext->device);
@@ -198,6 +202,8 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
}
MetalDriver::~MetalDriver() noexcept {
MetalBufferTracking::setPlatform(nullptr);
ScopedAllocationTimer::setPlatform(nullptr);
mContext->device = nil;
mContext->emptyTexture = nil;
CFRelease(mContext->textureCache);
@@ -212,25 +218,35 @@ void MetalDriver::tick(int) {
executeTickOps();
}
void MetalDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
void MetalDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) {
#if defined(FILAMENT_METAL_PROFILING)
os_signpost_interval_begin(mContext->log, mContext->signpostId, "Frame encoding", "%{public}d", frameId);
#endif
if (mPlatform.hasDebugUpdateStatFunc()) {
mPlatform.debugUpdateStat("filament.metal.alive_buffers", TrackedMetalBuffer::getAliveBuffers());
#if FILAMENT_METAL_BUFFER_TRACKING
const uint64_t generic = MetalBufferTracking::getAliveBuffers(MetalBufferTracking::Type::GENERIC);
const uint64_t ring = MetalBufferTracking::getAliveBuffers(MetalBufferTracking::Type::RING);
const uint64_t staging = MetalBufferTracking::getAliveBuffers(MetalBufferTracking::Type::STAGING);
const uint64_t total = generic + ring + staging;
mPlatform.debugUpdateStat("filament.metal.alive_buffers", total);
mPlatform.debugUpdateStat("filament.metal.alive_buffers.generic", generic);
mPlatform.debugUpdateStat("filament.metal.alive_buffers.ring", ring);
mPlatform.debugUpdateStat("filament.metal.alive_buffers.staging", staging);
#endif
}
}
void MetalDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
FrameScheduledCallback callback, void* user) {
void MetalDriver::setFrameScheduledCallback(
Handle<HwSwapChain> sch, CallbackHandler* handler, FrameScheduledCallback&& callback) {
auto* swapChain = handle_cast<MetalSwapChain>(sch);
swapChain->setFrameScheduledCallback(callback, user);
swapChain->setFrameScheduledCallback(handler, std::move(callback));
}
void MetalDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
void MetalDriver::setFrameCompletedCallback(
Handle<HwSwapChain> sch, CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
auto* swapChain = handle_cast<MetalSwapChain>(sch);
swapChain->setFrameCompletedCallback(handler, callback, user);
swapChain->setFrameCompletedCallback(handler, std::move(callback));
}
void MetalDriver::execute(std::function<void(void)> const& fn) noexcept {
@@ -805,6 +821,10 @@ bool MetalDriver::isDepthStencilResolveSupported() {
return false;
}
bool MetalDriver::isDepthStencilBlitSupported(TextureFormat format) {
return true;
}
bool MetalDriver::isProtectedTexturesSupported() {
return false;
}
@@ -1236,6 +1256,9 @@ void MetalDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
mContext->samplerBindings[index] = sb;
}
void MetalDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
backend::PushConstantVariant value) {}
void MetalDriver::insertEventMarker(const char* string, uint32_t len) {
}
@@ -1614,7 +1637,7 @@ void MetalDriver::finalizeSamplerGroup(MetalSamplerGroup* samplerGroup) {
}
}
void MetalDriver::bindPipeline(PipelineState ps) {
void MetalDriver::bindPipeline(PipelineState const& ps) {
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr,
"bindPipeline() without a valid command encoder.");
@@ -1634,7 +1657,7 @@ void MetalDriver::bindPipeline(PipelineState ps) {
return;
}
ASSERT_PRECONDITION(bool(functions), "Attempting to bind an invalid Metal program.");
functions.validate();
auto [fragment, vertex] = functions.getRasterFunctions();
+15 -13
View File
@@ -31,6 +31,8 @@
#include "private/backend/SamplerGroup.h"
#include <backend/DriverEnums.h>
#include <utils/bitset.h>
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
@@ -71,9 +73,9 @@ public:
void releaseDrawable();
void setFrameScheduledCallback(FrameScheduledCallback callback, void* user);
void setFrameCompletedCallback(CallbackHandler* handler,
CallbackHandler::Callback callback, void* user);
void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback);
void setFrameCompletedCallback(
CallbackHandler* handler, utils::Invocable<void(void)>&& callback);
// For CAMetalLayer-backed SwapChains, presents the drawable or schedules a
// FrameScheduledCallback.
@@ -110,19 +112,19 @@ private:
MetalExternalImage externalImage;
SwapChainType type;
// These two fields store a callback and user data to notify the client that a frame is ready
// for presentation.
// If frameScheduledCallback is nullptr, then the Metal backend automatically calls
// presentDrawable when the frame is committed.
// Otherwise, the Metal backend will not automatically present the frame. Instead, clients bear
// the responsibility of presenting the frame by calling the PresentCallable object.
FrameScheduledCallback frameScheduledCallback = nullptr;
void* frameScheduledUserData = nullptr;
// These fields store a callback to notify the client that a frame is ready for presentation. If
// !frameScheduled.callback, then the Metal backend automatically calls presentDrawable when the
// frame is committed. Otherwise, the Metal backend will not automatically present the frame.
// Instead, clients bear the responsibility of presenting the frame by calling the
// PresentCallable object.
struct {
CallbackHandler* handler = nullptr;
std::shared_ptr<FrameScheduledCallback> callback = nullptr;
} frameScheduled;
struct {
CallbackHandler* handler = nullptr;
CallbackHandler::Callback callback = {};
void* user = nullptr;
std::shared_ptr<utils::Invocable<void(void)>> callback = nullptr;
} frameCompleted;
};
+50 -21
View File
@@ -221,16 +221,16 @@ void MetalSwapChain::ensureDepthStencilTexture() {
depthStencilTexture = [context.device newTextureWithDescriptor:descriptor];
}
void MetalSwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void* user) {
frameScheduledCallback = callback;
frameScheduledUserData = user;
void MetalSwapChain::setFrameScheduledCallback(
CallbackHandler* handler, FrameScheduledCallback&& callback) {
frameScheduled.handler = handler;
frameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
}
void MetalSwapChain::setFrameCompletedCallback(CallbackHandler* handler,
CallbackHandler::Callback callback, void* user) {
void MetalSwapChain::setFrameCompletedCallback(
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
frameCompleted.handler = handler;
frameCompleted.callback = callback;
frameCompleted.user = user;
frameCompleted.callback = std::make_shared<utils::Invocable<void(void)>>(std::move(callback));
}
void MetalSwapChain::present() {
@@ -238,7 +238,7 @@ void MetalSwapChain::present() {
scheduleFrameCompletedCallback();
}
if (drawable) {
if (frameScheduledCallback) {
if (frameScheduled.callback) {
scheduleFrameScheduledCallback();
} else {
[getPendingCommandBuffer(&context) presentDrawable:drawable];
@@ -296,21 +296,38 @@ void presentDrawable(bool presentFrame, void* user) {
}
void MetalSwapChain::scheduleFrameScheduledCallback() {
if (!frameScheduledCallback) {
if (!frameScheduled.callback) {
return;
}
assert_invariant(drawable);
// Destroy this by calling maybePresentAndDestroyAsync() later.
auto* presentData = PresentDrawableData::create(drawable, context.driver);
struct Callback {
Callback(std::shared_ptr<FrameScheduledCallback> callback, id<CAMetalDrawable> drawable,
MetalDriver* driver)
: f(callback), data(PresentDrawableData::create(drawable, driver)) {}
std::shared_ptr<FrameScheduledCallback> f;
// PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later.
std::unique_ptr<PresentDrawableData> data;
static void func(void* user) {
auto* const c = reinterpret_cast<Callback*>(user);
PresentDrawableData* presentDrawableData = c->data.release();
PresentCallable presentCallable(presentDrawable, presentDrawableData);
c->f->operator()(presentCallable);
delete c;
}
};
FrameScheduledCallback userCallback = frameScheduledCallback;
void* userData = frameScheduledUserData;
// This callback pointer will be captured by the block. Even if the scheduled handler is never
// called, the unique_ptr will still ensure we don't leak memory.
__block auto callback =
std::make_unique<Callback>(frameScheduled.callback, drawable, context.driver);
backend::CallbackHandler* handler = frameScheduled.handler;
MetalDriver* driver = context.driver;
[getPendingCommandBuffer(&context) addScheduledHandler:^(id<MTLCommandBuffer> cb) {
PresentCallable callable(presentDrawable, static_cast<void*>(presentData));
userCallback(callable, userData);
Callback* user = callback.release();
driver->scheduleCallback(handler, user, &Callback::func);
}];
}
@@ -319,13 +336,25 @@ void MetalSwapChain::scheduleFrameCompletedCallback() {
return;
}
CallbackHandler* handler = frameCompleted.handler;
void* user = frameCompleted.user;
CallbackHandler::Callback callback = frameCompleted.callback;
struct Callback {
Callback(std::shared_ptr<utils::Invocable<void(void)>> callback) : f(callback) {}
std::shared_ptr<utils::Invocable<void(void)>> f;
static void func(void* user) {
auto* const c = reinterpret_cast<Callback*>(user);
c->f->operator()();
delete c;
}
};
// This callback pointer will be captured by the block. Even if the completed handler is never
// called, the unique_ptr will still ensure we don't leak memory.
__block auto callback = std::make_unique<Callback>(frameCompleted.callback);
CallbackHandler* handler = frameCompleted.handler;
MetalDriver* driver = context.driver;
[getPendingCommandBuffer(&context) addCompletedHandler:^(id<MTLCommandBuffer> cb) {
driver->scheduleCallback(handler, user, callback);
Callback* user = callback.release();
driver->scheduleCallback(handler, user, &Callback::func);
}];
}
@@ -771,13 +800,13 @@ void MetalTexture::loadWithCopyBuffer(uint32_t level, uint32_t slice, MTLRegion
PixelBufferDescriptor const& data, const PixelBufferShape& shape) {
const size_t stagingBufferSize = shape.totalBytes;
auto entry = context.bufferPool->acquireBuffer(stagingBufferSize);
memcpy(entry->buffer.get().contents,
memcpy(entry->buffer.contents,
static_cast<uint8_t*>(data.buffer) + shape.sourceOffset,
stagingBufferSize);
id<MTLCommandBuffer> blitCommandBuffer = getPendingCommandBuffer(&context);
id<MTLBlitCommandEncoder> blitCommandEncoder = [blitCommandBuffer blitCommandEncoder];
blitCommandEncoder.label = @"Texture upload buffer blit";
[blitCommandEncoder copyFromBuffer:entry->buffer.get()
[blitCommandEncoder copyFromBuffer:entry->buffer
sourceOffset:0
sourceBytesPerRow:shape.bytesPerRow
sourceBytesPerImage:shape.bytesPerSlice
@@ -106,26 +106,41 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
continue;
}
assert_invariant(source[source.size() - 1] == '\0');
// the shader string is null terminated and the length includes the null character
NSString* objcSource = [[NSString alloc] initWithBytes:source.data()
length:source.size() - 1
encoding:NSUTF8StringEncoding];
// By default, Metal uses the most recent language version.
MTLCompileOptions* options = [MTLCompileOptions new];
// Disable Fast Math optimizations.
// This ensures that operations adhere to IEEE standards for floating-point arithmetic,
// which is crucial for half precision floats in scenarios where fast math optimizations
// lead to inaccuracies, such as in handling special values like NaN or Infinity.
options.fastMathEnabled = NO;
NSError* error = nil;
id<MTLLibrary> library = [device newLibraryWithSource:objcSource
options:options
error:&error];
id<MTLLibrary> library = nil;
switch (program.getShaderLanguage()) {
case ShaderLanguage::MSL: {
// By default, Metal uses the most recent language version.
MTLCompileOptions* options = [MTLCompileOptions new];
// Disable Fast Math optimizations.
// This ensures that operations adhere to IEEE standards for floating-point
// arithmetic, which is crucial for half precision floats in scenarios where fast
// math optimizations lead to inaccuracies, such as in handling special values like
// NaN or Infinity.
options.fastMathEnabled = NO;
assert_invariant(source[source.size() - 1] == '\0');
// the shader string is null terminated and the length includes the null character
NSString* objcSource = [[NSString alloc] initWithBytes:source.data()
length:source.size() - 1
encoding:NSUTF8StringEncoding];
library = [device newLibraryWithSource:objcSource options:options error:&error];
break;
}
case ShaderLanguage::METAL_LIBRARY: {
dispatch_data_t data = dispatch_data_create(source.data(), source.size(),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
DISPATCH_DATA_DESTRUCTOR_DEFAULT);
library = [device newLibraryWithData:data error:&error];
break;
}
case ShaderLanguage::ESSL1:
case ShaderLanguage::ESSL3:
case ShaderLanguage::SPIRV:
break;
}
if (library == nil) {
NSString* errorMessage = @"unknown error";
if (error) {
@@ -154,6 +169,23 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
id<MTLFunction> function = [library newFunctionWithName:@"main0"
constantValues:constants
error:&error];
if (function == nil) {
// If the library loads but functions within it fail to load, it usually means the
// GPU backend crashed. (This can happen if it's a Metallib shader that was compiled
// with a minimum iOS version that's newer than this device.)
NSString* errorMessage = @"unknown error";
if (error) {
auto description =
[error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
utils::slog.w << description << utils::io::endl;
errorMessage = error.localizedDescription;
}
PANIC_LOG("Failed to load main0 in Metal program.");
NSString* programName =
[NSString stringWithFormat:@"%s::main0", program.getName().c_str_safe()];
return MetalFunctionBundle::error(errorMessage, programName);
}
if (!program.getName().empty()) {
function.label = @(program.getName().c_str());
}
+13 -4
View File
@@ -49,16 +49,17 @@ void NoopDriver::terminate() {
void NoopDriver::tick(int) {
}
void NoopDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
void NoopDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) {
}
void NoopDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
FrameScheduledCallback callback, void* user) {
CallbackHandler* handler, FrameScheduledCallback&& callback) {
}
void NoopDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
}
@@ -193,6 +194,10 @@ bool NoopDriver::isDepthStencilResolveSupported() {
return true;
}
bool NoopDriver::isDepthStencilBlitSupported(TextureFormat format) {
return true;
}
bool NoopDriver::isProtectedTexturesSupported() {
return true;
}
@@ -307,6 +312,10 @@ 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) {
}
@@ -350,7 +359,7 @@ void NoopDriver::blit(
math::uint2 size) {
}
void NoopDriver::bindPipeline(PipelineState pipelineState) {
void NoopDriver::bindPipeline(PipelineState const& pipelineState) {
}
void NoopDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
@@ -0,0 +1,51 @@
/*
* 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_GLBUFFEROBJECT_H
#define TNT_FILAMENT_BACKEND_OPENGL_GLBUFFEROBJECT_H
#include "DriverBase.h"
#include "gl_headers.h"
#include <backend/DriverEnums.h>
#include <stdint.h>
namespace filament::backend {
struct GLBufferObject : public HwBufferObject {
using HwBufferObject::HwBufferObject;
GLBufferObject(uint32_t size,
BufferObjectBinding bindingType, BufferUsage usage) noexcept
: HwBufferObject(size), usage(usage), bindingType(bindingType) {
}
struct {
GLuint id;
union {
GLenum binding;
void* buffer;
};
} gl;
BufferUsage usage;
BufferObjectBinding bindingType;
uint16_t age = 0;
};
} // namespace filament::backend
#endif //TNT_FILAMENT_BACKEND_OPENGL_GLBUFFEROBJECT_H
+55
View File
@@ -0,0 +1,55 @@
/*
* 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_GLTEXTURE_H
#define TNT_FILAMENT_BACKEND_OPENGL_GLTEXTURE_H
#include "DriverBase.h"
#include "gl_headers.h"
#include <backend/platforms/OpenGLPlatform.h>
#include <stdint.h>
namespace filament::backend {
struct GLTexture : public HwTexture {
using HwTexture::HwTexture;
struct GL {
GL() noexcept : imported(false), sidecarSamples(1), reserved1(0) {}
GLuint id = 0; // texture or renderbuffer id
GLenum target = 0;
GLenum internalFormat = 0;
GLuint sidecarRenderBufferMS = 0; // multi-sample sidecar renderbuffer
// texture parameters go here too
GLfloat anisotropy = 1.0;
int8_t baseLevel = 127;
int8_t maxLevel = -1;
uint8_t reserved0 = 0;
bool imported : 1;
uint8_t sidecarSamples : 4;
uint8_t reserved1 : 3;
} gl;
OpenGLPlatform::ExternalTexture* externalTexture = nullptr;
};
} // namespace filament::backend
#endif //TNT_FILAMENT_BACKEND_OPENGL_GLTEXTURE_H
+75 -10
View File
@@ -63,8 +63,10 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
#endif
}
OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept
: mPlatform(platform) {
OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
Platform::DriverConfig const& driverConfig) noexcept
: mPlatform(platform),
mSamplerMap(32) {
state.vao.p = &mDefaultVAO;
@@ -84,6 +86,13 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept
queryOpenGLVersion(&state.major, &state.minor);
#if defined(BACKEND_OPENGL_VERSION_GLES)
if (UTILS_UNLIKELY(driverConfig.forceGLES2Context)) {
state.major = 2;
state.minor = 0;
}
#endif
OpenGLContext::initExtensions(&ext, state.major, state.minor);
OpenGLContext::initProcs(&procs, ext, state.major, state.minor);
@@ -252,9 +261,23 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept
}
OpenGLContext::~OpenGLContext() noexcept {
// note: this is called from the main thread. Can't do any GL calls.
delete mTimerQueryFactory;
}
void OpenGLContext::terminate() noexcept {
// note: this is called from the backend thread
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (!isES2()) {
for (auto& item: mSamplerMap) {
unbindSampler(item.second);
glDeleteSamplers(1, &item.second);
}
mSamplerMap.clear();
}
#endif
}
void OpenGLContext::destroyWithContext(
size_t index, std::function<void(OpenGLContext&)> const& closure) noexcept {
if (index == 0) {
@@ -516,8 +539,6 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
bugs->delay_fbo_destruction = true;
// PowerVR seems to have no problem with this (which is good for us)
bugs->allow_read_only_ancillary_feedback_loop = true;
// PowerVR has a shader compiler thread pinned on the last core
bugs->disable_thread_affinity = true;
} else if (strstr(renderer, "Apple")) {
// Apple GPU
} else if (strstr(renderer, "Tegra") ||
@@ -868,19 +889,32 @@ default_case:
}
}
void OpenGLContext::unbindTexture(GLenum target, GLuint texture_id) noexcept {
void OpenGLContext::unbindTexture(
UTILS_UNUSED_IN_RELEASE GLenum target, GLuint texture_id) noexcept {
// unbind this texture from all the units it might be bound to
// no need unbind the texture from FBOs because we're not tracking that state (and there is
// no need to).
const size_t index = getIndexForTextureTarget(target);
UTILS_NOUNROLL
for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) {
if (state.textures.units[unit].targets[index].texture_id == texture_id) {
bindTexture(unit, target, (GLuint)0, index);
// Never attempt to unbind texture 0. This could happen with external textures w/ streaming if
// never populated.
if (texture_id) {
UTILS_NOUNROLL
for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) {
if (state.textures.units[unit].id == texture_id) {
// if this texture is bound, it should be at the same target
assert_invariant(state.textures.units[unit].target == target);
unbindTextureUnit(unit);
}
}
}
}
void OpenGLContext::unbindTextureUnit(GLuint unit) noexcept {
update_state(state.textures.units[unit].id, 0u, [&]() {
activeTexture(unit);
glBindTexture(state.textures.units[unit].target, 0u);
});
}
void OpenGLContext::unbindSampler(GLuint sampler) noexcept {
// unbind this sampler from all the units it might be bound to
UTILS_NOUNROLL // clang generates >800B of code!!!
@@ -934,6 +968,37 @@ void OpenGLContext::deleteVertexArray(GLuint vao) noexcept {
}
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLuint OpenGLContext::getSamplerSlow(SamplerParams params) const noexcept {
assert_invariant(mSamplerMap.find(params) == mSamplerMap.end());
using namespace GLUtils;
GLuint s;
glGenSamplers(1, &s);
glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, (GLint)getTextureFilter(params.filterMin));
glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, (GLint)getTextureFilter(params.filterMag));
glSamplerParameteri(s, GL_TEXTURE_WRAP_S, (GLint)getWrapMode(params.wrapS));
glSamplerParameteri(s, GL_TEXTURE_WRAP_T, (GLint)getWrapMode(params.wrapT));
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, (GLint)getWrapMode(params.wrapR));
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, (GLint)getTextureCompareMode(params.compareMode));
glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC, (GLint)getTextureCompareFunc(params.compareFunc));
#if defined(GL_EXT_texture_filter_anisotropic)
if (ext.EXT_texture_filter_anisotropic &&
!bugs.texture_filter_anisotropic_broken_on_sampler) {
GLfloat const anisotropy = float(1u << params.anisotropyLog2);
glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT,
std::min(gets.max_anisotropy, anisotropy));
}
#endif
CHECK_GL_ERROR(utils::slog.e)
mSamplerMap[params] = s;
return s;
}
#endif
void OpenGLContext::resetState() noexcept {
// Force GL state to match the Filament state
+52 -44
View File
@@ -34,6 +34,8 @@
#include <math/vec2.h>
#include <math/vec4.h>
#include <tsl/robin_map.h>
#include <array>
#include <functional>
#include <optional>
@@ -89,9 +91,13 @@ public:
static bool queryOpenGLVersion(GLint* major, GLint* minor) noexcept;
explicit OpenGLContext(OpenGLPlatform& platform) noexcept;
explicit OpenGLContext(OpenGLPlatform& platform,
Platform::DriverConfig const& driverConfig) noexcept;
~OpenGLContext() noexcept final;
void terminate() noexcept;
// TimerQueryInterface ------------------------------------------------------------------------
// note: OpenGLContext being final ensures (clang) these are not called through the vtable
@@ -132,7 +138,6 @@ public:
#endif
}
constexpr static inline size_t getIndexForTextureTarget(GLuint target) noexcept;
constexpr inline size_t getIndexForCap(GLenum cap) noexcept;
constexpr static inline size_t getIndexForBufferTarget(GLenum target) noexcept;
@@ -144,10 +149,10 @@ public:
void pixelStore(GLenum, GLint) noexcept;
inline void activeTexture(GLuint unit) noexcept;
inline void bindTexture(GLuint unit, GLuint target, GLuint texId, size_t targetIndex) noexcept;
inline void bindTexture(GLuint unit, GLuint target, GLuint texId) noexcept;
void unbindTexture(GLenum target, GLuint id) noexcept;
void unbindTextureUnit(GLuint unit) noexcept;
inline void bindVertexArray(RenderPrimitive const* p) noexcept;
inline void bindSampler(GLuint unit, GLuint sampler) noexcept;
void unbindSampler(GLuint sampler) noexcept;
@@ -306,10 +311,6 @@ public:
// a glFinish. So we must delay the destruction until we know the GPU is finished.
bool delay_fbo_destruction;
// The driver has some threads pinned, and we can't easily know on which core, it can hurt
// performance more if we end-up pinned on the same one.
bool disable_thread_affinity;
// Force feature level 0. Typically used for low end ES3 devices with significant driver
// bugs or performance issues.
bool force_feature_level0;
@@ -321,8 +322,14 @@ public:
// function to handle state changes we don't control
void updateTexImage(GLenum target, GLuint id) noexcept {
const size_t index = getIndexForTextureTarget(target);
state.textures.units[state.textures.active].targets[index].texture_id = id;
assert_invariant(target == GL_TEXTURE_EXTERNAL_OES);
// if another target is bound to this texture unit, unbind that texture
if (UTILS_UNLIKELY(state.textures.units[state.textures.active].target != target)) {
glBindTexture(state.textures.units[state.textures.active].target, 0);
state.textures.units[state.textures.active].target = GL_TEXTURE_EXTERNAL_OES;
}
// the texture is already bound to `target`, we just update our internal state
state.textures.units[state.textures.active].id = id;
}
void resetProgram() noexcept { state.program.use = 0; }
@@ -425,9 +432,8 @@ public:
GLuint active = 0; // zero-based
struct {
GLuint sampler = 0;
struct {
GLuint texture_id = 0;
} targets[7]; // this must match getIndexForTextureTarget()
GLuint target = 0;
GLuint id = 0;
} units[MAX_TEXTURE_UNIT_COUNT];
} textures;
@@ -467,6 +473,29 @@ 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;
inline GLuint getSampler(SamplerParams sp) const noexcept {
assert_invariant(!sp.padding0);
assert_invariant(!sp.padding1);
assert_invariant(!sp.padding2);
auto& samplerMap = mSamplerMap;
auto pos = samplerMap.find(sp);
if (UTILS_UNLIKELY(pos == samplerMap.end())) {
return getSamplerSlow(sp);
}
return pos->second;
}
#endif
private:
OpenGLPlatform& mPlatform;
@@ -476,6 +505,11 @@ 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;
void bindFramebufferResolved(GLenum target, GLuint buffer) noexcept;
@@ -522,9 +556,6 @@ private:
{ bugs.delay_fbo_destruction,
"delay_fbo_destruction",
""},
{ bugs.disable_thread_affinity,
"disable_thread_affinity",
""},
{ bugs.force_feature_level0,
"force_feature_level0",
""},
@@ -572,31 +603,10 @@ private:
}
void setDefaultState() noexcept;
static constexpr const size_t TEXTURE_TARGET_COUNT =
sizeof(state.textures.units[0].targets) / sizeof(state.textures.units[0].targets[0]);
};
// ------------------------------------------------------------------------------------------------
constexpr size_t OpenGLContext::getIndexForTextureTarget(GLuint target) noexcept {
// this must match state.textures[].targets[]
switch (target) {
case GL_TEXTURE_2D: return 0;
case GL_TEXTURE_2D_ARRAY: return 1;
case GL_TEXTURE_CUBE_MAP: return 2;
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
case GL_TEXTURE_2D_MULTISAMPLE: return 3;
#endif
case GL_TEXTURE_EXTERNAL_OES: return 4;
case GL_TEXTURE_3D: return 5;
case GL_TEXTURE_CUBE_MAP_ARRAY: return 6;
default:
return 0;
}
}
constexpr size_t OpenGLContext::getIndexForCap(GLenum cap) noexcept { //NOLINT
size_t index = 0;
switch (cap) {
@@ -744,19 +754,17 @@ void OpenGLContext::bindBufferRange(GLenum target, GLuint index, GLuint buffer,
#endif
}
void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId, size_t targetIndex) noexcept {
assert_invariant(targetIndex == getIndexForTextureTarget(target));
assert_invariant(targetIndex < TEXTURE_TARGET_COUNT);
update_state(state.textures.units[unit].targets[targetIndex].texture_id, texId, [&]() {
void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId) noexcept {
update_state(state.textures.units[unit].target, target, [&]() {
activeTexture(unit);
glBindTexture(state.textures.units[unit].target, 0);
});
update_state(state.textures.units[unit].id, texId, [&]() {
activeTexture(unit);
glBindTexture(target, texId);
}, target == GL_TEXTURE_EXTERNAL_OES);
}
void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId) noexcept {
bindTexture(unit, target, texId, getIndexForTextureTarget(target));
}
void OpenGLContext::useProgram(GLuint program) noexcept {
update_state(state.program.use, program, [&]() {
glUseProgram(program);
+125 -101
View File
@@ -173,6 +173,10 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
PANIC_LOG("OpenGL ES 2.0 minimum needed (current %d.%d)", major, minor);
goto cleanup;
}
if (UTILS_UNLIKELY(driverConfig.forceGLES2Context)) {
major = 2;
minor = 0;
}
#else
// we require GL 4.1 headers and minimum version
if (UTILS_UNLIKELY(!((major == 4 && minor >= 1) || major > 4))) {
@@ -203,13 +207,13 @@ OpenGLDriver::DebugMarker::~DebugMarker() noexcept {
OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept
: mPlatform(*platform),
mContext(mPlatform),
mContext(mPlatform, driverConfig),
mShaderCompilerService(*this),
mHandleAllocator("Handles",
driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck),
mSamplerMap(32),
mDriverConfig(driverConfig) {
mDriverConfig(driverConfig),
mCurrentPushConstants(new(std::nothrow) PushConstantBundle{}) {
std::fill(mSamplerBindings.begin(), mSamplerBindings.end(), nullptr);
@@ -233,10 +237,19 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
}
OpenGLDriver::~OpenGLDriver() noexcept { // NOLINT(modernize-use-equals-default)
// this is called from the main thread. Can't call GL.
}
Dispatcher OpenGLDriver::getDispatcher() const noexcept {
return ConcreteDispatcher<OpenGLDriver>::make();
auto dispatcher = ConcreteDispatcher<OpenGLDriver>::make();
if (mContext.isES2()) {
dispatcher.draw2_ = +[](Driver& driver, CommandBase* base, intptr_t* next){
using Cmd = COMMAND_TYPE(draw2);
OpenGLDriver& concreteDriver = static_cast<OpenGLDriver&>(driver);
Cmd::execute(&OpenGLDriver::draw2GLES2, concreteDriver, base, next);
};
}
return dispatcher;
}
// ------------------------------------------------------------------------------------------------
@@ -263,16 +276,13 @@ void OpenGLDriver::terminate() {
// because we called glFinish(), all callbacks should have been executed
assert_invariant(mGpuCommandCompleteOps.empty());
if (!getContext().isES2()) {
for (auto& item: mSamplerMap) {
mContext.unbindSampler(item.second);
glDeleteSamplers(1, &item.second);
}
mSamplerMap.clear();
}
#endif
delete mCurrentPushConstants;
mCurrentPushConstants = nullptr;
mContext.terminate();
mPlatform.terminate();
}
@@ -291,9 +301,42 @@ void OpenGLDriver::bindSampler(GLuint unit, GLuint sampler) noexcept {
mContext.bindSampler(unit, sampler);
}
void OpenGLDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
backend::PushConstantVariant value) {
assert_invariant(stage == ShaderStage::VERTEX || stage == ShaderStage::FRAGMENT);
utils::Slice<std::pair<GLint, ConstantType>> constants;
if (stage == ShaderStage::VERTEX) {
constants = mCurrentPushConstants->vertexConstants;
} else if (stage == ShaderStage::FRAGMENT) {
constants = mCurrentPushConstants->fragmentConstants;
}
assert_invariant(index < constants.size());
auto const& [location, type] = constants[index];
// This push constant wasn't found in the shader. It's ok to return without error-ing here.
if (location < 0) {
return;
}
if (std::holds_alternative<bool>(value)) {
assert_invariant(type == ConstantType::BOOL);
bool const bval = std::get<bool>(value);
glUniform1i(location, bval ? 1 : 0);
} else if (std::holds_alternative<float>(value)) {
assert_invariant(type == ConstantType::FLOAT);
float const fval = std::get<float>(value);
glUniform1f(location, fval);
} else {
assert_invariant(type == ConstantType::INT);
int const ival = std::get<int>(value);
glUniform1i(location, ival);
}
}
void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept {
assert_invariant(t != nullptr);
mContext.bindTexture(unit, t->gl.target, t->gl.id, t->gl.targetIndex);
mContext.bindTexture(unit, t->gl.target, t->gl.id);
}
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
@@ -307,7 +350,7 @@ bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
if (UTILS_UNLIKELY(mContext.isES2())) {
for (uint32_t i = 0; i < Program::UNIFORM_BINDING_COUNT; i++) {
auto [id, buffer, age] = mUniformBindings[i];
auto [id, buffer, age] = mContext.getEs2UniformBinding(i);
if (buffer) {
p->updateUniforms(i, id, buffer, age);
}
@@ -724,10 +767,19 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
GLenum internalFormat = getInternalFormat(format);
assert_invariant(internalFormat);
if (UTILS_UNLIKELY(usage & TextureUsage::PROTECTED)) {
if (any(usage & TextureUsage::PROTECTED)) {
// renderbuffers don't have a protected mode, so we need to use a texture
// because protected textures are only supported on GLES 3.2, MSAA will be available.
usage |= TextureUsage::SAMPLEABLE;
} else if (any(usage & TextureUsage::UPLOADABLE)) {
// if we have the uploadable flag, we also need to use a texture
usage |= TextureUsage::SAMPLEABLE;
} else if (target != SamplerType::SAMPLER_2D) {
// renderbuffers can only be 2D
usage |= TextureUsage::SAMPLEABLE;
} else if (levels > 1) {
// renderbuffers can't have mip levels
usage |= TextureUsage::SAMPLEABLE;
}
auto& gl = mContext;
@@ -746,7 +798,6 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
if (t->externalTexture) {
t->gl.target = t->externalTexture->target;
t->gl.id = t->externalTexture->id;
t->gl.targetIndex = (uint8_t)OpenGLContext::getIndexForTextureTarget(t->gl.target);
// internalFormat actually depends on the external image, but it doesn't matter
// because it's not used anywhere for anything important.
t->gl.internalFormat = internalFormat;
@@ -758,30 +809,23 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
t->gl.internalFormat = internalFormat;
// We DO NOT update targetIndex at function exit to take advantage of the fact that
// getIndexForTextureTarget() is constexpr -- so all of this disappears at compile time.
switch (target) {
case SamplerType::SAMPLER_EXTERNAL:
// we can't be here -- doesn't matter what we do
case SamplerType::SAMPLER_2D:
t->gl.target = GL_TEXTURE_2D;
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D);
break;
case SamplerType::SAMPLER_3D:
t->gl.target = GL_TEXTURE_3D;
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_3D);
break;
case SamplerType::SAMPLER_2D_ARRAY:
t->gl.target = GL_TEXTURE_2D_ARRAY;
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D_ARRAY);
break;
case SamplerType::SAMPLER_CUBEMAP:
t->gl.target = GL_TEXTURE_CUBE_MAP;
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP);
break;
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
t->gl.target = GL_TEXTURE_CUBE_MAP_ARRAY;
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP_ARRAY);
break;
}
@@ -792,8 +836,6 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
if (gl.features.multisample_texture) {
// multi-sample texture on GL 3.2 / GLES 3.1 and above
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
t->gl.targetIndex = (uint8_t)
OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D_MULTISAMPLE);
} else {
// Turn off multi-sampling for that texture. It's just not supported.
}
@@ -803,13 +845,6 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
textureStorage(t, w, h, depth, bool(usage & TextureUsage::PROTECTED));
}
} else {
assert_invariant(any(usage & (
TextureUsage::COLOR_ATTACHMENT |
TextureUsage::DEPTH_ATTACHMENT |
TextureUsage::STENCIL_ATTACHMENT)));
assert_invariant(levels == 1);
assert_invariant(target == SamplerType::SAMPLER_2D);
assert_invariant(none(usage & TextureUsage::PROTECTED));
t->gl.internalFormat = internalFormat;
t->gl.target = GL_RENDERBUFFER;
glGenRenderbuffers(1, &t->gl.id);
@@ -859,32 +894,24 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
t->gl.internalFormat = getInternalFormat(format);
assert_invariant(t->gl.internalFormat);
// We DO NOT update targetIndex at function exit to take advantage of the fact that
// getIndexForTextureTarget() is constexpr -- so all of this disappears at compile time.
switch (target) {
case SamplerType::SAMPLER_EXTERNAL:
t->gl.target = GL_TEXTURE_EXTERNAL_OES;
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_EXTERNAL_OES);
break;
case SamplerType::SAMPLER_2D:
t->gl.target = GL_TEXTURE_2D;
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D);
break;
case SamplerType::SAMPLER_3D:
t->gl.target = GL_TEXTURE_3D;
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_3D);
break;
case SamplerType::SAMPLER_2D_ARRAY:
t->gl.target = GL_TEXTURE_2D_ARRAY;
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D_ARRAY);
break;
case SamplerType::SAMPLER_CUBEMAP:
t->gl.target = GL_TEXTURE_CUBE_MAP;
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP);
break;
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
t->gl.target = GL_TEXTURE_CUBE_MAP_ARRAY;
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP_ARRAY);
break;
}
@@ -895,7 +922,6 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
if (gl.features.multisample_texture) {
// multi-sample texture on GL 3.2 / GLES 3.1 and above
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D_MULTISAMPLE);
} else {
// Turn off multi-sampling for that texture. It's just not supported.
}
@@ -906,16 +932,18 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
}
void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer const* vb) {
// NOTE: this is called from draw() and must be as efficient as possible.
// NOTE: this is called often and must be as efficient as possible.
auto& gl = mContext;
#ifndef NDEBUG
if (UTILS_LIKELY(gl.ext.OES_vertex_array_object)) {
// The VAO for the given render primitive must already be bound.
GLint vaoBinding;
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &vaoBinding);
assert_invariant(vaoBinding == (GLint)rp->gl.vao[gl.contextIndex]);
}
#endif
if (UTILS_LIKELY(rp->gl.vertexBufferVersion == vb->bufferObjectsVersion &&
rp->gl.stateVersion == gl.state.age)) {
@@ -931,7 +959,7 @@ void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer
// if a buffer is defined it must not be invalid.
assert_invariant(vb->gl.buffers[bi]);
// if w're on ES2, the user shouldn't use FLAG_INTEGER_TARGET
// if we're on ES2, the user shouldn't use FLAG_INTEGER_TARGET
assert_invariant(!(gl.isES2() && (attribute.flags & Attribute::FLAG_INTEGER_TARGET)));
gl.bindBuffer(GL_ARRAY_BUFFER, vb->gl.buffers[bi]);
@@ -1785,7 +1813,6 @@ void OpenGLDriver::updateStreams(DriverApi* driver) {
// the target and id can be reset each time
t->gl.target = t->externalTexture->target;
t->gl.id = t->externalTexture->id;
t->gl.targetIndex = (uint8_t)OpenGLContext::getIndexForTextureTarget(t->gl.target);
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
}
}
@@ -2047,6 +2074,10 @@ bool OpenGLDriver::isDepthStencilResolveSupported() {
return true;
}
bool OpenGLDriver::isDepthStencilBlitSupported(TextureFormat format) {
return true;
}
bool OpenGLDriver::isProtectedTexturesSupported() {
return getContext().ext.EXT_protected_textures;
}
@@ -2063,8 +2094,6 @@ bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
return mContext.bugs.disable_blit_into_texture_array;
case Workaround::POWER_VR_SHADER_WORKAROUNDS:
return mContext.bugs.powervr_shader_workarounds;
case Workaround::DISABLE_THREAD_AFFINITY:
return mContext.bugs.disable_thread_affinity;
default:
return false;
}
@@ -2343,7 +2372,7 @@ void OpenGLDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
#endif
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (UTILS_LIKELY(!es2)) {
samplerId = getSampler(params);
samplerId = mContext.getSampler(params);
} else
#endif
{
@@ -2472,7 +2501,9 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
size_t const stride = p.stride ? p.stride : width;
size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1);
size_t const bpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
void const* const buffer = static_cast<char const*>(p.buffer) + p.left * bpp + bpr * p.top;
size_t const bpl = bpr * height; // TODO: PBD should have a "layer stride"
void const* const buffer = static_cast<char const*>(p.buffer)
+ bpp* p.left + bpr * p.top + bpl * 0; // TODO: PBD should have a p.depth
switch (t->target) {
case SamplerType::SAMPLER_EXTERNAL:
@@ -2671,7 +2702,6 @@ void OpenGLDriver::setExternalImage(Handle<HwTexture> th, void* image) {
// the target and id can be reset each time
t->gl.target = t->externalTexture->target;
t->gl.id = t->externalTexture->id;
t->gl.targetIndex = (uint8_t)OpenGLContext::getIndexForTextureTarget(t->gl.target);
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
}
}
@@ -3066,11 +3096,10 @@ void OpenGLDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t ind
assert_invariant(offset + size <= ub->byteCount);
if (UTILS_UNLIKELY(ub->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
mUniformBindings[index] = {
gl.setEs2UniformBinding(index,
ub->gl.id,
static_cast<uint8_t const*>(ub->gl.buffer) + offset,
ub->age,
};
ub->age);
} else {
GLenum const target = GLUtils::getBufferBindingType(bindingType);
@@ -3088,7 +3117,7 @@ void OpenGLDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index)
auto& gl = mContext;
if (UTILS_UNLIKELY(bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
mUniformBindings[index] = {};
gl.setEs2UniformBinding(index, 0, nullptr, 0);
return;
}
@@ -3105,36 +3134,6 @@ void OpenGLDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
CHECK_GL_ERROR(utils::slog.e)
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLuint OpenGLDriver::getSamplerSlow(SamplerParams params) const noexcept {
assert_invariant(mSamplerMap.find(params) == mSamplerMap.end());
GLuint s;
glGenSamplers(1, &s);
glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, (GLint)getTextureFilter(params.filterMin));
glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, (GLint)getTextureFilter(params.filterMag));
glSamplerParameteri(s, GL_TEXTURE_WRAP_S, (GLint)getWrapMode(params.wrapS));
glSamplerParameteri(s, GL_TEXTURE_WRAP_T, (GLint)getWrapMode(params.wrapT));
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, (GLint)getWrapMode(params.wrapR));
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, (GLint)getTextureCompareMode(params.compareMode));
glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC, (GLint)getTextureCompareFunc(params.compareFunc));
#if defined(GL_EXT_texture_filter_anisotropic)
auto& gl = mContext;
if (gl.ext.EXT_texture_filter_anisotropic &&
!gl.bugs.texture_filter_anisotropic_broken_on_sampler) {
GLfloat const anisotropy = float(1u << params.anisotropyLog2);
glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT,
std::min(gl.gets.max_anisotropy, anisotropy));
}
#endif
CHECK_GL_ERROR(utils::slog.e)
mSamplerMap[params] = s;
return s;
}
#endif
void OpenGLDriver::insertEventMarker(char const* string, uint32_t len) {
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_debug_marker
@@ -3434,17 +3433,21 @@ void OpenGLDriver::executeGpuCommandsCompleteOps() noexcept {
void OpenGLDriver::tick(int) {
DEBUG_MARKER()
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
executeGpuCommandsCompleteOps();
#endif
executeEveryNowAndThenOps();
getShaderCompilerService().tick();
}
void OpenGLDriver::beginFrame(
UTILS_UNUSED int64_t monotonic_clock_ns,
UTILS_UNUSED int64_t refreshIntervalNs,
UTILS_UNUSED uint32_t frameId) {
DEBUG_MARKER()
auto& gl = mContext;
insertEventMarker("beginFrame");
mPlatform.beginFrame(monotonic_clock_ns, refreshIntervalNs, frameId);
if (UTILS_UNLIKELY(!mTexturesWithStreamsAttached.empty())) {
OpenGLPlatform& platform = mPlatform;
for (GLTexture const* t : mTexturesWithStreamsAttached) {
@@ -3453,7 +3456,7 @@ void OpenGLDriver::beginFrame(
assert_invariant(t->hwStream->stream);
platform.updateTexImage(t->hwStream->stream,
&static_cast<GLStream*>(t->hwStream)->user_thread.timestamp); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
// NOTE: We assume that updateTexImage() binds the texture on our behalf
// NOTE: We assume that OpenGLPlatform::updateTexImage() binds the texture on our behalf
gl.updateTexImage(GL_TEXTURE_EXTERNAL_OES, t->gl.id);
}
}
@@ -3461,12 +3464,12 @@ void OpenGLDriver::beginFrame(
}
void OpenGLDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
FrameScheduledCallback callback, void* user) {
CallbackHandler* handler, FrameScheduledCallback&& callback) {
DEBUG_MARKER()
}
void OpenGLDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
DEBUG_MARKER()
}
@@ -3492,6 +3495,7 @@ void OpenGLDriver::endFrame(UTILS_UNUSED uint32_t frameId) {
#endif
//SYSTRACE_NAME("glFinish");
//glFinish();
mPlatform.endFrame(frameId);
insertEventMarker("endFrame");
}
@@ -3703,7 +3707,7 @@ void OpenGLDriver::blit(
case SamplerType::SAMPLER_EXTERNAL:
break;
}
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, fbo[1]);
switch (s->target) {
@@ -3729,7 +3733,7 @@ void OpenGLDriver::blit(
case SamplerType::SAMPLER_EXTERNAL:
break;
}
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
gl.disable(GL_SCISSOR_TEST);
glBlitFramebuffer(
@@ -3843,7 +3847,7 @@ void OpenGLDriver::updateTextureLodRange(GLTexture* texture, int8_t targetLevel)
#endif
}
void OpenGLDriver::bindPipeline(PipelineState state) {
void OpenGLDriver::bindPipeline(PipelineState const& state) {
DEBUG_MARKER()
auto& gl = mContext;
setRasterState(state.rasterState);
@@ -3851,6 +3855,7 @@ void OpenGLDriver::bindPipeline(PipelineState state) {
gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant);
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(state.program);
mValidProgram = useProgram(p);
(*mCurrentPushConstants) = p->getPushConstants();
}
void OpenGLDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
@@ -3880,20 +3885,35 @@ void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
return;
}
if (UTILS_LIKELY(instanceCount <= 1)) {
glDrawElements(GLenum(rp->type), (GLsizei)indexCount, rp->gl.getIndicesType(),
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize));
} else {
assert_invariant(!mContext.isES2());
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
glDrawElementsInstanced(GLenum(rp->type), (GLsizei)indexCount,
rp->gl.getIndicesType(),
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize),
(GLsizei)instanceCount);
assert_invariant(!mContext.isES2());
glDrawElementsInstanced(GLenum(rp->type), (GLsizei)indexCount,
rp->gl.getIndicesType(),
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize),
(GLsizei)instanceCount);
#endif
#if FILAMENT_ENABLE_MATDBG
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
#else
CHECK_GL_ERROR(utils::slog.e)
#endif
}
void OpenGLDriver::draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
if (UTILS_UNLIKELY(!rp || !mValidProgram)) {
return;
}
#ifdef FILAMENT_ENABLE_MATDBG
assert_invariant(mContext.isES2());
assert_invariant(instanceCount == 1);
glDrawElements(GLenum(rp->type), (GLsizei)indexCount, rp->gl.getIndicesType(),
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize));
#if FILAMENT_ENABLE_MATDBG
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
#else
CHECK_GL_ERROR(utils::slog.e)
@@ -3912,7 +3932,11 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph,
state.vertexBufferInfo = rp->vbih;
bindPipeline(state);
bindRenderPrimitive(rph);
draw2(indexOffset, indexCount, instanceCount);
if (UTILS_UNLIKELY(mContext.isES2())) {
draw2GLES2(indexOffset, indexCount, instanceCount);
} else {
draw2(indexOffset, indexCount, instanceCount);
}
}
void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
@@ -3939,7 +3963,7 @@ void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGr
glDispatchCompute(workGroupCount.x, workGroupCount.y, workGroupCount.z);
#endif // BACKEND_OPENGL_LEVEL_GLES31
#ifdef FILAMENT_ENABLE_MATDBG
#if FILAMENT_ENABLE_MATDBG
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
#else
CHECK_GL_ERROR(utils::slog.e)
+13 -65
View File
@@ -20,6 +20,8 @@
#include "DriverBase.h"
#include "OpenGLContext.h"
#include "OpenGLTimerQuery.h"
#include "GLBufferObject.h"
#include "GLTexture.h"
#include "ShaderCompilerService.h"
#include <backend/platforms/OpenGLPlatform.h>
@@ -64,9 +66,9 @@ namespace filament::backend {
class OpenGLPlatform;
class PixelBufferDescriptor;
struct TargetBufferInfo;
class OpenGLProgram;
class TimerQueryFactoryInterface;
struct PushConstantBundle;
class OpenGLDriver final : public DriverBase {
inline explicit OpenGLDriver(OpenGLPlatform* platform,
@@ -92,25 +94,6 @@ public:
bool rec709 = false;
};
struct GLBufferObject : public HwBufferObject {
using HwBufferObject::HwBufferObject;
GLBufferObject(uint32_t size,
BufferObjectBinding bindingType, BufferUsage usage) noexcept
: HwBufferObject(size), usage(usage), bindingType(bindingType) {
}
struct {
GLuint id;
union {
GLenum binding;
void* buffer;
};
} gl;
BufferUsage usage;
BufferObjectBinding bindingType;
uint16_t age = 0;
};
struct GLVertexBufferInfo : public HwVertexBufferInfo {
GLVertexBufferInfo() noexcept = default;
GLVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
@@ -140,7 +123,6 @@ public:
} gl;
};
struct GLTexture;
struct GLSamplerGroup : public HwSamplerGroup {
using HwSamplerGroup::HwSamplerGroup;
struct Entry {
@@ -157,27 +139,9 @@ public:
Handle<HwVertexBufferInfo> vbih;
};
struct GLTexture : public HwTexture {
using HwTexture::HwTexture;
struct GL {
GL() noexcept : imported(false), sidecarSamples(1), reserved(0) {}
GLuint id = 0; // texture or renderbuffer id
GLenum target = 0;
GLenum internalFormat = 0;
GLuint sidecarRenderBufferMS = 0; // multi-sample sidecar renderbuffer
using GLBufferObject = filament::backend::GLBufferObject;
// texture parameters go here too
GLfloat anisotropy = 1.0;
int8_t baseLevel = 127;
int8_t maxLevel = -1;
uint8_t targetIndex = 0; // optimization: index corresponding to target
bool imported : 1;
uint8_t sidecarSamples : 4;
uint8_t reserved : 3;
} gl;
OpenGLPlatform::ExternalTexture* externalTexture = nullptr;
};
using GLTexture = filament::backend::GLTexture;
using GLTimerQuery = filament::backend::GLTimerQuery;
@@ -268,13 +232,13 @@ private:
HandleAllocatorGL mHandleAllocator;
template<typename D, typename ... ARGS>
Handle<D> initHandle(ARGS&& ... args) noexcept {
Handle<D> initHandle(ARGS&& ... args) {
return mHandleAllocator.allocateAndConstruct<D>(std::forward<ARGS>(args) ...);
}
template<typename D, typename B, typename ... ARGS>
typename std::enable_if<std::is_base_of<B, D>::value, D>::type*
construct(Handle<B> const& handle, ARGS&& ... args) noexcept {
construct(Handle<B> const& handle, ARGS&& ... args) {
return mHandleAllocator.destroyAndConstruct<D, B>(handle, std::forward<ARGS>(args) ...);
}
@@ -288,7 +252,7 @@ private:
typename std::enable_if_t<
std::is_pointer_v<Dp> &&
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
handle_cast(Handle<B>& handle) noexcept {
handle_cast(Handle<B>& handle) {
return mHandleAllocator.handle_cast<Dp, B>(handle);
}
@@ -296,7 +260,7 @@ private:
inline typename std::enable_if_t<
std::is_pointer_v<Dp> &&
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
handle_cast(Handle<B> const& handle) noexcept {
handle_cast(Handle<B> const& handle) {
return mHandleAllocator.handle_cast<Dp, B>(handle);
}
@@ -348,22 +312,6 @@ private:
void resolvePass(ResolveAction action, GLRenderTarget const* rt,
TargetBufferFlags discardFlags) noexcept;
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLuint getSamplerSlow(SamplerParams sp) const noexcept;
inline GLuint getSampler(SamplerParams sp) const noexcept {
assert_invariant(!sp.padding0);
assert_invariant(!sp.padding1);
assert_invariant(!sp.padding2);
auto& samplerMap = mSamplerMap;
auto pos = samplerMap.find(sp);
if (UTILS_UNLIKELY(pos == samplerMap.end())) {
return getSamplerSlow(sp);
}
return pos->second;
}
#endif
const std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT>& getSamplerBindings() const {
return mSamplerBindings;
}
@@ -388,16 +336,14 @@ private:
void setScissor(Viewport const& scissor) noexcept;
void draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount);
// ES2 only. Uniform buffer emulation binding points
GLuint mLastAssignedEmulatedUboId = 0;
std::array<std::tuple<GLuint, void const*, uint16_t>, Program::UNIFORM_BINDING_COUNT> mUniformBindings = {};
// sampler buffer binding points (nullptr if not used)
std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT> mSamplerBindings = {}; // 4 pointers
mutable tsl::robin_map<SamplerParams, GLuint,
SamplerParams::Hasher, SamplerParams::EqualTo> mSamplerMap;
// this must be accessed from the driver thread only
std::vector<GLTexture*> mTexturesWithStreamsAttached;
@@ -431,6 +377,8 @@ private:
// for ES2 sRGB support
GLSwapChain* mCurrentDrawSwapChain = nullptr;
bool mRec709OutputColorspace = false;
PushConstantBundle* mCurrentPushConstants = nullptr;
};
// ------------------------------------------------------------------------------------------------
@@ -55,6 +55,16 @@ uint32_t OpenGLPlatform::getDefaultFramebufferObject() noexcept {
return 0;
}
void OpenGLPlatform::beginFrame(int64_t monotonic_clock_ns, int64_t refreshIntervalNs,
uint32_t frameId) noexcept {
}
void OpenGLPlatform::endFrame(uint32_t frameId) noexcept {
}
void OpenGLPlatform::preCommit() noexcept {
}
OpenGLPlatform::ContextType OpenGLPlatform::getCurrentContextType() const noexcept {
return ContextType::UNPROTECTED;
}
+19 -1
View File
@@ -46,6 +46,8 @@ struct OpenGLProgram::LazyInitializationData {
Program::UniformBlockInfo uniformBlockInfo;
Program::SamplerGroupInfo samplerGroupInfo;
std::array<Program::UniformInfo, Program::UNIFORM_BINDING_COUNT> bindingUniformInfo;
utils::FixedCapacityVector<Program::PushConstant> vertexPushConstants;
utils::FixedCapacityVector<Program::PushConstant> fragmentPushConstants;
};
@@ -53,7 +55,6 @@ OpenGLProgram::OpenGLProgram() noexcept = default;
OpenGLProgram::OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept
: HwProgram(std::move(program.getName())) {
auto* const lazyInitializationData = new(std::nothrow) LazyInitializationData();
lazyInitializationData->samplerGroupInfo = std::move(program.getSamplerGroupInfo());
if (UTILS_UNLIKELY(gld.getContext().isES2())) {
@@ -61,6 +62,8 @@ OpenGLProgram::OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept
} else {
lazyInitializationData->uniformBlockInfo = std::move(program.getUniformBlockBindings());
}
lazyInitializationData->vertexPushConstants = std::move(program.getPushConstants(ShaderStage::VERTEX));
lazyInitializationData->fragmentPushConstants = std::move(program.getPushConstants(ShaderStage::FRAGMENT));
ShaderCompilerService& compiler = gld.getShaderCompilerService();
mToken = compiler.createProgram(name, std::move(program));
@@ -203,6 +206,21 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
}
}
mUsedBindingsCount = usedBindingCount;
auto& vertexConstants = lazyInitializationData.vertexPushConstants;
auto& fragmentConstants = lazyInitializationData.fragmentPushConstants;
size_t const totalConstantCount = vertexConstants.size() + fragmentConstants.size();
if (totalConstantCount > 0) {
mPushConstants.reserve(totalConstantCount);
mPushConstantFragmentStageOffset = vertexConstants.size();
auto const transformAndAdd = [&](Program::PushConstant const& constant) {
GLint const loc = glGetUniformLocation(program, constant.name.c_str());
mPushConstants.push_back({loc, constant.type});
};
std::for_each(vertexConstants.cbegin(), vertexConstants.cend(), transformAndAdd);
std::for_each(fragmentConstants.cbegin(), fragmentConstants.cend(), transformAndAdd);
}
}
void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept {
+34 -11
View File
@@ -27,6 +27,7 @@
#include <utils/compiler.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Slice.h>
#include <array>
#include <limits>
@@ -38,6 +39,11 @@ namespace filament::backend {
class OpenGLDriver;
struct PushConstantBundle {
utils::Slice<std::pair<GLint, ConstantType>> vertexConstants;
utils::Slice<std::pair<GLint, ConstantType>> fragmentConstants;
};
class OpenGLProgram : public HwProgram {
public:
@@ -70,14 +76,22 @@ public:
}
}
struct {
GLuint program = 0;
} gl; // 12 bytes
// For ES2 only
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) noexcept;
void setRec709ColorSpace(bool rec709) const noexcept;
struct {
GLuint program = 0;
} gl; // 4 bytes
PushConstantBundle getPushConstants() {
auto fragBegin = mPushConstants.begin() + mPushConstantFragmentStageOffset;
return {
.vertexConstants = utils::Slice(mPushConstants.begin(), fragBegin),
.fragmentConstants = utils::Slice(fragBegin, mPushConstants.end()),
};
}
private:
// keep these away from of other class attributes
struct LazyInitializationData;
@@ -89,14 +103,20 @@ private:
void updateSamplers(OpenGLDriver* gld) const noexcept;
ShaderCompilerService::program_token_t mToken{};
// number of bindings actually used by this program
uint8_t mUsedBindingsCount = 0u;
UTILS_UNUSED uint8_t padding[3] = {};
std::array<uint8_t, Program::SAMPLER_BINDING_COUNT> mUsedSamplerBindingPoints; // 4 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
// only needed for ES2
GLint mRec709Location = -1; // 4 bytes
using LocationInfo = utils::FixedCapacityVector<GLint>;
struct UniformsRecord {
Program::UniformInfo uniforms;
@@ -104,12 +124,15 @@ private:
mutable GLuint id = 0;
mutable uint16_t age = std::numeric_limits<uint16_t>::max();
};
UniformsRecord const* mUniformsRecords = nullptr;
GLint mRec709Location = -1;
UniformsRecord const* mUniformsRecords = nullptr; // 8 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
};
// if OpenGLProgram is larger tha 64 bytes, it'll fall in a larger Handle bucket.
static_assert(sizeof(OpenGLProgram) <= 64);
static_assert(sizeof(OpenGLProgram) <= 64); // currently 64 bytes
} // namespace filament::backend
@@ -30,6 +30,7 @@
#include <utils/Log.h>
#include <utils/Systrace.h>
#include <cctype>
#include <chrono>
#include <string>
#include <string_view>
@@ -264,6 +265,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
compileShaders(gl,
std::move(program.getShadersSource()),
program.getSpecializationConstants(),
program.isMultiview(),
shaders,
token->shaderSourceCode);
@@ -299,6 +301,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
compileShaders(gl,
std::move(program.getShadersSource()),
program.getSpecializationConstants(),
program.isMultiview(),
token->gl.shaders,
token->shaderSourceCode);
@@ -501,6 +504,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
void ShaderCompilerService::compileShaders(OpenGLContext& context,
Program::ShaderSource shadersSource,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
bool multiview,
std::array<GLuint, Program::SHADER_TYPE_COUNT>& outShaders,
UTILS_UNUSED_IN_RELEASE std::array<CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept {
@@ -514,8 +518,16 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
};
std::string specializationConstantString;
int32_t numViews = 2;
for (auto const& sc : specializationConstants) {
appendSpecConstantString(specializationConstantString, sc);
if (sc.id == 8) {
// This constant must match
// ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT
// which we can't use here because it's defined in EngineEnums.h.
// (we're breaking layering here, but it's for the good cause).
numViews = std::get<int32_t>(sc.value);
}
}
if (!specializationConstantString.empty()) {
specializationConstantString += '\n';
@@ -544,17 +556,23 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
if (UTILS_LIKELY(!shadersSource[i].empty())) {
Program::ShaderBlob& shader = shadersSource[i];
char* shader_src = reinterpret_cast<char*>(shader.data());
size_t shader_len = shader.size();
// remove GOOGLE_cpp_style_line_directive
std::string_view const source = process_GOOGLE_cpp_style_line_directive(context,
reinterpret_cast<char*>(shader.data()), shader.size());
process_GOOGLE_cpp_style_line_directive(context, shader_src, shader_len);
// replace the value of layout(num_views = X) for multiview extension
if (multiview && stage == ShaderStage::VERTEX) {
process_OVR_multiview2(context, numViews, shader_src, shader_len);
}
// add support for ARB_shading_language_packing if needed
auto const packingFunctions = process_ARB_shading_language_packing(context);
// split shader source, so we can insert the specialization constants and the packing
// functions
auto const [prolog, body] = splitShaderSource(source);
auto const [prolog, body] = splitShaderSource({ shader_src, shader_len });
const std::array<const char*, 4> sources = {
prolog.data(),
@@ -577,7 +595,7 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
#ifndef NDEBUG
// for debugging we return the original shader source (without the modifications we
// made here), otherwise the line numbers wouldn't match.
outShaderSourceCode[i] = { source.data(), source.length() };
outShaderSourceCode[i] = { shader_src, shader_len };
#endif
outShaders[i] = shaderId;
@@ -586,15 +604,59 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
}
// If usages of the Google-style line directive are present, remove them, as some
// drivers don't allow the quotation marks. This happens in-place.
std::string_view ShaderCompilerService::process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
// drivers don't allow the quotation marks. This source modification happens in-place.
void ShaderCompilerService::process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
char* source, size_t len) noexcept {
if (!context.ext.GOOGLE_cpp_style_line_directive) {
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) {
removeGoogleLineDirectives(source, len); // length is unaffected
}
}
return { source, len };
}
// Look up the `source` to replace the number of eyes for multiview with the given number. This is
// necessary for OpenGL because OpenGL relies on the number specified in shader files to determine
// the number of views, which is assumed as a single digit, for multiview.
// This source modification happens in-place.
void ShaderCompilerService::process_OVR_multiview2(OpenGLContext& context,
int32_t eyeCount, char* source, size_t len) noexcept {
// We don't use regular expression in favor of performance.
if (context.ext.OVR_multiview2) {
const std::string_view shader{ source, len };
const std::string_view layout = "layout";
const std::string_view num_views = "num_views";
size_t found = 0;
while (true) {
found = shader.find(layout, found);
if (found == std::string_view::npos) {
break;
}
found = shader.find_first_not_of(' ', found + layout.size());
if (found == std::string_view::npos || shader[found] != '(') {
continue;
}
found = shader.find_first_not_of(' ', found + 1);
if (found == std::string_view::npos) {
continue;
}
if (shader.compare(found, num_views.size(), num_views) != 0) {
continue;
}
found = shader.find_first_not_of(' ', found + num_views.size());
if (found == std::string_view::npos || shader[found] != '=') {
continue;
}
found = shader.find_first_not_of(' ', found + 1);
if (found == std::string_view::npos) {
continue;
}
// We assume the value should be one-digit number.
assert_invariant(eyeCount < 10);
assert_invariant(!::isdigit(source[found + 1]));
source[found] = '0' + eyeCount;
break;
}
}
}
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
@@ -134,10 +134,14 @@ private:
OpenGLContext& context,
Program::ShaderSource shadersSource,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
bool multiview,
std::array<GLuint, Program::SHADER_TYPE_COUNT>& outShaders,
std::array<utils::CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept;
static std::string_view process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
char* source, size_t len) noexcept;
static void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount,
char* source, size_t len) noexcept;
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
@@ -174,7 +174,7 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
};
#ifdef __ANDROID__
bool requestES2Context = false;
bool requestES2Context = driverConfig.forceGLES2Context;
char property[PROP_VALUE_MAX];
int const length = __system_property_get("debug.filament.es2", property);
if (length > 0) {
@@ -25,6 +25,8 @@
#include <android/api-level.h>
#include <android/hardware_buffer.h>
#include <utils/android/PerformanceHintManager.h>
#include <utils/compiler.h>
#include <utils/ostream.h>
#include <utils/Log.h>
@@ -36,8 +38,11 @@
#include <jni.h>
#include <chrono>
#include <new>
#include <unistd.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
@@ -112,8 +117,38 @@ void PlatformEGLAndroid::terminate() noexcept {
PlatformEGL::terminate();
}
void PlatformEGLAndroid::beginFrame(
int64_t monotonic_clock_ns,
int64_t refreshIntervalNs,
uint32_t frameId) noexcept {
if (mPerformanceHintSession.isValid()) {
if (refreshIntervalNs <= 0) {
// we're not provided with a target time, assume 16.67ms
refreshIntervalNs = 16'666'667;
}
mStartTimeOfActualWork = clock::time_point(std::chrono::nanoseconds(monotonic_clock_ns));
mPerformanceHintSession.updateTargetWorkDuration(refreshIntervalNs);
}
PlatformEGL::beginFrame(monotonic_clock_ns, refreshIntervalNs, frameId);
}
void backend::PlatformEGLAndroid::preCommit() noexcept {
if (mPerformanceHintSession.isValid()) {
auto const actualWorkDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(
clock::now() - mStartTimeOfActualWork);
mPerformanceHintSession.reportActualWorkDuration(actualWorkDuration.count());
}
PlatformEGL::preCommit();
}
Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
const Platform::DriverConfig& driverConfig) noexcept {
// the refresh rate default value doesn't matter, we change it later
int32_t const tid = gettid();
mPerformanceHintSession = PerformanceHintManager::Session{
mPerformanceHintManager, &tid, 1, 16'666'667 };
Driver* driver = PlatformEGL::createDriver(sharedContext, driverConfig);
auto extensions = GLUtils::split(eglQueryString(mEGLDisplay, EGL_EXTENSIONS));
+1
View File
@@ -410,6 +410,7 @@ io::ostream& operator<<(io::ostream& out, const RasterState& rs) {
io::ostream& operator<<(io::ostream& out, const TargetBufferInfo& tbi) {
return out << "TargetBufferInfo{"
<< "handle=" << tbi.handle
<< ", baseViewIndex=" << tbi.baseViewIndex
<< ", level=" << tbi.level
<< ", layer=" << tbi.layer << "}";
}
+12 -44
View File
@@ -26,21 +26,16 @@
#include <smolv.h>
#include "generated/vkshaders/vkshaders.h"
using namespace bluevk;
using namespace utils;
namespace filament::backend {
using ImgUtil = VulkanImageUtility;
namespace {
inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, VkFilter filter,
VulkanAttachment src, VulkanAttachment dst,
const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
<< " layout=" << src.getLayout()
@@ -48,21 +43,8 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
<< " layout=" << dst.getLayout() << utils::io::endl;
}
const VkImageSubresourceRange srcRange = {
.aspectMask = aspect,
.baseMipLevel = src.level,
.levelCount = 1,
.baseArrayLayer = src.layer,
.layerCount = 1,
};
const VkImageSubresourceRange dstRange = {
.aspectMask = aspect,
.baseMipLevel = dst.level,
.levelCount = 1,
.baseArrayLayer = dst.layer,
.layerCount = 1,
};
VkImageSubresourceRange const srcRange = src.getSubresourceRange();
VkImageSubresourceRange const dstRange = dst.getSubresourceRange();
VulkanLayout oldSrcLayout = src.getLayout();
VulkanLayout oldDstLayout = dst.getLayout();
@@ -77,15 +59,15 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
.dstOffsets = { dstRect[0], dstRect[1] },
}};
vkCmdBlitImage(cmdbuffer,
src.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC),
dst.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST),
src.getImage(), imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC),
dst.getImage(), imgutil::getVkLayout(VulkanLayout::TRANSFER_DST),
1, blitRegions, filter);
if (oldSrcLayout == VulkanLayout::UNDEFINED) {
oldSrcLayout = ImgUtil::getDefaultLayout(src.texture->usage);
oldSrcLayout = imgutil::getDefaultLayout(src.texture->usage);
}
if (oldDstLayout == VulkanLayout::UNDEFINED) {
oldDstLayout = ImgUtil::getDefaultLayout(dst.texture->usage);
oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage);
}
src.texture->transitionLayout(cmdbuffer, srcRange, oldSrcLayout);
dst.texture->transitionLayout(cmdbuffer, dstRange, oldDstLayout);
@@ -93,7 +75,6 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
VulkanAttachment src, VulkanAttachment dst) {
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
<< " layout=" << src.getLayout()
@@ -101,21 +82,8 @@ inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspe
<< " layout=" << dst.getLayout() << utils::io::endl;
}
const VkImageSubresourceRange srcRange = {
.aspectMask = aspect,
.baseMipLevel = src.level,
.levelCount = 1,
.baseArrayLayer = src.layer,
.layerCount = 1,
};
const VkImageSubresourceRange dstRange = {
.aspectMask = aspect,
.baseMipLevel = dst.level,
.levelCount = 1,
.baseArrayLayer = dst.layer,
.layerCount = 1,
};
VkImageSubresourceRange const srcRange = src.getSubresourceRange();
VkImageSubresourceRange const dstRange = dst.getSubresourceRange();
VulkanLayout oldSrcLayout = src.getLayout();
VulkanLayout oldDstLayout = dst.getLayout();
@@ -133,15 +101,15 @@ inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspe
.extent = { src.getExtent2D().width, src.getExtent2D().height, 1 },
}};
vkCmdResolveImage(cmdbuffer,
src.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC),
dst.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST),
src.getImage(), imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC),
dst.getImage(), imgutil::getVkLayout(VulkanLayout::TRANSFER_DST),
1, resolveRegions);
if (oldSrcLayout == VulkanLayout::UNDEFINED) {
oldSrcLayout = ImgUtil::getDefaultLayout(src.texture->usage);
oldSrcLayout = imgutil::getDefaultLayout(src.texture->usage);
}
if (oldDstLayout == VulkanLayout::UNDEFINED) {
oldDstLayout = ImgUtil::getDefaultLayout(dst.texture->usage);
oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage);
}
src.texture->transitionLayout(cmdbuffer, srcRange, oldSrcLayout);
dst.texture->transitionLayout(cmdbuffer, dstRange, oldDstLayout);
@@ -17,6 +17,7 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANBLITTER_H
#define TNT_FILAMENT_BACKEND_VULKANBLITTER_H
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include <utils/compiler.h>
+50 -17
View File
@@ -27,8 +27,8 @@ VulkanBuffer::VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool,
VkBufferUsageFlags usage, uint32_t numBytes)
: mAllocator(allocator),
mStagePool(stagePool),
mUsage(usage) {
mUsage(usage),
mUpdatedBytes(0) {
// for now make sure that only 1 bit is set in usage
// (because loadFromCpu() assumes that somewhat)
assert_invariant(usage && !(usage & (usage - 1)));
@@ -49,8 +49,7 @@ VulkanBuffer::~VulkanBuffer() {
}
void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint32_t byteOffset,
uint32_t numBytes) const {
assert_invariant(byteOffset == 0);
uint32_t numBytes) {
VulkanStage const* stage = mStagePool.acquireStage(numBytes);
void* mapped;
vmaMapMemory(mAllocator, stage->memory, &mapped);
@@ -58,15 +57,51 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
vmaUnmapMemory(mAllocator, stage->memory);
vmaFlushAllocation(mAllocator, stage->memory, byteOffset, numBytes);
VkBufferCopy region{ .size = numBytes };
// If there was a previous update, then we need to make sure the following write is properly
// synced with the previous read.
if (mUpdatedBytes > 0) {
VkAccessFlags srcAccess = 0;
VkPipelineStageFlags srcStage = 0;
if (mUsage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) {
srcAccess = VK_ACCESS_SHADER_READ_BIT;
srcStage = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
} else if (mUsage & VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) {
srcAccess = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
srcStage = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
} else if (mUsage & VK_BUFFER_USAGE_INDEX_BUFFER_BIT) {
srcAccess = VK_ACCESS_INDEX_READ_BIT;
srcStage = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
}
VkBufferMemoryBarrier barrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.srcAccessMask = srcAccess,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = mGpuBuffer,
.size = mUpdatedBytes,
};
vkCmdPipelineBarrier(cmdbuf, srcStage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1,
&barrier, 0, nullptr);
}
VkBufferCopy region {
.srcOffset = 0,
.dstOffset = byteOffset,
.size = numBytes,
};
vkCmdCopyBuffer(cmdbuf, stage->buffer, mGpuBuffer, 1, &region);
mUpdatedBytes = numBytes;
// Firstly, ensure that the copy finishes before the next draw call.
// Secondly, in case the user decides to upload another chunk (without ever using the first one)
// we need to ensure that this upload completes first (hence
// dstStageMask=VK_PIPELINE_STAGE_TRANSFER_BIT).
VkAccessFlags dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
if (mUsage & VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) {
dstAccessMask |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
dstStageMask |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
@@ -75,26 +110,24 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
dstStageMask |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
} else if (mUsage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) {
dstAccessMask |= VK_ACCESS_UNIFORM_READ_BIT;
// NOTE: ideally dstStageMask would include VERTEX_SHADER_BIT | FRAGMENT_SHADER_BIT, but
// this seems to be insufficient on Mali devices. To work around this we are using a more
// aggressive ALL_GRAPHICS_BIT barrier.
dstStageMask |= VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
dstStageMask |=
(VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
} else if (mUsage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) {
// TODO: implement me
}
VkBufferMemoryBarrier barrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = dstAccessMask,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = mGpuBuffer,
.size = VK_WHOLE_SIZE,
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = dstAccessMask,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = mGpuBuffer,
.size = VK_WHOLE_SIZE,
};
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, 0, 0, nullptr, 1,
&barrier, 0, nullptr);
&barrier, 0, nullptr);
}
} // namespace filament::backend
+3 -1
View File
@@ -19,6 +19,7 @@
#include "VulkanContext.h"
#include "VulkanStagePool.h"
#include "VulkanMemory.h"
namespace filament::backend {
@@ -29,7 +30,7 @@ public:
uint32_t numBytes);
~VulkanBuffer();
void loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint32_t byteOffset,
uint32_t numBytes) const;
uint32_t numBytes);
VkBuffer getGpuBuffer() const {
return mGpuBuffer;
}
@@ -41,6 +42,7 @@ private:
VmaAllocation mGpuMemory = VK_NULL_HANDLE;
VkBuffer mGpuBuffer = VK_NULL_HANDLE;
VkBufferUsageFlags mUsage = {};
uint32_t mUpdatedBytes = 0;
};
} // namespace filament::backend
@@ -47,7 +47,8 @@ VulkanCmdFence::VulkanCmdFence(VkFence ifence)
VulkanCommandBuffer::VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device,
VkCommandPool pool)
: mResourceManager(allocator) {
: mResourceManager(allocator),
mPipeline(VK_NULL_HANDLE) {
// Create the low-level command buffer.
const VkCommandBufferAllocateInfo allocateInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
@@ -89,6 +89,15 @@ struct VulkanCommandBuffer {
inline void reset() {
fence.reset();
mResourceManager.clear();
mPipeline = VK_NULL_HANDLE;
}
inline void setPipeline(VkPipeline pipeline) {
mPipeline = pipeline;
}
inline VkPipeline pipeline() const {
return mPipeline;
}
inline VkCommandBuffer buffer() const {
@@ -103,6 +112,7 @@ struct VulkanCommandBuffer {
private:
VulkanAcquireOnlyResourceManager mResourceManager;
VkCommandBuffer mBuffer;
VkPipeline mPipeline;
};
// Allows classes to be notified after a new command buffer has been activated.
+15 -14
View File
@@ -43,9 +43,12 @@
// Enables Android systrace
#define FVK_DEBUG_SYSTRACE 0x00000001
// Group markers are used to denote collection of GPU commands. It is typically at the granualarity
// of a renderpass.
// Group markers are used to denote collections of GPU commands. It is typically at the
// granualarity of a renderpass. You can enable this along with FVK_DEBUG_DEBUG_UTILS to take
// advantage of vkCmdBegin/EndDebugUtilsLabelEXT. You can also just enable this with
// FVK_DEBUG_PRINT_GROUP_MARKERS to print the current marker to stdout.
#define FVK_DEBUG_GROUP_MARKERS 0x00000002
#define FVK_DEBUG_TEXTURE 0x00000004
#define FVK_DEBUG_LAYOUT_TRANSITION 0x00000008
#define FVK_DEBUG_COMMAND_BUFFER 0x00000010
@@ -63,25 +66,23 @@
// Enable the debug utils extension if it is available.
#define FVK_DEBUG_DEBUG_UTILS 0x00008000
// Use this to debug potential Handle/Resource leakage. It will print out reference counts for all
// the currently active resources.
#define FVK_DEBUG_RESOURCE_LEAK 0x00010000
// Usefaul default combinations
// Useful default combinations
#define FVK_DEBUG_EVERYTHING 0xFFFFFFFF
#define FVK_DEBUG_PERFORMANCE \
FVK_DEBUG_SYSTRACE
#define FVK_DEBUG_CORRECTNESS \
FVK_DEBUG_VALIDATION | \
FVK_DEBUG_SHADER_MODULE | \
FVK_DEBUG_TEXTURE | \
FVK_DEBUG_LAYOUT_TRANSITION
#define FVK_DEBUG_RENDER_PASSES \
FVK_DEBUG_GROUP_MARKERS | \
FVK_DEBUG_PRINT_GROUP_MARKERS
#if defined(FILAMENT_BACKEND_DEBUG_FLAG)
#define FVK_DEBUG_FORWARDED_FLAG (FILAMENT_BACKEND_DEBUG_FLAG & FVK_DEBUG_EVERYTHING)
#else
#define FVK_DEBUG_FORWARDED_FLAG 0
#endif
#ifndef NDEBUG
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE)
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE | FVK_DEBUG_FORWARDED_FLAG)
#else
#define FVK_DEBUG_FLAGS 0
#endif
@@ -107,7 +108,7 @@ static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
// end dependcy checks
// Shorthand for combination of enabled debug flags
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) || FVK_ENABLED(FVK_DEBUG_TEXTURE)
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) && FVK_ENABLED(FVK_DEBUG_TEXTURE)
#define FVK_ENABLED_DEBUG_SAMPLER_NAME 1
#else
#define FVK_ENABLED_DEBUG_SAMPLER_NAME 0
@@ -57,15 +57,19 @@ VkExtent2D VulkanAttachment::getExtent2D() const {
return { std::max(1u, texture->width >> level), std::max(1u, texture->height >> level) };
}
VkImageView VulkanAttachment::getImageView(VkImageAspectFlags aspect) {
VkImageView VulkanAttachment::getImageView() {
assert_invariant(texture);
return texture->getAttachmentView(getSubresourceRange(aspect));
return texture->getAttachmentView(getSubresourceRange());
}
VkImageSubresourceRange VulkanAttachment::getSubresourceRange(VkImageAspectFlags aspect) const {
bool VulkanAttachment::isDepth() const {
return texture->getImageAspect() & VK_IMAGE_ASPECT_DEPTH_BIT;
}
VkImageSubresourceRange VulkanAttachment::getSubresourceRange() const {
assert_invariant(texture);
return {
.aspectMask = aspect,
.aspectMask = texture->getImageAspect(),
.baseMipLevel = uint32_t(level),
.levelCount = 1,
.baseArrayLayer = uint32_t(layer),
+13 -7
View File
@@ -19,7 +19,6 @@
#include "VulkanConstants.h"
#include "VulkanImageUtility.h"
#include "VulkanPipelineCache.h"
#include "VulkanUtility.h"
#include <utils/bitset.h>
@@ -42,16 +41,18 @@ struct VulkanTimerQuery;
struct VulkanCommandBuffer;
struct VulkanAttachment {
VulkanTexture* texture;
VulkanTexture* texture = nullptr;
uint8_t level = 0;
uint16_t layer = 0;
bool isDepth() const;
VkImage getImage() const;
VkFormat getFormat() const;
VulkanLayout getLayout() const;
VkExtent2D getExtent2D() const;
VkImageView getImageView(VkImageAspectFlags aspect);
VkImageView getImageView();
// TODO: maybe embed aspect into the attachment or texture itself.
VkImageSubresourceRange getSubresourceRange(VkImageAspectFlags aspect) const;
VkImageSubresourceRange getSubresourceRange() const;
};
class VulkanTimestamps {
@@ -102,8 +103,12 @@ public:
return (uint32_t) VK_MAX_MEMORY_TYPES;
}
inline VkFormatList const& getAttachmentDepthFormats() const {
return mDepthFormats;
inline VkFormatList const& getAttachmentDepthStencilFormats() const {
return mDepthStencilFormats;
}
inline VkFormatList const& getBlittableDepthStencilFormats() const {
return mBlittableDepthStencilFormats;
}
inline VkPhysicalDeviceLimits const& getPhysicalDeviceLimits() const noexcept {
@@ -132,7 +137,8 @@ private:
bool mDebugMarkersSupported = false;
bool mDebugUtilsSupported = false;
VkFormatList mDepthFormats;
VkFormatList mDepthStencilFormats;
VkFormatList mBlittableDepthStencilFormats;
// For convenience so that VulkanPlatform can initialize the private fields.
friend class VulkanPlatform;
+157 -135
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
#include "vulkan/VulkanDriver.h"
#include "VulkanDriver.h"
#include "CommandStreamDispatcher.h"
#include "DataReshaper.h"
@@ -22,7 +22,6 @@
#include "VulkanCommands.h"
#include "VulkanDriverFactory.h"
#include "VulkanHandles.h"
#include "VulkanImageUtility.h"
#include "VulkanMemory.h"
#include "VulkanTexture.h"
@@ -102,6 +101,15 @@ VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevi
return emptyTexture;
}
VulkanBufferObject* createEmptyBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanCommands* commands) {
VulkanBufferObject* obj =
new VulkanBufferObject(allocator, stagePool, 1, BufferObjectBinding::UNIFORM);
uint8_t byte = 0;
obj->buffer.loadFromCpu(commands->get().buffer(), &byte, 0, 1);
return obj;
}
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location,
@@ -197,8 +205,6 @@ void DebugUtils::setName(VkObjectType type, uint64_t handle, char const* name) {
}
#endif // FVK_EANBLED(FVK_DEBUG_DEBUG_UTILS)
using ImgUtil = VulkanImageUtility;
Dispatcher VulkanDriver::getDispatcher() const noexcept {
return ConcreteDispatcher<VulkanDriver>::make();
}
@@ -214,12 +220,14 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
mThreadSafeResourceManager(&mResourceAllocator),
mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(),
mPlatform->getGraphicsQueueFamilyIndex(), &mContext, &mResourceAllocator),
mPipelineCache(&mResourceAllocator),
mPipelineLayoutCache(mPlatform->getDevice(), &mResourceAllocator),
mPipelineCache(mPlatform->getDevice(), mAllocator),
mStagePool(mAllocator, &mCommands),
mFramebufferCache(mPlatform->getDevice()),
mSamplerCache(mPlatform->getDevice()),
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
mReadPixels(mPlatform->getDevice()),
mDescriptorSetManager(mPlatform->getDevice(), &mResourceAllocator),
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported) {
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
@@ -243,13 +251,17 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
#endif
mTimestamps = std::make_unique<VulkanTimestamps>(mPlatform->getDevice());
mCommands.setObserver(&mPipelineCache);
mPipelineCache.setDevice(mPlatform->getDevice(), mAllocator);
mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(),
mContext, mAllocator, &mCommands, mStagePool);
mEmptyBufferObject = createEmptyBufferObject(mAllocator, mStagePool, &mCommands);
mPipelineCache.setDummyTexture(mEmptyTexture->getPrimaryImageView());
mDescriptorSetManager.setPlaceHolders(mSamplerCache.getSampler({}), mEmptyTexture,
mEmptyBufferObject);
mGetPipelineFunction = [this](VulkanDescriptorSetLayoutList const& layouts, VulkanProgram* program) {
return mPipelineLayoutCache.getLayout(layouts, program);
};
}
VulkanDriver::~VulkanDriver() noexcept = default;
@@ -310,13 +322,14 @@ ShaderModel VulkanDriver::getShaderModel() const noexcept {
}
void VulkanDriver::terminate() {
delete mEmptyBufferObject;
delete mEmptyTexture;
// Command buffers should come first since it might have commands depending on resources that
// are about to be destroyed.
mCommands.terminate();
delete mEmptyTexture;
mResourceManager.clear();
mTimestamps.reset();
mBlitter.terminate();
@@ -329,6 +342,12 @@ void VulkanDriver::terminate() {
mPipelineCache.terminate();
mFramebufferCache.reset();
mSamplerCache.terminate();
mDescriptorSetManager.terminate();
mPipelineLayoutCache.terminate();
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
mResourceAllocator.print();
#endif
vmaDestroyAllocator(mAllocator);
@@ -360,6 +379,8 @@ void VulkanDriver::collectGarbage() {
mCommands.gc();
mStagePool.gc();
mFramebufferCache.gc();
mPipelineCache.gc();
mDescriptorSetManager.gc();
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
mResourceAllocator.print();
@@ -367,16 +388,17 @@ void VulkanDriver::collectGarbage() {
FVK_SYSTRACE_END();
}
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) {
// Do nothing.
}
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
FrameScheduledCallback callback, void* user) {
CallbackHandler* handler, FrameScheduledCallback&& callback) {
}
void VulkanDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
}
void VulkanDriver::setPresentationTime(int64_t monotonic_clock_ns) {
@@ -489,9 +511,6 @@ void VulkanDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
return;
}
auto bufferObject = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
if (bufferObject->bindingType == BufferObjectBinding::UNIFORM) {
mPipelineCache.unbindUniformBuffer(bufferObject->buffer.getGpuBuffer());
}
mResourceManager.release(bufferObject);
}
@@ -542,6 +561,7 @@ void VulkanDriver::destroyProgram(Handle<HwProgram> ph) {
return;
}
auto vkprogram = mResourceAllocator.handle_cast<VulkanProgram*>(ph);
mDescriptorSetManager.clearProgram(vkprogram);
mResourceManager.release(vkprogram);
}
@@ -898,6 +918,11 @@ bool VulkanDriver::isDepthStencilResolveSupported() {
return false;
}
bool VulkanDriver::isDepthStencilBlitSupported(TextureFormat format) {
auto const& formats = mContext.getBlittableDepthStencilFormats();
return std::find(formats.begin(), formats.end(), getVkFormat(format)) != formats.end();
}
bool VulkanDriver::isProtectedTexturesSupported() {
return false;
}
@@ -1211,12 +1236,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
}
}
VulkanLayout const currentDepthLayout = depth.getLayout();
VulkanLayout const renderPassDepthLayout = VulkanLayout::DEPTH_ATTACHMENT;
// We need to keep the final layout as an attachment because the implicit transition does not
// have any barrier guarrantees, meaning that if we want to sample from the output in the next
// pass, then we'd have a race-condition/validation error.
VulkanLayout const finalDepthLayout = renderPassDepthLayout;
VulkanLayout currentDepthLayout = depth.getLayout();
TargetBufferFlags clearVal = params.flags.clear;
TargetBufferFlags discardEndVal = params.flags.discardEnd;
@@ -1225,16 +1245,20 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
discardEndVal &= ~TargetBufferFlags::DEPTH;
clearVal &= ~TargetBufferFlags::DEPTH;
}
auto const attachmentSubresourceRange = depth.getSubresourceRange(VK_IMAGE_ASPECT_DEPTH_BIT);
depth.texture->setLayout(attachmentSubresourceRange, VulkanLayout::DEPTH_ATTACHMENT);
// 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;
}
}
// Create the VkRenderPass or fetch it from cache.
VulkanFboCache::RenderPassKey rpkey = {
.initialColorLayoutMask = 0,
.initialDepthLayout = currentDepthLayout,
.renderPassDepthLayout = renderPassDepthLayout,
.finalDepthLayout = finalDepthLayout,
.depthFormat = depth.getFormat(),
.clear = clearVal,
.discardStart = discardStart,
@@ -1251,9 +1275,9 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
rpkey.needsResolveMask |= (1 << i);
}
if (info.texture->getPrimaryImageLayout() != VulkanLayout::COLOR_ATTACHMENT) {
((VulkanTexture*) info.texture)->transitionLayout(cmdbuffer,
info.getSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT),
VulkanLayout::COLOR_ATTACHMENT);
((VulkanTexture*) info.texture)
->transitionLayout(cmdbuffer, info.getSubresourceRange(),
VulkanLayout::COLOR_ATTACHMENT);
}
} else {
rpkey.colorFormat[i] = VK_FORMAT_UNDEFINED;
@@ -1271,27 +1295,42 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
.layers = 1,
.samples = rpkey.samples,
};
auto& renderPassAttachments = mRenderPassFboInfo.attachments;
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (!rt->getColor(i).texture) {
fbkey.color[i] = VK_NULL_HANDLE;
fbkey.resolve[i] = VK_NULL_HANDLE;
} else if (fbkey.samples == 1) {
fbkey.color[i] = rt->getColor(i).getImageView(VK_IMAGE_ASPECT_COLOR_BIT);
auto& colorAttachment = rt->getColor(i);
renderPassAttachments.insert(colorAttachment);
fbkey.color[i] = colorAttachment.getImageView();
fbkey.resolve[i] = VK_NULL_HANDLE;
assert_invariant(fbkey.color[i]);
} else {
fbkey.color[i] = rt->getMsaaColor(i).getImageView(VK_IMAGE_ASPECT_COLOR_BIT);
VulkanTexture* texture = (VulkanTexture*) rt->getColor(i).texture;
auto& msaaColorAttachment = rt->getMsaaColor(i);
renderPassAttachments.insert(msaaColorAttachment);
auto& colorAttachment = rt->getColor(i);
fbkey.color[i] = msaaColorAttachment.getImageView();
VulkanTexture* texture = colorAttachment.texture;
if (texture->samples == 1) {
fbkey.resolve[i] = rt->getColor(i).getImageView(VK_IMAGE_ASPECT_COLOR_BIT);
mRenderPassFboInfo.hasColorResolve = true;
renderPassAttachments.insert(colorAttachment);
fbkey.resolve[i] = colorAttachment.getImageView();
assert_invariant(fbkey.resolve[i]);
}
assert_invariant(fbkey.color[i]);
}
}
if (depth.texture) {
fbkey.depth = depth.getImageView(VK_IMAGE_ASPECT_DEPTH_BIT);
fbkey.depth = depth.getImageView();
assert_invariant(fbkey.depth);
renderPassAttachments.insert(depth);
UTILS_UNUSED_IN_RELEASE bool const depthDiscardEnd =
any(rpkey.discardEnd & TargetBufferFlags::DEPTH);
// Vulkan 1.1 does not support multisampled depth resolve, so let's check here
// and assert if this is requested. (c.f. isAutoDepthResolveSupported)
@@ -1300,7 +1339,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
// - If the RT is MS then all SS attachments are auto resolved if not discarded.
assert_invariant(!(rt->getSamples() > 1 &&
rt->getDepth().texture->samples == 1 &&
!any(rpkey.discardEnd & TargetBufferFlags::DEPTH)));
!depthDiscardEnd));
}
VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
@@ -1313,16 +1352,10 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
}
#endif
// The current command buffer now owns a reference to the render target and its attachments.
// Note that we must acquire parent textures, not sidecars.
// The current command buffer now has references to the render target and its attachments.
commands.acquire(rt);
if (depth.texture) {
commands.acquire((VulkanTexture*) depth.texture);
}
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (rt->getColor(i).texture) {
commands.acquire(rt->getColor(i).texture);
}
for (auto const& attachment: renderPassAttachments) {
commands.acquire(attachment.texture);
}
// Populate the structures required for vkCmdBeginRenderPass.
@@ -1408,33 +1441,52 @@ void VulkanDriver::endRenderPass(int) {
// 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.
// NOTE: ideally dstStageMask would merely be VERTEX_SHADER_BIT | FRAGMENT_SHADER_BIT, but this
// seems to be insufficient on Mali devices. To work around this we are adding a more aggressive
// TOP_OF_PIPE barrier.
if (!rt->isSwapChain()) {
VkMemoryBarrier barrier{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
};
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
if (rt->hasDepth()) {
barrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
srcStageMask |= VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
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);
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);
}
vkCmdPipelineBarrier(cmdbuffer, srcStageMask,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | // <== For Mali
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0, 1, &barrier, 0, nullptr, 0, nullptr);
}
if (mCurrentRenderPass.currentSubpass > 0) {
for (uint32_t i = 0; i < VulkanPipelineCache::INPUT_ATTACHMENT_COUNT; i++) {
mPipelineCache.bindInputAttachment(i, {});
}
mCurrentRenderPass.currentSubpass = 0;
}
mRenderPassFboInfo.clear();
mDescriptorSetManager.clearState();
mCurrentRenderPass.renderTarget = nullptr;
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
FVK_SYSTRACE_END();
@@ -1453,15 +1505,9 @@ void VulkanDriver::nextSubpass(int) {
mPipelineCache.bindRenderPass(mCurrentRenderPass.renderPass,
++mCurrentRenderPass.currentSubpass);
for (uint32_t i = 0; i < VulkanPipelineCache::INPUT_ATTACHMENT_COUNT; i++) {
if ((1 << i) & mCurrentRenderPass.params.subpassMask) {
VulkanAttachment subpassInput = renderTarget->getColor(i);
VkDescriptorImageInfo info = {
.imageView = subpassInput.getImageView(VK_IMAGE_ASPECT_COLOR_BIT),
.imageLayout = ImgUtil::getVkLayout(subpassInput.getLayout()),
};
mPipelineCache.bindInputAttachment(i, info);
}
if (mCurrentRenderPass.params.subpassMask & 0x1) {
VulkanAttachment subpassInput = renderTarget->getColor(0);
mDescriptorSetManager.updateInputAttachment({}, subpassInput);
}
}
@@ -1501,25 +1547,24 @@ void VulkanDriver::commit(Handle<HwSwapChain> sch) {
void VulkanDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> boh) {
auto* bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
const VkDeviceSize offset = 0;
const VkDeviceSize size = VK_WHOLE_SIZE;
mPipelineCache.bindUniformBufferObject((uint32_t) index, bo, offset, size);
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::SHADER_STORAGE ||
bindingType == BufferObjectBinding::UNIFORM);
assert_invariant(bindingType == BufferObjectBinding::UNIFORM);
// TODO: implement BufferObjectBinding::SHADER_STORAGE case
auto* bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
mPipelineCache.bindUniformBufferObject((uint32_t)index, bo, offset, size);
mDescriptorSetManager.updateBuffer({}, (uint32_t) index, bo, offset, size);
}
void VulkanDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
// TODO: implement unbindBuffer()
mDescriptorSetManager.clearBuffer((uint32_t) index);
}
void VulkanDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
@@ -1527,6 +1572,14 @@ void VulkanDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> 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");
VulkanCommands* commands = &mCommands;
mBoundPipeline.program->writePushConstant(commands, mBoundPipeline.pipelineLayout, stage, index,
value);
}
void VulkanDriver::insertEventMarker(char const* string, uint32_t len) {
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
mCommands.insertEventMarker(string, len);
@@ -1710,7 +1763,7 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
FVK_SYSTRACE_END();
}
void VulkanDriver::bindPipeline(PipelineState pipelineState) {
void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("draw");
@@ -1767,22 +1820,14 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
// where "SamplerBinding" is the integer in the GLSL, and SamplerGroupBinding is the abstract
// Filament concept used to form groups of samplers.
VkDescriptorImageInfo samplerInfo[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
VulkanTexture* samplerTextures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr};
auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex();
VulkanPipelineCache::UsageFlags usage = program->getUsage();
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
auto const& bindingToName = program->getBindingToName();
#endif
UTILS_NOUNROLL
for (uint8_t binding = 0; binding < VulkanPipelineCache::SAMPLER_BINDING_COUNT; binding++) {
for (auto binding: program->getBindings()) {
uint16_t const indexPair = bindingToSamplerIndex[binding];
if (indexPair == 0xffff) {
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
continue;
}
@@ -1791,24 +1836,20 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupInd];
if (!vksb) {
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
continue;
}
SamplerDescriptor const* boundSampler = ((SamplerDescriptor*) vksb->sb->data()) + samplerInd;
if (UTILS_UNLIKELY(!boundSampler->t)) {
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
continue;
}
VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
VkImageViewType const expectedType = texture->getViewType();
// 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)
#if FVK_ENABLED(FVK_DEBUG_TEXTURE) && FVK_ENABLED_DEBUG_SAMPLER_NAME
utils::slog.w << "Uninitialized texture bound to '" << bindingToName[binding] << "'";
utils::slog.w << " in material '" << program->name.c_str() << "'";
utils::slog.w << " at binding point " << +binding << utils::io::endl;
@@ -1816,43 +1857,28 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
texture = mEmptyTexture;
}
SamplerParams const& samplerParams = boundSampler->s;
VkSampler const vksampler = mSamplerCache.getSampler(samplerParams);
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
VkImageView imageView = VK_NULL_HANDLE;
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
expectedType == VK_IMAGE_VIEW_TYPE_2D) {
// If the sampler is part of a mipmapped depth texture, where one of the level *can* be
// an attachment, then the sampler for this texture has the same view properties as a
// view for an attachment. Therefore, we can use getAttachmentView to get a
// corresponding VkImageView.
imageView = texture->getAttachmentView(range);
} else {
imageView = texture->getViewForType(range, expectedType);
}
samplerInfo[binding] = {
.sampler = vksampler,
.imageView = imageView,
.imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout())
};
samplerTextures[binding] = texture;
mDescriptorSetManager.updateSampler({}, binding, texture, vksampler);
}
mPipelineCache.bindSamplers(samplerInfo, samplerTextures, usage);
auto const pipelineLayout = mDescriptorSetManager.bind(commands, program, mGetPipelineFunction);
mBoundPipeline = {
.program = program,
.pipelineLayout = pipelineLayout,
};
mPipelineCache.bindLayout(pipelineLayout);
mPipelineCache.bindPipeline(commands);
// Since we don't statically define scissor as part of the pipeline, we need to call scissor at
// least once. Context: VUID-vkCmdDrawIndexed-None-07832.
auto const& extent = rt->getExtent();
scissor({0, 0, extent.width, extent.height});
// Bind a new pipeline if the pipeline state changed.
// If allocation failed, skip the draw call and bail. We do not emit an error since the
// validation layer will already do so.
if (!mPipelineCache.bindPipeline(commands)) {
return;
}
FVK_SYSTRACE_END();
}
@@ -1892,12 +1918,8 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
VulkanCommandBuffer& commands = mCommands.get();
VkCommandBuffer cmdbuffer = commands.buffer();
// Bind new descriptor sets if they need to change.
// If descriptor set allocation failed, skip the draw call and bail. No need to emit an error
// message since the validation layers already do so.
if (!mPipelineCache.bindDescriptors(cmdbuffer)) {
return;
}
// Bind "dynamic" UBOs if they need to change.
mDescriptorSetManager.dynamicBind(&commands, {});
// Finally, make the actual draw call. TODO: support subranges
const uint32_t firstIndex = indexOffset;
@@ -1947,7 +1969,7 @@ void VulkanDriver::scissor(Viewport scissorBox) {
const VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
rt->transformClientRectToPlatform(&scissor);
mPipelineCache.bindScissor(cmdbuffer, scissor);
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
}
void VulkanDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
+39 -1
View File
@@ -28,6 +28,8 @@
#include "VulkanSamplerCache.h"
#include "VulkanStagePool.h"
#include "VulkanUtility.h"
#include "caching/VulkanDescriptorSetManager.h"
#include "caching/VulkanPipelineLayoutCache.h"
#include "DriverBase.h"
#include "private/backend/Driver.h"
@@ -40,6 +42,25 @@ namespace filament::backend {
class VulkanPlatform;
struct VulkanSamplerGroup;
// The maximum number of attachments for any renderpass (color + resolve + depth)
constexpr uint8_t MAX_RENDERTARGET_ATTACHMENT_TEXTURES =
MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT * 2 + 1;
// We need to store information about a render pass to enable better barriers at the end of a
// renderpass.
struct RenderPassFboBundle {
using AttachmentArray =
CappedArray<VulkanAttachment, MAX_RENDERTARGET_ATTACHMENT_TEXTURES>;
AttachmentArray attachments;
bool hasColorResolve = false;
void clear() {
attachments.clear();
hasColorResolve = false;
}
};
class VulkanDriver final : public DriverBase {
public:
static Driver* create(VulkanPlatform* platform, VulkanContext const& context,
@@ -70,6 +91,8 @@ public:
#endif // FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
private:
static constexpr uint8_t MAX_SAMPLER_BINDING_COUNT = Program::SAMPLER_BINDING_COUNT;
void debugCommandBegin(CommandStream* cmds, bool synchronous,
const char* methodName) noexcept override;
@@ -106,7 +129,9 @@ private:
VulkanPlatform* mPlatform = nullptr;
std::unique_ptr<VulkanTimestamps> mTimestamps;
// Placeholder resources
VulkanTexture* mEmptyTexture;
VulkanBufferObject* mEmptyBufferObject;
VulkanSwapChain* mCurrentSwapChain = nullptr;
VulkanRenderTarget* mDefaultRenderTarget = nullptr;
@@ -123,13 +148,26 @@ private:
VulkanThreadSafeResourceManager mThreadSafeResourceManager;
VulkanCommands mCommands;
VulkanPipelineLayoutCache mPipelineLayoutCache;
VulkanPipelineCache mPipelineCache;
VulkanStagePool mStagePool;
VulkanFboCache mFramebufferCache;
VulkanSamplerCache mSamplerCache;
VulkanBlitter mBlitter;
VulkanSamplerGroup* mSamplerBindings[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
VulkanSamplerGroup* mSamplerBindings[MAX_SAMPLER_BINDING_COUNT] = {};
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;
};
BoundPipeline mBoundPipeline = {};
RenderPassFboBundle mRenderPassFboInfo;
bool const mIsSRGBSwapChainSupported;
};
+11 -15
View File
@@ -29,14 +29,10 @@ using namespace bluevk;
namespace filament::backend {
using ImgUtil = VulkanImageUtility;
bool VulkanFboCache::RenderPassEq::operator()(const RenderPassKey& k1,
const RenderPassKey& k2) const {
if (k1.initialColorLayoutMask != k2.initialColorLayoutMask) return false;
if (k1.initialDepthLayout != k2.initialDepthLayout) return false;
if (k1.renderPassDepthLayout != k2.renderPassDepthLayout) return false;
if (k1.finalDepthLayout != k2.finalDepthLayout) return false;
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (k1.colorFormat[i] != k2.colorFormat[i]) return false;
}
@@ -197,7 +193,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
if (config.colorFormat[i] == VK_FORMAT_UNDEFINED) {
continue;
}
const VkImageLayout subpassLayout = ImgUtil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT);
const VkImageLayout subpassLayout = imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT);
uint32_t index;
if (!hasSubpasses) {
@@ -239,13 +235,13 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
.format = config.colorFormat[i],
.samples = (VkSampleCountFlagBits) config.samples,
.loadOp = clear ? kClear : (discard ? kDontCare : kKeep),
.storeOp = config.samples == 1 ? kEnableStore : kDisableStore,
.storeOp = kEnableStore,
.stencilLoadOp = kDontCare,
.stencilStoreOp = kDisableStore,
.initialLayout = ((!discard && config.initialColorLayoutMask & (1 << i)) || clear)
? ImgUtil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT)
: ImgUtil::getVkLayout(VulkanLayout::UNDEFINED),
.finalLayout = ImgUtil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT),
? imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT)
: imgutil::getVkLayout(VulkanLayout::UNDEFINED),
.finalLayout = imgutil::getVkLayout(FINAL_COLOR_ATTACHMENT_LAYOUT),
};
}
@@ -272,7 +268,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
pResolveAttachment->attachment = attachmentIndex;
pResolveAttachment->layout
= ImgUtil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT_RESOLVE);
= imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT_RESOLVE);
++pResolveAttachment;
attachments[attachmentIndex++] = {
@@ -282,8 +278,8 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
.storeOp = kEnableStore,
.stencilLoadOp = kDontCare,
.stencilStoreOp = kDisableStore,
.initialLayout = ImgUtil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT),
.finalLayout = ImgUtil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT),
.initialLayout = imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT),
.finalLayout = imgutil::getVkLayout(FINAL_COLOR_ATTACHMENT_LAYOUT),
};
}
@@ -292,7 +288,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
const bool clear = any(config.clear & TargetBufferFlags::DEPTH);
const bool discardStart = any(config.discardStart & TargetBufferFlags::DEPTH);
const bool discardEnd = any(config.discardEnd & TargetBufferFlags::DEPTH);
depthAttachmentRef.layout = ImgUtil::getVkLayout(config.renderPassDepthLayout);
depthAttachmentRef.layout = imgutil::getVkLayout(VulkanLayout::DEPTH_ATTACHMENT);
depthAttachmentRef.attachment = attachmentIndex;
attachments[attachmentIndex++] = {
.format = config.depthFormat,
@@ -301,8 +297,8 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
.storeOp = discardEnd ? kDisableStore : kEnableStore,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = ImgUtil::getVkLayout(config.initialDepthLayout),
.finalLayout = ImgUtil::getVkLayout(config.finalDepthLayout),
.initialLayout = imgutil::getVkLayout(config.initialDepthLayout),
.finalLayout = imgutil::getVkLayout(FINAL_DEPTH_ATTACHMENT_LAYOUT),
};
}
renderPassInfo.attachmentCount = attachmentIndex;
+9 -8
View File
@@ -35,24 +35,25 @@ namespace filament::backend {
//
class VulkanFboCache {
public:
constexpr static VulkanLayout FINAL_COLOR_ATTACHMENT_LAYOUT = VulkanLayout::COLOR_ATTACHMENT;
constexpr static VulkanLayout FINAL_RESOLVE_ATTACHMENT_LAYOUT = VulkanLayout::COLOR_ATTACHMENT;
constexpr static VulkanLayout FINAL_DEPTH_ATTACHMENT_LAYOUT = VulkanLayout::DEPTH_ATTACHMENT;
// RenderPassKey is a small POD representing the immutable state that is used to construct
// a VkRenderPass. It is hashed and used as a lookup key.
// TODO: This struct can be reduced in size by using a subset of formats instead of VkFormat
// and removing the "finalDepthLayout" field.
struct alignas(8) RenderPassKey {
// For each target, we need to know three image layouts: the layout BEFORE the pass, the
// layout DURING the pass, and the layout AFTER the pass. Here are the rules:
// - For depth, we explicitly specify all three layouts.
// - Color targets have their initial image layout specified with a bitmask.
// - For each color target, the pre-existing layout is either UNDEFINED (0) or GENERAL (1).
// - The render pass and final images layout for color buffers is always GENERAL.
// - The render pass and final images layout for color buffers is always
// VulkanLayout::COLOR_ATTACHMENT.
uint8_t initialColorLayoutMask;
// Note that if VulkanLayout grows beyond 16, we'd need to up this.
VulkanLayout initialDepthLayout : 4;
VulkanLayout renderPassDepthLayout : 4;
VulkanLayout finalDepthLayout : 4;
uint8_t padding0 : 4;
VulkanLayout initialDepthLayout : 8;
uint8_t padding0;
uint8_t padding1;
VkFormat colorFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]; // 32 bytes
@@ -63,7 +64,7 @@ public:
uint8_t samples; // 1 byte
uint8_t needsResolveMask; // 1 byte
uint8_t subpassMask; // 1 byte
bool padding2; // 1 byte
uint8_t padding2; // 1 byte
};
struct RenderPassVal {
VkRenderPass handle;
+214 -30
View File
@@ -20,7 +20,9 @@
#include "VulkanConstants.h"
#include "VulkanDriver.h"
#include "VulkanMemory.h"
#include "VulkanUtility.h"
#include "spirv/VulkanSpirvUtils.h"
#include "utils/Log.h"
#include <backend/platforms/VulkanPlatform.h>
@@ -30,15 +32,17 @@ using namespace bluevk;
namespace filament::backend {
static void flipVertically(VkRect2D* rect, uint32_t framebufferHeight) {
namespace {
void flipVertically(VkRect2D* rect, uint32_t framebufferHeight) {
rect->offset.y = framebufferHeight - rect->offset.y - rect->extent.height;
}
static void flipVertically(VkViewport* rect, uint32_t framebufferHeight) {
void flipVertically(VkViewport* rect, uint32_t framebufferHeight) {
rect->y = framebufferHeight - rect->y - rect->height;
}
static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) {
void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) {
int32_t x = std::max(rect->offset.x, 0);
int32_t y = std::max(rect->offset.y, 0);
int32_t right = std::min(rect->offset.x + (int32_t) rect->extent.width, (int32_t) fbWidth);
@@ -49,17 +53,165 @@ static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeig
rect->extent.height = std::max(top - y, 0);
}
template<typename Bitmask>
static constexpr Bitmask fromStageFlags(ShaderStageFlags2 flags, uint8_t binding) {
Bitmask ret = 0;
if (flags & ShaderStageFlags2::VERTEX) {
ret |= (getVertexStage<Bitmask>() << binding);
}
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);
}
}
inline VkDescriptorSetLayout createDescriptorSetLayout(VkDevice device,
VkDescriptorSetLayoutCreateInfo const& info) {
VkDescriptorSetLayout layout;
vkCreateDescriptorSetLayout(device, &info, VKALLOC, &layout);
return layout;
}
inline VkShaderStageFlags getVkStage(backend::ShaderStage stage) {
switch(stage) {
case backend::ShaderStage::VERTEX:
return VK_SHADER_STAGE_VERTEX_BIT;
case backend::ShaderStage::FRAGMENT:
return VK_SHADER_STAGE_FRAGMENT_BIT;
case backend::ShaderStage::COMPUTE:
PANIC_POSTCONDITION("Unsupported stage");
}
}
} // 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)) {}
VulkanDescriptorSetLayout::~VulkanDescriptorSetLayout() {
vkDestroyDescriptorSetLayout(mDevice, vklayout, VKALLOC);
}
PushConstantDescription::PushConstantDescription(backend::Program const& program) noexcept {
mRangeCount = 0;
for (auto stage : { ShaderStage::VERTEX, ShaderStage::FRAGMENT, ShaderStage::COMPUTE }) {
auto const& constants = program.getPushConstants(stage);
if (constants.empty()) {
continue;
}
// We store the type of the constant for type-checking when writing.
auto& types = mTypes[(uint8_t) stage];
types.reserve(constants.size());
std::for_each(constants.cbegin(), constants.cend(), [&types] (Program::PushConstant t) {
types.push_back(t.type);
});
mRanges[mRangeCount++] = {
.stageFlags = getVkStage(stage),
.offset = 0,
.size = (uint32_t) constants.size() * ENTRY_SIZE,
};
}
}
void PushConstantDescription::write(VulkanCommands* commands, VkPipelineLayout layout,
backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) {
VulkanCommandBuffer* cmdbuf = &(commands->get());
uint32_t binaryValue = 0;
UTILS_UNUSED_IN_RELEASE auto const& types = mTypes[(uint8_t) stage];
if (std::holds_alternative<bool>(value)) {
assert_invariant(types[index] == ConstantType::BOOL);
bool const bval = std::get<bool>(value);
binaryValue = static_cast<uint32_t const>(bval ? VK_TRUE : VK_FALSE);
} else if (std::holds_alternative<float>(value)) {
assert_invariant(types[index] == ConstantType::FLOAT);
float const fval = std::get<float>(value);
binaryValue = *reinterpret_cast<uint32_t const*>(&fval);
} else {
assert_invariant(types[index] == ConstantType::INT);
int const ival = std::get<int>(value);
binaryValue = *reinterpret_cast<uint32_t const*>(&ival);
}
vkCmdPushConstants(cmdbuf->buffer(), layout, getVkStage(stage), index * ENTRY_SIZE, ENTRY_SIZE,
&binaryValue);
}
VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
: HwProgram(builder.getName()),
VulkanResource(VulkanResourceType::PROGRAM),
mInfo(new PipelineInfo()),
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);
for (size_t i = 0; i < MAX_SHADER_MODULES; i++) {
Program::ShaderBlob const& blob = blobs[i];
@@ -72,6 +224,12 @@ 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,
@@ -99,20 +257,33 @@ 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& usage = mInfo->usage;
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);
usage = VulkanPipelineCache::getUsageFlags(binding, group.stageFlags, usage);
assert_invariant(bindings.find(binding) == bindings.end());
bindings.insert(binding);
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
bindingToName[binding] = samplers[i].name.c_str();
@@ -126,24 +297,6 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
#endif
}
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs,
CustomSamplerInfoList const& samplerInfo) noexcept
: VulkanResource(VulkanResourceType::PROGRAM),
mInfo(new PipelineInfo()),
mDevice(device) {
mInfo->shaders[0] = vs;
mInfo->shaders[1] = fs;
auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex;
auto& usage = mInfo->usage;
bindingToSamplerIndex.resize(samplerInfo.size());
for (uint16_t binding = 0; binding < samplerInfo.size(); ++binding) {
auto const& sampler = samplerInfo[binding];
bindingToSamplerIndex[binding]
= (sampler.groupIndex << 8) | (0xff & sampler.samplerIndex);
usage = VulkanPipelineCache::getUsageFlags(binding, sampler.flags, usage);
}
}
VulkanProgram::~VulkanProgram() {
for (auto shader: mInfo->shaders) {
vkDestroyShaderModule(mDevice, shader, VKALLOC);
@@ -187,13 +340,13 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
// Constrain the sample count according to both kinds of sample count masks obtained from
// VkPhysicalDeviceProperties. This is consistent with the VulkanTexture constructor.
const auto& limits = context.getPhysicalDeviceLimits();
auto const& limits = context.getPhysicalDeviceLimits();
mSamples = samples = reduceSampleCount(samples, limits.framebufferDepthSampleCounts &
limits.framebufferColorSampleCounts);
// Create sidecar MSAA textures for color attachments if they don't already exist.
for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) {
const VulkanAttachment& spec = color[index];
VulkanAttachment const& spec = color[index];
VulkanTexture* texture = (VulkanTexture*) spec.texture;
if (texture && texture->samples == 1) {
auto msTexture = texture->getSidecar();
@@ -256,19 +409,19 @@ VkExtent2D VulkanRenderTarget::getExtent() const {
return {width, height};
}
VulkanAttachment VulkanRenderTarget::getColor(int target) const {
VulkanAttachment& VulkanRenderTarget::getColor(int target) {
return mColor[target];
}
VulkanAttachment VulkanRenderTarget::getMsaaColor(int target) const {
VulkanAttachment& VulkanRenderTarget::getMsaaColor(int target) {
return mMsaaAttachments[target];
}
VulkanAttachment VulkanRenderTarget::getDepth() const {
VulkanAttachment& VulkanRenderTarget::getDepth() {
return mDepth;
}
VulkanAttachment VulkanRenderTarget::getMsaaDepth() const {
VulkanAttachment& VulkanRenderTarget::getMsaaDepth() {
return mMsaaDepthAttachment;
}
@@ -405,4 +558,35 @@ 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
+218 -25
View File
@@ -21,35 +21,193 @@
#include "DriverBase.h"
#include "VulkanBuffer.h"
#include "VulkanPipelineCache.h"
#include "VulkanResources.h"
#include "VulkanSwapChain.h"
#include "VulkanTexture.h"
#include "VulkanUtility.h"
#include "private/backend/SamplerGroup.h"
#include <private/backend/SamplerGroup.h>
#include <backend/Program.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Mutex.h>
#include <utils/StructureOfArrays.h>
namespace filament::backend {
using namespace descset;
class VulkanTimestamps;
struct VulkanDescriptorSetLayout : public VulkanResource {
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = 3;
// 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
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);
};
// This is a convenience struct to quickly check layout compatibility in terms of descriptor set
// pools.
struct Count {
uint32_t ubo = 0;
uint32_t dynamicUbo = 0;
uint32_t sampler = 0;
uint32_t inputAttachment = 0;
bool operator==(Count const& right) const noexcept {
return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler &&
inputAttachment == right.inputAttachment;
}
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)),
};
}
Count operator*(uint16_t mult) const noexcept {
// TODO: check for overflow.
Count ret;
ret.ubo = ubo * mult;
ret.dynamicUbo = dynamicUbo * mult;
ret.sampler = sampler * mult;
ret.inputAttachment = inputAttachment * mult;
return ret;
}
};
static_assert(sizeof(Bitmask) % 8 == 0);
explicit VulkanDescriptorSetLayout(VkDevice device, VkDescriptorSetLayoutCreateInfo const& info,
Bitmask const& bitmask);
~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 {
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)
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET),
resources(allocator),
vkSet(rawSet),
mOnRecycleFn(std::move(onRecycleFn)) {}
~VulkanDescriptorSet() {
if (mOnRecycleFn) {
mOnRecycleFn();
}
}
// TODO: maybe change to fixed size for performance.
VulkanAcquireOnlyResourceManager resources;
VkDescriptorSet const vkSet;
private:
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>;
struct PushConstantDescription {
explicit PushConstantDescription(backend::Program const& program) noexcept;
VkPushConstantRange const* getVkRanges() const noexcept { return mRanges; }
uint32_t getVkRangeCount() const noexcept { return mRangeCount; }
void write(VulkanCommands* commands, VkPipelineLayout layout, backend::ShaderStage stage,
uint8_t index, backend::PushConstantVariant const& value);
private:
static constexpr uint32_t ENTRY_SIZE = sizeof(uint32_t);
utils::FixedCapacityVector<backend::ConstantType> mTypes[Program::SHADER_TYPE_COUNT];
VkPushConstantRange mRanges[Program::SHADER_TYPE_COUNT];
uint32_t mRangeCount;
};
struct VulkanProgram : public HwProgram, VulkanResource {
using BindingList = CappedArray<uint16_t, MAX_SAMPLER_COUNT>;
VulkanProgram(VkDevice device, Program const& builder) noexcept;
struct CustomSamplerInfo {
uint8_t groupIndex;
uint8_t samplerIndex;
ShaderStageFlags flags;
};
using CustomSamplerInfoList = utils::FixedCapacityVector<CustomSamplerInfo>;
// We allow custom descriptor of the samplers within shaders. This is needed if we want to use
// a program that exists only in the backend - for example, for shader-based bliting.
VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs,
CustomSamplerInfoList const& samplerInfo) noexcept;
~VulkanProgram();
inline VkShaderModule getVertexShader() const {
@@ -58,35 +216,69 @@ struct VulkanProgram : public HwProgram, VulkanResource {
inline VkShaderModule getFragmentShader() const { return mInfo->shaders[1]; }
inline VulkanPipelineCache::UsageFlags getUsage() const { return mInfo->usage; }
inline utils::FixedCapacityVector<uint16_t> const& getBindingToSamplerIndex() const {
return mInfo->bindingToSamplerIndex;
}
// Get a list of the sampler binding indices so that we don't have to loop through all possible
// 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();
}
inline VkPushConstantRange const* getPushConstantRanges() const {
return mInfo->pushConstantDescription.getVkRanges();
}
inline void writePushConstant(VulkanCommands* commands, VkPipelineLayout layout,
backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) {
mInfo->pushConstantDescription.write(commands, layout, stage, index, value);
}
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
inline utils::FixedCapacityVector<std::string> const& getBindingToName() const {
return mInfo->bindingToName;
}
#endif
private:
// TODO: handle compute shaders.
// The expected order of shaders - from frontend to backend - is vertex, fragment, compute.
static constexpr uint8_t const MAX_SHADER_MODULES = 2;
private:
struct PipelineInfo {
PipelineInfo()
: bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff) {}
explicit PipelineInfo(backend::Program const& program) noexcept
: bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff),
pushConstantDescription(program)
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
, bindingToName(MAX_SAMPLER_COUNT, "")
#endif
{}
// This bitset maps to each of the sampler in the sampler groups associated with this
// program, and whether each sampler is used in which shader (i.e. vert, frag, compute).
VulkanPipelineCache::UsageFlags usage;
BindingList bindings;
// We store the samplerGroupIndex as the top 8-bit and the index within each group as the lower 8-bit.
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
// We store the sampler name mapped from binding index (only for debug purposes).
utils::FixedCapacityVector<std::string> bindingToName;
@@ -120,10 +312,11 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
void transformClientRectToPlatform(VkRect2D* bounds) const;
void transformClientRectToPlatform(VkViewport* bounds) const;
VkExtent2D getExtent() const;
VulkanAttachment getColor(int target) const;
VulkanAttachment getMsaaColor(int target) const;
VulkanAttachment getDepth() const;
VulkanAttachment getMsaaDepth() const;
// We return references in the following methods to avoid a copy.
VulkanAttachment& getColor(int target);
VulkanAttachment& getMsaaColor(int target);
VulkanAttachment& getDepth();
VulkanAttachment& getMsaaDepth();
uint8_t getColorTargetCount(const VulkanRenderPass& pass) const;
uint8_t getSamples() const { return mSamples; }
bool hasDepth() const { return mDepth.texture; }
@@ -26,38 +26,10 @@
using namespace bluevk;
namespace filament::backend {
namespace filament::backend::imgutil {
namespace {
inline VkImageLayout getVkImageLayout(VulkanLayout layout) {
switch (layout) {
case VulkanLayout::UNDEFINED:
return VK_IMAGE_LAYOUT_UNDEFINED;
case VulkanLayout::READ_WRITE:
return VK_IMAGE_LAYOUT_GENERAL;
case VulkanLayout::READ_ONLY:
return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
case VulkanLayout::TRANSFER_SRC:
return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
case VulkanLayout::TRANSFER_DST:
return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
case VulkanLayout::DEPTH_ATTACHMENT:
return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
case VulkanLayout::DEPTH_SAMPLER:
return VK_IMAGE_LAYOUT_GENERAL;
case VulkanLayout::PRESENT:
return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
// Filament sometimes samples from one miplevel while writing to another level in the same
// texture (e.g. bloom does this). Moreover we'd like to avoid lots of expensive layout
// transitions. So, keep it simple and use GENERAL for all color-attachable textures.
case VulkanLayout::COLOR_ATTACHMENT:
return VK_IMAGE_LAYOUT_GENERAL;
case VulkanLayout::COLOR_ATTACHMENT_RESOLVE:
return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
}
}
inline std::tuple<VkAccessFlags, VkAccessFlags, VkPipelineStageFlags, VkPipelineStageFlags,
VkImageLayout, VkImageLayout>
getVkTransition(const VulkanLayoutTransition& transition) {
@@ -70,11 +42,11 @@ getVkTransition(const VulkanLayoutTransition& transition) {
srcStage = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
break;
case VulkanLayout::COLOR_ATTACHMENT:
srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
srcStage = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
| VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
srcStage = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
break;
case VulkanLayout::READ_WRITE:
srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
@@ -154,27 +126,12 @@ getVkTransition(const VulkanLayoutTransition& transition) {
}
return std::make_tuple(srcAccessMask, dstAccessMask, srcStage, dstStage,
getVkImageLayout(transition.oldLayout), getVkImageLayout(transition.newLayout));
getVkLayout(transition.oldLayout), getVkLayout(transition.newLayout));
}
}// anonymous namespace
VkImageViewType VulkanImageUtility::getViewType(SamplerType target) {
switch (target) {
case SamplerType::SAMPLER_CUBEMAP:
return VK_IMAGE_VIEW_TYPE_CUBE;
case SamplerType::SAMPLER_2D_ARRAY:
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
case SamplerType::SAMPLER_3D:
return VK_IMAGE_VIEW_TYPE_3D;
default:
return VK_IMAGE_VIEW_TYPE_2D;
}
}
void VulkanImageUtility::transitionLayout(VkCommandBuffer cmdbuffer,
void transitionLayout(VkCommandBuffer cmdbuffer,
VulkanLayoutTransition transition) {
if (transition.oldLayout == transition.newLayout) {
return;
@@ -197,10 +154,6 @@ void VulkanImageUtility::transitionLayout(VkCommandBuffer cmdbuffer,
vkCmdPipelineBarrier(cmdbuffer, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}
VkImageLayout VulkanImageUtility::getVkLayout(VulkanLayout layout) {
return getVkImageLayout(layout);
}
}// namespace filament::backend
bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) {
@@ -217,12 +170,12 @@ bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange&
return false;
}
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION | FVK_DEBUG_TEXTURE)
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION) || FVK_ENABLED(FVK_DEBUG_TEXTURE)
#define CASE(VALUE) \
case filament::backend::VulkanLayout::VALUE: { \
out << #VALUE; \
out << " [" \
<< filament::backend::VulkanImageUtility::getVkLayout( \
<< filament::backend::imgutil::getVkLayout( \
filament::backend::VulkanLayout::VALUE) \
<< "]"; \
break; \
@@ -59,44 +59,85 @@ struct VulkanLayoutTransition {
VkImageSubresourceRange subresources;
};
class VulkanImageUtility {
public:
static VkImageViewType getViewType(SamplerType target);
namespace imgutil {
inline static VulkanLayout getDefaultLayout(TextureUsage usage) {
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
if (any(usage & TextureUsage::SAMPLEABLE)) {
return VulkanLayout::DEPTH_SAMPLER;
} else {
return VulkanLayout::DEPTH_ATTACHMENT;
}
}
inline VkImageViewType getViewType(SamplerType target) {
switch (target) {
case SamplerType::SAMPLER_CUBEMAP:
return VK_IMAGE_VIEW_TYPE_CUBE;
case SamplerType::SAMPLER_2D_ARRAY:
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
case SamplerType::SAMPLER_3D:
return VK_IMAGE_VIEW_TYPE_3D;
default:
return VK_IMAGE_VIEW_TYPE_2D;
}
}
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
return VulkanLayout::COLOR_ATTACHMENT;
inline VulkanLayout getDefaultLayout(TextureUsage usage) {
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
if (any(usage & TextureUsage::SAMPLEABLE)) {
return VulkanLayout::DEPTH_SAMPLER;
} else {
return VulkanLayout::DEPTH_ATTACHMENT;
}
// Finally, the layout for an immutable texture is optimal read-only.
return VulkanLayout::READ_ONLY;
}
inline static VulkanLayout getDefaultLayout(VkImageUsageFlags vkusage) {
TextureUsage usage {};
if (vkusage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
usage = usage | TextureUsage::DEPTH_ATTACHMENT;
}
if (vkusage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) {
usage = usage | TextureUsage::COLOR_ATTACHMENT;
}
if (vkusage & VK_IMAGE_USAGE_SAMPLED_BIT) {
usage = usage | TextureUsage::SAMPLEABLE;
}
return getDefaultLayout(usage);
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
return VulkanLayout::COLOR_ATTACHMENT;
}
// Finally, the layout for an immutable texture is optimal read-only.
return VulkanLayout::READ_ONLY;
}
static VkImageLayout getVkLayout(VulkanLayout layout);
static void transitionLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition);
};
inline VulkanLayout getDefaultLayout(VkImageUsageFlags vkusage) {
TextureUsage usage{};
if (vkusage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
usage = usage | TextureUsage::DEPTH_ATTACHMENT;
}
if (vkusage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) {
usage = usage | TextureUsage::COLOR_ATTACHMENT;
}
if (vkusage & VK_IMAGE_USAGE_SAMPLED_BIT) {
usage = usage | TextureUsage::SAMPLEABLE;
}
return getDefaultLayout(usage);
}
constexpr inline VkImageLayout getVkLayout(VulkanLayout layout) {
switch (layout) {
case VulkanLayout::UNDEFINED:
return VK_IMAGE_LAYOUT_UNDEFINED;
case VulkanLayout::READ_WRITE:
return VK_IMAGE_LAYOUT_GENERAL;
case VulkanLayout::READ_ONLY:
return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
case VulkanLayout::TRANSFER_SRC:
return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
case VulkanLayout::TRANSFER_DST:
return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
case VulkanLayout::DEPTH_ATTACHMENT:
return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
case VulkanLayout::DEPTH_SAMPLER:
return VK_IMAGE_LAYOUT_GENERAL;
case VulkanLayout::PRESENT:
return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
// Filament sometimes samples from one miplevel while writing to another level in the
// same texture (e.g. bloom does this). Moreover we'd like to avoid lots of expensive
// layout transitions. So, keep it simple and use GENERAL for all color-attachable
// textures.
case VulkanLayout::COLOR_ATTACHMENT:
return VK_IMAGE_LAYOUT_GENERAL;
case VulkanLayout::COLOR_ATTACHMENT_RESOLVE:
return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
}
}
void transitionLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition);
} // namespace imgutil
} // namespace filament::backend
@@ -31,4 +31,8 @@
#include "vk_mem_alloc.h"
VK_DEFINE_HANDLE(VmaAllocator)
VK_DEFINE_HANDLE(VmaAllocation)
VK_DEFINE_HANDLE(VmaPool)
#endif // TNT_FILAMENT_BACKEND_VULKANMEMORY_H
@@ -14,8 +14,9 @@
* limitations under the License.
*/
#include "vulkan/VulkanMemory.h"
#include "vulkan/VulkanPipelineCache.h"
#include "VulkanPipelineCache.h"
#include "VulkanMemory.h"
#include "caching/VulkanDescriptorSetManager.h"
#include <utils/Log.h>
#include <utils/Panic.h>
@@ -34,63 +35,9 @@ using namespace bluevk;
namespace filament::backend {
static VkShaderStageFlags getShaderStageFlags(VulkanPipelineCache::UsageFlags key, uint16_t binding) {
// NOTE: if you modify this function, you also need to modify getUsageFlags.
assert_invariant(binding < MAX_SAMPLER_COUNT);
VkShaderStageFlags flags = 0;
if (key.test(binding)) {
flags |= VK_SHADER_STAGE_VERTEX_BIT;
}
if (key.test(MAX_SAMPLER_COUNT + binding)) {
flags |= VK_SHADER_STAGE_FRAGMENT_BIT;
}
return flags;
}
VulkanPipelineCache::UsageFlags
VulkanPipelineCache::getUsageFlags(uint16_t binding, ShaderStageFlags flags, UsageFlags src) {
// NOTE: if you modify this function, you also need to modify getShaderStageFlags.
assert_invariant(binding < MAX_SAMPLER_COUNT);
if (any(flags & ShaderStageFlags::VERTEX)) {
src.set(binding);
}
if (any(flags & ShaderStageFlags::FRAGMENT)) {
src.set(MAX_SAMPLER_COUNT + binding);
}
// TODO: add support for compute by extending SHADER_MODULE_COUNT and ensuring UsageFlags
// has 186 bits (MAX_SAMPLER_COUNT * 3)
// assert_invariant(!any(flags & ~(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT)));
return src;
}
VulkanPipelineCache::UsageFlags VulkanPipelineCache::disableUsageFlags(uint16_t binding,
UsageFlags src) {
src.unset(binding);
src.unset(MAX_SAMPLER_COUNT + binding);
return src;
}
VulkanPipelineCache::VulkanPipelineCache(VulkanResourceAllocator* allocator)
: mResourceAllocator(allocator),
mPipelineBoundResources(allocator) {
mDummyBufferWriteInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
mDummyBufferWriteInfo.pNext = nullptr;
mDummyBufferWriteInfo.dstArrayElement = 0;
mDummyBufferWriteInfo.descriptorCount = 1;
mDummyBufferWriteInfo.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
mDummyBufferWriteInfo.pImageInfo = nullptr;
mDummyBufferWriteInfo.pBufferInfo = &mDummyBufferInfo;
mDummyBufferWriteInfo.pTexelBufferView = nullptr;
mDummyTargetInfo.imageLayout = VulkanImageUtility::getVkLayout(VulkanLayout::READ_ONLY);
mDummyTargetWriteInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
mDummyTargetWriteInfo.pNext = nullptr;
mDummyTargetWriteInfo.dstArrayElement = 0;
mDummyTargetWriteInfo.descriptorCount = 1;
mDummyTargetWriteInfo.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
mDummyTargetWriteInfo.pImageInfo = &mDummyTargetInfo;
mDummyTargetWriteInfo.pBufferInfo = nullptr;
mDummyTargetWriteInfo.pTexelBufferView = nullptr;
VulkanPipelineCache::VulkanPipelineCache(VkDevice device, VmaAllocator allocator)
: mDevice(device),
mAllocator(allocator) {
}
VulkanPipelineCache::~VulkanPipelineCache() {
@@ -98,244 +45,43 @@ VulkanPipelineCache::~VulkanPipelineCache() {
// be explicit about teardown order of various components.
}
void VulkanPipelineCache::setDevice(VkDevice device, VmaAllocator allocator) {
assert_invariant(mDevice == VK_NULL_HANDLE);
mDevice = device;
mAllocator = allocator;
mDescriptorPool = createDescriptorPool(mDescriptorPoolSize);
// Formulate some dummy objects and dummy descriptor info used only for clearing out unused
// bindings. This is especially crucial after a texture has been destroyed. Since core Vulkan
// does not allow specifying VK_NULL_HANDLE without the robustness2 extension, we would need to
// change the pipeline layout more frequently if we wanted to get rid of these dummy objects.
VkBufferCreateInfo bufferInfo {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = 16,
.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
};
VmaAllocationCreateInfo allocInfo { .usage = VMA_MEMORY_USAGE_GPU_ONLY };
vmaCreateBuffer(mAllocator, &bufferInfo, &allocInfo, &mDummyBuffer, &mDummyMemory, nullptr);
mDummyBufferInfo.buffer = mDummyBuffer;
mDummyBufferInfo.range = bufferInfo.size;
void VulkanPipelineCache::bindLayout(VkPipelineLayout layout) noexcept {
mPipelineRequirements.layout = layout;
}
bool VulkanPipelineCache::bindDescriptors(VkCommandBuffer cmdbuffer) noexcept {
DescriptorMap::iterator descriptorIter = mDescriptorSets.find(mDescriptorRequirements);
// Check if the required descriptors are already bound. If so, there's no need to do anything.
if (DescEqual equals; UTILS_LIKELY(equals(mBoundDescriptor, mDescriptorRequirements))) {
// If the pipeline state during an app's first draw call happens to match the default state
// vector of the cache, then the cache is uninitialized and we should not return early.
if (UTILS_LIKELY(!mDescriptorSets.empty())) {
// Since the descriptors are already bound, they should be found in the cache.
assert_invariant(descriptorIter != mDescriptorSets.end());
// Update the LRU "time stamp" (really a count of cmd buf submissions) before returning.
descriptorIter.value().lastUsed = mCurrentTime;
return true;
}
}
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::getOrCreatePipeline() noexcept {
// If a cached object exists, re-use it, otherwise create a new one.
DescriptorCacheEntry* cacheEntry = UTILS_LIKELY(descriptorIter != mDescriptorSets.end()) ?
&descriptorIter.value() : createDescriptorSets();
// If a descriptor set overflow occurred, allow higher levels to handle it gracefully.
assert_invariant(cacheEntry != nullptr);
if (UTILS_UNLIKELY(cacheEntry == nullptr)) {
return false;
if (PipelineMap::iterator pipelineIter = mPipelines.find(mPipelineRequirements);
pipelineIter != mPipelines.end()) {
auto& pipeline = pipelineIter.value();
pipeline.lastUsed = mCurrentTime;
return &pipeline;
}
cacheEntry->lastUsed = mCurrentTime;
mBoundDescriptor = mDescriptorRequirements;
// This passes the currently "bound" uniform buffer objects to pipeline that will be used in the
// draw call.
auto resourceEntry = mDescriptorResources.find(cacheEntry->id);
if (resourceEntry == mDescriptorResources.end()) {
mDescriptorResources[cacheEntry->id]
= std::make_unique<VulkanAcquireOnlyResourceManager>(mResourceAllocator);
resourceEntry = mDescriptorResources.find(cacheEntry->id);
}
resourceEntry->second->acquireAll(&mPipelineBoundResources);
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
getOrCreatePipelineLayout()->handle, 0, VulkanPipelineCache::DESCRIPTOR_TYPE_COUNT,
cacheEntry->handles.data(), 0, nullptr);
return true;
auto ret = createPipeline();
ret->lastUsed = mCurrentTime;
return ret;
}
bool VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) noexcept {
void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) {
VkCommandBuffer const cmdbuffer = commands->buffer();
PipelineMap::iterator pipelineIter = mPipelines.find(mPipelineRequirements);
PipelineCacheEntry* cacheEntry = getOrCreatePipeline();
// Check if the required pipeline is already bound.
if (PipelineEqual equals; UTILS_LIKELY(equals(mBoundPipeline, mPipelineRequirements))) {
assert_invariant(pipelineIter != mPipelines.end());
pipelineIter.value().lastUsed = mCurrentTime;
return true;
if (cacheEntry->handle == commands->pipeline()) {
return;
}
// If a cached object exists, re-use it, otherwise create a new one.
PipelineCacheEntry* cacheEntry = UTILS_LIKELY(pipelineIter != mPipelines.end()) ?
&pipelineIter.value() : createPipeline();
// If an error occurred, allow higher levels to handle it gracefully.
assert_invariant(cacheEntry != nullptr);
if (UTILS_UNLIKELY(cacheEntry == nullptr)) {
return false;
}
cacheEntry->lastUsed = mCurrentTime;
getOrCreatePipelineLayout()->lastUsed = mCurrentTime;
assert_invariant(cacheEntry != nullptr && "Failed to create/find pipeline");
mBoundPipeline = mPipelineRequirements;
vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cacheEntry->handle);
return true;
}
void VulkanPipelineCache::bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept {
if (UTILS_UNLIKELY(!equivalent(mCurrentScissor, scissor))) {
mCurrentScissor = scissor;
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
}
}
VulkanPipelineCache::DescriptorCacheEntry* VulkanPipelineCache::createDescriptorSets() noexcept {
PipelineLayoutCacheEntry* layoutCacheEntry = getOrCreatePipelineLayout();
DescriptorCacheEntry descriptorCacheEntry = {
.pipelineLayout = mPipelineRequirements.layout,
.id = mDescriptorCacheEntryCount++,
};
// Each of the arenas for this particular layout are guaranteed to have the same size. Check
// the first arena to see if any descriptor sets are available that can be re-claimed. If not,
// create brand new ones (one for each type). They will be added to the arena later, after they
// are no longer used. This occurs during the cleanup phase during command buffer submission.
auto& descriptorSetArenas = layoutCacheEntry->descriptorSetArenas;
if (descriptorSetArenas[0].empty()) {
// If allocating a new descriptor set from the pool would cause it to overflow, then
// recreate the pool. The number of descriptor sets that have already been allocated from
// the pool is the sum of the "active" descriptor sets (mDescriptorSets) and the "dormant"
// descriptor sets (mDescriptorArenasCount).
//
// NOTE: technically both sides of the inequality below should be multiplied by
// DESCRIPTOR_TYPE_COUNT to get the true number of descriptor sets.
if (mDescriptorSets.size() + mDescriptorArenasCount + 1 > mDescriptorPoolSize) {
growDescriptorPool();
}
VkDescriptorSetAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = mDescriptorPool;
allocInfo.descriptorSetCount = DESCRIPTOR_TYPE_COUNT;
allocInfo.pSetLayouts = layoutCacheEntry->descriptorSetLayouts.data();
VkResult error = vkAllocateDescriptorSets(mDevice, &allocInfo,
descriptorCacheEntry.handles.data());
assert_invariant(error == VK_SUCCESS);
if (error != VK_SUCCESS) {
return nullptr;
}
} else {
for (uint32_t i = 0; i < DESCRIPTOR_TYPE_COUNT; ++i) {
descriptorCacheEntry.handles[i] = descriptorSetArenas[i].back();
descriptorSetArenas[i].pop_back();
}
assert_invariant(mDescriptorArenasCount > 0);
mDescriptorArenasCount--;
}
// Rewrite every binding in the new descriptor sets.
VkDescriptorBufferInfo descriptorBuffers[UBUFFER_BINDING_COUNT];
VkDescriptorImageInfo descriptorSamplers[SAMPLER_BINDING_COUNT];
VkDescriptorImageInfo descriptorInputAttachments[INPUT_ATTACHMENT_COUNT];
VkWriteDescriptorSet descriptorWrites[UBUFFER_BINDING_COUNT + SAMPLER_BINDING_COUNT +
INPUT_ATTACHMENT_COUNT];
uint32_t nwrites = 0;
VkWriteDescriptorSet* writes = descriptorWrites;
nwrites = 0;
for (uint32_t binding = 0; binding < UBUFFER_BINDING_COUNT; binding++) {
VkWriteDescriptorSet& writeInfo = writes[nwrites++];
if (mDescriptorRequirements.uniformBuffers[binding]) {
VkDescriptorBufferInfo& bufferInfo = descriptorBuffers[binding];
bufferInfo.buffer = mDescriptorRequirements.uniformBuffers[binding];
bufferInfo.offset = mDescriptorRequirements.uniformBufferOffsets[binding];
bufferInfo.range = mDescriptorRequirements.uniformBufferSizes[binding];
// We store size with 32 bits, so our "WHOLE" sentinel is different from Vk.
if (bufferInfo.range == WHOLE_SIZE) {
bufferInfo.range = VK_WHOLE_SIZE;
}
writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writeInfo.pNext = nullptr;
writeInfo.dstArrayElement = 0;
writeInfo.descriptorCount = 1;
writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
writeInfo.pImageInfo = nullptr;
writeInfo.pBufferInfo = &bufferInfo;
writeInfo.pTexelBufferView = nullptr;
} else {
writeInfo = mDummyBufferWriteInfo;
assert_invariant(mDummyBufferWriteInfo.pBufferInfo->buffer);
}
assert_invariant(writeInfo.pBufferInfo->buffer);
writeInfo.dstSet = descriptorCacheEntry.handles[0];
writeInfo.dstBinding = binding;
}
for (uint32_t binding = 0; binding < SAMPLER_BINDING_COUNT; binding++) {
if (mDescriptorRequirements.samplers[binding].sampler) {
VkWriteDescriptorSet& writeInfo = writes[nwrites++];
VkDescriptorImageInfo& imageInfo = descriptorSamplers[binding];
imageInfo = mDescriptorRequirements.samplers[binding];
writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writeInfo.pNext = nullptr;
writeInfo.dstArrayElement = 0;
writeInfo.descriptorCount = 1;
writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
writeInfo.pImageInfo = &imageInfo;
writeInfo.pBufferInfo = nullptr;
writeInfo.pTexelBufferView = nullptr;
writeInfo.dstSet = descriptorCacheEntry.handles[1];
writeInfo.dstBinding = binding;
}
}
for (uint32_t binding = 0; binding < INPUT_ATTACHMENT_COUNT; binding++) {
if (mDescriptorRequirements.inputAttachments[binding].imageView) {
VkWriteDescriptorSet& writeInfo = writes[nwrites++];
VkDescriptorImageInfo& imageInfo = descriptorInputAttachments[binding];
imageInfo = mDescriptorRequirements.inputAttachments[binding];
writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writeInfo.pNext = nullptr;
writeInfo.dstArrayElement = 0;
writeInfo.descriptorCount = 1;
writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
writeInfo.pImageInfo = &imageInfo;
writeInfo.pBufferInfo = nullptr;
writeInfo.pTexelBufferView = nullptr;
writeInfo.dstSet = descriptorCacheEntry.handles[2];
writeInfo.dstBinding = binding;
}
}
vkUpdateDescriptorSets(mDevice, nwrites, writes, 0, nullptr);
return &mDescriptorSets.emplace(mDescriptorRequirements, descriptorCacheEntry).first.value();
commands->setPipeline(cacheEntry->handle);
}
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept {
assert_invariant(mPipelineRequirements.shaders[0] && "Vertex shader is not bound.");
PipelineLayoutCacheEntry* layout = getOrCreatePipelineLayout();
assert_invariant(layout);
assert_invariant(mPipelineRequirements.layout && "No pipeline layout specified");
VkPipelineShaderStageCreateInfo shaderStages[SHADER_MODULE_COUNT];
shaderStages[0] = VkPipelineShaderStageCreateInfo{};
@@ -404,7 +150,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
VkGraphicsPipelineCreateInfo pipelineCreateInfo = {};
pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineCreateInfo.layout = layout->handle;
pipelineCreateInfo.layout = mPipelineRequirements.layout;
pipelineCreateInfo.renderPass = mPipelineRequirements.renderPass;
pipelineCreateInfo.subpass = mPipelineRequirements.subpassIndex;
pipelineCreateInfo.stageCount = hasFragmentShader ? SHADER_MODULE_COUNT : 1;
@@ -498,68 +244,6 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
return &mPipelines.emplace(mPipelineRequirements, cacheEntry).first.value();
}
VulkanPipelineCache::PipelineLayoutCacheEntry* VulkanPipelineCache::getOrCreatePipelineLayout() noexcept {
auto iter = mPipelineLayouts.find(mPipelineRequirements.layout);
if (UTILS_LIKELY(iter != mPipelineLayouts.end())) {
return &iter.value();
}
PipelineLayoutCacheEntry cacheEntry = {};
VkDescriptorSetLayoutBinding binding = {};
binding.descriptorCount = 1; // NOTE: We never use arrays-of-blocks.
binding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS; // NOTE: This is potentially non-optimal.
// First create the descriptor set layout for UBO's.
VkDescriptorSetLayoutBinding ubindings[UBUFFER_BINDING_COUNT];
binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
for (uint32_t i = 0; i < UBUFFER_BINDING_COUNT; i++) {
binding.binding = i;
ubindings[i] = binding;
}
VkDescriptorSetLayoutCreateInfo dlinfo = {};
dlinfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
dlinfo.bindingCount = UBUFFER_BINDING_COUNT;
dlinfo.pBindings = ubindings;
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &cacheEntry.descriptorSetLayouts[0]);
// Next create the descriptor set layout for samplers.
VkDescriptorSetLayoutBinding sbindings[SAMPLER_BINDING_COUNT];
binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
for (uint32_t i = 0; i < SAMPLER_BINDING_COUNT; i++) {
binding.stageFlags = getShaderStageFlags(mPipelineRequirements.layout, i);
binding.binding = i;
sbindings[i] = binding;
}
dlinfo.bindingCount = SAMPLER_BINDING_COUNT;
dlinfo.pBindings = sbindings;
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &cacheEntry.descriptorSetLayouts[1]);
// Next create the descriptor set layout for input attachments.
VkDescriptorSetLayoutBinding tbindings[INPUT_ATTACHMENT_COUNT];
binding.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
for (uint32_t i = 0; i < INPUT_ATTACHMENT_COUNT; i++) {
binding.binding = i;
tbindings[i] = binding;
}
dlinfo.bindingCount = INPUT_ATTACHMENT_COUNT;
dlinfo.pBindings = tbindings;
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &cacheEntry.descriptorSetLayouts[2]);
// Create VkPipelineLayout based on how to resources are bounded.
VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = {};
pPipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pPipelineLayoutCreateInfo.setLayoutCount = cacheEntry.descriptorSetLayouts.size();
pPipelineLayoutCreateInfo.pSetLayouts = cacheEntry.descriptorSetLayouts.data();
VkResult result = vkCreatePipelineLayout(mDevice, &pPipelineLayoutCreateInfo, VKALLOC,
&cacheEntry.handle);
if (UTILS_UNLIKELY(result != VK_SUCCESS)) {
return nullptr;
}
return &mPipelineLayouts.emplace(mPipelineRequirements.layout, cacheEntry).first.value();
}
void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept {
mPipelineRequirements.shaders[0] = program->getVertexShader();
mPipelineRequirements.shaders[1] = program->getFragmentShader();
@@ -600,97 +284,15 @@ void VulkanPipelineCache::bindVertexArray(VkVertexInputAttributeDescription cons
}
}
VulkanPipelineCache::UniformBufferBinding VulkanPipelineCache::getUniformBufferBinding(
uint32_t bindingIndex) const noexcept {
auto& key = mDescriptorRequirements;
return {
key.uniformBuffers[bindingIndex],
key.uniformBufferOffsets[bindingIndex],
key.uniformBufferSizes[bindingIndex],
};
}
void VulkanPipelineCache::unbindUniformBuffer(VkBuffer uniformBuffer) noexcept {
auto& key = mDescriptorRequirements;
for (uint32_t bindingIndex = 0u; bindingIndex < UBUFFER_BINDING_COUNT; ++bindingIndex) {
if (key.uniformBuffers[bindingIndex] == uniformBuffer) {
key.uniformBuffers[bindingIndex] = {};
key.uniformBufferSizes[bindingIndex] = {};
key.uniformBufferOffsets[bindingIndex] = {};
}
}
}
void VulkanPipelineCache::unbindImageView(VkImageView imageView) noexcept {
for (auto& sampler : mDescriptorRequirements.samplers) {
if (sampler.imageView == imageView) {
sampler = {};
}
}
for (auto& target : mDescriptorRequirements.inputAttachments) {
if (target.imageView == imageView) {
target = {};
}
}
}
void VulkanPipelineCache::bindUniformBufferObject(uint32_t bindingIndex,
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept {
VkBuffer buffer = bufferObject->buffer.getGpuBuffer();
ASSERT_POSTCONDITION(bindingIndex < UBUFFER_BINDING_COUNT,
"Uniform bindings overflow: index = %d, capacity = %d.", bindingIndex,
UBUFFER_BINDING_COUNT);
auto& key = mDescriptorRequirements;
key.uniformBuffers[bindingIndex] = buffer;
if (size == VK_WHOLE_SIZE) {
size = WHOLE_SIZE;
}
assert_invariant(offset <= 0xffffffffu);
assert_invariant(size <= 0xffffffffu);
key.uniformBufferOffsets[bindingIndex] = offset;
key.uniformBufferSizes[bindingIndex] = size;
mPipelineBoundResources.acquire(bufferObject);
}
void VulkanPipelineCache::bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT],
VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept {
for (uint32_t bindingIndex = 0; bindingIndex < SAMPLER_BINDING_COUNT; bindingIndex++) {
mDescriptorRequirements.samplers[bindingIndex] = samplers[bindingIndex];
if (textures[bindingIndex]) {
mPipelineBoundResources.acquire(textures[bindingIndex]);
}
}
mPipelineRequirements.layout = flags;
}
void VulkanPipelineCache::bindInputAttachment(uint32_t bindingIndex,
VkDescriptorImageInfo targetInfo) noexcept {
ASSERT_POSTCONDITION(bindingIndex < INPUT_ATTACHMENT_COUNT,
"Input attachment bindings overflow: index = %d, capacity = %d.",
bindingIndex, INPUT_ATTACHMENT_COUNT);
mDescriptorRequirements.inputAttachments[bindingIndex] = targetInfo;
}
void VulkanPipelineCache::terminate() noexcept {
// Symmetric to createLayoutsAndDescriptors.
destroyLayoutsAndDescriptors();
for (auto& iter : mPipelines) {
vkDestroyPipeline(mDevice, iter.second.handle, VKALLOC);
}
mPipelineBoundResources.clear();
mPipelines.clear();
mBoundPipeline = {};
vmaDestroyBuffer(mAllocator, mDummyBuffer, mDummyMemory);
mDummyBuffer = VK_NULL_HANDLE;
mDummyMemory = VK_NULL_HANDLE;
}
void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& commands) {
void VulkanPipelineCache::gc() noexcept {
// The timestamp associated with a given cache entry represents "time" as a count of flush
// events since the cache was constructed. If any cache entry was most recently used over
// FVK_MAX_PIPELINE_AGE flush events in the past, then we can be sure that it is no longer
@@ -700,194 +302,21 @@ void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& commands) {
// The Vulkan spec says: "When a command buffer begins recording, all state in that command
// buffer is undefined." Therefore, we need to clear all bindings at this time.
mBoundPipeline = {};
mBoundDescriptor = {};
mCurrentScissor = {};
// NOTE: Due to robin_map restrictions, we cannot use auto or range-based loops.
// Check if any bundles in the cache are no longer in use by any command buffer. Descriptors
// from unused bundles are moved back to their respective arenas.
using ConstDescIterator = decltype(mDescriptorSets)::const_iterator;
for (ConstDescIterator iter = mDescriptorSets.begin(); iter != mDescriptorSets.end();) {
const DescriptorCacheEntry& cacheEntry = iter.value();
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
auto& arenas = mPipelineLayouts[cacheEntry.pipelineLayout].descriptorSetArenas;
for (uint32_t i = 0; i < DESCRIPTOR_TYPE_COUNT; ++i) {
arenas[i].push_back(cacheEntry.handles[i]);
}
++mDescriptorArenasCount;
mDescriptorResources.erase(cacheEntry.id);
iter = mDescriptorSets.erase(iter);
} else {
++iter;
}
}
// Evict any pipelines that have not been used in a while.
// Any pipeline older than FVK_MAX_COMMAND_BUFFERS can be safely destroyed.
using ConstPipeIterator = decltype(mPipelines)::const_iterator;
for (ConstPipeIterator iter = mPipelines.begin(); iter != mPipelines.end();) {
const PipelineCacheEntry& cacheEntry = iter.value();
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
vkDestroyPipeline(mDevice, iter->second.handle, VKALLOC);
iter = mPipelines.erase(iter);
} else {
++iter;
}
}
// Evict any layouts that have not been used in a while.
using ConstLayoutIterator = decltype(mPipelineLayouts)::const_iterator;
for (ConstLayoutIterator iter = mPipelineLayouts.begin(); iter != mPipelineLayouts.end();) {
const PipelineLayoutCacheEntry& cacheEntry = iter.value();
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
vkDestroyPipelineLayout(mDevice, iter->second.handle, VKALLOC);
for (auto setLayout : iter->second.descriptorSetLayouts) {
#if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE)
PipelineLayoutKey key = iter.key();
for (auto& pair : mDescriptorSets) {
assert_invariant(pair.second.pipelineLayout != key);
}
#endif
vkDestroyDescriptorSetLayout(mDevice, setLayout, VKALLOC);
}
auto& arenas = iter->second.descriptorSetArenas;
assert_invariant(mDescriptorArenasCount >= arenas[0].size());
mDescriptorArenasCount -= arenas[0].size();
for (auto& arena : arenas) {
vkFreeDescriptorSets(mDevice, mDescriptorPool, arena.size(), arena.data());
}
iter = mPipelineLayouts.erase(iter);
} else {
++iter;
}
}
// If there are no descriptors from any extinct pool that are still in use, we can safely
// destroy the extinct pools, which implicitly frees their associated descriptor sets.
bool canPurgeExtinctPools = true;
for (auto& bundle : mExtinctDescriptorBundles) {
if (bundle.lastUsed + FVK_MAX_PIPELINE_AGE >= mCurrentTime) {
canPurgeExtinctPools = false;
break;
}
}
if (canPurgeExtinctPools) {
for (VkDescriptorPool pool : mExtinctDescriptorPools) {
vkDestroyDescriptorPool(mDevice, pool, VKALLOC);
}
mExtinctDescriptorPools.clear();
for (auto const& entry : mExtinctDescriptorBundles) {
mDescriptorResources.erase(entry.id);
}
mExtinctDescriptorBundles.clear();
}
}
VkDescriptorPool VulkanPipelineCache::createDescriptorPool(uint32_t size) const {
VkDescriptorPoolSize poolSizes[DESCRIPTOR_TYPE_COUNT] = {};
VkDescriptorPoolCreateInfo poolInfo {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.pNext = nullptr,
.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
.maxSets = size * DESCRIPTOR_TYPE_COUNT,
.poolSizeCount = DESCRIPTOR_TYPE_COUNT,
.pPoolSizes = poolSizes
};
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSizes[0].descriptorCount = poolInfo.maxSets * UBUFFER_BINDING_COUNT;
poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSizes[1].descriptorCount = poolInfo.maxSets * SAMPLER_BINDING_COUNT;
poolSizes[2].type = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
poolSizes[2].descriptorCount = poolInfo.maxSets * INPUT_ATTACHMENT_COUNT;
VkDescriptorPool pool;
const UTILS_UNUSED VkResult result = vkCreateDescriptorPool(mDevice, &poolInfo, VKALLOC, &pool);
assert_invariant(result == VK_SUCCESS);
return pool;
}
void VulkanPipelineCache::destroyLayoutsAndDescriptors() noexcept {
// Our current descriptor set strategy can cause the # of descriptor sets to explode in certain
// situations, so it's interesting to report the number that get stuffed into the cache.
#if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE)
utils::slog.d << "Destroying " << mDescriptorSets.size() << " bundles of descriptor sets."
<< utils::io::endl;
#endif
mDescriptorSets.clear();
// Our current layout bundle strategy can cause the # of layout bundles to explode in certain
// situations, so it's interesting to report the number that get stuffed into the cache.
#if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE)
utils::slog.d << "Destroying " << mPipelineLayouts.size() << " pipeline layouts."
<< utils::io::endl;
#endif
for (auto& iter : mPipelineLayouts) {
vkDestroyPipelineLayout(mDevice, iter.second.handle, VKALLOC);
for (auto setLayout : iter.second.descriptorSetLayouts) {
vkDestroyDescriptorSetLayout(mDevice, setLayout, VKALLOC);
}
// There is no need to free descriptor sets individually since destroying the VkDescriptorPool
// implicitly frees them.
}
mPipelineLayouts.clear();
vkDestroyDescriptorPool(mDevice, mDescriptorPool, VKALLOC);
mDescriptorPool = VK_NULL_HANDLE;
for (VkDescriptorPool pool : mExtinctDescriptorPools) {
vkDestroyDescriptorPool(mDevice, pool, VKALLOC);
}
mExtinctDescriptorPools.clear();
mExtinctDescriptorBundles.clear();
// Both mDescriptorSets and mExtinctDescriptorBundles have been cleared, so it's safe to call
// clear() on mDescriptorResources.
mDescriptorResources.clear();
mBoundDescriptor = {};
}
void VulkanPipelineCache::growDescriptorPool() noexcept {
// We need to destroy the old VkDescriptorPool, but we can't do so immediately because many
// of its descriptors are still in use. So, stash it in an "extinct" list.
mExtinctDescriptorPools.push_back(mDescriptorPool);
// Create the new VkDescriptorPool, twice as big as the old one.
mDescriptorPoolSize *= 2;
mDescriptorPool = createDescriptorPool(mDescriptorPoolSize);
// Clear out all unused descriptor sets in the arena so they don't get reclaimed. There is no
// need to free them individually since the old VkDescriptorPool will be destroyed.
for (auto iter = mPipelineLayouts.begin(); iter != mPipelineLayouts.end(); ++iter) {
for (auto& arena : iter.value().descriptorSetArenas) {
arena.clear();
}
}
mDescriptorArenasCount = 0;
// Move all in-use descriptors from the primary cache into an "extinct" list, so that they will
// later be destroyed rather than reclaimed.
using DescIterator = decltype(mDescriptorSets)::iterator;
for (DescIterator iter = mDescriptorSets.begin(); iter != mDescriptorSets.end(); ++iter) {
mExtinctDescriptorBundles.push_back(iter.value());
}
mDescriptorSets.clear();
}
size_t VulkanPipelineCache::PipelineLayoutKeyHashFn::operator()(
const PipelineLayoutKey& key) const {
std::hash<uint64_t> hasher;
auto h0 = hasher(key.getBitsAt(0));
auto h1 = hasher(key.getBitsAt(1));
return h0 ^ (h1 << 1);
}
bool VulkanPipelineCache::PipelineLayoutKeyEqual::operator()(const PipelineLayoutKey& k1,
const PipelineLayoutKey& k2) const {
return k1 == k2;
using ConstPipeIterator = decltype(mPipelines)::const_iterator;
for (ConstPipeIterator iter = mPipelines.begin(); iter != mPipelines.end();) {
const PipelineCacheEntry& cacheEntry = iter.value();
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
vkDestroyPipeline(mDevice, iter->second.handle, VKALLOC);
iter = mPipelines.erase(iter);
} else {
++iter;
}
}
}
bool VulkanPipelineCache::PipelineEqual::operator()(const PipelineKey& k1,
@@ -895,31 +324,6 @@ bool VulkanPipelineCache::PipelineEqual::operator()(const PipelineKey& k1,
return 0 == memcmp((const void*) &k1, (const void*) &k2, sizeof(k1));
}
bool VulkanPipelineCache::DescEqual::operator()(const DescriptorKey& k1,
const DescriptorKey& k2) const {
for (uint32_t i = 0; i < UBUFFER_BINDING_COUNT; i++) {
if (k1.uniformBuffers[i] != k2.uniformBuffers[i] ||
k1.uniformBufferOffsets[i] != k2.uniformBufferOffsets[i] ||
k1.uniformBufferSizes[i] != k2.uniformBufferSizes[i]) {
return false;
}
}
for (uint32_t i = 0; i < SAMPLER_BINDING_COUNT; i++) {
if (k1.samplers[i].sampler != k2.samplers[i].sampler ||
k1.samplers[i].imageView != k2.samplers[i].imageView ||
k1.samplers[i].imageLayout != k2.samplers[i].imageLayout) {
return false;
}
}
for (uint32_t i = 0; i < INPUT_ATTACHMENT_COUNT; i++) {
if (k1.inputAttachments[i].imageView != k2.inputAttachments[i].imageView ||
k1.inputAttachments[i].imageLayout != k2.inputAttachments[i].imageLayout) {
return false;
}
}
return true;
}
} // namespace filament::backend
#pragma clang diagnostic pop
+18 -211
View File
@@ -17,6 +17,11 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
#define TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
#include "VulkanCommands.h"
#include "VulkanMemory.h"
#include "VulkanResources.h"
#include "VulkanUtility.h"
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
@@ -34,12 +39,6 @@
#include <vector>
#include <unordered_map>
#include "VulkanCommands.h"
VK_DEFINE_HANDLE(VmaAllocator)
VK_DEFINE_HANDLE(VmaAllocation)
VK_DEFINE_HANDLE(VmaPool)
namespace filament::backend {
struct VulkanProgram;
@@ -56,32 +55,14 @@ class VulkanResourceAllocator;
// - Assumes that viewport and scissor should be dynamic. (not baked into VkPipeline)
// - Assumes that uniform buffers should be visible across all shader stages.
//
class VulkanPipelineCache : public CommandBufferObserver {
class VulkanPipelineCache {
public:
VulkanPipelineCache(VulkanPipelineCache const&) = delete;
VulkanPipelineCache& operator=(VulkanPipelineCache const&) = delete;
static constexpr uint32_t UBUFFER_BINDING_COUNT = Program::UNIFORM_BINDING_COUNT;
static constexpr uint32_t SAMPLER_BINDING_COUNT = MAX_SAMPLER_COUNT;
// We assume only one possible input attachment between two subpasses. See also the subpasses
// definition in VulkanFboCache.
static constexpr uint32_t INPUT_ATTACHMENT_COUNT = 1;
static constexpr uint32_t SHADER_MODULE_COUNT = 2;
static constexpr uint32_t VERTEX_ATTRIBUTE_COUNT = MAX_VERTEX_ATTRIBUTE_COUNT;
// Three descriptor set layouts: uniforms, combined image samplers, and input attachments.
static constexpr uint32_t DESCRIPTOR_TYPE_COUNT = 3;
static constexpr uint32_t INITIAL_DESCRIPTOR_SET_POOL_SIZE = 512;
// The VertexArray POD is an array of buffer targets and an array of attributes that refer to
// those targets. It does not include any references to actual buffers, so you can think of it
// as a vertex assembler configuration. For simplicity it contains fixed-size arrays and does
// not store sizes; all unused entries are simply zeroed out.
struct VertexArray {
};
// The ProgramBundle contains weak references to the compiled vertex and fragment shaders.
struct ProgramBundle {
VkShaderModule vertex;
@@ -89,10 +70,6 @@ public:
VkSpecializationInfo* specializationInfos = nullptr;
};
using UsageFlags = utils::bitset128;
static UsageFlags getUsageFlags(uint16_t binding, ShaderStageFlags stages, UsageFlags src = {});
static UsageFlags disableUsageFlags(uint16_t binding, UsageFlags src);
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wpadded"
@@ -135,62 +112,26 @@ public:
// Upon construction, the pipeCache initializes some internal state but does not make any Vulkan
// calls. On destruction it will free any cached Vulkan objects that haven't already been freed.
VulkanPipelineCache(VulkanResourceAllocator* allocator);
VulkanPipelineCache(VkDevice device, VmaAllocator allocator);
~VulkanPipelineCache();
void setDevice(VkDevice device, VmaAllocator allocator);
// Creates new descriptor sets if necessary and binds them using vkCmdBindDescriptorSets.
// Returns false if descriptor set allocation fails.
bool bindDescriptors(VkCommandBuffer cmdbuffer) noexcept;
void bindLayout(VkPipelineLayout layout) noexcept;
// Creates a new pipeline if necessary and binds it using vkCmdBindPipeline.
// Returns false if an error occurred.
bool bindPipeline(VulkanCommandBuffer* commands) noexcept;
// Sets up a new scissor rectangle if it has been dirtied.
void bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept;
void bindPipeline(VulkanCommandBuffer* commands);
// Each of the following methods are fast and do not make Vulkan calls.
void bindProgram(VulkanProgram* program) noexcept;
void bindRasterState(const RasterState& rasterState) noexcept;
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept;
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
void bindUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject,
VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) noexcept;
void bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT],
VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept;
void bindInputAttachment(uint32_t bindingIndex, VkDescriptorImageInfo imageInfo) noexcept;
void bindVertexArray(VkVertexInputAttributeDescription const* attribDesc,
VkVertexInputBindingDescription const* bufferDesc, uint8_t count);
// Gets the current UBO at the given slot, useful for push / pop.
UniformBufferBinding getUniformBufferBinding(uint32_t bindingIndex) const noexcept;
// Checks if the given uniform is bound to any slot, and if so binds "null" to that slot.
// Also invalidates all cached descriptors that refer to the given buffer.
// This is only necessary when the client knows that the UBO is about to be destroyed.
void unbindUniformBuffer(VkBuffer uniformBuffer) noexcept;
// Checks if an image view is bound to any sampler, and if so resets that particular slot.
// Also invalidates all cached descriptors that refer to the given image view.
// This is only necessary when the client knows that a texture is about to be destroyed.
void unbindImageView(VkImageView imageView) noexcept;
// NOTE: In theory we should proffer "unbindSampler" but in practice we never destroy samplers.
// Destroys all managed Vulkan objects. This should be called before changing the VkDevice.
void terminate() noexcept;
// vkCmdBindPipeline and vkCmdBindDescriptorSets establish bindings to a specific command
// buffer; they are not global to the device. Therefore we need to be notified when a
// new command buffer becomes active.
void onCommandBuffer(const VulkanCommandBuffer& cmdbuffer) override;
// Injects a dummy texture that can be used to clear out old descriptor sets.
void setDummyTexture(VkImageView imageView) {
mDummyTargetInfo.imageView = imageView;
}
static VkPrimitiveTopology getPrimitiveTopology(PrimitiveType pt) noexcept {
switch (pt) {
case PrimitiveType::POINTS:
@@ -206,22 +147,9 @@ public:
}
}
void gc() noexcept;
private:
// PIPELINE LAYOUT CACHE KEY
// -------------------------
using PipelineLayoutKey = utils::bitset128;
static_assert(PipelineLayoutKey::BIT_COUNT >= 2 * MAX_SAMPLER_COUNT);
struct PipelineLayoutKeyHashFn {
size_t operator()(const PipelineLayoutKey& key) const;
};
struct PipelineLayoutKeyEqual {
bool operator()(const PipelineLayoutKey& k1, const PipelineLayoutKey& k2) const;
};
// PIPELINE CACHE KEY
// ------------------
@@ -274,10 +202,10 @@ private:
VertexInputBindingDescription vertexBuffers[VERTEX_ATTRIBUTE_COUNT]; // 128 : 156
RasterState rasterState; // 16 : 284
uint32_t padding; // 4 : 300
PipelineLayoutKey layout; // 16 : 304
VkPipelineLayout layout; // 8 : 304
};
static_assert(sizeof(PipelineKey) == 320, "PipelineKey must not have implicit padding.");
static_assert(sizeof(PipelineKey) == 312, "PipelineKey must not have implicit padding.");
using PipelineHashFn = utils::hash::MurmurHashFn<PipelineKey>;
@@ -285,52 +213,6 @@ private:
bool operator()(const PipelineKey& k1, const PipelineKey& k2) const;
};
// DESCRIPTOR SET CACHE KEY
// ------------------------
// Equivalent to VkDescriptorImageInfo but with explicit padding.
struct DescriptorImageInfo {
DescriptorImageInfo& operator=(const VkDescriptorImageInfo& that) {
sampler = that.sampler;
imageView = that.imageView;
imageLayout = that.imageLayout;
padding = 0;
return *this;
}
operator VkDescriptorImageInfo() const { return { sampler, imageView, imageLayout }; }
// TODO: replace the 64-bit sampler handle with `uint32_t samplerParams` and remove the
// padding field. This is possible if we have access to the VulkanSamplerCache.
VkSampler sampler;
VkImageView imageView;
VkImageLayout imageLayout;
uint32_t padding;
};
// We store size with 32 bits, so our "WHOLE" sentinel is different from Vk.
static const uint32_t WHOLE_SIZE = 0xffffffffu;
// Represents all the Vulkan state that comprises a bound descriptor set.
struct DescriptorKey {
VkBuffer uniformBuffers[UBUFFER_BINDING_COUNT]; // 80 0
DescriptorImageInfo samplers[SAMPLER_BINDING_COUNT]; // 1488 80
DescriptorImageInfo inputAttachments[INPUT_ATTACHMENT_COUNT]; // 24 1568
uint32_t uniformBufferOffsets[UBUFFER_BINDING_COUNT]; // 40 1592
uint32_t uniformBufferSizes[UBUFFER_BINDING_COUNT]; // 40 1632
};
static_assert(offsetof(DescriptorKey, samplers) == 80);
static_assert(offsetof(DescriptorKey, inputAttachments) == 1568);
static_assert(offsetof(DescriptorKey, uniformBufferOffsets) == 1592);
static_assert(offsetof(DescriptorKey, uniformBufferSizes) == 1632);
static_assert(sizeof(DescriptorKey) == 1672, "DescriptorKey must not have implicit padding.");
using DescHashFn = utils::hash::MurmurHashFn<DescriptorKey>;
struct DescEqual {
bool operator()(const DescriptorKey& k1, const DescriptorKey& k2) const;
};
#pragma clang diagnostic pop
// CACHE ENTRY STRUCTS
@@ -343,16 +225,6 @@ private:
using Timestamp = uint64_t;
Timestamp mCurrentTime = 0;
// The descriptor set cache entry is a group of descriptor sets that are bound simultaneously.
struct DescriptorCacheEntry {
std::array<VkDescriptorSet, DESCRIPTOR_TYPE_COUNT> handles;
Timestamp lastUsed;
PipelineLayoutKey pipelineLayout;
uint32_t id;
};
uint32_t mDescriptorCacheEntryCount = 0;
struct PipelineCacheEntry {
VkPipeline handle;
Timestamp lastUsed;
@@ -361,98 +233,33 @@ private:
struct PipelineLayoutCacheEntry {
VkPipelineLayout handle;
Timestamp lastUsed;
std::array<VkDescriptorSetLayout, DESCRIPTOR_TYPE_COUNT> descriptorSetLayouts;
// Each pipeline layout has 3 arenas of unused descriptors (one for each binding type).
//
// The difference between the "arenas" and the "pool" are as follows.
//
// - The "pool" is a single, centralized factory for all descriptors (VkDescriptorPool).
//
// - Each "arena" is a set of unused (but alive) descriptors that can only be used with a
// specific pipeline layout and a specific binding type. We manually manage each arena.
// The arenas are created in an empty state, and they are gradually populated as new
// descriptors are reclaimed over time. This is quite different from the pool, which is
// given a fixed size when it is constructed.
//
std::array<std::vector<VkDescriptorSet>, DESCRIPTOR_TYPE_COUNT> descriptorSetArenas;
};
// CACHE CONTAINERS
// ----------------
using PipelineLayoutMap = tsl::robin_map<PipelineLayoutKey , PipelineLayoutCacheEntry,
PipelineLayoutKeyHashFn, PipelineLayoutKeyEqual>;
using PipelineMap = tsl::robin_map<PipelineKey, PipelineCacheEntry,
PipelineHashFn, PipelineEqual>;
using DescriptorMap
= tsl::robin_map<DescriptorKey, DescriptorCacheEntry, DescHashFn, DescEqual>;
using DescriptorResourceMap
= std::unordered_map<uint32_t, std::unique_ptr<VulkanAcquireOnlyResourceManager>>;
PipelineLayoutMap mPipelineLayouts;
private:
PipelineCacheEntry* getOrCreatePipeline() noexcept;
PipelineMap mPipelines;
DescriptorMap mDescriptorSets;
DescriptorResourceMap mDescriptorResources;
// These helpers all return unstable pointers that should not be stored.
DescriptorCacheEntry* createDescriptorSets() noexcept;
PipelineCacheEntry* createPipeline() noexcept;
PipelineLayoutCacheEntry* getOrCreatePipelineLayout() noexcept;
// Misc helper methods.
void destroyLayoutsAndDescriptors() noexcept;
VkDescriptorPool createDescriptorPool(uint32_t size) const;
void growDescriptorPool() noexcept;
// Immutable state.
VkDevice mDevice = VK_NULL_HANDLE;
VmaAllocator mAllocator = VK_NULL_HANDLE;
// Current requirements for the pipeline layout, pipeline, and descriptor sets.
PipelineKey mPipelineRequirements = {};
DescriptorKey mDescriptorRequirements = {};
// Current bindings for the pipeline and descriptor sets.
PipelineKey mBoundPipeline = {};
DescriptorKey mBoundDescriptor = {};
// Current state for scissoring.
VkRect2D mCurrentScissor = {};
// The descriptor set pool starts out with a decent number of descriptor sets. The cache can
// grow the pool by re-creating it with a larger size. See growDescriptorPool().
VkDescriptorPool mDescriptorPool;
// This describes the number of descriptor sets in mDescriptorPool. Note that this needs to be
// multiplied by DESCRIPTOR_TYPE_COUNT to get the actual number of descriptor sets. Also note
// that the number of low-level "descriptors" (not descriptor *sets*) is actually much more than
// this size. It can be computed only by factoring in UBUFFER_BINDING_COUNT etc.
uint32_t mDescriptorPoolSize = INITIAL_DESCRIPTOR_SET_POOL_SIZE;
// To get the actual number of descriptor sets that have been allocated from the pool,
// take the sum of mDescriptorArenasCount (these are inactive descriptor sets) and the
// number of entries in the mDescriptorPool map (active descriptor sets). Multiply the result by
// DESCRIPTOR_TYPE_COUNT.
uint32_t mDescriptorArenasCount = 0;
// After a growth event (i.e. when the VkDescriptorPool is replaced with a bigger version), all
// currently used descriptors are moved into the "extinct" sets so that they can be safely
// destroyed a few frames later.
std::list<VkDescriptorPool> mExtinctDescriptorPools;
std::list<DescriptorCacheEntry> mExtinctDescriptorBundles;
VkDescriptorBufferInfo mDummyBufferInfo = {};
VkWriteDescriptorSet mDummyBufferWriteInfo = {};
VkDescriptorImageInfo mDummyTargetInfo = {};
VkWriteDescriptorSet mDummyTargetWriteInfo = {};
VkBuffer mDummyBuffer;
VmaAllocation mDummyMemory;
VulkanResourceAllocator* mResourceAllocator;
VulkanAcquireOnlyResourceManager mPipelineBoundResources;
};
} // namespace filament::backend
@@ -28,7 +28,6 @@ using namespace bluevk;
namespace filament::backend {
using ImgUtil = VulkanImageUtility;
using TaskHandler = VulkanReadPixels::TaskHandler;
using WorkloadFunc = TaskHandler::WorkloadFunc;
using OnCompleteFunc = TaskHandler::OnCompleteFunc;
@@ -118,7 +117,7 @@ void VulkanReadPixels::terminate() noexcept {
VulkanReadPixels::VulkanReadPixels(VkDevice device)
: mDevice(device) {}
void VulkanReadPixels::run(VulkanRenderTarget const* srcTarget, uint32_t const x, uint32_t const y,
void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint32_t const y,
uint32_t const width, uint32_t const height, uint32_t const graphicsQueueFamilyIndex,
PixelBufferDescriptor&& pbd, SelecteMemoryFunction const& selectMemoryFunc,
OnReadCompleteFunction const& readCompleteFunc) {
@@ -218,7 +217,7 @@ void VulkanReadPixels::run(VulkanRenderTarget const* srcTarget, uint32_t const x
};
vkBeginCommandBuffer(cmdbuffer, &binfo);
ImgUtil::transitionLayout(cmdbuffer, {
imgutil::transitionLayout(cmdbuffer, {
.image = stagingImage,
.oldLayout = VulkanLayout::UNDEFINED,
.newLayout = VulkanLayout::TRANSFER_DST,
@@ -232,8 +231,7 @@ void VulkanReadPixels::run(VulkanRenderTarget const* srcTarget, uint32_t const x
});
VulkanAttachment const srcAttachment = srcTarget->getColor(0);
const VkImageSubresourceRange srcRange
= srcAttachment.getSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT);
VkImageSubresourceRange const srcRange = srcAttachment.getSubresourceRange();
srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
VkImageCopy const imageCopyRegion = {
@@ -266,8 +264,8 @@ void VulkanReadPixels::run(VulkanRenderTarget const* srcTarget, uint32_t const x
imageCopyRegion.srcOffset.y + imageCopyRegion.extent.height <= srcExtent.height);
vkCmdCopyImage(cmdbuffer, srcAttachment.getImage(),
ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC), stagingImage,
ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST), 1, &imageCopyRegion);
imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC), stagingImage,
imgutil::getVkLayout(VulkanLayout::TRANSFER_DST), 1, &imageCopyRegion);
// Restore the source image layout.
srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::COLOR_ATTACHMENT);
@@ -26,9 +26,7 @@
#include <functional>
#include <mutex>
#include <queue>
#include <set>
#include <thread>
#include <vector>
namespace filament::backend {
@@ -74,8 +72,8 @@ public:
void terminate() noexcept;
void run(VulkanRenderTarget const* srcTarget, uint32_t x, uint32_t y, uint32_t width,
uint32_t height, uint32_t graphicsQueueFamilyIndex, PixelBufferDescriptor&& pbd,
void run(VulkanRenderTarget* srcTarget, uint32_t x, uint32_t y, uint32_t width, uint32_t height,
uint32_t graphicsQueueFamilyIndex, PixelBufferDescriptor&& pbd,
SelecteMemoryFunction const& selectMemoryFunc,
OnReadCompleteFunction const& readCompleteFunc);
@@ -49,8 +49,8 @@ namespace filament::backend {
#endif
class VulkanResourceAllocator {
public:
using AllocatorImpl = HandleAllocatorVK;
VulkanResourceAllocator(size_t arenaSize, bool disableUseAfterFreeCheck)
: mHandleAllocatorImpl("Handles", arenaSize, disableUseAfterFreeCheck)
#if DEBUG_RESOURCE_LEAKS
@@ -106,7 +106,7 @@ public:
}
private:
HandleAllocatorVK mHandleAllocatorImpl;
AllocatorImpl mHandleAllocatorImpl;
#if DEBUG_RESOURCE_LEAKS
public:
@@ -17,7 +17,7 @@
#include "VulkanResources.h"
#include "VulkanHandles.h"
#include "VulkanResourceAllocator.h"
#include "caching/VulkanDescriptorSet.h"
#include "VulkanPipelineCache.h"
namespace filament::backend {
@@ -62,6 +62,9 @@ void deallocateResource(VulkanResourceAllocator* allocator, VulkanResourceType t
case VulkanResourceType::RENDER_PRIMITIVE:
allocator->destruct<VulkanRenderPrimitive>(Handle<VulkanRenderPrimitive>(id));
break;
case VulkanResourceType::DESCRIPTOR_SET_LAYOUT:
allocator->destruct<VulkanDescriptorSetLayout>(Handle<VulkanDescriptorSetLayout>(id));
break;
case VulkanResourceType::DESCRIPTOR_SET:
allocator->destruct<VulkanDescriptorSet>(Handle<VulkanDescriptorSet>(id));
break;
+16 -15
View File
@@ -35,24 +35,25 @@ struct VulkanThreadSafeResource;
// Subclasses of VulkanResource must provide this enum in their construction.
enum class VulkanResourceType : uint8_t {
BUFFER_OBJECT,
INDEX_BUFFER,
PROGRAM,
RENDER_TARGET,
SAMPLER_GROUP,
SWAP_CHAIN,
RENDER_PRIMITIVE,
TEXTURE,
TIMER_QUERY,
VERTEX_BUFFER,
VERTEX_BUFFER_INFO,
DESCRIPTOR_SET,
BUFFER_OBJECT = 0,
INDEX_BUFFER = 1,
PROGRAM = 2,
RENDER_TARGET = 3,
SAMPLER_GROUP = 4,
SWAP_CHAIN = 5,
RENDER_PRIMITIVE = 6,
TEXTURE = 7,
TIMER_QUERY = 8,
VERTEX_BUFFER = 9,
VERTEX_BUFFER_INFO = 10,
DESCRIPTOR_SET_LAYOUT = 11,
DESCRIPTOR_SET = 12,
// Below are resources that are managed manually (i.e. not ref counted).
FENCE,
HEAP_ALLOCATED,
FENCE = 13,
HEAP_ALLOCATED = 14,
END_TYPE, // A placeholder
END_TYPE = 15, // A placeholder
};
#define IS_HEAP_ALLOC_TYPE(f) \
@@ -17,6 +17,7 @@
#include "VulkanStagePool.h"
#include "VulkanConstants.h"
#include "VulkanImageUtility.h"
#include "VulkanMemory.h"
#include "VulkanUtility.h"
@@ -118,7 +119,7 @@ VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, Pi
// VK_IMAGE_LAYOUT_PREINITIALIZED or VK_IMAGE_LAYOUT_GENERAL layout. Calling
// vkGetImageSubresourceLayout for a linear image returns a subresource layout mapping that is
// valid for either of those image layouts."
VulkanImageUtility::transitionLayout(cmdbuffer, {
imgutil::transitionLayout(cmdbuffer, {
.image = image->image,
.oldLayout = VulkanLayout::UNDEFINED,
.newLayout = VulkanLayout::READ_WRITE, // (= VK_IMAGE_LAYOUT_GENERAL)
@@ -17,7 +17,9 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANSTAGEPOOL_H
#define TNT_FILAMENT_BACKEND_VULKANSTAGEPOOL_H
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include "VulkanMemory.h"
#include <map>
#include <unordered_set>

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