Compare commits

...
105 Commits
Author SHA1 Message Date
Benjamin Doherty d3be6a99e3 Test commit 2024-09-16 12:09:26 -07:00
Powei Feng 3e556588fc Merge branch 'rc/1.54.3' into release 2024-09-10 12:26:56 -07:00
Powei Feng 32b0625f36 Revert "reenable the SimplificationPass in spirv-opt"
This reverts commit 30387af61c.

Causes breakage on Pixel 8pro for 1P apps.
2024-09-09 10:37:11 -07:00
Sungun Park bb1d1c7349 Merge branch 'rc/1.54.2' into release 2024-09-04 18:55:59 +00:00
Sungun Park c967fb7860 Bump version to 1.54.3 2024-09-04 18:55:59 +00:00
Sungun Park 1b4afbab51 Release Filament 1.54.2 2024-09-04 18:55:52 +00:00
Powei Feng c677607353 github: Split Android CI to per ABI builds
- Pulled the android continuous workflow into an action
 - Split the android continuous build into 3 ABIs armv7, armv8a,
   and x86_64 (note that x86 is not present).
 - Remove the upload artifacts step from previous workflow.
   This was meant for letting client try a tip-of-tree Android
   build.  We can revisit this later. (Also removed mention
   in README.md)
 - Split the android continous into debug build and a release
   build commands, enabling deletion of intermediate output
   directory.
