Compare commits

..
Author SHA1 Message Date
Benjamin Doherty 4436ffaaab Add missing VulkanDriver.h header 2025-12-09 14:26:29 -08:00
Powei Feng c98038b2d7 vk: remove precondition for createStreamNative (#9497)
Keep it's original behavior of no-op
2025-12-09 13:48:38 -08:00
yrcloud 849c4bb8c5 webgpu: manage a pool of staging buffers for updateGPUBuffer (#9486)
- only MapAsync the staging buffers that are not being used by any command anymore (the commands have finished executing on GPU)
- additionally, in WebGPUQueueManager, create a blank new submission state when a new command encoder is generated instead of in submit()

BUGS=450620535
2025-12-09 12:20:03 -05:00
Mathias Agopian ee64322d76 improve the memcpy benchmark (#9490)
- remove the multi-threaded tests, they added too much noise
- added a memset test
- make sure we allocate the buffer aligned to 16KB
2025-12-08 21:23:51 -08:00
Mathias Agopian 79d65a768a Fix(FrameInfo): fix typos and endFrame logic (#9499)
When the frame history's circular queue is full and the oldest
frame is not yet ready to be processed, we must skip the current
frame.

This change ensures that if `beginFrame` is skipped, the
corresponding `endFrame` is also skipped. This prevents data
corruption in the frame history.
2025-12-09 01:17:42 +00:00
Doris Wu 37610dc8f3 Unify zstd dependency for basisu (#9493) 2025-12-09 01:00:38 +00:00
Mathias Agopian 2e31dc20e4 Fix(FrameInfo): Improve robustness of frame info history management (#9496)
Address potential issues in FrameInfoImpl and FrameInfoManager:
 - Modify FrameInfoManager::beginFrame to handle cases where the 
   circular queue is full and the oldest frame is not yet ready, 
   logging a warning and skipping the frame. 
   This prevents potential use-after-free or data corruption by 
   ensuring only ready frames are removed from the history.

FIXES=[466081317]
2025-12-08 15:28:58 -08:00
Mathias Agopian ae3d98fb47 fix use-after-free of the EGL swapchain (#9495)
PlatformEGLAndroid holds onto the current swapchain in order to use
it when beginFrame is called. Usually the swapchain is set just before
beginFrame is called. However, that's not the case for standalone
views. These are independent of the swapchain and doing "swapchain
stuff" for them is nonsensical.

So make sure that:

1. PlatfromEGLAndroid doesn't hold onto a dangling pointer when the 
   swapchain is destroyed. And add proper null checks.
2. Don't do the "swapchain stuff" when beginFrame is called in the
   context of a standalone view.
   We now reserve frameID 0 for that purpose (meaning the frameid is
   non sensical for these).

We're currently relying on frameID to not wrap-around. At 120 fps, 
that's about 1 year. This will be addressed in a later PR.

FIXES=[462827028, 461399487]
2025-12-08 14:54:34 -08:00
rafadevai a938a790fa VK: Do memcpy if the uniform buffer is WRITE_SHARED (#9491)
With UBO batching, the frontend guarantees that
doing a memcpy to the buffer if safe.
2025-12-08 17:52:53 +00:00
dependabot[bot]andPowei Feng 71d556dddf Bump urllib3 in /docs_src/build in the pip group across 1 directory (#9494)
Bumps the pip group with 1 update in the /docs_src/build directory: [urllib3](https://github.com/urllib3/urllib3).


Updates `urllib3` from 2.5.0 to 2.6.0
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.5.0...2.6.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.6.0
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Powei Feng <powei@google.com>
2025-12-08 17:35:27 +00:00
Powei Feng d61227b2fd material: remove checked-in .filamat for transform name test (#9487)
filament_test_material should not need to depend on a "fixed"
(or checked in) material.

Now the dependent .filamat is generated as needed as is
packaged with the checked-in test_material.filamat as before.
2025-12-07 00:39:41 +00:00
dependabot[bot] 612a6b00e4 Bump urllib3 in /test/renderdiff/src in the pip group across 1 directory (#9492)
Bumps the pip group with 1 update in the /test/renderdiff/src directory: [urllib3](https://github.com/urllib3/urllib3).


Updates `urllib3` from 2.5.0 to 2.6.0
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.5.0...2.6.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.6.0
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-06 16:21:41 -08:00
Ben Doherty b3353b518c Metal: ensure framebuffer fetch is only used on macOS 11.0+ (#9481) 2025-12-05 13:39:52 -08:00
Patrick Ribas 0b44761d43 Allow PlatformEGLHeadless to load GLES (#9488) 2025-12-05 13:37:51 -08:00
yein c7979e4023 update test materials to 68 (#9485) 2025-12-05 12:20:48 -08:00
Mathias Agopian e00904e100 Update NDK version to 29 (latest) (#9489)
also 
- update the android build script to pull the version from 
  build/common/versions

- remove the ability to specify your own ndk version (this was
  undocumented and not fully implemented)

The NDK version now needs to appear in two place (unfortunately):
1. build/common/versions
2. build/android/build.sh
2025-12-05 11:29:02 -08:00
Sungun Park 96e2366569 Add config option for asynchronous mode (#9462) 2025-12-05 02:48:22 +00:00
Mathias Agopian e76624862d don't use the -mcpu compiler flag (#9483)
`-mcpu` is intended to target a specific CPU instruction set and
optimization. That's not what we want to do here. What we want is
target an architecture, specifically armv8-a. So we use `-march` 
instead.  On 64 bits builds, we tune for cortex-a78, which is the
df the Pixel7 era. This doesn't mater much, but might give a little
boost on newer CPU cores. The idea here is that we care "less" about
older CPUs.
2025-12-04 11:29:19 -08:00
Powei Feng c5c83a61d3 vk: fix spurious VK_ERROR_OUT_OF_DATE_KHR on swapchain resize (#9482)
Fixes #9476
Fixes #8604
2025-12-04 19:24:29 +00:00
Powei Feng ccf0d6205f Fix a couple of CI errors (#9477)
- Make sure that renderdiff fails when either building or
   rendering fails (the previous problem was in the use of &&
   that masked the non-zero exit).
 - Make sure that golden branch is properly parsed in
   postsubmit.yml
 - Disable vk loader debug by default
2025-12-04 18:40:05 +00:00
Powei Feng f482c1d702 Fix texture leak in ImGuiHelper on shutdown (#9479) 2025-12-03 14:48:56 -08:00
Vignan Jitta a4c056e822 Fix Wayland window data invalidation in NativeWindowHelperLinux (#9461) 2025-12-03 19:27:07 +00:00
Evan Mezeske f5071b2aae Set the minimum required Metal version (#9425) 2025-12-03 19:10:00 +00:00
Sungun Park 0fa13ee43d Fix race condition with garbage collection (#9465)
This change fixes an intermittent crash caused by a thread race
condition.

The crash happened when a CallbackHandler::Callback (triggered by
engine.flush()) attempted to create an instance of a Structure of Arrays
(SoA) like TransformManager, which adds items to SoA) at the same time
that the garbage collection job (FEngine::gc, which removes items from
SoA) was running.

This conflict is resolved by modifying the logic to ensure that the
garbage collection task completes synchronously before the callback
operations begin.
2025-12-03 16:14:39 +00:00
Doris Wu 1d2b8a08fa Refactor the API usage for UBO batching (#9443) 2025-12-03 21:54:30 +08:00
rafadevai 0c06744450 Cleanup code related to stream transform matrix (#9464)
Clean up all the code related to setting a transform
matrix for a stream.
2025-12-03 00:47:56 -08:00
Powei Feng 4ada29fbc3 Revert "Add flag to guard camera near/far assertion (#9472)"
This reverts commit 3a92cdab3a.
2025-12-02 20:58:12 -08:00
Filament Bot 76182231dd [automated] Updating /docs due to commit 8cd6915
Full commit hash is 8cd6915b9d

DOCS_ALLOW_DIRECT_EDITS
2025-12-03 00:20:02 +00:00
Powei Feng 8cd6915b9d Release Filament 1.68.0 2025-12-02 16:17:24 -08:00
Mathias Agopian f90de26bc2 fix disable_gpu_frame_complete_metric flag (#9473)
- set the flag to true (metrics disabled) until downstream is ready
- make sure to exit the JobQueue thread before destroying it

BUGS=[464370498]
2025-12-02 15:02:52 -08:00
Powei Feng cc2e661aab Update ImGui to 1.92.5 (#9463) 2025-12-02 22:42:35 +00:00
Powei Feng 3a92cdab3a Add flag to guard camera near/far assertion (#9472)
BUGS=465516676
2025-12-02 22:22:38 +00:00
Powei Feng b367982c23 platform: fix PlatformEGLAndroid include (#9469)
Only need AndroidNdk (already included) and not AndroidNativeWindow
2025-12-02 21:46:48 +00:00
Sungun Park a4f4dc617e Support null initialization for Invocable (#9466)
This allows for code like:

Invocable<void()> inv = []() { /*do something*/ };
inv = nullptr;
2025-12-02 21:29:14 +00:00
dependabot[bot] 2eb0f11d05 Bump werkzeug (#9467)
Bumps the pip group with 1 update in the /test/renderdiff/src directory: [werkzeug](https://github.com/pallets/werkzeug).


Updates `werkzeug` from 3.1.3 to 3.1.4
- [Release notes](https://github.com/pallets/werkzeug/releases)
- [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/werkzeug/compare/3.1.3...3.1.4)

---
updated-dependencies:
- dependency-name: werkzeug
  dependency-version: 3.1.4
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-02 18:39:19 +00:00
Doris Wu 6694df991c Use vector instead of unordered_set in FenceManager (#9458) 2025-12-02 13:44:27 +08:00
imadrandSungun Park 8b2bfa7e9d Fix build on windows when vulkan is not supported (#9439)
Co-authored-by: Sungun Park <sungunpark@google.com>
2025-12-02 02:22:38 +00:00
Serge MetralandPowei Feng 7386220325 Stream API Vulkan Backend (#9164)
* Adding the begin frame message for later xtrace post processing.

* First commit

* This is not needed

* Bypass the AHB* issue.

* Typo: Forgot to remove the function.

* Typo: No const specifier.

* Removing the const.

* no exception

* Feedback from discussions with Powei.

* Making the changes discussed with the team.

* Removing useless comments.

* Adding the required methods for allocation.

* Forgot one more switch case.

* Moving the resource to –fvkmemory::Resource type.

* Adding streamed set.

* Allocating every frame for streamed textures.

* Changing the binding logic.

* Proper checks for the streamed texture.

* Cleanup.

* Chaning the logic of the state tracking for streamed textures.

* Fixed full screen bug.

* Refactoring of the code.

* Typo.

* GitHub feedback.

* Github feedback

* feedback.

* Typo

* Feedback.

* Feedback.

* Feedback

* Feedback

* Feedback

* Fixing the Android path.

* Fixing the Android path.

* Feedback.

* feedback

* Feedback.

* Nit

* Feedback

* Feedback

* Feedback

* vk: fix update after bind (wrt descriptor set) validation error

In filament, once a descriptor set is bound, we no longer make updates
to it. However, this guarrantee was broken in the external sampler/image
because a colorspace change might necessitate a layout change, and
thereby a new descriptor needs to be generated with the appropriate
externally sampled image updated in the new set.

Another use case is the Stream API, where each frame we might be
getting a different AHardwareBuffer, meaning we need to update one
or multiple sampler bindings in a set that might have been bound in
a previous frame.

In this change, we always create a new set when there's a change
in the image currently bound to an existing set (while accounting for
whether we need to use a new externally sampled layout or not).

* Removing the decision on whether to use external format or not out of the streamed texture manager. The issue is that all textures are external, not all are using external format. But it's more robust to let the external format image manager rely on the AHB* to decide if the format is external or not. Regardless (because the memory is external) we need to send this to the external image manager.

* Fixing the validation layer in the Stream CL.

* Typo and comment clarification.

---------

Co-authored-by: Powei Feng <powei@google.com>
2025-12-01 11:13:47 -08:00
Sungun Park 000faeaca7 Fix bindVAO for OpenGL (#9453)
Currently bindVertexArray call generates an object for the default VAO,
which is not originally intended.
2025-12-01 18:55:02 +00:00
Powei Feng 3f9783ac02 Bump MATERIAL_VERSION to 68 2025-12-01 10:28:23 -08:00
rafadevai 4e6dc67ae8 VK: Disable depth testing in some cases (#9455)
Depth testing come with a cost, so when the depth
test is marked to always pass and writting to depth
buffer is disable, just disable the feature.

This will also match the GL backend behavior.
2025-12-01 09:40:26 -08:00
Doris Wu 666140bb73 Fix ios x86 build (#9459) 2025-12-01 09:04:09 -08:00
Doris Wu 56050de5cd metal: implement memory mapping (#9454) 2025-11-27 11:05:07 +08:00
Mathias Agopian 772136decd fix a typo/bad merge that broke setPresentationTime (#9452)
FIXES=[462533574]
2025-11-21 11:40:17 -08:00
Powei Feng f664601c51 android: add readPixels callback for ModelViewer (#9440) 2025-11-21 19:33:36 +00:00
Ben Doherty f06b27b7fb Fix getter methods (#9450) 2025-11-21 00:37:28 +00:00
Mathias Agopian ef53ce88d4 don't assume compositor and frame timing are always available (#9449)
- this is not true fro headless swapchains
- and potentially some timings might not be available on a given nativewindow

Previously the code would handle these errors gracefully, but the 
errors were still generated. Now, we query the availability and only
make the calls  if supported.

Also on EGL, we don't attempt to use private APIs -- this code path
should never be used anyways.
2025-11-20 15:32:02 -08:00
Mathias Agopian ef18030e1a frameId must be monotonic in the SwapChain (#9447)
The frameId coming from a Renderer must be monotonic when seen from
a SwapChain (Specifically a ANativeWindow on Android), if it's not
the case, we must clear that part of the history.

This can happen if a SwapChain is used with two different Renderer; at
this point that SwapChain's history is no longer connected to that
Renderer.
2025-11-20 13:23:58 -08:00
Doris Wu f07176c0a2 Try to fix test_UboBatching (#9448) 2025-11-20 15:14:19 +08:00
Doris Wu 15db141c7a Add some unit tests for UboManager (#9446) 2025-11-20 14:28:13 +08:00
Doris Wu d4bbb7c591 buffer update opt: Some optimizations (#9438) 2025-11-20 00:52:04 +00:00
Sungun Park 92e620d2ad Simplify buffer object creation (#9436)
Simplify the buffer object creation logic to streamline and help make
the future integration of asynchronous features easier.
2025-11-19 22:03:05 +00:00
Mathias Agopian 311104da97 update google benchmark library to 1.9.4 (#9441)
* benchmark: update README and add update script

* update google benchmark library to 1.9.4

* update tnt CMakeLists to match the library new version
2025-11-19 11:50:34 -08:00
Filament Bot 3127632f96 [automated] Updating /docs due to commit 59f611b
Full commit hash is 59f611bfde

DOCS_ALLOW_DIRECT_EDITS
2025-11-19 19:32:56 +00:00
Sungun Park 59f611bfde Release Filament 1.67.1 2025-11-19 11:30:16 -08:00
Sungun Park 2d556bdca2 Workaround for screen space reflections artifacts fix (#9444)
This is a workaround for Samsung Galaxy S24 device failing to link this change.
https://github.com/google/filament/commit/ea608d409edd71febe85b683a90642597149a3e9
2025-11-19 19:03:00 +00:00
Ben Doherty 65e7dba8b8 Log GPU errors through Platform debugStat API (#9434) 2025-11-19 10:42:42 -08:00
Mathias Agopian 6550a63056 Fix android helper header usage (#9442)
These headers are private but were used by public headers. 
Refactor the platform code so that these headers stay private.

AndroidNdk.h is treated differently, it is now made public because
it simplifies a lot of things to be able to have the platform concrete
implementation derive from it.
2025-11-19 10:20:37 -08:00
491 changed files with 55067 additions and 19824 deletions
+2 -1
View File
@@ -21,8 +21,9 @@ jobs:
- name: Run update script - name: Run update script
env: env:
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }} GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
run: | run: |
GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | python3 test/renderdiff/src/commit_msg.py) GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py)
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}" COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
if [[ "${GOLDEN_BRANCH}" != "main" ]]; then if [[ "${GOLDEN_BRANCH}" != "main" ]]; then
git config --global user.email "filament.bot@gmail.com" git config --global user.email "filament.bot@gmail.com"
+6 -6
View File
@@ -134,18 +134,17 @@ jobs:
env: env:
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }} COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
run: | run: |
ls ./gltf/Models
TEST_DIR=test/renderdiff TEST_DIR=test/renderdiff
source ${TEST_DIR}/src/preamble.sh source ${TEST_DIR}/src/preamble.sh
start_ set -eux
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 ${TEST_DIR}/src/commit_msg.py) GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 ${TEST_DIR}/src/commit_msg.py)
bash ${TEST_DIR}/generate.sh && \ bash ${TEST_DIR}/generate.sh
python3 ${TEST_DIR}/src/golden_manager.py \ python3 ${TEST_DIR}/src/golden_manager.py \
--branch=${GOLDEN_BRANCH} \ --branch=${GOLDEN_BRANCH} \
--output=${GOLDEN_OUTPUT_DIR} --output=${GOLDEN_OUTPUT_DIR}
# Note that we need to upload the output even if comparison fails, so we undo `set -ex` # Note that we need to upload the output even if comparison fails, so we undo `set -eux`
end_ set +eux
python3 ${TEST_DIR}/src/compare.py \ python3 ${TEST_DIR}/src/compare.py \
--src=${GOLDEN_OUTPUT_DIR} \ --src=${GOLDEN_OUTPUT_DIR} \
@@ -158,6 +157,7 @@ jobs:
cat compare_output.txt >> "$GITHUB_OUTPUT" cat compare_output.txt >> "$GITHUB_OUTPUT"
echo "$DELIMITER" >> "$GITHUB_OUTPUT" echo "$DELIMITER" >> "$GITHUB_OUTPUT"
fi fi
shell: bash
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: presubmit-renderdiff-result name: presubmit-renderdiff-result
+4 -2
View File
@@ -872,7 +872,9 @@ add_subdirectory(${LIBRARIES}/utils)
add_subdirectory(${LIBRARIES}/viewer) add_subdirectory(${LIBRARIES}/viewer)
add_subdirectory(${FILAMENT}/shaders) add_subdirectory(${FILAMENT}/shaders)
add_subdirectory(${EXTERNAL}/abseil/tnt) add_subdirectory(${EXTERNAL}/abseil/tnt)
add_subdirectory(${EXTERNAL}/basisu/tnt) # Add zstd before basisu to force it to use the external zstd target,
# preventing a duplicate symbol conflict with its bundled version.
add_subdirectory(${EXTERNAL}/zstd/tnt)
add_subdirectory(${EXTERNAL}/civetweb/tnt) add_subdirectory(${EXTERNAL}/civetweb/tnt)
add_subdirectory(${EXTERNAL}/imgui/tnt) add_subdirectory(${EXTERNAL}/imgui/tnt)
add_subdirectory(${EXTERNAL}/robin-map/tnt) add_subdirectory(${EXTERNAL}/robin-map/tnt)
@@ -886,7 +888,7 @@ add_subdirectory(${EXTERNAL}/jsmn/tnt)
add_subdirectory(${EXTERNAL}/stb/tnt) add_subdirectory(${EXTERNAL}/stb/tnt)
add_subdirectory(${EXTERNAL}/getopt) add_subdirectory(${EXTERNAL}/getopt)
add_subdirectory(${EXTERNAL}/perfetto/tnt) add_subdirectory(${EXTERNAL}/perfetto/tnt)
add_subdirectory(${EXTERNAL}/zstd/tnt) add_subdirectory(${EXTERNAL}/basisu/tnt)
# Note that this has to be placed after mikktspace in order for combine_static_libs to work. # Note that this has to be placed after mikktspace in order for combine_static_libs to work.
-4
View File
@@ -6,7 +6,3 @@
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut ## Release notes for next branch cut
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
- materials: Make Material Instances' UBO descriptor use dynamic offsets. [⚠️ **Recompile Materials**]
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
} }
dependencies { dependencies {
implementation 'com.google.android.filament:filament-android:1.67.0' implementation 'com.google.android.filament:filament-android:1.68.0'
} }
``` ```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release: iOS projects can use CocoaPods to install the latest release:
```shell ```shell
pod 'Filament', '~> 1.67.0' pod 'Filament', '~> 1.68.0'
``` ```
## Documentation ## Documentation
+9
View File
@@ -7,6 +7,15 @@ 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 Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md). [NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.68.1
## v1.68.0
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
- Metal: report GPU errors to the platform via `debugUpdateStat` (b/431665753).
- materials: Make Material Instances' UBO descriptor use dynamic offsets. [⚠️ **Recompile Materials**]
## v1.67.1 ## v1.67.1
- Metal: Add support for the `SwapChain::CONFIG_MSAA_4_SAMPLES` flag. - Metal: Add support for the `SwapChain::CONFIG_MSAA_4_SAMPLES` flag.
+1 -1
View File
@@ -100,7 +100,7 @@ buildscript {
'kotlin': '2.0.21', 'kotlin': '2.0.21',
'kotlin_coroutines': '1.9.0', 'kotlin_coroutines': '1.9.0',
'buildTools': '35.0.0', 'buildTools': '35.0.0',
'ndk': '27.0.11718014', 'ndk': '29.0.14206865',
'androidx_core': '1.13.1', 'androidx_core': '1.13.1',
'androidx_annotations': '1.9.0' 'androidx_annotations': '1.9.0'
] ]
@@ -25,7 +25,7 @@ using namespace filament;
extern "C" JNIEXPORT jlong JNICALL extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass, Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass,
jlong nativeEngine, jobject buffer_, jint size, jint shBandCount, jint shadowQuality) { jlong nativeEngine, jobject buffer_, jint size, jint shBandCount, jint shadowQuality, jint uboBatchingMode) {
Engine* engine = (Engine*) nativeEngine; Engine* engine = (Engine*) nativeEngine;
AutoBuffer buffer(env, buffer_, size); AutoBuffer buffer(env, buffer_, size);
auto builder = Material::Builder(); auto builder = Material::Builder();
@@ -33,6 +33,7 @@ Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass,
builder.sphericalHarmonicsBandCount(shBandCount); builder.sphericalHarmonicsBandCount(shBandCount);
} }
builder.shadowSamplingQuality((Material::Builder::ShadowSamplingQuality)shadowQuality); builder.shadowSamplingQuality((Material::Builder::ShadowSamplingQuality)shadowQuality);
builder.uboBatching((Material::UboBatchingMode)uboBatchingMode);
Material* material = builder Material* material = builder
.package(buffer.getData(), buffer.getSize()) .package(buffer.getData(), buffer.getSize())
.build(*engine); .build(*engine);
@@ -261,6 +261,20 @@ public class Material {
LOW LOW
} }
/**
* Defines whether a material instance should use UBO batching or not.
*/
public enum UboBatchingMode {
/**
* For default, it follows the engine settings.
* If UBO batching is enabled on the engine and the material domain is SURFACE, it
* turns on the UBO batching. Otherwise, it turns off the UBO batching.
*/
DEFAULT,
/** Disable the Ubo Batching for this material */
DISABLED
}
public static class UserVariantFilterBit { public static class UserVariantFilterBit {
/** Directional lighting */ /** Directional lighting */
public static int DIRECTIONAL_LIGHTING = 0x01; public static int DIRECTIONAL_LIGHTING = 0x01;
@@ -372,6 +386,7 @@ public class Material {
private int mSize; private int mSize;
private int mShBandCount = 0; private int mShBandCount = 0;
private ShadowSamplingQuality mShadowSamplingQuality = ShadowSamplingQuality.LOW; private ShadowSamplingQuality mShadowSamplingQuality = ShadowSamplingQuality.LOW;
private UboBatchingMode mUboBatchingMode = UboBatchingMode.DEFAULT;
/** /**
@@ -416,6 +431,17 @@ public class Material {
return this; return this;
} }
/**
* Set the batching mode of the instances created from this material.
* @param uboBatchingMode
* @return Reference to this Builder for chaining calls.
*/
@NonNull
public Builder uboBatching(UboBatchingMode mode) {
mUboBatchingMode = mode;
return this;
}
/** /**
* Creates and returns the Material object. * Creates and returns the Material object.
* *
@@ -428,7 +454,7 @@ public class Material {
@NonNull @NonNull
public Material build(@NonNull Engine engine) { public Material build(@NonNull Engine engine) {
long nativeMaterial = nBuilderBuild(engine.getNativeObject(), long nativeMaterial = nBuilderBuild(engine.getNativeObject(),
mBuffer, mSize, mShBandCount, mShadowSamplingQuality.ordinal()); mBuffer, mSize, mShBandCount, mShadowSamplingQuality.ordinal(), mUboBatchingMode.ordinal());
if (nativeMaterial == 0) throw new IllegalStateException("Couldn't create Material"); if (nativeMaterial == 0) throw new IllegalStateException("Couldn't create Material");
return new Material(nativeMaterial); return new Material(nativeMaterial);
} }
@@ -1094,7 +1120,7 @@ public class Material {
mNativeObject = 0; mNativeObject = 0;
} }
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount, int shadowQuality); private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount, int shadowQuality, int uboBatchingMode);
private static native long nCreateInstance(long nativeMaterial); private static native long nCreateInstance(long nativeMaterial);
private static native long nCreateInstanceWithName(long nativeMaterial, @NonNull String name); private static native long nCreateInstanceWithName(long nativeMaterial, @NonNull String name);
private static native long nGetDefaultInstance(long nativeMaterial); private static native long nGetDefaultInstance(long nativeMaterial);
@@ -16,6 +16,9 @@
package com.google.android.filament.utils package com.google.android.filament.utils
import android.graphics.Bitmap
import android.os.Handler
import android.os.Looper
import android.view.MotionEvent import android.view.MotionEvent
import android.view.Surface import android.view.Surface
import android.view.SurfaceView import android.view.SurfaceView
@@ -26,6 +29,7 @@ import com.google.android.filament.android.UiHelper
import com.google.android.filament.gltfio.* import com.google.android.filament.gltfio.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.nio.Buffer import java.nio.Buffer
import java.nio.ByteBuffer
private const val kNearPlane = 0.05f // 5 cm private const val kNearPlane = 0.05f // 5 cm
private const val kFarPlane = 1000.0f // 1 km private const val kFarPlane = 1000.0f // 1 km
@@ -119,6 +123,8 @@ class ModelViewer(
private val target = DoubleArray(3) private val target = DoubleArray(3)
private val upward = DoubleArray(3) private val upward = DoubleArray(3)
private var debugFrameCallback: ((Bitmap) -> Unit)? = null
init { init {
renderer = engine.createRenderer() renderer = engine.createRenderer()
scene = engine.createScene() scene = engine.createScene()
@@ -305,10 +311,39 @@ class ModelViewer(
// Render the scene, unless the renderer wants to skip the frame. // Render the scene, unless the renderer wants to skip the frame.
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) { if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
renderer.render(view) renderer.render(view)
debugFrameCallback?.let {
val viewport = view.viewport
val bitmap = Bitmap.createBitmap(viewport.width, viewport.height,
Bitmap.Config.ARGB_8888)
val buffer = ByteBuffer.allocateDirect(viewport.width * viewport.height * 4)
val handler = Handler(Looper.getMainLooper())
val pixelBufferDescriptor = Texture.PixelBufferDescriptor(buffer,
Texture.Format.RGBA, Texture.Type.UBYTE, 1, 0, 0, 0, handler) {
buffer.rewind()
bitmap.copyPixelsFromBuffer(buffer)
it(bitmap)
}
renderer.readPixels(viewport.left, viewport.bottom, viewport.width,
viewport.height, pixelBufferDescriptor)
debugFrameCallback = null
}
renderer.endFrame() renderer.endFrame()
} }
} }
/*
* Sets a callback that will be invoked with the next rendered frame as a Bitmap. Note that this
* is a one-time callback.
*
* @param callback callback to be invoked with a rendered frame as [Bitmap]
*/
fun debugGetNextFrameCallback(callback: (Bitmap) -> Unit) {
debugFrameCallback = callback
}
private fun populateScene(asset: FilamentAsset) { private fun populateScene(asset: FilamentAsset) {
val rcm = engine.renderableManager val rcm = engine.renderableManager
var count = 0 var count = 0
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament GROUP=com.google.android.filament
VERSION_NAME=1.67.0 VERSION_NAME=1.68.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android. POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+1 -1
View File
@@ -158,7 +158,7 @@ function print_fgviewer_help {
} }
# Unless explicitly specified, NDK version will be selected as highest available version within same major release chain # Unless explicitly specified, NDK version will be selected as highest available version within same major release chain
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION:-$(cat `dirname $0`/build/common/versions | grep GITHUB_NDK_VERSION | sed s/GITHUB_NDK_VERSION=//g | cut -f 1 -d ".")} FILAMENT_NDK_VERSION=$(cat `dirname $0`/build/common/versions | grep GITHUB_NDK_VERSION | sed s/GITHUB_NDK_VERSION=//g | cut -f 1 -d ".")
# Internal variables # Internal variables
ISSUE_CLEAN=false ISSUE_CLEAN=false
+2 -2
View File
@@ -18,8 +18,8 @@ if [[ "$GITHUB_WORKFLOW" ]]; then
fi fi
fi fi
# Unless explicitly specified, NDK version will be set to match exactly the required one # Unless explicitly specified, NDK version will be selected as highest available version within same major release chain
FILAMENT_NDK_VERSION=${GITHUB_NDK_VERSION:-27.0.11718014} FILAMENT_NDK_VERSION=$(cat `dirname $0`/../common/versions | grep GITHUB_NDK_VERSION | sed s/GITHUB_NDK_VERSION=//g)
(! grep "${FILAMENT_NDK_VERSION}" `dirname $0`/../../android/build.gradle > /dev/null) && (! grep "${FILAMENT_NDK_VERSION}" `dirname $0`/../../android/build.gradle > /dev/null) &&
echo "Mismatch of NDK versions: want ${FILAMENT_NDK_VERSION} and not found in android/build.gradle" && echo "Mismatch of NDK versions: want ${FILAMENT_NDK_VERSION} and not found in android/build.gradle" &&
+2 -2
View File
@@ -3,7 +3,7 @@ GITHUB_CMAKE_VERSION=3.22.1
GITHUB_NINJA_VERSION=1.10.2 GITHUB_NINJA_VERSION=1.10.2
GITHUB_MESA_VERSION=24.2.1 GITHUB_MESA_VERSION=24.2.1
GITHUB_LLVM_VERSION=16 GITHUB_LLVM_VERSION=16
GITHUB_NDK_VERSION=27.0.11718014 GITHUB_NDK_VERSION=29.0.14206865
GITHUB_EMSDK_VERSION=3.1.60 GITHUB_EMSDK_VERSION=3.1.60
GITHUB_VULKANSDK_VERSION=1.4.321.0 GITHUB_VULKANSDK_VERSION=1.4.321.0
GITHUB_GLTF_SAMPLE_ASSETS_COMMIT=d441dfdb87413ff412c620849a649d61789a470f GITHUB_GLTF_SAMPLE_ASSETS_COMMIT=d441dfdb87413ff412c620849a649d61789a470f
+1 -1
View File
@@ -75,7 +75,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# C_FLAGS += -Wl,-pie # C_FLAGS += -Wl,-pie
# CXX_FLAGS += -lstdc++ # CXX_FLAGS += -lstdc++
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE -mcpu=cortex-a57" CACHE STRING "Toolchain CFLAGS") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE -march=armv8-a -mtune=cortex-a78" CACHE STRING "Toolchain CFLAGS")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS}" CACHE STRING "Toolchain CXXFLAGS") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS}" CACHE STRING "Toolchain CXXFLAGS")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIE -pie -static-libstdc++" CACHE STRING "Toolchain LDFLAGS") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIE -pie -static-libstdc++" CACHE STRING "Toolchain LDFLAGS")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libstdc++" CACHE STRING "Toolchain LDFLAGS") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libstdc++" CACHE STRING "Toolchain LDFLAGS")
+1 -1
View File
@@ -87,7 +87,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# for hardfp: CFLAGS must have -mhard-float # for hardfp: CFLAGS must have -mhard-float
# LDFLAGS must have -Wl,--no-warn-mismatch # LDFLAGS must have -Wl,--no-warn-mismatch
# #
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mthumb -march=armv7-a -mcpu=cortex-a15 -mfloat-abi=softfp -mfpu=neon-vfpv4 -fPIE" CACHE STRING "Toolchain CFLAGS") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=neon-vfpv4 -fPIE" CACHE STRING "Toolchain CFLAGS")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS}" CACHE STRING "Toolchain CXXFLAGS") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS}" CACHE STRING "Toolchain CXXFLAGS")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -march=armv7-a -Wl,--no-warn-mismatch -L${TOOLCHAIN}/arm-linux-androideabi/lib/armv7-a -static-libstdc++ -fPIE -pie" CACHE STRING "Toolchain LDFLAGS") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -march=armv7-a -Wl,--no-warn-mismatch -L${TOOLCHAIN}/arm-linux-androideabi/lib/armv7-a -static-libstdc++ -fPIE -pie" CACHE STRING "Toolchain LDFLAGS")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -march=armv7-a -Wl,--no-warn-mismatch -L${TOOLCHAIN}/arm-linux-androideabi/lib/armv7-a -static-libstdc++" CACHE STRING "Toolchain LDFLAGS") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -march=armv7-a -Wl,--no-warn-mismatch -L${TOOLCHAIN}/arm-linux-androideabi/lib/armv7-a -static-libstdc++" CACHE STRING "Toolchain LDFLAGS")
+2 -2
View File
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
} }
dependencies { dependencies {
implementation 'com.google.android.filament:filament-android:1.66.2' implementation 'com.google.android.filament:filament-android:1.68.0'
} }
</code></pre> </code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p> <p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
@@ -196,7 +196,7 @@ dependencies {
</div> </div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3> <h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<p>iOS projects can use CocoaPods to install the latest release:</p> <p>iOS projects can use CocoaPods to install the latest release:</p>
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.66.2' <pre><code class="language-shell">pod 'Filament', '~&gt; 1.68.0'
</code></pre> </code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2> <h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul> <ul>
+1575 -1546
View File
File diff suppressed because one or more lines are too long
+1058 -1033
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -10,6 +10,6 @@ sortedcontainers==2.4.0
trio==0.31.0 trio==0.31.0
trio-websocket==0.12.2 trio-websocket==0.12.2
typing_extensions==4.15.0 typing_extensions==4.15.0
urllib3==2.5.0 urllib3==2.6.0
websocket-client==1.9.0 websocket-client==1.9.0
wsproto==1.2.0 wsproto==1.2.0
+4
View File
@@ -206,6 +206,8 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanDriverFactory.h src/vulkan/VulkanDriverFactory.h
src/vulkan/VulkanExternalImageManager.cpp src/vulkan/VulkanExternalImageManager.cpp
src/vulkan/VulkanExternalImageManager.h src/vulkan/VulkanExternalImageManager.h
src/vulkan/VulkanStreamedImageManager.cpp
src/vulkan/VulkanStreamedImageManager.h
src/vulkan/VulkanFboCache.cpp src/vulkan/VulkanFboCache.cpp
src/vulkan/VulkanFboCache.h src/vulkan/VulkanFboCache.h
src/vulkan/VulkanHandles.cpp src/vulkan/VulkanHandles.cpp
@@ -316,6 +318,8 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPURenderPrimitive.h src/webgpu/WebGPURenderPrimitive.h
src/webgpu/WebGPURenderTarget.cpp src/webgpu/WebGPURenderTarget.cpp
src/webgpu/WebGPURenderTarget.h src/webgpu/WebGPURenderTarget.h
src/webgpu/WebGPUStagePool.cpp
src/webgpu/WebGPUStagePool.h
src/webgpu/WebGPUStrings.h src/webgpu/WebGPUStrings.h
src/webgpu/WebGPUSwapChain.cpp src/webgpu/WebGPUSwapChain.cpp
src/webgpu/WebGPUSwapChain.h src/webgpu/WebGPUSwapChain.h
@@ -1,63 +0,0 @@
/*
* Copyright (C) 2025 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_BUFFEROBJECTSTREAMDESCRIPTOR_H
#define TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
#include <backend/Handle.h>
#include <vector>
namespace filament::backend {
/**
* The type of association between a buffer object and a stream.
*/
enum class BufferObjectStreamAssociationType { TRANSFORM_MATRIX };
/**
* A descriptor for a buffer object to stream association.
*/
class UTILS_PUBLIC BufferObjectStreamDescriptor {
public:
//! creates an empty descriptor
BufferObjectStreamDescriptor() noexcept = default;
BufferObjectStreamDescriptor(const BufferObjectStreamDescriptor& rhs) = delete;
BufferObjectStreamDescriptor& operator=(const BufferObjectStreamDescriptor& rhs) = delete;
BufferObjectStreamDescriptor(BufferObjectStreamDescriptor&& rhs) = default;
BufferObjectStreamDescriptor& operator=(BufferObjectStreamDescriptor&& rhs) = default;
// --------------------------------------------------------------------------------------------
struct StreamDataDescriptor {
uint32_t offset;
Handle<HwStream> stream;
BufferObjectStreamAssociationType associationType;
};
std::vector<StreamDataDescriptor> mStreams;
};
} // namespace filament::backend
#if !defined(NDEBUG)
utils::io::ostream& operator<<(utils::io::ostream& out,
const filament::backend::BufferObjectStreamDescriptor& b);
#endif
#endif // TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
@@ -1713,6 +1713,8 @@ using FrameTimestamps = Platform::FrameTimestamps;
using CompositorTiming = Platform::CompositorTiming; using CompositorTiming = Platform::CompositorTiming;
using AsynchronousMode = Platform::AsynchronousMode;
} // namespace filament::backend } // namespace filament::backend
template<> struct utils::EnableBitMaskOperators<filament::backend::ShaderStageFlags> template<> struct utils::EnableBitMaskOperators<filament::backend::ShaderStageFlags>
+67 -7
View File
@@ -19,13 +19,17 @@
#ifndef TNT_FILAMENT_BACKEND_PLATFORM_H #ifndef TNT_FILAMENT_BACKEND_PLATFORM_H
#define TNT_FILAMENT_BACKEND_PLATFORM_H #define TNT_FILAMENT_BACKEND_PLATFORM_H
#include <utils/CString.h>
#include <utils/compiler.h> #include <utils/compiler.h>
#include <utils/Invocable.h> #include <utils/Invocable.h>
#include <utils/Mutex.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <atomic> #include <atomic>
#include <memory>
#include <mutex>
namespace filament::backend { namespace filament::backend {
@@ -70,6 +74,9 @@ public:
ExternalImageHandle& operator=(ExternalImageHandle const& rhs) noexcept; ExternalImageHandle& operator=(ExternalImageHandle const& rhs) noexcept;
ExternalImageHandle& operator=(ExternalImageHandle&& rhs) noexcept; ExternalImageHandle& operator=(ExternalImageHandle&& rhs) noexcept;
bool operator==(const ExternalImageHandle& rhs) const noexcept {
return mTarget == rhs.mTarget;
}
explicit operator bool() const noexcept { return mTarget != nullptr; } explicit operator bool() const noexcept { return mTarget != nullptr; }
ExternalImage* UTILS_NULLABLE get() noexcept { return mTarget; } ExternalImage* UTILS_NULLABLE get() noexcept { return mTarget; }
@@ -248,6 +255,28 @@ public:
REALTIME, REALTIME,
}; };
/**
* Defines how asynchronous operations are handled by the engine.
*/
enum class AsynchronousMode : uint8_t {
/**
* Asynchronous operations are disabled. This is the default.
*/
NONE,
/**
* Attempts to use a dedicated worker thread for asynchronous tasks. If threading is not
* supported by the platform, it automatically falls back to using an amortization strategy.
*/
THREAD_PREFERRED,
/**
* Uses an amortization strategy, processing a small number of asynchronous tasks during
* each engine update cycle.
*/
AMORTIZATION,
};
struct DriverConfig { struct DriverConfig {
/** /**
* Size of handle arena in bytes. Setting to 0 indicates default value is to be used. * Size of handle arena in bytes. Setting to 0 indicates default value is to be used.
@@ -317,6 +346,11 @@ public:
* - VulkanPlatform * - VulkanPlatform
*/ */
bool vulkanEnableStagingBufferBypass = false; bool vulkanEnableStagingBufferBypass = false;
/**
* Asynchronous mode for the engine. Defines how asynchronous operations are handled.
*/
AsynchronousMode asynchronousMode = AsynchronousMode::NONE;
}; };
Platform() noexcept; Platform() noexcept;
@@ -508,13 +542,22 @@ public:
// -------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------
// Debugging APIs // Debugging APIs
using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key, uint64_t value)>; using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key,
uint64_t intValue, utils::CString stringValue)>;
/** /**
* Sets the callback function that the backend can use to update backend-specific statistics * Sets the callback function that the backend can use to update backend-specific statistics
* to aid with debugging. This callback is guaranteed to be called on the Filament driver * to aid with debugging. This callback is guaranteed to be called on the Filament driver
* thread. * thread.
* *
* The callback signature is (key, intValue, stringValue). Note that for any given call,
* only one of the value parameters (intValue or stringValue) will be meaningful, depending on
* the specific key.
*
* IMPORTANT_NOTE: because the callback is called on the driver thread, only quick, non-blocking
* work should be done inside it. Furthermore, no graphics API calls (such as GL calls) should
* be made, which could interfere with Filament's driver state.
*
* @param debugUpdateStat an Invocable that updates debug statistics * @param debugUpdateStat an Invocable that updates debug statistics
*/ */
void setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept; void setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept;
@@ -533,15 +576,32 @@ public:
* This function is guaranteed to be called only on a single thread, the Filament driver * This function is guaranteed to be called only on a single thread, the Filament driver
* thread. * thread.
* *
* @param key a null-terminated C-string with the key of the debug statistic * @param key a null-terminated C-string with the key of the debug statistic
* @param value the updated value of key * @param intValue the updated integer value of key (the string value passed to the
* callback will be empty)
*/ */
void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t value); void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t intValue);
/**
* To track backend-specific statistics, the backend implementation can call the
* application-provided callback function debugUpdateStatFunc to associate or update a value
* with a given key. It is possible for this function to be called multiple times with the
* same key, in which case newer values should overwrite older values.
*
* This function is guaranteed to be called only on a single thread, the Filament driver
* thread.
*
* @param key a null-terminated C-string with the key of the debug statistic
* @param stringValue the updated string value of key (the integer value passed to the
* callback will be 0)
*/
void debugUpdateStat(const char* UTILS_NONNULL key, utils::CString stringValue);
private: private:
InsertBlobFunc mInsertBlob; std::shared_ptr<InsertBlobFunc> mInsertBlob;
RetrieveBlobFunc mRetrieveBlob; std::shared_ptr<RetrieveBlobFunc> mRetrieveBlob;
DebugUpdateStatFunc mDebugUpdateStat; std::shared_ptr<DebugUpdateStatFunc> mDebugUpdateStat;
mutable utils::Mutex mMutex;
}; };
} // namespace filament } // namespace filament
@@ -17,8 +17,6 @@
#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H #ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H #define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
#include "AndroidSwapChainHelper.h"
#include "AndroidFrameCallback.h"
#include "AndroidNdk.h" #include "AndroidNdk.h"
#include <backend/AcquiredImage.h> #include <backend/AcquiredImage.h>
@@ -32,8 +30,6 @@
#include <math/mat3.h> #include <math/mat3.h>
#include "AndroidNativeWindow.h"
#include <chrono> #include <chrono>
#include <stddef.h> #include <stddef.h>
@@ -171,19 +167,10 @@ protected:
SwapChain* drawSwapChain, SwapChain* drawSwapChain,
SwapChain* readSwapChain) override; SwapChain* readSwapChain) override;
struct SwapChainEGLAndroid : public SwapChainEGL {
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
void* nativeWindow, uint64_t flags);
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
uint32_t width, uint32_t height, uint64_t flags);
void terminate(PlatformEGLAndroid& platform);
bool setPresentFrameId(uint64_t frameId) const noexcept;
uint64_t getFrameId(uint64_t frameId) const noexcept;
private:
AndroidSwapChainHelper mImpl{};
};
private: private:
struct SwapChainEGLAndroid;
struct AndroidDetails;
// prevent derived classes' implementations to call through // prevent derived classes' implementations to call through
[[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override; [[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override;
[[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override; [[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override;
@@ -203,17 +190,14 @@ private:
int mOSVersion; int mOSVersion;
ExternalStreamManagerAndroid& mExternalStreamManager; ExternalStreamManagerAndroid& mExternalStreamManager;
AndroidDetails& mAndroidDetails;
InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded; InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded;
utils::PerformanceHintManager mPerformanceHintManager; utils::PerformanceHintManager mPerformanceHintManager;
utils::PerformanceHintManager::Session mPerformanceHintSession; utils::PerformanceHintManager::Session mPerformanceHintSession;
SwapChainEGLAndroid* mCurrentDrawSwapChain{};
using clock = std::chrono::high_resolution_clock; using clock = std::chrono::high_resolution_clock;
clock::time_point mStartTimeOfActualWork; clock::time_point mStartTimeOfActualWork;
AndroidProducerThrottling mProducerThrottling; SwapChainEGLAndroid* mCurrentDrawSwapChain{};
bool mAssertNativeWindowIsValid = false; bool mAssertNativeWindowIsValid = false;
AndroidFrameCallback mAndroidFrameCallback;
}; };
} // namespace filament::backend } // namespace filament::backend
@@ -17,12 +17,13 @@
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H #ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H #define TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
#include "AndroidFrameCallback.h"
#include "AndroidNdk.h" #include "AndroidNdk.h"
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <backend/platforms/VulkanPlatform.h> #include <backend/platforms/VulkanPlatform.h>
#include <utils/compiler.h>
#include <android/hardware_buffer.h> #include <android/hardware_buffer.h>
namespace filament::backend { namespace filament::backend {
@@ -91,6 +92,8 @@ protected:
VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override; VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override;
private: private:
struct AndroidDetails;
struct ExternalImageVulkanAndroid : public ExternalImage { struct ExternalImageVulkanAndroid : public ExternalImage {
AHardwareBuffer* aHardwareBuffer = nullptr; AHardwareBuffer* aHardwareBuffer = nullptr;
bool sRGB = false; bool sRGB = false;
@@ -99,7 +102,7 @@ private:
~ExternalImageVulkanAndroid() override; ~ExternalImageVulkanAndroid() override;
}; };
AndroidFrameCallback mAndroidFrameCallback; AndroidDetails& mAndroidDetails;
int mOSVersion{}; int mOSVersion{};
}; };
@@ -22,7 +22,6 @@
#include "private/backend/Driver.h" #include "private/backend/Driver.h"
#include <backend/BufferDescriptor.h> #include <backend/BufferDescriptor.h>
#include <backend/BufferObjectStreamDescriptor.h>
#include <backend/CallbackHandler.h> #include <backend/CallbackHandler.h>
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <backend/Handle.h> #include <backend/Handle.h>
@@ -51,7 +51,6 @@
namespace filament::backend { namespace filament::backend {
class BufferDescriptor; class BufferDescriptor;
class BufferObjectStreamDescriptor;
class CallbackHandler; class CallbackHandler;
class PixelBufferDescriptor; class PixelBufferDescriptor;
class Program; class Program;
@@ -392,10 +392,6 @@ DECL_DRIVER_API_N(updateBufferObject,
backend::BufferDescriptor&&, data, backend::BufferDescriptor&&, data,
uint32_t, byteOffset) uint32_t, byteOffset)
DECL_DRIVER_API_N(registerBufferObjectStreams,
backend::BufferObjectHandle, boh,
backend::BufferObjectStreamDescriptor&&, streams)
DECL_DRIVER_API_N(updateBufferObjectUnsynchronized, DECL_DRIVER_API_N(updateBufferObjectUnsynchronized,
backend::BufferObjectHandle, boh, backend::BufferObjectHandle, boh,
backend::BufferDescriptor&&, data, backend::BufferDescriptor&&, data,
@@ -61,6 +61,16 @@ int NativeWindow::enableFrameTimestamps(ANativeWindow* anw, bool enable) {
return pWindow->perform(anw, ENABLE_FRAME_TIMESTAMPS, enable); return pWindow->perform(anw, ENABLE_FRAME_TIMESTAMPS, enable);
} }
int NativeWindow::frameTimestampsSupportsPresent(ANativeWindow* anw, bool* outSupportsPresent) {
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
int value = 0;
bool const success = pWindow->perform(anw, FRAME_TIMESTAMPS_SUPPORTS_PRESENT, &value);
if (success) {
*outSupportsPresent = bool(value);
}
return success;
}
int NativeWindow::getCompositorTiming(ANativeWindow* anw, int NativeWindow::getCompositorTiming(ANativeWindow* anw,
int64_t* compositeDeadline, int64_t* compositeInterval, int64_t* compositeDeadline, int64_t* compositeInterval,
int64_t* compositeToPresentLatency) { int64_t* compositeToPresentLatency) {
@@ -32,6 +32,7 @@ struct NativeWindow {
// is valid query enum value // is valid query enum value
enum { enum {
IS_VALID = 17, IS_VALID = 17,
FRAME_TIMESTAMPS_SUPPORTS_PRESENT = 18,
GET_NEXT_FRAME_ID = 24, GET_NEXT_FRAME_ID = 24,
ENABLE_FRAME_TIMESTAMPS = 25, ENABLE_FRAME_TIMESTAMPS = 25,
GET_COMPOSITOR_TIMING = 26, GET_COMPOSITOR_TIMING = 26,
@@ -51,6 +52,7 @@ struct NativeWindow {
static int getNextFrameId(ANativeWindow* anw, uint64_t* frameId); static int getNextFrameId(ANativeWindow* anw, uint64_t* frameId);
static int enableFrameTimestamps(ANativeWindow* anw, bool enable); static int enableFrameTimestamps(ANativeWindow* anw, bool enable);
static int frameTimestampsSupportsPresent(ANativeWindow* anw, bool* outSupportsPresent);
static int getCompositorTiming(ANativeWindow* anw, static int getCompositorTiming(ANativeWindow* anw,
int64_t* compositeDeadline, int64_t* compositeInterval, int64_t* compositeDeadline, int64_t* compositeInterval,
int64_t* compositeToPresentLatency); int64_t* compositeToPresentLatency);
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
#include "AndroidNdk.h" #include <backend/platforms/AndroidNdk.h>
#include <android/hardware_buffer.h> #include <android/hardware_buffer.h>
@@ -19,6 +19,9 @@
#include <android/native_window.h> #include <android/native_window.h>
#include <utils/compiler.h>
#include <utils/Logger.h>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <limits> #include <limits>
@@ -36,11 +39,23 @@ bool AndroidSwapChainHelper::setPresentFrameId(
int const status = NativeWindow::getNextFrameId(anw, &sysFrameId); int const status = NativeWindow::getNextFrameId(anw, &sysFrameId);
if (status == 0) { if (status == 0) {
std::lock_guard const lock(mLock); std::lock_guard const lock(mLock);
auto const pos = mFrameIdToSystemFrameId.find(frameId); // frameIds must be strictly monotonic, if that's not the case (i.e. the new frameId is
if (pos && *pos != sysFrameId) { // less or equal to the last one in the map), we have to clear the map, because the
// we're trying to associate the same frame id to a different frame! // map's find() assume sorted keys.
return false; // This case can happen if two different filament::Renderer are used with the same
// ANativeWindow (the Renderer would have different frameIds). This is expected to
// be a rare case.
if (UTILS_UNLIKELY(!mFrameIdToSystemFrameId.empty() &&
frameId <= mFrameIdToSystemFrameId.back().first)) {
// this log is expected to happen very rarely
DLOG(INFO) << "clearing frame history anw=" << anw
<< ", frameId=" << frameId
<< ", previous=" << mFrameIdToSystemFrameId.back().first
<< ", sysFrameId=" << sysFrameId;
// clear the frame history
mFrameIdToSystemFrameId.clear();
} }
// oldest entry is removed // oldest entry is removed
mFrameIdToSystemFrameId.insert(frameId, sysFrameId); mFrameIdToSystemFrameId.insert(frameId, sysFrameId);
return true; return true;
+44 -13
View File
@@ -139,42 +139,73 @@ bool Platform::queryFrameTimestamps(SwapChain const*, uint64_t, FrameTimestamps*
} }
void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept { void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept {
mInsertBlob = std::move(insertBlob); std::lock_guard<decltype(mMutex)> lock(mMutex);
mRetrieveBlob = std::move(retrieveBlob); mInsertBlob = std::make_shared<InsertBlobFunc>(std::move(insertBlob));
mRetrieveBlob = std::make_shared<RetrieveBlobFunc>(std::move(retrieveBlob));
} }
bool Platform::hasInsertBlobFunc() const noexcept { bool Platform::hasInsertBlobFunc() const noexcept {
return bool(mInsertBlob); std::lock_guard<decltype(mMutex)> lock(mMutex);
return mInsertBlob && bool(*mInsertBlob);
} }
bool Platform::hasRetrieveBlobFunc() const noexcept { bool Platform::hasRetrieveBlobFunc() const noexcept {
return bool(mRetrieveBlob); std::lock_guard<decltype(mMutex)> lock(mMutex);
return mRetrieveBlob && bool(*mRetrieveBlob);
} }
void Platform::insertBlob(void const* key, size_t keySize, void const* value, size_t valueSize) { void Platform::insertBlob(void const* key, size_t keySize, void const* value, size_t valueSize) {
if (mInsertBlob) { std::shared_ptr<InsertBlobFunc> callback;
mInsertBlob(key, keySize, value, valueSize); {
std::unique_lock<decltype(mMutex)> lock(mMutex);
callback = mInsertBlob;
}
if (callback) {
(*callback)(key, keySize, value, valueSize);
} }
} }
size_t Platform::retrieveBlob(void const* key, size_t keySize, void* value, size_t valueSize) { size_t Platform::retrieveBlob(void const* key, size_t keySize, void* value, size_t valueSize) {
if (mRetrieveBlob) { std::shared_ptr<RetrieveBlobFunc> callback;
return mRetrieveBlob(key, keySize, value, valueSize); {
std::unique_lock<decltype(mMutex)> lock(mMutex);
callback = mRetrieveBlob;
}
if (callback) {
return (*callback)(key, keySize, value, valueSize);
} }
return 0; return 0;
} }
void Platform::setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept { void Platform::setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept {
mDebugUpdateStat = std::move(debugUpdateStat); std::lock_guard<decltype(mMutex)> lock(mMutex);
mDebugUpdateStat = std::make_shared<DebugUpdateStatFunc>(std::move(debugUpdateStat));
} }
bool Platform::hasDebugUpdateStatFunc() const noexcept { bool Platform::hasDebugUpdateStatFunc() const noexcept {
return bool(mDebugUpdateStat); std::lock_guard<decltype(mMutex)> lock(mMutex);
return mDebugUpdateStat && bool(*mDebugUpdateStat);
} }
void Platform::debugUpdateStat(const char* key, uint64_t value) { void Platform::debugUpdateStat(const char* key, uint64_t intValue) {
if (mDebugUpdateStat) { std::shared_ptr<DebugUpdateStatFunc> callback;
mDebugUpdateStat(key, value); {
std::unique_lock<decltype(mMutex)> lock(mMutex);
callback = mDebugUpdateStat;
}
if (callback) {
(*callback)(key, intValue, "");
}
}
void Platform::debugUpdateStat(const char* key, utils::CString stringValue) {
std::shared_ptr<DebugUpdateStatFunc> callback;
{
std::unique_lock<decltype(mMutex)> lock(mMutex);
callback = mDebugUpdateStat;
}
if (callback) {
(*callback)(key, 0, stringValue);
} }
} }
+3 -6
View File
@@ -165,7 +165,7 @@ public:
size_t size, bool forceGpuBuffer = false); size_t size, bool forceGpuBuffer = false);
~MetalBuffer(); ~MetalBuffer();
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer || mCpuBuffer; } [[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer; }
MetalBuffer(const MetalBuffer& rhs) = delete; MetalBuffer(const MetalBuffer& rhs) = delete;
MetalBuffer& operator=(const MetalBuffer& rhs) = delete; MetalBuffer& operator=(const MetalBuffer& rhs) = delete;
@@ -185,14 +185,12 @@ public:
* Denotes that this buffer is used for a draw call ensuring that its allocation remains valid * Denotes that this buffer is used for a draw call ensuring that its allocation remains valid
* until the end of the current frame. * until the end of the current frame.
* *
* @return The MTLBuffer representing the current state of the buffer to bind, or nil if there * @return The MTLBuffer representing the current state of the buffer to bind, it never returns
* is no device allocation. * nil.
* *
*/ */
id<MTLBuffer> getGpuBufferForDraw() noexcept; id<MTLBuffer> getGpuBufferForDraw() noexcept;
void* getCpuBuffer() const noexcept { return mCpuBuffer; }
void setLabel(const utils::ImmutableCString& label) { void setLabel(const utils::ImmutableCString& label) {
#if FILAMENT_METAL_DEBUG_LABELS #if FILAMENT_METAL_DEBUG_LABELS
if (label.empty()) { if (label.empty()) {
@@ -235,7 +233,6 @@ private:
UploadStrategy mUploadStrategy; UploadStrategy mUploadStrategy;
TrackedMetalBuffer mBuffer; TrackedMetalBuffer mBuffer;
size_t mBufferSize = 0; size_t mBufferSize = 0;
void* mCpuBuffer = nullptr;
MetalContext& mContext; MetalContext& mContext;
}; };
+13 -63
View File
@@ -39,34 +39,30 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
mUploadStrategy = UploadStrategy::POOL; mUploadStrategy = UploadStrategy::POOL;
} }
// If the buffer is less than 4K in size and is updated frequently, we don't use an explicit MTLResourceOptions options = MTLResourceStorageModePrivate;
// buffer. Instead, we use immediate command encoder methods like setVertexBytes:length:atIndex:.
// This won't work for SSBOs, since they are read/write.
/* // The buffer will be memory mapped for write operations.
if (size <= 4 * 1024 && bindingType != BufferObjectBinding::SHADER_STORAGE && if (any(usage & BufferUsage::SHARED_WRITE_BIT)) {
usage == BufferUsage::DYNAMIC && !forceGpuBuffer) { #if defined(FILAMENT_IOS) || defined(__arm64__) || defined(__aarch64__)
mBuffer = nil; // iOS and Apple Silicon devices use UMA (Unified Memory Architecture), so we use Shared memory.
mCpuBuffer = malloc(size); options = MTLResourceStorageModeShared;
return; #else
// Intel Macs require Managed memory for CPU/GPU synchronization.
options = MTLResourceStorageModeManaged;
#endif
} }
*/
// Otherwise, we allocate a private GPU buffer.
{ {
ScopedAllocationTimer timer("generic"); ScopedAllocationTimer timer("generic");
mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate], mBuffer = { [context.device newBufferWithLength:size options:options],
TrackedMetalBuffer::Type::GENERIC }; TrackedMetalBuffer::Type::GENERIC };
} }
// mBuffer might fail to be allocated. Clients can check for this by calling // mBuffer might fail to be allocated. Clients can check for this by calling
// wasAllocationSuccessful(). // wasAllocationSuccessful().
} }
MetalBuffer::~MetalBuffer() { MetalBuffer::~MetalBuffer() = default;
if (mCpuBuffer) {
free(mCpuBuffer);
}
}
void MetalBuffer::copyIntoBuffer( void MetalBuffer::copyIntoBuffer(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) { void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
@@ -83,12 +79,6 @@ void MetalBuffer::copyIntoBuffer(
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3)) FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3))
<< "byteOffset must be a multiple of 4, tag=" << getHandleTag(); << "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;
}
switch (mUploadStrategy) { switch (mUploadStrategy) {
case UploadStrategy::BUMP_ALLOCATOR: case UploadStrategy::BUMP_ALLOCATOR:
uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag)); uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag));
@@ -106,11 +96,6 @@ void MetalBuffer::copyIntoBufferUnsynchronized(
} }
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw() 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); assert_invariant(mBuffer);
return mBuffer.get(); return mBuffer.get();
} }
@@ -171,41 +156,6 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncod
offsets:metalOffsets.data() offsets:metalOffsets.data()
withRange:bufferRange]; withRange:bufferRange];
} }
for (size_t b = 0; b < count; b++) {
MetalBuffer* const buffer = buffers[b];
if (!buffer) {
continue;
}
const void* cpuBuffer = buffer->getCpuBuffer();
if (!cpuBuffer) {
continue;
}
const size_t bufferIndex = bufferStart + b;
const size_t offset = offsets[b];
auto* bytes = static_cast<const uint8_t*>(cpuBuffer);
if (stages & Stage::VERTEX) {
[(id<MTLRenderCommandEncoder>) encoder setVertexBytes:(bytes + offset)
length:(buffer->getSize() - offset)
atIndex:bufferIndex];
}
if (stages & Stage::FRAGMENT) {
[(id<MTLRenderCommandEncoder>) encoder setFragmentBytes:(bytes + offset)
length:(buffer->getSize() - offset)
atIndex:bufferIndex];
}
if (stages & Stage::COMPUTE) {
// TODO: using setBytes means the data is read-only, which currently isn't enforced.
// In practice this won't be an issue since MetalBuffer ensures all SSBOs are realized
// through actual id<MTLBuffer> allocations.
[(id<MTLComputeCommandEncoder>) encoder setBytes:(bytes + offset)
length:(buffer->getSize() - offset)
atIndex:bufferIndex];
}
}
} }
void MetalBuffer::uploadWithPoolBuffer( void MetalBuffer::uploadWithPoolBuffer(
@@ -17,6 +17,7 @@
#ifndef TNT_METALCONTEXT_H #ifndef TNT_METALCONTEXT_H
#define TNT_METALCONTEXT_H #define TNT_METALCONTEXT_H
#include "MetalErrorQueue.h"
#include "MetalResourceTracker.h" #include "MetalResourceTracker.h"
#include "MetalShaderCompiler.h" #include "MetalShaderCompiler.h"
#include "MetalState.h" #include "MetalState.h"
@@ -129,6 +130,7 @@ struct MetalContext {
id<MTLCommandBuffer> pendingCommandBuffer = nil; id<MTLCommandBuffer> pendingCommandBuffer = nil;
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nil; id<MTLRenderCommandEncoder> currentRenderPassEncoder = nil;
uint32_t currentFrame = 0; uint32_t currentFrame = 0;
MetalErrorQueue commandBufferErrors;
std::atomic<bool> memorylessLimitsReached = false; std::atomic<bool> memorylessLimitsReached = false;
@@ -153,6 +153,7 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) { if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) {
logMTLCommandBufferError(errorCode); logMTLCommandBufferError(errorCode);
context->commandBufferErrors.push(buffer.error);
} }
}]; }];
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer) FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)
+2
View File
@@ -61,6 +61,8 @@ public:
MetalContext* getContext() { return mContext; } MetalContext* getContext() { return mContext; }
using DriverBase::scheduleDestroy;
private: private:
friend class MetalSwapChain; friend class MetalSwapChain;
+27 -17
View File
@@ -254,6 +254,18 @@ MetalDriver::~MetalDriver() noexcept {
void MetalDriver::tick(int) { void MetalDriver::tick(int) {
executeTickOps(); executeTickOps();
executeDeferredOps(); executeDeferredOps();
// Notify platform of GPU errors.
auto& platform = mPlatform;
if (UTILS_UNLIKELY(!mContext->commandBufferErrors.isEmpty())) {
mContext->commandBufferErrors.flush([&platform](NSError* error) {
if (UTILS_VERY_UNLIKELY(!error)) {
return;
}
const utils::CString errorString(error.localizedDescription.UTF8String);
platform.debugUpdateStat("filament.metal.command_buffer_error", errorString);
});
}
} }
void MetalDriver::beginFrame(int64_t monotonic_clock_ns, void MetalDriver::beginFrame(int64_t monotonic_clock_ns,
@@ -1132,8 +1144,12 @@ bool MetalDriver::isRenderTargetFormatSupported(TextureFormat format) {
bool MetalDriver::isFrameBufferFetchSupported() { bool MetalDriver::isFrameBufferFetchSupported() {
// FrameBuffer fetch is achievable via "programmable blending" in Metal, and only supported on // FrameBuffer fetch is achievable via "programmable blending" in Metal, and only supported on
// Apple GPUs with readWriteTextureSupport. // Apple GPUs with readWriteTextureSupport.
return mContext->highestSupportedGpuFamily.apple >= 1 && // On macOS, framebuffer fetch requires MSL 2.3, which is only available with macOS 11.0.
mContext->device.readWriteTextureSupport; if (@available(macOS 11.0, *)) {
return mContext->highestSupportedGpuFamily.apple >= 1 &&
mContext->device.readWriteTextureSupport;
}
return false;
} }
bool MetalDriver::isFrameBufferFetchMultiSampleSupported() { bool MetalDriver::isFrameBufferFetchMultiSampleSupported() {
@@ -1274,10 +1290,6 @@ void MetalDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&
scheduleDestroy(std::move(data)); scheduleDestroy(std::move(data));
} }
void MetalDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh, BufferObjectStreamDescriptor&& streams) {
// Noop
}
void MetalDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& data, void MetalDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& data,
uint32_t byteOffset) { uint32_t byteOffset) {
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext)) FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
@@ -2263,7 +2275,10 @@ MemoryMappedBufferHandle MetalDriver::mapBufferS() noexcept {
void MetalDriver::mapBufferR(MemoryMappedBufferHandle mmbh, void MetalDriver::mapBufferR(MemoryMappedBufferHandle mmbh,
BufferObjectHandle boh, size_t offset, BufferObjectHandle boh, size_t offset,
size_t size, MapBufferAccessFlags access, utils::ImmutableCString&& tag) { size_t size, MapBufferAccessFlags access, utils::ImmutableCString&& tag) {
construct_handle<MetalMemoryMappedBuffer>(mmbh, boh, offset, size, access); assert_invariant(boh);
MetalBufferObject* bo = mHandleAllocator.handle_cast<MetalBufferObject*>(boh);
assert_invariant(bo);
construct_handle<MetalMemoryMappedBuffer>(mmbh, bo, offset, size, access);
mHandleAllocator.associateTagToHandle(mmbh.getId(), std::move(tag)); mHandleAllocator.associateTagToHandle(mmbh.getId(), std::move(tag));
} }
@@ -2271,21 +2286,16 @@ void MetalDriver::unmapBuffer(MemoryMappedBufferHandle mmbh) {
if (UTILS_UNLIKELY(!mmbh)) { if (UTILS_UNLIKELY(!mmbh)) {
return; return;
} }
auto* mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
mmb->unmap();
destruct_handle<MetalMemoryMappedBuffer>(mmbh); destruct_handle<MetalMemoryMappedBuffer>(mmbh);
} }
void MetalDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t offset, void MetalDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t offset,
BufferDescriptor&& data) { BufferDescriptor&& data) {
auto mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh); auto* mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
mmb->copy(*this, offset, std::move(data));
assert_invariant(any(mmb->access & MapBufferAccessFlags::WRITE_BIT));
assert_invariant(offset + data.size <= mmb->size);
// TODO: this isa zero-effort implementation of copyToMemoryMappedBuffer(), where we just
// call updateBufferObject(). This could be a fallback implementation for when
// shared memory is not available.
// On UMA systems, this should just be a memcpy into the memory-mapped buffer.
updateBufferObject(mmb->boh, std::move(data), mmb->offset + offset);
} }
// explicit instantiation of the Dispatcher // explicit instantiation of the Dispatcher
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2025 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_DRIVER_METALERRORQUEUE_H
#define TNT_FILAMENT_DRIVER_METALERRORQUEUE_H
#import <Foundation/Foundation.h>
#include <utils/compiler.h>
#include <atomic>
#include <functional>
#include <mutex>
#include <vector>
class MetalErrorQueue {
public:
bool isEmpty() const {
return !mHasErrors.load(std::memory_order_relaxed);
}
void push(NSError* error) {
std::lock_guard<std::mutex> lock(mMutex);
mErrors.push_back(error);
mHasErrors.store(true, std::memory_order_relaxed);
}
void flush(const std::function<void(NSError*)>& callback) {
if (UTILS_LIKELY(isEmpty())) {
return;
}
std::vector<NSError*> errors;
{
std::lock_guard<std::mutex> lock(mMutex);
std::swap(mErrors, errors);
mHasErrors.store(false, std::memory_order_relaxed);
}
for (const auto& error: errors) {
callback(error);
}
}
private:
std::vector<NSError*> mErrors;
std::mutex mMutex;
// Optimization to avoid locking the mutex at each call to flush.
std::atomic<bool> mHasErrors;
};
#endif // TNT_FILAMENT_DRIVER_METALERRORQUEUE_H
+15 -7
View File
@@ -569,14 +569,22 @@ struct MetalDescriptorSet : public HwDescriptorSet {
struct MetalMemoryMappedBuffer : public HwMemoryMappedBuffer { struct MetalMemoryMappedBuffer : public HwMemoryMappedBuffer {
MetalMemoryMappedBuffer(BufferObjectHandle boh, size_t const offset,
size_t const size, MapBufferAccessFlags const access)
: boh(boh), access(access), size(size), offset(offset) {
}
BufferObjectHandle boh{};
MapBufferAccessFlags access{}; MapBufferAccessFlags access{};
uint32_t size = 0; struct {
uint32_t offset = 0; MetalBufferObject* bo;
void* vaddr = nullptr;
uint32_t size = 0;
uint32_t offset = 0;
} mtl;
MetalMemoryMappedBuffer(MetalBufferObject* bo, size_t offset, size_t size,
MapBufferAccessFlags access) noexcept;
~MetalMemoryMappedBuffer();
void unmap();
void copy(MetalDriver& mtld, size_t offset, BufferDescriptor&& data) const;
}; };
} // namespace backend } // namespace backend
@@ -1664,5 +1664,44 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
return buffer.get(); return buffer.get();
} }
MetalMemoryMappedBuffer::MetalMemoryMappedBuffer(MetalBufferObject* bo, size_t offset, size_t size,
MapBufferAccessFlags access) noexcept : access(access) {
MetalBuffer* buffer = bo->getBuffer();
assert_invariant(buffer);
id<MTLBuffer> mtlBuffer = buffer->getGpuBufferForDraw();
assert_invariant(offset + size <= bo->byteCount);
assert_invariant(mtlBuffer.storageMode != MTLStorageModePrivate);
mtl.bo = bo;
mtl.vaddr = static_cast<char*>(mtlBuffer.contents) + offset;
mtl.size = size;
mtl.offset = offset;
}
MetalMemoryMappedBuffer::~MetalMemoryMappedBuffer() = default;
void MetalMemoryMappedBuffer::unmap() {
#if !defined(FILAMENT_IOS) && defined(__x86_64__)
// Managed memory requires didModifyRange to synchronize changes to the GPU. This is specific to Intel Macs.
MetalBuffer* buffer = mtl.bo->getBuffer();
id<MTLBuffer> mtlBuffer = buffer->getGpuBufferForDraw();
if (mtlBuffer && mtlBuffer.storageMode == MTLStorageModeManaged) {
[mtlBuffer didModifyRange:NSMakeRange(mtl.offset, mtl.size)];
}
#endif
// Shared memory on UMA systems is coherent; no explicit synchronization is required.
}
void MetalMemoryMappedBuffer::copy(MetalDriver& mtld, size_t offset, BufferDescriptor&& data) const {
assert_invariant(any(access & MapBufferAccessFlags::WRITE_BIT));
assert_invariant(offset + data.size <= mtl.size);
assert_invariant(mtl.vaddr);
memcpy(static_cast<char*>(mtl.vaddr) + offset, data.buffer, data.size);
mtld.scheduleDestroy(std::move(data));
}
} // namespace backend } // namespace backend
} // namespace filament } // namespace filament
@@ -111,9 +111,22 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
id<MTLLibrary> library = nil; id<MTLLibrary> library = nil;
switch (program.getShaderLanguage()) { switch (program.getShaderLanguage()) {
case ShaderLanguage::MSL: { case ShaderLanguage::MSL: {
// By default, Metal uses the most recent language version.
MTLCompileOptions* options = [MTLCompileOptions new]; MTLCompileOptions* options = [MTLCompileOptions new];
options.fastMathEnabled = YES; options.fastMathEnabled = YES;
// The docs for MTLCompileOptions say that Metal uses the most
// recent language version if it is not specified, but this is
// not always true: it is possible for the environment to be set
// up in a way where an older version is used by default. So we
// explicitly set the minimum version we require.
#if defined(FILAMENT_IOS)
options.languageVersion = MTLLanguageVersion2_0;
#else
if (@available(macOS 11.0, *)) {
options.languageVersion = MTLLanguageVersion2_3;
} else {
options.languageVersion = MTLLanguageVersion2_2;
}
#endif
assert_invariant(source[source.size() - 1] == '\0'); assert_invariant(source[source.size() - 1] == '\0');
// the shader string is null terminated and the length includes the null character // the shader string is null terminated and the length includes the null character
-2
View File
@@ -291,8 +291,6 @@ void NoopDriver::updateBufferObject(Handle<HwBufferObject> ibh, BufferDescriptor
scheduleDestroy(std::move(p)); scheduleDestroy(std::move(p));
} }
void NoopDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh, BufferObjectStreamDescriptor&& streams) { }
void NoopDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> ibh, BufferDescriptor&& p, void NoopDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> ibh, BufferDescriptor&& p,
uint32_t byteOffset) { uint32_t byteOffset) {
scheduleDestroy(std::move(p)); scheduleDestroy(std::move(p));
+1 -1
View File
@@ -735,7 +735,7 @@ void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept {
// - the nameVersion is out of date *and* we're on the protected context, in this case: // - the nameVersion is out of date *and* we're on the protected context, in this case:
// - the name must be stale from a previous use of this context because we always // - the name must be stale from a previous use of this context because we always
// destroy the protected context when we're done with it. // destroy the protected context when we're done with it.
bool const recreateVaoName = p != &mDefaultVAO && bool const recreateVaoName = vao != &mDefaultVAO &&
((vao->vao[contextIndex] == 0) || ((vao->vao[contextIndex] == 0) ||
(vao->nameVersion != state.age && contextIndex == 1)); (vao->nameVersion != state.age && contextIndex == 1));
if (UTILS_UNLIKELY(recreateVaoName)) { if (UTILS_UNLIKELY(recreateVaoName)) {
@@ -39,7 +39,6 @@
#include <backend/Platform.h> #include <backend/Platform.h>
#include <backend/Program.h> #include <backend/Program.h>
#include <backend/TargetBufferInfo.h> #include <backend/TargetBufferInfo.h>
#include <backend/BufferObjectStreamDescriptor.h>
#include "private/backend/CommandStream.h" #include "private/backend/CommandStream.h"
#include "private/backend/Dispatcher.h" #include "private/backend/Dispatcher.h"
@@ -2837,38 +2836,6 @@ void OpenGLDriver::updateIndexBuffer(
CHECK_GL_ERROR() CHECK_GL_ERROR()
} }
void OpenGLDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh, BufferObjectStreamDescriptor&& streams) {
DEBUG_MARKER()
GLBufferObject const* bo = handle_cast<GLBufferObject*>(boh);
mStreamUniformDescriptors[bo->gl.id] = std::move(streams);
}
// specialization for mat3f (which has a different alignment, see std140 layout rules)
static void copyMat3f(void* addr, size_t const offset, const mat3f& v) noexcept {
struct mat43 {
float v[3][4];
};
addr = static_cast<char*>(addr) + offset;
mat43& temp = *static_cast<mat43*>(addr);
temp.v[0][0] = v[0][0];
temp.v[0][1] = v[0][1];
temp.v[0][2] = v[0][2];
temp.v[1][0] = v[1][0];
temp.v[1][1] = v[1][1];
temp.v[1][2] = v[1][2];
temp.v[2][0] = v[2][0];
temp.v[2][1] = v[2][1];
temp.v[2][2] = v[2][2];
// don't store anything in temp.v[][3] because there could be uniforms packed there
}
void OpenGLDriver::updateBufferObject( void OpenGLDriver::updateBufferObject(
Handle<HwBufferObject> boh, BufferDescriptor&& bd, uint32_t const byteOffset) { Handle<HwBufferObject> boh, BufferDescriptor&& bd, uint32_t const byteOffset) {
DEBUG_MARKER() DEBUG_MARKER()
@@ -2882,19 +2849,6 @@ void OpenGLDriver::updateBufferObject(
gl.bindVertexArray(nullptr); gl.bindVertexArray(nullptr);
} }
if (UTILS_UNLIKELY(!mStreamUniformDescriptors.empty())) {
auto const streamDescriptors = mStreamUniformDescriptors.find(bo->gl.id);
if (streamDescriptors != mStreamUniformDescriptors.end()) {
for (auto const& [offset, stream, associationType] : streamDescriptors->second.mStreams) {
if (associationType == BufferObjectStreamAssociationType::TRANSFORM_MATRIX) {
auto transform = getStreamTransformMatrix(stream);
copyMat3f(bd.buffer, offset, transform);
}
}
mStreamUniformDescriptors.erase(streamDescriptors);
}
}
if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) { if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
assert_invariant(bo->gl.buffer); assert_invariant(bo->gl.buffer);
memcpy(static_cast<uint8_t*>(bo->gl.buffer) + byteOffset, bd.buffer, bd.size); memcpy(static_cast<uint8_t*>(bo->gl.buffer) + byteOffset, bd.buffer, bd.size);
@@ -36,7 +36,6 @@
#include <backend/Platform.h> #include <backend/Platform.h>
#include <backend/Program.h> #include <backend/Program.h>
#include <backend/TargetBufferInfo.h> #include <backend/TargetBufferInfo.h>
#include <backend/BufferObjectStreamDescriptor.h>
#include "private/backend/Driver.h" #include "private/backend/Driver.h"
#include "private/backend/HandleAllocator.h" #include "private/backend/HandleAllocator.h"
@@ -394,8 +393,6 @@ private:
// the must be accessed from the user thread only // the must be accessed from the user thread only
std::vector<GLStream*> mStreamsWithPendingAcquiredImage; std::vector<GLStream*> mStreamsWithPendingAcquiredImage;
std::unordered_map<GLuint, BufferObjectStreamDescriptor> mStreamUniformDescriptors;
void attachStream(GLTexture* t, GLStream* stream); void attachStream(GLTexture* t, GLStream* stream);
void detachStream(GLTexture* t) noexcept; void detachStream(GLTexture* t) noexcept;
void replaceStream(GLTexture* t, GLStream* stream) noexcept; void replaceStream(GLTexture* t, GLStream* stream) noexcept;
+1 -1
View File
@@ -37,7 +37,7 @@
*/ */
#if defined(__ANDROID__) || defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(__EMSCRIPTEN__) #if defined(__ANDROID__) || defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(__EMSCRIPTEN__) || defined(FILAMENT_SUPPORTS_EGL_ON_LINUX)
#if defined(__EMSCRIPTEN__) #if defined(__EMSCRIPTEN__)
# include <GLES3/gl3.h> # include <GLES3/gl3.h>
@@ -28,14 +28,13 @@
#include <private/backend/VirtualMachineEnv.h> #include <private/backend/VirtualMachineEnv.h>
#include "AndroidNativeWindow.h" #include "AndroidNativeWindow.h"
#include "AndroidFrameCallback.h"
#include "AndroidSwapChainHelper.h" #include "AndroidSwapChainHelper.h"
#include "ExternalStreamManagerAndroid.h" #include "ExternalStreamManagerAndroid.h"
#include <android/api-level.h> #include <android/api-level.h>
#include <android/native_window.h> #include <android/native_window.h>
#include <android/hardware_buffer.h> #include <android/hardware_buffer.h>
#include <android/choreographer.h>
#include <android/looper.h>
#include <utils/android/PerformanceHintManager.h> #include <utils/android/PerformanceHintManager.h>
#include <utils/compiler.h> #include <utils/compiler.h>
@@ -99,6 +98,27 @@ using namespace glext;
// --------------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------------
struct PlatformEGLAndroid::SwapChainEGLAndroid : public SwapChainEGL {
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
void* nativeWindow, uint64_t flags);
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
uint32_t width, uint32_t height, uint64_t flags);
void terminate(PlatformEGLAndroid& platform);
bool setPresentFrameId(uint64_t frameId) const noexcept;
uint64_t getFrameId(uint64_t frameId) const noexcept;
bool compositorTimingSupported = false;
bool frameTimestampsSupported = false;
private:
AndroidSwapChainHelper mImpl{};
};
struct PlatformEGLAndroid::AndroidDetails {
AndroidProducerThrottling producerThrottling;
AndroidFrameCallback androidFrameCallback;
};
// ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() { PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() {
// PerformanceHintManager() needs the calling thread to be a Java thread; so we need // PerformanceHintManager() needs the calling thread to be a Java thread; so we need
// to attach this thread to the JVM before we initialize PerformanceHintManager. // to attach this thread to the JVM before we initialize PerformanceHintManager.
@@ -112,17 +132,20 @@ PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmFor
// --------------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::PlatformEGLAndroid() noexcept PlatformEGLAndroid::PlatformEGLAndroid() noexcept
: mExternalStreamManager(ExternalStreamManagerAndroid::create()) { : mExternalStreamManager(ExternalStreamManagerAndroid::create()),
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
mOSVersion = android_get_device_api_level(); mOSVersion = android_get_device_api_level();
if (mOSVersion < 0) { if (mOSVersion < 0) {
mOSVersion = __ANDROID_API_FUTURE__; mOSVersion = __ANDROID_API_FUTURE__;
} }
} }
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept = default; PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
delete &mAndroidDetails;
}
void PlatformEGLAndroid::terminate() noexcept { void PlatformEGLAndroid::terminate() noexcept {
mAndroidFrameCallback.terminate(); mAndroidDetails.androidFrameCallback.terminate();
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager); ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
PlatformEGL::terminate(); PlatformEGL::terminate();
} }
@@ -155,20 +178,30 @@ void PlatformEGLAndroid::beginFrame(
int64_t const monotonic_clock_ns, int64_t const monotonic_clock_ns,
int64_t refreshIntervalNs, int64_t refreshIntervalNs,
uint32_t const frameId) noexcept { uint32_t const frameId) noexcept {
// associate the user frameid with the system frame id // if frameId is 0, it means we're not associated to a particular frame, which is the case
setPresentFrameId(mCurrentDrawSwapChain, frameId); // for standalone views. And in this case we skip the performance hint (since we wouldn't get
// the right timing anyway as well as the frame info timing collections)
if (mPerformanceHintSession.isValid()) { // associate the user frameid with the system frame id.
if (frameId && mCurrentDrawSwapChain) {
// mCurrentDrawSwapChain could be null if we're called from renderStandaloneView
setPresentFrameId(mCurrentDrawSwapChain, frameId);
}
if (frameId && mPerformanceHintSession.isValid()) {
if (refreshIntervalNs <= 0) { if (refreshIntervalNs <= 0) {
// we're not provided with a target time, use the display period, if everything fails, // we're not provided with a target time, use the display period, if everything fails,
// assume 16.67ms // assume 16.67ms
refreshIntervalNs = 16'666'667; refreshIntervalNs = 16'666'667;
CompositorTiming compositorTiming{}; if (mCurrentDrawSwapChain) {
bool const hasCompositorTiming = // mCurrentDrawSwapChain could be null if we're called from renderStandaloneView
queryCompositorTiming(mCurrentDrawSwapChain, &compositorTiming); CompositorTiming compositorTiming{};
if (hasCompositorTiming && compositorTiming.compositeInterval > 0) { bool const hasCompositorTiming =
refreshIntervalNs = compositorTiming.compositeInterval; queryCompositorTiming(mCurrentDrawSwapChain, &compositorTiming);
if (hasCompositorTiming && compositorTiming.compositeInterval > 0) {
refreshIntervalNs = compositorTiming.compositeInterval;
}
} }
} }
mStartTimeOfActualWork = clock::time_point(std::chrono::nanoseconds(monotonic_clock_ns)); mStartTimeOfActualWork = clock::time_point(std::chrono::nanoseconds(monotonic_clock_ns));
@@ -207,9 +240,9 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
"eglGetNativeClientBufferANDROID")); "eglGetNativeClientBufferANDROID"));
if (ext.egl.ANDROID_presentation_time) { if (ext.egl.ANDROID_presentation_time) {
eglGetNativeClientBufferANDROID = eglPresentationTimeANDROID =
PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC(eglGetProcAddress( PFNEGLPRESENTATIONTIMEANDROIDPROC(eglGetProcAddress(
"eglGetNativeClientBufferANDROID")); "eglPresentationTimeANDROID"));
} }
if (ext.egl.ANDROID_get_frame_timestamps) { if (ext.egl.ANDROID_get_frame_timestamps) {
@@ -240,7 +273,7 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid; mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
mAndroidFrameCallback.init(); mAndroidDetails.androidFrameCallback.init();
return driver; return driver;
} }
@@ -264,15 +297,25 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
} }
AndroidFrameCallback::Timeline const preferredTimeline{ AndroidFrameCallback::Timeline const preferredTimeline{
mAndroidFrameCallback.getPreferredTimeline() }; mAndroidDetails.androidFrameCallback.getPreferredTimeline() };
outCompositorTiming->frameTime = preferredTimeline.frameTime; outCompositorTiming->frameTime = preferredTimeline.frameTime;
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime; outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline; outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
outCompositorTiming->compositeDeadline = CompositorTiming::INVALID;
outCompositorTiming->compositeInterval = CompositorTiming::INVALID;
outCompositorTiming->compositeToPresentLatency = CompositorTiming::INVALID;
// From this point on, we always return "success" because some timings were returned.
if (!static_cast<SwapChainEGLAndroid const *>(swapchain)->compositorTimingSupported) {
// if this surface doesn't support it, don't attempt to query the values.
return true;
}
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) { if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
EGLSurface const sur = static_cast<SwapChainEGL const *>(swapchain)->sur; EGLSurface const sur = static_cast<SwapChainEGL const *>(swapchain)->sur;
if (sur == EGL_NO_SURFACE) { if (sur == EGL_NO_SURFACE) {
return false; return true;
} }
std::array<EGLnsecsANDROID, 3> values; std::array<EGLnsecsANDROID, 3> values;
@@ -283,26 +326,16 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
}; };
EGLBoolean const success = eglGetCompositorTimingANDROID(getEglDisplay(), sur, EGLBoolean const success = eglGetCompositorTimingANDROID(getEglDisplay(), sur,
names.size(), names.data(), values.data()); names.size(), names.data(), values.data());
if (!success) { if (UTILS_UNLIKELY(!success)) {
return false; // reset current error to EGL_SUCCESS
eglGetError();
} else {
outCompositorTiming->compositeDeadline = values[0];
outCompositorTiming->compositeInterval = values[1];
outCompositorTiming->compositeToPresentLatency = values[2];
} }
outCompositorTiming->compositeDeadline = values[0];
outCompositorTiming->compositeInterval = values[1];
outCompositorTiming->compositeToPresentLatency = values[2];
return true;
} }
return true;
// fallback to private APIs
auto const anw = static_cast<SwapChainEGL const *>(swapchain)->nativeWindow;
int const status = NativeWindow::getCompositorTiming(anw,
&outCompositorTiming->compositeDeadline,
&outCompositorTiming->compositeInterval,
&outCompositorTiming->compositeToPresentLatency);
if (status == 0) {
return true;
}
return PlatformEGL::queryCompositorTiming(swapchain, outCompositorTiming);
} }
bool PlatformEGLAndroid::setPresentFrameId(SwapChain const* swapchain, bool PlatformEGLAndroid::setPresentFrameId(SwapChain const* swapchain,
@@ -327,6 +360,10 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
return false; return false;
} }
if (!static_cast<SwapChainEGLAndroid const *>(swapchain)->frameTimestampsSupported) {
return false;
}
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) { if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
EGLSurface const sur = sc->sur; EGLSurface const sur = sc->sur;
if (sur == EGL_NO_SURFACE) { if (sur == EGL_NO_SURFACE) {
@@ -347,7 +384,9 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
}; };
EGLBoolean const success = eglGetFrameTimestampsANDROID(getEglDisplay(), sur, hwFrameId, EGLBoolean const success = eglGetFrameTimestampsANDROID(getEglDisplay(), sur, hwFrameId,
names.size(), names.data(), values.data()); names.size(), names.data(), values.data());
if (!success) { if (UTILS_UNLIKELY(!success)) {
// reset current error to EGL_SUCCESS
eglGetError();
return false; return false;
} }
outFrameTimestamps->requestedPresentTime = values[0]; outFrameTimestamps->requestedPresentTime = values[0];
@@ -361,28 +400,44 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
outFrameTimestamps->releaseTime = values[8]; outFrameTimestamps->releaseTime = values[8];
return true; return true;
} }
// fallback to private APIs
auto const anw = sc->nativeWindow;
int const status = NativeWindow::getFrameTimestamps(anw, hwFrameId,
&outFrameTimestamps->requestedPresentTime,
&outFrameTimestamps->acquireTime,
&outFrameTimestamps->latchTime,
&outFrameTimestamps->firstCompositionStartTime,
&outFrameTimestamps->lastCompositionStartTime,
&outFrameTimestamps->gpuCompositionDoneTime,
&outFrameTimestamps->displayPresentTime,
&outFrameTimestamps->dequeueReadyTime,
&outFrameTimestamps->releaseTime);
if (status == 0) {
return true;
}
return PlatformEGL::queryFrameTimestamps(swapchain, frameId, outFrameTimestamps); return PlatformEGL::queryFrameTimestamps(swapchain, frameId, outFrameTimestamps);
} }
Platform::SwapChain* PlatformEGLAndroid::createSwapChain(void* nativeWindow, uint64_t const flags) { Platform::SwapChain* PlatformEGLAndroid::createSwapChain(void* nativeWindow, uint64_t const flags) {
auto* const sc = new(std::nothrow) SwapChainEGLAndroid(*this, nativeWindow, flags); auto* const sc = new(std::nothrow) SwapChainEGLAndroid(*this, nativeWindow, flags);
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
EGLDisplay const dpy = getEglDisplay();
sc->compositorTimingSupported =
eglGetCompositorTimingSupportedANDROID(dpy, sc->sur,
EGL_COMPOSITE_DEADLINE_ANDROID) &&
eglGetCompositorTimingSupportedANDROID(dpy, sc->sur,
EGL_COMPOSITE_INTERVAL_ANDROID) &&
eglGetCompositorTimingSupportedANDROID(dpy, sc->sur,
EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID);
sc->frameTimestampsSupported =
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_REQUESTED_PRESENT_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_RENDERING_COMPLETE_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_COMPOSITION_LATCH_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_FIRST_COMPOSITION_START_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_LAST_COMPOSITION_START_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_DISPLAY_PRESENT_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_DEQUEUE_READY_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_READS_DONE_TIME_ANDROID);
}
// This is expected to be a low frequency log, only turned on in debug builds
DLOG(INFO) << "anw: " << nativeWindow
<< ", compositorTimingSupported=" << sc->compositorTimingSupported
<< ", frameTimestampsSupported=" << sc->frameTimestampsSupported;
return sc; return sc;
} }
@@ -395,6 +450,10 @@ Platform::SwapChain* PlatformEGLAndroid::createSwapChain(
void PlatformEGLAndroid::destroySwapChain(SwapChain* swapChain) noexcept { void PlatformEGLAndroid::destroySwapChain(SwapChain* swapChain) noexcept {
if (swapChain) { if (swapChain) {
SwapChainEGLAndroid* const sc = static_cast<SwapChainEGLAndroid*>(swapChain); SwapChainEGLAndroid* const sc = static_cast<SwapChainEGLAndroid*>(swapChain);
if (mCurrentDrawSwapChain == sc) {
// don't keep a dangling pointer around
mCurrentDrawSwapChain = nullptr;
}
sc->terminate(*this); sc->terminate(*this);
delete sc; delete sc;
} }
@@ -675,12 +734,12 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage const sou
bool PlatformEGLAndroid::isProducerThrottlingControlSupported() const { bool PlatformEGLAndroid::isProducerThrottlingControlSupported() const {
return mProducerThrottling.isSupported(); return mAndroidDetails.producerThrottling.isSupported();
} }
int32_t PlatformEGLAndroid::setProducerThrottlingEnabled( int32_t PlatformEGLAndroid::setProducerThrottlingEnabled(
EGLNativeWindowType const nativeWindow, bool const enabled) const { EGLNativeWindowType const nativeWindow, bool const enabled) const {
return mProducerThrottling.setProducerThrottlingEnabled(nativeWindow, enabled); return mAndroidDetails.producerThrottling.setProducerThrottlingEnabled(nativeWindow, enabled);
} }
// --------------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------------
@@ -704,8 +763,6 @@ PlatformEGLAndroid::SwapChainEGLAndroid::SwapChainEGLAndroid(PlatformEGLAndroid
// we ignore the result, it doesn't matter much if it fails // we ignore the result, it doesn't matter much if it fails
eglSurfaceAttrib(platform.getEglDisplay(), sur, EGL_TIMESTAMPS_ANDROID, EGL_TRUE); eglSurfaceAttrib(platform.getEglDisplay(), sur, EGL_TIMESTAMPS_ANDROID, EGL_TRUE);
} }
} else {
NativeWindow::enableFrameTimestamps(EGLNativeWindowType(nativeWindow), true);
} }
} }
@@ -16,7 +16,7 @@
#include <backend/platforms/PlatformEGLHeadless.h> #include <backend/platforms/PlatformEGLHeadless.h>
#include "opengl/GLUtils.h" #include <bluegl/BlueGL.h>
#include <EGL/egl.h> #include <EGL/egl.h>
#include <EGL/eglext.h> #include <EGL/eglext.h>
@@ -35,16 +35,31 @@ PlatformEGLHeadless::PlatformEGLHeadless() noexcept
} }
bool PlatformEGLHeadless::isOpenGL() const noexcept { bool PlatformEGLHeadless::isOpenGL() const noexcept {
return true; #if defined(BACKEND_OPENGL_VERSION_GL)
return true;
#else
return false;
#endif // defined(BACKEND_OPENGL_VERSION_GL)
} }
backend::Driver* PlatformEGLHeadless::createDriver(void* sharedContext, backend::Driver* PlatformEGLHeadless::createDriver(void* sharedContext,
const Platform::DriverConfig& driverConfig) { const Platform::DriverConfig& driverConfig) {
EGLBoolean bindAPI = eglBindAPI(EGL_OPENGL_API); auto bindApiHelper = [](EGLenum api, const char* errorString) -> bool {
if (UTILS_UNLIKELY(!bindAPI)) { EGLBoolean bindAPI = eglBindAPI(api);
LOG(ERROR) << "eglBindAPI EGL_OPENGL_API failed"; if (UTILS_UNLIKELY(bindAPI == EGL_FALSE || bindAPI == EGL_BAD_PARAMETER)) {
logEglError(errorString);
eglReleaseThread();
return false;
};
return true;
};
EGLenum api = isOpenGL() ? EGL_OPENGL_API : EGL_OPENGL_ES_API;
const char* apiString = isOpenGL() ? "eglBindAPI EGL_OPENGL_API" : "eglBindAPI EGL_OPENGL_ES_API";
if (!bindApiHelper(api, apiString)) {
return nullptr; return nullptr;
} }
int bindBlueGL = bluegl::bind(); int bindBlueGL = bluegl::bind();
if (UTILS_UNLIKELY(bindBlueGL != 0)) { if (UTILS_UNLIKELY(bindBlueGL != 0)) {
LOG(ERROR) << "bluegl bind failed"; LOG(ERROR) << "bluegl bind failed";
-5
View File
@@ -15,7 +15,6 @@
*/ */
#include <backend/BufferDescriptor.h> #include <backend/BufferDescriptor.h>
#include <backend/BufferObjectStreamDescriptor.h>
#include <backend/DescriptorSetOffsetArray.h> #include <backend/DescriptorSetOffsetArray.h>
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <backend/PipelineState.h> #include <backend/PipelineState.h>
@@ -434,10 +433,6 @@ io::ostream& operator<<(io::ostream& out, BufferDescriptor const& b) {
<< ", user=" << b.getUser() << " }"; << ", user=" << b.getUser() << " }";
} }
io::ostream& operator<<(io::ostream& out, const BufferObjectStreamDescriptor& b) {
return out << "BufferObjectStreamDescriptor{ streams(" << b.mStreams.size() << ")=... }";
}
io::ostream& operator<<(io::ostream& out, PixelBufferDescriptor const& b) { io::ostream& operator<<(io::ostream& out, PixelBufferDescriptor const& b) {
BufferDescriptor const& base = static_cast<BufferDescriptor const&>(b); BufferDescriptor const& base = static_cast<BufferDescriptor const&>(b);
return out << "PixelBufferDescriptor{ " << base return out << "PixelBufferDescriptor{ " << base
@@ -62,8 +62,9 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c
// a memcpy if its available. // a memcpy if its available.
bool const isStaticOrShared = bool const isStaticOrShared =
any(mUsage & (BufferUsage::STATIC | BufferUsage::SHARED_WRITE_BIT)); any(mUsage & (BufferUsage::STATIC | BufferUsage::SHARED_WRITE_BIT));
bool const useMemcpy = (isUniformAndAvailable || isStaticOrShared) && isMemcopyable && bool const useMemcpy =
mStagingBufferBypassEnabled; ((isUniformAndAvailable && mStagingBufferBypassEnabled) || isStaticOrShared) &&
isMemcopyable;
if (useMemcpy) { if (useMemcpy) {
char* dest = static_cast<char*>(mBuffer->getGpuBuffer()->allocationInfo.pMappedData) + char* dest = static_cast<char*>(mBuffer->getGpuBuffer()->allocationInfo.pMappedData) +
byteOffset; byteOffset;
@@ -19,6 +19,7 @@
#include "VulkanCommands.h" #include "VulkanCommands.h"
#include "VulkanHandles.h" #include "VulkanHandles.h"
#include "VulkanConstants.h" #include "VulkanConstants.h"
#include "utils/compiler.h"
#include <utils/FixedCapacityVector.h> #include <utils/FixedCapacityVector.h>
#include <utils/Panic.h> #include <utils/Panic.h>
@@ -31,6 +32,18 @@ namespace filament::backend {
namespace { namespace {
using Bitmask = fvkutils::UniformBufferBitmask;
static_assert(sizeof(Bitmask) * 8 == fvkutils::MAX_DESCRIPTOR_SET_BITMASK_BITS);
Bitmask foldBitsInHalf(Bitmask bitset) {
Bitmask outBitset;
bitset.forEachSetBit([&](size_t index) {
constexpr size_t BITMASK_LOWER_BITS_LEN = sizeof(outBitset) * 4;
outBitset.set(index % BITMASK_LOWER_BITS_LEN);
});
return outBitset;
}
using DescriptorSetLayoutArray = VulkanDescriptorSetCache::DescriptorSetLayoutArray; using DescriptorSetLayoutArray = VulkanDescriptorSetCache::DescriptorSetLayoutArray;
using DescriptorCount = VulkanDescriptorSetCache::DescriptorCount; using DescriptorCount = VulkanDescriptorSetCache::DescriptorCount;
@@ -304,8 +317,7 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
// This code actually binds the descriptor sets. // This code actually binds the descriptor sets.
auto set = updateSets[index]; auto set = updateSets[index];
VkCommandBuffer const cmdbuffer = commands->buffer(); VkCommandBuffer const cmdbuffer = commands->buffer();
VkDescriptorSet vkset = useExternalSamplers[index] ? set->getExternalSamplerVkSet() : VkDescriptorSet const vkset = set->getVkSet();
set->getVkSet();
commands->acquire(set); commands->acquire(set);
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index, vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
1, &vkset, set->uniqueDynamicUboCount, set->getOffsets()->data()); 1, &vkset, set->uniqueDynamicUboCount, set->getOffsets()->data());
@@ -341,17 +353,39 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
.pBufferInfo = &info, .pBufferInfo = &info,
}; };
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr); vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
if (auto externalSamplerSet = set->getExternalSamplerVkSet();
externalSamplerSet != VK_NULL_HANDLE) {
descriptorWrite.dstSet = externalSamplerSet;
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
}
set->acquire(bufferObject); set->acquire(bufferObject);
} }
void VulkanDescriptorSetCache::updateSamplerImpl(VkDescriptorSet vkset, uint8_t binding, void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept { uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
VkSampler sampler, VkDescriptorSetLayout externalSamplerLayout) noexcept {
// We have to update a bound set for two use cases
// - streaming API (a changing feed of AHardwareBuffer)
// - external samplers - potential changing of dataspace per-frame
if (UTILS_UNLIKELY(set->isBound())) {
auto layout = set->getLayout();
// Build a new descriptor set from the new layout
auto genLayout = externalSamplerLayout != VK_NULL_HANDLE ?
externalSamplerLayout : layout->getVkLayout();
VkDescriptorSet const newSet = getVkSet(layout->count, genLayout);
Bitmask const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
Bitmask samplers = layout->bitmask.sampler;
samplers.unset(binding);
// Each bitmask denotes a binding index, and separated into two stages - vertex and buffer
// We fold the two stages into just the lower half of the bits to denote a combined set of
// bindings.
Bitmask const copyBindings = foldBitsInHalf(ubo | samplers);
VkDescriptorSet const srcSet = set->getVkSet();
copySet(srcSet, newSet, copyBindings);
set->addNewSet(newSet,
[this, layoutCount = layout->count, genLayout, newSet](VulkanDescriptorSet*) {
this->manualRecycle(layoutCount, genLayout, newSet);
});
}
VkDescriptorSet const vkset = set->getVkSet();
VkImageSubresourceRange range = texture->getPrimaryViewRange(); VkImageSubresourceRange range = texture->getPrimaryViewRange();
VkImageViewType const expectedType = texture->getViewType(); VkImageViewType const expectedType = texture->getViewType();
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) && if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
@@ -376,19 +410,6 @@ void VulkanDescriptorSetCache::updateSamplerImpl(VkDescriptorSet vkset, uint8_t
.pImageInfo = &info, .pImageInfo = &info,
}; };
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr); vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
}
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
VkSampler sampler) noexcept {
updateSamplerImpl(set->getVkSet(), binding, texture, sampler);
set->acquire(texture);
}
void VulkanDescriptorSetCache::updateSamplerForExternalSamplerSet(
fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
fvkmemory::resource_ptr<VulkanTexture> texture) noexcept {
updateSamplerImpl(set->getExternalSamplerVkSet(), binding, texture, VK_NULL_HANDLE);
set->acquire(texture); set->acquire(texture);
} }
@@ -405,7 +426,7 @@ fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet
auto const& count = layout->count; auto const& count = layout->count;
auto const vklayout = layout->getVkLayout(); auto const vklayout = layout->getVkLayout();
auto set = fvkmemory::resource_ptr<VulkanDescriptorSet>::make( auto set = fvkmemory::resource_ptr<VulkanDescriptorSet>::make(
mResourceManager, handle, layout->bitmask.dynamicUbo, layout->count.dynamicUbo, mResourceManager, handle, layout,
[vkSet, count, vklayout, this]( [vkSet, count, vklayout, this](
VulkanDescriptorSet*) { this->manualRecycle(count, vklayout, vkSet); }, VulkanDescriptorSet*) { this->manualRecycle(count, vklayout, vkSet); },
vkSet); vkSet);
@@ -428,4 +449,22 @@ void VulkanDescriptorSetCache::manualRecycle(VulkanDescriptorSetLayout::Count co
void VulkanDescriptorSetCache::gc() { mStashedSets = {}; } void VulkanDescriptorSetCache::gc() { mStashedSets = {}; }
void VulkanDescriptorSetCache::copySet(VkDescriptorSet srcSet, VkDescriptorSet dstSet,
fvkutils::SamplerBitmask bindings) const {
// TODO: fix the size for better memory management
std::vector<VkCopyDescriptorSet> copies;
bindings.forEachSetBit([&](size_t index) {
copies.push_back({
.sType = VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET,
.srcSet = srcSet,
.srcBinding = (uint32_t) index,
.dstSet = dstSet,
.dstBinding = (uint32_t) index,
.descriptorCount = 1,
});
});
vkUpdateDescriptorSets(mDevice, 0, nullptr, copies.size(), copies.data());
}
} // namespace filament::backend } // namespace filament::backend
@@ -55,7 +55,8 @@ public:
VkDeviceSize size) noexcept; VkDeviceSize size) noexcept;
void updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding, void updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept; fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler,
VkDescriptorSetLayout externalSamplerLayout = VK_NULL_HANDLE) noexcept;
void updateSamplerForExternalSamplerSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding, void updateSamplerForExternalSamplerSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
fvkmemory::resource_ptr<VulkanTexture> texture) noexcept; fvkmemory::resource_ptr<VulkanTexture> texture) noexcept;
@@ -90,8 +91,8 @@ public:
void resetCachedState() noexcept { mLastBoundInfo = {}; } void resetCachedState() noexcept { mLastBoundInfo = {}; }
private: private:
void updateSamplerImpl(VkDescriptorSet set, uint8_t binding, void copySet(VkDescriptorSet srcSet, VkDescriptorSet destSet,
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept; fvkutils::SamplerBitmask copyBindings) const;
class DescriptorInfinitePool; class DescriptorInfinitePool;
+109 -20
View File
@@ -31,6 +31,7 @@
#include "vulkan/memory/ResourcePointer.h" #include "vulkan/memory/ResourcePointer.h"
#include "vulkan/utils/Conversion.h" #include "vulkan/utils/Conversion.h"
#include "vulkan/utils/Definitions.h" #include "vulkan/utils/Definitions.h"
#include "vulkan/utils/Image.h"
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <backend/platforms/VulkanPlatform.h> #include <backend/platforms/VulkanPlatform.h>
@@ -244,6 +245,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
mQueryManager(mPlatform->getDevice()), mQueryManager(mPlatform->getDevice()),
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache, mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
&mDescriptorSetLayoutCache), &mDescriptorSetLayoutCache),
mStreamedImageManager(&mExternalImageManager),
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported), mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
mIsMSAASwapChainSupported(false), // TODO: support MSAA swapchain mIsMSAASwapChainSupported(false), // TODO: support MSAA swapchain
mStereoscopicType(driverConfig.stereoscopicType) { mStereoscopicType(driverConfig.stereoscopicType) {
@@ -420,12 +422,14 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) { int64_t refreshIntervalNs, uint32_t frameId) {
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME); FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
if (mCurrentSwapChain) { // This should be guaranteed // if frameId is 0, it means we're not associated to a particular frame, which is the case
// for standalone views. And in this case we skip the frame info timing collection.
if (frameId && mCurrentSwapChain) { // This should be guaranteed
mPlatform->setPresentFrameId(mCurrentSwapChain->swapChain, frameId); mPlatform->setPresentFrameId(mCurrentSwapChain->swapChain, frameId);
} }
// Check if any command have finished and reset all its used resources. The resources // Check if any command have finished and reset all its used resources. The resources
// wont be destroyed but their reference count will decreased if the command is already // won't be destroyed but their reference count will be decreased if the command is already
// completed. // completed.
// //
// This will let us check if any VulkanBuffer is currently in flight or not. // This will let us check if any VulkanBuffer is currently in flight or not.
@@ -479,6 +483,9 @@ void VulkanDriver::updateDescriptorSetTexture(
if (UTILS_UNLIKELY(mExternalImageManager.isExternallySampledTexture(texture))) { if (UTILS_UNLIKELY(mExternalImageManager.isExternallySampledTexture(texture))) {
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params); mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
mAppState.hasBoundExternalImages = true; mAppState.hasBoundExternalImages = true;
} else if (bool(texture->getStream())) {
mStreamedImageManager.bindStreamedTexture(set, binding, texture, params);
mAppState.hasBoundExternalImages = true;
} else { } else {
VulkanSamplerCache::Params cacheParams = { VulkanSamplerCache::Params cacheParams = {
.sampler = params, .sampler = params,
@@ -486,6 +493,7 @@ void VulkanDriver::updateDescriptorSetTexture(
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams); VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler); mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
mExternalImageManager.clearTextureBinding(set, binding); mExternalImageManager.clearTextureBinding(set, binding);
mStreamedImageManager.unbindStreamedTexture(set, binding);
} }
} }
@@ -497,10 +505,7 @@ void VulkanDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_
assert_invariant(any(mmb->access & MapBufferAccessFlags::WRITE_BIT)); assert_invariant(any(mmb->access & MapBufferAccessFlags::WRITE_BIT));
assert_invariant(offset + data.size <= mmb->size); assert_invariant(offset + data.size <= mmb->size);
// TODO: this isa zero-effort implementation of copyToMemoryMappedBuffer(), where we just // On UMA systems, this is a memcpy into the memory-mapped buffer, otherwise use a staging buffer.
// call updateBufferObject(). This could be a fallback implementation for when
// shared memory is not available.
// On UMA systems, this should just be a memcpy into the memory-mapped buffer.
updateBufferObject(mmb->boh, std::move(data), mmb->offset + offset); updateBufferObject(mmb->boh, std::move(data), mmb->offset + offset);
} }
@@ -921,14 +926,6 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
} }
} }
#if defined(__ANDROID__)
// on Android, disable producer throttling
if (mProducerThrottling.isSupported()) {
mProducerThrottling.setProducerThrottlingEnabled(
static_cast<ANativeWindow*>(nativeWindow), false);
}
#endif
auto swapChain = resource_ptr<VulkanSwapChain>::make(&mResourceManager, sch, mPlatform, auto swapChain = resource_ptr<VulkanSwapChain>::make(&mResourceManager, sch, mPlatform,
mContext, &mResourceManager, mAllocator, &mCommands, mStagePool, nativeWindow, flags); mContext, &mResourceManager, mAllocator, &mCommands, mStagePool, nativeWindow, flags);
swapChain.inc(); swapChain.inc();
@@ -1116,6 +1113,12 @@ void VulkanDriver::destroySwapChain(Handle<HwSwapChain> sch) {
} }
void VulkanDriver::destroyStream(Handle<HwStream> sh) { void VulkanDriver::destroyStream(Handle<HwStream> sh) {
FVK_SYSTRACE_SCOPE();
if (!sh) {
return;
}
auto stream = resource_ptr<VulkanStream>::cast(&mResourceManager, sh);
stream.dec();
} }
void VulkanDriver::destroySync(Handle<HwSync> sh) { void VulkanDriver::destroySync(Handle<HwSync> sh) {
@@ -1153,18 +1156,38 @@ void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
} }
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream, utils::ImmutableCString tag) { Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream, utils::ImmutableCString tag) {
FVK_LOGW << "createStreamNative not supported in Vulkan.";
return {}; return {};
} }
Handle<HwStream> VulkanDriver::createStreamAcquired(utils::ImmutableCString tag) { Handle<HwStream> VulkanDriver::createStreamAcquired(utils::ImmutableCString tag) {
return {}; // @TODO This is still not thread-safe. We might have to revisit this question.
FVK_SYSTRACE_SCOPE();
auto handle = mResourceManager.allocHandle<VulkanStream>();
auto stream = resource_ptr<VulkanStream>::make(&mResourceManager, handle);
stream.inc();
return handle;
} }
void VulkanDriver::setAcquiredImage(Handle<HwStream> sh, void* image, const math::mat3f& transform, void VulkanDriver::setAcquiredImage(Handle<HwStream> sh, void* image, const math::mat3f& transform,
CallbackHandler* handler, StreamCallback cb, void* userData) { CallbackHandler* handler, StreamCallback cb, void* userData) {
FVK_SYSTRACE_SCOPE();
auto stream = resource_ptr<VulkanStream>::cast(&mResourceManager, sh);
assert_invariant(stream->streamType == StreamType::ACQUIRED);
assert_invariant(image != nullptr);
// This covers the case where setAcquiredImage is called back to back (no updateStreams in between)
if (stream->previousNeedsRelease()) {
scheduleRelease(stream->takePrevious());
}
stream->acquire({ image, cb, userData, handler});
mStreamsWithPendingAcquiredImage.push_back(stream);
} }
void VulkanDriver::setStreamDimensions(Handle<HwStream> sh, uint32_t width, uint32_t height) { void VulkanDriver::setStreamDimensions(Handle<HwStream> sh, uint32_t width, uint32_t height) {
FVK_SYSTRACE_SCOPE();
auto stream = resource_ptr<VulkanStream>::cast(&mResourceManager, sh);
stream->width = width;
stream->height = height;
} }
int64_t VulkanDriver::getStreamTimestamp(Handle<HwStream> sh) { int64_t VulkanDriver::getStreamTimestamp(Handle<HwStream> sh) {
@@ -1172,6 +1195,70 @@ int64_t VulkanDriver::getStreamTimestamp(Handle<HwStream> sh) {
} }
void VulkanDriver::updateStreams(CommandStream* driver) { void VulkanDriver::updateStreams(CommandStream* driver) {
FVK_SYSTRACE_SCOPE();
if (UTILS_UNLIKELY(!mStreamsWithPendingAcquiredImage.empty())) {
for (auto& stream: mStreamsWithPendingAcquiredImage) {
if (stream->previousNeedsRelease()) {
scheduleRelease(stream->takePrevious());
}
// This executes on the backend thread (updateStreams is synchonous which means it
// executes on the user thread) Note: stream is captured by copy which is fine, this is
// a copy of a resource_ptr<VulkanStream>. We only need it find the associated stream
// inside the mStreamedImageManager texture bindings
driver->queueCommand([this, stream, s = stream.get(),
image = stream->getAcquired().image]() {
auto texture = s->getTexture(image);
if (!texture) {
auto externalImage = fvkutils::createExternalImageFromRaw(mPlatform, image, false);
auto metadata = mPlatform->extractExternalImageMetadata(externalImage);
auto imgData = mPlatform->createVkImageFromExternal(externalImage);
assert_invariant(imgData.internal.valid() || imgData.external.valid());
VkFormat vkformat = metadata.format;
VkImage vkimage = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
if (imgData.internal.valid()) {
metadata.externalFormat = 0;
vkimage = imgData.internal.image;
memory = imgData.internal.memory;
} else {
vkformat = VK_FORMAT_UNDEFINED;
vkimage = imgData.external.image;
memory = imgData.external.memory;
}
VkSamplerYcbcrConversion const conversion =
mExternalImageManager.getVkSamplerYcbcrConversion(metadata);
auto newTexture = resource_ptr<VulkanTexture>::construct(&mResourceManager,
mContext, mPlatform->getDevice(), mAllocator, &mResourceManager,
&mCommands, vkimage, memory, vkformat, conversion, metadata.samples,
metadata.width, metadata.height, metadata.layers,
metadata.filamentUsage, mStagePool);
auto& commands = mCommands.get();
// Unlike uploaded textures or swapchains, we need to explicit transition this
// texture into the read layout.
newTexture->transitionLayout(&commands, newTexture->getPrimaryViewRange(),
VulkanLayout::FRAG_READ);
if (imgData.external.valid()) {
mExternalImageManager.addExternallySampledTexture(newTexture,
externalImage);
// Cache the AHB backed image. Acquires the image here.
s->pushImage(image, newTexture);
}
texture = newTexture;
}
mStreamedImageManager.onStreamAcquireImage(texture, stream);
});
}
mStreamsWithPendingAcquiredImage.clear();
}
} }
void VulkanDriver::destroyFence(Handle<HwFence> fh) { void VulkanDriver::destroyFence(Handle<HwFence> fh) {
@@ -1467,17 +1554,13 @@ void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor
scheduleDestroy(std::move(p)); scheduleDestroy(std::move(p));
} }
void VulkanDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh,
BufferObjectStreamDescriptor&& streams) {
// Noop
}
void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& bd, void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& bd,
uint32_t byteOffset) { uint32_t byteOffset) {
VulkanCommandBuffer& commands = mCommands.get(); VulkanCommandBuffer& commands = mCommands.get();
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh); auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
commands.acquire(bo); commands.acquire(bo);
bo->loadFromCpu(commands, bd.buffer, byteOffset, bd.size); bo->loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
scheduleDestroy(std::move(bd)); scheduleDestroy(std::move(bd));
@@ -1545,6 +1628,12 @@ TimerQueryResult VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint
} }
void VulkanDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) { void VulkanDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
FVK_SYSTRACE_SCOPE();
auto t = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
assert_invariant(t);
auto s = resource_ptr<VulkanStream>::cast(&mResourceManager, sh);
assert_invariant(s);
t->setStream(s);
} }
void VulkanDriver::generateMipmaps(Handle<HwTexture> th) { void VulkanDriver::generateMipmaps(Handle<HwTexture> th) {
+8 -7
View File
@@ -34,12 +34,14 @@
#include "vulkan/VulkanDescriptorSetCache.h" #include "vulkan/VulkanDescriptorSetCache.h"
#include "vulkan/VulkanDescriptorSetLayoutCache.h" #include "vulkan/VulkanDescriptorSetLayoutCache.h"
#include "vulkan/VulkanExternalImageManager.h" #include "vulkan/VulkanExternalImageManager.h"
#include "vulkan/VulkanStreamedImageManager.h"
#include "vulkan/VulkanPipelineLayoutCache.h" #include "vulkan/VulkanPipelineLayoutCache.h"
#include "vulkan/memory/ResourceManager.h" #include "vulkan/memory/ResourceManager.h"
#include "vulkan/memory/ResourcePointer.h" #include "vulkan/memory/ResourcePointer.h"
#include "vulkan/utils/Definitions.h" #include "vulkan/utils/Definitions.h"
#include "backend/DriverEnums.h" #include "backend/DriverEnums.h"
#include "backend/BufferObjectStreamDescriptor.h"
#include "DriverBase.h" #include "DriverBase.h"
#include "private/backend/Driver.h" #include "private/backend/Driver.h"
@@ -48,10 +50,6 @@
#include <utils/Allocator.h> #include <utils/Allocator.h>
#include <utils/compiler.h> #include <utils/compiler.h>
#if defined(__ANDROID__)
#include "AndroidNativeWindow.h"
#endif
namespace filament::backend { namespace filament::backend {
class VulkanPlatform; class VulkanPlatform;
@@ -161,6 +159,8 @@ private:
VulkanDescriptorSetCache mDescriptorSetCache; VulkanDescriptorSetCache mDescriptorSetCache;
VulkanQueryManager mQueryManager; VulkanQueryManager mQueryManager;
VulkanExternalImageManager mExternalImageManager; VulkanExternalImageManager mExternalImageManager;
VulkanStreamedImageManager mStreamedImageManager;
// This maps a VulkanSwapchain to a native swapchain. VulkanSwapchain should have a copy of the // This maps a VulkanSwapchain to a native swapchain. VulkanSwapchain should have a copy of the
// Platform::Swapchain pointer, but queryFrameTimestamps() and queryCompositorTiming() are // Platform::Swapchain pointer, but queryFrameTimestamps() and queryCompositorTiming() are
@@ -206,9 +206,10 @@ private:
bool const mIsSRGBSwapChainSupported; bool const mIsSRGBSwapChainSupported;
bool const mIsMSAASwapChainSupported; bool const mIsMSAASwapChainSupported;
backend::StereoscopicType const mStereoscopicType; backend::StereoscopicType const mStereoscopicType;
#if defined(__ANDROID__)
AndroidProducerThrottling mProducerThrottling; // setAcquiredImage is a DECL_DRIVER_API_SYNCHRONOUS_N which means we don't necessarily have the
#endif // data to process it at call time. So we store it and process it during updateStreams.
std::vector<resource_ptr<VulkanStream>> mStreamsWithPendingAcquiredImage;
}; };
} // namespace filament::backend } // namespace filament::backend
@@ -31,9 +31,6 @@ namespace filament::backend {
namespace { namespace {
using Bitmask = fvkutils::UniformBufferBitmask;
static_assert(sizeof(Bitmask) * 8 == fvkutils::MAX_DESCRIPTOR_SET_BITMASK_BITS);
template<typename T> template<typename T>
void erasep(std::vector<T>& v, std::function<bool(T const&)> f) { void erasep(std::vector<T>& v, std::function<bool(T const&)> f) {
auto newEnd = std::remove_if(v.begin(), v.end(), f); auto newEnd = std::remove_if(v.begin(), v.end(), f);
@@ -50,31 +47,6 @@ ImageData& findImage(std::vector<ImageData>& images,
return *itr; return *itr;
} }
void copySet(VkDevice device, VkDescriptorSet srcSet, VkDescriptorSet dstSet, Bitmask bindings) {
// TODO: fix the size for better memory management
std::vector<VkCopyDescriptorSet> copies;
bindings.forEachSetBit([&](size_t index) {
copies.push_back({
.sType = VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET,
.srcSet = srcSet,
.srcBinding = (uint32_t) index,
.dstSet = dstSet,
.dstBinding = (uint32_t) index,
.descriptorCount = 1,
});
});
vkUpdateDescriptorSets(device, 0, nullptr, copies.size(), copies.data());
}
Bitmask foldBitsInHalf(Bitmask bitset) {
Bitmask outBitset;
bitset.forEachSetBit([&](size_t index) {
constexpr size_t BITMASK_LOWER_BITS_LEN = sizeof(outBitset) * 4;
outBitset.set(index % BITMASK_LOWER_BITS_LEN);
});
return outBitset;
}
}// namespace }// namespace
VulkanExternalImageManager::VulkanExternalImageManager(VulkanPlatform* platform, VulkanExternalImageManager::VulkanExternalImageManager(VulkanPlatform* platform,
@@ -141,6 +113,9 @@ void VulkanExternalImageManager::updateSetAndLayout(
continue; continue;
} }
auto& imageData = findImage(mImages, bindingInfo.image); auto& imageData = findImage(mImages, bindingInfo.image);
// For non YUV images (some ext images are NOT ext FMT)
// getVkSamplerYcbcrConversion(metadata) will return NULL, and image->conversion will be
// null
updateImage(&imageData); updateImage(&imageData);
auto samplerParams = bindingInfo.samplerParams; auto samplerParams = bindingInfo.samplerParams;
@@ -172,38 +147,14 @@ void VulkanExternalImageManager::updateSetAndLayout(
std::for_each(samplerAndBindings.begin(), samplerAndBindings.end(), std::for_each(samplerAndBindings.begin(), samplerAndBindings.end(),
[&](auto const& b) { outSamplers.push_back(std::get<1>(b)); }); [&](auto const& b) { outSamplers.push_back(std::get<1>(b)); });
VkDescriptorSetLayout const oldLayout = layout->getExternalSamplerVkLayout();
VkDescriptorSetLayout const newLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask, VkDescriptorSetLayout const newLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
actualExternalSamplers, outSamplers); actualExternalSamplers, outSamplers);
layout->setExternalSamplerVkLayout(newLayout);
// Need to copy the set
VkDescriptorSet const oldSet = set->getExternalSamplerVkSet();
if (oldLayout != newLayout || oldSet == VK_NULL_HANDLE) {
// Build a new descriptor set from the new layout
VkDescriptorSet const newSet = mDescriptorSetCache->getVkSet(layout->count, newLayout);
auto const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
auto const samplers = layout->bitmask.sampler & (~actualExternalSamplers);
// Each bitmask denotes a binding index, and separated into two stages - vertex and buffer
// We fold the two stages into just the lower half of the bits to denote a combined set of
// bindings.
Bitmask const copyBindings = foldBitsInHalf(ubo | samplers);
VkDescriptorSet const srcSet = oldSet != VK_NULL_HANDLE ? oldSet : set->getVkSet();
copySet(mPlatform->getDevice(), srcSet, newSet, copyBindings);
set->setExternalSamplerVkSet(newSet,
[&descriptorSetCache = mDescriptorSetCache, layoutCount = layout->count, newLayout,
newSet](VulkanDescriptorSet*) {
descriptorSetCache->manualRecycle(layoutCount, newLayout, newSet);
});
if (oldLayout != newLayout) {
layout->setExternalSamplerVkLayout(newLayout);
}
}
// Update the external samplers in the set // Update the external samplers in the set
for (auto& [binding, sampler, image]: samplerAndBindings) { for (auto& [binding, sampler, image]: samplerAndBindings) {
mDescriptorSetCache->updateSamplerForExternalSamplerSet(set, binding, image); // We cannot call updateSamplerForExternalSamplerSet because some samplers are non NULL
// (RGB) and we cannot do a combined update with a NULL sampler.
mDescriptorSetCache->updateSampler(set, binding, image, sampler, newLayout);
} }
} }
@@ -261,13 +212,25 @@ void VulkanExternalImageManager::bindExternallySampledTexture(
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams) { fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams) {
// Should we do duplicate validation here? // Should we do duplicate validation here?
auto& imageData = findImage(mImages, image); auto& imageData = findImage(mImages, image);
mSetBindings.push_back({ bindingPoint, imageData.image, set, samplerParams }); auto itr = std::find_if(mSetBindings.begin(), mSetBindings.end(),
[&](SetBindingInfo const& binding) {
return (binding.set == set && binding.binding == bindingPoint);
});
if (itr == mSetBindings.end()) {
mSetBindings.push_back({ bindingPoint, imageData.image, set, samplerParams });
} else {
// override the image data in the binding point
itr->image = image;
itr->samplerParams = samplerParams;
}
} }
void VulkanExternalImageManager::addExternallySampledTexture( void VulkanExternalImageManager::addExternallySampledTexture(
fvkmemory::resource_ptr<VulkanTexture> image, fvkmemory::resource_ptr<VulkanTexture> image,
Platform::ExternalImageHandleRef platformHandleRef) { Platform::ExternalImageHandleRef platformHandleRef) {
mImages.push_back({ image, platformHandleRef, false }); // By passing VK_NULL_HANDLE which is already there by default.
// We make it clear that all default images do NOT have a chroma conversion.
mImages.push_back({ image, platformHandleRef, false, VK_NULL_HANDLE });
} }
void VulkanExternalImageManager::removeExternallySampledTexture( void VulkanExternalImageManager::removeExternallySampledTexture(
@@ -31,7 +31,7 @@ class VulkanSamplerCache;
class VulkanDescriptorSetLayoutCache; class VulkanDescriptorSetLayoutCache;
class VulkanDescriptorSetCache; class VulkanDescriptorSetCache;
// Manages the logic of external images and their quirks wrt Vulikan. // Manages the logic of external images and their quirks wrt Vulkan.
class VulkanExternalImageManager { class VulkanExternalImageManager {
public: public:
+62 -2
View File
@@ -40,6 +40,11 @@ namespace filament::backend {
namespace { namespace {
// We don't have a good estimate for this magic number. This is used to remove empty
// slots from a descriptor set (a VulkanDescriptorset is backed by multiple
// VkDescriptorSet.
size_t DESCRIPTOR_SET_GC_LIMIT = 10;
inline VulkanBufferBinding getBufferObjectBinding(BufferObjectBinding bindingType) noexcept { inline VulkanBufferBinding getBufferObjectBinding(BufferObjectBinding bindingType) noexcept {
switch (bindingType) { switch (bindingType) {
case BufferObjectBinding::VERTEX: case BufferObjectBinding::VERTEX:
@@ -192,15 +197,70 @@ VulkanDescriptorSetLayout::Bitmask VulkanDescriptorSetLayout::Bitmask::fromLayou
return fromBackendLayout(layout); return fromBackendLayout(layout);
} }
// This method will store an age associated with this command buffer into the VulkanBuffer, which VulkanDescriptorSet::~VulkanDescriptorSet() {
// will allow us to determine whether a barrier is necessary or not. for (auto setBundle: mSets) {
if (setBundle.onRecycleFn) {
setBundle.onRecycleFn(this);
}
}
}
VulkanDescriptorSet::VulkanDescriptorSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout,
OnRecycle&& onRecycleFn, VkDescriptorSet vkSet)
: dynamicUboMask(layout->bitmask.dynamicUbo),
uniqueDynamicUboCount(layout->count.dynamicUbo),
mLayout(layout),
mCurrentSetIndex(0) {
addNewSet(vkSet, std::move(onRecycleFn));
}
void VulkanDescriptorSet::referencedBy(VulkanCommandBuffer& commands) { void VulkanDescriptorSet::referencedBy(VulkanCommandBuffer& commands) {
// This will store an age associated with this command buffer into the VulkanBuffer, which
// will allow us to determine whether a barrier is necessary or not.
mUboMask.forEachSetBit([this, &commands](size_t index) { mUboMask.forEachSetBit([this, &commands](size_t index) {
auto& res = mResources[index]; auto& res = mResources[index];
fvkmemory::resource_ptr<VulkanBufferObject> bo = fvkmemory::resource_ptr<VulkanBufferObject> bo =
fvkmemory::resource_ptr<VulkanBufferObject>::cast((VulkanBufferObject*) res.get()); fvkmemory::resource_ptr<VulkanBufferObject>::cast((VulkanBufferObject*) res.get());
bo->referencedBy(commands); bo->referencedBy(commands);
}); });
mSets[mCurrentSetIndex].fenceStatus = commands.getFenceStatus();
}
void VulkanDescriptorSet::gc() {
size_t empty = 0;
for (auto& setBundle: mSets) {
assert_invariant(setBundle.onRecycleFn);
if (setBundle.vkSet == VK_NULL_HANDLE) {
empty++;
continue;
}
if (setBundle.fenceStatus && setBundle.fenceStatus->getStatus() == VK_SUCCESS) {
setBundle.onRecycleFn(this);
setBundle.vkSet = VK_NULL_HANDLE;
setBundle.fenceStatus = {};
empty++;
}
}
if (empty > DESCRIPTOR_SET_GC_LIMIT) {
std::vector<InternalVkSet> retainedSets;
for (auto& setBundle: mSets) {
if (setBundle.vkSet != VK_NULL_HANDLE) {
retainedSets.push_back({
setBundle.vkSet,
std::move(setBundle.onRecycleFn),
std::move(setBundle.fenceStatus)
});
}
}
std::swap(mSets, retainedSets);
}
}
void VulkanDescriptorSet::addNewSet(VkDescriptorSet vkSet, OnRecycle&& onRecycleFn) {
gc();
mCurrentSetIndex = mSets.size();
mSets.push_back({ vkSet, std::move(onRecycleFn) });
} }
PushConstantDescription::PushConstantDescription(backend::Program const& program) { PushConstantDescription::PushConstantDescription(backend::Program const& program) {
+28 -33
View File
@@ -28,6 +28,7 @@
#include "VulkanTexture.h" #include "VulkanTexture.h"
#include "vulkan/VulkanCommands.h" #include "vulkan/VulkanCommands.h"
#include "vulkan/memory/Resource.h" #include "vulkan/memory/Resource.h"
#include "vulkan/memory/ResourcePointer.h"
#include "vulkan/utils/Definitions.h" #include "vulkan/utils/Definitions.h"
#include "vulkan/utils/StaticVector.h" #include "vulkan/utils/StaticVector.h"
@@ -57,6 +58,8 @@ inline uint8_t collapsedCount(Bitmask const& mask) {
} // anonymous namespace } // anonymous namespace
class VulkanDescriptorSetCache;
struct VulkanBufferObject; struct VulkanBufferObject;
struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Resource { struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Resource {
@@ -159,38 +162,14 @@ public:
using OnRecycle = std::function<void(VulkanDescriptorSet*)>; using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
VulkanDescriptorSet( VulkanDescriptorSet(
fvkutils::UniformBufferBitmask const& dynamicUboMask, fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout,
uint8_t uniqueDynamicUboCount, OnRecycle&& onRecycleFn, VkDescriptorSet vkSet);
OnRecycle&& onRecycleFn, VkDescriptorSet vkSet)
: dynamicUboMask(dynamicUboMask),
uniqueDynamicUboCount(uniqueDynamicUboCount),
mVkSet(vkSet),
mOnRecycleFn(std::move(onRecycleFn)) {}
// NOLINTNEXTLINE(bugprone-exception-escape) // NOLINTNEXTLINE(bugprone-exception-escape)
~VulkanDescriptorSet() { ~VulkanDescriptorSet();
if (mOnRecycleFn) {
mOnRecycleFn(this);
}
if (mOnRecycleExternalSamplerFn) {
mOnRecycleExternalSamplerFn(this);
}
}
VkDescriptorSet getVkSet() const noexcept { VkDescriptorSet getVkSet() const noexcept {
return mVkSet; return mSets[mCurrentSetIndex].vkSet;
}
VkDescriptorSet getExternalSamplerVkSet() const noexcept {
return mExternalSamplerVkSet;
}
void setExternalSamplerVkSet(VkDescriptorSet vkset, OnRecycle onRecycle) {
mExternalSamplerVkSet = vkset;
if (mOnRecycleExternalSamplerFn) {
mOnRecycleExternalSamplerFn(this);
}
mOnRecycleExternalSamplerFn = onRecycle;
} }
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept { void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
@@ -211,17 +190,33 @@ public:
void referencedBy(VulkanCommandBuffer& commands); void referencedBy(VulkanCommandBuffer& commands);
fvkutils::UniformBufferBitmask const dynamicUboMask; bool isBound() const {
return bool(mSets[mCurrentSetIndex].fenceStatus);
}
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> getLayout() const { return mLayout; }
fvkutils::UniformBufferBitmask const& dynamicUboMask;
uint8_t const uniqueDynamicUboCount; uint8_t const uniqueDynamicUboCount;
private: private:
VkDescriptorSet const mVkSet; friend class VulkanDescriptorSetCache;
VkDescriptorSet mExternalSamplerVkSet = VK_NULL_HANDLE;
void addNewSet(VkDescriptorSet vkSet, OnRecycle&& onRecycleFn);
void gc();
struct InternalVkSet {
VkDescriptorSet vkSet = {};
OnRecycle onRecycleFn;
std::shared_ptr<VulkanCmdFence> fenceStatus;
};
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> mLayout;
backend::DescriptorSetOffsetArray mOffsets; backend::DescriptorSetOffsetArray mOffsets;
std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources; std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources;
OnRecycle mOnRecycleFn; uint8_t mCurrentSetIndex;
OnRecycle mOnRecycleExternalSamplerFn; std::vector<InternalVkSet> mSets;
fvkutils::UniformBufferBitmask mUboMask; fvkutils::UniformBufferBitmask mUboMask;
}; };
@@ -191,9 +191,12 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
.alphaToCoverageEnable = raster.alphaToCoverageEnable, .alphaToCoverageEnable = raster.alphaToCoverageEnable,
.alphaToOneEnable = VK_FALSE, .alphaToOneEnable = VK_FALSE,
}; };
bool const enableDepthTest =
raster.depthCompareOp != SamplerCompareFunc::A ||
raster.depthWriteEnable;
VkPipelineDepthStencilStateCreateInfo vkDs = { VkPipelineDepthStencilStateCreateInfo vkDs = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.depthTestEnable = VK_TRUE, .depthTestEnable = enableDepthTest ? VK_TRUE : VK_FALSE,
.depthWriteEnable = raster.depthWriteEnable, .depthWriteEnable = raster.depthWriteEnable,
.depthCompareOp = fvkutils::getCompareOp(raster.depthCompareOp), .depthCompareOp = fvkutils::getCompareOp(raster.depthCompareOp),
.depthBoundsTestEnable = VK_FALSE, .depthBoundsTestEnable = VK_FALSE,
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2025 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 "VulkanStreamedImageManager.h"
#include "VulkanDescriptorSetCache.h"
#include "VulkanExternalImageManager.h"
#include "VulkanDescriptorSetLayoutCache.h"
#include "VulkanSamplerCache.h"
namespace filament::backend {
VulkanStreamedImageManager::VulkanStreamedImageManager(VulkanExternalImageManager* manager)
: mExternalImageManager(manager) {}
VulkanStreamedImageManager::~VulkanStreamedImageManager() = default;
void VulkanStreamedImageManager::terminate() { mStreamedTexturesBindings.clear(); }
void VulkanStreamedImageManager::bindStreamedTexture(
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
uint8_t bindingPoint, fvkmemory::resource_ptr<VulkanTexture> image,
SamplerParams samplerParams) {
mStreamedTexturesBindings.push_back({ bindingPoint, image, set, samplerParams });
}
void VulkanStreamedImageManager::unbindStreamedTexture(
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
uint8_t bindingPoint) {
auto iter = std::remove_if(mStreamedTexturesBindings.begin(), mStreamedTexturesBindings.end(),
[&](StreamedTextureBinding& binding) {
return ((binding.set == set) && (binding.binding == bindingPoint));
});
mStreamedTexturesBindings.erase(iter, mStreamedTexturesBindings.end());
}
void VulkanStreamedImageManager::onStreamAcquireImage(fvkmemory::resource_ptr<VulkanTexture> image,
fvkmemory::resource_ptr<VulkanStream> stream) {
for (StreamedTextureBinding const& data: mStreamedTexturesBindings) {
// Find the right stream
if (data.image->getStream() == stream) {
mExternalImageManager->bindExternallySampledTexture(data.set, data.binding, image,
data.samplerParams);
}
}
}
} // namespace filament::backend
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2025 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_CACHING_VULKANSTREAMEDIMAGEMANAGER_H
#define TNT_FILAMENT_BACKEND_CACHING_VULKANSTREAMEDIMAGEMANAGER_H
#include "VulkanHandles.h"
#include <backend/DriverEnums.h>
#include <array>
#include <vector>
namespace filament::backend {
class VulkanExternalImageManager;
class VulkanDescriptorSetCache;
class VulkanSamplerCache;
// Manages the logic of streamed images.
class VulkanStreamedImageManager {
public:
VulkanStreamedImageManager(
VulkanExternalImageManager* manager);
~VulkanStreamedImageManager();
void terminate();
public:
void bindStreamedTexture(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t bindingPoint,
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams);
void unbindStreamedTexture(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
uint8_t bindingPoint);
void onStreamAcquireImage(fvkmemory::resource_ptr<VulkanTexture> image,
fvkmemory::resource_ptr<VulkanStream> stream);
private:
struct StreamedTextureBinding {
uint8_t binding = 0;
fvkmemory::resource_ptr<VulkanTexture> image;
fvkmemory::resource_ptr<VulkanDescriptorSet> set;
SamplerParams samplerParams;
};
// keep track of all the stream bindings
std::vector<StreamedTextureBinding> mStreamedTexturesBindings;
VulkanExternalImageManager* mExternalImageManager;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_CACHING_VULKANSTREAMEDIMAGEMANAGER_H
@@ -19,6 +19,7 @@
#include "VulkanCommands.h" #include "VulkanCommands.h"
#include "VulkanTexture.h" #include "VulkanTexture.h"
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h> #include <utils/FixedCapacityVector.h>
#include <utils/Panic.h> #include <utils/Panic.h>
@@ -105,6 +106,11 @@ void VulkanSwapChain::update() {
} }
void VulkanSwapChain::present(DriverBase& driver) { void VulkanSwapChain::present(DriverBase& driver) {
// The last acquire failed, so just skip presenting.
if (!mAcquired) {
return;
}
if (!mHeadless && mTransitionSwapChainImageLayoutForPresent) { if (!mHeadless && mTransitionSwapChainImageLayoutForPresent) {
VulkanCommandBuffer& commands = mCommands->get(); VulkanCommandBuffer& commands = mCommands->get();
VkImageSubresourceRange const subresources{ VkImageSubresourceRange const subresources{
@@ -161,7 +167,15 @@ void VulkanSwapChain::acquire(bool& resized) {
VulkanPlatform::ImageSyncData imageSyncData; VulkanPlatform::ImageSyncData imageSyncData;
VkResult const result = mPlatform->acquire(swapChain, &imageSyncData); VkResult const result = mPlatform->acquire(swapChain, &imageSyncData);
if (result != VK_SUCCESS) {
// We just don't set mAcquired here so the next present will just skip.
FVK_LOGD << "Failed to acquire next image in the swapchain result=" << (int) result;
return;
}
mCurrentSwapIndex = imageSyncData.imageIndex; mCurrentSwapIndex = imageSyncData.imageIndex;
assert_invariant(mCurrentSwapIndex < mFinishedDrawing.size());
mFinishedDrawing[mCurrentSwapIndex] = {}; mFinishedDrawing[mCurrentSwapIndex] = {};
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
<< "Cannot acquire in swapchain. error=" << static_cast<int32_t>(result); << "Cannot acquire in swapchain. error=" << static_cast<int32_t>(result);
@@ -36,6 +36,43 @@ namespace filament::backend {
struct VulkanTexture; struct VulkanTexture;
struct VulkanStream : public HwStream, fvkmemory::Resource {
//-- These methods are only called from the frontend
void acquire(const AcquiredImage& image) {
user_thread.mPrevious = user_thread.mAcquired;
user_thread.mAcquired = image;
}
bool previousNeedsRelease() const { return (user_thread.mPrevious.image != nullptr); }
// this function will null the previous once the caller takes it.
// It ensures we don't schedule for release twice.
AcquiredImage takePrevious() {
AcquiredImage previous = user_thread.mPrevious;
user_thread.mPrevious = {};
return previous;
}
const AcquiredImage& getAcquired() const { return user_thread.mAcquired; }
//-- These methods are only called from the backend thread
fvkmemory::resource_ptr<VulkanTexture> getTexture(void* ahb) {
if (auto itr = mTextures.find(ahb); itr != mTextures.end()) {
return itr->second;
}
return {};
}
void pushImage(void* ahb, fvkmemory::resource_ptr<VulkanTexture> tex) { mTextures[ahb] = tex; }
private:
// These are only called from the frontend
struct {
AcquiredImage mAcquired;
AcquiredImage mPrevious;
} user_thread;
// #TODO b/442937292
std::unordered_map<void*, fvkmemory::resource_ptr<VulkanTexture>> mTextures;
};
struct VulkanTextureState : public fvkmemory::Resource { struct VulkanTextureState : public fvkmemory::Resource {
VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands, VmaAllocator allocator, VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands, VmaAllocator allocator,
VkDevice device, VkImage image, VkDeviceMemory deviceMemory, VkFormat format, VkDevice device, VkImage image, VkDeviceMemory deviceMemory, VkFormat format,
@@ -74,6 +111,8 @@ private:
// The texture with the sidecar owns the sidecar. // The texture with the sidecar owns the sidecar.
fvkmemory::resource_ptr<VulkanTexture> mSidecarMSAA; fvkmemory::resource_ptr<VulkanTexture> mSidecarMSAA;
// The stream this texture is associated with.
fvkmemory::resource_ptr<VulkanStream> mStream;
VkImage const mTextureImage; VkImage const mTextureImage;
VkDeviceMemory const mTextureImageMemory; VkDeviceMemory const mTextureImageMemory;
@@ -105,6 +144,7 @@ private:
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>; using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews; std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
friend struct VulkanTexture; friend struct VulkanTexture;
}; };
@@ -182,6 +222,14 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
return mState->mSidecarMSAA; return mState->mSidecarMSAA;
} }
void setStream(fvkmemory::resource_ptr<VulkanStream> stream) {
mState->mStream = stream;
}
fvkmemory::resource_ptr<VulkanStream> getStream() const {
return mState->mStream;
}
bool isTransientAttachment() const { bool isTransientAttachment() const {
return mState->mUsage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT; return mState->mUsage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
} }
@@ -41,6 +41,7 @@ template ResourceType getTypeEnum<VulkanBuffer>() noexcept;
template ResourceType getTypeEnum<VulkanSync>() noexcept; template ResourceType getTypeEnum<VulkanSync>() noexcept;
template ResourceType getTypeEnum<VulkanMemoryMappedBuffer>() noexcept; template ResourceType getTypeEnum<VulkanMemoryMappedBuffer>() noexcept;
template ResourceType getTypeEnum<VulkanSemaphore>() noexcept; template ResourceType getTypeEnum<VulkanSemaphore>() noexcept;
template ResourceType getTypeEnum<VulkanStream>() noexcept;
template<typename D> template<typename D>
ResourceType getTypeEnum() noexcept { ResourceType getTypeEnum() noexcept {
@@ -104,6 +105,9 @@ ResourceType getTypeEnum() noexcept {
if constexpr (std::is_same_v<D, VulkanSemaphore>) { if constexpr (std::is_same_v<D, VulkanSemaphore>) {
return ResourceType::SEMAPHORE; return ResourceType::SEMAPHORE;
} }
if constexpr (std::is_same_v<D, VulkanStream>) {
return ResourceType::STREAM;
}
return ResourceType::UNDEFINED_TYPE; return ResourceType::UNDEFINED_TYPE;
} }
@@ -149,6 +153,8 @@ std::string_view getTypeStr(ResourceType type) {
return "VulkanMemoryMappedBuffer"; return "VulkanMemoryMappedBuffer";
case ResourceType::SEMAPHORE: case ResourceType::SEMAPHORE:
return "Semaphore"; return "Semaphore";
case ResourceType::STREAM:
return "VulkanStream";
case ResourceType::UNDEFINED_TYPE: case ResourceType::UNDEFINED_TYPE:
return ""; return "";
} }
@@ -55,7 +55,8 @@ enum class ResourceType : uint8_t {
SYNC = 17, SYNC = 17,
MEMORY_MAPPED_BUFFER = 18, MEMORY_MAPPED_BUFFER = 18,
SEMAPHORE = 19, SEMAPHORE = 19,
UNDEFINED_TYPE = 20, // Must be the last enum because we use it for iterating over the enums. STREAM = 20,
UNDEFINED_TYPE = 21, // Must be the last enum because we use it for iterating over the enums.
}; };
template<typename D> template<typename D>
@@ -123,6 +123,9 @@ void ResourceManager::destroyWithType(ResourceType type, HandleId id) {
case ResourceType::SEMAPHORE: case ResourceType::SEMAPHORE:
destruct<VulkanSemaphore>(Handle<VulkanSemaphore>(id)); destruct<VulkanSemaphore>(Handle<VulkanSemaphore>(id));
break; break;
case ResourceType::STREAM:
destruct<VulkanStream>(Handle<VulkanStream>(id));
break;
case ResourceType::UNDEFINED_TYPE: case ResourceType::UNDEFINED_TYPE:
break; break;
} }
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#include <backend/platforms/VulkanPlatformAndroid.h> #include <backend/platforms/VulkanPlatformAndroid.h>
#include "vulkan/VulkanConstants.h" #include "vulkan/VulkanConstants.h"
@@ -20,6 +21,8 @@
#include "vulkan/platform/VulkanPlatformSwapChainImpl.h" #include "vulkan/platform/VulkanPlatformSwapChainImpl.h"
#include "vulkan/utils/Image.h" #include "vulkan/utils/Image.h"
#include "AndroidFrameCallback.h"
#include <private/backend/BackendUtilsAndroid.h> #include <private/backend/BackendUtilsAndroid.h>
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
@@ -32,6 +35,7 @@
#include <android/hardware_buffer.h> #include <android/hardware_buffer.h>
#include <android/native_window.h> #include <android/native_window.h>
#include <new>
#include <utility> #include <utility>
using namespace bluevk; using namespace bluevk;
@@ -200,6 +204,14 @@ uint32_t selectMemoryTypeForExternalImage(VkPhysicalDevice physicalDevice, VkDev
} // namespace } // namespace
// ---------------------------------------------------------------------------------------------
struct VulkanPlatformAndroid::AndroidDetails {
AndroidFrameCallback androidFrameCallback;
};
// ---------------------------------------------------------------------------------------------
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() { VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() {
if (__builtin_available(android 26, *)) { if (__builtin_available(android 26, *)) {
if (aHardwareBuffer) { if (aHardwareBuffer) {
@@ -208,14 +220,19 @@ VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid()
} }
} }
VulkanPlatformAndroid::VulkanPlatformAndroid() { // ---------------------------------------------------------------------------------------------
VulkanPlatformAndroid::VulkanPlatformAndroid() :
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
mOSVersion = android_get_device_api_level(); mOSVersion = android_get_device_api_level();
if (mOSVersion < 0) { if (mOSVersion < 0) {
mOSVersion = __ANDROID_API_FUTURE__; mOSVersion = __ANDROID_API_FUTURE__;
} }
} }
VulkanPlatformAndroid::~VulkanPlatformAndroid() noexcept = default; VulkanPlatformAndroid::~VulkanPlatformAndroid() noexcept {
delete &mAndroidDetails;
}
Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage( Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
AHardwareBuffer const* buffer, bool sRGB) noexcept { AHardwareBuffer const* buffer, bool sRGB) noexcept {
@@ -497,14 +514,14 @@ int VulkanPlatformAndroid::getOSVersion() const noexcept {
} }
void VulkanPlatformAndroid::terminate() { void VulkanPlatformAndroid::terminate() {
mAndroidFrameCallback.terminate(); mAndroidDetails.androidFrameCallback.terminate();
VulkanPlatform::terminate(); VulkanPlatform::terminate();
} }
Driver* VulkanPlatformAndroid::createDriver(void* sharedContext, DriverConfig const& driverConfig) { Driver* VulkanPlatformAndroid::createDriver(void* sharedContext, DriverConfig const& driverConfig) {
Driver* driver = VulkanPlatform::createDriver(sharedContext, driverConfig); Driver* driver = VulkanPlatform::createDriver(sharedContext, driverConfig);
if (driver) { if (driver) {
mAndroidFrameCallback.init(); mAndroidDetails.androidFrameCallback.init();
} }
return driver; return driver;
} }
@@ -520,13 +537,19 @@ bool VulkanPlatformAndroid::queryCompositorTiming(SwapChain const* swapchain,
} }
AndroidFrameCallback::Timeline const preferredTimeline{ AndroidFrameCallback::Timeline const preferredTimeline{
mAndroidFrameCallback.getPreferredTimeline() }; mAndroidDetails.androidFrameCallback.getPreferredTimeline() };
outCompositorTiming->frameTime = preferredTimeline.frameTime; outCompositorTiming->frameTime = preferredTimeline.frameTime;
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime; outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline; outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
outCompositorTiming->compositeDeadline = CompositorTiming::INVALID;
outCompositorTiming->compositeInterval = CompositorTiming::INVALID;
outCompositorTiming->compositeToPresentLatency = CompositorTiming::INVALID;
// From this point on, we always return "success" because some timings were returned.
auto vulkanSwapchain = static_cast<VulkanPlatformSwapChainBase const *>(swapchain); auto vulkanSwapchain = static_cast<VulkanPlatformSwapChainBase const *>(swapchain);
return vulkanSwapchain->queryCompositorTiming(outCompositorTiming); vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
return true;
} }
bool VulkanPlatformAndroid::setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept { bool VulkanPlatformAndroid::setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept {
@@ -165,6 +165,11 @@ VulkanPlatformSurfaceSwapChain::~VulkanPlatformSurfaceSwapChain() {
VkResult VulkanPlatformSurfaceSwapChain::create() { VkResult VulkanPlatformSurfaceSwapChain::create() {
#ifdef __ANDROID__ #ifdef __ANDROID__
NativeWindow::enableFrameTimestamps(static_cast<ANativeWindow*>(mNativeWindow), true); NativeWindow::enableFrameTimestamps(static_cast<ANativeWindow*>(mNativeWindow), true);
// on Android, disable producer throttling
if (mProducerThrottling.isSupported()) {
mProducerThrottling.setProducerThrottlingEnabled(
static_cast<ANativeWindow*>(mNativeWindow), false);
}
#endif #endif
VkSurfaceFormatKHR surfaceFormat = {}; VkSurfaceFormatKHR surfaceFormat = {};
@@ -356,13 +361,15 @@ bool VulkanPlatformSurfaceSwapChain::queryCompositorTiming(
CompositorTiming* outCompositorTiming) const { CompositorTiming* outCompositorTiming) const {
#ifdef __ANDROID__ #ifdef __ANDROID__
// fallback to private APIs // fallback to private APIs
int const status = NativeWindow::getCompositorTiming( if (UTILS_VERY_LIKELY(mNativeWindow)) {
static_cast<ANativeWindow*>(mNativeWindow), int const status = NativeWindow::getCompositorTiming(
&outCompositorTiming->compositeDeadline, static_cast<ANativeWindow*>(mNativeWindow),
&outCompositorTiming->compositeInterval, &outCompositorTiming->compositeDeadline,
&outCompositorTiming->compositeToPresentLatency); &outCompositorTiming->compositeInterval,
if (status == 0) { &outCompositorTiming->compositeToPresentLatency);
return true; if (status == 0) {
return true;
}
} }
#endif #endif
return VulkanPlatformSwapChainBase::queryCompositorTiming(outCompositorTiming); return VulkanPlatformSwapChainBase::queryCompositorTiming(outCompositorTiming);
@@ -25,7 +25,8 @@
#include <bluevk/BlueVK.h> #include <bluevk/BlueVK.h>
#ifdef __ANDROID__ #ifdef __ANDROID__
#include <AndroidSwapChainHelper.h> #include "AndroidSwapChainHelper.h"
#include "AndroidNativeWindow.h"
#endif #endif
#include <unordered_map> #include <unordered_map>
@@ -122,6 +123,7 @@ private:
#ifdef __ANDROID__ #ifdef __ANDROID__
AndroidSwapChainHelper mImpl{}; AndroidSwapChainHelper mImpl{};
AndroidProducerThrottling mProducerThrottling;
#endif #endif
}; };
@@ -18,6 +18,12 @@
#include "vulkan/VulkanTexture.h" #include "vulkan/VulkanTexture.h"
#if defined(__ANDROID__)
#include "backend/platforms/VulkanPlatformAndroid.h"
#include <android/hardware_buffer.h>
#include <android/native_window.h>
#endif
#include <utils/Panic.h> #include <utils/Panic.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/debug.h> #include <utils/debug.h>
@@ -234,6 +240,19 @@ uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask) {
return mostSignificantBit((sampleCount - 1) & mask); return mostSignificantBit((sampleCount - 1) & mask);
} }
filament::backend::Platform::ExternalImageHandle createExternalImageFromRaw(
filament::backend::VulkanPlatform* platform, void* image, bool sRGB) {
#if defined(__ANDROID__)
if (__builtin_available(android 26, *)) {
return static_cast<filament::backend::VulkanPlatformAndroid*>(platform)
->createExternalImage(reinterpret_cast<AHardwareBuffer const*>(image), sRGB);
}
#endif
return {};
}
} // namespace filament::backend::fvkutils } // namespace filament::backend::fvkutils
bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) { bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) {
@@ -18,6 +18,7 @@
#define TNT_FILAMENT_BACKEND_VULKAN_UTILS_IMAGE_H #define TNT_FILAMENT_BACKEND_VULKAN_UTILS_IMAGE_H
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <backend/platforms/VulkanPlatform.h>
#include <utils/Log.h> #include <utils/Log.h>
@@ -106,6 +107,9 @@ VkImageAspectFlags getImageAspect(VkFormat format);
uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask); uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask);
Platform::ExternalImageHandle createExternalImageFromRaw(filament::backend::VulkanPlatform* platform,
void* image,
bool sRGB);
} // namespace fvkutils } // namespace fvkutils
} // namespace filament::backend } // namespace filament::backend
@@ -18,6 +18,7 @@
#include "WebGPUConstants.h" #include "WebGPUConstants.h"
#include "WebGPUQueueManager.h" #include "WebGPUQueueManager.h"
#include "WebGPUStagePool.h"
#include "DriverBase.h" #include "DriverBase.h"
#include <backend/BufferDescriptor.h> #include <backend/BufferDescriptor.h>
@@ -63,9 +64,9 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
// WebGPU requires that the size of the data copied from the staging buffer to the GPU buffer is a // WebGPU requires that the size of the data copied from the staging buffer to the GPU buffer is a
// multiple of 4. This function handles cases where the buffer descriptor's size is not a multiple // multiple of 4. This function handles cases where the buffer descriptor's size is not a multiple
// of 4 by padding with zeros. // of 4 by padding with zeros.
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor&& bufferDescriptor, void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
const uint32_t byteOffset, wgpu::Device const& device, const uint32_t byteOffset, wgpu::Device const& device,
WebGPUQueueManager* const webGPUQueueManager) { WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool) {
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer) FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
<< "updateGPUBuffer called with a null buffer"; << "updateGPUBuffer called with a null buffer";
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize()) FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
@@ -85,53 +86,27 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor&& bufferDescriptor,
const size_t stagingBufferSize = const size_t stagingBufferSize =
remainder == 0 ? bufferDescriptor.size : mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS; remainder == 0 ? bufferDescriptor.size : mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
// create a staging buffer wgpu::Buffer stagingBuffer = webGPUStagePool->acquireBuffer(stagingBufferSize,
wgpu::BufferDescriptor descriptor{ webGPUQueueManager->getLatestSubmissionState());
.label = "Filament WebGPU Staging Buffer",
.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
.size = stagingBufferSize};
wgpu::Buffer stagingBuffer = device.CreateBuffer(&descriptor);
struct UserData final { void* mappedRange = stagingBuffer.GetMappedRange();
uint32_t byteOffset; assert_invariant(mappedRange);
size_t stagingBufferSize;
size_t remainder; memcpy(mappedRange, bufferDescriptor.buffer, bufferDescriptor.size);
BufferDescriptor srcBufferDescriptor;
wgpu::Buffer stagingBuffer; // Make sure the padded memory is set to 0 to have deterministic behaviors
WebGPUQueueManager* const webGPUQueueManager; if (remainder != 0) {
wgpu::Buffer dstBuffer; uint8_t* paddingStart = static_cast<uint8_t*>(mappedRange) + bufferDescriptor.size;
}; memset(paddingStart, 0, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - remainder);
auto userData = std::make_unique<UserData>(UserData{ }
.byteOffset = byteOffset,
.stagingBufferSize = stagingBufferSize, stagingBuffer.Unmap();
.remainder = remainder,
.srcBufferDescriptor = std::move(bufferDescriptor), // Copy the staging buffer contents to the destination buffer.
.stagingBuffer = stagingBuffer, webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stagingBuffer, 0, mBuffer,
.webGPUQueueManager = webGPUQueueManager, byteOffset,
.dstBuffer = mBuffer}); remainder == 0 ? bufferDescriptor.size
stagingBuffer.MapAsync( : mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS);
wgpu::MapMode::Write, 0, stagingBufferSize, wgpu::CallbackMode::AllowProcessEvents,
[](wgpu::MapAsyncStatus status, const char* message, UserData* userdata) {
std::unique_ptr<UserData> data(static_cast<UserData*>(userdata));
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
void* mappedRange = data->stagingBuffer.GetMappedRange();
memcpy(mappedRange, data->srcBufferDescriptor.buffer,
data->srcBufferDescriptor.size);
if (data->remainder != 0) {
uint8_t* paddingStart =
static_cast<uint8_t*>(mappedRange) + data->srcBufferDescriptor.size;
memset(paddingStart, 0,
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - data->remainder);
}
data->stagingBuffer.Unmap();
data->webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(
data->stagingBuffer, 0, data->dstBuffer, data->byteOffset,
data->stagingBufferSize);
} else {
FWGPU_LOGE << "Failed to map staging buffer for readPixels: " << message;
}
},
userData.release());
} }
} // namespace filament::backend } // namespace filament::backend
@@ -25,6 +25,7 @@ namespace filament::backend {
class BufferDescriptor; class BufferDescriptor;
class WebGPUQueueManager; class WebGPUQueueManager;
class WebGPUStagePool;
/** /**
* A base class for WebGPU buffer objects, providing common functionality for creating and * A base class for WebGPU buffer objects, providing common functionality for creating and
@@ -39,8 +40,8 @@ public:
* happen after draw commands encoded in the encoder. Submitting any commands up to this point * happen after draw commands encoded in the encoder. Submitting any commands up to this point
* ensures the calls happen in the expected sequence. * ensures the calls happen in the expected sequence.
*/ */
void updateGPUBuffer(BufferDescriptor&&, uint32_t byteOffset, wgpu::Device const& device, void updateGPUBuffer(BufferDescriptor const&, uint32_t byteOffset, wgpu::Device const& device,
WebGPUQueueManager* const webGPUQueueManager); WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool);
[[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; } [[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; }
+7 -8
View File
@@ -107,6 +107,7 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
mAdapter{ mPlatform.requestAdapter(nullptr) }, mAdapter{ mPlatform.requestAdapter(nullptr) },
mDevice{ mPlatform.requestDevice(mAdapter) }, mDevice{ mPlatform.requestDevice(mAdapter) },
mQueueManager{ mDevice }, mQueueManager{ mDevice },
mStagePool{ mDevice },
mPipelineLayoutCache{ mDevice }, mPipelineLayoutCache{ mDevice },
mPipelineCache{ mDevice }, mPipelineCache{ mDevice },
mRenderPassMipmapGenerator{ mDevice, &mQueueManager }, mRenderPassMipmapGenerator{ mDevice, &mQueueManager },
@@ -177,6 +178,9 @@ void WebGPUDriver::endFrame(const uint32_t /* frameId */) {
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) { for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
mCurrentDescriptorSets[i] = {}; mCurrentDescriptorSets[i] = {};
} }
// Garbage collection (if necessary)
mStagePool.gc();
} }
// If a command encoder is in flight then the encoder is finished and submitted to the GPU queue. // If a command encoder is in flight then the encoder is finished and submitted to the GPU queue.
@@ -651,11 +655,6 @@ void WebGPUDriver::setAcquiredImage(Handle<HwStream> sh, void* image, const math
//todo //todo
} }
void WebGPUDriver::registerBufferObjectStreams(Handle<HwBufferObject> bufferObjectHandle,
BufferObjectStreamDescriptor&& streams) {
//todo
}
void WebGPUDriver::setStreamDimensions(Handle<HwStream> sh, uint32_t width, uint32_t height) { void WebGPUDriver::setStreamDimensions(Handle<HwStream> sh, uint32_t width, uint32_t height) {
//todo //todo
} }
@@ -856,7 +855,7 @@ void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
// draw calls are made. // draw calls are made.
flush(); flush();
handleCast<WebGPUIndexBuffer>(indexBufferHandle) handleCast<WebGPUIndexBuffer>(indexBufferHandle)
->updateGPUBuffer(std::move(bufferDescriptor), byteOffset, mDevice, &mQueueManager); ->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor)); scheduleDestroy(std::move(bufferDescriptor));
} }
@@ -867,14 +866,14 @@ void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
// draw calls are made. // draw calls are made.
flush(); flush();
handleCast<WebGPUBufferObject>(bufferObjectHandle) handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(std::move(bufferDescriptor), byteOffset, mDevice, &mQueueManager); ->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor)); scheduleDestroy(std::move(bufferDescriptor));
} }
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle, void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) { BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
handleCast<WebGPUBufferObject>(bufferObjectHandle) handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(std::move(bufferDescriptor), byteOffset, mDevice, &mQueueManager); ->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor)); scheduleDestroy(std::move(bufferDescriptor));
} }
@@ -25,6 +25,7 @@
#include "webgpu/WebGPUPipelineLayoutCache.h" #include "webgpu/WebGPUPipelineLayoutCache.h"
#include "webgpu/WebGPURenderPassMipmapGenerator.h" #include "webgpu/WebGPURenderPassMipmapGenerator.h"
#include "webgpu/WebGPUQueueManager.h" #include "webgpu/WebGPUQueueManager.h"
#include "webgpu/WebGPUStagePool.h"
#include "webgpu/utils/AsyncTaskCounter.h" #include "webgpu/utils/AsyncTaskCounter.h"
#include <backend/platforms/WebGPUPlatform.h> #include <backend/platforms/WebGPUPlatform.h>
@@ -81,6 +82,7 @@ private:
wgpu::Device mDevice = nullptr; wgpu::Device mDevice = nullptr;
wgpu::Limits mDeviceLimits = {}; wgpu::Limits mDeviceLimits = {};
WebGPUQueueManager mQueueManager; WebGPUQueueManager mQueueManager;
WebGPUStagePool mStagePool;
void* mNativeWindow = nullptr; void* mNativeWindow = nullptr;
WebGPUSwapChain* mSwapChain = nullptr; WebGPUSwapChain* mSwapChain = nullptr;
uint64_t mNextFakeHandle = 1; uint64_t mNextFakeHandle = 1;
@@ -21,7 +21,6 @@
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <thread> #include <thread>
#include <iostream>
namespace filament::backend { namespace filament::backend {
@@ -59,13 +58,13 @@ WebGPUQueueManager::WebGPUQueueManager(wgpu::Device const& device)
WebGPUQueueManager::~WebGPUQueueManager() = default; WebGPUQueueManager::~WebGPUQueueManager() = default;
wgpu::CommandEncoder WebGPUQueueManager::getCommandEncoder() { wgpu::CommandEncoder WebGPUQueueManager::getCommandEncoder() {
// std::unique_lock<std::mutex> lock(mLock);
if (!mCommandEncoder) { if (!mCommandEncoder) {
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = { wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
.label = "Filament Command Encoder", .label = "Filament Command Encoder",
}; };
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor); mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
ASSERT_POSTCONDITION(mCommandEncoder, "Failed to create command encoder."); ASSERT_POSTCONDITION(mCommandEncoder, "Failed to create command encoder.");
mLatestSubmissionState = std::make_shared<WebGPUSubmissionState>();
} }
return mCommandEncoder; return mCommandEncoder;
} }
@@ -92,14 +91,10 @@ std::shared_ptr<WebGPUSubmissionState> WebGPUQueueManager::getLatestSubmissionSt
} }
void WebGPUQueueManager::submit() { void WebGPUQueueManager::submit() {
// std::unique_lock<std::mutex> lock(mLock);
if (!mCommandEncoder) { if (!mCommandEncoder) {
std::cout << "Run Yu: no mCommandEncoder found!\n";
return; return;
} }
mLatestSubmissionState = std::make_shared<WebGPUSubmissionState>();
wgpu::CommandBufferDescriptor commandBufferDescriptor{ wgpu::CommandBufferDescriptor commandBufferDescriptor{
.label = "Filament Command Buffer", .label = "Filament Command Buffer",
}; };
@@ -74,7 +74,6 @@ private:
wgpu::Queue mQueue; wgpu::Queue mQueue;
wgpu::CommandEncoder mCommandEncoder; wgpu::CommandEncoder mCommandEncoder;
std::shared_ptr<WebGPUSubmissionState> mLatestSubmissionState; std::shared_ptr<WebGPUSubmissionState> mLatestSubmissionState;
std::mutex mLock;
}; };
} // namespace filament::backend } // namespace filament::backend
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2025 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 "WebGPUStagePool.h"
#include "WebGPUConstants.h"
#include "WebGPUQueueManager.h"
namespace filament::backend {
WebGPUStagePool::WebGPUStagePool(wgpu::Device const& device) : mDevice(device) {}
WebGPUStagePool::~WebGPUStagePool() = default;
wgpu::Buffer WebGPUStagePool::acquireBuffer(size_t requiredSize,
std::shared_ptr<WebGPUSubmissionState> submissionState) {
wgpu::Buffer buffer;
{
std::lock_guard<std::mutex> lock(mMutex);
auto iter = mBuffers.lower_bound(requiredSize);
if (iter != mBuffers.end()) {
buffer = iter->second;
mBuffers.erase(iter);
}
}
if (!buffer.Get()) {
buffer = createNewBuffer(requiredSize);
}
mInProgress.push_back({submissionState, buffer});
return buffer;
}
void WebGPUStagePool::recycleBuffer(wgpu::Buffer buffer) {
struct UserData final {
wgpu::Buffer buffer;
WebGPUStagePool* webGPUStagePool;
};
auto userData =
std::make_unique<UserData>(UserData{ .buffer = buffer, .webGPUStagePool = this });
buffer.MapAsync(wgpu::MapMode::Write, 0, buffer.GetSize(), wgpu::CallbackMode::AllowSpontaneous,
[data = std::move(userData)](wgpu::MapAsyncStatus status, const char* message) {
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
if (!data->webGPUStagePool) {
return;
}
std::lock_guard<std::mutex> lock(data->webGPUStagePool->mMutex);
data->webGPUStagePool->mBuffers.insert(
{ data->buffer.GetSize(), data->buffer });
} else {
FWGPU_LOGE << "Failed to MapAsync when recycling staging buffer: " << message;
}
});
}
void WebGPUStagePool::gc() {
// We found that MapAsync would sometimes lead to GetMappedRange returning nullptr if the
// command using that staging buffer has not finished executing, so here we only recycle those
// buffers that are not still being used by any command
std::vector<std::pair<std::shared_ptr<WebGPUSubmissionState>, wgpu::Buffer>> stillInProgress;
for (auto& [st, buffer]: mInProgress) {
if (st->getStatus() == FenceStatus::CONDITION_SATISFIED) {
recycleBuffer(buffer);
} else {
stillInProgress.push_back({st, buffer});
}
}
std::swap(mInProgress, stillInProgress);
}
wgpu::Buffer WebGPUStagePool::createNewBuffer(size_t bufferSize) {
wgpu::BufferDescriptor descriptor{
.label = "Filament WebGPU Staging Buffer",
.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
.size = bufferSize,
.mappedAtCreation = true };
return mDevice.CreateBuffer(&descriptor);
}
} // namespace filament::backend
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2025 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_WEBGPUSTAGEPOOL_H
#define TNT_FILAMENT_BACKEND_WEBGPUSTAGEPOOL_H
#include <webgpu/webgpu_cpp.h>
#include <map>
#include <mutex>
namespace filament::backend {
struct WebGPUSubmissionState;
class WebGPUStagePool {
public:
WebGPUStagePool(wgpu::Device const& device);
~WebGPUStagePool();
wgpu::Buffer acquireBuffer(size_t requiredSize,
std::shared_ptr<WebGPUSubmissionState> submissionState);
void recycleBuffer(wgpu::Buffer buffer);
void gc();
private:
wgpu::Buffer createNewBuffer(size_t bufferSize);
std::multimap<uint32_t, wgpu::Buffer> mBuffers;
std::vector<std::pair<std::shared_ptr<WebGPUSubmissionState>, wgpu::Buffer>> mInProgress;
std::mutex mMutex;
wgpu::Device mDevice;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUSTAGEPOOL_H
+14
View File
@@ -192,6 +192,7 @@ public:
using StereoscopicType = backend::StereoscopicType; using StereoscopicType = backend::StereoscopicType;
using Driver = backend::Driver; using Driver = backend::Driver;
using GpuContextPriority = backend::Platform::GpuContextPriority; using GpuContextPriority = backend::Platform::GpuContextPriority;
using AsynchronousMode = backend::AsynchronousMode;
/** /**
* Config is used to define the memory footprint used by the engine, such as the * Config is used to define the memory footprint used by the engine, such as the
@@ -428,6 +429,11 @@ public:
* initial memory usage. * initial memory usage.
*/ */
uint32_t sharedUboInitialSizeInBytes = 256 * 64; uint32_t sharedUboInitialSizeInBytes = 256 * 64;
/**
* Asynchronous mode for the engine. Defines how asynchronous operations are handled.
*/
AsynchronousMode asynchronousMode = AsynchronousMode::NONE;
}; };
@@ -742,6 +748,14 @@ public:
*/ */
bool isStereoSupported(StereoscopicType stereoscopicType) const noexcept; bool isStereoSupported(StereoscopicType stereoscopicType) const noexcept;
/**
* Checks if the engine is set up for asynchronous operation. If it returns true, the
* asynchronous versions of the APIs are available for use.
*
* @return true if the engine supports asynchronous operation.
*/
bool isAsynchronousOperationSupported() const noexcept;
/** /**
* Retrieves the configuration settings of this Engine. * Retrieves the configuration settings of this Engine.
* *
+21
View File
@@ -69,6 +69,20 @@ public:
using ShaderModel = backend::ShaderModel; using ShaderModel = backend::ShaderModel;
using SubpassType = backend::SubpassType; using SubpassType = backend::SubpassType;
/**
* Defines whether a material instance should use UBO batching or not.
*/
enum class UboBatchingMode {
/**
* For default, it follows the engine settings.
* If UBO batching is enabled on the engine and the material domain is SURFACE, it
* turns on the UBO batching. Otherwise, it turns off the UBO batching.
*/
DEFAULT,
//! Disable the Ubo Batching for this material
DISABLED,
};
/** /**
* Holds information about a material parameter. * Holds information about a material parameter.
*/ */
@@ -165,6 +179,13 @@ public:
*/ */
Builder& shadowSamplingQuality(ShadowSamplingQuality quality) noexcept; Builder& shadowSamplingQuality(ShadowSamplingQuality quality) noexcept;
/**
* Set the batching mode of the instances created from this material.
* @param uboBatchingMode
* @return
*/
Builder& uboBatching(UboBatchingMode uboBatchingMode) noexcept;
/** /**
* Creates the Material object and returns a pointer to it. * Creates the Material object and returns a pointer to it.
* *
+6
View File
@@ -113,8 +113,10 @@ public:
/** /**
* Retrieve a history of frame timing information. The maximum frame history size is * Retrieve a history of frame timing information. The maximum frame history size is
* given by getMaxFrameHistorySize(). * given by getMaxFrameHistorySize().
* All or part of the history can be lost when using a different SwapChain in beginFrame().
* @param historySize requested history size. The returned vector could be smaller. * @param historySize requested history size. The returned vector could be smaller.
* @return A vector of FrameInfo. * @return A vector of FrameInfo.
* @see beginFrame()
*/ */
utils::FixedCapacityVector<FrameInfo> getFrameInfoHistory( utils::FixedCapacityVector<FrameInfo> getFrameInfoHistory(
size_t historySize = 1) const noexcept; size_t historySize = 1) const noexcept;
@@ -326,6 +328,8 @@ public:
* or 0 if unknown. This value should be the timestamp of * or 0 if unknown. This value should be the timestamp of
* the last h/w vsync. It is expressed in the * the last h/w vsync. It is expressed in the
* std::chrono::steady_clock time base. * std::chrono::steady_clock time base.
* On Android this should be the frame time received from
* a Choreographer.
* @param swapChain A pointer to the SwapChain instance to use. * @param swapChain A pointer to the SwapChain instance to use.
* *
* @return * @return
@@ -337,6 +341,8 @@ public:
* *
* @note * @note
* All calls to render() must happen *after* beginFrame(). * All calls to render() must happen *after* beginFrame().
* It is recommended to use the same swapChain for every call to beginFrame, failing to do
* so can result is losing all or part of the FrameInfo history.
* *
* @see * @see
* endFrame() * endFrame()
+4
View File
@@ -455,6 +455,10 @@ bool Engine::isStereoSupported(StereoscopicType) const noexcept {
return downcast(this)->isStereoSupported(); return downcast(this)->isStereoSupported();
} }
bool Engine::isAsynchronousOperationSupported() const noexcept {
return downcast(this)->isAsynchronousOperationSupported();
}
size_t Engine::getMaxStereoscopicEyes() noexcept { size_t Engine::getMaxStereoscopicEyes() noexcept {
return FEngine::getMaxStereoscopicEyes(); return FEngine::getMaxStereoscopicEyes();
} }
+25 -4
View File
@@ -101,11 +101,24 @@ void FrameInfoManager::beginFrame(FSwapChain* swapChain, DriverApi& driver,
auto& history = mFrameTimeHistory; auto& history = mFrameTimeHistory;
// don't exceed the capacity, drop the oldest entry // don't exceed the capacity, drop the oldest entry
if (UTILS_LIKELY(history.size() == history.capacity())) { if (UTILS_LIKELY(history.size() == history.capacity())) {
if (!mDisableGpuFrameComplete) { FrameInfoImpl& frameInfo = history.back();
assert_invariant(history.back().fence); if (frameInfo.ready.load(std::memory_order_relaxed)) {
driver.destroyFence(std::move(history.back().fence)); if (!mDisableGpuFrameComplete) {
assert_invariant(frameInfo.fence);
driver.destroyFence(std::move(frameInfo.fence));
}
history.pop_back();
} else {
// This is a big problem, we ran out of space in the circular queue and that entry
// hasn't been processed yet. Because the code below keeps a reference to the
// front element of the queue, we can't pop/push. Our only option is to not record
// a new entry for this frame, which will create a false skipped frame in the
// data.
LOG(WARNING) << "FrameInfo's circular queue is full, but the oldest item hasn't "
"been processed yet. Skipping this frame, id = " << frameId;
mLastBeginFrameSkipped = true;
return;
} }
history.pop_back();
} }
// create a new entry // create a new entry
@@ -193,6 +206,14 @@ void FrameInfoManager::beginFrame(FSwapChain* swapChain, DriverApi& driver,
} }
void FrameInfoManager::endFrame(DriverApi& driver) noexcept { void FrameInfoManager::endFrame(DriverApi& driver) noexcept {
if (mLastBeginFrameSkipped) {
// if we had to skip the last beginFrame(), endFrame() needs to be skipped too
// because history.front() now references the wrong frame.
// It is guaranteed that if beginFrame() is called, endFrame() will be called too.
mLastBeginFrameSkipped = false;
return;
}
auto& front = mFrameTimeHistory.front(); auto& front = mFrameTimeHistory.front();
front.endFrame = std::chrono::steady_clock::now(); front.endFrame = std::chrono::steady_clock::now();
+3 -27
View File
@@ -95,33 +95,8 @@ struct FrameInfoImpl : public details::FrameInfo {
assert_invariant(!fence); assert_invariant(!fence);
} }
FrameInfoImpl(FrameInfoImpl&& rhs) noexcept : FrameInfoImpl(FrameInfoImpl& rhs) noexcept = delete;
details::FrameInfo(rhs), FrameInfoImpl& operator=(FrameInfoImpl& rhs) noexcept = delete;
frameId(rhs.frameId),
beginFrame(rhs.beginFrame),
endFrame(rhs.endFrame),
backendBeginFrame(rhs.backendBeginFrame),
backendEndFrame(rhs.backendEndFrame),
gpuFrameComplete(rhs.gpuFrameComplete),
vsync(rhs.vsync),
fence(rhs.fence),
ready(rhs.ready.load())
{
}
FrameInfoImpl& operator=(FrameInfoImpl&& rhs) noexcept {
details::FrameInfo::operator=(rhs);
frameId = rhs.frameId;
beginFrame = rhs.beginFrame;
endFrame = rhs.endFrame;
backendBeginFrame = rhs.backendBeginFrame;
backendEndFrame = rhs.backendEndFrame;
gpuFrameComplete = rhs.gpuFrameComplete;
vsync = rhs.vsync;
fence = rhs.fence;
ready.store(rhs.ready.load());
return *this;
}
}; };
template<typename T, size_t CAPACITY> template<typename T, size_t CAPACITY>
@@ -340,6 +315,7 @@ private:
FrameHistoryQueue mFrameTimeHistory{}; FrameHistoryQueue mFrameTimeHistory{};
utils::AsyncJobQueue mJobQueue; utils::AsyncJobQueue mJobQueue;
FSwapChain* mLastSeenSwapChain = nullptr; FSwapChain* mLastSeenSwapChain = nullptr;
bool mLastBeginFrameSkipped = false;
bool const mHasTimerQueries = false; bool const mHasTimerQueries = false;
bool const mDisableGpuFrameComplete = false; bool const mDisableGpuFrameComplete = false;
}; };
+2
View File
@@ -16,6 +16,7 @@
#include "details/BufferAllocator.h" #include "details/BufferAllocator.h"
#include <private/utils/Tracing.h>
#include <utils/Panic.h> #include <utils/Panic.h>
#include <utils/debug.h> #include <utils/debug.h>
@@ -169,6 +170,7 @@ void BufferAllocator::releaseGpu(AllocationId id) {
} }
void BufferAllocator::releaseFreeSlots() { void BufferAllocator::releaseFreeSlots() {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
if (!mHasPendingFrees) { if (!mHasPendingFrees) {
return; return;
} }
+15 -14
View File
@@ -98,7 +98,7 @@ using namespace filaflat;
namespace { namespace {
backend::Platform::DriverConfig getDriverConfig(FEngine* instance) { backend::Platform::DriverConfig getDriverConfig(FEngine* instance) {
return { Platform::DriverConfig driverConfig {
.handleArenaSize = instance->getRequestedDriverHandleArenaSize(), .handleArenaSize = instance->getRequestedDriverHandleArenaSize(),
.metalUploadBufferSizeBytes = instance->getConfig().metalUploadBufferSizeBytes, .metalUploadBufferSizeBytes = instance->getConfig().metalUploadBufferSizeBytes,
.disableParallelShaderCompile = instance->features.backend.disable_parallel_shader_compile, .disableParallelShaderCompile = instance->features.backend.disable_parallel_shader_compile,
@@ -116,7 +116,11 @@ backend::Platform::DriverConfig getDriverConfig(FEngine* instance) {
.gpuContextPriority = instance->getConfig().gpuContextPriority, .gpuContextPriority = instance->getConfig().gpuContextPriority,
.vulkanEnableStagingBufferBypass = .vulkanEnableStagingBufferBypass =
instance->features.backend.vulkan.enable_staging_buffer_bypass, instance->features.backend.vulkan.enable_staging_buffer_bypass,
.asynchronousMode = instance->features.backend.enable_asynchronous_operation ?
instance->getConfig().asynchronousMode : AsynchronousMode::NONE,
}; };
return driverConfig;
} }
} // anonymous } // anonymous
@@ -718,8 +722,7 @@ void FEngine::prepare() {
if (useUboBatching) { if (useUboBatching) {
assert_invariant(mUboManager != nullptr); assert_invariant(mUboManager != nullptr);
mUboManager->beginFrame(driver);
mUboManager->beginFrame(driver, mMaterialInstances);
} }
UboManager* uboManager = mUboManager; UboManager* uboManager = mUboManager;
@@ -728,7 +731,6 @@ void FEngine::prepare() {
// post-process materials instances must be commited explicitly because their // post-process materials instances must be commited explicitly because their
// parameters are typically not set at this point in time. // parameters are typically not set at this point in time.
if (item->getMaterial()->getMaterialDomain() == MaterialDomain::SURFACE) { if (item->getMaterial()->getMaterialDomain() == MaterialDomain::SURFACE) {
item->commitStreamUniformAssociations(driver);
item->commit(driver, uboManager); item->commit(driver, uboManager);
} }
}); });
@@ -758,7 +760,7 @@ void FEngine::gc() {
void FEngine::submitFrame() { void FEngine::submitFrame() {
if (isUboBatchingEnabled()) { if (isUboBatchingEnabled()) {
DriverApi& driver = getDriverApi(); DriverApi& driver = getDriverApi();
getUboManager()->endFrame(driver, getMaterialInstanceResourceList()); getUboManager()->endFrame(driver);
} }
} }
@@ -992,10 +994,9 @@ FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
return p; return p;
} }
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, const char* name, FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
UboBatchingMode batchingMode) noexcept { const char* name) noexcept {
FMaterialInstance* p = FMaterialInstance* p = mHeapAllocator.make<FMaterialInstance>(*this, material, name);
mHeapAllocator.make<FMaterialInstance>(*this, material, name, batchingMode);
if (UTILS_LIKELY(p)) { if (UTILS_LIKELY(p)) {
auto pos = mMaterialInstances.emplace(material, "MaterialInstance"); auto pos = mMaterialInstances.emplace(material, "MaterialInstance");
pos.first->second.insert(p); pos.first->second.insert(p);
@@ -1285,11 +1286,6 @@ UTILS_NOINLINE
bool FEngine::destroy(const FMaterialInstance* p) { bool FEngine::destroy(const FMaterialInstance* p) {
if (p == nullptr) return true; if (p == nullptr) return true;
if (p->isUsingUboBatching()) {
assert_invariant(isUboBatchingEnabled());
mUboManager->retireSlot(p->getAllocationId());
}
// Check that the material instance we're destroying is not in use in the RenderableManager // Check that the material instance we're destroying is not in use in the RenderableManager
// To do this, we currently need to inspect all render primitives in the RenderableManager // To do this, we currently need to inspect all render primitives in the RenderableManager
EntityManager const& em = mEntityManager; EntityManager const& em = mEntityManager;
@@ -1509,6 +1505,11 @@ Engine::FeatureLevel FEngine::setActiveFeatureLevel(FeatureLevel featureLevel) {
return (mActiveFeatureLevel = std::max(mActiveFeatureLevel, featureLevel)); return (mActiveFeatureLevel = std::max(mActiveFeatureLevel, featureLevel));
} }
bool FEngine::isAsynchronousOperationSupported() const noexcept {
return features.backend.enable_asynchronous_operation &&
mConfig.asynchronousMode != AsynchronousMode::NONE;
}
#if defined(__EMSCRIPTEN__) #if defined(__EMSCRIPTEN__)
void FEngine::resetBackendState() noexcept { void FEngine::resetBackendState() noexcept {
getDriverApi().resetState(); getDriverApi().resetState();
+8 -13
View File
@@ -216,6 +216,8 @@ public:
return getDriver().isStereoSupported(); return getDriver().isStereoSupported();
} }
bool isAsynchronousOperationSupported() const noexcept;
static size_t getMaxStereoscopicEyes() noexcept { static size_t getMaxStereoscopicEyes() noexcept {
return CONFIG_MAX_STEREOSCOPIC_EYES; return CONFIG_MAX_STEREOSCOPIC_EYES;
} }
@@ -345,21 +347,10 @@ public:
FRenderer* createRenderer() noexcept; FRenderer* createRenderer() noexcept;
// Defines whether a material instance should use UBO batching or not.
enum class UboBatchingMode {
// For default, it follows the engine settings.
// If UBO batching is enabled on the engine and the material domain is not SURFACE, it
// turns on the UBO batching. Otherwise, it turns off the UBO batching.
DEFAULT,
NO_UBO_BATCHING,
UBO_BATCHING
};
FMaterialInstance* createMaterialInstance(const FMaterial* material, FMaterialInstance* createMaterialInstance(const FMaterial* material,
const FMaterialInstance* other, const char* name) noexcept; const FMaterialInstance* other, const char* name) noexcept;
FMaterialInstance* createMaterialInstance(const FMaterial* material, const char* name, FMaterialInstance* createMaterialInstance(const FMaterial* material, const char* name) noexcept;
UboBatchingMode batchingMode) noexcept;
FScene* createScene() noexcept; FScene* createScene() noexcept;
FView* createView() noexcept; FView* createView() noexcept;
@@ -780,7 +771,7 @@ public:
bool assert_texture_can_generate_mipmap = CORRECTNESS_ASSERTION_DEFAULT; bool assert_texture_can_generate_mipmap = CORRECTNESS_ASSERTION_DEFAULT;
} debug; } debug;
struct { struct {
bool disable_gpu_frame_complete_metric = false; bool disable_gpu_frame_complete_metric = true;
} frame_info; } frame_info;
} engine; } engine;
struct { struct {
@@ -797,6 +788,7 @@ public:
bool disable_amortized_shader_compile = true; bool disable_amortized_shader_compile = true;
bool disable_handle_use_after_free_check = false; bool disable_handle_use_after_free_check = false;
bool disable_heap_handle_tags = true; // FIXME: this should be false bool disable_heap_handle_tags = true; // FIXME: this should be false
bool enable_asynchronous_operation = false;
} backend; } backend;
struct { struct {
bool check_crc32_after_loading = false; bool check_crc32_after_loading = false;
@@ -817,6 +809,9 @@ public:
{ "backend.disable_heap_handle_tags", { "backend.disable_heap_handle_tags",
"Disable Handle<> tags for heap-allocated handles.", "Disable Handle<> tags for heap-allocated handles.",
&features.backend.disable_heap_handle_tags, true }, &features.backend.disable_heap_handle_tags, true },
{ "backend.enable_asynchronous_operation",
"Enable asynchronous operation for resource management.",
&features.backend.enable_asynchronous_operation, true },
{ "backend.opengl.assert_native_window_is_valid", { "backend.opengl.assert_native_window_is_valid",
"Asserts that the ANativeWindow is valid when rendering starts.", "Asserts that the ANativeWindow is valid when rendering starts.",
&features.backend.opengl.assert_native_window_is_valid, true }, &features.backend.opengl.assert_native_window_is_valid, true },
+22 -3
View File
@@ -70,10 +70,19 @@
namespace filament { namespace filament {
namespace {
using namespace backend; using namespace backend;
using namespace filaflat; using namespace filaflat;
using namespace utils; using namespace utils;
using UboBatchingMode = FEngine::UboBatchingMode; using UboBatchingMode = Material::UboBatchingMode;
bool shouldEnableBatching(FEngine& engine, UboBatchingMode batchingMode, MaterialDomain domain) {
return batchingMode != UboBatchingMode::DISABLED && engine.isUboBatchingEnabled() &&
domain == MaterialDomain::SURFACE;
}
} // anonymous namespace
struct Material::BuilderDetails { struct Material::BuilderDetails {
const void* mPayload = nullptr; const void* mPayload = nullptr;
@@ -81,6 +90,7 @@ struct Material::BuilderDetails {
bool mDefaultMaterial = false; bool mDefaultMaterial = false;
int32_t mShBandsCount = 3; int32_t mShBandsCount = 3;
Builder::ShadowSamplingQuality mShadowSamplingQuality = Builder::ShadowSamplingQuality::LOW; Builder::ShadowSamplingQuality mShadowSamplingQuality = Builder::ShadowSamplingQuality::LOW;
UboBatchingMode mUboBatchingMode = UboBatchingMode::DEFAULT;
std::unordered_map< std::unordered_map<
CString, CString,
std::variant<int32_t, float, bool>, std::variant<int32_t, float, bool>,
@@ -115,6 +125,11 @@ Material::Builder& Material::Builder::shadowSamplingQuality(ShadowSamplingQualit
return *this; return *this;
} }
Material::Builder& Material::Builder::uboBatching(UboBatchingMode const mode) noexcept {
mImpl->mUboBatchingMode = mode;
return *this;
}
template<typename T, typename> template<typename T, typename>
Material::Builder& Material::Builder::constant(const char* name, size_t nameLength, T value) { Material::Builder& Material::Builder::constant(const char* name, size_t nameLength, T value) {
FILAMENT_CHECK_PRECONDITION(name != nullptr) << "name cannot be null"; FILAMENT_CHECK_PRECONDITION(name != nullptr) << "name cannot be null";
@@ -148,8 +163,12 @@ Material* Material::Builder::build(Engine& engine) const {
FMaterial::FMaterial(FEngine& engine, const Builder& builder, MaterialDefinition const& definition) FMaterial::FMaterial(FEngine& engine, const Builder& builder, MaterialDefinition const& definition)
: mDefinition(definition), : mDefinition(definition),
mIsDefaultMaterial(builder->mDefaultMaterial), mIsDefaultMaterial(builder->mDefaultMaterial),
mUseUboBatching(shouldEnableBatching(engine, builder->mUboBatchingMode,
definition.materialDomain)),
mEngine(engine), mEngine(engine),
mMaterialId(engine.getMaterialId()) { mMaterialId(engine.getMaterialId()) {
FILAMENT_CHECK_PRECONDITION(!mUseUboBatching || engine.isUboBatchingEnabled())
<< "UBO batching is not enabled.";
mSpecializationConstants = processSpecializationConstants(builder); mSpecializationConstants = processSpecializationConstants(builder);
precacheDepthVariants(engine); precacheDepthVariants(engine);
@@ -282,14 +301,14 @@ FMaterialInstance* FMaterial::createInstance(const char* name) const noexcept {
return FMaterialInstance::duplicate(mDefaultMaterialInstance, name); return FMaterialInstance::duplicate(mDefaultMaterialInstance, name);
} else { } else {
// but if we don't, just create an instance with all the default parameters // but if we don't, just create an instance with all the default parameters
return mEngine.createMaterialInstance(this, name, UboBatchingMode::DEFAULT); return mEngine.createMaterialInstance(this, name);
} }
} }
FMaterialInstance* FMaterial::getDefaultInstance() noexcept { FMaterialInstance* FMaterial::getDefaultInstance() noexcept {
if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) { if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) {
mDefaultMaterialInstance = mDefaultMaterialInstance =
mEngine.createMaterialInstance(this, mDefinition.name.c_str(), UboBatchingMode::DEFAULT); mEngine.createMaterialInstance(this, mDefinition.name.c_str());
mDefaultMaterialInstance->setDefaultInstance(true); mDefaultMaterialInstance->setDefaultInstance(true);
} }
return mDefaultMaterialInstance; return mDefaultMaterialInstance;
+6
View File
@@ -248,6 +248,10 @@ public:
return mDefinition.perViewLayoutIndex; return mDefinition.perViewLayoutIndex;
} }
bool useUboBatching() const noexcept {
return mUseUboBatching;
}
#if FILAMENT_ENABLE_MATDBG #if FILAMENT_ENABLE_MATDBG
void applyPendingEdits() noexcept; void applyPendingEdits() noexcept;
@@ -310,6 +314,8 @@ private:
bool mIsDefaultMaterial = false; bool mIsDefaultMaterial = false;
bool mUseUboBatching = false;
// reserve some space to construct the default material instance // reserve some space to construct the default material instance
mutable FMaterialInstance* mDefaultMaterialInstance = nullptr; mutable FMaterialInstance* mDefaultMaterialInstance = nullptr;
+13 -47
View File
@@ -58,18 +58,8 @@ using namespace utils;
namespace filament { namespace filament {
using namespace backend; using namespace backend;
using UboBatchingMode = FEngine::UboBatchingMode;
bool shouldEnableBatching(FEngine& engine, UboBatchingMode batchingMode, MaterialDomain domain) { FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name) noexcept
if (batchingMode != UboBatchingMode::DEFAULT) {
return batchingMode == UboBatchingMode::UBO_BATCHING;
}
return engine.isUboBatchingEnabled() && domain == MaterialDomain::SURFACE;
}
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name,
UboBatchingMode batchingMode) noexcept
: mMaterial(material), : mMaterial(material),
mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()), mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()),
mCulling(CullingMode::BACK), mCulling(CullingMode::BACK),
@@ -80,12 +70,9 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
mHasScissor(false), mHasScissor(false),
mIsDoubleSided(false), mIsDoubleSided(false),
mIsDefaultInstance(false), mIsDefaultInstance(false),
mUseUboBatching( mUseUboBatching(material->useUboBatching()),
shouldEnableBatching(engine, batchingMode, material->getMaterialDomain())),
mTransparencyMode(TransparencyMode::DEFAULT), mTransparencyMode(TransparencyMode::DEFAULT),
mName(name ? CString(name) : material->getName()) { mName(name ? CString(name) : material->getName()) {
FILAMENT_CHECK_PRECONDITION(!mUseUboBatching || engine.isUboBatchingEnabled())
<< "UBO batching is not enabled.";
FEngine::DriverApi& driver = engine.getDriverApi(); FEngine::DriverApi& driver = engine.getDriverApi();
// even if the material doesn't have any parameters, we allocate a small UBO because it's // even if the material doesn't have any parameters, we allocate a small UBO because it's
@@ -95,6 +82,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
if (mUseUboBatching) { if (mUseUboBatching) {
mUboData = BufferAllocator::UNALLOCATED; mUboData = BufferAllocator::UNALLOCATED;
engine.getUboManager()->manageMaterialInstance(this);
} else { } else {
mUboData = driver.createBufferObject(mUniforms.getSize(), BufferObjectBinding::UNIFORM, mUboData = driver.createBufferObject(mUniforms.getSize(), BufferObjectBinding::UNIFORM,
BufferUsage::STATIC, ImmutableCString{ material->getName().c_str_safe() }); BufferUsage::STATIC, ImmutableCString{ material->getName().c_str_safe() });
@@ -159,7 +147,6 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
mUseUboBatching(other->mUseUboBatching), mUseUboBatching(other->mUseUboBatching),
mScissorRect(other->mScissorRect), mScissorRect(other->mScissorRect),
mName(name ? CString(name) : other->mName) { mName(name ? CString(name) : other->mName) {
assert_invariant(!mUseUboBatching || engine.isUboBatchingEnabled());
FEngine::DriverApi& driver = engine.getDriverApi(); FEngine::DriverApi& driver = engine.getDriverApi();
FMaterial const* const material = other->getMaterial(); FMaterial const* const material = other->getMaterial();
@@ -167,6 +154,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
if (mUseUboBatching) { if (mUseUboBatching) {
mUboData = BufferAllocator::UNALLOCATED; mUboData = BufferAllocator::UNALLOCATED;
engine.getUboManager()->manageMaterialInstance(this);
} else { } else {
mUboData = driver.createBufferObject(mUniforms.getSize(), BufferObjectBinding::UNIFORM, mUboData = driver.createBufferObject(mUniforms.getSize(), BufferObjectBinding::UNIFORM,
BufferUsage::DYNAMIC, ImmutableCString{ material->getName().c_str_safe() }); BufferUsage::DYNAMIC, ImmutableCString{ material->getName().c_str_safe() });
@@ -211,37 +199,16 @@ FMaterialInstance::~FMaterialInstance() noexcept = default;
void FMaterialInstance::terminate(FEngine& engine) { void FMaterialInstance::terminate(FEngine& engine) {
FEngine::DriverApi& driver = engine.getDriverApi(); FEngine::DriverApi& driver = engine.getDriverApi();
mDescriptorSet.terminate(driver); mDescriptorSet.terminate(driver);
if (mUseUboBatching) {
engine.getUboManager()->unmanageMaterialInstance(this);
}
auto* ubHandle = std::get_if<Handle<HwBufferObject>>(&mUboData); auto* ubHandle = std::get_if<Handle<HwBufferObject>>(&mUboData);
if (ubHandle){ if (ubHandle){
driver.destroyBufferObject(*ubHandle); driver.destroyBufferObject(*ubHandle);
} }
} }
void FMaterialInstance::commitStreamUniformAssociations(FEngine::DriverApi& driver) {
mHasStreamUniformAssociations = false;
if (!mTextureParameters.empty()) {
BufferObjectStreamDescriptor descriptor;
for (auto const& [binding, p]: mTextureParameters) {
ssize_t offset = mMaterial->getUniformInterfaceBlock().getTransformFieldOffset(binding);
if (offset >= 0) {
if (auto stream = p.texture->getStream()) {
mHasStreamUniformAssociations = true;
descriptor.mStreams.push_back({ uint32_t(offset), stream->getHandle(),
BufferObjectStreamAssociationType::TRANSFORM_MATRIX });
}
}
}
if (descriptor.mStreams.size() > 0) {
// UBO batching is incompatible with stream uniform associations because streams require a
// dedicated UBO handle, not a sub-allocation. Assert that any instance here uses its own UBO.
assert_invariant(!mUseUboBatching);
auto const* ubHandle = std::get_if<Handle<HwBufferObject>>(&mUboData);
assert_invariant(ubHandle);
driver.registerBufferObjectStreams(*ubHandle, std::move(descriptor));
}
}
}
void FMaterialInstance::commit(FEngine& engine) const { void FMaterialInstance::commit(FEngine& engine) const {
if (UTILS_LIKELY(mMaterial->getMaterialDomain() != MaterialDomain::SURFACE)) { if (UTILS_LIKELY(mMaterial->getMaterialDomain() != MaterialDomain::SURFACE)) {
commit(engine.getDriverApi(), engine.getUboManager()); commit(engine.getDriverApi(), engine.getUboManager());
@@ -249,10 +216,9 @@ void FMaterialInstance::commit(FEngine& engine) const {
} }
void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManager) const { void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManager) const {
if (mUniforms.isDirty() || mHasStreamUniformAssociations) { if (mUniforms.isDirty()) {
mUniforms.clean(); mUniforms.clean();
if (mUseUboBatching) { if (isUsingUboBatching()) {
assert_invariant(uboManager != nullptr);
if (!BufferAllocator::isValid(getAllocationId())) { if (!BufferAllocator::isValid(getAllocationId())) {
// The allocation hasn't happened yet, return. // The allocation hasn't happened yet, return.
return; return;
@@ -283,7 +249,7 @@ void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManage
// TODO: eventually we should remove this in RELEASE builds // TODO: eventually we should remove this in RELEASE builds
fixMissingSamplers(); fixMissingSamplers();
if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) { if (isUsingUboBatching() && !BufferAllocator::isValid(getAllocationId())) {
return; return;
} }
@@ -433,7 +399,7 @@ void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const {
return; return;
} }
if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) { if (isUsingUboBatching() && !BufferAllocator::isValid(getAllocationId())) {
return; return;
} }
@@ -470,7 +436,7 @@ void FMaterialInstance::assignUboAllocation(
const Handle<HwBufferObject>& ubHandle, const Handle<HwBufferObject>& ubHandle,
BufferAllocator::AllocationId id, BufferAllocator::AllocationId id,
BufferAllocator::allocation_size_t offset) { BufferAllocator::allocation_size_t offset) {
assert_invariant(mUseUboBatching); assert_invariant(isUsingUboBatching());
mUboData = id; mUboData = id;
mUboOffset = offset; mUboOffset = offset;
+1 -5
View File
@@ -56,8 +56,7 @@ class FTexture;
class FMaterialInstance : public MaterialInstance { class FMaterialInstance : public MaterialInstance {
public: public:
FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name, FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name) noexcept;
FEngine::UboBatchingMode batchingMode) noexcept;
FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name); FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name);
FMaterialInstance(const FMaterialInstance& rhs) = delete; FMaterialInstance(const FMaterialInstance& rhs) = delete;
FMaterialInstance& operator=(const FMaterialInstance& rhs) = delete; FMaterialInstance& operator=(const FMaterialInstance& rhs) = delete;
@@ -67,8 +66,6 @@ public:
~FMaterialInstance() noexcept; ~FMaterialInstance() noexcept;
void terminate(FEngine& engine); void terminate(FEngine& engine);
void commitStreamUniformAssociations(FEngine::DriverApi& driver);
void commit(FEngine& engine) const; void commit(FEngine& engine) const;
@@ -288,7 +285,6 @@ private:
tsl::robin_map<backend::descriptor_binding_t, TextureParameter> mTextureParameters; tsl::robin_map<backend::descriptor_binding_t, TextureParameter> mTextureParameters;
mutable DescriptorSet mDescriptorSet; mutable DescriptorSet mDescriptorSet;
UniformBuffer mUniforms; UniformBuffer mUniforms;
bool mHasStreamUniformAssociations = false;
backend::PolygonOffset mPolygonOffset{}; backend::PolygonOffset mPolygonOffset{};
backend::StencilState mStencilState{}; backend::StencilState mStencilState{};
+8 -20
View File
@@ -292,16 +292,11 @@ void FRenderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) {
// do this before engine.flush() // do this before engine.flush()
mResourceAllocator->gc(true); mResourceAllocator->gc(true);
// Run the component managers' GC in parallel
// WARNING: while doing this we can't access any component manager
auto& js = engine.getJobSystem();
auto *job = js.runAndRetain(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers
engine.flush(); // flush command stream engine.flush(); // flush command stream
// make sure we're done with the gcs // Run the component managers' GC
js.waitAndRelease(job); auto& js = engine.getJobSystem();
js.runAndWait(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers
} }
bool FRenderer::shouldRenderFrame() const noexcept { bool FRenderer::shouldRenderFrame() const noexcept {
@@ -468,16 +463,11 @@ void FRenderer::endFrame() {
// do this before engine.flush() // do this before engine.flush()
mResourceAllocator->gc(); mResourceAllocator->gc();
// Run the component managers' GC in parallel
// WARNING: while doing this we can't access any component manager
auto& js = engine.getJobSystem();
auto *job = js.runAndRetain(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers
engine.flush(); // flush command stream engine.flush(); // flush command stream
// make sure we're done with the gcs // Run the component managers' GC
js.waitAndRelease(job); auto& js = engine.getJobSystem();
js.runAndWait(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers
} }
void FRenderer::readPixels( void FRenderer::readPixels(
@@ -571,7 +561,6 @@ void FRenderer::renderStandaloneView(FView const* view) {
if (UTILS_LIKELY(view->getScene())) { if (UTILS_LIKELY(view->getScene())) {
mPreviousRenderTargets.clear(); mPreviousRenderTargets.clear();
mFrameId++;
// ask the engine to do what it needs to (e.g. updates light buffer, materials...) // ask the engine to do what it needs to (e.g. updates light buffer, materials...)
FEngine& engine = mEngine; FEngine& engine = mEngine;
@@ -581,8 +570,7 @@ void FRenderer::renderStandaloneView(FView const* view) {
driver.beginFrame( driver.beginFrame(
steady_clock::now().time_since_epoch().count(), steady_clock::now().time_since_epoch().count(),
mDisplayInfo.refreshRate == 0.0 ? 0 : int64_t( mDisplayInfo.refreshRate == 0.0 ? 0 : int64_t(
1'000'000'000.0 / mDisplayInfo.refreshRate), 1'000'000'000.0 / mDisplayInfo.refreshRate), 0);
mFrameId);
// because we don't have a "present" call, we use flush so the driver can submit // because we don't have a "present" call, we use flush so the driver can submit
// the command buffer; we do this before driver.endFrame() to mimic what would // the command buffer; we do this before driver.endFrame() to mimic what would
@@ -591,7 +579,7 @@ void FRenderer::renderStandaloneView(FView const* view) {
engine.submitFrame(); engine.submitFrame();
driver.endFrame(mFrameId); driver.endFrame(0);
// engine.flush() has already been called by renderInternal(), but we need an extra one // engine.flush() has already been called by renderInternal(), but we need an extra one
// for endFrame() above. This operation in actually not too heavy, it just kicks the // for endFrame() above. This operation in actually not too heavy, it just kicks the
+1 -1
View File
@@ -209,7 +209,7 @@ private:
backend::Handle<backend::HwRenderTarget> mRenderTargetHandle; backend::Handle<backend::HwRenderTarget> mRenderTargetHandle;
FSwapChain* mSwapChain = nullptr; FSwapChain* mSwapChain = nullptr;
size_t mCommandsHighWatermark = 0; size_t mCommandsHighWatermark = 0;
uint32_t mFrameId = 0; uint32_t mFrameId = 1; // id 0 is reserved for standalone views
FrameInfoManager mFrameInfoManager; FrameInfoManager mFrameInfoManager;
backend::TextureFormat mHdrTranslucent; backend::TextureFormat mHdrTranslucent;
backend::TextureFormat mHdrQualityMedium; backend::TextureFormat mHdrQualityMedium;

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