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
78 changed files with 1103 additions and 921 deletions
+2 -1
View File
@@ -21,8 +21,9 @@ jobs:
- name: Run update script
env:
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
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 }}"
if [[ "${GOLDEN_BRANCH}" != "main" ]]; then
git config --global user.email "filament.bot@gmail.com"
+6 -6
View File
@@ -134,18 +134,17 @@ jobs:
env:
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
run: |
ls ./gltf/Models
TEST_DIR=test/renderdiff
source ${TEST_DIR}/src/preamble.sh
start_
set -eux
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 ${TEST_DIR}/src/commit_msg.py)
bash ${TEST_DIR}/generate.sh && \
python3 ${TEST_DIR}/src/golden_manager.py \
bash ${TEST_DIR}/generate.sh
python3 ${TEST_DIR}/src/golden_manager.py \
--branch=${GOLDEN_BRANCH} \
--output=${GOLDEN_OUTPUT_DIR}
# Note that we need to upload the output even if comparison fails, so we undo `set -ex`
end_
# Note that we need to upload the output even if comparison fails, so we undo `set -eux`
set +eux
python3 ${TEST_DIR}/src/compare.py \
--src=${GOLDEN_OUTPUT_DIR} \
@@ -158,6 +157,7 @@ jobs:
cat compare_output.txt >> "$GITHUB_OUTPUT"
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
fi
shell: bash
- uses: actions/upload-artifact@v4
with:
name: presubmit-renderdiff-result
+4 -2
View File
@@ -872,7 +872,9 @@ add_subdirectory(${LIBRARIES}/utils)
add_subdirectory(${LIBRARIES}/viewer)
add_subdirectory(${FILAMENT}/shaders)
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}/imgui/tnt)
add_subdirectory(${EXTERNAL}/robin-map/tnt)
@@ -886,7 +888,7 @@ add_subdirectory(${EXTERNAL}/jsmn/tnt)
add_subdirectory(${EXTERNAL}/stb/tnt)
add_subdirectory(${EXTERNAL}/getopt)
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.
+1 -1
View File
@@ -100,7 +100,7 @@ buildscript {
'kotlin': '2.0.21',
'kotlin_coroutines': '1.9.0',
'buildTools': '35.0.0',
'ndk': '27.0.11718014',
'ndk': '29.0.14206865',
'androidx_core': '1.13.1',
'androidx_annotations': '1.9.0'
]
@@ -25,7 +25,7 @@ using namespace filament;
extern "C" JNIEXPORT jlong JNICALL
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;
AutoBuffer buffer(env, buffer_, size);
auto builder = Material::Builder();
@@ -33,6 +33,7 @@ Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass,
builder.sphericalHarmonicsBandCount(shBandCount);
}
builder.shadowSamplingQuality((Material::Builder::ShadowSamplingQuality)shadowQuality);
builder.uboBatching((Material::UboBatchingMode)uboBatchingMode);
Material* material = builder
.package(buffer.getData(), buffer.getSize())
.build(*engine);
@@ -261,6 +261,20 @@ public class Material {
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 {
/** Directional lighting */
public static int DIRECTIONAL_LIGHTING = 0x01;
@@ -372,6 +386,7 @@ public class Material {
private int mSize;
private int mShBandCount = 0;
private ShadowSamplingQuality mShadowSamplingQuality = ShadowSamplingQuality.LOW;
private UboBatchingMode mUboBatchingMode = UboBatchingMode.DEFAULT;
/**
@@ -416,6 +431,17 @@ public class Material {
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.
*
@@ -428,7 +454,7 @@ public class Material {
@NonNull
public Material build(@NonNull Engine engine) {
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");
return new Material(nativeMaterial);
}
@@ -1094,7 +1120,7 @@ public class Material {
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 nCreateInstanceWithName(long nativeMaterial, @NonNull String name);
private static native long nGetDefaultInstance(long nativeMaterial);
+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
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
ISSUE_CLEAN=false
+2 -2
View File
@@ -18,8 +18,8 @@ if [[ "$GITHUB_WORKFLOW" ]]; then
fi
fi
# Unless explicitly specified, NDK version will be set to match exactly the required one
FILAMENT_NDK_VERSION=${GITHUB_NDK_VERSION:-27.0.11718014}
# Unless explicitly specified, NDK version will be selected as highest available version within same major release chain
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) &&
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_MESA_VERSION=24.2.1
GITHUB_LLVM_VERSION=16
GITHUB_NDK_VERSION=27.0.11718014
GITHUB_NDK_VERSION=29.0.14206865
GITHUB_EMSDK_VERSION=3.1.60
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
# 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_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")
+1 -1
View File
@@ -87,7 +87,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# for hardfp: CFLAGS must have -mhard-float
# 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_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")
+2 -2
View File
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.67.1'
implementation 'com.google.android.filament:filament-android:1.68.0'
}
</code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
@@ -196,7 +196,7 @@ dependencies {
</div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<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>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<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-websocket==0.12.2
typing_extensions==4.15.0
urllib3==2.5.0
urllib3==2.6.0
websocket-client==1.9.0
wsproto==1.2.0
+2
View File
@@ -318,6 +318,8 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPURenderPrimitive.h
src/webgpu/WebGPURenderTarget.cpp
src/webgpu/WebGPURenderTarget.h
src/webgpu/WebGPUStagePool.cpp
src/webgpu/WebGPUStagePool.h
src/webgpu/WebGPUStrings.h
src/webgpu/WebGPUSwapChain.cpp
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 AsynchronousMode = Platform::AsynchronousMode;
} // namespace filament::backend
template<> struct utils::EnableBitMaskOperators<filament::backend::ShaderStageFlags>
@@ -255,6 +255,28 @@ public:
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 {
/**
* Size of handle arena in bytes. Setting to 0 indicates default value is to be used.
@@ -324,6 +346,11 @@ public:
* - VulkanPlatform
*/
bool vulkanEnableStagingBufferBypass = false;
/**
* Asynchronous mode for the engine. Defines how asynchronous operations are handled.
*/
AsynchronousMode asynchronousMode = AsynchronousMode::NONE;
};
Platform() noexcept;
@@ -22,7 +22,6 @@
#include "private/backend/Driver.h"
#include <backend/BufferDescriptor.h>
#include <backend/BufferObjectStreamDescriptor.h>
#include <backend/CallbackHandler.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
@@ -51,7 +51,6 @@
namespace filament::backend {
class BufferDescriptor;
class BufferObjectStreamDescriptor;
class CallbackHandler;
class PixelBufferDescriptor;
class Program;
@@ -392,10 +392,6 @@ DECL_DRIVER_API_N(updateBufferObject,
backend::BufferDescriptor&&, data,
uint32_t, byteOffset)
DECL_DRIVER_API_N(registerBufferObjectStreams,
backend::BufferObjectHandle, boh,
backend::BufferObjectStreamDescriptor&&, streams)
DECL_DRIVER_API_N(updateBufferObjectUnsynchronized,
backend::BufferObjectHandle, boh,
backend::BufferDescriptor&&, data,
+6 -6
View File
@@ -1144,8 +1144,12 @@ bool MetalDriver::isRenderTargetFormatSupported(TextureFormat format) {
bool MetalDriver::isFrameBufferFetchSupported() {
// FrameBuffer fetch is achievable via "programmable blending" in Metal, and only supported on
// Apple GPUs with readWriteTextureSupport.
return mContext->highestSupportedGpuFamily.apple >= 1 &&
mContext->device.readWriteTextureSupport;
// On macOS, framebuffer fetch requires MSL 2.3, which is only available with macOS 11.0.
if (@available(macOS 11.0, *)) {
return mContext->highestSupportedGpuFamily.apple >= 1 &&
mContext->device.readWriteTextureSupport;
}
return false;
}
bool MetalDriver::isFrameBufferFetchMultiSampleSupported() {
@@ -1286,10 +1290,6 @@ void MetalDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&
scheduleDestroy(std::move(data));
}
void MetalDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh, BufferObjectStreamDescriptor&& streams) {
// Noop
}
void MetalDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& data,
uint32_t byteOffset) {
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
@@ -111,9 +111,22 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
id<MTLLibrary> library = nil;
switch (program.getShaderLanguage()) {
case ShaderLanguage::MSL: {
// By default, Metal uses the most recent language version.
MTLCompileOptions* options = [MTLCompileOptions new];
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');
// 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));
}
void NoopDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh, BufferObjectStreamDescriptor&& streams) { }
void NoopDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> ibh, BufferDescriptor&& p,
uint32_t byteOffset) {
scheduleDestroy(std::move(p));
@@ -39,7 +39,6 @@
#include <backend/Platform.h>
#include <backend/Program.h>
#include <backend/TargetBufferInfo.h>
#include <backend/BufferObjectStreamDescriptor.h>
#include "private/backend/CommandStream.h"
#include "private/backend/Dispatcher.h"
@@ -2837,38 +2836,6 @@ void OpenGLDriver::updateIndexBuffer(
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(
Handle<HwBufferObject> boh, BufferDescriptor&& bd, uint32_t const byteOffset) {
DEBUG_MARKER()
@@ -2882,19 +2849,6 @@ void OpenGLDriver::updateBufferObject(
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())) {
assert_invariant(bo->gl.buffer);
memcpy(static_cast<uint8_t*>(bo->gl.buffer) + byteOffset, bd.buffer, bd.size);
@@ -36,7 +36,6 @@
#include <backend/Platform.h>
#include <backend/Program.h>
#include <backend/TargetBufferInfo.h>
#include <backend/BufferObjectStreamDescriptor.h>
#include "private/backend/Driver.h"
#include "private/backend/HandleAllocator.h"
@@ -394,8 +393,6 @@ private:
// the must be accessed from the user thread only
std::vector<GLStream*> mStreamsWithPendingAcquiredImage;
std::unordered_map<GLuint, BufferObjectStreamDescriptor> mStreamUniformDescriptors;
void attachStream(GLTexture* t, GLStream* stream);
void detachStream(GLTexture* t) 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__)
# include <GLES3/gl3.h>
@@ -178,20 +178,30 @@ void PlatformEGLAndroid::beginFrame(
int64_t const monotonic_clock_ns,
int64_t refreshIntervalNs,
uint32_t const frameId) noexcept {
// associate the user frameid with the system frame id
setPresentFrameId(mCurrentDrawSwapChain, frameId);
// 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 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) {
// we're not provided with a target time, use the display period, if everything fails,
// assume 16.67ms
refreshIntervalNs = 16'666'667;
CompositorTiming compositorTiming{};
bool const hasCompositorTiming =
queryCompositorTiming(mCurrentDrawSwapChain, &compositorTiming);
if (hasCompositorTiming && compositorTiming.compositeInterval > 0) {
refreshIntervalNs = compositorTiming.compositeInterval;
if (mCurrentDrawSwapChain) {
// mCurrentDrawSwapChain could be null if we're called from renderStandaloneView
CompositorTiming compositorTiming{};
bool const hasCompositorTiming =
queryCompositorTiming(mCurrentDrawSwapChain, &compositorTiming);
if (hasCompositorTiming && compositorTiming.compositeInterval > 0) {
refreshIntervalNs = compositorTiming.compositeInterval;
}
}
}
mStartTimeOfActualWork = clock::time_point(std::chrono::nanoseconds(monotonic_clock_ns));
@@ -440,6 +450,10 @@ Platform::SwapChain* PlatformEGLAndroid::createSwapChain(
void PlatformEGLAndroid::destroySwapChain(SwapChain* swapChain) noexcept {
if (swapChain) {
SwapChainEGLAndroid* const sc = static_cast<SwapChainEGLAndroid*>(swapChain);
if (mCurrentDrawSwapChain == sc) {
// don't keep a dangling pointer around
mCurrentDrawSwapChain = nullptr;
}
sc->terminate(*this);
delete sc;
}
@@ -16,7 +16,7 @@
#include <backend/platforms/PlatformEGLHeadless.h>
#include "opengl/GLUtils.h"
#include <bluegl/BlueGL.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
@@ -35,16 +35,31 @@ PlatformEGLHeadless::PlatformEGLHeadless() 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,
const Platform::DriverConfig& driverConfig) {
EGLBoolean bindAPI = eglBindAPI(EGL_OPENGL_API);
if (UTILS_UNLIKELY(!bindAPI)) {
LOG(ERROR) << "eglBindAPI EGL_OPENGL_API failed";
auto bindApiHelper = [](EGLenum api, const char* errorString) -> bool {
EGLBoolean bindAPI = eglBindAPI(api);
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;
}
int bindBlueGL = bluegl::bind();
if (UTILS_UNLIKELY(bindBlueGL != 0)) {
LOG(ERROR) << "bluegl bind failed";
-5
View File
@@ -15,7 +15,6 @@
*/
#include <backend/BufferDescriptor.h>
#include <backend/BufferObjectStreamDescriptor.h>
#include <backend/DescriptorSetOffsetArray.h>
#include <backend/DriverEnums.h>
#include <backend/PipelineState.h>
@@ -434,10 +433,6 @@ io::ostream& operator<<(io::ostream& out, BufferDescriptor const& b) {
<< ", 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) {
BufferDescriptor const& base = static_cast<BufferDescriptor const&>(b);
return out << "PixelBufferDescriptor{ " << base
@@ -62,8 +62,9 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c
// a memcpy if its available.
bool const isStaticOrShared =
any(mUsage & (BufferUsage::STATIC | BufferUsage::SHARED_WRITE_BIT));
bool const useMemcpy = (isUniformAndAvailable || isStaticOrShared) && isMemcopyable &&
mStagingBufferBypassEnabled;
bool const useMemcpy =
((isUniformAndAvailable && mStagingBufferBypassEnabled) || isStaticOrShared) &&
isMemcopyable;
if (useMemcpy) {
char* dest = static_cast<char*>(mBuffer->getGpuBuffer()->allocationInfo.pMappedData) +
byteOffset;
+7 -70
View File
@@ -150,31 +150,6 @@ static CallbackHandler::Callback syncCallbackWrapper = [](void* userData) {
cbData->cb(cbData->sync, cbData->userData);
};
// This needs to be discussed because Vulkan has different default Matrix formats
// right now this is following GL.
static void copyMat3f(void* addr, size_t const offset, const math::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
}
}// anonymous namespace
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
@@ -447,12 +422,14 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) {
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);
}
// 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.
//
// This will let us check if any VulkanBuffer is currently in flight or not.
@@ -528,10 +505,7 @@ void VulkanDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_
assert_invariant(any(mmb->access & MapBufferAccessFlags::WRITE_BIT));
assert_invariant(offset + data.size <= mmb->size);
// TODO: this isa zero-effort implementation of copyToMemoryMappedBuffer(), where we just
// call updateBufferObject(). This could be a fallback implementation for when
// shared memory is not available.
// On UMA systems, this should just be a memcpy into the memory-mapped buffer.
// On UMA systems, this is a memcpy into the memory-mapped buffer, otherwise use a staging buffer.
updateBufferObject(mmb->boh, std::move(data), mmb->offset + offset);
}
@@ -1182,7 +1156,7 @@ void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
}
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream, utils::ImmutableCString tag) {
FILAMENT_CHECK_PRECONDITION(false) << "createStreamNative not supported in Vulkan.";
FVK_LOGW << "createStreamNative not supported in Vulkan.";
return {};
}
@@ -1206,7 +1180,6 @@ void VulkanDriver::setAcquiredImage(Handle<HwStream> sh, void* image, const math
scheduleRelease(stream->takePrevious());
}
stream->acquire({ image, cb, userData, handler});
stream->setFrontEndTransform(transform);
mStreamsWithPendingAcquiredImage.push_back(stream);
}
@@ -1234,10 +1207,8 @@ void VulkanDriver::updateStreams(CommandStream* driver) {
// 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,
transform = stream->getFrontEndTransform()]() {
image = stream->getAcquired().image]() {
auto texture = s->getTexture(image);
s->setBackendTransform(transform);
if (!texture) {
auto externalImage = fvkutils::createExternalImageFromRaw(mPlatform, image, false);
auto metadata = mPlatform->extractExternalImageMetadata(externalImage);
@@ -1583,13 +1554,6 @@ void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor
scheduleDestroy(std::move(p));
}
void VulkanDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh,
BufferObjectStreamDescriptor&& streams) {
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
mStreamUniformDescriptors[bo.get()] = std::move(streams);
}
void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& bd,
uint32_t byteOffset) {
VulkanCommandBuffer& commands = mCommands.get();
@@ -1597,33 +1561,6 @@ void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescript
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
commands.acquire(bo);
if (UTILS_UNLIKELY(!mStreamUniformDescriptors.empty())) {
auto streamDescriptors = mStreamUniformDescriptors.find(bo.get());
if (streamDescriptors != mStreamUniformDescriptors.end()) {
for (auto const& [offset, streamHandle, associationType]:
streamDescriptors->second.mStreams) {
if (associationType == BufferObjectStreamAssociationType::TRANSFORM_MATRIX) {
math::mat3f transform;
if (streamHandle) {
auto stream =
resource_ptr<VulkanStream>::cast(&mResourceManager, streamHandle);
if (stream->streamType == StreamType::NATIVE) {
FILAMENT_CHECK_PRECONDITION(false)
<< "Native Stream not supported in Vulkan.";
} else {
// Backend call since getStreamTransformMatrix is called from async
// updateBufferObject.
transform = stream->getBackEndTransform();
}
}
copyMat3f(bd.buffer, offset, transform);
}
}
mStreamUniformDescriptors.erase(streamDescriptors);
}
}
bo->loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
scheduleDestroy(std::move(bd));
+1 -4
View File
@@ -41,6 +41,7 @@
#include "vulkan/utils/Definitions.h"
#include "backend/DriverEnums.h"
#include "backend/BufferObjectStreamDescriptor.h"
#include "DriverBase.h"
#include "private/backend/Driver.h"
@@ -160,10 +161,6 @@ private:
VulkanExternalImageManager mExternalImageManager;
VulkanStreamedImageManager mStreamedImageManager;
// Stream transforms
std::unordered_map<VulkanBufferObject*, BufferObjectStreamDescriptor> mStreamUniformDescriptors;
math::mat3f getStreamTransformMatrix(Handle<HwStream> sh);
// This maps a VulkanSwapchain to a native swapchain. VulkanSwapchain should have a copy of the
// Platform::Swapchain pointer, but queryFrameTimestamps() and queryCompositorTiming() are
@@ -19,6 +19,7 @@
#include "VulkanCommands.h"
#include "VulkanTexture.h"
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Panic.h>
@@ -105,6 +106,11 @@ void VulkanSwapChain::update() {
}
void VulkanSwapChain::present(DriverBase& driver) {
// The last acquire failed, so just skip presenting.
if (!mAcquired) {
return;
}
if (!mHeadless && mTransitionSwapChainImageLayoutForPresent) {
VulkanCommandBuffer& commands = mCommands->get();
VkImageSubresourceRange const subresources{
@@ -161,7 +167,15 @@ void VulkanSwapChain::acquire(bool& resized) {
VulkanPlatform::ImageSyncData 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;
assert_invariant(mCurrentSwapIndex < mFinishedDrawing.size());
mFinishedDrawing[mCurrentSwapIndex] = {};
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
<< "Cannot acquire in swapchain. error=" << static_cast<int32_t>(result);
@@ -52,12 +52,8 @@ struct VulkanStream : public HwStream, fvkmemory::Resource {
return previous;
}
const AcquiredImage& getAcquired() const { return user_thread.mAcquired; }
void setFrontEndTransform(const math::mat3f& transform) { user_thread.mTransform = transform; }
const math::mat3f& getFrontEndTransform() const { return user_thread.mTransform; }
//-- These methods are only called from the backend thread
void setBackendTransform(const math::mat3f& transform) { mTransform = transform; }
const math::mat3f& getBackEndTransform() const { return mTransform; }
fvkmemory::resource_ptr<VulkanTexture> getTexture(void* ahb) {
if (auto itr = mTextures.find(ahb); itr != mTextures.end()) {
return itr->second;
@@ -71,11 +67,9 @@ private:
struct {
AcquiredImage mAcquired;
AcquiredImage mPrevious;
math::mat3f mTransform;
} user_thread;
// #TODO b/442937292
math::mat3f mTransform;
std::unordered_map<void*, fvkmemory::resource_ptr<VulkanTexture>> mTextures;
};
@@ -18,6 +18,7 @@
#include "WebGPUConstants.h"
#include "WebGPUQueueManager.h"
#include "WebGPUStagePool.h"
#include "DriverBase.h"
#include <backend/BufferDescriptor.h>
@@ -65,7 +66,7 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
// of 4 by padding with zeros.
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
const uint32_t byteOffset, wgpu::Device const& device,
WebGPUQueueManager* const webGPUQueueManager) {
WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool) {
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
<< "updateGPUBuffer called with a null buffer";
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
@@ -85,15 +86,12 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
const size_t stagingBufferSize =
remainder == 0 ? bufferDescriptor.size : mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
// create a staging buffer
wgpu::BufferDescriptor descriptor{
.label = "Filament WebGPU Staging Buffer",
.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
.size = stagingBufferSize,
.mappedAtCreation = true };
wgpu::Buffer stagingBuffer = device.CreateBuffer(&descriptor);
wgpu::Buffer stagingBuffer = webGPUStagePool->acquireBuffer(stagingBufferSize,
webGPUQueueManager->getLatestSubmissionState());
void* mappedRange = stagingBuffer.GetMappedRange();
assert_invariant(mappedRange);
memcpy(mappedRange, bufferDescriptor.buffer, bufferDescriptor.size);
// 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.
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stagingBuffer, 0, mBuffer,
byteOffset, stagingBufferSize);
byteOffset,
remainder == 0 ? bufferDescriptor.size
: mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS);
}
} // namespace filament::backend
@@ -25,6 +25,7 @@ namespace filament::backend {
class BufferDescriptor;
class WebGPUQueueManager;
class WebGPUStagePool;
/**
* 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.
*/
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; }
+7 -8
View File
@@ -107,6 +107,7 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
mAdapter{ mPlatform.requestAdapter(nullptr) },
mDevice{ mPlatform.requestDevice(mAdapter) },
mQueueManager{ mDevice },
mStagePool{ mDevice },
mPipelineLayoutCache{ mDevice },
mPipelineCache{ mDevice },
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++) {
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.
@@ -651,11 +655,6 @@ void WebGPUDriver::setAcquiredImage(Handle<HwStream> sh, void* image, const math
//todo
}
void WebGPUDriver::registerBufferObjectStreams(Handle<HwBufferObject> bufferObjectHandle,
BufferObjectStreamDescriptor&& streams) {
//todo
}
void WebGPUDriver::setStreamDimensions(Handle<HwStream> sh, uint32_t width, uint32_t height) {
//todo
}
@@ -856,7 +855,7 @@ void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
// draw calls are made.
flush();
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor));
}
@@ -867,14 +866,14 @@ void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
// draw calls are made.
flush();
handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor));
}
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor));
}
@@ -25,6 +25,7 @@
#include "webgpu/WebGPUPipelineLayoutCache.h"
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
#include "webgpu/WebGPUQueueManager.h"
#include "webgpu/WebGPUStagePool.h"
#include "webgpu/utils/AsyncTaskCounter.h"
#include <backend/platforms/WebGPUPlatform.h>
@@ -81,6 +82,7 @@ private:
wgpu::Device mDevice = nullptr;
wgpu::Limits mDeviceLimits = {};
WebGPUQueueManager mQueueManager;
WebGPUStagePool mStagePool;
void* mNativeWindow = nullptr;
WebGPUSwapChain* mSwapChain = nullptr;
uint64_t mNextFakeHandle = 1;
@@ -64,6 +64,7 @@ wgpu::CommandEncoder WebGPUQueueManager::getCommandEncoder() {
};
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
ASSERT_POSTCONDITION(mCommandEncoder, "Failed to create command encoder.");
mLatestSubmissionState = std::make_shared<WebGPUSubmissionState>();
}
return mCommandEncoder;
}
@@ -94,8 +95,6 @@ void WebGPUQueueManager::submit() {
return;
}
mLatestSubmissionState = std::make_shared<WebGPUSubmissionState>();
wgpu::CommandBufferDescriptor commandBufferDescriptor{
.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 Driver = backend::Driver;
using GpuContextPriority = backend::Platform::GpuContextPriority;
using AsynchronousMode = backend::AsynchronousMode;
/**
* Config is used to define the memory footprint used by the engine, such as the
@@ -428,6 +429,11 @@ public:
* initial memory usage.
*/
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;
/**
* 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.
*
+21
View File
@@ -69,6 +69,20 @@ public:
using ShaderModel = backend::ShaderModel;
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.
*/
@@ -165,6 +179,13 @@ public:
*/
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.
*
+10
View File
@@ -20,6 +20,8 @@
#include <math/mat4.h>
#include <utils/Panic.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
@@ -34,6 +36,10 @@ using namespace math;
void Camera::setProjection(double const fovInDegrees, double const aspect,
double const near, double const far, Fov const direction) {
FILAMENT_CHECK_PRECONDITION(near > 0 && far > near)
<< "Camera preconditions not met in setProjection(): near <= 0 or far <= near, near="
<< near << ", far=" << far;
setCustomProjection(
projection(direction, fovInDegrees, aspect, near),
projection(direction, fovInDegrees, aspect, near, far),
@@ -43,6 +49,10 @@ void Camera::setProjection(double const fovInDegrees, double const aspect,
void Camera::setLensProjection(double const focalLengthInMillimeters,
double const aspect, double const near, double const far) {
FILAMENT_CHECK_PRECONDITION(near > 0 && far > near)
<< "Camera preconditions not met in setLensProjection(): near <= 0 or far <= near, near="
<< near << ", far=" << far;
setCustomProjection(
projection(focalLengthInMillimeters, aspect, near),
projection(focalLengthInMillimeters, aspect, near, far),
+4
View File
@@ -455,6 +455,10 @@ bool Engine::isStereoSupported(StereoscopicType) const noexcept {
return downcast(this)->isStereoSupported();
}
bool Engine::isAsynchronousOperationSupported() const noexcept {
return downcast(this)->isAsynchronousOperationSupported();
}
size_t Engine::getMaxStereoscopicEyes() noexcept {
return FEngine::getMaxStereoscopicEyes();
}
+25 -4
View File
@@ -101,11 +101,24 @@ void FrameInfoManager::beginFrame(FSwapChain* swapChain, DriverApi& driver,
auto& history = mFrameTimeHistory;
// don't exceed the capacity, drop the oldest entry
if (UTILS_LIKELY(history.size() == history.capacity())) {
if (!mDisableGpuFrameComplete) {
assert_invariant(history.back().fence);
driver.destroyFence(std::move(history.back().fence));
FrameInfoImpl& frameInfo = history.back();
if (frameInfo.ready.load(std::memory_order_relaxed)) {
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
@@ -193,6 +206,14 @@ void FrameInfoManager::beginFrame(FSwapChain* swapChain, DriverApi& driver,
}
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();
front.endFrame = std::chrono::steady_clock::now();
+3 -27
View File
@@ -95,33 +95,8 @@ struct FrameInfoImpl : public details::FrameInfo {
assert_invariant(!fence);
}
FrameInfoImpl(FrameInfoImpl&& rhs) noexcept :
details::FrameInfo(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(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;
}
FrameInfoImpl(FrameInfoImpl& rhs) noexcept = delete;
FrameInfoImpl& operator=(FrameInfoImpl& rhs) noexcept = delete;
};
template<typename T, size_t CAPACITY>
@@ -340,6 +315,7 @@ private:
FrameHistoryQueue mFrameTimeHistory{};
utils::AsyncJobQueue mJobQueue;
FSwapChain* mLastSeenSwapChain = nullptr;
bool mLastBeginFrameSkipped = false;
bool const mHasTimerQueries = false;
bool const mDisableGpuFrameComplete = false;
};
+4 -14
View File
@@ -95,13 +95,8 @@ mat4 FCamera::projection(double const focalLengthInMillimeters,
void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& projection,
mat4 const& projectionForCulling, double const near, double const far) noexcept {
FILAMENT_CHECK_PRECONDITION(near > 0)
<< "Camera preconditions not met in setCustomProjection(): near <= 0, near=" << near;
auto const& featureFlags = mEngine.features.engine.debug;
FILAMENT_FLAG_GUARDED_CHECK_PRECONDITION(far > near,
featureFlags.assert_camera_projection_near_far)
<< "Camera preconditions not met in setCustomProjection(): far <= near, near="
<< near << ", far=" << far;
FILAMENT_CHECK_PRECONDITION(near != far)
<< "Camera preconditions not met in setCustomProjection(): near = far = " << near;
for (auto& eyeProjection: mEyeProjection) {
eyeProjection = projection;
@@ -115,13 +110,8 @@ void UTILS_NOINLINE FCamera::setCustomEyeProjection(mat4 const* projection, size
mat4 const& projectionForCulling, double const near, double const far) {
const Engine::Config& config = mEngine.getConfig();
FILAMENT_CHECK_PRECONDITION(near > 0)
<< "Camera preconditions not met in setCustomEyeProjection(): near <= 0, near=" << near;
auto const& featureFlags = mEngine.features.engine.debug;
FILAMENT_FLAG_GUARDED_CHECK_PRECONDITION(far > near,
featureFlags.assert_camera_projection_near_far)
<< "Camera preconditions not met in setCustomEyeProjection(): far <= near, near="
<< near << ", far=" << far;
FILAMENT_CHECK_PRECONDITION(near != far)
<< "Camera preconditions not met in setCustomEyeProjection(): near = far = " << near;
FILAMENT_CHECK_PRECONDITION(count >= config.stereoscopicEyeCount)
<< "All eye projections must be supplied together, count must be >= "
+13 -6
View File
@@ -98,7 +98,7 @@ using namespace filaflat;
namespace {
backend::Platform::DriverConfig getDriverConfig(FEngine* instance) {
return {
Platform::DriverConfig driverConfig {
.handleArenaSize = instance->getRequestedDriverHandleArenaSize(),
.metalUploadBufferSizeBytes = instance->getConfig().metalUploadBufferSizeBytes,
.disableParallelShaderCompile = instance->features.backend.disable_parallel_shader_compile,
@@ -116,7 +116,11 @@ backend::Platform::DriverConfig getDriverConfig(FEngine* instance) {
.gpuContextPriority = instance->getConfig().gpuContextPriority,
.vulkanEnableStagingBufferBypass =
instance->features.backend.vulkan.enable_staging_buffer_bypass,
.asynchronousMode = instance->features.backend.enable_asynchronous_operation ?
instance->getConfig().asynchronousMode : AsynchronousMode::NONE,
};
return driverConfig;
}
} // anonymous
@@ -727,7 +731,6 @@ void FEngine::prepare() {
// post-process materials instances must be commited explicitly because their
// parameters are typically not set at this point in time.
if (item->getMaterial()->getMaterialDomain() == MaterialDomain::SURFACE) {
item->commitStreamUniformAssociations(driver);
item->commit(driver, uboManager);
}
});
@@ -991,10 +994,9 @@ FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
return p;
}
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, const char* name,
UboBatchingMode batchingMode) noexcept {
FMaterialInstance* p =
mHeapAllocator.make<FMaterialInstance>(*this, material, name, batchingMode);
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
const char* name) noexcept {
FMaterialInstance* p = mHeapAllocator.make<FMaterialInstance>(*this, material, name);
if (UTILS_LIKELY(p)) {
auto pos = mMaterialInstances.emplace(material, "MaterialInstance");
pos.first->second.insert(p);
@@ -1503,6 +1505,11 @@ Engine::FeatureLevel FEngine::setActiveFeatureLevel(FeatureLevel 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__)
void FEngine::resetBackendState() noexcept {
getDriverApi().resetState();
+7 -16
View File
@@ -216,6 +216,8 @@ public:
return getDriver().isStereoSupported();
}
bool isAsynchronousOperationSupported() const noexcept;
static size_t getMaxStereoscopicEyes() noexcept {
return CONFIG_MAX_STEREOSCOPIC_EYES;
}
@@ -345,21 +347,10 @@ public:
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,
const FMaterialInstance* other, const char* name) noexcept;
FMaterialInstance* createMaterialInstance(const FMaterial* material, const char* name,
UboBatchingMode batchingMode) noexcept;
FMaterialInstance* createMaterialInstance(const FMaterial* material, const char* name) noexcept;
FScene* createScene() noexcept;
FView* createView() noexcept;
@@ -778,7 +769,6 @@ public:
bool assert_material_instance_texture_descriptor_set_compatible =
CORRECTNESS_ASSERTION_DEFAULT;
bool assert_texture_can_generate_mipmap = CORRECTNESS_ASSERTION_DEFAULT;
bool assert_camera_projection_near_far = CORRECTNESS_ASSERTION_DEFAULT;
} debug;
struct {
bool disable_gpu_frame_complete_metric = true;
@@ -798,6 +788,7 @@ public:
bool disable_amortized_shader_compile = true;
bool disable_handle_use_after_free_check = false;
bool disable_heap_handle_tags = true; // FIXME: this should be false
bool enable_asynchronous_operation = false;
} backend;
struct {
bool check_crc32_after_loading = false;
@@ -818,6 +809,9 @@ public:
{ "backend.disable_heap_handle_tags",
"Disable Handle<> tags for heap-allocated handles.",
&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",
"Asserts that the ANativeWindow is valid when rendering starts.",
&features.backend.opengl.assert_native_window_is_valid, true },
@@ -848,9 +842,6 @@ public:
{ "engine.debug.assert_texture_can_generate_mipmap",
"Assert if a texture has the correct usage set for generating mipmaps.",
&features.engine.debug.assert_texture_can_generate_mipmap, false },
{ "engine.debug.assert_camera_projection_near_far",
"Assert that the near plane is smaller than the far plane.",
&features.engine.debug.assert_camera_projection_near_far, false },
{ "material.check_crc32_after_loading",
"Verify the checksum of package data when a material is loaded.",
&features.material.check_crc32_after_loading, false },
+22 -3
View File
@@ -70,10 +70,19 @@
namespace filament {
namespace {
using namespace backend;
using namespace filaflat;
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 {
const void* mPayload = nullptr;
@@ -81,6 +90,7 @@ struct Material::BuilderDetails {
bool mDefaultMaterial = false;
int32_t mShBandsCount = 3;
Builder::ShadowSamplingQuality mShadowSamplingQuality = Builder::ShadowSamplingQuality::LOW;
UboBatchingMode mUboBatchingMode = UboBatchingMode::DEFAULT;
std::unordered_map<
CString,
std::variant<int32_t, float, bool>,
@@ -115,6 +125,11 @@ Material::Builder& Material::Builder::shadowSamplingQuality(ShadowSamplingQualit
return *this;
}
Material::Builder& Material::Builder::uboBatching(UboBatchingMode const mode) noexcept {
mImpl->mUboBatchingMode = mode;
return *this;
}
template<typename T, typename>
Material::Builder& Material::Builder::constant(const char* name, size_t nameLength, T value) {
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)
: mDefinition(definition),
mIsDefaultMaterial(builder->mDefaultMaterial),
mUseUboBatching(shouldEnableBatching(engine, builder->mUboBatchingMode,
definition.materialDomain)),
mEngine(engine),
mMaterialId(engine.getMaterialId()) {
FILAMENT_CHECK_PRECONDITION(!mUseUboBatching || engine.isUboBatchingEnabled())
<< "UBO batching is not enabled.";
mSpecializationConstants = processSpecializationConstants(builder);
precacheDepthVariants(engine);
@@ -282,14 +301,14 @@ FMaterialInstance* FMaterial::createInstance(const char* name) const noexcept {
return FMaterialInstance::duplicate(mDefaultMaterialInstance, name);
} else {
// 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 {
if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) {
mDefaultMaterialInstance =
mEngine.createMaterialInstance(this, mDefinition.name.c_str(), UboBatchingMode::DEFAULT);
mEngine.createMaterialInstance(this, mDefinition.name.c_str());
mDefaultMaterialInstance->setDefaultInstance(true);
}
return mDefaultMaterialInstance;
+6
View File
@@ -248,6 +248,10 @@ public:
return mDefinition.perViewLayoutIndex;
}
bool useUboBatching() const noexcept {
return mUseUboBatching;
}
#if FILAMENT_ENABLE_MATDBG
void applyPendingEdits() noexcept;
@@ -310,6 +314,8 @@ private:
bool mIsDefaultMaterial = false;
bool mUseUboBatching = false;
// reserve some space to construct the default material instance
mutable FMaterialInstance* mDefaultMaterialInstance = nullptr;
+7 -47
View File
@@ -58,18 +58,8 @@ using namespace utils;
namespace filament {
using namespace backend;
using UboBatchingMode = FEngine::UboBatchingMode;
bool shouldEnableBatching(FEngine& engine, UboBatchingMode batchingMode, MaterialDomain domain) {
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
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name) noexcept
: mMaterial(material),
mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()),
mCulling(CullingMode::BACK),
@@ -80,12 +70,9 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
mHasScissor(false),
mIsDoubleSided(false),
mIsDefaultInstance(false),
mUseUboBatching(
shouldEnableBatching(engine, batchingMode, material->getMaterialDomain())),
mUseUboBatching(material->useUboBatching()),
mTransparencyMode(TransparencyMode::DEFAULT),
mName(name ? CString(name) : material->getName()) {
FILAMENT_CHECK_PRECONDITION(!mUseUboBatching || engine.isUboBatchingEnabled())
<< "UBO batching is not enabled.";
FEngine::DriverApi& driver = engine.getDriverApi();
// 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),
mScissorRect(other->mScissorRect),
mName(name ? CString(name) : other->mName) {
assert_invariant(!mUseUboBatching || engine.isUboBatchingEnabled());
FEngine::DriverApi& driver = engine.getDriverApi();
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 {
if (UTILS_LIKELY(mMaterial->getMaterialDomain() != MaterialDomain::SURFACE)) {
commit(engine.getDriverApi(), engine.getUboManager());
@@ -255,10 +216,9 @@ void FMaterialInstance::commit(FEngine& engine) const {
}
void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManager) const {
if (mUniforms.isDirty() || mHasStreamUniformAssociations) {
if (mUniforms.isDirty()) {
mUniforms.clean();
if (mUseUboBatching) {
assert_invariant(uboManager != nullptr);
if (isUsingUboBatching()) {
if (!BufferAllocator::isValid(getAllocationId())) {
// The allocation hasn't happened yet, return.
return;
@@ -289,7 +249,7 @@ void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManage
// TODO: eventually we should remove this in RELEASE builds
fixMissingSamplers();
if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) {
if (isUsingUboBatching() && !BufferAllocator::isValid(getAllocationId())) {
return;
}
@@ -439,7 +399,7 @@ void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const {
return;
}
if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) {
if (isUsingUboBatching() && !BufferAllocator::isValid(getAllocationId())) {
return;
}
@@ -476,7 +436,7 @@ void FMaterialInstance::assignUboAllocation(
const Handle<HwBufferObject>& ubHandle,
BufferAllocator::AllocationId id,
BufferAllocator::allocation_size_t offset) {
assert_invariant(mUseUboBatching);
assert_invariant(isUsingUboBatching());
mUboData = id;
mUboOffset = offset;
+1 -5
View File
@@ -56,8 +56,7 @@ class FTexture;
class FMaterialInstance : public MaterialInstance {
public:
FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name,
FEngine::UboBatchingMode batchingMode) noexcept;
FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name) noexcept;
FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name);
FMaterialInstance(const FMaterialInstance& rhs) = delete;
FMaterialInstance& operator=(const FMaterialInstance& rhs) = delete;
@@ -67,8 +66,6 @@ public:
~FMaterialInstance() noexcept;
void terminate(FEngine& engine);
void commitStreamUniformAssociations(FEngine::DriverApi& driver);
void commit(FEngine& engine) const;
@@ -288,7 +285,6 @@ private:
tsl::robin_map<backend::descriptor_binding_t, TextureParameter> mTextureParameters;
mutable DescriptorSet mDescriptorSet;
UniformBuffer mUniforms;
bool mHasStreamUniformAssociations = false;
backend::PolygonOffset mPolygonOffset{};
backend::StencilState mStencilState{};
+8 -20
View File
@@ -292,16 +292,11 @@ void FRenderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) {
// do this before engine.flush()
mResourceAllocator->gc(true);
// Run the component managers' GC in parallel
// WARNING: while doing this we can't access any component manager
auto& js = engine.getJobSystem();
auto *job = js.runAndRetain(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers
engine.flush(); // flush command stream
// make sure we're done with the gcs
js.waitAndRelease(job);
// Run the component managers' GC
auto& js = engine.getJobSystem();
js.runAndWait(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers
}
bool FRenderer::shouldRenderFrame() const noexcept {
@@ -468,16 +463,11 @@ void FRenderer::endFrame() {
// do this before engine.flush()
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
// make sure we're done with the gcs
js.waitAndRelease(job);
// Run the component managers' GC
auto& js = engine.getJobSystem();
js.runAndWait(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers
}
void FRenderer::readPixels(
@@ -571,7 +561,6 @@ void FRenderer::renderStandaloneView(FView const* view) {
if (UTILS_LIKELY(view->getScene())) {
mPreviousRenderTargets.clear();
mFrameId++;
// ask the engine to do what it needs to (e.g. updates light buffer, materials...)
FEngine& engine = mEngine;
@@ -581,8 +570,7 @@ void FRenderer::renderStandaloneView(FView const* view) {
driver.beginFrame(
steady_clock::now().time_since_epoch().count(),
mDisplayInfo.refreshRate == 0.0 ? 0 : int64_t(
1'000'000'000.0 / mDisplayInfo.refreshRate),
mFrameId);
1'000'000'000.0 / mDisplayInfo.refreshRate), 0);
// 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
@@ -591,7 +579,7 @@ void FRenderer::renderStandaloneView(FView const* view) {
engine.submitFrame();
driver.endFrame(mFrameId);
driver.endFrame(0);
// 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
+1 -1
View File
@@ -209,7 +209,7 @@ private:
backend::Handle<backend::HwRenderTarget> mRenderTargetHandle;
FSwapChain* mSwapChain = nullptr;
size_t mCommandsHighWatermark = 0;
uint32_t mFrameId = 0;
uint32_t mFrameId = 1; // id 0 is reserved for standalone views
FrameInfoManager mFrameInfoManager;
backend::TextureFormat mHdrTranslucent;
backend::TextureFormat mHdrQualityMedium;
+15 -1
View File
@@ -13,11 +13,25 @@ set(GENERATION_ROOT ${CMAKE_CURRENT_BINARY_DIR})
set(RESOURCE_DIR "${GENERATION_ROOT}/resources")
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)
set(RESOURCE_BINS
${CMAKE_CURRENT_SOURCE_DIR}/test_material.filamat
${CMAKE_CURRENT_SOURCE_DIR}/test_material_transformname.filamat
${TEST_MATERIAL_TRANSFORM_NAME_FILAMAT}
)
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
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_transformname.filamat samples/materials/sandboxTransformName.mat
set +x
@@ -170,10 +170,6 @@ public:
// negative value if name doesn't exist or Panic if exceptions are enabled
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;
bool hasField(std::string_view name) const noexcept {
@@ -201,7 +197,6 @@ private:
utils::CString mName;
utils::FixedCapacityVector<FieldInfo> mFieldInfoList;
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
Alignment mAlignment = Alignment::std140;
Target mTarget = Target::UNIFORM;
@@ -122,8 +122,6 @@ BufferInterfaceBlock::BufferInterfaceBlock(Builder const& builder) noexcept
auto& uniformsInfoList = mFieldInfoList;
auto& transformOffsetMap = mTransformOffsetMap;
uint32_t i = 0;
uint16_t offset = 0;
for (auto const& e : builder.mEntries) {
@@ -150,11 +148,6 @@ BufferInterfaceBlock::BufferInterfaceBlock(Builder const& builder) noexcept
// record this uniform info
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
offset += stride * std::max(1u, e.size);
++i;
@@ -170,14 +163,6 @@ ssize_t BufferInterfaceBlock::getFieldOffset(std::string_view name, size_t 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(
std::string_view name) const {
auto pos = mInfoMap.find(name);
+5 -3
View File
@@ -17,9 +17,6 @@
#ifndef FILAGUI_IMGUIHELPER_H_
#define FILAGUI_IMGUIHELPER_H_
#include <vector>
#include <functional>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/Material.h>
@@ -32,6 +29,10 @@
#include <utils/Entity.h>
#include <utils/Path.h>
#include <functional>
#include <vector>
#include <unordered_set>
struct ImDrawData;
struct ImGuiIO;
struct ImGuiContext;
@@ -100,6 +101,7 @@ public:
filament::TextureSampler mSampler;
bool mFlipVertical = false;
utils::Path mSettingsPath;
std::unordered_set<filament::Texture*> mImGuiTextures;
};
} // namespace filagui
+14 -7
View File
@@ -148,6 +148,12 @@ ImGuiHelper::~ImGuiHelper() {
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();
em.destroy(mRenderable);
em.destroy(mCameraEntity);
@@ -189,7 +195,6 @@ void ImGuiHelper::render(float timeStepInSeconds, Callback imguiCommands) {
void ImGuiHelper::processImGuiCommands(ImDrawData* commands, const ImGuiIO& io) {
ImGui::SetCurrentContext(mImGuiContext);
if (commands->Textures != nullptr) {
for (ImTextureData* tex : *commands->Textures) {
if (tex->Status == ImTextureStatus_OK) {
@@ -203,7 +208,6 @@ void ImGuiHelper::processImGuiCommands(ImDrawData* commands, const ImGuiIO& io)
.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,
@@ -213,6 +217,7 @@ void ImGuiHelper::processImGuiCommands(ImDrawData* commands, const ImGuiIO& io)
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,
@@ -222,11 +227,13 @@ void ImGuiHelper::processImGuiCommands(ImDrawData* commands, const ImGuiIO& io)
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);
tex->UnusedFrames > 0) {
filament::Texture* ftex = (filament::Texture*)tex->TexID;
mEngine->destroy(ftex);
tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
mImGuiTextures.erase(ftex);
}
}
}
@@ -38,19 +38,18 @@ void* getNativeWindow(SDL_Window* sdlWindow) {
SDL_GetWindowSize(sdlWindow, &width, &height);
// 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
// random segfaults.
// Without static the valid struct quickly goes out of scope, and ends with seemingly
// random segfaults. We must update the values on each call.
static struct {
struct wl_display *display;
struct wl_surface *surface;
uint32_t width;
uint32_t height;
} wayland {
wmi.info.wl.display,
wmi.info.wl.surface,
static_cast<uint32_t>(width),
static_cast<uint32_t>(height)
};
} wayland;
wayland.display = wmi.info.wl.display;
wayland.surface = wmi.info.wl.surface;
wayland.width = static_cast<uint32_t>(width);
wayland.height = static_cast<uint32_t>(height);
return (void *) &wayland;
#endif
}
+117 -17
View File
@@ -18,25 +18,125 @@
#include <benchmark/benchmark.h>
#include <utils/memalign.h>
static void BM_memcpy(benchmark::State& state) {
char* src = new char[state.range(0)];
char* dst = new char[state.range(0)];
memset(src, 'x', (size_t)state.range(0));
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#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;
delete[] dst;
#ifdef __linux__
#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)
;
-1
View File
@@ -37,7 +37,6 @@ set(MATERIAL_SRCS
materials/sandboxLitSolidRefractionSsr.mat
materials/sandboxSpecGloss.mat
materials/sandboxSubsurface.mat
materials/sandboxTransformName.mat
materials/sandboxUnlit.mat
materials/texturedLit.mat
)
+2 -1
View File
@@ -42,7 +42,8 @@ def _render_single_model(gltf_viewer, test_json_path, named_output_dir,
env |= {
'VK_ICD_FILENAMES': vk_icd,
'VK_DRIVER_FILES': vk_icd,
'VK_LOADER_DEBUG': 'all',
# Uncomment when there's a problem finding the vk driver
# 'VK_LOADER_DEBUG': 'all',
}
out_name = f'{test_name}.{backend}.{model}'
+1 -1
View File
@@ -8,6 +8,6 @@ itsdangerous==2.2.0
Jinja2==3.1.6
MarkupSafe==3.0.2
requests==2.32.5
urllib3==2.5.0
urllib3==2.6.0
waitress==3.0.2
Werkzeug==3.1.4
+11 -6
View File
@@ -2,8 +2,6 @@ project(basisu)
cmake_minimum_required(VERSION 3.19)
set(ZSTD zstd-basisu)
set(TRANSCODER_SRC
../transcoder/basisu_transcoder.cpp
)
@@ -52,21 +50,28 @@ else()
set (BASIS_CONFIG ${BASIS_CONFIG} BASISD_SUPPORT_DXT5A=0 BASISD_SUPPORT_DXT1=0)
endif()
add_library(${ZSTD} ../zstd/zstd.c)
# If an external zstd target exists, use it.
if(NOT TARGET zstd)
add_library(zstd-basisu ../zstd/zstd.c)
target_include_directories(zstd-basisu PUBLIC ../zstd)
set_target_properties(zstd-basisu PROPERTIES FOLDER ThirdParty)
set(ZSTD_TARGET zstd-basisu)
else()
set(ZSTD_TARGET zstd)
endif()
add_library(basis_encoder ${ENCODER_SRC})
add_library(basis_transcoder ${TRANSCODER_SRC})
target_link_libraries(basis_transcoder PRIVATE ${ZSTD})
target_link_libraries(basis_transcoder PRIVATE ${ZSTD_TARGET})
target_link_libraries(basis_encoder basis_transcoder)
target_include_directories(${ZSTD} PUBLIC ../zstd)
target_include_directories(basis_encoder PUBLIC ../encoder)
target_include_directories(basis_transcoder PUBLIC ../transcoder)
target_compile_definitions(basis_encoder PRIVATE ${BASIS_CONFIG})
target_compile_definitions(basis_transcoder PRIVATE ${BASIS_CONFIG})
set_target_properties(${ZSTD} PROPERTIES FOLDER ThirdParty)
set_target_properties(basis_encoder PROPERTIES FOLDER ThirdParty)
set_target_properties(basis_transcoder PROPERTIES FOLDER ThirdParty)
+4 -2
View File
@@ -125,10 +125,12 @@ Filament.loadClassExtensions = function() {
/// createMaterial ::method::
/// package ::argument:: asset string, or Uint8Array, or [Buffer] with filamat contents
/// options ::argument:: optional dictionary with `uboBatching` key.
/// ::retval:: an instance of [createMaterial]
Filament.Engine.prototype.createMaterial = function(buffer) {
Filament.Engine.prototype.createMaterial = function(buffer, options) {
buffer = getBufferDescriptor(buffer);
const result = this._createMaterial(buffer);
const uboBatching = (options && options.uboBatching) || Filament.Material$UboBatchingMode.DEFAULT; // The default is DEFAULT
const result = this._createMaterial(buffer, uboBatching);
buffer.delete();
return result;
};
+5 -1
View File
@@ -375,6 +375,10 @@ export class Material {
public getName(): string;
}
export enum Material$UboBatchingMode {
DISABLED,
DEFAULT,
}
export class Frustum {
constructor(pv: mat4);
public setProjection(pv: mat4): void;
@@ -542,7 +546,7 @@ export class Engine {
public static destroy(engine: Engine): void;
public execute(): void;
public createCamera(entity: Entity): Camera;
public createMaterial(urlOrBuffer: BufferReference): Material;
public createMaterial(urlOrBuffer: BufferReference, options?: { uboBatching?: Material$UboBatchingMode }): Material;
public createRenderer(): Renderer;
public createScene(): Scene;
public createSwapChain(): SwapChain;
+9 -2
View File
@@ -495,8 +495,11 @@ class_<Engine>("Engine")
(Engine* engine, utils::Entity camera) { engine->destroyCameraComponent(camera); },
allow_raw_pointers())
.function("_createMaterial", EMBIND_LAMBDA(Material*, (Engine* engine, BufferDescriptor mbd), {
return Material::Builder().package(mbd.bd->buffer, mbd.bd->size).build(*engine);
.function("_createMaterial", EMBIND_LAMBDA(Material*, (Engine* engine, BufferDescriptor mbd, Material::UboBatchingMode uboBatching), {
return Material::Builder()
.package(mbd.bd->buffer, mbd.bd->size)
.uboBatching(uboBatching)
.build(*engine);
}), allow_raw_pointers())
/// destroyMaterial ::method::
/// material ::argument:: an instance of [Material]
@@ -1360,6 +1363,10 @@ class_<Material>("Material")
return transformName ? std::string(transformName) : std::string();
}), allow_raw_pointers());
enum_<Material::UboBatchingMode>("Material$UboBatchingMode")
.value("DISABLED", Material::UboBatchingMode::DISABLED)
.value("DEFAULT", Material::UboBatchingMode::DEFAULT);
class_<MaterialInstance>("MaterialInstance")
.function("getName", EMBIND_LAMBDA(std::string, (MaterialInstance* self), {
return std::string(self->getName());