2024-09-04 11:22:43 -07:00
Powei Feng 422fcea2cf Make builderMakeName public
This call is used in the BuilderNameMixin template definition,
which is a public API.
2024-09-04 17:07:51 +00:00
Powei Feng cdb539b3cf Make builderMakeName public
This call is used in the BuilderNameMixin template definition,
which is a public API.
2024-09-04 10:04:20 -07:00
Mathias Agopian b0d3f14243 a handle with a tag would sometime return "no tag"
this is because the key used to retrieve the tag was not "truncated"
like it was when inserting the tag in the hash-map.
2024-09-03 17:13:59 +00:00
Ben Doherty 16bed4de00 Enable mmap on iOS (#8100) 2024-09-03 09:58:13 -07:00
Mathias Agopian 2b620e65fd fix potential framegraph textures use-after free
make sure to unset all textures in the per-view sampler group after
they are used, because the resource could be destroyed after the
pass is finished

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

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

Fixes #6293
2024-08-30 10:56:31 -07:00
Sungun Park 639b933fd6 Rename customRenderTarget (#8093)
The currentRenderTarget is a misnomer as it's a custom RT. Rename it
properly.
2024-08-29 11:47:24 -07:00
Mathias Agopian 6653c6c08b fix a typo in RenderPassBuilder::renderFlags() 2024-08-28 21:51:03 -07:00
Maximilien Dagois 777f664b1b Refactored the handling of usage flags in the VulkanTexture constructor 2024-08-28 12:58:31 -07:00
Powei Feng f5f1e56123 Re-enable DebugRegistry::setProperty for release build (#8086)
`DebugRegistry::setProperty` is no longer just applicable to debug
builds.

This change was previously added in 6c0bd36 but then reverted
in a7317e7. We re-enable it and separate it from the shadow
changes in this commit.
2024-08-28 12:17:11 -07:00
Powei Feng 283d240409 Re-enable DebugRegistry::setProperty for release build (#8086)
`DebugRegistry::setProperty` is no longer just applicable to debug
builds.

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

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

Default materials are always compiled with either 'instanced' or
'multiview. Therefore Filament will emit warnings unintentionally if the
engine is set up for stereo. This commit fixes it.
2024-08-22 20:39:04 +00:00
Mathias Agopian 5e7106b521 custom variables can now have their precision specified
e.g.:

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

Disable depth-clamping when VSM is enabled.
2024-08-20 12:04:01 -07:00
Sungun Park 1f9a11802d Minor tweak (#8053)
Move the common part outside the loop.
2024-08-20 17:13:55 +00:00
Sungun Park 89835a7a67 Fix a bug for calculating distance (#8058)
This is a missing part from the change
22d99bac3d
2024-08-20 16:28:29 +00:00
Powei Feng aa5f36e1e3 Bump version to 1.54.1 2024-08-20 08:55:19 -07:00
Powei Feng c1a3450d9c Merge branch 'rc/1.54.0' into release 2024-08-20 08:55:18 -07:00
Powei Feng 28ef805e5d Release Filament 1.54.0 2024-08-20 08:55:10 -07:00
Mathias Agopian 6c0bd360b3 Add support for depth clamp and use it for shadows
vk, metal and desktop gl all support depth clamp, GLES/android also does
with ANGLE. Add support for it in the backends.

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

Fixes #6293
2024-08-19 17:13:30 -07:00
Mathias Agopian 063affb612 more improvement of csm display 2024-08-19 17:13:03 -07:00
Mathias Agopian 9c857f64ae improve split-view mode
- side panel doesn't overlap with content anymore
2024-08-19 17:13:03 -07:00
Mathias Agopian 5966b5dd8f fix shadow cascade computations
shadow cascades where not calculated properly because part of the 
calculation took the cascade near/far into account, while another
part didn't. This resulted in cascades being too large. It didn't
create wrong shadows, but reduced (and in some case canceled) the
usefulness of the cascade.

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

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


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

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

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

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

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


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

This requires the stereoscopic type to be written into the package,
which needs a material version bump.
2024-08-13 01:31:10 +00:00
Mathias Agopian 7f97363d9c fix debug build 2024-08-12 15:09:06 -07:00
Mathias Agopian d88b4c1bc3 Add a way to retrieve the count of all resources
This is intended for debugging.

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

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

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

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

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

"as soon as atomic operations that are not tagged memory_order_seq_cst 
enter the picture, the sequential consistency guarantee for the program 
is lost"
2024-07-31 10:11:01 -07:00
Mathias Agopian 324dcd3c86 early exit in fog to improve performance 2024-07-31 10:10:48 -07:00
Sungun Park c43c58af5d Bump version to 1.53.4 2024-07-31 16:23:06 +00:00
Sungun Park 4125802644 Release Filament 1.53.3 2024-07-31 16:22:54 +00:00
Balaji M 2d21fcbe55 [gltfio] Initialize mCgltfBuffersLoaded to fix crash (#7999)
Value of mCgltfBuffersLoaded is sometimes retained across creation of FAssetLoader which skips loading the buffer in AssetLoaderExtended#createPrimitive leading to null pointer crash
2024-07-31 02:12:04 +00:00
Sungun Park 3f4dac6248 Fix a crash for IBL resource loading (#8001)
This fixes a crash introduced by a8ace2891d

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

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

FIXES=[332425392]

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

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

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

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

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

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

* avoid calling wakeAll() when possible

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

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

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

* mActiveJobs fixes

some comments claimed mActiveJobs needed to be modified before or after
accessing the WorkQueue; this couldn't be correct because there were no
guaranteed global ordering with the workQueue.
2024-07-24 15:34:36 -07:00
Mathias Agopian d3a35de386 fix a crash with bloom when screen dimension smaller than 16px 2024-07-24 11:49:38 -07:00
141 changed files with 2321 additions and 845 deletions
@@ -0,0 +1,17 @@
name: 'Android Continuous'
inputs:
build-abi:
description: 'The target platform ABI'
required: true
default: 'armeabi-v7a'
runs:
using: "composite"
steps:
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Run build script
run: |
cd build/android && printf "y" | ./build.sh continuous ${{ inputs.build-abi }}
shell: bash
+25 -22
View File
@@ -8,32 +8,35 @@ on:
- rc/**
jobs:
build-android:
name: build-android
build-android-armv7:
name: build-android-armv7
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- uses: actions/setup-java@v3
- name: Run Android Continuous
uses: ./.github/actions/android-continuous
with:
distribution: 'temurin'
java-version: '17'
- name: Run build script
run: |
cd build/android && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v1.0.0
build-abi: armeabi-v7a
build-android-armv8a:
name: build-android-armv8a
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- name: Run Android Continuous
uses: ./.github/actions/android-continuous
with:
name: filament-android
path: out/filament-android-release.aar
- uses: actions/upload-artifact@v1.0.0
build-abi: arm64-v8a
build-android-x86_64:
name: build-android-x86_64
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- name: Run Android Continuous
uses: ./.github/actions/android-continuous
with:
name: filamat-android-full
path: out/filamat-android-release.aar
- uses: actions/upload-artifact@v1.0.0
with:
name: gltfio-android-release
path: out/gltfio-android-release.aar
- uses: actions/upload-artifact@v1.0.0
with:
name: filament-utils-android-release
path: out/filament-utils-android-release.aar
build-abi: x86_64
+3 -1
View File
@@ -49,8 +49,10 @@ jobs:
distribution: 'temurin'
java-version: '17'
- name: Run build script
# Only build 1 64 bit target during presubmit to cut down build times during presubmit
# Continuous builds will build everything
run: |
cd build/android && printf "y" | ./build.sh presubmit
cd build/android && printf "y" | ./build.sh presubmit arm64-v8a
build-ios:
name: build-iOS
+1 -1
View File
@@ -120,7 +120,7 @@ jobs:
env:
TAG: ${{ steps.git_ref.outputs.tag }}
run: |
cd build/android && printf "y" | ./build.sh release
cd build/android && printf "y" | ./build.sh release armeabi-v7a,arm64-v8a,x86,x86_64
cd ../..
mv out/filament-android-release.aar out/filament-${TAG}-android.aar
mv out/filamat-android-release.aar out/filamat-${TAG}-android.aar
+2 -12
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.53.3'
implementation 'com.google.android.filament:filament-android:1.54.3'
}
```
@@ -51,19 +51,9 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.53.3'
pod 'Filament', '~> 1.54.3'
```
### Snapshots
If you prefer to live on the edge, you can download a continuous build by following the following
steps:
1. Find the [commit](https://github.com/google/filament/commits/main) you're interested in.
2. Click the green check mark under the commit message.
3. Click on the _Details_ link for the platform you're interested in.
4. On the top left click _Summary_, then in the _Artifacts_ section choose the desired artifact.
## Documentation
- [Filament](https://google.github.io/filament/Filament.html), an in-depth explanation of
+23
View File
@@ -7,6 +7,29 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.54.3
## v1.54.2
- Add a `name` API to Filament objects for debugging handle use-after-free assertions
## v1.54.1
## v1.54.0
- materials: add a new `stereoscopicType` material parameter. [⚠️ **New Material Version**]
- Fix a crash when compiling shaders on IMG devices
## v1.53.5
- engine: Fix bug causing certain sampler parameters to not be applied correctly in GLES 2.0 and on
certain GLES 3.0 drivers.
## v1.53.4
## v1.53.3
- Add drag and drop support for IBL files for desktop gltf_viewer.
@@ -28,6 +28,14 @@
using namespace filament;
using namespace backend;
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Renderer_nSkipFrame(JNIEnv *, jclass, jlong nativeRenderer,
jlong vsyncSteadyClockTimeNano) {
Renderer *renderer = (Renderer *) nativeRenderer;
renderer->skipFrame(uint64_t(vsyncSteadyClockTimeNano));
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Renderer_nBeginFrame(JNIEnv *, jclass, jlong nativeRenderer,
jlong nativeSwapChain, jlong frameTimeNanos) {
@@ -531,3 +531,12 @@ Java_com_google_android_filament_View_nGetFogEntity(JNIEnv *env, jclass clazz,
View *view = (View *) nativeView;
return (jint)view->getFogEntity().getId();
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nClearFrameHistory(JNIEnv *env, jclass clazz,
jlong nativeView, jlong nativeEngine) {
View *view = (View *) nativeView;
Engine *engine = (Engine *) nativeEngine;
view->clearFrameHistory(*engine);
}
@@ -425,9 +425,12 @@ public class Engine {
public long resourceAllocatorCacheSizeMB = 64;
/*
* This value determines for how many frames are texture entries kept in the cache.
* This value determines how many frames texture entries are kept for in the cache. This
* is a soft limit, meaning some texture older than this are allowed to stay in the cache.
* Typically only one texture is evicted per frame.
* The default is 1.
*/
public long resourceAllocatorCacheMaxAge = 2;
public long resourceAllocatorCacheMaxAge = 1;
/*
* Disable backend handles use-after-free checks.
@@ -297,6 +297,20 @@ public class Renderer {
nSetVsyncTime(getNativeObject(), steadyClockTimeNano);
}
/**
* Call skipFrame when momentarily skipping frames, for instance if the content of the
* scene doesn't change.
*
* @param vsyncSteadyClockTimeNano The time in nanoseconds when the frame started being rendered,
* in the {@link System#nanoTime()} timebase. Divide this value by 1000000 to
* convert it to the {@link android.os.SystemClock#uptimeMillis()}
* time base. This typically comes from
* {@link android.view.Choreographer.FrameCallback}.
*/
public void skipFrame(long vsyncSteadyClockTimeNano) {
nSkipFrame(getNativeObject(), vsyncSteadyClockTimeNano);
}
/**
* Sets up a frame for this <code>Renderer</code>.
* <p><code>beginFrame</code> manages frame pacing, and returns whether or not a frame should be
@@ -716,6 +730,7 @@ public class Renderer {
private static native void nSetPresentationTime(long nativeObject, long monotonicClockNanos);
private static native void nSetVsyncTime(long nativeObject, long steadyClockTimeNano);
private static native void nSkipFrame(long nativeObject, long vsyncSteadyClockTimeNano);
private static native boolean nBeginFrame(long nativeRenderer, long nativeSwapChain, long frameTimeNanos);
private static native void nEndFrame(long nativeRenderer);
private static native void nRender(long nativeRenderer, long nativeView);
@@ -1233,6 +1233,18 @@ public class View {
return nGetFogEntity(getNativeObject());
}
/**
* When certain temporal features are used (e.g.: TAA or Screen-space reflections), the view
* keeps a history of previous frame renders associated with the Renderer the view was last
* used with. When switching Renderer, it may be necessary to clear that history by calling
* this method. Similarly, if the whole content of the screen change, like when a cut-scene
* starts, clearing the history might be needed to avoid artifacts due to the previous frame
* being very different.
*/
public void clearFrameHistory(Engine engine) {
nClearFrameHistory(getNativeObject(), engine.getNativeObject());
}
public long getNativeObject() {
if (mNativeObject == 0) {
throw new IllegalStateException("Calling method on destroyed View");
@@ -1294,7 +1306,7 @@ public class View {
private static native void nSetMaterialGlobal(long nativeView, int index, float x, float y, float z, float w);
private static native void nGetMaterialGlobal(long nativeView, int index, float[] out);
private static native int nGetFogEntity(long nativeView);
private static native void nClearFrameHistory(long nativeView, long nativeEngine);
/**
* List of available ambient occlusion techniques.
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.53.3
VERSION_NAME=1.54.3
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+15 -7
View File
@@ -60,13 +60,7 @@ if [[ ! -d "${ANDROID_HOME}/ndk/$FILAMENT_NDK_VERSION" ]]; then
yes | ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --licenses
${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager "ndk;$FILAMENT_NDK_VERSION"
fi
# Only build 1 64 bit target during presubmit to cut down build times during presubmit
# Continuous builds will build everything
ANDROID_ABIS=
if [[ "$TARGET" == "presubmit" ]]; then
ANDROID_ABIS="-q arm64-v8a"
fi
# Build the Android sample-gltf-viewer APK during release.
BUILD_SAMPLES=
@@ -74,5 +68,19 @@ if [[ "$TARGET" == "release" ]]; then
BUILD_SAMPLES="-k sample-gltf-viewer"
fi
function build_android() {
local ABI=$1
# Do the following in two steps so that we do not run out of space
if [[ -n "${BUILD_DEBUG}" ]]; then
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION} ./build.sh -p android -q ${ABI} -c ${BUILD_SAMPLES} ${GENERATE_ARCHIVES} ${BUILD_DEBUG}
rm -rf out/cmake-android-debug-*
fi
if [[ -n "${BUILD_RELEASE}" ]]; then
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION} ./build.sh -p android -q ${ABI} -c ${BUILD_SAMPLES} ${GENERATE_ARCHIVES} ${BUILD_RELEASE}
rm -rf out/cmake-android-release-*
fi
}
pushd `dirname $0`/../.. > /dev/null
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION} ./build.sh -p android $ANDROID_ABIS -c $BUILD_SAMPLES $GENERATE_ARCHIVES $BUILD_DEBUG $BUILD_RELEASE
build_android $2
+11 -2
View File
@@ -1308,7 +1308,12 @@ Description
declare a variable called `eyeDirection` you can access it in the fragment shader using
`variable_eyeDirection`. In the vertex shader, the interpolant name is simply a member of
the `MaterialVertexInputs` structure (`material.eyeDirection` in your example). Each
interpolant is of type `float4` (`vec4`) in the shaders.
interpolant is of type `float4` (`vec4`) in the shaders. By default the precision of the
interpolant is `highp` in *both* the vertex and fragment shaders.
An alternate syntax can be used to specify both the name and precision of the interpolant.
In this case the specified precision is used as-is in both fragment and vertex stages, in
particular if `default` is specified the default precision is used is the fragment shader
(`mediump`) and in the vertex shader (`highp`).
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
@@ -1320,7 +1325,11 @@ material {
}
],
variables : [
eyeDirection
eyeDirection,
{
name : eyeColor,
precision : medium
}
],
vertexDomain : device,
depthWrite : false,
+1
View File
@@ -61,6 +61,7 @@ set(SRCS
src/Engine.cpp
src/Exposure.cpp
src/Fence.cpp
src/FilamentBuilder.cpp
src/FrameInfo.cpp
src/FrameSkipper.cpp
src/Froxelizer.cpp
@@ -1058,7 +1058,7 @@ struct RasterState {
bool inverseFrontFaces : 1; // 31
//! padding, must be 0
uint8_t padding : 1; // 32
bool depthClamp : 1; // 32
};
uint32_t u = 0;
};
@@ -1244,7 +1244,7 @@ enum class Workaround : uint16_t {
ADRENO_UNIFORM_ARRAY_CRASH,
// Workaround a Metal pipeline compilation error with the message:
// "Could not statically determine the target of a texture". See light_indirect.fs
A8X_STATIC_TEXTURE_TARGET_ERROR,
METAL_STATIC_TEXTURE_TARGET_ERROR,
// Adreno drivers sometimes aren't able to blit into a layer of a texture array.
DISABLE_BLIT_INTO_TEXTURE_ARRAY,
// Multiple workarounds needed for PowerVR GPUs
@@ -162,6 +162,10 @@ DECL_DRIVER_API_0(finish)
// reset state tracking, if the driver does any state tracking (e.g. GL)
DECL_DRIVER_API_0(resetState)
DECL_DRIVER_API_N(setDebugTag,
backend::HandleBase::HandleId, handleId,
utils::CString, tag)
/*
* Creating driver objects
* -----------------------
@@ -305,6 +309,7 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isDepthStencilBlitSupported, backend::TextureFormat, format)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedTexturesSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthClampSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
@@ -439,12 +444,10 @@ DECL_DRIVER_API_N(setPushConstant,
backend::PushConstantVariant, value)
DECL_DRIVER_API_N(insertEventMarker,
const char*, string,
uint32_t, len = 0)
const char*, string)
DECL_DRIVER_API_N(pushGroupMarker,
const char*, string,
uint32_t, len = 0)
const char*, string)
DECL_DRIVER_API_0(popGroupMarker)
@@ -20,11 +20,12 @@
#include <backend/Handle.h>
#include <utils/Allocator.h>
#include <utils/CString.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <utils/Panic.h>
#include <tsl/robin_map.h>
@@ -39,7 +40,7 @@
#define HandleAllocatorGL HandleAllocator<32, 64, 136> // ~4520 / pool / MiB
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB
#define HandleAllocatorMTL HandleAllocator<32, 48, 552> // ~1660 / pool / MiB
#define HandleAllocatorMTL HandleAllocator<32, 64, 552> // ~1660 / pool / MiB
namespace filament::backend {
@@ -173,8 +174,10 @@ public:
uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
auto const pNode = static_cast<typename Allocator::Node*>(p);
uint8_t const expectedAge = pNode[-1].age;
FILAMENT_CHECK_POSTCONDITION(expectedAge == age) <<
"use-after-free of Handle with id=" << handle.getId();
// getHandleTag() is only called if the check fails.
FILAMENT_CHECK_POSTCONDITION(expectedAge == age)
<< "use-after-free of Handle with id=" << handle.getId()
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
}
}
@@ -201,6 +204,29 @@ public:
return handle_cast<Dp>(const_cast<Handle<B>&>(handle));
}
void associateTagToHandle(HandleBase::HandleId id, utils::CString&& tag) noexcept {
// TODO: for now, only pool handles check for use-after-free, so we only keep tags for
// those
if (isPoolHandle(id)) {
// Truncate the age to get the debug tag
uint32_t const key = id & ~(HANDLE_DEBUG_TAG_MASK ^ HANDLE_AGE_MASK);
// This line is the costly part. In the future, we could potentially use a custom
// allocator.
mDebugTags[key] = std::move(tag);
}
}
utils::CString getHandleTag(HandleBase::HandleId id) const noexcept {
if (!isPoolHandle(id)) {
return "(no tag)";
}
uint32_t const key = id & ~(HANDLE_DEBUG_TAG_MASK ^ HANDLE_AGE_MASK);
if (auto pos = mDebugTags.find(key); pos != mDebugTags.end()) {
return pos->second;
}
return "(no tag)";
}
private:
template<typename D>
@@ -318,12 +344,24 @@ private:
}
}
// we handle a 4 bits age per address
static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u; // pool vs heap handle
static constexpr uint32_t HANDLE_AGE_MASK = 0x78000000u; // handle's age
static constexpr uint32_t HANDLE_INDEX_MASK = 0x07FFFFFFu; // handle index
static constexpr uint32_t HANDLE_TAG_MASK = HANDLE_AGE_MASK;
static constexpr uint32_t HANDLE_AGE_SHIFT = 27;
// number if bits allotted to the handle's age (currently 4 max)
static constexpr uint32_t HANDLE_AGE_BIT_COUNT = 4;
// number if bits allotted to the handle's debug tag (HANDLE_AGE_BIT_COUNT max)
static constexpr uint32_t HANDLE_DEBUG_TAG_BIT_COUNT = 2;
// bit shift for both the age and debug tag
static constexpr uint32_t HANDLE_AGE_SHIFT = 27;
// mask for the heap (vs pool) flag
static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u;
// mask for the age
static constexpr uint32_t HANDLE_AGE_MASK =
((1 << HANDLE_AGE_BIT_COUNT) - 1) << HANDLE_AGE_SHIFT;
// mask for the debug tag
static constexpr uint32_t HANDLE_DEBUG_TAG_MASK =
((1 << HANDLE_DEBUG_TAG_BIT_COUNT) - 1) << HANDLE_AGE_SHIFT;
// mask for the index
static constexpr uint32_t HANDLE_INDEX_MASK = 0x07FFFFFFu;
static_assert(HANDLE_DEBUG_TAG_BIT_COUNT <= HANDLE_AGE_BIT_COUNT);
static bool isPoolHandle(HandleBase::HandleId id) noexcept {
return (id & HANDLE_HEAP_FLAG) == 0u;
@@ -338,7 +376,7 @@ private:
// a non-pool handle.
if (UTILS_LIKELY(isPoolHandle(id))) {
char* const base = (char*)mHandleArena.getArea().begin();
uint32_t const tag = id & HANDLE_TAG_MASK;
uint32_t const tag = id & HANDLE_AGE_MASK;
size_t const offset = (id & HANDLE_INDEX_MASK) * Allocator::getAlignment();
return { static_cast<void*>(base + offset), tag };
}
@@ -353,7 +391,7 @@ private:
size_t const offset = (char*)p - base;
assert_invariant((offset % Allocator::getAlignment()) == 0);
auto id = HandleBase::HandleId(offset / Allocator::getAlignment());
id |= tag & HANDLE_TAG_MASK;
id |= tag & HANDLE_AGE_MASK;
assert_invariant((id & HANDLE_HEAP_FLAG) == 0);
return id;
}
@@ -363,6 +401,7 @@ private:
// Below is only used when running out of space in the HandleArena
mutable utils::Mutex mLock;
tsl::robin_map<HandleBase::HandleId, void*> mOverflowMap;
tsl::robin_map<HandleBase::HandleId, utils::CString> mDebugTags;
HandleBase::HandleId mId = 0;
bool mUseAfterFreeCheckDisabled = false;
};
+1 -1
View File
@@ -24,7 +24,7 @@
#include <utils/debug.h>
#include <utils/ostream.h>
#if !defined(WIN32) && !defined(__EMSCRIPTEN__) && !defined(IOS)
#if !defined(WIN32) && !defined(__EMSCRIPTEN__)
# include <sys/mman.h>
# include <unistd.h>
# define HAS_MMAP 1
+8 -5
View File
@@ -101,9 +101,8 @@ void CommandBufferQueue::flush() noexcept {
size_t const used = std::distance(
static_cast<char const*>(begin), static_cast<char const*>(end));
std::unique_lock<utils::Mutex> lock(mLock);
mCommandBuffersToExecute.push_back({ begin, end });
mCondition.notify_one();
// circular buffer is too small, we corrupted the stream
FILAMENT_CHECK_POSTCONDITION(used <= mFreeSpace) <<
@@ -112,10 +111,12 @@ void CommandBufferQueue::flush() noexcept {
"Space used at this time: " << used <<
" bytes, overflow: " << used - mFreeSpace << " bytes";
// wait until there is enough space in the buffer
mFreeSpace -= used;
if (UTILS_UNLIKELY(mFreeSpace < requiredSize)) {
mCommandBuffersToExecute.push_back({ begin, end });
mCondition.notify_one();
// wait until there is enough space in the buffer
if (UTILS_UNLIKELY(mFreeSpace < requiredSize)) {
#ifndef NDEBUG
size_t const totalUsed = circularBuffer.size() - mFreeSpace;
@@ -153,8 +154,10 @@ std::vector<CommandBufferQueue::Range> CommandBufferQueue::waitForCommands() con
}
void CommandBufferQueue::releaseBuffer(CommandBufferQueue::Range const& buffer) {
size_t const used = std::distance(
static_cast<char const*>(buffer.begin), static_cast<char const*>(buffer.end));
std::lock_guard<utils::Mutex> const lock(mLock);
mFreeSpace += uintptr_t(buffer.end) - uintptr_t(buffer.begin);
mFreeSpace += used;
mCondition.notify_one();
}
+3
View File
@@ -80,6 +80,9 @@ HandleAllocator<P0, P1, P2>::HandleAllocator(const char* name, size_t size,
bool disableUseAfterFreeCheck) noexcept
: mHandleArena(name, size, disableUseAfterFreeCheck),
mUseAfterFreeCheckDisabled(disableUseAfterFreeCheck) {
// Reserve initial space for debug tags. This prevents excessive calls to malloc when the first
// few tags are set.
mDebugTags.reserve(512);
}
template <size_t P0, size_t P1, size_t P2>
+2
View File
@@ -160,6 +160,8 @@ public:
size_t size, bool forceGpuBuffer = false);
~MetalBuffer();
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer || mCpuBuffer; }
MetalBuffer(const MetalBuffer& rhs) = delete;
MetalBuffer& operator=(const MetalBuffer& rhs) = delete;
+2 -2
View File
@@ -53,8 +53,8 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate],
TrackedMetalBuffer::Type::GENERIC };
}
FILAMENT_CHECK_POSTCONDITION(mBuffer)
<< "Could not allocate Metal buffer of size " << size << ".";
// mBuffer might fail to be allocated. Clients can check for this by calling
// wasAllocationSuccessful().
}
MetalBuffer::~MetalBuffer() {
+2 -1
View File
@@ -93,7 +93,7 @@ struct MetalContext {
} highestSupportedGpuFamily;
struct {
bool a8xStaticTextureTargetError;
bool staticTextureTargetError;
} bugs;
// sampleCountLookup[requestedSamples] gives a <= sample count supported by the device.
@@ -112,6 +112,7 @@ struct MetalContext {
std::array<BufferState, MAX_SSBO_COUNT> ssboState;
CullModeStateTracker cullModeState;
WindingStateTracker windingState;
DepthClampStateTracker depthClampState;
Handle<HwRenderPrimitive> currentRenderPrimitive;
// State caches.
+3 -2
View File
@@ -57,7 +57,6 @@ class MetalDriver final : public DriverBase {
public:
static Driver* create(MetalPlatform* platform, const Platform::DriverConfig& driverConfig);
void runAtNextTick(const std::function<void()>& fn) noexcept;
private:
@@ -73,10 +72,12 @@ private:
/*
* Tasks run regularly on the driver thread.
* Not thread-safe; tasks are run from the driver thead and must be enqueued from the driver
* thread.
*/
void runAtNextTick(const std::function<void()>& fn) noexcept;
void executeTickOps() noexcept;
std::vector<std::function<void()>> mTickOps;
std::mutex mTickOpsLock;
/*
* Driver interface
+55 -16
View File
@@ -53,14 +53,14 @@ Driver* MetalDriverFactory::create(MetalPlatform* const platform, const Platform
// MetalRenderPrimitive : 24 many
// MetalVertexBuffer : 32 moderate
// -- less than or equal 32 bytes
// MetalIndexBuffer : 40 moderate
// MetalFence : 48 few
// MetalBufferObject : 48 many
// -- less than or equal 48 bytes
// MetalIndexBuffer : 56 moderate
// MetalBufferObject : 64 many
// -- less than or equal 64 bytes
// MetalSamplerGroup : 112 few
// MetalProgram : 152 moderate
// MetalTexture : 152 moderate
// MetalSwapChain : 184 few
// MetalSwapChain : 208 few
// MetalRenderTarget : 272 few
// MetalVertexBufferInfo : 552 moderate
// -- less than or equal to 552 bytes
@@ -162,8 +162,10 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
sc[s] = [mContext->device supportsTextureSampleCount:s] ? s : sc[s - 1];
}
mContext->bugs.a8xStaticTextureTargetError =
[mContext->device.name containsString:@"Apple A8X GPU"];
mContext->bugs.staticTextureTargetError =
[mContext->device.name containsString:@"Apple A8X GPU"] ||
[mContext->device.name containsString:@"Apple A8 GPU"] ||
[mContext->device.name containsString:@"Apple A7 GPU"];
mContext->commandQueue = mPlatform.createCommandQueue(mContext->device);
mContext->pipelineStateCache.setDevice(mContext->device);
@@ -318,17 +320,40 @@ void MetalDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh,
uint32_t vertexCount, Handle<HwVertexBufferInfo> vbih) {
MetalVertexBufferInfo const* const vbi = handle_cast<const MetalVertexBufferInfo>(vbih);
construct_handle<MetalVertexBuffer>(vbh, *mContext, vertexCount, vbi->bufferCount, vbih);
// No actual GPU memory is allocated here, so no need to check for allocation success.
}
void MetalDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
uint32_t indexCount, BufferUsage usage) {
auto elementSize = (uint8_t) getElementTypeSize(elementType);
construct_handle<MetalIndexBuffer>(ibh, *mContext, usage, elementSize, indexCount);
auto elementSize = (uint8_t)getElementTypeSize(elementType);
auto* indexBuffer =
construct_handle<MetalIndexBuffer>(ibh, *mContext, usage, elementSize, indexCount);
auto& buffer = indexBuffer->buffer;
// If the allocation was not successful, postpone the error message until the next tick, to give
// Filament a chance to call setDebugTag on the handle; this way we get a nicer error message.
if (UTILS_UNLIKELY(!buffer.wasAllocationSuccessful())) {
const size_t byteCount = buffer.getSize();
runAtNextTick([byteCount, this, ibh]() {
FILAMENT_CHECK_POSTCONDITION(false)
<< "Could not allocate Metal index buffer of size " << byteCount
<< ", tag=" << mHandleAllocator.getHandleTag(ibh.getId()).c_str_safe();
});
}
}
void MetalDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage) {
construct_handle<MetalBufferObject>(boh, *mContext, bindingType, usage, byteCount);
auto* bufferObject =
construct_handle<MetalBufferObject>(boh, *mContext, bindingType, usage, byteCount);
// If the allocation was not successful, postpone the error message until the next tick, to give
// Filament a chance to call setDebugTag on the handle; this way we get a nicer error message.
if (UTILS_UNLIKELY(!bufferObject->getBuffer()->wasAllocationSuccessful())) {
runAtNextTick([byteCount, this, boh]() {
FILAMENT_CHECK_POSTCONDITION(false)
<< "Could not allocate Metal buffer of size " << byteCount
<< ", tag=" << mHandleAllocator.getHandleTag(boh.getId()).c_str_safe();
});
}
}
void MetalDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
@@ -832,6 +857,10 @@ bool MetalDriver::isProtectedTexturesSupported() {
return false;
}
bool MetalDriver::isDepthClampSupported() {
return true;
}
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU:
@@ -840,8 +869,8 @@ bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
return true;
case Workaround::ADRENO_UNIFORM_ARRAY_CRASH:
return false;
case Workaround::A8X_STATIC_TEXTURE_TARGET_ERROR:
return mContext->bugs.a8xStaticTextureTargetError;
case Workaround::METAL_STATIC_TEXTURE_TARGET_ERROR:
return mContext->bugs.staticTextureTargetError;
case Workaround::DISABLE_BLIT_INTO_TEXTURE_ARRAY:
return false;
default:
@@ -1273,11 +1302,11 @@ void MetalDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
pushConstants.setPushConstant(value, index);
}
void MetalDriver::insertEventMarker(const char* string, uint32_t len) {
void MetalDriver::insertEventMarker(const char* string) {
}
void MetalDriver::pushGroupMarker(const char* string, uint32_t len) {
void MetalDriver::pushGroupMarker(const char* string) {
mContext->groupMarkers.push(string);
}
@@ -1749,6 +1778,13 @@ void MetalDriver::bindPipeline(PipelineState const& ps) {
[mContext->currentRenderPassEncoder setFrontFacingWinding:winding];
}
// depth clip mode
MTLDepthClipMode depthClipMode = rs.depthClamp ? MTLDepthClipModeClamp : MTLDepthClipModeClip;
mContext->depthClampState.updateState(depthClipMode);
if (mContext->depthClampState.stateChanged()) {
[mContext->currentRenderPassEncoder setDepthClipMode:depthClipMode];
}
// Set the depth-stencil state, if a state change is needed.
DepthStencilState depthState;
if (depthAttachment) {
@@ -1758,6 +1794,8 @@ void MetalDriver::bindPipeline(PipelineState const& ps) {
if (stencilAttachment) {
const auto& ss = ps.stencilState;
// Ben test
auto& front = depthState.front;
front.stencilCompare = getMetalCompareFunction(ss.front.stencilFunc);
front.stencilOperationStencilFail = getMetalStencilOperation(ss.front.stencilOpStencilFail);
@@ -2059,16 +2097,17 @@ void MetalDriver::enumerateBoundBuffers(BufferObjectBinding bindingType,
void MetalDriver::resetState(int) {
}
void MetalDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
mHandleAllocator.associateTagToHandle(handleId, std::move(tag));
}
void MetalDriver::runAtNextTick(const std::function<void()>& fn) noexcept {
std::lock_guard<std::mutex> const lock(mTickOpsLock);
mTickOps.push_back(fn);
}
void MetalDriver::executeTickOps() noexcept {
std::vector<std::function<void()>> ops;
mTickOpsLock.lock();
std::swap(ops, mTickOps);
mTickOpsLock.unlock();
for (const auto& f : ops) {
f();
}
@@ -257,10 +257,6 @@ void MetalSwapChain::present() {
}
}
#ifndef FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD
#define FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD 1
#endif
class PresentDrawableData {
public:
PresentDrawableData() = delete;
@@ -279,14 +275,10 @@ public:
[that->mDrawable present];
}
#if FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD == 1
// mDrawable is acquired on the driver thread. Typically, we would release this object on
// the same thread, but after receiving consistent crash reports from within
// [CAMetalDrawable dealloc], we suspect this object requires releasing on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{ cleanupAndDestroy(that); });
#else
that->mDriver->runAtNextTick([that]() { cleanupAndDestroy(that); });
#endif
}
private:
+1
View File
@@ -382,6 +382,7 @@ using SamplerStateCache = StateCache<SamplerState, id<MTLSamplerState>, SamplerS
using CullModeStateTracker = StateTracker<MTLCullMode>;
using WindingStateTracker = StateTracker<MTLWinding>;
using DepthClampStateTracker = StateTracker<MTLDepthClipMode>;
// Argument encoder
+9 -2
View File
@@ -202,6 +202,10 @@ bool NoopDriver::isProtectedTexturesSupported() {
return true;
}
bool NoopDriver::isDepthClampSupported() {
return false;
}
bool NoopDriver::isWorkaroundNeeded(Workaround) {
return false;
}
@@ -316,10 +320,10 @@ void NoopDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
backend::PushConstantVariant value) {
}
void NoopDriver::insertEventMarker(char const* string, uint32_t len) {
void NoopDriver::insertEventMarker(char const* string) {
}
void NoopDriver::pushGroupMarker(char const* string, uint32_t len) {
void NoopDriver::pushGroupMarker(char const* string) {
}
void NoopDriver::popGroupMarker(int) {
@@ -388,4 +392,7 @@ void NoopDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
void NoopDriver::resetState(int) {
}
void NoopDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
}
} // namespace filament
@@ -676,6 +676,7 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
#ifndef __EMSCRIPTEN__
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
#endif
ext->EXT_depth_clamp = exts.has("GL_EXT_depth_clamp"sv);
ext->EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
#ifndef __EMSCRIPTEN__
ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
@@ -746,6 +747,7 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor)
ext->EXT_color_buffer_half_float = true; // Assumes core profile.
ext->EXT_clip_cull_distance = true;
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
ext->EXT_depth_clamp = true;
ext->EXT_discard_framebuffer = false;
ext->EXT_disjoint_timer_query = true;
ext->EXT_multisampled_render_to_texture = false;
+6 -4
View File
@@ -220,8 +220,9 @@ public:
bool EXT_color_buffer_float;
bool EXT_color_buffer_half_float;
bool EXT_debug_marker;
bool EXT_disjoint_timer_query;
bool EXT_depth_clamp;
bool EXT_discard_framebuffer;
bool EXT_disjoint_timer_query;
bool EXT_multisampled_render_to_texture2;
bool EXT_multisampled_render_to_texture;
bool EXT_protected_textures;
@@ -239,10 +240,10 @@ public:
bool KHR_parallel_shader_compile;
bool KHR_texture_compression_astc_hdr;
bool KHR_texture_compression_astc_ldr;
bool OES_depth_texture;
bool OES_depth24;
bool OES_packed_depth_stencil;
bool OES_EGL_image_external_essl3;
bool OES_depth24;
bool OES_depth_texture;
bool OES_packed_depth_stencil;
bool OES_rgb8_rgba8;
bool OES_standard_derivatives;
bool OES_texture_npot;
@@ -627,6 +628,7 @@ constexpr size_t OpenGLContext::getIndexForCap(GLenum cap) noexcept { //NOLINT
#ifdef BACKEND_OPENGL_VERSION_GL
case GL_PROGRAM_POINT_SIZE: index = 10; break;
#endif
case GL_DEPTH_CLAMP: index = 11; break;
default: break;
}
assert_invariant(index < state.enables.caps.size());
+78 -15
View File
@@ -83,12 +83,20 @@
#define HAS_MAPBUFFERS 1
#endif
#define DEBUG_MARKER_NONE 0x00 // no debug marker
#define DEBUG_MARKER_OPENGL 0x01 // markers in the gl command queue (req. driver support)
#define DEBUG_MARKER_BACKEND 0x02 // markers on the backend side (systrace)
#define DEBUG_MARKER_ALL 0x03 // all markers
#define DEBUG_GROUP_MARKER_NONE 0x00 // no debug marker
#define DEBUG_GROUP_MARKER_OPENGL 0x01 // markers in the gl command queue (req. driver support)
#define DEBUG_GROUP_MARKER_BACKEND 0x02 // markers on the backend side (systrace)
#define DEBUG_GROUP_MARKER_ALL 0x03 // all markers
// set to the desired debug marker level
#define DEBUG_MARKER_NONE 0x00 // no debug marker
#define DEBUG_MARKER_OPENGL 0x01 // markers in the gl command queue (req. driver support)
#define DEBUG_MARKER_BACKEND 0x02 // markers on the backend side (systrace)
#define DEBUG_MARKER_ALL 0x03 // all markers
// set to the desired debug marker level (for user markers [default: All])
#define DEBUG_GROUP_MARKER_LEVEL DEBUG_GROUP_MARKER_ALL
// set to the desired debug level (for internal debugging [Default: None])
#define DEBUG_MARKER_LEVEL DEBUG_MARKER_NONE
#if DEBUG_MARKER_LEVEL > DEBUG_MARKER_NONE
@@ -196,11 +204,37 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
OpenGLDriver::DebugMarker::DebugMarker(OpenGLDriver& driver, const char* string) noexcept
: driver(driver) {
driver.pushGroupMarker(string, strlen(string));
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_debug_marker
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
if (UTILS_LIKELY(driver.getContext().ext.EXT_debug_marker)) {
glPushGroupMarkerEXT(GLsizei(strlen(string)), string);
}
#endif
#endif
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_BACKEND
SYSTRACE_CONTEXT();
SYSTRACE_NAME_BEGIN(string);
#endif
#endif
}
OpenGLDriver::DebugMarker::~DebugMarker() noexcept {
driver.popGroupMarker();
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_debug_marker
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
if (UTILS_LIKELY(driver.getContext().ext.EXT_debug_marker)) {
glPopGroupMarkerEXT();
}
#endif
#endif
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_BACKEND
SYSTRACE_CONTEXT();
SYSTRACE_NAME_END();
#endif
#endif
}
// ------------------------------------------------------------------------------------------------
@@ -417,6 +451,14 @@ void OpenGLDriver::setRasterState(RasterState rs) noexcept {
} else {
gl.disable(GL_SAMPLE_ALPHA_TO_COVERAGE);
}
if (gl.ext.EXT_depth_clamp) {
if (rs.depthClamp) {
gl.enable(GL_DEPTH_CLAMP);
} else {
gl.disable(GL_DEPTH_CLAMP);
}
}
}
void OpenGLDriver::setStencilState(StencilState ss) noexcept {
@@ -2085,6 +2127,10 @@ bool OpenGLDriver::isProtectedTexturesSupported() {
return getContext().ext.EXT_protected_textures;
}
bool OpenGLDriver::isDepthClampSupported() {
return getContext().ext.EXT_depth_clamp;
}
bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU:
@@ -2176,6 +2222,10 @@ void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain>
// Updating driver objects
// ------------------------------------------------------------------------------------------------
void OpenGLDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
mHandleAllocator.associateTagToHandle(handleId, std::move(tag));
}
void OpenGLDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh,
uint32_t index, Handle<HwBufferObject> boh) {
DEBUG_MARKER()
@@ -2341,6 +2391,15 @@ void OpenGLDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
GLTexture const* const t = handle_cast<const GLTexture*>(th);
assert_invariant(t);
if (UTILS_UNLIKELY(es2)
#if defined(GL_EXT_texture_filter_anisotropic)
|| UTILS_UNLIKELY(anisotropyWorkaround)
#endif
) {
// We must set texture parameters on the texture itself.
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
}
SamplerParams params = pSamplers[i].s;
if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) {
// From OES_EGL_image_external spec:
@@ -3137,28 +3196,28 @@ void OpenGLDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::insertEventMarker(char const* string, uint32_t len) {
void OpenGLDriver::insertEventMarker(char const* string) {
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_debug_marker
auto& gl = mContext;
if (gl.ext.EXT_debug_marker) {
glInsertEventMarkerEXT(GLsizei(len ? len : strlen(string)), string);
glInsertEventMarkerEXT(GLsizei(strlen(string)), string);
}
#endif
#endif
}
void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) {
void OpenGLDriver::pushGroupMarker(char const* string) {
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_debug_marker
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
#if DEBUG_GROUP_MARKER_LEVEL & DEBUG_GROUP_MARKER_OPENGL
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
glPushGroupMarkerEXT(GLsizei(len ? len : strlen(string)), string);
glPushGroupMarkerEXT(GLsizei(strlen(string)), string);
}
#endif
#endif
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_BACKEND
#if DEBUG_GROUP_MARKER_LEVEL & DEBUG_GROUP_MARKER_BACKEND
SYSTRACE_CONTEXT();
SYSTRACE_NAME_BEGIN(string);
#endif
@@ -3168,14 +3227,14 @@ void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) {
void OpenGLDriver::popGroupMarker(int) {
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_debug_marker
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
#if DEBUG_GROUP_MARKER_LEVEL & DEBUG_GROUP_MARKER_OPENGL
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
glPopGroupMarkerEXT();
}
#endif
#endif
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_BACKEND
#if DEBUG_GROUP_MARKER_LEVEL & DEBUG_GROUP_MARKER_BACKEND
SYSTRACE_CONTEXT();
SYSTRACE_NAME_END();
#endif
@@ -3881,6 +3940,7 @@ void OpenGLDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
}
void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
DEBUG_MARKER()
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
if (UTILS_UNLIKELY(!rp || !mValidProgram)) {
return;
@@ -3902,6 +3962,7 @@ void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
}
void OpenGLDriver::draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
DEBUG_MARKER()
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
if (UTILS_UNLIKELY(!rp || !mValidProgram)) {
return;
@@ -3922,6 +3983,7 @@ void OpenGLDriver::draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_
}
void OpenGLDriver::scissor(Viewport scissor) {
DEBUG_MARKER()
setScissor(scissor);
}
@@ -3941,6 +4003,7 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph,
}
void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
DEBUG_MARKER()
getShaderCompilerService().tick();
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(program);
+1 -1
View File
@@ -81,7 +81,7 @@ public:
const Platform::DriverConfig& driverConfig) noexcept;
class DebugMarker {
OpenGLDriver& driver;
UTILS_UNUSED OpenGLDriver& driver;
public:
DebugMarker(OpenGLDriver& driver, const char* string) noexcept;
~DebugMarker() noexcept;
@@ -26,16 +26,28 @@
#include <utils/compiler.h>
#include <utils/CString.h>
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <utils/JobSystem.h>
#include <utils/Log.h>
#include <utils/ostream.h>
#include <utils/Panic.h>
#include <utils/Systrace.h>
#include <array>
#include <cctype>
#include <chrono>
#include <mutex>
#include <memory>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <variant>
#include <stddef.h>
#include <stdint.h>
namespace filament::backend {
using namespace utils;
@@ -476,6 +488,12 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
// check status of program linking and shader compilation, logs error and free all resources
// in case of error.
bool const success = checkProgramStatus(token);
// Unless we have matdbg, we panic if a program is invalid. Otherwise, we'd get a UB.
// The compilation error has been logged to log.e by this point.
FILAMENT_CHECK_POSTCONDITION(FILAMENT_ENABLE_MATDBG || success)
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
if (UTILS_LIKELY(success)) {
program = token->gl.program;
// no need to keep the shaders around
@@ -579,24 +597,31 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
version = "#version 310 es\n";
}
const std::array<const char*, 5> sources = {
version.data(),
prolog.data(),
specializationConstantString.c_str(),
packingFunctions.data(),
body.data()
std::array<std::string_view, 5> sources = {
version,
prolog,
specializationConstantString,
packingFunctions,
{ body.data(), body.size() - 1 } // null-terminated
};
const std::array<GLint, 5> lengths = {
(GLint)version.length(),
(GLint)prolog.length(),
(GLint)specializationConstantString.length(),
(GLint)packingFunctions.length(),
(GLint)body.length() - 1 // null terminated
};
// Some of the sources may be zero-length. Remove them as to avoid passing lengths of
// zero to glShaderSource(). glShaderSource should work with lengths of zero, but some
// drivers instead interpret zero as a sentinel for a null-terminated string.
auto partitionPoint = std::stable_partition(
sources.begin(), sources.end(), [](std::string_view s) { return !s.empty(); });
size_t count = std::distance(sources.begin(), partitionPoint);
std::array<const char*, 5> shaderStrings;
std::array<GLint, 5> lengths;
for (size_t i = 0; i < count; i++) {
shaderStrings[i] = sources[i].data();
lengths[i] = sources[i].size();
}
GLuint const shaderId = glCreateShader(glShaderType);
glShaderSource(shaderId, sources.size(), sources.data(), lengths.data());
glShaderSource(shaderId, count, shaderStrings.data(), lengths.data());
glCompileShader(shaderId);
#ifndef NDEBUG
+6
View File
@@ -201,6 +201,12 @@ using namespace glext;
# define GL_CLIP_DISTANCE1 0x3001
#endif
#if defined(GL_EXT_depth_clamp)
# define GL_DEPTH_CLAMP GL_DEPTH_CLAMP_EXT
#else
# define GL_DEPTH_CLAMP 0x864F
#endif
#if defined(GL_KHR_debug)
# define GL_DEBUG_OUTPUT GL_DEBUG_OUTPUT_KHR
# define GL_DEBUG_OUTPUT_SYNCHRONOUS GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR
@@ -59,7 +59,11 @@ VkExtent2D VulkanAttachment::getExtent2D() const {
VkImageView VulkanAttachment::getImageView() {
assert_invariant(texture);
return texture->getAttachmentView(getSubresourceRange());
VkImageSubresourceRange range = getSubresourceRange();
if (range.layerCount > 1) {
return texture->getMultiviewAttachmentView(range);
}
return texture->getAttachmentView(range);
}
bool VulkanAttachment::isDepth() const {
@@ -73,7 +77,7 @@ VkImageSubresourceRange VulkanAttachment::getSubresourceRange() const {
.baseMipLevel = uint32_t(level),
.levelCount = 1,
.baseArrayLayer = uint32_t(layer),
.layerCount = 1,
.layerCount = layerCount,
};
}
@@ -43,6 +43,8 @@ struct VulkanCommandBuffer;
struct VulkanAttachment {
VulkanTexture* texture = nullptr;
uint8_t level = 0;
uint8_t baseViewIndex = 0;
uint8_t layerCount = 1;
uint16_t layer = 0;
bool isDepth() const;
@@ -123,6 +125,10 @@ public:
return mPhysicalDeviceFeatures.imageCubeArray == VK_TRUE;
}
inline bool isDepthClampSupported() const noexcept {
return mPhysicalDeviceFeatures.depthClamp == VK_TRUE;
}
inline bool isDebugMarkersSupported() const noexcept {
return mDebugMarkersSupported;
}
+23 -4
View File
@@ -595,6 +595,8 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
colorTargets[i] = {
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(color[i].handle),
.level = color[i].level,
.baseViewIndex = color[i].baseViewIndex,
.layerCount = layerCount,
.layer = color[i].layer,
};
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = colorTargets[i].getExtent2D();
@@ -609,6 +611,8 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
depthStencil[0] = {
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(depth.handle),
.level = depth.level,
.baseViewIndex = depth.baseViewIndex,
.layerCount = layerCount,
.layer = depth.layer,
};
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = depthStencil[0].getExtent2D();
@@ -621,6 +625,8 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
depthStencil[1] = {
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(stencil.handle),
.level = stencil.level,
.baseViewIndex = stencil.baseViewIndex,
.layerCount = layerCount,
.layer = stencil.layer,
};
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = depthStencil[1].getExtent2D();
@@ -637,7 +643,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
auto renderTarget = mResourceAllocator.construct<VulkanRenderTarget>(rth, mPlatform->getDevice(),
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, width, height,
samples, colorTargets, depthStencil, mStagePool);
samples, colorTargets, depthStencil, mStagePool, layerCount);
mResourceManager.acquire(renderTarget);
}
@@ -938,6 +944,10 @@ bool VulkanDriver::isProtectedTexturesSupported() {
return false;
}
bool VulkanDriver::isDepthClampSupported() {
return mContext.isDepthClampSupported();
}
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU: {
@@ -1267,6 +1277,8 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
}
}
uint8_t const renderTargetLayerCount = rt->getLayerCount();
// Create the VkRenderPass or fetch it from cache.
VulkanFboCache::RenderPassKey rpkey = {
.initialColorLayoutMask = 0,
@@ -1277,10 +1289,12 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
.discardEnd = discardEndVal,
.samples = rt->getSamples(),
.subpassMask = uint8_t(params.subpassMask),
.viewCount = renderTargetLayerCount,
};
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
const VulkanAttachment& info = rt->getColor(i);
if (info.texture) {
assert_invariant(info.layerCount == renderTargetLayerCount);
rpkey.initialColorLayoutMask |= 1 << i;
rpkey.colorFormat[i] = info.getFormat();
if (rpkey.samples > 1 && info.texture->samples == 1) {
@@ -1592,13 +1606,13 @@ void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
value);
}
void VulkanDriver::insertEventMarker(char const* string, uint32_t len) {
void VulkanDriver::insertEventMarker(char const* string) {
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
mCommands.insertEventMarker(string, len);
mCommands.insertEventMarker(string, strlen(string));
#endif
}
void VulkanDriver::pushGroupMarker(char const* string, uint32_t) {
void VulkanDriver::pushGroupMarker(char const* string) {
// Turns out all the markers are 0-terminated, so we can just pass it without len.
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
mCommands.pushGroupMarker(string);
@@ -1806,6 +1820,7 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
.dstAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionDstAlpha),
.colorWriteMask = (VkColorComponentFlags) (rasterState.colorWrite ? 0xf : 0x0),
.rasterizationSamples = rt->getSamples(),
.depthClamp = rasterState.depthClamp,
.colorTargetCount = rt->getColorTargetCount(mCurrentRenderPass),
.colorBlendOp = rasterState.blendEquationRGB,
.alphaBlendOp = rasterState.blendEquationAlpha,
@@ -2022,6 +2037,10 @@ void VulkanDriver::debugCommandBegin(CommandStream* cmds, bool synchronous, cons
void VulkanDriver::resetState(int) {
}
void VulkanDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
mResourceAllocator.associateHandle(handleId, std::move(tag));
}
// explicit instantiation of the Dispatcher
template class ConcreteDispatcher<VulkanDriver>;
@@ -43,6 +43,7 @@ bool VulkanFboCache::RenderPassEq::operator()(const RenderPassKey& k1,
if (k1.samples != k2.samples) return false;
if (k1.needsResolveMask != k2.needsResolveMask) return false;
if (k1.subpassMask != k2.subpassMask) return false;
if (k1.viewCount != k2.viewCount) return false;
return true;
}
@@ -186,6 +187,25 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
.pDependencies = dependencies
};
VkRenderPassMultiviewCreateInfo multiviewCreateInfo = {};
uint32_t const subpassViewMask = (1 << config.viewCount) - 1;
// Prepare a view mask array for the maximum number of subpasses. All subpasses have all views
// activated.
uint32_t const viewMasks[2] = {subpassViewMask, subpassViewMask};
if (config.viewCount > 1) {
// Fill the multiview create info.
multiviewCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO;
multiviewCreateInfo.pNext = nullptr;
multiviewCreateInfo.subpassCount = hasSubpasses ? 2u : 1u;
multiviewCreateInfo.pViewMasks = viewMasks;
multiviewCreateInfo.dependencyCount = 0;
multiviewCreateInfo.pViewOffsets = nullptr;
multiviewCreateInfo.correlationMaskCount = 1;
multiviewCreateInfo.pCorrelationMasks = &subpassViewMask;
renderPassInfo.pNext = &multiviewCreateInfo;
}
int attachmentIndex = 0;
// Populate the Color Attachments.
+1 -1
View File
@@ -64,7 +64,7 @@ public:
uint8_t samples; // 1 byte
uint8_t needsResolveMask; // 1 byte
uint8_t subpassMask; // 1 byte
uint8_t padding2; // 1 byte
uint8_t viewCount; // 1 byte
};
struct RenderPassVal {
VkRenderPass handle;
@@ -16,7 +16,6 @@
#include "VulkanHandles.h"
#include "VulkanDriver.h"
#include "VulkanConstants.h"
#include "VulkanDriver.h"
#include "VulkanMemory.h"
@@ -323,11 +322,12 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
uint32_t width, uint32_t height, uint8_t samples,
VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool)
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount)
: HwRenderTarget(width, height),
VulkanResource(VulkanResourceType::RENDER_TARGET),
mOffscreen(true),
mSamples(samples) {
mSamples(samples),
mLayerCount(layerCount) {
for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) {
mColor[index] = color[index];
}
+3 -1
View File
@@ -304,7 +304,7 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
VulkanContext const& context, VmaAllocator allocator,
VulkanCommands* commands, uint32_t width, uint32_t height,
uint8_t samples, VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool);
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount);
// Creates a special "default" render target (i.e. associated with the swap chain)
explicit VulkanRenderTarget();
@@ -319,6 +319,7 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
VulkanAttachment& getMsaaDepth();
uint8_t getColorTargetCount(const VulkanRenderPass& pass) const;
uint8_t getSamples() const { return mSamples; }
uint8_t getLayerCount() const { return mLayerCount; }
bool hasDepth() const { return mDepth.texture; }
bool isSwapChain() const { return !mOffscreen; }
void bindToSwapChain(VulkanSwapChain& surf);
@@ -330,6 +331,7 @@ private:
VulkanAttachment mMsaaDepthAttachment = {};
const bool mOffscreen : 1;
uint8_t mSamples : 7;
uint8_t mLayerCount = 1;
};
struct VulkanBufferObject;
@@ -184,6 +184,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
vkRaster.polygonMode = VK_POLYGON_MODE_FILL;
vkRaster.cullMode = raster.cullMode;
vkRaster.frontFace = raster.frontFace;
vkRaster.depthClampEnable = raster.depthClamp;
vkRaster.depthBiasEnable = raster.depthBiasEnable;
vkRaster.depthBiasConstantFactor = raster.depthBiasConstantFactor;
vkRaster.depthBiasClamp = 0.0f;
@@ -90,7 +90,8 @@ public:
VkBlendFactor srcAlphaBlendFactor : 5;
VkBlendFactor dstAlphaBlendFactor : 5;
VkColorComponentFlags colorWriteMask : 4;
uint8_t rasterizationSamples; // offset = 4 bytes
uint8_t rasterizationSamples : 4;// offset = 4 bytes
uint8_t depthClamp : 4;
uint8_t colorTargetCount; // offset = 5 bytes
BlendEquation colorBlendOp : 4; // offset = 6 bytes
BlendEquation alphaBlendOp : 4;
@@ -105,6 +105,10 @@ public:
mHandleAllocatorImpl.deallocate(handle, obj);
}
inline void associateHandle(HandleBase::HandleId id, utils::CString&& tag) noexcept {
mHandleAllocatorImpl.associateTagToHandle(id, std::move(tag));
}
private:
AllocatorImpl mHandleAllocatorImpl;
+11 -11
View File
@@ -97,13 +97,11 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.extent.depth = 1;
}
// Filament expects blit() to work with any texture, so we almost always set these usage flags.
// TODO: investigate performance implications of setting these flags.
constexpr VkImageUsageFlags blittable = VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
if (any(usage & (TextureUsage::BLIT_DST | TextureUsage::BLIT_SRC))) {
imageInfo.usage |= blittable;
if (any(usage & TextureUsage::BLIT_SRC)) {
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
if (any(usage & TextureUsage::BLIT_DST)) {
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
if (any(usage & TextureUsage::SAMPLEABLE)) {
@@ -121,7 +119,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | blittable;
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
if (any(usage & TextureUsage::SUBPASS_INPUT)) {
imageInfo.usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
}
@@ -130,10 +128,9 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
}
if (any(usage & TextureUsage::UPLOADABLE)) {
imageInfo.usage |= blittable;
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
imageInfo.usage |= blittable;
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
// Depth resolves uses a custom shader and therefore needs to be sampleable.
@@ -387,12 +384,15 @@ void VulkanTexture::setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel)
}
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
// Attachments should only have one mipmap level and one layer.
range.levelCount = 1;
range.layerCount = 1;
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D, {});
}
VkImageView VulkanTexture::getMultiviewAttachmentView(VkImageSubresourceRange range) {
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D_ARRAY, {});
}
VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range, VkImageViewType type) {
return getImageView(range, type, mSwizzle);
}
@@ -73,6 +73,10 @@ struct VulkanTexture : public HwTexture, VulkanResource {
// and the identity swizzle.
VkImageView getAttachmentView(VkImageSubresourceRange range);
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
// target attachment when rendering with multiview.
VkImageView getMultiviewAttachmentView(VkImageSubresourceRange range);
// This is a workaround for the first few frames where we're waiting for the texture to actually
// be uploaded. In that case, we bind the sampler to an empty texture, but the corresponding
// imageView needs to be of the right type. Hence, we provide an option to indicate the
@@ -172,7 +172,7 @@ public:
UTILS_UNUSED VkResult result = vkAllocateDescriptorSets(mDevice, &allocInfo, &vkSet);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "Failed to allocate descriptor set code=" << result << " size=" << mSize
<< " capacity=" << mCapacity << " count=" << "%s";
<< " capacity=" << mCapacity << " count=" << mUnusedCount;
mSize++;
return createSet(layout->bitmask, vkSet);
}
@@ -325,6 +325,7 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
// We could simply enable all supported features, but since that may have performance
// consequences let's just enable the features we need.
VkPhysicalDeviceFeatures enabledFeatures{
.depthClamp = features.depthClamp,
.samplerAnisotropy = features.samplerAnisotropy,
.textureCompressionETC2 = features.textureCompressionETC2,
.textureCompressionBC = features.textureCompressionBC,
+16 -1
View File
@@ -54,7 +54,7 @@ public:
using BufferDescriptor = backend::BufferDescriptor;
using BindingType = backend::BufferObjectBinding;
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
@@ -78,6 +78,21 @@ public:
*/
Builder& bindingType(BindingType bindingType) noexcept;
/**
* Associate an optional name with this BufferObject for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this BufferObject
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the BufferObject and returns a pointer to it. After creation, the buffer
* object is uninitialized. Use BufferObject::setBuffer() to initialize it.
+28 -2
View File
@@ -350,9 +350,12 @@ public:
uint32_t resourceAllocatorCacheSizeMB = 64;
/*
* This value determines for how many frames are texture entries kept in the cache.
* This value determines how many frames texture entries are kept for in the cache. This
* is a soft limit, meaning some texture older than this are allowed to stay in the cache.
* Typically only one texture is evicted per frame.
* The default is 1.
*/
uint32_t resourceAllocatorCacheMaxAge = 2;
uint32_t resourceAllocatorCacheMaxAge = 1;
/*
* Disable backend handles use-after-free checks.
@@ -893,6 +896,29 @@ public:
/** Tells whether an InstanceBuffer object is valid */
bool isValid(const InstanceBuffer* UTILS_NULLABLE p) const;
/**
* Retrieve the count of each resource tracked by Engine.
* This is intended for debugging.
* @{
*/
size_t getBufferObjectCount() const noexcept;
size_t getViewCount() const noexcept;
size_t getSceneCount() const noexcept;
size_t getSwapChainCount() const noexcept;
size_t getStreamCount() const noexcept;
size_t getIndexBufferCount() const noexcept;
size_t getSkinningBufferCount() const noexcept;
size_t getMorphTargetBufferCount() const noexcept;
size_t getInstanceBufferCount() const noexcept;
size_t getVertexBufferCount() const noexcept;
size_t getIndirectLightCount() const noexcept;
size_t getMaterialCount() const noexcept;
size_t getTextureCount() const noexcept;
size_t getSkyboxeCount() const noexcept;
size_t getColorGradingCount() const noexcept;
size_t getRenderTargetCount() const noexcept;
/** @} */
/**
* Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) and blocks until
* all commands to this point are executed. Note that does guarantee that the
+18
View File
@@ -19,6 +19,7 @@
#include <utils/compiler.h>
#include <utils/PrivateImplementation.h>
#include <utils/CString.h>
#include <stddef.h>
@@ -54,6 +55,23 @@ public:
template<typename T>
using BuilderBase = utils::PrivateImplementation<T>;
// This needs to be public because it is used in the following template.
UTILS_PUBLIC void builderMakeName(utils::CString& outName, const char* name, size_t len) noexcept;
template <typename Builder>
class UTILS_PUBLIC BuilderNameMixin {
public:
Builder& name(const char* name, size_t len) noexcept {
builderMakeName(mName, name, len);
return static_cast<Builder&>(*this);
}
utils::CString const& getName() const noexcept { return mName; }
private:
utils::CString mName;
};
} // namespace filament
#endif // TNT_FILAMENT_FILAMENTAPI_H
+16 -1
View File
@@ -59,7 +59,7 @@ public:
UINT = uint8_t(backend::ElementType::UINT), //!< 32-bit indices
};
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
@@ -83,6 +83,21 @@ public:
*/
Builder& bufferType(IndexType indexType) noexcept;
/**
* Associate an optional name with this IndexBuffer for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this IndexBuffer
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the IndexBuffer object and returns a pointer to it. After creation, the index
* buffer is uninitialized. Use IndexBuffer::setBuffer() to initialize the IndexBuffer.
+16 -1
View File
@@ -38,7 +38,7 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI {
struct BuilderDetails;
public:
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
@@ -70,6 +70,21 @@ public:
*/
Builder& localTransforms(math::mat4f const* UTILS_NULLABLE localTransforms) noexcept;
/**
* Associate an optional name with this InstanceBuffer for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this InstanceBuffer
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the InstanceBuffer object and returns a pointer to it.
*/
+16 -1
View File
@@ -39,7 +39,7 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI {
struct BuilderDetails;
public:
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
@@ -63,6 +63,21 @@ public:
*/
Builder& count(size_t count) noexcept;
/**
* Associate an optional name with this MorphTargetBuffer for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this MorphTargetBuffer
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the MorphTargetBuffer object and returns a pointer to it.
*
+8
View File
@@ -271,6 +271,14 @@ public:
*/
void setVsyncTime(uint64_t steadyClockTimeNano) noexcept;
/**
* Call skipFrame when momentarily skipping frames, for instance if the content of the
* scene doesn't change.
*
* @param vsyncSteadyClockTimeNano
*/
void skipFrame(uint64_t vsyncSteadyClockTimeNano = 0u);
/**
* Set-up a frame for this Renderer.
*
+16 -1
View File
@@ -39,7 +39,7 @@ class UTILS_PUBLIC SkinningBuffer : public FilamentAPI {
struct BuilderDetails;
public:
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
@@ -69,6 +69,21 @@ public:
*/
Builder& initialize(bool initialize = true) noexcept;
/**
* Associate an optional name with this SkinningBuffer for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this SkinningBuffer
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the SkinningBuffer object and returns a pointer to it.
*
+16 -1
View File
@@ -94,7 +94,7 @@ public:
*
* To create a NATIVE stream, call the <pre>stream</pre> method on the builder.
*/
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
@@ -136,6 +136,21 @@ public:
*/
Builder& height(uint32_t height) noexcept;
/**
* Associate an optional name with this Stream for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this Stream
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the Stream object and returns a pointer to it.
*
+16 -1
View File
@@ -112,7 +112,7 @@ public:
//! Use Builder to construct a Texture object instance
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
@@ -202,6 +202,21 @@ public:
*/
Builder& swizzle(Swizzle r, Swizzle g, Swizzle b, Swizzle a) noexcept;
/**
* Associate an optional name with this Texture for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this Texture
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the Texture object and returns a pointer to it.
*
+16 -1
View File
@@ -61,7 +61,7 @@ public:
using AttributeType = backend::ElementType;
using BufferDescriptor = backend::BufferDescriptor;
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
@@ -158,6 +158,21 @@ public:
*/
Builder& advancedSkinning(bool enabled) noexcept;
/**
* Associate an optional name with this VertexBuffer for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this VertexBuffer
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited
/**
* Creates the VertexBuffer object and returns a pointer to it.
*
+14 -1
View File
@@ -24,6 +24,7 @@
#include <utils/compiler.h>
#include <utils/Entity.h>
#include <utils/FixedCapacityVector.h>
#include <math/mathfwd.h>
@@ -40,6 +41,7 @@ class CallbackHandler;
class Camera;
class ColorGrading;
class Engine;
class MaterialInstance;
class RenderTarget;
class Scene;
@@ -726,7 +728,7 @@ public:
void setDebugCamera(Camera* UTILS_NULLABLE camera) noexcept;
//! debugging: returns a Camera from the point of view of *the* dominant directional light used for shadowing.
Camera const* UTILS_NULLABLE getDirectionalShadowCamera() const noexcept;
utils::FixedCapacityVector<Camera const*> getDirectionalShadowCameras() const noexcept;
/** Result of a picking query */
@@ -878,6 +880,17 @@ public:
*/
utils::Entity getFogEntity() const noexcept;
/**
* When certain temporal features are used (e.g.: TAA or Screen-space reflections), the view
* keeps a history of previous frame renders associated with the Renderer the view was last
* used with. When switching Renderer, it may be necessary to clear that history by calling
* this method. Similarly, if the whole content of the screen change, like when a cut-scene
* starts, clearing the history might be needed to avoid artifacts due to the previous frame
* being very different.
*/
void clearFrameHistory(Engine& engine) noexcept;
/**
* List of available ambient occlusion techniques
* @deprecated use AmbientOcclusionOptions::enabled instead
+67
View File
@@ -16,6 +16,8 @@
#include "details/Engine.h"
#include "ResourceAllocator.h"
#include "details/BufferObject.h"
#include "details/Camera.h"
#include "details/Fence.h"
@@ -266,6 +268,71 @@ bool Engine::isValid(const InstanceBuffer* p) const {
return downcast(this)->isValid(downcast(p));
}
size_t Engine::getBufferObjectCount() const noexcept {
return downcast(this)->getBufferObjectCount();
}
size_t Engine::getViewCount() const noexcept {
return downcast(this)->getViewCount();
}
size_t Engine::getSceneCount() const noexcept {
return downcast(this)->getSceneCount();
}
size_t Engine::getSwapChainCount() const noexcept {
return downcast(this)->getSwapChainCount();
}
size_t Engine::getStreamCount() const noexcept {
return downcast(this)->getStreamCount();
}
size_t Engine::getIndexBufferCount() const noexcept {
return downcast(this)->getIndexBufferCount();
}
size_t Engine::getSkinningBufferCount() const noexcept {
return downcast(this)->getSkinningBufferCount();
}
size_t Engine::getMorphTargetBufferCount() const noexcept {
return downcast(this)->getMorphTargetBufferCount();
}
size_t Engine::getInstanceBufferCount() const noexcept {
return downcast(this)->getInstanceBufferCount();
}
size_t Engine::getVertexBufferCount() const noexcept {
return downcast(this)->getVertexBufferCount();
}
size_t Engine::getIndirectLightCount() const noexcept {
return downcast(this)->getIndirectLightCount();
}
size_t Engine::getMaterialCount() const noexcept {
return downcast(this)->getMaterialCount();
}
size_t Engine::getTextureCount() const noexcept {
return downcast(this)->getTextureCount();
}
size_t Engine::getSkyboxeCount() const noexcept {
return downcast(this)->getSkyboxeCount();
}
size_t Engine::getColorGradingCount() const noexcept {
return downcast(this)->getColorGradingCount();
}
size_t Engine::getRenderTargetCount() const noexcept {
return downcast(this)->getRenderTargetCount();
}
void Engine::flushAndWait() {
downcast(this)->flushAndWait();
}
+31
View File
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <filament/FilamentAPI.h>
#include <algorithm>
namespace filament {
void builderMakeName(utils::CString& outName, const char* name, size_t len) noexcept {
if (!name) {
return;
}
size_t const length = std::min(len, size_t { 128u });
outName = utils::CString(name, length);
}
} // namespace filament
+3
View File
@@ -21,6 +21,9 @@
#include <fg/FrameGraphTexture.h>
#include <math/mat4.h>
#include <math/vec2.h>
#include <stdint.h>
namespace filament {
+26 -19
View File
@@ -25,6 +25,7 @@
#include <utils/FixedCapacityVector.h>
#include <utils/Log.h>
#include <utils/ostream.h>
#include <utils/Systrace.h>
#include <algorithm>
#include <atomic>
@@ -78,25 +79,33 @@ void FrameInfoManager::beginFrame(DriverApi& driver, Config const& config, uint3
});
// now is a good time to check the oldest active query
uint64_t elapsed = 0;
TimerQueryResult const result = driver.getTimerQueryValue(mQueries[mLast].handle, &elapsed);
switch (result) {
case TimerQueryResult::NOT_READY:
// nothing to do
break;
case TimerQueryResult::ERROR:
mLast = (mLast + 1) % POOL_COUNT;
break;
case TimerQueryResult::AVAILABLE:
// conversion to our duration happens here
pFront = mQueries[mLast].pInfo;
pFront->frameTime = std::chrono::duration<uint64_t, std::nano>(elapsed);
mLast = (mLast + 1) % POOL_COUNT;
denoiseFrameTime(config);
while (mLast != mIndex) {
uint64_t elapsed = 0;
TimerQueryResult const result = driver.getTimerQueryValue(mQueries[mLast].handle, &elapsed);
switch (result) {
case TimerQueryResult::NOT_READY:
// nothing to do
break;
case TimerQueryResult::ERROR:
mLast = (mLast + 1) % POOL_COUNT;
break;
case TimerQueryResult::AVAILABLE: {
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("FrameInfo::elapsed", uint32_t(elapsed));
// conversion to our duration happens here
pFront = mQueries[mLast].pInfo;
pFront->frameTime = std::chrono::duration<uint64_t, std::nano>(elapsed);
mLast = (mLast + 1) % POOL_COUNT;
denoiseFrameTime(history, config);
break;
}
}
if (result != TimerQueryResult::AVAILABLE) {
break;
}
// read the pending timer queries until we find one that's not ready
}
// keep this just for debugging
if constexpr (false) {
using namespace utils;
@@ -129,15 +138,13 @@ void FrameInfoManager::endFrame(DriverApi& driver) noexcept {
mIndex = (mIndex + 1) % POOL_COUNT;
}
void FrameInfoManager::denoiseFrameTime(Config const& config) noexcept {
auto& history = mFrameTimeHistory;
void FrameInfoManager::denoiseFrameTime(FrameHistoryQueue& history, Config const& config) noexcept {
assert_invariant(!history.empty());
// find the first slot that has a valid frame duration
size_t first = history.size();
for (size_t i = 0, c = history.size(); i < c; ++i) {
if (history[i].frameTime != duration(0)) {
assert_invariant(std::addressof(history[i]) == pFront);
first = i;
break;
}
+3 -2
View File
@@ -171,7 +171,8 @@ public:
utils::FixedCapacityVector<Renderer::FrameInfo> getFrameInfoHistory(size_t historySize) const noexcept;
private:
void denoiseFrameTime(Config const& config) noexcept;
using FrameHistoryQueue = CircularQueue<FrameInfoImpl, MAX_FRAMETIME_HISTORY>;
static void denoiseFrameTime(FrameHistoryQueue& history, Config const& config) noexcept;
struct Query {
backend::Handle<backend::HwTimerQuery> handle{};
FrameInfoImpl* pInfo = nullptr;
@@ -180,7 +181,7 @@ private:
uint32_t mIndex = 0; // index of current query
uint32_t mLast = 0; // index of oldest query still active
FrameInfoImpl* pFront = nullptr; // the most recent slot with a valid frame time
CircularQueue<FrameInfoImpl, MAX_FRAMETIME_HISTORY> mFrameTimeHistory;
FrameHistoryQueue mFrameTimeHistory;
};
+25 -18
View File
@@ -16,16 +16,22 @@
#include "FrameSkipper.h"
#include <utils/Log.h>
#include <backend/DriverEnums.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <algorithm>
#include <stddef.h>
namespace filament {
using namespace utils;
using namespace backend;
FrameSkipper::FrameSkipper(size_t latency) noexcept
: mLast(latency - 1) {
: mLast(std::max(latency, MAX_FRAME_LATENCY) - 1) {
assert_invariant(latency <= MAX_FRAME_LATENCY);
}
@@ -41,31 +47,32 @@ void FrameSkipper::terminate(DriverApi& driver) noexcept {
bool FrameSkipper::beginFrame(DriverApi& driver) noexcept {
auto& fences = mDelayedFences;
auto fence = fences.front();
if (fence) {
auto status = driver.getFenceStatus(fence);
if (status == FenceStatus::TIMEOUT_EXPIRED) {
// Sync not ready, skip frame
if (fences.front()) {
// Do we have a latency old fence?
auto status = driver.getFenceStatus(fences.front());
if (UTILS_UNLIKELY(status == FenceStatus::TIMEOUT_EXPIRED)) {
// The fence hasn't signaled yet, skip this frame
return false;
}
assert_invariant(status == FenceStatus::CONDITION_SATISFIED);
driver.destroyFence(fence);
}
// shift all fences down by 1
std::move(fences.begin() + 1, fences.end(), fences.begin());
fences.back() = {};
return true;
}
void FrameSkipper::endFrame(DriverApi& driver) noexcept {
// If the user produced a new frame despite the fact that the previous one wasn't finished
// (i.e. FrameSkipper::beginFrame() returned false), we need to make sure to replace
// a fence that might be here already)
auto& fence = mDelayedFences[mLast];
if (fence) {
driver.destroyFence(fence);
auto& fences = mDelayedFences;
size_t const last = mLast;
// pop the oldest fence and advance the other ones
if (fences.front()) {
driver.destroyFence(fences.front());
}
fence = driver.createFence();
std::move(fences.begin() + 1, fences.end(), fences.begin());
// add a new fence to the end
assert_invariant(!fences[last]);
fences[last] = driver.createFence();
}
} // namespace filament
+4 -1
View File
@@ -22,6 +22,9 @@
#include <array>
#include <stddef.h>
#include <stdint.h>
namespace filament {
/*
@@ -60,7 +63,7 @@ public:
private:
using Container = std::array<backend::Handle<backend::HwFence>, MAX_FRAME_LATENCY>;
mutable Container mDelayedFences{};
size_t mLast;
uint8_t const mLast;
};
} // namespace filament
+6
View File
@@ -354,6 +354,12 @@ bool MaterialParser::getSpecularAntiAliasingThreshold(float* value) const noexce
return mImpl.getFromSimpleChunk(ChunkType::MaterialSpecularAntiAliasingThreshold, value);
}
bool MaterialParser::getStereoscopicType(backend::StereoscopicType* value) const noexcept {
static_assert(sizeof(StereoscopicType) == sizeof(uint8_t),
"StereoscopicType expected size is wrong");
return mImpl.getFromSimpleChunk(ChunkType::MaterialStereoscopicType, reinterpret_cast<uint8_t*>(value));
}
bool MaterialParser::getRequiredAttributes(AttributeBitset* value) const noexcept {
uint32_t rawAttributes = 0;
if (!mImpl.getFromSimpleChunk(ChunkType::MaterialRequiredAttributes, &rawAttributes)) {
+1
View File
@@ -122,6 +122,7 @@ public:
bool hasSpecularAntiAliasing(bool* value) const noexcept;
bool getSpecularAntiAliasingVariance(float* value) const noexcept;
bool getSpecularAntiAliasingThreshold(float* value) const noexcept;
bool getStereoscopicType(backend::StereoscopicType*) const noexcept;
bool getShader(filaflat::ShaderContent& shader, backend::ShaderModel shaderModel,
Variant variant, backend::ShaderStage stage) noexcept;
+3 -1
View File
@@ -459,10 +459,12 @@ void PerViewUniforms::bind(backend::DriverApi& driver) noexcept {
void PerViewUniforms::unbindSamplers() noexcept {
auto& samplerGroup = mSamplers;
samplerGroup.clearSampler(PerViewSib::SHADOW_MAP);
samplerGroup.clearSampler(PerViewSib::IBL_SPECULAR);
samplerGroup.clearSampler(PerViewSib::SSAO);
samplerGroup.clearSampler(PerViewSib::SSR);
samplerGroup.clearSampler(PerViewSib::STRUCTURE);
samplerGroup.clearSampler(PerViewSib::SHADOW_MAP);
samplerGroup.clearSampler(PerViewSib::FOG);
}
} // namespace filament
+18 -8
View File
@@ -494,7 +494,6 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
struct StructureMipmapData {
FrameGraphId<FrameGraphTexture> depth;
uint32_t rt[8];
};
fg.addPass<StructureMipmapData>("StructureMipmap",
@@ -505,7 +504,7 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
.level = uint8_t(i)
});
out = builder.write(out, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
data.rt[i - 1] = builder.declareRenderPass("Structure mip target", {
builder.declareRenderPass("Structure mip target", {
.attachments = { .depth = out }
});
}
@@ -1965,8 +1964,8 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
// - visible bloom size changes with dynamic resolution in non-homogenous mode
// This allows us to use the 9 sample downsampling filter (instead of 13)
// for at least 4 levels.
uint32_t width = std::max(1u, uint32_t(std::floor(bloomWidth)));
uint32_t height = std::max(1u, uint32_t(std::floor(bloomHeight)));
uint32_t width = std::max(16u, uint32_t(std::floor(bloomWidth)));
uint32_t height = std::max(16u, uint32_t(std::floor(bloomHeight)));
width &= ~((1 << 4) - 1); // at least 4 levels
height &= ~((1 << 4) - 1);
bloomWidth = float(width);
@@ -1978,6 +1977,8 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
// we don't need to do the fireflies reduction if we have TAA (it already does it)
bool fireflies = threshold && !taaOptions.enabled;
assert_invariant(bloomWidth && bloomHeight);
while (2 * bloomWidth < float(desc.width) || 2 * bloomHeight < float(desc.height)) {
if (inoutBloomOptions.quality == QualityLevel::LOW ||
inoutBloomOptions.quality == QualityLevel::MEDIUM) {
@@ -2944,6 +2945,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool
1.0f / outputDesc.height});
}
if (blitterNames[index] == "blitLow") {
mi->setParameter("levelOfDetail", 0.0f);
}
mi->setParameter("viewport", float4{
(float)vp.left / inputDesc.width,
(float)vp.bottom / inputDesc.height,
@@ -3002,9 +3007,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool tr
SamplerMagFilter filterMag,
SamplerMinFilter filterMin) noexcept {
// TODO: add support for sub-resources
assert_invariant(fg.getSubResourceDescriptor(input).layer == 0);
assert_invariant(fg.getSubResourceDescriptor(input).level == 0);
uint32_t const layer = fg.getSubResourceDescriptor(input).layer;
float const levelOfDetail = fg.getSubResourceDescriptor(input).level;
struct QuadBlitData {
FrameGraphId<FrameGraphTexture> input;
@@ -3030,7 +3034,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool tr
// --------------------------------------------------------------------------------
// set uniforms
PostProcessMaterial const& material = getPostProcessMaterial("blitLow");
PostProcessMaterial const& material =
getPostProcessMaterial(layer ? "blitArray" : "blitLow");
auto* mi = material.getMaterialInstance(mEngine);
mi->setParameter("color", color, {
.filterMag = filterMag,
@@ -3042,6 +3047,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool tr
float(vp.width) / inputDesc.width,
float(vp.height) / inputDesc.height
});
mi->setParameter("levelOfDetail", levelOfDetail);
if (layer) {
mi->setParameter("layerIndex", layer);
}
mi->commit(driver);
mi->use(driver);
@@ -3184,6 +3193,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
assert_invariant(dst);
assert_invariant(srcDesc.format == dstDesc.format);
assert_invariant(srcDesc.width == dstDesc.width && srcDesc.height == dstDesc.height);
assert_invariant(srcDesc.samples > 1 && dstDesc.samples <= 1);
driver.resolve(
dst, dstSubDesc.level, dstSubDesc.layer,
src, srcSubDesc.level, srcSubDesc.layer);
+42 -32
View File
@@ -118,10 +118,11 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
uint32_t const customCommandCount =
builder.mCustomCommands.has_value() ? builder.mCustomCommands->size() : 0;
Command* const curr = builder.mArena.alloc<Command>(commandCount + customCommandCount);
assert_invariant(curr);
Command* const commandBegin = builder.mArena.alloc<Command>(commandCount + customCommandCount);
Command* commandEnd = commandBegin + (commandCount + customCommandCount);
assert_invariant(commandBegin);
if (UTILS_UNLIKELY(builder.mArena.getAllocator().isHeapAllocation(curr))) {
if (UTILS_UNLIKELY(builder.mArena.getAllocator().isHeapAllocation(commandBegin))) {
static bool sLogOnce = true;
if (UTILS_UNLIKELY(sLogOnce)) {
sLogOnce = false;
@@ -130,10 +131,7 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
}
}
mCommandBegin = curr;
mCommandEnd = curr + commandCount + customCommandCount;
appendCommands(engine, { curr, commandCount },
appendCommands(engine, { commandBegin, commandCount },
builder.mUboHandle,
builder.mVisibleRenderables,
builder.mCommandTypeFlags,
@@ -144,32 +142,36 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
builder.mCameraForwardVector);
if (builder.mCustomCommands.has_value()) {
Command* p = curr + commandCount;
Command* p = commandBegin + commandCount;
for (auto [channel, passId, command, order, fn]: builder.mCustomCommands.value()) {
appendCustomCommand(p++, channel, passId, command, order, fn);
}
}
// sort commands once we're done adding commands
sortCommands(builder.mArena);
commandEnd = resize(builder.mArena,
RenderPass::sortCommands(commandBegin, commandEnd));
if (engine.isAutomaticInstancingEnabled()) {
int32_t stereoscopicEyeCount = 1;
if (builder.mFlags & IS_INSTANCED_STEREOSCOPIC) {
stereoscopicEyeCount *= engine.getConfig().stereoscopicEyeCount;
}
instanceify(engine, builder.mArena, stereoscopicEyeCount);
commandEnd = resize(builder.mArena,
instanceify(engine, commandBegin, commandEnd, stereoscopicEyeCount));
}
// these are `const` from this point on...
mCommandBegin = commandBegin;
mCommandEnd = commandEnd;
}
// this destructor is actually heavy because it inlines ~vector<>
RenderPass::~RenderPass() noexcept = default;
void RenderPass::resize(Arena& arena, size_t count) noexcept {
if (mCommandBegin) {
mCommandEnd = mCommandBegin + count;
arena.rewind(mCommandEnd);
}
RenderPass::Command* RenderPass::resize(Arena& arena, Command* const last) noexcept {
arena.rewind(last);
return last;
}
void RenderPass::appendCommands(FEngine& engine,
@@ -221,7 +223,7 @@ void RenderPass::appendCommands(FEngine& engine,
work(vr.first, vr.size());
} else {
auto* jobCommandsParallel = jobs::parallel_for(js, nullptr, vr.first, (uint32_t)vr.size(),
std::cref(work), jobs::CountSplitter<JOBS_PARALLEL_FOR_COMMANDS_COUNT, 5>());
std::cref(work), jobs::CountSplitter<JOBS_PARALLEL_FOR_COMMANDS_COUNT>());
js.runAndWait(jobCommandsParallel);
}
@@ -260,18 +262,19 @@ void RenderPass::appendCustomCommand(Command* commands,
commands->key = cmd;
}
void RenderPass::sortCommands(Arena& arena) noexcept {
SYSTRACE_NAME("sort and trim commands");
RenderPass::Command* RenderPass::sortCommands(
Command* const begin, Command* const end) noexcept {
SYSTRACE_NAME("sort commands");
std::sort(mCommandBegin, mCommandEnd);
std::sort(begin, end);
// find the last command
Command const* const last = std::partition_point(mCommandBegin, mCommandEnd,
Command* const last = std::partition_point(begin, end,
[](Command const& c) {
return c.key != uint64_t(Pass::SENTINEL);
});
resize(arena, uint32_t(last - mCommandBegin));
return last;
}
void RenderPass::execute(RenderPass const& pass,
@@ -284,7 +287,9 @@ void RenderPass::execute(RenderPass const& pass,
driver.endRenderPass();
}
void RenderPass::instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) noexcept {
RenderPass::Command* RenderPass::instanceify(FEngine& engine,
Command* curr, Command* const last,
int32_t eyeCount) const noexcept {
SYSTRACE_NAME("instanceify");
// instanceify works by scanning the **sorted** command stream, looking for repeat draw
@@ -297,14 +302,12 @@ void RenderPass::instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) no
UTILS_UNUSED uint32_t drawCallsSavedCount = 0;
Command* curr = mCommandBegin;
Command* const last = mCommandEnd;
Command* firstSentinel = nullptr;
PerRenderableData const* uboData = nullptr;
PerRenderableData* stagingBuffer = nullptr;
uint32_t stagingBufferSize = 0;
uint32_t instancedPrimitiveOffset = 0;
size_t const count = last - curr;
// TODO: for the case of instancing we could actually use 128 instead of 64 instances
constexpr size_t maxInstanceCount = CONFIG_MAX_INSTANCES;
@@ -343,7 +346,6 @@ void RenderPass::instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) no
if (UTILS_UNLIKELY(!stagingBuffer)) {
// create a temporary UBO for instancing
size_t const count = mCommandEnd - mCommandBegin;
mInstancedUboHandle = BufferObjectSharedHandle{
engine.getDriverApi().createBufferObject(
count * sizeof(PerRenderableData) + sizeof(PerRenderableUib),
@@ -385,7 +387,7 @@ void RenderPass::instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) no
if (UTILS_UNLIKELY(firstSentinel)) {
//slog.d << "auto-instancing, saving " << drawCallsSavedCount << " draw calls, out of "
// << mCommandEnd - mCommandBegin << io::endl;
// << count << io::endl;
// we have instanced primitives
DriverApi& driver = engine.getDriverApi();
@@ -401,14 +403,15 @@ void RenderPass::instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) no
stagingBuffer = nullptr;
// remove all the canceled commands
auto lastCommand = std::remove_if(firstSentinel, mCommandEnd, [](auto const& command) {
auto lastCommand = std::remove_if(firstSentinel, last, [](auto const& command) {
return command.key == uint64_t(Pass::SENTINEL);
});
resize(arena, uint32_t(lastCommand - mCommandBegin));
return lastCommand;
}
assert_invariant(stagingBuffer == nullptr);
return last;
}
@@ -416,7 +419,8 @@ void RenderPass::instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) no
UTILS_ALWAYS_INLINE // This function exists only to make the code more readable. we want it inlined.
inline // and we don't need it in the compilation unit
void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant,
FMaterialInstance const* const UTILS_RESTRICT mi, bool inverseFrontFaces) noexcept {
FMaterialInstance const* const UTILS_RESTRICT mi,
bool inverseFrontFaces, bool hasDepthClamp) noexcept {
FMaterial const * const UTILS_RESTRICT ma = mi->getMaterial();
variant = Variant::filterVariant(variant, ma->isVariantLit());
@@ -457,6 +461,7 @@ void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant,
cmdDraw.info.rasterState.colorWrite = mi->isColorWriteEnabled();
cmdDraw.info.rasterState.depthWrite = mi->isDepthWriteEnabled();
cmdDraw.info.rasterState.depthFunc = mi->getDepthFunc();
cmdDraw.info.rasterState.depthClamp = hasDepthClamp;
cmdDraw.info.materialVariant = variant;
// we keep "RasterState::colorWrite" to the value set by material (could be disabled)
}
@@ -555,6 +560,9 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
bool const hasInstancedStereo =
renderFlags & IS_INSTANCED_STEREOSCOPIC;
bool const hasDepthClamp =
renderFlags & HAS_DEPTH_CLAMP;
float const cameraPositionDotCameraForward = dot(cameraPosition, cameraForward);
auto const* const UTILS_RESTRICT soaWorldAABBCenter = soa.data<FScene::WORLD_AABB_CENTER>();
@@ -574,6 +582,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
cmd.info.rasterState.depthWrite = true;
cmd.info.rasterState.depthFunc = RasterState::DepthFunc::GE;
cmd.info.rasterState.alphaToCoverage = false;
cmd.info.rasterState.depthClamp = hasDepthClamp;
}
for (uint32_t i = range.first; i < range.last; ++i) {
@@ -607,7 +616,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
// Here, objects close to the camera (but behind) will be drawn first.
// An alternative that keeps the mathematical ordering is given here:
// distanceBits ^= ((int32_t(distanceBits) >> 31) | 0x80000000u);
float const distance = -dot(soaWorldAABBCenter[i], cameraForward) - cameraPositionDotCameraForward;
float const distance = -(dot(soaWorldAABBCenter[i], cameraForward) - cameraPositionDotCameraForward);
uint32_t const distanceBits = reinterpret_cast<uint32_t const&>(distance);
// calculate the per-primitive face winding order inversion
@@ -688,7 +697,8 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
cmd.info.morphingOffset = primitive.getMorphingBufferOffset();
if constexpr (isColorPass) {
RenderPass::setupColorCommand(cmd, renderableVariant, mi, inverseFrontFaces);
RenderPass::setupColorCommand(cmd, renderableVariant, mi,
inverseFrontFaces, hasDepthClamp);
const bool blendPass = Pass(cmd.key & PASS_MASK) == Pass::BLENDED;
if (blendPass) {
// TODO: at least for transparent objects, AABB should be per primitive
+15 -9
View File
@@ -284,6 +284,7 @@ public:
static constexpr RenderFlags HAS_SHADOWING = 0x01;
static constexpr RenderFlags HAS_INVERSE_FRONT_FACES = 0x02;
static constexpr RenderFlags IS_INSTANCED_STEREOSCOPIC = 0x04;
static constexpr RenderFlags HAS_DEPTH_CLAMP = 0x08;
// Arena used for commands
using Arena = utils::Arena<
@@ -408,16 +409,19 @@ private:
uint8_t channel, Pass pass, CustomCommand custom, uint32_t order,
Executor::CustomCommandFn command);
void resize(Arena& arena, size_t count) noexcept;
static Command* resize(Arena& arena, Command* const last) noexcept;
// sorts commands then trims sentinels
void sortCommands(Arena& arena) noexcept;
static Command* sortCommands(
Command* begin, Command* end) noexcept;
// instanceify commands then trims sentinels
void instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) noexcept;
RenderPass::Command* instanceify(FEngine& engine,
Command* begin, Command* end,
int32_t eyeCount) const noexcept;
// We choose the command count per job to minimize JobSystem overhead.
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_COUNT = 1024;
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_COUNT = 128;
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_SIZE =
sizeof(Command) * JOBS_PARALLEL_FOR_COMMANDS_COUNT;
@@ -441,17 +445,17 @@ private:
uint8_t instancedStereoEyeCount) noexcept;
static void setupColorCommand(Command& cmdDraw, Variant variant,
FMaterialInstance const* mi, bool inverseFrontFaces) noexcept;
FMaterialInstance const* mi, bool inverseFrontFaces, bool hasDepthClamp) noexcept;
static void updateSummedPrimitiveCounts(
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> vr) noexcept;
FScene::RenderableSoa const& mRenderableSoa;
backend::Viewport const mScissorViewport;
Command* mCommandBegin = nullptr; // Pointer to the first command
Command* mCommandEnd = nullptr; // Pointer to one past the last command
Command const* /* const */ mCommandBegin = nullptr; // Pointer to the first command
Command const* /* const */ mCommandEnd = nullptr; // Pointer to one past the last command
// a UBO for instanced primitives
BufferObjectSharedHandle mInstancedUboHandle;
mutable BufferObjectSharedHandle mInstancedUboHandle;
// a vector for our custom commands
using CustomCommandVector = std::vector<Executor::CustomCommandFn,
utils::STLAllocator<Executor::CustomCommandFn, LinearAllocatorArena>>;
@@ -525,7 +529,9 @@ public:
// like above but allows to set specific flags
RenderPassBuilder& renderFlags(
RenderPass::RenderFlags mask, RenderPass::RenderFlags value) noexcept {
mFlags = (mFlags & mask) | (value & mask);
value &= mask;
mFlags &= ~mask;
mFlags |= value;
return *this;
}
+6 -1
View File
@@ -16,9 +16,10 @@
#include <filament/Renderer.h>
#include "details/Renderer.h"
#include "ResourceAllocator.h"
#include "details/Engine.h"
#include "details/Renderer.h"
#include "details/View.h"
#include <utils/FixedCapacityVector.h>
@@ -45,6 +46,10 @@ void Renderer::setPresentationTime(int64_t monotonic_clock_ns) {
downcast(this)->setPresentationTime(monotonic_clock_ns);
}
void Renderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) {
downcast(this)->skipFrame(vsyncSteadyClockTimeNano);
}
bool Renderer::beginFrame(SwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano) {
return downcast(this)->beginFrame(downcast(swapChain), vsyncSteadyClockTimeNano);
}
+43 -45
View File
@@ -40,6 +40,7 @@
#include <utils/Panic.h>
#include <algorithm>
#include <optional>
#include <utility>
#include <stddef.h>
@@ -50,8 +51,9 @@ namespace filament {
using namespace backend;
using namespace math;
FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
RendererUtils::ColorPassOutput RendererUtils::colorPass(
FrameGraph& fg, const char* name, FEngine& engine, FView const& view,
ColorPassInput const& colorPassInput,
FrameGraphTexture::Descriptor const& colorBufferDesc,
ColorPassConfig const& config, PostProcessManager::ColorGradingConfig colorGradingConfig,
RenderPass::Executor passExecutor) noexcept {
@@ -67,31 +69,28 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
FrameGraphId<FrameGraphTexture> structure;
};
Blackboard& blackboard = fg.getBlackboard();
auto& colorPass = fg.addPass<ColorPassData>(name,
[&](FrameGraph::Builder& builder, ColorPassData& data) {
TargetBufferFlags const clearColorFlags = config.clearFlags & TargetBufferFlags::COLOR;
TargetBufferFlags clearDepthFlags = config.clearFlags & TargetBufferFlags::DEPTH;
TargetBufferFlags clearStencilFlags = config.clearFlags & TargetBufferFlags::STENCIL;
uint8_t layerCount = 0;
data.shadows = blackboard.get<FrameGraphTexture>("shadows");
data.ssao = blackboard.get<FrameGraphTexture>("ssao");
data.color = blackboard.get<FrameGraphTexture>("color");
data.depth = blackboard.get<FrameGraphTexture>("depth");
data.color = colorPassInput.linearColor;
data.depth = colorPassInput.depth;
data.shadows = colorPassInput.shadows;
data.ssao = colorPassInput.ssao;
// Screen-space reflection or refractions
if (config.hasScreenSpaceReflectionsOrRefractions) {
data.ssr = blackboard.get<FrameGraphTexture>("ssr");
data.ssr = colorPassInput.ssr;
if (data.ssr) {
data.ssr = builder.sample(data.ssr);
}
}
if (config.hasContactShadows) {
data.structure = blackboard.get<FrameGraphTexture>("structure");
data.structure = colorPassInput.structure;
assert_invariant(data.structure);
data.structure = builder.sample(data.structure);
}
@@ -154,6 +153,8 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
}
if (colorGradingConfig.asSubpass) {
assert_invariant(config.msaa <= 1);
assert_invariant(colorBufferDesc.samples <= 1);
data.output = builder.createTexture("Tonemapped Buffer", {
.width = colorBufferDesc.width,
.height = colorBufferDesc.height,
@@ -172,9 +173,6 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
data.color = builder.write(data.color, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
data.depth = builder.write(data.depth, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
if (engine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW) {
layerCount = engine.getConfig().stereoscopicEyeCount;
}
/*
* There is a bit of magic happening here regarding the viewport used.
@@ -196,9 +194,8 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
.stencil = data.stencil },
.clearColor = config.clearColor,
.samples = config.msaa,
.layerCount = layerCount,
.layerCount = static_cast<uint8_t>(colorBufferDesc.depth),
.clearFlags = clearColorFlags | clearDepthFlags | clearStencilFlags});
blackboard["depth"] = data.depth;
},
[=, passExecutor = std::move(passExecutor), &view, &engine](FrameGraphResources const& resources,
ColorPassData const& data, DriverApi& driver) {
@@ -264,24 +261,21 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
}
);
// when color grading is done as a subpass, the output of the color-pass is the ldr buffer
auto output = colorGradingConfig.asSubpass ? colorPass->output : colorPass->color;
blackboard["color"] = output;
return output;
return {
.linearColor = colorPass->color,
.tonemappedColor = colorPass->output, // can be null
.depth = colorPass->depth
};
}
std::pair<FrameGraphId<FrameGraphTexture>, bool> RendererUtils::refractionPass(
std::optional<RendererUtils::ColorPassOutput> RendererUtils::refractionPass(
FrameGraph& fg, FEngine& engine, FView const& view,
ColorPassInput colorPassInput,
ColorPassConfig config,
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
PostProcessManager::ColorGradingConfig colorGradingConfig,
RenderPass const& pass) noexcept {
auto& blackboard = fg.getBlackboard();
auto input = blackboard.get<FrameGraphTexture>("color");
FrameGraphId<FrameGraphTexture> output;
// find the first refractive object in channel 2
RenderPass::Command const* const refraction = std::partition_point(pass.begin(), pass.end(),
[](auto const& command) {
@@ -296,34 +290,35 @@ std::pair<FrameGraphId<FrameGraphTexture>, bool> RendererUtils::refractionPass(
// if there wasn't any refractive object, just skip everything below.
if (UTILS_UNLIKELY(hasScreenSpaceRefraction)) {
PostProcessManager& ppm = engine.getPostProcessManager();
// clear the color/depth buffers, which will orphan (and cull) the color pass
input.clear();
blackboard.remove("color");
blackboard.remove("depth");
assert_invariant(!colorPassInput.linearColor);
assert_invariant(!colorPassInput.depth);
config.hasScreenSpaceReflectionsOrRefractions = true;
input = RendererUtils::colorPass(fg, "Color Pass (opaque)", engine, view, {
PostProcessManager& ppm = engine.getPostProcessManager();
auto const opaquePassOutput = RendererUtils::colorPass(fg,
"Color Pass (opaque)", engine, view, colorPassInput, {
// When rendering the opaques, we need to conserve the sample buffer,
// so create a config that specifies the sample count.
.width = config.physicalViewport.width,
.height = config.physicalViewport.height,
.samples = config.msaa,
.format = config.hdrFormat
}, config, { .asSubpass = false },
},
config, { .asSubpass = false, .customResolve = false },
pass.getExecutor(pass.begin(), refraction));
// generate the mipmap chain
PostProcessManager::generateMipmapSSR(ppm, fg,
input, ssrConfig.refraction, true, ssrConfig);
opaquePassOutput.linearColor,
ssrConfig.refraction,
true, ssrConfig);
// Now we're doing the refraction pass proper.
// This uses the same framebuffer (color and depth) used by the opaque pass. This happens
// automatically because these are set in the Blackboard (they were set by the opaque
// pass). For this reason, `desc` below is only used in colorPass() for the width and
// height.
// This uses the same framebuffer (color and depth) used by the opaque pass.
// For this reason, the `colorBufferDesc` parameter of colorPass() below is only used for
// the width and height.
colorPassInput.linearColor = opaquePassOutput.linearColor;
colorPassInput.depth = opaquePassOutput.depth;
// Since we're reusing the existing target we don't want to clear any of its buffer.
// Important: if this target ended up being an imported target, then the clearFlags
@@ -331,22 +326,25 @@ std::pair<FrameGraphId<FrameGraphTexture>, bool> RendererUtils::refractionPass(
// and we'd end up clearing the opaque pass. This scenario never happens because it is
// prevented in Renderer.cpp's final blit.
config.clearFlags = TargetBufferFlags::NONE;
output = RendererUtils::colorPass(fg, "Color Pass (transparent)", engine, view, {
auto transparentPassOutput = RendererUtils::colorPass(fg, "Color Pass (transparent)",
engine, view, colorPassInput, {
.width = config.physicalViewport.width,
.height = config.physicalViewport.height },
config, colorGradingConfig, pass.getExecutor(refraction, pass.end()));
config, colorGradingConfig,
pass.getExecutor(refraction, pass.end()));
if (config.msaa > 1 && !colorGradingConfig.asSubpass) {
// We need to do a resolve here because later passes (such as color grading or DoF) will
// need to sample from 'output'. However, because we have MSAA, we know we're not
// sampleable. And this is because in the SSR case, we had to use a renderbuffer to
// conserve the multi-sample buffer.
output = ppm.resolve(fg, "Resolved Color Buffer", output, { .levels = 1 });
transparentPassOutput.linearColor = ppm.resolve(fg, "Resolved Color Buffer",
transparentPassOutput.linearColor, { .levels = 1 });
}
} else {
output = input;
return transparentPassOutput;
}
return { output, hasScreenSpaceRefraction };
return std::nullopt;
}
UTILS_NOINLINE
+24 -2
View File
@@ -21,14 +21,19 @@
#include "RenderPass.h"
#include "fg/FrameGraphId.h"
#include "fg/FrameGraphTexture.h"
#include <filament/Viewport.h>
#include <backend/DriverEnums.h>
#include <backend/PixelBufferDescriptor.h>
#include <math/vec2.h>
#include <math/vec4.h>
#include <stdint.h>
#include <optional>
#include <utility>
namespace filament {
@@ -71,15 +76,32 @@ public:
bool screenSpaceReflectionHistoryNotReady;
};
static FrameGraphId<FrameGraphTexture> colorPass(
struct ColorPassInput {
FrameGraphId<FrameGraphTexture> linearColor;
FrameGraphId<FrameGraphTexture> tonemappedColor;
FrameGraphId<FrameGraphTexture> depth;
FrameGraphId<FrameGraphTexture> shadows;
FrameGraphId<FrameGraphTexture> ssao;
FrameGraphId<FrameGraphTexture> ssr;
FrameGraphId<FrameGraphTexture> structure;
};
struct ColorPassOutput {
FrameGraphId<FrameGraphTexture> linearColor;
FrameGraphId<FrameGraphTexture> tonemappedColor;
FrameGraphId<FrameGraphTexture> depth;
};
static ColorPassOutput colorPass(
FrameGraph& fg, const char* name, FEngine& engine, FView const& view,
ColorPassInput const& colorPassInput,
FrameGraphTexture::Descriptor const& colorBufferDesc,
ColorPassConfig const& config,
PostProcessManager::ColorGradingConfig colorGradingConfig,
RenderPass::Executor passExecutor) noexcept;
static std::pair<FrameGraphId<FrameGraphTexture>, bool> refractionPass(
static std::optional<RendererUtils::ColorPassOutput> refractionPass(
FrameGraph& fg, FEngine& engine, FView const& view,
ColorPassInput colorPassInput,
ColorPassConfig config,
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
PostProcessManager::ColorGradingConfig colorGradingConfig,
+137 -35
View File
@@ -27,15 +27,18 @@
#include "private/backend/DriverApi.h"
#include <utils/algorithm.h>
#include <utils/bitset.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Log.h>
#include <utils/ostream.h>
#include <array>
#include <algorithm>
#include <iterator>
#include <memory>
#include <optional>
#include <utility>
#include <stddef.h>
@@ -89,8 +92,15 @@ void ResourceAllocator::AssociativeContainer<K, V, H>::emplace(ARGS&& ... args)
}
// ------------------------------------------------------------------------------------------------
ResourceAllocatorInterface::~ResourceAllocatorInterface() = default;
// ------------------------------------------------------------------------------------------------
ResourceAllocatorDisposerInterface::~ResourceAllocatorDisposerInterface() = default;
// ------------------------------------------------------------------------------------------------
size_t ResourceAllocator::TextureKey::getSize() const noexcept {
size_t const pixelCount = width * height * depth;
size_t size = pixelCount * FTexture::getFormatSize(format);
@@ -110,16 +120,22 @@ size_t ResourceAllocator::TextureKey::getSize() const noexcept {
ResourceAllocator::ResourceAllocator(Engine::Config const& config, DriverApi& driverApi) noexcept
: mCacheMaxAge(config.resourceAllocatorCacheMaxAge),
mBackend(driverApi) {
mBackend(driverApi),
mDisposer(std::make_shared<ResourceAllocatorDisposer>(driverApi)) {
}
ResourceAllocator::ResourceAllocator(std::shared_ptr<ResourceAllocatorDisposer> disposer,
Engine::Config const& config, DriverApi& driverApi) noexcept
: mCacheMaxAge(config.resourceAllocatorCacheMaxAge),
mBackend(driverApi),
mDisposer(std::move(disposer)) {
}
ResourceAllocator::~ResourceAllocator() noexcept {
assert_invariant(!mTextureCache.size());
assert_invariant(!mInUseTextures.size());
assert_invariant(mTextureCache.empty());
}
void ResourceAllocator::terminate() noexcept {
assert_invariant(!mInUseTextures.size());
auto& textureCache = mTextureCache;
for (auto it = textureCache.begin(); it != textureCache.end();) {
mBackend.destroyTexture(it->second.handle);
@@ -127,12 +143,14 @@ void ResourceAllocator::terminate() noexcept {
}
}
RenderTargetHandle ResourceAllocator::createRenderTarget(const char*,
RenderTargetHandle ResourceAllocator::createRenderTarget(const char* name,
TargetBufferFlags targetBufferFlags, uint32_t width, uint32_t height,
uint8_t samples, uint8_t layerCount, MRT color, TargetBufferInfo depth,
TargetBufferInfo stencil) noexcept {
return mBackend.createRenderTarget(targetBufferFlags,
auto handle = mBackend.createRenderTarget(targetBufferFlags,
width, height, samples ? samples : 1u, layerCount, color, depth, stencil);
mBackend.setDebugTag(handle.getId(), CString{ name });
return handle;
}
void ResourceAllocator::destroyRenderTarget(RenderTargetHandle h) noexcept {
@@ -154,9 +172,9 @@ backend::TextureHandle ResourceAllocator::createTexture(const char* name,
// do we have a suitable texture in the cache?
TextureHandle handle;
TextureKey const key{ name, target, levels, format, samples, width, height, depth, usage, swizzle };
if constexpr (mEnabled) {
auto& textureCache = mTextureCache;
const TextureKey key{ name, target, levels, format, samples, width, height, depth, usage, swizzle };
auto it = textureCache.find(key);
if (UTILS_LIKELY(it != textureCache.end())) {
// we do, move the entry to the in-use list, and remove from the cache
@@ -174,7 +192,6 @@ backend::TextureHandle ResourceAllocator::createTexture(const char* name,
swizzle[0], swizzle[1], swizzle[2], swizzle[3]);
}
}
mInUseTextures.emplace(handle, key);
} else {
if (swizzle == defaultSwizzle) {
handle = mBackend.createTexture(
@@ -185,63 +202,106 @@ backend::TextureHandle ResourceAllocator::createTexture(const char* name,
swizzle[0], swizzle[1], swizzle[2], swizzle[3]);
}
}
mDisposer->checkout(handle, key);
mBackend.setDebugTag(handle.getId(), CString{ name });
return handle;
}
void ResourceAllocator::destroyTexture(TextureHandle h) noexcept {
auto const key = mDisposer->checkin(h);
if constexpr (mEnabled) {
// find the texture in the in-use list (it must be there!)
auto it = mInUseTextures.find(h);
assert_invariant(it != mInUseTextures.end());
// move it to the cache
const TextureKey key = it->second;
uint32_t const size = key.getSize();
mTextureCache.emplace(key, TextureCachePayload{ h, mAge, size });
mCacheSize += size;
// remove it from the in-use list
mInUseTextures.erase(it);
if (UTILS_LIKELY(key.has_value())) {
uint32_t const size = key.value().getSize();
mTextureCache.emplace(key.value(), TextureCachePayload{ h, mAge, size });
mCacheSize += size;
mCacheSizeHiWaterMark = std::max(mCacheSizeHiWaterMark, mCacheSize);
}
} else {
mBackend.destroyTexture(h);
}
}
void ResourceAllocator::gc() noexcept {
// this is called regularly -- usually once per frame of each Renderer
ResourceAllocatorDisposerInterface& ResourceAllocator::getDisposer() noexcept {
return *mDisposer;
}
// increase our age
const size_t age = mAge++;
void ResourceAllocator::gc(bool skippedFrame) noexcept {
// this is called regularly -- usually once per frame
// increase our age at each (non-skipped) frame
const size_t age = mAge;
if (!skippedFrame) {
mAge++;
}
// Purging strategy:
// - remove entries that are older than a certain age
// - remove only one entry per gc(),
// - remove all entries older than MAX_AGE_SKIPPED_FRAME when skipping a frame
// - remove entries older than mCacheMaxAgeSoft
// - remove only MAX_EVICTION_COUNT entry per gc(),
// - look for the number of unique resource ages present in the cache (this basically gives
// us how many buckets of resources we have corresponding to previous frames.
// - remove all resources that have an age older than the MAX_UNIQUE_AGE_COUNT'th bucket
auto& textureCache = mTextureCache;
// when skipping a frame, the maximum age to keep in the cache
constexpr size_t MAX_AGE_SKIPPED_FRAME = 1;
// maximum entry count to evict per GC, under the mCacheMaxAgeSoft limit
constexpr size_t MAX_EVICTION_COUNT = 1;
// maximum number of unique ages in the cache
constexpr size_t MAX_UNIQUE_AGE_COUNT = 3;
utils::bitset32 ages;
uint32_t evictedCount = 0;
for (auto it = textureCache.begin(); it != textureCache.end();) {
const size_t ageDiff = age - it->second.age;
if (ageDiff >= mCacheMaxAge) {
size_t const ageDiff = age - it->second.age;
if ((ageDiff >= MAX_AGE_SKIPPED_FRAME && skippedFrame) ||
(ageDiff >= mCacheMaxAge && evictedCount < MAX_EVICTION_COUNT)) {
evictedCount++;
purge(it);
// only purge a single entry per gc
break;
} else {
// build the set of ages present in the cache after eviction
ages.set(std::min(size_t(31), ageDiff));
++it;
}
}
// if we have MAX_UNIQUE_AGE_COUNT ages or more, we evict all the resources that
// are older than the MAX_UNIQUE_AGE_COUNT'th age.
if (!skippedFrame && ages.count() >= MAX_UNIQUE_AGE_COUNT) {
uint32_t bits = ages.getValue();
// remove from the set the ages we keep
for (size_t i = 0; i < MAX_UNIQUE_AGE_COUNT - 1; i++) {
bits &= ~(1 << utils::ctz(bits));
}
size_t const maxAge = utils::ctz(bits);
for (auto it = textureCache.begin(); it != textureCache.end();) {
const size_t ageDiff = age - it->second.age;
if (ageDiff >= maxAge) {
purge(it);
} else {
++it;
}
}
}
}
UTILS_NOINLINE
void ResourceAllocator::dump(bool brief) const noexcept {
slog.d << "# entries=" << mTextureCache.size() << ", sz=" << mCacheSize / float(1u << 20u)
<< " MiB" << io::endl;
constexpr float MiB = 1.0f / float(1u << 20u);
slog.d << "# entries=" << mTextureCache.size()
<< ", sz=" << (float)mCacheSize * MiB << " MiB"
<< ", max=" << (float)mCacheSizeHiWaterMark * MiB << " MiB"
<< io::endl;
if (!brief) {
for (auto const& it : mTextureCache) {
auto w = it.first.width;
auto h = it.first.height;
auto f = FTexture::getFormatSize(it.first.format);
slog.d << it.first.name << ": w=" << w << ", h=" << h << ", f=" << f << ", sz="
<< it.second.size / float(1u << 20u) << io::endl;
<< (float)it.second.size * MiB << io::endl;
}
}
}
@@ -254,4 +314,46 @@ void ResourceAllocator::purge(
mTextureCache.erase(pos);
}
// ------------------------------------------------------------------------------------------------
ResourceAllocatorDisposer::ResourceAllocatorDisposer(DriverApi& driverApi) noexcept
: mBackend(driverApi) {
}
ResourceAllocatorDisposer::~ResourceAllocatorDisposer() noexcept {
assert_invariant(mInUseTextures.empty());
}
void ResourceAllocatorDisposer::terminate() noexcept {
assert_invariant(mInUseTextures.empty());
}
void ResourceAllocatorDisposer::destroy(backend::TextureHandle handle) noexcept {
if (handle) {
auto r = checkin(handle);
if (r.has_value()) {
mBackend.destroyTexture(handle);
}
}
}
void ResourceAllocatorDisposer::checkout(backend::TextureHandle handle,
ResourceAllocator::TextureKey key) {
mInUseTextures.emplace(handle, key);
}
std::optional<ResourceAllocator::TextureKey> ResourceAllocatorDisposer::checkin(
backend::TextureHandle handle) {
// find the texture in the in-use list (it must be there!)
auto it = mInUseTextures.find(handle);
assert_invariant(it != mInUseTextures.end());
if (it == mInUseTextures.end()) {
return std::nullopt;
}
TextureKey const key = it->second;
// remove it from the in-use list
mInUseTextures.erase(it);
return key;
}
} // namespace filament
+46 -5
View File
@@ -28,16 +28,29 @@
#include <utils/Hash.h>
#include <array>
#include <vector>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include <cstddef>
#include <stddef.h>
#include <stdint.h>
namespace filament {
class ResourceAllocatorDisposer;
// The only reason we use an interface here is for unit-tests, so we can mock this allocator.
// This is not too time-critical, so that's okay.
class ResourceAllocatorDisposerInterface {
public:
virtual void destroy(backend::TextureHandle handle) noexcept = 0;
protected:
virtual ~ResourceAllocatorDisposerInterface();
};
class ResourceAllocatorInterface {
public:
virtual backend::RenderTargetHandle createRenderTarget(const char* name,
@@ -60,15 +73,20 @@ public:
virtual void destroyTexture(backend::TextureHandle h) noexcept = 0;
virtual ResourceAllocatorDisposerInterface& getDisposer() noexcept = 0;
protected:
virtual ~ResourceAllocatorInterface();
};
class ResourceAllocator final : public ResourceAllocatorInterface {
public:
explicit ResourceAllocator(std::shared_ptr<ResourceAllocatorDisposer> disposer,
Engine::Config const& config, backend::DriverApi& driverApi) noexcept;
explicit ResourceAllocator(
Engine::Config const& config, backend::DriverApi& driverApi) noexcept;
~ResourceAllocator() noexcept override;
void terminate() noexcept;
@@ -93,7 +111,9 @@ public:
void destroyTexture(backend::TextureHandle h) noexcept override;
void gc() noexcept;
ResourceAllocatorDisposerInterface& getDisposer() noexcept override;
void gc(bool skippedFrame = false) noexcept;
private:
size_t const mCacheMaxAge;
@@ -181,6 +201,7 @@ private:
using value_type = typename Container::value_type::second_type;
size_t size() const { return mContainer.size(); }
bool empty() const { return size() == 0; }
iterator begin() { return mContainer.begin(); }
const_iterator begin() const { return mContainer.begin(); }
iterator end() { return mContainer.end(); }
@@ -193,16 +214,36 @@ private:
};
using CacheContainer = AssociativeContainer<TextureKey, TextureCachePayload>;
using InUseContainer = AssociativeContainer<backend::TextureHandle, TextureKey>;
void purge(ResourceAllocator::CacheContainer::iterator const& pos);
backend::DriverApi& mBackend;
std::shared_ptr<ResourceAllocatorDisposer> mDisposer;
CacheContainer mTextureCache;
InUseContainer mInUseTextures;
size_t mAge = 0;
uint32_t mCacheSize = 0;
uint32_t mCacheSizeHiWaterMark = 0;
static constexpr bool mEnabled = true;
friend class ResourceAllocatorDisposer;
};
class ResourceAllocatorDisposer final : public ResourceAllocatorDisposerInterface {
using TextureKey = ResourceAllocator::TextureKey;
public:
explicit ResourceAllocatorDisposer(backend::DriverApi& driverApi) noexcept;
~ResourceAllocatorDisposer() noexcept override;
void terminate() noexcept;
void destroy(backend::TextureHandle handle) noexcept override;
private:
friend class ResourceAllocator;
void checkout(backend::TextureHandle handle, TextureKey key);
std::optional<TextureKey> checkin(backend::TextureHandle handle);
using InUseContainer = ResourceAllocator::AssociativeContainer<backend::TextureHandle, TextureKey>;
backend::DriverApi& mBackend;
InUseContainer mInUseTextures;
};
} // namespace filament
+9
View File
@@ -28,6 +28,7 @@ namespace filament {
class ResourceListBase {
public:
using iterator = typename tsl::robin_set<void*>::iterator;
using const_iterator = typename tsl::robin_set<void*>::const_iterator;
explicit ResourceListBase(const char* typeName);
ResourceListBase(ResourceListBase&& rhs) noexcept = default;
@@ -58,6 +59,14 @@ public:
return mList.end();
}
const_iterator begin() const noexcept {
return mList.begin();
}
const_iterator end() const noexcept {
return mList.end();
}
protected:
void forEach(void(*f)(void* user, void *p), void* user) const noexcept;
tsl::robin_set<void*> mList;
+68 -50
View File
@@ -57,9 +57,6 @@ namespace filament {
using namespace math;
using namespace backend;
// do this only if depth-clamp is available
static constexpr bool USE_DEPTH_CLAMP = false;
ShadowMap::ShadowMap(FEngine& engine) noexcept
: mPerShadowMapUniforms(engine),
mShadowType(ShadowType::DIRECTIONAL),
@@ -126,7 +123,8 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
FScene::LightSoa const& lightData, size_t index,
filament::CameraInfo const& camera,
ShadowMapInfo const& shadowMapInfo,
SceneInfo const& sceneInfo) noexcept {
SceneInfo const& sceneInfo,
bool useDepthClamp) noexcept {
// reset the visible shadow status
mHasVisibleShadows = false;
@@ -137,13 +135,20 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
const auto direction = lightData.elementAt<FScene::SHADOW_DIRECTION>(index);
auto [Mv, znear, zfar, lsClippedShadowVolume, vertexCount, visibleShadows] =
computeDirectionalShadowBounds(engine, direction, params, camera, sceneInfo);
auto const [Mv, znear, zfar, lsClippedShadowVolume, vertexCount, visibleShadows] =
computeDirectionalShadowBounds(engine, direction, params, camera, sceneInfo,
useDepthClamp);
if (UTILS_UNLIKELY(!visibleShadows)) {
return {};
}
// if the 2D bounds of the intersection is empty seen from the light, we have no shadow
Aabb const bounds = compute2DBounds(Mv, lsClippedShadowVolume.data(), vertexCount);
if (UTILS_UNLIKELY(!(bounds.min.x < bounds.max.x && bounds.min.y < bounds.max.y))) {
return {};
}
/*
* Compute the light's projection matrix
* (directional/point lights, i.e. projection to use, including znear/zfar clip planes)
@@ -161,12 +166,17 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
mat4f W, Wp, L;
// We can't use LISPSM in stable mode
const bool useLispsm = params.options.lispsm && !params.options.stable;
if (useLispsm) {
if (useLispsm ||
(!params.options.stable && !engine.debug.shadowmap.disable_light_frustum_align)) {
// Orient the shadow map in the direction of the view vector by constructing a
const float3 lsCameraFwd = Mv.upperLeft() * camera.getForwardVector();
L = computeLightRotation(lsCameraFwd);
LMp = L * Mp;
LMpMv = LMp * Mv;
}
if (useLispsm) {
W = applyLISPSM(Wp, camera, params,
LMp, Mv, LMpMv, lsClippedShadowVolume, vertexCount, direction);
}
@@ -184,8 +194,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
// In LiPSM mode, we're using the warped space here.
float4 f = computeFocusParams(LMpMv, WLMp,
lsClippedShadowVolume, vertexCount,
camera, sceneInfo.csNearFar,
params.options.shadowFar, params.options.stable);
camera, params.options.shadowFar, params.options.stable);
if (params.options.stable) {
const auto lsRef = lightData.elementAt<FScene::SHADOW_REF>(index);
@@ -386,7 +395,8 @@ ShadowMap::DirectionalShadowBounds ShadowMap::computeDirectionalShadowBounds(
float3 const direction,
FLightManager::ShadowParams params,
filament::CameraInfo const& camera,
SceneInfo const& sceneInfo) noexcept {
SceneInfo const& sceneInfo,
bool useDepthClamp) noexcept {
// we use aligned_storage<> here to avoid the default initialization of std::array<>
using Storage = std::aligned_storage<sizeof(FrustumBoxIntersection)>::type;
@@ -448,36 +458,49 @@ ShadowMap::DirectionalShadowBounds ShadowMap::computeDirectionalShadowBounds(
// Compute the intersection of the view volume with the intersection of receivers and casters
// in light space. This returns a set of points on the convex-hull of the intersection.
mat4f const projection = camera.cullingProjection *
(camera.view * FCamera::rigidTransformInverse(MvAtOrigin));
size_t const vertexCount = intersectFrustumWithBox(lsClippedShadowVolume,
camera.cullingProjection * camera.view * FCamera::rigidTransformInverse(MvAtOrigin),
sceneInfo.csNearFar, lsLightFrustumBounds);
projection, lsLightFrustumBounds);
if (UTILS_UNLIKELY(vertexCount < 4)) {
return {};
}
/*
* Adjust the scene's zmax (i.e. Near plane) and zmin (i.e. Far plane) in light space.
* (near/far correspond to max/min because the light looks down the -z axis).
* - The Near plane is set to the shadow casters max z (i.e. closest to the light)
* - The Far plane is set to the closest of the farthest shadow casters and receivers
* i.e.: shadow casters behind the last receivers can't cast any shadows
*
* If "depth clamp" is supported, we can further tighten the near plane to the
* shadow receiver.
*
* Note: L has no influence here, since we're only interested in z values
* (L is a rotation around z)
*/
* Adjust the scene's zmax (i.e. Near plane) and zmin (i.e. Far plane) in light space.
* (near/far correspond to max/min because the light looks down the -z axis).
*
* - For the Far plane, the light frustum is already set to the closest of receivers and
* casters (i.e.: shadow casters behind the last receivers can't cast any shadows).
* But it can be moved closer up to the farthest point in the camera frustum & light
* frustum intersection.
*
* - The Near plane is already set to the shadow casters max z (i.e. closest to the light)
*
* If "depth clamp" is supported, we can further tighten the near plane to the
* shadow receiver (i.e. to the closest point of camera frustum & light frustum intersection).
*
* Note: L has no influence here, since we're only interested in z values
* (L is a rotation around z)
*/
float lsClippedShadowVolumeFarthest = std::numeric_limits<float>::max();
float lsClippedShadowVolumeNearest = std::numeric_limits<float>::lowest();
for (size_t i = 0; i < vertexCount; ++i) {
float3 const v = lsClippedShadowVolume[i];
// far: figure out the farthest shadow receivers
lsLightFrustumBounds.min.z = min(lsLightFrustumBounds.min.z, v.z);
if constexpr (USE_DEPTH_CLAMP) {
lsLightFrustumBounds.max.z = max(lsLightFrustumBounds.max.z, v.z);
}
lsClippedShadowVolumeFarthest = std::min(lsClippedShadowVolumeFarthest, v.z);
lsClippedShadowVolumeNearest = std::max(lsClippedShadowVolumeNearest, v.z);
}
lsLightFrustumBounds.min.z =
std::max(lsLightFrustumBounds.min.z, lsClippedShadowVolumeFarthest);
if (useDepthClamp) {
lsLightFrustumBounds.max.z =
std::min(lsLightFrustumBounds.max.z, lsClippedShadowVolumeNearest);
}
if (engine.debug.shadowmap.far_uses_shadowcasters) {
// far: closest of the farthest shadow casters and receivers
lsLightFrustumBounds.min.z =
@@ -611,7 +634,7 @@ math::float4 ShadowMap::computeFocusParams(
mat4f const& LMpMv,
mat4f const& WLMp,
FrustumBoxIntersection const& lsShadowVolume, size_t vertexCount,
filament::CameraInfo const& camera, float2 const& csNearFar,
filament::CameraInfo const& camera,
float shadowFar, bool stable) noexcept {
float2 s, o;
if (stable) {
@@ -626,7 +649,7 @@ math::float4 ShadowMap::computeFocusParams(
return wsViewVolumeBoundingSphere;
} else {
mat4f const viewFromClip = inverse(camera.cullingProjection);
Corners const wsFrustumVertices = computeFrustumCorners(viewFromClip, csNearFar);
Corners const wsFrustumVertices = computeFrustumCorners(viewFromClip);
float4 const wsViewVolumeBoundingSphere =
computeBoundingSphere(wsFrustumVertices.vertices, 8);
return wsViewVolumeBoundingSphere;
@@ -794,26 +817,25 @@ Aabb ShadowMap::compute2DBounds(const mat4f& lightView,
}
ShadowMap::Corners ShadowMap::computeFrustumCorners(
mat4f const& projectionInverse,
float2 csNearFar) noexcept {
mat4f const& projectionInverse) noexcept {
Corners out;
// compute view frustum in world space (from its NDC)
// matrix to convert: ndc -> camera -> world
float const near = csNearFar.x;
float const far = csNearFar.y;
float const near = -1.0f;
float const far = 1.0f;
Corners const csViewFrustumCorners = {
.vertices = {
{ -1, -1, far },
{ 1, -1, far },
{ -1, 1, far },
{ 1, 1, far },
{ 1, -1, far },
{ -1, 1, far },
{ 1, 1, far },
{ -1, -1, near },
{ 1, -1, near },
{ -1, 1, near },
{ 1, 1, near },
{ 1, -1, near },
{ -1, 1, near },
{ 1, 1, near },
}
};
@@ -895,11 +917,11 @@ void ShadowMap::snapLightFrustum(float2& s, float2& o,
size_t ShadowMap::intersectFrustumWithBox(
FrustumBoxIntersection& UTILS_RESTRICT outVertices,
mat4f const& UTILS_RESTRICT projection, math::float2 const& UTILS_RESTRICT csNearFar,
mat4f const& UTILS_RESTRICT projection,
Aabb const& UTILS_RESTRICT box)
{
Frustum const frustum{ projection };
Corners const frustumVertices{ computeFrustumCorners(inverse(projection), csNearFar) };
Corners const frustumVertices{ computeFrustumCorners(inverse(projection)) };
constexpr const float EPSILON = 1.0f / 8192.0f; // ~0.012 mm
size_t vertexCount = 0;
@@ -1203,12 +1225,11 @@ void ShadowMap::visitScene(const FScene& scene, uint32_t visibleLayers,
}
ShadowMap::SceneInfo::SceneInfo(
FScene const& scene, uint8_t visibleLayers, mat4f const& viewMatrix) noexcept
: vsNearFar(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max()),
visibleLayers(visibleLayers) {
FScene const& scene, uint8_t visibleLayers) noexcept
: visibleLayers(visibleLayers) {
// the code below only works with affine transforms
assert_invariant(transpose(viewMatrix)[3] == float4(0, 0, 0, 1));
// Filament's API requires viewMatrix to be rigid (and thus affine).
// We assume the light is at the origin to compute the SceneInfo. This is consumed later by
// computeShadowCameraDirectional() which takes this into account.
@@ -1224,9 +1245,6 @@ ShadowMap::SceneInfo::SceneInfo(
[&](Aabb receiver, Culler::result_type) {
wsShadowReceiversVolume.min = min(wsShadowReceiversVolume.min, receiver.min);
wsShadowReceiversVolume.max = max(wsShadowReceiversVolume.max, receiver.max);
auto r = Aabb::transform(viewMatrix.upperLeft(), viewMatrix[3].xyz, receiver);
vsNearFar.x = std::max(vsNearFar.x, r.max.z);
vsNearFar.y = std::min(vsNearFar.y, r.min.z);
}
);
}
+8 -14
View File
@@ -109,20 +109,13 @@ public:
struct SceneInfo {
SceneInfo() noexcept = default;
SceneInfo(FScene const& scene, uint8_t visibleLayers, math::mat4f const& viewMatrix) noexcept;
// scratch data: The near and far planes, in clip space, to use for this shadow map
math::float2 csNearFar = { -1.0f, 1.0f };
SceneInfo(FScene const& scene, uint8_t visibleLayers) noexcept;
// scratch data: light's near/far expressed in light-space, calculated from the scene's
// content assuming the light is at the origin.
math::float2 lsCastersNearFar;
math::float2 lsReceiversNearFar;
// Viewing camera's near/far expressed in view-space, calculated from the
// scene's content.
math::float2 vsNearFar;
// World-space shadow-casters volume
Aabb wsShadowCastersVolume;
@@ -154,7 +147,8 @@ public:
const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera,
ShadowMapInfo const& shadowMapInfo,
SceneInfo const& sceneInfo) noexcept;
SceneInfo const& sceneInfo,
bool useDepthClamp) noexcept;
ShaderParameters updateSpot(FEngine& engine,
const FScene::LightSoa& lightData, size_t index,
@@ -244,7 +238,8 @@ private:
math::float3 direction,
FLightManager::ShadowParams params,
filament::CameraInfo const& camera,
SceneInfo const& sceneInfo) noexcept;
SceneInfo const& sceneInfo,
bool useDepthClamp) noexcept;
static math::mat4f applyLISPSM(math::mat4f& Wp,
filament::CameraInfo const& camera, FLightManager::ShadowParams const& params,
@@ -260,7 +255,7 @@ private:
math::mat4f const& LMpMv,
math::mat4f const& WLMp,
FrustumBoxIntersection const& lsShadowVolume, size_t vertexCount,
filament::CameraInfo const& camera, math::float2 const& csNearFar,
filament::CameraInfo const& camera,
float shadowFar, bool stable) noexcept;
static inline void snapLightFrustum(math::float2& s, math::float2& o,
@@ -271,8 +266,7 @@ private:
SceneInfo const& sceneInfo,
bool stable, bool focusShadowCasters, bool farUsesShadowCasters) noexcept;
static Corners computeFrustumCorners(const math::mat4f& projectionInverse,
math::float2 csNearFar = { -1.0f, 1.0f }) noexcept;
static Corners computeFrustumCorners(const math::mat4f& projectionInverse) noexcept;
static inline math::float2 computeNearFar(math::mat4f const& view,
math::float3 const* wsVertices, size_t count) noexcept;
@@ -307,7 +301,7 @@ private:
static size_t intersectFrustumWithBox(
FrustumBoxIntersection& outVertices,
math::mat4f const& projection, math::float2 const& csNearFar,
math::mat4f const& projection,
Aabb const& box);
static math::mat4f warpFrustum(float n, float f) noexcept;
+117 -84
View File
@@ -39,17 +39,20 @@
#include <backend/DriverApiForward.h>
#include <backend/DriverEnums.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Range.h>
#include <utils/Slice.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <utils/BitmaskEnum.h>
#include <utils/Range.h>
#include <utils/Slice.h>
#include <math/half.h>
#include <math/mat4.h>
#include <math/vec4.h>
#include <math/scalar.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <new>
@@ -63,12 +66,15 @@ namespace filament {
using namespace backend;
using namespace math;
ShadowMapManager::ShadowMapManager(FEngine& engine) {
ShadowMapManager::ShadowMapManager(FEngine& engine)
: mIsDepthClampSupported(engine.getDriverApi().isDepthClampSupported()) {
FDebugRegistry& debugRegistry = engine.getDebugRegistry();
debugRegistry.registerProperty("d.shadowmap.visualize_cascades",
&engine.debug.shadowmap.visualize_cascades);
debugRegistry.registerProperty("d.shadowmap.tightly_bound_scene",
&engine.debug.shadowmap.tightly_bound_scene);
debugRegistry.registerProperty("d.shadowmap.disable_light_frustum_align",
&engine.debug.shadowmap.disable_light_frustum_align);
debugRegistry.registerProperty("d.shadowmap.depth_clamp",
&engine.debug.shadowmap.depth_clamp);
}
ShadowMapManager::~ShadowMapManager() {
@@ -147,7 +153,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::update(
calculateTextureRequirements(engine, view, lightData);
// Compute scene-dependent values shared across all shadow maps
ShadowMap::SceneInfo const info{ *view.getScene(), view.getVisibleLayers(), cameraInfo.view };
ShadowMap::SceneInfo const info{ *view.getScene(), view.getVisibleLayers() };
shadowTechnique |= updateCascadeShadowMaps(
engine, view, cameraInfo, renderableData, lightData, info);
@@ -361,7 +367,20 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
// generate and sort the commands for rendering the shadow map
RenderPass::RenderFlags renderPassFlags{};
bool const canUseDepthClamp =
shadowMap.getShadowType() == ShadowType::DIRECTIONAL &&
!view.hasVSM() &&
mIsDepthClampSupported &&
engine.debug.shadowmap.depth_clamp;
if (canUseDepthClamp) {
renderPassFlags |= RenderPass::HAS_DEPTH_CLAMP;
}
RenderPass const pass = passBuilder
.renderFlags(RenderPass::HAS_DEPTH_CLAMP, renderPassFlags)
.camera(cameraInfo)
.visibilityMask(entry.visibilityMask)
.geometry(scene->getRenderableData(),
@@ -543,65 +562,9 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
FLightManager::ShadowParams const& params = lcm.getShadowParams(directionalLight);
// Adjust the camera's projection for the light's shadowFar
cameraInfo.zf = params.options.shadowFar > 0.0f ? params.options.shadowFar : cameraInfo.zf;
if (UTILS_UNLIKELY(params.options.shadowFar > 0.0f)) {
cameraInfo.zf = params.options.shadowFar;
float const n = cameraInfo.zn;
float const f = cameraInfo.zf;
/*
* Updating a projection matrix near and far planes:
*
* We assume that the near and far plane equations are of the form:
* N = { 0, 0, 1, n }
* F = { 0, 0, -1, -f }
*
* We also assume that the lower-left 2x2 of the projection is all 0:
* P = A 0 C 0
* 0 B D 0
* 0 0 E F
* 0 0 G H
*
* It result that we need to calculate E and F while leaving all other parameter unchanged.
*
* We know that:
* with N, F the near/far normalized plane equation parameters
* sn, sf arbitrary scale factors (they don't affect the planes)
* m is the transpose of projection (see Frustum.cpp)
*
* sn * N == -m[3] - m[2]
* sf * F == -m[3] + m[2]
*
* sn * N + sf * F == -2 * m[3]
* sn * N - sf * F == -2 * m[2]
*
* sn * N.z + sf * F.z == -2 * m[3].z
* sn * N.w + sf * F.w == -2 * m[3].w
* sn * N.z - sf * F.z == -2 * m[2].z
* sn * N.w - sf * F.w == -2 * m[2].w
*
* sn * N.z + sf * F.z == -2 * p[2].w
* sn * N.w + sf * F.w == -2 * p[3].w
* sn * N.z - sf * F.z == -2 * p[2].z
* sn * N.w - sf * F.w == -2 * p[3].z
*
* We now need to solve for { p[2].z, p[3].z, sn, sf } :
*
* sn == -2 * (p[2].w * F.w - p[3].w * F.z) / (N.z * F.w - N.w * F.z)
* sf == -2 * (p[2].w * N.w - p[3].w * N.z) / (F.z * N.w - F.w * N.z)
* p[2].z == (sf * F.z - sn * N.z) / 2
* p[3].z == (sf * F.w - sn * N.w) / 2
*/
auto& p = cameraInfo.cullingProjection;
float4 const N = { 0, 0, 1, n }; // near plane equation
float4 const F = { 0, 0, -1, -f }; // far plane equation
// near plane equation scale factor
float const sn = -2.0f * (p[2].w * F.w - p[3].w * F.z) / (N.z * F.w - N.w * F.z);
// far plane equation scale factor
float const sf = -2.0f * (p[2].w * N.w - p[3].w * N.z) / (F.z * N.w - F.w * N.z);
// New values for the projection
p[2].z = (sf * F.z - sn * N.z) * 0.5f;
p[3].z = (sf * F.w - sn * N.w) * 0.5f;
updateNearFarPlanes(&cameraInfo.cullingProjection, cameraInfo.zn, cameraInfo.zf);
}
const ShadowMap::ShadowMapInfo shadowMapInfo{
@@ -631,8 +594,10 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
// Compute scene-dependent values shared across all cascades
ShadowMap::updateSceneInfoDirectional(MvAtOrigin, *scene, sceneInfo);
// we always do culling without depth clamp, because objects behind the camera
// must be rendered regardless
shadowMap.updateDirectional(engine,
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo);
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo, false);
hasVisibleShadows = shadowMap.hasVisibleShadows();
@@ -648,15 +613,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
uint32_t cascadeHasVisibleShadows = 0;
if (hasVisibleShadows) {
// Adjust the near and far planes to tightly bound the scene.
float vsNear = -cameraInfo.zn;
float vsFar = -cameraInfo.zf;
if (engine.debug.shadowmap.tightly_bound_scene && !params.options.stable) {
vsNear = std::min(vsNear, sceneInfo.vsNearFar.x);
vsFar = std::max(vsFar, sceneInfo.vsNearFar.y);
}
const size_t cascadeCount = cascadedShadowMaps.size();
uint32_t const cascadeCount = cascadedShadowMaps.size();
// We divide the camera frustum into N cascades. This gives us N + 1 split positions.
// The first split position is the near plane; the last split position is the far plane.
@@ -667,9 +624,8 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
}
const CascadeSplits splits({
.proj = cameraInfo.cullingProjection,
.near = vsNear,
.far = vsFar,
.near = -cameraInfo.zn,
.far = -cameraInfo.zf,
.cascadeCount = cascadeCount,
.splitPositions = splitPercentages
});
@@ -679,10 +635,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
static_assert(CONFIG_MAX_SHADOW_CASCADES <= 5,
"At most, a float4 can fit 4 split positions for 5 shadow cascades");
float4 wsSplitPositionUniform{ -std::numeric_limits<float>::infinity() };
std::copy(splits.beginWs() + 1, splits.endWs(), &wsSplitPositionUniform[0]);
float csSplitPosition[CONFIG_MAX_SHADOW_CASCADES + 1];
std::copy(splits.beginCs(), splits.endCs(), csSplitPosition);
std::copy(splits.begin() + 1, splits.end(), &wsSplitPositionUniform[0]);
mShadowMappingUniforms.cascadeSplits = wsSplitPositionUniform;
@@ -695,10 +648,20 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
ShadowMap& shadowMap = cascadedShadowMaps[i];
assert_invariant(shadowMap.getLightIndex() == 0);
sceneInfo.csNearFar = { csSplitPosition[i], csSplitPosition[i + 1] };
// update cameraInfo culling projection for the cascade
float const* nearFarPlanes = splits.begin();
cameraInfo.zn = -nearFarPlanes[i];
cameraInfo.zf = -nearFarPlanes[i + 1];
updateNearFarPlanes(&cameraInfo.cullingProjection, cameraInfo.zn, cameraInfo.zf);
bool const canUseDepthClamp =
!view.hasVSM() &&
mIsDepthClampSupported &&
engine.debug.shadowmap.depth_clamp;
auto shaderParameters = shadowMap.updateDirectional(engine,
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo);
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo,
canUseDepthClamp);
if (shadowMap.hasVisibleShadows()) {
const size_t shadowIndex = shadowMap.getShadowIndex();
@@ -1069,8 +1032,78 @@ ShadowMapManager::CascadeSplits::CascadeSplits(Params const& params) noexcept
: mSplitCount(params.cascadeCount + 1) {
for (size_t s = 0; s < mSplitCount; s++) {
mSplitsWs[s] = params.near + (params.far - params.near) * params.splitPositions[s];
mSplitsCs[s] = mat4f::project(params.proj, float3(0.0f, 0.0f, mSplitsWs[s])).z;
}
}
void ShadowMapManager::updateNearFarPlanes(mat4f* projection,
float nearDistance, float farDistance) noexcept {
float const n = nearDistance;
float const f = farDistance;
/*
* Updating a projection matrix near and far planes:
*
* We assume that the near and far plane equations are of the form:
* N = { 0, 0, 1, n }
* F = { 0, 0, -1, -f }
*
* We also assume that the lower-left 2x2 of the projection is all 0:
* P = A 0 C 0
* 0 B D 0
* 0 0 E F
* 0 0 G H
*
* It result that we need to calculate E and F while leaving all other parameter unchanged.
*
* We know that:
* with N, F the near/far normalized plane equation parameters
* sn, sf arbitrary scale factors (they don't affect the planes)
* m is the transpose of projection (see Frustum.cpp)
*
* sn * N == -m[3] - m[2]
* sf * F == -m[3] + m[2]
*
* sn * N + sf * F == -2 * m[3]
* sn * N - sf * F == -2 * m[2]
*
* sn * N.z + sf * F.z == -2 * m[3].z
* sn * N.w + sf * F.w == -2 * m[3].w
* sn * N.z - sf * F.z == -2 * m[2].z
* sn * N.w - sf * F.w == -2 * m[2].w
*
* sn * N.z + sf * F.z == -2 * p[2].w
* sn * N.w + sf * F.w == -2 * p[3].w
* sn * N.z - sf * F.z == -2 * p[2].z
* sn * N.w - sf * F.w == -2 * p[3].z
*
* We now need to solve for { p[2].z, p[3].z, sn, sf } :
*
* sn == -2 * (p[2].w * F.w - p[3].w * F.z) / (N.z * F.w - N.w * F.z)
* sf == -2 * (p[2].w * N.w - p[3].w * N.z) / (F.z * N.w - F.w * N.z)
* p[2].z == (sf * F.z - sn * N.z) / 2
* p[3].z == (sf * F.w - sn * N.w) / 2
*/
auto& p = *projection;
float4 const N = { 0, 0, 1, n }; // near plane equation
float4 const F = { 0, 0, -1, -f }; // far plane equation
// near plane equation scale factor
float const sn = -2.0f * (p[2].w * F.w - p[3].w * F.z) / (N.z * F.w - N.w * F.z);
// far plane equation scale factor
float const sf = -2.0f * (p[2].w * N.w - p[3].w * N.z) / (F.z * N.w - F.w * N.z);
// New values for the projection
p[2].z = (sf * F.z - sn * N.z) * 0.5f;
p[3].z = (sf * F.w - sn * N.w) * 0.5f;
}
utils::FixedCapacityVector<Camera const*>
ShadowMapManager::getDirectionalShadowCameras() const noexcept {
if (!mInitialized) return {};
auto const csm = getCascadedShadowMap();
auto result = utils::FixedCapacityVector<Camera const*>::with_capacity(csm.size());
for (ShadowMap const& sm : csm) {
result.push_back(sm.hasVisibleShadows() ? sm.getDebugCamera() : nullptr);
}
return result;
}
} // namespace filament
+16 -15
View File
@@ -40,6 +40,7 @@
#include <utils/BitmaskEnum.h>
#include <utils/compiler.h>
#include <utils/FixedCapacityVector.h>
#include <utils/debug.h>
#include <utils/Range.h>
#include <utils/Slice.h>
@@ -58,6 +59,7 @@
namespace filament {
class Camera;
class FCamera;
class FView;
class FrameGraph;
@@ -138,16 +140,16 @@ public:
bool hasSpotShadows() const { return !mSpotShadowMapCount; }
// for debugging only
FCamera const* getDirectionalShadowCamera() const noexcept {
if (!mInitialized) return nullptr;
return getShadowMap(0).getDebugCamera();
}
utils::FixedCapacityVector<Camera const*> getDirectionalShadowCameras() const noexcept;
private:
explicit ShadowMapManager(FEngine& engine);
void terminate(FEngine& engine);
static void updateNearFarPlanes(math::mat4f* projection,
float nearDistance, float farDistance) noexcept;
ShadowMapManager::ShadowTechnique updateCascadeShadowMaps(FEngine& engine,
FView& view, CameraInfo cameraInfo, FScene::RenderableSoa& renderableData,
FScene::LightSoa const& lightData, ShadowMap::SceneInfo sceneInfo) noexcept;
@@ -186,27 +188,21 @@ private:
constexpr static size_t SPLIT_COUNT = CONFIG_MAX_SHADOW_CASCADES + 1;
struct Params {
math::mat4f proj;
float near = 0.0f;
float far = 0.0f;
size_t cascadeCount = 1;
uint32_t cascadeCount = 1;
std::array<float, SPLIT_COUNT> splitPositions = { 0.0f };
};
explicit CascadeSplits(Params const& params) noexcept;
// Split positions in world-space.
const float* beginWs() const { return mSplitsWs; }
const float* endWs() const { return mSplitsWs + mSplitCount; }
// Split positions in clip-space.
const float* beginCs() const { return mSplitsCs; }
const float* endCs() const { return mSplitsCs + mSplitCount; }
const float* begin() const { return mSplitsWs; }
const float* end() const { return mSplitsWs + mSplitCount; }
private:
float mSplitsWs[SPLIT_COUNT];
float mSplitsCs[SPLIT_COUNT];
size_t mSplitCount;
uint32_t mSplitCount;
};
// Atlas requirements, updated in ShadowMapManager::update(),
@@ -236,6 +232,7 @@ private:
ShadowMapCacheContainer mShadowMapCache;
uint32_t mDirectionalShadowMapCount = 0;
uint32_t mSpotShadowMapCount = 0;
bool const mIsDepthClampSupported;
bool mInitialized = false;
ShadowMap& getShadowMap(size_t index) noexcept {
@@ -248,10 +245,14 @@ private:
}
utils::Slice<ShadowMap> getCascadedShadowMap() noexcept {
ShadowMap* const p = &getShadowMap(0);
ShadowMap const* const p = &getShadowMap(0);
return { p, mDirectionalShadowMapCount };
}
utils::Slice<ShadowMap> getCascadedShadowMap() const noexcept {
return const_cast<ShadowMapManager*>(this)->getCascadedShadowMap();
}
utils::Slice<ShadowMap> getSpotShadowMaps() noexcept {
ShadowMap* const p = &getShadowMap(CONFIG_MAX_SHADOW_CASCADES);
return { p, mSpotShadowMapCount };
+8 -2
View File
@@ -15,6 +15,8 @@
*/
#include "details/View.h"
#include "filament/View.h"
namespace filament {
@@ -71,8 +73,8 @@ const char* View::getName() const noexcept {
return downcast(this)->getName();
}
Camera const* View::getDirectionalShadowCamera() const noexcept {
return downcast(this)->getDirectionalShadowCamera();
utils::FixedCapacityVector<Camera const*> View::getDirectionalShadowCameras() const noexcept {
return downcast(this)->getDirectionalShadowCameras();
}
void View::setShadowingEnabled(bool enabled) noexcept {
@@ -312,4 +314,8 @@ utils::Entity View::getFogEntity() const noexcept {
return downcast(this)->getFogEntity();
}
void View::clearFrameHistory(Engine& engine) noexcept {
downcast(this)->clearFrameHistory(downcast(engine));
}
} // namespace filament
@@ -588,6 +588,9 @@ void FRenderableManager::create(
// full size of the UBO.
instances.handle = driver.createBufferObject(sizeof(PerRenderableUib),
BufferObjectBinding::UNIFORM, backend::BufferUsage::DYNAMIC);
if (auto name = instances.buffer->getName(); !name.empty()) {
driver.setDebugTag(instances.handle.getId(), std::move(name));
}
}
const uint32_t boneCount = builder->mSkinningBoneCount;
+5
View File
@@ -20,6 +20,8 @@
#include "FilamentAPI-impl.h"
#include <utils/CString.h>
namespace filament {
struct BufferObject::BuilderDetails {
@@ -56,6 +58,9 @@ FBufferObject::FBufferObject(FEngine& engine, const BufferObject::Builder& build
FEngine::DriverApi& driver = engine.getDriverApi();
mHandle = driver.createBufferObject(builder->mByteCount, builder->mBindingType,
backend::BufferUsage::STATIC);
if (auto name = builder.getName(); !name.empty()) {
driver.setDebugTag(mHandle.getId(), std::move(name));
}
}
void FBufferObject::terminate(FEngine& engine) {
+25 -20
View File
@@ -28,12 +28,6 @@
#include <string_view>
#include <utility>
#ifndef NDEBUG
# define DEBUG_PROPERTIES_WRITABLE true
#else
# define DEBUG_PROPERTIES_WRITABLE false
#endif
using namespace filament::math;
using namespace utils;
@@ -79,17 +73,15 @@ bool FDebugRegistry::hasProperty(const char* name) const noexcept {
template<typename T>
bool FDebugRegistry::setProperty(const char* name, T v) noexcept {
if constexpr (DEBUG_PROPERTIES_WRITABLE) {
auto info = getPropertyInfo(name);
T* const addr = static_cast<T*>(info.first);
if (addr) {
auto old = *addr;
*addr = v;
if (info.second && old != v) {
info.second();
}
return true;
auto info = getPropertyInfo(name);
T* const addr = static_cast<T*>(info.first);
if (addr) {
auto old = *addr;
*addr = v;
if (info.second && old != v) {
info.second();
}
return true;
}
return false;
}
@@ -118,19 +110,32 @@ template bool FDebugRegistry::getProperty<float2>(const char* name, float2* v) c
template bool FDebugRegistry::getProperty<float3>(const char* name, float3* v) const noexcept;
template bool FDebugRegistry::getProperty<float4>(const char* name, float4* v) const noexcept;
void FDebugRegistry::registerDataSource(std::string_view name,
bool FDebugRegistry::registerDataSource(std::string_view name,
void const* data, size_t count) noexcept {
auto& dataSourceMap = mDataSourceMap;
if (dataSourceMap.find(name) == dataSourceMap.end()) {
bool const found = dataSourceMap.find(name) == dataSourceMap.end();
if (found) {
dataSourceMap[name] = { data, count };
}
return found;
}
void FDebugRegistry::registerDataSource(std::string_view name,
bool FDebugRegistry::registerDataSource(std::string_view name,
utils::Invocable<DataSource()>&& creator) noexcept {
mDataSourceCreatorMap[name] = std::move(creator);
auto& dataSourceCreatorMap = mDataSourceCreatorMap;
bool const found = dataSourceCreatorMap.find(name) == dataSourceCreatorMap.end();
if (found) {
dataSourceCreatorMap[name] = std::move(creator);
}
return found;
}
void FDebugRegistry::unregisterDataSource(std::string_view name) noexcept {
mDataSourceCreatorMap.erase(name);
mDataSourceMap.erase(name);
}
DebugRegistry::DataSource FDebugRegistry::getDataSource(const char* name) const noexcept {
std::string_view const key{ name };
auto& dataSourceMap = mDataSourceMap;
+4 -2
View File
@@ -101,12 +101,14 @@ public:
}
// registers a DataSource directly
void registerDataSource(std::string_view name, void const* data, size_t count) noexcept;
bool registerDataSource(std::string_view name, void const* data, size_t count) noexcept;
// registers a DataSource lazily
void registerDataSource(std::string_view name,
bool registerDataSource(std::string_view name,
utils::Invocable<DataSource()>&& creator) noexcept;
void unregisterDataSource(std::string_view name) noexcept;
#if !defined(_MSC_VER)
private:
#endif
+28 -17
View File
@@ -71,7 +71,7 @@ struct Engine::BuilderDetails {
FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1;
void* mSharedContext = nullptr;
bool mPaused = false;
static Config validateConfig(const Config* pConfig) noexcept;
static Config validateConfig(Config config) noexcept;
};
Engine* FEngine::create(Engine::Builder const& builder) {
@@ -163,8 +163,7 @@ FEngine* FEngine::getEngine(void* token) {
FILAMENT_CHECK_PRECONDITION(ThreadUtils::isThisThread(instance->mMainThreadId))
<< "Engine::createAsync() and Engine::getEngine() must be called on the same thread.";
// we use mResourceAllocator as a proxy for "am I already initialized"
if (!instance->mResourceAllocator) {
if (!instance->mInitialized) {
if (UTILS_UNLIKELY(!instance->mDriver)) {
// something went horribly wrong during driver initialization
instance->mDriverThread.join();
@@ -262,7 +261,7 @@ void FEngine::init() {
slog.i << "FEngine feature level: " << int(mActiveFeatureLevel) << io::endl;
mResourceAllocator = new ResourceAllocator(mConfig, driverApi);
mResourceAllocatorDisposer = std::make_shared<ResourceAllocatorDisposer>(driverApi);
mFullScreenTriangleVb = downcast(VertexBuffer::Builder()
.vertexCount(3)
@@ -425,11 +424,13 @@ void FEngine::init() {
}
});
});
mInitialized = true;
}
FEngine::~FEngine() noexcept {
SYSTRACE_CALL();
delete mResourceAllocator;
assert_invariant(!mResourceAllocatorDisposer);
delete mDriver;
if (mOwnPlatform) {
PlatformFactory::destroy(&mPlatform);
@@ -440,7 +441,7 @@ void FEngine::shutdown() {
SYSTRACE_CALL();
// by construction this should never be nullptr
assert_invariant(mResourceAllocator);
assert_invariant(mResourceAllocatorDisposer);
FILAMENT_CHECK_PRECONDITION(ThreadUtils::isThisThread(mMainThreadId))
<< "Engine::shutdown() called from the wrong thread!";
@@ -460,7 +461,8 @@ void FEngine::shutdown() {
*/
mPostProcessManager.terminate(driver); // free-up post-process manager resources
mResourceAllocator->terminate();
mResourceAllocatorDisposer->terminate();
mResourceAllocatorDisposer.reset();
mDFG.terminate(*this); // free-up the DFG
mRenderableManager.terminate(); // free-up all renderables
mLightManager.terminate(); // free-up all lights
@@ -1195,6 +1197,22 @@ bool FEngine::isValid(const FInstanceBuffer* p) const {
return isValid(p, mInstanceBuffers);
}
size_t FEngine::getBufferObjectCount() const noexcept { return mBufferObjects.size(); }
size_t FEngine::getViewCount() const noexcept { return mViews.size(); }
size_t FEngine::getSceneCount() const noexcept { return mScenes.size(); }
size_t FEngine::getSwapChainCount() const noexcept { return mSwapChains.size(); }
size_t FEngine::getStreamCount() const noexcept { return mStreams.size(); }
size_t FEngine::getIndexBufferCount() const noexcept { return mIndexBuffers.size(); }
size_t FEngine::getSkinningBufferCount() const noexcept { return mSkinningBuffers.size(); }
size_t FEngine::getMorphTargetBufferCount() const noexcept { return mMorphTargetBuffers.size(); }
size_t FEngine::getInstanceBufferCount() const noexcept { return mInstanceBuffers.size(); }
size_t FEngine::getVertexBufferCount() const noexcept { return mVertexBuffers.size(); }
size_t FEngine::getIndirectLightCount() const noexcept { return mIndirectLights.size(); }
size_t FEngine::getMaterialCount() const noexcept { return mMaterials.size(); }
size_t FEngine::getTextureCount() const noexcept { return mTextures.size(); }
size_t FEngine::getSkyboxeCount() const noexcept { return mSkyboxes.size(); }
size_t FEngine::getColorGradingCount() const noexcept { return mColorGradings.size(); }
size_t FEngine::getRenderTargetCount() const noexcept { return mRenderTargets.size(); }
void* FEngine::streamAlloc(size_t size, size_t alignment) noexcept {
// we allow this only for small allocations
@@ -1284,7 +1302,7 @@ Engine::Builder& Engine::Builder::platform(Platform* platform) noexcept {
}
Engine::Builder& Engine::Builder::config(Engine::Config const* config) noexcept {
mImpl->mConfig = BuilderDetails::validateConfig(config);
mImpl->mConfig = config ? *config : Engine::Config{};
return *this;
}
@@ -1312,22 +1330,15 @@ void Engine::Builder::build(Invocable<void(void*)>&& callback) const {
#endif
Engine* Engine::Builder::build() const {
mImpl->mConfig = BuilderDetails::validateConfig(mImpl->mConfig);
return FEngine::create(*this);
}
Engine::Config Engine::BuilderDetails::validateConfig(const Config* const pConfig) noexcept {
Engine::Config Engine::BuilderDetails::validateConfig(Config config) noexcept {
// Rule of thumb: perRenderPassArenaMB must be roughly 1 MB larger than perFrameCommandsMB
constexpr uint32_t COMMAND_ARENA_OVERHEAD = 1;
constexpr uint32_t CONCURRENT_FRAME_COUNT = 3;
Config config;
if (!pConfig) {
return config;
}
// make sure to copy all the fields
config = *pConfig;
// Use at least the defaults set by the build system
config.minCommandBufferSizeMB = std::max(
config.minCommandBufferSizeMB,
+32 -6
View File
@@ -86,6 +86,7 @@ namespace filament {
class Renderer;
class MaterialParser;
class ResourceAllocatorDisposer;
namespace backend {
class Driver;
@@ -254,9 +255,13 @@ public:
}
}
ResourceAllocator& getResourceAllocator() noexcept {
assert_invariant(mResourceAllocator);
return *mResourceAllocator;
ResourceAllocatorDisposer& getResourceAllocatorDisposer() noexcept {
assert_invariant(mResourceAllocatorDisposer);
return *mResourceAllocatorDisposer;
}
std::shared_ptr<ResourceAllocatorDisposer> const& getSharedResourceAllocatorDisposer() noexcept {
return mResourceAllocatorDisposer;
}
void* streamAlloc(size_t size, size_t alignment) noexcept;
@@ -346,6 +351,23 @@ public:
bool isValid(const FView* p) const;
bool isValid(const FInstanceBuffer* p) const;
size_t getBufferObjectCount() const noexcept;
size_t getViewCount() const noexcept;
size_t getSceneCount() const noexcept;
size_t getSwapChainCount() const noexcept;
size_t getStreamCount() const noexcept;
size_t getIndexBufferCount() const noexcept;
size_t getSkinningBufferCount() const noexcept;
size_t getMorphTargetBufferCount() const noexcept;
size_t getInstanceBufferCount() const noexcept;
size_t getVertexBufferCount() const noexcept;
size_t getIndirectLightCount() const noexcept;
size_t getMaterialCount() const noexcept;
size_t getTextureCount() const noexcept;
size_t getSkyboxeCount() const noexcept;
size_t getColorGradingCount() const noexcept;
size_t getRenderTargetCount() const noexcept;
void destroy(utils::Entity e);
bool isPaused() const noexcept;
@@ -490,7 +512,7 @@ private:
FTransformManager mTransformManager;
FLightManager mLightManager;
FCameraManager mCameraManager;
ResourceAllocator* mResourceAllocator = nullptr;
std::shared_ptr<ResourceAllocatorDisposer> mResourceAllocatorDisposer;
HwVertexBufferInfoFactory mHwVertexBufferInfoFactory;
ResourceList<FBufferObject> mBufferObjects{ "BufferObject" };
@@ -562,11 +584,14 @@ private:
std::thread::id mMainThreadId{};
bool mInitialized = false;
// Creation parameters
Config mConfig;
public:
// these are the debug properties used by FDebug. They're accessed directly by modules who need them.
// These are the debug properties used by FDebug.
// They're accessed directly by modules who need them.
struct {
struct {
bool debug_directional_shadowmap = false;
@@ -574,7 +599,8 @@ public:
bool far_uses_shadowcasters = true;
bool focus_shadowcasters = true;
bool visualize_cascades = false;
bool tightly_bound_scene = true;
bool disable_light_frustum_align = false;
bool depth_clamp = true;
float dzn = -1.0f;
float dzf = 1.0f;
float display_shadow_texture_scale = 0.25f;
+5
View File
@@ -20,6 +20,8 @@
#include "FilamentAPI-impl.h"
#include <utils/CString.h>
namespace filament {
struct IndexBuffer::BuilderDetails {
@@ -58,6 +60,9 @@ FIndexBuffer::FIndexBuffer(FEngine& engine, const IndexBuffer::Builder& builder)
(backend::ElementType)builder->mIndexType,
uint32_t(builder->mIndexCount),
backend::BufferUsage::STATIC);
if (auto name = builder.getName(); !name.empty()) {
driver.setDebugTag(mHandle.getId(), std::move(name));
}
}
void FIndexBuffer::terminate(FEngine& engine) {
+2 -1
View File
@@ -61,7 +61,8 @@ InstanceBuffer* InstanceBuffer::Builder::build(Engine& engine) {
// ------------------------------------------------------------------------------------------------
FInstanceBuffer::FInstanceBuffer(FEngine& engine, const Builder& builder) {
FInstanceBuffer::FInstanceBuffer(FEngine& engine, const Builder& builder)
: mName(builder.getName()) {
mInstanceCount = builder->mInstanceCount;
mLocalTransforms.reserve(mInstanceCount);
+4
View File
@@ -25,6 +25,7 @@
#include <math/mat4.h>
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
namespace filament {
@@ -46,10 +47,13 @@ public:
void prepare(FEngine& engine, math::mat4f rootTransform, const PerRenderableData& ubo,
backend::Handle<backend::HwBufferObject> handle);
utils::CString const& getName() const noexcept { return mName; }
private:
friend class RenderableManager;
utils::FixedCapacityVector<math::mat4f> mLocalTransforms;
utils::CString mName;
size_t mInstanceCount;
};
+27 -3
View File
@@ -192,6 +192,30 @@ Material* Material::Builder::build(Engine& engine) {
return nullptr;
}
// Print a warning if the material's stereo type doesn't align with the engine's setting.
MaterialDomain materialDomain;
UserVariantFilterMask variantFilterMask;
materialParser->getMaterialDomain(&materialDomain);
materialParser->getMaterialVariantFilterMask(&variantFilterMask);
bool const hasStereoVariants = !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::STE));
if (materialDomain == MaterialDomain::SURFACE && hasStereoVariants) {
StereoscopicType const engineStereoscopicType = engine.getConfig().stereoscopicType;
// Default materials are always compiled with either 'instanced' or 'multiview'.
// So, we only verify compatibility if the engine is set up for stereo.
if (engineStereoscopicType != StereoscopicType::NONE) {
StereoscopicType materialStereoscopicType = StereoscopicType::NONE;
materialParser->getStereoscopicType(&materialStereoscopicType);
if (materialStereoscopicType != engineStereoscopicType) {
CString name;
materialParser->getName(&name);
slog.w << "The stereoscopic type in the compiled material '" << name.c_str_safe()
<< "' is " << (int)materialStereoscopicType
<< ", which is not compatiable with the engine's setting "
<< (int)engineStereoscopicType << "." << io::endl;
}
}
}
return downcast(engine).createMaterial(*this, std::move(materialParser));
}
@@ -200,8 +224,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
: mIsDefaultMaterial(builder->mDefaultMaterial),
mEngine(engine),
mMaterialId(engine.getMaterialId()),
mMaterialParser(std::move(materialParser))
{
mMaterialParser(std::move(materialParser)) {
MaterialParser* const parser = mMaterialParser.get();
UTILS_UNUSED_IN_RELEASE bool const nameOk = parser->getName(&mName);
@@ -595,6 +618,7 @@ Program FMaterial::getProgramWithVariants(
void FMaterial::createAndCacheProgram(Program&& p, Variant variant) const noexcept {
auto program = mEngine.getDriverApi().createProgram(std::move(p));
mEngine.getDriverApi().setDebugTag(program.getId(), mName);
assert_invariant(program);
mCachedPrograms[variant.key] = program;
}
@@ -873,7 +897,7 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Material::Builde
engine.getDriverApi().getMaxUniformBufferSize() / 16u);
bool const staticTextureWorkaround =
engine.getDriverApi().isWorkaroundNeeded(Workaround::A8X_STATIC_TEXTURE_TARGET_ERROR);
engine.getDriverApi().isWorkaroundNeeded(Workaround::METAL_STATIC_TEXTURE_TARGET_ERROR);
bool const powerVrShaderWorkarounds =
engine.getDriverApi().isWorkaroundNeeded(Workaround::POWER_VR_SHADER_WORKAROUNDS);
@@ -51,6 +51,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material)
mUniforms = UniformBuffer(material->getUniformInterfaceBlock().getSize());
mUbHandle = driver.createBufferObject(mUniforms.getSize(),
BufferObjectBinding::UNIFORM, backend::BufferUsage::STATIC);
driver.setDebugTag(mUbHandle.getId(), material->getName());
}
if (!material->getSamplerInterfaceBlock().isEmpty()) {
@@ -116,6 +117,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
mUniforms.setUniforms(other->getUniformBuffer());
mUbHandle = driver.createBufferObject(mUniforms.getSize(),
BufferObjectBinding::UNIFORM, backend::BufferUsage::DYNAMIC);
driver.setDebugTag(mUbHandle.getId(), material->getName());
}
if (!material->getSamplerInterfaceBlock().isEmpty()) {
@@ -25,6 +25,8 @@
#include <math/mat4.h>
#include <math/norm.h>
#include <utils/CString.h>
namespace filament {
using namespace backend;
@@ -124,6 +126,11 @@ FMorphTargetBuffer::FMorphTargetBuffer(FEngine& engine, const Builder& builder)
mCount,
TextureUsage::DEFAULT);
if (auto name = builder.getName(); !name.empty()) {
driver.setDebugTag(mPbHandle.getId(), name);
driver.setDebugTag(mTbHandle.getId(), std::move(name));
}
// create and update sampler group
mSbHandle = driver.createSamplerGroup(PerRenderPrimitiveMorphingSib::SAMPLER_COUNT,
utils::FixedSizeString<32>("Morph target samplers"));
+124 -43
View File
@@ -60,6 +60,7 @@
#include <chrono>
#include <limits>
#include <memory>
#include <utility>
#include <stddef.h>
@@ -88,7 +89,11 @@ FRenderer::FRenderer(FEngine& engine) :
mHdrQualityMedium(TextureFormat::R11F_G11F_B10F),
mHdrQualityHigh(TextureFormat::RGB16F),
mIsRGB8Supported(false),
mUserEpoch(engine.getEngineEpoch())
mUserEpoch(engine.getEngineEpoch()),
mResourceAllocator(std::make_unique<ResourceAllocator>(
engine.getSharedResourceAllocatorDisposer(),
engine.getConfig(),
engine.getDriverApi()))
{
FDebugRegistry& debugRegistry = engine.getDebugRegistry();
debugRegistry.registerProperty("d.renderer.doFrameCapture",
@@ -176,6 +181,7 @@ void FRenderer::terminate(FEngine& engine) {
}
mFrameInfoManager.terminate(driver);
mFrameSkipper.terminate(driver);
mResourceAllocator->terminate();
}
void FRenderer::resetUserTime() {
@@ -236,6 +242,39 @@ void FRenderer::setVsyncTime(uint64_t steadyClockTimeNano) noexcept {
mVsyncSteadyClockTimeNano = steadyClockTimeNano;
}
void FRenderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) {
SYSTRACE_CALL();
FILAMENT_CHECK_PRECONDITION(!mSwapChain) <<
"skipFrame() can't be called between beginFrame() and endFrame()";
if (!vsyncSteadyClockTimeNano) {
vsyncSteadyClockTimeNano = mVsyncSteadyClockTimeNano;
mVsyncSteadyClockTimeNano = 0;
}
FEngine& engine = mEngine;
FEngine::DriverApi& driver = engine.getDriverApi();
// Gives the backend a chance to execute periodic tasks. This must be called before
// the frame skipper.
driver.tick();
// do this before engine.flush()
mResourceAllocator->gc(true);
// Run the component managers' GC in parallel
// WARNING: while doing this we can't access any component manager
auto& js = engine.getJobSystem();
auto *job = js.runAndRetain(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers
engine.flush(); // flush command stream
// make sure we're done with the gcs
js.waitAndRelease(job);
}
bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano) {
assert_invariant(swapChain);
@@ -305,10 +344,14 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN
* to ignore the return value and render the frame anyway -- which is perfectly fine.
* The remaining work will be done when the first render() call is made.
*/
auto beginFrameInternal = [this, appVsync]() {
auto beginFrameInternal = [this, appVsync, swapChain]() {
FEngine& engine = mEngine;
FEngine::DriverApi& driver = engine.getDriverApi();
// we need to re-set mSwapChain here because if a frame was marked as "skip" but the
// user ignored us, we'd still want mSwapChain to be set.
mSwapChain = swapChain;
driver.beginFrame(
appVsync.time_since_epoch().count(),
mDisplayInfo.refreshRate == 0.0 ? 0 : int64_t(
@@ -337,6 +380,10 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN
// so we need to delay this work until that happens.
mBeginFrameInternal = beginFrameInternal;
// clear mSwapChain because the frame should be skipped (it will be re-set if the user
// ignores the skip)
mSwapChain = nullptr;
// we need to flush in this case, to make sure the tick() call is executed at some point
engine.flush();
@@ -380,7 +427,7 @@ void FRenderer::endFrame() {
}
// do this before engine.flush()
engine.getResourceAllocator().gc();
mResourceAllocator->gc();
// Run the component managers' GC in parallel
// WARNING: while doing this we can't access any component manager
@@ -495,8 +542,6 @@ void FRenderer::renderStandaloneView(FView const* view) {
void FRenderer::render(FView const* view) {
SYSTRACE_CALL();
assert_invariant(mSwapChain);
if (UTILS_UNLIKELY(mBeginFrameInternal)) {
// this should not happen, the user should not call render() if we returned false from
// beginFrame(). But because this is allowed, we handle it gracefully.
@@ -504,6 +549,9 @@ void FRenderer::render(FView const* view) {
mBeginFrameInternal = {};
}
// after beginFrame() is called, mSwapChain should be true
assert_invariant(mSwapChain);
if (UTILS_LIKELY(view && view->getScene() && view->hasCamera())) {
if (mViewRenderedCount) {
// This is a good place to kick the GPU, since we've rendered a View before,
@@ -603,20 +651,25 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
const bool isProtectedContent = mSwapChain && mSwapChain->isProtected();
// Conditions to meet to be able to use the sub-pass rendering path. This is regardless of
// whether the backend supports subpasses (or if they are disabled in the debugRegistry).
const bool isSubpassPossible =
msaaSampleCount <= 1 &&
hasColorGrading &&
!bloomOptions.enabled && !dofOptions.enabled && !taaOptions.enabled;
// asSubpass is disabled with TAA (although it's supported) because performance was degraded
// on qualcomm hardware -- we might need a backend dependent toggle at some point
const PostProcessManager::ColorGradingConfig colorGradingConfig{
.asSubpass =
hasColorGrading &&
msaaSampleCount <= 1 &&
!bloomOptions.enabled && !dofOptions.enabled && !taaOptions.enabled &&
isSubpassPossible &&
driver.isFrameBufferFetchSupported() &&
!engine.debug.renderer.disable_subpasses,
.customResolve =
msaaOptions.customResolve &&
msaaSampleCount > 1 &&
hasColorGrading &&
driver.isFrameBufferFetchMultiSampleSupported() &&
msaaOptions.customResolve &&
hasColorGrading &&
!engine.debug.renderer.disable_subpasses,
.translucent = needsAlphaChannel,
.fxaa = hasFXAA,
@@ -625,6 +678,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
TextureFormat::RGBA8 : getLdrFormat(needsAlphaChannel)
};
// by construction (msaaSampleCount) both asSubpass and customResolve can't be true
assert_invariant(colorGradingConfig.asSubpass + colorGradingConfig.customResolve < 2);
// whether we're scaled at all
bool scaled = any(notEqual(scale, float2(1.0f)));
@@ -647,13 +703,12 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
CameraInfo cameraInfo = view.computeCameraInfo(engine);
// when colorgrading-as-subpass is active, we know that many other effects are disabled
// such as dof, bloom. Moreover, if fxaa and scaling are not enabled, we're essentially in
// a very fast rendering path -- in this case, we would need an extra blit to "resolve" the
// buffer padding (because there are no other pass that can do it as a side effect).
// In this case, it is better to skip the padding, which won't be helping much.
const bool noBufferPadding = (colorGradingConfig.asSubpass && !hasFXAA && !scaled)
|| engine.debug.renderer.disable_buffer_padding;
// If fxaa and scaling are not enabled, we're essentially in a very fast rendering path -- in
// this case, we would need an extra blit to "resolve" the buffer padding (because there are no
// other pass that can do it as a side effect). In this case, it is better to skip the padding,
// which won't be helping much.
const bool noBufferPadding = (isSubpassPossible &&
!hasFXAA && !scaled) || engine.debug.renderer.disable_buffer_padding;
// guardBand must be a multiple of 16 to guarantee the same exact rendering up to 4 mip levels.
float const guardBand = guardBandOptions.enabled ? 16.0f : 0.0f;
@@ -750,7 +805,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
* Frame graph
*/
FrameGraph fg(engine.getResourceAllocator(),
FrameGraph fg(*mResourceAllocator,
isProtectedContent ? FrameGraph::Mode::PROTECTED : FrameGraph::Mode::UNPROTECTED);
auto& blackboard = fg.getBlackboard();
@@ -768,14 +823,14 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
blackboard["shadows"] = shadows;
}
// When we don't have a custom RenderTarget, currentRenderTarget below is nullptr and is
// When we don't have a custom RenderTarget, customRenderTarget below is nullptr and is
// recorded in the list of targets already rendered into -- this ensures that
// initializeClearFlags() is called only once for the default RenderTarget.
auto& previousRenderTargets = mPreviousRenderTargets;
FRenderTarget* const currentRenderTarget = downcast(view.getRenderTarget());
FRenderTarget* const customRenderTarget = downcast(view.getRenderTarget());
if (UTILS_LIKELY(
previousRenderTargets.find(currentRenderTarget) == previousRenderTargets.end())) {
previousRenderTargets.insert(currentRenderTarget);
previousRenderTargets.find(customRenderTarget) == previousRenderTargets.end())) {
previousRenderTargets.insert(customRenderTarget);
initializeClearFlags();
}
@@ -792,10 +847,10 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
const TargetBufferFlags keepOverrideStartFlags = TargetBufferFlags::ALL & ~discardStartFlags;
TargetBufferFlags keepOverrideEndFlags = TargetBufferFlags::NONE;
if (currentRenderTarget) {
if (customRenderTarget) {
// For custom RenderTarget, we look at each attachment flag and if they have their
// SAMPLEABLE usage bit set, we assume they must not be discarded after the render pass.
keepOverrideEndFlags |= currentRenderTarget->getSampleableAttachmentsMask();
keepOverrideEndFlags |= customRenderTarget->getSampleableAttachmentsMask();
}
// Renderer's ClearOptions apply once at the beginning of the frame (not for each View),
@@ -904,7 +959,6 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
.scale = aoOptions.resolution,
.picking = view.hasPicking()
});
blackboard["structure"] = structure;
const auto picking = picking_;
@@ -952,7 +1006,6 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
ssReflectionsOptions.enabled ? TextureFormat::RGBA16F : TextureFormat::R11F_G11F_B10F,
view.getCameraUser().getFieldOfView(Camera::Fov::VERTICAL), config.scale);
config.ssrLodOffset = ssrConfig.lodOffset;
blackboard["ssr"] = ssrConfig.ssr;
// --------------------------------------------------------------------------------------------
// screen-space reflections pass
@@ -966,6 +1019,10 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
{ .width = svp.width, .height = svp.height });
if (UTILS_LIKELY(reflections)) {
fg.addTrivialSideEffectPass("SSR Cleanup", [&view](DriverApi& driver) {
view.getPerViewUniforms().prepareStructure({});
view.commitUniforms(driver);
});
// generate the mipchain
PostProcessManager::generateMipmapSSR(ppm, fg,
reflections, ssrConfig.reflection, false, ssrConfig);
@@ -1056,24 +1113,45 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
);
// the color pass itself + color-grading as subpass if needed
auto colorPassOutput = RendererUtils::colorPass(fg, "Color Pass", mEngine, view,
auto colorPassOutput = RendererUtils::colorPass(fg, "Color Pass", mEngine, view, {
.shadows = blackboard.get<FrameGraphTexture>("shadows"),
.ssao = blackboard.get<FrameGraphTexture>("ssao"),
.ssr = ssrConfig.ssr,
.structure = structure
},
colorBufferDesc, config, colorGradingConfigForColor, pass.getExecutor());
if (view.isScreenSpaceRefractionEnabled() && !pass.empty()) {
// this cancels the colorPass() call above if refraction is active.
// the color pass + refraction + color-grading as subpass if needed
const auto [output, enabled] = RendererUtils::refractionPass(fg, mEngine, view,
// This cancels the colorPass() call above if refraction is active.
// The color pass + refraction + color-grading as subpass if needed
auto const output = RendererUtils::refractionPass(fg, mEngine, view, {
.shadows = blackboard.get<FrameGraphTexture>("shadows"),
.ssao = blackboard.get<FrameGraphTexture>("ssao"),
.ssr = ssrConfig.ssr,
.structure = structure
},
config, ssrConfig, colorGradingConfigForColor, pass);
colorPassOutput = output;
hasScreenSpaceRefraction = enabled;
hasScreenSpaceRefraction = output.has_value();
if (hasScreenSpaceRefraction) {
colorPassOutput = output.value();
}
}
fg.addTrivialSideEffectPass("Finish Color Passes", [&view](DriverApi& driver) {
// Unbind SSAO sampler, b/c the FrameGraph will delete the texture at the end of the pass.
view.cleanupRenderPasses();
view.commitUniforms(driver);
});
if (colorGradingConfig.customResolve) {
assert_invariant(fg.getDescriptor(colorPassOutput.linearColor).samples <= 1);
// TODO: we have to "uncompress" (i.e. detonemap) the color buffer here because it's used
// by many other passes (Bloom, TAA, DoF, etc...). We could make this more
// efficient by using ARM_shader_framebuffer_fetch. We use a load/store (i.e.
// subpass) here because it's more convenient.
colorPassOutput = ppm.customResolveUncompressPass(fg, colorPassOutput);
colorPassOutput.linearColor =
ppm.customResolveUncompressPass(fg, colorPassOutput.linearColor);
}
// export the color buffer if screen-space reflections are enabled
@@ -1089,7 +1167,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
// The "output" of this pass is going to be used during the next frame as
// an "import".
builder.sideEffect();
data.history = builder.sample(colorPassOutput); // FIXME: an access must be declared for detach(), why?
// we can't use colorPassOutput here because it could be tonemapped
data.history = builder.sample(colorPassOutput.linearColor); // FIXME: an access must be declared for detach(), why?
}, [&view, projection](FrameGraphResources const& resources, auto const& data,
backend::DriverApi&) {
auto& history = view.getFrameHistory();
@@ -1099,19 +1179,20 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
});
}
FrameGraphId<FrameGraphTexture> input = colorPassOutput;
fg.addTrivialSideEffectPass("Finish Color Passes", [&view](DriverApi& driver) {
// Unbind SSAO sampler, b/c the FrameGraph will delete the texture at the end of the pass.
view.cleanupRenderPasses();
view.commitUniforms(driver);
});
// this is the output of the color pass / input to post processing,
// this is only used later for comparing it with the output after post-processing
FrameGraphId<FrameGraphTexture> const postProcessInput = colorGradingConfig.asSubpass ?
colorPassOutput.tonemappedColor :
colorPassOutput.linearColor;
// input can change below
FrameGraphId<FrameGraphTexture> input = postProcessInput;
// Resolve depth -- which might be needed because of TAA or DoF. This pass will be culled
// if the depth is not used below or if the depth is not MS (e.g. it could have been
// auto-resolved).
// In practice, this is used on Vulkan and older Metal devices.
auto depth = blackboard.get<FrameGraphTexture>("depth");
depth = ppm.resolve(fg, "Resolved Depth Buffer", depth, { .levels = 1 });
auto depth = ppm.resolve(fg, "Resolved Depth Buffer", colorPassOutput.depth, { .levels = 1 });
// Debug: CSM visualisation
if (UTILS_UNLIKELY(engine.debug.shadowmap.visualize_cascades &&
@@ -1227,7 +1308,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
// The intermediate buffer is accomplished with a "fake" opaqueBlit (i.e. blit) operation.
const bool outputIsSwapChain =
(input == colorPassOutput) && (viewRenderTarget == mRenderTargetHandle);
(input == postProcessInput) && (viewRenderTarget == mRenderTargetHandle);
if (mightNeedFinalBlit) {
if (blendModeTranslucent ||
xvp != svp ||
+7
View File
@@ -46,6 +46,7 @@
#include <algorithm>
#include <chrono>
#include <functional>
#include <memory>
#include <utility>
#include <stddef.h>
@@ -53,6 +54,8 @@
namespace filament {
class ResourceAllocator;
namespace backend {
class Driver;
} // namespace backend
@@ -88,6 +91,9 @@ public:
void setVsyncTime(uint64_t steadyClockTimeNano) noexcept;
// skip a frame
void skipFrame(uint64_t vsyncSteadyClockTimeNano);
// start a frame
bool beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano);
@@ -206,6 +212,7 @@ private:
tsl::robin_set<FRenderTarget*> mPreviousRenderTargets;
std::function<void()> mBeginFrameInternal;
uint64_t mVsyncSteadyClockTimeNano = 0;
std::unique_ptr<ResourceAllocator> mResourceAllocator{};
};
FILAMENT_DOWNCAST(Renderer)
+1 -1
View File
@@ -248,7 +248,7 @@ void FScene::prepare(utils::JobSystem& js,
auto* renderableJob = jobs::parallel_for(js, rootJob,
renderableInstances.data(), renderableInstances.size(),
std::cref(renderableWork), jobs::CountSplitter<128, 5>());
std::cref(renderableWork), jobs::CountSplitter<64>());
auto* lightJob = jobs::parallel_for(js, rootJob,
lightInstances.data(), lightInstances.size(),

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