Compare commits

..
Author SHA1 Message Date
Powei Feng 37d0bd71db Update ImGui to 1.92.5 2025-12-02 09:49:48 -08:00
123 changed files with 992 additions and 2750 deletions
+1 -2
View File
@@ -21,9 +21,8 @@ jobs:
- name: Run update script
env:
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
run: |
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py)
GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | 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,17 +134,18 @@ jobs:
env:
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
run: |
ls ./gltf/Models
TEST_DIR=test/renderdiff
source ${TEST_DIR}/src/preamble.sh
set -eux
start_
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 -eux`
set +eux
# Note that we need to upload the output even if comparison fails, so we undo `set -ex`
end_
python3 ${TEST_DIR}/src/compare.py \
--src=${GOLDEN_OUTPUT_DIR} \
@@ -157,7 +158,6 @@ jobs:
cat compare_output.txt >> "$GITHUB_OUTPUT"
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
fi
shell: bash
- uses: actions/upload-artifact@v4
with:
name: presubmit-renderdiff-result
+2 -4
View File
@@ -872,9 +872,7 @@ add_subdirectory(${LIBRARIES}/utils)
add_subdirectory(${LIBRARIES}/viewer)
add_subdirectory(${FILAMENT}/shaders)
add_subdirectory(${EXTERNAL}/abseil/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}/basisu/tnt)
add_subdirectory(${EXTERNAL}/civetweb/tnt)
add_subdirectory(${EXTERNAL}/imgui/tnt)
add_subdirectory(${EXTERNAL}/robin-map/tnt)
@@ -888,7 +886,7 @@ add_subdirectory(${EXTERNAL}/jsmn/tnt)
add_subdirectory(${EXTERNAL}/stb/tnt)
add_subdirectory(${EXTERNAL}/getopt)
add_subdirectory(${EXTERNAL}/perfetto/tnt)
add_subdirectory(${EXTERNAL}/basisu/tnt)
add_subdirectory(${EXTERNAL}/zstd/tnt)
# 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 {
implementation 'com.google.android.filament:filament-android:1.68.2'
implementation 'com.google.android.filament:filament-android:1.67.1'
}
```
@@ -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:
```shell
pod 'Filament', '~> 1.68.2'
pod 'Filament', '~> 1.67.1'
```
## Documentation
-7
View File
@@ -7,13 +7,6 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.68.2
- Support `setPresentationTime` with the Metal backend.
## v1.68.1
## v1.68.0
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
+1 -1
View File
@@ -100,7 +100,7 @@ buildscript {
'kotlin': '2.0.21',
'kotlin_coroutines': '1.9.0',
'buildTools': '35.0.0',
'ndk': '29.0.14206865',
'ndk': '27.0.11718014',
'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, jint uboBatchingMode) {
jlong nativeEngine, jobject buffer_, jint size, jint shBandCount, jint shadowQuality) {
Engine* engine = (Engine*) nativeEngine;
AutoBuffer buffer(env, buffer_, size);
auto builder = Material::Builder();
@@ -33,7 +33,6 @@ 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,20 +261,6 @@ 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;
@@ -386,7 +372,6 @@ public class Material {
private int mSize;
private int mShBandCount = 0;
private ShadowSamplingQuality mShadowSamplingQuality = ShadowSamplingQuality.LOW;
private UboBatchingMode mUboBatchingMode = UboBatchingMode.DEFAULT;
/**
@@ -431,17 +416,6 @@ 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.
*
@@ -454,7 +428,7 @@ public class Material {
@NonNull
public Material build(@NonNull Engine engine) {
long nativeMaterial = nBuilderBuild(engine.getNativeObject(),
mBuffer, mSize, mShBandCount, mShadowSamplingQuality.ordinal(), mUboBatchingMode.ordinal());
mBuffer, mSize, mShBandCount, mShadowSamplingQuality.ordinal());
if (nativeMaterial == 0) throw new IllegalStateException("Couldn't create Material");
return new Material(nativeMaterial);
}
@@ -1120,7 +1094,7 @@ public class Material {
mNativeObject = 0;
}
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount, int shadowQuality, int uboBatchingMode);
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount, int shadowQuality);
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
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.68.2
VERSION_NAME=1.67.1
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
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=${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 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)
# Unless explicitly specified, NDK version will be set to match exactly the required one
FILAMENT_NDK_VERSION=${GITHUB_NDK_VERSION:-27.0.11718014}
(! 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=29.0.14206865
GITHUB_NDK_VERSION=27.0.11718014
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 -march=armv8-a -mtune=cortex-a78" CACHE STRING "Toolchain CFLAGS")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE -mcpu=cortex-a57" 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 -mfloat-abi=softfp -mfpu=neon-vfpv4 -fPIE" CACHE STRING "Toolchain CFLAGS")
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_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.68.0'
implementation 'com.google.android.filament:filament-android:1.67.1'
}
</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.68.0'
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.67.1'
</code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul>
+270 -268
View File
File diff suppressed because one or more lines are too long
+164 -162
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.6.0
urllib3==2.5.0
websocket-client==1.9.0
wsproto==1.2.0
-7
View File
@@ -36,7 +36,6 @@ set(SRCS
src/Driver.cpp
src/Handle.cpp
src/HandleAllocator.cpp
src/JobQueue.cpp
src/ostream.cpp
src/noop/NoopDriver.cpp
src/noop/PlatformNoop.cpp
@@ -60,7 +59,6 @@ set(PRIVATE_HDRS
src/CompilerThreadPool.h
src/DataReshaper.h
src/DriverBase.h
src/JobQueue.h
)
# ==================================================================================================
@@ -208,8 +206,6 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanDriverFactory.h
src/vulkan/VulkanExternalImageManager.cpp
src/vulkan/VulkanExternalImageManager.h
src/vulkan/VulkanStreamedImageManager.cpp
src/vulkan/VulkanStreamedImageManager.h
src/vulkan/VulkanFboCache.cpp
src/vulkan/VulkanFboCache.h
src/vulkan/VulkanHandles.cpp
@@ -320,8 +316,6 @@ 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
@@ -570,7 +564,6 @@ if (APPLE OR LINUX)
test/test_ReadPixels.cpp
test/test_BufferUpdates.cpp
test/test_Callbacks.cpp
test/test_JobQueue.cpp
test/test_MemoryMappedBuffer.cpp
test/test_MsaaSwapChain.cpp
test/test_MRT.cpp
@@ -0,0 +1,63 @@
/*
* 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,8 +1713,6 @@ using FrameTimestamps = Platform::FrameTimestamps;
using CompositorTiming = Platform::CompositorTiming;
using AsynchronousMode = Platform::AsynchronousMode;
} // namespace filament::backend
template<> struct utils::EnableBitMaskOperators<filament::backend::ShaderStageFlags>
@@ -74,9 +74,6 @@ public:
ExternalImageHandle& operator=(ExternalImageHandle const& 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; }
ExternalImage* UTILS_NULLABLE get() noexcept { return mTarget; }
@@ -255,28 +252,6 @@ 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.
@@ -346,11 +321,6 @@ public:
* - VulkanPlatform
*/
bool vulkanEnableStagingBufferBypass = false;
/**
* Asynchronous mode for the engine. Defines how asynchronous operations are handled.
*/
AsynchronousMode asynchronousMode = AsynchronousMode::NONE;
};
Platform() noexcept;
@@ -30,6 +30,8 @@
#include <math/mat3.h>
#include "AndroidNativeWindow.h"
#include <chrono>
#include <stddef.h>
@@ -22,6 +22,7 @@
#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,6 +51,7 @@
namespace filament::backend {
class BufferDescriptor;
class BufferObjectStreamDescriptor;
class CallbackHandler;
class PixelBufferDescriptor;
class Program;
@@ -392,6 +392,10 @@ 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,
-247
View File
@@ -1,247 +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.
*/
#include "JobQueue.h"
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Panic.h>
namespace filament::backend {
JobQueue::JobQueue(PassKey) {}
JobQueue::JobId JobQueue::push(Job job, JobId const preIssuedJobId/* = InvalidJobId*/) {
JobId jobId = preIssuedJobId;
{
std::lock_guard<std::mutex> lock(mQueueMutex);
if (mIsStopping) {
return InvalidJobId;
}
if (jobId == InvalidJobId) {
jobId = genNextJobId();
mJobsMap[jobId] = std::move(job);
} else {
// Use the job ID previously issued by `issueJobId()`
auto it = mJobsMap.find(jobId);
if (it == mJobsMap.end()) {
// Pre-issued job does not exist, either users passed a wrong id (unlikely)
// or the job must have been canceled (likely)
return InvalidJobId;
}
FILAMENT_CHECK_PRECONDITION(!static_cast<bool>(it->second))
<< "pre-issued job has already been populated";
it->second = std::move(job);
}
mJobOrder.push(jobId);
}
// Always notify. A ThreadWorker might be waiting.
mQueueCondition.notify_one();
return jobId;
}
JobQueue::Job JobQueue::pop(bool shouldBlock) {
std::unique_lock<std::mutex> lock(mQueueMutex);
decltype(mJobsMap)::iterator it;
while (true) {
if (shouldBlock) {
// Wait only if we're in blocking mode and the queue is empty
mQueueCondition.wait(lock, [this] { return !mJobOrder.empty() || mIsStopping; });
}
if (mJobOrder.empty()) {
// When `shouldBlock` is true, this means the queue is stopping now.
// When `shouldBlock` is false, this means there's no job.
return nullptr;
}
JobId jobId = mJobOrder.front();
mJobOrder.pop();
it = mJobsMap.find(jobId);
if (it != mJobsMap.end()) {
break;
}
// If execution reaches this line, the job must have been canceled right after being added.
// Therefore, we should continue the loop and attempt to retrieve the next available job.
}
Job job = std::move(it->second);
mJobsMap.erase(it);
return job;
}
utils::FixedCapacityVector<JobQueue::Job> JobQueue::popBatch(int const maxJobsToPop) {
utils::FixedCapacityVector<Job> jobs;
if (UTILS_UNLIKELY(maxJobsToPop == 0)) {
return jobs;
}
std::lock_guard<std::mutex> lock(mQueueMutex);
if (mJobOrder.empty()) {
return jobs;
}
// Calculate jobs to take. If maxJobsToPop is negative, we take all jobs.
size_t jobsToTake = mJobOrder.size();
if (0 < maxJobsToPop && maxJobsToPop < static_cast<int>(jobsToTake)) {
jobsToTake = maxJobsToPop;
}
jobs.reserve(jobsToTake);
while (0 < jobsToTake && !mJobOrder.empty()) {
JobId jobId = mJobOrder.front();
mJobOrder.pop();
auto it = mJobsMap.find(jobId);
if (UTILS_UNLIKELY(it == mJobsMap.end())) {
// The job was probably canceled.
continue;
}
jobs.push_back(std::move(it->second));
--jobsToTake;
mJobsMap.erase(it);
}
return jobs;
}
JobQueue::JobId JobQueue::issueJobId() noexcept {
std::lock_guard<std::mutex> lock(mQueueMutex);
JobId const jobId = genNextJobId();
// Preallocate a job, which serves two main purposes. It provides a valid jobId that can be
// checked for integrity when passed to the `push` method, and it enables job cancellation for
// tasks that are yet to be pushed.
mJobsMap[jobId];
return jobId;
}
bool JobQueue::cancel(JobId const jobId) noexcept {
std::lock_guard<std::mutex> lock(mQueueMutex);
auto it = mJobsMap.find(jobId);
if (it == mJobsMap.end()) {
return false; // Job not found, must have been completed or canceled.
}
mJobsMap.erase(it);
return true;
}
void JobQueue::stop() noexcept {
{
std::lock_guard<std::mutex> lock(mQueueMutex);
mIsStopping = true;
}
mQueueCondition.notify_all(); // Wake up all waiting threads
}
JobQueue::JobId JobQueue::genNextJobId() noexcept {
// We assume this method is called within the critical section.
JobId newJobId = mNextJobId++;
// We assume the job ID won't overflow or wraps around to zero within the application's lifetime.
assert_invariant(newJobId != InvalidJobId);
return newJobId;
}
JobWorker::~JobWorker() = default;
void JobWorker::terminate() {
// This is called from workers `terminate()`, which may hinder the concurrent use of multiple
// workers. Consider removing this line and require the owner/caller to explicitly invoke it to
// enable multiple worker instances.
if (mQueue) {
mQueue->stop();
}
}
AmortizationWorker::AmortizationWorker(JobQueue::Ptr queue, PassKey)
: JobWorker(std::move(queue)) {
}
AmortizationWorker::~AmortizationWorker() = default;
void AmortizationWorker::process(int const jobCount) {
if (!mQueue || jobCount == 0) {
return;
}
if (jobCount == 1) {
// Handle single job without vector allocation.
if (auto job = mQueue->pop(false)) {
job();
}
return;
}
// Handle batch (jobCount > 1 or jobCount < 0 for "all pending jobs")
utils::FixedCapacityVector<JobQueue::Job> jobs = mQueue->popBatch(jobCount);
if (jobs.empty()) {
return;
}
for (auto& job: jobs) {
job();
}
}
void AmortizationWorker::terminate() {
JobWorker::terminate();
// Drain all pending jobs.
process(-1);
}
ThreadWorker::ThreadWorker(JobQueue::Ptr queue, Config config, PassKey)
: JobWorker(std::move(queue)), mConfig(std::move(config)) {
mThread = std::thread([this]() {
utils::JobSystem::setThreadName(mConfig.name.data());
utils::JobSystem::setThreadPriority(mConfig.priority);
if (mConfig.onBegin) {
mConfig.onBegin();
}
while (JobQueue::Job job = mQueue->pop(true)) {
job();
}
if (mConfig.onEnd) {
mConfig.onEnd();
}
});
}
ThreadWorker::~ThreadWorker() = default;
void ThreadWorker::terminate() {
JobWorker::terminate();
if (mThread.joinable()) {
mThread.join();
}
}
} // namespace utils
-275
View File
@@ -1,275 +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_PRIVATE_JOBQUEUE_H
#define TNT_FILAMENT_BACKEND_PRIVATE_JOBQUEUE_H
#include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h>
#include <utils/JobSystem.h>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <memory>
#include <unordered_map>
#include <limits>
#include <queue>
namespace filament::backend {
/**
* A thread-safe producer-consumer queue with batching capabilities.
*
* This class is thread-safe. All public methods can be called from any thread.
*
* This class is stateless regarding concurrency. The *caller* decides the blocking behavior and/or
* batching when they call a 'pop' methods.
*
* A typical use case looks like this:
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* #include "private/backend/JobQueue.h"
* using namespace filament::backend;
*
* JobQueue::Ptr queue = JobQueue::create();
* JobWorker::Ptr worker = AmortizationWorker::create(queue);
* [ or JobWorker::Ptr worker = ThreadWorker::create(queue, config); ]
*
* void loop() {
* worker->process(2); // for AmortizationWorker
* }
*
* void cleanup() {
* worker->terminate();
* }
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* JobId id = queue->push([](){ ... });
* queue->cancel(id);
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* JobId preIssuedId = queue->issueJobId();
* JobId id = queue->push([](){ ... }, preIssuedId);
* assert(id == preIssuedId);
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
class JobQueue {
struct PassKey {};
public:
using Job = utils::Invocable<void()>;
using JobId = uint32_t;
using Ptr = std::shared_ptr<JobQueue>;
static constexpr JobId InvalidJobId = std::numeric_limits<JobId>::max();
/**
* Creates an instance of JobQueue. Users should call this to create one.
* @return An instance of JobQueue
*/
static Ptr create() {
return std::make_shared<JobQueue>(PassKey{});
}
explicit JobQueue(PassKey); // This can be created only via `create()`
/**
* Pushes a new job into queue.
*
* If the queue is in the process of shutting down (via a call to `stop`), this method does
* nothing (a no-op) and returns an invalid job ID.
*
* @param job The function/lambda to be executed.
* @param preIssuedJobId The previously issued job ID where this job is assigned to.
* If the value is `InvalidJobId`, a new job ID is generated internally.
* @return A new job ID. If a `preIssuedJobId` is provided, that specific ID is returned instead.
*/
JobId push(Job job, JobId preIssuedJobId = InvalidJobId);
/**
* Retrieves the next job from queue.
*
* This method returns a valid job ID if there are pending jobs in the queue, even if the queue
* is currently in the process of shutting down (via a call to `stop`).
*
* @param shouldBlock If true (typically used by ThreadWorker), waits for a job and returns it.
* If false (typically used by AmortizationWorker), tries retrieving a job. But may return an
* empty job if there's no pending job.
* @return The next job. If an empty job is returned, it has different meaning depending on the
* `shouldBlock` value:
* - true: Stop processing (shutting down).
* - false: No jobs currently in queue.
*/
Job pop(bool shouldBlock);
/**
* Retrieves a batch of next jobs from queue. Always non-blocking.
*
* This method returns a valid job ID if there are pending jobs in the queue, even if the queue
* is currently in the process of shutting down (via a call to `stop`).
*
* @param maxJobsToPop The maximum number of jobs to retrieve.
* If < 0, retrieves all pending jobs.
* @return A FixedCapacityVector<Job> containing the retrieved jobs.
* Returns an empty vector if the queue is empty.
*/
utils::FixedCapacityVector<Job> popBatch(int maxJobsToPop);
/**
* Generate a new job ID. This newly generated ID is meant to be used for the `preIssuedJobId`
* parameter of `push` method.
*
* @return A new job ID.
*/
JobId issueJobId() noexcept;
/**
* Cancels a job by its ID.
*
* @param jobId The job ID to cancel.
* @return true if the job was found and cancelled, false otherwise.
*/
bool cancel(JobId jobId) noexcept;
/**
* Signals the queue to shut down, after which no further jobs can be added using the `push`
* method. but all jobs already pushed can still be processed using `pop`.
*/
void stop() noexcept;
private:
JobQueue(const JobQueue&) = delete;
JobQueue& operator=(const JobQueue&) = delete;
JobId genNextJobId() noexcept;
std::mutex mQueueMutex;
std::condition_variable mQueueCondition;
std::unordered_map<JobId, Job> mJobsMap;
std::queue<JobId> mJobOrder;
JobId mNextJobId = 0;
bool mIsStopping = false;
};
/**
* Abstract base class for all worker types.
*/
class JobWorker {
public:
using Ptr = std::unique_ptr<JobWorker>;
virtual ~JobWorker();
/**
* Processes a batch of jobs. (For non-threaded workers)
* @param jobCount Max jobs to process (<= 0 for all).
*/
virtual void process(int jobCount) {}
/**
* Terminates the worker.
*/
virtual void terminate();
protected:
explicit JobWorker(JobQueue::Ptr queue) : mQueue(std::move(queue)) {}
JobQueue::Ptr mQueue;
private:
JobWorker(const JobWorker&) = delete;
JobWorker& operator=(const JobWorker&) = delete;
};
/**
* A non-threaded worker that consumes jobs in batches.
*/
class AmortizationWorker final : public JobWorker {
struct PassKey {};
public:
/**
* Creates an instance of AmortizationWorker. Users should call this to create one.
* @return An instance of AmortizationWorker
*/
static Ptr create(JobQueue::Ptr queue) {
return std::make_unique<AmortizationWorker>(std::move(queue), PassKey{});
}
explicit AmortizationWorker(JobQueue::Ptr queue, PassKey); // This can be created only via `create()`
~AmortizationWorker() override;
/**
* Polls the queue and executes a batch of jobs.
*
* @param jobCount The max number of jobs to process.
* 0 = do nothing.
* 1 = pop one (optimized).
* > 1 = pop batch.
* <= -1 = pop all.
*/
void process(int jobCount) override;
/**
* Signals the queue to stop and drain all pending jobs.
* This is safe to call multiple times.
*/
void terminate() override;
};
/**
* A threaded worker that consumes jobs one by one, blocking when empty.
*/
class ThreadWorker final : public JobWorker {
struct PassKey {};
public:
using Priority = utils::JobSystem::Priority;
/**
* Config settings for the worker
*/
struct Config {
std::string_view name = "";
Priority priority = Priority::NORMAL;
utils::Invocable<void()> onBegin; // Executed when the thread worker begins
utils::Invocable<void()> onEnd; // Executed when the thread worker ends
};
/**
* Creates an instance of ThreadWorker. Users should call this to create one.
* @return An instance of ThreadWorker
*/
static Ptr create(JobQueue::Ptr queue, Config config) {
return std::make_unique<ThreadWorker>(std::move(queue), std::move(config), PassKey{});
}
ThreadWorker(JobQueue::Ptr queue, Config config, PassKey); // This can be created only via `create()`
~ThreadWorker() override;
/**
* Signals the queue to stop and joins the worker thread.
* This is safe to call multiple times.
*/
void terminate() override;
private:
Config mConfig;
std::thread mThread;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_PRIVATE_JOBQUEUE_H
+6 -8
View File
@@ -312,8 +312,6 @@ void MetalDriver::execute(std::function<void(void)> const& fn) noexcept {
}
void MetalDriver::setPresentationTime(int64_t monotonic_clock_ns) {
assert_invariant(mContext->currentDrawSwapChain);
mContext->currentDrawSwapChain->setPresentationTime(monotonic_clock_ns);
}
void MetalDriver::endFrame(uint32_t frameId) {
@@ -1146,12 +1144,8 @@ 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.
// 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;
return mContext->highestSupportedGpuFamily.apple >= 1 &&
mContext->device.readWriteTextureSupport;
}
bool MetalDriver::isFrameBufferFetchMultiSampleSupported() {
@@ -1292,6 +1286,10 @@ 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))
+1 -8
View File
@@ -141,8 +141,6 @@ public:
// FrameScheduledCallback.
void present();
void setPresentationTime(int64_t timeNs) { presentationTimeNs = timeNs; }
NSUInteger getSurfaceWidth() const;
NSUInteger getSurfaceHeight() const;
NSUInteger getSampleCount() const;
@@ -161,7 +159,7 @@ private:
bool isCaMetalLayer() const { return type == SwapChainType::CAMETALLAYER; }
bool isHeadless() const { return type == SwapChainType::HEADLESS; }
void scheduleFrameScheduledCallback(int64_t presentationTimeNs);
void scheduleFrameScheduledCallback();
void scheduleFrameCompletedCallback();
MetalAttachment acquireBaseDrawable();
@@ -194,11 +192,6 @@ private:
int64_t abandonedUntilFrame = -1;
// If zero, the next presentation should happen as soon as possible.
// Otherwise, this is the timestamp when the present should happen.
// Resets to 0 after the present.
int64_t presentationTimeNs = 0;
// These fields store a callback to notify the client that a frame is ready for presentation. If
// !frameScheduled.callback, then the Metal backend automatically calls presentDrawable when the
// frame is committed. Otherwise, the Metal backend will not automatically present the frame.
+11 -34
View File
@@ -325,19 +325,11 @@ void MetalSwapChain::present() {
if (frameCompleted.callback) {
scheduleFrameCompletedCallback();
}
const auto timeNs = presentationTimeNs;
presentationTimeNs = 0;
if (drawable) {
if (frameScheduled.callback) {
scheduleFrameScheduledCallback(timeNs);
scheduleFrameScheduledCallback();
} else {
if (presentationTimeNs) {
const CFTimeInterval timeSeconds =
(CFTimeInterval) presentationTimeNs / 1000000000.0;
[getPendingCommandBuffer(&context) presentDrawable:drawable atTime:timeSeconds];
} else {
[getPendingCommandBuffer(&context) presentDrawable:drawable];
}
[getPendingCommandBuffer(&context) presentDrawable:drawable];
}
}
}
@@ -349,22 +341,15 @@ public:
PresentDrawableData& operator=(const PresentDrawableData&) = delete;
static PresentDrawableData* create(id<CAMetalDrawable> drawable,
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags,
int64_t presentationTimeNs) {
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags) {
assert_invariant(drawableMutex);
assert_invariant(driver);
return new PresentDrawableData(drawable, drawableMutex, driver, flags, presentationTimeNs);
return new PresentDrawableData(drawable, drawableMutex, driver, flags);
}
static void maybePresentAndDestroyAsync(PresentDrawableData* that, bool shouldPresent) {
if (shouldPresent) {
if (that->mPresentationTimeNs) {
const CFTimeInterval timeSeconds =
(CFTimeInterval) that->mPresentationTimeNs / 1000000000.0;
[that->mDrawable presentAtTime:timeSeconds];
} else {
[that->mDrawable present];
}
[that->mDrawable present];
}
if (that->mFlags & SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER) {
@@ -382,12 +367,8 @@ public:
private:
PresentDrawableData(id<CAMetalDrawable> drawable, std::shared_ptr<std::mutex> drawableMutex,
MetalDriver* driver, uint64_t flags, int64_t presentationTimeNs)
: mDrawable(drawable),
mDrawableMutex(drawableMutex),
mDriver(driver),
mFlags(flags),
mPresentationTimeNs(presentationTimeNs) {}
MetalDriver* driver, uint64_t flags)
: mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver), mFlags(flags) {}
static void cleanupAndDestroy(PresentDrawableData *that) {
if (that->mDrawable) {
@@ -403,7 +384,6 @@ private:
std::shared_ptr<std::mutex> mDrawableMutex;
MetalDriver* mDriver = nullptr;
uint64_t mFlags = 0;
int64_t mPresentationTimeNs = 0;
};
void presentDrawable(bool presentFrame, void* user) {
@@ -411,7 +391,7 @@ void presentDrawable(bool presentFrame, void* user) {
PresentDrawableData::maybePresentAndDestroyAsync(presentDrawableData, presentFrame);
}
void MetalSwapChain::scheduleFrameScheduledCallback(int64_t presentationTimeNs) {
void MetalSwapChain::scheduleFrameScheduledCallback() {
if (!frameScheduled.callback) {
return;
}
@@ -420,11 +400,8 @@ void MetalSwapChain::scheduleFrameScheduledCallback(int64_t presentationTimeNs)
struct Callback {
Callback(std::shared_ptr<FrameScheduledCallback> callback, id<CAMetalDrawable> drawable,
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags,
int64_t presentationTimeNs)
: f(callback),
data(PresentDrawableData::create(drawable, drawableMutex, driver, flags,
presentationTimeNs)) {}
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags)
: f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver, flags)) {}
std::shared_ptr<FrameScheduledCallback> f;
// PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later.
std::unique_ptr<PresentDrawableData> data;
@@ -442,7 +419,7 @@ void MetalSwapChain::scheduleFrameScheduledCallback(int64_t presentationTimeNs)
uint64_t const flags = frameScheduled.flags;
ReleasablePointer* callback = [[ReleasablePointer alloc]
initWithPointer:new Callback(frameScheduled.callback, drawable, layerDrawableMutex,
context.driver, flags, presentationTimeNs)];
context.driver, flags)];
backend::CallbackHandler* handler = frameScheduled.handler;
MetalDriver* driver = context.driver;
@@ -111,22 +111,9 @@ 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,6 +291,8 @@ 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,6 +39,7 @@
#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"
@@ -2836,6 +2837,38 @@ 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()
@@ -2849,6 +2882,19 @@ 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,6 +36,7 @@
#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"
@@ -393,6 +394,8 @@ 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__) || defined(FILAMENT_SUPPORTS_EGL_ON_LINUX)
#if defined(__ANDROID__) || defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(__EMSCRIPTEN__)
#if defined(__EMSCRIPTEN__)
# include <GLES3/gl3.h>
@@ -178,30 +178,20 @@ void PlatformEGLAndroid::beginFrame(
int64_t const monotonic_clock_ns,
int64_t refreshIntervalNs,
uint32_t const frameId) noexcept {
// 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)
// associate the user frameid with the system frame id
setPresentFrameId(mCurrentDrawSwapChain, frameId);
// 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 (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;
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;
}
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));
@@ -450,10 +440,6 @@ 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 <bluegl/BlueGL.h>
#include "opengl/GLUtils.h"
#include <EGL/egl.h>
#include <EGL/eglext.h>
@@ -35,31 +35,16 @@ PlatformEGLHeadless::PlatformEGLHeadless() noexcept
}
bool PlatformEGLHeadless::isOpenGL() const noexcept {
#if defined(BACKEND_OPENGL_VERSION_GL)
return true;
#else
return false;
#endif // defined(BACKEND_OPENGL_VERSION_GL)
return true;
}
backend::Driver* PlatformEGLHeadless::createDriver(void* sharedContext,
const Platform::DriverConfig& driverConfig) {
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)) {
EGLBoolean bindAPI = eglBindAPI(EGL_OPENGL_API);
if (UTILS_UNLIKELY(!bindAPI)) {
LOG(ERROR) << "eglBindAPI EGL_OPENGL_API failed";
return nullptr;
}
int bindBlueGL = bluegl::bind();
if (UTILS_UNLIKELY(bindBlueGL != 0)) {
LOG(ERROR) << "bluegl bind failed";
+5
View File
@@ -15,6 +15,7 @@
*/
#include <backend/BufferDescriptor.h>
#include <backend/BufferObjectStreamDescriptor.h>
#include <backend/DescriptorSetOffsetArray.h>
#include <backend/DriverEnums.h>
#include <backend/PipelineState.h>
@@ -433,6 +434,10 @@ 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,9 +62,8 @@ 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 && mStagingBufferBypassEnabled) || isStaticOrShared) &&
isMemcopyable;
bool const useMemcpy = (isUniformAndAvailable || isStaticOrShared) && isMemcopyable &&
mStagingBufferBypassEnabled;
if (useMemcpy) {
char* dest = static_cast<char*>(mBuffer->getGpuBuffer()->allocationInfo.pMappedData) +
byteOffset;
@@ -19,7 +19,6 @@
#include "VulkanCommands.h"
#include "VulkanHandles.h"
#include "VulkanConstants.h"
#include "utils/compiler.h"
#include <utils/FixedCapacityVector.h>
#include <utils/Panic.h>
@@ -32,18 +31,6 @@ namespace filament::backend {
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 DescriptorCount = VulkanDescriptorSetCache::DescriptorCount;
@@ -317,7 +304,8 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
// This code actually binds the descriptor sets.
auto set = updateSets[index];
VkCommandBuffer const cmdbuffer = commands->buffer();
VkDescriptorSet const vkset = set->getVkSet();
VkDescriptorSet vkset = useExternalSamplers[index] ? set->getExternalSamplerVkSet() :
set->getVkSet();
commands->acquire(set);
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
1, &vkset, set->uniqueDynamicUboCount, set->getOffsets()->data());
@@ -353,39 +341,17 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
.pBufferInfo = &info,
};
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);
}
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
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();
void VulkanDescriptorSetCache::updateSamplerImpl(VkDescriptorSet vkset, uint8_t binding,
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept {
VkImageSubresourceRange range = texture->getPrimaryViewRange();
VkImageViewType const expectedType = texture->getViewType();
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
@@ -410,6 +376,19 @@ void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescr
.pImageInfo = &info,
};
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);
}
@@ -426,7 +405,7 @@ fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet
auto const& count = layout->count;
auto const vklayout = layout->getVkLayout();
auto set = fvkmemory::resource_ptr<VulkanDescriptorSet>::make(
mResourceManager, handle, layout,
mResourceManager, handle, layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
[vkSet, count, vklayout, this](
VulkanDescriptorSet*) { this->manualRecycle(count, vklayout, vkSet); },
vkSet);
@@ -449,22 +428,4 @@ void VulkanDescriptorSetCache::manualRecycle(VulkanDescriptorSetLayout::Count co
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
@@ -55,8 +55,7 @@ public:
VkDeviceSize size) noexcept;
void updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler,
VkDescriptorSetLayout externalSamplerLayout = VK_NULL_HANDLE) noexcept;
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept;
void updateSamplerForExternalSamplerSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
fvkmemory::resource_ptr<VulkanTexture> texture) noexcept;
@@ -91,8 +90,8 @@ public:
void resetCachedState() noexcept { mLastBoundInfo = {}; }
private:
void copySet(VkDescriptorSet srcSet, VkDescriptorSet destSet,
fvkutils::SamplerBitmask copyBindings) const;
void updateSamplerImpl(VkDescriptorSet set, uint8_t binding,
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept;
class DescriptorInfinitePool;
+12 -109
View File
@@ -31,7 +31,6 @@
#include "vulkan/memory/ResourcePointer.h"
#include "vulkan/utils/Conversion.h"
#include "vulkan/utils/Definitions.h"
#include "vulkan/utils/Image.h"
#include <backend/DriverEnums.h>
#include <backend/platforms/VulkanPlatform.h>
@@ -245,7 +244,6 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
mQueryManager(mPlatform->getDevice()),
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
&mDescriptorSetLayoutCache),
mStreamedImageManager(&mExternalImageManager),
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
mIsMSAASwapChainSupported(false), // TODO: support MSAA swapchain
mStereoscopicType(driverConfig.stereoscopicType) {
@@ -422,14 +420,12 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) {
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
// 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
if (mCurrentSwapChain) { // This should be guaranteed
mPlatform->setPresentFrameId(mCurrentSwapChain->swapChain, frameId);
}
// Check if any command have finished and reset all its used resources. The resources
// won't be destroyed but their reference count will be decreased if the command is already
// wont be destroyed but their reference count will decreased if the command is already
// completed.
//
// This will let us check if any VulkanBuffer is currently in flight or not.
@@ -483,9 +479,6 @@ void VulkanDriver::updateDescriptorSetTexture(
if (UTILS_UNLIKELY(mExternalImageManager.isExternallySampledTexture(texture))) {
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
mAppState.hasBoundExternalImages = true;
} else if (bool(texture->getStream())) {
mStreamedImageManager.bindStreamedTexture(set, binding, texture, params);
mAppState.hasBoundExternalImages = true;
} else {
VulkanSamplerCache::Params cacheParams = {
.sampler = params,
@@ -493,7 +486,6 @@ void VulkanDriver::updateDescriptorSetTexture(
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
mExternalImageManager.clearTextureBinding(set, binding);
mStreamedImageManager.unbindStreamedTexture(set, binding);
}
}
@@ -505,7 +497,10 @@ void VulkanDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_
assert_invariant(any(mmb->access & MapBufferAccessFlags::WRITE_BIT));
assert_invariant(offset + data.size <= mmb->size);
// On UMA systems, this is a memcpy into the memory-mapped buffer, otherwise use a staging buffer.
// TODO: this isa zero-effort implementation of copyToMemoryMappedBuffer(), where we just
// call updateBufferObject(). This could be a fallback implementation for when
// shared memory is not available.
// On UMA systems, this should just be a memcpy into the memory-mapped buffer.
updateBufferObject(mmb->boh, std::move(data), mmb->offset + offset);
}
@@ -1113,12 +1108,6 @@ void VulkanDriver::destroySwapChain(Handle<HwSwapChain> sch) {
}
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) {
@@ -1156,38 +1145,18 @@ void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
}
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream, utils::ImmutableCString tag) {
FVK_LOGW << "createStreamNative not supported in Vulkan.";
return {};
}
Handle<HwStream> VulkanDriver::createStreamAcquired(utils::ImmutableCString tag) {
// @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;
return {};
}
void VulkanDriver::setAcquiredImage(Handle<HwStream> sh, void* image, const math::mat3f& transform,
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) {
FVK_SYSTRACE_SCOPE();
auto stream = resource_ptr<VulkanStream>::cast(&mResourceManager, sh);
stream->width = width;
stream->height = height;
}
int64_t VulkanDriver::getStreamTimestamp(Handle<HwStream> sh) {
@@ -1195,70 +1164,6 @@ int64_t VulkanDriver::getStreamTimestamp(Handle<HwStream> sh) {
}
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) {
@@ -1554,13 +1459,17 @@ void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor
scheduleDestroy(std::move(p));
}
void VulkanDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh,
BufferObjectStreamDescriptor&& streams) {
// Noop
}
void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& bd,
uint32_t byteOffset) {
VulkanCommandBuffer& commands = mCommands.get();
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
commands.acquire(bo);
bo->loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
scheduleDestroy(std::move(bd));
@@ -1628,12 +1537,6 @@ TimerQueryResult VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint
}
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) {
@@ -34,7 +34,6 @@
#include "vulkan/VulkanDescriptorSetCache.h"
#include "vulkan/VulkanDescriptorSetLayoutCache.h"
#include "vulkan/VulkanExternalImageManager.h"
#include "vulkan/VulkanStreamedImageManager.h"
#include "vulkan/VulkanPipelineLayoutCache.h"
#include "vulkan/memory/ResourceManager.h"
#include "vulkan/memory/ResourcePointer.h"
@@ -158,8 +157,6 @@ private:
VulkanDescriptorSetCache mDescriptorSetCache;
VulkanQueryManager mQueryManager;
VulkanExternalImageManager mExternalImageManager;
VulkanStreamedImageManager mStreamedImageManager;
// This maps a VulkanSwapchain to a native swapchain. VulkanSwapchain should have a copy of the
// Platform::Swapchain pointer, but queryFrameTimestamps() and queryCompositorTiming() are
@@ -205,10 +202,6 @@ private:
bool const mIsSRGBSwapChainSupported;
bool const mIsMSAASwapChainSupported;
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
@@ -31,6 +31,9 @@ namespace filament::backend {
namespace {
using Bitmask = fvkutils::UniformBufferBitmask;
static_assert(sizeof(Bitmask) * 8 == fvkutils::MAX_DESCRIPTOR_SET_BITMASK_BITS);
template<typename T>
void erasep(std::vector<T>& v, std::function<bool(T const&)> f) {
auto newEnd = std::remove_if(v.begin(), v.end(), f);
@@ -47,6 +50,31 @@ ImageData& findImage(std::vector<ImageData>& images,
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
VulkanExternalImageManager::VulkanExternalImageManager(VulkanPlatform* platform,
@@ -113,9 +141,6 @@ void VulkanExternalImageManager::updateSetAndLayout(
continue;
}
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);
auto samplerParams = bindingInfo.samplerParams;
@@ -147,14 +172,38 @@ void VulkanExternalImageManager::updateSetAndLayout(
std::for_each(samplerAndBindings.begin(), samplerAndBindings.end(),
[&](auto const& b) { outSamplers.push_back(std::get<1>(b)); });
VkDescriptorSetLayout const oldLayout = layout->getExternalSamplerVkLayout();
VkDescriptorSetLayout const newLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
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
for (auto& [binding, sampler, image]: samplerAndBindings) {
// 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);
mDescriptorSetCache->updateSamplerForExternalSamplerSet(set, binding, image);
}
}
@@ -212,25 +261,13 @@ void VulkanExternalImageManager::bindExternallySampledTexture(
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams) {
// Should we do duplicate validation here?
auto& imageData = findImage(mImages, image);
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;
}
mSetBindings.push_back({ bindingPoint, imageData.image, set, samplerParams });
}
void VulkanExternalImageManager::addExternallySampledTexture(
fvkmemory::resource_ptr<VulkanTexture> image,
Platform::ExternalImageHandleRef platformHandleRef) {
// 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 });
mImages.push_back({ image, platformHandleRef, false });
}
void VulkanExternalImageManager::removeExternallySampledTexture(
@@ -31,7 +31,7 @@ class VulkanSamplerCache;
class VulkanDescriptorSetLayoutCache;
class VulkanDescriptorSetCache;
// Manages the logic of external images and their quirks wrt Vulkan.
// Manages the logic of external images and their quirks wrt Vulikan.
class VulkanExternalImageManager {
public:
+2 -62
View File
@@ -40,11 +40,6 @@ namespace filament::backend {
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 {
switch (bindingType) {
case BufferObjectBinding::VERTEX:
@@ -197,70 +192,15 @@ VulkanDescriptorSetLayout::Bitmask VulkanDescriptorSetLayout::Bitmask::fromLayou
return fromBackendLayout(layout);
}
VulkanDescriptorSet::~VulkanDescriptorSet() {
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));
}
// This method 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.
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) {
auto& res = mResources[index];
fvkmemory::resource_ptr<VulkanBufferObject> bo =
fvkmemory::resource_ptr<VulkanBufferObject>::cast((VulkanBufferObject*) res.get());
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) {
+33 -28
View File
@@ -28,7 +28,6 @@
#include "VulkanTexture.h"
#include "vulkan/VulkanCommands.h"
#include "vulkan/memory/Resource.h"
#include "vulkan/memory/ResourcePointer.h"
#include "vulkan/utils/Definitions.h"
#include "vulkan/utils/StaticVector.h"
@@ -58,8 +57,6 @@ inline uint8_t collapsedCount(Bitmask const& mask) {
} // anonymous namespace
class VulkanDescriptorSetCache;
struct VulkanBufferObject;
struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Resource {
@@ -162,14 +159,38 @@ public:
using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
VulkanDescriptorSet(
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout,
OnRecycle&& onRecycleFn, VkDescriptorSet vkSet);
fvkutils::UniformBufferBitmask const& dynamicUboMask,
uint8_t uniqueDynamicUboCount,
OnRecycle&& onRecycleFn, VkDescriptorSet vkSet)
: dynamicUboMask(dynamicUboMask),
uniqueDynamicUboCount(uniqueDynamicUboCount),
mVkSet(vkSet),
mOnRecycleFn(std::move(onRecycleFn)) {}
// NOLINTNEXTLINE(bugprone-exception-escape)
~VulkanDescriptorSet();
~VulkanDescriptorSet() {
if (mOnRecycleFn) {
mOnRecycleFn(this);
}
if (mOnRecycleExternalSamplerFn) {
mOnRecycleExternalSamplerFn(this);
}
}
VkDescriptorSet getVkSet() const noexcept {
return mSets[mCurrentSetIndex].vkSet;
return mVkSet;
}
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 {
@@ -190,33 +211,17 @@ public:
void referencedBy(VulkanCommandBuffer& commands);
bool isBound() const {
return bool(mSets[mCurrentSetIndex].fenceStatus);
}
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> getLayout() const { return mLayout; }
fvkutils::UniformBufferBitmask const& dynamicUboMask;
fvkutils::UniformBufferBitmask const dynamicUboMask;
uint8_t const uniqueDynamicUboCount;
private:
friend class VulkanDescriptorSetCache;
VkDescriptorSet const mVkSet;
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;
std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources;
uint8_t mCurrentSetIndex;
std::vector<InternalVkSet> mSets;
OnRecycle mOnRecycleFn;
OnRecycle mOnRecycleExternalSamplerFn;
fvkutils::UniformBufferBitmask mUboMask;
};
@@ -1,60 +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.
*/
#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
@@ -1,64 +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_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,7 +19,6 @@
#include "VulkanCommands.h"
#include "VulkanTexture.h"
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Panic.h>
@@ -106,11 +105,6 @@ 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{
@@ -167,15 +161,7 @@ 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);
@@ -36,43 +36,6 @@ namespace filament::backend {
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 {
VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands, VmaAllocator allocator,
VkDevice device, VkImage image, VkDeviceMemory deviceMemory, VkFormat format,
@@ -111,8 +74,6 @@ private:
// The texture with the sidecar owns the sidecar.
fvkmemory::resource_ptr<VulkanTexture> mSidecarMSAA;
// The stream this texture is associated with.
fvkmemory::resource_ptr<VulkanStream> mStream;
VkImage const mTextureImage;
VkDeviceMemory const mTextureImageMemory;
@@ -144,7 +105,6 @@ private:
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
friend struct VulkanTexture;
};
@@ -222,14 +182,6 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
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 {
return mState->mUsage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
}
@@ -41,7 +41,6 @@ template ResourceType getTypeEnum<VulkanBuffer>() noexcept;
template ResourceType getTypeEnum<VulkanSync>() noexcept;
template ResourceType getTypeEnum<VulkanMemoryMappedBuffer>() noexcept;
template ResourceType getTypeEnum<VulkanSemaphore>() noexcept;
template ResourceType getTypeEnum<VulkanStream>() noexcept;
template<typename D>
ResourceType getTypeEnum() noexcept {
@@ -105,9 +104,6 @@ ResourceType getTypeEnum() noexcept {
if constexpr (std::is_same_v<D, VulkanSemaphore>) {
return ResourceType::SEMAPHORE;
}
if constexpr (std::is_same_v<D, VulkanStream>) {
return ResourceType::STREAM;
}
return ResourceType::UNDEFINED_TYPE;
}
@@ -153,8 +149,6 @@ std::string_view getTypeStr(ResourceType type) {
return "VulkanMemoryMappedBuffer";
case ResourceType::SEMAPHORE:
return "Semaphore";
case ResourceType::STREAM:
return "VulkanStream";
case ResourceType::UNDEFINED_TYPE:
return "";
}
@@ -55,8 +55,7 @@ enum class ResourceType : uint8_t {
SYNC = 17,
MEMORY_MAPPED_BUFFER = 18,
SEMAPHORE = 19,
STREAM = 20,
UNDEFINED_TYPE = 21, // Must be the last enum because we use it for iterating over the enums.
UNDEFINED_TYPE = 20, // Must be the last enum because we use it for iterating over the enums.
};
template<typename D>
@@ -123,9 +123,6 @@ void ResourceManager::destroyWithType(ResourceType type, HandleId id) {
case ResourceType::SEMAPHORE:
destruct<VulkanSemaphore>(Handle<VulkanSemaphore>(id));
break;
case ResourceType::STREAM:
destruct<VulkanStream>(Handle<VulkanStream>(id));
break;
case ResourceType::UNDEFINED_TYPE:
break;
}
@@ -18,12 +18,6 @@
#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/algorithm.h>
#include <utils/debug.h>
@@ -240,19 +234,6 @@ uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags 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
bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) {
@@ -18,7 +18,6 @@
#define TNT_FILAMENT_BACKEND_VULKAN_UTILS_IMAGE_H
#include <backend/DriverEnums.h>
#include <backend/platforms/VulkanPlatform.h>
#include <utils/Log.h>
@@ -107,9 +106,6 @@ VkImageAspectFlags getImageAspect(VkFormat format);
uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask);
Platform::ExternalImageHandle createExternalImageFromRaw(filament::backend::VulkanPlatform* platform,
void* image,
bool sRGB);
} // namespace fvkutils
} // namespace filament::backend
@@ -18,7 +18,6 @@
#include "WebGPUConstants.h"
#include "WebGPUQueueManager.h"
#include "WebGPUStagePool.h"
#include "DriverBase.h"
#include <backend/BufferDescriptor.h>
@@ -66,7 +65,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, WebGPUStagePool* const webGPUStagePool) {
WebGPUQueueManager* const webGPUQueueManager) {
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
<< "updateGPUBuffer called with a null buffer";
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
@@ -86,12 +85,15 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
const size_t stagingBufferSize =
remainder == 0 ? bufferDescriptor.size : mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
wgpu::Buffer stagingBuffer = webGPUStagePool->acquireBuffer(stagingBufferSize,
webGPUQueueManager->getLatestSubmissionState());
// 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);
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
@@ -104,9 +106,7 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
// Copy the staging buffer contents to the destination buffer.
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stagingBuffer, 0, mBuffer,
byteOffset,
remainder == 0 ? bufferDescriptor.size
: mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS);
byteOffset, stagingBufferSize);
}
} // namespace filament::backend
@@ -25,7 +25,6 @@ namespace filament::backend {
class BufferDescriptor;
class WebGPUQueueManager;
class WebGPUStagePool;
/**
* A base class for WebGPU buffer objects, providing common functionality for creating and
@@ -41,7 +40,7 @@ public:
* ensures the calls happen in the expected sequence.
*/
void updateGPUBuffer(BufferDescriptor const&, uint32_t byteOffset, wgpu::Device const& device,
WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool);
WebGPUQueueManager* const webGPUQueueManager);
[[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; }
+8 -7
View File
@@ -107,7 +107,6 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
mAdapter{ mPlatform.requestAdapter(nullptr) },
mDevice{ mPlatform.requestDevice(mAdapter) },
mQueueManager{ mDevice },
mStagePool{ mDevice },
mPipelineLayoutCache{ mDevice },
mPipelineCache{ mDevice },
mRenderPassMipmapGenerator{ mDevice, &mQueueManager },
@@ -178,9 +177,6 @@ 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.
@@ -655,6 +651,11 @@ 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
}
@@ -855,7 +856,7 @@ void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
// draw calls are made.
flush();
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
scheduleDestroy(std::move(bufferDescriptor));
}
@@ -866,14 +867,14 @@ void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
// draw calls are made.
flush();
handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
scheduleDestroy(std::move(bufferDescriptor));
}
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
scheduleDestroy(std::move(bufferDescriptor));
}
@@ -25,7 +25,6 @@
#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>
@@ -82,7 +81,6 @@ private:
wgpu::Device mDevice = nullptr;
wgpu::Limits mDeviceLimits = {};
WebGPUQueueManager mQueueManager;
WebGPUStagePool mStagePool;
void* mNativeWindow = nullptr;
WebGPUSwapChain* mSwapChain = nullptr;
uint64_t mNextFakeHandle = 1;
@@ -64,7 +64,6 @@ wgpu::CommandEncoder WebGPUQueueManager::getCommandEncoder() {
};
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
ASSERT_POSTCONDITION(mCommandEncoder, "Failed to create command encoder.");
mLatestSubmissionState = std::make_shared<WebGPUSubmissionState>();
}
return mCommandEncoder;
}
@@ -95,6 +94,8 @@ void WebGPUQueueManager::submit() {
return;
}
mLatestSubmissionState = std::make_shared<WebGPUSubmissionState>();
wgpu::CommandBufferDescriptor commandBufferDescriptor{
.label = "Filament Command Buffer",
};
@@ -1,92 +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.
*/
#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
@@ -1,50 +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_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
-288
View File
@@ -1,288 +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.
*/
#include <gtest/gtest.h>
#include "JobQueue.h"
#include <atomic>
#include <thread>
using namespace filament::backend;
TEST(JobQueue, PushAndPop) {
JobQueue::Ptr queue = JobQueue::create();
int v = 0;
queue->push([&v]() { v = 1; });
JobQueue::Job job = queue->pop(false);
ASSERT_TRUE(job);
job();
EXPECT_EQ(1, v);
}
TEST(JobQueue, PopEmpty) {
JobQueue::Ptr queue = JobQueue::create();
JobQueue::Job job = queue->pop(false);
ASSERT_FALSE(job);
}
TEST(JobQueue, PopBatch) {
JobQueue::Ptr queue = JobQueue::create();
int v = 0;
queue->push([&v]() { v++; });
queue->push([&v]() { v++; });
queue->push([&v]() { v++; });
auto jobs = queue->popBatch(2);
EXPECT_EQ(2, jobs.size());
for (auto& job : jobs) {
job();
}
EXPECT_EQ(2, v);
jobs = queue->popBatch(10);
EXPECT_EQ(1, jobs.size());
for (auto& job : jobs) {
job();
}
EXPECT_EQ(3, v);
}
TEST(JobQueue, PopAll) {
JobQueue::Ptr queue = JobQueue::create();
int v = 0;
queue->push([&v]() { v++; });
queue->push([&v]() { v++; });
queue->push([&v]() { v++; });
auto jobs = queue->popBatch(-1);
EXPECT_EQ(3, jobs.size());
for (auto& job : jobs) {
job();
}
EXPECT_EQ(3, v);
}
TEST(JobQueue, Cancel) {
JobQueue::Ptr queue = JobQueue::create();
int v = 0;
JobQueue::JobId idToCancel = queue->push([&v]() { v = 1; });
queue->push([&v]() { v = 2; });
EXPECT_TRUE(queue->cancel(idToCancel));
auto jobs = queue->popBatch(-1);
EXPECT_EQ(1, jobs.size());
jobs[0]();
EXPECT_EQ(2, v);
}
TEST(JobQueue, CancelInvalid) {
JobQueue::Ptr queue = JobQueue::create();
EXPECT_FALSE(queue->cancel(123));
}
TEST(JobQueue, Stop) {
JobQueue::Ptr queue = JobQueue::create();
int v = 0;
queue->push([&v]() { v = 1; });
queue->stop();
// After stop, we can't push new jobs. This should be a no-op.
JobQueue::JobId id = queue->push([&v]() { v = 2; });
EXPECT_EQ(JobQueue::InvalidJobId, id);
auto job = queue->pop(false);
EXPECT_TRUE(job);
job();
EXPECT_EQ(1, v);
job = queue->pop(false);
EXPECT_FALSE(job);
}
TEST(JobQueue, PreIssuedJobId) {
JobQueue::Ptr queue = JobQueue::create();
JobQueue::JobId preIssuedId = queue->issueJobId();
JobQueue::JobId id = queue->push([]() {}, preIssuedId);
EXPECT_EQ(id, preIssuedId);
}
TEST(JobQueue, MultipleProducersConsumers) {
JobQueue::Ptr queue = JobQueue::create();
std::atomic_int v = {0};
constexpr int NUM_THREADS = 4;
constexpr int JOBS_PER_THREAD = 200;
// Multiple producers
std::vector<std::thread> producers;
std::atomic_bool doneProducing = false;
for (int i = 0; i < NUM_THREADS; ++i) {
producers.emplace_back([&]() {
for (int j = 0; j < JOBS_PER_THREAD; ++j) {
queue->push([&v]() { v++; });
}
});
}
// Multiple consumers
std::thread blockingConsumer = std::thread([&]() {
while (true) {
if (auto job = queue->pop(true)) {
job();
} else {
break; // This means the job queue is stopped.
}
}
});
std::thread nonBlockingConsumer = std::thread([&]() {
while (true) {
if (auto job = queue->pop(false)) {
job();
} else {
if (doneProducing.load()) {
break;
}
std::this_thread::yield();
}
}
});
std::thread nonBlockingPopBatchConsumer = std::thread([&]() {
while (true) {
utils::FixedCapacityVector<JobQueue::Job> jobs = queue->popBatch(2);
if (!jobs.empty()) {
for (auto& job : jobs) {
job();
}
} else {
if (doneProducing.load()) {
break;
}
std::this_thread::yield();
}
}
});
// Waiting for producers to complete pushing jobs
for (auto& t : producers) {
t.join();
}
doneProducing = true; // signal for non-blocking consumer
queue->stop();
// Waiting for consumers to complete handling jobs
blockingConsumer.join();
nonBlockingConsumer.join();
nonBlockingPopBatchConsumer.join();
EXPECT_EQ(NUM_THREADS * JOBS_PER_THREAD, v.load());
}
TEST(AmortizationWorker, Process) {
JobQueue::Ptr queue = JobQueue::create();
JobWorker::Ptr worker = AmortizationWorker::create(queue);
int v = 0;
queue->push([&v]() { v++; });
queue->push([&v]() { v++; });
queue->push([&v]() { v++; });
worker->process(2);
EXPECT_EQ(2, v);
worker->process(1);
EXPECT_EQ(3, v);
// No pending jobs, so it should be a no-op.
worker->process(1);
EXPECT_EQ(3, v);
}
TEST(AmortizationWorker, ProcessAll) {
JobQueue::Ptr queue = JobQueue::create();
JobWorker::Ptr worker = AmortizationWorker::create(queue);
int v = 0;
queue->push([&v]() { v++; });
queue->push([&v]() { v++; });
queue->push([&v]() { v++; });
worker->process(-1);
EXPECT_EQ(3, v);
// No pending jobs, so it should be a no-op.
worker->process(1);
EXPECT_EQ(3, v);
}
TEST(AmortizationWorker, TerminateDrainsAllJobs) {
JobQueue::Ptr queue = JobQueue::create();
JobWorker::Ptr worker = AmortizationWorker::create(queue);
int v = 0;
queue->push([&v]() { v++; });
queue->push([&v]() { v++; });
// `terminate` should drain all jobs
worker->terminate();
EXPECT_EQ(2, v);
// After terminate, pushing new jobs should not work.
queue->push([&v]() { v++; });
worker->process(1);
EXPECT_EQ(2, v);
}
TEST(ThreadWorker, Process) {
JobQueue::Ptr queue = JobQueue::create();
JobWorker::Ptr worker = ThreadWorker::create(queue, {});
std::atomic_int v = {0};
queue->push([&v]() { v++; });
queue->push([&v]() { v++; });
// `terminate` should drain all jobs
worker->terminate();
EXPECT_EQ(2, v.load());
// After terminate, pushing new jobs should not work.
queue->push([&v]() { v++; });
worker->terminate();
EXPECT_EQ(2, v.load());
}
TEST(ThreadWorker, Callbacks) {
JobQueue::Ptr queue = JobQueue::create();
bool beginCalled = false;
bool endCalled = false;
ThreadWorker::Config config = {
.name = "TestThread",
.priority = ThreadWorker::Priority::NORMAL,
.onBegin = [&beginCalled]() { beginCalled = true; },
.onEnd = [&endCalled]() { endCalled = true; }
};
JobWorker::Ptr worker = ThreadWorker::create(queue, std::move(config));
worker->terminate();
EXPECT_TRUE(beginCalled);
EXPECT_TRUE(endCalled);
}
-14
View File
@@ -192,7 +192,6 @@ 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
@@ -429,11 +428,6 @@ public:
* initial memory usage.
*/
uint32_t sharedUboInitialSizeInBytes = 256 * 64;
/**
* Asynchronous mode for the engine. Defines how asynchronous operations are handled.
*/
AsynchronousMode asynchronousMode = AsynchronousMode::NONE;
};
@@ -748,14 +742,6 @@ 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.
*
+1 -27
View File
@@ -69,20 +69,6 @@ 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.
*/
@@ -179,13 +165,6 @@ 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.
*
@@ -371,12 +350,7 @@ public:
//! Indicates whether an existing parameter is a sampler or not.
bool isSampler(const char* UTILS_NONNULL name) const noexcept;
/**
* Returns a view of the material source (.mat which is a JSON-ish file) string,
* if it has been set. Otherwise, it returns a view of an empty string.
* The lifetime of the string_view is tied to the lifetime of the Material.
*/
std::string_view getSource() const noexcept;
/**
*
* Gets the name of the transform field associated for the given sampler parameter.
-4
View File
@@ -455,10 +455,6 @@ 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();
}
+4 -25
View File
@@ -101,24 +101,11 @@ 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())) {
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;
if (!mDisableGpuFrameComplete) {
assert_invariant(history.back().fence);
driver.destroyFence(std::move(history.back().fence));
}
history.pop_back();
}
// create a new entry
@@ -206,14 +193,6 @@ 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();
+27 -3
View File
@@ -95,8 +95,33 @@ struct FrameInfoImpl : public details::FrameInfo {
assert_invariant(!fence);
}
FrameInfoImpl(FrameInfoImpl& rhs) noexcept = delete;
FrameInfoImpl& operator=(FrameInfoImpl& rhs) noexcept = delete;
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;
}
};
template<typename T, size_t CAPACITY>
@@ -315,7 +340,6 @@ private:
FrameHistoryQueue mFrameTimeHistory{};
utils::AsyncJobQueue mJobQueue;
FSwapChain* mLastSeenSwapChain = nullptr;
bool mLastBeginFrameSkipped = false;
bool const mHasTimerQueries = false;
bool const mDisableGpuFrameComplete = false;
};
-4
View File
@@ -146,10 +146,6 @@ bool Material::isSampler(const char* name) const noexcept {
return downcast(this)->isSampler(name);
}
std::string_view Material::getSource() const noexcept {
return downcast(this)->getSource();
}
const char* Material::getParameterTransformName(const char* samplerName) const noexcept {
return downcast(this)->getParameterTransformName(samplerName);
}
-2
View File
@@ -257,8 +257,6 @@ void MaterialDefinition::processMain() {
bool const hasFog = !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::FOG));
perViewLayoutIndex = ColorPassDescriptorSet::getIndex(isLit, isSSR, hasFog);
mMaterialParser->getSourceShader(&source);
}
void MaterialDefinition::processBlendingMode() {
-1
View File
@@ -113,7 +113,6 @@ struct MaterialDefinition {
utils::CString name;
uint64_t cacheId = 0;
utils::CString source;
private:
friend class MaterialCache;
-36
View File
@@ -35,8 +35,6 @@
#include <backend/DriverEnums.h>
#include <backend/Program.h>
#include <zstd.h>
#include <utils/compiler.h>
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
@@ -239,40 +237,6 @@ bool MaterialParser::getName(CString* cstring) const noexcept {
return unflattener.read(cstring);
}
bool MaterialParser::getSourceShader(CString* cstring) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialSource);
// Source material is optional, treat it as a success.
if (start == end) return true;
Unflattener unflattener(start, end);
// First get the compressed blob.
const char* compressed;
size_t compressedSize = 0;
if (!unflattener.read(&compressed, &compressedSize)) {
return false;
}
// Get the bound and decompress it.
const size_t decompressBound =
ZSTD_getFrameContentSize(compressed, compressedSize);
if (ZSTD_isError(decompressBound)) {
return false;
}
auto dst_buffer = std::make_unique<char[]>(decompressBound);
const size_t decompressed =
ZSTD_decompress(
dst_buffer.get(), decompressBound,
compressed, compressedSize);
if (ZSTD_isError(decompressed)) {
return false;
}
*cstring = CString { dst_buffer.get(), decompressed };
return true;
}
bool MaterialParser::getCacheId(uint64_t* cacheId) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialCacheId);
if (start == end) return false;
-2
View File
@@ -145,8 +145,6 @@ public:
return getMaterialChunk().hasShader(model, variant, stage);
}
bool getSourceShader(utils::CString* cstring) const noexcept;
filaflat::MaterialChunk const& getMaterialChunk() const noexcept {
return mImpl.mMaterialChunk;
}
+6 -13
View File
@@ -98,7 +98,7 @@ using namespace filaflat;
namespace {
backend::Platform::DriverConfig getDriverConfig(FEngine* instance) {
Platform::DriverConfig driverConfig {
return {
.handleArenaSize = instance->getRequestedDriverHandleArenaSize(),
.metalUploadBufferSizeBytes = instance->getConfig().metalUploadBufferSizeBytes,
.disableParallelShaderCompile = instance->features.backend.disable_parallel_shader_compile,
@@ -116,11 +116,7 @@ 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
@@ -731,6 +727,7 @@ 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);
}
});
@@ -994,9 +991,10 @@ FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
return p;
}
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
const char* name) noexcept {
FMaterialInstance* p = mHeapAllocator.make<FMaterialInstance>(*this, material, name);
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, const char* name,
UboBatchingMode batchingMode) noexcept {
FMaterialInstance* p =
mHeapAllocator.make<FMaterialInstance>(*this, material, name, batchingMode);
if (UTILS_LIKELY(p)) {
auto pos = mMaterialInstances.emplace(material, "MaterialInstance");
pos.first->second.insert(p);
@@ -1505,11 +1503,6 @@ 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();
+13 -8
View File
@@ -216,8 +216,6 @@ public:
return getDriver().isStereoSupported();
}
bool isAsynchronousOperationSupported() const noexcept;
static size_t getMaxStereoscopicEyes() noexcept {
return CONFIG_MAX_STEREOSCOPIC_EYES;
}
@@ -347,10 +345,21 @@ 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) noexcept;
FMaterialInstance* createMaterialInstance(const FMaterial* material, const char* name,
UboBatchingMode batchingMode) noexcept;
FScene* createScene() noexcept;
FView* createView() noexcept;
@@ -771,7 +780,7 @@ public:
bool assert_texture_can_generate_mipmap = CORRECTNESS_ASSERTION_DEFAULT;
} debug;
struct {
bool disable_gpu_frame_complete_metric = true;
bool disable_gpu_frame_complete_metric = false;
} frame_info;
} engine;
struct {
@@ -788,7 +797,6 @@ 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;
@@ -809,9 +817,6 @@ 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 },
+3 -22
View File
@@ -70,19 +70,10 @@
namespace filament {
namespace {
using namespace backend;
using namespace filaflat;
using namespace utils;
using UboBatchingMode = Material::UboBatchingMode;
bool shouldEnableBatching(FEngine& engine, UboBatchingMode batchingMode, MaterialDomain domain) {
return batchingMode != UboBatchingMode::DISABLED && engine.isUboBatchingEnabled() &&
domain == MaterialDomain::SURFACE;
}
} // anonymous namespace
using UboBatchingMode = FEngine::UboBatchingMode;
struct Material::BuilderDetails {
const void* mPayload = nullptr;
@@ -90,7 +81,6 @@ 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>,
@@ -125,11 +115,6 @@ 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";
@@ -163,12 +148,8 @@ 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);
@@ -301,14 +282,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);
return mEngine.createMaterialInstance(this, name, UboBatchingMode::DEFAULT);
}
}
FMaterialInstance* FMaterial::getDefaultInstance() noexcept {
if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) {
mDefaultMaterialInstance =
mEngine.createMaterialInstance(this, mDefinition.name.c_str());
mEngine.createMaterialInstance(this, mDefinition.name.c_str(), UboBatchingMode::DEFAULT);
mDefaultMaterialInstance->setDefaultInstance(true);
}
return mDefaultMaterialInstance;
-10
View File
@@ -248,14 +248,6 @@ public:
return mDefinition.perViewLayoutIndex;
}
bool useUboBatching() const noexcept {
return mUseUboBatching;
}
std::string_view getSource() const noexcept {
return mDefinition.source.c_str_safe();
}
#if FILAMENT_ENABLE_MATDBG
void applyPendingEdits() noexcept;
@@ -318,8 +310,6 @@ private:
bool mIsDefaultMaterial = false;
bool mUseUboBatching = false;
// reserve some space to construct the default material instance
mutable FMaterialInstance* mDefaultMaterialInstance = nullptr;
+47 -7
View File
@@ -58,8 +58,18 @@ using namespace utils;
namespace filament {
using namespace backend;
using UboBatchingMode = FEngine::UboBatchingMode;
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name) noexcept
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
: mMaterial(material),
mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()),
mCulling(CullingMode::BACK),
@@ -70,9 +80,12 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
mHasScissor(false),
mIsDoubleSided(false),
mIsDefaultInstance(false),
mUseUboBatching(material->useUboBatching()),
mUseUboBatching(
shouldEnableBatching(engine, batchingMode, material->getMaterialDomain())),
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
@@ -147,6 +160,7 @@ 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();
@@ -209,6 +223,31 @@ 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());
@@ -216,9 +255,10 @@ void FMaterialInstance::commit(FEngine& engine) const {
}
void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManager) const {
if (mUniforms.isDirty()) {
if (mUniforms.isDirty() || mHasStreamUniformAssociations) {
mUniforms.clean();
if (isUsingUboBatching()) {
if (mUseUboBatching) {
assert_invariant(uboManager != nullptr);
if (!BufferAllocator::isValid(getAllocationId())) {
// The allocation hasn't happened yet, return.
return;
@@ -249,7 +289,7 @@ void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManage
// TODO: eventually we should remove this in RELEASE builds
fixMissingSamplers();
if (isUsingUboBatching() && !BufferAllocator::isValid(getAllocationId())) {
if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) {
return;
}
@@ -399,7 +439,7 @@ void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const {
return;
}
if (isUsingUboBatching() && !BufferAllocator::isValid(getAllocationId())) {
if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) {
return;
}
@@ -436,7 +476,7 @@ void FMaterialInstance::assignUboAllocation(
const Handle<HwBufferObject>& ubHandle,
BufferAllocator::AllocationId id,
BufferAllocator::allocation_size_t offset) {
assert_invariant(isUsingUboBatching());
assert_invariant(mUseUboBatching);
mUboData = id;
mUboOffset = offset;
+5 -1
View File
@@ -56,7 +56,8 @@ class FTexture;
class FMaterialInstance : public MaterialInstance {
public:
FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name) noexcept;
FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name,
FEngine::UboBatchingMode batchingMode) noexcept;
FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name);
FMaterialInstance(const FMaterialInstance& rhs) = delete;
FMaterialInstance& operator=(const FMaterialInstance& rhs) = delete;
@@ -66,6 +67,8 @@ public:
~FMaterialInstance() noexcept;
void terminate(FEngine& engine);
void commitStreamUniformAssociations(FEngine::DriverApi& driver);
void commit(FEngine& engine) const;
@@ -285,6 +288,7 @@ private:
tsl::robin_map<backend::descriptor_binding_t, TextureParameter> mTextureParameters;
mutable DescriptorSet mDescriptorSet;
UniformBuffer mUniforms;
bool mHasStreamUniformAssociations = false;
backend::PolygonOffset mPolygonOffset{};
backend::StencilState mStencilState{};
+20 -8
View File
@@ -292,11 +292,16 @@ 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
// Run the component managers' GC
auto& js = engine.getJobSystem();
js.runAndWait(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers
// make sure we're done with the gcs
js.waitAndRelease(job);
}
bool FRenderer::shouldRenderFrame() const noexcept {
@@ -463,11 +468,16 @@ 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
// Run the component managers' GC
auto& js = engine.getJobSystem();
js.runAndWait(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers
// make sure we're done with the gcs
js.waitAndRelease(job);
}
void FRenderer::readPixels(
@@ -561,6 +571,7 @@ 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;
@@ -570,7 +581,8 @@ 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), 0);
1'000'000'000.0 / mDisplayInfo.refreshRate),
mFrameId);
// 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
@@ -579,7 +591,7 @@ void FRenderer::renderStandaloneView(FView const* view) {
engine.submitFrame();
driver.endFrame(0);
driver.endFrame(mFrameId);
// 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 = 1; // id 0 is reserved for standalone views
uint32_t mFrameId = 0;
FrameInfoManager mFrameInfoManager;
backend::TextureFormat mHdrTranslucent;
backend::TextureFormat mHdrQualityMedium;
+4 -5
View File
@@ -39,7 +39,7 @@ using allocation_size_t = BufferAllocator::allocation_size_t;
// FenceManager
// ------------------------------------------------------------------------------------------------
void UboManager::FenceManager::track(DriverApi& driver, AllocationIdContainer&& allocationIds) {
void UboManager::FenceManager::track(DriverApi& driver, std::unordered_set<AllocationId>&& allocationIds) {
if (allocationIds.empty()) {
return;
}
@@ -159,16 +159,15 @@ void UboManager::finishBeginFrame(DriverApi& driver) {
}
void UboManager::endFrame(DriverApi& driver) {
auto allocationIds =
FenceManager::AllocationIdContainer::with_capacity(mManagedInstances.size());
for (const auto* mi: mManagedInstances) {
std::unordered_set<AllocationId> allocationIds;
for (const auto* mi : mManagedInstances) {
const AllocationId id = mi->getAllocationId();
if (UTILS_UNLIKELY(!BufferAllocator::isValid(id))) {
continue;
}
mAllocator.acquireGpu(id);
allocationIds.push_back(id);
allocationIds.insert(id);
}
mFenceManager.track(driver, std::move(allocationIds));
+2 -3
View File
@@ -55,7 +55,6 @@ public:
class FenceManager {
public:
using AllocationId = BufferAllocator::AllocationId;
using AllocationIdContainer = utils::FixedCapacityVector<AllocationId>;
FenceManager() = default;
~FenceManager() = default;
@@ -66,7 +65,7 @@ public:
// 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.
void track(backend::DriverApi& driver, AllocationIdContainer&& allocationIds);
void track(backend::DriverApi& driver, std::unordered_set<AllocationId>&& allocationIds);
// Checks all tracked fences and invokes a callback for resources associated with
@@ -81,7 +80,7 @@ public:
private:
// Not ideal, but we need to know which slots to decrement gpuUseCount for each frame.
using FenceAndAllocations =
std::pair<backend::Handle<backend::HwFence>, AllocationIdContainer>;
std::pair<backend::Handle<backend::HwFence>, std::unordered_set<AllocationId>>;
std::vector<FenceAndAllocations> mFenceAllocationList;
};
+2 -16
View File
@@ -13,25 +13,11 @@ 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
${TEST_MATERIAL_TRANSFORM_NAME_FILAMAT}
${CMAKE_CURRENT_SOURCE_DIR}/test_material_transformname.filamat
)
add_custom_command(
@@ -100,7 +86,7 @@ set_target_properties(test_material_parser PROPERTIES FOLDER Tests)
add_executable(test_material
filament_test_material.cpp
${RESGEN_SOURCE})
target_link_libraries(test_material PRIVATE filamat filament gtest)
target_link_libraries(test_material PRIVATE filament gtest)
target_compile_options(test_material PRIVATE ${COMPILER_FLAGS})
target_include_directories(test_material PRIVATE ${RESOURCE_DIR})
set_target_properties(test_material PROPERTIES FOLDER Tests)
+1 -49
View File
@@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gtest/gtest.h>
#include <filamat/MaterialBuilder.h>
#include <gtest/gtest.h>
#include <filament/Engine.h>
#include <filament/Material.h>
@@ -85,53 +84,6 @@ TEST(MaterialTransformName, QueryMultipleSamplersWithoutTransforms) {
Engine::destroy(engine);
}
TEST(Material, MaterialWithSourceMaterialSuccessfullyRetrieveSource) {
// Need to set a specific backend to create a proper MaterialParser.
Engine* engine = Engine::create(Engine::Backend::OPENGL);
std::string shaderCode(R"(
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = vec4(1.);
}
)");
filamat::MaterialBuilder builder;
builder.init();
builder.materialSource(shaderCode);
filamat::Package result = builder.build(engine->getJobSystem());
ASSERT_TRUE(result.isValid());
Material* material = Material::Builder()
.package(result.getData(), result.getSize())
.build(*engine);
ASSERT_NE(material, nullptr);
EXPECT_EQ(material->getSource(), shaderCode);
engine->destroy(material);
Engine::destroy(engine);
}
TEST(Material, MaterialWithoutSourceMaterialReturnsEmptySource) {
// Need to set a specific backend to create a proper MaterialParser.
Engine* engine = Engine::create(Engine::Backend::OPENGL);
filamat::MaterialBuilder builder;
builder.init();
filamat::Package result = builder.build(engine->getJobSystem());
ASSERT_TRUE(result.isValid());
Material* material = Material::Builder()
.package(result.getData(), result.getSize())
.build(*engine);
ASSERT_NE(material, nullptr);
EXPECT_EQ(material->getSource(), "");
engine->destroy(material);
Engine::destroy(engine);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
Binary file not shown.
Binary file not shown.
+1
View File
@@ -11,6 +11,7 @@ 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
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.68.2"
spec.version = "1.67.1"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
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.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.68.2/filament-v1.68.2-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.67.1/filament-v1.67.1-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
@@ -101,8 +101,6 @@ enum UTILS_PUBLIC ChunkType : uint64_t {
DictionaryMetalLibrary = charTo64bitNum("DIC_MLIB"),
MaterialCrc32 = charTo64bitNum("MAT_CRC "),
MaterialSource = charTo64bitNum("MAT_SRC "),
};
} // namespace filamat
@@ -170,6 +170,10 @@ 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 {
@@ -197,6 +201,7 @@ 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,6 +122,8 @@ 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) {
@@ -148,6 +150,11 @@ 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;
@@ -163,6 +170,14 @@ 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);
+3 -5
View File
@@ -17,6 +17,9 @@
#ifndef FILAGUI_IMGUIHELPER_H_
#define FILAGUI_IMGUIHELPER_H_
#include <vector>
#include <functional>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/Material.h>
@@ -29,10 +32,6 @@
#include <utils/Entity.h>
#include <utils/Path.h>
#include <functional>
#include <vector>
#include <unordered_set>
struct ImDrawData;
struct ImGuiIO;
struct ImGuiContext;
@@ -101,7 +100,6 @@ public:
filament::TextureSampler mSampler;
bool mFlipVertical = false;
utils::Path mSettingsPath;
std::unordered_set<filament::Texture*> mImGuiTextures;
};
} // namespace filagui
+7 -14
View File
@@ -148,12 +148,6 @@ 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);
@@ -195,6 +189,7 @@ 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) {
@@ -208,6 +203,7 @@ 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,
@@ -217,7 +213,6 @@ 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,
@@ -227,13 +222,11 @@ 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);
mImGuiTextures.erase(ftex);
tex->UnusedFrames > 0) {
filament::Texture* ftex = (filament::Texture*)tex->TexID;
mEngine->destroy(ftex);
tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
}
}
}
+1 -4
View File
@@ -16,7 +16,6 @@ set(HDRS
set(COMMON_PRIVATE_HDRS
src/eiff/Chunk.h
src/eiff/ChunkContainer.h
src/eiff/CompressedStringChunk.h
src/eiff/DictionaryTextChunk.h
src/eiff/Flattener.h
src/eiff/LineDictionary.h
@@ -29,7 +28,6 @@ set(COMMON_PRIVATE_HDRS
set(COMMON_SRCS
src/eiff/Chunk.cpp
src/eiff/ChunkContainer.cpp
src/eiff/CompressedStringChunk.cpp
src/eiff/DictionaryTextChunk.cpp
src/eiff/LineDictionary.cpp
src/eiff/MaterialTextChunk.cpp
@@ -83,7 +81,7 @@ include_directories(${CMAKE_BINARY_DIR})
add_library(${TARGET} STATIC ${HDRS} ${PRIVATE_HDRS} ${SRCS})
target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR})
set_target_properties(${TARGET} PROPERTIES FOLDER Libs)
target_link_libraries(${TARGET} backend_headers shaders filabridge utils smol-v zstd)
target_link_libraries(${TARGET} backend_headers shaders filabridge utils smol-v)
if (FILAMENT_SUPPORTS_WEBGPU)
target_link_libraries(${TARGET} libtint)
@@ -135,7 +133,6 @@ set(FILAMAT_DEPS
spirv-cross-core
spirv-cross-glsl
spirv-cross-msl
zstd
)
set(FILAMAT_COMBINED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/libfilamat_combined.a")
@@ -660,12 +660,6 @@ public:
*/
MaterialBuilder& useDefaultDepthVariant() noexcept;
/**
* Sets the source ASCII material (aka .mat file).
* The provided `source` string_view must remain valid until MaterialBuilder::build() is called.
*/
MaterialBuilder& materialSource(std::string_view source) noexcept;
/**
* Build the material. If you are using the Filament engine with this library, you should use
* the job system provided by Engine.
@@ -911,7 +905,6 @@ private:
ShaderCode mMaterialFragmentCode;
ShaderCode mMaterialVertexCode;
std::string_view mMaterialSource;
PropertyList mProperties;
ParameterList mParameters;

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