Compare commits

...

283 Commits

Author SHA1 Message Date
Powei Feng
36c76daf34 material: fix depth variant leak
The default material does not cover all of the depth variants,
and so for the client material's depth variants (with no custom
depth shader), we need to check if the program is allocated for
the material or if it is actually part of the default material.
2024-11-14 15:13:48 -08:00
Powei Feng
43b9c06f79 vk: increase number of command buffers (#8271)
Small number of pre-allocated command buffers can cause
unnecessary blocking on the CPU.  We increase this number.
2024-11-14 19:24:29 +00:00
Powei Feng
05ffc76032 vk: clean up group marker logic (#8272)
The old logic was unnecessarily complex. We simplify it and also
properly store the marker stack on the CommandPool instead of
VulkanCommands.
2024-11-14 19:11:17 +00:00
Ben Doherty
8e5ba51718 Fix TSAN warning in MetalHandles (#8262) 2024-11-14 10:12:53 -08:00
Mathias Agopian
8aa9c4d004 Don't use AtomicFreeList to squash TSAN complains
The previous fix attempt didn't work on some test. There are no known
bugs with AtomicFreeList other than tripping TSAN, and it's unclear
that TSAN isn't at fault.

However, switching to using a mutex works fine and doesn't appear to
be slower (it's actually faster with synthetic benchmarks on macOS)

BUGS=[377369108]
2024-11-14 10:00:00 -08:00
Mathias Agopian
d075a877f2 Fix TSAN warnings with AtomicFreeList
I do not think there was an actual error with AtomicFreeList, however
TSAN detected a data race when concurrent pop() happened. In that case,
there is indeed a race, where we can end-up reading data that is
already corrupted by the concurrent pop. However, that situation is
corrected by the following CAS. Somehow TSAN didn't see that.
The fix is strange and consists in replacing:
```
auto pNext = storage[offset].next;
```

with

```
auto s = storage[offset];
auto pNext = s.next;
```

In this PR we also adjust the memory ordering to be less strong. i.e.
we do not need `memory_order_seq_cst`, only the appropriate acquire or
release semantic.

In addition we also make `Node* next` a non-atomic variable again. It
should have been, but was change to placate an older version of TSAN.

BUGS=[377369108]
2024-11-13 21:50:05 -08:00
Mathias Agopian
5dfd285105 ostream buffer growth was broken
It only grew by 3/2 of the needed size instead of the needed capacity,
which could be extremely slow when growing by a constant amount
many times.
2024-11-12 16:03:35 -08:00
Ben Doherty
51392c77aa Metal: unbind descriptor sets upon destruction (#8268) 2024-11-12 14:08:15 -08:00
mdagois
e78691b12b Forcing profile mode (#8256) 2024-11-12 20:05:08 +00:00
Powei Feng
bc65135c51 vk: refactor resource ref-counting (#8254)
Continuing from PR #8220.

Here we change all of the references from the old ref-counting ways
to the new ref-counting structs and mechanisms. There should be no
functional change.
2024-11-12 10:33:00 -08:00
Serge Metral
0108d66aa1 vk: Implement protected content
- Cache the render buffer in the render pass strucutre
- Enable the proteded path in the Render Target
- Make sure we build the protected version of the depth/color in the swap chaing
- Minor cleanup/restrucuring of the code
2024-11-12 09:10:03 -08:00
1558287830
96443fa1fd vk: fix stage pool gc logic (#8260)
* vk: fix stage pool gc logic

* Update NEW_RELEASE_NOTES.md

---------

Co-authored-by: linkunhai <linkunhai@bytedance.com>
2024-11-12 06:49:12 +00:00
Mathias Agopian
61c4df9503 fix uninitialized variable on ES2 devices
On ES2 devices (or in forceES2 mode), we emulate the sRGB swapchain
in the shader if the h/w doesn't support it. In that case, the emulation
is controlled by a uniform that technically lives in the frameUniforms
block. However, the frameUniforms buffer is not updated, instead,
the uniform is manually set. Unfortunately, the UBO emulation
overrides it with the uninitialized variable.

BUGS=[377913730]
2024-11-11 16:30:24 -08:00
Mathias Agopian
b990652702 assert if post-processing is used at Feature Level 0
We assert in Renderer::render(View*) as early as possible. The
current behavior can result in a SEG FAULT which is harder to debug.
2024-11-11 16:30:24 -08:00
Mathias Agopian
7f7d4d89ea fix some missing highp qualifiers
BUGS=[377751005]
2024-11-08 14:37:34 -08:00
Powei Feng
2b7f325cc7 renderdiff: Use single lib for osmesa (#8255)
Previously, we linked against libOSMesa through the linker and
then used dlopen to link against libGL. This is not how libOSMesa
is intended to be used.  Instead, we use dlopen on libOSMesa
(via bluegl), which then will map the correct GL methods for us,
and for the OSMesa functions, we also map those functions
from libOSMesa (instead of relying on compile-time linker).
2024-11-07 21:55:20 +00:00
Ben Doherty
b9768394a1 Metal: remove cached pipeline states with deleted programs (#8243) 2024-11-07 11:23:30 -08:00
Powei Feng
3e02d5016e vk: fix uninitialized param (#8253) 2024-11-06 22:04:57 +00:00
Benjamin Doherty
483ef2f125 Release Filament 1.56.0 2024-11-06 10:48:24 -08:00
Serge Metral
46dc9e7772 vk: Refactor command buffers to include protected buffers (#8246)
By providing a queue and the family index, we can now create
protected comand buffers that will be submitted to a protected
queue.  The main abstraction is in VulkanCommands.h where we
introduced a CommandBufferPool.

Also did refactor on the "mStorage", fences, and semaphores of
the original VulkanCommands so that each buffer is associated
with one fence and one semaphore.
2024-11-06 00:41:37 +00:00
Mathias Agopian
b8551e50e1 a new Feature Flag API
Feature flags are intended to be used when a new feature is added to 
filament and have generally two purposes:

1) during feature development, the feature can be implemented but
   disabled which can help developing large features. This way the
   feature can be tested by stakeholders while it's being developed
   without impacting other clients.

2) once a feature is ready, its flag can be enabled by default, but 
   in case the feature breaks something or has unintended consequence,
   clients have the option to turn it off.

Feature flags are intended to have a relatively short life span, i.e.
once a feature is stable, the flag fill be removed.

There two types of feature flags. Constant feature flags can only be
set during Engine initialization via Engine::Builder. Non-constant
feature flags can be set at any time. 

Feature flags SHOULD NOT be used as configuration or settings.


Feature flags are designed with a few ideas in mind:
- they are very cheap to check inside the engine
- non-constant flags can easily be toggled using ImGUI
2024-11-05 15:34:56 -08:00
Powei Feng
b64df46522 vk: temporary workaround for sampler external (#8250) 2024-11-05 11:23:51 -08:00
Benjamin Doherty
7f92dfd06c Release Filament 1.55.1 2024-11-04 09:34:01 -08:00
Powei Feng
3304a0c8b6 vk: add resource ref-counting alternative (#8220)
We add a set of classes to the vk backend that will enable
cleaner ref-counting.

In particular, all allocation from the HandleAllocator will be
wrapped within a resource_ptr<> smart pointer. This struct will
maintain a count that will increment/decrement with respect to
references (similar to std::shared_ptr).

This commit only introduces the new classes/structs and does not
actually make any functional difference. A follow-up commit will
make the switch to use the smart pointer.

We also put VulkanFence and VulkanTimerQuery in a different header
because we will need to depend on them separately in the follow-up
(reason: they are accessed on the backend thread but allocated on
"sync"/filament thread).
2024-11-01 21:36:56 +00:00
Ben Doherty
0c2d23e55c Metal: unbind SwapChain upon destruction (#8244) 2024-11-01 14:16:54 -07:00
mdagois
59d1ed6a94 Trace (#8218)
Add performance tracing for GL/VK on Android
2024-11-01 14:11:32 -07:00
Evan Mezeske
33c299d3ba Ignore another warning for vk_mem_alloc.h so that Filament can compile with -Wthread-safety enabled 2024-10-30 11:57:18 -07:00
Mathias Agopian
1cc87aa9af descriptor layouts distinguish samplers and external samplers
BUGS=[376089915]
2024-10-30 11:17:38 -07:00
Serge Metral
eb1b30c7b1 vk: protected content platform (#8226)
Adding the basic mechanisms for creating and managing the protected
state. We also create the queue and get it. Finally we allocate
protected memory. Also removed some of the shared context logic
2024-10-29 21:32:18 +00:00
Mathias Agopian
ecedbad5ba allow bindDescriptorSet() to take a null handle 2024-10-29 11:10:52 -07:00
Ben Doherty
d1998c1b67 Metal: unbind descriptor set with null handle (#8237) 2024-10-29 10:53:24 -07:00
Powei Feng
c2177ab86a vk: implement descriptor set unbind (#8236) 2024-10-29 17:21:13 +00:00
Mathias Agopian
f4d87ad526 prepare rendering loop for multithreading
The goal of this PR is to get closer to being able to run all
FrameGraph passes in parallel. To achieve this we need all data
consumed by the "execute" closure of the FrameGraph passes to be
immutable or thread-safe. We also need the passes to never use the
Engine's global `DriverAPi&` object.

Specifically in this PR, we turn as many objects to `const` as possible
without major changes, and we pass the `DriverApi&` object as parameter
to render passes.

This work is far from being complete. So we also annotate with FIXMEs
all the places we can identify will be problematic (there are probably
others).

The main remaining issues are:
- main allocator is not thread-safe
- some places take a non-const View, Scene or Engine
- lazy allocation of materials and material instance usages are not
  thread-safe.

This PR shouldn't change any behavior.
2024-10-28 12:31:24 -07:00
Evan Mezeske
f2f9c547b0 Fix clang c++20 compilation under debug mode as well. (#8233)
* Fix clang c++20 compilation under debug mode as well.

* Fix whitespace
2024-10-25 11:10:46 -07:00
Powei Feng
ea1c5d8f24 vk: improve rendertarget initialization (#8228)
Move a lot of the code from beginRenderPass() to the initialization
of the RenderTarget. This will save us a bit of CPU when
RenderTarget is re-used.

We also reduce the size of the VulkanRenderTarget handle and put
must of the caching bits into a heap struct.
2024-10-25 17:36:30 +00:00
Mathias Agopian
42d39834df update remote ui 2024-10-25 00:30:10 -07:00
Mathias Agopian
7ea7fa18eb update beamsplitter template
the generated file was modified, which would be undo'ed next time
we run beamsplitter
2024-10-25 00:29:13 -07:00
Mathias Agopian
a6b4c9fae1 fix gltf_viewer android sample build 2024-10-25 00:14:08 -07:00
Evan Mezeske
8d02cd0383 Fix bug in ResourceAllocator with erasing iterators inside loops (#8223) 2024-10-24 13:56:41 -07:00
Bob Conan
016882336a Update RELEASE_NOTES.md, fix a typo (#8227) 2024-10-24 13:56:05 -07:00
Ben Doherty
3f150e03bf Check that SwapChain is valid in endFrame (#8219) 2024-10-24 12:33:11 -07:00
Romain Guy
99308c98ba Update Android dependencies (#8224) 2024-10-23 15:49:01 -07:00
Evan Mezeske
6eee4520c7 vk: Add a workaround for a Windows NVIDIA Vulkan driver bug that causes swap chains to be created with incorrect dimensions for native windows created with thread-specific DPI-awareness context. (#8221) 2024-10-22 22:01:58 -07:00
Sungun Park
fa3a5d9eb4 Use xvp for multiview + debug view (#8222)
When multiview is enabled with the combining debug option toggled on, we
use an intermediate buffer that requires us to use the entire area of
the buffer as the viewport. Use xvp in this case.
2024-10-22 21:21:53 +00:00
show50726
17e4d2b92e Add public API in View for transparent picking (#8206) 2024-10-22 10:46:24 -07:00
kunyoung.park
13310e4ba0 Fix issue where material instance name cannot be set without creating a default instance (#8213) 2024-10-22 10:45:32 -07:00
Ben Doherty
eab8490d14 Metal: add better logging for invalid index element size (#8194) 2024-10-22 11:08:51 -04:00
Evan Mezeske
edece8f3dc Fix a bug in the OpenGL backend that causes win32 errors not to be logged #8214 (#8216)
* Capture the last win32 error immediately after failing win32 API functions are called in order to log it correctly. Prior to this change, intervening win32 API calls could clear the error code and it would not be logged.

* Oops fix bad whitespace in previous commit
2024-10-21 16:35:54 -07:00
Sungun Park
2456299337 Add a warning for FRenderableManager (#8210)
In some cases, users set materials first without providing render primitives, which has incurred the attribute mismatching warning. This isn't helpful because users don't know what action they should take to remove the warning.

Emit the warning only when the primitive handle is initialized so the AttributeBitset is properly populated.

BUGS=[372755205]
2024-10-20 15:08:13 +00:00
Ben Doherty
6e9afff7ba Fix potential crash when a descriptor set is destroyed but not unbound (#8215) 2024-10-18 16:42:14 -04:00
Mathias Agopian
4425338559 avoid setting the scissor rect when possible
RenderPass is now tracking the scissor state locally so it can avoid
re-setting the scissor when it doesn't change.

We also consolidate the scissor override and the scissor-viewport in
RenderPass::Execute.
2024-10-18 10:05:02 -07:00
Ben Doherty
a494f9ff6a Metal: avoid redundant scissor rect state changes (#8207) 2024-10-18 11:07:11 -04:00
Mathias Agopian
ba680cf11a Revert "avoid setting the scissor rect when possible"
This reverts commit fc095413b3.

clipping is wrong when post-processing is disabled
2024-10-17 16:00:48 -07:00
Mathias Agopian
fc095413b3 avoid setting the scissor rect when possible
RenderPass is now tracking the scissor state locally so it can avoid
re-setting the scissor when it doesn't change.

We also consolidate the scissor override and the scissor-viewport in
RenderPass::Execute.
2024-10-17 14:16:36 -07:00
Mathias Agopian
d80526528c Cleanup PostPorcessManager rendering
- the main goal of this change was to move some state changes outside
  of loops, usually with mip generation.

- for instance bindPostProcessDesciptorSet() must now be issued
  manually (and can be done outside the loop)

- we also don't set the scissor for each pass

- we also move prepareMaterial() outside of getPipelineState(), it's
  now done in PostProcessMaterial::getMaterial().

- We use PostProcessVariant instead of uint8_t everywhere

In the end we have three drawing helpers:

- commitAndRenderFullScreenQuad() which updates a material instance,
  binds the corresponding material and draws a full screen quad.
- renderFullScreenQuad() which just renders a full screen quad.
- renderFullScreenQuadWithScissor() which does the same but scisorred

Additionally, we have the following helpers for getting materials and
instances:

- PostProcessMaterial::getMaterial(): which returns the FMaterial
- PostProcessMaterial::getMaterialInstance(): which is now a helper
  returning a material instance from a PostProcessMaterial or FMaterial

Most of the change is pluming through these API changes.
2024-10-17 14:16:05 -07:00
Guilhem Saurel
38e59fbd6b add missing includes 2024-10-17 14:15:40 -07:00
show50726
a5e6df1ad3 Address the comments 2024-10-17 10:19:02 -07:00
show50726
23d1329f37 Disable transparent pickable by default 2024-10-17 10:19:02 -07:00
show50726
25b37d36ad First push
Fix the texture format

Decide texture format based on feature level
2024-10-17 10:19:02 -07:00
Mathias Agopian
68265ae793 no need to copy the PostProcessMaterial mConstants field
It always references static data. Additionally, we don't need
to use a vector to store the specialization constants, because
it's also all static data.

And finally, we don't need a boolean to know the state of the
PostProcessMaterial, the mSize field can encode the same
information.
2024-10-16 17:25:38 -07:00
Ben Doherty
e8f3fc5a46 Implement setThreadPriority for Apple devices (#8200) 2024-10-16 12:55:25 -04:00
Evan Mezeske
22db1be939 Add support for compiling under c++20 on clang-based compilers. The warnings that this change ignores are tricky to fix in the code in a way that is backwards compatible with c++17, but it is possible to do and it would allow these warnings to be reinstated. (#8199) 2024-10-15 16:45:32 -04:00
Powei Feng
a3290d7656 vk: remove history from DescriptorSetManager (#8193)
"history" is a map from a DescriptorSet pointer to a set of
bookkeeping values (we delay "binding" until "commit" so need to
keep values until then). Instead of using a map, we can store
these values in the DescriptorSet itself so that we save on a
map look-up.
2024-10-14 19:46:27 +00:00
Powei Feng
4ee33cd591 Release Filament 1.55.0 2024-10-14 06:07:34 +00:00
Evan Mezeske
fd2f9555f1 Fix tiny typo in VulkanPlatformSwapChainImpl.cpp logging (#8196) 2024-10-11 23:09:32 +00:00
Mathias Agopian
ccdb58e93c don't set ANGLE features in filament (#8192)
ANGLE features should be set by apps, the system or developers but it's
not a good idea to set them in a library as it might conflict with other
libs etc.

we did it because it improved performance, but that should be fixed at
the angle level instead.
2024-10-11 13:52:54 -07:00
Ben Doherty
1eb1df2cf2 Check for use-after-free for heap handles (#8182) 2024-10-11 20:28:18 +00:00
Mathias Agopian
d784ce311f gles: add a flag to enable the validation of the nativewindow
We are seeing a cluster of crashes that could be due to using an
EGLSurface whose ANativeWindow has become invalid. This could happen if
we continued to use (i.e. draw with) an EGLSurface after 
SurfaceHolder::onSurfaceDestroyed() has returned.

This new flag enables an assertion that the native window is valid at
the time of makeCurrent(), which happens early in the frame.

BUG=[330392256]
2024-10-10 13:38:35 -07:00
Powei Feng
875b295967 vk: refactor texture default layout (#8183)
Previously, default layout is based on usage, but this actually
has two paths (Filament's TextureUsage and the computed
VkTextureUsage) that do not always agree.  We simplify so that
default layout is stored in the texture itself.

Also remove some unnecessary code that is no longer necessary.
In particular, we shouldn't be doing a flush and wait for the
transition to complete before updating a sampler descriptor.
We just need to make sure the layout before it is accessed is
correctly given in the update struct.
2024-10-10 19:31:20 +00:00
Powei Feng
b1b2d7072d Revert "Release Filament 1.55.0" (#8191)
This reverts commit 4f5369cefa.

Reason: Initial attempt to release 1.55.0 failed due to a few
   small bugs.
2024-10-10 12:16:00 -07:00
Sungun Park
014e6bcbde Update hardware Handle (#8187)
In certain compilers, the assignment operators defined as default
doesn't automatically make a call to the parent's method if it's
user-defined.

Make this behavior explicit to avoid this edge case.

BUGS=[371980551]
2024-10-10 17:12:22 +00:00
Powei Feng
614dbb44d5 Make sure color attachment textures have copy-able usage set (#8184)
To ensure the source of readPixels() is properly copy-able, we
want the backing textures to be created with the right BLIT_SRC usage.
However, this was not documented in the API. We workaround the issue
to tag all color attachment textures as BLIT_SRC.

This workaround will be removed in the future. For now, violations of
this condition will elicit a warning being printed out.
2024-10-10 16:02:11 +00:00
Ben Doherty
40c0d59464 Metal: enhance assertions for buffer uploads (#8188) 2024-10-09 16:54:14 -07:00
mdagois
1b0ff3aadb vk: Transient attachment support (#8021) 2024-10-08 23:20:35 +00:00
Haneul Kim
164a25cac4 Update FreeFlightManipulator.h 2024-10-08 10:38:00 -07:00
Mathias Agopian
2bbcb6de4c fix potential shadow rendering bug
the problem stems from a mismatch between the shader code
and the cpu code. if the shader is configured to read the shadow
map, then the cpu must generate it, otherwise we can get stale data.

Wether the shader reads the shadow map depends on the shadow type.
For directional shadows, the shader needs the SRE variant + a
"shadow enabled" bit per cascade in the main UBO.
For punctual shadows, the shader only needs the SRE variant.

Because of all that, if the conditions are met on the CPU side for
the shader to access the shadow map, we must make sure to generate it,
but in the case the shadow map would be empty (e.g. no shadow receivers),
we need to initialize it (and we can skip some work in the case of VSM).

BUGS=[369908659]
2024-10-08 10:22:50 -07:00
Mathias Agopian
58aa74b4ae cleanup code so it's easier to understand
this should make it clearer how we set the per primitive variant
when SSR and shadowin is involved
2024-10-08 10:22:50 -07:00
Mathias Agopian
26b42a7685 fix typo when generating the SSR pass
this caused the HAS_SHADOWS flag to not be disabled, this didn't
actually cause a problem because shadowing and SSR share the same
SRE variant bit. But both should never be active together.
2024-10-08 10:22:50 -07:00
Mathias Agopian
99b55cb888 minor renaming to be more consistant 2024-10-08 10:22:50 -07:00
Powei Feng
4375ffb3e8 vk: remove unnecessary hiding of impl for DescriptorSetManager (#8170)
We remove one layer of indirection for clarity and very small bit
performance saving.
2024-10-07 19:06:38 +00:00
Mathias Agopian
e9aeb9312b fix shadow multiplier mode
when shadow multiplier was used, the material used the wrong
variant (unlit).
2024-10-04 15:18:23 -07:00
Benjamin Doherty
4be172050d Fix Android build 2024-10-04 13:45:22 -07:00
Ben Doherty
3b9fc8f751 Remove textureUseAfterFreePoolSize (#8163) 2024-10-04 10:33:09 -07:00
Mathias Agopian
3bd4c45d6e workaround Mesa glDeleteBuffers() bug
Mesa always clears the generic binding if the buffer deleted
is bound to an indexed binding, even if it's not bound to the
generic binding.

BUGS=[371324321]
2024-10-04 10:17:19 -07:00
Ben Doherty
1809aa7b11 Fix OpenGL ES 2.0 descriptor set crash (#8176) 2024-10-03 17:23:20 -07:00
Powei Feng
739d4007e2 Fix imported texture path (#8175)
We need to also account for external image textures that are
imported in texture creation.
2024-10-03 15:16:56 -07:00
Powei Feng
3ed9e8f2c0 Initialize bool in DescriptorSet.h (#8173)
This class has a ( = default) constructor and hence should have
explicit initialization in its definition.
2024-10-02 13:45:05 -07:00
Powei Feng
e77ae6ec2c Work around client descriptor set issues (#8171)
- We change GLDescriptorSet::Buffer default constructor to
  workaround a client's compiler set up issue.
- We removed the assert_invariant that checks that ubo/samplers
  are not changed after committed in DescriptorSet. This caused
  an existing client's build to crash.
2024-10-02 06:24:41 -07:00
Powei Feng
4f5369cefa Release Filament 1.55.0 2024-10-01 17:09:03 +00:00
Powei Feng
93d28bc16a Change std::memcpy to memcpy in SkinningBuffer (#8169) 2024-10-01 09:53:14 -07:00
Sungun Park
e85c22c6f5 Fix incomplete use of MI for debugCombineArrayTexture (#8168)
`commit` call is required before `use`, which became a new norm for the
new descriptor set design.
2024-09-30 10:54:10 -07:00
Powei Feng
2cade209b5 Add Descriptor Sets to Filament (#8165)
We add the concept of the descriptor set as a way to describe
shader resources into Filament. This is a comprehensive change
across Filament.

Info on descriptor sets is available here:
https://docs.vulkan.org/spec/latest/chapters/descriptorsets.html

Co-authored-by: Benjamin Doherty <bendoherty@google.com>
Co-authored-by: Mathias Agopian <mathias@google.com>
Co-authored-by: Sungun Park <sungunpark@google.com>
2024-09-27 23:20:20 -07:00
Powei Feng
3d4fc7852b github: add software rasterizer job for GL to presubmit (#8158)
We use Mesa's gallium swrast to render as the driver with
Filament's backend set to GL. We provide a few scripts to parse
the tests (as jsons) and run gltf_viewer to produce the rendering.
2024-09-26 16:06:40 -07:00
Powei Feng
7dc17980a3 addressed comment 2024-09-25 21:05:42 -07:00
Powei Feng
7da09a7f45 gl: add PlatformOSMesa as an offscreen context
For GL+Linux, PlatformGLX will try to open an X11 window
regardless of whether we are doing headless/offscreen rendering
or not.

Here we add an OSMesa platform, which will allow us to avoid
opening any window on Linux. This is particularly useful for
situation where a display is not available, like for CI.

One important detail is that even though we are displaying through
a window, we keep the SDL2 dependency in tact for gltf_viewer.
This is due to the fact that gltf_viewer is built upon
FilamentApp, which is heavily integrated with SDL2. This is mostly
ok since we won't be hitting any path for opening a window due to
gltf_viewer's existing support for headless mode.
2024-09-25 21:05:42 -07:00
Ben Doherty
b97221b8de Improve setFrameScheduled threading (#8139) 2024-09-25 10:43:19 -07:00
Benjamin Doherty
3f37efe4c9 Release Filament 1.54.5 2024-09-24 12:34:25 -07:00
Sungun Park
9d57ced452 Fix broken layerCount for some types of textures (#8151)
Currently mLayerCount for RenderTarget is always updated to the number
of depth for attachments, which incurs unintended behaviors for some
types of textures. i.e., 2d array, cubemap array, and 3d textures.

Fix this by updating mLayerCount only for multiview use case.

BUGS=[369165616]
2024-09-24 10:24:32 -07:00
Mathias Agopian
fe15a3047e make scissor() work the same way on all backends
scissor() works like on metal now, that is, it is disabled when a
render pass starts.

The GL backend already assumed this in debug mode. We cannot really run
into issues at the moment because every time we get a new 
MaterialInstance we set the scissor -- we do this both for the
color pass and the post-process passes. With this change we will be
able to skip setting the scissor altogether in a lot of cases.
2024-09-24 09:44:42 -07:00
Powei Feng
67f37d4c15 github: make windows build use all available cores
- Also used a smaller runner as the gains from the 32-core was
  not efficient when comparing output times.
- Clean-up:
    - Rename the android continuous to a proper name
    - set 'echo on' for the Windows release build so we'll know
      why the output asset does not get "moved" correclty.
2024-09-19 17:19:16 -07:00
Mathias Agopian
c5fe185834 KHR_create_context doesn't, in fact, imply HR_surfaceless_context 2024-09-19 15:56:56 -07:00
Powei Feng
4c5261106a github: upgrade runners on release/continuous
- Previously we also split Android build into 3, but because we
   get larger disk with large runners, we combine them again.
2024-09-18 22:36:51 -07:00
Powei Feng
2147a7a640 vk: workaround a renderStandaloneView issue
Found through testing that renderStandaloneView+vk+swiftshader
seems to cause synchronization issues, which results in incorrect
rendering. Here we workaround the issue by forcibly flush and
wait per renderStandaloneView call.

BUG=361822355
2024-09-17 22:47:46 -07:00
Powei Feng
dd7106bfcf Release Filament 1.54.4 2024-09-17 15:24:57 -07:00
Powei Feng
b26c5ef0dc github: upgrade presubmit runners
- Also migrate web/android builds to linux
2024-09-16 14:20:43 -07:00
Mathias Agopian
d16fc01cfe Add support for exr files in gltf_Vvewer 2024-09-15 23:08:58 -07:00
Powei Feng
0fd2436cd1 sample: fix two IBL loading crashes
- Both Scene and IBL are holding on to a skybox reference. We
   need to make sure the order they are destroyed in right order.
 - Reloading IBL should trigger resetting the indrect light in
   gltf_viewer.
2024-09-13 17:02:02 -07:00
Powei Feng
374fc6b519 github: use up-to-date actions/upload-artifact
v1.0.0 has been deprecated and is causing our builds to fail. We
update the action to use v4 (the most current and recommended
version).
2024-09-13 11:20:30 -07:00
Mathias Agopian
d4bb6b82df Support 3D texture attachments
BUGS=[365833134]
2024-09-12 12:11:29 -07:00
Mathias Agopian
351d77f87b update remote ui 2024-09-11 21:55:58 -07:00
Powei Feng
72ba2eee6d Release Filament 1.54.3 2024-09-10 12:26:44 -07:00
Sungun Park
5974065798 Prefer using C header (#8113)
Use stddef.h instead of cstddef for ptrdiff_t
2024-09-10 18:21:28 +00:00
Sungun Park
e9e7abe287 Add missing include (#8112)
ptrdiff_t was transiently included by type_traits. However, this is not
true for some cpp toolchains. Add the header inclusion explicitly.
2024-09-10 09:24:28 -07:00
Mathias Agopian
684d441ba7 fix FrameSkipper (#8110)
FrameSkipper was recently broken because of a typo resulting in 
the frame latency being always 3 instead of the default of 2.

This change also makes the maximum latency 2 instead of 3, because on
ANDROID, 3 can cause CPU throttling at seemingly random places on the
GL thread.
2024-09-09 22:19:25 -07:00
Sungun Park
37c615e249 Support multi-layered render target (#8108)
Clients can create a multi-layered render target that consists of array
textures, and use it as a custom render target.

A new sample app "hellostereo" demonstrates how to use this feature.
2024-09-09 20:27:58 +00:00
Powei Feng
8999b21187 Revert "reenable the SimplificationPass in spirv-opt"
This reverts commit 30387af61c.

Causes breakage on Pixel 8pro for 1P apps.
2024-09-09 10:21:53 -07:00
Powei Feng
78aa1c4b10 vk: use std::unordered_map in PipelineLayoutCache (#7990)
This is to address a weird map.find() miss that only happens on
ARM in release mode.

BUG=365159519

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

FIXES=[332425392]

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

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

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

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

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

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

* avoid calling wakeAll() when possible

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

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

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

* mActiveJobs fixes

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

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

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


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

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

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


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

Also made a couple of small improvements:

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

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

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

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

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

It attempts to load gltf files first then tries to load IBL files.
2024-06-27 23:14:13 +00:00
Ben Doherty
44b4de904e Add saveRawVariants flag to matc (#7949) 2024-06-27 14:36:03 -07:00
Tibor Hencz
a2fe9f745c Add ability to disable panning in the OrbitManipulator (#7928) 2024-06-27 00:05:14 +00:00
Benjamin Doherty
659b8b6e03 Release Filament 1.53.1 2024-06-25 11:46:46 -07:00
Ben Doherty
c73f4e64d5 Metal: use a bump allocator for vertex and index uploads (#7926) 2024-06-25 11:45:45 -07:00
Mathias Agopian
fe743523bf fix use-after-free dereference risk
Texture handles were resolved to pointers when updating a SamplerGroup,
as that point the handle was checked for use-after-free. However, the
texture could be destroyed later while still active in the SamplerGroup,
this would result in using the pointer which now contains garbage.

We now keep the handle and resolve the texture when binding samplers
to the program; which will also perform the use-after-free check.
2024-06-24 16:16:12 -07:00
Ryan
2c1044e812 Fix use-after-free of a std::mutex in PresentCallable. (#7934)
If a user is using `setFrameScheduledCallback()`, managing the provided PresentCallable during engine shutdown is tricky -- we'll likely get a final frame scheduled when we flush the engine's work queue, but the PresentCallable will schedule the final CAMetalDrawable to be released on main thread afterwards, even if we call `present(false)` to skip it. If the swap chain is destroyed before that main queue block gets executed, the mutex presenting that drawable will no longer exist, causing a crash.

To make things easier, store the std::mutex in a shared_ptr, so that a PresentCallable can safely outlive the FSwapChain instance that created it and clean itself up afterwards.

Alternatives considered, all of which seem unfortunate:
* Require users to clear out the callback before shutting down the engine, so that any final drawables are immediately discarded instead of using PresentCallable
* Require users to split up the engine teardown across two main queue blocks, ensuring that the PresentCallable cleanup executes before swapchains are destroyed
* Drop the PresentCallable on the ground and leak the memory
2024-06-24 11:00:58 -07:00
Sungun Park
db9183a105 Fix memory leak of asset loading for gltf_viewer (#7933)
When a user drags and drops a gltf file to the gltf_viewer window, it
loads the new asset.

While this happens, the function `checkAsset` tries parsing the gltf
file, but it doesn't free it. Fix this.
2024-06-21 10:37:01 -07:00
toddZ_CG
e4a0bb8fa0 Add support for KHR_materials_Specular (#7564)
Co-authored-by: Todd Zhang <toddzx@amazon.com>
Co-authored-by: Romain Guy <romain.guy@gmail.com>
Co-authored-by: Mathias Agopian <mathias@google.com>
2024-06-21 17:06:17 +00:00
Mathias Agopian
d15c53e530 an easier way to pipe vsync time to beginFrame
BUGS=[343764098]
2024-06-21 09:41:19 -07:00
Mathias Agopian
cc8a3ed96b fix typo that broke glsl compilation on device 2024-06-20 15:48:17 -07:00
Mathias Agopian
213eb6af9e Lit Materials can now specify the quality of SH computations
The number of SH bands used for the indirect light irradiance
computations can be set to 1, 2 or 3 (default) in Material::Builder.

For e.g. in lower-end devices w/ non HDR content, it might be
beneficial to set this value to 2.

BUGS=[341971013]
2024-06-20 10:25:04 -07:00
Mathias Agopian
e04c6d406f inject the missing packing/unpacking function in ESSL 3.0
FIXES=[343784713]
2024-06-18 13:37:20 -07:00
Mathias Agopian
cab799f531 matdbg would cause a crash if program was invalid 2024-06-18 00:27:24 -07:00
Benjamin Doherty
e95edef9d7 Release Filament 1.53.0 2024-06-17 17:26:47 -07:00
Grant Commodore
9cfef925bb Updates vulkan swapchain synchronization (#7915)
Abstracts the synchronization of the vulkan swapchain so that it is easier to override during the acquisition and presentation of images. A new structure (ImageSyncData) is created to hold swapchain synchronization data.

FIXES=[338303279]
2024-06-17 22:29:28 +00:00
Hanno J. Gödecke
3900fc6c37 feat: support getParameter from MaterialInstance (#7686) 2024-06-17 11:31:17 -07:00
Powei Feng
b8ec1658f3 vk: single output stream debug logging (#7925)
In some debug instances, it's nice to have the logs all go to one
output stream so that logs are ordered. We add a FVK_DEBUG flag
for this use case.
2024-06-14 22:31:45 +00:00
Grant Commodore
371bd1f01f Checks/enable if the multiview extension is supported (#7923)
Checks if the vulkan driver supports multiview. The result is stored in the vulkan context.

BUG=[332425392]
FIXES=[346860138]
2024-06-13 10:20:24 -07:00
Benjamin Doherty
df34f92f8f Release Filament 1.52.3 2024-06-11 16:42:10 -07:00
Powei Feng
43331d04e5 gltfio: [extended] Fix generated color default (#7917)
Fixes #7905
2024-06-11 12:21:14 -07:00
Powei Feng
9917d4b7d9 Null out pending edits once latched (#7916)
Otherwise, we'd keep swapping between the edited version and the
original, causing matdbg to not work.
2024-06-10 21:32:49 +00:00
Mathias Agopian
682150ceec add debug option to disable sub-passes 2024-06-10 13:18:59 -07:00
Powei Feng
af2ecf201f vk: workaround extension name length = 0 (adreno) (#7910) 2024-06-07 10:29:38 -07:00
Serge Metral
020668733a Adding the begin frame message for later xtrace post processing. (#7903)
BUG=343930963
2024-06-05 21:58:31 +00:00
Powei Feng
4c6d653d60 samples: Enable backend selection for hellopbr (#7898) 2024-06-05 19:15:45 +00:00
Mathias Agopian
d1022527ac export PanicStream since it's a public API 2024-06-05 11:00:06 -07:00
Romain Guy
68c7a6c7b6 Update Android dependencies to latest (#7902) 2024-06-04 22:33:02 +00:00
Romain Guy
f077fff012 Update actions/checkout to a supported version of Node.js (#7904) 2024-06-04 15:03:43 -07:00
Mathias Agopian
bea02427ed keep the normal transforms in float during skinning
FIXES=[342459864]
2024-06-04 13:37:47 -07:00
Mathias Agopian
b5d7de06bc Engine::unprotected() drops the command queue back to unprotected mode
FIXES=[344021154]
2024-06-04 11:46:00 -07:00
Mathias Agopian
749b063af3 PerformanceHintManager needs a valid Java thread
FIXES=[343965368, 343550453]
2024-06-04 10:22:21 -07:00
Ryan
7aefa99e06 Acquire a mutex before releasing CAMetalDrawables on main thread. (#7888)
PresentDrawable was moved to main thread by default in google#7535 and stopped
most crashes when a drawable is released. But there still appears to be crashes
if a drawable is released on main thread at the same time that -nextDrawable is
called from the Filament render thread. (It's likely that the drawable pool in
CAMetalLayer is completely non-thread-safe.)

So, add a mutex to the swapchain and always acquire it before creating or
releasing a CAMetalDrawable.

Users can opt out of this behavior by passing
-DFILAMENT_LOCK_METAL_DRAWABLE_POOL=0.
2024-06-03 19:37:46 +00:00
Sungun Park
3c46788e06 Release Filament 1.52.2 2024-06-03 18:10:27 +00:00
Ben Doherty
51d749f451 Deprecate use of hat-trie (#7889) 2024-05-30 15:45:33 -07:00
Powei Feng
343be60eb3 vk: flush commands in terminate() (#7890)
Fixes #7866
2024-05-30 17:29:19 +00:00
Powei Feng
783c35e85b Release Filament 1.52.1 2024-05-29 16:19:30 -07:00
Powei Feng
278e706d20 gltfio: fix invalid gltf crash (#7885)
Invalid gltf but valid json should not crash but
return null for asset.

Fixes #7868
2024-05-27 21:33:29 +00:00
Ben Doherty
cf91e42847 Switch ASSERT macros to new stream API (#7881) 2024-05-24 20:46:34 +00:00
Benjamin Doherty
3ec2249b2a Revert "Metal: implement more accurate buffer tracking (#7839)"
This reverts commit 54a800a25d.
2024-05-24 13:11:22 -07:00
Mathias Agopian
a52ae3a7ef fix a few typos in the new panic apis 2024-05-24 12:00:22 -07:00
Ryan
b4b702b977 Throw an exception when failing to build a Metal render pipeline state. (#7878)
Currently, if this fails we log the error message to stderr (which
doesn't get captured by most crash reporting systems) and then crash in
a postcondition assert. By including the error message in an exception
reason and throwing an ObjC exception, we get better discoverability of
error causes.

(Building a render pipeline state from shaders is usually when a shader
actually gets JITted from LLVM IR to GPU-specific code, so if we
accidentally used a feature that's not available on the local GPU, we'll
find out about it here.)
2024-05-23 11:14:29 -07:00
Mathias Agopian
75158847f7 A new stream-based Panic API
going forward, instead of using the printf style syntax for panics
we use the c++ stream syntax

The new macros that replace ASSERT_*CONDITON are

FILAMENT_CHECK_PRECONDITON
FILAMENT_CHECK_POSTCONDITION
FILAMENT_CHECK_ARITIHMETIC

Example usage:

FILAMENT_CHECK_PRECONDITON(condition) << "Message";

It's also now possible to define FILAMENT_PANIC_USES_ABSL=1 to redirect
all these calls to Abseil's CHECK() macro.
2024-05-22 12:31:18 -07:00
Minjae Kim
ddf1d422bc add explicit headers for supporting libstdc++ 2024-05-22 10:40:26 -07:00
Benjamin Doherty
bbb2e1a454 Bump MATERIAL_VERSION to 52 2024-05-21 12:50:35 -07:00
Benjamin Doherty
c7202c575a Release Filament 1.52.0 2024-05-21 12:47:58 -07:00
Powei Feng
8cfdab0c28 Fix stereo variant defines in common_getters (#7879)
This caused a breakage in shader validation at runtime. Repro:
  - Remove ./out
  - ./build.sh release gltf_viewer
  - run gltf_viewer
2024-05-21 17:34:05 +00:00
Benjamin Doherty
180c326bb7 Include sstream.h in distribution headers 2024-05-21 10:11:44 -07:00
Mathias Agopian
7d80975c3c add getReasonLiteral() on TPanic
currently it only returns the format string.
2024-05-17 16:53:31 -07:00
Sungun Park
813e6f805b Update combine_multiview_images flag (#7867)
Set combine_multiview_images to false by default as it's the desirable
setting for most Android devices.

Set the flag to true for GUI by default.

Put the `Combine Multiview Images` checkbox under the `Stereo mode` box
for an easier access.
2024-05-17 19:42:23 +00:00
Powei Feng
450644ccd5 Add vk/gl conditions for enabling clip distance (#7861) 2024-05-17 17:39:32 +00:00
Mathias Agopian
979421c019 fix/remove wrong asserts 2024-05-17 10:01:50 -07:00
Mathias Agopian
18ccf0cd8d change the morphing API so it uses only one buffer per renderable
The current API allowed to have a buffer for each primitive in a
renderable. We instead restrict the API so that there is a single 
MorphTargetBuffer for the whole renderable, shared by all primitives.
The buffer can be shared thanks to the "offset" parameter on
setMorphTargetBufferAt().

Also
- fix FMorphTargetBuffer::updateDataAt()
- add support for the "offset" parameter of setMorphTargetBufferAt()
2024-05-16 14:11:07 -07:00
Ben Doherty
5b80407f6c Implement push constants for Metal (#7858) 2024-05-16 13:20:12 -07:00
Powei Feng
a9f3971989 Refactor stereo build flags and configs (#7857)
- Add option to build.sh to build for paritcular stereo
   techniques (default to NONE). Only applies to samples.
 - Consoldiate viewer checkbox for debugging stereo rendering
 - Add DriverConfig flag for stereoscopic type so that it can
   be used to determine availability of the feature and
   (to be completed) enable corresponding GPU features.

Co-authored-by: Mathias Agopian <mathias@google.com>
2024-05-16 17:37:27 +00:00
Powei Feng
ab12984912 Add Mesa software rasterizer BUILD.md instructions (#7860) 2024-05-16 10:03:50 -07:00
Ben Doherty
1d4f1fe71f Metal, fix callbacks being called only once (#7856) 2024-05-15 10:50:10 -07:00
Mathias Agopian
c93aa4c90d minor libutils improvements 2024-05-14 13:22:38 -07:00
Mathias Agopian
499939ed3c backend: bindPipeline now takes const& 2024-05-14 13:21:38 -07:00
Mathias Agopian
6be97ee01d backend: zero-cost ES2 draw()
we use a different hook for the draw() call when on an ES2 context,
this eliminates completely the overhead of supporting ES2 for the draw
call. draw calls are expected to be the most common calls.
2024-05-14 13:21:24 -07:00
Powei Feng
7267696cbf vk: fix dynamic scissor validation error (#7853) 2024-05-14 19:08:50 +00:00
Powei Feng
85eb724a90 Reduce explicit swiftshader paths (#7848)
- Use custom ICD path to enable Swiftshader instead of
   specifying direct path to the lib.
   - Remove unused `swiftshader` directory in `build`
   - Remove swiftshader options in `build.sh` and cmakefiles
   - Change BUILD.md
 - Correctly handle XCB-only swapchain surface in VulkanPlatform
   for swiftshader.
 - Refactor `VulkanPlatform::ExtensionSet` so that `utils::CString`
   is used instead of string_view, so that we don't get into
   tricky lifetime issues with `const char*`
2024-05-14 17:40:54 +00:00
Mathias Agopian
6dd6db89d5 align android libraries to 16 KiB 2024-05-13 23:14:32 -07:00
Mathias Agopian
c76f67c139 fix typo checking FILAMENT_ENABLE_MATDBG
this macro must be checked with #if not #ifdef
2024-05-13 23:14:13 -07:00
679 changed files with 50589 additions and 33949 deletions

View File

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

View File

@@ -0,0 +1,9 @@
name: 'ubuntu apt add deb-src'
runs:
using: "composite"
steps:
- name: "ubuntu apt add deb-src"
run: |
echo "deb-src http://archive.ubuntu.com/ubuntu jammy main restricted universe" | sudo tee /etc/apt/sources.list.d/my.list
sudo apt-get update
shell: bash

View File

@@ -10,30 +10,13 @@ on:
jobs:
build-android:
name: build-android
runs-on: macos-14
# We intentially use a larger runner here to enable larger disk space
# (standard linux runner will fail on disk space and faster build time).
runs-on: ubuntu-22.04-32core
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/setup-java@v3
- uses: actions/checkout@v4.1.6
- name: Run Android Continuous
uses: ./.github/actions/android-continuous
with:
distribution: 'temurin'
java-version: '17'
- name: Run build script
run: |
cd build/android && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v1.0.0
with:
name: filament-android
path: out/filament-android-release.aar
- uses: actions/upload-artifact@v1.0.0
with:
name: filamat-android-full
path: out/filamat-android-release.aar
- uses: actions/upload-artifact@v1.0.0
with:
name: gltfio-android-release
path: out/gltfio-android-release.aar
- uses: actions/upload-artifact@v1.0.0
with:
name: filament-utils-android-release
path: out/filament-utils-android-release.aar
build-abi: armeabi-v7a,arm64-v8a,x86_64

View File

@@ -10,14 +10,14 @@ on:
jobs:
build-ios:
name: build-ios
runs-on: macos-14
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/ios && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v1.0.0
- uses: actions/upload-artifact@v4
with:
name: filament-ios
path: out/filament-release-ios.tgz

View File

@@ -10,14 +10,14 @@ on:
jobs:
build-linux:
name: build-linux
runs-on: ubuntu-22.04
runs-on: ubuntu-22.04-16core
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/linux && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v1.0.0
- uses: actions/upload-artifact@v4
with:
name: filament-linux
path: out/filament-release-linux.tgz

View File

@@ -10,14 +10,14 @@ on:
jobs:
build-mac:
name: build-mac
runs-on: macos-14
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/mac && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v1.0.0
- uses: actions/upload-artifact@v4
with:
name: filament-mac
path: out/filament-release-darwin.tgz

View File

@@ -13,7 +13,7 @@ jobs:
name: npm-deploy
runs-on: macos-14
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
with:
ref: ${{ github.event.inputs.release_tag }}
# Setup .npmrc file to publish to npm

View File

@@ -15,10 +15,10 @@ jobs:
strategy:
matrix:
os: [macos-14, ubuntu-22.04]
os: [macos-14-xlarge, ubuntu-22.04-16core]
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
WORKFLOW_OS=`echo \`uname\` | sed "s/Darwin/mac/" | tr [:upper:] [:lower:]`
@@ -29,10 +29,10 @@ jobs:
build-windows:
name: build-windows
runs-on: windows-2019
runs-on: win-2019-16core
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
build\windows\build-github.bat presubmit
@@ -40,24 +40,26 @@ jobs:
build-android:
name: build-android
runs-on: macos-14
runs-on: ubuntu-22.04-16core
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Run build script
# Only build 1 64 bit target during presubmit to cut down build times during presubmit
# Continuous builds will build everything
run: |
cd build/android && printf "y" | ./build.sh presubmit
cd build/android && printf "y" | ./build.sh presubmit arm64-v8a
build-ios:
name: build-iOS
runs-on: macos-14
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/ios && printf "y" | ./build.sh presubmit
@@ -67,10 +69,25 @@ jobs:
build-web:
name: build-web
runs-on: macos-14
runs-on: ubuntu-22.04-16core
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/web && printf "y" | ./build.sh presubmit
test-renderdiff:
name: test-renderdiff
runs-on: ubuntu-22.04-32core
steps:
- uses: actions/checkout@v4.1.6
- uses: ./.github/actions/ubuntu-apt-add-src
- name: Run script
run: |
source ./build/linux/ci-common.sh && bash test/renderdiff_tests.sh
- uses: actions/upload-artifact@v4
with:
name: presubmit-renderdiff-result
path: ./out/renderdiff_tests

View File

@@ -31,7 +31,7 @@ jobs:
strategy:
matrix:
os: [macos-14, ubuntu-22.04]
os: [macos-14-xlarge, ubuntu-22.04-32core]
steps:
- name: Decide Git ref
@@ -41,7 +41,7 @@ jobs:
TAG=${REF##*/}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
with:
ref: ${{ steps.git_ref.outputs.ref }}
- name: Run build script
@@ -65,7 +65,7 @@ jobs:
build-web:
name: build-web
runs-on: macos-14
runs-on: ubuntu-22.04-16core
if: github.event_name == 'release' || github.event.inputs.platform == 'web'
steps:
@@ -76,7 +76,7 @@ jobs:
TAG=${REF##*/}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
with:
ref: ${{ steps.git_ref.outputs.ref }}
- name: Run build script
@@ -98,7 +98,7 @@ jobs:
build-android:
name: build-android
runs-on: macos-14
runs-on: ubuntu-22.04-16core
if: github.event_name == 'release' || github.event.inputs.platform == 'android'
steps:
@@ -109,7 +109,7 @@ jobs:
TAG=${REF##*/}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
with:
ref: ${{ steps.git_ref.outputs.ref }}
- uses: actions/setup-java@v3
@@ -120,7 +120,7 @@ jobs:
env:
TAG: ${{ steps.git_ref.outputs.tag }}
run: |
cd build/android && printf "y" | ./build.sh release
cd build/android && printf "y" | ./build.sh release armeabi-v7a,arm64-v8a,x86,x86_64
cd ../..
mv out/filament-android-release.aar out/filament-${TAG}-android.aar
mv out/filamat-android-release.aar out/filamat-${TAG}-android.aar
@@ -152,7 +152,7 @@ jobs:
build-ios:
name: build-ios
runs-on: macos-14
runs-on: macos-14-xlarge
if: github.event_name == 'release' || github.event.inputs.platform == 'ios'
steps:
@@ -163,7 +163,7 @@ jobs:
TAG=${REF##*/}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
with:
ref: ${{ steps.git_ref.outputs.ref }}
- name: Run build script
@@ -185,7 +185,7 @@ jobs:
build-windows:
name: build-windows
runs-on: windows-2019
runs-on: windows-2019-32core
if: github.event_name == 'release' || github.event.inputs.platform == 'windows'
steps:
@@ -197,7 +197,7 @@ jobs:
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
shell: bash
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
with:
ref: ${{ steps.git_ref.outputs.ref }}
- name: Run build script
@@ -205,6 +205,7 @@ jobs:
TAG: ${{ steps.git_ref.outputs.tag }}
run: |
build\windows\build-github.bat release
echo on
move out\filament-windows.tgz out\filament-%TAG%-windows.tgz
shell: cmd
- uses: actions/github-script@v6

View File

@@ -10,14 +10,14 @@ on:
jobs:
build-web:
name: build-web
runs-on: macos-14
runs-on: ubuntu-22.04-16core
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/web && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v1.0.0
- uses: actions/upload-artifact@v4
with:
name: filament-web
path: out/filament-release-web.tgz

View File

@@ -10,15 +10,15 @@ on:
jobs:
build-windows:
name: build-windows
runs-on: windows-2019
runs-on: windows-2019-32core
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
build\windows\build-github.bat continuous
shell: cmd
- uses: actions/upload-artifact@v1.0.0
- uses: actions/upload-artifact@v4
with:
name: filament-windows
path: out/filament-windows.tgz

View File

@@ -73,7 +73,6 @@ The following CMake options are boolean options specific to Filament:
- `FILAMENT_SUPPORTS_VULKAN`: Include the Vulkan backend
- `FILAMENT_INSTALL_BACKEND_TEST`: Install the backend test library so it can be consumed on iOS
- `FILAMENT_USE_EXTERNAL_GLES3`: Experimental: Compile Filament against OpenGL ES 3
- `FILAMENT_USE_SWIFTSHADER`: Compile Filament against SwiftShader
- `FILAMENT_SKIP_SAMPLES`: Don't build sample apps
To turn an option on or off:
@@ -426,7 +425,7 @@ value is the desired roughness between 0 and 1.
## Generating C++ documentation
To generate the documentation you must first install `doxygen` and `graphviz`, then run the
To generate the documentation you must first install `doxygen` and `graphviz`, then run the
following commands:
```shell
@@ -436,32 +435,62 @@ doxygen docs/doxygen/filament.doxygen
Finally simply open `docs/html/index.html` in your web browser.
## SwiftShader
## Software Rasterization
To try out Filament's Vulkan support with SwiftShader, first build SwiftShader and set the
`SWIFTSHADER_LD_LIBRARY_PATH` variable to the folder that contains `libvk_swiftshader.dylib`:
We have tested swiftshader and Mesa for software rasterization on the Vulkan/GL backends.
To use this for Vulkan, please first make sure that the [Vulkan SDK](https://www.lunarg.com/vulkan-sdk/) is
installed on your machine. If you are doing a manual installation of the SDK on Linux, you will have
to source `setup-env.sh` in the SDK's root folder to make sure the Vulkan loader is the first lib loaded.
### Swiftshader (Vulkan) [tested on macOS and Linux]
First, build SwiftShader
```shell
git clone https://github.com/google/swiftshader.git
cd swiftshader/build
cmake .. && make -j
export SWIFTSHADER_LD_LIBRARY_PATH=`pwd`
```
Next, go to your Filament repo and use the [easy build](#easy-build) script with `-t`.
and then set `VK_ICD_FILENAMES` to the ICD json produced in the build. For example,
```shell
export VK_ICD_FILENAMES=/Users/user/swiftshader/build/Darwin/vk_swiftshader_icd.json
```
## SwiftShader for CI
Build and run Filament as usual and specify the Vulkan backend when creating the Engine.
Continuous testing turnaround can be quite slow if you need to build SwiftShader from scratch, so we
provide an Ubuntu-based Docker image that has it already built. The Docker image also includes
everything necessary for building Filament. You can fetch and run the image as follows:
### Mesa's LLVMPipe (GL) and Lavapipe (Vulkan) [tested on Linux]
We will only cover steps that build Mesa from source. The official documentation of Mesa mentioned
that in general precompiled libraries [are **not** made available](https://docs.mesa3d.org/precompiled.html).
Download the repo and make sure you have the build depedencies. For example (assuming an Ubuntu/Debian distro),
```shell
git clone https://gitlab.freedesktop.org/mesa/mesa.git
sudo apt-get build-dep mesa
```
To build both the GL and Vulkan rasterizers,
```shell
docker pull ghcr.io/filament-assets/swiftshader
docker run -it ghcr.io/filament-assets/swiftshader
cd mesa
mkdir -p out
meson setup builddir/ -Dprefix=$(pwd)/out -Dglx=xlib -Dgallium-drivers=swrast -Dvulkan-drivers=swrast
meson install -C builddir/
```
To do more with the container, see the helper script at `build/swiftshader/test.sh`.
For GL, we need to ensure that we load the GL lib from the mesa output directory. For example, to run
the debug `gltf_viewer`, we would execute
```shell
LD_LIBRARY_PATH=/Users/user/mesa/out/lib/x86_64-linux-gnu \
./out/cmake-debug/samples/gltf_viewer -a opengl
```
If you are a team member, you can update the public image to the latest SwiftShader by
following the instructions at the top of `build/swiftshader/Dockerfile`.
For Vulkan, we need to set the path to the ICD json, which tells the loader where to find the driver
library. To run `gltf_viewer`, we would execute
```shell
VK_ICD_FILENAMES=/Users/user/mesa/out/share/vulkan/icd.d/lvp_icd.x86_64.json \
./out/cmake-debug/samples/gltf_viewer -a vulkan
```

View File

@@ -21,8 +21,6 @@ project(TNT)
# ==================================================================================================
option(FILAMENT_USE_EXTERNAL_GLES3 "Experimental: Compile Filament against OpenGL ES 3" OFF)
option(FILAMENT_USE_SWIFTSHADER "Compile Filament against SwiftShader" OFF)
option(FILAMENT_ENABLE_LTO "Enable link-time optimizations if supported by the compiler" OFF)
option(FILAMENT_SKIP_SAMPLES "Don't build samples" OFF)
@@ -47,6 +45,8 @@ option(FILAMENT_ENABLE_FEATURE_LEVEL_0 "Enable Feature Level 0" ON)
option(FILAMENT_ENABLE_MULTIVIEW "Enable multiview for Filament" OFF)
option(FILAMENT_SUPPORTS_OSMESA "Enable OSMesa (headless GL context) for Filament" OFF)
set(FILAMENT_NDK_VERSION "" CACHE STRING
"Android NDK version or version prefix to be used when building for Android."
)
@@ -75,6 +75,10 @@ set(FILAMENT_BACKEND_DEBUG_FLAG "" CACHE STRING
"A debug flag meant for enabling/disabling backend debugging paths"
)
set(FILAMENT_OSMESA_PATH "" CACHE STRING
"Path to the OSMesa header and lib"
)
# Enable exceptions by default in spirv-cross.
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS OFF)
@@ -134,22 +138,27 @@ else()
endif()
if (LINUX)
if (NOT FILAMENT_OSMESA_PATH STREQUAL "")
if (NOT EXISTS ${FILAMENT_OSMESA_PATH}/)
message(FATAL_ERROR "Cannot find specified OSMesa build directory: ${FILAMENT_OSMESA_PATH}")
endif()
set(FILAMENT_SUPPORTS_OSMESA TRUE)
endif()
if (FILAMENT_SUPPORTS_WAYLAND)
add_definitions(-DFILAMENT_SUPPORTS_WAYLAND)
set(FILAMENT_SUPPORTS_X11 FALSE)
elseif (FILAMENT_SUPPORTS_EGL_ON_LINUX)
add_definitions(-DFILAMENT_SUPPORTS_EGL_ON_LINUX)
set(FILAMENT_SUPPORTS_X11 FALSE)
elseif (FILAMENT_SUPPORTS_OSMESA)
set(FILAMENT_SUPPORTS_X11 FALSE)
add_definitions(-DFILAMENT_SUPPORTS_OSMESA)
else ()
if (FILAMENT_SUPPORTS_XCB)
add_definitions(-DFILAMENT_SUPPORTS_XCB)
endif()
# Default Swiftshader build does not enable the xlib extension
if (FILAMENT_SUPPORTS_XLIB AND FILAMENT_USE_SWIFTSHADER)
set(FILAMENT_SUPPORTS_XLIB OFF)
endif()
if (FILAMENT_SUPPORTS_XLIB)
add_definitions(-DFILAMENT_SUPPORTS_XLIB)
endif()
@@ -327,10 +336,6 @@ if (FILAMENT_SUPPORTS_EGL_ON_LINUX)
set(EGL TRUE)
endif()
if (FILAMENT_USE_SWIFTSHADER)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DFILAMENT_USE_SWIFTSHADER")
endif()
if (WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_USE_MATH_DEFINES=1")
endif()
@@ -451,8 +456,13 @@ endif()
if (NOT WEBGL)
set(GC_SECTIONS "-Wl,--gc-sections")
endif()
set(B_SYMBOLIC_FUNCTIONS "-Wl,-Bsymbolic-functions")
if (ANDROID)
set(BINARY_ALIGNMENT "-Wl,-z,max-page-size=16384")
endif()
if (APPLE)
set(GC_SECTIONS "-Wl,-dead_strip")
set(B_SYMBOLIC_FUNCTIONS "")
@@ -466,7 +476,7 @@ if (APPLE)
endif()
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GC_SECTIONS}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GC_SECTIONS} ${B_SYMBOLIC_FUNCTIONS}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GC_SECTIONS} ${B_SYMBOLIC_FUNCTIONS} ${BINARY_ALIGNMENT}")
if (WEBGL_PTHREADS)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pthread")
@@ -530,13 +540,15 @@ else()
endif()
# This only affects the prebuilt shader files in gltfio and samples, not filament library.
# The value can be either "instanced" or "multiview".
set(FILAMENT_SAMPLES_STEREO_TYPE "instanced" CACHE STRING
# The value can be either "instanced", "multiview", or "none"
set(FILAMENT_SAMPLES_STEREO_TYPE "none" CACHE STRING
"Stereoscopic type that shader files in gltfio and samples are built for."
)
string(TOLOWER "${FILAMENT_SAMPLES_STEREO_TYPE}" FILAMENT_SAMPLES_STEREO_TYPE)
if (NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "instanced" AND NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview")
message(FATAL_ERROR "Invalid stereo type: \"${FILAMENT_SAMPLES_STEREO_TYPE}\" choose either \"instanced\" or \"multiview\" ")
if (NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "instanced"
AND NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview"
AND NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "none")
message(FATAL_ERROR "Invalid stereo type: \"${FILAMENT_SAMPLES_STEREO_TYPE}\" choose either \"instanced\", \"multiview\", or \"none\" ")
endif ()
# Compiling samples for multiview implies enabling multiview feature as well.
@@ -678,21 +690,6 @@ else()
set(IMPORT_EXECUTABLES ${FILAMENT}/${IMPORT_EXECUTABLES_DIR}/ImportExecutables-${CMAKE_BUILD_TYPE}.cmake)
endif()
# ==================================================================================================
# Try to find Vulkan if the SDK is installed, otherwise fall back to the bundled version.
# This needs to stay in our top-level CMakeLists because it sets up variables that are used by the
# "bluevk" and "samples" targets.
# ==================================================================================================
if (FILAMENT_USE_SWIFTSHADER)
if (NOT FILAMENT_SUPPORTS_VULKAN)
message(ERROR "SwiftShader is only useful when Vulkan is enabled.")
endif()
find_library(SWIFTSHADER_VK NAMES vk_swiftshader HINTS "$ENV{SWIFTSHADER_LD_LIBRARY_PATH}")
message(STATUS "Found SwiftShader VK library in: ${SWIFTSHADER_VK}.")
add_definitions(-DFILAMENT_VKLIBRARY_PATH=\"${SWIFTSHADER_VK}\")
endif()
# ==================================================================================================
# Common Functions
# ==================================================================================================
@@ -754,7 +751,6 @@ add_subdirectory(${FILAMENT}/filament)
add_subdirectory(${FILAMENT}/shaders)
add_subdirectory(${EXTERNAL}/basisu/tnt)
add_subdirectory(${EXTERNAL}/civetweb/tnt)
add_subdirectory(${EXTERNAL}/hat-trie/tnt)
add_subdirectory(${EXTERNAL}/imgui/tnt)
add_subdirectory(${EXTERNAL}/robin-map/tnt)
add_subdirectory(${EXTERNAL}/smol-v/tnt)

View File

@@ -7,3 +7,4 @@ for next branch cut* header.
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- vk: fix stage pool gc logic

View File

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

View File

@@ -7,6 +7,72 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.56.1
## v1.56.0
- backend: descriptor layouts distinguish samplers and external samplers (b/376089915) [⚠️ **New Material Version**]
## v1.55.1
## v1.55.0
- Add descriptor sets to describe shader resources. [⚠️ **New Material Version**]
## v1.54.5
## v1.54.4
- Add support for multi-layered render target with array textures.
## v1.54.3
## v1.54.2
- Add a `name` API to Filament objects for debugging handle use-after-free assertions
## v1.54.1
## v1.54.0
- materials: add a new `stereoscopicType` material parameter. [⚠️ **New Material Version**]
- Fix a crash when compiling shaders on IMG devices
## v1.53.5
- engine: Fix bug causing certain sampler parameters to not be applied correctly in GLES 2.0 and on
certain GLES 3.0 drivers.
## v1.53.4
## v1.53.3
- Add drag and drop support for IBL files for desktop gltf_viewer.
## v1.53.2
## v1.53.1
## v1.53.0
- engine: fix skinning normals with large transforms (b/342459864) [⚠️ **New Material Version**]
## v1.52.3
## v1.52.2
## v1.52.1
- Add instructions for using Mesa for software rasterization
## v1.51.9
@@ -594,7 +660,7 @@ Instead, if you are authoring a PR for the main branch, add your release note to
- engine: Binary size improvements.
- engine: Add basic support for instanced renderables [**NEW API**].
- engine: Fix, first imaged passsed to `Stream::SetAcquiredImage` is ignored and leaked.
- engine: Fix, first imaged passed to `Stream::SetAcquiredImage` is ignored and leaked.
- Vulkan: Robustness improvements.
- Java: Fix, lookAt z axis negated.
- gltfio: Be graceful when model has > 4 weights per vert.

View File

@@ -83,12 +83,12 @@ buildscript {
'minSdk': 21,
'targetSdk': 34,
'compileSdk': 34,
'kotlin': '1.9.21',
'kotlin_coroutines': '1.7.3',
'buildTools': '34.0.0',
'ndk': '26.1.10909125',
'androidx_core': '1.12.0',
'androidx_annotations': '1.7.0'
'kotlin': '2.0.21',
'kotlin_coroutines': '1.9.0',
'buildTools': '35.0.0',
'ndk': '27.0.11718014',
'androidx_core': '1.13.1',
'androidx_annotations': '1.9.0'
]
ext.deps = [
@@ -104,7 +104,7 @@ buildscript {
]
dependencies {
classpath 'com.android.tools.build:gradle:8.2.0'
classpath 'com.android.tools.build:gradle:8.6.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
}

View File

@@ -38,6 +38,7 @@ set(FILAMAT_INCLUDE_DIRS
include_directories(${FILAMENT_DIR}/include)
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_SOURCE_DIR}/libfilamat-jni.map")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
add_library(filamat-jni SHARED src/main/cpp/MaterialBuilder.cpp)
target_include_directories(filamat-jni PRIVATE ${FILAMAT_INCLUDE_DIRS})

View File

@@ -59,6 +59,7 @@ endif()
set(VERSION_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/libfilament-jni.map")
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${VERSION_SCRIPT}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
add_library(filament-jni SHARED
src/main/cpp/BufferObject.cpp

View File

@@ -420,6 +420,13 @@ Java_com_google_android_filament_Engine_nSetPaused(JNIEnv*, jclass,
engine->setPaused(paused);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Engine_nUnprotected(JNIEnv*, jclass,
jlong nativeEngine, jboolean paused) {
Engine* engine = (Engine*) nativeEngine;
engine->unprotected();
}
// Managers...
extern "C" JNIEXPORT jlong JNICALL
@@ -492,6 +499,37 @@ Java_com_google_android_filament_Engine_nGetActiveFeatureLevel(JNIEnv *, jclass,
return (jint)engine->getActiveFeatureLevel();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nHasFeatureFlag(JNIEnv *env, jclass clazz,
jlong nativeEngine, jstring name_) {
Engine* engine = (Engine*) nativeEngine;
const char *name = env->GetStringUTFChars(name_, 0);
std::optional<bool> result = engine->getFeatureFlag(name);
env->ReleaseStringUTFChars(name_, name);
return result.has_value();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nSetFeatureFlag(JNIEnv *env, jclass clazz,
jlong nativeEngine, jstring name_, jboolean value) {
Engine* engine = (Engine*) nativeEngine;
const char *name = env->GetStringUTFChars(name_, 0);
jboolean result = engine->setFeatureFlag(name, (bool)value);
env->ReleaseStringUTFChars(name_, name);
return result;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nGetFeatureFlag(JNIEnv *env, jclass clazz,
jlong nativeEngine, jstring name_) {
Engine* engine = (Engine*) nativeEngine;
const char *name = env->GetStringUTFChars(name_, 0);
std::optional<bool> result = engine->getFeatureFlag(name);
env->ReleaseStringUTFChars(name_, name);
return result.value_or(false); // we should never fail here
}
extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_Engine_nCreateBuilder(JNIEnv*,
jclass) {
Engine::Builder* builder = new Engine::Builder{};
@@ -513,13 +551,12 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderConfig(JNIEnv*,
jclass, jlong nativeBuilder, jlong commandBufferSizeMB, jlong perRenderPassArenaSizeMB,
jlong driverHandleArenaSizeMB, jlong minCommandBufferSizeMB, jlong perFrameCommandsSizeMB,
jlong jobSystemThreadCount,
jlong textureUseAfterFreePoolSize, jboolean disableParallelShaderCompile,
jlong jobSystemThreadCount, jboolean disableParallelShaderCompile,
jint stereoscopicType, jlong stereoscopicEyeCount,
jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge,
jboolean disableHandleUseAfterFreeCheck,
jint preferredShaderLanguage,
jboolean forceGLES2Context) {
jboolean forceGLES2Context, jboolean assertNativeWindowIsValid) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
Engine::Config config = {
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
@@ -528,7 +565,6 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
.minCommandBufferSizeMB = (uint32_t) minCommandBufferSizeMB,
.perFrameCommandsSizeMB = (uint32_t) perFrameCommandsSizeMB,
.jobSystemThreadCount = (uint32_t) jobSystemThreadCount,
.textureUseAfterFreePoolSize = (uint32_t) textureUseAfterFreePoolSize,
.disableParallelShaderCompile = (bool) disableParallelShaderCompile,
.stereoscopicType = (Engine::StereoscopicType) stereoscopicType,
.stereoscopicEyeCount = (uint8_t) stereoscopicEyeCount,
@@ -537,6 +573,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
.disableHandleUseAfterFreeCheck = (bool) disableHandleUseAfterFreeCheck,
.preferredShaderLanguage = (Engine::Config::ShaderLanguage) preferredShaderLanguage,
.forceGLES2Context = (bool) forceGLES2Context,
.assertNativeWindowIsValid = (bool) assertNativeWindowIsValid,
};
builder->config(&config);
}
@@ -559,8 +596,24 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
builder->paused((bool) paused);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_Engine_nSetBuilderFeature(JNIEnv *env, jclass clazz,
jlong nativeBuilder, jstring name_, jboolean value) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
const char *name = env->GetStringUTFChars(name_, 0);
builder->feature(name, (bool)value);
env->ReleaseStringUTFChars(name_, name);
}
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_Engine_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
return (jlong) builder->build();
}
extern "C"
JNIEXPORT jlong JNICALL
Java_com_google_android_filament_Engine_getSteadyClockTimeNano(JNIEnv *env, jclass clazz) {
return (jlong)Engine::getSteadyClockTimeNano();
}

View File

@@ -25,12 +25,17 @@ using namespace filament;
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass,
jlong nativeEngine, jobject buffer_, jint size) {
jlong nativeEngine, jobject buffer_, jint size, jint shBandCount) {
Engine* engine = (Engine*) nativeEngine;
AutoBuffer buffer(env, buffer_, size);
Material* material = Material::Builder()
auto builder = Material::Builder();
if (shBandCount) {
builder.sphericalHarmonicsBandCount(shBandCount);
}
Material* material = builder
.package(buffer.getData(), buffer.getSize())
.build(*engine);
return (jlong) material;
}

View File

@@ -245,12 +245,18 @@ Java_com_google_android_filament_RenderableManager_nBuilderMorphing(JNIEnv*, jcl
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_RenderableManager_nBuilderSetMorphTargetBufferAt(JNIEnv*, jclass,
jlong nativeBuilder, int level, int primitiveIndex, jlong nativeMorphTargetBuffer,
int offset, int count) {
Java_com_google_android_filament_RenderableManager_nBuilderMorphingStandard(JNIEnv*, jclass,
jlong nativeBuilder, jlong nativeMorphTargetBuffer) {
RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder;
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
builder->morphing(level, primitiveIndex, morphTargetBuffer, offset, count);
builder->morphing(morphTargetBuffer);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_RenderableManager_nBuilderSetMorphTargetBufferOffsetAt(JNIEnv*, jclass,
jlong nativeBuilder, int level, int primitiveIndex, int offset) {
RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder;
builder->morphing(level, primitiveIndex, offset);
}
extern "C" JNIEXPORT void JNICALL
@@ -322,13 +328,12 @@ Java_com_google_android_filament_RenderableManager_nSetMorphWeights(JNIEnv* env,
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferAt(JNIEnv*,
Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferOffsetAt(JNIEnv*,
jclass, jlong nativeRenderableManager, jint i, int level, jint primitiveIndex,
jlong nativeMorphTargetBuffer, jint offset, jint count) {
jlong, jint offset) {
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level,
(size_t) primitiveIndex, morphTargetBuffer, (size_t) offset, (size_t) count);
rm->setMorphTargetBufferOffsetAt((RenderableManager::Instance) i, (uint8_t) level,
(size_t) primitiveIndex, (size_t) offset);
}
extern "C" JNIEXPORT jint JNICALL

View File

@@ -28,6 +28,14 @@
using namespace filament;
using namespace backend;
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Renderer_nSkipFrame(JNIEnv *, jclass, jlong nativeRenderer,
jlong vsyncSteadyClockTimeNano) {
Renderer *renderer = (Renderer *) nativeRenderer;
renderer->skipFrame(uint64_t(vsyncSteadyClockTimeNano));
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Renderer_nBeginFrame(JNIEnv *, jclass, jlong nativeRenderer,
jlong nativeSwapChain, jlong frameTimeNanos) {
@@ -187,3 +195,10 @@ Java_com_google_android_filament_Renderer_nSetPresentationTime(JNIEnv *, jclass
Renderer *renderer = (Renderer *) nativeRenderer;
renderer->setPresentationTime(monotonicClockNanos);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Renderer_nSetVsyncTime(JNIEnv *, jclass,
jlong nativeRenderer, jlong steadyClockTimeNano) {
Renderer *renderer = (Renderer *) nativeRenderer;
renderer->setVsyncTime(steadyClockTimeNano);
}

View File

@@ -222,6 +222,20 @@ Java_com_google_android_filament_View_nIsFrontFaceWindingInverted(JNIEnv*,
return static_cast<jboolean>(view->isFrontFaceWindingInverted());
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nSetTransparentPickingEnabled(JNIEnv*,
jclass, jlong nativeView, jboolean enabled) {
View* view = (View*) nativeView;
view->setTransparentPickingEnabled(enabled);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_View_nIsTransparentPickingEnabled(JNIEnv*,
jclass, jlong nativeView) {
View* view = (View*) nativeView;
return static_cast<jboolean>(view->isTransparentPickingEnabled());
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nSetAmbientOcclusion(JNIEnv*, jclass, jlong nativeView, jint ordinal) {
View* view = (View*) nativeView;
@@ -531,3 +545,12 @@ Java_com_google_android_filament_View_nGetFogEntity(JNIEnv *env, jclass clazz,
View *view = (View *) nativeView;
return (jint)view->getFogEntity().getId();
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nClearFrameHistory(JNIEnv *env, jclass clazz,
jlong nativeView, jlong nativeEngine) {
View *view = (View *) nativeView;
Engine *engine = (Engine *) nativeEngine;
view->clearFrameHistory(*engine);
}

View File

@@ -159,9 +159,12 @@ public class Engine {
};
/**
* The type of technique for stereoscopic rendering
* The type of technique for stereoscopic rendering. (Note that the materials used will need to be
* compatible with the chosen technique.)
*/
public enum StereoscopicType {
/** No stereoscopic rendering. */
NONE,
/** Stereoscopic rendering is performed using instanced rendering technique. */
INSTANCED,
/** Stereoscopic rendering is performed using the multiview feature from the graphics backend. */
@@ -221,13 +224,12 @@ public class Engine {
nSetBuilderConfig(mNativeBuilder, config.commandBufferSizeMB,
config.perRenderPassArenaSizeMB, config.driverHandleArenaSizeMB,
config.minCommandBufferSizeMB, config.perFrameCommandsSizeMB,
config.jobSystemThreadCount,
config.textureUseAfterFreePoolSize, config.disableParallelShaderCompile,
config.jobSystemThreadCount, config.disableParallelShaderCompile,
config.stereoscopicType.ordinal(), config.stereoscopicEyeCount,
config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge,
config.disableHandleUseAfterFreeCheck,
config.preferredShaderLanguage.ordinal(),
config.forceGLES2Context);
config.forceGLES2Context, config.assertNativeWindowIsValid);
return this;
}
@@ -256,6 +258,17 @@ public class Engine {
return this;
}
/**
* Set a feature flag value. This is the only way to set constant feature flags.
* @param name feature name
* @param value true to enable, false to disable
* @return A reference to this Builder for chaining calls.
*/
public Builder feature(@NonNull String name, boolean value) {
nSetBuilderFeature(mNativeBuilder, name, value);
return this;
}
/**
* Creates an instance of Engine
*
@@ -391,6 +404,7 @@ public class Engine {
/**
* Set to `true` to forcibly disable parallel shader compilation in the backend.
* Currently only honored by the GL backend.
* @Deprecated use "backend.disable_parallel_shader_compile" feature flag instead
*/
public boolean disableParallelShaderCompile = false;
@@ -405,7 +419,7 @@ public class Engine {
*
* @see View#setStereoscopicOptions
*/
public StereoscopicType stereoscopicType = StereoscopicType.INSTANCED;
public StereoscopicType stereoscopicType = StereoscopicType.NONE;
/**
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
@@ -416,22 +430,26 @@ public class Engine {
*/
public long stereoscopicEyeCount = 2;
/*
/**
* @Deprecated This value is no longer used.
*/
public long resourceAllocatorCacheSizeMB = 64;
/*
* This value determines for how many frames are texture entries kept in the cache.
/**
* This value determines how many frames texture entries are kept for in the cache. This
* is a soft limit, meaning some texture older than this are allowed to stay in the cache.
* Typically only one texture is evicted per frame.
* The default is 1.
*/
public long resourceAllocatorCacheMaxAge = 2;
public long resourceAllocatorCacheMaxAge = 1;
/*
/**
* Disable backend handles use-after-free checks.
* @Deprecated use "backend.disable_handle_use_after_free_check" feature flag instead
*/
public boolean disableHandleUseAfterFreeCheck = false;
/*
/**
* Sets a preferred shader language for Filament to use.
*
* The Metal backend supports two shader languages: MSL (Metal Shading Language) and
@@ -453,12 +471,20 @@ public class Engine {
};
public ShaderLanguage preferredShaderLanguage = ShaderLanguage.DEFAULT;
/*
/**
* When the OpenGL ES backend is used, setting this value to true will force a GLES2.0
* context if supported by the Platform, or if not, will have the backend pretend
* it's a GLES2 context. Ignored on other backends.
*/
public boolean forceGLES2Context = false;
/**
* Assert the native window associated to a SwapChain is valid when calling makeCurrent().
* This is only supported for:
* - PlatformEGLAndroid
* @Deprecated use "backend.opengl.assert_native_window_is_valid" feature flag instead
*/
public boolean assertNativeWindowIsValid = false;
}
private Engine(long nativeEngine, Config config) {
@@ -681,11 +707,11 @@ public class Engine {
/**
* Returns the maximum number of stereoscopic eyes supported by Filament. The actual number of
* eyes rendered is set at Engine creation time with the {@link
* Engine#Config#stereoscopicEyeCount} setting.
* eyes rendered is set at Engine creation time with the {@link Config#stereoscopicEyeCount}
* setting.
*
* @return the max number of stereoscopic eyes supported
* @see Engine#Config#stereoscopicEyeCount
* @see Config#stereoscopicEyeCount
*/
public long getMaxStereoscopicEyes() {
return nGetMaxStereoscopicEyes(getNativeObject());
@@ -882,7 +908,8 @@ public class Engine {
/**
* Returns whether the object is valid.
* @param object Object to check for validity
* @param ma Material
* @param mi MaterialInstance to check for validity
* @return returns true if the specified object is valid.
*/
public boolean isValidMaterialInstance(@NonNull Material ma, MaterialInstance mi) {
@@ -1286,6 +1313,57 @@ public class Engine {
nSetPaused(getNativeObject(), paused);
}
/**
* Switch the command queue to unprotected mode. Protected mode can be activated via
* Renderer::beginFrame() using a protected SwapChain.
* @see Renderer
* @see SwapChain
*/
public void unprotected() {
nUnprotected(getNativeObject());
}
/**
* Get the current time. This is a convenience function that simply returns the
* time in nanosecond since epoch of std::chrono::steady_clock.
* @return current time in nanosecond since epoch of std::chrono::steady_clock.
* @see Renderer#beginFrame
*/
public static native long getSteadyClockTimeNano();
/**
* Checks if a feature flag exists
* @param name name of the feature flag to check
* @return true if it exists false otherwise
*/
public boolean hasFeatureFlag(@NonNull String name) {
return nHasFeatureFlag(mNativeObject, name);
}
/**
* Set the value of a non-constant feature flag.
* @param name name of the feature flag to set
* @param value value to set
* @return true if the value was set, false if the feature flag is constant or doesn't exist.
*/
public boolean setFeatureFlag(@NonNull String name, boolean value) {
return nSetFeatureFlag(mNativeObject, name, value);
}
/**
* Retrieves the value of any feature flag.
* @param name name of the feature flag
* @return the value of the flag if it exists
* @exception IllegalArgumentException is thrown if the feature flag doesn't exist
*/
public boolean getFeatureFlag(@NonNull String name) {
if (!hasFeatureFlag(name)) {
throw new IllegalArgumentException("The feature flag \"" + name + "\" doesn't exist");
}
return nGetFeatureFlag(mNativeObject, name);
}
@UsedByReflection("TextureHelper.java")
public long getNativeObject() {
if (mNativeObject == 0) {
@@ -1363,6 +1441,7 @@ public class Engine {
private static native void nFlush(long nativeEngine);
private static native boolean nIsPaused(long nativeEngine);
private static native void nSetPaused(long nativeEngine, boolean paused);
private static native void nUnprotected(long nativeEngine);
private static native long nGetTransformManager(long nativeEngine);
private static native long nGetLightManager(long nativeEngine);
private static native long nGetRenderableManager(long nativeEngine);
@@ -1374,6 +1453,9 @@ public class Engine {
private static native int nGetSupportedFeatureLevel(long nativeEngine);
private static native int nSetActiveFeatureLevel(long nativeEngine, int ordinal);
private static native int nGetActiveFeatureLevel(long nativeEngine);
private static native boolean nHasFeatureFlag(long nativeEngine, String name);
private static native boolean nSetFeatureFlag(long nativeEngine, String name, boolean value);
private static native boolean nGetFeatureFlag(long nativeEngine, String name);
private static native long nCreateBuilder();
private static native void nDestroyBuilder(long nativeBuilder);
@@ -1381,14 +1463,14 @@ public class Engine {
private static native void nSetBuilderConfig(long nativeBuilder, long commandBufferSizeMB,
long perRenderPassArenaSizeMB, long driverHandleArenaSizeMB,
long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount,
long textureUseAfterFreePoolSize, boolean disableParallelShaderCompile,
int stereoscopicType, long stereoscopicEyeCount,
boolean disableParallelShaderCompile, int stereoscopicType, long stereoscopicEyeCount,
long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge,
boolean disableHandleUseAfterFreeCheck,
int preferredShaderLanguage,
boolean forceGLES2Context);
boolean forceGLES2Context, boolean assertNativeWindowIsValid);
private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal);
private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
private static native void nSetBuilderPaused(long nativeBuilder, boolean paused);
private static native void nSetBuilderFeature(long nativeBuilder, String name, boolean value);
private static native long nBuilderBuild(long nativeBuilder);
}

View File

@@ -346,6 +346,7 @@ public class Material {
public static class Builder {
private Buffer mBuffer;
private int mSize;
private int mShBandCount = 0;
/**
* Specifies the material data. The material data is a binary blob produced by
@@ -361,6 +362,22 @@ public class Material {
return this;
}
/**
* Sets the quality of the indirect lights computations. This is only taken into account
* if this material is lit and in the surface domain. This setting will affect the
* IndirectLight computation if one is specified on the Scene and Spherical Harmonics
* are used for the irradiance.
*
* @param shBandCount Number of spherical harmonic bands. Must be 1, 2 or 3 (default).
* @return Reference to this Builder for chaining calls.
* @see IndirectLight
*/
@NonNull
public Builder sphericalHarmonicsBandCount(@IntRange(from = 0) int shBandCount) {
mShBandCount = shBandCount;
return this;
}
/**
* Creates and returns the Material object.
*
@@ -372,7 +389,8 @@ public class Material {
*/
@NonNull
public Material build(@NonNull Engine engine) {
long nativeMaterial = nBuilderBuild(engine.getNativeObject(), mBuffer, mSize);
long nativeMaterial = nBuilderBuild(engine.getNativeObject(),
mBuffer, mSize, mShBandCount);
if (nativeMaterial == 0) throw new IllegalStateException("Couldn't create Material");
return new Material(nativeMaterial);
}
@@ -1023,7 +1041,7 @@ public class Material {
mNativeObject = 0;
}
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size);
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount);
private static native long nCreateInstance(long nativeMaterial);
private static native long nCreateInstanceWithName(long nativeMaterial, @NonNull String name);
private static native long nGetDefaultInstance(long nativeMaterial);

View File

@@ -74,7 +74,7 @@ public class MorphTargetBuffer {
*
* @exception IllegalStateException if the MorphTargetBuffer could not be created
*
* @see #setMorphTargetBufferAt
* @see #setMorphTargetBufferOffsetAt
*/
@NonNull
public MorphTargetBuffer build(@NonNull Engine engine) {

View File

@@ -524,14 +524,7 @@ public class RenderableManager {
}
/**
* Controls if the renderable has vertex morphing targets, zero by default. This is
* required to enable GPU morphing.
*
* <p>Filament supports two morphing modes: standard (default) and legacy.</p>
*
* <p>For standard morphing, A {@link MorphTargetBuffer} must be created and provided via
* {@link RenderableManager#setMorphTargetBufferAt}. Standard morphing supports up to
* <code>CONFIG_MAX_MORPH_TARGET_COUNT</code> morph targets.</p>
* Controls if the renderable has legacy vertex morphing targets, zero by default.
*
* For legacy morphing, the attached {@link VertexBuffer} must provide data in the
* appropriate {@link VertexBuffer.VertexAttribute} slots (<code>MORPH_POSITION_0</code> etc).
@@ -549,6 +542,22 @@ public class RenderableManager {
return this;
}
/**
* Controls if the renderable has vertex morphing targets, zero by default.
*
* <p>For standard morphing, A {@link MorphTargetBuffer} must be provided.
* Standard morphing supports up to
* <code>CONFIG_MAX_MORPH_TARGET_COUNT</code> morph targets.</p>
*
* <p>See also {@link RenderableManager#setMorphWeights}, which can be called on a per-frame basis
* to advance the animation.</p>
*/
@NonNull
public Builder morphing(@NonNull MorphTargetBuffer morphTargetBuffer) {
nBuilderMorphingStandard(mNativeBuilder, morphTargetBuffer.getNativeObject());
return this;
}
/**
* Specifies the morph target buffer for a primitive.
*
@@ -560,31 +569,13 @@ public class RenderableManager {
*
* @param level the level of detail (lod), only 0 can be specified
* @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor
* @param morphTargetBuffer specifies the morph target buffer
* @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices)
* @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3)
*/
@NonNull
public Builder morphing(@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@NonNull MorphTargetBuffer morphTargetBuffer,
@IntRange(from = 0) int offset,
@IntRange(from = 0) int count) {
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex,
morphTargetBuffer.getNativeObject(), offset, count);
return this;
}
/**
* Utility method to specify morph target buffer for a primitive.
* For details, see the {@link RenderableManager.Builder#morphing}.
*/
@NonNull
public Builder morphing(@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@NonNull MorphTargetBuffer morphTargetBuffer) {
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex,
morphTargetBuffer.getNativeObject(), 0, morphTargetBuffer.getVertexCount());
@IntRange(from = 0) int offset) {
nBuilderSetMorphTargetBufferOffsetAt(mNativeBuilder, level, primitiveIndex, offset);
return this;
}
@@ -687,26 +678,11 @@ public class RenderableManager {
*
* @see Builder#morphing
*/
public void setMorphTargetBufferAt(@EntityInstance int i,
public void setMorphTargetBufferOffsetAt(@EntityInstance int i,
@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@NonNull MorphTargetBuffer morphTargetBuffer,
@IntRange(from = 0) int offset,
@IntRange(from = 0) int count) {
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex,
morphTargetBuffer.getNativeObject(), offset, count);
}
/**
* Utility method to change morph target buffer for the given primitive.
* For details, see the {@link RenderableManager#setMorphTargetBufferAt}.
*/
public void setMorphTargetBufferAt(@EntityInstance int i,
@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@NonNull MorphTargetBuffer morphTargetBuffer) {
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex,
morphTargetBuffer.getNativeObject(), 0, morphTargetBuffer.getVertexCount());
@IntRange(from = 0) int offset) {
nSetMorphTargetBufferOffsetAt(mNativeObject, i, level, primitiveIndex, 0, offset);
}
/**
@@ -1006,7 +982,8 @@ public class RenderableManager {
private static native int nBuilderSkinningBones(long nativeBuilder, int boneCount, Buffer bones, int remaining);
private static native void nBuilderSkinningBuffer(long nativeBuilder, long nativeSkinningBuffer, int boneCount, int offset);
private static native void nBuilderMorphing(long nativeBuilder, int targetCount);
private static native void nBuilderSetMorphTargetBufferAt(long nativeBuilder, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count);
private static native void nBuilderMorphingStandard(long nativeBuilder, long nativeMorphTargetBuffer);
private static native void nBuilderSetMorphTargetBufferOffsetAt(long nativeBuilder, int level, int primitiveIndex, int offset);
private static native void nBuilderEnableSkinningBuffers(long nativeBuilder, boolean enabled);
private static native void nBuilderFog(long nativeBuilder, boolean enabled);
private static native void nBuilderLightChannel(long nativeRenderableManager, int channel, boolean enable);
@@ -1016,7 +993,7 @@ public class RenderableManager {
private static native int nSetBonesAsMatrices(long nativeObject, int i, Buffer matrices, int remaining, int boneCount, int offset);
private static native int nSetBonesAsQuaternions(long nativeObject, int i, Buffer quaternions, int remaining, int boneCount, int offset);
private static native void nSetMorphWeights(long nativeObject, int instance, float[] weights, int offset);
private static native void nSetMorphTargetBufferAt(long nativeObject, int i, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count);
private static native void nSetMorphTargetBufferOffsetAt(long nativeObject, int i, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset);
private static native int nGetMorphTargetCount(long nativeObject, int i);
private static native void nSetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float cx, float cy, float cz, float ex, float ey, float ez);
private static native void nSetLayerMask(long nativeRenderableManager, int i, int select, int value);

View File

@@ -284,6 +284,33 @@ public class Renderer {
nSetPresentationTime(getNativeObject(), monotonicClockNanos);
}
/**
* The use of this method is optional. It sets the VSYNC time expressed as the duration in
* nanosecond since epoch of std::chrono::steady_clock.
* If called, passing 0 to frameTimeNanos in Renderer.BeginFrame will use this
* time instead.
* @param steadyClockTimeNano duration in nanosecond since epoch of std::chrono::steady_clock
* @see Engine#getSteadyClockTimeNano
* @see Renderer#beginFrame
*/
public void setVsyncTime(long steadyClockTimeNano) {
nSetVsyncTime(getNativeObject(), steadyClockTimeNano);
}
/**
* Call skipFrame when momentarily skipping frames, for instance if the content of the
* scene doesn't change.
*
* @param vsyncSteadyClockTimeNano The time in nanoseconds when the frame started being rendered,
* in the {@link System#nanoTime()} timebase. Divide this value by 1000000 to
* convert it to the {@link android.os.SystemClock#uptimeMillis()}
* time base. This typically comes from
* {@link android.view.Choreographer.FrameCallback}.
*/
public void skipFrame(long vsyncSteadyClockTimeNano) {
nSkipFrame(getNativeObject(), vsyncSteadyClockTimeNano);
}
/**
* Sets up a frame for this <code>Renderer</code>.
* <p><code>beginFrame</code> manages frame pacing, and returns whether or not a frame should be
@@ -702,6 +729,8 @@ public class Renderer {
}
private static native void nSetPresentationTime(long nativeObject, long monotonicClockNanos);
private static native void nSetVsyncTime(long nativeObject, long steadyClockTimeNano);
private static native void nSkipFrame(long nativeObject, long vsyncSteadyClockTimeNano);
private static native boolean nBeginFrame(long nativeRenderer, long nativeSwapChain, long frameTimeNanos);
private static native void nEndFrame(long nativeRenderer);
private static native void nRender(long nativeRenderer, long nativeView);

View File

@@ -745,6 +745,33 @@ public class View {
nSetFrontFaceWindingInverted(getNativeObject(), inverted);
}
/**
* Returns true if transparent picking is enabled.
*
* @see #setTransparentPickingEnabled
*/
public boolean isTransparentPickingEnabled() {
return nIsTransparentPickingEnabled(getNativeObject());
}
/**
* Enables or disables transparent picking. Disabled by default.
*
* When transparent picking is enabled, View::pick() will pick from both
* transparent and opaque renderables. When disabled, View::pick() will only
* pick from opaque renderables.
*
* <p>
* Transparent picking will create an extra pass for rendering depth
* from both transparent and opaque renderables.
* </p>
*
* @param enabled true enables transparent picking, false disables it.
*/
public void setTransparentPickingEnabled(boolean enabled) {
nSetTransparentPickingEnabled(getNativeObject(), enabled);
}
/**
* Sets options relative to dynamic lighting for this view.
*
@@ -1233,6 +1260,18 @@ public class View {
return nGetFogEntity(getNativeObject());
}
/**
* When certain temporal features are used (e.g.: TAA or Screen-space reflections), the view
* keeps a history of previous frame renders associated with the Renderer the view was last
* used with. When switching Renderer, it may be necessary to clear that history by calling
* this method. Similarly, if the whole content of the screen change, like when a cut-scene
* starts, clearing the history might be needed to avoid artifacts due to the previous frame
* being very different.
*/
public void clearFrameHistory(Engine engine) {
nClearFrameHistory(getNativeObject(), engine.getNativeObject());
}
public long getNativeObject() {
if (mNativeObject == 0) {
throw new IllegalStateException("Calling method on destroyed View");
@@ -1269,6 +1308,8 @@ public class View {
private static native boolean nIsPostProcessingEnabled(long nativeView);
private static native void nSetFrontFaceWindingInverted(long nativeView, boolean inverted);
private static native boolean nIsFrontFaceWindingInverted(long nativeView);
private static native void nSetTransparentPickingEnabled(long nativeView, boolean enabled);
private static native boolean nIsTransparentPickingEnabled(long nativeView);
private static native void nSetAmbientOcclusion(long nativeView, int ordinal);
private static native int nGetAmbientOcclusion(long nativeView);
private static native void nSetAmbientOcclusionOptions(long nativeView, float radius, float bias, float power, float resolution, float intensity, float bilateralThreshold, int quality, int lowPassFilter, int upsampling, boolean enabled, boolean bentNormals, float minHorizonAngleRad);
@@ -1294,7 +1335,7 @@ public class View {
private static native void nSetMaterialGlobal(long nativeView, int index, float x, float y, float z, float w);
private static native void nGetMaterialGlobal(long nativeView, int index, float[] out);
private static native int nGetFogEntity(long nativeView);
private static native void nClearFrameHistory(long nativeView, long nativeEngine);
/**
* List of available ambient occlusion techniques.

View File

@@ -31,6 +31,7 @@ set_target_properties(iblprefilter PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilament-iblprefilter.a)
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libfilament-utils-jni.map")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
add_library(filament-utils-jni SHARED
src/main/cpp/AutomationEngine.cpp

View File

@@ -125,6 +125,11 @@ extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBu
builder->groundPlane(a, b, c, d);
}
extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderPanning(JNIEnv*, jclass, jlong nativeBuilder, jboolean enabled) {
Builder* builder = (Builder*) nativeBuilder;
builder->panning(enabled);
}
extern "C" JNIEXPORT long Java_com_google_android_filament_utils_Manipulator_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder, jint mode) {
Builder* builder = (Builder*) nativeBuilder;
return (jlong) builder->build((Mode) mode);

View File

@@ -274,6 +274,17 @@ public class Manipulator {
return this;
}
/**
* Sets whether panning is enabled in the manipulator.
*
* @return this <code>Builder</code> object for chaining calls
*/
@NonNull
public Builder panning(Boolean enabled) {
nBuilderPanning(mNativeBuilder, enabled);
return this;
}
/**
* Creates and returns the <code>Manipulator</code> object.
*
@@ -483,6 +494,7 @@ public class Manipulator {
private static native void nBuilderFlightPanSpeed(long nativeBuilder, float x, float y);
private static native void nBuilderFlightMoveDamping(long nativeBuilder, float damping);
private static native void nBuilderGroundPlane(long nativeBuilder, float a, float b, float c, float d);
private static native void nBuilderPanning(long nativeBuilder, Boolean enabled);
private static native long nBuilderBuild(long nativeBuilder, int mode);
private static native void nDestroyManipulator(long nativeManip);

View File

@@ -44,6 +44,7 @@ set_target_properties(uberarchive PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libuberarchive.a)
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.map")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
set(GLTFIO_SRCS
${GLTFIO_DIR}/include/gltfio/Animator.h
@@ -119,7 +120,6 @@ set(GLTFIO_INCLUDE_DIRS
../../third_party/cgltf
../../third_party/meshoptimizer/src
../../third_party/robin-map
../../third_party/hat-trie
../../third_party/stb
../../libs/utils/include
../../libs/ktxreader/include

View File

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

View File

@@ -1,6 +1,6 @@
#Wed Nov 17 10:40:18 PST 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -6,6 +6,10 @@ plugins {
project.ext.isSample = true
kotlin {
jvmToolchain(versions.jdk)
}
filamentTools {
cmgenArgs = "-q --format=ktx --size=256 --extract-blur=0.1 --deploy=src/main/assets/envs/default_env"
iblInputFile = project.layout.projectDirectory.file("../../../third_party/environments/lightroom_14b.hdr")
@@ -13,7 +17,7 @@ filamentTools {
}
// don't forget to update MainACtivity.kt when/if changing this.
task copyMesh(type: Copy) {
tasks.register('copyMesh', Copy) {
from "../../../third_party/models/BusterDrone"
into "src/main/assets/models"
}

View File

@@ -112,7 +112,13 @@ class MainActivity : Activity() {
}
private fun setupFilament() {
engine = Engine.Builder().featureLevel(Engine.FeatureLevel.FEATURE_LEVEL_0).build()
val config = Engine.Config()
//config.forceGLES2Context = true
engine = Engine.Builder()
.config(config)
.featureLevel(Engine.FeatureLevel.FEATURE_LEVEL_0)
.build()
renderer = engine.createRenderer()
scene = engine.createScene()
view = engine.createView()
@@ -123,7 +129,9 @@ class MainActivity : Activity() {
scene.skybox = Skybox.Builder().color(0.035f, 0.035f, 0.035f, 1.0f).build(engine)
// post-processing is not supported at feature level 0
view.isPostProcessingEnabled = false
if (engine.activeFeatureLevel == Engine.FeatureLevel.FEATURE_LEVEL_0) {
view.isPostProcessingEnabled = false
}
// Tell the view which camera we want to use
view.camera = camera

View File

@@ -44,8 +44,6 @@ function print_help {
echo " Exclude Vulkan support from the Android build."
echo " -s"
echo " Add iOS simulator support to the iOS build."
echo " -t"
echo " Enable SwiftShader support for Vulkan in desktop builds."
echo " -e"
echo " Enable EGL on Linux support for desktop builds."
echo " -l"
@@ -66,6 +64,12 @@ function print_help {
echo " enabling debug paths in the backend from the build script. For example, make a"
echo " systrace-enabled build without directly changing #defines. Remember to add -f when"
echo " changing this option."
echo " -X osmesa_path"
echo " Indicates a path to a completed OSMesa build. OSMesa is used to create an offscreen GL"
echo " context for software rasterization"
echo " -S type"
echo " Enable stereoscopic rendering where type is one of [instanced|multiview]. This is only"
echo " meant for building the samples."
echo ""
echo "Build types:"
echo " release"
@@ -165,8 +169,6 @@ INSTALL_COMMAND=
VULKAN_ANDROID_OPTION="-DFILAMENT_SUPPORTS_VULKAN=ON"
VULKAN_ANDROID_GRADLE_OPTION=""
SWIFTSHADER_OPTION="-DFILAMENT_USE_SWIFTSHADER=OFF"
EGL_ON_LINUX_OPTION="-DFILAMENT_SUPPORTS_EGL_ON_LINUX=OFF"
MATDBG_OPTION="-DFILAMENT_ENABLE_MATDBG=OFF"
@@ -179,6 +181,10 @@ ASAN_UBSAN_OPTION=""
BACKEND_DEBUG_FLAG_OPTION=""
STEREOSCOPIC_OPTION=""
OSMESA_OPTION=""
IOS_BUILD_SIMULATOR=false
BUILD_UNIVERSAL_LIBRARIES=false
@@ -233,12 +239,13 @@ function build_desktop_target {
-DIMPORT_EXECUTABLES_DIR=out \
-DCMAKE_BUILD_TYPE="$1" \
-DCMAKE_INSTALL_PREFIX="../${lc_target}/filament" \
${SWIFTSHADER_OPTION} \
${EGL_ON_LINUX_OPTION} \
${MATDBG_OPTION} \
${MATOPT_OPTION} \
${ASAN_UBSAN_OPTION} \
${BACKEND_DEBUG_FLAG_OPTION} \
${STEREOSCOPIC_OPTION} \
${OSMESA_OPTION} \
${architectures} \
../..
ln -sf "out/cmake-${lc_target}/compile_commands.json" \
@@ -373,6 +380,7 @@ function build_android_target {
${MATOPT_OPTION} \
${VULKAN_ANDROID_OPTION} \
${BACKEND_DEBUG_FLAG_OPTION} \
${STEREOSCOPIC_OPTION} \
../..
ln -sf "out/cmake-android-${lc_target}-${arch}/compile_commands.json" \
../../compile_commands.json
@@ -607,7 +615,7 @@ function build_ios_target {
-DCMAKE_TOOLCHAIN_FILE=../../third_party/clang/iOS.cmake \
${MATDBG_OPTION} \
${MATOPT_OPTION} \
${BACKEND_DEBUG_FLAG_OPTION} \
${STEREOSCOPIC_OPTION} \
../..
ln -sf "out/cmake-ios-${lc_target}-${arch}/compile_commands.json" \
../../compile_commands.json
@@ -794,7 +802,7 @@ function check_debug_release_build {
pushd "$(dirname "$0")" > /dev/null
while getopts ":hacCfgijmp:q:uvslwtedk:bx:" opt; do
while getopts ":hacCfgijmp:q:uvslwedk:bx:S:X:" opt; do
case ${opt} in
h)
print_help
@@ -913,10 +921,6 @@ while getopts ":hacCfgijmp:q:uvslwtedk:bx:" opt; do
IOS_BUILD_SIMULATOR=true
echo "iOS simulator support enabled."
;;
t)
SWIFTSHADER_OPTION="-DFILAMENT_USE_SWIFTSHADER=ON"
echo "SwiftShader support enabled."
;;
e)
EGL_ON_LINUX_OPTION="-DFILAMENT_SUPPORTS_EGL_ON_LINUX=ON -DFILAMENT_SKIP_SDL2=ON -DFILAMENT_SKIP_SAMPLES=ON"
echo "EGL on Linux support enabled; skipping SDL2."
@@ -938,6 +942,22 @@ while getopts ":hacCfgijmp:q:uvslwtedk:bx:" opt; do
;;
x) BACKEND_DEBUG_FLAG_OPTION="-DFILAMENT_BACKEND_DEBUG_FLAG=${OPTARG}"
;;
S) case $(echo "${OPTARG}" | tr '[:upper:]' '[:lower:]') in
instanced)
STEREOSCOPIC_OPTION="-DFILAMENT_SAMPLES_STEREO_TYPE=instanced"
;;
multiview)
STEREOSCOPIC_OPTION="-DFILAMENT_SAMPLES_STEREO_TYPE=multiview"
;;
*)
echo "Unknown stereoscopic type ${OPTARG}"
echo "Type must be one of [instanced|multiview]"
echo ""
exit 1
esac
;;
X) OSMESA_OPTION="-DFILAMENT_OSMESA_PATH=${OPTARG}"
;;
\?)
echo "Invalid option: -${OPTARG}" >&2
echo ""

View File

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

View File

@@ -1 +1 @@
26.1.10909125
27.0.11718014

View File

@@ -1,53 +0,0 @@
# Build the image:
# docker build --no-cache --tag ssfilament -f build/swiftshader/Dockerfile .
# docker tag ssfilament ghcr.io/filament-assets/swiftshader
#
# Publish the image:
# docker login ghcr.io --username <user> --password <token>
# docker push ghcr.io/filament-assets/swiftshader
#
# Run the image and mount the current directory:
# docker run -it -v `pwd`:/trees/filament -t ssfilament
FROM ubuntu:focal
WORKDIR /trees
ARG DEBIAN_FRONTEND=noninteractive
ENV SWIFTSHADER_LD_LIBRARY_PATH=/trees/swiftshader/build
ENV CXXFLAGS='-fno-builtin -Wno-pass-failed'
RUN apt-get update && \
apt-get --no-install-recommends install -y \
apt-transport-https \
apt-utils \
build-essential \
cmake \
ca-certificates \
git \
ninja-build \
python \
python3 \
xorg-dev \
clang-7 \
libc++-7-dev \
libc++abi-7-dev \
lldb
# Ensure that clang is used instead of gcc.
RUN set -eux ;\
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-7 100 ;\
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-7 100 ;\
update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100 ;\
update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100
# Get patch files from the local Filament tree.
COPY build/swiftshader/*.diff .
# Clone SwiftShader, apply patches, and build it.
RUN set -eux ;\
git clone https://swiftshader.googlesource.com/SwiftShader swiftshader ;\
cd swiftshader ;\
git checkout 139f5c3 ;\
git apply /trees/*.diff ;\
cd build ;\
cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release ;\
ninja

View File

@@ -1,56 +0,0 @@
#!/usr/bin/env python3
from pathlib import Path
import os
spath = os.path.dirname(os.path.realpath(__file__))
path = Path(spath)
folder = "../../results/"
images = list(path.glob(folder + '*.png'))
images.sort()
gallery = open(path.absolute().joinpath(folder + 'index.html'), 'w')
gallery.write("""<html>
<head>
<script type="module" src="https://unpkg.com/img-comparison-slider@latest/dist/component/component.esm.js"></script>
<script nomodule="" src="https://unpkg.com/img-comparison-slider@latest/dist/component/component.js"></script>
<link rel="stylesheet" href="https://unpkg.com/img-comparison-slider@latest/dist/collection/styles/initial.css"/>
<style>
h2 {
font-weight: normal;
margin-top: 150px;
margin-bottom: 20px;
}
a {
text-decoration: none;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: blue;
}
a:hover {
font-weight: bold;
}
</style>
</head>
<body>
""")
tag = ''
for image in images:
group = image.stem.rstrip('0123456789')
before = f'https://filament-assets.github.io/golden/{group}/{image.name}'
after = image.name
gallery.write('\n')
gallery.write(f'<h2><a href="{image.stem}.json">{image.stem}.json</a></h2>\n')
gallery.write('<img-comparison-slider>\n')
gallery.write(f'<img slot="before" src="{before}" /> <img slot="after" src="{after}" />\n')
gallery.write('</img-comparison-slider>\n')
gallery.write("""</body>
</html>
""")

View File

@@ -1,62 +0,0 @@
diff --git a/src/Vulkan/VkPipeline.cpp b/src/Vulkan/VkPipeline.cpp
index 86913ec72..3b35345af 100644
--- a/src/Vulkan/VkPipeline.cpp
+++ b/src/Vulkan/VkPipeline.cpp
@@ -71,7 +71,56 @@ std::vector<uint32_t> preprocessSpirv(
if(optimize)
{
// Full optimization list taken from spirv-opt.
- opt.RegisterPerformancePasses();
+
+ // We have removed CreateRedundancyEliminationPass because it segfaults when encountering:
+ // %389 = OpCompositeConstruct %7 %386 %387 %388 %86
+ // When inserting an entry into instruction_to_value_ (which is an unordered_map)
+ // This could perhaps be investigated further with help from asan.
+
+ using namespace spvtools;
+ opt.RegisterPass(CreateWrapOpKillPass())
+ .RegisterPass(CreateDeadBranchElimPass())
+ .RegisterPass(CreateMergeReturnPass())
+ .RegisterPass(CreateInlineExhaustivePass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreatePrivateToLocalPass())
+ .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
+ .RegisterPass(CreateLocalSingleStoreElimPass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateScalarReplacementPass())
+ .RegisterPass(CreateLocalAccessChainConvertPass())
+ .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
+ .RegisterPass(CreateLocalSingleStoreElimPass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateLocalMultiStoreElimPass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateCCPPass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateLoopUnrollPass(true))
+ .RegisterPass(CreateDeadBranchElimPass())
+ .RegisterPass(CreateRedundancyEliminationPass()) // workaround for SEGFAULT
+ .RegisterPass(CreateCombineAccessChainsPass())
+ .RegisterPass(CreateSimplificationPass())
+ .RegisterPass(CreateScalarReplacementPass())
+ .RegisterPass(CreateLocalAccessChainConvertPass())
+ .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
+ .RegisterPass(CreateLocalSingleStoreElimPass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateSSARewritePass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateVectorDCEPass())
+ .RegisterPass(CreateDeadInsertElimPass())
+ .RegisterPass(CreateDeadBranchElimPass())
+ .RegisterPass(CreateSimplificationPass())
+ .RegisterPass(CreateIfConversionPass())
+ .RegisterPass(CreateCopyPropagateArraysPass())
+ .RegisterPass(CreateReduceLoadSizePass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateBlockMergePass())
+ .RegisterPass(CreateRedundancyEliminationPass()) // workaround for SEGFAULT
+ .RegisterPass(CreateDeadBranchElimPass())
+ .RegisterPass(CreateBlockMergePass())
+ .RegisterPass(CreateSimplificationPass());
}
std::vector<uint32_t> optimized;

View File

@@ -1,127 +0,0 @@
#!/bin/bash
set -e
function print_help {
local self_name=$(basename "$0")
echo "This script issues docker commands for testing Filament with SwiftShader."
echo "The usual sequence of commands is: fetch, start, build filament release, and run."
echo ""
echo "Usage:"
echo " $self_name [command]"
echo ""
echo "Commands:"
echo " build filament [debug | release]"
echo " Use the container to build Filament."
echo " build swiftshader [debug | release]"
echo " Use the container to do a clean rebuild of SwiftShader."
echo " (Note that the container already has SwiftShader built.)"
echo " fetch"
echo " Download the docker image from the central repository."
echo " help"
echo " Print this help message."
echo " logs"
echo " Print messages from the container's kernel ring buffer."
echo " This is useful for diagnosing OOM issues."
echo " run [lldb]"
echo " Launch a test inside the container, optionally via lldb."
echo " shell"
echo " Interact with a bash prompt in the container."
echo " start"
echo " Start a container from the image."
echo " stop"
echo " Stop the container."
echo ""
}
# Change the current working directory to the Filament root.
pushd "$(dirname "$0")/../.." > /dev/null
if [[ "$1" == "build" ]] && [[ "$2" == "filament" ]]; then
docker exec runner filament/build.sh -t $3 gltf_viewer
exit $?
fi
if [[ "$1" == "build" ]] && [[ "$2" == "swiftshader" ]]; then
BUILD_TYPE="$3"
BUILD_TYPE="$(tr '[:lower:]' '[:upper:]' <<< ${BUILD_TYPE:0:1})${BUILD_TYPE:1}"
docker exec --workdir /trees/swiftshader runner rm -rf build
docker exec --workdir /trees/swiftshader runner mkdir build
docker exec --workdir /trees/swiftshader/build runner cmake -GNinja -DCMAKE_BUILD_TYPE="$BUILD_TYPE" ..
docker exec --workdir /trees/swiftshader/build runner ninja
exit $?
fi
if [[ "$1" == "fetch" ]]; then
docker pull ghcr.io/filament-assets/swiftshader:latest
docker tag ghcr.io/filament-assets/swiftshader:latest ssfilament
exit $?
fi
if [[ "$1" == "help" ]]; then
print_help
exit 0
fi
if [[ "$1" == "logs" ]]; then
docker exec runner dmesg --human --read-clear
exit $?
fi
if [[ "$1" == "run" ]] && [[ "$2" == "lldb" ]]; then
docker exec -i --workdir /trees/filament/results runner \
lldb --batch -o run -o bt -- \
../out/cmake-release/samples/gltf_viewer \
--headless \
--batch ../libs/viewer/tests/basic.json \
--api vulkan
docker exec runner /trees/filament/build/swiftshader/gallery.py
exit $?
fi
if [[ "$1" == "run" ]]; then
docker exec --tty --workdir /trees/filament/results runner \
/usr/bin/catchsegv \
../out/cmake-release/samples/gltf_viewer \
--headless \
--batch ../libs/viewer/tests/basic.json \
--api vulkan
docker exec runner /trees/filament/build/swiftshader/gallery.py
exit $?
fi
if [[ "$1" == "shell" ]]; then
docker exec --interactive --tty runner /bin/bash
exit $?
fi
# Notes on options being passed to docker's run command:
#
# - The memory constraint seems to prevent an OOM signal in GitHub Actions.
# - The cap / security args allow use of lldb and creation of core dumps.
# - The privileged arg allows use of dmesg for examining OOM logs.
#
# Currently, a GitHub Actions VM has 2 CPUs, 7 GB RAM, and 14 GB of SSD disk space.
#
# Please be aware that Docker Desktop might impose additional resource constraints, and that those
# settings can only be controlled with its GUI. We recommend at least 7 GB of memory and 2 GB swap.
if [[ "$1" == "start" ]]; then
mkdir -p results
docker run --tty --rm --detach --privileged \
--memory 6.5g \
--name runner \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
--security-opt apparmor=unconfined \
--volume `pwd`:/trees/filament \
--workdir /trees \
ssfilament
exit $?
fi
if [[ "$1" == "stop" ]]; then
docker container rm runner --force
exit $?
fi
print_help
exit 1

View File

@@ -1,7 +1,6 @@
#!/bin/bash
if [ `uname` == "Linux" ];then
curl -OL https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-linux.zip
unzip -q ninja-linux.zip
source `dirname $0`/../linux/ci-common.sh
elif [ `uname` == "Darwin" ];then
curl -OL https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-mac.zip
unzip -q ninja-mac.zip
@@ -13,9 +12,6 @@ fi
chmod +x ninja
export PATH="$PWD:$PATH"
# FIXME: kokoro machines have node and npm but currently they are symlinked to non-existent files
# npm install -g typescript
# Install emscripten.
curl -L https://github.com/emscripten-core/emsdk/archive/refs/tags/3.1.15.zip > emsdk.zip
unzip emsdk.zip ; mv emsdk-* emsdk ; cd emsdk

View File

@@ -115,17 +115,23 @@ cmake ..\.. ^
-DFILAMENT_SUPPORTS_VULKAN=ON ^
|| exit /b
set build_flags=-j %NUMBER_OF_PROCESSORS%
@echo on
:: we've upgraded the windows machines, so the following are no longer accurate as of 09/19/24, but
:: keeping around the comment for record.
:: Attempt to fix "error C1060: compiler is out of heap space" seen on CI.
:: Some resource libraries require significant heap space to compile, so first compile them serially.
@echo on
cmake --build . --target filagui --config %config% || exit /b
cmake --build . --target uberarchive --config %config% || exit /b
cmake --build . --target gltf-demo-resources --config %config% || exit /b
cmake --build . --target filamentapp-resources --config %config% || exit /b
cmake --build . --target sample-resources --config %config% || exit /b
cmake --build . --target suzanne-resources --config %config% || exit /b
:: cmake --build . --target filagui --config %config% %build_flags% || exit /b
:: cmake --build . --target uberarchive --config %config% %build_flags% || exit /b
:: cmake --build . --target gltf-demo-resources --config %config% %build_flags% || exit /b
:: cmake --build . --target filamentapp-resources --config %config% %build_flags% || exit /b
:: cmake --build . --target sample-resources --config %config% %build_flags% || exit /b
:: cmake --build . --target suzanne-resources --config %config% %build_flags% || exit /b
cmake --build . %INSTALL% --config %config% -- /m || exit /b
cmake --build . %INSTALL% --config %config% %build_flags% -- /m || exit /b
@echo off
echo Disk info after building variant: %variant%

View File

@@ -1308,7 +1308,12 @@ Description
declare a variable called `eyeDirection` you can access it in the fragment shader using
`variable_eyeDirection`. In the vertex shader, the interpolant name is simply a member of
the `MaterialVertexInputs` structure (`material.eyeDirection` in your example). Each
interpolant is of type `float4` (`vec4`) in the shaders.
interpolant is of type `float4` (`vec4`) in the shaders. By default the precision of the
interpolant is `highp` in *both* the vertex and fragment shaders.
An alternate syntax can be used to specify both the name and precision of the interpolant.
In this case the specified precision is used as-is in both fragment and vertex stages, in
particular if `default` is specified the default precision is used is the fragment shader
(`mediump`) and in the vertex shader (`highp`).
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
@@ -1320,7 +1325,11 @@ material {
}
],
variables : [
eyeDirection
eyeDirection,
{
name : eyeColor,
precision : medium
}
],
vertexDomain : device,
depthWrite : false,

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -61,10 +61,12 @@ set(SRCS
src/Engine.cpp
src/Exposure.cpp
src/Fence.cpp
src/FilamentBuilder.cpp
src/FrameInfo.cpp
src/FrameSkipper.cpp
src/Froxelizer.cpp
src/Frustum.cpp
src/HwDescriptorSetLayoutFactory.cpp
src/HwRenderPrimitiveFactory.cpp
src/HwVertexBufferInfoFactory.cpp
src/IndexBuffer.cpp
@@ -75,8 +77,6 @@ set(SRCS
src/MaterialInstance.cpp
src/MaterialParser.cpp
src/MorphTargetBuffer.cpp
src/PerViewUniforms.cpp
src/PerShadowMapUniforms.cpp
src/PostProcessManager.cpp
src/RenderPass.cpp
src/RenderPrimitive.cpp
@@ -125,6 +125,12 @@ set(SRCS
src/details/Texture.cpp
src/details/VertexBuffer.cpp
src/details/View.cpp
src/ds/ColorPassDescriptorSet.cpp
src/ds/DescriptorSet.cpp
src/ds/DescriptorSetLayout.cpp
src/ds/PostProcessDescriptorSet.cpp
src/ds/ShadowMapDescriptorSet.cpp
src/ds/SsrPassDescriptorSet.cpp
src/fg/Blackboard.cpp
src/fg/DependencyGraph.cpp
src/fg/FrameGraph.cpp
@@ -148,23 +154,21 @@ set(PRIVATE_HDRS
src/FrameInfo.h
src/FrameSkipper.h
src/Froxelizer.h
src/HwDescriptorSetLayoutFactory.h
src/HwRenderPrimitiveFactory.h
src/HwVertexBufferInfoFactory.h
src/Intersections.h
src/MaterialParser.h
src/PerViewUniforms.h
src/PerShadowMapUniforms.h
src/PIDController.h
src/PostProcessManager.h
src/RendererUtils.h
src/RenderPass.h
src/RenderPrimitive.h
src/RendererUtils.h
src/ResourceAllocator.h
src/ResourceList.h
src/ShadowMap.h
src/ShadowMapManager.h
src/SharedHandle.h
src/TypedUniformBuffer.h
src/UniformBuffer.h
src/components/CameraManager.h
src/components/LightManager.h
@@ -192,6 +196,14 @@ set(PRIVATE_HDRS
src/details/Texture.h
src/details/VertexBuffer.h
src/details/View.h
src/downcast.h
src/ds/ColorPassDescriptorSet.h
src/ds/DescriptorSetLayout.h
src/ds/PostProcessDescriptorSet.h
src/ds/ShadowMapDescriptorSet.h
src/ds/SsrPassDescriptorSet.h
src/ds/TypedBuffer.h
src/ds/TypedUniformBuffer.h
src/fg/Blackboard.h
src/fg/FrameGraph.h
src/fg/FrameGraphId.h
@@ -209,7 +221,6 @@ set(PRIVATE_HDRS
src/materials/fsr/ffx_a.h
src/materials/fsr/ffx_fsr1.h
src/materials/fsr/ffx_fsr1_mobile.fs
src/downcast.h
)
set(MATERIAL_SRCS
@@ -297,6 +308,11 @@ if (FILAMENT_ENABLE_MULTIVIEW)
add_definitions(-DFILAMENT_ENABLE_MULTIVIEW)
endif()
# Whether to force the profiling mode.
if (FILAMENT_FORCE_PROFILING_MODE)
add_definitions(-DFILAMENT_FORCE_PROFILING_MODE)
endif()
# ==================================================================================================
# Definitions
# ==================================================================================================
@@ -596,6 +612,13 @@ else()
-Wover-aligned
-Werror
)
if (CMAKE_CXX_STANDARD EQUAL 20)
# The lambdas for passes in PostProcessManager.cpp capture this
# implicitly in a way that's deprecated in c++20, but can't easily be
# fixed in a way that's backwards compatible with c++17:
# https://www.nextptr.com/tutorial/ta1430524603/capture-this-in-lambda-expression-timeline-of-change
list(APPEND FILAMENT_WARNINGS -Wno-deprecated-this-capture)
endif()
endif()
target_compile_options(${TARGET} PRIVATE

View File

@@ -12,6 +12,7 @@ set(PUBLIC_HDRS
include/backend/AcquiredImage.h
include/backend/BufferDescriptor.h
include/backend/CallbackHandler.h
include/backend/DescriptorSetOffsetArray.h
include/backend/DriverApiForward.h
include/backend/DriverEnums.h
include/backend/Handle.h
@@ -66,12 +67,16 @@ set(PRIVATE_HDRS
# OpenGL / OpenGL ES Sources
# ==================================================================================================
if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMENT_USE_SWIFTSHADER)
if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
list(APPEND SRCS
include/backend/platforms/OpenGLPlatform.h
src/opengl/BindingMap.h
src/opengl/gl_headers.cpp
src/opengl/gl_headers.h
src/opengl/GLBufferObject.h
src/opengl/GLDescriptorSet.cpp
src/opengl/GLDescriptorSet.h
src/opengl/GLDescriptorSetLayout.h
src/opengl/GLTexture.h
src/opengl/GLUtils.cpp
src/opengl/GLUtils.h
@@ -114,6 +119,8 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMEN
list(APPEND SRCS src/opengl/platforms/PlatformGLX.cpp)
elseif (FILAMENT_SUPPORTS_EGL_ON_LINUX)
list(APPEND SRCS src/opengl/platforms/PlatformEGLHeadless.cpp)
elseif (FILAMENT_SUPPORTS_OSMESA)
list(APPEND SRCS src/opengl/platforms/PlatformOSMesa.cpp)
endif()
elseif (WIN32)
list(APPEND SRCS src/opengl/platforms/PlatformWGL.cpp)
@@ -172,11 +179,18 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/caching/VulkanDescriptorSetManager.h
src/vulkan/caching/VulkanPipelineLayoutCache.cpp
src/vulkan/caching/VulkanPipelineLayoutCache.h
src/vulkan/memory/ResourceManager.cpp
src/vulkan/memory/ResourceManager.h
src/vulkan/memory/ResourcePointer.h
src/vulkan/memory/Resource.cpp
src/vulkan/memory/Resource.h
src/vulkan/platform/VulkanPlatform.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
src/vulkan/spirv/VulkanSpirvUtils.cpp
src/vulkan/spirv/VulkanSpirvUtils.h
src/vulkan/VulkanAsyncHandles.cpp
src/vulkan/VulkanAsyncHandles.h
src/vulkan/VulkanBlitter.cpp
src/vulkan/VulkanBlitter.h
src/vulkan/VulkanBuffer.cpp
@@ -207,9 +221,6 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanSwapChain.h
src/vulkan/VulkanReadPixels.cpp
src/vulkan/VulkanReadPixels.h
src/vulkan/VulkanResourceAllocator.h
src/vulkan/VulkanResources.cpp
src/vulkan/VulkanResources.h
src/vulkan/VulkanTexture.cpp
src/vulkan/VulkanTexture.h
src/vulkan/VulkanUtility.cpp
@@ -361,6 +372,11 @@ set(LINUX_LINKER_OPTIMIZATION_FLAGS
-Wl,--exclude-libs,bluegl
)
if (LINUX AND FILAMENT_SUPPORTS_OSMESA)
set(OSMESA_COMPILE_FLAGS
-I${FILAMENT_OSMESA_PATH}/include/GL)
endif()
if (MSVC)
set(FILAMENT_WARNINGS /W3)
else()
@@ -372,6 +388,9 @@ else()
-Wover-aligned
-Werror
)
if (CMAKE_CXX_STANDARD EQUAL 20)
list(APPEND FILAMENT_WARNINGS -Wno-deprecated-this-capture)
endif()
endif()
if (APPLE)
@@ -381,6 +400,7 @@ endif()
target_compile_options(${TARGET} PRIVATE
${FILAMENT_WARNINGS}
${OSMESA_COMPILE_FLAGS}
$<$<CONFIG:Release>:${OPTIMIZATION_FLAGS}>
$<$<AND:$<PLATFORM_ID:Darwin>,$<CONFIG:Release>>:${DARWIN_OPTIMIZATION_FLAGS}>
)
@@ -390,6 +410,7 @@ if (FILAMENT_SUPPORTS_METAL)
endif()
target_link_libraries(${TARGET} PRIVATE
${OSMESA_LINKER_FLAGS}
$<$<AND:$<PLATFORM_ID:Linux>,$<CONFIG:Release>>:${LINUX_LINKER_OPTIMIZATION_FLAGS}>
)
@@ -417,11 +438,14 @@ if (APPLE OR LINUX)
test/test_MissingRequiredAttributes.cpp
test/test_ReadPixels.cpp
test/test_BufferUpdates.cpp
test/test_Callbacks.cpp
test/test_MRT.cpp
test/test_PushConstants.cpp
test/test_LoadImage.cpp
test/test_StencilBuffer.cpp
test/test_Scissor.cpp
test/test_MipLevels.cpp
test/test_Handles.cpp
)
set(BACKEND_TEST_LIBS
backend
@@ -493,21 +517,40 @@ endif()
# ==================================================================================================
# Compute tests
#
#if (NOT IOS AND NOT WEBGL)
#
#add_executable(compute_test
# test/ComputeTest.cpp
# test/Arguments.cpp
# test/test_ComputeBasic.cpp
# )
#
#target_link_libraries(compute_test PRIVATE
# backend
# getopt
# gtest
# )
#
#set_target_properties(compute_test PROPERTIES FOLDER Tests)
#
#endif()
if (NOT IOS AND NOT WEBGL)
# ==================================================================================================
# Metal utils tests
add_executable(compute_test
test/ComputeTest.cpp
test/Arguments.cpp
test/test_ComputeBasic.cpp
)
if (APPLE AND NOT IOS)
target_link_libraries(compute_test PRIVATE
add_executable(metal_utils_test test/MetalTest.mm)
target_compile_options(metal_utils_test PRIVATE "-fobjc-arc")
target_link_libraries(metal_utils_test PRIVATE
backend
getopt
gtest
)
set_target_properties(compute_test PROPERTIES FOLDER Tests)
set_target_properties(metal_utils_test PROPERTIES FOLDER Tests)
endif()

View File

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

View File

@@ -19,12 +19,16 @@
#ifndef TNT_FILAMENT_BACKEND_DRIVERENUMS_H
#define TNT_FILAMENT_BACKEND_DRIVERENUMS_H
#include <utils/BitmaskEnum.h>
#include <utils/unwindows.h> // Because we define ERROR in the FenceStatus enum.
#include <backend/Platform.h>
#include <backend/PresentCallable.h>
#include <utils/BitmaskEnum.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <math/vec4.h>
@@ -96,6 +100,8 @@ static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guarantee
static constexpr size_t MAX_SAMPLER_COUNT = 62; // Maximum needed at feature level 3.
static constexpr size_t MAX_VERTEX_BUFFER_COUNT = 16; // Max number of bound buffer objects.
static constexpr size_t MAX_SSBO_COUNT = 4; // This is guaranteed by OpenGL ES.
static constexpr size_t MAX_DESCRIPTOR_SET_COUNT = 4; // This is guaranteed by Vulkan.
static constexpr size_t MAX_DESCRIPTOR_COUNT = 64; // per set
static constexpr size_t MAX_PUSH_CONSTANT_COUNT = 32; // Vulkan 1.1 spec allows for 128-byte
// of push constant (we assume 4-byte
@@ -117,7 +123,7 @@ static_assert(MAX_VERTEX_BUFFER_COUNT <= MAX_VERTEX_ATTRIBUTE_COUNT,
"The number of buffer objects that can be attached to a VertexBuffer must be "
"less than or equal to the maximum number of vertex attributes.");
static constexpr size_t CONFIG_UNIFORM_BINDING_COUNT = 10; // This is guaranteed by OpenGL ES.
static constexpr size_t CONFIG_UNIFORM_BINDING_COUNT = 9; // This is guaranteed by OpenGL ES.
static constexpr size_t CONFIG_SAMPLER_BINDING_COUNT = 4; // This is guaranteed by OpenGL ES.
/**
@@ -190,6 +196,71 @@ static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguag
}
}
enum class ShaderStage : uint8_t {
VERTEX = 0,
FRAGMENT = 1,
COMPUTE = 2
};
static constexpr size_t PIPELINE_STAGE_COUNT = 2;
enum class ShaderStageFlags : uint8_t {
NONE = 0,
VERTEX = 0x1,
FRAGMENT = 0x2,
COMPUTE = 0x4,
ALL_SHADER_STAGE_FLAGS = VERTEX | FRAGMENT | COMPUTE
};
static inline constexpr bool hasShaderType(ShaderStageFlags flags, ShaderStage type) noexcept {
switch (type) {
case ShaderStage::VERTEX:
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::VERTEX));
case ShaderStage::FRAGMENT:
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::FRAGMENT));
case ShaderStage::COMPUTE:
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::COMPUTE));
}
}
enum class DescriptorType : uint8_t {
UNIFORM_BUFFER,
SHADER_STORAGE_BUFFER,
SAMPLER,
INPUT_ATTACHMENT,
SAMPLER_EXTERNAL
};
enum class DescriptorFlags : uint8_t {
NONE = 0x00,
DYNAMIC_OFFSET = 0x01
};
using descriptor_set_t = uint8_t;
using descriptor_binding_t = uint8_t;
struct DescriptorSetLayoutBinding {
DescriptorType type;
ShaderStageFlags stageFlags;
descriptor_binding_t binding;
DescriptorFlags flags = DescriptorFlags::NONE;
uint16_t count = 0;
friend inline bool operator==(
DescriptorSetLayoutBinding const& lhs,
DescriptorSetLayoutBinding const& rhs) noexcept {
return lhs.type == rhs.type &&
lhs.flags == rhs.flags &&
lhs.count == rhs.count &&
lhs.stageFlags == rhs.stageFlags;
}
};
struct DescriptorSetLayout {
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
};
/**
* Bitmask for selecting render buffers
*/
@@ -248,8 +319,19 @@ struct Viewport {
int32_t right() const noexcept { return left + int32_t(width); }
//! get the top coordinate in window space of the viewport
int32_t top() const noexcept { return bottom + int32_t(height); }
};
friend bool operator==(Viewport const& lhs, Viewport const& rhs) noexcept {
// clang can do this branchless with xor/or
return lhs.left == rhs.left && lhs.bottom == rhs.bottom &&
lhs.width == rhs.width && lhs.height == rhs.height;
}
friend bool operator!=(Viewport const& lhs, Viewport const& rhs) noexcept {
// clang is being dumb and uses branches
return bool(((lhs.left ^ rhs.left) | (lhs.bottom ^ rhs.bottom)) |
((lhs.width ^ rhs.width) | (lhs.height ^ rhs.height)));
}
};
/**
* Specifies the mapping of the near and far clipping plane to window coordinates.
@@ -269,15 +351,6 @@ enum class FenceStatus : int8_t {
TIMEOUT_EXPIRED = 1, //!< wait()'s timeout expired. The Fence condition is not satisfied.
};
/**
* Status codes for sync objects
*/
enum class SyncStatus : int8_t {
ERROR = -1, //!< An error occurred. The Sync is not signaled.
SIGNALED = 0, //!< The Sync is signaled.
NOT_SIGNALED = 1, //!< The Sync is not signaled yet
};
static constexpr uint64_t FENCE_WAIT_FOR_EVER = uint64_t(-1);
/**
@@ -367,6 +440,18 @@ enum class SamplerType : uint8_t {
SAMPLER_CUBEMAP_ARRAY, //!< Cube map array texture (feature level 2)
};
inline const char* stringify(SamplerType samplerType) {
switch (samplerType) {
case SamplerType::SAMPLER_2D: return "SAMPLER_2D";
case SamplerType::SAMPLER_2D_ARRAY: return "SAMPLER_2D_ARRAY";
case SamplerType::SAMPLER_CUBEMAP: return "SAMPLER_CUBEMAP";
case SamplerType::SAMPLER_EXTERNAL: return "SAMPLER_EXTERNAL";
case SamplerType::SAMPLER_3D: return "SAMPLER_3D";
case SamplerType::SAMPLER_CUBEMAP_ARRAY: return "SAMPLER_CUBEMAP_ARRAY";
}
return "UNKNOWN";
}
//! Subpass type
enum class SubpassType : uint8_t {
SUBPASS_INPUT
@@ -692,9 +777,27 @@ enum class TextureUsage : uint16_t {
BLIT_SRC = 0x0040, //!< Texture can be used the source of a blit()
BLIT_DST = 0x0080, //!< Texture can be used the destination of a blit()
PROTECTED = 0x0100, //!< Texture can be used for protected content
DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage
DEFAULT = UPLOADABLE | SAMPLEABLE, //!< Default texture usage
ALL_ATTACHMENTS = COLOR_ATTACHMENT | DEPTH_ATTACHMENT | STENCIL_ATTACHMENT | SUBPASS_INPUT, //!< Mask of all attachments
};
inline const char* stringify(TextureUsage usage) {
switch (usage) {
case TextureUsage::NONE: return "NONE";
case TextureUsage::COLOR_ATTACHMENT: return "COLOR_ATTACHMENT";
case TextureUsage::DEPTH_ATTACHMENT: return "DEPTH_ATTACHMENT";
case TextureUsage::STENCIL_ATTACHMENT: return "STENCIL_ATTACHMENT";
case TextureUsage::UPLOADABLE: return "UPLOADABLE";
case TextureUsage::SAMPLEABLE: return "SAMPLEABLE";
case TextureUsage::SUBPASS_INPUT: return "SUBPASS_INPUT";
case TextureUsage::BLIT_SRC: return "BLIT_SRC";
case TextureUsage::BLIT_DST: return "BLIT_DST";
case TextureUsage::PROTECTED: return "PROTECTED";
case TextureUsage::DEFAULT: return "DEFAULT";
default: return "UNKNOWN";
}
}
//! Texture swizzle
enum class TextureSwizzle : uint8_t {
SUBSTITUTE_ZERO,
@@ -886,6 +989,9 @@ struct SamplerParams { // NOLINT
struct EqualTo {
bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept {
assert_invariant(lhs.padding0 == 0);
assert_invariant(lhs.padding1 == 0);
assert_invariant(lhs.padding2 == 0);
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs == *pRhs;
@@ -894,6 +1000,9 @@ struct SamplerParams { // NOLINT
struct LessThan {
bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept {
assert_invariant(lhs.padding0 == 0);
assert_invariant(lhs.padding1 == 0);
assert_invariant(lhs.padding2 == 0);
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs == *pRhs;
@@ -901,6 +1010,12 @@ struct SamplerParams { // NOLINT
};
private:
friend inline bool operator == (SamplerParams lhs, SamplerParams rhs) noexcept {
return SamplerParams::EqualTo{}(lhs, rhs);
}
friend inline bool operator != (SamplerParams lhs, SamplerParams rhs) noexcept {
return !SamplerParams::EqualTo{}(lhs, rhs);
}
friend inline bool operator < (SamplerParams lhs, SamplerParams rhs) noexcept {
return SamplerParams::LessThan{}(lhs, rhs);
}
@@ -1057,7 +1172,7 @@ struct RasterState {
bool inverseFrontFaces : 1; // 31
//! padding, must be 0
uint8_t padding : 1; // 32
bool depthClamp : 1; // 32
};
uint32_t u = 0;
};
@@ -1068,32 +1183,6 @@ struct RasterState {
* \privatesection
*/
enum class ShaderStage : uint8_t {
VERTEX = 0,
FRAGMENT = 1,
COMPUTE = 2
};
static constexpr size_t PIPELINE_STAGE_COUNT = 2;
enum class ShaderStageFlags : uint8_t {
NONE = 0,
VERTEX = 0x1,
FRAGMENT = 0x2,
COMPUTE = 0x4,
ALL_SHADER_STAGE_FLAGS = VERTEX | FRAGMENT | COMPUTE
};
static inline constexpr bool hasShaderType(ShaderStageFlags flags, ShaderStage type) noexcept {
switch (type) {
case ShaderStage::VERTEX:
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::VERTEX));
case ShaderStage::FRAGMENT:
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::FRAGMENT));
case ShaderStage::COMPUTE:
return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::COMPUTE));
}
}
/**
* Selects which buffers to clear at the beginning of the render pass, as well as which buffers
* can be discarded at the beginning and end of the render pass.
@@ -1243,20 +1332,14 @@ enum class Workaround : uint16_t {
ADRENO_UNIFORM_ARRAY_CRASH,
// Workaround a Metal pipeline compilation error with the message:
// "Could not statically determine the target of a texture". See light_indirect.fs
A8X_STATIC_TEXTURE_TARGET_ERROR,
METAL_STATIC_TEXTURE_TARGET_ERROR,
// Adreno drivers sometimes aren't able to blit into a layer of a texture array.
DISABLE_BLIT_INTO_TEXTURE_ARRAY,
// Multiple workarounds needed for PowerVR GPUs
POWER_VR_SHADER_WORKAROUNDS,
};
//! The type of technique for stereoscopic rendering
enum class StereoscopicType : uint8_t {
// Stereoscopic rendering is performed using instanced rendering technique.
INSTANCED,
// Stereoscopic rendering is performed using the multiview feature from the graphics backend.
MULTIVIEW,
};
using StereoscopicType = backend::Platform::StereoscopicType;
} // namespace filament::backend
@@ -1264,6 +1347,8 @@ template<> struct utils::EnableBitMaskOperators<filament::backend::ShaderStageFl
: public std::true_type {};
template<> struct utils::EnableBitMaskOperators<filament::backend::TargetBufferFlags>
: public std::true_type {};
template<> struct utils::EnableBitMaskOperators<filament::backend::DescriptorFlags>
: public std::true_type {};
template<> struct utils::EnableBitMaskOperators<filament::backend::TextureUsage>
: public std::true_type {};
template<> struct utils::EnableBitMaskOperators<filament::backend::StencilFace>

View File

@@ -23,6 +23,7 @@
#include <utils/debug.h>
#include <type_traits> // FIXME: STL headers are not allowed in public headers
#include <utility>
#include <stdint.h>
@@ -41,6 +42,8 @@ struct HwTexture;
struct HwTimerQuery;
struct HwVertexBufferInfo;
struct HwVertexBuffer;
struct HwDescriptorSetLayout;
struct HwDescriptorSet;
/*
* A handle to a backend resource. HandleBase is for internal use only.
@@ -104,8 +107,18 @@ struct Handle : public HandleBase {
Handle(Handle const& rhs) noexcept = default;
Handle(Handle&& rhs) noexcept = default;
Handle& operator=(Handle const& rhs) noexcept = default;
Handle& operator=(Handle&& rhs) noexcept = default;
// Explicitly redefine copy/move assignment operators rather than just using default here.
// Because it doesn't make a call to the parent's method automatically during the std::move
// function call(https://en.cppreference.com/w/cpp/algorithm/move) in certain compilers like
// NDK 25.1.8937393 and below (see b/371980551)
Handle& operator=(Handle const& rhs) noexcept {
HandleBase::operator=(rhs);
return *this;
}
Handle& operator=(Handle&& rhs) noexcept {
HandleBase::operator=(std::move(rhs));
return *this;
}
explicit Handle(HandleId id) noexcept : HandleBase(id) { }
@@ -130,19 +143,21 @@ private:
// Types used by the command stream
// (we use this renaming because the macro-system doesn't deal well with "<" and ">")
using BufferObjectHandle = Handle<HwBufferObject>;
using FenceHandle = Handle<HwFence>;
using IndexBufferHandle = Handle<HwIndexBuffer>;
using ProgramHandle = Handle<HwProgram>;
using RenderPrimitiveHandle = Handle<HwRenderPrimitive>;
using RenderTargetHandle = Handle<HwRenderTarget>;
using SamplerGroupHandle = Handle<HwSamplerGroup>;
using StreamHandle = Handle<HwStream>;
using SwapChainHandle = Handle<HwSwapChain>;
using TextureHandle = Handle<HwTexture>;
using TimerQueryHandle = Handle<HwTimerQuery>;
using VertexBufferHandle = Handle<HwVertexBuffer>;
using VertexBufferInfoHandle = Handle<HwVertexBufferInfo>;
using BufferObjectHandle = Handle<HwBufferObject>;
using FenceHandle = Handle<HwFence>;
using IndexBufferHandle = Handle<HwIndexBuffer>;
using ProgramHandle = Handle<HwProgram>;
using RenderPrimitiveHandle = Handle<HwRenderPrimitive>;
using RenderTargetHandle = Handle<HwRenderTarget>;
using SamplerGroupHandle = Handle<HwSamplerGroup>;
using StreamHandle = Handle<HwStream>;
using SwapChainHandle = Handle<HwSwapChain>;
using TextureHandle = Handle<HwTexture>;
using TimerQueryHandle = Handle<HwTimerQuery>;
using VertexBufferHandle = Handle<HwVertexBuffer>;
using VertexBufferInfoHandle = Handle<HwVertexBufferInfo>;
using DescriptorSetLayoutHandle = Handle<HwDescriptorSetLayout>;
using DescriptorSetHandle = Handle<HwDescriptorSet>;
} // namespace filament::backend

View File

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

View File

@@ -41,6 +41,26 @@ public:
struct Fence {};
struct Stream {};
/**
* The type of technique for stereoscopic rendering. (Note that the materials used will need to
* be compatible with the chosen technique.)
*/
enum class StereoscopicType : uint8_t {
/**
* No stereoscopic rendering
*/
NONE,
/**
* Stereoscopic rendering is performed using instanced rendering technique.
*/
INSTANCED,
/**
* Stereoscopic rendering is performed using the multiview feature from the graphics
* backend.
*/
MULTIVIEW,
};
struct DriverConfig {
/**
* Size of handle arena in bytes. Setting to 0 indicates default value is to be used.
@@ -48,12 +68,7 @@ public:
*/
size_t handleArenaSize = 0;
/**
* This number of most-recently destroyed textures will be tracked for use-after-free.
* Throws an exception when a texture is freed but still bound to a SamplerGroup and used in
* a draw call. 0 disables completely. Currently only respected by the Metal backend.
*/
size_t textureUseAfterFreePoolSize = 0;
size_t metalUploadBufferSizeBytes = 512 * 1024;
/**
* Set to `true` to forcibly disable parallel shader compilation in the backend.
@@ -71,6 +86,18 @@ public:
* GLES 3.x backends.
*/
bool forceGLES2Context = false;
/**
* Sets the technique for stereoscopic rendering.
*/
StereoscopicType stereoscopicType = StereoscopicType::NONE;
/**
* Assert the native window associated to a SwapChain is valid when calling makeCurrent().
* This is only supported for:
* - PlatformEGLAndroid
*/
bool assertNativeWindowIsValid = false;
};
Platform() noexcept;

View File

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

View File

@@ -30,10 +30,6 @@ namespace filament::backend {
struct TargetBufferInfo {
// note: the parameters of this constructor are not in the order of this structure's fields
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level, uint16_t layer, uint8_t baseViewIndex) noexcept
: handle(handle), baseViewIndex(baseViewIndex), level(level), layer(layer) {
}
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level, uint16_t layer) noexcept
: handle(handle), level(level), layer(layer) {
}
@@ -51,14 +47,15 @@ struct TargetBufferInfo {
// texture to be used as render target
Handle<HwTexture> handle;
// Starting layer index for multiview. This value is only used when the `layerCount` for the
// render target is greater than 1.
uint8_t baseViewIndex = 0;
// level to be used
uint8_t level = 0;
// For cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping
// - For cubemap textures, this indicates the face of the cubemap. See TextureCubemapFace for
// the face->layer mapping)
// - For 2d array, cubemap array, and 3d textures, this indicates an index of a single layer of
// them.
// - For multiview textures (i.e., layerCount for the RenderTarget is greater than 1), this
// indicates a starting layer index of the current 2d array texture for multiview.
uint16_t layer = 0;
};
@@ -103,7 +100,7 @@ public:
// this is here for backward compatibility
MRT(Handle<HwTexture> handle, uint8_t level, uint16_t layer) noexcept
: mInfos{{ handle, level, layer, 0 }} {
: mInfos{{ handle, level, layer }} {
}
};

View File

@@ -89,14 +89,28 @@ protected:
*/
AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept override;
protected:
bool makeCurrent(ContextType type,
SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept override;
private:
struct InitializeJvmForPerformanceManagerIfNeeded {
InitializeJvmForPerformanceManagerIfNeeded();
};
int mOSVersion;
ExternalStreamManagerAndroid& mExternalStreamManager;
InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded;
utils::PerformanceHintManager mPerformanceHintManager;
utils::PerformanceHintManager::Session mPerformanceHintSession;
using clock = std::chrono::high_resolution_clock;
clock::time_point mStartTimeOfActualWork;
void* mNativeWindowLib = nullptr;
int32_t (*ANativeWindow_getBuffersDefaultDataSpace)(ANativeWindow* window) = nullptr;
bool mAssertNativeWindowIsValid = false;
};
} // namespace filament::backend

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H
#include <stdint.h>
#include "bluegl/BlueGL.h"
#include <osmesa.h>
#include <backend/platforms/OpenGLPlatform.h>
#include <backend/DriverEnums.h>
namespace filament::backend {
/**
* A concrete implementation of OpenGLPlatform that uses OSMesa, which is an offscreen
* context that can be used in conjunction with Mesa for software rasterization.
* See https://docs.mesa3d.org/osmesa.html for more information.
*/
class PlatformOSMesa : public OpenGLPlatform {
protected:
// --------------------------------------------------------------------------------------------
// Platform Interface
Driver* createDriver(void* sharedGLContext, const DriverConfig& driverConfig) noexcept override;
int getOSVersion() const noexcept final override { return 0; }
// --------------------------------------------------------------------------------------------
// OpenGLPlatform Interface
void terminate() noexcept override;
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
bool makeCurrent(ContextType type, SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept override;
void commit(SwapChain* swapChain) noexcept override;
private:
OSMesaContext mContext;
void* mOsMesaApi = nullptr;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H

View File

@@ -23,9 +23,9 @@
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Hash.h>
#include <utils/PrivateImplementation.h>
#include <string_view>
#include <tuple>
#include <unordered_set>
@@ -47,6 +47,14 @@ struct VulkanPlatformPrivate;
class VulkanPlatform : public Platform, utils::PrivateImplementation<VulkanPlatformPrivate> {
public:
struct ExtensionHashFn {
std::size_t operator()(utils::CString const& s) const noexcept {
return std::hash<std::string>{}(s.data());
}
};
// Utility for managing device or instance extensions during initialization.
using ExtensionSet = std::unordered_set<utils::CString, ExtensionHashFn>;
/**
* A collection of handles to objects and metadata that comprises a Vulkan context. The client
* can instantiate this struct and pass to Engine::Builder::sharedContext if they wishes to
@@ -80,6 +88,21 @@ public:
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
VkFormat depthFormat = VK_FORMAT_UNDEFINED;
VkExtent2D extent = {0, 0};
bool isProtected = false;
};
struct ImageSyncData {
static constexpr uint32_t INVALID_IMAGE_INDEX = UINT32_MAX;
// The index of the next image as returned by vkAcquireNextImage or equivalent.
uint32_t imageIndex = INVALID_IMAGE_INDEX;
// Semaphore to be signaled once the image is available.
VkSemaphore imageReadySemaphore = VK_NULL_HANDLE;
// A function called right before vkQueueSubmit. After this call, the image must be
// available. This pointer can be null if imageReadySemaphore is not VK_NULL_HANDLE.
std::function<void(SwapChainPtr handle)> explicitImageReadyWait = nullptr;
};
VulkanPlatform();
@@ -119,6 +142,12 @@ public:
* before recreating the swapchain. Default is true.
*/
bool flushAndWaitOnWindowResize = true;
/**
* Whether the swapchain image should be transitioned to a layout suitable for
* presentation. Default is true.
*/
bool transitionSwapChainImageLayoutForPresent = true;
};
/**
@@ -147,13 +176,10 @@ public:
* corresponding VkImage will be used as the output color attachment. The client should signal
* the `clientSignal` semaphore when the image is ready to be used by the backend.
* @param handle The handle returned by createSwapChain()
* @param clientSignal The semaphore that the client will signal to indicate that the backend
* may render into the image.
* @param index Pointer to memory that will be filled with the index that corresponding
* to an image in the `SwapChainBundle.colors` array.
* @param outImageSyncData The synchronization data used for image readiness
* @return Result of acquire
*/
virtual VkResult acquire(SwapChainPtr handle, VkSemaphore clientSignal, uint32_t* index);
virtual VkResult acquire(SwapChainPtr handle, ImageSyncData* outImageSyncData);
/**
* Present the image corresponding to `index` to the display. The client should wait on
@@ -174,6 +200,13 @@ public:
*/
virtual bool hasResized(SwapChainPtr handle);
/**
* Check if the surface is protected.
* @param handle The handle returned by createSwapChain()
* @return Whether the swapchain is protected
*/
virtual bool isProtected(SwapChainPtr handle);
/**
* Carry out a recreation of the swapchain.
* @param handle The handle returned by createSwapChain()
@@ -192,6 +225,13 @@ public:
virtual SwapChainPtr createSwapChain(void* nativeWindow, uint64_t flags = 0,
VkExtent2D extent = {0, 0});
/**
* Allows implementers to provide instance extensions that they'd like to include in the
* instance creation.
* @return A set of extensions to enable for the instance.
*/
virtual ExtensionSet getRequiredInstanceExtensions() { return {}; }
/**
* Destroy the swapchain.
* @param handle The handle returned by createSwapChain()
@@ -235,11 +275,27 @@ public:
*/
VkQueue getGraphicsQueue() const noexcept;
private:
// Platform dependent helper methods
using ExtensionSet = std::unordered_set<std::string_view>;
static ExtensionSet getRequiredInstanceExtensions();
/**
* @return The family index of the protected graphics queue selected for the
* Vulkan backend.
*/
uint32_t getProtectedGraphicsQueueFamilyIndex() const noexcept;
/**
* @return The index of the protected graphics queue (if there are multiple
* graphics queues) selected for the Vulkan backend.
*/
uint32_t getProtectedGraphicsQueueIndex() const noexcept;
/**
* @return The protected queue that was selected for the Vulkan backend.
*/
VkQueue getProtectedGraphicsQueue() const noexcept;
private:
static ExtensionSet getSwapchainInstanceExtensions();
// Platform dependent helper methods
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
static SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
uint64_t flags) noexcept;

View File

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

View File

@@ -139,13 +139,13 @@ DECL_DRIVER_API_N(beginFrame,
DECL_DRIVER_API_N(setFrameScheduledCallback,
backend::SwapChainHandle, sch,
backend::CallbackHandler*, handler,
backend::FrameScheduledCallback&&, callback)
backend::FrameScheduledCallback&&, callback,
uint64_t, flags)
DECL_DRIVER_API_N(setFrameCompletedCallback,
backend::SwapChainHandle, sch,
backend::CallbackHandler*, handler,
backend::CallbackHandler::Callback, callback,
void*, user)
utils::Invocable<void(void)>&&, callback)
DECL_DRIVER_API_N(setPresentationTime,
int64_t, monotonic_clock_ns)
@@ -163,6 +163,10 @@ DECL_DRIVER_API_0(finish)
// reset state tracking, if the driver does any state tracking (e.g. GL)
DECL_DRIVER_API_0(resetState)
DECL_DRIVER_API_N(setDebugTag,
backend::HandleBase::HandleId, handleId,
utils::CString, tag)
/*
* Creating driver objects
* -----------------------
@@ -197,20 +201,33 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, createTexture,
uint32_t, depth,
backend::TextureUsage, usage)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureSwizzled,
backend::SamplerType, target,
uint8_t, levels,
backend::TextureFormat, format,
uint8_t, samples,
uint32_t, width,
uint32_t, height,
uint32_t, depth,
backend::TextureUsage, usage,
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureView,
backend::TextureHandle, texture,
uint8_t, baseLevel,
uint8_t, levelCount)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureViewSwizzle,
backend::TextureHandle, texture,
backend::TextureSwizzle, r,
backend::TextureSwizzle, g,
backend::TextureSwizzle, b,
backend::TextureSwizzle, a)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureExternalImage,
backend::TextureFormat, format,
uint32_t, width,
uint32_t, height,
backend::TextureUsage, usage,
void*, image)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureExternalImagePlane,
backend::TextureFormat, format,
uint32_t, width,
uint32_t, height,
backend::TextureUsage, usage,
void*, image,
uint32_t, plane)
DECL_DRIVER_API_R_N(backend::TextureHandle, importTexture,
intptr_t, id,
backend::SamplerType, target,
@@ -222,9 +239,6 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, importTexture,
uint32_t, depth,
backend::TextureUsage, usage)
DECL_DRIVER_API_R_N(backend::SamplerGroupHandle, createSamplerGroup,
uint32_t, size, utils::FixedSizeString<32>, debugName)
DECL_DRIVER_API_R_N(backend::RenderPrimitiveHandle, createRenderPrimitive,
backend::VertexBufferHandle, vbh,
backend::IndexBufferHandle, ibh,
@@ -258,25 +272,53 @@ DECL_DRIVER_API_R_N(backend::SwapChainHandle, createSwapChainHeadless,
DECL_DRIVER_API_R_0(backend::TimerQueryHandle, createTimerQuery)
DECL_DRIVER_API_R_N(backend::DescriptorSetLayoutHandle, createDescriptorSetLayout,
backend::DescriptorSetLayout&&, info)
DECL_DRIVER_API_R_N(backend::DescriptorSetHandle, createDescriptorSet,
backend::DescriptorSetLayoutHandle, dslh)
DECL_DRIVER_API_N(updateDescriptorSetBuffer,
backend::DescriptorSetHandle, dsh,
backend::descriptor_binding_t, binding,
backend::BufferObjectHandle, boh,
uint32_t, offset,
uint32_t, size
)
DECL_DRIVER_API_N(updateDescriptorSetTexture,
backend::DescriptorSetHandle, dsh,
backend::descriptor_binding_t, binding,
backend::TextureHandle, th,
SamplerParams, params
)
DECL_DRIVER_API_N(bindDescriptorSet,
backend::DescriptorSetHandle, dsh,
backend::descriptor_set_t, set,
backend::DescriptorSetOffsetArray&&, offsets
)
/*
* Destroying driver objects
* -------------------------
*/
DECL_DRIVER_API_N(destroyVertexBuffer, backend::VertexBufferHandle, vbh)
DECL_DRIVER_API_N(destroyVertexBufferInfo,backend::VertexBufferInfoHandle, vbih)
DECL_DRIVER_API_N(destroyIndexBuffer, backend::IndexBufferHandle, ibh)
DECL_DRIVER_API_N(destroyBufferObject, backend::BufferObjectHandle, ibh)
DECL_DRIVER_API_N(destroyRenderPrimitive, backend::RenderPrimitiveHandle, rph)
DECL_DRIVER_API_N(destroyProgram, backend::ProgramHandle, ph)
DECL_DRIVER_API_N(destroySamplerGroup, backend::SamplerGroupHandle, sbh)
DECL_DRIVER_API_N(destroyTexture, backend::TextureHandle, th)
DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth)
DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch)
DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh)
DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh)
DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh)
DECL_DRIVER_API_N(destroyVertexBuffer, backend::VertexBufferHandle, vbh)
DECL_DRIVER_API_N(destroyVertexBufferInfo, backend::VertexBufferInfoHandle, vbih)
DECL_DRIVER_API_N(destroyIndexBuffer, backend::IndexBufferHandle, ibh)
DECL_DRIVER_API_N(destroyBufferObject, backend::BufferObjectHandle, ibh)
DECL_DRIVER_API_N(destroyRenderPrimitive, backend::RenderPrimitiveHandle, rph)
DECL_DRIVER_API_N(destroyProgram, backend::ProgramHandle, ph)
DECL_DRIVER_API_N(destroyTexture, backend::TextureHandle, th)
DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth)
DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch)
DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh)
DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh)
DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh)
DECL_DRIVER_API_N(destroyDescriptorSetLayout, backend::DescriptorSetLayoutHandle, dslh)
DECL_DRIVER_API_N(destroyDescriptorSet, backend::DescriptorSetHandle, dsh)
/*
* Synchronous APIs
@@ -301,11 +343,12 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameTimeSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAutoDepthResolveSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isSRGBSwapChainSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedContentSupported)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isStereoSupported, backend::StereoscopicType, stereoscopicType)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isStereoSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isDepthStencilBlitSupported, backend::TextureFormat, format)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedTexturesSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthClampSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
@@ -342,15 +385,6 @@ DECL_DRIVER_API_N(updateBufferObjectUnsynchronized,
DECL_DRIVER_API_N(resetBufferObject,
backend::BufferObjectHandle, ibh)
DECL_DRIVER_API_N(updateSamplerGroup,
backend::SamplerGroupHandle, ubh,
backend::BufferDescriptor&&, data)
DECL_DRIVER_API_N(setMinMaxLevels,
backend::TextureHandle, th,
uint32_t, minLevel,
uint32_t, maxLevel)
DECL_DRIVER_API_N(update3DImage,
backend::TextureHandle, th,
uint32_t, level,
@@ -365,10 +399,12 @@ DECL_DRIVER_API_N(update3DImage,
DECL_DRIVER_API_N(generateMipmaps,
backend::TextureHandle, th)
// Deprecated
DECL_DRIVER_API_N(setExternalImage,
backend::TextureHandle, th,
void*, image)
// Deprecated
DECL_DRIVER_API_N(setExternalImagePlane,
backend::TextureHandle, th,
void*, image,
@@ -415,37 +451,16 @@ DECL_DRIVER_API_N(commit,
* -----------------------
*/
DECL_DRIVER_API_N(bindUniformBuffer,
uint32_t, index,
backend::BufferObjectHandle, ubh)
DECL_DRIVER_API_N(bindBufferRange,
BufferObjectBinding, bindingType,
uint32_t, index,
backend::BufferObjectHandle, ubh,
uint32_t, offset,
uint32_t, size)
DECL_DRIVER_API_N(unbindBuffer,
BufferObjectBinding, bindingType,
uint32_t, index)
DECL_DRIVER_API_N(bindSamplers,
uint32_t, index,
backend::SamplerGroupHandle, sbh)
DECL_DRIVER_API_N(setPushConstant,
backend::ShaderStage, stage,
uint8_t, index,
backend::PushConstantVariant, value)
DECL_DRIVER_API_N(insertEventMarker,
const char*, string,
uint32_t, len = 0)
const char*, string)
DECL_DRIVER_API_N(pushGroupMarker,
const char*, string,
uint32_t, len = 0)
const char*, string)
DECL_DRIVER_API_0(popGroupMarker)
@@ -502,7 +517,7 @@ DECL_DRIVER_API_N(blit,
math::uint2, size)
DECL_DRIVER_API_N(bindPipeline,
backend::PipelineState, state)
backend::PipelineState const&, state)
DECL_DRIVER_API_N(bindRenderPrimitive,
backend::RenderPrimitiveHandle, rph)

View File

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

View File

@@ -20,11 +20,12 @@
#include <backend/Handle.h>
#include <utils/Allocator.h>
#include <utils/CString.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <utils/Panic.h>
#include <tsl/robin_map.h>
@@ -37,9 +38,9 @@
#include <stddef.h>
#include <stdint.h>
#define HandleAllocatorGL HandleAllocator<32, 64, 136> // ~4520 / pool / MiB
#define HandleAllocatorGL HandleAllocator<32, 96, 136> // ~4520 / pool / MiB
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB
#define HandleAllocatorMTL HandleAllocator<32, 48, 552> // ~1660 / pool / MiB
#define HandleAllocatorMTL HandleAllocator<32, 64, 552> // ~1660 / pool / MiB
namespace filament::backend {
@@ -168,19 +169,53 @@ public:
auto [p, tag] = handleToPointer(handle.getId());
if (isPoolHandle(handle.getId())) {
// check for use after free
// check for pool handle use-after-free
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
auto const pNode = static_cast<typename Allocator::Node*>(p);
uint8_t const expectedAge = pNode[-1].age;
ASSERT_POSTCONDITION(expectedAge == age,
"use-after-free of Handle with id=%d", handle.getId());
// getHandleTag() is only called if the check fails.
FILAMENT_CHECK_POSTCONDITION(expectedAge == age)
<< "use-after-free of Handle with id=" << handle.getId()
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
}
} else {
// check for heap handle use-after-free
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
uint8_t const index = (handle.getId() & HANDLE_INDEX_MASK);
// if we've already handed out this handle index before, it's definitely a
// use-after-free, otherwise it's probably just a corrupted handle
if (index < mId) {
FILAMENT_CHECK_POSTCONDITION(p != nullptr)
<< "use-after-free of heap Handle with id=" << handle.getId()
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
} else {
FILAMENT_CHECK_POSTCONDITION(p != nullptr)
<< "corrupted heap Handle with id=" << handle.getId()
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
}
}
}
return static_cast<Dp>(p);
}
template<typename B>
bool is_valid(Handle<B>& handle) {
if (!handle) {
// null handles are invalid
return false;
}
auto [p, tag] = handleToPointer(handle.getId());
if (isPoolHandle(handle.getId())) {
uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
auto const pNode = static_cast<typename Allocator::Node*>(p);
uint8_t const expectedAge = pNode[-1].age;
return expectedAge == age;
}
return p != nullptr;
}
template<typename Dp, typename B>
inline typename std::enable_if_t<
std::is_pointer_v<Dp> &&
@@ -189,6 +224,29 @@ public:
return handle_cast<Dp>(const_cast<Handle<B>&>(handle));
}
void associateTagToHandle(HandleBase::HandleId id, utils::CString&& tag) noexcept {
// TODO: for now, only pool handles check for use-after-free, so we only keep tags for
// those
if (isPoolHandle(id)) {
// Truncate the age to get the debug tag
uint32_t const key = id & ~(HANDLE_DEBUG_TAG_MASK ^ HANDLE_AGE_MASK);
// This line is the costly part. In the future, we could potentially use a custom
// allocator.
mDebugTags[key] = std::move(tag);
}
}
utils::CString getHandleTag(HandleBase::HandleId id) const noexcept {
if (!isPoolHandle(id)) {
return "(no tag)";
}
uint32_t const key = id & ~(HANDLE_DEBUG_TAG_MASK ^ HANDLE_AGE_MASK);
if (auto pos = mDebugTags.find(key); pos != mDebugTags.end()) {
return pos->second;
}
return "(no tag)";
}
private:
template<typename D>
@@ -240,8 +298,8 @@ private:
Node* const pNode = static_cast<Node*>(p);
uint8_t& expectedAge = pNode[-1].age;
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
ASSERT_POSTCONDITION(expectedAge == age,
"double-free of Handle of size %d at %p", size, p);
FILAMENT_CHECK_POSTCONDITION(expectedAge == age) <<
"double-free of Handle of size " << size << " at " << p;
}
expectedAge = (expectedAge + 1) & 0xF; // fixme
@@ -306,12 +364,24 @@ private:
}
}
// we handle a 4 bits age per address
static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u; // pool vs heap handle
static constexpr uint32_t HANDLE_AGE_MASK = 0x78000000u; // handle's age
static constexpr uint32_t HANDLE_INDEX_MASK = 0x07FFFFFFu; // handle index
static constexpr uint32_t HANDLE_TAG_MASK = HANDLE_AGE_MASK;
static constexpr uint32_t HANDLE_AGE_SHIFT = 27;
// number if bits allotted to the handle's age (currently 4 max)
static constexpr uint32_t HANDLE_AGE_BIT_COUNT = 4;
// number if bits allotted to the handle's debug tag (HANDLE_AGE_BIT_COUNT max)
static constexpr uint32_t HANDLE_DEBUG_TAG_BIT_COUNT = 2;
// bit shift for both the age and debug tag
static constexpr uint32_t HANDLE_AGE_SHIFT = 27;
// mask for the heap (vs pool) flag
static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u;
// mask for the age
static constexpr uint32_t HANDLE_AGE_MASK =
((1 << HANDLE_AGE_BIT_COUNT) - 1) << HANDLE_AGE_SHIFT;
// mask for the debug tag
static constexpr uint32_t HANDLE_DEBUG_TAG_MASK =
((1 << HANDLE_DEBUG_TAG_BIT_COUNT) - 1) << HANDLE_AGE_SHIFT;
// mask for the index
static constexpr uint32_t HANDLE_INDEX_MASK = 0x07FFFFFFu;
static_assert(HANDLE_DEBUG_TAG_BIT_COUNT <= HANDLE_AGE_BIT_COUNT);
static bool isPoolHandle(HandleBase::HandleId id) noexcept {
return (id & HANDLE_HEAP_FLAG) == 0u;
@@ -326,7 +396,7 @@ private:
// a non-pool handle.
if (UTILS_LIKELY(isPoolHandle(id))) {
char* const base = (char*)mHandleArena.getArea().begin();
uint32_t const tag = id & HANDLE_TAG_MASK;
uint32_t const tag = id & HANDLE_AGE_MASK;
size_t const offset = (id & HANDLE_INDEX_MASK) * Allocator::getAlignment();
return { static_cast<void*>(base + offset), tag };
}
@@ -341,7 +411,7 @@ private:
size_t const offset = (char*)p - base;
assert_invariant((offset % Allocator::getAlignment()) == 0);
auto id = HandleBase::HandleId(offset / Allocator::getAlignment());
id |= tag & HANDLE_TAG_MASK;
id |= tag & HANDLE_AGE_MASK;
assert_invariant((id & HANDLE_HEAP_FLAG) == 0);
return id;
}
@@ -351,6 +421,7 @@ private:
// Below is only used when running out of space in the HandleArena
mutable utils::Mutex mLock;
tsl::robin_map<HandleBase::HandleId, void*> mOverflowMap;
tsl::robin_map<HandleBase::HandleId, utils::CString> mDebugTags;
HandleBase::HandleId mId = 0;
bool mUseAfterFreeCheckDisabled = false;
};

View File

@@ -27,8 +27,8 @@ PresentCallable::PresentCallable(PresentFn fn, void* user) noexcept
}
void PresentCallable::operator()(bool presentFrame) noexcept {
ASSERT_PRECONDITION(mPresentFn, "This PresentCallable was already called. " \
"PresentCallables should be called exactly once.");
FILAMENT_CHECK_PRECONDITION(mPresentFn) << "This PresentCallable was already called. "
"PresentCallables should be called exactly once.";
mPresentFn(presentFrame, mUser);
// Set mPresentFn to nullptr to denote that the callable has been called.
mPresentFn = nullptr;

View File

@@ -24,7 +24,7 @@
#include <utils/debug.h>
#include <utils/ostream.h>
#if !defined(WIN32) && !defined(__EMSCRIPTEN__) && !defined(IOS)
#if !defined(WIN32) && !defined(__EMSCRIPTEN__)
# include <sys/mman.h>
# include <unistd.h>
# define HAS_MMAP 1
@@ -32,10 +32,11 @@
# define HAS_MMAP 0
#endif
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace utils;
@@ -81,6 +82,9 @@ void* CircularBuffer::alloc(size_t size) noexcept {
// map the circular buffer once...
vaddr = mmap(reserve_vaddr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if (vaddr != MAP_FAILED) {
// populate the address space with pages (because this is a circular buffer,
// all the pages will be allocated eventually, might as well do it now)
memset(vaddr, 0, size);
// and map the circular buffer again, behind the previous copy...
vaddr_shadow = mmap((char*)vaddr + size, size,
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
@@ -101,7 +105,7 @@ void* CircularBuffer::alloc(size_t size) noexcept {
if (UTILS_UNLIKELY(mAshmemFd < 0)) {
// ashmem failed
if (vaddr_guard != MAP_FAILED) {
munmap(vaddr_guard, size);
munmap(vaddr_guard, BLOCK_SIZE);
}
if (vaddr_shadow != MAP_FAILED) {
@@ -119,12 +123,11 @@ void* CircularBuffer::alloc(size_t size) noexcept {
data = mmap(nullptr, size * 2 + BLOCK_SIZE,
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_POSTCONDITION(data,
"couldn't allocate %u KiB of virtual address space for the command buffer",
(size * 2 / 1024));
FILAMENT_CHECK_POSTCONDITION(data != MAP_FAILED) <<
"couldn't allocate " << (size * 2 / 1024) <<
" KiB of virtual address space for the command buffer";
slog.d << "WARNING: Using soft CircularBuffer (" << (size * 2 / 1024) << " KiB)"
<< io::endl;
slog.w << "Using 'soft' CircularBuffer (" << (size * 2 / 1024) << " KiB)" << io::endl;
// guard page at the end
void* guard = (void*)(uintptr_t(data) + size * 2);

View File

@@ -74,8 +74,6 @@ void CommandBufferQueue::setPaused(bool paused) {
bool CommandBufferQueue::isExitRequested() const {
std::lock_guard<utils::Mutex> const lock(mLock);
ASSERT_PRECONDITION( mExitRequested == 0 || mExitRequested == EXIT_REQUESTED,
"mExitRequested is corrupted (value = 0x%08x)!", mExitRequested);
return (bool)mExitRequested;
}
@@ -103,22 +101,23 @@ void CommandBufferQueue::flush() noexcept {
size_t const used = std::distance(
static_cast<char const*>(begin), static_cast<char const*>(end));
std::unique_lock<utils::Mutex> lock(mLock);
// circular buffer is too small, we corrupted the stream
FILAMENT_CHECK_POSTCONDITION(used <= mFreeSpace) <<
"Backend CommandStream overflow. Commands are corrupted and unrecoverable.\n"
"Please increase minCommandBufferSizeMB inside the Config passed to Engine::create.\n"
"Space used at this time: " << used <<
" bytes, overflow: " << used - mFreeSpace << " bytes";
mFreeSpace -= used;
mCommandBuffersToExecute.push_back({ begin, end });
mCondition.notify_one();
// circular buffer is too small, we corrupted the stream
ASSERT_POSTCONDITION(used <= mFreeSpace,
"Backend CommandStream overflow. Commands are corrupted and unrecoverable.\n"
"Please increase minCommandBufferSizeMB inside the Config passed to Engine::create.\n"
"Space used at this time: %u bytes, overflow: %u bytes",
(unsigned)used, unsigned(used - mFreeSpace));
// wait until there is enough space in the buffer
mFreeSpace -= used;
if (UTILS_UNLIKELY(mFreeSpace < requiredSize)) {
#ifndef NDEBUG
size_t const totalUsed = circularBuffer.size() - mFreeSpace;
slog.d << "CommandStream used too much space (will block): "
@@ -131,9 +130,11 @@ void CommandBufferQueue::flush() noexcept {
#endif
SYSTRACE_NAME("waiting: CircularBuffer::flush()");
ASSERT_POSTCONDITION(!mPaused,
FILAMENT_CHECK_POSTCONDITION(!mPaused) <<
"CommandStream is full, but since the rendering thread is paused, "
"the buffer cannot flush and we will deadlock. Instead, abort.");
"the buffer cannot flush and we will deadlock. Instead, abort.";
mCondition.wait(lock, [this, requiredSize]() -> bool {
// TODO: on macOS, we need to call pumpEvents from time to time
return mFreeSpace >= requiredSize;
@@ -149,16 +150,14 @@ std::vector<CommandBufferQueue::Range> CommandBufferQueue::waitForCommands() con
while ((mCommandBuffersToExecute.empty() || mPaused) && !mExitRequested) {
mCondition.wait(lock);
}
ASSERT_PRECONDITION( mExitRequested == 0 || mExitRequested == EXIT_REQUESTED,
"mExitRequested is corrupted (value = 0x%08x)!", mExitRequested);
return std::move(mCommandBuffersToExecute);
}
void CommandBufferQueue::releaseBuffer(CommandBufferQueue::Range const& buffer) {
size_t const used = std::distance(
static_cast<char const*>(buffer.begin), static_cast<char const*>(buffer.end));
std::lock_guard<utils::Mutex> const lock(mLock);
mFreeSpace += uintptr_t(buffer.end) - uintptr_t(buffer.begin);
mFreeSpace += used;
mCondition.notify_one();
}

View File

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

View File

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

View File

@@ -80,6 +80,9 @@ HandleAllocator<P0, P1, P2>::HandleAllocator(const char* name, size_t size,
bool disableUseAfterFreeCheck) noexcept
: mHandleArena(name, size, disableUseAfterFreeCheck),
mUseAfterFreeCheckDisabled(disableUseAfterFreeCheck) {
// Reserve initial space for debug tags. This prevents excessive calls to malloc when the first
// few tags are set.
mDebugTags.reserve(512);
}
template <size_t P0, size_t P1, size_t P2>
@@ -113,9 +116,9 @@ HandleBase::HandleId HandleAllocator<P0, P1, P2>::allocateHandleSlow(size_t size
HandleBase::HandleId id = (++mId) | HANDLE_HEAP_FLAG;
ASSERT_POSTCONDITION(mId < HANDLE_HEAP_FLAG,
FILAMENT_CHECK_POSTCONDITION(mId < HANDLE_HEAP_FLAG) <<
"No more Handle ids available! This can happen if HandleAllocator arena has been full"
" for a while. Please increase FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB");
" for a while. Please increase FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB";
mOverflowMap.emplace(id, p);
lock.unlock();

View File

@@ -29,21 +29,25 @@
#include "backend/platforms/PlatformCocoaTouchGL.h"
#endif
#elif defined(__APPLE__)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
#include <backend/platforms/PlatformCocoaGL.h>
#endif
#elif defined(__linux__)
#if defined(FILAMENT_SUPPORTS_X11)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
#include "backend/platforms/PlatformGLX.h"
#endif
#elif defined(FILAMENT_SUPPORTS_EGL_ON_LINUX)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
#include "backend/platforms/PlatformEGLHeadless.h"
#endif
#elif defined(FILAMENT_SUPPORTS_OSMESA)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
#include "backend/platforms/PlatformOSMesa.h"
#endif
#endif
#elif defined(WIN32)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
#include "backend/platforms/PlatformWGL.h"
#endif
#elif defined(__EMSCRIPTEN__)
@@ -111,8 +115,7 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
}
assert_invariant(*backend == Backend::OPENGL);
#if defined(FILAMENT_SUPPORTS_OPENGL)
#if defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(FILAMENT_USE_SWIFTSHADER)
// Swiftshader OpenGLES support is deprecated and incomplete
#if defined(FILAMENT_USE_EXTERNAL_GLES3)
return nullptr;
#elif defined(__ANDROID__)
return new PlatformEGLAndroid();
@@ -125,6 +128,8 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
return new PlatformGLX();
#elif defined(FILAMENT_SUPPORTS_EGL_ON_LINUX)
return new PlatformEGLHeadless();
#elif defined(FILAMENT_SUPPORTS_OSMESA)
return new PlatformOSMesa();
#else
return nullptr;
#endif

View File

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

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_SYSTRACEPROFILE_H
#define TNT_FILAMENT_BACKEND_SYSTRACEPROFILE_H
#include <utils/Systrace.h>
#define PROFILE_SCOPE(marker) SYSTRACE_NAME(marker)
#define PROFILE_NAME_BEGINFRAME "backend::beginFrame"
#define PROFILE_NAME_ENDFRAME "backend::endFrame"
#endif // TNT_FILAMENT_BACKEND_SYSTRACEPROFILE_H

View File

@@ -16,13 +16,23 @@
#include "private/backend/VirtualMachineEnv.h"
#include <utils/compiler.h>
#include <utils/debug.h>
#include <jni.h>
namespace filament {
JavaVM* VirtualMachineEnv::sVirtualMachine = nullptr;
// This is called when the library is loaded. We need this to get a reference to the global VM
/*
* This is typically called by filament_jni.so when it is loaded. If filament_jni.so is not used,
* then this must be called manually -- however, this is a problem because VirtualMachineEnv.h
* is currently private and part of backend.
* For now, we authorize this usage, but we will need to fix it; by making a proper public
* API for this.
*/
UTILS_PUBLIC
UTILS_NOINLINE
jint VirtualMachineEnv::JNI_OnLoad(JavaVM* vm) noexcept {
JNIEnv* env = nullptr;

View File

@@ -109,9 +109,8 @@ inline bool MTLSizeEqual(T a, T b) noexcept {
MetalBlitter::MetalBlitter(MetalContext& context) noexcept : mContext(context) { }
void MetalBlitter::blit(id<MTLCommandBuffer> cmdBuffer, const BlitArgs& args, const char* label) {
ASSERT_PRECONDITION(args.source.region.size.depth == args.destination.region.size.depth,
"Blitting requires the source and destination regions to have the same depth.");
FILAMENT_CHECK_PRECONDITION(args.source.region.size.depth == args.destination.region.size.depth)
<< "Blitting requires the source and destination regions to have the same depth.";
// Determine if the blit for color or depth are eligible to use a MTLBlitCommandEncoder.
// blitFastPath returns true upon success.
@@ -327,7 +326,8 @@ id<MTLFunction> MetalBlitter::compileFragmentFunction(BlitFunctionKey key) const
utils::slog.e << description << utils::io::endl;
}
}
ASSERT_POSTCONDITION(library && function, "Unable to compile fragment shader for MetalBlitter.");
FILAMENT_CHECK_POSTCONDITION(library && function)
<< "Unable to compile fragment shader for MetalBlitter.";
return function;
}
@@ -352,7 +352,8 @@ id<MTLFunction> MetalBlitter::getBlitVertexFunction() {
utils::slog.e << description << utils::io::endl;
}
}
ASSERT_POSTCONDITION(library && function, "Unable to compile vertex shader for MetalBlitter.");
FILAMENT_CHECK_POSTCONDITION(library && function)
<< "Unable to compile vertex shader for MetalBlitter.";
mVertexFunction = function;

View File

@@ -65,21 +65,19 @@ private:
const char* mName;
};
#ifndef FILAMENT_METAL_BUFFER_TRACKING
#define FILAMENT_METAL_BUFFER_TRACKING 0
#endif
class MetalBufferTracking {
class TrackedMetalBuffer {
public:
static constexpr size_t EXCESS_BUFFER_COUNT = 30000;
enum class Type {
NONE = 0,
GENERIC = 1,
RING = 2,
RING = 2, // deprecated
STAGING = 3,
DESCRIPTOR_SET = 4,
};
static constexpr size_t TypeCount = 3;
static constexpr size_t TypeCount = 4;
static constexpr auto toIndex(Type t) {
assert_invariant(t != Type::NONE);
@@ -91,60 +89,71 @@ public:
return 1;
case Type::STAGING:
return 2;
case Type::DESCRIPTOR_SET:
return 3;
}
}
#if FILAMENT_METAL_BUFFER_TRACKING
static void initialize() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
for (size_t i = 0; i < TypeCount; i++) {
aliveBuffers[i] = [NSHashTable weakObjectsHashTable];
}
});
}
static void setPlatform(MetalPlatform* p) { platform = p; }
static void track(id<MTLBuffer> buffer, Type type) {
TrackedMetalBuffer() noexcept : mBuffer(nil) {}
TrackedMetalBuffer(nullptr_t) noexcept : mBuffer(nil) {}
TrackedMetalBuffer(id<MTLBuffer> buffer, Type type) : mBuffer(buffer), mType(type) {
assert_invariant(type != Type::NONE);
if (UTILS_UNLIKELY(getAliveBuffers() >= EXCESS_BUFFER_COUNT)) {
if (platform && platform->hasDebugUpdateStatFunc()) {
platform->debugUpdateStat("filament.metal.excess_buffers_allocated",
MetalBufferTracking::getAliveBuffers());
if (buffer) {
aliveBuffers[toIndex(type)]++;
mType = type;
if (getAliveBuffers() >= EXCESS_BUFFER_COUNT) {
if (platform && platform->hasDebugUpdateStatFunc()) {
platform->debugUpdateStat("filament.metal.excess_buffers_allocated",
TrackedMetalBuffer::getAliveBuffers());
}
}
}
[aliveBuffers[toIndex(type)] addObject:buffer];
}
~TrackedMetalBuffer() {
if (mBuffer) {
assert_invariant(mType != Type::NONE);
aliveBuffers[toIndex(mType)]--;
}
}
TrackedMetalBuffer(TrackedMetalBuffer&&) = delete;
TrackedMetalBuffer(TrackedMetalBuffer const&) = delete;
TrackedMetalBuffer& operator=(TrackedMetalBuffer const&) = delete;
TrackedMetalBuffer& operator=(TrackedMetalBuffer&& rhs) noexcept {
swap(rhs);
return *this;
}
id<MTLBuffer> get() const noexcept { return mBuffer; }
operator bool() const noexcept { return bool(mBuffer); }
static uint64_t getAliveBuffers() {
uint64_t sum = 0;
for (size_t i = 1; i < TypeCount; i++) {
sum += getAliveBuffers(static_cast<Type>(i));
for (const auto& v : aliveBuffers) {
sum += v;
}
return sum;
}
static uint64_t getAliveBuffers(Type type) {
assert_invariant(type != Type::NONE);
NSHashTable* hashTable = aliveBuffers[toIndex(type)];
// Caution! We can't simply use hashTable.count here, which is inaccurate.
// See http://cocoamine.net/blog/2013/12/13/nsmaptable-and-zeroing-weak-references/
return hashTable.objectEnumerator.allObjects.count;
return aliveBuffers[toIndex(type)];
}
#else
static void initialize() {}
static void setPlatform(MetalPlatform* p) {}
static id<MTLBuffer> track(id<MTLBuffer> buffer, Type type) { return buffer; }
static uint64_t getAliveBuffers() { return 0; }
static uint64_t getAliveBuffers(Type type) { return 0; }
#endif
static void setPlatform(MetalPlatform* p) { platform = p; }
private:
#if FILAMENT_METAL_BUFFER_TRACKING
static std::array<NSHashTable<id<MTLBuffer>>*, TypeCount> aliveBuffers;
void swap(TrackedMetalBuffer& other) noexcept {
std::swap(mBuffer, other.mBuffer);
std::swap(mType, other.mType);
}
id<MTLBuffer> mBuffer;
Type mType = Type::NONE;
static MetalPlatform* platform;
#endif
static std::array<uint64_t, TypeCount> aliveBuffers;
};
class MetalBuffer {
@@ -154,6 +163,8 @@ public:
size_t size, bool forceGpuBuffer = false);
~MetalBuffer();
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer || mCpuBuffer; }
MetalBuffer(const MetalBuffer& rhs) = delete;
MetalBuffer& operator=(const MetalBuffer& rhs) = delete;
@@ -163,8 +174,10 @@ public:
* Update the buffer with data inside src. Potentially allocates a new buffer allocation to hold
* the bytes which will be released when the current frame is finished.
*/
void copyIntoBuffer(void* src, size_t size, size_t byteOffset);
void copyIntoBufferUnsynchronized(void* src, size_t size, size_t byteOffset);
using TagResolver = utils::Invocable<const char*(void)>;
void copyIntoBuffer(void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag);
void copyIntoBufferUnsynchronized(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag);
/**
* Denotes that this buffer is used for a draw call ensuring that its allocation remains valid
@@ -174,7 +187,7 @@ public:
* is no device allocation.
*
*/
id<MTLBuffer> getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) noexcept;
id<MTLBuffer> getGpuBufferForDraw() noexcept;
void* getCpuBuffer() const noexcept { return mCpuBuffer; }
@@ -198,7 +211,18 @@ public:
private:
id<MTLBuffer> mBuffer;
enum class UploadStrategy {
POOL,
BUMP_ALLOCATOR,
};
void uploadWithPoolBuffer(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const;
void uploadWithBumpAllocator(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const;
UploadStrategy mUploadStrategy;
TrackedMetalBuffer mBuffer;
size_t mBufferSize = 0;
void* mCpuBuffer = nullptr;
MetalContext& mContext;
@@ -247,11 +271,9 @@ public:
mBufferOptions(options),
mSlotSizeBytes(computeSlotSize(layout)),
mSlotCount(slotCount) {
{
ScopedAllocationTimer timer("ring");
mBuffer = [device newBufferWithLength:mSlotSizeBytes * mSlotCount options:mBufferOptions];
}
MetalBufferTracking::track(mBuffer, MetalBufferTracking::Type::RING);
ScopedAllocationTimer timer("ring");
mBuffer = { [device newBufferWithLength:mSlotSizeBytes * mSlotCount options:mBufferOptions],
TrackedMetalBuffer::Type::RING };
assert_invariant(mBuffer);
}
@@ -271,11 +293,11 @@ public:
// finishes executing.
{
ScopedAllocationTimer timer("ring");
mAuxBuffer = [mDevice newBufferWithLength:mSlotSizeBytes options:mBufferOptions];
mAuxBuffer = { [mDevice newBufferWithLength:mSlotSizeBytes options:mBufferOptions],
TrackedMetalBuffer::Type::RING };
}
MetalBufferTracking::track(mAuxBuffer, MetalBufferTracking::Type::RING);
assert_invariant(mAuxBuffer);
return { mAuxBuffer, 0 };
return { mAuxBuffer.get(), 0 };
}
mCurrentSlot = (mCurrentSlot + 1) % mSlotCount;
mOccupiedSlots->fetch_add(1, std::memory_order_relaxed);
@@ -304,9 +326,9 @@ public:
*/
std::pair<id<MTLBuffer>, NSUInteger> getCurrentAllocation() const {
if (UTILS_UNLIKELY(mAuxBuffer)) {
return { mAuxBuffer, 0 };
return { mAuxBuffer.get(), 0 };
}
return { mBuffer, mCurrentSlot * mSlotSizeBytes };
return { mBuffer.get(), mCurrentSlot * mSlotSizeBytes };
}
bool canAccomodateLayout(MTLSizeAndAlign layout) const {
@@ -315,8 +337,8 @@ public:
private:
id<MTLDevice> mDevice;
id<MTLBuffer> mBuffer;
id<MTLBuffer> mAuxBuffer;
TrackedMetalBuffer mBuffer;
TrackedMetalBuffer mAuxBuffer;
MTLResourceOptions mBufferOptions;

View File

@@ -22,33 +22,42 @@
namespace filament {
namespace backend {
std::array<uint64_t, TrackedMetalBuffer::TypeCount> TrackedMetalBuffer::aliveBuffers = { 0 };
MetalPlatform* TrackedMetalBuffer::platform = nullptr;
MetalPlatform* ScopedAllocationTimer::platform = nullptr;
#if FILAMENT_METAL_BUFFER_TRACKING
std::array<NSHashTable<id<MTLBuffer>>*, MetalBufferTracking::TypeCount>
MetalBufferTracking::aliveBuffers;
MetalPlatform* MetalBufferTracking::platform = nullptr;
#endif
MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage,
size_t size, bool forceGpuBuffer) : mBufferSize(size), mContext(context) {
size_t size, bool forceGpuBuffer)
: mBufferSize(size), mContext(context) {
const MetalBumpAllocator& allocator = *mContext.bumpAllocator;
// VERTEX is also used for index buffers
if (allocator.getCapacity() > 0 && bindingType == BufferObjectBinding::VERTEX) {
mUploadStrategy = UploadStrategy::BUMP_ALLOCATOR;
} else {
mUploadStrategy = UploadStrategy::POOL;
}
// If the buffer is less than 4K in size and is updated frequently, we don't use an explicit
// buffer. Instead, we use immediate command encoder methods like setVertexBytes:length:atIndex:.
// This won't work for SSBOs, since they are read/write.
/*
if (size <= 4 * 1024 && bindingType != BufferObjectBinding::SHADER_STORAGE &&
usage == BufferUsage::DYNAMIC && !forceGpuBuffer) {
mBuffer = nil;
mCpuBuffer = malloc(size);
return;
}
*/
// Otherwise, we allocate a private GPU buffer.
{
ScopedAllocationTimer timer("generic");
mBuffer = [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate];
mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate],
TrackedMetalBuffer::Type::GENERIC };
}
MetalBufferTracking::track(mBuffer, MetalBufferTracking::Type::GENERIC);
ASSERT_POSTCONDITION(mBuffer, "Could not allocate Metal buffer of size %zu.", size);
// mBuffer might fail to be allocated. Clients can check for this by calling
// wasAllocationSuccessful().
}
MetalBuffer::~MetalBuffer() {
@@ -57,56 +66,51 @@ MetalBuffer::~MetalBuffer() {
}
}
void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
void MetalBuffer::copyIntoBuffer(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
if (size <= 0) {
return;
}
ASSERT_PRECONDITION(size + byteOffset <= mBufferSize,
"Attempting to copy %zu bytes into a buffer of size %zu at offset %zu",
size, mBufferSize, byteOffset);
// Either copy into the Metal buffer or into our cpu buffer.
FILAMENT_CHECK_PRECONDITION(src)
<< "copyIntoBuffer called with a null src, tag=" << getHandleTag();
FILAMENT_CHECK_PRECONDITION(size + byteOffset <= mBufferSize)
<< "Attempting to copy " << size << " bytes into a buffer of size " << mBufferSize
<< " at offset " << byteOffset << ", tag=" << getHandleTag();
// The copy blit requires that byteOffset be a multiple of 4.
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3))
<< "byteOffset must be a multiple of 4, tag=" << getHandleTag();
// If we have a cpu buffer, we can directly copy into it.
if (mCpuBuffer) {
memcpy(static_cast<uint8_t*>(mCpuBuffer) + byteOffset, src, size);
return;
}
// Acquire a staging buffer to hold the contents of this update.
MetalBufferPool* bufferPool = mContext.bufferPool;
const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size);
memcpy(staging->buffer.contents, src, size);
// The blit below requires that byteOffset be a multiple of 4.
ASSERT_PRECONDITION(!(byteOffset & 0x3u), "byteOffset must be a multiple of 4");
// Encode a blit from the staging buffer into the private GPU buffer.
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
blitEncoder.label = @"Buffer upload blit";
[blitEncoder copyFromBuffer:staging->buffer
sourceOffset:0
toBuffer:mBuffer
destinationOffset:byteOffset
size:size];
[blitEncoder endEncoding];
[cmdBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
bufferPool->releaseBuffer(staging);
}];
switch (mUploadStrategy) {
case UploadStrategy::BUMP_ALLOCATOR:
uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag));
break;
case UploadStrategy::POOL:
uploadWithPoolBuffer(src, size, byteOffset, std::move(getHandleTag));
break;
}
}
void MetalBuffer::copyIntoBufferUnsynchronized(void* src, size_t size, size_t byteOffset) {
void MetalBuffer::copyIntoBufferUnsynchronized(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
// TODO: implement the unsynchronized version
copyIntoBuffer(src, size, byteOffset);
copyIntoBuffer(src, size, byteOffset, std::move(getHandleTag));
}
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) noexcept {
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw() noexcept {
// If there's a CPU buffer, then we return nil here, as the CPU-side buffer will be bound
// separately.
if (mCpuBuffer) {
return nil;
}
assert_invariant(mBuffer);
return mBuffer;
return mBuffer.get();
}
void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncoder> encoder,
@@ -142,7 +146,7 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncod
}
// getGpuBufferForDraw() might return nil, which means there isn't a device allocation for
// this buffer. In this case, we'll bind the buffer below with the CPU-side memory.
id<MTLBuffer> gpuBuffer = buffer->getGpuBufferForDraw(cmdBuffer);
id<MTLBuffer> gpuBuffer = buffer->getGpuBufferForDraw();
if (!gpuBuffer) {
continue;
}
@@ -202,5 +206,54 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncod
}
}
void MetalBuffer::uploadWithPoolBuffer(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const {
MetalBufferPool* bufferPool = mContext.bufferPool;
const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size);
FILAMENT_CHECK_POSTCONDITION(staging)
<< "uploadWithPoolbuffer unable to acquire staging buffer of size " << size
<< ", tag=" << getHandleTag();
memcpy(staging->buffer.get().contents, src, size);
// Encode a blit from the staging buffer into the private GPU buffer.
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
blitEncoder.label = @"Buffer upload blit - pool buffer";
[blitEncoder copyFromBuffer:staging->buffer.get()
sourceOffset:0
toBuffer:mBuffer.get()
destinationOffset:byteOffset
size:size];
[blitEncoder endEncoding];
[cmdBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
bufferPool->releaseBuffer(staging);
}];
}
void MetalBuffer::uploadWithBumpAllocator(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const {
MetalBumpAllocator& allocator = *mContext.bumpAllocator;
auto [buffer, offset] = allocator.allocateStagingArea(size);
FILAMENT_CHECK_POSTCONDITION(buffer)
<< "uploadWithBumpAllocator unable to acquire staging area of size " << size
<< ", tag=" << getHandleTag();
void* const contents = buffer.contents;
FILAMENT_CHECK_POSTCONDITION(contents)
<< "uploadWithBumpAllocator unable to acquire pointer to staging area, size " << size
<< ", tag=" << getHandleTag();
memcpy(static_cast<char*>(contents) + offset, src, size);
// Encode a blit from the staging buffer into the private GPU buffer.
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
blitEncoder.label = @"Buffer upload blit - bump allocator";
[blitEncoder copyFromBuffer:buffer
sourceOffset:offset
toBuffer:mBuffer.get()
destinationOffset:byteOffset
size:size];
[blitEncoder endEncoding];
}
} // namespace backend
} // namespace filament

View File

@@ -32,12 +32,34 @@ struct MetalContext;
// Immutable POD representing a shared CPU-GPU buffer.
struct MetalBufferPoolEntry {
id<MTLBuffer> buffer;
TrackedMetalBuffer buffer;
size_t capacity;
mutable uint64_t lastAccessed;
mutable uint32_t referenceCount;
};
class MetalBumpAllocator {
public:
MetalBumpAllocator(id<MTLDevice> device, size_t capacity);
/**
* Allocates a staging area of the given size. Returns a pair of the buffer and the offset
* within the buffer. The buffer is guaranteed to be at least the given size, but may be larger.
* Clients must not write to the buffer beyond the returned offset + size.
* Clients are responsible for holding a reference to the returned buffer.
* Allocations are guaranteed to be aligned to 4 bytes.
*/
std::pair<id<MTLBuffer>, size_t> allocateStagingArea(size_t size);
size_t getCapacity() const noexcept { return mCapacity; }
private:
id<MTLDevice> mDevice;
TrackedMetalBuffer mCurrentUploadBuffer = nil;
size_t mHead = 0;
size_t mCapacity;
};
// Manages a pool of Metal buffers, periodically releasing ones that have been unused for awhile.
class MetalBufferPool {
public:

View File

@@ -48,10 +48,10 @@ MetalBufferPoolEntry const* MetalBufferPool::acquireBuffer(size_t numBytes) {
buffer = [mContext.device newBufferWithLength:numBytes
options:MTLResourceStorageModeShared];
}
MetalBufferTracking::track(buffer, MetalBufferTracking::Type::STAGING);
ASSERT_POSTCONDITION(buffer, "Could not allocate Metal staging buffer of size %zu.", numBytes);
FILAMENT_CHECK_POSTCONDITION(buffer)
<< "Could not allocate Metal staging buffer of size " << numBytes << ".";
MetalBufferPoolEntry* stage = new MetalBufferPoolEntry {
.buffer = buffer,
.buffer = { buffer, TrackedMetalBuffer::Type::STAGING },
.capacity = numBytes,
.lastAccessed = mCurrentFrame,
.referenceCount = 1
@@ -116,5 +116,39 @@ void MetalBufferPool::reset() noexcept {
mFreeStages.clear();
}
MetalBumpAllocator::MetalBumpAllocator(id<MTLDevice> device, size_t capacity)
: mDevice(device), mCapacity(capacity) {
if (mCapacity > 0) {
mCurrentUploadBuffer = { [device newBufferWithLength:capacity options:MTLStorageModeShared],
TrackedMetalBuffer::Type::STAGING };
}
}
std::pair<id<MTLBuffer>, size_t> MetalBumpAllocator::allocateStagingArea(size_t size) {
if (size == 0) {
return { nil, 0 };
}
if (size > mCapacity) {
return { [mDevice newBufferWithLength:size options:MTLStorageModeShared], 0 };
}
assert_invariant(mCurrentUploadBuffer);
// Align the head to a 4-byte boundary.
mHead = (mHead + 3) & ~3;
if (UTILS_LIKELY(mHead + size <= mCapacity)) {
const size_t oldHead = mHead;
mHead += size;
return { mCurrentUploadBuffer.get(), oldHead };
}
// We're finished with the current allocation.
mCurrentUploadBuffer = { [mDevice newBufferWithLength:mCapacity options:MTLStorageModeShared],
TrackedMetalBuffer::Type::STAGING };
mHead = size;
return { mCurrentUploadBuffer.get(), 0 };
}
} // namespace backend
} // namespace filament

View File

@@ -21,6 +21,8 @@
#include "MetalShaderCompiler.h"
#include "MetalState.h"
#include <backend/DriverEnums.h>
#include <CoreVideo/CVMetalTextureCache.h>
#include <Metal/Metal.h>
#include <QuartzCore/QuartzCore.h>
@@ -44,27 +46,88 @@ namespace backend {
class MetalDriver;
class MetalBlitter;
class MetalBufferPool;
class MetalBumpAllocator;
class MetalRenderTarget;
class MetalSamplerGroup;
class MetalSwapChain;
class MetalTexture;
class MetalTimerQueryInterface;
struct MetalUniformBuffer;
struct MetalIndexBuffer;
struct MetalVertexBuffer;
struct MetalDescriptorSet;
constexpr static uint8_t MAX_SAMPLE_COUNT = 8; // Metal devices support at most 8 MSAA samples
struct MetalContext {
explicit MetalContext(size_t metalFreedTextureListSize)
: texturesToDestroy(metalFreedTextureListSize) {}
class MetalPushConstantBuffer {
public:
void setPushConstant(PushConstantVariant value, uint8_t index);
bool isDirty() const { return mDirty; }
void setBytes(id<MTLCommandEncoder> encoder, ShaderStage stage);
void clear();
private:
std::vector<PushConstantVariant> mPushConstants;
bool mDirty = false;
};
class MetalDynamicOffsets {
public:
void setOffsets(uint32_t set, const uint32_t* offsets, uint32_t count) {
assert(set < MAX_DESCRIPTOR_SET_COUNT);
auto getStartIndexForSet = [&](uint32_t s) {
uint32_t startIndex = 0;
for (uint32_t i = 0; i < s; i++) {
startIndex += mCounts[i];
}
return startIndex;
};
const bool resizeNecessary = mCounts[set] != count;
if (UTILS_UNLIKELY(resizeNecessary)) {
int delta = count - mCounts[set];
auto thisSetStart = mOffsets.begin() + getStartIndexForSet(set);
if (delta > 0) {
mOffsets.insert(thisSetStart, delta, 0);
} else {
mOffsets.erase(thisSetStart, thisSetStart - delta);
}
mCounts[set] = count;
}
if (resizeNecessary ||
!std::equal(
offsets, offsets + count, mOffsets.begin() + getStartIndexForSet(set))) {
std::copy(offsets, offsets + count, mOffsets.begin() + getStartIndexForSet(set));
mDirty = true;
}
}
bool isDirty() const { return mDirty; }
void setDirty(bool dirty) { mDirty = dirty; }
std::pair<uint32_t, const uint32_t*> getOffsets() const {
return { mOffsets.size(), mOffsets.data() };
}
private:
std::array<uint32_t, MAX_DESCRIPTOR_SET_COUNT> mCounts = { 0 };
std::vector<uint32_t> mOffsets;
bool mDirty = false;
};
struct MetalContext {
MetalDriver* driver;
id<MTLDevice> device = nullptr;
id<MTLCommandQueue> commandQueue = nullptr;
id<MTLCommandBuffer> pendingCommandBuffer = nullptr;
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nullptr;
// The ID of pendingCommandBuffer (or the next command buffer, if pendingCommandBuffer is nil).
uint64_t pendingCommandBufferId = 1;
// read from driver thread, set from completion handlers
std::atomic<uint64_t> latestCompletedCommandBufferId = 0;
id<MTLCommandBuffer> pendingCommandBuffer = nil;
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nil;
std::atomic<bool> memorylessLimitsReached = false;
@@ -80,7 +143,7 @@ struct MetalContext {
} highestSupportedGpuFamily;
struct {
bool a8xStaticTextureTargetError;
bool staticTextureTargetError;
} bugs;
// sampleCountLookup[requestedSamples] gives a <= sample count supported by the device.
@@ -95,10 +158,10 @@ struct MetalContext {
// State trackers.
PipelineStateTracker pipelineState;
DepthStencilStateTracker depthStencilState;
std::array<BufferState, Program::UNIFORM_BINDING_COUNT> uniformState;
std::array<BufferState, MAX_SSBO_COUNT> ssboState;
CullModeStateTracker cullModeState;
WindingStateTracker windingState;
DepthClampStateTracker depthClampState;
ScissorRectStateTracker scissorRectState;
Handle<HwRenderPrimitive> currentRenderPrimitive;
// State caches.
@@ -109,24 +172,21 @@ struct MetalContext {
PolygonOffset currentPolygonOffset = {0.0f, 0.0f};
MetalSamplerGroup* samplerBindings[Program::SAMPLER_BINDING_COUNT] = {};
std::array<MetalPushConstantBuffer, Program::SHADER_TYPE_COUNT> currentPushConstants;
// Keeps track of sampler groups we've finalized for the current render pass.
tsl::robin_set<MetalSamplerGroup*> finalizedSamplerGroups;
// Keeps track of descriptor sets we've finalized for the current render pass.
tsl::robin_set<MetalDescriptorSet*> finalizedDescriptorSets;
std::array<MetalDescriptorSet*, MAX_DESCRIPTOR_SET_COUNT> currentDescriptorSets = {};
MetalBufferBindings<MAX_DESCRIPTOR_SET_COUNT, ShaderStage::VERTEX> vertexDescriptorBindings;
MetalBufferBindings<MAX_DESCRIPTOR_SET_COUNT, ShaderStage::FRAGMENT> fragmentDescriptorBindings;
MetalBufferBindings<MAX_DESCRIPTOR_SET_COUNT, ShaderStage::COMPUTE> computeDescriptorBindings;
MetalDynamicOffsets dynamicOffsets;
// Keeps track of all alive sampler groups, textures.
tsl::robin_set<MetalSamplerGroup*> samplerGroups;
// Keeps track of all alive textures.
tsl::robin_set<MetalTexture*> textures;
// This circular buffer implements delayed destruction for Metal texture handles. It keeps a
// handle to a fixed number of the most recently destroyed texture handles. When we're asked to
// destroy a texture handle, we free its texture memory, but keep the MetalTexture object alive,
// marking it as "terminated". If we later are asked to use that texture, we can check its
// terminated status and throw an Objective-C error instead of crashing, which is helpful for
// debugging use-after-free issues in release builds.
utils::FixedCircularBuffer<Handle<HwTexture>> texturesToDestroy;
MetalBufferPool* bufferPool;
MetalBumpAllocator* bumpAllocator;
MetalSwapChain* currentDrawSwapChain = nil;
MetalSwapChain* currentReadSwapChain = nil;
@@ -137,6 +197,7 @@ struct MetalContext {
// Empty texture used to prevent GPU errors when a sampler has been bound without a texture.
id<MTLTexture> emptyTexture = nil;
id<MTLBuffer> emptyBuffer = nil;
MetalBlitter* blitter = nullptr;

View File

@@ -101,9 +101,14 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
context->pendingCommandBuffer = [context->commandQueue commandBuffer];
// It's safe for this block to capture the context variable. MetalDriver::terminate will ensure
// all frames and their completion handlers finish before context is deallocated.
uint64_t thisCommandBufferId = context->pendingCommandBufferId;
[context->pendingCommandBuffer addCompletedHandler:^(id <MTLCommandBuffer> buffer) {
context->resourceTracker.clearResources((__bridge void*) buffer);
// Command buffers should complete in order, so latestCompletedCommandBufferId will only
// ever increase.
context->latestCompletedCommandBufferId = thisCommandBufferId;
auto errorCode = (MTLCommandBufferError)buffer.error.code;
if (@available(macOS 11.0, *)) {
if (errorCode == MTLCommandBufferErrorMemoryless) {
@@ -113,7 +118,8 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
}
}
}];
ASSERT_POSTCONDITION(context->pendingCommandBuffer, "Could not obtain command buffer.");
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)
<< "Could not obtain command buffer.";
return context->pendingCommandBuffer;
}
@@ -124,6 +130,7 @@ void submitPendingCommands(MetalContext* context) {
assert_invariant(context->pendingCommandBuffer.status != MTLCommandBufferStatusCommitted);
[context->pendingCommandBuffer commit];
context->pendingCommandBuffer = nil;
context->pendingCommandBufferId++;
}
id<MTLTexture> getOrCreateEmptyTexture(MetalContext* context) {
@@ -153,5 +160,67 @@ bool isInRenderPass(MetalContext* context) {
return context->currentRenderPassEncoder != nil;
}
void MetalPushConstantBuffer::setPushConstant(PushConstantVariant value, uint8_t index) {
if (mPushConstants.size() <= index) {
mPushConstants.resize(index + 1);
mDirty = true;
}
if (UTILS_LIKELY(mPushConstants[index] != value)) {
mDirty = true;
mPushConstants[index] = value;
}
}
void MetalPushConstantBuffer::setBytes(id<MTLCommandEncoder> encoder, ShaderStage stage) {
constexpr size_t PUSH_CONSTANT_SIZE_BYTES = 4;
static char buffer[MAX_PUSH_CONSTANT_COUNT * PUSH_CONSTANT_SIZE_BYTES];
assert_invariant(mPushConstants.size() <= MAX_PUSH_CONSTANT_COUNT);
size_t bufferSize = PUSH_CONSTANT_SIZE_BYTES * mPushConstants.size();
for (size_t i = 0; i < mPushConstants.size(); i++) {
const auto& constant = mPushConstants[i];
std::visit(
[i](auto arg) {
if constexpr (std::is_same_v<decltype(arg), bool>) {
// bool push constants are converted to uints in MSL.
// We must ensure we write all the bytes for boolean values to work
// correctly.
uint32_t boolAsUint = arg ? 0x00000001 : 0x00000000;
*(reinterpret_cast<uint32_t*>(buffer + PUSH_CONSTANT_SIZE_BYTES * i)) =
boolAsUint;
} else {
*(decltype(arg)*)(buffer + PUSH_CONSTANT_SIZE_BYTES * i) = arg;
}
},
constant);
}
switch (stage) {
case ShaderStage::VERTEX:
[(id<MTLRenderCommandEncoder>)encoder setVertexBytes:buffer
length:bufferSize
atIndex:PUSH_CONSTANT_BUFFER_INDEX];
break;
case ShaderStage::FRAGMENT:
[(id<MTLRenderCommandEncoder>)encoder setFragmentBytes:buffer
length:bufferSize
atIndex:PUSH_CONSTANT_BUFFER_INDEX];
break;
case ShaderStage::COMPUTE:
[(id<MTLComputeCommandEncoder>)encoder setBytes:buffer
length:bufferSize
atIndex:PUSH_CONSTANT_BUFFER_INDEX];
break;
}
mDirty = false;
}
void MetalPushConstantBuffer::clear() {
mPushConstants.clear();
mDirty = false;
}
} // namespace backend
} // namespace filament

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -71,7 +71,8 @@ constexpr inline MTLIndexType getIndexType(size_t elementSize) noexcept {
} else if (elementSize == 4) {
return MTLIndexTypeUInt32;
}
ASSERT_POSTCONDITION(false, "Index element size not supported.");
assert_invariant(false);
return MTLIndexTypeUInt16;
}
constexpr inline MTLVertexFormat getMetalFormat(ElementType type, bool normalized) noexcept {
@@ -100,7 +101,7 @@ constexpr inline MTLVertexFormat getMetalFormat(ElementType type, bool normalize
case ElementType::SHORT4: return MTLVertexFormatShort4Normalized;
case ElementType::USHORT4: return MTLVertexFormatUShort4Normalized;
default:
ASSERT_POSTCONDITION(false, "Normalized format does not exist.");
FILAMENT_CHECK_POSTCONDITION(false) << "Normalized format does not exist.";
return MTLVertexFormatInvalid;
}
}
@@ -326,7 +327,8 @@ constexpr inline MTLCullMode getMetalCullMode(CullingMode cullMode) noexcept {
case CullingMode::FRONT: return MTLCullModeFront;
case CullingMode::BACK: return MTLCullModeBack;
case CullingMode::FRONT_AND_BACK:
ASSERT_POSTCONDITION(false, "FRONT_AND_BACK culling is not supported in Metal.");
FILAMENT_CHECK_POSTCONDITION(false)
<< "FRONT_AND_BACK culling is not supported in Metal.";
}
}

View File

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

View File

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

View File

@@ -44,6 +44,7 @@
#include <condition_variable>
#include <memory>
#include <type_traits>
#include <vector>
namespace filament {
namespace backend {
@@ -73,9 +74,10 @@ public:
void releaseDrawable();
void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback);
void setFrameScheduledCallback(
CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags);
void setFrameCompletedCallback(
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
CallbackHandler* handler, utils::Invocable<void(void)>&& callback);
// For CAMetalLayer-backed SwapChains, presents the drawable or schedules a
// FrameScheduledCallback.
@@ -84,6 +86,8 @@ public:
NSUInteger getSurfaceWidth() const;
NSUInteger getSurfaceHeight() const;
bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; }
private:
enum class SwapChainType {
@@ -93,7 +97,6 @@ private:
};
bool isCaMetalLayer() const { return type == SwapChainType::CAMETALLAYER; }
bool isHeadless() const { return type == SwapChainType::HEADLESS; }
bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; }
void scheduleFrameScheduledCallback();
void scheduleFrameCompletedCallback();
@@ -109,6 +112,7 @@ private:
NSUInteger headlessWidth = 0;
NSUInteger headlessHeight = 0;
CAMetalLayer* layer = nullptr;
std::shared_ptr<std::mutex> layerDrawableMutex;
MetalExternalImage externalImage;
SwapChainType type;
@@ -119,31 +123,29 @@ private:
// PresentCallable object.
struct {
CallbackHandler* handler = nullptr;
FrameScheduledCallback callback = {};
std::shared_ptr<FrameScheduledCallback> callback = nullptr;
uint64_t flags = 0;
} frameScheduled;
struct {
CallbackHandler* handler = nullptr;
CallbackHandler::Callback callback = {};
void* user = nullptr;
std::shared_ptr<utils::Invocable<void(void)>> callback = nullptr;
} frameCompleted;
};
class MetalBufferObject : public HwBufferObject {
public:
using TagResolver = MetalBuffer::TagResolver;
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);
void updateBuffer(void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag);
void updateBufferUnsynchronized(
void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag);
MetalBuffer* getBuffer() { return &buffer; }
// Tracks which uniform/ssbo buffers this buffer object is bound into.
static_assert(Program::UNIFORM_BINDING_COUNT <= 32);
static_assert(MAX_SSBO_COUNT <= 32);
utils::bitset32 boundUniformBuffers;
utils::bitset32 boundSsbos;
private:
MetalBuffer buffer;
};
@@ -200,12 +202,11 @@ public:
MetalProgram(MetalContext& context, Program&& program) noexcept;
const MetalShaderCompiler::MetalFunctionBundle& getFunctions();
const Program::SamplerGroupInfo& getSamplerGroupInfo() { return samplerGroupInfo; }
const MetalShaderCompiler::MetalFunctionBundle& getFunctionsIfPresent() const;
private:
void initialize();
Program::SamplerGroupInfo samplerGroupInfo;
MetalContext& mContext;
MetalShaderCompiler::MetalFunctionBundle mFunctionBundle;
MetalShaderCompiler::program_token_t mToken;
@@ -227,43 +228,42 @@ struct PixelBufferShape {
class MetalTexture : public HwTexture {
public:
MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format,
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage,
TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a)
noexcept;
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
TextureUsage usage) noexcept;
// constructors for creating texture views
MetalTexture(MetalContext& context, MetalTexture const* src, uint8_t baseLevel,
uint8_t levelCount) noexcept;
MetalTexture(MetalContext& context, MetalTexture const* src, TextureSwizzle r, TextureSwizzle g,
TextureSwizzle b, TextureSwizzle a) noexcept;
// Constructor for importing an id<MTLTexture> outside of Filament.
MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format,
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage,
id<MTLTexture> texture) noexcept;
~MetalTexture();
// Constructors for importing external images.
MetalTexture(MetalContext& context, TextureFormat format, uint32_t width, uint32_t height,
TextureUsage usage, CVPixelBufferRef image) noexcept;
MetalTexture(MetalContext& context, TextureFormat format, uint32_t width, uint32_t height,
TextureUsage usage, CVPixelBufferRef image, uint32_t plane) noexcept;
// Returns an id<MTLTexture> suitable for reading in a shader, taking into account swizzle and
// LOD clamping.
id<MTLTexture> getMtlTextureForRead() noexcept;
// Returns an id<MTLTexture> suitable for reading in a shader, taking into account swizzle.
id<MTLTexture> getMtlTextureForRead() const noexcept;
// Returns the id<MTLTexture> for attaching to a render pass.
id<MTLTexture> getMtlTextureForWrite() noexcept {
id<MTLTexture> getMtlTextureForWrite() const noexcept {
return texture;
}
std::shared_ptr<MetalExternalImage> getExternalImage() const noexcept { return externalImage; }
void loadImage(uint32_t level, MTLRegion region, PixelBufferDescriptor& p) noexcept;
void generateMipmaps() noexcept;
// A texture starts out with none of its mip levels (also referred to as LODs) available for
// reading. 4 actions update the range of LODs available:
// - calling loadImage
// - calling generateMipmaps
// - using the texture as a render target attachment
// - calling setMinMaxLevels
// A texture's available mips are consistent throughout a render pass.
void setLodRange(uint16_t minLevel, uint16_t maxLevel);
void extendLodRangeTo(uint16_t level);
static MTLPixelFormat decidePixelFormat(MetalContext* context, TextureFormat format);
MetalContext& context;
MetalExternalImage externalImage;
// A "sidecar" texture used to implement automatic MSAA resolve.
// This is created by MetalRenderTarget and stored here so it can be used with multiple
@@ -272,26 +272,6 @@ public:
MTLPixelFormat devicePixelFormat;
// Frees memory associated with this texture and marks it as "terminated".
// Used to track "use after free" scenario.
void terminate() noexcept;
bool isTerminated() const noexcept { return terminated; }
inline void checkUseAfterFree(const char* samplerGroupDebugName, size_t textureIndex) const {
if (UTILS_LIKELY(!isTerminated())) {
return;
}
NSString* reason =
[NSString stringWithFormat:
@"Filament Metal texture use after free, sampler group = "
@"%s, texture index = %zu",
samplerGroupDebugName, textureIndex];
NSException* useAfterFreeException =
[NSException exceptionWithName:@"MetalTextureUseAfterFree"
reason:reason
userInfo:nil];
[useAfterFreeException raise];
}
private:
void loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice,
PixelBufferDescriptor const& data) noexcept;
@@ -302,95 +282,12 @@ private:
id<MTLTexture> texture = nil;
std::shared_ptr<MetalExternalImage> externalImage;
// If non-nil, a swizzled texture view to use instead of "texture".
// Filament swizzling only affects texture reads, so this should not be used when the texture is
// bound as a render target attachment.
id<MTLTexture> swizzledTextureView = nil;
id<MTLTexture> lodTextureView = nil;
uint16_t minLod = std::numeric_limits<uint16_t>::max();
uint16_t maxLod = 0;
bool terminated = false;
};
class MetalSamplerGroup : public HwSamplerGroup {
public:
explicit MetalSamplerGroup(size_t size, utils::FixedSizeString<32> name) noexcept
: size(size),
debugName(name),
textureHandles(size, Handle<HwTexture>()),
textures(size, nil),
samplers(size, nil) {}
inline void setTextureHandle(size_t index, Handle<HwTexture> th) {
assert_invariant(!finalized);
textureHandles[index] = th;
}
// This method is only used for debugging, to ensure all texture handles are alive.
const auto& getTextureHandles() const {
return textureHandles;
}
// Encode a MTLTexture into this SamplerGroup at the given index.
inline void setFinalizedTexture(size_t index, id<MTLTexture> t) {
assert_invariant(!finalized);
textures[index] = t;
}
// Encode a MTLSamplerState into this SamplerGroup at the given index.
inline void setFinalizedSampler(size_t index, id<MTLSamplerState> s) {
assert_invariant(!finalized);
samplers[index] = s;
}
// A SamplerGroup is "finalized" when all of its textures have been set and is ready for use in
// a draw call.
// Once a SamplerGroup is finalized, it must be reset or mutated to be written into again.
void finalize();
bool isFinalized() const noexcept { return finalized; }
// Both of these methods "unfinalize" a SamplerGroup, allowing it to be updated via calls to
// setFinalizedTexture or setFinalizedSampler. The difference is that when reset is called, all
// the samplers/textures must be rebound. The MTLArgumentEncoder must be specified, in case
// the texture types have changed.
// Mutate re-encodes the current set of samplers/textures into the new argument
// buffer.
void reset(id<MTLCommandBuffer> cmdBuffer, id<MTLArgumentEncoder> e, id<MTLDevice> device);
void mutate(id<MTLCommandBuffer> cmdBuffer);
id<MTLBuffer> getArgumentBuffer() const {
assert_invariant(finalized);
return argBuffer->getCurrentAllocation().first;
}
NSUInteger getArgumentBufferOffset() const {
return argBuffer->getCurrentAllocation().second;
}
inline std::pair<Handle<HwTexture>, id<MTLTexture>> getFinalizedTexture(size_t index) {
return {textureHandles[index], textures[index]};
}
// Calls the Metal useResource:usage:stages: method for all the textures in this SamplerGroup.
void useResources(id<MTLRenderCommandEncoder> renderPassEncoder);
size_t size;
utils::FixedSizeString<32> debugName;
public:
// These vectors are kept in sync with one another.
utils::FixedCapacityVector<Handle<HwTexture>> textureHandles;
utils::FixedCapacityVector<id<MTLTexture>> textures;
utils::FixedCapacityVector<id<MTLSamplerState>> samplers;
id<MTLArgumentEncoder> encoder;
std::unique_ptr<MetalRingBuffer> argBuffer = nullptr;
bool finalized = false;
};
class MetalRenderTarget : public HwRenderTarget {
@@ -541,12 +438,67 @@ struct MetalTimerQuery : public HwTimerQuery {
struct Status {
std::atomic<bool> available {false};
uint64_t elapsed {0}; // only valid if available is true
std::atomic<uint64_t> elapsed {0}; // only valid if available is true
};
std::shared_ptr<Status> status;
};
class MetalDescriptorSetLayout : public HwDescriptorSetLayout {
public:
MetalDescriptorSetLayout(DescriptorSetLayout&& layout) noexcept;
const auto& getBindings() const noexcept { return mLayout.bindings; }
size_t getDynamicOffsetCount() const noexcept { return mDynamicOffsetCount; }
/**
* Get an argument encoder for this descriptor set and shader stage.
* textureTypes should only include the textures present in the corresponding shader stage.
*/
id<MTLArgumentEncoder> getArgumentEncoder(id<MTLDevice> device, ShaderStage stage,
utils::FixedCapacityVector<MTLTextureType> const& textureTypes);
private:
id<MTLArgumentEncoder> getArgumentEncoderSlow(id<MTLDevice> device, ShaderStage stage,
utils::FixedCapacityVector<MTLTextureType> const& textureTypes);
DescriptorSetLayout mLayout;
size_t mDynamicOffsetCount = 0;
std::array<id<MTLArgumentEncoder>, Program::SHADER_TYPE_COUNT> mCachedArgumentEncoder = { nil };
std::array<utils::FixedCapacityVector<MTLTextureType>, Program::SHADER_TYPE_COUNT>
mCachedTextureTypes;
};
struct MetalDescriptorSet : public HwDescriptorSet {
MetalDescriptorSet(MetalDescriptorSetLayout* layout) noexcept;
void finalize(MetalDriver* driver);
id<MTLBuffer> finalizeAndGetBuffer(MetalDriver* driver, ShaderStage stage);
MetalDescriptorSetLayout* layout;
struct BufferBinding {
id<MTLBuffer> buffer;
uint32_t offset;
uint32_t size;
};
struct TextureBinding {
id<MTLTexture> texture;
SamplerParams sampler;
};
tsl::robin_map<descriptor_binding_t, BufferBinding> buffers;
tsl::robin_map<descriptor_binding_t, TextureBinding> textures;
std::vector<id<MTLResource>> vertexResources;
std::vector<id<MTLResource>> fragmentResources;
std::vector<std::shared_ptr<MetalExternalImage>> externalImages;
std::array<TrackedMetalBuffer, Program::SHADER_TYPE_COUNT> cachedBuffer = { nil };
};
} // namespace backend
} // namespace filament

View File

@@ -73,7 +73,7 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, CAMetalLayer* nativeWindow
: context(context),
depthStencilFormat(decideDepthStencilFormat(flags)),
layer(nativeWindow),
externalImage(context),
layerDrawableMutex(std::make_shared<std::mutex>()),
type(SwapChainType::CAMETALLAYER) {
if (!(flags & SwapChain::CONFIG_TRANSPARENT) && !nativeWindow.opaque) {
@@ -99,17 +99,15 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, int32_t width, int32_t hei
depthStencilFormat(decideDepthStencilFormat(flags)),
headlessWidth(width),
headlessHeight(height),
externalImage(context),
type(SwapChainType::HEADLESS) {}
MetalSwapChain::MetalSwapChain(MetalContext& context, CVPixelBufferRef pixelBuffer, uint64_t flags)
: context(context),
depthStencilFormat(decideDepthStencilFormat(flags)),
externalImage(context),
externalImage(MetalExternalImage::createFromImage(context, pixelBuffer)),
type(SwapChainType::CVPIXELBUFFERREF) {
assert_invariant(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
MetalExternalImage::assertWritableImage(pixelBuffer);
externalImage.set(pixelBuffer);
assert_invariant(externalImage.isValid());
}
@@ -120,7 +118,6 @@ MTLPixelFormat MetalSwapChain::decideDepthStencilFormat(uint64_t flags) {
}
MetalSwapChain::~MetalSwapChain() {
externalImage.set(nullptr);
}
NSUInteger MetalSwapChain::getSurfaceWidth() const {
@@ -170,18 +167,28 @@ id<MTLTexture> MetalSwapChain::acquireDrawable() {
}
if (isPixelBuffer()) {
return externalImage.getMetalTextureForDraw();
return externalImage.getMtlTexture();
}
assert_invariant(isCaMetalLayer());
drawable = [layer nextDrawable];
ASSERT_POSTCONDITION(drawable != nil, "Could not obtain drawable.");
// CAMetalLayer's drawable pool is not thread safe. Use a mutex when
// calling -nextDrawable, or when releasing the last known reference
// to any CAMetalDrawable returned from a previous -nextDrawable.
{
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
drawable = [layer nextDrawable];
}
FILAMENT_CHECK_POSTCONDITION(drawable != nil) << "Could not obtain drawable.";
return drawable.texture;
}
void MetalSwapChain::releaseDrawable() {
drawable = nil;
if (drawable) {
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
drawable = nil;
}
}
id<MTLTexture> MetalSwapChain::acquireDepthTexture() {
@@ -222,16 +229,16 @@ void MetalSwapChain::ensureDepthStencilTexture() {
}
void MetalSwapChain::setFrameScheduledCallback(
CallbackHandler* handler, FrameScheduledCallback&& callback) {
CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) {
frameScheduled.handler = handler;
frameScheduled.callback = std::move(callback);
frameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
frameScheduled.flags = flags;
}
void MetalSwapChain::setFrameCompletedCallback(CallbackHandler* handler,
CallbackHandler::Callback callback, void* user) {
void MetalSwapChain::setFrameCompletedCallback(
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
frameCompleted.handler = handler;
frameCompleted.callback = callback;
frameCompleted.user = user;
frameCompleted.callback = std::make_shared<utils::Invocable<void(void)>>(std::move(callback));
}
void MetalSwapChain::present() {
@@ -247,19 +254,17 @@ void MetalSwapChain::present() {
}
}
#ifndef FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD
#define FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD 1
#endif
class PresentDrawableData {
public:
PresentDrawableData() = delete;
PresentDrawableData(const PresentDrawableData&) = delete;
PresentDrawableData& operator=(const PresentDrawableData&) = delete;
static PresentDrawableData* create(id<CAMetalDrawable> drawable, MetalDriver* driver) {
static PresentDrawableData* create(id<CAMetalDrawable> drawable,
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags) {
assert_invariant(drawableMutex);
assert_invariant(driver);
return new PresentDrawableData(drawable, driver);
return new PresentDrawableData(drawable, drawableMutex, driver, flags);
}
static void maybePresentAndDestroyAsync(PresentDrawableData* that, bool shouldPresent) {
@@ -267,28 +272,38 @@ public:
[that->mDrawable present];
}
#if FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD == 1
// mDrawable is acquired on the driver thread. Typically, we would release this object on
// the same thread, but after receiving consistent crash reports from within
// [CAMetalDrawable dealloc], we suspect this object requires releasing on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{ cleanupAndDestroy(that); });
#else
that->mDriver->runAtNextTick([that]() { cleanupAndDestroy(that); });
#endif
if (that->mFlags & SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER) {
cleanupAndDestroy(that);
} else {
// mDrawable is acquired on the driver thread. Typically, we would release this object
// on the same thread, but after receiving consistent crash reports from within
// [CAMetalDrawable dealloc], we suspect this object requires releasing on the main
// thread.
dispatch_async(dispatch_get_main_queue(), ^{
cleanupAndDestroy(that);
});
}
}
private:
PresentDrawableData(id<CAMetalDrawable> drawable, MetalDriver* driver)
: mDrawable(drawable), mDriver(driver) {}
PresentDrawableData(id<CAMetalDrawable> drawable, std::shared_ptr<std::mutex> drawableMutex,
MetalDriver* driver, uint64_t flags)
: mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver), mFlags(flags) {}
static void cleanupAndDestroy(PresentDrawableData *that) {
that->mDrawable = nil;
if (that->mDrawable) {
std::lock_guard<std::mutex> lock(*(that->mDrawableMutex));
that->mDrawable = nil;
}
that->mDrawableMutex.reset();
that->mDriver = nullptr;
delete that;
}
id<CAMetalDrawable> mDrawable;
std::shared_ptr<std::mutex> mDrawableMutex;
MetalDriver* mDriver = nullptr;
uint64_t mFlags = 0;
};
void presentDrawable(bool presentFrame, void* user) {
@@ -304,31 +319,36 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
assert_invariant(drawable);
struct Callback {
Callback(FrameScheduledCallback&& callback, id<CAMetalDrawable> drawable,
MetalDriver* driver)
: f(std::move(callback)), data(PresentDrawableData::create(drawable, driver)) {}
FrameScheduledCallback f;
Callback(std::shared_ptr<FrameScheduledCallback> callback, id<CAMetalDrawable> drawable,
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags)
: f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver, flags)) {}
std::shared_ptr<FrameScheduledCallback> f;
// PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later.
std::unique_ptr<PresentDrawableData> data;
static void func(void* user) {
auto* const c = reinterpret_cast<Callback*>(user);
PresentDrawableData* presentDrawableData = c->data.release();
PresentCallable presentCallable(presentDrawable, presentDrawableData);
c->f(presentCallable);
c->f->operator()(presentCallable);
delete c;
}
};
// This callback pointer will be captured by the block. Even if the scheduled handler is never
// called, the unique_ptr will still ensure we don't leak memory.
__block auto callback =
std::make_unique<Callback>(std::move(frameScheduled.callback), drawable, context.driver);
uint64_t const flags = frameScheduled.flags;
__block auto callback = std::make_unique<Callback>(
frameScheduled.callback, drawable, layerDrawableMutex, context.driver, flags);
backend::CallbackHandler* handler = frameScheduled.handler;
MetalDriver* driver = context.driver;
[getPendingCommandBuffer(&context) addScheduledHandler:^(id<MTLCommandBuffer> cb) {
Callback* user = callback.release();
driver->scheduleCallback(handler, user, &Callback::func);
if (flags & SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER) {
Callback::func(user);
} else {
driver->scheduleCallback(handler, user, &Callback::func);
}
}];
}
@@ -337,13 +357,25 @@ void MetalSwapChain::scheduleFrameCompletedCallback() {
return;
}
CallbackHandler* handler = frameCompleted.handler;
void* user = frameCompleted.user;
CallbackHandler::Callback callback = frameCompleted.callback;
struct Callback {
Callback(std::shared_ptr<utils::Invocable<void(void)>> callback) : f(callback) {}
std::shared_ptr<utils::Invocable<void(void)>> f;
static void func(void* user) {
auto* const c = reinterpret_cast<Callback*>(user);
c->f->operator()();
delete c;
}
};
// This callback pointer will be captured by the block. Even if the completed handler is never
// called, the unique_ptr will still ensure we don't leak memory.
__block auto callback = std::make_unique<Callback>(frameCompleted.callback);
CallbackHandler* handler = frameCompleted.handler;
MetalDriver* driver = context.driver;
[getPendingCommandBuffer(&context) addCompletedHandler:^(id<MTLCommandBuffer> cb) {
driver->scheduleCallback(handler, user, callback);
Callback* user = callback.release();
driver->scheduleCallback(handler, user, &Callback::func);
}];
}
@@ -351,12 +383,14 @@ MetalBufferObject::MetalBufferObject(MetalContext& context, BufferObjectBinding
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);
void MetalBufferObject::updateBuffer(
void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag) {
buffer.copyIntoBuffer(data, size, byteOffset, std::move(getHandleTag));
}
void MetalBufferObject::updateBufferUnsynchronized(void* data, size_t size, uint32_t byteOffset) {
buffer.copyIntoBufferUnsynchronized(data, size, byteOffset);
void MetalBufferObject::updateBufferUnsynchronized(
void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag) {
buffer.copyIntoBufferUnsynchronized(data, size, byteOffset, std::move(getHandleTag));
}
MetalVertexBufferInfo::MetalVertexBufferInfo(MetalContext& context, uint8_t bufferCount,
@@ -452,11 +486,6 @@ void MetalRenderPrimitive::setBuffers(MetalVertexBufferInfo const* const vbi,
MetalProgram::MetalProgram(MetalContext& context, Program&& program) noexcept
: HwProgram(program.getName()), mContext(context) {
// Save this program's SamplerGroupInfo, it's used during draw calls to bind sampler groups to
// the appropriate stage(s).
samplerGroupInfo = program.getSamplerGroupInfo();
mToken = context.shaderCompiler->createProgram(program.getName(), std::move(program));
assert_invariant(mToken);
}
@@ -466,6 +495,10 @@ const MetalShaderCompiler::MetalFunctionBundle& MetalProgram::getFunctions() {
return mFunctionBundle;
}
const MetalShaderCompiler::MetalFunctionBundle& MetalProgram::getFunctionsIfPresent() const {
return mFunctionBundle;
}
void MetalProgram::initialize() {
if (!mToken) {
return;
@@ -476,21 +509,21 @@ void MetalProgram::initialize() {
MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
TextureUsage usage, TextureSwizzle r, TextureSwizzle g, TextureSwizzle b,
TextureSwizzle a) noexcept
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context),
externalImage(context, r, g, b, a) {
TextureUsage usage) noexcept
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context) {
assert_invariant(target != SamplerType::SAMPLER_EXTERNAL);
devicePixelFormat = decidePixelFormat(&context, format);
ASSERT_POSTCONDITION(devicePixelFormat != MTLPixelFormatInvalid, "Texture format not supported.");
FILAMENT_CHECK_POSTCONDITION(devicePixelFormat != MTLPixelFormatInvalid)
<< "Texture format not supported.";
const BOOL mipmapped = levels > 1;
const BOOL multisampled = samples > 1;
#if defined(IOS)
const BOOL textureArray = target == SamplerType::SAMPLER_2D_ARRAY;
ASSERT_PRECONDITION(!textureArray || !multisampled,
"iOS does not support multisampled texture arrays.");
FILAMENT_CHECK_PRECONDITION(!textureArray || !multisampled)
<< "iOS does not support multisampled texture arrays.";
#endif
const auto get2DTextureType = [](SamplerType target, bool isMultisampled) {
@@ -525,12 +558,12 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
descriptor.usage = getMetalTextureUsage(usage);
descriptor.storageMode = MTLStorageModePrivate;
texture = [context.device newTextureWithDescriptor:descriptor];
ASSERT_POSTCONDITION(texture != nil, "Could not create Metal texture. Out of memory?");
break;
case SamplerType::SAMPLER_CUBEMAP:
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
ASSERT_POSTCONDITION(!multisampled, "Multisampled cubemap faces not supported.");
ASSERT_POSTCONDITION(width == height, "Cubemap faces must be square.");
FILAMENT_CHECK_POSTCONDITION(!multisampled)
<< "Multisampled cubemap faces not supported.";
FILAMENT_CHECK_POSTCONDITION(width == height) << "Cubemap faces must be square.";
descriptor = [MTLTextureDescriptor textureCubeDescriptorWithPixelFormat:devicePixelFormat
size:width
mipmapped:mipmapped];
@@ -539,7 +572,6 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
descriptor.usage = getMetalTextureUsage(usage);
descriptor.storageMode = MTLStorageModePrivate;
texture = [context.device newTextureWithDescriptor:descriptor];
ASSERT_POSTCONDITION(texture != nil, "Could not create Metal texture. Out of memory?");
break;
case SamplerType::SAMPLER_3D:
descriptor = [MTLTextureDescriptor new];
@@ -552,7 +584,6 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
descriptor.usage = getMetalTextureUsage(usage);
descriptor.storageMode = MTLStorageModePrivate;
texture = [context.device newTextureWithDescriptor:descriptor];
ASSERT_POSTCONDITION(texture != nil, "Could not create Metal texture. Out of memory?");
break;
case SamplerType::SAMPLER_EXTERNAL:
// If we're using external textures (CVPixelBufferRefs), we don't need to make any
@@ -561,15 +592,33 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
break;
}
// If swizzling is set, set up a swizzled texture view that we'll use when sampling this texture.
const bool isDefaultSwizzle =
r == TextureSwizzle::CHANNEL_0 &&
g == TextureSwizzle::CHANNEL_1 &&
b == TextureSwizzle::CHANNEL_2 &&
a == TextureSwizzle::CHANNEL_3;
// If texture is nil, then it must be a SAMPLER_EXTERNAL texture.
// Swizzling for external textures is handled inside MetalExternalImage.
if (!isDefaultSwizzle && texture && context.supportsTextureSwizzling) {
FILAMENT_CHECK_POSTCONDITION(target == SamplerType::SAMPLER_EXTERNAL || texture != nil)
<< "Could not create Metal texture (SamplerType = " << int(target)
<< ", levels = " << int(levels) << ", MTLPixelFormat = " << int(devicePixelFormat)
<< ", width = " << width << ", height = " << height << ", depth = " << depth
<< "). Out of memory?";
}
MetalTexture::MetalTexture(MetalContext& context, MetalTexture const* src, uint8_t baseLevel,
uint8_t levelCount) noexcept
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
context(context),
devicePixelFormat(src->devicePixelFormat),
externalImage(src->externalImage) {
texture = createTextureViewWithLodRange(
src->getMtlTextureForRead(), baseLevel, baseLevel + levelCount - 1);
}
MetalTexture::MetalTexture(MetalContext& context, MetalTexture const* src, TextureSwizzle r,
TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) noexcept
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
context(context),
devicePixelFormat(src->devicePixelFormat),
externalImage(src->externalImage) {
texture = src->getMtlTextureForRead();
if (context.supportsTextureSwizzling) {
// Even though we've already checked context.supportsTextureSwizzling, we still need to
// guard these calls with @availability, otherwise the API usage will generate compiler
// warnings.
@@ -583,44 +632,30 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format,
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage,
id<MTLTexture> metalTexture) noexcept
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context),
externalImage(context) {
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context) {
texture = metalTexture;
setLodRange(0, levels - 1);
}
void MetalTexture::terminate() noexcept {
texture = nil;
swizzledTextureView = nil;
lodTextureView = nil;
msaaSidecar = nil;
externalImage.set(nullptr);
terminated = true;
MetalTexture::MetalTexture(MetalContext& context, TextureFormat format, uint32_t width,
uint32_t height, TextureUsage usage, CVPixelBufferRef image) noexcept
: HwTexture(SamplerType::SAMPLER_EXTERNAL, 1, 1, width, height, 1, format, usage),
context(context),
externalImage(std::make_shared<MetalExternalImage>(
MetalExternalImage::createFromImage(context, image))) {
texture = externalImage->getMtlTexture();
}
MetalTexture::~MetalTexture() {
externalImage.set(nullptr);
MetalTexture::MetalTexture(MetalContext& context, TextureFormat format, uint32_t width,
uint32_t height, TextureUsage usage, CVPixelBufferRef image, uint32_t plane) noexcept
: HwTexture(SamplerType::SAMPLER_EXTERNAL, 1, 1, width, height, 1, format, usage),
context(context),
externalImage(std::make_shared<MetalExternalImage>(
MetalExternalImage::createFromImagePlane(context, image, plane))) {
texture = externalImage->getMtlTexture();
}
id<MTLTexture> MetalTexture::getMtlTextureForRead() noexcept {
if (lodTextureView) {
return lodTextureView;
}
// The texture's swizzle remains constant throughout its lifetime, however its LOD range can
// change. We'll cache the LOD view, and set lodTextureView to nil if minLod or maxLod is
// updated.
id<MTLTexture> t = swizzledTextureView ? swizzledTextureView : texture;
if (!t) {
return nil;
}
if (UTILS_UNLIKELY(minLod > maxLod)) {
// If the texture does not have any available LODs, provide a view of only level 0.
// Filament should prevent this from ever occurring.
lodTextureView = createTextureViewWithLodRange(t, 0, 0);
return lodTextureView;
}
lodTextureView = createTextureViewWithLodRange(t, minLod, maxLod);
return lodTextureView;
id<MTLTexture> MetalTexture::getMtlTextureForRead() const noexcept {
return swizzledTextureView ? swizzledTextureView : texture;
}
MTLPixelFormat MetalTexture::decidePixelFormat(MetalContext* context, TextureFormat format) {
@@ -739,24 +774,21 @@ void MetalTexture::loadImage(uint32_t level, MTLRegion region, PixelBufferDescri
assert_invariant(false);
}
}
extendLodRangeTo(level);
}
void MetalTexture::generateMipmaps() noexcept {
id <MTLBlitCommandEncoder> blitEncoder = [getPendingCommandBuffer(&context) blitCommandEncoder];
[blitEncoder generateMipmapsForTexture:texture];
[blitEncoder endEncoding];
setLodRange(0, texture.mipmapLevelCount - 1);
}
void MetalTexture::loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice,
PixelBufferDescriptor const& data) noexcept {
const PixelBufferShape shape = PixelBufferShape::compute(data, format, region.size, byteOffset);
ASSERT_PRECONDITION(data.size >= shape.totalBytes,
"Expected buffer size of at least %d but "
"received PixelBufferDescriptor with size %d.", shape.totalBytes, data.size);
FILAMENT_CHECK_PRECONDITION(data.size >= shape.totalBytes)
<< "Expected buffer size of at least " << shape.totalBytes
<< " but received PixelBufferDescriptor with size " << data.size << ".";
// Earlier versions of iOS don't have the maxBufferLength query, but 256 MB is a safe bet.
NSUInteger deviceMaxBufferLength = 256 * 1024 * 1024; // 256 MB
@@ -789,13 +821,13 @@ void MetalTexture::loadWithCopyBuffer(uint32_t level, uint32_t slice, MTLRegion
PixelBufferDescriptor const& data, const PixelBufferShape& shape) {
const size_t stagingBufferSize = shape.totalBytes;
auto entry = context.bufferPool->acquireBuffer(stagingBufferSize);
memcpy(entry->buffer.contents,
memcpy(entry->buffer.get().contents,
static_cast<uint8_t*>(data.buffer) + shape.sourceOffset,
stagingBufferSize);
id<MTLCommandBuffer> blitCommandBuffer = getPendingCommandBuffer(&context);
id<MTLBlitCommandEncoder> blitCommandEncoder = [blitCommandBuffer blitCommandEncoder];
blitCommandEncoder.label = @"Texture upload buffer blit";
[blitCommandEncoder copyFromBuffer:entry->buffer
[blitCommandEncoder copyFromBuffer:entry->buffer.get()
sourceOffset:0
sourceBytesPerRow:shape.bytesPerRow
sourceBytesPerImage:shape.bytesPerSlice
@@ -871,98 +903,6 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
context.blitter->blit(getPendingCommandBuffer(&context), args, "Texture upload blit");
}
void MetalTexture::extendLodRangeTo(uint16_t level) {
assert_invariant(!isInRenderPass(&context));
minLod = std::min(minLod, level);
maxLod = std::max(maxLod, level);
lodTextureView = nil;
}
void MetalTexture::setLodRange(uint16_t min, uint16_t max) {
assert_invariant(!isInRenderPass(&context));
assert_invariant(min <= max);
minLod = min;
maxLod = max;
lodTextureView = nil;
}
void MetalSamplerGroup::finalize() {
assert_invariant(encoder);
// TODO: we should be able to encode textures and samplers inside setFinalizedTexture and
// setFinalizedSampler as they become available, but Metal doesn't seem to like this; the arg
// buffer gets encoded incorrectly. This warrants more investigation.
auto [buffer, offset] = argBuffer->getCurrentAllocation();
[encoder setArgumentBuffer:buffer offset:offset];
// Encode all textures and samplers.
for (size_t s = 0; s < size; s++) {
[encoder setTexture:textures[s] atIndex:(s * 2 + 0)];
[encoder setSamplerState:samplers[s] atIndex:(s * 2 + 1)];
}
finalized = true;
}
void MetalSamplerGroup::reset(id<MTLCommandBuffer> cmdBuffer, id<MTLArgumentEncoder> e,
id<MTLDevice> device) {
encoder = e;
// The number of slots in the ring buffer we use to manage argument buffer allocations.
// This number was chosen to avoid running out of slots and having to allocate a "fallback"
// buffer when SamplerGroups are updated multiple times a frame. This value can reduced after
// auditing Filament's calls to updateSamplerGroup, which should be as few times as possible.
// For example, the bloom downsample pass should be refactored to maintain two separate
// MaterialInstances instead of "ping ponging" between two texture bindings, which causes a
// single SamplerGroup to be updated many times a frame.
static constexpr auto METAL_ARGUMENT_BUFFER_SLOTS = 32;
MTLSizeAndAlign argBufferLayout;
argBufferLayout.size = encoder.encodedLength;
argBufferLayout.align = encoder.alignment;
// Chances are, even though the MTLArgumentEncoder might change, the required size and alignment
// probably won't. So we can re-use the previous ring buffer.
if (UTILS_UNLIKELY(!argBuffer || !argBuffer->canAccomodateLayout(argBufferLayout))) {
argBuffer = std::make_unique<MetalRingBuffer>(device, MTLResourceStorageModeShared,
argBufferLayout, METAL_ARGUMENT_BUFFER_SLOTS);
} else {
argBuffer->createNewAllocation(cmdBuffer);
}
// Clear all textures and samplers.
assert_invariant(textureHandles.size() == textures.size());
assert_invariant(textures.size() == samplers.size());
for (size_t s = 0; s < textureHandles.size(); s++) {
textureHandles[s] = {};
textures[s] = nil;
samplers[s] = nil;
}
finalized = false;
}
void MetalSamplerGroup::mutate(id<MTLCommandBuffer> cmdBuffer) {
assert_invariant(finalized); // only makes sense to mutate if this sampler group is finalized
assert_invariant(argBuffer);
argBuffer->createNewAllocation(cmdBuffer);
finalized = false;
}
void MetalSamplerGroup::useResources(id<MTLRenderCommandEncoder> renderPassEncoder) {
assert_invariant(finalized);
if (@available(iOS 13, *)) {
// TODO: pass only the appropriate stages to useResources.
[renderPassEncoder useResources:textures.data()
count:textures.size()
usage:MTLResourceUsageRead | MTLResourceUsageSample
stages:MTLRenderStageFragment | MTLRenderStageVertex];
} else {
[renderPassEncoder useResources:textures.data()
count:textures.size()
usage:MTLResourceUsageRead | MTLResourceUsageSample];
}
}
MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height,
uint8_t samples, Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
Attachment depthAttachment, Attachment stencilAttachment) :
@@ -977,9 +917,9 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
}
color[i] = colorAttachments[i];
ASSERT_PRECONDITION(color[i].getSampleCount() <= samples,
"MetalRenderTarget was initialized with a MSAA COLOR%d texture, but sample count is %d.",
i, samples);
FILAMENT_CHECK_PRECONDITION(color[i].getSampleCount() <= samples)
<< "MetalRenderTarget was initialized with a MSAA COLOR" << i
<< " texture, but sample count is " << samples << ".";
auto t = color[i].metalTexture;
const auto twidth = std::max(1u, t->width >> color[i].level);
@@ -1002,9 +942,10 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
if (depthAttachment) {
depth = depthAttachment;
ASSERT_PRECONDITION(depth.getSampleCount() <= samples,
"MetalRenderTarget was initialized with a MSAA DEPTH texture, but sample count is %d.",
samples);
FILAMENT_CHECK_PRECONDITION(depth.getSampleCount() <= samples)
<< "MetalRenderTarget was initialized with a MSAA DEPTH texture, but sample count "
"is "
<< samples << ".";
auto t = depth.metalTexture;
const auto twidth = std::max(1u, t->width >> depth.level);
@@ -1027,9 +968,10 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
if (stencilAttachment) {
stencil = stencilAttachment;
ASSERT_PRECONDITION(stencil.getSampleCount() <= samples,
"MetalRenderTarget was initialized with a MSAA STENCIL texture, but sample count is %d.",
samples);
FILAMENT_CHECK_PRECONDITION(stencil.getSampleCount() <= samples)
<< "MetalRenderTarget was initialized with a MSAA STENCIL texture, but sample "
"count is "
<< samples << ".";
auto t = stencil.metalTexture;
const auto twidth = std::max(1u, t->width >> stencil.level);
@@ -1306,5 +1248,195 @@ FenceStatus MetalFence::wait(uint64_t timeoutNs) {
return FenceStatus::ERROR;
}
MetalDescriptorSetLayout::MetalDescriptorSetLayout(DescriptorSetLayout&& l) noexcept
: mLayout(std::move(l)) {
size_t dynamicBindings = 0;
for (const auto& binding : mLayout.bindings) {
if (any(binding.flags & DescriptorFlags::DYNAMIC_OFFSET)) {
dynamicBindings++;
}
}
mDynamicOffsetCount = dynamicBindings;
}
id<MTLArgumentEncoder> MetalDescriptorSetLayout::getArgumentEncoder(id<MTLDevice> device, ShaderStage stage,
utils::FixedCapacityVector<MTLTextureType> const& textureTypes) {
auto const index = static_cast<size_t>(stage);
assert_invariant(index < mCachedArgumentEncoder.size());
if (mCachedArgumentEncoder[index] &&
std::equal(
textureTypes.begin(), textureTypes.end(), mCachedTextureTypes[index].begin())) {
return mCachedArgumentEncoder[index];
}
mCachedArgumentEncoder[index] = getArgumentEncoderSlow(device, stage, textureTypes);
mCachedTextureTypes[index] = textureTypes;
return mCachedArgumentEncoder[index];
}
id<MTLArgumentEncoder> MetalDescriptorSetLayout::getArgumentEncoderSlow(id<MTLDevice> device,
ShaderStage stage, utils::FixedCapacityVector<MTLTextureType> const& textureTypes) {
auto const& bindings = getBindings();
NSMutableArray<MTLArgumentDescriptor*>* arguments = [NSMutableArray new];
// Important! The bindings must be sorted by binding number. This has already been done inside
// createDescriptorSetLayout.
size_t textureIndex = 0;
for (auto const& binding : bindings) {
if (!hasShaderType(binding.stageFlags, stage)) {
continue;
}
switch (binding.type) {
case DescriptorType::UNIFORM_BUFFER:
case DescriptorType::SHADER_STORAGE_BUFFER: {
MTLArgumentDescriptor* bufferArgument = [MTLArgumentDescriptor argumentDescriptor];
bufferArgument.index = binding.binding * 2;
bufferArgument.dataType = MTLDataTypePointer;
bufferArgument.access = MTLArgumentAccessReadOnly;
[arguments addObject:bufferArgument];
break;
}
case DescriptorType::SAMPLER:
case DescriptorType::SAMPLER_EXTERNAL: {
MTLArgumentDescriptor* textureArgument = [MTLArgumentDescriptor argumentDescriptor];
textureArgument.index = binding.binding * 2;
textureArgument.dataType = MTLDataTypeTexture;
MTLTextureType textureType = MTLTextureType2D;
if (textureIndex < textureTypes.size()) {
textureType = textureTypes[textureIndex++];
}
textureArgument.textureType = textureType;
textureArgument.access = MTLArgumentAccessReadOnly;
[arguments addObject:textureArgument];
MTLArgumentDescriptor* samplerArgument = [MTLArgumentDescriptor argumentDescriptor];
samplerArgument.index = binding.binding * 2 + 1;
samplerArgument.dataType = MTLDataTypeSampler;
textureArgument.access = MTLArgumentAccessReadOnly;
[arguments addObject:samplerArgument];
break;
}
case DescriptorType::INPUT_ATTACHMENT:
// TODO: support INPUT_ATTACHMENT
assert_invariant(false);
break;
}
}
return [device newArgumentEncoderWithArguments:arguments];
}
MetalDescriptorSet::MetalDescriptorSet(MetalDescriptorSetLayout* layout) noexcept
: layout(layout) {}
void MetalDescriptorSet::finalize(MetalDriver* driver) {
[driver->mContext->currentRenderPassEncoder useResource:driver->mContext->emptyBuffer
usage:MTLResourceUsageRead];
[driver->mContext->currentRenderPassEncoder
useResource:getOrCreateEmptyTexture(driver->mContext)
usage:MTLResourceUsageRead];
if (@available(iOS 13.0, *)) {
[driver->mContext->currentRenderPassEncoder useResources:vertexResources.data()
count:vertexResources.size()
usage:MTLResourceUsageRead
stages:MTLRenderStageVertex];
[driver->mContext->currentRenderPassEncoder useResources:fragmentResources.data()
count:fragmentResources.size()
usage:MTLResourceUsageRead
stages:MTLRenderStageFragment];
} else {
[driver->mContext->currentRenderPassEncoder useResources:vertexResources.data()
count:vertexResources.size()
usage:MTLResourceUsageRead];
[driver->mContext->currentRenderPassEncoder useResources:fragmentResources.data()
count:fragmentResources.size()
usage:MTLResourceUsageRead];
}
}
id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, ShaderStage stage) {
auto const index = static_cast<size_t>(stage);
assert_invariant(index < cachedBuffer.size());
auto& buffer = cachedBuffer[index];
if (buffer) {
return buffer.get();
}
// Map all the texture bindings to their respective texture types.
auto const& bindings = layout->getBindings();
auto textureTypes = utils::FixedCapacityVector<MTLTextureType>::with_capacity(bindings.size());
for (auto const& binding : bindings) {
if (!hasShaderType(binding.stageFlags, stage)) {
continue;
}
MTLTextureType textureType = MTLTextureType2D;
if (auto found = textures.find(binding.binding); found != textures.end()) {
auto const& textureBinding = textures[binding.binding];
textureType = textureBinding.texture.textureType;
}
textureTypes.push_back(textureType);
}
MetalContext const& context = *driver->mContext;
id<MTLArgumentEncoder> encoder =
layout->getArgumentEncoder(context.device, stage, textureTypes);
{
ScopedAllocationTimer timer("descriptor_set");
buffer = { [context.device newBufferWithLength:encoder.encodedLength
options:MTLResourceStorageModeShared],
TrackedMetalBuffer::Type::DESCRIPTOR_SET };
}
[encoder setArgumentBuffer:buffer.get() offset:0];
for (auto const& binding : bindings) {
if (!hasShaderType(binding.stageFlags, stage)) {
continue;
}
switch (binding.type) {
case DescriptorType::UNIFORM_BUFFER:
case DescriptorType::SHADER_STORAGE_BUFFER: {
auto found = buffers.find(binding.binding);
if (found == buffers.end()) {
[encoder setBuffer:driver->mContext->emptyBuffer
offset:0
atIndex:binding.binding * 2];
continue;
}
auto const& bufferBinding = buffers[binding.binding];
[encoder setBuffer:bufferBinding.buffer
offset:bufferBinding.offset
atIndex:binding.binding * 2];
break;
}
case DescriptorType::SAMPLER:
case DescriptorType::SAMPLER_EXTERNAL: {
auto found = textures.find(binding.binding);
if (found == textures.end()) {
[encoder setTexture:driver->mContext->emptyTexture atIndex:binding.binding * 2];
id<MTLSamplerState> sampler =
driver->mContext->samplerStateCache.getOrCreateState({});
[encoder setSamplerState:sampler atIndex:binding.binding * 2 + 1];
continue;
}
auto const& textureBinding = textures[binding.binding];
[encoder setTexture:textureBinding.texture atIndex:binding.binding * 2];
SamplerState samplerState { .samplerParams = textureBinding.sampler };
id<MTLSamplerState> sampler =
driver->mContext->samplerStateCache.getOrCreateState(samplerState);
[encoder setSamplerState:sampler atIndex:binding.binding * 2 + 1];
break;
}
case DescriptorType::INPUT_ATTACHMENT:
assert_invariant(false);
break;
}
}
return buffer.get();
}
} // namespace backend
} // namespace filament

View File

@@ -83,6 +83,10 @@ public:
return std::get<Compute>(mPrograms);
}
bool isRaster() const { return std::holds_alternative<Raster>(mPrograms); }
bool isCompute() const { return std::holds_alternative<Compute>(mPrograms); }
static MetalFunctionBundle none() {
return MetalFunctionBundle(None{});
}

View File

@@ -28,35 +28,35 @@
#include <memory>
#include <tsl/robin_map.h>
#include <utils/Hash.h>
#include <utils/Invocable.h>
namespace filament {
namespace backend {
inline bool operator==(const SamplerParams& lhs, const SamplerParams& rhs) {
return SamplerParams::EqualTo{}(lhs, rhs);
}
// Rasterization Bindings
// ----------------------
// Bindings Buffer name Count
// ------------------------------------------------------
// 0 Zero buffer (placeholder vertex buffer) 1
// 1-16 Filament vertex buffers 16 limited by MAX_VERTEX_BUFFER_COUNT
// 17-26 Uniform buffers 10 Program::UNIFORM_BINDING_COUNT
// 27-30 Sampler groups (argument buffers) 4 Program::SAMPLER_BINDING_COUNT
// 20 Push constants 1
// 21-24 Descriptor sets (argument buffers) 4 limited by MAX_DESCRIPTOR_SET_COUNT
// 25 Dynamic offset buffer 1
//
// Total 31
// Total 23
// Compute Bindings
// ----------------------
// Bindings Buffer name Count
// ------------------------------------------------------
// 0-3 SSBO buffers 4 MAX_SSBO_COUNT
// 17-26 Uniform buffers 10 Program::UNIFORM_BINDING_COUNT
// 27-30 Sampler groups (argument buffers) 4 Program::SAMPLER_BINDING_COUNT
// 20 Push constants 1
// 21-24 Descriptor sets (argument buffers) 4 limited by MAX_DESCRIPTOR_SET_COUNT
// 25 Dynamic offset buffer 1
//
// Total 18
// Total 10
// The total number of vertex buffer "slots" that the Metal backend can bind.
// + 1 to account for the zero buffer, a placeholder buffer used internally by the Metal backend.
@@ -69,10 +69,11 @@ static constexpr uint32_t ZERO_VERTEX_BUFFER_BINDING = 0u;
static constexpr uint32_t USER_VERTEX_BUFFER_BINDING_START = 1u;
// These constants must match the equivalent in CodeGenerator.h.
static constexpr uint32_t UNIFORM_BUFFER_BINDING_START = 17u;
static constexpr uint32_t SSBO_BINDING_START = 0u;
static constexpr uint32_t SAMPLER_GROUP_BINDING_START = 27u;
static constexpr uint32_t PUSH_CONSTANT_BUFFER_INDEX = 20u;
static constexpr uint32_t DESCRIPTOR_SET_BINDING_START = 21u;
static constexpr uint32_t DYNAMIC_OFFSET_BINDING = 25u;
// Forward declarations necessary here, definitions at end of file.
inline bool operator==(const MTLViewport& lhs, const MTLViewport& rhs);
@@ -161,6 +162,8 @@ template<typename StateType,
typename HashFn = utils::hash::MurmurHashFn<StateType>>
class StateCache {
using MapType = tsl::robin_map<StateType, MetalType, HashFn>;
public:
StateCache() = default;
@@ -170,6 +173,18 @@ public:
void setDevice(id<MTLDevice> device) noexcept { mDevice = device; }
void removeIf(utils::Invocable<bool(const StateType&)> fn) noexcept {
typename MapType::const_iterator it = mStateCache.begin();
while (it != mStateCache.end()) {
const auto& [key, _] = *it;
if (UTILS_UNLIKELY(fn(key))) {
it = mStateCache.erase(it);
} else {
++it;
}
}
}
MetalType getOrCreateState(const StateType& state) noexcept {
assert_invariant(mDevice);
@@ -197,7 +212,7 @@ private:
StateCreator creator;
id<MTLDevice> mDevice = nil;
tsl::robin_map<StateType, MetalType, HashFn> mStateCache;
MapType mStateCache;
};
@@ -205,9 +220,8 @@ private:
// Different kinds of state, like pipeline state, uniform buffer state, etc., are passed to the
// current Metal command encoder and persist throughout the lifetime of the encoder (a frame).
// StateTracker is used to prevent calling redundant state change methods.
template<typename StateType>
template <typename StateType, typename StateEqual = std::equal_to<StateType>>
class StateTracker {
public:
// Call to force the state to dirty at the beginning of each frame, as all state must be
@@ -215,7 +229,7 @@ public:
void invalidate() noexcept { mStateDirty = true; }
void updateState(const StateType& newState) noexcept {
if (mCurrentState != newState) {
if (!StateEqual()(mCurrentState, newState)) {
mCurrentState = newState;
mStateDirty = true;
}
@@ -236,7 +250,6 @@ private:
bool mStateDirty = true;
StateType mCurrentState = {};
};
// Pipeline state
@@ -344,6 +357,16 @@ using DepthStencilStateTracker = StateTracker<DepthStencilState>;
using DepthStencilStateCache = StateCache<DepthStencilState, id<MTLDepthStencilState>,
DepthStateCreator>;
struct MtlScissorRectEqual {
bool operator()(const MTLScissorRect& lhs, const MTLScissorRect& rhs) const {
return lhs.height == rhs.height &&
lhs.width == rhs.width &&
lhs.x == rhs.x &&
lhs.y == rhs.y;
}
};
using ScissorRectStateTracker = StateTracker<MTLScissorRect, MtlScissorRectEqual>;
// Uniform buffers
class MetalBufferObject;
@@ -380,18 +403,22 @@ using SamplerStateCache = StateCache<SamplerState, id<MTLSamplerState>, SamplerS
using CullModeStateTracker = StateTracker<MTLCullMode>;
using WindingStateTracker = StateTracker<MTLWinding>;
using DepthClampStateTracker = StateTracker<MTLDepthClipMode>;
// Argument encoder
struct ArgumentEncoderState {
NSUInteger bufferCount;
utils::FixedCapacityVector<MTLTextureType> textureTypes;
explicit ArgumentEncoderState(utils::FixedCapacityVector<MTLTextureType>&& types)
: textureTypes(std::move(types)) {}
explicit ArgumentEncoderState(
NSUInteger bufferCount, utils::FixedCapacityVector<MTLTextureType>&& types)
: bufferCount(bufferCount), textureTypes(std::move(types)) {}
bool operator==(const ArgumentEncoderState& rhs) const noexcept {
return std::equal(textureTypes.begin(), textureTypes.end(), rhs.textureTypes.begin(),
rhs.textureTypes.end());
rhs.textureTypes.end()) &&
bufferCount == rhs.bufferCount;
}
bool operator!=(const ArgumentEncoderState& rhs) const noexcept {
@@ -413,6 +440,30 @@ struct ArgumentEncoderCreator {
using ArgumentEncoderCache = StateCache<ArgumentEncoderState, id<MTLArgumentEncoder>,
ArgumentEncoderCreator, ArgumentEncoderHasher>;
template <NSUInteger N, ShaderStage stage>
class MetalBufferBindings {
public:
MetalBufferBindings() { invalidate(); }
void invalidate() {
mDirtyBuffers.reset();
mDirtyOffsets.reset();
for (int i = 0; i < int(N); i++) {
mDirtyBuffers.set(i, true);
mDirtyOffsets.set(i, true);
}
}
void setBuffer(const id<MTLBuffer> buffer, NSUInteger offset, NSUInteger index);
void bindBuffers(id<MTLCommandEncoder> encoder, NSUInteger startIndex);
private:
static_assert(N <= 8);
std::array<__weak id<MTLBuffer>, N> mBuffers = { nil };
std::array<NSUInteger, N> mOffsets = { 0 };
utils::bitset8 mDirtyBuffers;
utils::bitset8 mDirtyOffsets;
};
} // namespace backend
} // namespace filament

View File

@@ -90,11 +90,17 @@ id<MTLRenderPipelineState> PipelineStateCreator::operator()(id<MTLDevice> device
NSError* error = nullptr;
id<MTLRenderPipelineState> pipeline = [device newRenderPipelineStateWithDescriptor:descriptor
error:&error];
if (error) {
auto description = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
if (UTILS_UNLIKELY(pipeline == nil)) {
NSString *errorMessage =
[NSString stringWithFormat:@"Could not create Metal pipeline state: %@",
error ? error.localizedDescription : @"unknown error"];
auto description = [errorMessage cStringUsingEncoding:NSUTF8StringEncoding];
utils::slog.e << description << utils::io::endl;
[[NSException exceptionWithName:@"MetalRenderPipelineFailure"
reason:errorMessage
userInfo:nil] raise];
}
ASSERT_POSTCONDITION(error == nil, "Could not create Metal pipeline state.");
FILAMENT_CHECK_POSTCONDITION(error == nil) << "Could not create Metal pipeline state.";
return pipeline;
}
@@ -160,28 +166,40 @@ id<MTLSamplerState> SamplerStateCreator::operator()(id<MTLDevice> device,
id<MTLArgumentEncoder> ArgumentEncoderCreator::operator()(id<MTLDevice> device,
const ArgumentEncoderState &state) noexcept {
const auto& textureTypes = state.textureTypes;
const auto& count = textureTypes.size();
assert_invariant(count > 0);
const auto& textureCount = textureTypes.size();
const auto& bufferCount = state.bufferCount;
assert_invariant(textureCount > 0);
// Metal has separate data types for textures versus samplers, so the argument buffer layout
// alternates between texture and sampler, i.e.:
// buffer0
// buffer1
// textureA
// samplerA
// textureB
// samplerB
// etc
NSMutableArray<MTLArgumentDescriptor*>* arguments =
[NSMutableArray arrayWithCapacity:(count * 2)];
for (size_t i = 0; i < count; i++) {
[NSMutableArray arrayWithCapacity:(bufferCount + textureCount * 2)];
size_t i = 0;
for (size_t j = 0; j < bufferCount; j++) {
MTLArgumentDescriptor* bufferArgument = [MTLArgumentDescriptor argumentDescriptor];
bufferArgument.index = i++;
bufferArgument.dataType = MTLDataTypePointer;
bufferArgument.access = MTLArgumentAccessReadOnly;
[arguments addObject:bufferArgument];
}
for (size_t j = 0; j < textureCount; j++) {
MTLArgumentDescriptor* textureArgument = [MTLArgumentDescriptor argumentDescriptor];
textureArgument.index = i * 2 + 0;
textureArgument.index = i++;
textureArgument.dataType = MTLDataTypeTexture;
textureArgument.textureType = textureTypes[i];
textureArgument.access = MTLArgumentAccessReadOnly;
[arguments addObject:textureArgument];
MTLArgumentDescriptor* samplerArgument = [MTLArgumentDescriptor argumentDescriptor];
samplerArgument.index = i * 2 + 1;
samplerArgument.index = i++;
samplerArgument.dataType = MTLDataTypeSampler;
textureArgument.access = MTLArgumentAccessReadOnly;
[arguments addObject:samplerArgument];
@@ -190,5 +208,64 @@ id<MTLArgumentEncoder> ArgumentEncoderCreator::operator()(id<MTLDevice> device,
return [device newArgumentEncoderWithArguments:arguments];
}
template <NSUInteger N, ShaderStage stage>
void MetalBufferBindings<N, stage>::setBuffer(const id<MTLBuffer> buffer, NSUInteger offset, NSUInteger index) {
assert_invariant(offset + 1 <= N);
if (mBuffers[index] != buffer) {
mBuffers[index] = buffer;
mDirtyBuffers.set(index);
}
if (mOffsets[index] != offset) {
mOffsets[index] = offset;
mDirtyOffsets.set(index);
}
}
template <NSUInteger N, ShaderStage stage>
void MetalBufferBindings<N, stage>::bindBuffers(
id<MTLCommandEncoder> encoder, NSUInteger startIndex) {
if (mDirtyBuffers.none() && mDirtyOffsets.none()) {
return;
}
utils::bitset8 onlyOffsetDirty = mDirtyOffsets & ~mDirtyBuffers;
onlyOffsetDirty.forEachSetBit([&](size_t i) {
if constexpr (stage == ShaderStage::VERTEX) {
[(id<MTLRenderCommandEncoder>)encoder setVertexBufferOffset:mOffsets[i]
atIndex:i + startIndex];
} else if constexpr (stage == ShaderStage::FRAGMENT) {
[(id<MTLRenderCommandEncoder>)encoder setFragmentBufferOffset:mOffsets[i]
atIndex:i + startIndex];
} else if constexpr (stage == ShaderStage::COMPUTE) {
[(id<MTLComputeCommandEncoder>)encoder setBufferOffset:mOffsets[i]
atIndex:i + startIndex];
}
});
mDirtyOffsets.reset();
mDirtyBuffers.forEachSetBit([&](size_t i) {
if constexpr (stage == ShaderStage::VERTEX) {
[(id<MTLRenderCommandEncoder>)encoder setVertexBuffer:mBuffers[i]
offset:mOffsets[i]
atIndex:i + startIndex];
} else if constexpr (stage == ShaderStage::FRAGMENT) {
[(id<MTLRenderCommandEncoder>)encoder setFragmentBuffer:mBuffers[i]
offset:mOffsets[i]
atIndex:i + startIndex];
} else if constexpr (stage == ShaderStage::COMPUTE) {
[(id<MTLComputeCommandEncoder>)encoder setBuffer:mBuffers[i]
offset:mOffsets[i]
atIndex:i + startIndex];
}
});
mDirtyBuffers.reset();
}
template class MetalBufferBindings<MAX_DESCRIPTOR_SET_COUNT, ShaderStage::VERTEX>;
template class MetalBufferBindings<MAX_DESCRIPTOR_SET_COUNT, ShaderStage::FRAGMENT>;
template class MetalBufferBindings<MAX_DESCRIPTOR_SET_COUNT, ShaderStage::COMPUTE>;
} // namespace backend
} // namespace filament

View File

@@ -54,12 +54,12 @@ void NoopDriver::beginFrame(int64_t monotonic_clock_ns,
}
void NoopDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
CallbackHandler* handler, FrameScheduledCallback&& callback) {
CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) {
}
void NoopDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
}
@@ -99,9 +99,6 @@ void NoopDriver::destroyProgram(Handle<HwProgram> ph) {
void NoopDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
}
void NoopDriver::destroySamplerGroup(Handle<HwSamplerGroup> sbh) {
}
void NoopDriver::destroySwapChain(Handle<HwSwapChain> sch) {
}
@@ -111,6 +108,12 @@ void NoopDriver::destroyStream(Handle<HwStream> sh) {
void NoopDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
}
void NoopDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> tqh) {
}
void NoopDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) {
}
Handle<HwStream> NoopDriver::createStreamNative(void* nativeStream) {
return {};
}
@@ -182,7 +185,7 @@ bool NoopDriver::isProtectedContentSupported() {
return false;
}
bool NoopDriver::isStereoSupported(backend::StereoscopicType) {
bool NoopDriver::isStereoSupported() {
return false;
}
@@ -202,6 +205,10 @@ bool NoopDriver::isProtectedTexturesSupported() {
return true;
}
bool NoopDriver::isDepthClampSupported() {
return false;
}
bool NoopDriver::isWorkaroundNeeded(Workaround) {
return false;
}
@@ -244,9 +251,6 @@ void NoopDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t inde
Handle<HwBufferObject> boh) {
}
void NoopDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint32_t maxLevel) {
}
void NoopDriver::update3DImage(Handle<HwTexture> th,
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
uint32_t width, uint32_t height, uint32_t depth,
@@ -272,11 +276,6 @@ void NoopDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
void NoopDriver::generateMipmaps(Handle<HwTexture> th) { }
void NoopDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
BufferDescriptor&& data) {
scheduleDestroy(std::move(data));
}
void NoopDriver::compilePrograms(CompilerPriorityQueue priority,
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
if (callback) {
@@ -299,27 +298,14 @@ void NoopDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> re
void NoopDriver::commit(Handle<HwSwapChain> sch) {
}
void NoopDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> ubh) {
}
void NoopDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index,
Handle<HwBufferObject> ubh, uint32_t offset, uint32_t size) {
}
void NoopDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
}
void NoopDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
}
void NoopDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
backend::PushConstantVariant value) {
}
void NoopDriver::insertEventMarker(char const* string, uint32_t len) {
void NoopDriver::insertEventMarker(char const* string) {
}
void NoopDriver::pushGroupMarker(char const* string, uint32_t len) {
void NoopDriver::pushGroupMarker(char const* string) {
}
void NoopDriver::popGroupMarker(int) {
@@ -359,7 +345,7 @@ void NoopDriver::blit(
math::uint2 size) {
}
void NoopDriver::bindPipeline(PipelineState pipelineState) {
void NoopDriver::bindPipeline(PipelineState const& pipelineState) {
}
void NoopDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
@@ -388,4 +374,28 @@ void NoopDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
void NoopDriver::resetState(int) {
}
void NoopDriver::updateDescriptorSetBuffer(
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
backend::BufferObjectHandle boh,
uint32_t offset,
uint32_t size) {
}
void NoopDriver::updateDescriptorSetTexture(
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
backend::TextureHandle th,
SamplerParams params) {
}
void NoopDriver::bindDescriptorSet(
backend::DescriptorSetHandle dsh,
backend::descriptor_set_t set,
backend::DescriptorSetOffsetArray&& offsets) {
}
void NoopDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
}
} // namespace filament

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -66,7 +66,8 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
Platform::DriverConfig const& driverConfig) noexcept
: mPlatform(platform),
mSamplerMap(32) {
mSamplerMap(32),
mDriverConfig(driverConfig) {
state.vao.p = &mDefaultVAO;
@@ -366,7 +367,8 @@ void OpenGLContext::setDefaultState() noexcept {
}
#endif
if (ext.EXT_clip_cull_distance) {
if (ext.EXT_clip_cull_distance
&& mDriverConfig.stereoscopicType == StereoscopicType::INSTANCED) {
glEnable(GL_CLIP_DISTANCE0);
glEnable(GL_CLIP_DISTANCE1);
}
@@ -550,6 +552,14 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
} else if (strstr(renderer, "AMD") ||
strstr(renderer, "ATI")) {
// AMD/ATI GPU
} else if (strstr(vendor, "Mesa")) {
// Seen on
// [Mesa],
// [llvmpipe (LLVM 17.0.6, 256 bits)],
// [4.5 (Core Profile) Mesa 24.0.6-1],
// [4.50]
// not known which version are affected
bugs->rebind_buffer_after_deletion = true;
} else if (strstr(renderer, "Mozilla")) {
bugs->disable_invalidate_framebuffer = true;
}
@@ -604,7 +614,7 @@ FeatureLevel OpenGLContext::resolveFeatureLevel(GLint major, GLint minor,
featureLevel = FeatureLevel::FEATURE_LEVEL_2;
if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
gets.max_combined_texture_image_units >=
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
featureLevel = FeatureLevel::FEATURE_LEVEL_3;
}
}
@@ -613,15 +623,13 @@ FeatureLevel OpenGLContext::resolveFeatureLevel(GLint major, GLint minor,
# ifndef IOS // IOS is guaranteed to have ES3.x
else if (UTILS_UNLIKELY(major == 2)) {
// Runtime OpenGL version is ES 2.x
# if defined(BACKEND_OPENGL_LEVEL_GLES30)
// mandatory extensions (all supported by Mali-400 and Adreno 304)
assert_invariant(exts.OES_depth_texture);
assert_invariant(exts.OES_depth24);
assert_invariant(exts.OES_packed_depth_stencil);
assert_invariant(exts.OES_rgb8_rgba8);
assert_invariant(exts.OES_standard_derivatives);
assert_invariant(exts.OES_texture_npot);
# endif
// note: mandatory extensions (all supported by Mali-400 and Adreno 304)
// OES_depth_texture
// OES_depth24
// OES_packed_depth_stencil
// OES_rgb8_rgba8
// OES_standard_derivatives
// OES_texture_npot
featureLevel = FeatureLevel::FEATURE_LEVEL_0;
}
# endif // IOS
@@ -674,6 +682,7 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
#ifndef __EMSCRIPTEN__
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
#endif
ext->EXT_depth_clamp = exts.has("GL_EXT_depth_clamp"sv);
ext->EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
#ifndef __EMSCRIPTEN__
ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
@@ -744,6 +753,7 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor)
ext->EXT_color_buffer_half_float = true; // Assumes core profile.
ext->EXT_clip_cull_distance = true;
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
ext->EXT_depth_clamp = true;
ext->EXT_discard_framebuffer = false;
ext->EXT_disjoint_timer_query = true;
ext->EXT_multisampled_render_to_texture = false;
@@ -925,15 +935,19 @@ void OpenGLContext::unbindSampler(GLuint sampler) noexcept {
}
}
void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept {
glDeleteBuffers(n, buffers);
void OpenGLContext::deleteBuffer(GLuint buffer, GLenum target) noexcept {
glDeleteBuffers(1, &buffer);
// bindings of bound buffers are reset to 0
const size_t targetIndex = getIndexForBufferTarget(target);
auto& genericBuffer = state.buffers.genericBinding[targetIndex];
UTILS_NOUNROLL
for (GLsizei i = 0; i < n; ++i) {
if (genericBuffer == buffers[i]) {
genericBuffer = 0;
size_t const targetIndex = getIndexForBufferTarget(target);
auto& genericBinding = state.buffers.genericBinding[targetIndex];
if (genericBinding == buffer) {
genericBinding = 0;
}
if (UTILS_UNLIKELY(bugs.rebind_buffer_after_deletion)) {
if (genericBinding) {
glBindBuffer(target, genericBinding);
}
}
@@ -942,16 +956,13 @@ void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum targe
(target != GL_UNIFORM_BUFFER && target != GL_TRANSFORM_FEEDBACK_BUFFER));
if (target == GL_UNIFORM_BUFFER || target == GL_TRANSFORM_FEEDBACK_BUFFER) {
auto& indexedBuffer = state.buffers.targets[targetIndex];
UTILS_NOUNROLL // clang generates >1 KiB of code!!
for (GLsizei i = 0; i < n; ++i) {
UTILS_NOUNROLL
for (auto& buffer : indexedBuffer.buffers) {
if (buffer.name == buffers[i]) {
buffer.name = 0;
buffer.offset = 0;
buffer.size = 0;
}
auto& indexedBinding = state.buffers.targets[targetIndex];
UTILS_NOUNROLL
for (auto& entry: indexedBinding.buffers) {
if (entry.name == buffer) {
entry.name = 0;
entry.offset = 0;
entry.size = 0;
}
}
}

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