Compare commits

..
Author SHA1 Message Date
Benjamin Doherty d67e1a9ee8 Fix matc variantFilter 2022-11-03 15:53:48 -07:00
Benjamin Doherty dc44e20be0 Fix RELEASE_NOTES 2022-11-03 09:57:21 -07:00
daemyung jang e64b498d6e Update a comment about the range of morph target weight (#6265) 2022-11-03 09:19:01 -07:00
Mathias Agopian 31ab4a13c9 fix recently introduced crash on init
certain material properties can only be set if supported by the 
material.
2022-11-01 13:23:52 -07:00
Benjamin Doherty e9728dcc8d Fix iOS build 2022-11-01 15:03:44 -04:00
Mathias Agopian 40a3644086 Reduce the size of ShadowMap to 40 bytes from 2KB+
ShadowMap had recently inflated due to its internal use of 
PerViewUniforms, which keeps a shadow copy of the uniform data. This
copy is not needed for ShadowMaps.
2022-11-01 11:58:28 -07:00
Mathias Agopian 41ef71abdf Add MaterialInstance getters for overridable properties (#6243)
In addition to the new getters, this change fixes the duplication of
a MaterialInstance, which didn't carry along the following states:
  - alpha mask threshold
  - specular AA threshold
  - specular AA variance
  - double sidedness

TODO: stencil state, polygon offset and scissor are stil not queryable
2022-11-01 11:22:01 -07:00
daemyung jangandRomain Guy 3b40e52c98 Recompute bounding boxes with morph targets (#6246)
* Recompute bounding boxes with morph targets

* Reduce the number of heap allocation while recomputing bounding boxes

Co-authored-by: Romain Guy <romainguy@curious-creature.com>
2022-11-01 11:20:28 -07:00
daemyung jang de8c7969f3 Return the correct permit type corresponding to the component type (#6255) 2022-11-01 11:03:27 -07:00
Benjamin Doherty f2c049222b Release Filament 1.28.2 2022-11-01 13:01:13 -04:00
Ben Doherty be30ac1fcb Fix G3 reported ubsan warning (#6257) 2022-11-01 12:50:27 -04:00
Ben Doherty c5cc7fe26a Metal: generate argument buffer structures ourselves (#6242) 2022-11-01 12:44:42 -04:00
Ben Doherty d04b0a5c73 Update Vulkan headers to v1.3.232 (#6256) 2022-11-01 12:38:23 -04:00
Mathias Agopian 72ab2aef0a fix UBO handle leak with ShadowMap
This wasn't a big issue because the "leak" would be cleaned when the
engine is destroyed.
2022-10-31 11:29:46 -07:00
Koncz Levente 1527bb7ad3 Preserve iOS stack traces (#6240) 2022-10-31 13:58:31 -04:00
daemyung jang 8cb26fc9ac Fix a build error on Android
The size of struct could be different on each architecture.
2022-10-30 20:52:56 -07:00
Matt Vera c21794d0a0 Add Vulkan GGP platform. (#6235)
* Add GGP vulkan platform.

* Add release note for GGP support
2022-10-28 11:53:29 -07:00
daemyung jangandMathias Agopian 1a6338569a Calculate primitive's AABB correctly (#6232)
AABB of morph target is displacement data so it should be added to
primitive's base AABB.

Co-authored-by: Mathias Agopian <mathias@google.com>
2022-10-28 11:51:02 -07:00
Mathias Agopian c5ebe1a88a skip primitives with FRONT_AND_BACK face culling 2022-10-28 11:50:21 -07:00
Mathias Agopian a3f2028ae4 Reduce the size of PerViewUniforms by 24 bytes
This seems a bit pointless because PerViewUniforms has a 2KB field,
however, it adds up now that it's used by ShadowMap.
2022-10-28 11:49:55 -07:00
Mathias Agopian c393ee4591 Each shadowmap now manages its own UBO
Instead of constantly reusing the View's UBO, each shadowmap now
manages it own. All UBOs for all shadow maps are updated together, 
then later during rendering, we're simply binding the correct UBO.
This change will be needed later so that we can update several
shadow maps in a single texture.

There are two main changes to achieve this:
1. ShadowMap now has PerViewUniforms object
2. For point lights, each face now has a ShadowMap object

One consequence is that ShadowMap is now very large (over 2KB). This
will be addressed later.
2022-10-28 11:49:55 -07:00
Mathias Agopian 83a82ae10e shadowing: store Executor instead of whole RenderPass
We didn't need to store the actual RenderPass for each shadowmap pass,
the executor is enough. This might prevent some mistakes later.
2022-10-28 11:47:12 -07:00
daemyung jang 676694e458 Fix glTF breaking issue (#6239) 2022-10-28 09:56:01 -07:00
Mathias Agopian 2ca218928f utils: get rid of GrowingSlice
it's just not used anywhere. change the implementation of Slice<> to
use two pointers.
2022-10-27 22:48:45 -07:00
Philip Rideout 776ce223fc Fix incorrect Vulkan documentation wrt layouts. 2022-10-26 19:18:32 -07:00
Philip Rideout 0d4d1f55c9 Updates to the Vulkan architecture doc. 2022-10-26 19:18:32 -07:00
Romain Guy d6dba71ace Update docs 2022-10-26 12:28:56 -07:00
Mathias AgopianandRomain Guy 61ee11ae6c fix the doubleSided property documentation (#6228)
* fix the `doubleSided` property documentation

* Update Materials.md.html

Co-authored-by: Romain Guy <romainguy@curious-creature.com>
2022-10-26 12:28:03 -07:00
Mathias Agopian 90f32e7277 A basic POT 2D allocator
This 2D allocator only supports power-of-two, square allocations. It
uses a quadtree to store the allocated regions.

The quadtree implementation currently has a fixed depth (templated).

The allocator does a best-fit match, but currently doesn't implement a
free method.
2022-10-26 11:02:17 -07:00
daemyung jang b85d9afdb3 Remove an useless whitespace (#6226) 2022-10-26 00:10:33 -07:00
Mathias Agopian 4297d43d9a more improvements to RenderPass
- RenderPass::Executor now only executes the commands, it doesn't 
  create a backend render pass anymore. This is still done in the
  RenderPass::execute() helper.

- scissor and poloyon offset overrides are now set on Executor because
  they are not needed to generate the commands.
2022-10-25 14:38:56 -07:00
Mathias Agopian cfcf714c21 Get rid of PrimitiveType::NONE 2022-10-25 14:38:08 -07:00
Benjamin Doherty f7cfc6a65e Release Filament 1.28.1 2022-10-25 13:07:56 -04:00
Benjamin Doherty 4ead3bcebf Force spirv-cross to keep Metal argument buffer equal sized 2022-10-25 12:07:24 -04:00
Balazs Vegh 21e0677039 Check GPU support for depth resolve on Metal (#6220) 2022-10-25 11:55:17 -04:00
Ben Doherty 107b5a4854 Metal: fix microshadowing artifacts (#6221) 2022-10-24 19:12:14 -04:00
Balazs Vegh 9761b65137 Fix Metal unused variable warning (#6217) 2022-10-24 12:41:58 -04:00
Ben Doherty b1831d2786 Fix, don't use encoder.device (#6212) 2022-10-20 13:05:07 -04:00
Benjamin Doherty 977d369e40 Fix unused variable warnings 2022-10-19 12:44:07 -04:00
Josh Faust 5cf5b25175 Add a function to reset internal GL backend state (#6105)
This allows bug-free integration with external renderers in WebGL

See https://github.com/google/filament/issues/6091
2022-10-19 09:31:11 -07:00
Ben Doherty e75c3020c8 Initial support for Metal compute shaders / SSBOs (#6189) 2022-10-18 16:21:57 -04:00
k1rnt aeb0a4008d fix(ci): replace set-output to GITHUB_OUTPUT (#6195) 2022-10-18 13:41:25 -04:00
Benjamin Doherty 2d43ad59ec Revert Android API_LEVEL change
glDispatchCompute requires Android API level 21, however bumping our
required minimum from 19 to 21 caused some clients' builds to fail.
Commenting-out that line for now to proceed with the 1.28.0 upgrade.
2022-10-18 13:34:50 -04:00
daemyung jang f86ec07c45 Fix ios hello-gltf runtime errors 2022-10-18 10:19:22 -07:00
Ben Doherty fc012d6d65 Metal: fix use-after-free ASAN error (#6203) 2022-10-17 19:13:36 -04:00
Philip Rideout 6b71232e38 matc / matinfo / filamesh: add vulnerability message 2022-10-17 15:49:35 -07:00
Philip Rideout e157023fc1 Use mField style for fields in FFilamentInstance 2022-10-17 15:49:19 -07:00
Philip Rideout 6e38db882f Fix installed IBL location for desktop.
Fixes #6193
2022-10-17 12:30:11 -07:00
daemyung jang 09600699cb Update README.md 2022-10-17 09:57:58 -07:00
daemyung jang 6a5e74f031 Update README for meshoptimizer 2022-10-17 09:57:58 -07:00
daemyung jang 795bd1e995 Fix build errors in ios samples 2022-10-17 09:57:58 -07:00
daemyung jang 784416f658 Support glTF EXT_meshopt_compression 2022-10-17 09:57:58 -07:00
daemyung jang b151afe567 Update meshoptimizer to version 0.18 2022-10-17 09:57:58 -07:00
Mathias Agopian 117ae1d648 RenderPass doesn't store an FEngine anymore 2022-10-14 15:59:03 -07:00
Mathias Agopian 7165d8aa54 fix a directional light shadowing issue
the shadow frustum could be be smaller than expected.
2022-10-14 13:16:14 -07:00
Ben Doherty 6b461b2d89 Metal: fix simulator regression due to arg buffers (#6181) 2022-10-13 14:34:33 -04:00
Benjamin Doherty affc5be840 Release Filament 1.28.0 2022-10-13 12:35:45 -04:00
Ben Doherty e3ff5616ef G3 fixes for 1.28.0 (#6174) 2022-10-11 17:02:46 -07:00
Mathias Agopian d9a401d57e Reduce size of ShadowMap object
- remove mEngine from ShadowMap
- remove storage of ShadowMapInfo in ShadowMap
- ShadowMap::render should be on ShadowMapManager
- remove more attributes from ShadowMap

ShadowMap is now reduced to 32 bytes.
2022-10-10 13:03:36 -07:00
Mathias Agopian acd5823953 Add support for point light shadows
[experimental]
2022-10-10 13:03:36 -07:00
Mathias Agopian 9b13565750 spotlight shadow map skipped if the scene is empty. 2022-10-10 13:03:36 -07:00
Mathias Agopian 734d8ff85c rename upcast() to downcast()
it was down casting all along!
2022-10-07 17:50:56 -07:00
Mathias Agopian a02ef1a6c7 Remove the limit to 14 spot shadow maps, now 64.
Spotlight shadow maps are now limited only by h/w limits (UBO size and
2d array maximum layers), which works out to about 146 shadows.

In practice we hardcode the limite to 64, but this can be changed at
compile time. A higher maximum increases memory usage.

There used to be a bitfield per renderable with a bit for each shadow
map indicating if the renderable was "visible" from this light. The
limit to 14 came from that bitfield. All shadow maps used to be culled
first, setting the bitfield appropriately.

Instead, we are now processing spotlight shadows one at a time,
performing culling as we go. For this reason we don't need a bit per
shadow map.
2022-10-07 14:07:51 -07:00
Mathias Agopian f13bf11d0f simplify command generation
No need to generate a sentinel command in the main loop when a 
command is canceled, this is now done automatically at a higher level.
The main benefit is that we can reject renderables that are not visible
a bit faster.
2022-10-07 14:07:51 -07:00
Ben Doherty 7d0b5fed8f Fix Metal argument buffer encoding (#6150) 2022-10-07 13:53:26 -07:00
Philip Rideout e1f51f04c1 Vulkan: fix black screen regression
The VulkanProgram constructor was bailing out early and emitting a
warning because it saw that one of the stages wasn't fulfilled.
However it's okay for a pipeline to be missing a compute program.

Fixes regression that started with fabba73b1.
2022-10-04 10:47:16 -07:00
Philip Rideout f6d74be123 Fix windows build. 2022-10-03 12:35:35 -07:00
Philip Rideout 50a1935b68 Vulkan performance improvements for readPixels. 2022-10-03 09:54:35 -07:00
Mathias Agopian 694ecce150 fix shadowing when the scene is outside the "shadow" view frustum
this case was causing uninitialized variables to be used creating a
false positive "shadows on" in the first cascade.

The main change here is to bail out as soon as we know there is no shadowing.
2022-09-30 16:12:36 -07:00
Mathias Agopian 3e3bd2722f fix lispsm when camera and light direction are aligned
in that case we have to revert to the ortho projection
2022-09-30 16:12:15 -07:00
Mathias Agopian 44fdf83e4c fix multiview mode in gltf_viewer
The "shadow" camera now works again.
2022-09-30 16:12:15 -07:00
Benjamin Doherty 99c4ce67e4 Print warning when Metal reaches memoryless geo limit 2022-09-30 13:59:49 -07:00
Balazs Vegh 71aceec4cf Memoryless storage mode with fallback (Metal) (#6124) 2022-09-30 13:54:54 -07:00
Philip Rideout 98e4cdbd4c gltfio ubershader mode: fix re-loading.
Fixes #6128
2022-09-30 13:54:36 -07:00
Mathias Agopian 86ef0109f4 use the correct gaussian blur shader
we attempted to select a shader which processed the required number of
component, but were always selecting the 4-components version.

this should improve blur performance for VSM.
2022-09-30 13:30:15 -07:00
Mathias Agopian b4142261c2 trivial typo and ide warnings fixes 2022-09-30 13:30:15 -07:00
Mathias Agopian 359624e8eb Fix gaussian blur kernel computation
This improves significantly VSM shadows with blur,
 but will also improve SSR and the flare.
2022-09-30 13:30:15 -07:00
Mathias Agopian 5bcc11d9e8 fix typo that broke the VSM blur radius setting 2022-09-30 13:30:15 -07:00
Mathias Agopian 5aa414dd24 support for ELVSM and 32 bits xVSM
This basically adds two settings:
- highPrecision at the View level, which controls the bit depth
  of the vsm shadow texture used. This affects all shadowmaps.
- elvsm per shadowmap, which enables Exponential Layered VSM.

the main change in the shaders is that we now always output the 
"negative" EVSM, even when it's not enabled. If no shadowmap uses
ELVSM, then the texture is stil an RG texture and the calculation is
lost (with some luck culled by the shader compiler), either way it's
not a lot of math and it's done only once per shadow texel.

During the color pass, on the other hand, we compute the "negative"
EVSM only if enabled.
2022-09-30 12:04:27 -07:00
Mathias Agopian 1e51b03971 fix VSM 2nd moment calculation
the linear approximation only works with a linear depth, it fails
with our warped depth.
2022-09-30 12:04:27 -07:00
Mathias Agopian fc484bd319 fix "stable" shadows
We were always adjusting the near/far of the view volume based on the
scene content for shadowing, which defeated the "stable" shadows.

Also changed the default cascade splits from a linear split to a
log2 split, because due to the perspective projection, the log2 split
actually looks linear. Also intuitively, it makes more sense to give
more resolution to the shadows close to the camera.
2022-09-30 12:00:02 -07:00
Mathias Agopian 9c4783142e matc: minor cleanup 2022-09-29 19:34:07 -07:00
Mathias Agopian 4dc03318e8 break circular dependency between Package and Flattener 2022-09-29 19:34:07 -07:00
Philip Rideout 15ba54c4c5 Update README wrt Android Studio Version. 2022-09-29 16:28:50 -07:00
Philip Rideout 342138aecb gltfio: Update Java API. 2022-09-29 16:28:50 -07:00
Philip Rideout 9f53c62e06 gltfio: Update Web API.
Java API changes will come next.
2022-09-29 16:28:50 -07:00
Philip Rideout 2d996033e3 gltfio API change part 2: remove asset-level animator 2022-09-29 16:28:50 -07:00
Philip Rideout 9fa6dc4131 gltfio API change part 1: move all MaterialInstance methods to FilamentInstance 2022-09-29 16:28:50 -07:00
daemyung jangandPhilip Rideout 4930227743 Fix a crash by setting not exist material parameters on ubershader (#6122)
* Fix a crash by setting not exist material parameters on ubershader

* gltfio: add getFeatureMap, remove logic for dummy textures

Co-authored-by: Philip Rideout <philiprideout@gmail.com>
2022-09-29 15:51:35 -07:00
Benjamin Doherty fbfcce563d Release Filament 1.27.2 2022-09-29 13:11:43 -07:00
Ben Doherty 7f5ad3743b Fixes for 1.27.2 release (#6125) 2022-09-29 13:08:00 -07:00
daemyung jang 981020e72b Get the morph target buffer to the given primitive 2022-09-29 13:01:42 -07:00
Mathias Agopian 7c1a705a07 reduce setSarameter(texture) calls 2022-09-28 22:10:27 -07:00
Mathias Agopian 3d53ae583f switch our min sdk from 19 to 21
this is so that we can support OpenGL ES3.1
2022-09-28 22:08:27 -07:00
Mathias Agopian dc9f03cbd0 don't attempt to use more texture units than present
to emulate the bindless API in the gl backend we always used the highest
texture unit available. However at feature level 3, we support up to 62
textures, so the that max was bumped to 62 -- however, where we're not
on a feature level 2 device, that texture unit doesn't exist.

Instead we now always use binding 31, which is guaranteed to exist by 
EGL's minspec.
2022-09-28 22:05:05 -07:00
Philip Rideout de7bca7ef8 gltfio: fix occlusion strength
Fixes #6106
2022-09-28 17:42:29 -07:00
Ben Doherty f151f56a2e Metal: use argument buffers for SamplerGroups (#6093) 2022-09-28 16:50:11 -07:00
Ben Doherty fe3d1713a4 Fix generic/Mutex.h not installed on Linux (#6117) 2022-09-28 16:36:38 -07:00
Joel Winarske 2cad3be7a3 Wayland FilamentApp
- move Wayland specific code into getNativeWindow
- make wayland struct static

Signed-off-by: Joel Winarske <joel.winarske@gmail.com>
2022-09-28 16:14:01 -07:00
Thomas Gorisse 5262512eea Fix JNI for TransformManager.getChildren() (#6116) 2022-09-28 11:27:35 -07:00
Mathias Agopian fabba73b1e Initial support for compute
* minimal backend support for compute
- added api to dispatch a compute shader
- added api to create and bind a ssbo
- added api to read back a buffer
Only implemented in the gl backend

* Add a backend compute test suite
* basic support for compute shaders in matc
this is still very much work-in-progress.
We're not supporting images nor ssbo for now.

* rename UniformInterfaceBlock to BufferInterfaceBlock
* augment BufferInterfaceBlock to support ssbo features
- add support for std430
- add support for ssbo
- add support for variable-size array
- add support for memory qualifiers

* reformat MaterialBuilder
* material format: move subpasses outside of parameters
subpasses now are their own json property instead of being a
"parameter".

* refactor parameter() methods to match Buffer/SamplerInterfaceBlock
We're just shuffling the arguments.

* add support for buffers in .mat files
* filamat now generates buffer blocks (ssbo)
* take feature level into consideration when optimizing shaders
* don't store the 'uniform binding' chunk for level 2 materials
this includes some refactoring/cleanups of MaterialParser

* matinfo: fixes for compute
- separate subpasses from parameters
- don't attempt to print material properties
2022-09-27 15:52:40 -07:00
daemyung jang d291ec739b Update cgltf to version 1.13 (#6099) 2022-09-23 11:52:57 -07:00
Mathias Agopian 0dc0bbd4f3 LiSPSM is now a user settable option 2022-09-22 09:35:45 -07:00
Mathias Agopian 984a029ce4 fix (again) the box-frustum intersection
it is not true that the whole box is inside the frustum if we have
8 vertices and some frustum vertices are in the box -- that's even
non-sensical.

We now test for this case by checking that we have all (8) vertices of
the box inside the frustum.

This caused some shadows to be missing.
2022-09-22 09:35:28 -07:00
Mathias Agopian 1cbc1f30a6 filamesh: handle empty meshes
empty meshes would cause a OOB read.

b/246719043
2022-09-20 16:45:45 -07:00
Mathias Agopian 08fb39a1e4 Fix buffer overflow in matc
b/247441389
2022-09-20 16:45:45 -07:00
Mathias Agopian d8a71ce195 minor cleanup and IDE warning suppression 2022-09-20 16:45:45 -07:00
Mathias Agopian 13986c12bc fix out of bounds access in matc
b/247442731
2022-09-20 16:45:45 -07:00
Mathias Agopian cc64704a82 fix buffer overflow filamat
b/247447117
2022-09-20 16:45:45 -07:00
420 changed files with 51922 additions and 6954 deletions
+10 -10
View File
@@ -39,8 +39,8 @@ jobs:
run: |
REF=${RELEASE_TAG:-${GITHUB_REF}}
TAG=${REF##*/}
echo ::set-output name=ref::${REF}
echo ::set-output name=tag::${TAG}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- uses: actions/checkout@v2.0.0
with:
ref: ${{ steps.git_ref.outputs.ref }}
@@ -69,8 +69,8 @@ jobs:
run: |
REF=${RELEASE_TAG:-${GITHUB_REF}}
TAG=${REF##*/}
echo ::set-output name=ref::${REF}
echo ::set-output name=tag::${TAG}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- uses: actions/checkout@v2.0.0
with:
ref: ${{ steps.git_ref.outputs.ref }}
@@ -97,8 +97,8 @@ jobs:
run: |
REF=${RELEASE_TAG:-${GITHUB_REF}}
TAG=${REF##*/}
echo ::set-output name=ref::${REF}
echo ::set-output name=tag::${TAG}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- uses: actions/checkout@v2.0.0
with:
ref: ${{ steps.git_ref.outputs.ref }}
@@ -143,8 +143,8 @@ jobs:
run: |
REF=${RELEASE_TAG:-${GITHUB_REF}}
TAG=${REF##*/}
echo ::set-output name=ref::${REF}
echo ::set-output name=tag::${TAG}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- uses: actions/checkout@v2.0.0
with:
ref: ${{ steps.git_ref.outputs.ref }}
@@ -171,8 +171,8 @@ jobs:
run: |
REF=${RELEASE_TAG:-${GITHUB_REF}}
TAG=${REF##*/}
echo ::set-output name=ref::${REF}
echo ::set-output name=tag::${TAG}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
shell: bash
- uses: actions/checkout@v2.0.0
with:
+10 -6
View File
@@ -337,19 +337,23 @@ endif()
# ==================================================================================================
# Release compiler flags
# ==================================================================================================
if (NOT MSVC)
if (NOT MSVC AND NOT IOS)
# Omitting stack frame pointers prevents the generation of readable stack traces in crash reports on iOS
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fomit-frame-pointer")
# These aren't compatible with -fembed-bitcode (and seem to have no effect on Apple platforms anyway)
if (NOT IOS)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffunction-sections -fdata-sections")
endif()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffunction-sections -fdata-sections")
endif()
# On Android RELEASE builds, we disable exceptions and RTTI to save some space (about 75 KiB
# saved by -fno-exception and 10 KiB saved by -fno-rtti).
if (ANDROID OR IOS OR WEBGL)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-rtti")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-exceptions -fno-rtti")
if (ANDROID OR WEBGL)
# Omitting unwind info prevents the generation of readable stack traces in crash reports on iOS
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-unwind-tables -fno-asynchronous-unwind-tables")
endif()
endif()
# With WebGL, we disable RTTI even for debug builds because we pass emscripten::val back and forth
@@ -666,7 +670,7 @@ add_subdirectory(${EXTERNAL}/imgui/tnt)
add_subdirectory(${EXTERNAL}/robin-map/tnt)
add_subdirectory(${EXTERNAL}/smol-v/tnt)
add_subdirectory(${EXTERNAL}/benchmark/tnt)
add_subdirectory(${EXTERNAL}/meshoptimizer)
add_subdirectory(${EXTERNAL}/meshoptimizer/tnt)
add_subdirectory(${EXTERNAL}/cgltf/tnt)
add_subdirectory(${EXTERNAL}/draco/tnt)
add_subdirectory(${EXTERNAL}/jsmn/tnt)
+3 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.27.2'
implementation 'com.google.android.filament:filament-android:1.28.2'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```
pod 'Filament', '~> 1.27.2'
pod 'Filament', '~> 1.28.2'
```
### Snapshots
@@ -179,6 +179,7 @@ steps:
- [x] KHR_mesh_quantization
- [x] KHR_texture_basisu
- [x] KHR_texture_transform
- [x] EXT_meshopt_compression
## Rendering with Filament
+37
View File
@@ -5,6 +5,42 @@ A new header is inserted each time a *tag* is created.
## main branch
- gltfio: calculate primitive's AABB correctly.
- gltfio: recompute bounding boxes with morph targets
- engine: add missing getters on `MaterialInstance`
## v1.28.3
- backend: add support for GGP platform
- engine: primitives with `CullingMode::FRONT_AND_BACK` are now skipped.
## v1.28.2
- gltfio: support EXT_meshopt_compression
- release packaging: fixed location of default IBL file
## v1.28.1
- gltfio: fix reloading crash in ubershader mode
- Vulkan: improve performance in the readPixels path
- engine: raise the spot shadows limit to 64, from 14.
- engine: add experimental support for point light shadows.
- gltfio: fix ubershader issues with assignment of dummy textures
- gltfio: material instances and variants are now accessed via `FilamentInstance` [⚠️ **API Change**]
- gltfio: the animator can now only be accessed via `FilamentInstance` [⚠️ **API Change**]
- engine: fix "stable" shadows and make the default cascade splits logarithmic.
- engine: Add new quality options to EVSM shadows + rendering fixes
## v1.28.0
- engine: LiSPSM is now a user settable option
- engine: get the morph target buffer to the given primitive
- Java: Fix TransformManager.getChildren()
- Metal: newer devices are no longer limited to 16 samplers per Material.
- gltfio: fix interpretation of occlusion strength
- engine: minsdk is now 21 instead of 19. This allows the use of OpenGL ES 3.1
- Vulkan: fix black screen regression
## v1.27.2
- gltfio: punctual lights are now duplicated when adding new asset instances
@@ -13,6 +49,7 @@ A new header is inserted each time a *tag* is created.
- gltfio: fix regression with meshes that have no material
- gltfio: fix regression with recomputeBoundingBoxes()
- filamesh / matinfo: fix minor ASAN issues
- engine: Added `Engine::resetBackendState()`, available only in WebGL builds
## v1.27.1
@@ -126,8 +126,8 @@ Java_com_google_android_filament_filamat_MaterialBuilder_nMaterialBuilderUniform
JNIEnv* env, jclass, jlong nativeBuilder, jint uniformType, jint precision, jstring name_) {
auto builder = (MaterialBuilder*) nativeBuilder;
const char* name = env->GetStringUTFChars(name_, nullptr);
builder->parameter((MaterialBuilder::UniformType) uniformType,
(MaterialBuilder::ParameterPrecision) precision, name);
builder->parameter(name, (MaterialBuilder::UniformType) uniformType,
(MaterialBuilder::ParameterPrecision) precision);
env->ReleaseStringUTFChars(name_, name);
}
@@ -137,8 +137,8 @@ Java_com_google_android_filament_filamat_MaterialBuilder_nMaterialBuilderUniform
jstring name_) {
auto builder = (MaterialBuilder*) nativeBuilder;
const char* name = env->GetStringUTFChars(name_, nullptr);
builder->parameter((MaterialBuilder::UniformType) uniformType, (size_t) size,
(MaterialBuilder::ParameterPrecision) precision, name);
builder->parameter(name, (size_t) size, (MaterialBuilder::UniformType) uniformType,
(MaterialBuilder::ParameterPrecision) precision);
env->ReleaseStringUTFChars(name_, name);
}
@@ -148,9 +148,8 @@ Java_com_google_android_filament_filamat_MaterialBuilder_nMaterialBuilderSampler
jint precision, jstring name_) {
auto builder = (MaterialBuilder*) nativeBuilder;
const char* name = env->GetStringUTFChars(name_, nullptr);
builder->parameter((MaterialBuilder::SamplerType) samplerType,
(MaterialBuilder::SamplerFormat) format, (MaterialBuilder::ParameterPrecision) precision,
name);
builder->parameter(name, (MaterialBuilder::SamplerType) samplerType,
(MaterialBuilder::SamplerFormat) format, (MaterialBuilder::ParameterPrecision) precision);
env->ReleaseStringUTFChars(name_, name);
}
@@ -76,10 +76,10 @@ extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_LightManager_nBuilderShadowOptions(JNIEnv* env, jclass,
jlong nativeBuilder, jint mapSize, jint cascades, jfloatArray splitPositions,
jfloat constantBias, jfloat normalBias, jfloat shadowFar, jfloat shadowNearHint,
jfloat shadowFarHint, jboolean stable,
jfloat shadowFarHint, jboolean stable, jboolean lispsm,
jfloat polygonOffsetConstant, jfloat polygonOffsetSlope,
jboolean screenSpaceContactShadows, jint stepCount,
jfloat maxShadowDistance, jint vsmMsaaSamples, jfloat blurWidth, jfloat shadowBulbRadius) {
jfloat maxShadowDistance, jint vsmMsaaSamples, jboolean elvsm, jfloat blurWidth, jfloat shadowBulbRadius) {
LightManager::Builder *builder = (LightManager::Builder *) nativeBuilder;
LightManager::ShadowOptions shadowOptions {
.mapSize = (uint32_t)mapSize,
@@ -90,6 +90,7 @@ Java_com_google_android_filament_LightManager_nBuilderShadowOptions(JNIEnv* env,
.shadowNearHint = shadowNearHint,
.shadowFarHint = shadowFarHint,
.stable = (bool)stable,
.lispsm = (bool)lispsm,
.polygonOffsetConstant = polygonOffsetConstant,
.polygonOffsetSlope = polygonOffsetConstant,
.screenSpaceContactShadows = (bool)screenSpaceContactShadows,
@@ -97,6 +98,7 @@ Java_com_google_android_filament_LightManager_nBuilderShadowOptions(JNIEnv* env,
.maxShadowDistance = maxShadowDistance,
.vsm = {
.msaaSamples = (uint8_t) vsmMsaaSamples,
.elvsm = (bool)elvsm,
.blurWidth = blurWidth
},
.shadowBulbRadius = shadowBulbRadius
@@ -452,3 +452,84 @@ Java_com_google_android_filament_MaterialInstance_nDuplicate(JNIEnv* env, jclass
}
return (jlong)mi;
}
extern "C"
JNIEXPORT jfloat JNICALL
Java_com_google_android_filament_MaterialInstance_nGetMaskThreshold(JNIEnv* env, jclass clazz,
jlong nativeMaterialInstance) {
// TODO: implement nGetMaskThreshold()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->getMaskThreshold();
}
extern "C"
JNIEXPORT jfloat JNICALL
Java_com_google_android_filament_MaterialInstance_nGetSpecularAntiAliasingVariance(JNIEnv* env,
jclass clazz, jlong nativeMaterialInstance) {
// TODO: implement nGetSpecularAntiAliasingVariance()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->getSpecularAntiAliasingVariance();
}
extern "C"
JNIEXPORT jfloat JNICALL
Java_com_google_android_filament_MaterialInstance_nGetSpecularAntiAliasingThreshold(JNIEnv* env,
jclass clazz, jlong nativeMaterialInstance) {
// TODO: implement nGetSpecularAntiAliasingThreshold()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->getSpecularAntiAliasingThreshold();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MaterialInstance_nIsDoubleSided(JNIEnv* env, jclass clazz,
jlong nativeMaterialInstance) {
// TODO: implement nIsDoubleSided()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->isDoubleSided();
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_google_android_filament_MaterialInstance_nGetCullingMode(JNIEnv* env, jclass clazz,
jlong nativeMaterialInstance) {
// TODO: implement nGetCullingMode()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return (jint)instance->getCullingMode();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MaterialInstance_nIsColorWriteEnabled(JNIEnv* env, jclass clazz,
jlong nativeMaterialInstance) {
// TODO: implement nIsColorWriteEnabled()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->isColorWriteEnabled();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MaterialInstance_nIsDepthWriteEnabled(JNIEnv* env, jclass clazz,
jlong nativeMaterialInstance) {
// TODO: implement nIsDepthWriteEnabled()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->isDepthWriteEnabled();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MaterialInstance_nIsStencilWriteEnabled(JNIEnv* env, jclass clazz,
jlong nativeMaterialInstance) {
// TODO: implement nIsStencilWriteEnabled()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->isStencilWriteEnabled();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MaterialInstance_nIsDepthCullingEnabled(JNIEnv* env, jclass clazz,
jlong nativeMaterialInstance) {
// TODO: implement nIsDepthCullingEnabled()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->isDepthCullingEnabled();
}
@@ -127,7 +127,7 @@ Java_com_google_android_filament_TransformManager_nGetChildren(JNIEnv* env,
// (which it is), but still.
tm->getChildren((TransformManager::Instance) i,
reinterpret_cast<Entity *>(entities), (size_t) count);
env->ReleaseIntArrayElements(outEntities_, entities, JNI_ABORT);
env->ReleaseIntArrayElements(outEntities_, entities, 0);
}
extern "C" JNIEXPORT void JNICALL
@@ -149,12 +149,13 @@ Java_com_google_android_filament_View_nSetShadowType(JNIEnv*, jclass, jlong nati
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nSetVsmShadowOptions(JNIEnv*, jclass, jlong nativeView,
jint anisotropy, jboolean mipmapping, jfloat minVarianceScale,
jint anisotropy, jboolean mipmapping, jboolean highPrecision, jfloat minVarianceScale,
jfloat lightBleedReduction) {
View* view = (View*) nativeView;
View::VsmShadowOptions options;
options.anisotropy = (uint8_t)anisotropy;
options.mipmapping = (bool)mipmapping;
options.highPrecision = (bool)highPrecision;
options.minVarianceScale = minVarianceScale;
options.lightBleedReduction = lightBleedReduction;
view->setVsmShadowOptions(options);
@@ -240,7 +240,7 @@ public class LightManager {
*/
@NonNull
@Size(min = 3)
public float[] cascadeSplitPositions = { 0.25f, 0.50f, 0.75f };
public float[] cascadeSplitPositions = { 0.125f, 0.25f, 0.50f };
/** Constant bias in world units (e.g. meters) by which shadows are moved away from the
* light. 1mm by default.
@@ -280,9 +280,25 @@ public class LightManager {
* Controls whether the shadow map should be optimized for resolution or stability.
* When set to true, all resolution enhancing features that can affect stability are
* disabling, resulting in significantly lower resolution shadows, albeit stable ones.
*
* Setting this flag to true always disables LiSPSM (see below).
*/
public boolean stable = false;
/**
* LiSPSM, or light-space perspective shadow-mapping is a technique allowing to better
* optimize the use of the shadow-map texture. When enabled the effective resolution of
* shadows is greatly improved and yields result similar to using cascades without the
* extra cost. LiSPSM comes with some drawbacks however, in particular it is incompatible
* with blurring because it effectively affects the blur kernel size.
*
* Blurring is only an issue when using ShadowType.VSM with a large blur or with
* ShadowType.PCSS however.
*
* If these blurring artifacts become problematic, this flag can be used to disable LiSPSM.
*/
public boolean lispsm = false;
/**
* Constant bias in depth-resolution units by which shadows are moved away from the
* light. The default value of 0.5 is used to round depth values up.
@@ -346,6 +362,15 @@ public class LightManager {
@IntRange(from = 1)
public int vsmMsaaSamples = 1;
/**
* When elvsm is set to true, "Exponential Layered VSM without Layers" are used. It is
* an improvement to the default EVSM which suffers important light leaks. Enabling
* ELVSM for a single shadowmap doubles the memory usage of all shadow maps.
* ELVSM is mostly useful when large blurs are used.
*/
public boolean elvsm = false;
/**
* Blur width for the VSM blur. Zero do disable.
* The maximum value is 125.
@@ -470,11 +495,6 @@ public class LightManager {
/**
* Whether this Light casts shadows (disabled by default)
*
* <p>
* <b>warning:</b>
* {@link Type#POINT} lights cannot cast shadows.
* </p>
*
* @param enable Enables or disables casting shadows from this Light.
*
* @return This Builder, for chaining calls.
@@ -496,11 +516,11 @@ public class LightManager {
nBuilderShadowOptions(mNativeBuilder,
options.mapSize, options.shadowCascades, options.cascadeSplitPositions,
options.constantBias, options.normalBias, options.shadowFar, options.shadowNearHint,
options.shadowFarHint, options.stable,
options.shadowFarHint, options.stable, options.lispsm,
options.polygonOffsetConstant, options.polygonOffsetSlope,
options.screenSpaceContactShadows,
options.stepCount, options.maxShadowDistance, options.vsmMsaaSamples,
options.blurWidth, options.shadowBulbRadius);
options.elvsm, options.blurWidth, options.shadowBulbRadius);
return this;
}
@@ -1158,7 +1178,14 @@ public class LightManager {
private static native void nDestroyBuilder(long nativeBuilder);
private static native boolean nBuilderBuild(long nativeBuilder, long nativeEngine, int entity);
private static native void nBuilderCastShadows(long nativeBuilder, boolean enable);
private static native void nBuilderShadowOptions(long nativeBuilder, int mapSize, int cascades, float[] splitPositions, float constantBias, float normalBias, float shadowFar, float shadowNearHint, float shadowFarhint, boolean stable, float polygonOffsetConstant, float polygonOffsetSlope, boolean screenSpaceContactShadows, int stepCount, float maxShadowDistance, int vsmMsaaSamples, float blurWidth, float shadowBulbRadius);
private static native void nBuilderShadowOptions(long nativeBuilder, int mapSize,
int cascades, float[] splitPositions,
float constantBias, float normalBias,
float shadowFar, float shadowNearHint, float shadowFarhint,
boolean stable, boolean lispsm,
float polygonOffsetConstant, float polygonOffsetSlope,
boolean screenSpaceContactShadows, int stepCount, float maxShadowDistance,
int vsmMsaaSamples, boolean elvsm, float blurWidth, float shadowBulbRadius);
private static native void nBuilderCastLight(long nativeBuilder, boolean enabled);
private static native void nBuilderPosition(long nativeBuilder, float x, float y, float z);
private static native void nBuilderDirection(long nativeBuilder, float x, float y, float z);
@@ -24,6 +24,7 @@ import com.google.android.filament.proguard.UsedByNative;
@UsedByNative("AssetLoader.cpp")
public class MaterialInstance {
private static final Material.CullingMode[] sCullingModeValues = Material.CullingMode.values();
private Material mMaterial;
private String mName;
private long mNativeObject;
@@ -473,6 +474,14 @@ public class MaterialInstance {
nSetMaskThreshold(getNativeObject(), threshold);
}
/**
* Gets the minimum alpha value a fragment must have to not be discarded when the blend
* mode is MASKED
*/
public float getMaskThreshold() {
return nGetMaskThreshold(getNativeObject());
}
/**
* Sets the screen space variance of the filter kernel used when applying specular
* anti-aliasing. The default value is set to 0.15. The specified value should be between
@@ -486,6 +495,14 @@ public class MaterialInstance {
nSetSpecularAntiAliasingVariance(getNativeObject(), variance);
}
/**
* Gets the screen space variance of the filter kernel used when applying specular
* anti-aliasing.
*/
public float getSpecularAntiAliasingVariance() {
return nGetSpecularAntiAliasingVariance(getNativeObject());
}
/**
* Sets the clamping threshold used to suppress estimation errors when applying specular
* anti-aliasing. The default value is set to 0.2. The specified value should be between 0
@@ -499,6 +516,14 @@ public class MaterialInstance {
nSetSpecularAntiAliasingThreshold(getNativeObject(), threshold);
}
/**
* Gets the clamping threshold used to suppress estimation errors when applying specular
* anti-aliasing.
*/
public float getSpecularAntiAliasingThreshold() {
return nGetSpecularAntiAliasingThreshold(getNativeObject());
}
/**
* Enables or disables double-sided lighting if the parent Material has double-sided capability,
* otherwise prints a warning. If double-sided lighting is enabled, backface culling is
@@ -512,6 +537,14 @@ public class MaterialInstance {
nSetDoubleSided(getNativeObject(), doubleSided);
}
/**
* Returns whether double-sided lighting is enabled when the parent Material has double-sided
* capability.
*/
public boolean isDoubleSided() {
return nIsDoubleSided(getNativeObject());
}
/**
* Overrides the default triangle culling state that was set on the material.
*
@@ -519,10 +552,18 @@ public class MaterialInstance {
* <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/rasterization:culling">
* Rasterization: culling</a>
*/
public void setCullingMode(Material.CullingMode mode) {
public void setCullingMode(@NonNull Material.CullingMode mode) {
nSetCullingMode(getNativeObject(), mode.ordinal());
}
/**
* Returns the face culling mode.
*/
@NonNull
public Material.CullingMode getCullingMode() {
return sCullingModeValues[nGetCullingMode(getNativeObject())];
}
/**
* Overrides the default color-buffer write state that was set on the material.
*
@@ -534,6 +575,13 @@ public class MaterialInstance {
nSetColorWrite(getNativeObject(), enable);
}
/**
* Returns whether color write is enabled.
*/
public boolean isColorWriteEnabled() {
return nIsColorWriteEnabled(getNativeObject());
}
/**
* Overrides the default depth-buffer write state that was set on the material.
*
@@ -545,10 +593,27 @@ public class MaterialInstance {
nSetDepthWrite(getNativeObject(), enable);
}
/**
* Returns whether depth write is enabled.
*/
public boolean isDepthWriteEnabled() {
return nIsDepthWriteEnabled(getNativeObject());
}
/**
* Enables or Disable stencil writes
*/
public void setStencilWrite(boolean enable) {
nSetStencilWrite(getNativeObject(), enable);
}
/**
* Returns whether stencil write is enabled.
*/
public boolean isStencilWriteEnabled() {
return nIsStencilWriteEnabled(getNativeObject());
}
/**
* Overrides the default depth testing state that was set on the material.
*
@@ -560,6 +625,13 @@ public class MaterialInstance {
nSetDepthCulling(getNativeObject(), enable);
}
/**
* Returns whether depth culling is enabled.
*/
public boolean isDepthCullingEnabled() {
return nIsDepthCullingEnabled(getNativeObject());
}
/**
* Sets the stencil comparison function (default is {@link TextureSampler.CompareFunction#ALWAYS}).
*
@@ -856,4 +928,15 @@ public class MaterialInstance {
private static native long nGetMaterial(long nativeMaterialInstance);
private static native long nDuplicate(long otherNativeMaterialInstance, String name);
private static native float nGetMaskThreshold(long nativeMaterialInstance);
private static native float nGetSpecularAntiAliasingVariance(long nativeMaterialInstance);
private static native float nGetSpecularAntiAliasingThreshold(long nativeMaterialInstance);
private static native boolean nIsDoubleSided(long nativeMaterialInstance);
private static native int nGetCullingMode(long nativeMaterialInstance);
private static native boolean nIsColorWriteEnabled(long nativeMaterialInstance);
private static native boolean nIsDepthWriteEnabled(long nativeMaterialInstance);
private static native boolean nIsStencilWriteEnabled(long nativeMaterialInstance);
private static native boolean nIsDepthCullingEnabled(long nativeMaterialInstance);
}
@@ -793,7 +793,7 @@ public class View {
public void setVsmShadowOptions(@NonNull VsmShadowOptions options) {
mVsmShadowOptions = options;
nSetVsmShadowOptions(getNativeObject(), options.anisotropy, options.mipmapping,
options.minVarianceScale, options.lightBleedReduction);
options.highPrecision, options.minVarianceScale, options.lightBleedReduction);
}
/**
@@ -1144,7 +1144,7 @@ public class View {
private static native void nSetRenderQuality(long nativeView, int hdrColorBufferQuality);
private static native void nSetDynamicLightingOptions(long nativeView, float zLightNear, float zLightFar);
private static native void nSetShadowType(long nativeView, int type);
private static native void nSetVsmShadowOptions(long nativeView, int anisotropy, boolean mipmapping, float minVarianceScale, float lightBleedReduction);
private static native void nSetVsmShadowOptions(long nativeView, int anisotropy, boolean mipmapping, boolean highPrecision, float minVarianceScale, float lightBleedReduction);
private static native void nSetSoftShadowOptions(long nativeView, float penumbraScale, float penumbraRatioScale);
private static native void nSetColorGrading(long nativeView, long nativeColorGrading);
private static native void nSetPostProcessingEnabled(long nativeView, boolean enabled);
@@ -1817,6 +1817,13 @@ public class View {
* Whether to generate mipmaps for all VSM shadow maps.
*/
public boolean mipmapping = false;
/**
* Whether to use a 32-bits or 16-bits texture format for VSM shadow maps. 32-bits
* precision is rarely needed, but it does reduces light leaks as well as "fading"
* of the shadows in some situations. Setting highPrecision to true for a single
* shadow map will double the memory usage of all shadow maps.
*/
public boolean highPrecision = false;
/**
* VSM minimum variance scale, must be positive.
*/
@@ -179,7 +179,7 @@ class ModelViewer(
asset = assetLoader.createAsset(buffer)
asset?.let { asset ->
resourceLoader.asyncBeginLoad(asset)
animator = asset.animator
animator = asset.getInstance().animator
asset.releaseSourceData()
}
}
@@ -202,7 +202,7 @@ class ModelViewer(
resourceLoader.addResourceData(uri, resourceBuffer)
}
resourceLoader.asyncBeginLoad(asset)
animator = asset.animator
animator = asset.getInstance().animator
asset.releaseSourceData()
}
}
@@ -359,7 +359,7 @@ class ModelViewer(
resourceLoader.addResourceData(uri, buffer)
}
resourceLoader.asyncBeginLoad(asset)
animator = asset.animator
animator = asset.getInstance().animator
asset.releaseSourceData()
}
}
+7 -2
View File
@@ -11,6 +11,10 @@ add_library(dracodec STATIC IMPORTED)
set_target_properties(dracodec PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libdracodec.a)
add_library(meshoptimizer STATIC IMPORTED)
set_target_properties(meshoptimizer PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libmeshoptimizer.a)
add_library(ktxreader STATIC IMPORTED)
set_target_properties(ktxreader PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libktxreader.a)
@@ -76,7 +80,7 @@ set(GLTFIO_SRCS
${GLTFIO_DIR}/src/UbershaderProvider.cpp
${GLTFIO_DIR}/src/Wireframe.cpp
${GLTFIO_DIR}/src/Wireframe.h
${GLTFIO_DIR}/src/upcast.h
${GLTFIO_DIR}/src/downcast.h
src/main/cpp/Animator.cpp
src/main/cpp/AssetLoader.cpp
@@ -99,6 +103,7 @@ set(GLTFIO_INCLUDE_DIRS
../../libs/gltfio/include
../../third_party/basisu/zstd
../../third_party/cgltf
../../third_party/meshoptimizer/src
../../third_party/robin-map
../../third_party/hat-trie
../../third_party/stb
@@ -111,7 +116,7 @@ target_include_directories(gltfio-jni PRIVATE ${GLTFIO_INCLUDE_DIRS})
set_target_properties(gltfio-jni PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.symbols)
set_target_properties(gltfio-jni PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.map)
target_link_libraries(gltfio-jni filament-jni utils uberzlib log stb ktxreader basis_transcoder zstd uberarchive)
target_link_libraries(gltfio-jni dracodec)
target_link_libraries(gltfio-jni dracodec meshoptimizer)
target_compile_definitions(gltfio-jni PUBLIC GLTFIO_DRACO_SUPPORTED=1)
target_include_directories(gltfio-jni PRIVATE ${DRACO_DIR}/src)
target_include_directories(gltfio-jni PRIVATE ${DRACO_DIR}/tnt)
@@ -172,27 +172,6 @@ Java_com_google_android_filament_gltfio_FilamentAsset_nGetCameraEntityCount(JNIE
return asset->getCameraEntityCount();
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_gltfio_FilamentAsset_nGetMaterialInstanceCount(JNIEnv*, jclass,
jlong nativeAsset) {
FilamentAsset* asset = (FilamentAsset*) nativeAsset;
return asset->getMaterialInstanceCount();
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_gltfio_FilamentAsset_nGetMaterialInstances(JNIEnv* env, jclass,
jlong nativeAsset, jlongArray result) {
FilamentAsset* asset = (FilamentAsset*) nativeAsset;
jsize available = env->GetArrayLength(result);
jsize count = std::min(available, (jsize) asset->getMaterialInstanceCount());
jlong* dst = env->GetLongArrayElements(result, nullptr);
const MaterialInstance * const* src = asset->getMaterialInstances();
for (jsize i = 0; i < count; i++) {
dst[i] = (jlong) src[i];
}
env->ReleaseLongArrayElements(result, dst, 0);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_gltfio_FilamentAsset_nGetBoundingBox(JNIEnv* env, jclass,
jlong nativeAsset, jfloatArray result) {
@@ -229,10 +208,10 @@ Java_com_google_android_filament_gltfio_FilamentAsset_nGetExtras(JNIEnv* env, jc
}
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_gltfio_FilamentAsset_nGetAnimator(JNIEnv* , jclass,
Java_com_google_android_filament_gltfio_FilamentAsset_nGetInstance(JNIEnv* , jclass,
jlong nativeAsset) {
FilamentAsset* asset = (FilamentAsset*) nativeAsset;
return (jlong) asset->getAnimator();
return (jlong) asset->getInstance();
}
extern "C" JNIEXPORT jint JNICALL
@@ -272,30 +251,6 @@ Java_com_google_android_filament_gltfio_FilamentAsset_nGetMorphTargetNames(JNIEn
}
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_gltfio_FilamentAsset_nGetMaterialVariantCount(JNIEnv*, jclass,
jlong nativeAsset) {
FilamentAsset* asset = (FilamentAsset*) nativeAsset;
return (jint) asset->getMaterialVariantCount();
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_gltfio_FilamentAsset_nGetMaterialVariantNames(JNIEnv* env, jclass,
jlong nativeAsset, jobjectArray result) {
FilamentAsset* asset = (FilamentAsset*) nativeAsset;
for (int i = 0; i < asset->getMaterialVariantCount(); ++i) {
const char* name = asset->getMaterialVariantName(i);
env->SetObjectArrayElement(result, (jsize) i, env->NewStringUTF(name));
}
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_gltfio_FilamentAsset_nApplyMaterialVariant(JNIEnv* env, jclass,
jlong nativeAsset, jint variantIndex) {
FilamentAsset* asset = (FilamentAsset*) nativeAsset;
asset->applyMaterialVariant(variantIndex);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_gltfio_FilamentAsset_nReleaseSourceData(JNIEnv* env, jclass,
jlong nativeAsset) {
@@ -20,6 +20,7 @@
#include <algorithm>
using namespace filament;
using namespace filament::gltfio;
using namespace utils;
@@ -62,6 +63,44 @@ Java_com_google_android_filament_gltfio_FilamentInstance_nApplyMaterialVariant(J
instance->applyMaterialVariant(variantIndex);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_gltfio_FilamentInstance_nGetMaterialVariantCount(JNIEnv*, jclass,
jlong nativeInstance) {
FilamentInstance* instance = (FilamentInstance*) nativeInstance;
return (jint) instance->getMaterialVariantCount();
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_gltfio_FilamentInstance_nGetMaterialVariantNames(JNIEnv* env, jclass,
jlong nativeInstance, jobjectArray result) {
FilamentInstance* instance = (FilamentInstance*) nativeInstance;
for (int i = 0; i < instance->getMaterialVariantCount(); ++i) {
const char* name = instance->getMaterialVariantName(i);
env->SetObjectArrayElement(result, (jsize) i, env->NewStringUTF(name));
}
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_gltfio_FilamentInstance_nGetMaterialInstanceCount(JNIEnv*, jclass,
jlong nativeInstance) {
FilamentInstance* instance = (FilamentInstance*) nativeInstance;
return instance->getMaterialInstanceCount();
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_gltfio_FilamentInstance_nGetMaterialInstances(JNIEnv* env, jclass,
jlong nativeInstance, jlongArray result) {
FilamentInstance* instance = (FilamentInstance*) nativeInstance;
jsize available = env->GetArrayLength(result);
jsize count = std::min(available, (jsize) instance->getMaterialInstanceCount());
jlong* dst = env->GetLongArrayElements(result, nullptr);
const MaterialInstance * const* src = instance->getMaterialInstances();
for (jsize i = 0; i < count; i++) {
dst[i] = (jlong) src[i];
}
env->ReleaseLongArrayElements(result, dst, 0);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_gltfio_FilamentInstance_nAttachSkin(JNIEnv* env, jclass,
jlong nativeInstance, jint skinIndex, jint targetEntity) {
@@ -23,7 +23,6 @@ import androidx.annotation.Nullable;
import com.google.android.filament.Box;
import com.google.android.filament.Engine;
import com.google.android.filament.Entity;
import com.google.android.filament.MaterialInstance;
/**
* Owns a bundle of Filament objects that have been created by <code>AssetLoader</code>.
@@ -35,25 +34,32 @@ import com.google.android.filament.MaterialInstance;
* <code>NameComponentManager</code>, <code>RenderableManager</code>, and others.</p>
*
* <p>In addition to the aforementioned entities, an asset has strong ownership over a list of
* <code>VertexBuffer</code>, <code>IndexBuffer</code>, <code>MaterialInstance</code>, and
* <code>Texture</code>.</p>
* <code>VertexBuffer</code>, <code>IndexBuffer</code>, and <code>Texture</code>.</p>
*
* <p>Clients can use {@link ResourceLoader} to create textures, compute tangent quaternions, and
* upload data into vertex buffers and index buffers.</p>
*
* @see ResourceLoader
* @see Animator
* @see FilamentInstance
* @see AssetLoader
*/
public class FilamentAsset {
private long mNativeObject;
private Animator mAnimator;
private FilamentInstance mPrimaryInstance;
private Engine mEngine;
FilamentAsset(Engine engine, long nativeObject) {
mEngine = engine;
mNativeObject = nativeObject;
mAnimator = null;
}
public FilamentInstance getInstance() {
if (mPrimaryInstance != null) {
return mPrimaryInstance;
}
long nativeInstance = nGetInstance(getNativeObject());
mPrimaryInstance = new FilamentInstance(this, nativeInstance);
return mPrimaryInstance;
}
long getNativeObject() {
@@ -169,17 +175,6 @@ public class FilamentAsset {
return result;
}
public @NonNull MaterialInstance[] getMaterialInstances() {
final int count = nGetMaterialInstanceCount(mNativeObject);
MaterialInstance[] result = new MaterialInstance[count];
long[] natives = new long[count];
nGetMaterialInstances(mNativeObject, natives);
for (int i = 0; i < count; i++) {
result[i] = new MaterialInstance(mEngine, natives[i]);
}
return result;
}
/**
* Gets the bounding box computed from the supplied min / max values in glTF accessors.
*
@@ -209,25 +204,6 @@ public class FilamentAsset {
return nGetExtras(mNativeObject, entity);
}
/**
* Retrieves the <code>Animator</code> interface for this asset.
*
* <p>When calling this for the first time, this must be called after
* {@link ResourceLoader#loadResources} or {@link ResourceLoader#asyncBeginLoad}. When the asset
* is destroyed, its animator becomes invalid.</p>
*/
public @NonNull Animator getAnimator() {
if (mAnimator != null) {
return mAnimator;
}
long nativeAnimator = nGetAnimator(getNativeObject());
if (nativeAnimator == 0) {
throw new IllegalStateException("Unable to create animator");
}
mAnimator = new Animator(nativeAnimator);
return mAnimator;
}
/**
* Gets the names of all morph targets in the given entity.
*/
@@ -246,31 +222,6 @@ public class FilamentAsset {
return uris;
}
/**
* Returns the names of all material variants.
*/
public @NonNull String[] getMaterialVariantNames() {
String[] names = new String[nGetMaterialVariantCount(mNativeObject)];
nGetMaterialVariantNames(mNativeObject, names);
return names;
}
/**
* Applies the given material variant to all primitives that it affects.
*
* This is efficient because it merely swaps around persistent MaterialInstances. If you change
* a material parameter while a certain variant is active, the updated value will be remembered
* after you re-apply that variant.
*
* If the asset is instanced, this affects all instances in the same way.
* To set the variant on an individual instance, use FilamentInstance#applyMaterialVariant.
*
* Ignored if variantIndex is out of bounds.
*/
public void applyMaterialVariant(@IntRange(from = 0) int variantIndex) {
nApplyMaterialVariant(getNativeObject(), variantIndex);
}
/**
* Reclaims CPU-side memory for URI strings, binding lists, and raw animation data.
*
@@ -282,8 +233,10 @@ public class FilamentAsset {
nReleaseSourceData(mNativeObject);
}
public Engine getEngine() { return mEngine; }
void clearNativeObject() {
if (mAnimator != null) mAnimator.clearNativeObject();
mPrimaryInstance = null;
mNativeObject = 0;
}
@@ -307,21 +260,17 @@ public class FilamentAsset {
private static native int nGetCameraEntityCount(long nativeAsset);
private static native void nGetCameraEntities(long nativeAsset, int[] result);
private static native int nGetMaterialInstanceCount(long nativeAsset);
private static native void nGetMaterialInstances(long nativeAsset, long[] nativeResults);
private static native int nGetMaterialVariantCount(long nativeAsset);
private static native void nGetMaterialVariantNames(long nativeAsset, String[] result);
private static native int nGetMorphTargetCount(long nativeAsset, int entity);
private static native void nGetMorphTargetNames(long nativeAsset, int entity, String[] result);
private static native void nGetBoundingBox(long nativeAsset, float[] box);
private static native String nGetName(long nativeAsset, int entity);
private static native String nGetExtras(long nativeAsset, int entity);
private static native long nGetAnimator(long nativeAsset);
private static native void nApplyMaterialVariant(long nativeAsset, int variantIndex);
private static native long nGetInstance(long nativeAsset);
private static native int nGetResourceUriCount(long nativeAsset);
private static native void nGetResourceUris(long nativeAsset, String[] result);
private static native void nReleaseSourceData(long nativeAsset);
}
@@ -19,7 +19,9 @@ package com.google.android.filament.gltfio;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import com.google.android.filament.Engine;
import com.google.android.filament.Entity;
import com.google.android.filament.MaterialInstance;
/**
* Provides access to a hierarchy of entities that have been instanced from a glTF asset.
@@ -146,11 +148,39 @@ public class FilamentInstance {
nApplyMaterialVariant(mNativeObject, variantIndex);
}
public @NonNull MaterialInstance[] getMaterialInstances() {
final int count = nGetMaterialInstanceCount(mNativeObject);
MaterialInstance[] result = new MaterialInstance[count];
long[] natives = new long[count];
nGetMaterialInstances(mNativeObject, natives);
Engine engine = mAsset.getEngine();
for (int i = 0; i < count; i++) {
result[i] = new MaterialInstance(engine, natives[i]);
}
return result;
}
/**
* Returns the names of all material variants.
*/
public @NonNull String[] getMaterialVariantNames() {
String[] names = new String[nGetMaterialVariantCount(mNativeObject)];
nGetMaterialVariantNames(mNativeObject, names);
return names;
}
private static native int nGetRoot(long nativeInstance);
private static native int nGetEntityCount(long nativeInstance);
private static native void nGetEntities(long nativeInstance, int[] result);
private static native long nGetAnimator(long nativeInstance);
private static native int nGetMaterialInstanceCount(long nativeAsset);
private static native void nGetMaterialInstances(long nativeAsset, long[] nativeResults);
private static native void nApplyMaterialVariant(long nativeInstance, int variantIndex);
private static native int nGetMaterialVariantCount(long nativeAsset);
private static native void nGetMaterialVariantNames(long nativeAsset, String[] result);
private static native void nGetJointsAt(long nativeInstance, int skinIndex, int[] result);
private static native int nGetSkinCount(long nativeInstance);
private static native void nGetSkinNames(long nativeInstance, String[] result);
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.27.2
VERSION_NAME=1.28.2
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+3 -3
View File
@@ -112,9 +112,9 @@ the Android SDK.
## Android Studio
You must use Android Studio 3.6 RC 1 or higher. To open the project, point Studio to the `android`
folder. After opening the project and syncing to gradle, select the sample of your choice using the
drop-down widget in the toolbar.
You must use the latest stable release of Android Studio. To open the project, point Studio to the
`android` folder. After opening the project and syncing to gradle, select the sample of your choice
using the drop-down widget in the toolbar.
## Compiling
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env bash
set -e
function print_help {
local self_name=$(basename "$0")
echo "This tool verifies that every Metal shader in the given Filament material file(s) successfully compiles."
echo "Requires the matinfo tool and xcrun to be on the system PATH."
echo ""
echo "Usage:"
echo " $self_name [options] [<material> ...]"
echo ""
echo "Options:"
echo " -h"
echo " Print this help message."
echo " -w"
echo " Pass the -w flag to the Metal compiler, to disable warnings."
echo ""
}
COMP_FLAGS=""
while getopts ":hw" opt; do
case ${opt} in
h)
print_help
exit 1
;;
w)
COMP_FLAGS="${COMP_FLAGS} -w"
;;
esac
done
shift $((OPTIND - 1))
if [[ "$#" == "0" ]]; then
print_help
exit 1
fi
# Make sure matinfo and xcrun are available.
if [[ ! $(command -v matinfo) ]]; then
echo "Error: matinfo not on PATH."
exit 1
fi
if [[ ! $(command -v xcrun) ]]; then
echo "Error: xcrun not on PATH."
exit 1
fi
function check_material {
local material="$1"
tmpdir="$(mktemp -d /tmp/check_metal_shaders.XXXXX)"
# First check that the material is valid.
if [[ ! $(matinfo "${material}") ]]; then
echo "Invalid material file: ${material}"
exit 1
fi
echo "Checking that Metal shaders compile: ${material}"
set +e
local i=0
while true; do
# The file must have a .metal extension.
metalFile="${tmpdir}/${i}.metal"
# Extract shader i. matinfo will return a non-zero exit code if the shader doesn't exist,
# which means we're finished with this material.
matinfo --print-metal=${i} "${material}" > "${metalFile}" 2> /dev/null
if [[ "$?" -ne 0 ]]; then
break
fi
echo "Testing Metal shader ${i}"
# Attempt to compile the Metal shader.
xcrun -sdk macosx metal ${COMP_FLAGS} -c "${metalFile}" -o /dev/null
if [[ "$?" -ne 0 ]]; then
echo "Error compiling Metal shader: ${metalFile}"
exit 1
fi
((i++))
done
set -e
rm -r "${tmpdir}"
}
for material in "$@"
do
check_material "${material}"
done
+1 -1
View File
File diff suppressed because one or more lines are too long
+11 -12
View File
@@ -1432,12 +1432,13 @@ non-shader data.
</p><dl><table><tbody><tr valign="top"><td><dt>Type</dt></td><td><dd><p> <code>number</code>
</p></dd></td></tr><tr valign="top"><td><dt>Value</dt></td><td><dd><p> An integer value, either 1 or 2. Defaults to 1.
</p></dd></td></tr><tr valign="top"><td><dt>Value</dt></td><td><dd><p> An integer value, either 1, 2 or 3. Defaults to 1.
</p></dd></td></tr></tbody></table></dl><div class="table">
<table class="table"><tbody><tr><th style="text-align:left"> Feature Level </th><th style="text-align:left"> Guaranteed features </th></tr>
<tr><td style="text-align:left"> 1 </td><td style="text-align:left"> 9 textures per material </td></tr>
<tr><td style="text-align:left"> 2 </td><td style="text-align:left"> 12 textures per material, cubemap arrays, ESSL 3.10 </td></tr>
<tr><td style="text-align:left"> 2 </td><td style="text-align:left"> 9 textures per material, cubemap arrays, ESSL 3.10 </td></tr>
<tr><td style="text-align:left"> 3 </td><td style="text-align:left"> 12 textures per material, cubemap arrays, ESSL 3.10 </td></tr>
</tbody></table><center><div class="tablecaption"><a class="target" name="table_featurelevels">&nbsp;</a><b style="font-style:normal;">Table&nbsp;13:</b> Feature levels</div></center></div>
<p></p><p>
@@ -2036,23 +2037,21 @@ and sorting issues are minimized or eliminated</span></center></div></center>
</p></dd><dt>Value</dt><dd><p> <code>true</code> or <code>false</code>. Defaults to <code>false</code>.
</p></dd><dt>Description</dt><dd><p> Only available in the <code>unlit</code> shading model. If this property is enabled, the final color
computed by the material is multiplied by the shadowing factor (or visibility). This allows to
create transparent shadow-receiving objects (for instance an invisible ground plane in AR).
This is only supported with shadows from directional lights.
</p></dd><dt>Description</dt><dd><p> Enables two-sided rendering and its capability to be toggled at run time. When set to <code>true</code>,
<code>culling</code> is automatically set to <code>none</code>; if the triangle is back-facing, the triangle's
normal is flipped to become front-facing. When explicitly set to <code>false</code>, this allows the
double-sidedness to be toggled at run time.
</p></dd></dl><p></p><pre class="listing tilde"><code><span class="line">material {</span>
<span class="line"> name : <span class="hljs-string">"Invisible shadow plane"</span>,</span>
<span class="line"> shadingModel : unlit,</span>
<span class="line"> shadowMultiplier : <span class="hljs-literal">true</span>,</span>
<span class="line"> blending : transparent</span>
<span class="line"> name : <span class="hljs-string">"Double sided material"</span>,</span>
<span class="line"> shadingModel : lit,</span>
<span class="line"> doubleSided : <span class="hljs-literal">true</span></span>
<span class="line">}</span>
<span class="line"></span>
<span class="line">fragment {</span>
<span class="line"> void material(inout MaterialInputs material) {</span>
<span class="line"> prepareMaterial(material);</span>
<span class="line"> <span class="hljs-comment">// baseColor defines the color and opacity of the final shadow</span></span>
<span class="line"> material.baseColor = vec4(0.0, 0.0, 0.0, 0.7);</span>
<span class="line"> material.baseColor = materialParams.albedo;</span>
<span class="line"> }</span>
<span class="line">}</span></code></pre>
<a class="target" name="lighting:reflections">&nbsp;</a><a class="target" name="materialdefinitions/materialblock/lighting:reflections">&nbsp;</a><a class="target" name="toc4.2.25">&nbsp;</a><h3>Lighting: reflections</h3>
+8 -10
View File
@@ -1582,24 +1582,22 @@ Value
: `true` or `false`. Defaults to `false`.
Description
: Only available in the `unlit` shading model. If this property is enabled, the final color
computed by the material is multiplied by the shadowing factor (or visibility). This allows to
create transparent shadow-receiving objects (for instance an invisible ground plane in AR).
This is only supported with shadows from directional lights.
: Enables two-sided rendering and its capability to be toggled at run time. When set to `true`,
`culling` is automatically set to `none`; if the triangle is back-facing, the triangle's
normal is flipped to become front-facing. When explicitly set to `false`, this allows the
double-sidedness to be toggled at run time.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
name : "Invisible shadow plane",
shadingModel : unlit,
shadowMultiplier : true,
blending : transparent
name : "Double sided material",
shadingModel : lit,
doubleSided : true
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
// baseColor defines the color and opacity of the final shadow
material.baseColor = vec4(0.0, 0.0, 0.0, 0.7);
material.baseColor = materialParams.albedo;
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+4 -1
View File
@@ -48,6 +48,7 @@ set(PUBLIC_HDRS
)
set(SRCS
src/AtlasAllocator.cpp
src/Box.cpp
src/BufferObject.cpp
src/Camera.cpp
@@ -72,6 +73,7 @@ set(SRCS
src/MaterialParser.cpp
src/MorphTargetBuffer.cpp
src/PerViewUniforms.cpp
src/PerShadowMapUniforms.cpp
src/PostProcessManager.cpp
src/RenderPass.cpp
src/RenderPrimitive.cpp
@@ -145,6 +147,7 @@ set(PRIVATE_HDRS
src/Intersections.h
src/MaterialParser.h
src/PerViewUniforms.h
src/PerShadowMapUniforms.h
src/PIDController.h
src/PostProcessManager.h
src/RendererUtils.h
@@ -198,7 +201,7 @@ set(PRIVATE_HDRS
src/materials/fsr/ffx_a.h
src/materials/fsr/ffx_fsr1.h
src/materials/fsr/ffx_fsr1_mobile.fs
src/upcast.h
src/downcast.h
)
set(MATERIAL_SRCS
+20
View File
@@ -450,3 +450,23 @@ if (APPLE AND NOT IOS)
set_target_properties(backend_test_mac PROPERTIES FOLDER Tests)
endif()
# ==================================================================================================
# Compute tests
if (NOT IOS AND NOT WEBGL)
add_executable(compute_test
test/ComputeTest.cpp
test/Arguments.cpp
test/test_ComputeBasic.cpp
)
target_link_libraries(compute_test PRIVATE
backend
getopt
gtest
)
set_target_properties(compute_test PROPERTIES FOLDER Tests)
endif()
+10 -5
View File
@@ -50,6 +50,7 @@ static constexpr uint64_t SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER = 0x8;
static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES.
static constexpr size_t MAX_SAMPLER_COUNT = 62; // Maximum needed at feature level 3.
static constexpr size_t MAX_VERTEX_BUFFER_COUNT = 16; // Max number of bound buffer objects.
static constexpr size_t MAX_SSBO_COUNT = 4; // This is guaranteed by OpenGL ES.
// Per feature level caps
// Use (int)FeatureLevel to index this array
@@ -219,8 +220,7 @@ enum class PrimitiveType : uint8_t {
LINES = 1, //!< lines
LINE_STRIP = 3, //!< line strip
TRIANGLES = 4, //!< triangles
TRIANGLE_STRIP = 5, //!< triangle strip
NONE = 0xFF
TRIANGLE_STRIP = 5 //!< triangle strip
};
/**
@@ -313,7 +313,8 @@ enum class ElementType : uint8_t {
//! Buffer object binding type
enum class BufferObjectBinding : uint8_t {
VERTEX,
UNIFORM
UNIFORM,
SHADER_STORAGE
};
//! Face culling Mode
@@ -897,7 +898,8 @@ struct RasterState {
enum class ShaderStage : uint8_t {
VERTEX = 0,
FRAGMENT = 1
FRAGMENT = 1,
COMPUTE = 2
};
static constexpr size_t PIPELINE_STAGE_COUNT = 2;
@@ -905,7 +907,8 @@ enum class ShaderStageFlags : uint8_t {
NONE = 0,
VERTEX = 0x1,
FRAGMENT = 0x2,
ALL_SHADER_STAGE_FLAGS = VERTEX | FRAGMENT
COMPUTE = 0x4,
ALL_SHADER_STAGE_FLAGS = VERTEX | FRAGMENT | COMPUTE
};
static inline constexpr bool hasShaderType(ShaderStageFlags flags, ShaderStage type) noexcept {
@@ -914,6 +917,8 @@ static inline constexpr bool hasShaderType(ShaderStageFlags flags, ShaderStage t
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::VERTEX));
case ShaderStage::FRAGMENT:
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::FRAGMENT));
case ShaderStage::COMPUTE:
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::COMPUTE));
}
}
+1 -1
View File
@@ -34,7 +34,7 @@ namespace filament::backend {
class Program {
public:
static constexpr size_t SHADER_TYPE_COUNT = 2;
static constexpr size_t SHADER_TYPE_COUNT = 3;
static constexpr size_t UNIFORM_BINDING_COUNT = CONFIG_UNIFORM_BINDING_COUNT;
static constexpr size_t SAMPLER_BINDING_COUNT = CONFIG_SAMPLER_BINDING_COUNT;
@@ -49,6 +49,11 @@ void removeGoogleLineDirectives(char* shader, size_t length) noexcept;
*/
size_t getFormatSize(TextureFormat format) noexcept;
/**
* Returns the number of component (1 to 4) for the given format.
*/
size_t getFormatComponentCount(TextureFormat format) noexcept;
/**
* For compressed texture formats, returns the number of horizontal texels per block. Otherwise
* returns 0.
@@ -158,6 +158,9 @@ DECL_DRIVER_API_0(flush)
// flush and wait for the effects to be done
DECL_DRIVER_API_0(finish)
// reset state tracking, if the driver does any state tracking (e.g. GL)
DECL_DRIVER_API_0(resetState)
/*
* Creating driver objects
* -----------------------
@@ -406,12 +409,17 @@ DECL_DRIVER_API_N(bindUniformBuffer,
uint32_t, index,
backend::BufferObjectHandle, ubh)
DECL_DRIVER_API_N(bindUniformBufferRange,
DECL_DRIVER_API_N(bindBufferRange,
BufferObjectBinding, bindingType,
uint32_t, index,
backend::BufferObjectHandle, ubh,
uint32_t, offset,
uint32_t, size)
DECL_DRIVER_API_N(unbindBuffer,
BufferObjectBinding, bindingType,
uint32_t, index)
DECL_DRIVER_API_N(bindSamplers,
uint32_t, index,
backend::SamplerGroupHandle, sbh)
@@ -444,6 +452,12 @@ DECL_DRIVER_API_N(readPixels,
uint32_t, height,
backend::PixelBufferDescriptor&&, data)
DECL_DRIVER_API_N(readBufferSubData,
backend::BufferObjectHandle, src,
uint32_t, offset,
uint32_t, size,
backend::BufferDescriptor&&, data)
/*
* Rendering operations
* --------------------
@@ -462,6 +476,11 @@ DECL_DRIVER_API_N(draw,
backend::RenderPrimitiveHandle, rph,
uint32_t, instanceCount)
DECL_DRIVER_API_N(dispatchCompute,
backend::ProgramHandle, program,
math::uint3, workGroupCount)
#pragma clang diagnostic pop
#undef EXPAND
+139 -16
View File
@@ -209,6 +209,129 @@ size_t getFormatSize(TextureFormat format) noexcept {
}
}
size_t getFormatComponentCount(TextureFormat format) noexcept {
switch (format) {
case TextureFormat::R8:
case TextureFormat::R8_SNORM:
case TextureFormat::R8UI:
case TextureFormat::R8I:
case TextureFormat::R16F:
case TextureFormat::R16UI:
case TextureFormat::R16I:
case TextureFormat::R32F:
case TextureFormat::R32I:
case TextureFormat::R32UI:
case TextureFormat::STENCIL8:
case TextureFormat::DEPTH16:
case TextureFormat::DEPTH24:
case TextureFormat::DEPTH32F:
return 1;
case TextureFormat::RG8:
case TextureFormat::RG8_SNORM:
case TextureFormat::RG8UI:
case TextureFormat::RG8I:
case TextureFormat::RG16F:
case TextureFormat::RG16UI:
case TextureFormat::RG16I:
case TextureFormat::RG32F:
case TextureFormat::RG32UI:
case TextureFormat::RG32I:
case TextureFormat::DEPTH24_STENCIL8:
case TextureFormat::DEPTH32F_STENCIL8:
return 2;
case TextureFormat::RGB565:
case TextureFormat::RGB8:
case TextureFormat::SRGB8:
case TextureFormat::RGB8_SNORM:
case TextureFormat::RGB8UI:
case TextureFormat::RGB8I:
case TextureFormat::R11F_G11F_B10F:
case TextureFormat::RGB16F:
case TextureFormat::RGB16UI:
case TextureFormat::RGB16I:
case TextureFormat::RGB32F:
case TextureFormat::RGB32UI:
case TextureFormat::RGB32I:
return 3;
case TextureFormat::RGB5_A1:
case TextureFormat::RGBA4:
case TextureFormat::RGB9_E5:
case TextureFormat::RGBA8:
case TextureFormat::SRGB8_A8:
case TextureFormat::RGBA8_SNORM:
case TextureFormat::RGB10_A2:
case TextureFormat::RGBA8UI:
case TextureFormat::RGBA8I:
case TextureFormat::RGBA16F:
case TextureFormat::RGBA16UI:
case TextureFormat::RGBA16I:
case TextureFormat::RGBA32F:
case TextureFormat::RGBA32UI:
case TextureFormat::RGBA32I:
return 4;
// Compressed formats ---------------------------------------------------------------------
case TextureFormat::EAC_R11:
case TextureFormat::EAC_R11_SIGNED:
return 1;
case TextureFormat::EAC_RG11:
case TextureFormat::EAC_RG11_SIGNED:
return 2;
case TextureFormat::ETC2_RGB8:
case TextureFormat::ETC2_SRGB8:
case TextureFormat::DXT1_RGB:
case TextureFormat::DXT1_SRGB:
return 3;
case TextureFormat::ETC2_EAC_RGBA8:
case TextureFormat::ETC2_EAC_SRGBA8:
case TextureFormat::ETC2_RGB8_A1:
case TextureFormat::ETC2_SRGB8_A1:
case TextureFormat::DXT1_RGBA:
case TextureFormat::DXT1_SRGBA:
case TextureFormat::DXT3_RGBA:
case TextureFormat::DXT3_SRGBA:
case TextureFormat::DXT5_RGBA:
case TextureFormat::DXT5_SRGBA:
case TextureFormat::RGBA_ASTC_4x4:
case TextureFormat::RGBA_ASTC_5x4:
case TextureFormat::RGBA_ASTC_5x5:
case TextureFormat::RGBA_ASTC_6x5:
case TextureFormat::RGBA_ASTC_6x6:
case TextureFormat::RGBA_ASTC_8x5:
case TextureFormat::RGBA_ASTC_8x6:
case TextureFormat::RGBA_ASTC_8x8:
case TextureFormat::RGBA_ASTC_10x5:
case TextureFormat::RGBA_ASTC_10x6:
case TextureFormat::RGBA_ASTC_10x8:
case TextureFormat::RGBA_ASTC_10x10:
case TextureFormat::RGBA_ASTC_12x10:
case TextureFormat::RGBA_ASTC_12x12:
case TextureFormat::SRGB8_ALPHA8_ASTC_4x4:
case TextureFormat::SRGB8_ALPHA8_ASTC_5x4:
case TextureFormat::SRGB8_ALPHA8_ASTC_5x5:
case TextureFormat::SRGB8_ALPHA8_ASTC_6x5:
case TextureFormat::SRGB8_ALPHA8_ASTC_6x6:
case TextureFormat::SRGB8_ALPHA8_ASTC_8x5:
case TextureFormat::SRGB8_ALPHA8_ASTC_8x6:
case TextureFormat::SRGB8_ALPHA8_ASTC_8x8:
case TextureFormat::SRGB8_ALPHA8_ASTC_10x5:
case TextureFormat::SRGB8_ALPHA8_ASTC_10x6:
case TextureFormat::SRGB8_ALPHA8_ASTC_10x8:
case TextureFormat::SRGB8_ALPHA8_ASTC_10x10:
case TextureFormat::SRGB8_ALPHA8_ASTC_12x10:
case TextureFormat::SRGB8_ALPHA8_ASTC_12x12:
return 4;
case TextureFormat::UNUSED:
return 0;
}
}
size_t getBlockWidth(TextureFormat format) noexcept {
switch (format) {
case TextureFormat::EAC_RG11:
@@ -384,53 +507,53 @@ bool reshape(const PixelBufferDescriptor& data, PixelBufferDescriptor& reshaped)
namespace utils {
template<>
CString to_string<filament::backend::TextureUsage>(filament::backend::TextureUsage usage) noexcept {
CString to_string<filament::backend::TextureUsage>(filament::backend::TextureUsage value) noexcept {
using namespace filament::backend;
char string[7] = {'-', '-', '-', '-', '-', '-', 0};
if (any(usage & TextureUsage::UPLOADABLE)) {
if (any(value & TextureUsage::UPLOADABLE)) {
string[0]='U';
}
if (any(usage & TextureUsage::SAMPLEABLE)) {
if (any(value & TextureUsage::SAMPLEABLE)) {
string[1]='S';
}
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
if (any(value & TextureUsage::COLOR_ATTACHMENT)) {
string[2]='c';
}
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
if (any(value & TextureUsage::DEPTH_ATTACHMENT)) {
string[3]='d';
}
if (any(usage & TextureUsage::STENCIL_ATTACHMENT)) {
if (any(value & TextureUsage::STENCIL_ATTACHMENT)) {
string[4] = 's';
}
if (any(usage & TextureUsage::SUBPASS_INPUT)) {
if (any(value & TextureUsage::SUBPASS_INPUT)) {
string[5]='f';
}
return CString(string, 6);
return { string, 6 };
}
template<>
CString to_string<filament::backend::TargetBufferFlags>(filament::backend::TargetBufferFlags flags) noexcept {
CString to_string<filament::backend::TargetBufferFlags>(filament::backend::TargetBufferFlags value) noexcept {
using namespace filament::backend;
char string[7] = {'-', '-', '-', '-', '-', '-', 0};
if (any(flags & TargetBufferFlags::COLOR0)) {
if (any(value & TargetBufferFlags::COLOR0)) {
string[0]='0';
}
if (any(flags & TargetBufferFlags::COLOR1)) {
if (any(value & TargetBufferFlags::COLOR1)) {
string[1]='1';
}
if (any(flags & TargetBufferFlags::COLOR2)) {
if (any(value & TargetBufferFlags::COLOR2)) {
string[2]='2';
}
if (any(flags & TargetBufferFlags::COLOR3)) {
if (any(value & TargetBufferFlags::COLOR3)) {
string[3]='3';
}
if (any(flags & TargetBufferFlags::DEPTH)) {
if (any(value & TargetBufferFlags::DEPTH)) {
string[4]='D';
}
if (any(flags & TargetBufferFlags::STENCIL)) {
if (any(value & TargetBufferFlags::STENCIL)) {
string[5]='S';
}
return CString(string, 6);
return { string, 6 };
}
} // namespace utils
+22 -1
View File
@@ -59,6 +59,21 @@ public:
}
}
static void copyImage(uint8_t* UTILS_RESTRICT dest,
const uint8_t* UTILS_RESTRICT src,
size_t srcBytesPerRow, size_t /*srcChannelCount*/,
size_t dstBytesPerRow, size_t /*dstChannelCount*/,
size_t /*width*/, size_t height, bool /*swizzle*/) {
if (srcBytesPerRow == dstBytesPerRow) {
std::memcpy(dest, src, height * srcBytesPerRow);
return;
}
const size_t minBytesPerRow = std::min(srcBytesPerRow, dstBytesPerRow);
for (size_t i = 0; i < height; ++i, src += srcBytesPerRow, dest += dstBytesPerRow) {
std::memcpy(dest, src, minBytesPerRow);
}
}
// Converts a n-channel image of UBYTE, INT, UINT, or FLOAT to a different type.
template<typename dstComponentType, typename srcComponentType>
static void reshapeImage(uint8_t* UTILS_RESTRICT dest, const uint8_t* UTILS_RESTRICT src,
@@ -119,7 +134,13 @@ public:
switch (dst->type) {
case UBYTE:
switch (srcType) {
case UBYTE: reshaper = reshapeImage<uint8_t, uint8_t>; break;
case UBYTE:
reshaper = reshapeImage<uint8_t, uint8_t>;
if (dst->format == PixelDataFormat::RGBA &&
dstChannelCount == srcChannelCount && !swizzle) {
reshaper = copyImage;
}
break;
case FLOAT: reshaper = reshapeImage<uint8_t, float>; break;
case INT: reshaper = reshapeImage<uint8_t, int32_t>; break;
case UINT: reshaper = reshapeImage<uint8_t, uint32_t>; break;
+6 -2
View File
@@ -42,7 +42,9 @@
#include "vulkan/PlatformVkCocoa.h"
#endif
#elif defined(__linux__)
#if defined(FILAMENT_SUPPORTS_WAYLAND)
#if defined(FILAMENT_SUPPORTS_GGP)
#include "vulkan/PlatformVkLinuxGGP.h"
#elif defined(FILAMENT_SUPPORTS_WAYLAND)
#if defined (FILAMENT_DRIVER_SUPPORTS_VULKAN)
#include "vulkan/PlatformVkLinuxWayland.h"
#endif
@@ -120,7 +122,9 @@ DefaultPlatform* DefaultPlatform::create(Backend* backend) noexcept {
#elif defined(IOS)
return new PlatformVkCocoaTouch();
#elif defined(__linux__)
#if defined(FILAMENT_SUPPORTS_WAYLAND)
#if defined(FILAMENT_SUPPORTS_GGP)
return new PlatformVkLinuxGGP();
#elif defined(FILAMENT_SUPPORTS_WAYLAND)
return new PlatformVkLinuxWayland();
#elif defined(FILAMENT_SUPPORTS_X11)
return new PlatformVkLinuxX11();
@@ -91,12 +91,14 @@ blitterFrag(VertexOut in [[stage_in]],
{
FragmentOut out = {};
#if defined(BLIT_COLOR) || defined(BLIT_DEPTH)
// These coordinates match the Vulkan vkCmdBlitImage spec:
// https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdBlitImage.html
float2 uvbase = in.position.xy; // unnormalized coordinates at center of texel: (1.5, 2.5, etc)
float2 uvoffset = uvbase - args->dstOffset;
float2 uvscaled = uvoffset * args->scale;
float2 uv = uvscaled + args->srcOffset;
#endif
#ifdef BLIT_COLOR
#ifdef MSAA_COLOR_SOURCE
+30 -14
View File
@@ -26,14 +26,16 @@
#include <utils/compiler.h>
#include <tuple>
#include <utility>
#include <memory>
namespace filament::backend {
class MetalBuffer {
public:
MetalBuffer(MetalContext& context, BufferUsage usage, size_t size, bool forceGpuBuffer = false);
MetalBuffer(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage,
size_t size, bool forceGpuBuffer = false);
~MetalBuffer();
MetalBuffer(const MetalBuffer& rhs) = delete;
@@ -60,18 +62,21 @@ public:
void* getCpuBuffer() const noexcept { return mCpuBuffer; }
enum Stage {
VERTEX = 1,
FRAGMENT = 2
enum Stage : uint8_t {
VERTEX = 1u << 0u,
FRAGMENT = 1u << 1u,
COMPUTE = 1u << 2u
};
/**
* Bind multiple buffers to pipeline stages.
*
* bindBuffers binds an array of buffers to the given stage(s) of a MTLRenderCommandEncoder's
* pipeline.
* bindBuffers binds an array of buffers to the given stage(s) of a MTLCommandEncoders's
* pipeline. The encoder must be either a MTLRenderCommandEncoder or a MTLComputeCommandEncoder.
* For MTLRenderCommandEncoders, only the VERTEX and FRAGMENT stages may be specified.
* For MTLComputeCommandEncoders, only the COMPUTE stage may be specified.
*/
static void bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLRenderCommandEncoder> encoder,
static void bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncoder> encoder,
size_t bufferStart, uint8_t stages, MetalBuffer* const* buffers, size_t const* offsets,
size_t count);
@@ -106,7 +111,12 @@ public:
// address space. Constant buffers have specific alignment requirements when specifying an
// offset.
#if defined(IOS)
#if TARGET_OS_SIMULATOR
// The iOS simulator has differing alignment requirements.
static constexpr auto METAL_CONSTANT_BUFFER_OFFSET_ALIGNMENT = 256;
#else
static constexpr auto METAL_CONSTANT_BUFFER_OFFSET_ALIGNMENT = 4;
#endif // TARGET_OS_SIMULATOR
#else
static constexpr auto METAL_CONSTANT_BUFFER_OFFSET_ALIGNMENT = 32;
#endif
@@ -131,8 +141,8 @@ public:
* ring buffer allocation will be freed.
* @return the id<MTLBuffer> and offset for the new allocation
*/
std::tuple<id<MTLBuffer>, NSUInteger> createNewAllocation(id<MTLCommandBuffer> cmdBuffer) {
const auto occupiedSlots = mOccupiedSlots.load(std::memory_order_relaxed);
std::pair<id<MTLBuffer>, NSUInteger> createNewAllocation(id<MTLCommandBuffer> cmdBuffer) {
const auto occupiedSlots = mOccupiedSlots->load(std::memory_order_relaxed);
assert_invariant(occupiedSlots <= mSlotCount);
if (UTILS_UNLIKELY(occupiedSlots == mSlotCount)) {
// We don't have any room left, so we fall back to creating a one-off aux buffer.
@@ -144,14 +154,19 @@ public:
return {mAuxBuffer, 0};
}
mCurrentSlot = (mCurrentSlot + 1) % mSlotCount;
mOccupiedSlots.fetch_add(1, std::memory_order_relaxed);
mOccupiedSlots->fetch_add(1, std::memory_order_relaxed);
// Release the previous allocation.
if (UTILS_UNLIKELY(mAuxBuffer)) {
mAuxBuffer = nil;
} else {
// Capture the mOccupiedSlots var via a weak_ptr because the MetalRingBuffer could be
// destructed before the block executes.
std::weak_ptr<AtomicCounterType> slots = mOccupiedSlots;
[cmdBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
mOccupiedSlots.fetch_sub(1, std::memory_order_relaxed);
if (auto s = slots.lock()) {
s->fetch_sub(1, std::memory_order_relaxed);
}
}];
}
return getCurrentAllocation();
@@ -163,7 +178,7 @@ public:
* ring buffer allocation will be freed.
* @return the id<MTLBuffer> and offset for the current allocation
*/
std::tuple<id<MTLBuffer>, NSUInteger> getCurrentAllocation() const {
std::pair<id<MTLBuffer>, NSUInteger> getCurrentAllocation() const {
if (UTILS_UNLIKELY(mAuxBuffer)) {
return { mAuxBuffer, 0 };
}
@@ -185,7 +200,8 @@ private:
NSUInteger mSlotCount;
NSUInteger mCurrentSlot = 0;
std::atomic<NSUInteger> mOccupiedSlots = 1;
using AtomicCounterType = std::atomic<NSUInteger>;
std::shared_ptr<AtomicCounterType> mOccupiedSlots = std::make_shared<AtomicCounterType>(1);
};
} // namespace filament::backend
+50 -20
View File
@@ -21,15 +21,17 @@
namespace filament {
namespace backend {
MetalBuffer::MetalBuffer(MetalContext& context, BufferUsage usage, size_t size, bool forceGpuBuffer)
: mBufferSize(size), mContext(context) {
MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage,
size_t size, bool forceGpuBuffer) : mBufferSize(size), mContext(context) {
// If the buffer is less than 4K in size and is updated frequently, we don't use an explicit
// buffer. Instead, we use immediate command encoder methods like setVertexBytes:length:atIndex:.
if (size <= 4 * 1024 && usage == BufferUsage::DYNAMIC && !forceGpuBuffer) {
mBuffer = nil;
mCpuBuffer = malloc(size);
return;
}
// This won't work for SSBOs, since they are read/write.
if (size <= 4 * 1024 && bindingType != BufferObjectBinding::SHADER_STORAGE &&
usage == BufferUsage::DYNAMIC && !forceGpuBuffer) {
mBuffer = nil;
mCpuBuffer = malloc(size);
return;
}
// Otherwise, we allocate a private GPU buffer.
mBuffer = [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate];
@@ -93,9 +95,24 @@ id<MTLBuffer> MetalBuffer::getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) n
return mBuffer;
}
void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLRenderCommandEncoder> encoder,
void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncoder> encoder,
size_t bufferStart, uint8_t stages, MetalBuffer* const* buffers, size_t const* offsets,
size_t count) {
// Ensure we were given the correct type of encoder:
// either a MTLRenderCommandEncoder or a MTLComputeCommandEncoder.
if (stages & MetalBuffer::Stage::VERTEX || stages & MetalBuffer::Stage::FRAGMENT) {
assert_invariant([encoder respondsToSelector:@selector(setVertexBuffers:offsets:withRange:)]);
assert_invariant([encoder respondsToSelector:@selector(setFragmentBuffers:offsets:withRange:)]);
assert_invariant([encoder respondsToSelector:@selector(setVertexBytes:length:atIndex:)]);
assert_invariant([encoder respondsToSelector:@selector(setFragmentBytes:length:atIndex:)]);
assert_invariant(!(stages & MetalBuffer::Stage::COMPUTE));
}
if (stages & MetalBuffer::Stage::COMPUTE) {
assert_invariant([encoder respondsToSelector:@selector(setBuffers:offsets:withRange:)]);
assert_invariant([encoder respondsToSelector:@selector(setBytes:length:atIndex:)]);
assert_invariant(!(stages & (MetalBuffer::Stage::FRAGMENT | MetalBuffer::Stage::VERTEX)));
}
const NSRange bufferRange = NSMakeRange(bufferStart, count);
constexpr size_t MAX_BUFFERS = 16;
@@ -120,14 +137,19 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLRenderComman
}
if (stages & Stage::VERTEX) {
[encoder setVertexBuffers:metalBuffers.data()
offsets:metalOffsets.data()
withRange:bufferRange];
[(id<MTLRenderCommandEncoder>) encoder setVertexBuffers:metalBuffers.data()
offsets:metalOffsets.data()
withRange:bufferRange];
}
if (stages & Stage::FRAGMENT) {
[encoder setFragmentBuffers:metalBuffers.data()
offsets:metalOffsets.data()
withRange:bufferRange];
[(id<MTLRenderCommandEncoder>) encoder setFragmentBuffers:metalBuffers.data()
offsets:metalOffsets.data()
withRange:bufferRange];
}
if (stages & Stage::COMPUTE) {
[(id<MTLComputeCommandEncoder>) encoder setBuffers:metalBuffers.data()
offsets:metalOffsets.data()
withRange:bufferRange];
}
for (size_t b = 0; b < count; b++) {
@@ -146,14 +168,22 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLRenderComman
auto* bytes = static_cast<const uint8_t*>(cpuBuffer);
if (stages & Stage::VERTEX) {
[encoder setVertexBytes:(bytes + offset)
length:(buffer->getSize() - offset)
atIndex:bufferIndex];
[(id<MTLRenderCommandEncoder>) encoder setVertexBytes:(bytes + offset)
length:(buffer->getSize() - offset)
atIndex:bufferIndex];
}
if (stages & Stage::FRAGMENT) {
[encoder setFragmentBytes:(bytes + offset)
length:(buffer->getSize() - offset)
atIndex:bufferIndex];
[(id<MTLRenderCommandEncoder>) encoder setFragmentBytes:(bytes + offset)
length:(buffer->getSize() - offset)
atIndex:bufferIndex];
}
if (stages & Stage::COMPUTE) {
// TODO: using setBytes means the data is read-only, which currently isn't enforced.
// In practice this won't be an issue since MetalBuffer ensures all SSBOs are realized
// through actual id<MTLBuffer> allocations.
[(id<MTLComputeCommandEncoder>) encoder setBytes:(bytes + offset)
length:(buffer->getSize() - offset)
atIndex:bufferIndex];
}
}
}
+16 -8
View File
@@ -25,6 +25,7 @@
#include <QuartzCore/QuartzCore.h>
#include <array>
#include <atomic>
#include <stack>
#if defined(FILAMENT_METAL_PROFILING)
@@ -41,11 +42,12 @@ class MetalDriver;
class MetalBlitter;
class MetalBufferPool;
class MetalRenderTarget;
class MetalSamplerGroup;
class MetalSwapChain;
class MetalTexture;
class MetalTimerQueryInterface;
struct MetalUniformBuffer;
struct MetalIndexBuffer;
struct MetalSamplerGroup;
struct MetalVertexBuffer;
constexpr static uint8_t MAX_SAMPLE_COUNT = 8; // Metal devices support at most 8 MSAA samples
@@ -58,8 +60,12 @@ struct MetalContext {
id<MTLCommandBuffer> pendingCommandBuffer = nullptr;
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nullptr;
std::atomic<bool> memorylessLimitsReached = false;
// Supported features.
bool supportsTextureSwizzling = false;
bool supportsAutoDepthResolve = false;
bool supportsMemorylessRenderTargets = false;
uint8_t maxColorRenderTargets = 4;
struct {
uint8_t common;
@@ -79,7 +85,8 @@ struct MetalContext {
// State trackers.
PipelineStateTracker pipelineState;
DepthStencilStateTracker depthStencilState;
UniformBufferState uniformState[UNIFORM_BUFFER_COUNT];
std::array<BufferState, Program::UNIFORM_BINDING_COUNT> uniformState;
std::array<BufferState, MAX_SSBO_COUNT> ssboState;
CullModeStateTracker cullModeState;
WindingStateTracker windingState;
@@ -87,11 +94,16 @@ struct MetalContext {
DepthStencilStateCache depthStencilStateCache;
PipelineStateCache pipelineStateCache;
SamplerStateCache samplerStateCache;
ArgumentEncoderCache argumentEncoderCache;
MetalSamplerGroup* samplerBindings[SAMPLER_BINDING_COUNT] = {};
MetalSamplerGroup* samplerBindings[Program::SAMPLER_BINDING_COUNT] = {};
// Keeps track of all alive sampler groups.
// Keeps track of sampler groups we've finalized for the current render pass.
tsl::robin_set<MetalSamplerGroup*> finalizedSamplerGroups;
// Keeps track of all alive sampler groups, textures.
tsl::robin_set<MetalSamplerGroup*> samplerGroups;
tsl::robin_set<MetalTexture*> textures;
MetalBufferPool* bufferPool;
@@ -123,10 +135,6 @@ struct MetalContext {
os_log_t log;
os_signpost_id_t signpostId;
#endif
#ifndef NDEBUG
tsl::robin_set<HandleBase::HandleId> aliveTextures;
#endif
};
void initializeSupportedGpuFamilies(MetalContext* context);
@@ -103,6 +103,15 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
// all frames and their completion handlers finish before context is deallocated.
[context->pendingCommandBuffer addCompletedHandler:^(id <MTLCommandBuffer> buffer) {
context->resourceTracker.clearResources((__bridge void*) buffer);
auto errorCode = (MTLCommandBufferError)buffer.error.code;
if (@available(macOS 11.0, *)) {
if (errorCode == MTLCommandBufferErrorMemoryless) {
utils::slog.w << "Metal: memoryless geometry limit reached. "
"Continuing with private storage mode." << utils::io::endl;
context->memorylessLimitsReached = true;
}
}
}];
ASSERT_POSTCONDITION(context->pendingCommandBuffer, "Could not obtain command buffer.");
return context->pendingCommandBuffer;
+6 -5
View File
@@ -34,10 +34,12 @@ namespace backend {
class MetalPlatform;
class MetalBuffer;
class MetalSamplerGroup;
class MetalTexture;
struct MetalUniformBuffer;
struct MetalContext;
struct MetalProgram;
struct UniformBufferState;
struct BufferState;
#ifndef FILAMENT_METAL_HANDLE_ARENA_SIZE_IN_MB
#define FILAMENT_METAL_HANDLE_ARENA_SIZE_IN_MB 8
@@ -126,10 +128,9 @@ private:
inline void setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph, PrimitiveType pt,
uint32_t offset, uint32_t minIndex, uint32_t maxIndex, uint32_t count);
void enumerateSamplerGroups(const MetalProgram* program, ShaderStage shaderType,
const std::function<void(const SamplerDescriptor*, size_t)>& f);
void enumerateBoundUniformBuffers(const std::function<void(const UniformBufferState&,
MetalBuffer*, uint32_t)>& f);
void finalizeSamplerGroup(MetalSamplerGroup* sg);
void enumerateBoundBuffers(BufferObjectBinding bindingType,
const std::function<void(const BufferState&, MetalBuffer*, uint32_t)>& f);
};
+462 -205
View File
@@ -37,6 +37,8 @@
#include <utils/Log.h>
#include <utils/Panic.h>
#include <algorithm>
namespace filament {
namespace backend {
@@ -90,6 +92,18 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
mContext->highestSupportedGpuFamily.mac >= 2; // newer macOS GPUs
}
// In order to support resolve store action on depth attachment, the GPU needs to support it.
// Note that support for depth resolve implies support for stencil resolve using .sample0 resolve filter.
// (Other resolve filters are supported starting .apple5 and .mac2 families).
mContext->supportsAutoDepthResolve =
mContext->highestSupportedGpuFamily.apple >= 3 ||
mContext->highestSupportedGpuFamily.mac >= 2;
// In order to support memoryless render targets, an Apple GPU is needed.
// Available starting macOS 11.0, the first version to run on Apple GPUs.
// On iOS, it's available on all OS versions.
mContext->supportsMemorylessRenderTargets = mContext->highestSupportedGpuFamily.apple >= 1;
mContext->maxColorRenderTargets = 4;
if (mContext->highestSupportedGpuFamily.apple >= 2 ||
mContext->highestSupportedGpuFamily.mac >= 1) {
@@ -107,6 +121,7 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
mContext->pipelineStateCache.setDevice(mContext->device);
mContext->depthStencilStateCache.setDevice(mContext->device);
mContext->samplerStateCache.setDevice(mContext->device);
mContext->argumentEncoderCache.setDevice(mContext->device);
mContext->bufferPool = new MetalBufferPool(*mContext);
mContext->blitter = new MetalBlitter(*mContext);
@@ -188,6 +203,18 @@ void MetalDriver::endFrame(uint32_t frameId) {
assert_invariant(mContext->groupMarkers.empty());
// If we exceeded memoryless limits, turn it off for the rest of the lifetime of the driver.
if (mContext->supportsMemorylessRenderTargets && mContext->memorylessLimitsReached) {
for (MetalTexture* texture : mContext->textures) {
// Release memoryless MTLTexture-s, which are currently only the MSAA sidecars.
// Creation of new render targets is going to trigger the re-allocation of sidecars,
// with private storage mode from now on. Here, at the end of the frame,
// all render targets that have textures with sidecars are assumed to be destroyed.
texture->msaaSidecar = nil;
}
mContext->supportsMemorylessRenderTargets = false;
}
#if defined(FILAMENT_METAL_PROFILING)
os_signpost_interval_end(mContext->log, mContext->signpostId, "Frame encoding");
#endif
@@ -223,7 +250,7 @@ void MetalDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elem
void MetalDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage) {
construct_handle<MetalBufferObject>(boh, *mContext, usage, byteCount);
construct_handle<MetalBufferObject>(boh, *mContext, bindingType, usage, byteCount);
}
void MetalDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
@@ -233,13 +260,10 @@ void MetalDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8
auto& sc = mContext->sampleCountLookup;
samples = sc[std::min(MAX_SAMPLE_COUNT, samples)];
construct_handle<MetalTexture>(th, *mContext, target, levels, format, samples,
width, height, depth, usage, TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_1,
TextureSwizzle::CHANNEL_2, TextureSwizzle::CHANNEL_3);
#ifndef NDEBUG
mContext->aliveTextures.insert(th.getId());
#endif
mContext->textures.insert(construct_handle<MetalTexture>(th, *mContext,
target, levels, format, samples, width, height, depth, usage,
TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_1,
TextureSwizzle::CHANNEL_2, TextureSwizzle::CHANNEL_3));
}
void MetalDriver::createTextureSwizzledR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
@@ -250,12 +274,8 @@ void MetalDriver::createTextureSwizzledR(Handle<HwTexture> th, SamplerType targe
auto& sc = mContext->sampleCountLookup;
samples = sc[std::min(MAX_SAMPLE_COUNT, samples)];
construct_handle<MetalTexture>(th, *mContext, target, levels, format, samples,
width, height, depth, usage, r, g, b, a);
#ifndef NDEBUG
mContext->aliveTextures.insert(th.getId());
#endif
mContext->textures.insert(construct_handle<MetalTexture>(th, *mContext,
target, levels, format, samples, width, height, depth, usage, r, g, b, a));
}
void MetalDriver::importTextureR(Handle<HwTexture> th, intptr_t i,
@@ -276,12 +296,8 @@ void MetalDriver::importTextureR(Handle<HwTexture> th, intptr_t i,
ASSERT_PRECONDITION(metalTexture.textureType == filamentMetalType,
"Imported id<MTLTexture> type (%d) != Filament texture type (%d)",
metalTexture.textureType, filamentMetalType);
construct_handle<MetalTexture>(th, *mContext, target, levels, format, samples,
width, height, depth, usage, metalTexture);
#ifndef NDEBUG
mContext->aliveTextures.insert(th.getId());
#endif
mContext->textures.insert(construct_handle<MetalTexture>(th, *mContext,
target, levels, format, samples, width, height, depth, usage, metalTexture));
}
void MetalDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t size) {
@@ -309,6 +325,8 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
TargetBufferFlags targetBufferFlags, uint32_t width, uint32_t height,
uint8_t samples, MRT color,
TargetBufferInfo depth, TargetBufferInfo stencil) {
ASSERT_PRECONDITION(!isInRenderPass(mContext),
"createRenderTarget must be called outside of a render pass.");
// Clamp sample count to what the device supports.
auto& sc = mContext->sampleCountLookup;
samples = sc[std::min(MAX_SAMPLE_COUNT, samples)];
@@ -322,7 +340,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
ASSERT_PRECONDITION(buffer.handle,
"The COLOR%u flag was specified, but invalid color handle provided.", i);
auto colorTexture = handle_cast<MetalTexture>(buffer.handle);
ASSERT_PRECONDITION(colorTexture->texture,
ASSERT_PRECONDITION(colorTexture->getMtlTextureForWrite(),
"Color texture passed to render target has no texture allocation");
colorTexture->updateLodRange(buffer.level);
colorAttachments[i] = { colorTexture, color[i].level, color[i].layer };
@@ -333,7 +351,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
ASSERT_PRECONDITION(depth.handle,
"The DEPTH flag was specified, but invalid depth handle provided.");
auto depthTexture = handle_cast<MetalTexture>(depth.handle);
ASSERT_PRECONDITION(depthTexture->texture,
ASSERT_PRECONDITION(depthTexture->getMtlTextureForWrite(),
"Depth texture passed to render target has no texture allocation.");
depthTexture->updateLodRange(depth.level);
depthAttachment = { depthTexture, depth.level, depth.layer };
@@ -344,7 +362,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
ASSERT_PRECONDITION(stencil.handle,
"The STENCIL flag was specified, but invalid stencil handle provided.");
auto stencilTexture = handle_cast<MetalTexture>(stencil.handle);
ASSERT_PRECONDITION(stencilTexture->texture,
ASSERT_PRECONDITION(stencilTexture->getMtlTextureForWrite(),
"Stencil texture passed to render target has no texture allocation.");
stencilTexture->updateLodRange(stencil.level);
stencilAttachment = { stencilTexture, stencil.level, stencil.layer };
@@ -470,10 +488,20 @@ void MetalDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
return;
}
auto* bo = handle_cast<MetalBufferObject>(boh);
// Unbind this buffer object from any uniform slots it's still bound to.
// Unbind this buffer object from any uniform / SSBO slots it's still bound to.
bo->boundUniformBuffers.forEachSetBit([this](size_t index) {
mContext->uniformState[index].buffer = nullptr;
mContext->uniformState[index].bound = false;
mContext->uniformState[index] = BufferState {
.buffer = nullptr,
.offset = 0,
.bound = false
};
});
bo->boundSsbos.forEachSetBit([this](size_t index) {
mContext->ssboState[index] = BufferState {
.buffer = nullptr,
.offset = 0,
.bound = false
};
});
destruct_handle<MetalBufferObject>(boh);
}
@@ -510,21 +538,7 @@ void MetalDriver::destroyTexture(Handle<HwTexture> th) {
return;
}
#ifndef NDEBUG
mContext->aliveTextures.erase(th.getId());
#endif
// Unbind this texture from any sampler groups that currently reference it.
for (auto* metalSamplerGroup : mContext->samplerGroups) {
const SamplerDescriptor* samplers = metalSamplerGroup->sb->data();
for (size_t i = 0; i < metalSamplerGroup->sb->getSize(); i++) {
const SamplerDescriptor* sampler = samplers + i;
if (sampler->t == th) {
metalSamplerGroup->sb->setSampler(i, {{}, {}});
}
}
}
mContext->textures.erase(handle_cast<MetalTexture>(th));
destruct_handle<MetalTexture>(th);
}
@@ -689,7 +703,7 @@ bool MetalDriver::isFrameTimeSupported() {
}
bool MetalDriver::isAutoDepthResolveSupported() {
return true;
return mContext->supportsAutoDepthResolve;
}
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
@@ -705,10 +719,14 @@ bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
}
FeatureLevel MetalDriver::getFeatureLevel() {
// TODO: before we can return FEATURE_LEVEL_2 we need to decouple the samplers from
// the textures. Metal only supports 16 samplers, but at least 31 textures on all
// hardware.
return FeatureLevel::FEATURE_LEVEL_1;
// FEATURE_LEVEL_3 requires >= 31 textures, which all Metal devices support. However, older
// Metal devices only support 16 unique samplers. We could get around this in the future by
// virtualizing samplers.
if (mContext->highestSupportedGpuFamily.apple >= 6 ||
mContext->highestSupportedGpuFamily.mac >= 2) {
return FeatureLevel::FEATURE_LEVEL_3;
}
return FeatureLevel::FEATURE_LEVEL_2;
}
math::float2 MetalDriver::getClipSpaceParams() {
@@ -791,11 +809,15 @@ void MetalDriver::cancelExternalImage(void* image) {
}
void MetalDriver::setExternalImage(Handle<HwTexture> th, void* image) {
ASSERT_PRECONDITION(!isInRenderPass(mContext),
"setExternalImage must be called outside of a render pass.");
auto texture = handle_cast<MetalTexture>(th);
texture->externalImage.set((CVPixelBufferRef) image);
}
void MetalDriver::setExternalImagePlane(Handle<HwTexture> th, void* image, uint32_t plane) {
ASSERT_PRECONDITION(!isInRenderPass(mContext),
"setExternalImagePlane must be called outside of a render pass.");
auto texture = handle_cast<MetalTexture>(th);
texture->externalImage.set((CVPixelBufferRef) image, plane);
}
@@ -823,11 +845,7 @@ void MetalDriver::generateMipmaps(Handle<HwTexture> th) {
ASSERT_PRECONDITION(!isInRenderPass(mContext),
"generateMipmaps must be called outside of a render pass.");
auto tex = handle_cast<MetalTexture>(th);
id <MTLBlitCommandEncoder> blitEncoder = [getPendingCommandBuffer(mContext) blitCommandEncoder];
[blitEncoder generateMipmapsForTexture:tex->texture];
[blitEncoder endEncoding];
tex->minLod = 0;
tex->maxLod = tex->texture.mipmapLevelCount - 1;
tex->generateMipmaps();
}
bool MetalDriver::canGenerateMipmaps() {
@@ -839,39 +857,125 @@ void MetalDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh, BufferDescripto
"updateSamplerGroup must be called outside of a render pass.");
auto sb = handle_cast<MetalSamplerGroup>(sbh);
assert_invariant(sb->size == data.size / sizeof(SamplerDescriptor));
auto const* const samplers = (SamplerDescriptor const*) data.buffer;
#ifndef NDEBUG
// In debug builds, verify that all the textures in the sampler group are still alive.
// These bugs lead to memory corruption and can be difficult to track down.
auto const* const samplers = (SamplerDescriptor const*) data.buffer;
for (size_t s = 0; s < data.size / sizeof(SamplerDescriptor); s++) {
if (!samplers[s].t) {
continue;
}
auto iter = mContext->aliveTextures.find(samplers[s].t.getId());
if (iter == mContext->aliveTextures.end()) {
auto iter = mContext->textures.find(handle_cast<MetalTexture>(samplers[s].t));
if (iter == mContext->textures.end()) {
utils::slog.e << "updateSamplerGroup: texture #"
<< (int) s << " is dead, texture handle = "
<< samplers[s].t << utils::io::endl;
}
assert_invariant(iter != mContext->aliveTextures.end());
assert_invariant(iter != mContext->textures.end());
}
#endif
// FIXME: we shouldn't be using SamplerGroup here, instead the backend should create
// a descriptor or any internal data-structure that represents the textures/samplers.
// It's preferable to do as much work as possible here.
// Here, we emulate the older backend API by re-creating a SamplerGroup from the
// passed data.
SamplerGroup samplerGroup(data.size / sizeof(SamplerDescriptor));
memcpy(samplerGroup.data(), data.buffer, data.size);
*sb->sb = std::move(samplerGroup);
// Create a MTLArgumentEncoder for these textures.
// Ideally, we would create this encoder at createSamplerGroup time, but we need to know the
// texture type of each texture. It's also not guaranteed that the types won't change between
// calls to updateSamplerGroup.
utils::FixedCapacityVector<MTLTextureType> textureTypes(sb->size);
std::transform(samplers, samplers + data.size / sizeof(SamplerDescriptor), textureTypes.begin(),
[this](const SamplerDescriptor& sampler) {
if (!sampler.t) {
// Use Type2D for non-bound textures.
return MTLTextureType2D;
}
auto* t = handle_cast<MetalTexture>(sampler.t);
if (t->target == SamplerType::SAMPLER_EXTERNAL) {
// Use Type2D for external image textures.
return MTLTextureType2D;
}
id<MTLTexture> mtlTexture = t->getMtlTextureForRead();
assert_invariant(mtlTexture);
return mtlTexture.textureType;
});
auto& encoderCache = mContext->argumentEncoderCache;
id<MTLArgumentEncoder> encoder =
encoderCache.getOrCreateState(ArgumentEncoderState(std::move(textureTypes)));
sb->reset(getPendingCommandBuffer(mContext), encoder, mContext->device);
// In a perfect world, all the MTLTexture bindings would be known at updateSamplerGroup time.
// However, there are two special cases preventing this:
// 1. External images
// 2. LOD-clamped textures
//
// Both of these cases prevent us from knowing the final id<MTLTexture> that will be bound into
// the argument buffer representing the sampler group. So, we bind what we can now and wait
// until draw call time to bind any special cases (done in finalizeSamplerGroup).
// The good news is that once a render pass has started, the texture bindings won't change.
// A SamplerGroup is "finalized" when all of its textures have been set and is ready for use in
// a draw call.
// Even if we do know all the final textures at this point, we still wait until draw call time
// to call finalizeSamplerGroup, which has one additional responsibility: to call useResources
// for all the textures, which is required by Metal.
for (size_t s = 0; s < data.size / sizeof(SamplerDescriptor); s++) {
if (!samplers[s].t) {
// Assign a default texture / sampler to empty slots.
// Metal requires all samplers referenced in shaders to be bound.
id<MTLTexture> empty = getOrCreateEmptyTexture(mContext);
sb->setFinalizedTexture(s, empty);
id<MTLSamplerState> sampler = mContext->samplerStateCache.getOrCreateState({});
sb->setFinalizedSampler(s, sampler);
continue;
}
// First, bind the sampler state. We always know the full sampler state at
// updateSamplerGroup time.
SamplerState samplerState {
.samplerParams = samplers[s].s,
};
id<MTLSamplerState> sampler = mContext->samplerStateCache.getOrCreateState(samplerState);
sb->setFinalizedSampler(s, sampler);
sb->setTextureHandle(s, samplers[s].t);
auto* t = handle_cast<MetalTexture>(samplers[s].t);
assert_invariant(t);
// If this texture is an external texture, we defer binding the texture until draw call time
// (in finalizeSamplerGroup).
if (t->target == SamplerType::SAMPLER_EXTERNAL) {
continue;
}
if (!t->allLodsValid()) {
// The texture doesn't have all of its LODs loaded, and this could change by the time we
// issue a draw call with this sampler group. So, we defer binding the texture until
// draw call time (in finalizeSamplerGroup).
continue;
}
// If we get here, we know we have a valid MTLTexture that's guaranteed not to change.
id<MTLTexture> mtlTexture = t->getMtlTextureForRead();
assert_invariant(mtlTexture);
sb->setFinalizedTexture(s, mtlTexture);
}
scheduleDestroy(std::move(data));
}
void MetalDriver::beginRenderPass(Handle<HwRenderTarget> rth,
const RenderPassParams& params) {
#if defined(FILAMENT_METAL_PROFILING)
const char* renderPassName = "Unknown";
if (!mContext->groupMarkers.empty()) {
renderPassName = mContext->groupMarkers.top();
}
os_signpost_interval_begin(mContext->log, OS_SIGNPOST_ID_EXCLUSIVE, "Render pass", "%s",
renderPassName);
#endif
auto renderTarget = handle_cast<MetalRenderTarget>(rth);
mContext->currentRenderTarget = renderTarget;
mContext->currentRenderPassFlags = params.flags;
@@ -901,11 +1005,17 @@ void MetalDriver::beginRenderPass(Handle<HwRenderTarget> rth,
mContext->depthStencilState.invalidate();
mContext->cullModeState.invalidate();
mContext->windingState.invalidate();
mContext->finalizedSamplerGroups.clear();
}
void MetalDriver::nextSubpass(int dummy) {}
void MetalDriver::endRenderPass(int dummy) {
#if defined(FILAMENT_METAL_PROFILING)
os_signpost_interval_end(mContext->log, OS_SIGNPOST_ID_EXCLUSIVE, "Render pass");
#endif
[mContext->currentRenderPassEncoder endEncoding];
// Command encoders are one time use. Set it to nil to release the encoder and ensure we don't
@@ -951,34 +1061,101 @@ void MetalDriver::commit(Handle<HwSwapChain> sch) {
}
void MetalDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> boh) {
assert_invariant(index < UNIFORM_BUFFER_COUNT);
assert_invariant(index < Program::UNIFORM_BINDING_COUNT);
auto* bo = handle_cast<MetalBufferObject>(boh);
auto* currentBo = mContext->uniformState[index].buffer;
if (currentBo) {
currentBo->boundUniformBuffers.unset(index);
}
bo->boundUniformBuffers.set(index);
mContext->uniformState[index] = UniformBufferState{
mContext->uniformState[index] = BufferState{
.buffer = bo,
.offset = 0,
.bound = true
};
}
void MetalDriver::bindUniformBufferRange(uint32_t index, Handle<HwBufferObject> boh,
uint32_t offset, uint32_t size) {
assert_invariant(index < UNIFORM_BUFFER_COUNT);
void MetalDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index,
Handle<HwBufferObject> boh, uint32_t offset, uint32_t size) {
assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE ||
bindingType == BufferObjectBinding::UNIFORM);
auto* bo = handle_cast<MetalBufferObject>(boh);
auto* currentBo = mContext->uniformState[index].buffer;
if (currentBo) {
currentBo->boundUniformBuffers.unset(index);
switch (bindingType) {
default:
case BufferObjectBinding::UNIFORM: {
assert_invariant(index < Program::UNIFORM_BINDING_COUNT);
auto* currentBo = mContext->uniformState[index].buffer;
if (currentBo) {
currentBo->boundUniformBuffers.unset(index);
}
bo->boundUniformBuffers.set(index);
mContext->uniformState[index] = BufferState {
.buffer = bo,
.offset = offset,
.bound = true
};
break;
}
case BufferObjectBinding::SHADER_STORAGE: {
assert_invariant(index < MAX_SSBO_COUNT);
auto* currentBo = mContext->ssboState[index].buffer;
if (currentBo) {
currentBo->boundSsbos.unset(index);
}
bo->boundSsbos.set(index);
mContext->ssboState[index] = BufferState {
.buffer = bo,
.offset = offset,
.bound = true
};
break;
}
}
}
void MetalDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE ||
bindingType == BufferObjectBinding::UNIFORM);
switch (bindingType) {
default:
case BufferObjectBinding::UNIFORM: {
assert_invariant(index < Program::UNIFORM_BINDING_COUNT);
auto* currentBo = mContext->uniformState[index].buffer;
if (currentBo) {
currentBo->boundUniformBuffers.unset(index);
}
mContext->uniformState[index] = BufferState {
.buffer = nullptr,
.offset = 0,
.bound = false
};
break;
}
case BufferObjectBinding::SHADER_STORAGE: {
assert_invariant(index < MAX_SSBO_COUNT);
auto* currentBo = mContext->ssboState[index].buffer;
if (currentBo) {
currentBo->boundSsbos.unset(index);
}
mContext->ssboState[index] = BufferState {
.buffer = nullptr,
.offset = 0,
.bound = false
};
break;
}
}
bo->boundUniformBuffers.set(index);
mContext->uniformState[index] = UniformBufferState{
.buffer = bo,
.offset = offset,
.bound = true
};
}
void MetalDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
@@ -1108,6 +1285,12 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
}];
}
void MetalDriver::readBufferSubData(backend::BufferObjectHandle boh,
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
// TODO: implement readBufferSubData
scheduleDestroy(std::move(p));
}
void MetalDriver::blit(TargetBufferFlags buffers,
Handle<HwRenderTarget> dst, Viewport dstRect,
Handle<HwRenderTarget> src, Viewport srcRect,
@@ -1187,6 +1370,97 @@ void MetalDriver::blit(TargetBufferFlags buffers,
}
}
void MetalDriver::finalizeSamplerGroup(MetalSamplerGroup* samplerGroup) {
// All of the id<MTLSamplerState> objects have already been bound to the argument buffer.
// Here we bind any textures that were unable to be bound in updateSamplerGroup.
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(mContext);
#ifndef NDEBUG
// In debug builds, verify that all the textures in the sampler group are still alive.
// These bugs lead to memory corruption and can be difficult to track down.
const auto& handles = samplerGroup->getTextureHandles();
for (size_t s = 0; s < handles.size(); s++) {
if (!handles[s]) {
continue;
}
auto iter = mContext->textures.find(handle_cast<MetalTexture>(handles[s]));
if (iter == mContext->textures.end()) {
utils::slog.e << "finalizeSamplerGroup: texture #"
<< (int) s << " is dead, texture handle = "
<< handles[s] << utils::io::endl;
}
assert_invariant(iter != mContext->textures.end());
}
#endif
for (size_t binding = 0; binding < samplerGroup->size; binding++) {
auto [th, t] = samplerGroup->getFinalizedTexture(binding);
// This may be an external texture, in which case we can't cache the id<MTLTexture>, we
// need to refetch it in case the external image has changed.
bool isExternalImage = false;
if (th) {
auto* texture = handle_cast<MetalTexture>(th);
isExternalImage = texture->target == SamplerType::SAMPLER_EXTERNAL;
}
// If t is non-nil, then we've already finalized this texture.
if (t && !isExternalImage) {
continue;
}
// It's possible that some texture handles are null, but we should have already handled
// these inside updateSamplerGroup by binding an "empty" texture.
assert_invariant(th);
auto* texture = handle_cast<MetalTexture>(th);
// Determine if this SamplerGroup needs mutation.
// We can't just simply mutate the SamplerGroup, since it could currently be in use by the
// GPU from a prior render pass.
// If the SamplerGroup does need mutation, then there's two cases:
// 1. The SamplerGroup has not been finalized yet (which means it has not yet been used in a
// draw call). We're free to mutate it.
// 2. The SamplerGroup is finalized. We must call mutate(), which will create a new argument
// buffer that we can then mutate freely.
// TODO: don't just always call mutate, check to see if the texture is actually different.
if (samplerGroup->isFinalized()) {
samplerGroup->mutate(cmdBuffer);
}
// External images
if (texture->target == SamplerType::SAMPLER_EXTERNAL) {
if (texture->externalImage.isValid()) {
id<MTLTexture> mtlTexture = texture->externalImage.getMetalTextureForDraw();
assert_invariant(mtlTexture);
samplerGroup->setFinalizedTexture(binding, mtlTexture);
} else {
// Bind an empty texture.
samplerGroup->setFinalizedTexture(binding, getOrCreateEmptyTexture(mContext));
}
continue;
}
samplerGroup->setFinalizedTexture(binding, texture->getMtlTextureForRead());
}
samplerGroup->finalize();
// At this point, all the id<MTLTextures> should be set to valid textures. Some of them will be
// the "empty" texture. Per Apple documentation, the useResource method must be called once per
// render pass.
samplerGroup->useResources(mContext->currentRenderPassEncoder);
// useResources won't retain references to the textures, so we need to do so manually.
for (id<MTLTexture> texture : samplerGroup->textures) {
const void* retainedTexture = CFBridgingRetain(texture);
[cmdBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
CFBridgingRelease(retainedTexture);
}];
}
}
void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t instanceCount) {
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr,
"Attempted to draw without a valid command encoder.");
@@ -1352,8 +1626,9 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
MetalBuffer* uniformsToBind[Program::UNIFORM_BINDING_COUNT] = { nil };
NSUInteger offsets[Program::UNIFORM_BINDING_COUNT] = { 0 };
enumerateBoundUniformBuffers([&uniformsToBind, &offsets](const UniformBufferState& state,
MetalBuffer* buffer, uint32_t index) {
enumerateBoundBuffers(BufferObjectBinding::UNIFORM,
[&uniformsToBind, &offsets](const BufferState& state, MetalBuffer* buffer,
uint32_t index) {
uniformsToBind[index] = buffer;
offsets[index] = state.offset;
});
@@ -1361,105 +1636,37 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
UNIFORM_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX | MetalBuffer::Stage::FRAGMENT,
uniformsToBind, offsets, Program::UNIFORM_BINDING_COUNT);
// Enumerate all the sampler buffers for the program and check which textures and samplers need
// to be bound.
auto getTextureToBind = [this](const SamplerDescriptor* sampler) {
const auto metalTexture = handle_const_cast<MetalTexture>(sampler->t);
id<MTLTexture> textureToBind = metalTexture->swizzledTextureView ? metalTexture->swizzledTextureView
: metalTexture->texture;
if (metalTexture->externalImage.isValid()) {
textureToBind = metalTexture->externalImage.getMetalTextureForDraw();
// Bind sampler groups (argument buffers).
for (size_t s = 0; s < Program::SAMPLER_BINDING_COUNT; s++) {
MetalSamplerGroup* const samplerGroup = mContext->samplerBindings[s];
if (!samplerGroup) {
continue;
}
return textureToBind;
};
auto getSamplerToBind = [this](const SamplerDescriptor* sampler) {
const auto metalTexture = handle_const_cast<MetalTexture>(sampler->t);
SamplerState s {
.samplerParams = sampler->s,
.minLod = metalTexture->minLod,
.maxLod = metalTexture->maxLod
};
return mContext->samplerStateCache.getOrCreateState(s);
};
id<MTLTexture> texturesToBindVertex[FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_1].MAX_VERTEX_SAMPLER_COUNT] = {};
id<MTLSamplerState> samplersToBindVertex[FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_1].MAX_VERTEX_SAMPLER_COUNT] = {};
enumerateSamplerGroups(program, ShaderStage::VERTEX,
[this, &getTextureToBind, &getSamplerToBind, &texturesToBindVertex, &samplersToBindVertex](
const SamplerDescriptor* sampler, uint8_t binding) {
// We currently only support a max of MAX_VERTEX_SAMPLER_COUNT samplers. Ignore any additional
// samplers that may be bound.
if (binding >= FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_1].MAX_VERTEX_SAMPLER_COUNT) {
return;
const auto& stageFlags = program->samplerGroupInfo[s].stageFlags;
if (stageFlags == ShaderStageFlags::NONE) {
continue;
}
auto& textureToBind = texturesToBindVertex[binding];
textureToBind = getTextureToBind(sampler);
if (!textureToBind) {
utils::slog.w << "Warning: no texture bound at binding point " << (size_t) binding
<< " at the vertex shader." << utils::io::endl;
textureToBind = getOrCreateEmptyTexture(mContext);
auto iter = mContext->finalizedSamplerGroups.find(samplerGroup);
if (iter == mContext->finalizedSamplerGroups.end()) {
finalizeSamplerGroup(samplerGroup);
mContext->finalizedSamplerGroups.insert(samplerGroup);
}
auto& samplerToBind = samplersToBindVertex[binding];
samplerToBind = getSamplerToBind(sampler);
});
assert_invariant(samplerGroup->getArgumentBuffer());
// Assign a default sampler to empty slots, in case Filament hasn't bound all samplers.
// Metal requires all samplers referenced in shaders to be bound.
for (auto& sampler : samplersToBindVertex) {
if (!sampler) {
sampler = mContext->samplerStateCache.getOrCreateState({});
if (uint8_t(stageFlags) & uint8_t(ShaderStageFlags::VERTEX)) {
[mContext->currentRenderPassEncoder setVertexBuffer:samplerGroup->getArgumentBuffer()
offset:samplerGroup->getArgumentBufferOffset()
atIndex:(SAMPLER_GROUP_BINDING_START + s)];
}
if (uint8_t(stageFlags) & uint8_t(ShaderStageFlags::FRAGMENT)) {
[mContext->currentRenderPassEncoder setFragmentBuffer:samplerGroup->getArgumentBuffer()
offset:samplerGroup->getArgumentBufferOffset()
atIndex:(SAMPLER_GROUP_BINDING_START + s)];
}
}
NSRange vertexSamplerRange = NSMakeRange(0, FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_1].MAX_VERTEX_SAMPLER_COUNT);
[mContext->currentRenderPassEncoder setVertexTextures:texturesToBindVertex
withRange:vertexSamplerRange];
[mContext->currentRenderPassEncoder setVertexSamplerStates:samplersToBindVertex
withRange:vertexSamplerRange];
id<MTLTexture> texturesToBindFragment[FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_1].MAX_FRAGMENT_SAMPLER_COUNT] = {};
id<MTLSamplerState> samplersToBindFragment[FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_1].MAX_FRAGMENT_SAMPLER_COUNT] = {};
enumerateSamplerGroups(program, ShaderStage::FRAGMENT,
[this, &getTextureToBind, &getSamplerToBind, &texturesToBindFragment, &samplersToBindFragment](
const SamplerDescriptor* sampler, uint8_t binding) {
// We currently only support a max of MAX_FRAGMENT_SAMPLER_COUNT samplers. Ignore any additional
// samplers that may be bound.
if (binding >= FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_1].MAX_FRAGMENT_SAMPLER_COUNT) {
return;
}
auto& textureToBind = texturesToBindFragment[binding];
textureToBind = getTextureToBind(sampler);
if (!textureToBind) {
utils::slog.w << "Warning: no texture bound at binding point " << (size_t) binding
<< " at the fragment shader." << utils::io::endl;
textureToBind = getOrCreateEmptyTexture(mContext);
}
auto& samplerToBind = samplersToBindFragment[binding];
samplerToBind = getSamplerToBind(sampler);
});
// Assign a default sampler to empty slots, in case Filament hasn't bound all samplers.
// Metal requires all samplers referenced in shaders to be bound.
for (auto& sampler : samplersToBindFragment) {
if (!sampler) {
sampler = mContext->samplerStateCache.getOrCreateState({});
}
}
NSRange fragmentSamplerRange = NSMakeRange(0, FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_1].MAX_FRAGMENT_SAMPLER_COUNT);
[mContext->currentRenderPassEncoder setFragmentTextures:texturesToBindFragment
withRange:fragmentSamplerRange];
[mContext->currentRenderPassEncoder setFragmentSamplerStates:samplersToBindFragment
withRange:fragmentSamplerRange];
// Bind the user vertex buffers.
MetalBuffer* buffers[MAX_VERTEX_BUFFER_COUNT] = {};
@@ -1502,6 +1709,70 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
instanceCount:instanceCount];
}
void MetalDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
ASSERT_PRECONDITION(!isInRenderPass(mContext),
"dispatchCompute must be called outside of a render pass.");
auto mtlProgram = handle_cast<MetalProgram>(program);
// If the material debugger is enabled, avoid fatal (or cascading) errors and that can occur
// during the draw call when the program is invalid. The shader compile error has already been
// dumped to the console at this point, so it's fine to simply return early.
if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!mtlProgram->isValid)) {
return;
}
assert_invariant(mtlProgram->isValid && mtlProgram->computeFunction);
id<MTLComputeCommandEncoder> computeEncoder =
[getPendingCommandBuffer(mContext) computeCommandEncoder];
NSError* error = nil;
id<MTLComputePipelineState> computePipelineState =
[mContext->device newComputePipelineStateWithFunction:mtlProgram->computeFunction
error:&error];
if (error) {
auto description = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
utils::slog.e << description << utils::io::endl;
}
assert_invariant(!error);
// Bind uniform buffers.
MetalBuffer* uniformsToBind[Program::UNIFORM_BINDING_COUNT] = { nil };
NSUInteger uniformOffsets[Program::UNIFORM_BINDING_COUNT] = { 0 };
enumerateBoundBuffers(BufferObjectBinding::UNIFORM,
[&uniformsToBind, &uniformOffsets](const BufferState& state, MetalBuffer* buffer,
uint32_t index) {
uniformsToBind[index] = buffer;
uniformOffsets[index] = state.offset;
});
MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), computeEncoder,
UNIFORM_BUFFER_BINDING_START, MetalBuffer::Stage::COMPUTE, uniformsToBind,
uniformOffsets, Program::UNIFORM_BINDING_COUNT);
// Bind SSBOs.
MetalBuffer* ssbosToBind[MAX_SSBO_COUNT] = { nil };
NSUInteger ssboOffsets[MAX_SSBO_COUNT] = { 0 };
enumerateBoundBuffers(BufferObjectBinding::SHADER_STORAGE,
[&ssbosToBind, &ssboOffsets](const BufferState& state, MetalBuffer* buffer,
uint32_t index) {
ssbosToBind[index] = buffer;
ssboOffsets[index] = state.offset;
});
MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), computeEncoder, SSBO_BINDING_START,
MetalBuffer::Stage::COMPUTE, ssbosToBind, ssboOffsets, MAX_SSBO_COUNT);
[computeEncoder setComputePipelineState:computePipelineState];
MTLSize threadgroupsPerGrid = MTLSizeMake(workGroupCount.x, workGroupCount.y, workGroupCount.z);
// FIXME: the threadgroup size should be specified in the Program
MTLSize threadsPerThreadgroup = MTLSizeMake(16u, 1u, 1u);
[computeEncoder dispatchThreadgroups:threadgroupsPerGrid
threadsPerThreadgroup:threadsPerThreadgroup];
[computeEncoder endEncoding];
}
void MetalDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
ASSERT_PRECONDITION(!isInRenderPass(mContext),
"beginTimerQuery must be called outside of a render pass.");
@@ -1516,47 +1787,33 @@ void MetalDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
mContext->timerQueryImpl->endTimeElapsedQuery(tq);
}
void MetalDriver::enumerateSamplerGroups(
const MetalProgram* program, ShaderStage shaderType,
const std::function<void(const SamplerDescriptor*, size_t)>& f) {
auto& samplerBlockInfo = (shaderType == ShaderStage::VERTEX) ?
program->vertexSamplerBlockInfo : program->fragmentSamplerBlockInfo;
auto maxSamplerCount = (shaderType == ShaderStage::VERTEX) ?
FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_1].MAX_VERTEX_SAMPLER_COUNT :
FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_1].MAX_FRAGMENT_SAMPLER_COUNT;
for (size_t bindingIdx = 0; bindingIdx != maxSamplerCount; ++bindingIdx) {
auto& blockInfo = samplerBlockInfo[bindingIdx];
if (blockInfo.samplerGroup == UINT8_MAX) {
continue;
void MetalDriver::enumerateBoundBuffers(BufferObjectBinding bindingType,
const std::function<void(const BufferState&, MetalBuffer*, uint32_t)>& f) {
assert_invariant(bindingType == BufferObjectBinding::UNIFORM ||
bindingType == BufferObjectBinding::SHADER_STORAGE);
auto enumerate = [&](auto arrayType){
for (auto i = 0u; i < arrayType.size(); i++) {
const auto& thisBuffer = arrayType[i];
if (!thisBuffer.bound) {
continue;
}
f(thisBuffer, thisBuffer.buffer->getBuffer(), i);
}
};
const auto* metalSamplerGroup = mContext->samplerBindings[blockInfo.samplerGroup];
if (!metalSamplerGroup) {
// Do not emit warning here. For example this can arise when skinning is enabled
// and the morphing texture is unused.
continue;
}
SamplerGroup* sb = metalSamplerGroup->sb.get();
const SamplerDescriptor* boundSampler = sb->data() + blockInfo.sampler;
if (!boundSampler->t) {
continue;
}
f(boundSampler, bindingIdx);
switch (bindingType) {
default:
case (BufferObjectBinding::UNIFORM):
enumerate(mContext->uniformState);
break;
case (BufferObjectBinding::SHADER_STORAGE):
enumerate(mContext->ssboState);
break;
}
}
void MetalDriver::enumerateBoundUniformBuffers(
const std::function<void(const UniformBufferState&, MetalBuffer*, uint32_t)>& f) {
for (uint32_t i = 0; i < Program::UNIFORM_BINDING_COUNT; i++) {
auto& thisUniform = mContext->uniformState[i];
if (!thisUniform.bound) {
continue;
}
f(thisUniform, thisUniform.buffer->getBuffer(), i);
}
void MetalDriver::resetState(int) {
}
// explicit instantiation of the Dispatcher
-2
View File
@@ -337,8 +337,6 @@ constexpr inline MTLPrimitiveType getMetalPrimitiveType(PrimitiveType type) noex
case PrimitiveType::LINE_STRIP: return MTLPrimitiveTypeLineStrip;
case PrimitiveType::TRIANGLES: return MTLPrimitiveTypeTriangle;
case PrimitiveType::TRIANGLE_STRIP: return MTLPrimitiveTypeTriangleStrip;
case PrimitiveType::NONE:
ASSERT_POSTCONDITION(false, "NONE is not a valid primitive type.");
}
}
+127 -25
View File
@@ -118,15 +118,18 @@ private:
class MetalBufferObject : public HwBufferObject {
public:
MetalBufferObject(MetalContext& context, BufferUsage usage, uint32_t byteCount);
MetalBufferObject(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage,
uint32_t byteCount);
void updateBuffer(void* data, size_t size, uint32_t byteOffset);
void updateBufferUnsynchronized(void* data, size_t size, uint32_t byteOffset);
MetalBuffer* getBuffer() { return &buffer; }
// Tracks which uniform buffers this buffer object is bound into.
// Tracks which uniform/ssbo buffers this buffer object is bound into.
static_assert(Program::UNIFORM_BINDING_COUNT <= 32);
static_assert(MAX_SSBO_COUNT <= 32);
utils::bitset32 boundUniformBuffers;
utils::bitset32 boundSsbos;
private:
MetalBuffer buffer;
@@ -163,14 +166,9 @@ struct MetalProgram : public HwProgram {
id<MTLFunction> vertexFunction;
id<MTLFunction> fragmentFunction;
id<MTLFunction> computeFunction;
struct SamplerBlockInfo {
uint8_t samplerGroup = UINT8_MAX;
uint8_t sampler = UINT8_MAX;
};
std::array<SamplerBlockInfo, FEATURE_LEVEL_CAPS[1].MAX_VERTEX_SAMPLER_COUNT> vertexSamplerBlockInfo;
std::array<SamplerBlockInfo, FEATURE_LEVEL_CAPS[1].MAX_FRAGMENT_SAMPLER_COUNT> fragmentSamplerBlockInfo;
Program::SamplerGroupInfo samplerGroupInfo;
bool isValid = false;
};
@@ -188,7 +186,8 @@ struct PixelBufferShape {
MTLSize size, uint32_t byteOffset);
};
struct MetalTexture : public HwTexture {
class MetalTexture : public HwTexture {
public:
MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format,
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage,
TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a)
@@ -201,40 +200,144 @@ struct MetalTexture : public HwTexture {
~MetalTexture();
// Returns an id<MTLTexture> suitable for reading in a shader, taking into account swizzle and
// LOD clamping.
id<MTLTexture> getMtlTextureForRead() noexcept;
// Returns the id<MTLTexture> for attaching to a render pass.
id<MTLTexture> getMtlTextureForWrite() noexcept {
return texture;
}
void loadImage(uint32_t level, MTLRegion region, PixelBufferDescriptor& p) noexcept;
void loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice,
PixelBufferDescriptor const& data) noexcept;
void loadWithCopyBuffer(uint32_t level, uint32_t slice, MTLRegion region, PixelBufferDescriptor const& data,
const PixelBufferShape& shape);
void loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region, PixelBufferDescriptor const& data,
const PixelBufferShape& shape);
void generateMipmaps() noexcept;
// A texture starts out with none of its mip levels (also referred to as LODs) available for
// reading. 3 actions update the range of LODs available:
// - calling loadImage
// - calling generateMipmaps
// - using the texture as a render target attachment
// The range of available mips can only increase, never decrease.
// A texture's available mips are consistent throughout a render pass.
void updateLodRange(uint32_t level);
void updateLodRange(uint32_t minLevel, uint32_t maxLevel);
// Returns true if the texture has all of its mip levels accessible for reading.
// For any MetalTexture, once this is true, will always return true.
// The value returned will remain consistent for an entire render pass.
bool allLodsValid() const {
return minLod == 0 && maxLod == levels - 1;
}
static MTLPixelFormat decidePixelFormat(MetalContext* context, TextureFormat format);
MetalContext& context;
MetalExternalImage externalImage;
id<MTLTexture> texture = nil;
// A "sidecar" texture used to implement automatic MSAA resolve.
// This is created by MetalRenderTarget and stored here so it can be used with multiple
// render targets.
id<MTLTexture> msaaSidecar = nil;
MTLPixelFormat devicePixelFormat;
private:
void loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice,
PixelBufferDescriptor const& data) noexcept;
void loadWithCopyBuffer(uint32_t level, uint32_t slice, MTLRegion region, PixelBufferDescriptor const& data,
const PixelBufferShape& shape);
void loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region, PixelBufferDescriptor const& data,
const PixelBufferShape& shape);
id<MTLTexture> texture = nil;
// If non-nil, a swizzled texture view to use instead of "texture".
// Filament swizzling only affects texture reads, so this should not be used when the texture is
// bound as a render target attachment.
id<MTLTexture> swizzledTextureView = nil;
id<MTLTexture> lodTextureView = nil;
MTLPixelFormat devicePixelFormat;
uint32_t minLod = UINT_MAX;
uint32_t maxLod = 0;
};
struct MetalSamplerGroup : public HwSamplerGroup {
// NOTE: we have to use out-of-line allocation here because the size of a Handle<> is limited
std::unique_ptr<SamplerGroup> sb; // FIXME: this shouldn't depend on filament::SamplerGroup
explicit MetalSamplerGroup(size_t size) noexcept : sb(new SamplerGroup(size)) { }
class MetalSamplerGroup : public HwSamplerGroup {
public:
explicit MetalSamplerGroup(size_t size) noexcept
: size(size),
textureHandles(size, Handle<HwTexture>()),
textures(size, nil),
samplers(size, nil) {}
inline void setTextureHandle(size_t index, Handle<HwTexture> th) {
assert_invariant(!finalized);
textureHandles[index] = th;
}
#ifndef NDEBUG
// This method is only used for debugging, to ensure all texture handles are alive.
const auto& getTextureHandles() const {
return textureHandles;
}
#endif
// Encode a MTLTexture into this SamplerGroup at the given index.
inline void setFinalizedTexture(size_t index, id<MTLTexture> t) {
assert_invariant(!finalized);
textures[index] = t;
}
// Encode a MTLSamplerState into this SamplerGroup at the given index.
inline void setFinalizedSampler(size_t index, id<MTLSamplerState> s) {
assert_invariant(!finalized);
samplers[index] = s;
}
// A SamplerGroup is "finalized" when all of its textures have been set and is ready for use in
// a draw call.
// Once a SamplerGroup is finalized, it must be reset or mutated to be written into again.
void finalize();
bool isFinalized() const noexcept { return finalized; }
// Both of these methods "unfinalize" a SamplerGroup, allowing it to be updated via calls to
// setFinalizedTexture or setFinalizedSampler. The difference is that when reset is called, all
// the samplers/textures must be rebound. The MTLArgumentEncoder must be specified, in case
// the texture types have changed.
// Mutate re-encodes the current set of samplers/textures into the new argument
// buffer.
void reset(id<MTLCommandBuffer> cmdBuffer, id<MTLArgumentEncoder> e, id<MTLDevice> device);
void mutate(id<MTLCommandBuffer> cmdBuffer);
id<MTLBuffer> getArgumentBuffer() const {
assert_invariant(finalized);
return argBuffer->getCurrentAllocation().first;
}
NSUInteger getArgumentBufferOffset() const {
return argBuffer->getCurrentAllocation().second;
}
inline std::pair<Handle<HwTexture>, id<MTLTexture>> getFinalizedTexture(size_t index) {
return {textureHandles[index], textures[index]};
}
// Calls the Metal useResource:usage:stages: method for all the textures in this SamplerGroup.
void useResources(id<MTLRenderCommandEncoder> renderPassEncoder);
size_t size;
public:
// These vectors are kept in sync with one another.
utils::FixedCapacityVector<Handle<HwTexture>> textureHandles;
utils::FixedCapacityVector<id<MTLTexture>> textures;
utils::FixedCapacityVector<id<MTLSamplerState>> samplers;
id<MTLArgumentEncoder> encoder;
std::unique_ptr<MetalRingBuffer> argBuffer = nullptr;
bool finalized = false;
};
class MetalRenderTarget : public HwRenderTarget {
@@ -248,7 +351,7 @@ public:
Attachment() = default;
Attachment(MetalTexture* metalTexture, uint8_t level = 0, uint16_t layer = 0) :
level(level), layer(layer),
texture(metalTexture->texture),
texture(metalTexture->getMtlTextureForWrite()),
metalTexture(metalTexture) { }
id<MTLTexture> getTexture() const {
@@ -330,8 +433,7 @@ private:
static MTLLoadAction getLoadAction(const RenderPassParams& params, TargetBufferFlags buffer);
static MTLStoreAction getStoreAction(const RenderPassParams& params, TargetBufferFlags buffer);
static id<MTLTexture> createMultisampledTexture(id<MTLDevice> device, MTLPixelFormat format,
uint32_t width, uint32_t height, uint8_t samples);
id<MTLTexture> createMultisampledTexture(MTLPixelFormat format, uint32_t width, uint32_t height, uint8_t samples) const;
MetalContext* context;
bool defaultRenderTarget = false;
+154 -66
View File
@@ -26,6 +26,7 @@
#include "private/backend/BackendUtils.h"
#include <utils/compiler.h>
#include <utils/Panic.h>
#include <utils/trap.h>
#include <utils/debug.h>
@@ -270,8 +271,9 @@ void MetalSwapChain::scheduleFrameCompletedCallback() {
}];
}
MetalBufferObject::MetalBufferObject(MetalContext& context, BufferUsage usage, uint32_t byteCount)
: HwBufferObject(byteCount), buffer(context, usage, byteCount) {}
MetalBufferObject::MetalBufferObject(MetalContext& context, BufferObjectBinding bindingType,
BufferUsage usage, uint32_t byteCount)
: HwBufferObject(byteCount), buffer(context, bindingType, usage, byteCount) {}
void MetalBufferObject::updateBuffer(void* data, size_t size, uint32_t byteOffset) {
buffer.copyIntoBuffer(data, size, byteOffset);
@@ -287,7 +289,7 @@ MetalVertexBuffer::MetalVertexBuffer(MetalContext& context, uint8_t bufferCount,
MetalIndexBuffer::MetalIndexBuffer(MetalContext& context, BufferUsage usage, uint8_t elementSize,
uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount),
buffer(context, usage, elementSize * indexCount, true) { }
buffer(context, BufferObjectBinding::VERTEX, usage, elementSize * indexCount, true) { }
void MetalRenderPrimitive::setBuffers(MetalVertexBuffer* vertexBuffer, MetalIndexBuffer*
indexBuffer) {
@@ -338,12 +340,12 @@ void MetalRenderPrimitive::setBuffers(MetalVertexBuffer* vertexBuffer, MetalInde
MetalProgram::MetalProgram(id<MTLDevice> device, const Program& program) noexcept
: HwProgram(program.getName()), vertexFunction(nil), fragmentFunction(nil),
isValid(false) {
computeFunction(nil), isValid(false) {
using MetalFunctionPtr = __strong id<MTLFunction>*;
static_assert(Program::SHADER_TYPE_COUNT == 2, "Only vertex and fragment shaders expected.");
MetalFunctionPtr shaderFunctions[2] = { &vertexFunction, &fragmentFunction };
static_assert(Program::SHADER_TYPE_COUNT == 3, "Only vertex, fragment, and/or compute shaders expected.");
MetalFunctionPtr shaderFunctions[3] = { &vertexFunction, &fragmentFunction, &computeFunction };
const auto& sources = program.getShadersSource();
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
@@ -386,63 +388,25 @@ MetalProgram::MetalProgram(id<MTLDevice> device, const Program& program) noexcep
}, sc.value);
}
*shaderFunctions[i] = [library newFunctionWithName:@"main0"
constantValues:constants
error:&error];
id<MTLFunction> function = [library newFunctionWithName:@"main0"
constantValues:constants
error:&error];
assert_invariant(function);
*shaderFunctions[i] = function;
}
UTILS_UNUSED_IN_RELEASE const bool isRasterizationProgram =
vertexFunction != nil && fragmentFunction != nil;
UTILS_UNUSED_IN_RELEASE const bool isComputeProgram = computeFunction != nil;
// The program must be either a rasterization program XOR a compute program.
assert_invariant(isRasterizationProgram != isComputeProgram);
// All stages of the program have compiled successfully, this is a valid program.
isValid = true;
// This calculates Metal resource binding indices. Filament's sampler bindings range from 0-31
// across both vertex and fragment stages. However, Metal binding indices must be < 16, with a
// separate binding "namespace" for each stage. So, we recompute binding indices for each shader
// stage. This logic should match updateResourceBinding in GLSLPostProcessor.cpp
// This is an example how binding indices each of shader stages is generated from sampler's
// bindings. Below is sampler's bindings.
// 0 shadowMap { fragment }
// 1 structure { fragment }
// 2 targets { vertex }
// 3 color { vertex | fragment }
// 4 depth { vertex | fragment }
// Below is generated vertex sampler binding indices.
// 0 targets
// 1 color
// 2 depth
// Below is generated fragment sampler binding indices.
// 0 shadowMap
// 1 structure
// 2 color
// 3 depth
auto& samplerGroupInfo = program.getSamplerGroupInfo();
for (size_t shaderType = 0; shaderType != PIPELINE_STAGE_COUNT; ++shaderType) {
ShaderStage stage = (ShaderStage)shaderType;
size_t bindingIdx = 0;
auto getShaderStage = [this](ShaderStage type) -> auto& {
switch (type) {
case ShaderStage::VERTEX: return vertexSamplerBlockInfo;
case ShaderStage::FRAGMENT: return fragmentSamplerBlockInfo;
}
};
auto& samplerBlockInfo = getShaderStage(stage);
for (size_t samplerGroupIdx = 0; samplerGroupIdx != SAMPLER_GROUP_COUNT; ++samplerGroupIdx) {
auto& groupData = samplerGroupInfo[samplerGroupIdx];
auto stageFlags = groupData.stageFlags;
if (!hasShaderType(stageFlags, static_cast<ShaderStage>(shaderType))) {
continue;
}
auto& samplers = groupData.samplers;
for (size_t samplerIdx = 0, c = samplers.size(); samplerIdx != c; ++samplerIdx) {
samplerBlockInfo[bindingIdx].samplerGroup = samplerGroupIdx;
samplerBlockInfo[bindingIdx].sampler = samplerIdx;
++bindingIdx;
}
}
}
// Save this program's SamplerGroupInfo, it's used during draw calls to bind sampler groups to
// the appropriate stage(s).
samplerGroupInfo = program.getSamplerGroupInfo();
}
MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels,
@@ -557,14 +521,33 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context),
externalImage(context) {
texture = metalTexture;
minLod = 0;
maxLod = levels - 1;
updateLodRange(0, levels - 1);
}
MetalTexture::~MetalTexture() {
externalImage.set(nullptr);
}
id<MTLTexture> MetalTexture::getMtlTextureForRead() noexcept {
if (lodTextureView) {
return lodTextureView;
}
// The texture's swizzle remains constant throughout its lifetime, however its LOD range can
// change. We'll cache the LOD view, and set lodTextureView to nil if minLod or maxLod is
// updated.
id<MTLTexture> t = swizzledTextureView ? swizzledTextureView : texture;
if (!t) {
return nil;
}
if (UTILS_UNLIKELY(minLod > maxLod)) {
// If the texture does not have any available LODs, provide a view of only level 0.
// Filament should prevent this from ever occurring.
lodTextureView = createTextureViewWithLodRange(t, 0, 0);
return lodTextureView;
}
lodTextureView = createTextureViewWithLodRange(t, minLod, maxLod);
return lodTextureView;
}
MTLPixelFormat MetalTexture::decidePixelFormat(MetalContext* context, TextureFormat format) {
const MTLPixelFormat metalFormat = getMetalFormat(context, format);
@@ -686,6 +669,13 @@ void MetalTexture::loadImage(uint32_t level, MTLRegion region, PixelBufferDescri
updateLodRange(level);
}
void MetalTexture::generateMipmaps() noexcept {
id <MTLBlitCommandEncoder> blitEncoder = [getPendingCommandBuffer(&context) blitCommandEncoder];
[blitEncoder generateMipmapsForTexture:texture];
[blitEncoder endEncoding];
updateLodRange(0, texture.mipmapLevelCount - 1);
}
void MetalTexture::loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice,
PixelBufferDescriptor const& data) noexcept {
const PixelBufferShape shape = PixelBufferShape::compute(data, format, region.size, byteOffset);
@@ -806,8 +796,95 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
}
void MetalTexture::updateLodRange(uint32_t level) {
assert_invariant(!isInRenderPass(&context));
minLod = std::min(minLod, level);
maxLod = std::max(maxLod, level);
lodTextureView = nil;
}
void MetalTexture::updateLodRange(uint32_t min, uint32_t max) {
assert_invariant(!isInRenderPass(&context));
assert_invariant(min <= max);
minLod = std::min(minLod, min);
maxLod = std::max(maxLod, max);
lodTextureView = nil;
}
void MetalSamplerGroup::finalize() {
assert_invariant(encoder);
// TODO: we should be able to encode textures and samplers inside setFinalizedTexture and
// setFinalizedSampler as they become available, but Metal doesn't seem to like this; the arg
// buffer gets encoded incorrectly. This warrants more investigation.
auto [buffer, offset] = argBuffer->getCurrentAllocation();
[encoder setArgumentBuffer:buffer offset:offset];
// Encode all textures and samplers.
for (size_t s = 0; s < size; s++) {
[encoder setTexture:textures[s] atIndex:(s * 2 + 0)];
[encoder setSamplerState:samplers[s] atIndex:(s * 2 + 1)];
}
finalized = true;
}
void MetalSamplerGroup::reset(id<MTLCommandBuffer> cmdBuffer, id<MTLArgumentEncoder> e,
id<MTLDevice> device) {
encoder = e;
// The number of slots in the ring buffer we use to manage argument buffer allocations.
// This number was chosen to avoid running out of slots and having to allocate a "fallback"
// buffer when SamplerGroups are updated multiple times a frame. This value can reduced after
// auditing Filament's calls to updateSamplerGroup, which should be as few times as possible.
// For example, the bloom downsample pass should be refactored to maintain two separate
// MaterialInstances instead of "ping ponging" between two texture bindings, which causes a
// single SamplerGroup to be updated many times a frame.
static constexpr auto METAL_ARGUMENT_BUFFER_SLOTS = 32;
MTLSizeAndAlign argBufferLayout;
argBufferLayout.size = encoder.encodedLength;
argBufferLayout.align = encoder.alignment;
// Chances are, even though the MTLArgumentEncoder might change, the required size and alignment
// probably won't. So we can re-use the previous ring buffer.
if (UTILS_UNLIKELY(!argBuffer || !argBuffer->canAccomodateLayout(argBufferLayout))) {
argBuffer = std::make_unique<MetalRingBuffer>(device, MTLResourceStorageModeShared,
argBufferLayout, METAL_ARGUMENT_BUFFER_SLOTS);
} else {
argBuffer->createNewAllocation(cmdBuffer);
}
// Clear all textures and samplers.
assert_invariant(textureHandles.size() == textures.size());
assert_invariant(textures.size() == samplers.size());
for (size_t s = 0; s < textureHandles.size(); s++) {
textureHandles[s] = {};
textures[s] = nil;
samplers[s] = nil;
}
finalized = false;
}
void MetalSamplerGroup::mutate(id<MTLCommandBuffer> cmdBuffer) {
assert_invariant(finalized); // only makes sense to mutate if this sampler group is finalized
assert_invariant(argBuffer);
argBuffer->createNewAllocation(cmdBuffer);
finalized = false;
}
void MetalSamplerGroup::useResources(id<MTLRenderCommandEncoder> renderPassEncoder) {
assert_invariant(finalized);
if (@available(iOS 13, *)) {
// TODO: pass only the appropriate stages to useResources.
[renderPassEncoder useResources:textures.data()
count:textures.size()
usage:MTLResourceUsageRead | MTLResourceUsageSample
stages:MTLRenderStageFragment | MTLRenderStageVertex];
} else {
[renderPassEncoder useResources:textures.data()
count:textures.size()
usage:MTLResourceUsageRead | MTLResourceUsageSample];
}
}
MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height,
@@ -840,7 +917,7 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
if (samples > 1 && color[i].getSampleCount() == 1) {
auto& sidecar = color[i].metalTexture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(context->device, color[i].getPixelFormat(),
sidecar = createMultisampledTexture(color[i].getPixelFormat(),
color[i].metalTexture->width, color[i].metalTexture->height, samples);
}
}
@@ -865,7 +942,7 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
if (samples > 1 && depth.getSampleCount() == 1) {
auto& sidecar = depth.metalTexture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(context->device, depth.getPixelFormat(),
sidecar = createMultisampledTexture(depth.getPixelFormat(),
depth.metalTexture->width, depth.metalTexture->height, samples);
}
}
@@ -890,7 +967,7 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
if (samples > 1 && stencil.getSampleCount() == 1) {
auto& sidecar = stencil.metalTexture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(context->device, stencil.getPixelFormat(),
sidecar = createMultisampledTexture(stencil.getPixelFormat(),
stencil.metalTexture->width, stencil.metalTexture->height, samples);
}
}
@@ -977,6 +1054,7 @@ void MetalRenderTarget::setUpRenderPassAttachments(MTLRenderPassDescriptor* desc
descriptor.depthAttachment.slice = 0;
const bool discard = any(discardFlags & TargetBufferFlags::DEPTH);
if (!discard) {
assert_invariant(context->supportsAutoDepthResolve);
descriptor.depthAttachment.resolveTexture = depthAttachment.getTexture();
descriptor.depthAttachment.resolveLevel = depthAttachment.level;
descriptor.depthAttachment.resolveSlice = depthAttachment.layer;
@@ -1009,10 +1087,14 @@ void MetalRenderTarget::setUpRenderPassAttachments(MTLRenderPassDescriptor* desc
descriptor.stencilAttachment.slice = 0;
const bool discard = any(discardFlags & TargetBufferFlags::STENCIL);
if (!discard) {
assert_invariant(context->supportsAutoDepthResolve);
descriptor.stencilAttachment.resolveTexture = stencilAttachment.getTexture();
descriptor.stencilAttachment.resolveLevel = stencilAttachment.level;
descriptor.stencilAttachment.resolveSlice = stencilAttachment.layer;
descriptor.stencilAttachment.storeAction = MTLStoreActionMultisampleResolve;
if (@available(iOS 12.0, *)) {
descriptor.stencilAttachment.stencilResolveFilter = MTLMultisampleStencilResolveFilterSample0;
}
}
}
}
@@ -1074,8 +1156,8 @@ MTLStoreAction MetalRenderTarget::getStoreAction(const RenderPassParams& params,
return MTLStoreActionStore;
}
id<MTLTexture> MetalRenderTarget::createMultisampledTexture(id<MTLDevice> device,
MTLPixelFormat format, uint32_t width, uint32_t height, uint8_t samples) {
id<MTLTexture> MetalRenderTarget::createMultisampledTexture(MTLPixelFormat format,
uint32_t width, uint32_t height, uint8_t samples) const {
MTLTextureDescriptor* descriptor =
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format
width:width
@@ -1086,7 +1168,13 @@ id<MTLTexture> MetalRenderTarget::createMultisampledTexture(id<MTLDevice> device
descriptor.usage = MTLTextureUsageRenderTarget;
descriptor.resourceOptions = MTLResourceStorageModePrivate;
return [device newTextureWithDescriptor:descriptor];
if (context->supportsMemorylessRenderTargets) {
if (@available(macOS 11.0, *)) {
descriptor.resourceOptions = MTLResourceStorageModeMemoryless;
}
}
return [context->device newTextureWithDescriptor:descriptor];
}
math::uint2 MetalRenderTarget::getAttachmentSize() noexcept {
+59 -31
View File
@@ -24,6 +24,8 @@
#include <backend/DriverEnums.h>
#include <utils/FixedCapacityVector.h>
#include <memory>
#include <tsl/robin_map.h>
#include <utils/Hash.h>
@@ -35,17 +37,26 @@ inline bool operator==(const SamplerParams& lhs, const SamplerParams& rhs) {
return lhs.u == rhs.u;
}
// Rasterization Bindings
// ----------------------
// Bindings Buffer name Count
// ------------------------------------------------------
// 0 Zero buffer (placeholder vertex buffer) 1
// 1-16 Filament vertex buffers 16 limited by MAX_VERTEX_BUFFER_COUNT
// 17-26 Uniform buffers 10
// 27-30 Sampler Uniforms (argument buffers) 4 TODO: use arg buffers for samplers
// 17-26 Uniform buffers 10 Program::UNIFORM_BINDING_COUNT
// 27-30 Sampler groups (argument buffers) 4 Program::SAMPLER_BINDING_COUNT
//
// Total 31
static constexpr uint32_t SAMPLER_GROUP_COUNT = Program::SAMPLER_BINDING_COUNT;
static constexpr uint32_t SAMPLER_BINDING_COUNT = MAX_SAMPLER_COUNT;
// Compute Bindings
// ----------------------
// Bindings Buffer name Count
// ------------------------------------------------------
// 0-3 SSBO buffers 4 MAX_SSBO_COUNT
// 17-26 Uniform buffers 10 Program::UNIFORM_BINDING_COUNT
// 27-30 Sampler groups (argument buffers) 4 Program::SAMPLER_BINDING_COUNT
//
// Total 18
// The total number of vertex buffer "slots" that the Metal backend can bind.
// + 1 to account for the zero buffer, a placeholder buffer used internally by the Metal backend.
@@ -58,10 +69,10 @@ static constexpr uint32_t ZERO_VERTEX_BUFFER_BINDING = 0u;
static constexpr uint32_t USER_VERTEX_BUFFER_BINDING_START = 1u;
// The max number of uniform buffers that the Metal backend can simultaneously bind.
static constexpr uint32_t UNIFORM_BUFFER_COUNT = 10u;
// This constant must match the equivalent in CodeGenerator.cpp.
// These constants must match the equivalent in CodeGenerator.h.
static constexpr uint32_t UNIFORM_BUFFER_BINDING_START = 17u;
static constexpr uint32_t SSBO_BINDING_START = 0u;
static constexpr uint32_t SAMPLER_GROUP_BINDING_START = 27u;
// Forward declarations necessary here, definitions at end of file.
inline bool operator==(const MTLViewport& lhs, const MTLViewport& rhs);
@@ -143,9 +154,11 @@ static_assert(sizeof(BlendState) == 56, "BlendState is unexpected size.");
// StateCache caches Metal state objects using StateType as a key.
// MetalType is the corresponding Metal API type.
// StateCreator is a functor that creates a new state of type MetalType.
// HashFn is a functor that hashes StateType.
template<typename StateType,
typename MetalType,
typename StateCreator>
typename StateCreator,
typename HashFn = utils::hash::MurmurHashFn<StateType>>
class StateCache {
public:
@@ -158,6 +171,8 @@ public:
void setDevice(id<MTLDevice> device) noexcept { mDevice = device; }
MetalType getOrCreateState(const StateType& state) noexcept {
assert_invariant(mDevice);
// Check if a valid state already exists in the cache.
auto iter = mStateCache.find(state);
if (UTILS_LIKELY(iter != mStateCache.end())) {
@@ -167,6 +182,7 @@ public:
// If we reach this point, we couldn't find one in the cache; create a new one.
const auto& metalObject = creator(mDevice, state);
assert_invariant(metalObject);
mStateCache.emplace(std::make_pair(
state,
@@ -181,7 +197,6 @@ private:
StateCreator creator;
id<MTLDevice> mDevice = nil;
using HashFn = utils::hash::MurmurHashFn<StateType>;
tsl::robin_map<StateType, MetalType, HashFn> mStateCache;
};
@@ -333,38 +348,19 @@ using DepthStencilStateCache = StateCache<DepthStencilState, id<MTLDepthStencilS
class MetalBufferObject;
struct UniformBufferState {
struct BufferState {
MetalBufferObject* buffer = nullptr; // 8 bytes
uint32_t offset = 0; // 4 bytes
bool bound = false; // 1 byte
char padding[3] = { 0 }; // 3 bytes
bool operator==(const UniformBufferState& rhs) const noexcept {
return this->bound == rhs.bound &&
this->buffer == rhs.buffer &&
this->offset == rhs.offset;
}
bool operator!=(const UniformBufferState& rhs) const noexcept {
return !operator==(rhs);
}
};
static_assert(sizeof(UniformBufferState) == 16, "UniformBufferState unexpected size.");
using UniformBufferStateTracker = StateTracker<UniformBufferState>;
// Sampler states
struct SamplerState {
SamplerParams samplerParams;
uint32_t minLod = 0;
uint32_t maxLod = UINT_MAX;
bool operator==(const SamplerState& rhs) const noexcept {
return this->samplerParams == rhs.samplerParams &&
this->minLod == rhs.minLod &&
this->maxLod == rhs.maxLod;
return this->samplerParams == rhs.samplerParams;
}
bool operator!=(const SamplerState& rhs) const noexcept {
@@ -372,7 +368,7 @@ struct SamplerState {
}
};
static_assert(sizeof(SamplerState) == 12, "SamplerState unexpected size.");
static_assert(sizeof(SamplerState) == 4, "SamplerState unexpected size.");
struct SamplerStateCreator {
id<MTLSamplerState> operator()(id<MTLDevice> device, const SamplerState& state) noexcept;
@@ -385,6 +381,38 @@ using SamplerStateCache = StateCache<SamplerState, id<MTLSamplerState>, SamplerS
using CullModeStateTracker = StateTracker<MTLCullMode>;
using WindingStateTracker = StateTracker<MTLWinding>;
// Argument encoder
struct ArgumentEncoderState {
utils::FixedCapacityVector<MTLTextureType> textureTypes;
explicit ArgumentEncoderState(utils::FixedCapacityVector<MTLTextureType>&& types)
: textureTypes(std::move(types)) {}
bool operator==(const ArgumentEncoderState& rhs) const noexcept {
return std::equal(textureTypes.begin(), textureTypes.end(), rhs.textureTypes.begin(),
rhs.textureTypes.end());
}
bool operator!=(const ArgumentEncoderState& rhs) const noexcept {
return !operator==(rhs);
}
};
struct ArgumentEncoderHasher {
uint32_t operator()(const ArgumentEncoderState& key) const noexcept {
return utils::hash::murmur3((const uint32_t*)key.textureTypes.data(),
sizeof(MTLTextureType) * key.textureTypes.size() / 4, 0);
}
};
struct ArgumentEncoderCreator {
id<MTLArgumentEncoder> operator()(id<MTLDevice> device, const ArgumentEncoderState& state) noexcept;
};
using ArgumentEncoderCache = StateCache<ArgumentEncoderState, id<MTLArgumentEncoder>,
ArgumentEncoderCreator, ArgumentEncoderHasher>;
} // namespace backend
} // namespace filament
+34 -2
View File
@@ -137,11 +137,10 @@ id<MTLSamplerState> SamplerStateCreator::operator()(id<MTLDevice> device,
samplerDescriptor.tAddressMode = getAddressMode(params.wrapT);
samplerDescriptor.rAddressMode = getAddressMode(params.wrapR);
samplerDescriptor.maxAnisotropy = 1u << params.anisotropyLog2;
samplerDescriptor.lodMaxClamp = (float) state.maxLod;
samplerDescriptor.lodMinClamp = (float) state.minLod;
samplerDescriptor.compareFunction =
params.compareMode == SamplerCompareMode::NONE ?
MTLCompareFunctionNever : getCompareFunction(params.compareFunc);
samplerDescriptor.supportArgumentBuffers = YES;
#if defined(IOS)
// Older Apple devices (and the simulator) don't support setting a comparison function in
@@ -156,5 +155,38 @@ id<MTLSamplerState> SamplerStateCreator::operator()(id<MTLDevice> device,
return [device newSamplerStateWithDescriptor:samplerDescriptor];
}
id<MTLArgumentEncoder> ArgumentEncoderCreator::operator()(id<MTLDevice> device,
const ArgumentEncoderState &state) noexcept {
const auto& textureTypes = state.textureTypes;
const auto& count = textureTypes.size();
assert_invariant(count > 0);
// Metal has separate data types for textures versus samplers, so the argument buffer layout
// alternates between texture and sampler, i.e.:
// textureA
// samplerA
// textureB
// samplerB
// etc
NSMutableArray<MTLArgumentDescriptor*>* arguments =
[NSMutableArray arrayWithCapacity:(count * 2)];
for (size_t i = 0; i < count; i++) {
MTLArgumentDescriptor* textureArgument = [MTLArgumentDescriptor argumentDescriptor];
textureArgument.index = i * 2 + 0;
textureArgument.dataType = MTLDataTypeTexture;
textureArgument.textureType = textureTypes[i];
textureArgument.access = MTLArgumentAccessReadOnly;
[arguments addObject:textureArgument];
MTLArgumentDescriptor* samplerArgument = [MTLArgumentDescriptor argumentDescriptor];
samplerArgument.index = i * 2 + 1;
samplerArgument.dataType = MTLDataTypeSampler;
textureArgument.access = MTLArgumentAccessReadOnly;
[arguments addObject:samplerArgument];
}
return [device newArgumentEncoderWithArguments:arguments];
}
} // namespace backend
} // namespace filament
+8
View File
@@ -29,6 +29,14 @@ API_AVAILABLE(ios(13.0))
id<MTLTexture> createTextureViewWithSwizzle(id<MTLTexture> texture,
MTLTextureSwizzleChannels swizzle);
/**
* Creates a texture view of the passed-in texture with the given lod range (inclusive). If the
* range represents all mip levels, simply returns texture.
* lodMax must be greater than or equal to lodMin.
*/
id<MTLTexture> createTextureViewWithLodRange(id<MTLTexture> texture, NSUInteger lodMin,
NSUInteger lodMax);
/**
* Creates a texture view of the passed-in texture with the given layer.
*
+20
View File
@@ -16,6 +16,8 @@
#include "MetalUtils.h"
#include <utils/debug.h>
namespace filament::backend {
id<MTLTexture> createTextureViewWithSwizzle(id<MTLTexture> texture,
@@ -37,6 +39,24 @@ id<MTLTexture> createTextureViewWithSwizzle(id<MTLTexture> texture,
swizzle:swizzle];
}
id<MTLTexture> createTextureViewWithLodRange(id<MTLTexture> texture, NSUInteger lodMin,
NSUInteger lodMax) {
assert_invariant(lodMax >= lodMin);
if (lodMin == 0 && lodMax >= texture.mipmapLevelCount - 1) {
return texture;
}
NSUInteger slices = texture.arrayLength;
if (texture.textureType == MTLTextureTypeCube ||
texture.textureType == MTLTextureTypeCubeArray) {
slices *= 6;
}
NSRange levelRange = NSMakeRange(lodMin, lodMax - lodMin + 1);
return [texture newTextureViewWithPixelFormat:texture.pixelFormat
textureType:texture.textureType
levels:levelRange
slices:NSMakeRange(0, slices)];
}
id<MTLTexture> createTextureViewWithSingleSlice(id<MTLTexture> texture, NSUInteger slice) {
if (texture.textureType == MTLTextureType2D) {
return texture;
+16 -2
View File
@@ -273,8 +273,11 @@ void NoopDriver::commit(Handle<HwSwapChain> sch) {
void NoopDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> ubh) {
}
void NoopDriver::bindUniformBufferRange(uint32_t index, Handle<HwBufferObject> ubh,
uint32_t offset, uint32_t size) {
void NoopDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index,
Handle<HwBufferObject> ubh, uint32_t offset, uint32_t size) {
}
void NoopDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
}
void NoopDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
@@ -301,6 +304,11 @@ void NoopDriver::readPixels(Handle<HwRenderTarget> src,
scheduleDestroy(std::move(p));
}
void NoopDriver::readBufferSubData(backend::BufferObjectHandle boh,
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
scheduleDestroy(std::move(p));
}
void NoopDriver::blit(TargetBufferFlags buffers,
Handle<HwRenderTarget> dst, Viewport dstRect,
Handle<HwRenderTarget> src, Viewport srcRect,
@@ -311,10 +319,16 @@ void NoopDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph
uint32_t instanceCount) {
}
void NoopDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
}
void NoopDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
}
void NoopDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
}
void NoopDriver::resetState(int) {
}
} // namespace filament
+2
View File
@@ -119,6 +119,8 @@ constexpr inline GLenum getBufferBindingType(BufferObjectBinding bindingType) no
return GL_ARRAY_BUFFER;
case BufferObjectBinding::UNIFORM:
return GL_UNIFORM_BUFFER;
case BufferObjectBinding::SHADER_STORAGE:
return GL_SHADER_STORAGE_BUFFER;
}
}
+204 -29
View File
@@ -61,6 +61,8 @@ OpenGLContext::OpenGLContext() noexcept {
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &gets.max_draw_buffers);
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &gets.max_texture_image_units);
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gets.max_combined_texture_image_units);
glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &gets.max_transform_feedback_separate_attribs);
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &gets.max_uniform_buffer_bindings);
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &gets.uniform_buffer_offset_alignment);
constexpr auto const caps3 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_3];
@@ -247,31 +249,7 @@ OpenGLContext::OpenGLContext() noexcept {
flush(slog.v);
#endif
/*
* Set our default state
*/
// We need to make sure our internal state matches the GL state when we start.
// (some of these calls may be unneeded as they might be the gl defaults)
glDisable(GL_BLEND);
glDisable(GL_CULL_FACE);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
glDisable(GL_DITHER);
glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
glDisable(GL_SAMPLE_COVERAGE);
glDisable(GL_POLYGON_OFFSET_FILL);
// Point sprite size and seamless cubemap filtering are disabled by default in desktop GL.
// In OpenGL ES, these flags do not exist because they are always on.
#if BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL
enable(GL_PROGRAM_POINT_SIZE);
#endif
#ifdef GL_ARB_seamless_cube_map
enable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
#endif
setDefaultState();
#ifdef GL_EXT_texture_filter_anisotropic
if (ext.EXT_texture_filter_anisotropic) {
@@ -290,10 +268,6 @@ OpenGLContext::OpenGLContext() noexcept {
}
#endif
#ifdef GL_FRAGMENT_SHADER_DERIVATIVE_HINT
glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST);
#endif
#if !defined(NDEBUG) && defined(GL_KHR_debug)
if (ext.KHR_debug) {
auto cb = [](GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
@@ -325,6 +299,50 @@ OpenGLContext::OpenGLContext() noexcept {
glDebugMessageCallback(cb, nullptr);
}
#endif
}
void OpenGLContext::setDefaultState() noexcept {
// We need to make sure our internal state matches the GL state when we start.
// (some of these calls may be unneeded as they might be the gl defaults)
GLenum caps[] = {
GL_BLEND,
GL_CULL_FACE,
GL_SCISSOR_TEST,
GL_DEPTH_TEST,
GL_STENCIL_TEST,
GL_DITHER,
GL_SAMPLE_ALPHA_TO_COVERAGE,
GL_SAMPLE_COVERAGE,
GL_POLYGON_OFFSET_FILL,
};
size_t capsCount = sizeof(caps) / sizeof(caps[0]);
UTILS_NOUNROLL
for (GLenum capi = 0; capi < capsCount; ++capi) {
size_t capIndex = getIndexForCap(caps[capi]);
GLenum cap = caps[capi];
if (state.enables.caps[capIndex]) {
glEnable(cap);
} else {
glDisable(cap);
}
}
// Point sprite size and seamless cubemap filtering are disabled by default in desktop GL.
// In OpenGL ES, these flags do not exist because they are always on.
#if BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL
glEnable(GL_PROGRAM_POINT_SIZE);
enable(GL_PROGRAM_POINT_SIZE);
#endif
#ifdef GL_ARB_seamless_cube_map
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
enable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
#endif
#ifdef GL_FRAGMENT_SHADER_DERIVATIVE_HINT
glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST);
#endif
#if defined(GL_EXT_clip_control) || defined(GL_ARB_clip_control) || defined(GL_VERSION_4_5)
if (ext.EXT_clip_control) {
@@ -521,4 +539,161 @@ void OpenGLContext::deleteVertexArrays(GLsizei n, const GLuint* arrays) noexcept
}
}
void OpenGLContext::resetState() noexcept {
// Force GL state to match the Filament state
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, state.draw_fbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, state.read_fbo);
// state.program
glUseProgram(state.program.use);
// state.vao
if (state.vao.p) {
glBindVertexArray(state.vao.p->vao);
} else {
bindVertexArray(nullptr);
}
// state.raster
glFrontFace(state.raster.frontFace);
glCullFace(state.raster.cullFace);
glBlendEquationSeparate(state.raster.blendEquationRGB, state.raster.blendEquationA);
glBlendFuncSeparate(
state.raster.blendFunctionSrcRGB,
state.raster.blendFunctionDstRGB,
state.raster.blendFunctionSrcA,
state.raster.blendFunctionDstA
);
glColorMask(
state.raster.colorMask,
state.raster.colorMask,
state.raster.colorMask,
state.raster.colorMask
);
glDepthMask(state.raster.depthMask);
glDepthFunc(state.raster.depthFunc);
// state.stencil
glStencilFuncSeparate(
GL_FRONT,
state.stencil.front.func.func,
state.stencil.front.func.ref,
state.stencil.front.func.mask
);
glStencilFuncSeparate(
GL_BACK,
state.stencil.back.func.func,
state.stencil.back.func.ref,
state.stencil.back.func.mask
);
glStencilOpSeparate(
GL_FRONT,
state.stencil.front.op.sfail,
state.stencil.front.op.dpfail,
state.stencil.front.op.dppass
);
glStencilOpSeparate(
GL_BACK,
state.stencil.back.op.sfail,
state.stencil.back.op.dpfail,
state.stencil.back.op.dppass
);
glStencilMaskSeparate(GL_FRONT, state.stencil.front.stencilMask);
glStencilMaskSeparate(GL_BACK, state.stencil.back.stencilMask);
// state.polygonOffset
glPolygonOffset(state.polygonOffset.factor, state.polygonOffset.units);
// state.enables
setDefaultState();
// state.buffers
// Reset state.buffers to its default state to avoid the complexity and error-prone
// nature of resetting the GL state to its existing state
state.buffers = {};
GLenum bufferTargets[] = {
GL_UNIFORM_BUFFER,
GL_TRANSFORM_FEEDBACK_BUFFER,
#if !defined(__EMSCRIPTEN__)
GL_SHADER_STORAGE_BUFFER,
#endif
GL_ARRAY_BUFFER,
GL_COPY_READ_BUFFER,
GL_COPY_WRITE_BUFFER,
GL_ELEMENT_ARRAY_BUFFER,
GL_PIXEL_PACK_BUFFER,
GL_PIXEL_UNPACK_BUFFER,
};
size_t bufferTargetCount = sizeof(bufferTargets) / sizeof(bufferTargets[0]);
for (size_t targetArrayIndex = 0; targetArrayIndex < bufferTargetCount; ++targetArrayIndex) {
GLenum target = bufferTargets[targetArrayIndex];
glBindBuffer(target, 0);
}
for (size_t bufferIndex = 0; bufferIndex < MAX_BUFFER_BINDINGS; ++bufferIndex) {
if (bufferIndex < (size_t)gets.max_uniform_buffer_bindings) {
glBindBufferBase(GL_UNIFORM_BUFFER, bufferIndex, 0);
}
if (bufferIndex < (size_t)gets.max_transform_feedback_separate_attribs) {
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, bufferIndex, 0);
}
}
// state.textures
// Reset state.textures to its default state to avoid the complexity and error-prone
// nature of resetting the GL state to its existing state
state.textures = {};
const GLuint textureTargets[] = {
GL_TEXTURE_2D,
GL_TEXTURE_2D_ARRAY,
GL_TEXTURE_CUBE_MAP,
GL_TEXTURE_3D,
#if !defined(__EMSCRIPTEN__)
GL_TEXTURE_2D_MULTISAMPLE,
GL_TEXTURE_EXTERNAL_OES,
GL_TEXTURE_CUBE_MAP_ARRAY,
#endif
};
const size_t textureTargetCount = sizeof(textureTargets) / sizeof(textureTargets[0]);
for (GLint unit = 0; unit < gets.max_combined_texture_image_units; ++unit) {
glActiveTexture(GL_TEXTURE0 + unit);
glBindSampler(unit, 0);
for (size_t textureTargetArrayIndex = 0; textureTargetArrayIndex < textureTargetCount; ++textureTargetArrayIndex) {
GLuint target = textureTargets[textureTargetArrayIndex];
glBindTexture(target, 0);
}
}
glActiveTexture(GL_TEXTURE0 + state.textures.active);
// state.unpack
glPixelStorei(GL_UNPACK_ALIGNMENT, state.unpack.alignment);
glPixelStorei(GL_UNPACK_ROW_LENGTH, state.unpack.row_length);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, state.unpack.skip_pixels);
glPixelStorei(GL_UNPACK_SKIP_ROWS, state.unpack.skip_row);
// state.pack
glPixelStorei(GL_PACK_ALIGNMENT, state.pack.alignment);
glPixelStorei(GL_PACK_ROW_LENGTH, state.pack.row_length);
glPixelStorei(GL_PACK_SKIP_PIXELS, state.pack.skip_pixels);
glPixelStorei(GL_PACK_SKIP_ROWS, state.pack.skip_row);
// state.window
glScissor(
state.window.scissor.x,
state.window.scissor.y,
state.window.scissor.z,
state.window.scissor.w
);
glViewport(
state.window.viewport.x,
state.window.viewport.y,
state.window.viewport.z,
state.window.viewport.w
);
glDepthRangef(state.window.depthRange.x, state.window.depthRange.y);
}
} // namesapce filament
+16 -10
View File
@@ -73,6 +73,7 @@ public:
ShaderModel getShaderModel() const noexcept { return mShaderModel; }
void resetState() noexcept;
inline void useProgram(GLuint program) noexcept;
@@ -129,6 +130,8 @@ public:
GLint max_uniform_block_size;
GLint max_texture_image_units;
GLint max_combined_texture_image_units;
GLint max_transform_feedback_separate_attribs;
GLint max_uniform_buffer_bindings;
GLint uniform_buffer_offset_alignment;
} gets = {};
@@ -311,7 +314,7 @@ public:
GLsizeiptr size = 0;
} buffers[MAX_BUFFER_BINDINGS];
} targets[2]; // there are only 2 indexed buffer target (uniform and transform feedback)
GLuint genericBinding[8] = { 0 };
GLuint genericBinding[9] = { 0 };
} buffers;
struct {
@@ -409,6 +412,8 @@ private:
}
}
void setDefaultState() noexcept;
static constexpr const size_t TEXTURE_TARGET_COUNT =
sizeof(state.textures.units[0].targets) / sizeof(state.textures.units[0].targets[0]);
@@ -460,17 +465,18 @@ constexpr size_t OpenGLContext::getIndexForCap(GLenum cap) noexcept { //NOLINT
constexpr size_t OpenGLContext::getIndexForBufferTarget(GLenum target) noexcept {
size_t index = 0;
switch (target) {
// The indexed buffers MUST be first in this list
// The indexed buffers MUST be first in this list (those usable with bindBufferRange)
case GL_UNIFORM_BUFFER: index = 0; break;
case GL_TRANSFORM_FEEDBACK_BUFFER: index = 1; break;
case GL_SHADER_STORAGE_BUFFER: index = 2; break;
case GL_ARRAY_BUFFER: index = 2; break;
case GL_COPY_READ_BUFFER: index = 3; break;
case GL_COPY_WRITE_BUFFER: index = 4; break;
case GL_ELEMENT_ARRAY_BUFFER: index = 5; break;
case GL_PIXEL_PACK_BUFFER: index = 6; break;
case GL_PIXEL_UNPACK_BUFFER: index = 7; break;
default: index = 8; break; // should never happen
case GL_ARRAY_BUFFER: index = 3; break;
case GL_COPY_READ_BUFFER: index = 4; break;
case GL_COPY_WRITE_BUFFER: index = 5; break;
case GL_ELEMENT_ARRAY_BUFFER: index = 6; break;
case GL_PIXEL_PACK_BUFFER: index = 7; break;
case GL_PIXEL_UNPACK_BUFFER: index = 8; break;
default: index = 9; break; // should never happen
}
assert_invariant(index < sizeof(state.buffers.genericBinding)/sizeof(state.buffers.genericBinding[0])); // NOLINT(misc-redundant-expression)
return index;
@@ -531,7 +537,7 @@ void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept {
void OpenGLContext::bindBufferRange(GLenum target, GLuint index, GLuint buffer,
GLintptr offset, GLsizeiptr size) noexcept {
size_t targetIndex = getIndexForBufferTarget(target);
assert_invariant(targetIndex <= 1); // validity check
assert_invariant(targetIndex <= 2); // validity check
// this ALSO sets the generic binding
if ( state.buffers.targets[targetIndex].buffers[index].name != buffer
+101 -4
View File
@@ -235,6 +235,9 @@ ShaderModel OpenGLDriver::getShaderModel() const noexcept {
// ------------------------------------------------------------------------------------------------
// Change and track GL state
// ------------------------------------------------------------------------------------------------
void OpenGLDriver::resetState(int) {
mContext.resetState();
}
void OpenGLDriver::bindSampler(GLuint unit, GLuint sampler) noexcept {
mContext.bindSampler(unit, sampler);
@@ -2574,15 +2577,31 @@ void OpenGLDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> ubh)
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::bindUniformBufferRange(uint32_t index, Handle<HwBufferObject> ubh,
uint32_t offset, uint32_t size) {
void OpenGLDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index,
Handle<HwBufferObject> ubh, uint32_t offset, uint32_t size) {
DEBUG_MARKER()
auto& gl = mContext;
assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE ||
bindingType == BufferObjectBinding::UNIFORM);
GLBufferObject* ub = handle_cast<GLBufferObject *>(ubh);
assert_invariant(ub->gl.binding == GL_UNIFORM_BUFFER);
GLenum target = GLUtils::getBufferBindingType(bindingType);
assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE ||
ub->gl.binding == target);
assert_invariant(offset + size <= ub->byteCount);
gl.bindBufferRange(ub->gl.binding, GLuint(index), ub->gl.id, offset, size);
gl.bindBufferRange(target, GLuint(index), ub->gl.id, offset, size);
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
DEBUG_MARKER()
auto& gl = mContext;
GLenum target = GLUtils::getBufferBindingType(bindingType);
gl.bindBufferRange(target, GLuint(index), 0, 0, 0);
CHECK_GL_ERROR(utils::slog.e)
}
@@ -2759,6 +2778,58 @@ void OpenGLDriver::readPixels(Handle<HwRenderTarget> src,
});
}
void OpenGLDriver::readBufferSubData(backend::BufferObjectHandle boh,
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
auto& gl = mContext;
GLBufferObject const* bo = handle_cast<GLBufferObject const*>(boh);
// TODO: measure the two solutions
if constexpr (true) {
// schedule a copy of the buffer we're reading into a PBO, this *should* happen
// asynchronously without stalling the CPU.
GLuint pbo;
glGenBuffers(1, &pbo);
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, (GLsizeiptr)size, nullptr, GL_STATIC_DRAW);
gl.bindBuffer(bo->gl.binding, bo->gl.id);
glCopyBufferSubData(bo->gl.binding, GL_PIXEL_PACK_BUFFER, offset, 0, size);
gl.bindBuffer(bo->gl.binding, 0);
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, 0);
CHECK_GL_ERROR(utils::slog.e)
// then, we schedule a mapBuffer of the PBO later, once the fence has signaled
auto* pUserBuffer = new BufferDescriptor(std::move(p));
whenGpuCommandsComplete([this, size, pbo, pUserBuffer]() mutable {
BufferDescriptor& p = *pUserBuffer;
auto& gl = mContext;
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
void* vaddr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, size, GL_MAP_READ_BIT);
if (vaddr) {
memcpy(p.buffer, vaddr, size);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
}
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glDeleteBuffers(1, &pbo);
scheduleDestroy(std::move(p));
delete pUserBuffer;
CHECK_GL_ERROR(utils::slog.e)
});
} else {
gl.bindBuffer(bo->gl.binding, bo->gl.id);
// TODO: this glMapBufferRange may stall. Ideally we want to use whenGpuCommandsComplete
// but that's tricky because boh could be destroyed right after this call.
void* vaddr = glMapBufferRange(bo->gl.binding, offset, size, GL_MAP_READ_BIT);
if (vaddr) {
memcpy(p.buffer, vaddr, size);
glUnmapBuffer(bo->gl.binding);
}
gl.bindBuffer(bo->gl.binding, 0);
scheduleDestroy(std::move(p));
CHECK_GL_ERROR(utils::slog.e)
}
}
void OpenGLDriver::whenGpuCommandsComplete(std::function<void()> fn) noexcept {
GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
mGpuCommandCompleteOps.emplace_back(sync, std::move(fn));
@@ -3102,6 +3173,32 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph, uint
#endif
}
void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
executeRenderPassOps();
OpenGLProgram* p = handle_cast<OpenGLProgram*>(program);
// If the material debugger is enabled, avoid fatal (or cascading) errors and that can occur
// during the draw call when the program is invalid. The shader compile error has already been
// dumped to the console at this point, so it's fine to simply return early.
if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!p->isValid())) {
return;
}
useProgram(p);
#if defined(GL_ES_VERSION_3_1) || defined(GL_VERSION_4_3)
// FIXME: this line is commented-out for now due to issues switching G3 clients to API 21.
// glDispatchCompute(workGroupCount.x, workGroupCount.y, workGroupCount.z);
#endif
#ifdef FILAMENT_ENABLE_MATDBG
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
#else
CHECK_GL_ERROR(utils::slog.e)
#endif
}
// explicit instantiation of the Dispatcher
template class ConcreteDispatcher<OpenGLDriver>;
@@ -120,15 +120,12 @@ void OpenGLProgram::compileShaders(OpenGLContext& context,
// build all shaders
UTILS_NOUNROLL
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
ShaderStage type = static_cast<ShaderStage>(i);
const ShaderStage stage = static_cast<ShaderStage>(i);
GLenum glShaderType;
switch (type) {
case ShaderStage::VERTEX:
glShaderType = GL_VERTEX_SHADER;
break;
case ShaderStage::FRAGMENT:
glShaderType = GL_FRAGMENT_SHADER;
break;
switch (stage) {
case ShaderStage::VERTEX: glShaderType = GL_VERTEX_SHADER; break;
case ShaderStage::FRAGMENT: glShaderType = GL_FRAGMENT_SHADER; break;
case ShaderStage::COMPUTE: glShaderType = GL_COMPUTE_SHADER; break;
}
if (UTILS_LIKELY(!shadersSource[i].empty())) {
@@ -428,6 +425,7 @@ void logCompilationError(io::ostream& out, ShaderStage shaderType,
switch (type) {
case ShaderStage::VERTEX: return "vertex";
case ShaderStage::FRAGMENT: return "fragment";
case ShaderStage::COMPUTE: return "compute";
}
};
+16 -2
View File
@@ -19,7 +19,11 @@
#if defined(__ANDROID__) || defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(__EMSCRIPTEN__)
#include <GLES3/gl31.h>
#if defined(__EMSCRIPTEN__)
# include <GLES3/gl3.h>
#else
# include <GLES3/gl31.h>
#endif
#include <GLES2/gl2ext.h>
/* The Android NDK doesn't expose extensions, fake it with eglGetProcAddress */
@@ -89,8 +93,11 @@
*/
#if defined(GL_ES_VERSION_2_0)
#ifdef GL_EXT_disjoint_timer_query
# define GL_TIME_ELAPSED GL_TIME_ELAPSED_EXT
# ifndef GL_TIME_ELAPSED
# define GL_TIME_ELAPSED GL_TIME_ELAPSED_EXT
# endif
#endif
#ifdef GL_EXT_clip_control
@@ -157,9 +164,16 @@
/* The iOS SDK only provides OpenGL ES headers up to 3.0. Filament works with OpenGL 3.0, but
* requires ES3.1 headers */
#if !defined(GL_ES_VERSION_3_1)
#define GL_SHADER_STORAGE_BUFFER 0x90D2
#define GL_COMPUTE_SHADER 0x91B9
#define GL_TEXTURE_2D_MULTISAMPLE 0x9100
// FIXME: The GL_TIME_ELAPSED define is used unconditionally in Filament, but
// requires extension support.
#ifndef GL_TIME_ELAPSED
#define GL_TIME_ELAPSED 0x88BF
#endif
#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY 0x900A
#define GL_SAMPLER_CUBE_MAP_ARRAY 0x900C
+5 -2
View File
@@ -54,7 +54,6 @@ io::ostream& operator<<(io::ostream& out, PrimitiveType type) {
CASE(PrimitiveType, LINES)
CASE(PrimitiveType, LINE_STRIP)
CASE(PrimitiveType, POINTS)
CASE(PrimitiveType, NONE)
}
return out;
}
@@ -367,6 +366,7 @@ io::ostream& operator<<(io::ostream& out, BufferObjectBinding binding) {
switch (binding) {
CASE(BufferObjectBinding, VERTEX)
CASE(BufferObjectBinding, UNIFORM)
CASE(BufferObjectBinding, SHADER_STORAGE)
}
return out;
}
@@ -498,8 +498,11 @@ io::ostream& operator<<(io::ostream& stream, ShaderStageFlags stageFlags) {
case ShaderStageFlags::FRAGMENT:
str = "{ fragment }";
break;
case ShaderStageFlags::COMPUTE:
str = "{ compute }";
break;
case ShaderStageFlags::ALL_SHADER_STAGE_FLAGS:
str = "{ vertex | fragment }";
str = "{ vertex | fragment | compute }";
break;
}
return stream << str;
@@ -0,0 +1,74 @@
/*
* Copyright (C) 2018 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 "vulkan/PlatformVkLinuxGGP.h"
#include <bluevk/BlueVK.h>
#if defined(FILAMENT_SUPPORTS_GGP)
#include <ggp_c/ggp.h>
#endif
#include <utils/Panic.h>
#include "VulkanDriverFactory.h"
using namespace bluevk;
namespace filament::backend {
Driver* PlatformVkLinuxGGP::createDriver(
void* const sharedContext,
const Platform::DriverConfig& driverConfig) noexcept {
#if defined(FILAMENT_SUPPORTS_GGP)
ASSERT_PRECONDITION(sharedContext == nullptr,
"Vulkan does not support shared contexts.");
const char* requiredInstanceExtensions[] = {
VK_GGP_STREAM_DESCRIPTOR_SURFACE_EXTENSION_NAME};
return VulkanDriverFactory::create(this, requiredInstanceExtensions,
sizeof(requiredInstanceExtensions) /
sizeof(requiredInstanceExtensions[0]),
driverConfig);
#else
PANIC_PRECONDITION("Filament does not support GGP.");
return nullptr;
#endif
}
void* PlatformVkLinuxGGP::createVkSurfaceKHR(void* nativeWindow, void* instance,
uint64_t flags) noexcept {
#if defined(FILAMENT_SUPPORTS_GGP)
VkStreamDescriptorSurfaceCreateInfoGGP surface_create_info = {
VK_STRUCTURE_TYPE_STREAM_DESCRIPTOR_SURFACE_CREATE_INFO_GGP};
surface_create_info.streamDescriptor = kGgpPrimaryStreamDescriptor;
PFN_vkCreateStreamDescriptorSurfaceGGP fpCreateStreamDescriptorSurfaceGGP =
reinterpret_cast<PFN_vkCreateStreamDescriptorSurfaceGGP>(
vkGetInstanceProcAddr(static_cast<VkInstance>(instance),
"vkCreateStreamDescriptorSurfaceGGP"));
ASSERT_PRECONDITION(fpCreateStreamDescriptorSurfaceGGP != nullptr,
"Error getting VkInstance "
"function vkCreateStreamDescriptorSurfaceGGP");
VkSurfaceKHR surface = nullptr;
VkResult res = fpCreateStreamDescriptorSurfaceGGP(
static_cast<VkInstance>(instance), &surface_create_info, nullptr,
&surface);
ASSERT_PRECONDITION(res == VK_SUCCESS, "Error in vulkan: %d", res);
return surface;
#else
PANIC_PRECONDITION("Filament does not support GGP.");
return nullptr;
#endif
}
} // namespace filament::backend
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_VULKAN_PLATFORM_VK_LINUX_GGP_H
#define TNT_FILAMENT_BACKEND_VULKAN_PLATFORM_VK_LINUX_GGP_H
#include <backend/DriverEnums.h>
#include <stdint.h>
#include "VulkanPlatform.h"
namespace filament::backend {
class PlatformVkLinuxGGP final : public VulkanPlatform {
public:
Driver* createDriver(
void* const sharedContext,
const Platform::DriverConfig& driverConfig) noexcept override;
void* createVkSurfaceKHR(void* nativeWindow, void* instance,
uint64_t flags) noexcept override;
int getOSVersion() const noexcept override { return 0; }
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_VULKAN_PLATFORM_VK_LINUX_GGP_H
@@ -111,6 +111,10 @@ void VulkanBuffer::loadFromCpu(VulkanContext& context, VulkanStagePool& stagePoo
VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
0, 0, nullptr, 1, &barrier, 0, nullptr);
}
if (mUsage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) {
// TODO: implement me
}
}
} // namespace filament::backend
+27 -3
View File
@@ -1470,12 +1470,22 @@ void VulkanDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> boh)
mPipelineCache.bindUniformBuffer((uint32_t) index, bo->buffer.getGpuBuffer(), offset, size);
}
void VulkanDriver::bindUniformBufferRange(uint32_t index, Handle<HwBufferObject> boh,
uint32_t offset, uint32_t size) {
void VulkanDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index,
Handle<HwBufferObject> boh, uint32_t offset, uint32_t size) {
assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE ||
bindingType == BufferObjectBinding::UNIFORM);
// TODO: implement BufferObjectBinding::SHADER_STORAGE case
auto* bo = handle_cast<VulkanBufferObject*>(boh);
mPipelineCache.bindUniformBuffer((uint32_t)index, bo->buffer.getGpuBuffer(), offset, size);
}
void VulkanDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
// TODO: implement unbindBuffer()
}
void VulkanDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
auto* hwsb = handle_cast<VulkanSamplerGroup*>(sbh);
mSamplerBindings[index] = hwsb;
@@ -1573,7 +1583,8 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
.memoryTypeIndex = mContext.selectMemoryType(memReqs.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT)
};
vkAllocateMemory(device, &allocInfo, nullptr, &stagingMemory);
@@ -1689,6 +1700,12 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
scheduleDestroy(std::move(pbd));
}
void VulkanDriver::readBufferSubData(backend::BufferObjectHandle boh,
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
// TODO: implement readBufferSubData
scheduleDestroy(std::move(p));
}
void VulkanDriver::blit(TargetBufferFlags buffers, Handle<HwRenderTarget> dst, Viewport dstRect,
Handle<HwRenderTarget> src, Viewport srcRect, SamplerMagFilter filter) {
assert_invariant(mContext.currentRenderPass.renderPass == VK_NULL_HANDLE);
@@ -1937,6 +1954,10 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
}
void VulkanDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
// FIXME: implement me
}
void VulkanDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
VulkanCommandBuffer const* commands = &mContext.commands->get();
VulkanTimerQuery* vtq = handle_cast<VulkanTimerQuery*>(tqh);
@@ -1991,6 +2012,9 @@ void VulkanDriver::debugCommandBegin(CommandStream* cmds, bool synchronous, cons
#endif
}
void VulkanDriver::resetState(int) {
}
// explicit instantiation of the Dispatcher
template class ConcreteDispatcher<VulkanDriver>;
+2 -14
View File
@@ -49,14 +49,10 @@ VulkanProgram::VulkanProgram(VulkanContext& context, const Program& builder) noe
HwProgram(builder.getName()), context(context) {
auto const& blobs = builder.getShadersSource();
VkShaderModule* modules[2] = { &bundle.vertex, &bundle.fragment };
bool missing = false;
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
// TODO: handle compute shaders.
for (size_t i = 0; i < 2; i++) {
const auto& blob = blobs[i];
VkShaderModule* module = modules[i];
if (blob.empty()) {
missing = true;
continue;
}
VkShaderModuleCreateInfo moduleInfo = {};
moduleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
moduleInfo.codeSize = blob.size();
@@ -106,13 +102,6 @@ VulkanProgram::VulkanProgram(VulkanContext& context, const Program& builder) noe
bundle.specializationInfos = pInfo;
}
// Output a warning because it's okay to encounter empty blobs, but it's not okay to use
// this program handle in a draw call.
if (missing) {
utils::slog.w << "Missing SPIR-V shader: " << this->name.c_str() << utils::io::endl;
return;
}
// Make a copy of the binding map
samplerGroupInfo = builder.getSamplerGroupInfo();
if constexpr (FILAMENT_VULKAN_VERBOSE) {
@@ -274,7 +263,6 @@ VulkanBufferObject::VulkanBufferObject(VulkanContext& context, VulkanStagePool&
void VulkanRenderPrimitive::setPrimitiveType(PrimitiveType pt) {
this->type = pt;
switch (pt) {
case PrimitiveType::NONE:
case PrimitiveType::POINTS:
primitiveTopology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
break;
+3 -1
View File
@@ -143,7 +143,9 @@ inline constexpr VkBufferUsageFlagBits getBufferObjectUsage(
return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
case BufferObjectBinding::UNIFORM:
return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
// when adding more buffer types here, make sure to update VulkanBuffer::loadFromCpu()
case BufferObjectBinding::SHADER_STORAGE:
return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
// when adding more buffer-types here, make sure to update VulkanBuffer::loadFromCpu()
// if necessary.
}
}
@@ -58,7 +58,9 @@ VulkanPipelineCache::getUsageFlags(uint16_t binding, ShaderStageFlags flags, Usa
if (any(flags & ShaderStageFlags::FRAGMENT)) {
src.set(MAX_SAMPLER_COUNT + binding);
}
assert_invariant(!any(flags & ~(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT)));
// TODO: add support for compute by extending SHADER_MODULE_COUNT and ensuring UsageFlags
// has 186 bits (MAX_SAMPLER_COUNT * 3)
// assert_invariant(!any(flags & ~(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT)));
return src;
}
+95
View File
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ComputeTest.h"
#include "PlatformRunner.h"
#include <backend/Platform.h>
#include "private/backend/CommandBufferQueue.h"
#include "private/backend/DriverApi.h"
using namespace filament;
using namespace filament::backend;
int main(int argc, char* argv[]) {
auto backend = (Backend)test::parseArgumentsForBackend(argc, argv);
ComputeTest::init(backend);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
// ------------------------------------------------------------------------------------------------
static constexpr size_t CONFIG_MIN_COMMAND_BUFFERS_SIZE = 1 * 1024 * 1024;
static constexpr size_t CONFIG_COMMAND_BUFFERS_SIZE = 3 * CONFIG_MIN_COMMAND_BUFFERS_SIZE;
Backend ComputeTest::sBackend = Backend::NOOP;
void ComputeTest::init(Backend backend) {
sBackend = backend;
}
ComputeTest::ComputeTest()
: commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE, CONFIG_COMMAND_BUFFERS_SIZE) {
}
ComputeTest::~ComputeTest() = default;
void ComputeTest::SetUp() {
Test::SetUp();
auto backend = sBackend;
DefaultPlatform* platform = DefaultPlatform::create(&backend);
GTEST_ASSERT_EQ(uint8_t(backend), uint8_t(sBackend));
driver = platform->createDriver(nullptr, {});
commandStream = std::make_unique<CommandStream>(*driver, commandBufferQueue.getCircularBuffer());
// We need at least feature level 2 to run the Compute tests
if (driver->getFeatureLevel() < FeatureLevel::FEATURE_LEVEL_2) {
GTEST_SKIP();
}
}
void ComputeTest::TearDown() {
Test::TearDown();
flushAndWait();
driver->terminate();
delete driver;
}
void ComputeTest::executeCommands() {
commandBufferQueue.flush();
auto buffers = commandBufferQueue.waitForCommands();
for (auto& item : buffers) {
if (UTILS_LIKELY(item.begin)) {
getDriverApi().execute(item.begin);
commandBufferQueue.releaseBuffer(item);
}
}
}
void ComputeTest::flushAndWait(uint64_t timeout) {
auto& api = getDriverApi();
auto fence = api.createFence();
api.finish();
executeCommands();
api.wait(fence, timeout);
api.destroyFence(fence);
}
+51
View File
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_COMPUTE_TEST_H
#define TNT_COMPUTE_TEST_H
#include <gtest/gtest.h>
#include "private/backend/CommandBufferQueue.h"
#include "private/backend/DriverApi.h"
class ComputeTest : public ::testing::Test {
static filament::backend::Backend sBackend;
filament::backend::Driver* driver = nullptr;
filament::backend::CommandBufferQueue commandBufferQueue;
std::unique_ptr<filament::backend::DriverApi> commandStream;
protected:
void SetUp() override;
void TearDown() override;
void executeCommands();
void flushAndWait(uint64_t timeout = 1000);
filament::backend::DriverApi& getDriverApi() { return *commandStream; }
filament::backend::Driver& getDriver() { return *driver; }
static filament::backend::Backend getBackend() noexcept { return sBackend; };
bool isMobile() noexcept { return driver->getShaderModel() == filament::backend::ShaderModel::MOBILE; };
public:
static void init(filament::backend::Backend backend);
ComputeTest();
~ComputeTest() override;
};
#endif // TNT_COMPUTE_TEST_H
+176
View File
@@ -0,0 +1,176 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ComputeTest.h"
using namespace filament;
using namespace filament::backend;
TEST_F(ComputeTest, basic) {
auto& driver = getDriverApi();
std::string shader_gles310 = {R"(
#version 310 es
layout(local_size_x = 16) in;
void main() {
}
)"};
std::string shader_gl450 = {R"(
#version 450 core
layout(local_size_x = 16) in;
void main() {
}
)"};
std::string shader_msl = {R"(
#include <simd/simd.h>
#include <metal_stdlib>
using namespace metal;
constant uint3 WorkGroupSize [[maybe_unused]] = uint3(16u, 1u, 1u);
kernel void main0() {}
)"};
std::string shader_spirv = {R"(
// TODO: spirv test
)"};
std::string_view shader;
switch (getBackend()) {
case Backend::OPENGL: shader = isMobile() ? shader_gles310 : shader_gl450; break;
case Backend::VULKAN: shader = shader_spirv; break;
case Backend::METAL: shader = shader_msl; break;
default:
GTEST_FATAL_FAILURE_("unexpected backend");
}
Program program;
program.shader(ShaderStage::COMPUTE, shader.data(), shader.size() + 1);
Handle<HwProgram> ph = driver.createProgram(std::move(program));
driver.dispatchCompute(ph, { 1, 1, 1 });
driver.destroyProgram(ph);
driver.finish();
executeCommands();
getDriver().purge();
}
TEST_F(ComputeTest, copy) {
auto& driver = getDriverApi();
std::string shader_gles310 = {R"(
#version 310 es
layout(local_size_x = 16) in;
layout(std430) buffer;
layout(binding = 0) writeonly buffer Output { float elements[]; } output_data;
layout(binding = 1) readonly buffer Input0 { float elements[]; } input_data;
void main() {
uint ident = gl_GlobalInvocationID.x;
output_data.elements[ident] = input_data.elements[ident];
}
)"};
std::string shader_gl450 = {R"(
#version 450 core
layout(local_size_x = 16) in;
layout(std430) buffer;
layout(binding = 0) writeonly buffer Output { float elements[]; } output_data;
layout(binding = 1) readonly buffer Input0 { float elements[]; } input_data;
void main() {
uint ident = gl_GlobalInvocationID.x;
output_data.elements[ident] = input_data.elements[ident];
}
)"};
std::string shader_msl = {R"(
#include <simd/simd.h>
#include <metal_stdlib>
using namespace metal;
struct Output_data {
float elements[1];
};
struct Input_data {
float elements[1];
};
constant uint3 WorkGroupSize [[maybe_unused]] = uint3(16u, 1u, 1u);
kernel void main0(device Output_data& output_data [[buffer(0)]],
device Input_data& input_data [[buffer(1)]],
uint3 GlobalInvocationID [[thread_position_in_grid]]) {
output_data.elements[GlobalInvocationID.x] = input_data.elements[GlobalInvocationID.x];
}
)"};
std::string shader_spirv = {R"(
// TODO: spirv test
)"};
std::string_view shader;
switch (getBackend()) {
case Backend::OPENGL: shader = isMobile() ? shader_gles310 : shader_gl450; break;
case Backend::VULKAN: shader = shader_spirv; break;
case Backend::METAL: shader = shader_msl; break;
default:
GTEST_FATAL_FAILURE_("unexpected backend");
}
size_t groupSize = 16;
size_t groupCount = 1024;
size_t size = groupSize * groupCount * sizeof(float);
std::vector<float> data(groupSize * groupCount);
std::generate(data.begin(), data.end(), [v = 0.0f]() mutable {
v = v + 1.0f;
return v;
});
driver.startCapture(0);
auto output_data = driver.createBufferObject(size, BufferObjectBinding::SHADER_STORAGE, BufferUsage::STATIC);
auto input_data = driver.createBufferObject(size, BufferObjectBinding::SHADER_STORAGE, BufferUsage::STATIC);
driver.updateBufferObject(input_data, { data.data(), size }, 0);
Program program;
program.shader(ShaderStage::COMPUTE, shader.data(), shader.size() + 1);
Handle<HwProgram> ph = driver.createProgram(std::move(program));
driver.bindBufferRange(BufferObjectBinding::SHADER_STORAGE, 0, output_data, 0, size);
driver.bindBufferRange(BufferObjectBinding::SHADER_STORAGE, 1, input_data, 0, size);
driver.dispatchCompute(ph, { groupCount, 1, 1 });
driver.unbindBuffer(BufferObjectBinding::SHADER_STORAGE, 0);
driver.unbindBuffer(BufferObjectBinding::SHADER_STORAGE, 1);
float* const user = (float*)malloc(size);
driver.readBufferSubData(output_data, 0, size, { user, size });
driver.destroyProgram(ph);
driver.destroyBufferObject(input_data);
driver.destroyBufferObject(output_data);
driver.finish();
driver.stopCapture(0);
executeCommands();
getDriver().purge();
// TODO: check buffer content
EXPECT_EQ(memcmp(user, data.data(), size), 0);
free(user);
}
+76 -9
View File
@@ -11,7 +11,7 @@
</style>
**Vulkan Backend**
*Last updated: 11 June 2021*
*Last updated: 26 October 2022*
![](../../docs/images/filament_logo.png)
@@ -47,7 +47,7 @@ single `VkDevice` and `VkInstance`.
* | | | | *
* |------------------------------------------| |-----------------------------------------| *
* | | | | *
* | Helpers VulkanContext | | SwapContext SwapContext | *
* | Helpers VulkanContext | | VulkanTexture VulkanTexture | *
* | .--------------. .---------------. | | .----------------+----------------. | *
* | | PipelineCache| | VkDevice | | | | VkImage | VkImage | | *
* | +--------------+ | VkInstance | | | | VkImageView | VkImageView | | *
@@ -91,13 +91,14 @@ that are defined in `DriverBase.h`.
Most of the handle types do not contain much logic, but they often do more than mere wrapping of
Vk objects. Notably, `VulkanRenderTarget` does not have a straightforward mapping to any one Vk
object. See its class comment for details.
object. See its class comment for details. Also, `VulkanTexture` and `VulkanSwapChain` have their
own cpps file because of complexity around layouts and subresources.
VulkanProgram
: Descends from `HwProgram`, owns a pair of `VkShaderModule`.
VulkanRenderTarget
: Descends from `HwRenderTarget`, contains references to `VkImage` objects.
: Descends from `HwRenderTarget`, contains references to `VulkanTexture` objects.
VulkanSwapChain
: Descends from `HwSwapChain`, has a reference to `VkSurfaceKHR` and `VkSwapchainKHR`.
@@ -127,14 +128,16 @@ they do follow a common set of conventions. For example, most of them implement
that is called once per frame to free unused objects.
VulkanPipelineCache
: Cache of VkDescriptorSet and VkPipeline.
: Cache of VkDescriptorSet, VkPipeline, and VkPipelineLayout.
VulkanDisposer
: Holds reference counts to Vulkan objects in order to defer their destruction.
VulkanFboCache
: Cache of VkRenderPass and VkFramebuffer. Contains weak references to VkImage handles, but does not
own any pixel data.
own any pixel data. The cache key for render passes is quite complex because it needs to include
image layout information in a compact way. For details see the comments in the `RenderPassKey`
declaration.
VulkanStagePool
: Pool of VkBuffer objects that can be memory mapped (shared between host and device).
@@ -149,6 +152,71 @@ no subfolders. All backend-specific types and functions live directly in the `fi
namespace, and we simply prefix our names with "Vulkan" when necessary to avoid ambiguation with
OpenGL.
# Image Layout Strategy
Adhering to a coherent strategy for image layout transitions is crucial. Without this, validation
errors (and actual black screens with some GPU vendors) occur far too easily and can feel like
whack-a-mole.
Vulkan provides several mechanisms for transitioning layout (render passes, barriers) so the
strategy outlined below specifies which mechanism should be used in various scenarios.
The current image layouts are tracked at the subresource level in `VulkanTexture` using a
specialized STL-ish container that we designed for this purpose:
[RangeMap](https://github.com/google/filament/blob/main/libs/utils/include/utils/RangeMap.h).
In the past we tried to avoid tracking since it theoritically duplicates state that exists in the
GPU vendor's driver. However in practice this was impossible to manage, because complex logic was
required to deduce the current layout. Also, tracking lets us use `assert_invariant` in many places
to ensure that the behavior described in this document is implemented properly. It is much easier
to catch errors with asserts than with validation.
As of this writing, the current rules are:
(1) **Read-Only Color Textures**
These should be created in an UNDEFINED state and stay that way until data gets uploaded into them,
at which point the appropriate subresources are transitioned to TRANSFER_DST_OPTIMAL for the upload,
then to SHADER_READ_ONLY_OPTIMAL, both times using a barrier. Note that a subresource might be
updated several times; each time it undergoes these two transitions.
If a shader samples from a texture whose mipmaps are only partially loaded, we might see validation
warnings about how some subresources are in an UNDEFINED layout. However if we are properly using
the `setMinMaxLevels` driver API, then the Vulkan backend will not bind those particular
subresources, so validation should not complain.
(2) **Writeable Color Textures**
During construction of the HwTexture, these are transitioned to GENERAL using a barrier.
GENERAL layout is not recommended by AMD best practices but in our case we wish to avoid thrashing
the layout between COLOR_ATTACHMENT_OPTIMAL and SHADER_READ_ONLY_OPTIMAL for post-processing
effects. Moreover we do not have a priori knowledge about blitting and read pixels usage.
(3) **Depth Textures**
During construction of the HwTexture, these are transitioned to GENERAL using a barrier.
Note that some post-processing passes (e.g. SSAO) sample from the depth buffer while also enabling
depth testing. Using GENERAL makes this legal.
(4) **Swap Chain Textures**
During construction, these are transitioned to GENERAL using a barrier. They are then transitioned
to PRESENT at the end of the frame using a barrier, and transitioned back to GENERAL using the first
render pass of the frame.
Note that we transition to PRESENT using a barrier, but with some GPU vendors it might be preferable
to transition them using the last render pass. However we have no way of knowing ahead of time which
pass is last.
Note that Filament allows swap chains to be used as blit targets and blit sources (e.g. for
ReadPixels). Using GENERAL makes this easier to manage.
(5) **Headless Swap Chain Textures**
These follow the same rules as writeable color texture and depth textures.
# VulkanPipelineCache
Uniforms, samplers, and shaders are not directly bindable in Vulkan; instead they are bound through
@@ -161,8 +229,6 @@ public methods. See its header file for details.
The pipeline cache also manages the *layout objects*, which constrain the number of uniform buffers
and samplers that can be bound simultaneously (see `VkDescriptorSetLayout` and `VkPipelineLayout`).
Here we take advantage of Filament's opinionated shading model in order to keep things simple, which
allows us to create only one layout for the entire lifetime of the app.
# Memory Allocation
@@ -196,7 +262,8 @@ Here we list some of the most significant issues and to-be-done items for the Vu
- Improve the UBO strategy
- Currently we use staging buffers and perform lots of copies (host-to-host, then host-to-device)
- Consider an approach similar to MetalDriver, which effectively implements double-buffering using a pool of shared mem buffers
- Consider an approach similar to MetalDriver, which effectively implements double-buffering
using a pool of shared mem buffers
- Consider using subpasses for the depth prepass
- We lazily create pipeline objects
1. This is notoriously slow in Vulkan
-1
View File
@@ -281,7 +281,6 @@ public:
* By default, this is an identity matrix.
*
* @param shift x and y translation added to the projection matrix, specified in NDC
* @param shift x and y translation added to the projection matrix, specified in NDC
* coordinates, that is, if the translation must be specified in pixels,
* shift must be scaled by 1.0 / { viewport.width, viewport.height }.
*
+16
View File
@@ -725,6 +725,22 @@ public:
*/
utils::JobSystem& getJobSystem() noexcept;
#if defined(__EMSCRIPTEN__)
/**
* WebGL only: Tells the driver to reset any internal state tracking if necessary.
*
* This is only useful when integrating an external renderer into Filament on platforms
* like WebGL, where share contexts do not exist. Filament keeps track of the GL
* state it has set (like which texture is bound), and does not re-set that state if
* it does not think it needs to. However, if an external renderer has set different
* state in the mean time, Filament will use that new state unknowingly.
*
* If you are in this situation, call this function - ideally only once per frame,
* immediately after calling Engine::execute().
*/
void resetBackendState() noexcept;
#endif
DebugRegistry& getDebugRegistry() noexcept;
protected:
+29 -4
View File
@@ -226,7 +226,7 @@ public:
* @see ShadowCascades::computeLogSplits
* @see ShadowCascades::computePracticalSplits
*/
float cascadeSplitPositions[3] = { 0.25f, 0.50f, 0.75f };
float cascadeSplitPositions[3] = { 0.125f, 0.25f, 0.50f };
/** Constant bias in world units (e.g. meters) by which shadows are moved away from the
* light. 1mm by default.
@@ -266,9 +266,29 @@ public:
* Controls whether the shadow map should be optimized for resolution or stability.
* When set to true, all resolution enhancing features that can affect stability are
* disabling, resulting in significantly lower resolution shadows, albeit stable ones.
*
* Setting this flag to true always disables LiSPSM (see below).
*
* @see lispsm
*/
bool stable = false;
/**
* LiSPSM, or light-space perspective shadow-mapping is a technique allowing to better
* optimize the use of the shadow-map texture. When enabled the effective resolution of
* shadows is greatly improved and yields result similar to using cascades without the
* extra cost. LiSPSM comes with some drawbacks however, in particular it is incompatible
* with blurring because it effectively affects the blur kernel size.
*
* Blurring is only an issue when using ShadowType::VSM with a large blur or with
* ShadowType::PCSS however.
*
* If these blurring artifacts become problematic, this flag can be used to disable LiSPSM.
*
* @see stable
*/
bool lispsm = true;
/**
* Constant bias in depth-resolution units by which shadows are moved away from the
* light. The default value of 0.5 is used to round depth values up.
@@ -328,6 +348,14 @@ public:
*/
uint8_t msaaSamples = 1;
/**
* When elvsm is set to true, "Exponential Layered VSM without Layers" are used. It is
* an improvement to the default EVSM which suffers important light leaks. Enabling
* ELVSM for a single shadowmap doubles the memory usage of all shadow maps.
* ELVSM is mostly useful when large blurs are used.
*/
bool elvsm = false;
/**
* Blur width for the VSM blur. Zero do disable.
* The maximum value is 125.
@@ -418,9 +446,6 @@ public:
* @param enable Enables or disables casting shadows from this Light.
*
* @return This Builder, for chaining calls.
*
* @warning
* - Only a Type.DIRECTIONAL, Type.SUN, Type.SPOT, or Type.FOCUSED_SPOT light can cast shadows
*/
Builder& castShadows(bool enable) noexcept;
+55 -1
View File
@@ -34,7 +34,7 @@ class Material;
class Texture;
class TextureSampler;
class UniformBuffer;
class UniformInterfaceBlock;
class BufferInterfaceBlock;
class UTILS_PUBLIC MaterialInstance : public FilamentAPI {
template<size_t N>
@@ -277,6 +277,12 @@ public:
*/
void setMaskThreshold(float threshold) noexcept;
/**
* Gets the minimum alpha value a fragment must have to not be discarded when the blend
* mode is MASKED
*/
float getMaskThreshold() const noexcept;
/**
* Sets the screen space variance of the filter kernel used when applying specular
* anti-aliasing. The default value is set to 0.15. The specified value should be between
@@ -284,6 +290,12 @@ public:
*/
void setSpecularAntiAliasingVariance(float variance) noexcept;
/**
* Gets the screen space variance of the filter kernel used when applying specular
* anti-aliasing.
*/
float getSpecularAntiAliasingVariance() const noexcept;
/**
* Sets the clamping threshold used to suppress estimation errors when applying specular
* anti-aliasing. The default value is set to 0.2. The specified value should be between 0
@@ -291,6 +303,12 @@ public:
*/
void setSpecularAntiAliasingThreshold(float threshold) noexcept;
/**
* Gets the clamping threshold used to suppress estimation errors when applying specular
* anti-aliasing.
*/
float getSpecularAntiAliasingThreshold() const noexcept;
/**
* Enables or disables double-sided lighting if the parent Material has double-sided capability,
* otherwise prints a warning. If double-sided lighting is enabled, backface culling is
@@ -298,36 +316,72 @@ public:
*/
void setDoubleSided(bool doubleSided) noexcept;
/**
* Returns whether double-sided lighting is enabled when the parent Material has double-sided
* capability.
*/
bool isDoubleSided() const noexcept;
/**
* Specifies how transparent objects should be rendered (default is DEFAULT).
*/
void setTransparencyMode(TransparencyMode mode) noexcept;
/**
* Returns the transparency mode.
*/
TransparencyMode getTransparencyMode() const noexcept;
/**
* Overrides the default triangle culling state that was set on the material.
*/
void setCullingMode(CullingMode culling) noexcept;
/**
* Returns the face culling mode.
*/
CullingMode getCullingMode() const noexcept;
/**
* Overrides the default color-buffer write state that was set on the material.
*/
void setColorWrite(bool enable) noexcept;
/**
* Returns whether color write is enabled.
*/
bool isColorWriteEnabled() const noexcept;
/**
* Overrides the default depth-buffer write state that was set on the material.
*/
void setDepthWrite(bool enable) noexcept;
/**
* Returns whether depth write is enabled.
*/
bool isDepthWriteEnabled() const noexcept;
/**
* Overrides the default depth testing state that was set on the material.
*/
void setDepthCulling(bool enable) noexcept;
/**
* Returns whether depth culling is enabled.
*/
bool isDepthCullingEnabled() const noexcept;
/**
* Overrides the default stencil-buffer write state that was set on the material.
*/
void setStencilWrite(bool enable) noexcept;
/**
* Returns whether stencil write is enabled.
*/
bool isStencilWriteEnabled() const noexcept;
/**
* Sets the stencil comparison function (default is StencilCompareFunc::A).
*
+8
View File
@@ -390,6 +390,14 @@ struct VsmShadowOptions {
*/
bool mipmapping = false;
/**
* Whether to use a 32-bits or 16-bits texture format for VSM shadow maps. 32-bits
* precision is rarely needed, but it does reduces light leaks as well as "fading"
* of the shadows in some situations. Setting highPrecision to true for a single
* shadow map will double the memory usage of all shadow maps.
*/
bool highPrecision = false;
/**
* VSM minimum variance scale, must be positive.
*/
@@ -566,6 +566,11 @@ public:
inline void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
MorphTargetBuffer* morphTargetBuffer);
/**
* Get a MorphTargetBuffer to the given primitive or null if it doesn't exist.
*/
MorphTargetBuffer* getMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex) const noexcept;
/**
* Gets the number of morphing in the given entity.
*/
+161
View File
@@ -0,0 +1,161 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AtlasAllocator.h"
#include <utils/debug.h>
namespace filament {
using namespace utils;
static inline constexpr std::pair<uint8_t, uint8_t> unmorton(uint16_t m) noexcept {
uint32_t r = (m | (uint32_t(m) << 15u)) & 0x55555555u;
r = (r | (r >> 1u)) & 0x33333333u;
r = (r | (r >> 2u)) & 0x0f0f0f0fu;
r = r | (r >> 4u);
return { uint8_t(r), uint8_t(r >> 16u) };
}
AtlasAllocator::AtlasAllocator(size_t maxTextureSize) noexcept {
// round to power-of-two immediately inferior or equal to the size specified.
mMaxTextureSizePot = (sizeof(maxTextureSize) * 8 - 1u) - utils::clz(maxTextureSize);
}
Viewport AtlasAllocator::allocate(size_t textureSize) noexcept {
Viewport result{};
const size_t powerOfTwo = (sizeof(textureSize) * 8 - 1u) - utils::clz(textureSize);
// asked for a texture size too large
if (UTILS_UNLIKELY(powerOfTwo > mMaxTextureSizePot)) {
return result;
}
// asked for a texture size too small
if (UTILS_UNLIKELY(mMaxTextureSizePot - powerOfTwo >= QuadTree::height())) {
return result;
}
const size_t layer = mMaxTextureSizePot - powerOfTwo;
const NodeId loc = allocateInLayer(layer);
if (loc.l >= 0) {
assert_invariant(loc.l == int8_t(layer));
size_t dimension = 1u << powerOfTwo;
// find the location of in the texture from the morton code (quadtree position)
auto [x, y] = unmorton(loc.code);
// scale to our maximum allocation size
result.left = int32_t(x) << powerOfTwo;
result.bottom = int32_t(y) << powerOfTwo;
result.width = dimension;
result.height = dimension;
}
return result;
}
void AtlasAllocator::clear(size_t maxTextureSize) noexcept {
std::fill(mQuadTree.begin(), mQuadTree.end(), Node{});
mMaxTextureSizePot = (sizeof(maxTextureSize) * 8 - 1u) - utils::clz(maxTextureSize);
}
AtlasAllocator::NodeId AtlasAllocator::allocateInLayer(size_t maxHeight) noexcept {
using namespace QuadTreeUtils;
NodeId candidate{ -1, 0 };
if (UTILS_UNLIKELY(maxHeight > QuadTree::height())) {
return candidate;
}
const int8_t n = int8_t(maxHeight);
QuadTree::traverse(0, 0, n,
[this, n, &candidate](NodeId const& curr) -> QuadTree::TraversalResult {
// we should never reach a level higher than n
assert_invariant(curr.l <= n);
// get the node being processed
const size_t i = index(curr.l, curr.code);
Node const& node = mQuadTree[i];
// if the node is allocated, the whole tree below it is unavailable
if (node.isAllocated()) {
// if a node is allocated it can't have children
assert_invariant(!node.hasChildren());
return QuadTree::TraversalResult::SKIP_CHILDREN;
}
// we're looking for the `deepest` unallocated node that has no children,
// up to the depth we're trying to allocate
if (curr.l > candidate.l && !node.hasChildren()) {
candidate = curr;
// Special case where we found a fitting node, just exit.
if (curr.l == n) {
return QuadTree::TraversalResult::EXIT;
}
}
// We only want to find a fitting node that already has siblings, in order
// to accomplish a "best fit" allocation. So if we're a parent of a 'fitting'
// node and don't have children, we skip the children recursion.
if (curr.l == n - 1 && !node.hasChildren()) {
return QuadTree::TraversalResult::SKIP_CHILDREN;
}
// continue going down the tree
return QuadTree::TraversalResult::RECURSE;
});
if (candidate.l >= 0) {
const size_t i = index(candidate.l, candidate.code);
Node& allocation = mQuadTree[i];
if (candidate.l == n) {
allocation.allocated = true;
if (UTILS_LIKELY(n > 0)) {
// the root doesn't have a parent
const size_t p = parent(candidate.l, candidate.code);
Node& parent = mQuadTree[p];
assert_invariant(!parent.isAllocated());
assert_invariant(parent.hasChildren());
assert_invariant(!parent.hasAllChildren());
parent.children++;
}
} else if (candidate.l < int8_t(QuadTree::height())) {
// we need to create the hierarchy down to the level we need
assert_invariant(!allocation.isAllocated());
assert_invariant(!allocation.hasChildren());
NodeId found{};
QuadTree::traverse(candidate.l, candidate.code,
[this, n, &found](NodeId const& curr) -> QuadTree::TraversalResult {
size_t i = index(curr.l, curr.code);
Node& node = mQuadTree[i];
if (curr.l == n) {
found = curr;
node.allocated = true;
return QuadTree::TraversalResult::EXIT;
}
node.children++;
return QuadTree::TraversalResult::RECURSE;
});
candidate = found;
}
}
return candidate;
}
} // namespace filament
+95
View File
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_ATLASALLOCATOR_H
#define TNT_FILAMENT_ATLASALLOCATOR_H
#include <utils/QuadTree.h>
#include <filament/Viewport.h>
class AtlasAllocator_AllocateFirstLevel_Test;
class AtlasAllocator_AllocateSecondLevel_Test;
class AtlasAllocator_AllocateMixed0_Test;
class AtlasAllocator_AllocateMixed1_Test;
class AtlasAllocator_AllocateMixed2_Test;
namespace filament {
/*
* A 2D allocator. Allocations must be square and have a power-of-two size.
* AtlasAllocator hard codes a depth of 4, that is only 4 allocation sizes are permitted.
* This doesn't actually allocate memory, just manages space within an abstract 2D image.
*/
class AtlasAllocator {
/*
* A quadtree is used to track the allocated regions. Each node of the quadtree stores a
* `Node` data structure below.
* The `Node` tracks if it is allocated as well as the number of children it has. It doesn't
* track which children though.
*/
struct Node {
// whether this node is allocated. Implies no children.
constexpr bool isAllocated() const noexcept { return allocated; }
// whether this node has children. Implies it's not allocated.
constexpr bool hasChildren() const noexcept { return children != 0; }
// whether this node has all four children. Implies hasChildren().
constexpr bool hasAllChildren() const noexcept { return children == 4; }
bool allocated : 1; // true / false
uint8_t children : 3; // 0, 1, 2, 3, 4
};
static constexpr size_t QUAD_TREE_DEPTH = 4u;
using QuadTree = utils::QuadTreeArray<Node, QUAD_TREE_DEPTH>;
using NodeId = QuadTree::NodeId;
public:
/*
* Create allocator and specify the maximum texture size. Must be a power of two.
* Allocations size allowed are the four power-of-two smaller or equal to this size.
*/
explicit AtlasAllocator(size_t maxTextureSize = 1024) noexcept;
/*
* Allocates a square of size `textureSize`. Must be one of the power-of-two allowed
* (see above).
* Returns the location of the allocation within the maxTextureSize^2 square.
*/
Viewport allocate(size_t textureSize) noexcept;
/*
* Frees all allocations and reset the maximum texture size.
*/
void clear(size_t maxTextureSize = 1024) noexcept;
private:
friend AtlasAllocator_AllocateFirstLevel_Test;
friend AtlasAllocator_AllocateSecondLevel_Test;
friend AtlasAllocator_AllocateMixed0_Test;
friend AtlasAllocator_AllocateMixed1_Test;
friend AtlasAllocator_AllocateMixed2_Test;
QuadTree::NodeId allocateInLayer(size_t n) noexcept;
// quad-tree array to store the allocated list
QuadTree mQuadTree{};
uint8_t mMaxTextureSizePot = 0;
};
} // namespace filament
#endif // TNT_FILAMENT_ATLASALLOCATOR_H
+2 -2
View File
@@ -24,11 +24,11 @@ namespace filament {
void BufferObject::setBuffer(Engine& engine,
BufferObject::BufferDescriptor&& buffer, uint32_t byteOffset) {
upcast(this)->setBuffer(upcast(engine), std::move(buffer), byteOffset);
downcast(this)->setBuffer(downcast(engine), std::move(buffer), byteOffset);
}
size_t BufferObject::getByteCount() const noexcept {
return upcast(this)->getByteCount();
return downcast(this)->getByteCount();
}
} // namespace filament
+32 -32
View File
@@ -69,134 +69,134 @@ mat4 Camera::inverseProjection(const mat4 & p) noexcept {
void Camera::setProjection(Camera::Projection projection, double left, double right, double bottom,
double top, double near, double far) {
upcast(this)->setProjection(projection, left, right, bottom, top, near, far);
downcast(this)->setProjection(projection, left, right, bottom, top, near, far);
}
void Camera::setProjection(double fovInDegrees, double aspect, double near, double far,
Camera::Fov direction) {
upcast(this)->setProjection(fovInDegrees, aspect, near, far, direction);
downcast(this)->setProjection(fovInDegrees, aspect, near, far, direction);
}
void Camera::setLensProjection(double focalLengthInMillimeters,
double aspect, double near, double far) {
upcast(this)->setLensProjection(focalLengthInMillimeters, aspect, near, far);
downcast(this)->setLensProjection(focalLengthInMillimeters, aspect, near, far);
}
void Camera::setCustomProjection(mat4 const& projection, double near, double far) noexcept {
upcast(this)->setCustomProjection(projection, near, far);
downcast(this)->setCustomProjection(projection, near, far);
}
void Camera::setCustomProjection(mat4 const& projection, mat4 const& projectionForCulling,
double near, double far) noexcept {
upcast(this)->setCustomProjection(projection, projectionForCulling, near, far);
downcast(this)->setCustomProjection(projection, projectionForCulling, near, far);
}
void Camera::setScaling(double2 scaling) noexcept {
upcast(this)->setScaling(scaling);
downcast(this)->setScaling(scaling);
}
void Camera::setShift(double2 shift) noexcept {
upcast(this)->setShift(shift);
downcast(this)->setShift(shift);
}
mat4 Camera::getProjectionMatrix() const noexcept {
return upcast(this)->getUserProjectionMatrix();
return downcast(this)->getUserProjectionMatrix();
}
mat4 Camera::getCullingProjectionMatrix() const noexcept {
return upcast(this)->getUserCullingProjectionMatrix();
return downcast(this)->getUserCullingProjectionMatrix();
}
double4 Camera::getScaling() const noexcept {
return upcast(this)->getScaling();
return downcast(this)->getScaling();
}
double2 Camera::getShift() const noexcept {
return upcast(this)->getShift();
return downcast(this)->getShift();
}
double Camera::getNear() const noexcept {
return upcast(this)->getNear();
return downcast(this)->getNear();
}
double Camera::getCullingFar() const noexcept {
return upcast(this)->getCullingFar();
return downcast(this)->getCullingFar();
}
void Camera::setModelMatrix(const mat4& modelMatrix) noexcept {
upcast(this)->setModelMatrix(modelMatrix);
downcast(this)->setModelMatrix(modelMatrix);
}
void Camera::setModelMatrix(const mat4f& modelMatrix) noexcept {
upcast(this)->setModelMatrix(modelMatrix);
downcast(this)->setModelMatrix(modelMatrix);
}
void Camera::lookAt(double3 const& eye, double3 const& center, double3 const& up) noexcept {
upcast(this)->lookAt(eye, center, up);
downcast(this)->lookAt(eye, center, up);
}
mat4 Camera::getModelMatrix() const noexcept {
return upcast(this)->getModelMatrix();
return downcast(this)->getModelMatrix();
}
mat4 Camera::getViewMatrix() const noexcept {
return upcast(this)->getViewMatrix();
return downcast(this)->getViewMatrix();
}
double3 Camera::getPosition() const noexcept {
return upcast(this)->getPosition();
return downcast(this)->getPosition();
}
float3 Camera::getLeftVector() const noexcept {
return upcast(this)->getLeftVector();
return downcast(this)->getLeftVector();
}
float3 Camera::getUpVector() const noexcept {
return upcast(this)->getUpVector();
return downcast(this)->getUpVector();
}
float3 Camera::getForwardVector() const noexcept {
return upcast(this)->getForwardVector();
return downcast(this)->getForwardVector();
}
float Camera::getFieldOfViewInDegrees(Camera::Fov direction) const noexcept {
return upcast(this)->getFieldOfViewInDegrees(direction);
return downcast(this)->getFieldOfViewInDegrees(direction);
}
Frustum Camera::getFrustum() const noexcept {
return upcast(this)->getCullingFrustum();
return downcast(this)->getCullingFrustum();
}
utils::Entity Camera::getEntity() const noexcept {
return upcast(this)->getEntity();
return downcast(this)->getEntity();
}
void Camera::setExposure(float aperture, float shutterSpeed, float ISO) noexcept {
upcast(this)->setExposure(aperture, shutterSpeed, ISO);
downcast(this)->setExposure(aperture, shutterSpeed, ISO);
}
float Camera::getAperture() const noexcept {
return upcast(this)->getAperture();
return downcast(this)->getAperture();
}
float Camera::getShutterSpeed() const noexcept {
return upcast(this)->getShutterSpeed();
return downcast(this)->getShutterSpeed();
}
float Camera::getSensitivity() const noexcept {
return upcast(this)->getSensitivity();
return downcast(this)->getSensitivity();
}
void Camera::setFocusDistance(float distance) noexcept {
upcast(this)->setFocusDistance(distance);
downcast(this)->setFocusDistance(distance);
}
float Camera::getFocusDistance() const noexcept {
return upcast(this)->getFocusDistance();
return downcast(this)->getFocusDistance();
}
double Camera::getFocalLength() const noexcept {
return upcast(this)->getFocalLength();
return downcast(this)->getFocalLength();
}
double Camera::computeEffectiveFocalLength(double focalLength, double focusDistance) noexcept {
+7 -6
View File
@@ -86,7 +86,10 @@ void Culler::intersects(
visible &= fast::signbit(dot) << bit;
}
results[i] |= result_type(visible);
auto r = results[i];
r &= ~result_type(1u << bit);
r |= result_type(visible);
results[i] = r;
}
}
@@ -101,22 +104,20 @@ bool Culler::intersects(Frustum const& frustum, Box const& box) noexcept {
Culler::result_type results[MODULO];
centers[0] = box.center;
extents[0] = box.halfExtent;
results[0] = 0;
Culler::intersects(results, frustum, centers, extents, MODULO, 0);
return bool(results[0]);
return bool(results[0] & 1);
}
/*
* returns whether an sphere intersects with the frustum
* returns whether a sphere intersects with the frustum
*/
bool Culler::intersects(Frustum const& frustum, float4 const& sphere) noexcept {
// The main intersection routine assumes multiples of 8 items
float4 spheres[MODULO];
Culler::result_type results[MODULO];
spheres[0] = sphere;
results[0] = 0;
Culler::intersects(results, frustum, spheres, MODULO);
return bool(results[0]);
return bool(results[0] & 1);
}
// For testing...
+1 -1
View File
@@ -42,7 +42,7 @@ void DFG::init(FEngine& engine) noexcept {
lut->setImage(engine, 0, std::move(buffer));
mLUT = upcast(lut);
mLUT = downcast(lut);
}
void DFG::terminate(FEngine& engine) noexcept {
+15 -15
View File
@@ -25,64 +25,64 @@ namespace filament {
using namespace math;
bool DebugRegistry::hasProperty(const char* name) const noexcept {
return upcast(this)->hasProperty(name);
return downcast(this)->hasProperty(name);
}
bool DebugRegistry::setProperty(const char* name, bool v) noexcept {
return upcast(this)->setProperty(name, v);
return downcast(this)->setProperty(name, v);
}
bool DebugRegistry::setProperty(const char* name, int v) noexcept {
return upcast(this)->setProperty(name, v);
return downcast(this)->setProperty(name, v);
}
bool DebugRegistry::setProperty(const char* name, float v) noexcept {
return upcast(this)->setProperty(name, v);
return downcast(this)->setProperty(name, v);
}
bool DebugRegistry::setProperty(const char* name, float2 v) noexcept {
return upcast(this)->setProperty(name, v);
return downcast(this)->setProperty(name, v);
}
bool DebugRegistry::setProperty(const char* name, float3 v) noexcept {
return upcast(this)->setProperty(name, v);
return downcast(this)->setProperty(name, v);
}
bool DebugRegistry::setProperty(const char* name, float4 v) noexcept {
return upcast(this)->setProperty(name, v);
return downcast(this)->setProperty(name, v);
}
bool DebugRegistry::getProperty(const char* name, bool* v) const noexcept {
return upcast(this)->getProperty(name, v);
return downcast(this)->getProperty(name, v);
}
bool DebugRegistry::getProperty(const char* name, int* v) const noexcept {
return upcast(this)->getProperty(name, v);
return downcast(this)->getProperty(name, v);
}
bool DebugRegistry::getProperty(const char* name, float* v) const noexcept {
return upcast(this)->getProperty(name, v);
return downcast(this)->getProperty(name, v);
}
bool DebugRegistry::getProperty(const char* name, float2* v) const noexcept {
return upcast(this)->getProperty(name, v);
return downcast(this)->getProperty(name, v);
}
bool DebugRegistry::getProperty(const char* name, float3* v) const noexcept {
return upcast(this)->getProperty(name, v);
return downcast(this)->getProperty(name, v);
}
bool DebugRegistry::getProperty(const char* name, float4* v) const noexcept {
return upcast(this)->getProperty(name, v);
return downcast(this)->getProperty(name, v);
}
void *DebugRegistry::getPropertyAddress(const char *name) noexcept {
return upcast(this)->getPropertyAddress(name);
return downcast(this)->getPropertyAddress(name);
}
DebugRegistry::DataSource DebugRegistry::getDataSource(const char* name) const noexcept {
return upcast(this)->getDataSource(name);
return downcast(this)->getDataSource(name);
}
+56 -50
View File
@@ -51,7 +51,7 @@ Engine* Engine::create(Backend backend, Platform* platform, void* sharedGLContex
}
void Engine::destroy(Engine* engine) {
FEngine::destroy(upcast(engine));
FEngine::destroy(downcast(engine));
}
#if UTILS_HAS_THREADING
@@ -68,7 +68,7 @@ Engine* Engine::getEngine(void* token) {
void Engine::destroy(Engine** pEngine) {
if (pEngine) {
Engine* engine = *pEngine;
FEngine::destroy(upcast(engine));
FEngine::destroy(downcast(engine));
*pEngine = nullptr;
}
}
@@ -78,151 +78,151 @@ void Engine::destroy(Engine** pEngine) {
// -----------------------------------------------------------------------------------------------
const Material* Engine::getDefaultMaterial() const noexcept {
return upcast(this)->getDefaultMaterial();
return downcast(this)->getDefaultMaterial();
}
Backend Engine::getBackend() const noexcept {
return upcast(this)->getBackend();
return downcast(this)->getBackend();
}
Platform* Engine::getPlatform() const noexcept {
return upcast(this)->getPlatform();
return downcast(this)->getPlatform();
}
Renderer* Engine::createRenderer() noexcept {
return upcast(this)->createRenderer();
return downcast(this)->createRenderer();
}
View* Engine::createView() noexcept {
return upcast(this)->createView();
return downcast(this)->createView();
}
Scene* Engine::createScene() noexcept {
return upcast(this)->createScene();
return downcast(this)->createScene();
}
Camera* Engine::createCamera(Entity entity) noexcept {
return upcast(this)->createCamera(entity);
return downcast(this)->createCamera(entity);
}
Camera* Engine::getCameraComponent(utils::Entity entity) noexcept {
return upcast(this)->getCameraComponent(entity);
return downcast(this)->getCameraComponent(entity);
}
void Engine::destroyCameraComponent(utils::Entity entity) noexcept {
upcast(this)->destroyCameraComponent(entity);
downcast(this)->destroyCameraComponent(entity);
}
Fence* Engine::createFence() noexcept {
return upcast(this)->createFence(FFence::Type::SOFT);
return downcast(this)->createFence(FFence::Type::SOFT);
}
SwapChain* Engine::createSwapChain(void* nativeWindow, uint64_t flags) noexcept {
return upcast(this)->createSwapChain(nativeWindow, flags);
return downcast(this)->createSwapChain(nativeWindow, flags);
}
SwapChain* Engine::createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept {
return upcast(this)->createSwapChain(width, height, flags);
return downcast(this)->createSwapChain(width, height, flags);
}
bool Engine::destroy(const BufferObject* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const VertexBuffer* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const IndexBuffer* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const SkinningBuffer* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const MorphTargetBuffer* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const IndirectLight* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const Material* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const MaterialInstance* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const Renderer* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const View* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const Scene* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const Skybox* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const ColorGrading* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const Stream* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const Texture* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const RenderTarget* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const Fence* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const SwapChain* p) {
return upcast(this)->destroy(upcast(p));
return downcast(this)->destroy(downcast(p));
}
void Engine::destroy(Entity e) {
upcast(this)->destroy(e);
downcast(this)->destroy(e);
}
void Engine::flushAndWait() {
upcast(this)->flushAndWait();
downcast(this)->flushAndWait();
}
void Engine::flush() {
upcast(this)->flush();
downcast(this)->flush();
}
utils::EntityManager& Engine::getEntityManager() noexcept {
return upcast(this)->getEntityManager();
return downcast(this)->getEntityManager();
}
RenderableManager& Engine::getRenderableManager() noexcept {
return upcast(this)->getRenderableManager();
return downcast(this)->getRenderableManager();
}
LightManager& Engine::getLightManager() noexcept {
return upcast(this)->getLightManager();
return downcast(this)->getLightManager();
}
TransformManager& Engine::getTransformManager() noexcept {
return upcast(this)->getTransformManager();
return downcast(this)->getTransformManager();
}
void Engine::enableAccurateTranslations() noexcept {
@@ -230,47 +230,53 @@ void Engine::enableAccurateTranslations() noexcept {
}
void* Engine::streamAlloc(size_t size, size_t alignment) noexcept {
return upcast(this)->streamAlloc(size, alignment);
return downcast(this)->streamAlloc(size, alignment);
}
// The external-facing execute does a flush, and is meant only for single-threaded environments.
// It also discards the boolean return value, which would otherwise indicate a thread exit.
void Engine::execute() {
ASSERT_PRECONDITION(!UTILS_HAS_THREADING, "Execute is meant for single-threaded platforms.");
upcast(this)->flush();
upcast(this)->execute();
downcast(this)->flush();
downcast(this)->execute();
}
utils::JobSystem& Engine::getJobSystem() noexcept {
return upcast(this)->getJobSystem();
return downcast(this)->getJobSystem();
}
DebugRegistry& Engine::getDebugRegistry() noexcept {
return upcast(this)->getDebugRegistry();
return downcast(this)->getDebugRegistry();
}
void Engine::pumpMessageQueues() {
upcast(this)->pumpMessageQueues();
downcast(this)->pumpMessageQueues();
}
void Engine::setAutomaticInstancingEnabled(bool enable) noexcept {
upcast(this)->setAutomaticInstancingEnabled(enable);
downcast(this)->setAutomaticInstancingEnabled(enable);
}
bool Engine::isAutomaticInstancingEnabled() const noexcept {
return upcast(this)->isAutomaticInstancingEnabled();
return downcast(this)->isAutomaticInstancingEnabled();
}
FeatureLevel Engine::getSupportedFeatureLevel() const noexcept {
return upcast(this)->getSupportedFeatureLevel();
return downcast(this)->getSupportedFeatureLevel();
}
FeatureLevel Engine::setActiveFeatureLevel(FeatureLevel featureLevel) {
return upcast(this)->setActiveFeatureLevel(featureLevel);
return downcast(this)->setActiveFeatureLevel(featureLevel);
}
FeatureLevel Engine::getActiveFeatureLevel() const noexcept {
return upcast(this)->getActiveFeatureLevel();
return downcast(this)->getActiveFeatureLevel();
}
#if defined(__EMSCRIPTEN__)
void Engine::resetBackendState() noexcept {
downcast(this)->resetBackendState();
}
#endif
} // namespace filament
+4 -4
View File
@@ -24,7 +24,7 @@ namespace filament {
namespace Exposure {
float ev100(const Camera& c) noexcept {
const FCamera& camera = upcast(c);
const FCamera& camera = downcast(c);
return ev100(camera.getAperture(), camera.getShutterSpeed(), camera.getSensitivity());
}
@@ -79,7 +79,7 @@ float ev100FromIlluminance(float illuminance) noexcept {
}
float exposure(const Camera& c) noexcept {
const FCamera& camera = upcast(c);
const FCamera& camera = downcast(c);
return exposure(camera.getAperture(), camera.getShutterSpeed(), camera.getSensitivity());
}
@@ -136,7 +136,7 @@ float exposure(float ev100) noexcept {
}
float luminance(const Camera& c) noexcept {
const FCamera& camera = upcast(c);
const FCamera& camera = downcast(c);
return luminance(camera.getAperture(), camera.getShutterSpeed(), camera.getSensitivity());
}
@@ -168,7 +168,7 @@ float luminance(float ev100) noexcept {
}
float illuminance(const Camera& c) noexcept {
const FCamera& camera = upcast(c);
const FCamera& camera = downcast(c);
return illuminance(camera.getAperture(), camera.getShutterSpeed(), camera.getSensitivity());
}
+2 -2
View File
@@ -21,11 +21,11 @@ namespace filament {
using namespace backend;
FenceStatus Fence::waitAndDestroy(Fence* fence, Mode mode) {
return FFence::waitAndDestroy(upcast(fence), mode);
return FFence::waitAndDestroy(downcast(fence), mode);
}
FenceStatus Fence::wait(Mode mode, uint64_t timeout) {
return upcast(this)->wait(mode, timeout);
return downcast(this)->wait(mode, timeout);
}
} // namespace filament
+2 -2
View File
@@ -22,11 +22,11 @@ namespace filament {
void IndexBuffer::setBuffer(Engine& engine,
IndexBuffer::BufferDescriptor&& buffer, uint32_t byteOffset) {
upcast(this)->setBuffer(upcast(engine), std::move(buffer), byteOffset);
downcast(this)->setBuffer(downcast(engine), std::move(buffer), byteOffset);
}
size_t IndexBuffer::getIndexCount() const noexcept {
return upcast(this)->getIndexCount();
return downcast(this)->getIndexCount();
}
} // namespace filament
+8 -8
View File
@@ -23,35 +23,35 @@ namespace filament {
using namespace math;
void IndirectLight::setIntensity(float intensity) noexcept {
upcast(this)->setIntensity(intensity);
downcast(this)->setIntensity(intensity);
}
float IndirectLight::getIntensity() const noexcept {
return upcast(this)->getIntensity();
return downcast(this)->getIntensity();
}
void IndirectLight::setRotation(mat3f const& rotation) noexcept {
upcast(this)->setRotation(rotation);
downcast(this)->setRotation(rotation);
}
const mat3f& IndirectLight::getRotation() const noexcept {
return upcast(this)->getRotation();
return downcast(this)->getRotation();
}
Texture const* IndirectLight::getReflectionsTexture() const noexcept {
return upcast(this)->getReflectionsTexture();
return downcast(this)->getReflectionsTexture();
}
Texture const* IndirectLight::getIrradianceTexture() const noexcept {
return upcast(this)->getIrradianceTexture();
return downcast(this)->getIrradianceTexture();
}
float3 IndirectLight::getDirectionEstimate() const noexcept {
return upcast(this)->getDirectionEstimate();
return downcast(this)->getDirectionEstimate();
}
float4 IndirectLight::getColorEstimate(float3 direction) const noexcept {
return upcast(this)->getColorEstimate(direction);
return downcast(this)->getColorEstimate(direction);
}
float3 IndirectLight::getDirectionEstimate(const float3* sh) noexcept {
+32 -32
View File
@@ -23,132 +23,132 @@ namespace filament {
using namespace math;
size_t LightManager::getComponentCount() const noexcept {
return upcast(this)->getComponentCount();
return downcast(this)->getComponentCount();
}
utils::Entity const* LightManager::getEntities() const noexcept {
return upcast(this)->getEntities();
return downcast(this)->getEntities();
}
bool LightManager::hasComponent(Entity e) const noexcept {
return upcast(this)->hasComponent(e);
return downcast(this)->hasComponent(e);
}
LightManager::Instance LightManager::getInstance(Entity e) const noexcept {
return upcast(this)->getInstance(e);
return downcast(this)->getInstance(e);
}
void LightManager::destroy(Entity e) noexcept {
return upcast(this)->destroy(e);
return downcast(this)->destroy(e);
}
void LightManager::setLightChannel(Instance i, unsigned int channel, bool enable) noexcept {
upcast(this)->setLightChannel(i, channel, enable);
downcast(this)->setLightChannel(i, channel, enable);
}
bool LightManager::getLightChannel(LightManager::Instance i, unsigned int channel) const noexcept {
return upcast(this)->getLightChannel(i, channel);
return downcast(this)->getLightChannel(i, channel);
}
void LightManager::setPosition(Instance i, const float3& position) noexcept {
upcast(this)->setLocalPosition(i, position);
downcast(this)->setLocalPosition(i, position);
}
const float3& LightManager::getPosition(Instance i) const noexcept {
return upcast(this)->getLocalPosition(i);
return downcast(this)->getLocalPosition(i);
}
void LightManager::setDirection(Instance i, const float3& direction) noexcept {
upcast(this)->setLocalDirection(i, direction);
downcast(this)->setLocalDirection(i, direction);
}
const float3& LightManager::getDirection(Instance i) const noexcept {
return upcast(this)->getLocalDirection(i);
return downcast(this)->getLocalDirection(i);
}
void LightManager::setColor(Instance i, const LinearColor& color) noexcept {
upcast(this)->setColor(i, color);
downcast(this)->setColor(i, color);
}
const float3& LightManager::getColor(Instance i) const noexcept {
return upcast(this)->getColor(i);
return downcast(this)->getColor(i);
}
void LightManager::setIntensity(Instance i, float intensity) noexcept {
upcast(this)->setIntensity(i, intensity, FLightManager::IntensityUnit::LUMEN_LUX);
downcast(this)->setIntensity(i, intensity, FLightManager::IntensityUnit::LUMEN_LUX);
}
void LightManager::setIntensityCandela(Instance i, float intensity) noexcept {
upcast(this)->setIntensity(i, intensity, FLightManager::IntensityUnit::CANDELA);
downcast(this)->setIntensity(i, intensity, FLightManager::IntensityUnit::CANDELA);
}
float LightManager::getIntensity(Instance i) const noexcept {
return upcast(this)->getIntensity(i);
return downcast(this)->getIntensity(i);
}
void LightManager::setFalloff(Instance i, float radius) noexcept {
upcast(this)->setFalloff(i, radius);
downcast(this)->setFalloff(i, radius);
}
float LightManager::getFalloff(Instance i) const noexcept {
return upcast(this)->getFalloff(i);
return downcast(this)->getFalloff(i);
}
void LightManager::setSpotLightCone(Instance i, float inner, float outer) noexcept {
upcast(this)->setSpotLightCone(i, inner, outer);
downcast(this)->setSpotLightCone(i, inner, outer);
}
float LightManager::getSpotLightOuterCone(Instance i) const noexcept {
return upcast(this)->getSpotParams(i).outerClamped;
return downcast(this)->getSpotParams(i).outerClamped;
}
float LightManager::getSpotLightInnerCone(Instance i) const noexcept {
return upcast(this)->getSpotLightInnerCone(i);
return downcast(this)->getSpotLightInnerCone(i);
}
void LightManager::setSunAngularRadius(Instance i, float angularRadius) noexcept {
upcast(this)->setSunAngularRadius(i, angularRadius);
downcast(this)->setSunAngularRadius(i, angularRadius);
}
float LightManager::getSunAngularRadius(Instance i) const noexcept {
float radius = upcast(this)->getSunAngularRadius(i);
float radius = downcast(this)->getSunAngularRadius(i);
return radius * f::RAD_TO_DEG;
}
void LightManager::setSunHaloSize(Instance i, float haloSize) noexcept {
upcast(this)->setSunHaloSize(i, haloSize);
downcast(this)->setSunHaloSize(i, haloSize);
}
float LightManager::getSunHaloSize(Instance i) const noexcept {
return upcast(this)->getSunHaloSize(i);
return downcast(this)->getSunHaloSize(i);
}
void LightManager::setSunHaloFalloff(Instance i, float haloFalloff) noexcept {
upcast(this)->setSunHaloFalloff(i, haloFalloff);
downcast(this)->setSunHaloFalloff(i, haloFalloff);
}
float LightManager::getSunHaloFalloff(Instance i) const noexcept {
return upcast(this)->getSunHaloFalloff(i);
return downcast(this)->getSunHaloFalloff(i);
}
LightManager::Type LightManager::getType(LightManager::Instance i) const noexcept {
return upcast(this)->getType(i);
return downcast(this)->getType(i);
}
const LightManager::ShadowOptions& LightManager::getShadowOptions(Instance i) const noexcept {
return upcast(this)->getShadowOptions(i);
return downcast(this)->getShadowOptions(i);
}
void LightManager::setShadowOptions(Instance i, ShadowOptions const& options) noexcept {
upcast(this)->setShadowOptions(i, options);
downcast(this)->setShadowOptions(i, options);
}
bool LightManager::isShadowCaster(Instance i) const noexcept {
return upcast(this)->isShadowCaster(i);
return downcast(this)->isShadowCaster(i);
}
void LightManager::setShadowCaster(Instance i, bool castShadows) noexcept {
upcast(this)->setShadowCaster(i, castShadows);
downcast(this)->setShadowCaster(i, castShadows);
}
} // namespace filament
+28 -28
View File
@@ -21,115 +21,115 @@ namespace filament {
using namespace backend;
MaterialInstance* Material::createInstance(const char* name) const noexcept {
return upcast(this)->createInstance(name);
return downcast(this)->createInstance(name);
}
const char* Material::getName() const noexcept {
return upcast(this)->getName().c_str();
return downcast(this)->getName().c_str();
}
Shading Material::getShading() const noexcept {
return upcast(this)->getShading();
return downcast(this)->getShading();
}
Interpolation Material::getInterpolation() const noexcept {
return upcast(this)->getInterpolation();
return downcast(this)->getInterpolation();
}
BlendingMode Material::getBlendingMode() const noexcept {
return upcast(this)->getBlendingMode();
return downcast(this)->getBlendingMode();
}
VertexDomain Material::getVertexDomain() const noexcept {
return upcast(this)->getVertexDomain();
return downcast(this)->getVertexDomain();
}
MaterialDomain Material::getMaterialDomain() const noexcept {
return upcast(this)->getMaterialDomain();
return downcast(this)->getMaterialDomain();
}
CullingMode Material::getCullingMode() const noexcept {
return upcast(this)->getCullingMode();
return downcast(this)->getCullingMode();
}
TransparencyMode Material::getTransparencyMode() const noexcept {
return upcast(this)->getTransparencyMode();
return downcast(this)->getTransparencyMode();
}
bool Material::isColorWriteEnabled() const noexcept {
return upcast(this)->isColorWriteEnabled();
return downcast(this)->isColorWriteEnabled();
}
bool Material::isDepthWriteEnabled() const noexcept {
return upcast(this)->isDepthWriteEnabled();
return downcast(this)->isDepthWriteEnabled();
}
bool Material::isDepthCullingEnabled() const noexcept {
return upcast(this)->isDepthCullingEnabled();
return downcast(this)->isDepthCullingEnabled();
}
bool Material::isDoubleSided() const noexcept {
return upcast(this)->isDoubleSided();
return downcast(this)->isDoubleSided();
}
float Material::getMaskThreshold() const noexcept {
return upcast(this)->getMaskThreshold();
return downcast(this)->getMaskThreshold();
}
bool Material::hasShadowMultiplier() const noexcept {
return upcast(this)->hasShadowMultiplier();
return downcast(this)->hasShadowMultiplier();
}
bool Material::hasSpecularAntiAliasing() const noexcept {
return upcast(this)->hasSpecularAntiAliasing();
return downcast(this)->hasSpecularAntiAliasing();
}
float Material::getSpecularAntiAliasingVariance() const noexcept {
return upcast(this)->getSpecularAntiAliasingVariance();
return downcast(this)->getSpecularAntiAliasingVariance();
}
float Material::getSpecularAntiAliasingThreshold() const noexcept {
return upcast(this)->getSpecularAntiAliasingThreshold();
return downcast(this)->getSpecularAntiAliasingThreshold();
}
size_t Material::getParameterCount() const noexcept {
return upcast(this)->getParameterCount();
return downcast(this)->getParameterCount();
}
size_t Material::getParameters(ParameterInfo* parameters, size_t count) const noexcept {
return upcast(this)->getParameters(parameters, count);
return downcast(this)->getParameters(parameters, count);
}
AttributeBitset Material::getRequiredAttributes() const noexcept {
return upcast(this)->getRequiredAttributes();
return downcast(this)->getRequiredAttributes();
}
RefractionMode Material::getRefractionMode() const noexcept {
return upcast(this)->getRefractionMode();
return downcast(this)->getRefractionMode();
}
RefractionType Material::getRefractionType() const noexcept {
return upcast(this)->getRefractionType();
return downcast(this)->getRefractionType();
}
ReflectionMode Material::getReflectionMode() const noexcept {
return upcast(this)->getReflectionMode();
return downcast(this)->getReflectionMode();
}
bool Material::hasParameter(const char* name) const noexcept {
return upcast(this)->hasParameter(name);
return downcast(this)->hasParameter(name);
}
bool Material::isSampler(const char* name) const noexcept {
return upcast(this)->isSampler(name);
return downcast(this)->isSampler(name);
}
MaterialInstance* Material::getDefaultInstance() noexcept {
return upcast(this)->getDefaultInstance();
return downcast(this)->getDefaultInstance();
}
MaterialInstance const* Material::getDefaultInstance() const noexcept {
return upcast(this)->getDefaultInstance();
return downcast(this)->getDefaultInstance();
}
} // namespace filament
+71 -31
View File
@@ -30,7 +30,7 @@ using namespace backend;
template<size_t Size>
UTILS_NOINLINE
void FMaterialInstance::setParameterUntypedImpl(std::string_view name, const void* value) {
ssize_t offset = mMaterial->getUniformInterfaceBlock().getUniformOffset(name, 0);
ssize_t offset = mMaterial->getUniformInterfaceBlock().getFieldOffset(name, 0);
if (UTILS_LIKELY(offset >= 0)) {
mUniforms.setUniformUntyped<Size>(size_t(offset), value); // handles specialization for mat3f
}
@@ -49,7 +49,7 @@ inline void FMaterialInstance::setParameterImpl(std::string_view name, T const&
// specialization for mat3f
template<>
inline void FMaterialInstance::setParameterImpl(std::string_view name, mat3f const& value) {
ssize_t offset = mMaterial->getUniformInterfaceBlock().getUniformOffset(name, 0);
ssize_t offset = mMaterial->getUniformInterfaceBlock().getFieldOffset(name, 0);
if (UTILS_LIKELY(offset >= 0)) {
mUniforms.setUniform(size_t(offset), value);
}
@@ -61,7 +61,7 @@ template<size_t Size>
UTILS_NOINLINE
void FMaterialInstance::setParameterUntypedImpl(std::string_view name,
const void* value, size_t count) {
ssize_t offset = mMaterial->getUniformInterfaceBlock().getUniformOffset(name, 0);
ssize_t offset = mMaterial->getUniformInterfaceBlock().getFieldOffset(name, 0);
if (UTILS_LIKELY(offset >= 0)) {
mUniforms.setUniformArrayUntyped<Size>(size_t(offset), value, count);
}
@@ -79,7 +79,7 @@ inline void FMaterialInstance::setParameterImpl(std::string_view name,
template<typename T, typename>
void MaterialInstance::setParameter(const char* name, size_t nameLength, T const& value) {
upcast(this)->setParameterImpl({ name, nameLength }, value);
downcast(this)->setParameterImpl({ name, nameLength }, value);
}
template<>
@@ -126,7 +126,7 @@ template UTILS_PUBLIC void MaterialInstance::setParameter<mat4f> (const char*
template <typename T, typename>
void MaterialInstance::setParameter(const char* name, size_t nameLength, const T* value, size_t count) {
upcast(this)->setParameterImpl({ name, nameLength }, value, count);
downcast(this)->setParameterImpl({ name, nameLength }, value, count);
}
template<>
@@ -184,112 +184,152 @@ template UTILS_PUBLIC void MaterialInstance::setParameter<mat4f> (const char*
// ------------------------------------------------------------------------------------------------
Material const* MaterialInstance::getMaterial() const noexcept {
return upcast(this)->getMaterial();
return downcast(this)->getMaterial();
}
const char* MaterialInstance::getName() const noexcept {
return upcast(this)->getName();
return downcast(this)->getName();
}
void MaterialInstance::setParameter(const char* name, size_t nameLength, Texture const* texture,
TextureSampler const& sampler) {
return upcast(this)->setParameterImpl({ name, nameLength }, upcast(texture), sampler);
return downcast(this)->setParameterImpl({ name, nameLength }, downcast(texture), sampler);
}
void MaterialInstance::setParameter(
const char* name, size_t nameLength, RgbType type, float3 color) {
upcast(this)->setParameterImpl<float3>({ name, nameLength }, Color::toLinear(type, color));
downcast(this)->setParameterImpl<float3>({ name, nameLength }, Color::toLinear(type, color));
}
void MaterialInstance::setParameter(
const char* name, size_t nameLength, RgbaType type, float4 color) {
upcast(this)->setParameterImpl<float4>({ name, nameLength }, Color::toLinear(type, color));
downcast(this)->setParameterImpl<float4>({ name, nameLength }, Color::toLinear(type, color));
}
void MaterialInstance::setScissor(
uint32_t left, uint32_t bottom, uint32_t width, uint32_t height) noexcept {
upcast(this)->setScissor(left, bottom, width, height);
downcast(this)->setScissor(left, bottom, width, height);
}
void MaterialInstance::unsetScissor() noexcept {
upcast(this)->unsetScissor();
downcast(this)->unsetScissor();
}
void MaterialInstance::setPolygonOffset(float scale, float constant) noexcept {
upcast(this)->setPolygonOffset(scale, constant);
downcast(this)->setPolygonOffset(scale, constant);
}
void MaterialInstance::setMaskThreshold(float threshold) noexcept {
upcast(this)->setMaskThreshold(threshold);
downcast(this)->setMaskThreshold(threshold);
}
void MaterialInstance::setSpecularAntiAliasingVariance(float variance) noexcept {
upcast(this)->setSpecularAntiAliasingVariance(variance);
downcast(this)->setSpecularAntiAliasingVariance(variance);
}
void MaterialInstance::setSpecularAntiAliasingThreshold(float threshold) noexcept {
upcast(this)->setSpecularAntiAliasingThreshold(threshold);
downcast(this)->setSpecularAntiAliasingThreshold(threshold);
}
void MaterialInstance::setDoubleSided(bool doubleSided) noexcept {
upcast(this)->setDoubleSided(doubleSided);
downcast(this)->setDoubleSided(doubleSided);
}
void MaterialInstance::setTransparencyMode(TransparencyMode mode) noexcept {
upcast(this)->setTransparencyMode(mode);
downcast(this)->setTransparencyMode(mode);
}
void MaterialInstance::setCullingMode(CullingMode culling) noexcept {
upcast(this)->setCullingMode(culling);
downcast(this)->setCullingMode(culling);
}
void MaterialInstance::setColorWrite(bool enable) noexcept {
upcast(this)->setColorWrite(enable);
downcast(this)->setColorWrite(enable);
}
void MaterialInstance::setDepthWrite(bool enable) noexcept {
upcast(this)->setDepthWrite(enable);
downcast(this)->setDepthWrite(enable);
}
void MaterialInstance::setDepthCulling(bool enable) noexcept {
upcast(this)->setDepthCulling(enable);
downcast(this)->setDepthCulling(enable);
}
void MaterialInstance::setStencilWrite(bool enable) noexcept {
upcast(this)->setStencilWrite(enable);
downcast(this)->setStencilWrite(enable);
}
void MaterialInstance::setStencilCompareFunction(StencilCompareFunc func, StencilFace face) noexcept {
upcast(this)->setStencilCompareFunction(func, face);
downcast(this)->setStencilCompareFunction(func, face);
}
void MaterialInstance::setStencilOpStencilFail(StencilOperation op, StencilFace face) noexcept {
upcast(this)->setStencilOpStencilFail(op, face);
downcast(this)->setStencilOpStencilFail(op, face);
}
void MaterialInstance::setStencilOpDepthFail(StencilOperation op, StencilFace face) noexcept {
upcast(this)->setStencilOpDepthFail(op, face);
downcast(this)->setStencilOpDepthFail(op, face);
}
void MaterialInstance::setStencilOpDepthStencilPass(StencilOperation op, StencilFace face) noexcept {
upcast(this)->setStencilOpDepthStencilPass(op, face);
downcast(this)->setStencilOpDepthStencilPass(op, face);
}
void MaterialInstance::setStencilReferenceValue(uint8_t value, StencilFace face) noexcept {
upcast(this)->setStencilReferenceValue(value, face);
downcast(this)->setStencilReferenceValue(value, face);
}
void MaterialInstance::setStencilReadMask(uint8_t readMask, StencilFace face) noexcept {
upcast(this)->setStencilReadMask(readMask, face);
downcast(this)->setStencilReadMask(readMask, face);
}
void MaterialInstance::setStencilWriteMask(uint8_t writeMask, StencilFace face) noexcept {
upcast(this)->setStencilWriteMask(writeMask, face);
downcast(this)->setStencilWriteMask(writeMask, face);
}
MaterialInstance* MaterialInstance::duplicate(MaterialInstance const* other, const char* name) noexcept {
return FMaterialInstance::duplicate(upcast(other), name);
return FMaterialInstance::duplicate(downcast(other), name);
}
float MaterialInstance::getMaskThreshold() const noexcept {
return downcast(this)->getMaskThreshold();
}
float MaterialInstance::getSpecularAntiAliasingVariance() const noexcept {
return downcast(this)->getSpecularAntiAliasingVariance();
}
float MaterialInstance::getSpecularAntiAliasingThreshold() const noexcept {
return downcast(this)->getSpecularAntiAliasingThreshold();
}
bool MaterialInstance::isDoubleSided() const noexcept {
return downcast(this)->isDoubleSided();
}
TransparencyMode MaterialInstance::getTransparencyMode() const noexcept {
return downcast(this)->getTransparencyMode();
}
CullingMode MaterialInstance::getCullingMode() const noexcept {
return downcast(this)->getCullingMode();
}
bool MaterialInstance::isColorWriteEnabled() const noexcept {
return downcast(this)->isColorWriteEnabled();
}
bool MaterialInstance::isDepthWriteEnabled() const noexcept {
return downcast(this)->isDepthWriteEnabled();
}
bool MaterialInstance::isStencilWriteEnabled() const noexcept {
return downcast(this)->isStencilWriteEnabled();
}
bool MaterialInstance::isDepthCullingEnabled() const noexcept {
return downcast(this)->isDepthCullingEnabled();
}
} // namespace filament
+18 -24
View File
@@ -26,7 +26,7 @@
#include <private/filament/SamplerBindingsInfo.h>
#include <private/filament/SamplerInterfaceBlock.h>
#include <private/filament/UniformInterfaceBlock.h>
#include <private/filament/BufferInterfaceBlock.h>
#include <private/filament/SubpassInfo.h>
#include <private/filament/Variant.h>
@@ -124,33 +124,29 @@ bool MaterialParser::getFeatureLevel(uint8_t* value) const noexcept {
}
bool MaterialParser::getName(utils::CString* cstring) const noexcept {
ChunkType type = ChunkType::MaterialName;
const uint8_t* start = mImpl.mChunkContainer.getChunkStart(type);
const uint8_t* end = mImpl.mChunkContainer.getChunkEnd(type);
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialName);
if (start == end) return false;
Unflattener unflattener(start, end);
return unflattener.read(cstring);
}
bool MaterialParser::getUIB(UniformInterfaceBlock* uib) const noexcept {
auto type = MaterialUib;
const uint8_t* start = mImpl.mChunkContainer.getChunkStart(type);
const uint8_t* end = mImpl.mChunkContainer.getChunkEnd(type);
bool MaterialParser::getUIB(BufferInterfaceBlock* uib) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialUib);
if (start == end) return false;
Unflattener unflattener(start, end);
return ChunkUniformInterfaceBlock::unflatten(unflattener, uib);
}
bool MaterialParser::getSIB(SamplerInterfaceBlock* sib) const noexcept {
auto type = MaterialSib;
const uint8_t* start = mImpl.mChunkContainer.getChunkStart(type);
const uint8_t* end = mImpl.mChunkContainer.getChunkEnd(type);
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialSib);
if (start == end) return false;
Unflattener unflattener(start, end);
return ChunkSamplerInterfaceBlock::unflatten(unflattener, sib);
}
bool MaterialParser::getSubpasses(SubpassInfo* subpass) const noexcept {
auto type = MaterialSubpass;
const uint8_t* start = mImpl.mChunkContainer.getChunkStart(type);
const uint8_t* end = mImpl.mChunkContainer.getChunkEnd(type);
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialSubpass);
if (start == end) return false;
Unflattener unflattener(start, end);
return ChunkSubpassInterfaceBlock::unflatten(unflattener, subpass);
}
@@ -165,9 +161,8 @@ bool MaterialParser::getMaterialProperties(uint64_t* value) const noexcept {
bool MaterialParser::getUniformBlockBindings(
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>>* value) const noexcept {
auto type = MaterialUniformBindings;
const uint8_t* start = mImpl.mChunkContainer.getChunkStart(type);
const uint8_t* end = mImpl.mChunkContainer.getChunkEnd(type);
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialUniformBindings);
if (start == end) return false;
Unflattener unflattener(start, end);
return ChunkUniformBlockBindings::unflatten(unflattener, value);
}
@@ -175,9 +170,8 @@ bool MaterialParser::getUniformBlockBindings(
bool MaterialParser::getSamplerBlockBindings(
SamplerGroupBindingInfoList* pSamplerGroupInfoList,
SamplerBindingToNameMap* pSamplerBindingToNameMap) const noexcept {
auto type = MaterialSamplerBindings;
const uint8_t* start = mImpl.mChunkContainer.getChunkStart(type);
const uint8_t* end = mImpl.mChunkContainer.getChunkEnd(type);
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialSamplerBindings);
if (start == end) return false;
Unflattener unflattener(start, end);
return ChunkSamplerBlockBindings::unflatten(unflattener,
pSamplerGroupInfoList, pSamplerBindingToNameMap);
@@ -316,9 +310,9 @@ bool MaterialParser::getShader(ShaderContent& shader,
bool ChunkUniformInterfaceBlock::unflatten(Unflattener& unflattener,
filament::UniformInterfaceBlock* uib) {
filament::BufferInterfaceBlock* uib) {
UniformInterfaceBlock::Builder builder = UniformInterfaceBlock::Builder();
BufferInterfaceBlock::Builder builder = BufferInterfaceBlock::Builder();
CString name;
if (!unflattener.read(&name)) {
@@ -358,8 +352,8 @@ bool ChunkUniformInterfaceBlock::unflatten(Unflattener& unflattener,
// a size of 1 means not an array
builder.add({{{ fieldName.data(), fieldName.size() },
uint32_t(fieldSize == 1 ? 0 : fieldSize),
UniformInterfaceBlock::Type(fieldType),
UniformInterfaceBlock::Precision(fieldPrecision) }});
BufferInterfaceBlock::Type(fieldType),
BufferInterfaceBlock::Precision(fieldPrecision) }});
}
*uib = builder.build();
+3 -3
View File
@@ -40,7 +40,7 @@ class Unflattener;
namespace filament {
class UniformInterfaceBlock;
class BufferInterfaceBlock;
class SamplerInterfaceBlock;
struct SubpassInfo;
@@ -63,7 +63,7 @@ public:
bool getMaterialVersion(uint32_t* value) const noexcept;
bool getFeatureLevel(uint8_t* value) const noexcept;
bool getName(utils::CString*) const noexcept;
bool getUIB(UniformInterfaceBlock* uib) const noexcept;
bool getUIB(BufferInterfaceBlock* uib) const noexcept;
bool getSIB(SamplerInterfaceBlock* sib) const noexcept;
bool getSubpasses(SubpassInfo* subpass) const noexcept;
bool getShaderModels(uint32_t* value) const noexcept;
@@ -144,7 +144,7 @@ private:
};
struct ChunkUniformInterfaceBlock {
static bool unflatten(filaflat::Unflattener& unflattener, UniformInterfaceBlock* uib);
static bool unflatten(filaflat::Unflattener& unflattener, BufferInterfaceBlock* uib);
};
struct ChunkSamplerInterfaceBlock {
+5 -5
View File
@@ -22,25 +22,25 @@ namespace filament {
void MorphTargetBuffer::setPositionsAt(Engine& engine, size_t targetIndex,
math::float3 const* positions, size_t count, size_t offset) {
upcast(this)->setPositionsAt(upcast(engine), targetIndex, positions, count, offset);
downcast(this)->setPositionsAt(downcast(engine), targetIndex, positions, count, offset);
}
void MorphTargetBuffer::setPositionsAt(Engine& engine, size_t targetIndex,
math::float4 const* positions, size_t count, size_t offset) {
upcast(this)->setPositionsAt(upcast(engine), targetIndex, positions, count, offset);
downcast(this)->setPositionsAt(downcast(engine), targetIndex, positions, count, offset);
}
void MorphTargetBuffer::setTangentsAt(Engine& engine, size_t targetIndex,
math::short4 const* tangents, size_t count, size_t offset) {
upcast(this)->setTangentsAt(upcast(engine), targetIndex, tangents, count, offset);
downcast(this)->setTangentsAt(downcast(engine), targetIndex, tangents, count, offset);
}
size_t MorphTargetBuffer::getVertexCount() const noexcept {
return upcast(this)->getVertexCount();
return downcast(this)->getVertexCount();
}
size_t MorphTargetBuffer::getCount() const noexcept {
return upcast(this)->getCount();
return downcast(this)->getCount();
}
} // namespace filament
+169
View File
@@ -0,0 +1,169 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "PerShadowMapUniforms.h"
#include "ShadowMapManager.h"
#include "details/Camera.h"
#include "details/Engine.h"
#include <private/filament/EngineEnums.h>
namespace filament {
using namespace backend;
using namespace math;
PerShadowMapUniforms::PerShadowMapUniforms(FEngine& engine) noexcept {
DriverApi& driver = engine.getDriverApi();
mUniformBufferHandle = driver.createBufferObject(sizeof(PerViewUib),
BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC);
}
void PerShadowMapUniforms::terminate(DriverApi& driver) {
driver.destroyBufferObject(mUniformBufferHandle);
}
PerViewUib& PerShadowMapUniforms::edit(Transaction const& transaction) noexcept {
assert_invariant(transaction.uniforms);
return *transaction.uniforms;
}
void PerShadowMapUniforms::prepareCamera(Transaction const& transaction,
FEngine& engine, const CameraInfo& camera) noexcept {
mat4f const& viewFromWorld = camera.view;
mat4f const& worldFromView = camera.model;
mat4f const& clipFromView = camera.projection;
const mat4f viewFromClip{ inverse((mat4)camera.projection) };
const mat4f clipFromWorld{ highPrecisionMultiply(clipFromView, viewFromWorld) };
const mat4f worldFromClip{ highPrecisionMultiply(worldFromView, viewFromClip) };
auto& s = edit(transaction);
s.viewFromWorldMatrix = viewFromWorld; // view
s.worldFromViewMatrix = worldFromView; // model
s.clipFromViewMatrix = clipFromView; // projection
s.viewFromClipMatrix = viewFromClip; // 1/projection
s.clipFromWorldMatrix = clipFromWorld; // projection * view
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
s.clipTransform = camera.clipTransfrom;
s.cameraPosition = float3{ camera.getPosition() };
s.worldOffset = camera.getWorldOffset();
s.cameraFar = camera.zf;
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);
s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn);
// with a clip-space of [-w, w] ==> z' = -z
// with a clip-space of [0, w] ==> z' = (w - z)/2
s.clipControl = engine.getDriverApi().getClipSpaceParams();
}
void PerShadowMapUniforms::prepareLodBias(Transaction const& transaction,
float bias) noexcept {
auto& s = edit(transaction);
s.lodBias = bias;
}
void PerShadowMapUniforms::prepareViewport(Transaction const& transaction,
const filament::Viewport& viewport,
uint32_t xoffset, uint32_t yoffset) noexcept {
const float w = float(viewport.width);
const float h = float(viewport.height);
auto& s = edit(transaction);
s.resolution = float4{ w, h, 1.0f / w, 1.0f / h };
s.origin = float2{ viewport.left, viewport.bottom };
s.offset = float2{ xoffset, yoffset };
}
void PerShadowMapUniforms::prepareTime(Transaction const& transaction,
FEngine& engine, math::float4 const& userTime) noexcept {
auto& s = edit(transaction);
const uint64_t oneSecondRemainder = engine.getEngineTime().count() % 1000000000;
const float fraction = float(double(oneSecondRemainder) / 1000000000.0);
s.time = fraction;
s.userTime = userTime;
}
void PerShadowMapUniforms::prepareDirectionalLight(Transaction const& transaction,
FEngine& engine,
float exposure,
float3 const& sceneSpaceDirection,
PerShadowMapUniforms::LightManagerInstance directionalLight) noexcept {
FLightManager& lcm = engine.getLightManager();
auto& s = edit(transaction);
const float3 l = -sceneSpaceDirection; // guaranteed normalized
if (directionalLight.isValid()) {
const float4 colorIntensity = {
lcm.getColor(directionalLight), lcm.getIntensity(directionalLight) * exposure };
s.lightDirection = l;
s.lightColorIntensity = colorIntensity;
s.lightChannels = lcm.getLightChannels(directionalLight);
const bool isSun = lcm.isSunLight(directionalLight);
// The last parameter must be < 0.0f for regular directional lights
float4 sun{ 0.0f, 0.0f, 0.0f, -1.0f };
if (UTILS_UNLIKELY(isSun && colorIntensity.w > 0.0f)) {
// currently we have only a single directional light, so it's probably likely that it's
// also the Sun. However, conceptually, most directional lights won't be sun lights.
float radius = lcm.getSunAngularRadius(directionalLight);
float haloSize = lcm.getSunHaloSize(directionalLight);
float haloFalloff = lcm.getSunHaloFalloff(directionalLight);
sun.x = std::cos(radius);
sun.y = std::sin(radius);
sun.z = 1.0f / (std::cos(radius * haloSize) - sun.x);
sun.w = haloFalloff;
}
s.sun = sun;
} else {
// Disable the sun if there's no directional light
s.sun = float4{ 0.0f, 0.0f, 0.0f, -1.0f };
}
}
void PerShadowMapUniforms::prepareShadowMapping(Transaction const& transaction,
bool highPrecision) noexcept {
auto& s = edit(transaction);
constexpr float low = 5.54f; // ~ std::log(std::numeric_limits<math::half>::max()) * 0.5f;
constexpr float high = 42.0f; // ~ std::log(std::numeric_limits<float>::max()) * 0.5f;
s.vsmExponent = highPrecision ? high : low;
}
PerShadowMapUniforms::Transaction PerShadowMapUniforms::open(backend::DriverApi& driver) noexcept {
Transaction transaction;
// TODO: use out-of-line buffer if too large
transaction.uniforms = (PerViewUib *)driver.allocate(sizeof(PerViewUib));
assert_invariant(transaction.uniforms);
return transaction;
}
void PerShadowMapUniforms::commit(Transaction& transaction,
backend::DriverApi& driver) noexcept {
driver.updateBufferObject(mUniformBufferHandle, {
transaction.uniforms, sizeof(PerViewUib) }, 0);
transaction.uniforms = nullptr;
}
void PerShadowMapUniforms::bind(backend::DriverApi& driver) noexcept {
driver.bindUniformBuffer(+UniformBindingPoints::PER_VIEW, mUniformBufferHandle);
}
} // namespace filament
+95
View File
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_PERSHADOWMAPUNIFORMS_H
#define TNT_FILAMENT_PERSHADOWMAPUNIFORMS_H
#include <filament/Viewport.h>
#include <private/filament/UibStructs.h>
#include <private/backend/SamplerGroup.h>
#include "TypedUniformBuffer.h"
#include <backend/Handle.h>
#include <utils/EntityInstance.h>
#include <random>
namespace filament {
struct CameraInfo;
class FEngine;
class LightManager;
/*
* PerShadowMapUniforms manages the UBO needed to generate our shadow maps. Internally it just
* holds onto a `PerViewUniform` UBO handle, but doesn't keep any shadow copy of it, instead it
* writes the data directly into the commandstream, for this reason partial update of the data
* is not possible.
*/
class PerShadowMapUniforms {
using LightManagerInstance = utils::EntityInstance<LightManager>;
public:
class Transaction {
friend PerShadowMapUniforms;
PerViewUib* uniforms = nullptr;
Transaction() = default; // disallow creation by the caller
};
explicit PerShadowMapUniforms(FEngine& engine) noexcept;
void terminate(backend::DriverApi& driver);
static void prepareCamera(Transaction const& transaction,
FEngine& engine, const CameraInfo& camera) noexcept;
static void prepareLodBias(Transaction const& transaction,
float bias) noexcept;
static void prepareViewport(Transaction const& transaction,
const filament::Viewport& viewport, uint32_t xoffset, uint32_t yoffset) noexcept;
static void prepareTime(Transaction const& transaction,
FEngine& engine, math::float4 const& userTime) noexcept;
static void prepareShadowMapping(Transaction const& transaction,
bool highPrecision) noexcept;
static void prepareDirectionalLight(Transaction const& transaction,
FEngine& engine, float exposure,
math::float3 const& sceneSpaceDirection, LightManagerInstance instance) noexcept;
static Transaction open(backend::DriverApi& driver) noexcept;
// update local data into GPU UBO
void commit(Transaction& transaction, backend::DriverApi& driver) noexcept;
// bind this UBO
void bind(backend::DriverApi& driver) noexcept;
private:
static PerViewUib& edit(Transaction const& transaction) noexcept;
backend::Handle<backend::HwBufferObject> mUniformBufferHandle;
};
} // namespace filament
#endif //TNT_FILAMENT_PERSHADOWMAPUNIFORMS_H
+26 -28
View File
@@ -34,15 +34,13 @@
#include <math/mat4.h>
namespace filament {
using namespace backend;
using namespace math;
PerViewUniforms::PerViewUniforms(FEngine& engine) noexcept
: mEngine(engine),
mSamplers(PerViewSib::SAMPLER_COUNT) {
: mSamplers(PerViewSib::SAMPLER_COUNT) {
DriverApi& driver = engine.getDriverApi();
mSamplerGroupHandle = driver.createSamplerGroup(mSamplers.getSize());
@@ -50,10 +48,6 @@ PerViewUniforms::PerViewUniforms(FEngine& engine) noexcept
mUniformBufferHandle = driver.createBufferObject(mUniforms.getSize(),
BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC);
// with a clip-space of [-w, w] ==> z' = -z
// with a clip-space of [0, w] ==> z' = (w - z)/2
mClipControl = driver.getClipSpaceParams();
if (engine.getDFG().isValid()) {
TextureSampler sampler(TextureSampler::MagFilter::LINEAR);
mSamplers.setSampler(PerViewSib::IBL_DFG_LUT,
@@ -66,7 +60,7 @@ void PerViewUniforms::terminate(DriverApi& driver) {
driver.destroySamplerGroup(mSamplerGroupHandle);
}
void PerViewUniforms::prepareCamera(const CameraInfo& camera) noexcept {
void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) noexcept {
mat4f const& viewFromWorld = camera.view;
mat4f const& worldFromView = camera.model;
mat4f const& clipFromView = camera.projection;
@@ -88,17 +82,15 @@ void PerViewUniforms::prepareCamera(const CameraInfo& camera) noexcept {
s.cameraFar = camera.zf;
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);
s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn);
s.clipControl = mClipControl;
// with a clip-space of [-w, w] ==> z' = -z
// with a clip-space of [0, w] ==> z' = (w - z)/2
s.clipControl = engine.getDriverApi().getClipSpaceParams();
}
void PerViewUniforms::prepareUpscaler(math::float2 scale,
DynamicResolutionOptions const& options) noexcept {
void PerViewUniforms::prepareLodBias(float bias) noexcept {
auto& s = mUniforms.edit();
if (options.quality >= QualityLevel::HIGH) {
s.lodBias = std::log2(std::min(scale.x, scale.y));
} else {
s.lodBias = 0.0f;
}
s.lodBias = bias;
}
void PerViewUniforms::prepareExposure(float ev100) noexcept {
@@ -118,17 +110,19 @@ void PerViewUniforms::prepareViewport(const filament::Viewport& viewport,
s.offset = float2{ xoffset, yoffset };
}
void PerViewUniforms::prepareTime(math::float4 const& userTime) noexcept {
void PerViewUniforms::prepareTime(FEngine& engine, math::float4 const& userTime) noexcept {
auto& s = mUniforms.edit();
const uint64_t oneSecondRemainder = mEngine.getEngineTime().count() % 1000000000;
const uint64_t oneSecondRemainder = engine.getEngineTime().count() % 1000000000;
const float fraction = float(double(oneSecondRemainder) / 1000000000.0);
s.time = fraction;
s.userTime = userTime;
}
void PerViewUniforms::prepareTemporalNoise(TemporalAntiAliasingOptions const& options) noexcept {
void PerViewUniforms::prepareTemporalNoise(FEngine& engine,
TemporalAntiAliasingOptions const& options) noexcept {
std::uniform_real_distribution<float> uniformDistribution{ 0.0f, 1.0f };
auto& s = mUniforms.edit();
const float temporalNoise = mUniformDistribution(mEngine.getRandomEngine());
const float temporalNoise = uniformDistribution(engine.getRandomEngine());
s.temporalNoise = options.enabled ? temporalNoise : 0.0f;
}
@@ -217,11 +211,11 @@ void PerViewUniforms::prepareStructure(Handle<HwTexture> structure) noexcept {
mSamplers.setSampler(PerViewSib::STRUCTURE, { structure, {}});
}
void PerViewUniforms::prepareDirectionalLight(
void PerViewUniforms::prepareDirectionalLight(FEngine& engine,
float exposure,
float3 const& sceneSpaceDirection,
PerViewUniforms::LightManagerInstance directionalLight) noexcept {
FLightManager& lcm = mEngine.getLightManager();
FLightManager& lcm = engine.getLightManager();
auto& s = mUniforms.edit();
const float3 l = -sceneSpaceDirection; // guaranteed normalized
@@ -255,9 +249,8 @@ void PerViewUniforms::prepareDirectionalLight(
}
}
void PerViewUniforms::prepareAmbientLight(FIndirectLight const& ibl,
void PerViewUniforms::prepareAmbientLight(FEngine& engine, FIndirectLight const& ibl,
float intensity, float exposure) noexcept {
auto& engine = mEngine;
auto& s = mUniforms.edit();
// Set up uniforms and sampler for the IBL, guaranteed to be non-null at this point.
@@ -288,9 +281,11 @@ void PerViewUniforms::prepareDynamicLights(Froxelizer& froxelizer) noexcept {
s.lightFarAttenuationParams = 0.5f * float2{ 10.0f, 10.0f / (f * f) };
}
void PerViewUniforms::prepareShadowMapping() noexcept {
void PerViewUniforms::prepareShadowMapping(bool highPrecision) noexcept {
auto& s = mUniforms.edit();
s.vsmExponent = 5.54f; // fp16: max 5.54f, fp32: max 42.0
constexpr float low = 5.54f; // ~ std::log(std::numeric_limits<math::half>::max()) * 0.5f;
constexpr float high = 42.0f; // ~ std::log(std::numeric_limits<float>::max()) * 0.5f;
s.vsmExponent = highPrecision ? high : low;
}
void PerViewUniforms::prepareShadowSampling(PerViewUib& uniforms,
@@ -302,11 +297,14 @@ void PerViewUniforms::prepareShadowSampling(PerViewUib& uniforms,
uniforms.ssContactShadowDistance = shadowMappingUniforms.ssContactShadowDistance;
uniforms.directionalShadows = shadowMappingUniforms.directionalShadows;
uniforms.cascades = shadowMappingUniforms.cascades;
uniforms.cascades |= uint32_t(shadowMappingUniforms.elvsm) << 31u;
}
void PerViewUniforms::prepareShadowVSM(Handle<HwTexture> texture,
ShadowMappingUniforms const& shadowMappingUniforms,
VsmShadowOptions const& options) noexcept {
constexpr float low = 5.54f; // ~ std::log(std::numeric_limits<math::half>::max()) * 0.5f;
constexpr float high = 42.0f; // ~ std::log(std::numeric_limits<float>::max()) * 0.5f;
SamplerMinFilter filterMin = SamplerMinFilter::LINEAR;
if (options.anisotropy > 0 || options.mipmapping) {
filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR;
@@ -318,8 +316,8 @@ void PerViewUniforms::prepareShadowVSM(Handle<HwTexture> texture,
.anisotropyLog2 = options.anisotropy,
}});
auto& s = mUniforms.edit();
s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_VSM;
s.vsmExponent = 5.54f; // fp16: max 5.54f, fp32: max 42.0
s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_EVSM;
s.vsmExponent = options.highPrecision ? high : low;
s.vsmDepthScale = options.minVarianceScale * 0.01f * s.vsmExponent;
s.vsmLightBleedReduction = options.lightBleedReduction;
PerViewUniforms::prepareShadowSampling(s, shadowMappingUniforms);
+17 -14
View File
@@ -48,23 +48,28 @@ class FIndirectLight;
class Froxelizer;
class LightManager;
/*
* PerViewUniforms manages the UBO and samplers needed to render the color passes. Internally it
* holds onto handles for the PER_VIEW UBO and SamplerGroup. This class maintains a shadow copy
* of the UBO/sampler data, so it is possible to partially update it between commits.
*/
class PerViewUniforms {
using LightManagerInstance = utils::EntityInstance<LightManager>;
using TextureHandle = backend::Handle<backend::HwTexture>;
static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_PCF = 0u;
static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_VSM = 1u;
static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_DPCF = 2u;
static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_PCSS = 3u;
static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_PCF = 0u;
static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_EVSM = 1u;
static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_DPCF = 2u;
static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_PCSS = 3u;
public:
explicit PerViewUniforms(FEngine& engine) noexcept;
void terminate(backend::DriverApi& driver);
void prepareCamera(const CameraInfo& camera) noexcept;
void prepareUpscaler(math::float2 scale, DynamicResolutionOptions const& options) noexcept;
void prepareCamera(FEngine& engine, const CameraInfo& camera) noexcept;
void prepareLodBias(float bias) noexcept;
/*
* @param viewport viewport (should be same as RenderPassParams::viewport)
@@ -75,8 +80,8 @@ public:
*/
void prepareViewport(const filament::Viewport& viewport, uint32_t xoffset, uint32_t yoffset) noexcept;
void prepareTime(math::float4 const& userTime) noexcept;
void prepareTemporalNoise(TemporalAntiAliasingOptions const& options) noexcept;
void prepareTime(FEngine& engine, math::float4 const& userTime) noexcept;
void prepareTemporalNoise(FEngine& engine, TemporalAntiAliasingOptions const& options) noexcept;
void prepareExposure(float ev100) noexcept;
void prepareFog(math::float3 const& cameraPosition, FogOptions const& options) noexcept;
void prepareStructure(TextureHandle structure) noexcept;
@@ -93,12 +98,13 @@ public:
math::mat4f const& uvFromViewMatrix,
ScreenSpaceReflectionsOptions const& ssrOptions) noexcept;
void prepareShadowMapping() noexcept;
void prepareShadowMapping(bool highPrecision) noexcept;
void prepareDirectionalLight(float exposure,
void prepareDirectionalLight(FEngine& engine, float exposure,
math::float3 const& sceneSpaceDirection, LightManagerInstance instance) noexcept;
void prepareAmbientLight(FIndirectLight const& ibl, float intensity, float exposure) noexcept;
void prepareAmbientLight(FEngine& engine,
FIndirectLight const& ibl, float intensity, float exposure) noexcept;
void prepareDynamicLights(Froxelizer& froxelizer) noexcept;
@@ -126,13 +132,10 @@ public:
void unbindSamplers() noexcept;
private:
FEngine& mEngine;
math::float2 mClipControl{};
TypedUniformBuffer<PerViewUib> mUniforms;
backend::SamplerGroup mSamplers;
backend::Handle<backend::HwBufferObject> mUniformBufferHandle;
backend::Handle<backend::HwSamplerGroup> mSamplerGroupHandle;
std::uniform_real_distribution<float> mUniformDistribution{ 0.0f, 1.0f };
static void prepareShadowSampling(PerViewUib& uniforms,
ShadowMappingUniforms const& shadowMappingUniforms) noexcept;
};
+93 -54
View File
@@ -127,7 +127,7 @@ void PostProcessManager::PostProcessMaterial::loadMaterial(FEngine& engine) cons
// TODO: After all materials using this class have been converted to the post-process material
// domain, load both OPAQUE and TRANSPARENT variants here.
mHasMaterial = true;
mMaterial = upcast(Material::Builder().package(mData, mSize).build(engine));
mMaterial = downcast(Material::Builder().package(mData, mSize).build(engine));
}
UTILS_NOINLINE
@@ -399,9 +399,9 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
auto out = resources.getRenderPassInfo();
renderPass.setRenderFlags(structureRenderFlags);
renderPass.setVariant(structureVariant);
renderPass.appendCommands(RenderPass::CommandTypeFlags::SSAO);
renderPass.sortCommands();
renderPass.execute(resources.getPassName(), out.target, out.params);
renderPass.appendCommands(mEngine, RenderPass::CommandTypeFlags::SSAO);
renderPass.sortCommands(mEngine);
renderPass.execute(mEngine, resources.getPassName(), out.target, out.params);
});
auto depth = structurePass->depth;
@@ -430,13 +430,13 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
},
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
auto in = resources.getTexture(data.depth);
auto& material = getPostProcessMaterial("mipmapDepth");
FMaterialInstance* const mi = material.getMaterialInstance(mEngine);
mi->setParameter("depth", in, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
// The first mip already exists, so we process n-1 lods
for (size_t level = 0; level < levelCount - 1; level++) {
auto out = resources.getRenderPassInfo(level);
driver.setMinMaxLevels(in, level, level);
auto& material = getPostProcessMaterial("mipmapDepth");
FMaterialInstance* const mi = material.getMaterialInstance(mEngine);
mi->setParameter("depth", in, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
mi->setParameter("level", uint32_t(level));
commitAndRender(out, material, driver);
}
@@ -544,9 +544,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::ssr(FrameGraph& fg,
// the SCREEN_SPACE ReflectionMode.
renderPass.setVariant(Variant{Variant::SPECIAL_SSR});
// generate all our drawing commands, except blended objects.
renderPass.appendCommands(RenderPass::CommandTypeFlags::SCREEN_SPACE_REFLECTIONS);
renderPass.sortCommands();
renderPass.execute(resources.getPassName(), out.target, out.params);
renderPass.appendCommands(mEngine, RenderPass::CommandTypeFlags::SCREEN_SPACE_REFLECTIONS);
renderPass.sortCommands(mEngine);
renderPass.execute(mEngine, resources.getPassName(), out.target, out.params);
});
return ssrPass->reflections;
@@ -990,8 +990,15 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::gaussianBlurPass(FrameGraph&
float x1 = float(i * 2);
float k0 = std::exp(-alpha * x0 * x0);
float k1 = std::exp(-alpha * x1 * x1);
// k * textureLod(..., o) with bilinear sampling is equivalent to:
// k * (s[0] * (1 - o) + s[1] * o)
// solve:
// k0 = k * (1 - o)
// k1 = k * o
float k = k0 + k1;
float o = k0 / k;
float o = k1 / k;
kernel[i].x = k;
kernel[i].y = o;
totalWeight += (k0 + k1) * 2.0f;
@@ -1039,7 +1046,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::gaussianBlurPass(FrameGraph&
[=](FrameGraphResources const& resources,
auto const& data, DriverApi& driver) {
// don't use auto for those, b/c the can't resolve them
// don't use auto for those, b/c the ide can't resolve them
using FGTD = FrameGraphTexture::Descriptor;
using FGTSD = FrameGraphTexture::SubResourceDescriptor;
@@ -1055,7 +1062,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::gaussianBlurPass(FrameGraph&
using namespace std::literals;
std::string_view materialName;
const bool is2dArray = inDesc.type == SamplerType::SAMPLER_2D_ARRAY;
switch (backend::getFormatSize(outDesc.format)) {
switch (backend::getFormatComponentCount(outDesc.format)) {
case 1: materialName = is2dArray ?
"separableGaussianBlur1L"sv : "separableGaussianBlur1"sv; break;
case 2: materialName = is2dArray ?
@@ -1919,45 +1926,64 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg
[=](FrameGraphResources const& resources,
auto const& data, DriverApi& driver) {
auto const& material = getPostProcessMaterial("bloomDownsample");
FMaterialInstance* mi = material.getMaterialInstance(mEngine);
const PipelineState pipeline(material.getPipelineState(mEngine));
auto hwIn = resources.getTexture(data.in);
auto hwOut = resources.getTexture(data.out);
auto hwStage = resources.getTexture(data.stage);
mi->use(driver);
mi->setParameter("source", hwIn, {
auto const& material = getPostProcessMaterial("bloomDownsample");
auto const* ma = material.getMaterial(mEngine);
FMaterialInstance* mis[] = {
ma->createInstance("bloomDownsample-ping"),
ma->createInstance("bloomDownsample-pong"),
ma->createInstance("bloomDownsample-first"),
};
mis[0]->setParameter("source", hwOut, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR /* level is always 0 */
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
mi->setParameter("level", 0.0f);
mi->setParameter("threshold", inoutBloomOptions.threshold ? 1.0f : 0.0f);
mi->setParameter("invHighlight",
std::isinf(inoutBloomOptions.highlight) ? 0.0f : 1.0f
/ inoutBloomOptions.highlight);
for (size_t i = 0; i < inoutBloomOptions.levels; i++) {
const bool parity = (i % 2) == 0;
auto hwDstRT = resources.getRenderPassInfo(
parity ? data.outRT[i] : data.stageRT[i]);
mis[1]->setParameter("source", hwStage, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
mis[2]->setParameter("source", hwIn, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
for (auto* mi : mis) {
mi->setParameter("level", 0.0f);
mi->setParameter("threshold", inoutBloomOptions.threshold ? 1.0f : 0.0f);
mi->setParameter("invHighlight", std::isinf(inoutBloomOptions.highlight)
? 0.0f : 1.0f / inoutBloomOptions.highlight);
mi->commit(driver);
}
const PipelineState pipeline(material.getPipelineState(mEngine));
{ // first iteration
auto hwDstRT = resources.getRenderPassInfo(data.outRT[0]);
hwDstRT.params.flags.discardStart = TargetBufferFlags::COLOR;
hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE;
mis[2]->use(driver);
render(hwDstRT, pipeline, driver);
}
// prepare the next level
if (i != inoutBloomOptions.levels - 1) {
mi->setParameter("source", parity ? hwOut : hwStage, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
mi->setParameter("level", float(i));
}
for (size_t i = 1; i < inoutBloomOptions.levels; i++) {
const size_t parity = 1u - (i & 0x1u);
auto hwDstRT = resources.getRenderPassInfo(parity ? data.outRT[i] : data.stageRT[i]);
hwDstRT.params.flags.discardStart = TargetBufferFlags::COLOR;
hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE;
mis[parity]->setParameter("level", float(i - 1));
mis[parity]->commit(driver);
mis[parity]->use(driver);
render(hwDstRT, pipeline, driver);
}
for (auto& mi : mis) {
mEngine.destroy(mi);
}
});
@@ -1988,35 +2014,48 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg
auto const& outDesc = resources.getDescriptor(data.out);
auto const& material = getPostProcessMaterial("bloomUpsample");
FMaterialInstance* mi = material.getMaterialInstance(mEngine);
auto const* ma = material.getMaterial(mEngine);
FMaterialInstance* mis[] = {
ma->createInstance("bloomUpsample-ping"),
ma->createInstance("bloomUpsample-pong"),
};
mis[0]->setParameter("source", hwOut, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
mis[1]->setParameter("source", hwStage, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
PipelineState pipeline(material.getPipelineState(mEngine));
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE;
mi->use(driver);
for (size_t j = inoutBloomOptions.levels, i = j - 1; i >= 1; i--, j++) {
const bool parity = (j % 2) == 0;
const size_t parity = 1u - (j % 2u);
auto hwDstRT = resources.getRenderPassInfo(
parity ? data.outRT[i - 1] : data.stageRT[i - 1]);
hwDstRT.params
.flags
.discardStart = TargetBufferFlags::NONE; // because we'll blend
hwDstRT.params.flags.discardStart = TargetBufferFlags::NONE; // b/c we'll blend
hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE;
auto w = FTexture::valueForLevel(i - 1, outDesc.width);
auto h = FTexture::valueForLevel(i - 1, outDesc.height);
mi->setParameter("resolution", float4{ w, h, 1.0f / w, 1.0f / h });
mi->setParameter("source", parity ? hwStage : hwOut, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
mi->setParameter("level", float(i));
mi->commit(driver);
mis[parity]->setParameter("resolution", float4{ w, h, 1.0f / w, 1.0f / h });
mis[parity]->setParameter("level", float(i));
mis[parity]->commit(driver);
mis[parity]->use(driver);
render(hwDstRT, pipeline, driver);
}
for (auto& mi : mis) {
mEngine.destroy(mi);
}
// Every other level is missing from the out texture, so we need to do
// blits to complete the chain.
const SamplerMagFilter filter = SamplerMagFilter::NEAREST;
@@ -2226,7 +2265,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
if (bloomOptions.enabled) {
bloomStrength = clamp(bloomOptions.strength, 0.0f, 1.0f);
if (bloomOptions.dirt) {
FTexture* fdirt = upcast(bloomOptions.dirt);
FTexture* fdirt = downcast(bloomOptions.dirt);
FrameGraphTexture frameGraphTexture{ .handle = fdirt->getHwHandle() };
bloomDirt = fg.import("dirt", {
.width = (uint32_t)fdirt->getWidth(0u),
@@ -2437,7 +2476,7 @@ void PostProcessManager::prepareTaa(FrameGraph& fg, filament::Viewport const& sv
fg.addTrivialSideEffectPass("Jitter Camera",
[=, &uniforms] (DriverApi& driver) {
uniforms.prepareCamera(*inoutCameraInfo);
uniforms.prepareCamera(mEngine, *inoutCameraInfo);
uniforms.commit(driver);
});
}
+79 -79
View File
@@ -39,7 +39,7 @@ using namespace backend;
RenderPass::RenderPass(FEngine& engine,
RenderPass::Arena& arena) noexcept
: mEngine(engine), mCommandArena(arena),
: mCommandArena(arena),
mCustomCommands(engine.getPerRenderPassAllocator()) {
}
@@ -79,26 +79,13 @@ void RenderPass::setCamera(const CameraInfo& camera) noexcept {
mCameraForwardVector = camera.getForwardVector();
}
void RenderPass::overridePolygonOffset(backend::PolygonOffset const* polygonOffset) noexcept {
if ((mPolygonOffsetOverride = (polygonOffset != nullptr))) {
mPolygonOffset = *polygonOffset;
}
}
void RenderPass::setScissorViewport(backend::Viewport viewport) noexcept {
assert_invariant(viewport.width <= std::numeric_limits<int32_t>::max());
assert_invariant(viewport.height <= std::numeric_limits<int32_t>::max());
mScissorViewport = viewport;
}
void RenderPass::overrideScissor(backend::Viewport const* scissor) noexcept {
if ((mScissorOverride = (scissor != nullptr))) {
mScissor = *scissor;
}
}
void RenderPass::appendCommands(CommandTypeFlags const commandTypeFlags) noexcept {
void RenderPass::appendCommands(FEngine& engine, CommandTypeFlags const commandTypeFlags) noexcept {
SYSTRACE_CONTEXT();
assert_invariant(mRenderableSoa);
@@ -110,7 +97,6 @@ void RenderPass::appendCommands(CommandTypeFlags const commandTypeFlags) noexcep
return;
}
FEngine& engine = mEngine;
JobSystem& js = engine.getJobSystem();
const RenderFlags renderFlags = mFlags;
const Variant variant = mVariant;
@@ -179,7 +165,7 @@ void RenderPass::appendCustomCommand(Pass pass, CustomCommand custom, uint32_t o
curr->key = cmd;
}
void RenderPass::sortCommands() noexcept {
void RenderPass::sortCommands(FEngine& engine) noexcept {
SYSTRACE_NAME("sort and trim commands");
std::sort(mCommandBegin, mCommandEnd);
@@ -192,12 +178,28 @@ void RenderPass::sortCommands() noexcept {
resize(uint32_t(last - mCommandBegin));
if (mEngine.isAutomaticInstancingEnabled()) {
instanceify();
if (engine.isAutomaticInstancingEnabled()) {
instanceify(engine);
}
}
void RenderPass::instanceify() noexcept {
void RenderPass::execute(FEngine& engine, const char* name,
backend::Handle<backend::HwRenderTarget> renderTarget,
backend::RenderPassParams params) const noexcept {
DriverApi& driver = engine.getDriverApi();
// this is a good time to flush the CommandStream, because we're about to potentially
// output a lot of commands. This guarantees here that we have at least
// FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB bytes (1MiB by default).
engine.flush();
driver.beginRenderPass(renderTarget, params);
getExecutor().execute(engine, name);
driver.endRenderPass();
}
void RenderPass::instanceify(FEngine& engine) noexcept {
SYSTRACE_NAME("instanceify");
// instanceify works by scanning the **sorted** command stream, looking for repeat draw
@@ -284,7 +286,7 @@ void RenderPass::instanceify() noexcept {
#endif
// we have instanced primitives
DriverApi& driver = mEngine.getDriverApi();
DriverApi& driver = engine.getDriverApi();
// TODO: maybe use a pool? so we can reuse the buffer.
// create a ubo to hold the instanced primitive data
@@ -356,8 +358,8 @@ void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant,
cmdDraw.primitive.rasterState.inverseFrontFaces = inverseFrontFaces;
cmdDraw.primitive.rasterState.culling = mi->getCullingMode();
cmdDraw.primitive.rasterState.colorWrite = mi->getColorWrite();
cmdDraw.primitive.rasterState.depthWrite = mi->getDepthWrite();
cmdDraw.primitive.rasterState.colorWrite = mi->isColorWriteEnabled();
cmdDraw.primitive.rasterState.depthWrite = mi->isDepthWriteEnabled();
cmdDraw.primitive.rasterState.depthFunc = mi->getDepthFunc();
cmdDraw.primitive.mi = mi;
cmdDraw.primitive.materialVariant = variant;
@@ -378,12 +380,14 @@ void RenderPass::generateCommands(uint32_t commandTypeFlags, Command* const comm
// the list twice)
// compute how much maximum storage we need
uint32_t offset = FScene::getPrimitiveCount(soa, range.first);
// double the color pass for transparent objects that need to render twice
const bool colorPass = bool(commandTypeFlags & CommandTypeFlags::COLOR);
const bool depthPass = bool(commandTypeFlags & CommandTypeFlags::DEPTH);
offset *= uint32_t(colorPass * 2 + depthPass);
Command* const curr = commands + offset;
const size_t commandsPerPrimitive = uint32_t(colorPass * 2 + depthPass);
const size_t offsetBegin = FScene::getPrimitiveCount(soa, range.first) * commandsPerPrimitive;
const size_t offsetEnd = FScene::getPrimitiveCount(soa, range.last) * commandsPerPrimitive;
Command* curr = commands + offsetBegin;
Command* const last = commands + offsetEnd;
/*
* The switch {} below is to coerce the compiler into generating different versions of
@@ -396,23 +400,31 @@ void RenderPass::generateCommands(uint32_t commandTypeFlags, Command* const comm
switch (commandTypeFlags & (CommandTypeFlags::COLOR | CommandTypeFlags::DEPTH)) {
case CommandTypeFlags::COLOR:
generateCommandsImpl<CommandTypeFlags::COLOR>(commandTypeFlags, curr,
curr = generateCommandsImpl<CommandTypeFlags::COLOR>(commandTypeFlags, curr,
soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward);
break;
case CommandTypeFlags::DEPTH:
generateCommandsImpl<CommandTypeFlags::DEPTH>(commandTypeFlags, curr,
curr = generateCommandsImpl<CommandTypeFlags::DEPTH>(commandTypeFlags, curr,
soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward);
break;
default:
// we should never end-up here
break;
}
assert_invariant(curr <= last);
// commands may have been skipped, cancel all of them.
while (curr != last) {
curr->key = uint64_t(Pass::SENTINEL);
++curr;
}
}
/* static */
template<uint32_t commandTypeFlags>
UTILS_NOINLINE
void RenderPass::generateCommandsImpl(uint32_t extraFlags,
RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags,
Command* UTILS_RESTRICT curr,
FScene::RenderableSoa const& UTILS_RESTRICT soa, Range<uint32_t> range,
Variant variant, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask,
@@ -458,17 +470,8 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags,
const float cameraPositionDotCameraForward = dot(cameraPosition, cameraForward);
for (uint32_t i = range.first; i < range.last; ++i) {
// Check if this renderable passes the visibilityMask. If it doesn't, encode SENTINEL
// commands (no-op).
// Check if this renderable passes the visibilityMask.
if (UTILS_UNLIKELY(!(soaVisibilityMask[i] & visibilityMask))) {
// We need to encode a SENTINEL for each command that would have been generated
// otherwise. Color passes get 2 commands per primitive; depth passes get 1.
const Slice<FRenderPrimitive>& primitives = soaPrimitives[i];
const size_t commandsToEncode = (isColorPass * 2 + isDepthPass) * primitives.size();
for (size_t j = 0; j < commandsToEncode; j++) {
curr->key = uint64_t(Pass::SENTINEL);
++curr;
}
continue;
}
@@ -597,15 +600,15 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags,
// draw this command AFTER THE NEXT ONE
key |= makeField(1, BLEND_TWO_PASS_MASK, BLEND_TWO_PASS_SHIFT);
// handle the case where this primitive is empty / no-op
key |= select(primitive.getPrimitiveType() == PrimitiveType::NONE);
// correct for TransparencyMode::DEFAULT -- i.e. cancel the command
key |= select(mode == TransparencyMode::DEFAULT);
// cancel command if asked to filter translucent objects
key |= select(filterTranslucentObjects);
// cancel command if both front and back faces are culled
key |= select(mi->getCullingMode() == CullingMode::FRONT_AND_BACK);
*curr = cmdColor;
curr->key = key;
++curr;
@@ -629,14 +632,13 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags,
// bucketizes the depth by its log2 and in 4 linear chunks in each bucket.
cmdColor.key &= ~Z_BUCKET_MASK;
cmdColor.key |= makeField(distanceBits >> 22u, Z_BUCKET_MASK, Z_BUCKET_SHIFT);
curr->key = uint64_t(Pass::SENTINEL);
++curr;
}
*curr = cmdColor;
// handle the case where this primitive is empty / no-op
curr->key |= select(primitive.getPrimitiveType() == PrimitiveType::NONE);
// cancel command if both front and back faces are culled
curr->key |= select(mi->getCullingMode() == CullingMode::FRONT_AND_BACK);
++curr;
}
@@ -661,18 +663,21 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags,
// FIXME: should writeDepthForShadowCasters take precedence over mi->getDepthWrite()?
cmdDepth.primitive.rasterState.depthWrite = (1 // only keep bit 0
& (mi->getDepthWrite() | (mode == TransparencyMode::TWO_PASSES_ONE_SIDE))
& (mi->isDepthWriteEnabled() | (mode == TransparencyMode::TWO_PASSES_ONE_SIDE))
& !(filterTranslucentObjects & translucent)
& !(depthFilterAlphaMaskedObjects & rs.alphaToCoverage))
| writeDepthForShadowCasters;
*curr = cmdDepth;
// handle the case where this primitive is empty / no-op
curr->key |= select(primitive.getPrimitiveType() == PrimitiveType::NONE);
// cancel command if both front and back faces are culled
curr->key |= select(mi->getCullingMode() == CullingMode::FRONT_AND_BACK);
++curr;
}
}
}
return curr;
}
void RenderPass::updateSummedPrimitiveCounts(
@@ -690,25 +695,25 @@ void RenderPass::updateSummedPrimitiveCounts(
// ------------------------------------------------------------------------------------------------
void RenderPass::Executor::execute(const char* name,
backend::Handle<backend::HwRenderTarget> renderTarget,
backend::RenderPassParams const& params) const noexcept {
FEngine& engine = mEngine;
DriverApi& driver = engine.getDriverApi();
void RenderPass::Executor::overridePolygonOffset(backend::PolygonOffset const* polygonOffset) noexcept {
if ((mPolygonOffsetOverride = (polygonOffset != nullptr))) {
mPolygonOffset = *polygonOffset;
}
}
// this is a good time to flush the CommandStream, because we're about to potentially
// output a lot of commands. This guarantees here that we have at least
// FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB bytes (1MiB by default).
engine.flush();
void RenderPass::Executor::overrideScissor(backend::Viewport const* scissor) noexcept {
if ((mScissorOverride = (scissor != nullptr))) {
mScissor = *scissor;
}
}
driver.beginRenderPass(renderTarget, params);
recordDriverCommands(engine, driver, mBegin, mEnd, params.readOnlyDepthStencil);
driver.endRenderPass();
void RenderPass::Executor::execute(FEngine& engine, const char* name) const noexcept {
execute(engine.getDriverApi(), mCommands.begin(), mCommands.end());
}
UTILS_NOINLINE // no need to be inlined
void RenderPass::Executor::recordDriverCommands(FEngine& engine, backend::DriverApi& driver,
const Command* first, const Command* last, uint16_t readOnlyDepthStencil) const noexcept {
void RenderPass::Executor::execute(backend::DriverApi& driver,
const Command* first, const Command* last) const noexcept {
SYSTRACE_CALL();
if (first != last) {
@@ -728,7 +733,7 @@ void RenderPass::Executor::recordDriverCommands(FEngine& engine, backend::Driver
Handle<HwBufferObject> uboHandle = mUboHandle;
FMaterialInstance const* UTILS_RESTRICT mi = nullptr;
FMaterial const* UTILS_RESTRICT ma = nullptr;
auto customCommands = mCustomCommands.data();
auto const* UTILS_RESTRICT pCustomCommands = mCustomCommands.data();
first--;
while (++first != last) {
@@ -741,7 +746,7 @@ void RenderPass::Executor::recordDriverCommands(FEngine& engine, backend::Driver
if (UTILS_UNLIKELY((first->key & CUSTOM_MASK) != uint64_t(CustomCommand::PASS))) {
uint32_t index = (first->key & CUSTOM_INDEX_MASK) >> CUSTOM_INDEX_SHIFT;
assert_invariant(index < mCustomCommands.size());
customCommands[index]();
pCustomCommands[index]();
continue;
}
@@ -749,11 +754,6 @@ void RenderPass::Executor::recordDriverCommands(FEngine& engine, backend::Driver
const PrimitiveInfo info = first->primitive;
pipeline.rasterState = info.rasterState;
#ifndef NDEBUG
const bool readOnlyDepthGuaranteed = readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH;
assert_invariant(!readOnlyDepthGuaranteed || !pipeline.rasterState.depthWrite);
#endif
if (UTILS_UNLIKELY(mi != info.mi)) {
// this is always taken the first time
mi = info.mi;
@@ -795,14 +795,16 @@ void RenderPass::Executor::recordDriverCommands(FEngine& engine, backend::Driver
// bind per-renderable uniform block. there is no need to attempt to skip this command
// because the backends already do this.
driver.bindUniformBufferRange(+UniformBindingPoints::PER_RENDERABLE,
driver.bindBufferRange(BufferObjectBinding::UNIFORM,
+UniformBindingPoints::PER_RENDERABLE,
(info.instanceCount > 1) ? mInstancedUboHandle : uboHandle,
info.index * sizeof(PerRenderableData),
sizeof(PerRenderableUib));
if (UTILS_UNLIKELY(info.skinningHandle)) {
// note: we can't bind less than sizeof(PerRenderableBoneUib) due to glsl limitations
driver.bindUniformBufferRange(+UniformBindingPoints::PER_RENDERABLE_BONES,
driver.bindBufferRange(BufferObjectBinding::UNIFORM,
+UniformBindingPoints::PER_RENDERABLE_BONES,
info.skinningHandle,
info.skinningOffset * sizeof(PerRenderableBoneUib::BoneData),
sizeof(PerRenderableBoneUib));
@@ -833,15 +835,13 @@ void RenderPass::Executor::recordDriverCommands(FEngine& engine, backend::Driver
// ------------------------------------------------------------------------------------------------
RenderPass::Executor::Executor(RenderPass const* pass, Command const* b, Command const* e) noexcept
: mEngine(pass->mEngine), mBegin(b), mEnd(e),
mCustomCommands(pass->mCustomCommands),
: mCommands(b, e),
mCustomCommands(pass->mCustomCommands.data(), pass->mCustomCommands.size()),
mUboHandle(pass->mUboHandle),
mInstancedUboHandle(pass->mInstancedUboHandle),
mPolygonOffset(pass->mPolygonOffset),
mScissor(pass->mScissor),
mScissorViewport(pass->mScissorViewport),
mPolygonOffsetOverride(pass->mPolygonOffsetOverride),
mScissorOverride(pass->mScissorOverride) {
mPolygonOffsetOverride(false),
mScissorOverride(false) {
assert_invariant(b >= pass->begin());
assert_invariant(e <= pass->end());
}
+42 -48
View File
@@ -276,15 +276,9 @@ public:
// allocated commands ARE NOT freed, they're owned by the Arena
~RenderPass() noexcept;
// if non-null, overrides the material's polygon offset
void overridePolygonOffset(backend::PolygonOffset const* polygonOffset) noexcept;
// a box that both offsets the viewport and clips it
void setScissorViewport(backend::Viewport viewport) noexcept;
// if non-null, overrides the material's scissor
void overrideScissor(backend::Viewport const* scissor) noexcept;
// specifies the geometry to generate commands for
void setGeometry(FScene::RenderableSoa const& soa, utils::Range<uint32_t> vr,
backend::Handle<backend::HwBufferObject> uboHandle) noexcept;
@@ -310,58 +304,69 @@ public:
// This is the main function of this class, this appends commands to the pass using
// the current camera, geometry and flags set. This can be called multiple times if needed.
void appendCommands(CommandTypeFlags commandTypeFlags) noexcept;
void appendCommands(FEngine& engine, CommandTypeFlags commandTypeFlags) noexcept;
// sorts and instancify commands then trims sentinels
void sortCommands() noexcept;
// sorts and instanceify commands then trims sentinels
void sortCommands(FEngine& engine) noexcept;
// Helper to execute all the commands generated by this RenderPass
void execute(const char* name,
void execute(FEngine& engine, const char* name,
backend::Handle<backend::HwRenderTarget> renderTarget,
backend::RenderPassParams params) const noexcept {
getExecutor().execute(name, renderTarget, params);
}
backend::RenderPassParams params) const noexcept;
/*
* Executor holds the range of commands to execute for a given pass
*/
class Executor {
using CustomCommandFn = std::function<void()>;
using CustomCommandVector = std::vector<CustomCommandFn,
utils::STLAllocator<CustomCommandFn, LinearAllocatorArena>>;
friend class RenderPass;
FEngine& mEngine;
Command const* mBegin;
Command const* mEnd;
const CustomCommandVector mCustomCommands;
const backend::Handle<backend::HwBufferObject> mUboHandle;
const backend::Handle<backend::HwBufferObject> mInstancedUboHandle;
const backend::PolygonOffset mPolygonOffset;
const backend::Viewport mScissor;
const backend::Viewport mScissorViewport;
const bool mPolygonOffsetOverride : 1;
const bool mScissorOverride : 1;
// these fields are constant after creation
utils::Slice<Command> mCommands;
utils::Slice<CustomCommandFn> mCustomCommands;
backend::Handle<backend::HwBufferObject> mUboHandle;
backend::Handle<backend::HwBufferObject> mInstancedUboHandle;
backend::Viewport mScissorViewport;
backend::Viewport mScissor{}; // value of scissor override
backend::PolygonOffset mPolygonOffset{}; // value of the override
bool mPolygonOffsetOverride : 1; // whether to override the polygon offset setting
bool mScissorOverride : 1; // whether to override the polygon offset setting
Executor(RenderPass const* pass, Command const* b, Command const* e) noexcept;
void recordDriverCommands(FEngine& engine, backend::DriverApi& driver,
const Command* first, const Command* last, uint16_t readOnlyDepthStencil) const noexcept;
void execute(backend::DriverApi& driver,
const Command* first, const Command* last) const noexcept;
public:
Executor() = default;
Executor(Executor const& rhs);
Executor& operator=(Executor const& rhs) = default;
~Executor() noexcept;
void execute(const char* name,
backend::Handle<backend::HwRenderTarget> renderTarget,
backend::RenderPassParams const& params) const noexcept;
// if non-null, overrides the material's polygon offset
void overridePolygonOffset(backend::PolygonOffset const* polygonOffset) noexcept;
// if non-null, overrides the material's scissor
void overrideScissor(backend::Viewport const* scissor) noexcept;
void execute(FEngine& engine, const char* name) const noexcept;
};
// returns a new executor for this pass
Executor getExecutor() {
return { this, mCommandBegin, mCommandEnd };
}
Executor getExecutor() const {
return { this, mCommandBegin, mCommandEnd };
}
// returns a new executor for this pass with a custom range
Executor getExecutor(Command const* b, Command const* e) {
return { this, b, e };
}
Executor getExecutor(Command const* b, Command const* e) const {
return { this, b, e };
}
@@ -376,7 +381,7 @@ private:
Command* append(size_t count) noexcept;
void resize(size_t count) noexcept;
void instanceify() noexcept;
void instanceify(FEngine& engine) noexcept;
// on 64-bits systems, we process batches of 256 (64 bytes) cache-lines, or 512 (32 bytes) commands
// on 32-bits systems, we process batches of 512 (32 bytes) cache-lines, or 512 (32 bytes) commands
@@ -394,7 +399,7 @@ private:
math::float3 cameraPosition, math::float3 cameraForward) noexcept;
template<uint32_t commandTypeFlags>
static inline void generateCommandsImpl(uint32_t, Command* commands,
static inline Command* generateCommandsImpl(uint32_t extraFlags, Command* curr,
FScene::RenderableSoa const& soa, utils::Range<uint32_t> range,
Variant variant, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask,
math::float3 cameraPosition, math::float3 cameraForward) noexcept;
@@ -406,7 +411,6 @@ private:
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> vr) noexcept;
// a reference to the Engine, mostly to get to things like JobSystem
FEngine& mEngine;
// Arena where all Commands are allocated. The Arena owns the commands.
Arena& mCommandArena;
@@ -440,24 +444,14 @@ private:
// Additional visibility mask
FScene::VisibleMaskType mVisibilityMask = std::numeric_limits<FScene::VisibleMaskType>::max();
// whether to override the polygon offset setting
bool mPolygonOffsetOverride = false;
// whether to override the polygon offset setting
bool mScissorOverride = false;
// value of the override
backend::PolygonOffset mPolygonOffset{};
// value of scissor override
backend::Viewport mScissor{};
backend::Viewport mScissorViewport{ 0, 0,
std::numeric_limits<int32_t>::max(),
std::numeric_limits<int32_t>::max() };
// a vector for our custom commands
mutable Executor::CustomCommandVector mCustomCommands;
using CustomCommandVector = std::vector<Executor::CustomCommandFn,
utils::STLAllocator<Executor::CustomCommandFn, LinearAllocatorArena>>;
mutable CustomCommandVector mCustomCommands;
};
} // namespace filament
+3 -3
View File
@@ -30,12 +30,12 @@ void FRenderPrimitive::init(HwRenderPrimitiveFactory& factory, backend::DriverAp
assert_invariant(entry.materialInstance);
mMaterialInstance = upcast(entry.materialInstance);
mMaterialInstance = downcast(entry.materialInstance);
mBlendOrder = entry.blendOrder;
if (entry.indices && entry.vertices) {
FVertexBuffer* vertexBuffer = upcast(entry.vertices);
FIndexBuffer* indexBuffer = upcast(entry.indices);
FVertexBuffer* vertexBuffer = downcast(entry.vertices);
FIndexBuffer* indexBuffer = downcast(entry.indices);
AttributeBitset enabledAttributes = vertexBuffer->getDeclaredAttributes();
+1 -1
View File
@@ -71,7 +71,7 @@ private:
AttributeBitset mEnabledAttributes = {};
uint16_t mBlendOrder = 0;
bool mGlobalBlendOrderEnabled = false;
backend::PrimitiveType mPrimitiveType = backend::PrimitiveType::NONE;
backend::PrimitiveType mPrimitiveType = backend::PrimitiveType::TRIANGLES;
UTILS_UNUSED uint8_t reserved[4] = {};
};
+5 -5
View File
@@ -23,23 +23,23 @@ namespace filament {
using namespace backend;
Texture* RenderTarget::getTexture(AttachmentPoint attachment) const noexcept {
return upcast(this)->getAttachment(attachment).texture;
return downcast(this)->getAttachment(attachment).texture;
}
uint8_t RenderTarget::getMipLevel(AttachmentPoint attachment) const noexcept {
return upcast(this)->getAttachment(attachment).mipLevel;
return downcast(this)->getAttachment(attachment).mipLevel;
}
RenderTarget::CubemapFace RenderTarget::getFace(AttachmentPoint attachment) const noexcept {
return upcast(this)->getAttachment(attachment).face;
return downcast(this)->getAttachment(attachment).face;
}
uint32_t RenderTarget::getLayer(AttachmentPoint attachment) const noexcept {
return upcast(this)->getAttachment(attachment).layer;
return downcast(this)->getAttachment(attachment).layer;
}
uint8_t RenderTarget::getSupportedColorAttachmentsCount() const noexcept {
return upcast(this)->getSupportedColorAttachmentsCount();
return downcast(this)->getSupportedColorAttachmentsCount();
}
} // namespace filament

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