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
285 changed files with 35458 additions and 12508 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.
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
} }
dependencies { dependencies {
implementation 'com.google.android.filament:filament-android:1.67.1' 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.1' pod 'Filament', '~> 1.68.0'
``` ```
## Documentation ## Documentation
+3
View File
@@ -7,6 +7,9 @@ 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 ## v1.68.0
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622) - engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
+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);
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament GROUP=com.google.android.filament
VERSION_NAME=1.67.1 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.67.1' 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.67.1' <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>
+268 -270
View File
File diff suppressed because one or more lines are too long
+162 -164
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>
@@ -74,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; }
@@ -252,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.
@@ -321,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;
@@ -30,8 +30,6 @@
#include <math/mat3.h> #include <math/mat3.h>
#include "AndroidNativeWindow.h"
#include <chrono> #include <chrono>
#include <stddef.h> #include <stddef.h>
@@ -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,
+6 -6
View File
@@ -1144,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() {
@@ -1286,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))
+1 -1
View File
@@ -1684,7 +1684,7 @@ MetalMemoryMappedBuffer::~MetalMemoryMappedBuffer() = default;
void MetalMemoryMappedBuffer::unmap() { void MetalMemoryMappedBuffer::unmap() {
#if !defined(FILAMENT_IOS) && defined(__x86_64__) #if !defined(FILAMENT_IOS) && defined(__x86_64__)
// Managed memory requires didModifyRange to synchronize changes to the GPU. This is specific to Intel Macs. // Managed memory requires didModifyRange to synchronize changes to the GPU. This is specific to Intel Macs.
MetalBuffer* buffer = bo->getBuffer(); MetalBuffer* buffer = mtl.bo->getBuffer();
id<MTLBuffer> mtlBuffer = buffer->getGpuBufferForDraw(); id<MTLBuffer> mtlBuffer = buffer->getGpuBufferForDraw();
if (mtlBuffer && mtlBuffer.storageMode == MTLStorageModeManaged) { if (mtlBuffer && mtlBuffer.storageMode == MTLStorageModeManaged) {
[mtlBuffer didModifyRange:NSMakeRange(mtl.offset, mtl.size)]; [mtlBuffer didModifyRange:NSMakeRange(mtl.offset, mtl.size)];
@@ -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>
@@ -178,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));
@@ -440,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;
} }
@@ -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 -12
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);
} }
@@ -1108,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) {
@@ -1145,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) {
@@ -1164,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) {
@@ -1459,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));
@@ -1537,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) {
@@ -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"
@@ -157,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
@@ -202,6 +206,10 @@ private:
bool const mIsSRGBSwapChainSupported; bool const mIsSRGBSwapChainSupported;
bool const mIsMSAASwapChainSupported; bool const mIsMSAASwapChainSupported;
backend::StereoscopicType const mStereoscopicType; backend::StereoscopicType const mStereoscopicType;
// setAcquiredImage is a DECL_DRIVER_API_SYNCHRONOUS_N which means we don't necessarily have the
// 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;
} }
@@ -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>
@@ -65,7 +66,7 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
// of 4 by padding with zeros. // of 4 by padding with zeros.
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& 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,15 +86,12 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& 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,
.mappedAtCreation = true };
wgpu::Buffer stagingBuffer = device.CreateBuffer(&descriptor);
void* mappedRange = stagingBuffer.GetMappedRange(); void* mappedRange = stagingBuffer.GetMappedRange();
assert_invariant(mappedRange);
memcpy(mappedRange, bufferDescriptor.buffer, bufferDescriptor.size); memcpy(mappedRange, bufferDescriptor.buffer, bufferDescriptor.size);
// Make sure the padded memory is set to 0 to have deterministic behaviors // Make sure the padded memory is set to 0 to have deterministic behaviors
@@ -106,7 +104,9 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
// Copy the staging buffer contents to the destination buffer. // Copy the staging buffer contents to the destination buffer.
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stagingBuffer, 0, mBuffer, webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stagingBuffer, 0, mBuffer,
byteOffset, stagingBufferSize); byteOffset,
remainder == 0 ? bufferDescriptor.size
: mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS);
} }
} // 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
@@ -40,7 +41,7 @@ public:
* ensures the calls happen in the expected sequence. * ensures the calls happen in the expected sequence.
*/ */
void updateGPUBuffer(BufferDescriptor const&, 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(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(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(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;
@@ -64,6 +64,7 @@ wgpu::CommandEncoder WebGPUQueueManager::getCommandEncoder() {
}; };
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;
} }
@@ -94,8 +95,6 @@ void WebGPUQueueManager::submit() {
return; return;
} }
mLatestSubmissionState = std::make_shared<WebGPUSubmissionState>();
wgpu::CommandBufferDescriptor commandBufferDescriptor{ wgpu::CommandBufferDescriptor commandBufferDescriptor{
.label = "Filament Command Buffer", .label = "Filament Command Buffer",
}; };
@@ -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.
* *
+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;
}; };
+13 -6
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
@@ -727,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);
} }
}); });
@@ -991,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);
@@ -1503,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;
+7 -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
@@ -160,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();
@@ -223,31 +209,6 @@ void FMaterialInstance::terminate(FEngine& engine) {
} }
} }
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());
@@ -255,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;
@@ -289,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;
} }
@@ -439,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;
} }
@@ -476,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;
+5 -4
View File
@@ -39,7 +39,7 @@ using allocation_size_t = BufferAllocator::allocation_size_t;
// FenceManager // FenceManager
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
void UboManager::FenceManager::track(DriverApi& driver, std::unordered_set<AllocationId>&& allocationIds) { void UboManager::FenceManager::track(DriverApi& driver, AllocationIdContainer&& allocationIds) {
if (allocationIds.empty()) { if (allocationIds.empty()) {
return; return;
} }
@@ -159,15 +159,16 @@ void UboManager::finishBeginFrame(DriverApi& driver) {
} }
void UboManager::endFrame(DriverApi& driver) { void UboManager::endFrame(DriverApi& driver) {
std::unordered_set<AllocationId> allocationIds; auto allocationIds =
for (const auto* mi : mManagedInstances) { FenceManager::AllocationIdContainer::with_capacity(mManagedInstances.size());
for (const auto* mi: mManagedInstances) {
const AllocationId id = mi->getAllocationId(); const AllocationId id = mi->getAllocationId();
if (UTILS_UNLIKELY(!BufferAllocator::isValid(id))) { if (UTILS_UNLIKELY(!BufferAllocator::isValid(id))) {
continue; continue;
} }
mAllocator.acquireGpu(id); mAllocator.acquireGpu(id);
allocationIds.insert(id); allocationIds.push_back(id);
} }
mFenceManager.track(driver, std::move(allocationIds)); mFenceManager.track(driver, std::move(allocationIds));
+3 -2
View File
@@ -55,6 +55,7 @@ public:
class FenceManager { class FenceManager {
public: public:
using AllocationId = BufferAllocator::AllocationId; using AllocationId = BufferAllocator::AllocationId;
using AllocationIdContainer = utils::FixedCapacityVector<AllocationId>;
FenceManager() = default; FenceManager() = default;
~FenceManager() = default; ~FenceManager() = default;
@@ -65,7 +66,7 @@ public:
// Creates a new fence to track a set of allocation IDs for the current frame. // Creates a new fence to track a set of allocation IDs for the current frame.
// This marks the beginning of GPU's usage of these resources. // This marks the beginning of GPU's usage of these resources.
void track(backend::DriverApi& driver, std::unordered_set<AllocationId>&& allocationIds); void track(backend::DriverApi& driver, AllocationIdContainer&& allocationIds);
// Checks all tracked fences and invokes a callback for resources associated with // Checks all tracked fences and invokes a callback for resources associated with
@@ -80,7 +81,7 @@ public:
private: private:
// Not ideal, but we need to know which slots to decrement gpuUseCount for each frame. // Not ideal, but we need to know which slots to decrement gpuUseCount for each frame.
using FenceAndAllocations = using FenceAndAllocations =
std::pair<backend::Handle<backend::HwFence>, std::unordered_set<AllocationId>>; std::pair<backend::Handle<backend::HwFence>, AllocationIdContainer>;
std::vector<FenceAndAllocations> mFenceAllocationList; std::vector<FenceAndAllocations> mFenceAllocationList;
}; };
+15 -1
View File
@@ -13,11 +13,25 @@ set(GENERATION_ROOT ${CMAKE_CURRENT_BINARY_DIR})
set(RESOURCE_DIR "${GENERATION_ROOT}/resources") set(RESOURCE_DIR "${GENERATION_ROOT}/resources")
file(MAKE_DIRECTORY ${RESOURCE_DIR}) file(MAKE_DIRECTORY ${RESOURCE_DIR})
set(MATERIAL_DIR "${GENERATION_ROOT}/material")
file(MAKE_DIRECTORY ${MATERIAL_DIR})
set(SANDBOX_TRANSFORM_NAME_MAT ${CMAKE_CURRENT_SOURCE_DIR}/sandboxTransformName.mat)
set(TEST_MATERIAL_TRANSFORM_NAME_FILAMAT ${MATERIAL_DIR}/test_material_transformname.filamat)
add_custom_command(
OUTPUT ${TEST_MATERIAL_TRANSFORM_NAME_FILAMAT}
COMMAND matc ${MATC_BASE_FLAGS} -o ${TEST_MATERIAL_TRANSFORM_NAME_FILAMAT} ${SANDBOX_TRANSFORM_NAME_MAT}
MAIN_DEPENDENCY ${SANDBOX_TRANSFORM_NAME_MAT}
DEPENDS matc
COMMENT "Compiling material ${SANDBOX_TRANSFORM_NAME_MAT}"
)
get_resgen_vars(${RESOURCE_DIR} filament_test_resources) get_resgen_vars(${RESOURCE_DIR} filament_test_resources)
set(RESOURCE_BINS set(RESOURCE_BINS
${CMAKE_CURRENT_SOURCE_DIR}/test_material.filamat ${CMAKE_CURRENT_SOURCE_DIR}/test_material.filamat
${CMAKE_CURRENT_SOURCE_DIR}/test_material_transformname.filamat ${TEST_MATERIAL_TRANSFORM_NAME_FILAMAT}
) )
add_custom_command( add_custom_command(
Binary file not shown.
Binary file not shown.
-1
View File
@@ -11,7 +11,6 @@ OUTPUT_DIR="filament/test/"
./build.sh -p desktop release matc ./build.sh -p desktop release matc
MATC=out/cmake-release/tools/matc/matc MATC=out/cmake-release/tools/matc/matc
${MATC} --platform all --api all -o ${OUTPUT_DIR}/test_material.filamat samples/materials/sandboxLit.mat ${MATC} --platform all --api all -o ${OUTPUT_DIR}/test_material.filamat samples/materials/sandboxLit.mat
${MATC} --platform all --api all -o ${OUTPUT_DIR}/test_material_transformname.filamat samples/materials/sandboxTransformName.mat
set +x set +x
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec| Pod::Spec.new do |spec|
spec.name = "Filament" spec.name = "Filament"
spec.version = "1.67.1" spec.version = "1.68.0"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament" spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC." spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0" spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.67.1/filament-v1.67.1-ios.tgz" } spec.source = { :http => "https://github.com/google/filament/releases/download/v1.68.0/filament-v1.68.0-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = { spec.pod_target_xcconfig = {
@@ -28,7 +28,7 @@
namespace filament { namespace filament {
// update this when a new version of filament wouldn't work with older materials // update this when a new version of filament wouldn't work with older materials
static constexpr size_t MATERIAL_VERSION = 67; static constexpr size_t MATERIAL_VERSION = 68;
/** /**
* Supported shading models * Supported shading models
@@ -170,10 +170,6 @@ public:
// negative value if name doesn't exist or Panic if exceptions are enabled // negative value if name doesn't exist or Panic if exceptions are enabled
ssize_t getFieldOffset(std::string_view name, size_t index) const; ssize_t getFieldOffset(std::string_view name, size_t index) const;
// returns offset in bytes of the transform matrix for the given external texture binding
// returns -1 if the field doesn't exist
ssize_t getTransformFieldOffset(uint8_t binding) const;
FieldInfo const* getFieldInfo(std::string_view name) const; FieldInfo const* getFieldInfo(std::string_view name) const;
bool hasField(std::string_view name) const noexcept { bool hasField(std::string_view name) const noexcept {
@@ -201,7 +197,6 @@ private:
utils::CString mName; utils::CString mName;
utils::FixedCapacityVector<FieldInfo> mFieldInfoList; utils::FixedCapacityVector<FieldInfo> mFieldInfoList;
std::unordered_map<std::string_view , uint32_t> mInfoMap; std::unordered_map<std::string_view , uint32_t> mInfoMap;
std::unordered_map<uint8_t, uint32_t> mTransformOffsetMap;
uint32_t mSize = 0; // size in bytes rounded to multiple of 4 uint32_t mSize = 0; // size in bytes rounded to multiple of 4
Alignment mAlignment = Alignment::std140; Alignment mAlignment = Alignment::std140;
Target mTarget = Target::UNIFORM; Target mTarget = Target::UNIFORM;
@@ -122,8 +122,6 @@ BufferInterfaceBlock::BufferInterfaceBlock(Builder const& builder) noexcept
auto& uniformsInfoList = mFieldInfoList; auto& uniformsInfoList = mFieldInfoList;
auto& transformOffsetMap = mTransformOffsetMap;
uint32_t i = 0; uint32_t i = 0;
uint16_t offset = 0; uint16_t offset = 0;
for (auto const& e : builder.mEntries) { for (auto const& e : builder.mEntries) {
@@ -150,11 +148,6 @@ BufferInterfaceBlock::BufferInterfaceBlock(Builder const& builder) noexcept
// record this uniform info // record this uniform info
infoMap[{ info.name.data(), info.name.size() }] = i; infoMap[{ info.name.data(), info.name.size() }] = i;
// record the transform offset if this is an external sampler
if (e.associatedSampler != 0) {
transformOffsetMap[e.associatedSampler] = offset;
}
// advance offset to next slot // advance offset to next slot
offset += stride * std::max(1u, e.size); offset += stride * std::max(1u, e.size);
++i; ++i;
@@ -170,14 +163,6 @@ ssize_t BufferInterfaceBlock::getFieldOffset(std::string_view name, size_t index
return (ssize_t)info->getBufferOffset(index); return (ssize_t)info->getBufferOffset(index);
} }
ssize_t BufferInterfaceBlock::getTransformFieldOffset(uint8_t binding) const {
auto pos = mTransformOffsetMap.find(binding);
if (pos == mTransformOffsetMap.end()) {
return -1;
}
return (ssize_t)pos->second * sizeof(uint32_t);
}
BufferInterfaceBlock::FieldInfo const* BufferInterfaceBlock::getFieldInfo( BufferInterfaceBlock::FieldInfo const* BufferInterfaceBlock::getFieldInfo(
std::string_view name) const { std::string_view name) const {
auto pos = mInfoMap.find(name); auto pos = mInfoMap.find(name);
+5 -3
View File
@@ -17,9 +17,6 @@
#ifndef FILAGUI_IMGUIHELPER_H_ #ifndef FILAGUI_IMGUIHELPER_H_
#define FILAGUI_IMGUIHELPER_H_ #define FILAGUI_IMGUIHELPER_H_
#include <vector>
#include <functional>
#include <filament/Engine.h> #include <filament/Engine.h>
#include <filament/IndexBuffer.h> #include <filament/IndexBuffer.h>
#include <filament/Material.h> #include <filament/Material.h>
@@ -32,6 +29,10 @@
#include <utils/Entity.h> #include <utils/Entity.h>
#include <utils/Path.h> #include <utils/Path.h>
#include <functional>
#include <vector>
#include <unordered_set>
struct ImDrawData; struct ImDrawData;
struct ImGuiIO; struct ImGuiIO;
struct ImGuiContext; struct ImGuiContext;
@@ -100,6 +101,7 @@ public:
filament::TextureSampler mSampler; filament::TextureSampler mSampler;
bool mFlipVertical = false; bool mFlipVertical = false;
utils::Path mSettingsPath; utils::Path mSettingsPath;
std::unordered_set<filament::Texture*> mImGuiTextures;
}; };
} // namespace filagui } // namespace filagui
+49 -1
View File
@@ -148,6 +148,12 @@ ImGuiHelper::~ImGuiHelper() {
mEngine->destroy(ib); mEngine->destroy(ib);
} }
// These are textures that have been created by imgui but were not explicitly freed before
// shutdown.
for (auto itextures: mImGuiTextures) {
mEngine->destroy(itextures);
}
EntityManager& em = utils::EntityManager::get(); EntityManager& em = utils::EntityManager::get();
em.destroy(mRenderable); em.destroy(mRenderable);
em.destroy(mCameraEntity); em.destroy(mCameraEntity);
@@ -189,6 +195,48 @@ void ImGuiHelper::render(float timeStepInSeconds, Callback imguiCommands) {
void ImGuiHelper::processImGuiCommands(ImDrawData* commands, const ImGuiIO& io) { void ImGuiHelper::processImGuiCommands(ImDrawData* commands, const ImGuiIO& io) {
ImGui::SetCurrentContext(mImGuiContext); ImGui::SetCurrentContext(mImGuiContext);
if (commands->Textures != nullptr) {
for (ImTextureData* tex : *commands->Textures) {
if (tex->Status == ImTextureStatus_OK) {
continue;
} else if (tex->Status == ImTextureStatus_WantCreate) {
IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr);
Texture* ftex = Texture::Builder()
.width(tex->Width)
.height(tex->Height)
.levels(1)
.format(Texture::InternalFormat::RGBA8)
.sampler(Texture::Sampler::SAMPLER_2D)
.build(*mEngine);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
const int size = tex->Width * tex->Height * 4;
Texture::PixelBufferDescriptor pb(tex->GetPixels(), size,
Texture::Format::RGBA,
Texture::Type::UBYTE);
ftex->setImage(*mEngine, 0, std::move(pb));
tex->SetTexID((ImTextureID)ftex);
tex->SetStatus(ImTextureStatus_OK);
mImGuiTextures.insert(ftex);
} else if (tex->Status == ImTextureStatus_WantUpdates) {
const int size = tex->Width * tex->Height * 4;
Texture::PixelBufferDescriptor pb(tex->GetPixels(), size,
Texture::Format::RGBA,
Texture::Type::UBYTE);
filament::Texture* ftex = (filament::Texture*)tex->TexID;
ftex->setImage(*mEngine, 0, std::move(pb));
tex->SetStatus(ImTextureStatus_OK);
} else if (tex->Status == ImTextureStatus_WantDestroy &&
tex->UnusedFrames > 0) {
filament::Texture* ftex = (filament::Texture*)tex->TexID;
mEngine->destroy(ftex);
tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
mImGuiTextures.erase(ftex);
}
}
}
mHasSynced = false; mHasSynced = false;
auto& rcm = mEngine->getRenderableManager(); auto& rcm = mEngine->getRenderableManager();
@@ -228,7 +276,7 @@ void ImGuiHelper::processImGuiCommands(ImDrawData* commands, const ImGuiIO& io)
if (pcmd.UserCallback) { if (pcmd.UserCallback) {
pcmd.UserCallback(cmds, &pcmd); pcmd.UserCallback(cmds, &pcmd);
} else { } else {
auto texture = (Texture const*)pcmd.TextureId; auto texture = (Texture const*)pcmd.GetTexID();
MaterialInstance* materialInstance; MaterialInstance* materialInstance;
#ifdef __ANDROID__ #ifdef __ANDROID__
if (texture && texture->getTarget() == Texture::Sampler::SAMPLER_EXTERNAL) { if (texture && texture->getTarget() == Texture::Sampler::SAMPLER_EXTERNAL) {
+10 -2
View File
@@ -37,9 +37,13 @@ set(SRCS
src/IcoSphere.cpp src/IcoSphere.cpp
src/MeshAssimp.cpp src/MeshAssimp.cpp
src/Sphere.cpp src/Sphere.cpp
src/VulkanPlatformHelper.cpp src/KeyInputConversion.h
) )
if (FILAMENT_SUPPORTS_VULKAN)
list(APPEND SRCS src/VulkanPlatformHelper.cpp)
endif()
set(LIBS set(LIBS
assimp assimp
camutils camutils
@@ -79,7 +83,11 @@ endif()
if (WIN32) if (WIN32)
list(APPEND SRCS src/NativeWindowHelperWindows.cpp) list(APPEND SRCS src/NativeWindowHelperWindows.cpp)
list(APPEND SRCS src/VulkanPlatformHelperWindows.cpp)
if (FILAMENT_SUPPORTS_VULKAN)
list(APPEND SRCS src/VulkanPlatformHelperWindows.cpp)
endif()
list(APPEND LIBS sdl2main) list(APPEND LIBS sdl2main)
endif() endif()
+15 -30
View File
@@ -16,6 +16,8 @@
#include <filamentapp/FilamentApp.h> #include <filamentapp/FilamentApp.h>
#include "KeyInputConversion.h"
#if defined(WIN32) #if defined(WIN32)
# include <SDL_syswm.h> # include <SDL_syswm.h>
# include <utils/unwindows.h> # include <utils/unwindows.h>
@@ -211,27 +213,6 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
SDL_GetWindowWMInfo(window->getSDLWindow(), &wmInfo); SDL_GetWindowWMInfo(window->getSDLWindow(), &wmInfo);
ImGui::GetMainViewport()->PlatformHandleRaw = wmInfo.info.win.window; ImGui::GetMainViewport()->PlatformHandleRaw = wmInfo.info.win.window;
#endif #endif
io.KeyMap[ImGuiKey_Tab] = SDL_SCANCODE_TAB;
io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT;
io.KeyMap[ImGuiKey_RightArrow] = SDL_SCANCODE_RIGHT;
io.KeyMap[ImGuiKey_UpArrow] = SDL_SCANCODE_UP;
io.KeyMap[ImGuiKey_DownArrow] = SDL_SCANCODE_DOWN;
io.KeyMap[ImGuiKey_PageUp] = SDL_SCANCODE_PAGEUP;
io.KeyMap[ImGuiKey_PageDown] = SDL_SCANCODE_PAGEDOWN;
io.KeyMap[ImGuiKey_Home] = SDL_SCANCODE_HOME;
io.KeyMap[ImGuiKey_End] = SDL_SCANCODE_END;
io.KeyMap[ImGuiKey_Insert] = SDL_SCANCODE_INSERT;
io.KeyMap[ImGuiKey_Delete] = SDL_SCANCODE_DELETE;
io.KeyMap[ImGuiKey_Backspace] = SDL_SCANCODE_BACKSPACE;
io.KeyMap[ImGuiKey_Space] = SDL_SCANCODE_SPACE;
io.KeyMap[ImGuiKey_Enter] = SDL_SCANCODE_RETURN;
io.KeyMap[ImGuiKey_Escape] = SDL_SCANCODE_ESCAPE;
io.KeyMap[ImGuiKey_A] = SDL_SCANCODE_A;
io.KeyMap[ImGuiKey_C] = SDL_SCANCODE_C;
io.KeyMap[ImGuiKey_V] = SDL_SCANCODE_V;
io.KeyMap[ImGuiKey_X] = SDL_SCANCODE_X;
io.KeyMap[ImGuiKey_Y] = SDL_SCANCODE_Y;
io.KeyMap[ImGuiKey_Z] = SDL_SCANCODE_Z;
io.SetClipboardTextFn = [](void*, const char* text) { io.SetClipboardTextFn = [](void*, const char* text) {
SDL_SetClipboardText(text); SDL_SetClipboardText(text);
}; };
@@ -305,15 +286,19 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
io.AddInputCharactersUTF8(event->text.text); io.AddInputCharactersUTF8(event->text.text);
break; break;
} }
case SDL_KEYDOWN: case SDL_KEYUP:
case SDL_KEYUP: { case SDL_KEYDOWN: {
int key = event->key.keysym.scancode; SDL_Scancode const scancode = event->key.keysym.scancode;
IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown)); SDL_Keycode const keycode = event->key.keysym.sym;
io.KeysDown[key] = (event->type == SDL_KEYDOWN);
io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0); auto modState = SDL_GetModState();
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0); io.AddKeyEvent(ImGuiMod_Ctrl, (modState & KMOD_CTRL) != 0);
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0); io.AddKeyEvent(ImGuiMod_Shift, (modState & KMOD_SHIFT) != 0);
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0); io.AddKeyEvent(ImGuiMod_Alt, (modState & KMOD_ALT) != 0);
io.AddKeyEvent(ImGuiMod_Super, (modState & KMOD_GUI) != 0);
io.AddKeyEvent(
filamentapp_utils::ImGui_ImplSDL2_KeyEventToImGuiKey(keycode, scancode),
event->type == SDL_KEYDOWN);
break; break;
} }
} }
+297
View File
@@ -0,0 +1,297 @@
/*
* 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_FILAMENTAPP_KEYINPUTCONVERSION_H
#define TNT_FILAMENT_FILAMENTAPP_KEYINPUTCONVERSION_H
#include <imgui.h>
#include <SDL.h>
namespace filamentapp_utils {
// Copied from
// https://github.com/ocornut/imgui/blob/940627d008b8f0584b7f50d24574537cf24a32e1/backends/imgui_impl_sdl2.cpp#L206
ImGuiKey ImGui_ImplSDL2_KeyEventToImGuiKey(SDL_Keycode keycode, SDL_Scancode scancode) {
switch (keycode) {
case SDLK_TAB:
return ImGuiKey_Tab;
case SDLK_LEFT:
return ImGuiKey_LeftArrow;
case SDLK_RIGHT:
return ImGuiKey_RightArrow;
case SDLK_UP:
return ImGuiKey_UpArrow;
case SDLK_DOWN:
return ImGuiKey_DownArrow;
case SDLK_PAGEUP:
return ImGuiKey_PageUp;
case SDLK_PAGEDOWN:
return ImGuiKey_PageDown;
case SDLK_HOME:
return ImGuiKey_Home;
case SDLK_END:
return ImGuiKey_End;
case SDLK_INSERT:
return ImGuiKey_Insert;
case SDLK_DELETE:
return ImGuiKey_Delete;
case SDLK_BACKSPACE:
return ImGuiKey_Backspace;
case SDLK_SPACE:
return ImGuiKey_Space;
case SDLK_RETURN:
return ImGuiKey_Enter;
case SDLK_ESCAPE:
return ImGuiKey_Escape;
// case SDLK_QUOTE: return ImGuiKey_Apostrophe;
case SDLK_COMMA:
return ImGuiKey_Comma;
// case SDLK_MINUS: return ImGuiKey_Minus;
case SDLK_PERIOD:
return ImGuiKey_Period;
// case SDLK_SLASH: return ImGuiKey_Slash;
case SDLK_SEMICOLON:
return ImGuiKey_Semicolon;
// case SDLK_EQUALS: return ImGuiKey_Equal;
// case SDLK_LEFTBRACKET: return ImGuiKey_LeftBracket;
// case SDLK_BACKSLASH: return ImGuiKey_Backslash;
// case SDLK_RIGHTBRACKET: return ImGuiKey_RightBracket;
// case SDLK_BACKQUOTE: return ImGuiKey_GraveAccent;
case SDLK_CAPSLOCK:
return ImGuiKey_CapsLock;
case SDLK_SCROLLLOCK:
return ImGuiKey_ScrollLock;
case SDLK_NUMLOCKCLEAR:
return ImGuiKey_NumLock;
case SDLK_PRINTSCREEN:
return ImGuiKey_PrintScreen;
case SDLK_PAUSE:
return ImGuiKey_Pause;
case SDLK_KP_0:
return ImGuiKey_Keypad0;
case SDLK_KP_1:
return ImGuiKey_Keypad1;
case SDLK_KP_2:
return ImGuiKey_Keypad2;
case SDLK_KP_3:
return ImGuiKey_Keypad3;
case SDLK_KP_4:
return ImGuiKey_Keypad4;
case SDLK_KP_5:
return ImGuiKey_Keypad5;
case SDLK_KP_6:
return ImGuiKey_Keypad6;
case SDLK_KP_7:
return ImGuiKey_Keypad7;
case SDLK_KP_8:
return ImGuiKey_Keypad8;
case SDLK_KP_9:
return ImGuiKey_Keypad9;
case SDLK_KP_PERIOD:
return ImGuiKey_KeypadDecimal;
case SDLK_KP_DIVIDE:
return ImGuiKey_KeypadDivide;
case SDLK_KP_MULTIPLY:
return ImGuiKey_KeypadMultiply;
case SDLK_KP_MINUS:
return ImGuiKey_KeypadSubtract;
case SDLK_KP_PLUS:
return ImGuiKey_KeypadAdd;
case SDLK_KP_ENTER:
return ImGuiKey_KeypadEnter;
case SDLK_KP_EQUALS:
return ImGuiKey_KeypadEqual;
case SDLK_LCTRL:
return ImGuiKey_LeftCtrl;
case SDLK_LSHIFT:
return ImGuiKey_LeftShift;
case SDLK_LALT:
return ImGuiKey_LeftAlt;
case SDLK_LGUI:
return ImGuiKey_LeftSuper;
case SDLK_RCTRL:
return ImGuiKey_RightCtrl;
case SDLK_RSHIFT:
return ImGuiKey_RightShift;
case SDLK_RALT:
return ImGuiKey_RightAlt;
case SDLK_RGUI:
return ImGuiKey_RightSuper;
case SDLK_APPLICATION:
return ImGuiKey_Menu;
case SDLK_0:
return ImGuiKey_0;
case SDLK_1:
return ImGuiKey_1;
case SDLK_2:
return ImGuiKey_2;
case SDLK_3:
return ImGuiKey_3;
case SDLK_4:
return ImGuiKey_4;
case SDLK_5:
return ImGuiKey_5;
case SDLK_6:
return ImGuiKey_6;
case SDLK_7:
return ImGuiKey_7;
case SDLK_8:
return ImGuiKey_8;
case SDLK_9:
return ImGuiKey_9;
case SDLK_a:
return ImGuiKey_A;
case SDLK_b:
return ImGuiKey_B;
case SDLK_c:
return ImGuiKey_C;
case SDLK_d:
return ImGuiKey_D;
case SDLK_e:
return ImGuiKey_E;
case SDLK_f:
return ImGuiKey_F;
case SDLK_g:
return ImGuiKey_G;
case SDLK_h:
return ImGuiKey_H;
case SDLK_i:
return ImGuiKey_I;
case SDLK_j:
return ImGuiKey_J;
case SDLK_k:
return ImGuiKey_K;
case SDLK_l:
return ImGuiKey_L;
case SDLK_m:
return ImGuiKey_M;
case SDLK_n:
return ImGuiKey_N;
case SDLK_o:
return ImGuiKey_O;
case SDLK_p:
return ImGuiKey_P;
case SDLK_q:
return ImGuiKey_Q;
case SDLK_r:
return ImGuiKey_R;
case SDLK_s:
return ImGuiKey_S;
case SDLK_t:
return ImGuiKey_T;
case SDLK_u:
return ImGuiKey_U;
case SDLK_v:
return ImGuiKey_V;
case SDLK_w:
return ImGuiKey_W;
case SDLK_x:
return ImGuiKey_X;
case SDLK_y:
return ImGuiKey_Y;
case SDLK_z:
return ImGuiKey_Z;
case SDLK_F1:
return ImGuiKey_F1;
case SDLK_F2:
return ImGuiKey_F2;
case SDLK_F3:
return ImGuiKey_F3;
case SDLK_F4:
return ImGuiKey_F4;
case SDLK_F5:
return ImGuiKey_F5;
case SDLK_F6:
return ImGuiKey_F6;
case SDLK_F7:
return ImGuiKey_F7;
case SDLK_F8:
return ImGuiKey_F8;
case SDLK_F9:
return ImGuiKey_F9;
case SDLK_F10:
return ImGuiKey_F10;
case SDLK_F11:
return ImGuiKey_F11;
case SDLK_F12:
return ImGuiKey_F12;
case SDLK_F13:
return ImGuiKey_F13;
case SDLK_F14:
return ImGuiKey_F14;
case SDLK_F15:
return ImGuiKey_F15;
case SDLK_F16:
return ImGuiKey_F16;
case SDLK_F17:
return ImGuiKey_F17;
case SDLK_F18:
return ImGuiKey_F18;
case SDLK_F19:
return ImGuiKey_F19;
case SDLK_F20:
return ImGuiKey_F20;
case SDLK_F21:
return ImGuiKey_F21;
case SDLK_F22:
return ImGuiKey_F22;
case SDLK_F23:
return ImGuiKey_F23;
case SDLK_F24:
return ImGuiKey_F24;
case SDLK_AC_BACK:
return ImGuiKey_AppBack;
case SDLK_AC_FORWARD:
return ImGuiKey_AppForward;
default:
break;
}
// Fallback to scancode
switch (scancode) {
case SDL_SCANCODE_GRAVE:
return ImGuiKey_GraveAccent;
case SDL_SCANCODE_MINUS:
return ImGuiKey_Minus;
case SDL_SCANCODE_EQUALS:
return ImGuiKey_Equal;
case SDL_SCANCODE_LEFTBRACKET:
return ImGuiKey_LeftBracket;
case SDL_SCANCODE_RIGHTBRACKET:
return ImGuiKey_RightBracket;
case SDL_SCANCODE_NONUSBACKSLASH:
return ImGuiKey_Oem102;
case SDL_SCANCODE_BACKSLASH:
return ImGuiKey_Backslash;
case SDL_SCANCODE_SEMICOLON:
return ImGuiKey_Semicolon;
case SDL_SCANCODE_APOSTROPHE:
return ImGuiKey_Apostrophe;
case SDL_SCANCODE_COMMA:
return ImGuiKey_Comma;
case SDL_SCANCODE_PERIOD:
return ImGuiKey_Period;
case SDL_SCANCODE_SLASH:
return ImGuiKey_Slash;
default:
break;
}
return ImGuiKey_None;
}
} // namespace filamentapp_utils
#endif // TNT_FILAMENT_FILAMENTAPP_KEYINPUTCONVERSION_H
@@ -38,19 +38,18 @@ void* getNativeWindow(SDL_Window* sdlWindow) {
SDL_GetWindowSize(sdlWindow, &width, &height); SDL_GetWindowSize(sdlWindow, &width, &height);
// Static is used here to allocate the struct pointer for the lifetime of the program. // Static is used here to allocate the struct pointer for the lifetime of the program.
// Without static the valid struct quickyly goes out of scope, and ends with seemingly // Without static the valid struct quickly goes out of scope, and ends with seemingly
// random segfaults. // random segfaults. We must update the values on each call.
static struct { static struct {
struct wl_display *display; struct wl_display *display;
struct wl_surface *surface; struct wl_surface *surface;
uint32_t width; uint32_t width;
uint32_t height; uint32_t height;
} wayland { } wayland;
wmi.info.wl.display, wayland.display = wmi.info.wl.display;
wmi.info.wl.surface, wayland.surface = wmi.info.wl.surface;
static_cast<uint32_t>(width), wayland.width = static_cast<uint32_t>(width);
static_cast<uint32_t>(height) wayland.height = static_cast<uint32_t>(height);
};
return (void *) &wayland; return (void *) &wayland;
#endif #endif
} }
+117 -17
View File
@@ -18,25 +18,125 @@
#include <benchmark/benchmark.h> #include <benchmark/benchmark.h>
#include <utils/memalign.h>
static void BM_memcpy(benchmark::State& state) { #include <cstddef>
char* src = new char[state.range(0)]; #include <cstdint>
char* dst = new char[state.range(0)]; #include <cstdlib>
memset(src, 'x', (size_t)state.range(0)); #include <cstring>
#include <vector>
{ // ------------------------------------------------------------------------------------------------
PerformanceCounters pc(state);
for (auto _ : state) {
memcpy(dst, src, (size_t)state.range(0));
benchmark::DoNotOptimize(dst);
benchmark::DoNotOptimize(src);
benchmark::ClobberMemory();
}
}
state.SetBytesProcessed(int64_t(state.iterations()) * int64_t(state.range(0)));
delete[] src; #ifdef __linux__
delete[] dst; #include <sched.h>
#include <unistd.h>
#include <sys/syscall.h>
#endif
// Returns true if successful, false if failed
static bool pinThreadToCore(int core_id) {
#ifdef __linux__
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(core_id, &cpuset);
// 0 = current thread
int const result = sched_setaffinity(0, sizeof(cpu_set_t), &cpuset);
return (result == 0);
#else
(void)core_id;
return false;
#endif
} }
BENCHMARK(BM_memcpy)->Range(8, 8192<<10)->Threads(1)->Threads(8); // ------------------------------------------------------------------------------------------------
static constexpr size_t ALIGNMENT = 16 << 10;
template<class T, size_t Align>
struct AlignedAllocator {
using value_type = T;
template<class U>
struct rebind {
using other = AlignedAllocator<U, Align>;
};
AlignedAllocator() noexcept = default;
template<class U>
explicit AlignedAllocator(const AlignedAllocator<U, Align>&) noexcept {}
T* allocate(size_t const n) {
size_t const bytes = n * sizeof(T);
// std::aligned_alloc requires size to be a multiple of alignment.
size_t const aligned_bytes = ((bytes + Align - 1) / Align) * Align;
void* ptr = utils::aligned_alloc(aligned_bytes, Align);
return static_cast<T*>(ptr);
}
void deallocate(T* p, size_t) noexcept {
utils::aligned_free(p);
}
};
template<typename T>
using PageAlignedVector = std::vector<T, AlignedAllocator<T, ALIGNMENT>>;
// ------------------------------------------------------------------------------------------------
static void BM_memcpy(benchmark::State& state) {
// pinThreadToCore(7);
int64_t const size = state.range(0);
PageAlignedVector<char> src(size);
PageAlignedVector<char> dst(size);
// make all these pages resident
memset(src.data(), 0, size);
memset(dst.data(), 0, size);
benchmark::ClobberMemory();
{
// PerformanceCounters const pc(state);
for (auto _: state) {
memcpy(dst.data(), src.data(), size);
benchmark::DoNotOptimize(dst);
benchmark::DoNotOptimize(src);
}
}
state.SetBytesProcessed(int64_t(state.iterations()) * int64_t(size));
}
static void BM_memset(benchmark::State& state) {
//pinThreadToCore(7);
int64_t const size = state.range(0);
PageAlignedVector<char> src(size);
// make all these pages resident
memset(src.data(), 0, size);
benchmark::ClobberMemory();
{
// PerformanceCounters const pc(state);
for (auto _: state) {
memset(src.data(), 0, size);
benchmark::DoNotOptimize(src);
}
}
state.SetBytesProcessed(int64_t(state.iterations()) * int64_t(size));
}
BENCHMARK(BM_memcpy)
->DenseRange( 4<<10, 128<<10, 8<<10)
->DenseRange(128<<10, 4<<20, 128<<10)
->DenseRange( 4<<20, 16<<20, 1<<20)
->DenseRange( 16<<20, 32<<20, 2<<20)
;
BENCHMARK(BM_memset)
->DenseRange( 4<<10, 128<<10, 8<<10)
->DenseRange(128<<10, 4<<20, 128<<10)
->DenseRange( 4<<20, 16<<20, 1<<20)
->DenseRange( 16<<20, 32<<20, 2<<20)
;
+13
View File
@@ -65,6 +65,7 @@ public:
// Creates an Invocable that does not contain a functor. // Creates an Invocable that does not contain a functor.
// Will evaluate to false. // Will evaluate to false.
Invocable() = default; Invocable() = default;
Invocable(std::nullptr_t) noexcept {}
~Invocable() noexcept; ~Invocable() noexcept;
@@ -77,6 +78,7 @@ public:
Invocable& operator=(const Invocable&) = delete; Invocable& operator=(const Invocable&) = delete;
Invocable& operator=(Invocable&& rhs) noexcept; Invocable& operator=(Invocable&& rhs) noexcept;
Invocable& operator=(std::nullptr_t) noexcept;
// Invokes the invocable with the args passed in. // Invokes the invocable with the args passed in.
// If the Invocable is empty, this will assert. // If the Invocable is empty, this will assert.
@@ -141,6 +143,17 @@ Invocable<R(Args...)>& Invocable<R(Args...)>::operator=(Invocable&& rhs) noexcep
return *this; return *this;
} }
template<typename R, typename... Args>
Invocable<R(Args...)>& Invocable<R(Args...)>::operator=(std::nullptr_t) noexcept {
if (mDeleter) {
mDeleter(mInvocable);
}
mInvocable = nullptr;
mDeleter = nullptr;
mInvoker = nullptr;
return *this;
}
template<typename R, typename... Args> template<typename R, typename... Args>
template<typename... OperatorArgs> template<typename... OperatorArgs>
R Invocable<R(Args...)>::operator()(OperatorArgs&& ... args) { R Invocable<R(Args...)>::operator()(OperatorArgs&& ... args) {
+6
View File
@@ -55,7 +55,10 @@ AsyncJobQueue::AsyncJobQueue(const char* name, Priority priority) {
} }
AsyncJobQueue::~AsyncJobQueue() noexcept { AsyncJobQueue::~AsyncJobQueue() noexcept {
// wait for all pending callbacks to be called & terminate the thread
drainAndExit();
#if !defined(__EMSCRIPTEN__) #if !defined(__EMSCRIPTEN__)
assert_invariant(!mThread.joinable());
assert_invariant(mQueue.empty()); assert_invariant(mQueue.empty());
#endif #endif
} }
@@ -94,6 +97,9 @@ bool AsyncJobQueue::isValid() const noexcept {
void AsyncJobQueue::drainAndExit() { void AsyncJobQueue::drainAndExit() {
#if !defined(__EMSCRIPTEN__) #if !defined(__EMSCRIPTEN__)
std::unique_lock lock(mLock); std::unique_lock lock(mLock);
if (mExitRequested) {
return;
}
// we request the service thread to exit, but we're guaranteed that it'll only exit // we request the service thread to exit, but we're guaranteed that it'll only exit
// after all current callbacks are processed. In addition, once mExitRequested is set, // after all current callbacks are processed. In addition, once mExitRequested is set,
// no new jobs can be added, so we can join the thread. // no new jobs can be added, so we can join the thread.
+43 -26
View File
@@ -41,6 +41,36 @@ using namespace filament::math;
namespace filament { namespace filament {
namespace viewer { namespace viewer {
namespace {
ImGuiKey convertKey(int keycode) {
// The following table uses normal ANSI codes, which is consistent with the keyCode that
// comes from a web "keydown" event.
switch (keycode) {
case 9: return ImGuiKey_Tab;
case 37: return ImGuiKey_LeftArrow;
case 39: return ImGuiKey_RightArrow;
case 38: return ImGuiKey_UpArrow;
case 40: return ImGuiKey_DownArrow;
case 36: return ImGuiKey_Home;
case 35: return ImGuiKey_End;
case 46: return ImGuiKey_Delete;
case 8: return ImGuiKey_Backspace;
case 13: return ImGuiKey_Enter;
case 27: return ImGuiKey_Escape;
case 65: return ImGuiKey_A;
case 67: return ImGuiKey_C;
case 86: return ImGuiKey_V;
case 88: return ImGuiKey_X;
case 89: return ImGuiKey_Y;
case 90: return ImGuiKey_Z;
default: return ImGuiKey_None;
}
}
}
mat4f fitIntoUnitCube(const Aabb& bounds, float zoffset) { mat4f fitIntoUnitCube(const Aabb& bounds, float zoffset) {
float3 minpt = bounds.min; float3 minpt = bounds.min;
float3 maxpt = bounds.max; float3 maxpt = bounds.max;
@@ -563,27 +593,6 @@ void ViewerGui::renderUserInterface(float timeStepInSeconds, View* guiView, floa
mImGuiHelper = new ImGuiHelper(mEngine, guiView, ""); mImGuiHelper = new ImGuiHelper(mEngine, guiView, "");
auto& io = ImGui::GetIO(); auto& io = ImGui::GetIO();
// The following table uses normal ANSI codes, which is consistent with the keyCode that
// comes from a web "keydown" event.
io.KeyMap[ImGuiKey_Tab] = 9;
io.KeyMap[ImGuiKey_LeftArrow] = 37;
io.KeyMap[ImGuiKey_RightArrow] = 39;
io.KeyMap[ImGuiKey_UpArrow] = 38;
io.KeyMap[ImGuiKey_DownArrow] = 40;
io.KeyMap[ImGuiKey_Home] = 36;
io.KeyMap[ImGuiKey_End] = 35;
io.KeyMap[ImGuiKey_Delete] = 46;
io.KeyMap[ImGuiKey_Backspace] = 8;
io.KeyMap[ImGuiKey_Enter] = 13;
io.KeyMap[ImGuiKey_Escape] = 27;
io.KeyMap[ImGuiKey_A] = 65;
io.KeyMap[ImGuiKey_C] = 67;
io.KeyMap[ImGuiKey_V] = 86;
io.KeyMap[ImGuiKey_X] = 88;
io.KeyMap[ImGuiKey_Y] = 89;
io.KeyMap[ImGuiKey_Z] = 90;
// TODO: this is not the best way to handle high DPI in ImGui, but it is fine when using the // TODO: this is not the best way to handle high DPI in ImGui, but it is fine when using the
// proggy font. Users need to refresh their window when dragging between displays with // proggy font. Users need to refresh their window when dragging between displays with
// different pixel ratios. // different pixel ratios.
@@ -609,20 +618,28 @@ void ViewerGui::mouseEvent(float mouseX, float mouseY, bool mouseButton, float m
io.MouseDown[0] = mouseButton != 0; io.MouseDown[0] = mouseButton != 0;
io.MouseDown[1] = false; io.MouseDown[1] = false;
io.MouseDown[2] = false; io.MouseDown[2] = false;
io.KeyCtrl = control; io.AddKeyEvent(ImGuiMod_Ctrl, control);
} }
} }
void ViewerGui::keyDownEvent(int keyCode) { void ViewerGui::keyDownEvent(int keyCode) {
if (mImGuiHelper && keyCode < IM_ARRAYSIZE(ImGui::GetIO().KeysDown)) { if (!mImGuiHelper) {
ImGui::GetIO().KeysDown[keyCode] = true; return;
} }
ImGuiIO& io = ImGui::GetIO();
io.AddKeyEvent(
convertKey(keyCode),
true);
} }
void ViewerGui::keyUpEvent(int keyCode) { void ViewerGui::keyUpEvent(int keyCode) {
if (mImGuiHelper && keyCode < IM_ARRAYSIZE(ImGui::GetIO().KeysDown)) { if (!mImGuiHelper) {
ImGui::GetIO().KeysDown[keyCode] = false; return;
} }
ImGuiIO& io = ImGui::GetIO();
io.AddKeyEvent(
convertKey(keyCode),
false);
} }
void ViewerGui::keyPressEvent(int charCode) { void ViewerGui::keyPressEvent(int charCode) {
-1
View File
@@ -37,7 +37,6 @@ set(MATERIAL_SRCS
materials/sandboxLitSolidRefractionSsr.mat materials/sandboxLitSolidRefractionSsr.mat
materials/sandboxSpecGloss.mat materials/sandboxSpecGloss.mat
materials/sandboxSubsurface.mat materials/sandboxSubsurface.mat
materials/sandboxTransformName.mat
materials/sandboxUnlit.mat materials/sandboxUnlit.mat
materials/texturedLit.mat materials/texturedLit.mat
) )

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