Compare commits

..
Author SHA1 Message Date
Powei Feng 910c70f21b vk: record timestmps on command buffers
Timer queries are inserted into the beginning and the end of each
command buffer. Refactor vk's implementation of Filament's timer
queries so that they are now computed as a sum of the time taken
by one or more command buffers (and not direct insertion of queries
into the command stream).

This is to address a potential issue where the timestamps returned
between command buffers are not consistent.
2025-11-12 12:18:30 -08:00
514 changed files with 20133 additions and 55704 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
View File
@@ -6,3 +6,5 @@
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.68.0'
implementation 'com.google.android.filament:filament-android:1.67.0'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.68.0'
pod 'Filament', '~> 1.67.0'
```
## Documentation
-9
View File
@@ -7,15 +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.1
## v1.68.0
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
- Metal: report GPU errors to the platform via `debugUpdateStat` (b/431665753).
- materials: Make Material Instances' UBO descriptor use dynamic offsets. [⚠️ **Recompile Materials**]
## v1.67.1
- Metal: Add support for the `SwapChain::CONFIG_MSAA_4_SAMPLES` flag.
+1 -1
View File
@@ -100,7 +100,7 @@ buildscript {
'kotlin': '2.0.21',
'kotlin_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);
@@ -16,9 +16,6 @@
package com.google.android.filament.utils
import android.graphics.Bitmap
import android.os.Handler
import android.os.Looper
import android.view.MotionEvent
import android.view.Surface
import android.view.SurfaceView
@@ -29,7 +26,6 @@ import com.google.android.filament.android.UiHelper
import com.google.android.filament.gltfio.*
import kotlinx.coroutines.*
import java.nio.Buffer
import java.nio.ByteBuffer
private const val kNearPlane = 0.05f // 5 cm
private const val kFarPlane = 1000.0f // 1 km
@@ -123,8 +119,6 @@ class ModelViewer(
private val target = DoubleArray(3)
private val upward = DoubleArray(3)
private var debugFrameCallback: ((Bitmap) -> Unit)? = null
init {
renderer = engine.createRenderer()
scene = engine.createScene()
@@ -311,39 +305,10 @@ class ModelViewer(
// Render the scene, unless the renderer wants to skip the frame.
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
renderer.render(view)
debugFrameCallback?.let {
val viewport = view.viewport
val bitmap = Bitmap.createBitmap(viewport.width, viewport.height,
Bitmap.Config.ARGB_8888)
val buffer = ByteBuffer.allocateDirect(viewport.width * viewport.height * 4)
val handler = Handler(Looper.getMainLooper())
val pixelBufferDescriptor = Texture.PixelBufferDescriptor(buffer,
Texture.Format.RGBA, Texture.Type.UBYTE, 1, 0, 0, 0, handler) {
buffer.rewind()
bitmap.copyPixelsFromBuffer(buffer)
it(bitmap)
}
renderer.readPixels(viewport.left, viewport.bottom, viewport.width,
viewport.height, pixelBufferDescriptor)
debugFrameCallback = null
}
renderer.endFrame()
}
}
/*
* Sets a callback that will be invoked with the next rendered frame as a Bitmap. Note that this
* is a one-time callback.
*
* @param callback callback to be invoked with a rendered frame as [Bitmap]
*/
fun debugGetNextFrameCallback(callback: (Bitmap) -> Unit) {
debugFrameCallback = callback
}
private fun populateScene(asset: FilamentAsset) {
val rcm = engine.renderableManager
var count = 0
@@ -22,7 +22,6 @@ object Utils {
/**
* Initializes the utils JNI layer. Must be called before using any utils functionality.
*/
@JvmStatic
fun init() {
// Load Filament first to ensure that the NioUtils Java class is available in the JNIEnv.
Filament.init()
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.68.0
VERSION_NAME=1.67.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -17,20 +17,12 @@ filamentTools {
}
// don't forget to update MainACtivity.kt when/if changing this.
tasks.register('copyDamagedHelmetGltf', Copy) {
from file("../../../third_party/models/DamagedHelmet/DamagedHelmet.glb")
into file("src/main/assets/models")
rename {String fileName -> "helmet.glb"}
}
// don't forget to update MainACtivity.kt when/if changing this.
tasks.register('copyBusterGltf', Copy) {
tasks.register('copyMesh', Copy) {
from "../../../third_party/models/BusterDrone"
into "src/main/assets/models"
}
preBuild.dependsOn copyDamagedHelmetGltf
preBuild.dependsOn copyBusterGltf
preBuild.dependsOn copyMesh
clean.doFirst {
delete "src/main/assets"
+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.66.2'
}
</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.66.2'
</code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul>
+1541 -1570
View File
File diff suppressed because one or more lines are too long
+1033 -1058
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
-6
View File
@@ -206,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
@@ -218,8 +216,6 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanPipelineCache.h
src/vulkan/VulkanPipelineLayoutCache.cpp
src/vulkan/VulkanPipelineLayoutCache.h
src/vulkan/VulkanQueryManager.cpp
src/vulkan/VulkanQueryManager.h
src/vulkan/VulkanReadPixels.cpp
src/vulkan/VulkanReadPixels.h
src/vulkan/VulkanSamplerCache.cpp
@@ -318,8 +314,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
@@ -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>
+7 -67
View File
@@ -19,17 +19,13 @@
#ifndef TNT_FILAMENT_BACKEND_PLATFORM_H
#define TNT_FILAMENT_BACKEND_PLATFORM_H
#include <utils/CString.h>
#include <utils/compiler.h>
#include <utils/Invocable.h>
#include <utils/Mutex.h>
#include <stddef.h>
#include <stdint.h>
#include <atomic>
#include <memory>
#include <mutex>
namespace filament::backend {
@@ -74,9 +70,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 +248,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 +317,6 @@ public:
* - VulkanPlatform
*/
bool vulkanEnableStagingBufferBypass = false;
/**
* Asynchronous mode for the engine. Defines how asynchronous operations are handled.
*/
AsynchronousMode asynchronousMode = AsynchronousMode::NONE;
};
Platform() noexcept;
@@ -542,22 +508,13 @@ public:
// --------------------------------------------------------------------------------------------
// Debugging APIs
using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key,
uint64_t intValue, utils::CString stringValue)>;
using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key, uint64_t value)>;
/**
* Sets the callback function that the backend can use to update backend-specific statistics
* to aid with debugging. This callback is guaranteed to be called on the Filament driver
* thread.
*
* The callback signature is (key, intValue, stringValue). Note that for any given call,
* only one of the value parameters (intValue or stringValue) will be meaningful, depending on
* the specific key.
*
* IMPORTANT_NOTE: because the callback is called on the driver thread, only quick, non-blocking
* work should be done inside it. Furthermore, no graphics API calls (such as GL calls) should
* be made, which could interfere with Filament's driver state.
*
* @param debugUpdateStat an Invocable that updates debug statistics
*/
void setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept;
@@ -576,32 +533,15 @@ public:
* This function is guaranteed to be called only on a single thread, the Filament driver
* thread.
*
* @param key a null-terminated C-string with the key of the debug statistic
* @param intValue the updated integer value of key (the string value passed to the
* callback will be empty)
* @param key a null-terminated C-string with the key of the debug statistic
* @param value the updated value of key
*/
void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t intValue);
/**
* To track backend-specific statistics, the backend implementation can call the
* application-provided callback function debugUpdateStatFunc to associate or update a value
* with a given key. It is possible for this function to be called multiple times with the
* same key, in which case newer values should overwrite older values.
*
* This function is guaranteed to be called only on a single thread, the Filament driver
* thread.
*
* @param key a null-terminated C-string with the key of the debug statistic
* @param stringValue the updated string value of key (the integer value passed to the
* callback will be 0)
*/
void debugUpdateStat(const char* UTILS_NONNULL key, utils::CString stringValue);
void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t value);
private:
std::shared_ptr<InsertBlobFunc> mInsertBlob;
std::shared_ptr<RetrieveBlobFunc> mRetrieveBlob;
std::shared_ptr<DebugUpdateStatFunc> mDebugUpdateStat;
mutable utils::Mutex mMutex;
InsertBlobFunc mInsertBlob;
RetrieveBlobFunc mRetrieveBlob;
DebugUpdateStatFunc mDebugUpdateStat;
};
} // namespace filament
@@ -17,6 +17,8 @@
#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
#include "AndroidSwapChainHelper.h"
#include "AndroidFrameCallback.h"
#include "AndroidNdk.h"
#include <backend/AcquiredImage.h>
@@ -30,6 +32,8 @@
#include <math/mat3.h>
#include "AndroidNativeWindow.h"
#include <chrono>
#include <stddef.h>
@@ -167,10 +171,19 @@ protected:
SwapChain* drawSwapChain,
SwapChain* readSwapChain) override;
private:
struct SwapChainEGLAndroid;
struct AndroidDetails;
struct SwapChainEGLAndroid : public SwapChainEGL {
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
void* nativeWindow, uint64_t flags);
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
uint32_t width, uint32_t height, uint64_t flags);
void terminate(PlatformEGLAndroid& platform);
bool setPresentFrameId(uint64_t frameId) const noexcept;
uint64_t getFrameId(uint64_t frameId) const noexcept;
private:
AndroidSwapChainHelper mImpl{};
};
private:
// prevent derived classes' implementations to call through
[[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override;
[[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override;
@@ -190,14 +203,17 @@ private:
int mOSVersion;
ExternalStreamManagerAndroid& mExternalStreamManager;
AndroidDetails& mAndroidDetails;
InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded;
utils::PerformanceHintManager mPerformanceHintManager;
utils::PerformanceHintManager::Session mPerformanceHintSession;
SwapChainEGLAndroid* mCurrentDrawSwapChain{};
using clock = std::chrono::high_resolution_clock;
clock::time_point mStartTimeOfActualWork;
SwapChainEGLAndroid* mCurrentDrawSwapChain{};
AndroidProducerThrottling mProducerThrottling;
bool mAssertNativeWindowIsValid = false;
AndroidFrameCallback mAndroidFrameCallback;
};
} // namespace filament::backend
@@ -48,7 +48,7 @@ struct VulkanPlatformPrivate;
// Forward declare the fence status that will be maintained by the command
// buffer manager.
struct VulkanCmdFence;
struct VulkanCmdBufferState;
/**
* A Platform interface that creates a Vulkan backend.
@@ -242,7 +242,7 @@ public:
* @return A Platform::Sync object tracking the provided fence.
*/
virtual Platform::Sync* createSync(VkFence fence,
std::shared_ptr<VulkanCmdFence> fenceStatus) noexcept;
std::shared_ptr<VulkanCmdBufferState> fenceStatus) noexcept;
/**
* Destroys a sync. If called with a sync not created by this platform
@@ -434,7 +434,7 @@ public:
protected:
struct VulkanSync : public Platform::Sync {
VkFence fence;
std::shared_ptr<VulkanCmdFence> fenceStatus;
std::shared_ptr<VulkanCmdBufferState> fenceStatus;
};
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
@@ -444,11 +444,6 @@ protected:
virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept;
/**
* Query if transient attachments are supported by the backend.
*/
bool isTransientAttachmentSupported() const noexcept;
private:
friend struct VulkanPlatformPrivate;
};
@@ -17,13 +17,12 @@
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
#include "AndroidFrameCallback.h"
#include "AndroidNdk.h"
#include <backend/DriverEnums.h>
#include <backend/platforms/VulkanPlatform.h>
#include <utils/compiler.h>
#include <android/hardware_buffer.h>
namespace filament::backend {
@@ -92,8 +91,6 @@ protected:
VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override;
private:
struct AndroidDetails;
struct ExternalImageVulkanAndroid : public ExternalImage {
AHardwareBuffer* aHardwareBuffer = nullptr;
bool sRGB = false;
@@ -102,7 +99,7 @@ private:
~ExternalImageVulkanAndroid() override;
};
AndroidDetails& mAndroidDetails;
AndroidFrameCallback mAndroidFrameCallback;
int mOSVersion{};
};
@@ -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,
@@ -61,16 +61,6 @@ int NativeWindow::enableFrameTimestamps(ANativeWindow* anw, bool enable) {
return pWindow->perform(anw, ENABLE_FRAME_TIMESTAMPS, enable);
}
int NativeWindow::frameTimestampsSupportsPresent(ANativeWindow* anw, bool* outSupportsPresent) {
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
int value = 0;
bool const success = pWindow->perform(anw, FRAME_TIMESTAMPS_SUPPORTS_PRESENT, &value);
if (success) {
*outSupportsPresent = bool(value);
}
return success;
}
int NativeWindow::getCompositorTiming(ANativeWindow* anw,
int64_t* compositeDeadline, int64_t* compositeInterval,
int64_t* compositeToPresentLatency) {
@@ -32,7 +32,6 @@ struct NativeWindow {
// is valid query enum value
enum {
IS_VALID = 17,
FRAME_TIMESTAMPS_SUPPORTS_PRESENT = 18,
GET_NEXT_FRAME_ID = 24,
ENABLE_FRAME_TIMESTAMPS = 25,
GET_COMPOSITOR_TIMING = 26,
@@ -52,7 +51,6 @@ struct NativeWindow {
static int getNextFrameId(ANativeWindow* anw, uint64_t* frameId);
static int enableFrameTimestamps(ANativeWindow* anw, bool enable);
static int frameTimestampsSupportsPresent(ANativeWindow* anw, bool* outSupportsPresent);
static int getCompositorTiming(ANativeWindow* anw,
int64_t* compositeDeadline, int64_t* compositeInterval,
int64_t* compositeToPresentLatency);
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
#include <backend/platforms/AndroidNdk.h>
#include "AndroidNdk.h"
#include <android/hardware_buffer.h>
@@ -19,9 +19,6 @@
#include <android/native_window.h>
#include <utils/compiler.h>
#include <utils/Logger.h>
#include <cstddef>
#include <cstdint>
#include <limits>
@@ -39,23 +36,11 @@ bool AndroidSwapChainHelper::setPresentFrameId(
int const status = NativeWindow::getNextFrameId(anw, &sysFrameId);
if (status == 0) {
std::lock_guard const lock(mLock);
// frameIds must be strictly monotonic, if that's not the case (i.e. the new frameId is
// less or equal to the last one in the map), we have to clear the map, because the
// map's find() assume sorted keys.
// This case can happen if two different filament::Renderer are used with the same
// ANativeWindow (the Renderer would have different frameIds). This is expected to
// be a rare case.
if (UTILS_UNLIKELY(!mFrameIdToSystemFrameId.empty() &&
frameId <= mFrameIdToSystemFrameId.back().first)) {
// this log is expected to happen very rarely
DLOG(INFO) << "clearing frame history anw=" << anw
<< ", frameId=" << frameId
<< ", previous=" << mFrameIdToSystemFrameId.back().first
<< ", sysFrameId=" << sysFrameId;
// clear the frame history
mFrameIdToSystemFrameId.clear();
auto const pos = mFrameIdToSystemFrameId.find(frameId);
if (pos && *pos != sysFrameId) {
// we're trying to associate the same frame id to a different frame!
return false;
}
// oldest entry is removed
mFrameIdToSystemFrameId.insert(frameId, sysFrameId);
return true;
+13 -44
View File
@@ -139,73 +139,42 @@ bool Platform::queryFrameTimestamps(SwapChain const*, uint64_t, FrameTimestamps*
}
void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept {
std::lock_guard<decltype(mMutex)> lock(mMutex);
mInsertBlob = std::make_shared<InsertBlobFunc>(std::move(insertBlob));
mRetrieveBlob = std::make_shared<RetrieveBlobFunc>(std::move(retrieveBlob));
mInsertBlob = std::move(insertBlob);
mRetrieveBlob = std::move(retrieveBlob);
}
bool Platform::hasInsertBlobFunc() const noexcept {
std::lock_guard<decltype(mMutex)> lock(mMutex);
return mInsertBlob && bool(*mInsertBlob);
return bool(mInsertBlob);
}
bool Platform::hasRetrieveBlobFunc() const noexcept {
std::lock_guard<decltype(mMutex)> lock(mMutex);
return mRetrieveBlob && bool(*mRetrieveBlob);
return bool(mRetrieveBlob);
}
void Platform::insertBlob(void const* key, size_t keySize, void const* value, size_t valueSize) {
std::shared_ptr<InsertBlobFunc> callback;
{
std::unique_lock<decltype(mMutex)> lock(mMutex);
callback = mInsertBlob;
}
if (callback) {
(*callback)(key, keySize, value, valueSize);
if (mInsertBlob) {
mInsertBlob(key, keySize, value, valueSize);
}
}
size_t Platform::retrieveBlob(void const* key, size_t keySize, void* value, size_t valueSize) {
std::shared_ptr<RetrieveBlobFunc> callback;
{
std::unique_lock<decltype(mMutex)> lock(mMutex);
callback = mRetrieveBlob;
}
if (callback) {
return (*callback)(key, keySize, value, valueSize);
if (mRetrieveBlob) {
return mRetrieveBlob(key, keySize, value, valueSize);
}
return 0;
}
void Platform::setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept {
std::lock_guard<decltype(mMutex)> lock(mMutex);
mDebugUpdateStat = std::make_shared<DebugUpdateStatFunc>(std::move(debugUpdateStat));
mDebugUpdateStat = std::move(debugUpdateStat);
}
bool Platform::hasDebugUpdateStatFunc() const noexcept {
std::lock_guard<decltype(mMutex)> lock(mMutex);
return mDebugUpdateStat && bool(*mDebugUpdateStat);
return bool(mDebugUpdateStat);
}
void Platform::debugUpdateStat(const char* key, uint64_t intValue) {
std::shared_ptr<DebugUpdateStatFunc> callback;
{
std::unique_lock<decltype(mMutex)> lock(mMutex);
callback = mDebugUpdateStat;
}
if (callback) {
(*callback)(key, intValue, "");
}
}
void Platform::debugUpdateStat(const char* key, utils::CString stringValue) {
std::shared_ptr<DebugUpdateStatFunc> callback;
{
std::unique_lock<decltype(mMutex)> lock(mMutex);
callback = mDebugUpdateStat;
}
if (callback) {
(*callback)(key, 0, stringValue);
void Platform::debugUpdateStat(const char* key, uint64_t value) {
if (mDebugUpdateStat) {
mDebugUpdateStat(key, value);
}
}
+6 -3
View File
@@ -165,7 +165,7 @@ public:
size_t size, bool forceGpuBuffer = false);
~MetalBuffer();
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer; }
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer || mCpuBuffer; }
MetalBuffer(const MetalBuffer& rhs) = delete;
MetalBuffer& operator=(const MetalBuffer& rhs) = delete;
@@ -185,12 +185,14 @@ public:
* Denotes that this buffer is used for a draw call ensuring that its allocation remains valid
* until the end of the current frame.
*
* @return The MTLBuffer representing the current state of the buffer to bind, it never returns
* nil.
* @return The MTLBuffer representing the current state of the buffer to bind, or nil if there
* is no device allocation.
*
*/
id<MTLBuffer> getGpuBufferForDraw() noexcept;
void* getCpuBuffer() const noexcept { return mCpuBuffer; }
void setLabel(const utils::ImmutableCString& label) {
#if FILAMENT_METAL_DEBUG_LABELS
if (label.empty()) {
@@ -233,6 +235,7 @@ private:
UploadStrategy mUploadStrategy;
TrackedMetalBuffer mBuffer;
size_t mBufferSize = 0;
void* mCpuBuffer = nullptr;
MetalContext& mContext;
};
+63 -13
View File
@@ -39,30 +39,34 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
mUploadStrategy = UploadStrategy::POOL;
}
MTLResourceOptions options = MTLResourceStorageModePrivate;
// If the buffer is less than 4K in size and is updated frequently, we don't use an explicit
// buffer. Instead, we use immediate command encoder methods like setVertexBytes:length:atIndex:.
// This won't work for SSBOs, since they are read/write.
// The buffer will be memory mapped for write operations.
if (any(usage & BufferUsage::SHARED_WRITE_BIT)) {
#if defined(FILAMENT_IOS) || defined(__arm64__) || defined(__aarch64__)
// iOS and Apple Silicon devices use UMA (Unified Memory Architecture), so we use Shared memory.
options = MTLResourceStorageModeShared;
#else
// Intel Macs require Managed memory for CPU/GPU synchronization.
options = MTLResourceStorageModeManaged;
#endif
/*
if (size <= 4 * 1024 && bindingType != BufferObjectBinding::SHADER_STORAGE &&
usage == BufferUsage::DYNAMIC && !forceGpuBuffer) {
mBuffer = nil;
mCpuBuffer = malloc(size);
return;
}
*/
// Otherwise, we allocate a private GPU buffer.
{
ScopedAllocationTimer timer("generic");
mBuffer = { [context.device newBufferWithLength:size options:options],
mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate],
TrackedMetalBuffer::Type::GENERIC };
}
// mBuffer might fail to be allocated. Clients can check for this by calling
// wasAllocationSuccessful().
}
MetalBuffer::~MetalBuffer() = default;
MetalBuffer::~MetalBuffer() {
if (mCpuBuffer) {
free(mCpuBuffer);
}
}
void MetalBuffer::copyIntoBuffer(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
@@ -79,6 +83,12 @@ void MetalBuffer::copyIntoBuffer(
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3))
<< "byteOffset must be a multiple of 4, tag=" << getHandleTag();
// If we have a cpu buffer, we can directly copy into it.
if (mCpuBuffer) {
memcpy(static_cast<uint8_t*>(mCpuBuffer) + byteOffset, src, size);
return;
}
switch (mUploadStrategy) {
case UploadStrategy::BUMP_ALLOCATOR:
uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag));
@@ -96,6 +106,11 @@ void MetalBuffer::copyIntoBufferUnsynchronized(
}
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw() noexcept {
// If there's a CPU buffer, then we return nil here, as the CPU-side buffer will be bound
// separately.
if (mCpuBuffer) {
return nil;
}
assert_invariant(mBuffer);
return mBuffer.get();
}
@@ -156,6 +171,41 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncod
offsets:metalOffsets.data()
withRange:bufferRange];
}
for (size_t b = 0; b < count; b++) {
MetalBuffer* const buffer = buffers[b];
if (!buffer) {
continue;
}
const void* cpuBuffer = buffer->getCpuBuffer();
if (!cpuBuffer) {
continue;
}
const size_t bufferIndex = bufferStart + b;
const size_t offset = offsets[b];
auto* bytes = static_cast<const uint8_t*>(cpuBuffer);
if (stages & Stage::VERTEX) {
[(id<MTLRenderCommandEncoder>) encoder setVertexBytes:(bytes + offset)
length:(buffer->getSize() - offset)
atIndex:bufferIndex];
}
if (stages & Stage::FRAGMENT) {
[(id<MTLRenderCommandEncoder>) encoder setFragmentBytes:(bytes + offset)
length:(buffer->getSize() - offset)
atIndex:bufferIndex];
}
if (stages & Stage::COMPUTE) {
// TODO: using setBytes means the data is read-only, which currently isn't enforced.
// In practice this won't be an issue since MetalBuffer ensures all SSBOs are realized
// through actual id<MTLBuffer> allocations.
[(id<MTLComputeCommandEncoder>) encoder setBytes:(bytes + offset)
length:(buffer->getSize() - offset)
atIndex:bufferIndex];
}
}
}
void MetalBuffer::uploadWithPoolBuffer(
@@ -17,7 +17,6 @@
#ifndef TNT_METALCONTEXT_H
#define TNT_METALCONTEXT_H
#include "MetalErrorQueue.h"
#include "MetalResourceTracker.h"
#include "MetalShaderCompiler.h"
#include "MetalState.h"
@@ -130,7 +129,6 @@ struct MetalContext {
id<MTLCommandBuffer> pendingCommandBuffer = nil;
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nil;
uint32_t currentFrame = 0;
MetalErrorQueue commandBufferErrors;
std::atomic<bool> memorylessLimitsReached = false;
@@ -153,7 +153,6 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) {
logMTLCommandBufferError(errorCode);
context->commandBufferErrors.push(buffer.error);
}
}];
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)
-2
View File
@@ -61,8 +61,6 @@ public:
MetalContext* getContext() { return mContext; }
using DriverBase::scheduleDestroy;
private:
friend class MetalSwapChain;
+17 -27
View File
@@ -254,18 +254,6 @@ MetalDriver::~MetalDriver() noexcept {
void MetalDriver::tick(int) {
executeTickOps();
executeDeferredOps();
// Notify platform of GPU errors.
auto& platform = mPlatform;
if (UTILS_UNLIKELY(!mContext->commandBufferErrors.isEmpty())) {
mContext->commandBufferErrors.flush([&platform](NSError* error) {
if (UTILS_VERY_UNLIKELY(!error)) {
return;
}
const utils::CString errorString(error.localizedDescription.UTF8String);
platform.debugUpdateStat("filament.metal.command_buffer_error", errorString);
});
}
}
void MetalDriver::beginFrame(int64_t monotonic_clock_ns,
@@ -1144,12 +1132,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() {
@@ -1290,6 +1274,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))
@@ -2275,10 +2263,7 @@ MemoryMappedBufferHandle MetalDriver::mapBufferS() noexcept {
void MetalDriver::mapBufferR(MemoryMappedBufferHandle mmbh,
BufferObjectHandle boh, size_t offset,
size_t size, MapBufferAccessFlags access, utils::ImmutableCString&& tag) {
assert_invariant(boh);
MetalBufferObject* bo = mHandleAllocator.handle_cast<MetalBufferObject*>(boh);
assert_invariant(bo);
construct_handle<MetalMemoryMappedBuffer>(mmbh, bo, offset, size, access);
construct_handle<MetalMemoryMappedBuffer>(mmbh, boh, offset, size, access);
mHandleAllocator.associateTagToHandle(mmbh.getId(), std::move(tag));
}
@@ -2286,16 +2271,21 @@ void MetalDriver::unmapBuffer(MemoryMappedBufferHandle mmbh) {
if (UTILS_UNLIKELY(!mmbh)) {
return;
}
auto* mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
mmb->unmap();
destruct_handle<MetalMemoryMappedBuffer>(mmbh);
}
void MetalDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t offset,
BufferDescriptor&& data) {
auto* mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
mmb->copy(*this, offset, std::move(data));
auto mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
assert_invariant(any(mmb->access & MapBufferAccessFlags::WRITE_BIT));
assert_invariant(offset + data.size <= mmb->size);
// TODO: this isa zero-effort implementation of copyToMemoryMappedBuffer(), where we just
// call updateBufferObject(). This could be a fallback implementation for when
// shared memory is not available.
// On UMA systems, this should just be a memcpy into the memory-mapped buffer.
updateBufferObject(mmb->boh, std::move(data), mmb->offset + offset);
}
// explicit instantiation of the Dispatcher
@@ -1,66 +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_DRIVER_METALERRORQUEUE_H
#define TNT_FILAMENT_DRIVER_METALERRORQUEUE_H
#import <Foundation/Foundation.h>
#include <utils/compiler.h>
#include <atomic>
#include <functional>
#include <mutex>
#include <vector>
class MetalErrorQueue {
public:
bool isEmpty() const {
return !mHasErrors.load(std::memory_order_relaxed);
}
void push(NSError* error) {
std::lock_guard<std::mutex> lock(mMutex);
mErrors.push_back(error);
mHasErrors.store(true, std::memory_order_relaxed);
}
void flush(const std::function<void(NSError*)>& callback) {
if (UTILS_LIKELY(isEmpty())) {
return;
}
std::vector<NSError*> errors;
{
std::lock_guard<std::mutex> lock(mMutex);
std::swap(mErrors, errors);
mHasErrors.store(false, std::memory_order_relaxed);
}
for (const auto& error: errors) {
callback(error);
}
}
private:
std::vector<NSError*> mErrors;
std::mutex mMutex;
// Optimization to avoid locking the mutex at each call to flush.
std::atomic<bool> mHasErrors;
};
#endif // TNT_FILAMENT_DRIVER_METALERRORQUEUE_H
+7 -15
View File
@@ -569,22 +569,14 @@ struct MetalDescriptorSet : public HwDescriptorSet {
struct MetalMemoryMappedBuffer : public HwMemoryMappedBuffer {
MetalMemoryMappedBuffer(BufferObjectHandle boh, size_t const offset,
size_t const size, MapBufferAccessFlags const access)
: boh(boh), access(access), size(size), offset(offset) {
}
BufferObjectHandle boh{};
MapBufferAccessFlags access{};
struct {
MetalBufferObject* bo;
void* vaddr = nullptr;
uint32_t size = 0;
uint32_t offset = 0;
} mtl;
MetalMemoryMappedBuffer(MetalBufferObject* bo, size_t offset, size_t size,
MapBufferAccessFlags access) noexcept;
~MetalMemoryMappedBuffer();
void unmap();
void copy(MetalDriver& mtld, size_t offset, BufferDescriptor&& data) const;
uint32_t size = 0;
uint32_t offset = 0;
};
} // namespace backend
@@ -1664,44 +1664,5 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
return buffer.get();
}
MetalMemoryMappedBuffer::MetalMemoryMappedBuffer(MetalBufferObject* bo, size_t offset, size_t size,
MapBufferAccessFlags access) noexcept : access(access) {
MetalBuffer* buffer = bo->getBuffer();
assert_invariant(buffer);
id<MTLBuffer> mtlBuffer = buffer->getGpuBufferForDraw();
assert_invariant(offset + size <= bo->byteCount);
assert_invariant(mtlBuffer.storageMode != MTLStorageModePrivate);
mtl.bo = bo;
mtl.vaddr = static_cast<char*>(mtlBuffer.contents) + offset;
mtl.size = size;
mtl.offset = offset;
}
MetalMemoryMappedBuffer::~MetalMemoryMappedBuffer() = default;
void MetalMemoryMappedBuffer::unmap() {
#if !defined(FILAMENT_IOS) && defined(__x86_64__)
// Managed memory requires didModifyRange to synchronize changes to the GPU. This is specific to Intel Macs.
MetalBuffer* buffer = mtl.bo->getBuffer();
id<MTLBuffer> mtlBuffer = buffer->getGpuBufferForDraw();
if (mtlBuffer && mtlBuffer.storageMode == MTLStorageModeManaged) {
[mtlBuffer didModifyRange:NSMakeRange(mtl.offset, mtl.size)];
}
#endif
// Shared memory on UMA systems is coherent; no explicit synchronization is required.
}
void MetalMemoryMappedBuffer::copy(MetalDriver& mtld, size_t offset, BufferDescriptor&& data) const {
assert_invariant(any(access & MapBufferAccessFlags::WRITE_BIT));
assert_invariant(offset + data.size <= mtl.size);
assert_invariant(mtl.vaddr);
memcpy(static_cast<char*>(mtl.vaddr) + offset, data.buffer, data.size);
mtld.scheduleDestroy(std::move(data));
}
} // namespace backend
} // namespace filament
@@ -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));
+1 -1
View File
@@ -735,7 +735,7 @@ void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept {
// - the nameVersion is out of date *and* we're on the protected context, in this case:
// - the name must be stale from a previous use of this context because we always
// destroy the protected context when we're done with it.
bool const recreateVaoName = vao != &mDefaultVAO &&
bool const recreateVaoName = p != &mDefaultVAO &&
((vao->vao[contextIndex] == 0) ||
(vao->nameVersion != state.age && contextIndex == 1));
if (UTILS_UNLIKELY(recreateVaoName)) {
@@ -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>
@@ -28,13 +28,14 @@
#include <private/backend/VirtualMachineEnv.h>
#include "AndroidNativeWindow.h"
#include "AndroidFrameCallback.h"
#include "AndroidSwapChainHelper.h"
#include "ExternalStreamManagerAndroid.h"
#include <android/api-level.h>
#include <android/native_window.h>
#include <android/hardware_buffer.h>
#include <android/choreographer.h>
#include <android/looper.h>
#include <utils/android/PerformanceHintManager.h>
#include <utils/compiler.h>
@@ -98,27 +99,6 @@ using namespace glext;
// ---------------------------------------------------------------------------------------------
struct PlatformEGLAndroid::SwapChainEGLAndroid : public SwapChainEGL {
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
void* nativeWindow, uint64_t flags);
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
uint32_t width, uint32_t height, uint64_t flags);
void terminate(PlatformEGLAndroid& platform);
bool setPresentFrameId(uint64_t frameId) const noexcept;
uint64_t getFrameId(uint64_t frameId) const noexcept;
bool compositorTimingSupported = false;
bool frameTimestampsSupported = false;
private:
AndroidSwapChainHelper mImpl{};
};
struct PlatformEGLAndroid::AndroidDetails {
AndroidProducerThrottling producerThrottling;
AndroidFrameCallback androidFrameCallback;
};
// ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() {
// PerformanceHintManager() needs the calling thread to be a Java thread; so we need
// to attach this thread to the JVM before we initialize PerformanceHintManager.
@@ -132,20 +112,17 @@ PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmFor
// ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::PlatformEGLAndroid() noexcept
: mExternalStreamManager(ExternalStreamManagerAndroid::create()),
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
: mExternalStreamManager(ExternalStreamManagerAndroid::create()) {
mOSVersion = android_get_device_api_level();
if (mOSVersion < 0) {
mOSVersion = __ANDROID_API_FUTURE__;
}
}
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
delete &mAndroidDetails;
}
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept = default;
void PlatformEGLAndroid::terminate() noexcept {
mAndroidDetails.androidFrameCallback.terminate();
mAndroidFrameCallback.terminate();
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
PlatformEGL::terminate();
}
@@ -178,30 +155,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));
@@ -240,9 +207,9 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
"eglGetNativeClientBufferANDROID"));
if (ext.egl.ANDROID_presentation_time) {
eglPresentationTimeANDROID =
PFNEGLPRESENTATIONTIMEANDROIDPROC(eglGetProcAddress(
"eglPresentationTimeANDROID"));
eglGetNativeClientBufferANDROID =
PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC(eglGetProcAddress(
"eglGetNativeClientBufferANDROID"));
}
if (ext.egl.ANDROID_get_frame_timestamps) {
@@ -273,7 +240,7 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
mAndroidDetails.androidFrameCallback.init();
mAndroidFrameCallback.init();
return driver;
}
@@ -297,25 +264,15 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
}
AndroidFrameCallback::Timeline const preferredTimeline{
mAndroidDetails.androidFrameCallback.getPreferredTimeline() };
mAndroidFrameCallback.getPreferredTimeline() };
outCompositorTiming->frameTime = preferredTimeline.frameTime;
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
outCompositorTiming->compositeDeadline = CompositorTiming::INVALID;
outCompositorTiming->compositeInterval = CompositorTiming::INVALID;
outCompositorTiming->compositeToPresentLatency = CompositorTiming::INVALID;
// From this point on, we always return "success" because some timings were returned.
if (!static_cast<SwapChainEGLAndroid const *>(swapchain)->compositorTimingSupported) {
// if this surface doesn't support it, don't attempt to query the values.
return true;
}
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
EGLSurface const sur = static_cast<SwapChainEGL const *>(swapchain)->sur;
if (sur == EGL_NO_SURFACE) {
return true;
return false;
}
std::array<EGLnsecsANDROID, 3> values;
@@ -326,16 +283,26 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
};
EGLBoolean const success = eglGetCompositorTimingANDROID(getEglDisplay(), sur,
names.size(), names.data(), values.data());
if (UTILS_UNLIKELY(!success)) {
// reset current error to EGL_SUCCESS
eglGetError();
} else {
outCompositorTiming->compositeDeadline = values[0];
outCompositorTiming->compositeInterval = values[1];
outCompositorTiming->compositeToPresentLatency = values[2];
if (!success) {
return false;
}
outCompositorTiming->compositeDeadline = values[0];
outCompositorTiming->compositeInterval = values[1];
outCompositorTiming->compositeToPresentLatency = values[2];
return true;
}
return true;
// fallback to private APIs
auto const anw = static_cast<SwapChainEGL const *>(swapchain)->nativeWindow;
int const status = NativeWindow::getCompositorTiming(anw,
&outCompositorTiming->compositeDeadline,
&outCompositorTiming->compositeInterval,
&outCompositorTiming->compositeToPresentLatency);
if (status == 0) {
return true;
}
return PlatformEGL::queryCompositorTiming(swapchain, outCompositorTiming);
}
bool PlatformEGLAndroid::setPresentFrameId(SwapChain const* swapchain,
@@ -360,10 +327,6 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
return false;
}
if (!static_cast<SwapChainEGLAndroid const *>(swapchain)->frameTimestampsSupported) {
return false;
}
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
EGLSurface const sur = sc->sur;
if (sur == EGL_NO_SURFACE) {
@@ -384,9 +347,7 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
};
EGLBoolean const success = eglGetFrameTimestampsANDROID(getEglDisplay(), sur, hwFrameId,
names.size(), names.data(), values.data());
if (UTILS_UNLIKELY(!success)) {
// reset current error to EGL_SUCCESS
eglGetError();
if (!success) {
return false;
}
outFrameTimestamps->requestedPresentTime = values[0];
@@ -400,44 +361,28 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
outFrameTimestamps->releaseTime = values[8];
return true;
}
// fallback to private APIs
auto const anw = sc->nativeWindow;
int const status = NativeWindow::getFrameTimestamps(anw, hwFrameId,
&outFrameTimestamps->requestedPresentTime,
&outFrameTimestamps->acquireTime,
&outFrameTimestamps->latchTime,
&outFrameTimestamps->firstCompositionStartTime,
&outFrameTimestamps->lastCompositionStartTime,
&outFrameTimestamps->gpuCompositionDoneTime,
&outFrameTimestamps->displayPresentTime,
&outFrameTimestamps->dequeueReadyTime,
&outFrameTimestamps->releaseTime);
if (status == 0) {
return true;
}
return PlatformEGL::queryFrameTimestamps(swapchain, frameId, outFrameTimestamps);
}
Platform::SwapChain* PlatformEGLAndroid::createSwapChain(void* nativeWindow, uint64_t const flags) {
auto* const sc = new(std::nothrow) SwapChainEGLAndroid(*this, nativeWindow, flags);
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
EGLDisplay const dpy = getEglDisplay();
sc->compositorTimingSupported =
eglGetCompositorTimingSupportedANDROID(dpy, sc->sur,
EGL_COMPOSITE_DEADLINE_ANDROID) &&
eglGetCompositorTimingSupportedANDROID(dpy, sc->sur,
EGL_COMPOSITE_INTERVAL_ANDROID) &&
eglGetCompositorTimingSupportedANDROID(dpy, sc->sur,
EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID);
sc->frameTimestampsSupported =
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_REQUESTED_PRESENT_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_RENDERING_COMPLETE_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_COMPOSITION_LATCH_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_FIRST_COMPOSITION_START_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_LAST_COMPOSITION_START_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_DISPLAY_PRESENT_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_DEQUEUE_READY_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_READS_DONE_TIME_ANDROID);
}
// This is expected to be a low frequency log, only turned on in debug builds
DLOG(INFO) << "anw: " << nativeWindow
<< ", compositorTimingSupported=" << sc->compositorTimingSupported
<< ", frameTimestampsSupported=" << sc->frameTimestampsSupported;
return sc;
}
@@ -450,10 +395,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;
}
@@ -734,12 +675,12 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage const sou
bool PlatformEGLAndroid::isProducerThrottlingControlSupported() const {
return mAndroidDetails.producerThrottling.isSupported();
return mProducerThrottling.isSupported();
}
int32_t PlatformEGLAndroid::setProducerThrottlingEnabled(
EGLNativeWindowType const nativeWindow, bool const enabled) const {
return mAndroidDetails.producerThrottling.setProducerThrottlingEnabled(nativeWindow, enabled);
return mProducerThrottling.setProducerThrottlingEnabled(nativeWindow, enabled);
}
// ---------------------------------------------------------------------------------------------
@@ -763,6 +704,8 @@ PlatformEGLAndroid::SwapChainEGLAndroid::SwapChainEGLAndroid(PlatformEGLAndroid
// we ignore the result, it doesn't matter much if it fails
eglSurfaceAttrib(platform.getEglDisplay(), sur, EGL_TIMESTAMPS_ANDROID, EGL_TRUE);
}
} else {
NativeWindow::enableFrameTimestamps(EGLNativeWindowType(nativeWindow), true);
}
}
@@ -16,7 +16,7 @@
#include <backend/platforms/PlatformEGLHeadless.h>
#include <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
@@ -29,9 +29,8 @@ using namespace bluevk;
namespace filament::backend {
FenceStatus VulkanCmdFence::wait(VkDevice device, uint64_t const timeout,
std::chrono::steady_clock::time_point const until) {
FenceStatus VulkanCmdBufferState::waitOnFence(VkDevice device, uint64_t const timeout,
std::chrono::steady_clock::time_point const until) {
// this lock MUST be held for READ when calling vkWaitForFences()
std::shared_lock rl(mLock);
@@ -81,7 +80,7 @@ FenceStatus VulkanCmdFence::wait(VkDevice device, uint64_t const timeout,
return FenceStatus::ERROR; // not supported
}
void VulkanCmdFence::resetFence(VkDevice device) {
void VulkanCmdBufferState::resetFence(VkDevice device) {
// This lock prevents vkResetFences() from being called simultaneously with vkWaitForFences(),
// but by construction, when we're here, we know that the fence has signaled and
// vkWaitForFences() will return shortly.
@@ -90,4 +89,35 @@ void VulkanCmdFence::resetFence(VkDevice device) {
vkResetFences(device, 1, &mFence);
}
uint64_t VulkanTimerQuery::getResult() {
{
std::lock_guard<std::mutex> lock(mDurationLock);
if (mDuration != UNKNOWN_QUERY_RESULT) {
return mDuration;
}
}
VulkanCmdBufferState* startState = nullptr;
VulkanCmdBufferState* endState = nullptr;
{
std::shared_lock const l(mMutex);
startState = mBeginState.get();
endState = mEndState.get();
}
// Here we sum up the time taken by each command buffer that were marked by this timer query.
uint64_t duration = 0;
do {
uint64_t const stDuration = startState->getBufferDuration();
assert_invariant(stDuration != VulkanCmdBufferState::UNKNOWN_BUFFER_DURATION);
duration += stDuration;
startState = startState->getNextState().get();
} while (startState != endState);
{
std::lock_guard<std::mutex> lock(mDurationLock);
mDuration = duration;
}
return duration;
}
} // namespace filament::backend
@@ -27,6 +27,7 @@
#include <chrono>
#include <cstdint>
#include <limits>
#include <memory>
#include <mutex>
#include <shared_mutex>
@@ -35,25 +36,36 @@
namespace filament::backend {
// Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences.
struct VulkanCmdFence {
explicit VulkanCmdFence(VkFence fence) : mFence(fence) { }
~VulkanCmdFence() = default;
// Wrapper to enable use of shared_ptr for implementing shared ownership of Vulkan fences
// and timer query results.
struct VulkanCmdBufferState {
static uint64_t const UNKNOWN_BUFFER_DURATION = std::numeric_limits<uint64_t>::max();
void setStatus(VkResult const value) {
explicit VulkanCmdBufferState(VkFence fence)
: mFence(fence) {
}
~VulkanCmdBufferState() = default;
void setStatus(VkResult const value, uint64_t duration) {
std::lock_guard const l(mLock);
mStatus = value;
mDuration = duration;
mCond.notify_all();
}
VkResult getStatus() {
uint64_t getBufferDuration() {
std::shared_lock const l(mLock);
return mDuration;
}
VkResult getFenceStatus() {
std::shared_lock const l(mLock);
return mStatus;
}
void resetFence(VkDevice device);
FenceStatus wait(VkDevice device, uint64_t timeout,
FenceStatus waitOnFence(VkDevice device, uint64_t timeout,
std::chrono::steady_clock::time_point until);
void cancel() {
@@ -62,6 +74,18 @@ struct VulkanCmdFence {
mCond.notify_all();
}
void setNextState(std::shared_ptr<VulkanCmdBufferState> next) {
mNextState = std::move(next);
}
std::shared_ptr<VulkanCmdBufferState>& getNextState() {
return mNextState;
}
VkFence getVkFence() const {
return mFence;
}
private:
std::shared_mutex mLock; // NOLINT(*-include-cleaner)
std::condition_variable_any mCond;
@@ -70,38 +94,42 @@ private:
// gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually
// finishes executing the command buffer, the status changes to VK_SUCCESS.
VkResult mStatus{ VK_INCOMPLETE };
VkFence mFence;
VkFence const mFence;
uint64_t mDuration = UNKNOWN_BUFFER_DURATION;
// We assume that command buffers are serialized. This will point to the state of the next
// buffer. This is necessary so that we can return total duration of multiple command buffers
// when a TimerQuery is requested by the front-end.
std::shared_ptr<VulkanCmdBufferState> mNextState;
};
struct VulkanFence : public HwFence, fvkmemory::ThreadSafeResource {
VulkanFence() {}
void setFence(std::shared_ptr<VulkanCmdFence> fence) {
std::lock_guard const l(lock);
sharedFence = std::move(fence);
void setCmdBufferState(std::shared_ptr<VulkanCmdBufferState> state) {
std::lock_guard l(lock);
cmdbufState = std::move(state);
cond.notify_all();
}
std::shared_ptr<VulkanCmdFence>& getSharedFence() {
std::lock_guard const l(lock);
return sharedFence;
std::shared_ptr<VulkanCmdBufferState>& getCmdBufferState() {
std::lock_guard l(lock);
return cmdbufState;
}
std::pair<std::shared_ptr<VulkanCmdFence>, bool>
wait(std::chrono::steady_clock::time_point const until) {
// hold a reference so that our state doesn't disappear while we wait
std::pair<std::shared_ptr<VulkanCmdBufferState>, bool> wait(
std::chrono::steady_clock::time_point const until) {
std::unique_lock l(lock);
cond.wait_until(l, until, [this] {
return bool(sharedFence) || canceled;
});
// here mSharedFence will be null if we timed out
return { sharedFence, canceled };
cond.wait_until(l, until, [&] { return bool(cmdbufState) || canceled; });
// here cmdbufState will be null if we timed out
return { cmdbufState, canceled };
}
void cancel() const {
std::lock_guard const l(lock);
if (sharedFence) {
sharedFence->cancel();
if (cmdbufState) {
cmdbufState->cancel();
}
canceled = true;
cond.notify_all();
@@ -111,10 +139,10 @@ private:
mutable std::mutex lock;
mutable std::condition_variable cond;
mutable bool canceled = false;
std::shared_ptr<VulkanCmdFence> sharedFence;
std::shared_ptr<VulkanCmdBufferState> cmdbufState;
};
struct VulkanSync : fvkmemory::ThreadSafeResource, public HwSync {
struct VulkanSync : public HwSync, fvkmemory::ThreadSafeResource {
struct CallbackData {
CallbackHandler* handler;
Platform::SyncCallback cb;
@@ -128,38 +156,50 @@ struct VulkanSync : fvkmemory::ThreadSafeResource, public HwSync {
};
struct VulkanTimerQuery : public HwTimerQuery, fvkmemory::ThreadSafeResource {
VulkanTimerQuery(uint32_t startingIndex, uint32_t stoppingIndex)
: mStartingQueryIndex(startingIndex),
mStoppingQueryIndex(stoppingIndex) {}
void setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept {
std::lock_guard const lock(mFenceMutex);
mFence = std::move(fence);
static decltype(VulkanCmdBufferState::UNKNOWN_BUFFER_DURATION) const
UNKNOWN_QUERY_RESULT = VulkanCmdBufferState::UNKNOWN_BUFFER_DURATION;
VulkanTimerQuery() {}
void setBeginState(std::shared_ptr<VulkanCmdBufferState> state) {
{
std::lock_guard const lock(mMutex);
mBeginState = std::move(state);
}
{
std::lock_guard<std::mutex> lock(mDurationLock);
mDuration = UNKNOWN_QUERY_RESULT;
}
}
bool isCompleted() noexcept {
std::lock_guard const lock(mFenceMutex);
// QueryValue is a synchronous call and might occur before beginTimerQuery has written
// anything into the command buffer, which is an error according to the validation layer
// that ships in the Android NDK. Even when AVAILABILITY_BIT is set, validation seems to
// require that the timestamp has at least been written into a processed command buffer.
// This fence indicates that the corresponding buffer has been completed.
return mFence && mFence->getStatus() == VK_SUCCESS;
void setEndState(std::shared_ptr<VulkanCmdBufferState> state) {
{
std::lock_guard const lock(mMutex);
mEndState = std::move(state);
}
{
std::lock_guard<std::mutex> lock(mDurationLock);
mDuration = UNKNOWN_QUERY_RESULT;
}
}
uint32_t getStartingQueryIndex() const { return mStartingQueryIndex; }
uint32_t getStoppingQueryIndex() const {
return mStoppingQueryIndex;
bool isComplete() {
std::shared_lock const lock(mMutex);
return mBeginState && mEndState &&
mBeginState->getBufferDuration() != UNKNOWN_QUERY_RESULT &&
mEndState->getBufferDuration() != UNKNOWN_QUERY_RESULT;
}
uint64_t getResult();
private:
uint32_t mStartingQueryIndex;
uint32_t mStoppingQueryIndex;
std::shared_ptr<VulkanCmdFence> mFence;
utils::Mutex mFenceMutex;
std::shared_mutex mMutex; // NOLINT(*-include-cleaner)
std::mutex mDurationLock;
std::shared_ptr<VulkanCmdBufferState> mBeginState;
std::shared_ptr<VulkanCmdBufferState> mEndState;
uint64_t mDuration = UNKNOWN_QUERY_RESULT;
};
} // namespace filament::backend
@@ -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;
+107 -37
View File
@@ -55,6 +55,22 @@ VkCommandBuffer createCommandBuffer(VkDevice device, VkCommandPool pool) {
return cmdbuffer;
}
VkFence createFence(VkDevice device, VulkanContext const& context, VkCommandPool pool) {
VkFenceCreateInfo fenceCreateInfo{ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
VkExportFenceCreateInfo exportFenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO,
};
// Necessary to guard this. Otherwise, swiftshader would throw an error.
if (context.getFenceExportFlags()) {
exportFenceCreateInfo.handleTypes = context.getFenceExportFlags(),
fenceCreateInfo.pNext = &exportFenceCreateInfo;
}
VkFence fence;
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &fence);
return fence;
}
} // anonymous namespace
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
@@ -91,30 +107,21 @@ uint32_t VulkanCommandBuffer::sAgeCounter = 0;
VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext const& context, VkDevice device,
VkQueue queue, VkCommandPool pool, VulkanSemaphoreManager* semaphoreManager,
bool isProtected)
: mContext(context),
mMarkerCount(0),
isProtected(isProtected),
mDevice(device),
mQueue(queue),
mSemaphoreManager(semaphoreManager),
mBuffer(createCommandBuffer(device, pool)),
mSubmission(semaphoreManager->acquire()),
mAge(++sAgeCounter) {
VkFenceCreateInfo fenceCreateInfo{.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
VkExportFenceCreateInfo exportFenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO,
.handleTypes = context.getFenceExportFlags()
};
// Necessary to guard this. Otherwise, swiftshader would throw an error.
if (context.getFenceExportFlags()) {
fenceCreateInfo.pNext = &exportFenceCreateInfo;
}
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &mFence);
mFenceStatus = std::make_shared<VulkanCmdFence>(mFence);
}
bool isProtected, uint32_t timerQueryIndex, VkQueryPool queryPool)
: mContext(context),
mMarkerCount(0),
isProtected(isProtected),
mDevice(device),
mQueue(queue),
mSemaphoreManager(semaphoreManager),
mBuffer(createCommandBuffer(device, pool)),
mSubmission(semaphoreManager->acquire()),
mFence(createFence(device, context, pool)),
mCmdbufState(std::make_shared<VulkanCmdBufferState>(mFence)),
mAge(++sAgeCounter),
mQueryBeginIndex(timerQueryIndex),
mQueryEndIndex(timerQueryIndex + 1),
mQueryPool(queryPool) {}
VulkanCommandBuffer::~VulkanCommandBuffer() {
vkDestroyFence(mDevice, mFence, VKALLOC);
@@ -129,12 +136,12 @@ void VulkanCommandBuffer::reset() noexcept {
mSubmission = mSemaphoreManager->acquire();
// reset the fence with proper host synchronization
mFenceStatus->resetFence(mDevice);
mCmdbufState->resetFence(mDevice);
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence
// gets, gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually
// finishes executing the command buffer, the status changes to VK_SUCCESS.
mFenceStatus = std::make_shared<VulkanCmdFence>(mFence);
mCmdbufState = std::make_shared<VulkanCmdBufferState>(mFence);
}
void VulkanCommandBuffer::pushMarker(char const* marker) noexcept {
@@ -191,6 +198,12 @@ void VulkanCommandBuffer::begin() noexcept {
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
vkBeginCommandBuffer(mBuffer, &binfo);
// We need to reset the queries before writing to it. (This must happen within a command buffer,
// but not before the queries have occurred).
vkCmdResetQueryPool(mBuffer, mQueryPool, mQueryBeginIndex, 2);
vkCmdWriteTimestamp(mBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, mQueryPool,
mQueryBeginIndex);
}
fvkmemory::resource_ptr<VulkanSemaphore> VulkanCommandBuffer::submit() {
@@ -198,6 +211,9 @@ fvkmemory::resource_ptr<VulkanSemaphore> VulkanCommandBuffer::submit() {
popMarker();
}
vkCmdWriteTimestamp(mBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, mQueryPool,
mQueryEndIndex);
vkEndCommandBuffer(mBuffer);
VkSemaphore submissionSemaphore = mSubmission->getVkSemaphore();
@@ -236,7 +252,7 @@ fvkmemory::resource_ptr<VulkanSemaphore> VulkanCommandBuffer::submit() {
UTILS_UNUSED_IN_RELEASE VkResult result =
vkQueueSubmit(mQueue, 1, &submitInfo, mFence);
mFenceStatus->setStatus(VK_NOT_READY);
mCmdbufState->setStatus(VK_NOT_READY, VulkanCmdBufferState::UNKNOWN_BUFFER_DURATION);
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
if (result != VK_SUCCESS) {
@@ -248,6 +264,45 @@ fvkmemory::resource_ptr<VulkanSemaphore> VulkanCommandBuffer::submit() {
return mSubmission;
}
void VulkanCommandBuffer::setComplete() {
if (mCmdbufState->getBufferDuration() != VulkanCmdBufferState::UNKNOWN_BUFFER_DURATION) {
return;
}
// We always query the duration of this buffer on the GPU. This is then used to inform timer
// queries from the Filament-side. The reason for doing it this way is because the timestamp is
// consistent within a command buffer, but not guarranteed to be consistent outside of a command
// buffer. So we can only infer duration by looking at execution duration with respect to a
// buffer.
struct QueryResult {
uint64_t beginTime = 0;
uint64_t beginAvailable = 0;
uint64_t endTime = 0;
uint64_t endAvailable = 0;
} result;
constexpr size_t dataSize = sizeof(result);
constexpr VkDeviceSize stride = sizeof(uint64_t) * 2;
VkResult const vkresult =
vkGetQueryPoolResults(mDevice, mQueryPool, mQueryBeginIndex, 2, dataSize,
(void*) &result, stride,
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT);
FILAMENT_CHECK_POSTCONDITION(vkresult == VK_SUCCESS || vkresult == VK_NOT_READY)
<< "vkGetQueryPoolResults error=" << static_cast<int32_t>(vkresult);
uint64_t const begin = result.beginTime;
uint64_t const end = result.endTime;
uint64_t duration = end - begin;
assert_invariant(result.beginAvailable != 0 && result.endAvailable != 0);
if (begin > end) {
FVK_LOGW << "Timestamps are not monotonically increasing. begin=" << begin <<
" end=" << end;
duration = begin - end;
}
mCmdbufState->setStatus(VK_SUCCESS, duration);
}
CommandBufferPool::CommandBufferPool(VulkanContext const& context, VkDevice device, VkQueue queue,
uint8_t queueFamilyIndex, VulkanSemaphoreManager* semaphoreManager, bool isProtected)
: mDevice(device),
@@ -259,11 +314,24 @@ CommandBufferPool::CommandBufferPool(VulkanContext const& context, VkDevice devi
(isProtected ? VK_COMMAND_POOL_CREATE_PROTECTED_BIT : 0u),
.queueFamilyIndex = queueFamilyIndex,
};
vkCreateCommandPool(device, &createInfo, VKALLOC, &mPool);
VkResult result = vkCreateCommandPool(device, &createInfo, VKALLOC, &mPool);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateCommandPool failed."
<< " error=" << static_cast<int32_t>(result);
// Create a timestamp pool large enough to hold a pair of queries for each command buffer.
VkQueryPoolCreateInfo tqpCreateInfo = {
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
.queryType = VK_QUERY_TYPE_TIMESTAMP,
.queryCount = CAPACITY *2,
};
result = vkCreateQueryPool(mDevice, &tqpCreateInfo, VKALLOC, &mQueryPool);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateQueryPool failed."
<< " error=" << static_cast<int32_t>(result);
for (size_t i = 0; i < CAPACITY; ++i) {
mBuffers.emplace_back(std::make_unique<VulkanCommandBuffer>(
context, device, queue, mPool, semaphoreManager, isProtected));
mBuffers.emplace_back(std::make_unique<VulkanCommandBuffer>(context, device, queue, mPool,
semaphoreManager, isProtected, i * 2, mQueryPool));
}
}
@@ -271,6 +339,7 @@ CommandBufferPool::~CommandBufferPool() {
wait();
gc();
vkDestroyCommandPool(mDevice, mPool, VKALLOC);
vkDestroyQueryPool(mDevice, mQueryPool, VKALLOC);
}
VulkanCommandBuffer& CommandBufferPool::getRecording() {
@@ -414,7 +483,7 @@ void VulkanCommands::terminate() {
mPool.reset();
mProtectedPool.reset();
mLastSubmit = {};
mLastFenceStatus = {};
mLastBufferState = {};
}
VulkanCommandBuffer& VulkanCommands::get() {
@@ -443,8 +512,7 @@ bool VulkanCommands::flush() {
fvkmemory::resource_ptr<VulkanSemaphore> dependency;
bool hasFlushed = false;
VkFence flushedFence = VK_NULL_HANDLE;
std::shared_ptr<VulkanCmdFence> flushedFenceStatus;
std::shared_ptr<VulkanCmdBufferState> flushedBufferState;
// Note that we've ordered it so that the non-protected commands are followed by the protected
// commands. This assumes that the protected commands will be that one doing the rendering into
@@ -467,8 +535,7 @@ bool VulkanCommands::flush() {
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT);
mLastSubmit = {};
}
flushedFence = pool->getRecording().getVkFence();
flushedFenceStatus = pool->getRecording().getFenceStatus();
flushedBufferState = pool->getRecording().getState();
dependency = pool->flush();
hasFlushed = true;
}
@@ -476,8 +543,11 @@ bool VulkanCommands::flush() {
if (hasFlushed) {
mInjectedDependency = VK_NULL_HANDLE;
mLastSubmit = dependency;
mLastFence = flushedFence;
mLastFenceStatus = flushedFenceStatus;
if (mLastBufferState) {
mLastBufferState->setNextState(flushedBufferState);
}
mLastBufferState = flushedBufferState;
}
return true;
+27 -25
View File
@@ -42,6 +42,7 @@
namespace filament::backend {
using namespace fvkmemory;
struct CommandBufferPool;
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
class VulkanGroupMarkers {
@@ -65,7 +66,8 @@ private:
// we're done waiting on the most recent submission of the given command buffer.
struct VulkanCommandBuffer {
VulkanCommandBuffer(VulkanContext const& mContext, VkDevice device, VkQueue queue,
VkCommandPool pool, VulkanSemaphoreManager* semaphoreManager, bool isProtected);
VkCommandPool pool, VulkanSemaphoreManager* semaphoreManager, bool isProtected,
uint32_t timerQueryIndex, VkQueryPool queryPool);
VulkanCommandBuffer(VulkanCommandBuffer const&) = delete;
VulkanCommandBuffer& operator=(VulkanCommandBuffer const&) = delete;
@@ -90,20 +92,14 @@ struct VulkanCommandBuffer {
void begin() noexcept;
fvkmemory::resource_ptr<VulkanSemaphore> submit();
inline void setComplete() {
mFenceStatus->setStatus(VK_SUCCESS);
}
void setComplete();
VkResult getStatus() {
return mFenceStatus->getStatus();
return mCmdbufState->getFenceStatus();
}
std::shared_ptr<VulkanCmdFence> getFenceStatus() const {
return mFenceStatus;
}
VkFence getVkFence() const {
return mFence;
std::shared_ptr<VulkanCmdBufferState> getState() const {
return mCmdbufState;
}
VkCommandBuffer buffer() const {
@@ -115,22 +111,31 @@ struct VulkanCommandBuffer {
}
private:
VkFence getVkFence() const {
return mFence;
}
static uint32_t sAgeCounter;
VulkanContext const& mContext;
uint8_t mMarkerCount;
bool const isProtected;
VkDevice mDevice;
VkQueue mQueue;
VkDevice const mDevice;
VkQueue const mQueue;
VulkanSemaphoreManager* mSemaphoreManager;
fvkutils::StaticVector<VkSemaphore, 2> mWaitSemaphores;
fvkutils::StaticVector<VkPipelineStageFlags, 2> mWaitSemaphoreStages;
VkCommandBuffer mBuffer;
VkCommandBuffer const mBuffer;
fvkmemory::resource_ptr<VulkanSemaphore> mSubmission;
VkFence mFence;
std::shared_ptr<VulkanCmdFence> mFenceStatus;
VkFence const mFence;
std::shared_ptr<VulkanCmdBufferState> mCmdbufState;
std::vector<fvkmemory::resource_ptr<Resource>> mResources;
uint32_t mAge;
uint32_t const mQueryBeginIndex;
uint32_t const mQueryEndIndex;
VkQueryPool const mQueryPool;
friend struct CommandBufferPool;
};
struct CommandBufferPool {
@@ -174,6 +179,8 @@ private:
std::vector<std::unique_ptr<VulkanCommandBuffer>> mBuffers;
int8_t mRecording;
VkQueryPool mQueryPool;
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
std::unique_ptr<VulkanGroupMarkers> mGroupMarkers;
#endif
@@ -199,7 +206,7 @@ private:
//
// - Allows off-thread queries of command buffer status.
// - Exposes an "updateFences" method that transfers current fence status into atomics.
// - Users can examine these atomic variables (see VulkanCmdFence) to determine status.
// - Users can examine these atomic variables (see VulkanCmdBufferState) to determine status.
// - We do this because vkGetFenceStatus must be called from the rendering thread.
//
class VulkanCommands {
@@ -230,12 +237,8 @@ public:
return sem;
}
VkFence getMostRecentFence() {
return mLastFence;
}
std::shared_ptr<VulkanCmdFence> getMostRecentFenceStatus() {
return mLastFenceStatus;
std::shared_ptr<VulkanCmdBufferState> getMostRecentBufferState() {
return mLastBufferState;
}
// Takes a semaphore that signals when the next flush can occur. Only one injected
@@ -276,8 +279,7 @@ private:
VkSemaphore mInjectedDependency = VK_NULL_HANDLE;
fvkmemory::resource_ptr<VulkanSemaphore> mLastSubmit;
VkFence mLastFence = VK_NULL_HANDLE;
std::shared_ptr<VulkanCmdFence> mLastFenceStatus;
std::shared_ptr<VulkanCmdBufferState> mLastBufferState;
VkPipelineStageFlags mInjectedDependencyWaitStage = 0;
};
@@ -214,10 +214,4 @@ constexpr static const int FVK_MAX_PIPELINE_AGE = FVK_MAX_COMMAND_BUFFERS;
// destroying any unused pipeline object.
static_assert(FVK_MAX_PIPELINE_AGE >= FVK_MAX_COMMAND_BUFFERS);
// Indicates if the backend must be setup to allow doing a RenderDoc capture.
//
// If this is true, the features not supported by RenderDoc must be disabled, otherwise
// when using RenderDoc the application will crash or will fail to do a capture.
constexpr static const int FVK_RENDERDOC_CAPTURE_MODE = false;
#endif
@@ -154,10 +154,6 @@ public:
return mStagingBufferBypassEnabled;
}
inline bool pipelineCreationFeedbackSupported() const noexcept {
return mPipelineCreationFeedbackSupported;
}
private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
@@ -185,7 +181,6 @@ private:
bool mProtectedMemorySupported = false;
bool mIsUnifiedMemoryArchitecture = false;
bool mStagingBufferBypassEnabled = false;
bool mPipelineCreationFeedbackSupported = false;
fvkutils::VkFormatList mDepthStencilFormats;
fvkutils::VkFormatList mBlittableDepthStencilFormats;
@@ -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;
+45 -147
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>
@@ -232,7 +231,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
mPlatform->getGraphicsQueueFamilyIndex(), mPlatform->getProtectedGraphicsQueue(),
mPlatform->getProtectedGraphicsQueueFamilyIndex(), mContext, &mSemaphoreManager),
mPipelineLayoutCache(mPlatform->getDevice()),
mPipelineCache(mPlatform->getDevice(), mContext),
mPipelineCache(mPlatform->getDevice()),
mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()),
mBufferCache(mContext, mResourceManager, mAllocator),
mFramebufferCache(mPlatform->getDevice()),
@@ -242,10 +241,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
mReadPixels(mPlatform->getDevice()),
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager),
mDescriptorSetCache(mPlatform->getDevice(), &mResourceManager),
mQueryManager(mPlatform->getDevice()),
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
&mDescriptorSetLayoutCache),
mStreamedImageManager(&mExternalImageManager),
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
mIsMSAASwapChainSupported(false), // TODO: support MSAA swapchain
mStereoscopicType(driverConfig.stereoscopicType) {
@@ -340,8 +337,6 @@ void VulkanDriver::terminate() {
mDefaultRenderTarget = {};
mPipelineState = {};
mQueryManager.terminate();
mBlitter.terminate();
mReadPixels.terminate();
@@ -422,14 +417,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 +476,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 +483,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 +494,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);
}
@@ -865,32 +857,33 @@ void VulkanDriver::createFenceR(Handle<HwFence> fh, utils::ImmutableCString&& ta
cmdbuf = &mCommands.get();
}
// Note at this point, the fence has already been constructed via createFenceS, so we just tag
// it with appropriate VulkanCmdFence, which is associated with the current, recording command
// it with appropriate VulkanCmdBufferState, which is associated with the current, recording command
// buffer.
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
fence->setFence(cmdbuf->getFenceStatus());
fence->setCmdBufferState(cmdbuf->getState());
mResourceManager.associateHandle(fh.getId(), std::move(tag));
}
void VulkanDriver::createSyncR(Handle<HwSync> sh, utils::ImmutableCString&& tag) {
auto sync = resource_ptr<VulkanSync>::cast(&mResourceManager, sh);
VkFence fence = VK_NULL_HANDLE;
std::shared_ptr<VulkanCmdFence> fenceStatus;
std::shared_ptr<VulkanCmdBufferState> cmdbufState;
if (mCurrentRenderPass.commandBuffer) {
VulkanCommandBuffer* cmdBuff = mCurrentRenderPass.commandBuffer;
fence = cmdBuff->getVkFence();
fenceStatus = cmdBuff->getFenceStatus();
cmdbufState = cmdBuff->getState();
fence = cmdbufState->getVkFence();
// If we're currently recording, flush so that the fence only applies
// to commands already issued.
mCommands.flush();
} else {
fence = mCommands.getMostRecentFence();
fenceStatus = mCommands.getMostRecentFenceStatus();
cmdbufState = mCommands.getMostRecentBufferState();
fence = cmdbufState->getVkFence();
}
{
std::lock_guard<std::mutex> guard(sync->lock);
sync->sync = mPlatform->createSync(fence, fenceStatus);
sync->sync = mPlatform->createSync(fence, cmdbufState);
}
for (auto& cbData : sync->conversionCallbacks) {
@@ -926,6 +919,14 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
}
}
#if defined(__ANDROID__)
// on Android, disable producer throttling
if (mProducerThrottling.isSupported()) {
mProducerThrottling.setProducerThrottlingEnabled(
static_cast<ANativeWindow*>(nativeWindow), false);
}
#endif
auto swapChain = resource_ptr<VulkanSwapChain>::make(&mResourceManager, sch, mPlatform,
mContext, &mResourceManager, mAllocator, &mCommands, mStagePool, nativeWindow, flags);
swapChain.inc();
@@ -1072,9 +1073,10 @@ Handle<HwSwapChain> VulkanDriver::createSwapChainHeadlessS() noexcept {
Handle<HwTimerQuery> VulkanDriver::createTimerQueryS() noexcept {
// The handle must be constructed here, as a synchronous call to getTimerQueryValue might happen
// before createTimerQueryR is executed.
auto query = mQueryManager.getNextQuery(&mResourceManager);
auto handle = mResourceManager.allocHandle<VulkanTimerQuery>();
auto query = resource_ptr<VulkanTimerQuery>::make(&mResourceManager, handle);
query.inc();
return Handle<VulkanTimerQuery>(query.id());
return handle;
}
Handle<HwDescriptorSetLayout> VulkanDriver::createDescriptorSetLayoutS() noexcept {
@@ -1113,12 +1115,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) {
@@ -1137,7 +1133,6 @@ void VulkanDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
return;
}
auto vtq = resource_ptr<VulkanTimerQuery>::cast(&mResourceManager, tqh);
mQueryManager.clearQuery(vtq);
vtq.dec();
}
@@ -1156,38 +1151,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 +1170,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) {
@@ -1284,7 +1195,6 @@ FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> const fh) {
FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout) {
// Even though this is a synchronous call, the fence handle must be (and stay) valid
assert_invariant(fh);
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
// we have to take into account that the STL's wait_for() actually works with
@@ -1301,14 +1211,14 @@ FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout
until = now + nanoseconds(timeout);
}
auto const [cmdfence, canceled] = fence->wait(until);
if (!cmdfence || canceled) {
auto const [cmdbufState, canceled] = fence->wait(until);
if (!cmdbufState || canceled) {
return canceled ? FenceStatus::ERROR : FenceStatus::TIMEOUT_EXPIRED;
}
// now we are holding a reference to our VulkanCmdFence, so we know it can't
// now we are holding a reference to our VulkanCmdBufferState, so we know it can't
// disappear while we wait
return cmdfence->wait(mPlatform->getDevice(), timeout, until);
return cmdbufState->waitOnFence(mPlatform->getDevice(), timeout, until);
}
void VulkanDriver::getPlatformSync(Handle<HwSync> sh, CallbackHandler* handler,
@@ -1554,13 +1464,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));
@@ -1601,39 +1515,21 @@ void VulkanDriver::setupExternalImage(void* image) {
TimerQueryResult VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
auto vtq = resource_ptr<VulkanTimerQuery>::cast(&mResourceManager, tqh);
if (!vtq->isCompleted()) {
if (!vtq->isComplete()) {
return TimerQueryResult::NOT_READY;
}
auto results = mQueryManager.getResult(vtq);
if (results.beginAvailable == 0 || results.endAvailable == 0) {
return TimerQueryResult::NOT_READY;
}
uint64_t const begin = results.beginTime;
uint64_t const end = results.endTime;
if (begin >= end) {
// TODO: queries might have ran on different command buffers.
FVK_LOGW << "Timestamps are not monotonically increasing. ";
*elapsedTime = 0;
return TimerQueryResult::ERROR;
}
uint64_t result = vtq->getResult();
assert_invariant(result != VulkanTimerQuery::UNKNOWN_QUERY_RESULT);
// NOTE: MoltenVK currently writes system time so the following delta will always be zero.
// However there are plans for implementing this properly. See the following GitHub ticket.
// https://github.com/KhronosGroup/MoltenVK/issues/773
float const period = mContext.getPhysicalDeviceLimits().timestampPeriod;
*elapsedTime = uint64_t(float(end - begin) * period);
*elapsedTime = uint64_t(float(result) * period);
return TimerQueryResult::AVAILABLE;
}
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) {
@@ -2388,12 +2284,14 @@ void VulkanDriver::scissor(Viewport scissorBox) {
void VulkanDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
auto vtq = resource_ptr<VulkanTimerQuery>::cast(&mResourceManager, tqh);
mQueryManager.beginQuery(&(mCommands.get()), vtq);
auto cmdbuf = &mCommands.get();
vtq->setBeginState(cmdbuf->getState());
}
void VulkanDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
auto vtq = resource_ptr<VulkanTimerQuery>::cast(&mResourceManager, tqh);
mQueryManager.endQuery(&(mCommands.get()), vtq);
auto cmdbuf = &mCommands.get();
vtq->setEndState(cmdbuf->getState());
}
void VulkanDriver::debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept {
+7 -10
View File
@@ -25,7 +25,6 @@
#include "VulkanHandles.h"
#include "VulkanMemory.h"
#include "VulkanPipelineCache.h"
#include "VulkanQueryManager.h"
#include "VulkanReadPixels.h"
#include "VulkanSamplerCache.h"
#include "VulkanSemaphoreManager.h"
@@ -34,14 +33,12 @@
#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"
#include "vulkan/utils/Definitions.h"
#include "backend/DriverEnums.h"
#include "backend/BufferObjectStreamDescriptor.h"
#include "DriverBase.h"
#include "private/backend/Driver.h"
@@ -50,6 +47,10 @@
#include <utils/Allocator.h>
#include <utils/compiler.h>
#if defined(__ANDROID__)
#include "AndroidNativeWindow.h"
#endif
namespace filament::backend {
class VulkanPlatform;
@@ -157,10 +158,7 @@ private:
VulkanReadPixels mReadPixels;
VulkanDescriptorSetLayoutCache mDescriptorSetLayoutCache;
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
@@ -206,10 +204,9 @@ 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;
#if defined(__ANDROID__)
AndroidProducerThrottling mProducerThrottling;
#endif
};
} // 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;
};
@@ -34,41 +34,8 @@ using namespace bluevk;
namespace filament::backend {
namespace {
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
void printPipelineFeedbackInfo(VkPipelineCreationFeedbackCreateInfo const& feedbackInfo) {
VkPipelineCreationFeedback const& pipelineInfo = *feedbackInfo.pPipelineCreationFeedback;
if (!(pipelineInfo.flags & VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT)) {
return;
}
bool const isCacheHit =
(pipelineInfo.flags & VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT);
FVK_LOGD << "Pipeline build stats - Cache hit: " << isCacheHit
<< ", Time: " << pipelineInfo.duration / 1000000.0 << "ms";
for (uint32_t i = 0; i < feedbackInfo.pipelineStageCreationFeedbackCount; ++i) {
VkPipelineCreationFeedback const& stageInfo = feedbackInfo.pPipelineStageCreationFeedbacks[i];
if (!(stageInfo.flags & VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT)) {
continue;
}
bool const isVertexShader = (i == 0);
bool const isCacheHit = (stageInfo.flags &
VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT);
FVK_LOGD << (isVertexShader ? "Vertex" : "Fragment")
<< " shader build stats - Cache hit: " << isCacheHit
<< ", Time: " << stageInfo.duration / 1000000.0 << "ms";
}
}
#endif
} // namespace
VulkanPipelineCache::VulkanPipelineCache(VkDevice device, VulkanContext const& context)
: mDevice(device),
mContext(context) {
VulkanPipelineCache::VulkanPipelineCache(VkDevice device)
: mDevice(device) {
VkPipelineCacheCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
};
@@ -191,12 +158,9 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
.alphaToCoverageEnable = raster.alphaToCoverageEnable,
.alphaToOneEnable = VK_FALSE,
};
bool const enableDepthTest =
raster.depthCompareOp != SamplerCompareFunc::A ||
raster.depthWriteEnable;
VkPipelineDepthStencilStateCreateInfo vkDs = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.depthTestEnable = enableDepthTest ? VK_TRUE : VK_FALSE,
.depthTestEnable = VK_TRUE,
.depthWriteEnable = raster.depthWriteEnable,
.depthCompareOp = fvkutils::getCompareOp(raster.depthCompareOp),
.depthBoundsTestEnable = VK_FALSE,
@@ -252,40 +216,15 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
}
}
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" << shaderStages[0].module << ", "
<< shaderStages[1].module << ")";
VkPipelineCreationFeedback stageFeedbacks[SHADER_MODULE_COUNT] = {};
VkPipelineCreationFeedback pipelineFeedback = {};
VkPipelineCreationFeedbackCreateInfo feedbackInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_CREATION_FEEDBACK_CREATE_INFO_EXT,
.pNext = nullptr,
.pPipelineCreationFeedback = &pipelineFeedback,
.pipelineStageCreationFeedbackCount = hasFragmentShader ? SHADER_MODULE_COUNT : 1,
.pPipelineStageCreationFeedbacks = stageFeedbacks,
};
if (mContext.pipelineCreationFeedbackSupported()) {
feedbackInfo.pNext = pipelineCreateInfo.pNext;
pipelineCreateInfo.pNext = &feedbackInfo;
}
#endif
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = ("
<< shaderStages[0].module << ", " << shaderStages[1].module << ")";
#endif
PipelineCacheEntry cacheEntry = {
.lastUsed = mCurrentTime,
};
VkResult error = vkCreateGraphicsPipelines(mDevice, mPipelineCache, 1, &pipelineCreateInfo,
VKALLOC, &cacheEntry.handle);
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" << shaderStages[0].module << ", "
<< shaderStages[1].module << ")";
if (mContext.pipelineCreationFeedbackSupported()) {
printPipelineFeedbackInfo(feedbackInfo);
}
#endif
assert_invariant(error == VK_SUCCESS);
if (error != VK_SUCCESS) {
FVK_LOGE << "vkCreateGraphicsPipelines error " << error;
@@ -86,7 +86,7 @@ public:
static_assert(sizeof(RasterState) == 16, "RasterState must not have implicit padding.");
VulkanPipelineCache(VkDevice device, VulkanContext const& context);
VulkanPipelineCache(VkDevice device);
void bindLayout(VkPipelineLayout layout) noexcept;
@@ -210,8 +210,6 @@ private:
// Current bindings for the pipeline and descriptor sets.
PipelineKey mBoundPipeline = {};
[[maybe_unused]] VulkanContext const& mContext;
};
} // namespace filament::backend
@@ -1,106 +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 "VulkanQueryManager.h"
#include "VulkanAsyncHandles.h"
#include "VulkanCommands.h"
#include "VulkanConstants.h"
#include <bluevk/BlueVK.h>
namespace filament::backend {
using namespace bluevk;
VulkanQueryManager::VulkanQueryManager(VkDevice device) : mDevice(device) {
// Create a timestamp pool large enough to hold a pair of queries for each timer.
VkQueryPoolCreateInfo tqpCreateInfo = {
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
.queryType = VK_QUERY_TYPE_TIMESTAMP,
.queryCount = (uint32_t) mUsed.size() * 2,
};
VkResult result = vkCreateQueryPool(mDevice, &tqpCreateInfo, VKALLOC, &mPool);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateQueryPool failed."
<< " error=" << static_cast<int32_t>(result);
}
fvkmemory::resource_ptr<VulkanTimerQuery> VulkanQueryManager::getNextQuery(
fvkmemory::ResourceManager* resourceManager) {
auto unused = ~mUsed;
if (unused.empty()) {
FVK_LOGE << "More than " << mUsed.size() << " timers are not supported.";
return {};
}
std::pair<uint32_t, uint32_t> queryIndices;
size_t const firstUnused = unused.firstSetBit();
{
std::unique_lock<utils::Mutex> lock(mMutex);
mUsed.set(firstUnused);
queryIndices = { firstUnused * 2, firstUnused * 2 + 1 };
}
return fvkmemory::resource_ptr<VulkanTimerQuery>::construct(resourceManager, queryIndices.first,
queryIndices.second);
}
void VulkanQueryManager::clearQuery(fvkmemory::resource_ptr<VulkanTimerQuery> query) {
std::unique_lock<utils::Mutex> lock(mMutex);
uint32_t const startingIndex = query->getStartingQueryIndex();
mUsed.unset(startingIndex / 2);
}
void VulkanQueryManager::beginQuery(VulkanCommandBuffer const* commands,
fvkmemory::resource_ptr<VulkanTimerQuery> query) {
uint32_t const index = query->getStartingQueryIndex();
auto const cmdbuffer = commands->buffer();
vkCmdResetQueryPool(cmdbuffer, mPool, index, 2);
vkCmdWriteTimestamp(cmdbuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
// We stash this because getResult might come before the query is actually processed.
query->setFence(commands->getFenceStatus());
}
void VulkanQueryManager::endQuery(VulkanCommandBuffer const* commands,
fvkmemory::resource_ptr<VulkanTimerQuery> query) {
uint32_t const index = query->getStoppingQueryIndex();
vkCmdWriteTimestamp(commands->buffer(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
}
VulkanQueryManager::QueryResult VulkanQueryManager::getResult(
fvkmemory::resource_ptr<VulkanTimerQuery> query) {
uint32_t const index = query->getStartingQueryIndex();
QueryResult result;
size_t const dataSize = sizeof(result);
VkDeviceSize const stride = sizeof(uint64_t) * 2;
VkResult vkresult =
vkGetQueryPoolResults(mDevice, mPool, index, 2, dataSize, (void*) &result, stride,
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT);
FILAMENT_CHECK_POSTCONDITION(vkresult == VK_SUCCESS || vkresult == VK_NOT_READY)
<< "vkGetQueryPoolResults error=" << static_cast<int32_t>(vkresult);
if (vkresult == VK_NOT_READY) {
return {};
}
return result;
}
void VulkanQueryManager::terminate() noexcept {
vkDestroyQueryPool(mDevice, mPool, VKALLOC);
mPool = VK_NULL_HANDLE;
}
} // filament::backend
@@ -1,63 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_VULKANQUERYMANAGER_H
#define TNT_FILAMENT_BACKEND_VULKANQUERYMANAGER_H
#include "vulkan/memory/ResourcePointer.h"
namespace filament::backend {
struct VulkanCommandBuffer;
struct VulkanTimerQuery;
class VulkanQueryManager {
public:
struct QueryResult {
uint64_t beginTime = 0;
uint64_t beginAvailable = 0;
uint64_t endTime = 0;
uint64_t endAvailable = 0;
};
VulkanQueryManager(VkDevice device);
~VulkanQueryManager() = default;
// Not copy-able.
VulkanQueryManager(VulkanQueryManager const&) = delete;
VulkanQueryManager& operator=(VulkanQueryManager const&) = delete;
fvkmemory::resource_ptr<VulkanTimerQuery>
getNextQuery(fvkmemory::ResourceManager* mResourceManager);
void clearQuery(fvkmemory::resource_ptr<VulkanTimerQuery> query);
void beginQuery(VulkanCommandBuffer const* commands,
fvkmemory::resource_ptr<VulkanTimerQuery> query);
void endQuery(VulkanCommandBuffer const* commands,
fvkmemory::resource_ptr<VulkanTimerQuery> query);
QueryResult getResult(fvkmemory::resource_ptr<VulkanTimerQuery> query);
void terminate() noexcept;
private:
VkDevice mDevice;
VkQueryPool mPool;
utils::bitset32 mUsed;
utils::Mutex mMutex;
};
} // filament::backend
#endif // TNT_FILAMENT_BACKEND_VULKANQUERYMANAGER_H
@@ -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;
}
@@ -228,10 +228,6 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
#endif
VK_KHR_MULTIVIEW_EXTENSION_NAME,
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME,
#endif
};
ExtensionSet exts;
// Identify supported physical device extensions
@@ -869,8 +865,6 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
if (!mImpl->mSharedContext) {
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
context.mPipelineCreationFeedbackSupported =
setContains(deviceExts, VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME);
} else {
VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext;
context.mDebugUtilsSupported = scontext->debugUtilsSupported;
@@ -879,16 +873,13 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
// Check the availability of lazily allocated memory
context.mLazilyAllocatedMemorySupported = false;
// RenderDoc doesn't support lazy allocated memory
if constexpr (!FVK_RENDERDOC_CAPTURE_MODE) {
for (uint32_t i = 0, typeCount = context.mMemoryProperties.memoryTypeCount; i < typeCount;
++i) {
VkMemoryType const type = context.mMemoryProperties.memoryTypes[i];
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
context.mLazilyAllocatedMemorySupported = true;
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
break;
}
for (uint32_t i = 0, typeCount = context.mMemoryProperties.memoryTypeCount; i < typeCount;
++i) {
VkMemoryType const type = context.mMemoryProperties.memoryTypes[i];
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
context.mLazilyAllocatedMemorySupported = true;
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
break;
}
}
@@ -980,7 +971,7 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
}
Platform::Sync* VulkanPlatform::createSync(VkFence fence,
std::shared_ptr<VulkanCmdFence> fenceStatus) noexcept {
std::shared_ptr<VulkanCmdBufferState> fenceStatus) noexcept {
return new VulkanSync{.fence = fence, .fenceStatus = fenceStatus};
}
@@ -1031,8 +1022,4 @@ VkExternalFenceHandleTypeFlagBits VulkanPlatform::getFenceExportFlags() const no
return static_cast<VkExternalFenceHandleTypeFlagBits>(0);
}
bool VulkanPlatform::isTransientAttachmentSupported() const noexcept {
return mImpl->mContext.isLazilyAllocatedMemorySupported();
}
} // namespace filament::backend
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <backend/platforms/VulkanPlatformAndroid.h>
#include "vulkan/VulkanConstants.h"
@@ -21,8 +20,6 @@
#include "vulkan/platform/VulkanPlatformSwapChainImpl.h"
#include "vulkan/utils/Image.h"
#include "AndroidFrameCallback.h"
#include <private/backend/BackendUtilsAndroid.h>
#include <backend/DriverEnums.h>
@@ -35,7 +32,6 @@
#include <android/hardware_buffer.h>
#include <android/native_window.h>
#include <new>
#include <utility>
using namespace bluevk;
@@ -162,55 +158,7 @@ std::pair<TextureFormat, TextureUsage> getFilamentFormatAndUsage(const AHardware
};
}
uint32_t selectMemoryTypeForExternalImage(VkPhysicalDevice physicalDevice, VkDevice device,
VkImage image, uint32_t types, VkFlags requiredMemoryFlags) {
VkPhysicalDeviceMemoryProperties memoryProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
uint32_t const memoryTypeIndex =
VulkanContext::selectMemoryType(memoryProperties, types, requiredMemoryFlags);
if constexpr (FVK_RENDERDOC_CAPTURE_MODE) {
// RenderDoc will replay external resources as non-external.
// Adjust properties that will trip it up when replaying, even though these are not valid.
// Update memory type index if necessary so that we can replay the capture.
VkMemoryRequirements imageMemoryRequirements;
vkGetImageMemoryRequirements(device, image, &imageMemoryRequirements);
uint32_t const imageMemoryTypeBits = imageMemoryRequirements.memoryTypeBits;
bool const isMemoryTypeSupported = ((1 << memoryTypeIndex) & imageMemoryTypeBits) != 0;
if (isMemoryTypeSupported) {
return memoryTypeIndex;
}
// Current memory type will not be replayable by RenderDoc.
// Attempt to change the memory type index
VkMemoryPropertyFlags const kRenderDocFallBackReqs = 0;
uint32_t commonMemoryTypeBits = types & imageMemoryTypeBits;
uint32_t commonTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
commonMemoryTypeBits, kRenderDocFallBackReqs);
if (commonMemoryTypeBits && commonTypeIndex != VK_MAX_MEMORY_TYPES) {
return commonTypeIndex;
}
uint32_t imageTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
imageMemoryTypeBits, kRenderDocFallBackReqs);
if (imageTypeIndex != VK_MAX_MEMORY_TYPES) {
return imageTypeIndex;
}
}
return memoryTypeIndex;
}
} // namespace
// ---------------------------------------------------------------------------------------------
struct VulkanPlatformAndroid::AndroidDetails {
AndroidFrameCallback androidFrameCallback;
};
// ---------------------------------------------------------------------------------------------
}// namespace
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() {
if (__builtin_available(android 26, *)) {
@@ -220,19 +168,14 @@ VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid()
}
}
// ---------------------------------------------------------------------------------------------
VulkanPlatformAndroid::VulkanPlatformAndroid() :
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
VulkanPlatformAndroid::VulkanPlatformAndroid() {
mOSVersion = android_get_device_api_level();
if (mOSVersion < 0) {
mOSVersion = __ANDROID_API_FUTURE__;
}
}
VulkanPlatformAndroid::~VulkanPlatformAndroid() noexcept {
delete &mAndroidDetails;
}
VulkanPlatformAndroid::~VulkanPlatformAndroid() noexcept = default;
Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
AHardwareBuffer const* buffer, bool sRGB) noexcept {
@@ -411,6 +354,8 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
.image = image,
.buffer = VK_NULL_HANDLE,
};
VkPhysicalDeviceMemoryProperties memoryProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
VkMemoryPropertyFlags requiredMemoryFlags =
!isExternal && any(metadata.filamentUsage & TextureUsage::UPLOADABLE)
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
@@ -420,8 +365,8 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
requiredMemoryFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT;
}
uint32_t const memoryTypeIndex = selectMemoryTypeForExternalImage(physicalDevice, device,
image, metadata.memoryTypeBits, requiredMemoryFlags);
uint32_t const memoryTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
metadata.memoryTypeBits, requiredMemoryFlags);
VkMemoryAllocateInfo const allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
@@ -462,7 +407,7 @@ bool VulkanPlatformAndroid::convertSyncToFd(Platform::Sync* sync, int* fd) const
VulkanSync& vulkanSync = static_cast<VulkanSync&>(*sync);
assert_invariant(vulkanSync.fenceStatus);
if (vulkanSync.fenceStatus->getStatus() == VK_SUCCESS) {
if (vulkanSync.fenceStatus->getFenceStatus() == VK_SUCCESS) {
// We've already signaled; return -1 so that operations will proceed
// immediately. Also, signal that fence conversion was successful.
*fd = -1;
@@ -514,14 +459,14 @@ int VulkanPlatformAndroid::getOSVersion() const noexcept {
}
void VulkanPlatformAndroid::terminate() {
mAndroidDetails.androidFrameCallback.terminate();
mAndroidFrameCallback.terminate();
VulkanPlatform::terminate();
}
Driver* VulkanPlatformAndroid::createDriver(void* sharedContext, DriverConfig const& driverConfig) {
Driver* driver = VulkanPlatform::createDriver(sharedContext, driverConfig);
if (driver) {
mAndroidDetails.androidFrameCallback.init();
mAndroidFrameCallback.init();
}
return driver;
}
@@ -537,19 +482,13 @@ bool VulkanPlatformAndroid::queryCompositorTiming(SwapChain const* swapchain,
}
AndroidFrameCallback::Timeline const preferredTimeline{
mAndroidDetails.androidFrameCallback.getPreferredTimeline() };
mAndroidFrameCallback.getPreferredTimeline() };
outCompositorTiming->frameTime = preferredTimeline.frameTime;
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
outCompositorTiming->compositeDeadline = CompositorTiming::INVALID;
outCompositorTiming->compositeInterval = CompositorTiming::INVALID;
outCompositorTiming->compositeToPresentLatency = CompositorTiming::INVALID;
// From this point on, we always return "success" because some timings were returned.
auto vulkanSwapchain = static_cast<VulkanPlatformSwapChainBase const *>(swapchain);
vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
return true;
return vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
}
bool VulkanPlatformAndroid::setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept {
@@ -165,11 +165,6 @@ VulkanPlatformSurfaceSwapChain::~VulkanPlatformSurfaceSwapChain() {
VkResult VulkanPlatformSurfaceSwapChain::create() {
#ifdef __ANDROID__
NativeWindow::enableFrameTimestamps(static_cast<ANativeWindow*>(mNativeWindow), true);
// on Android, disable producer throttling
if (mProducerThrottling.isSupported()) {
mProducerThrottling.setProducerThrottlingEnabled(
static_cast<ANativeWindow*>(mNativeWindow), false);
}
#endif
VkSurfaceFormatKHR surfaceFormat = {};
@@ -361,15 +356,13 @@ bool VulkanPlatformSurfaceSwapChain::queryCompositorTiming(
CompositorTiming* outCompositorTiming) const {
#ifdef __ANDROID__
// fallback to private APIs
if (UTILS_VERY_LIKELY(mNativeWindow)) {
int const status = NativeWindow::getCompositorTiming(
static_cast<ANativeWindow*>(mNativeWindow),
&outCompositorTiming->compositeDeadline,
&outCompositorTiming->compositeInterval,
&outCompositorTiming->compositeToPresentLatency);
if (status == 0) {
return true;
}
int const status = NativeWindow::getCompositorTiming(
static_cast<ANativeWindow*>(mNativeWindow),
&outCompositorTiming->compositeDeadline,
&outCompositorTiming->compositeInterval,
&outCompositorTiming->compositeToPresentLatency);
if (status == 0) {
return true;
}
#endif
return VulkanPlatformSwapChainBase::queryCompositorTiming(outCompositorTiming);
@@ -25,8 +25,7 @@
#include <bluevk/BlueVK.h>
#ifdef __ANDROID__
#include "AndroidSwapChainHelper.h"
#include "AndroidNativeWindow.h"
#include <AndroidSwapChainHelper.h>
#endif
#include <unordered_map>
@@ -123,7 +122,6 @@ private:
#ifdef __ANDROID__
AndroidSwapChainHelper mImpl{};
AndroidProducerThrottling mProducerThrottling;
#endif
};
@@ -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
-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.
*
-21
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.
*

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