Compare commits

..
Author SHA1 Message Date
Serge Metral 531bfd6f6e Fix Android build. 2025-11-19 17:41:17 +00:00
Serge Metral db272484f9 Merge branch 'main' into serge/vulkan_pipeline_cache_json 2025-11-19 09:05:45 -08:00
Serge Metral 9dadf400d2 Adding render pass key. 2025-11-19 08:47:03 -08:00
Serge Metral 16c7a3c968 Adding render pass serialization. 2025-11-18 16:00:15 -08:00
Serge Metral 626f338cba First commit. 2025-11-18 07:06:46 -08:00
Serge Metral 4e05cd73c6 Merge branch 'google:main' into main 2025-11-04 11:20:17 -08:00
Serge Metral 49f9708312 Merge branch 'google:main' into main 2025-10-29 17:00:00 +00:00
Serge Metral 2791195a92 Merge branch 'google:main' into main 2025-10-22 10:50:16 -07:00
Serge Metral 65f78c06bb Merge branch 'google:main' into main 2025-10-21 11:32:23 -07:00
Serge Metral 71175ee410 Merge branch 'google:main' into main 2025-10-13 10:06:53 -07:00
Serge Metral c9b310450c Merge branch 'google:main' into main 2025-09-19 14:47:15 +00:00
Serge Metral 54c636d54c Merge branch 'google:main' into main 2025-09-04 03:15:27 +00:00
Serge Metral f503205be2 Merge branch 'google:main' into main 2025-08-29 17:25:01 +00:00
Serge Metral 02106110fe Merge branch 'google:main' into main 2025-08-25 16:31:27 +00:00
Serge Metral 5a0f78d915 Merge branch 'google:main' into main 2025-08-18 11:23:24 -07:00
Serge Metral f548c588d9 Merge branch 'google:main' into main 2025-08-13 18:17:53 +00:00
Serge Metral 487fb273f1 Merge branch 'google:main' into main 2025-08-12 15:25:09 +00:00
Serge Metral 111ac62d2f Merge branch 'google:main' into main 2025-08-07 11:24:34 -07:00
Serge Metral 2fac13e154 Merge branch 'google:main' into main 2025-07-31 20:45:26 -07:00
Serge Metral bcae04c9ef Merge branch 'google:main' into main 2025-05-12 14:15:56 -07:00
Serge Metral 55fc91b35f Merge branch 'google:main' into main 2025-05-08 18:25:41 +00:00
Serge Metral ff2827b226 Merge branch 'google:main' into main 2025-03-28 14:40:50 +00:00
Serge Metral 13e1d2a042 Merge branch 'google:main' into main 2025-03-21 23:32:55 +00:00
Serge Metral b09cbaf0db Merge branch 'google:main' into main 2025-03-18 19:47:10 +00:00
Serge Metral b570d489e3 Merge branch 'google:main' into main 2025-03-17 16:09:21 +00:00
Serge Metral 7f7daddbfa Merge branch 'google:main' into main 2025-03-14 17:28:06 +00:00
Serge Metral cfe6a423d3 Merge branch 'google:main' into main 2025-03-06 10:48:37 -08:00
Serge Metral ba99844f22 Merge branch 'google:main' into main 2025-03-05 16:00:28 -08:00
Serge Metral ea4c20ec2c Merge branch 'google:main' into main 2025-01-29 20:49:02 -08:00
Serge Metral 0a661c1f3c Merge branch 'google:main' into main 2025-01-27 08:55:26 -08:00
Serge Metral 021c4f6691 Merge branch 'google:main' into main 2025-01-13 12:07:40 -08:00
Serge Metral be6b6ae7be Merge branch 'google:main' into main 2024-12-18 20:56:10 -08:00
Serge Metral ad3a5b70b6 Merge branch 'google:main' into main 2024-12-03 10:05:08 -08:00
Serge Metral 80803f9693 Merge branch 'google:main' into main 2024-11-06 02:54:52 +00:00
Serge Metral 3b0e44c9a3 Merge branch 'google:main' into main 2024-11-05 22:20:25 +00:00
Serge Metral c5c2d42467 Merge branch 'google:main' into main 2024-11-05 05:25:51 +00:00
Serge Metral ff145f6ccf Merge branch 'google:main' into main 2024-10-31 15:55:28 +00:00
Serge Metral 48bb10a4b5 Merge branch 'google:main' into main 2024-10-30 04:14:21 +00:00
Serge Metral 01a2480175 Merge branch 'google:main' into main 2024-10-26 05:37:19 +00:00
Serge Metral 9e921936c6 Merge branch 'google:main' into main 2024-10-18 13:29:27 +00:00
Serge Metral 8bc4d3a9df Merge branch 'google:main' into main 2024-10-09 17:16:55 +00:00
Serge Metral 0fdf1cac1d Merge branch 'google:main' into main 2024-10-03 09:10:04 -07:00
Serge Metral 387942cc39 Merge branch 'google:main' into main 2024-09-20 20:48:06 -07:00
Serge Metral c81cedd887 Merge branch 'google:main' into main 2024-09-04 08:36:42 -07:00
Serge Metral abe8a49d17 Merge branch 'google:main' into main 2024-06-26 17:26:00 -07:00
Serge Metral 96cba96058 Merge branch 'google:main' into main 2024-06-18 09:38:05 -07:00
Serge Metral 4fccd957db Merge branch 'google:main' into main 2024-06-11 16:54:22 -07:00
Powei Feng d49554d5ce Merge branch 'main' into main 2024-06-05 14:36:17 -07:00
Powei Feng 867161773e Merge branch 'main' into main 2024-06-05 11:53:35 -07:00
Serge Metral e2658506f6 Merge branch 'main' into main 2024-06-04 15:39:46 -07:00
Serge Metral 6218164f79 Adding the begin frame message for later xtrace post processing. 2024-06-04 12:35:17 -07:00
545 changed files with 20864 additions and 56513 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.2'
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.2'
pod 'Filament', '~> 1.67.0'
```
## Documentation
-13
View File
@@ -7,19 +7,6 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.68.2
- Support `setPresentationTime` with the Metal backend.
## v1.68.1
## v1.68.0
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
- 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.2
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
+8 -7
View File
@@ -36,7 +36,6 @@ set(SRCS
src/Driver.cpp
src/Handle.cpp
src/HandleAllocator.cpp
src/JobQueue.cpp
src/ostream.cpp
src/noop/NoopDriver.cpp
src/noop/PlatformNoop.cpp
@@ -60,7 +59,6 @@ set(PRIVATE_HDRS
src/CompilerThreadPool.h
src/DataReshaper.h
src/DriverBase.h
src/JobQueue.h
)
# ==================================================================================================
@@ -208,8 +206,6 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanDriverFactory.h
src/vulkan/VulkanExternalImageManager.cpp
src/vulkan/VulkanExternalImageManager.h
src/vulkan/VulkanStreamedImageManager.cpp
src/vulkan/VulkanStreamedImageManager.h
src/vulkan/VulkanFboCache.cpp
src/vulkan/VulkanFboCache.h
src/vulkan/VulkanHandles.cpp
@@ -218,6 +214,14 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanMemory.h
src/vulkan/VulkanPipelineCache.cpp
src/vulkan/VulkanPipelineCache.h
src/vulkan/VulkanPipelineStateSerializer.cpp
src/vulkan/VulkanPipelineStateSerializer.h
src/vulkan/VulkanSamplerStateSerializer.h
src/vulkan/VulkanSamplerStateSerializer.cpp
src/vulkan/VulkanRenderPassStateSerializer.h
src/vulkan/VulkanRenderPassStateSerializer.cpp
src/vulkan/VulkanDescriptorSetLayoutSerializer.h
src/vulkan/VulkanDescriptorSetLayoutSerializer.cpp
src/vulkan/VulkanPipelineLayoutCache.cpp
src/vulkan/VulkanPipelineLayoutCache.h
src/vulkan/VulkanQueryManager.cpp
@@ -320,8 +324,6 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPURenderPrimitive.h
src/webgpu/WebGPURenderTarget.cpp
src/webgpu/WebGPURenderTarget.h
src/webgpu/WebGPUStagePool.cpp
src/webgpu/WebGPUStagePool.h
src/webgpu/WebGPUStrings.h
src/webgpu/WebGPUSwapChain.cpp
src/webgpu/WebGPUSwapChain.h
@@ -570,7 +572,6 @@ if (APPLE OR LINUX)
test/test_ReadPixels.cpp
test/test_BufferUpdates.cpp
test/test_Callbacks.cpp
test/test_JobQueue.cpp
test/test_MemoryMappedBuffer.cpp
test/test_MsaaSwapChain.cpp
test/test_MRT.cpp
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
#define TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
#include <backend/Handle.h>
#include <vector>
namespace filament::backend {
/**
* The type of association between a buffer object and a stream.
*/
enum class BufferObjectStreamAssociationType { TRANSFORM_MATRIX };
/**
* A descriptor for a buffer object to stream association.
*/
class UTILS_PUBLIC BufferObjectStreamDescriptor {
public:
//! creates an empty descriptor
BufferObjectStreamDescriptor() noexcept = default;
BufferObjectStreamDescriptor(const BufferObjectStreamDescriptor& rhs) = delete;
BufferObjectStreamDescriptor& operator=(const BufferObjectStreamDescriptor& rhs) = delete;
BufferObjectStreamDescriptor(BufferObjectStreamDescriptor&& rhs) = default;
BufferObjectStreamDescriptor& operator=(BufferObjectStreamDescriptor&& rhs) = default;
// --------------------------------------------------------------------------------------------
struct StreamDataDescriptor {
uint32_t offset;
Handle<HwStream> stream;
BufferObjectStreamAssociationType associationType;
};
std::vector<StreamDataDescriptor> mStreams;
};
} // namespace filament::backend
#if !defined(NDEBUG)
utils::io::ostream& operator<<(utils::io::ostream& out,
const filament::backend::BufferObjectStreamDescriptor& b);
#endif
#endif // TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
@@ -1713,8 +1713,6 @@ using FrameTimestamps = Platform::FrameTimestamps;
using CompositorTiming = Platform::CompositorTiming;
using AsynchronousMode = Platform::AsynchronousMode;
} // namespace filament::backend
template<> struct utils::EnableBitMaskOperators<filament::backend::ShaderStageFlags>
+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
@@ -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;
-247
View File
@@ -1,247 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "JobQueue.h"
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Panic.h>
namespace filament::backend {
JobQueue::JobQueue(PassKey) {}
JobQueue::JobId JobQueue::push(Job job, JobId const preIssuedJobId/* = InvalidJobId*/) {
JobId jobId = preIssuedJobId;
{
std::lock_guard<std::mutex> lock(mQueueMutex);
if (mIsStopping) {
return InvalidJobId;
}
if (jobId == InvalidJobId) {
jobId = genNextJobId();
mJobsMap[jobId] = std::move(job);
} else {
// Use the job ID previously issued by `issueJobId()`
auto it = mJobsMap.find(jobId);
if (it == mJobsMap.end()) {
// Pre-issued job does not exist, either users passed a wrong id (unlikely)
// or the job must have been canceled (likely)
return InvalidJobId;
}
FILAMENT_CHECK_PRECONDITION(!static_cast<bool>(it->second))
<< "pre-issued job has already been populated";
it->second = std::move(job);
}
mJobOrder.push(jobId);
}
// Always notify. A ThreadWorker might be waiting.
mQueueCondition.notify_one();
return jobId;
}
JobQueue::Job JobQueue::pop(bool shouldBlock) {
std::unique_lock<std::mutex> lock(mQueueMutex);
decltype(mJobsMap)::iterator it;
while (true) {
if (shouldBlock) {
// Wait only if we're in blocking mode and the queue is empty
mQueueCondition.wait(lock, [this] { return !mJobOrder.empty() || mIsStopping; });
}
if (mJobOrder.empty()) {
// When `shouldBlock` is true, this means the queue is stopping now.
// When `shouldBlock` is false, this means there's no job.
return nullptr;
}
JobId jobId = mJobOrder.front();
mJobOrder.pop();
it = mJobsMap.find(jobId);
if (it != mJobsMap.end()) {
break;
}
// If execution reaches this line, the job must have been canceled right after being added.
// Therefore, we should continue the loop and attempt to retrieve the next available job.
}
Job job = std::move(it->second);
mJobsMap.erase(it);
return job;
}
utils::FixedCapacityVector<JobQueue::Job> JobQueue::popBatch(int const maxJobsToPop) {
utils::FixedCapacityVector<Job> jobs;
if (UTILS_UNLIKELY(maxJobsToPop == 0)) {
return jobs;
}
std::lock_guard<std::mutex> lock(mQueueMutex);
if (mJobOrder.empty()) {
return jobs;
}
// Calculate jobs to take. If maxJobsToPop is negative, we take all jobs.
size_t jobsToTake = mJobOrder.size();
if (0 < maxJobsToPop && maxJobsToPop < static_cast<int>(jobsToTake)) {
jobsToTake = maxJobsToPop;
}
jobs.reserve(jobsToTake);
while (0 < jobsToTake && !mJobOrder.empty()) {
JobId jobId = mJobOrder.front();
mJobOrder.pop();
auto it = mJobsMap.find(jobId);
if (UTILS_UNLIKELY(it == mJobsMap.end())) {
// The job was probably canceled.
continue;
}
jobs.push_back(std::move(it->second));
--jobsToTake;
mJobsMap.erase(it);
}
return jobs;
}
JobQueue::JobId JobQueue::issueJobId() noexcept {
std::lock_guard<std::mutex> lock(mQueueMutex);
JobId const jobId = genNextJobId();
// Preallocate a job, which serves two main purposes. It provides a valid jobId that can be
// checked for integrity when passed to the `push` method, and it enables job cancellation for
// tasks that are yet to be pushed.
mJobsMap[jobId];
return jobId;
}
bool JobQueue::cancel(JobId const jobId) noexcept {
std::lock_guard<std::mutex> lock(mQueueMutex);
auto it = mJobsMap.find(jobId);
if (it == mJobsMap.end()) {
return false; // Job not found, must have been completed or canceled.
}
mJobsMap.erase(it);
return true;
}
void JobQueue::stop() noexcept {
{
std::lock_guard<std::mutex> lock(mQueueMutex);
mIsStopping = true;
}
mQueueCondition.notify_all(); // Wake up all waiting threads
}
JobQueue::JobId JobQueue::genNextJobId() noexcept {
// We assume this method is called within the critical section.
JobId newJobId = mNextJobId++;
// We assume the job ID won't overflow or wraps around to zero within the application's lifetime.
assert_invariant(newJobId != InvalidJobId);
return newJobId;
}
JobWorker::~JobWorker() = default;
void JobWorker::terminate() {
// This is called from workers `terminate()`, which may hinder the concurrent use of multiple
// workers. Consider removing this line and require the owner/caller to explicitly invoke it to
// enable multiple worker instances.
if (mQueue) {
mQueue->stop();
}
}
AmortizationWorker::AmortizationWorker(JobQueue::Ptr queue, PassKey)
: JobWorker(std::move(queue)) {
}
AmortizationWorker::~AmortizationWorker() = default;
void AmortizationWorker::process(int const jobCount) {
if (!mQueue || jobCount == 0) {
return;
}
if (jobCount == 1) {
// Handle single job without vector allocation.
if (auto job = mQueue->pop(false)) {
job();
}
return;
}
// Handle batch (jobCount > 1 or jobCount < 0 for "all pending jobs")
utils::FixedCapacityVector<JobQueue::Job> jobs = mQueue->popBatch(jobCount);
if (jobs.empty()) {
return;
}
for (auto& job: jobs) {
job();
}
}
void AmortizationWorker::terminate() {
JobWorker::terminate();
// Drain all pending jobs.
process(-1);
}
ThreadWorker::ThreadWorker(JobQueue::Ptr queue, Config config, PassKey)
: JobWorker(std::move(queue)), mConfig(std::move(config)) {
mThread = std::thread([this]() {
utils::JobSystem::setThreadName(mConfig.name.data());
utils::JobSystem::setThreadPriority(mConfig.priority);
if (mConfig.onBegin) {
mConfig.onBegin();
}
while (JobQueue::Job job = mQueue->pop(true)) {
job();
}
if (mConfig.onEnd) {
mConfig.onEnd();
}
});
}
ThreadWorker::~ThreadWorker() = default;
void ThreadWorker::terminate() {
JobWorker::terminate();
if (mThread.joinable()) {
mThread.join();
}
}
} // namespace utils
-275
View File
@@ -1,275 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_PRIVATE_JOBQUEUE_H
#define TNT_FILAMENT_BACKEND_PRIVATE_JOBQUEUE_H
#include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h>
#include <utils/JobSystem.h>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <memory>
#include <unordered_map>
#include <limits>
#include <queue>
namespace filament::backend {
/**
* A thread-safe producer-consumer queue with batching capabilities.
*
* This class is thread-safe. All public methods can be called from any thread.
*
* This class is stateless regarding concurrency. The *caller* decides the blocking behavior and/or
* batching when they call a 'pop' methods.
*
* A typical use case looks like this:
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* #include "private/backend/JobQueue.h"
* using namespace filament::backend;
*
* JobQueue::Ptr queue = JobQueue::create();
* JobWorker::Ptr worker = AmortizationWorker::create(queue);
* [ or JobWorker::Ptr worker = ThreadWorker::create(queue, config); ]
*
* void loop() {
* worker->process(2); // for AmortizationWorker
* }
*
* void cleanup() {
* worker->terminate();
* }
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* JobId id = queue->push([](){ ... });
* queue->cancel(id);
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* JobId preIssuedId = queue->issueJobId();
* JobId id = queue->push([](){ ... }, preIssuedId);
* assert(id == preIssuedId);
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
class JobQueue {
struct PassKey {};
public:
using Job = utils::Invocable<void()>;
using JobId = uint32_t;
using Ptr = std::shared_ptr<JobQueue>;
static constexpr JobId InvalidJobId = std::numeric_limits<JobId>::max();
/**
* Creates an instance of JobQueue. Users should call this to create one.
* @return An instance of JobQueue
*/
static Ptr create() {
return std::make_shared<JobQueue>(PassKey{});
}
explicit JobQueue(PassKey); // This can be created only via `create()`
/**
* Pushes a new job into queue.
*
* If the queue is in the process of shutting down (via a call to `stop`), this method does
* nothing (a no-op) and returns an invalid job ID.
*
* @param job The function/lambda to be executed.
* @param preIssuedJobId The previously issued job ID where this job is assigned to.
* If the value is `InvalidJobId`, a new job ID is generated internally.
* @return A new job ID. If a `preIssuedJobId` is provided, that specific ID is returned instead.
*/
JobId push(Job job, JobId preIssuedJobId = InvalidJobId);
/**
* Retrieves the next job from queue.
*
* This method returns a valid job ID if there are pending jobs in the queue, even if the queue
* is currently in the process of shutting down (via a call to `stop`).
*
* @param shouldBlock If true (typically used by ThreadWorker), waits for a job and returns it.
* If false (typically used by AmortizationWorker), tries retrieving a job. But may return an
* empty job if there's no pending job.
* @return The next job. If an empty job is returned, it has different meaning depending on the
* `shouldBlock` value:
* - true: Stop processing (shutting down).
* - false: No jobs currently in queue.
*/
Job pop(bool shouldBlock);
/**
* Retrieves a batch of next jobs from queue. Always non-blocking.
*
* This method returns a valid job ID if there are pending jobs in the queue, even if the queue
* is currently in the process of shutting down (via a call to `stop`).
*
* @param maxJobsToPop The maximum number of jobs to retrieve.
* If < 0, retrieves all pending jobs.
* @return A FixedCapacityVector<Job> containing the retrieved jobs.
* Returns an empty vector if the queue is empty.
*/
utils::FixedCapacityVector<Job> popBatch(int maxJobsToPop);
/**
* Generate a new job ID. This newly generated ID is meant to be used for the `preIssuedJobId`
* parameter of `push` method.
*
* @return A new job ID.
*/
JobId issueJobId() noexcept;
/**
* Cancels a job by its ID.
*
* @param jobId The job ID to cancel.
* @return true if the job was found and cancelled, false otherwise.
*/
bool cancel(JobId jobId) noexcept;
/**
* Signals the queue to shut down, after which no further jobs can be added using the `push`
* method. but all jobs already pushed can still be processed using `pop`.
*/
void stop() noexcept;
private:
JobQueue(const JobQueue&) = delete;
JobQueue& operator=(const JobQueue&) = delete;
JobId genNextJobId() noexcept;
std::mutex mQueueMutex;
std::condition_variable mQueueCondition;
std::unordered_map<JobId, Job> mJobsMap;
std::queue<JobId> mJobOrder;
JobId mNextJobId = 0;
bool mIsStopping = false;
};
/**
* Abstract base class for all worker types.
*/
class JobWorker {
public:
using Ptr = std::unique_ptr<JobWorker>;
virtual ~JobWorker();
/**
* Processes a batch of jobs. (For non-threaded workers)
* @param jobCount Max jobs to process (<= 0 for all).
*/
virtual void process(int jobCount) {}
/**
* Terminates the worker.
*/
virtual void terminate();
protected:
explicit JobWorker(JobQueue::Ptr queue) : mQueue(std::move(queue)) {}
JobQueue::Ptr mQueue;
private:
JobWorker(const JobWorker&) = delete;
JobWorker& operator=(const JobWorker&) = delete;
};
/**
* A non-threaded worker that consumes jobs in batches.
*/
class AmortizationWorker final : public JobWorker {
struct PassKey {};
public:
/**
* Creates an instance of AmortizationWorker. Users should call this to create one.
* @return An instance of AmortizationWorker
*/
static Ptr create(JobQueue::Ptr queue) {
return std::make_unique<AmortizationWorker>(std::move(queue), PassKey{});
}
explicit AmortizationWorker(JobQueue::Ptr queue, PassKey); // This can be created only via `create()`
~AmortizationWorker() override;
/**
* Polls the queue and executes a batch of jobs.
*
* @param jobCount The max number of jobs to process.
* 0 = do nothing.
* 1 = pop one (optimized).
* > 1 = pop batch.
* <= -1 = pop all.
*/
void process(int jobCount) override;
/**
* Signals the queue to stop and drain all pending jobs.
* This is safe to call multiple times.
*/
void terminate() override;
};
/**
* A threaded worker that consumes jobs one by one, blocking when empty.
*/
class ThreadWorker final : public JobWorker {
struct PassKey {};
public:
using Priority = utils::JobSystem::Priority;
/**
* Config settings for the worker
*/
struct Config {
std::string_view name = "";
Priority priority = Priority::NORMAL;
utils::Invocable<void()> onBegin; // Executed when the thread worker begins
utils::Invocable<void()> onEnd; // Executed when the thread worker ends
};
/**
* Creates an instance of ThreadWorker. Users should call this to create one.
* @return An instance of ThreadWorker
*/
static Ptr create(JobQueue::Ptr queue, Config config) {
return std::make_unique<ThreadWorker>(std::move(queue), std::move(config), PassKey{});
}
ThreadWorker(JobQueue::Ptr queue, Config config, PassKey); // This can be created only via `create()`
~ThreadWorker() override;
/**
* Signals the queue to stop and joins the worker thread.
* This is safe to call multiple times.
*/
void terminate() override;
private:
Config mConfig;
std::thread mThread;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_PRIVATE_JOBQUEUE_H
+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 -29
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,
@@ -312,8 +300,6 @@ void MetalDriver::execute(std::function<void(void)> const& fn) noexcept {
}
void MetalDriver::setPresentationTime(int64_t monotonic_clock_ns) {
assert_invariant(mContext->currentDrawSwapChain);
mContext->currentDrawSwapChain->setPresentationTime(monotonic_clock_ns);
}
void MetalDriver::endFrame(uint32_t frameId) {
@@ -1146,12 +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() {
@@ -1292,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))
@@ -2277,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));
}
@@ -2288,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
+8 -23
View File
@@ -141,8 +141,6 @@ public:
// FrameScheduledCallback.
void present();
void setPresentationTime(int64_t timeNs) { presentationTimeNs = timeNs; }
NSUInteger getSurfaceWidth() const;
NSUInteger getSurfaceHeight() const;
NSUInteger getSampleCount() const;
@@ -161,7 +159,7 @@ private:
bool isCaMetalLayer() const { return type == SwapChainType::CAMETALLAYER; }
bool isHeadless() const { return type == SwapChainType::HEADLESS; }
void scheduleFrameScheduledCallback(int64_t presentationTimeNs);
void scheduleFrameScheduledCallback();
void scheduleFrameCompletedCallback();
MetalAttachment acquireBaseDrawable();
@@ -194,11 +192,6 @@ private:
int64_t abandonedUntilFrame = -1;
// If zero, the next presentation should happen as soon as possible.
// Otherwise, this is the timestamp when the present should happen.
// Resets to 0 after the present.
int64_t presentationTimeNs = 0;
// These fields store a callback to notify the client that a frame is ready for presentation. If
// !frameScheduled.callback, then the Metal backend automatically calls presentDrawable when the
// frame is committed. Otherwise, the Metal backend will not automatically present the frame.
@@ -576,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
+11 -73
View File
@@ -325,19 +325,11 @@ void MetalSwapChain::present() {
if (frameCompleted.callback) {
scheduleFrameCompletedCallback();
}
const auto timeNs = presentationTimeNs;
presentationTimeNs = 0;
if (drawable) {
if (frameScheduled.callback) {
scheduleFrameScheduledCallback(timeNs);
scheduleFrameScheduledCallback();
} else {
if (presentationTimeNs) {
const CFTimeInterval timeSeconds =
(CFTimeInterval) presentationTimeNs / 1000000000.0;
[getPendingCommandBuffer(&context) presentDrawable:drawable atTime:timeSeconds];
} else {
[getPendingCommandBuffer(&context) presentDrawable:drawable];
}
[getPendingCommandBuffer(&context) presentDrawable:drawable];
}
}
}
@@ -349,22 +341,15 @@ public:
PresentDrawableData& operator=(const PresentDrawableData&) = delete;
static PresentDrawableData* create(id<CAMetalDrawable> drawable,
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags,
int64_t presentationTimeNs) {
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags) {
assert_invariant(drawableMutex);
assert_invariant(driver);
return new PresentDrawableData(drawable, drawableMutex, driver, flags, presentationTimeNs);
return new PresentDrawableData(drawable, drawableMutex, driver, flags);
}
static void maybePresentAndDestroyAsync(PresentDrawableData* that, bool shouldPresent) {
if (shouldPresent) {
if (that->mPresentationTimeNs) {
const CFTimeInterval timeSeconds =
(CFTimeInterval) that->mPresentationTimeNs / 1000000000.0;
[that->mDrawable presentAtTime:timeSeconds];
} else {
[that->mDrawable present];
}
[that->mDrawable present];
}
if (that->mFlags & SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER) {
@@ -382,12 +367,8 @@ public:
private:
PresentDrawableData(id<CAMetalDrawable> drawable, std::shared_ptr<std::mutex> drawableMutex,
MetalDriver* driver, uint64_t flags, int64_t presentationTimeNs)
: mDrawable(drawable),
mDrawableMutex(drawableMutex),
mDriver(driver),
mFlags(flags),
mPresentationTimeNs(presentationTimeNs) {}
MetalDriver* driver, uint64_t flags)
: mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver), mFlags(flags) {}
static void cleanupAndDestroy(PresentDrawableData *that) {
if (that->mDrawable) {
@@ -403,7 +384,6 @@ private:
std::shared_ptr<std::mutex> mDrawableMutex;
MetalDriver* mDriver = nullptr;
uint64_t mFlags = 0;
int64_t mPresentationTimeNs = 0;
};
void presentDrawable(bool presentFrame, void* user) {
@@ -411,7 +391,7 @@ void presentDrawable(bool presentFrame, void* user) {
PresentDrawableData::maybePresentAndDestroyAsync(presentDrawableData, presentFrame);
}
void MetalSwapChain::scheduleFrameScheduledCallback(int64_t presentationTimeNs) {
void MetalSwapChain::scheduleFrameScheduledCallback() {
if (!frameScheduled.callback) {
return;
}
@@ -420,11 +400,8 @@ void MetalSwapChain::scheduleFrameScheduledCallback(int64_t presentationTimeNs)
struct Callback {
Callback(std::shared_ptr<FrameScheduledCallback> callback, id<CAMetalDrawable> drawable,
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags,
int64_t presentationTimeNs)
: f(callback),
data(PresentDrawableData::create(drawable, drawableMutex, driver, flags,
presentationTimeNs)) {}
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags)
: f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver, flags)) {}
std::shared_ptr<FrameScheduledCallback> f;
// PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later.
std::unique_ptr<PresentDrawableData> data;
@@ -442,7 +419,7 @@ void MetalSwapChain::scheduleFrameScheduledCallback(int64_t presentationTimeNs)
uint64_t const flags = frameScheduled.flags;
ReleasablePointer* callback = [[ReleasablePointer alloc]
initWithPointer:new Callback(frameScheduled.callback, drawable, layerDrawableMutex,
context.driver, flags, presentationTimeNs)];
context.driver, flags)];
backend::CallbackHandler* handler = frameScheduled.handler;
MetalDriver* driver = context.driver;
@@ -1687,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
@@ -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;
@@ -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;
@@ -15,6 +15,7 @@
*/
#include "VulkanDescriptorSetLayoutCache.h"
#include "VulkanDescriptorSetLayoutSerializer.h"
#include "VulkanHandles.h"
@@ -100,22 +101,32 @@ uint32_t appendSamplerBindings(VkDescriptorSetLayoutBinding* toBind,
return count;
}
uint64_t computeImmutableSamplerHash(utils::FixedCapacityVector<VkSampler> const& samplers) {
uint64_t computeImmutableSamplerHash(utils::FixedCapacityVector<VkSampler> const& samplers, VulkanSamplerCache* cache) {
size_t const size = samplers.size();
if (size == 0) {
return 0;
} else if (size == 1) {
return (uint64_t) samplers[0];
}
return utils::hash::murmur3((uint32_t*) samplers.data(), samplers.size() * 2, 0);
utils::FixedCapacityVector<uint32_t> samplerHash;
if (size > 0) {
samplerHash.reserve(MAX_SAMPLER_COUNT);
for (auto sampler : samplers) {
samplerHash.push_back(cache->getKey(sampler));
}
}
if (size == 1) {
return (uint64_t) samplerHash[0];
}
return utils::hash::murmur3((uint32_t*) samplerHash.data(), samplerHash.size() * 2, 0);
}
} // anonymous namespace
VulkanDescriptorSetLayoutCache::VulkanDescriptorSetLayoutCache(VkDevice device,
fvkmemory::ResourceManager* resourceManager)
fvkmemory::ResourceManager* resourceManager, VulkanSamplerCache* cache)
: mDevice(device),
mResourceManager(resourceManager) {}
mResourceManager(resourceManager), mSamplerCache(cache) {}
VulkanDescriptorSetLayoutCache::~VulkanDescriptorSetLayoutCache() = default;
@@ -131,7 +142,7 @@ VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
utils::FixedCapacityVector<VkSampler> immutableSamplers) {
LayoutKey key = {
.bitmask = bitmasks,
.immutableSamplerHash = computeImmutableSamplerHash(immutableSamplers),
.immutableSamplerHash = computeImmutableSamplerHash(immutableSamplers, mSamplerCache),
};
if (auto itr = mVkLayouts.find(key); itr != mVkLayouts.end()) {
return itr->second;
@@ -153,9 +164,20 @@ VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
.bindingCount = count,
.pBindings = toBind,
};
LayoutKeyHashFn hashFunc;
uint32_t hashedKey = hashFunc(key);
utils::FixedCapacityVector<uint32_t> immutableSamplersHashes;
immutableSamplersHashes.reserve(MAX_SAMPLER_COUNT);
for (auto sampler: immutableSamplers) {
immutableSamplersHashes.push_back(mSamplerCache->getKey(sampler));
}
VulkanDescriptorSetLayoutSerializer setSer(dlinfo, immutableSamplersHashes, hashedKey);
VkDescriptorSetLayout vklayout;
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &vklayout);
mVkLayouts[key] = vklayout;
mLayoutToKey[vklayout] = hashedKey;
return vklayout;
}
@@ -19,6 +19,8 @@
#include "VulkanHandles.h"
#include "VulkanSamplerCache.h"
#include "vulkan/memory/ResourcePointer.h"
#include <backend/DriverEnums.h>
@@ -35,7 +37,8 @@ namespace filament::backend {
class VulkanDescriptorSetLayoutCache {
public:
VulkanDescriptorSetLayoutCache(VkDevice device, fvkmemory::ResourceManager* resourceManager);
VulkanDescriptorSetLayoutCache(VkDevice device, fvkmemory::ResourceManager* resourceManager,
VulkanSamplerCache* cache);
~VulkanDescriptorSetLayoutCache();
void terminate() noexcept;
@@ -48,10 +51,19 @@ public:
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks,
fvkutils::SamplerBitmask externalSamplers,
utils::FixedCapacityVector<VkSampler> immutableSamplers = {});
uint32_t getKey(VkDescriptorSetLayout layout) {
uint32_t key = 0;
auto iter = mLayoutToKey.find(layout);
if (iter != mLayoutToKey.end()) {
key = iter->second;
}
return key;
}
private:
VkDevice mDevice;
fvkmemory::ResourceManager* mResourceManager;
VulkanSamplerCache* mSamplerCache;
struct LayoutKey {
// this describes the layout using bitset.
@@ -69,6 +81,7 @@ private:
};
tsl::robin_map<LayoutKey, VkDescriptorSetLayout, LayoutKeyHashFn, LayoutKeyEqual> mVkLayouts;
std::map<VkDescriptorSetLayout, uint32_t> mLayoutToKey;
};
} // namespace filament::backend
@@ -0,0 +1,87 @@
#include "VulkanDescriptorSetLayoutSerializer.h"
#include <fstream>
#include <sstream>
#include <utils/Hash.h>
namespace filament::backend {
VulkanDescriptorSetLayoutSerializer::VulkanDescriptorSetLayoutSerializer(
const VkDescriptorSetLayoutCreateInfo& info,
utils::FixedCapacityVector<uint32_t> immutableSamplers, uint32_t hash) {
std::stringstream filename;
filename << "descriptor_set_layout_" << hash << ".json";
std::ofstream file(filename.str());
if (file.is_open()) {
std::stringstream buffer;
buffer << "[" << std::endl;
for (uint32_t i = 0; i < info.bindingCount; ++i) {
buffer << "{" << std::endl;
buffer << "\"binding\":" << info.pBindings[i].binding << "," << std::endl;
buffer << "\"descriptor_type\":" << info.pBindings[i].descriptorType << "," << std::endl;
buffer << "\"stage_flags\":" << info.pBindings[i].stageFlags;
if (immutableSamplers.size()) {
buffer << "," << std::endl;
buffer << "\"immutable_samplers\":[" << std::endl;
for (uint32_t i = 0; i < immutableSamplers.size(); ++i) {
buffer << immutableSamplers[i];
if (i < immutableSamplers.size() - 1) {
buffer << ",";
}
}
buffer << "]";
}
buffer << std::endl;
buffer << "}";
if (i < info.bindingCount - 1) {
buffer << ",";
}
buffer << std::endl;
}
buffer << "]" << std::endl;
file << buffer.str();
}
file.close();
}
VulkanPipelineLayoutSerializer::VulkanPipelineLayoutSerializer(
const VkPipelineLayoutCreateInfo& info, VulkanDescriptorSetLayoutCache* cache,
uint32_t hash) {
std::stringstream filename;
filename << "pipeline_layout_" << hash << ".json";
std::ofstream file(filename.str());
if (file.is_open()) {
std::stringstream buffer;
buffer << "{" << std::endl;
buffer << "\"flags\":" << info.flags << "," << std::endl;
buffer << "\"layouts\":[" << std::endl;
for (uint32_t i = 0; i < info.setLayoutCount; ++i) {
buffer << cache->getKey(info.pSetLayouts[i]);
if (i < info.setLayoutCount - 1) {
buffer << ",";
}
}
buffer << "]," << std::endl;
buffer << "\"push_constants\":[" << std::endl;
for (uint32_t i = 0; i < info.pushConstantRangeCount; ++i) {
buffer << "{" << std::endl;
buffer << "\"stage_flags\":" << info.pPushConstantRanges[i].stageFlags << "," << std::endl;
buffer << "\"offset\":" << info.pPushConstantRanges[i].offset << ","
<< std::endl;
buffer << "\"size\":" << info.pPushConstantRanges[i].size
<< std::endl;
buffer << "}" << std::endl;
if (i < info.pushConstantRangeCount - 1) {
buffer << ",";
}
}
buffer << "]" << std::endl;
buffer << "}" << std::endl;
file << buffer.str();
}
file.close();
}
} // namespace filament::backend
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2022 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_VULKANDESCRIPTORSETLAYOUTSERIALIZER_H
#define TNT_FILAMENT_BACKEND_VULKANDESCRIPTORSETLAYOUTSERIALIZER_H
#include "VulkanPipelineLayoutCache.h"
#include "VulkanYcbcrConversionCache.h"
#include <backend/DriverEnums.h>
#include <bluevk/BlueVK.h>
#include <iostream>
#include <sstream>
#include <vector>
namespace filament::backend {
class VulkanDescriptorSetLayoutSerializer {
public:
VulkanDescriptorSetLayoutSerializer(VulkanDescriptorSetLayoutSerializer const&) = delete;
VulkanDescriptorSetLayoutSerializer& operator=(
VulkanDescriptorSetLayoutSerializer const&) = delete;
VulkanDescriptorSetLayoutSerializer(const VkDescriptorSetLayoutCreateInfo& info,
utils::FixedCapacityVector<uint32_t> immutableSamplers,
uint32_t hash);
};
class VulkanPipelineLayoutSerializer {
public:
VulkanPipelineLayoutSerializer(VulkanPipelineLayoutSerializer const&) = delete;
VulkanPipelineLayoutSerializer& operator=(VulkanPipelineLayoutSerializer const&) = delete;
VulkanPipelineLayoutSerializer(const VkPipelineLayoutCreateInfo& layout,
VulkanDescriptorSetLayoutCache* cache, uint32_t hash);
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_VULKANDESCRIPTORSETLAYOUTSERIALIZER_H
+28 -115
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>
@@ -231,21 +230,20 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(),
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()),
mYcbcrConversionCache(mPlatform->getDevice()),
mSamplerCache(mPlatform->getDevice()),
mSamplerCache(mPlatform->getDevice(), &mYcbcrConversionCache),
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
mReadPixels(mPlatform->getDevice()),
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager),
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager, &mSamplerCache),
mPipelineLayoutCache(mPlatform->getDevice(), &mDescriptorSetLayoutCache),
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) {
@@ -422,14 +420,12 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) {
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
// if frameId is 0, it means we're not associated to a particular frame, which is the case
// for standalone views. And in this case we skip the frame info timing collection.
if (frameId && mCurrentSwapChain) { // This should be guaranteed
if (mCurrentSwapChain) { // This should be guaranteed
mPlatform->setPresentFrameId(mCurrentSwapChain->swapChain, frameId);
}
// Check if any command have finished and reset all its used resources. The resources
// won't be destroyed but their reference count will be decreased if the command is already
// wont be destroyed but their reference count will decreased if the command is already
// completed.
//
// This will let us check if any VulkanBuffer is currently in flight or not.
@@ -483,9 +479,6 @@ void VulkanDriver::updateDescriptorSetTexture(
if (UTILS_UNLIKELY(mExternalImageManager.isExternallySampledTexture(texture))) {
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
mAppState.hasBoundExternalImages = true;
} else if (bool(texture->getStream())) {
mStreamedImageManager.bindStreamedTexture(set, binding, texture, params);
mAppState.hasBoundExternalImages = true;
} else {
VulkanSamplerCache::Params cacheParams = {
.sampler = params,
@@ -493,7 +486,6 @@ void VulkanDriver::updateDescriptorSetTexture(
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
mExternalImageManager.clearTextureBinding(set, binding);
mStreamedImageManager.unbindStreamedTexture(set, binding);
}
}
@@ -505,7 +497,10 @@ void VulkanDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_
assert_invariant(any(mmb->access & MapBufferAccessFlags::WRITE_BIT));
assert_invariant(offset + data.size <= mmb->size);
// On UMA systems, this is a memcpy into the memory-mapped buffer, otherwise use a staging buffer.
// TODO: this isa zero-effort implementation of copyToMemoryMappedBuffer(), where we just
// call updateBufferObject(). This could be a fallback implementation for when
// shared memory is not available.
// On UMA systems, this should just be a memcpy into the memory-mapped buffer.
updateBufferObject(mmb->boh, std::move(data), mmb->offset + offset);
}
@@ -926,6 +921,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();
@@ -1113,12 +1116,6 @@ void VulkanDriver::destroySwapChain(Handle<HwSwapChain> sch) {
}
void VulkanDriver::destroyStream(Handle<HwStream> sh) {
FVK_SYSTRACE_SCOPE();
if (!sh) {
return;
}
auto stream = resource_ptr<VulkanStream>::cast(&mResourceManager, sh);
stream.dec();
}
void VulkanDriver::destroySync(Handle<HwSync> sh) {
@@ -1156,38 +1153,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 +1172,6 @@ int64_t VulkanDriver::getStreamTimestamp(Handle<HwStream> sh) {
}
void VulkanDriver::updateStreams(CommandStream* driver) {
FVK_SYSTRACE_SCOPE();
if (UTILS_UNLIKELY(!mStreamsWithPendingAcquiredImage.empty())) {
for (auto& stream: mStreamsWithPendingAcquiredImage) {
if (stream->previousNeedsRelease()) {
scheduleRelease(stream->takePrevious());
}
// This executes on the backend thread (updateStreams is synchonous which means it
// executes on the user thread) Note: stream is captured by copy which is fine, this is
// a copy of a resource_ptr<VulkanStream>. We only need it find the associated stream
// inside the mStreamedImageManager texture bindings
driver->queueCommand([this, stream, s = stream.get(),
image = stream->getAcquired().image]() {
auto texture = s->getTexture(image);
if (!texture) {
auto externalImage = fvkutils::createExternalImageFromRaw(mPlatform, image, false);
auto metadata = mPlatform->extractExternalImageMetadata(externalImage);
auto imgData = mPlatform->createVkImageFromExternal(externalImage);
assert_invariant(imgData.internal.valid() || imgData.external.valid());
VkFormat vkformat = metadata.format;
VkImage vkimage = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
if (imgData.internal.valid()) {
metadata.externalFormat = 0;
vkimage = imgData.internal.image;
memory = imgData.internal.memory;
} else {
vkformat = VK_FORMAT_UNDEFINED;
vkimage = imgData.external.image;
memory = imgData.external.memory;
}
VkSamplerYcbcrConversion const conversion =
mExternalImageManager.getVkSamplerYcbcrConversion(metadata);
auto newTexture = resource_ptr<VulkanTexture>::construct(&mResourceManager,
mContext, mPlatform->getDevice(), mAllocator, &mResourceManager,
&mCommands, vkimage, memory, vkformat, conversion, metadata.samples,
metadata.width, metadata.height, metadata.layers,
metadata.filamentUsage, mStagePool);
auto& commands = mCommands.get();
// Unlike uploaded textures or swapchains, we need to explicit transition this
// texture into the read layout.
newTexture->transitionLayout(&commands, newTexture->getPrimaryViewRange(),
VulkanLayout::FRAG_READ);
if (imgData.external.valid()) {
mExternalImageManager.addExternallySampledTexture(newTexture,
externalImage);
// Cache the AHB backed image. Acquires the image here.
s->pushImage(image, newTexture);
}
texture = newTexture;
}
mStreamedImageManager.onStreamAcquireImage(texture, stream);
});
}
mStreamsWithPendingAcquiredImage.clear();
}
}
void VulkanDriver::destroyFence(Handle<HwFence> fh) {
@@ -1554,13 +1467,17 @@ void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor
scheduleDestroy(std::move(p));
}
void VulkanDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh,
BufferObjectStreamDescriptor&& streams) {
// Noop
}
void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& bd,
uint32_t byteOffset) {
VulkanCommandBuffer& commands = mCommands.get();
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
commands.acquire(bo);
bo->loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
scheduleDestroy(std::move(bd));
@@ -1628,12 +1545,6 @@ TimerQueryResult VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint
}
void VulkanDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
FVK_SYSTRACE_SCOPE();
auto t = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
assert_invariant(t);
auto s = resource_ptr<VulkanStream>::cast(&mResourceManager, sh);
assert_invariant(s);
t->setStream(s);
}
void VulkanDriver::generateMipmaps(Handle<HwTexture> th) {
@@ -1755,7 +1666,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
rpkey.subpassMask = uint8_t(params.subpassMask);
VkRenderPass renderPass = mFramebufferCache.getRenderPass(rpkey);
mPipelineCache.bindRenderPass(renderPass, 0);
mPipelineCache.bindRenderPass(renderPass, mFramebufferCache.getRenderPassKey(renderPass), 0);
// Create the VkFramebuffer or fetch it from cache.
VulkanFboCache::FboKey fbkey = rt->getFboKey();
@@ -1874,6 +1785,7 @@ void VulkanDriver::nextSubpass(int) {
VK_SUBPASS_CONTENTS_INLINE);
mPipelineCache.bindRenderPass(mCurrentRenderPass.renderPass,
mFramebufferCache.getRenderPassKey(mCurrentRenderPass.renderPass),
++mCurrentRenderPass.currentSubpass);
if (mCurrentRenderPass.params.subpassMask & 0x1) {
@@ -2249,7 +2161,8 @@ void VulkanDriver::bindPipelineImpl(PipelineState const& pipelineState,
mPipelineState.pipelineLayout = pipelineLayout;
mPipelineState.descriptorSetMask = descriptorSetMask;
mPipelineCache.bindLayout(pipelineLayout);
auto key = mPipelineLayoutCache.getKey(pipelineLayout);
mPipelineCache.bindLayout(pipelineLayout, key);
mPipelineCache.bindPipeline(mCurrentRenderPass.commandBuffer);
}
+8 -8
View File
@@ -34,7 +34,6 @@
#include "vulkan/VulkanDescriptorSetCache.h"
#include "vulkan/VulkanDescriptorSetLayoutCache.h"
#include "vulkan/VulkanExternalImageManager.h"
#include "vulkan/VulkanStreamedImageManager.h"
#include "vulkan/VulkanPipelineLayoutCache.h"
#include "vulkan/memory/ResourceManager.h"
#include "vulkan/memory/ResourcePointer.h"
@@ -49,6 +48,10 @@
#include <utils/Allocator.h>
#include <utils/compiler.h>
#if defined(__ANDROID__)
#include "AndroidNativeWindow.h"
#endif
namespace filament::backend {
class VulkanPlatform;
@@ -145,7 +148,6 @@ private:
VulkanSemaphoreManager mSemaphoreManager;
VulkanCommands mCommands;
VulkanPipelineLayoutCache mPipelineLayoutCache;
VulkanPipelineCache mPipelineCache;
VulkanStagePool mStagePool;
VulkanBufferCache mBufferCache;
@@ -155,11 +157,10 @@ private:
VulkanBlitter mBlitter;
VulkanReadPixels mReadPixels;
VulkanDescriptorSetLayoutCache mDescriptorSetLayoutCache;
VulkanPipelineLayoutCache mPipelineLayoutCache;
VulkanDescriptorSetCache mDescriptorSetCache;
VulkanQueryManager mQueryManager;
VulkanExternalImageManager mExternalImageManager;
VulkanStreamedImageManager mStreamedImageManager;
// This maps a VulkanSwapchain to a native swapchain. VulkanSwapchain should have a copy of the
// Platform::Swapchain pointer, but queryFrameTimestamps() and queryCompositorTiming() are
@@ -205,10 +206,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:
@@ -15,6 +15,7 @@
*/
#include "VulkanFboCache.h"
#include "VulkanRenderPassStateSerializer.h"
#include "VulkanConstants.h"
#include "vulkan/utils/Image.h"
@@ -69,6 +70,15 @@ VulkanFboCache::~VulkanFboCache() {
<< "Please explicitly call terminate() while the VkDevice is still alive.";
}
uint32_t VulkanFboCache::getRenderPassKey(VkRenderPass pass) noexcept {
uint32_t key = 0;
auto iter = mRenderPassToKey.find(pass);
if (iter != mRenderPassToKey.end()) {
key = iter->second;
}
return key;
}
VkFramebuffer VulkanFboCache::getFramebuffer(FboKey const& config) noexcept {
FboMap::iterator iter = mFramebufferCache.find(config);
if (UTILS_LIKELY(iter != mFramebufferCache.end() && iter->second.handle != VK_NULL_HANDLE)) {
@@ -117,6 +127,7 @@ VkFramebuffer VulkanFboCache::getFramebuffer(FboKey const& config) noexcept {
VkResult error = vkCreateFramebuffer(mDevice, &info, VKALLOC, &framebuffer);
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create framebuffer."
<< " error=" << static_cast<int32_t>(error);
mFramebufferCache[config] = {framebuffer, mCurrentTime};
return framebuffer;
}
@@ -326,6 +337,12 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
VkResult error = vkCreateRenderPass(mDevice, &renderPassInfo, VKALLOC, &renderPass);
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create render pass."
<< " error=" << error;
RenderPassHash hashFunc;
uint32_t key = hashFunc(config);
VulkanRenderPassStateSerializer passSer(renderPassInfo, key);
mRenderPassToKey[renderPass] = key;
mRenderPassCache[config] = {renderPass, mCurrentTime};
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
@@ -24,6 +24,7 @@
#include <backend/TargetBufferInfo.h>
#include <tsl/robin_map.h>
#include <map>
namespace filament::backend {
@@ -97,6 +98,8 @@ public:
explicit VulkanFboCache(VkDevice device);
~VulkanFboCache();
uint32_t getRenderPassKey(VkRenderPass pass) noexcept;
// Retrieves or creates a VkFramebuffer handle.
VkFramebuffer getFramebuffer(FboKey const& config) noexcept;
@@ -118,6 +121,7 @@ private:
FboMap mFramebufferCache;
tsl::robin_map<RenderPassKey, RenderPassVal, RenderPassHash, RenderPassEq> mRenderPassCache;
std::map<VkRenderPass, uint32_t> mRenderPassToKey;
tsl::robin_map<VkRenderPass, uint32_t> mRenderPassRefCount;
uint32_t mCurrentTime = 0;
};
+26 -62
View File
@@ -33,6 +33,7 @@
#include <utils/compiler.h> // UTILS_FALLTHROUGH
#include <utils/Panic.h> // ASSERT_POSTCONDITION
#include <utils/CString.h>
#include <fstream>
using namespace bluevk;
@@ -40,11 +41,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:
@@ -184,6 +180,23 @@ VulkanAttachment createSwapchainAttachment(const fvkmemory::resource_ptr<VulkanT
};
}
static inline uint64_t serializeShaderModule(utils::CString progName, VkShaderModuleCreateInfo info,
VkShaderStageFlagBits stage) {
std::stringstream filename;
filename << progName.c_str() << "_" << stage << "_";
constexpr uint32_t wordReadCount = 8;
uint64_t key = utils::hash::murmur3(info.pCode, wordReadCount, 0);
key |= uint64_t(utils::hash::murmur3(info.pCode + (wordReadCount / 2), wordReadCount, 0)) << 32;
filename << key << ".bin";
std::ofstream file(filename.str());
if (file.is_open()) {
file.write(reinterpret_cast<const char*>(info.pCode), info.codeSize);
}
file.close();
return key;
}
} // anonymous namespace
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout&& layout,
@@ -197,70 +210,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) {
@@ -340,6 +298,12 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
.codeSize = dataSize,
.pCode = data,
};
if (i == 0) {
mVertexShaderHash = serializeShaderModule(name, moduleInfo, VK_SHADER_STAGE_VERTEX_BIT);
} else if (i == 1) {
mFragmentShaderHash =
serializeShaderModule(name, moduleInfo, VK_SHADER_STAGE_FRAGMENT_BIT);
}
VkResult result = vkCreateShaderModule(mDevice, &moduleInfo, VKALLOC, &module);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "Unable to create shader module."
+41 -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 {
@@ -68,6 +65,8 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
using DescriptorSetLayoutArray = std::array<VkDescriptorSetLayout,
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
using DescriptorSetLayoutHashArray = std::array<uint64_t,
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
// The bitmask representation of a set layout.
struct Bitmask {
@@ -162,14 +161,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 +213,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;
};
@@ -275,6 +282,10 @@ struct VulkanProgram : public HwProgram, fvkmemory::Resource {
mInfo->pushConstantDescription.write(cmdbuf, layout, stage, index, value);
}
inline uint64_t getVertexShaderHash() { return mVertexShaderHash;
}
inline uint64_t getFragmentShaderHash() { return mFragmentShaderHash; }
// TODO: handle compute shaders.
// The expected order of shaders - from frontend to backend - is vertex, fragment, compute.
static constexpr uint8_t const MAX_SHADER_MODULES = 2;
@@ -290,6 +301,8 @@ private:
};
PipelineInfo* mInfo;
uint64_t mVertexShaderHash;
uint64_t mFragmentShaderHash;
VkDevice mDevice = VK_NULL_HANDLE;
};
@@ -15,6 +15,7 @@
*/
#include "VulkanPipelineCache.h"
#include "VulkanPipelineStateSerializer.h"
#include <utils/Log.h>
#include <utils/Panic.h>
@@ -34,49 +35,17 @@ 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,
};
bluevk::vkCreatePipelineCache(mDevice, &createInfo, VKALLOC, &mPipelineCache);
}
void VulkanPipelineCache::bindLayout(VkPipelineLayout layout) noexcept {
void VulkanPipelineCache::bindLayout(VkPipelineLayout layout, uint32_t key) noexcept {
mPipelineRequirements.layout = layout;
mPipelineLayoutKey = key;
}
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::getOrCreatePipeline() noexcept {
@@ -110,6 +79,7 @@ void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) {
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept {
assert_invariant(mPipelineRequirements.shaders[0] && "Vertex shader is not bound.");
assert_invariant(mPipelineRequirements.layout && "No pipeline layout specified");
VulkanPipelineStateSerializer serializer(mProgramName);
VkPipelineShaderStageCreateInfo shaderStages[SHADER_MODULE_COUNT];
shaderStages[0] = {
@@ -122,6 +92,12 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
shaderStages[1].module = mPipelineRequirements.shaders[1];
VulkanPipelineStateSerializer::ShaderStageInfo vtx = { shaderStages[0].stage, mVertexShaderHash,
shaderStages[0].pName };
VulkanPipelineStateSerializer::ShaderStageInfo frg = { shaderStages[1].stage, mFragmentShaderHash,
shaderStages[1].pName };
serializer << vtx << frg;
bool const hasFragmentShader = shaderStages[1].module != VK_NULL_HANDLE;
VkPipelineColorBlendAttachmentState colorBlendAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT];
@@ -140,9 +116,11 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
for (uint32_t i = 0; i < VERTEX_ATTRIBUTE_COUNT; i++) {
if (mPipelineRequirements.vertexAttributes[i].format > 0) {
vertexAttributes[numVertexAttribs++] = mPipelineRequirements.vertexAttributes[i];
serializer << mPipelineRequirements.vertexAttributes[i];
}
if (mPipelineRequirements.vertexBuffers[i].stride > 0) {
vertexBuffers[numVertexBuffers++] = mPipelineRequirements.vertexBuffers[i];
serializer << mPipelineRequirements.vertexBuffers[i];
}
}
VkPipelineVertexInputStateCreateInfo vertexInputState = {
@@ -156,11 +134,15 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.topology = (VkPrimitiveTopology) mPipelineRequirements.topology,
};
serializer << inputAssemblyState;
VkPipelineViewportStateCreateInfo viewportState = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.viewportCount = 1,
.scissorCount = 1,
};
serializer << viewportState;
VkDynamicState dynamicStateEnables[] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
@@ -170,6 +152,8 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
.dynamicStateCount = 2,
.pDynamicStates = dynamicStateEnables,
};
serializer << dynamicState;
auto const& raster = mPipelineRequirements.rasterState;
VkPipelineRasterizationStateCreateInfo vkRaster = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
@@ -183,6 +167,8 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
.depthBiasSlopeFactor = raster.depthBiasSlopeFactor,
.lineWidth = 1.0f,
};
serializer << vkRaster;
VkPipelineMultisampleStateCreateInfo vkMs = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.rasterizationSamples = (VkSampleCountFlagBits) raster.rasterizationSamples,
@@ -191,12 +177,11 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
.alphaToCoverageEnable = raster.alphaToCoverageEnable,
.alphaToOneEnable = VK_FALSE,
};
bool const enableDepthTest =
raster.depthCompareOp != SamplerCompareFunc::A ||
raster.depthWriteEnable;
serializer << vkMs;
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,
@@ -213,6 +198,12 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
.writeMask = 0u,
.reference = 0u,
};
serializer << vkDs;
//layout
serializer.setPipelineLayoutKey(mPipelineLayoutKey);
// renderPass
serializer.setRenderPassKey(mRenderPassKey);
VkGraphicsPipelineCreateInfo pipelineCreateInfo = {
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
@@ -251,52 +242,36 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
colorBlendAttachments[i] = colorBlendAttachments[0];
}
}
serializer << colorBlendState;
#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;
return nullptr;
}
PipelineHashFn hashFunc;
serializer.setID(hashFunc(mPipelineRequirements));
return &mPipelines.emplace(mPipelineRequirements, cacheEntry).first.value();
}
void VulkanPipelineCache::bindProgram(fvkmemory::resource_ptr<VulkanProgram> program) noexcept {
mPipelineRequirements.shaders[0] = program->getVertexShader();
mPipelineRequirements.shaders[1] = program->getFragmentShader();
mProgramName = program->name;
mVertexShaderHash = program->getVertexShaderHash();
mFragmentShaderHash= program->getFragmentShaderHash();
// If this is a debug build, validate the current shader.
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
@@ -311,9 +286,10 @@ void VulkanPipelineCache::bindRasterState(RasterState const& rasterState) noexce
mPipelineRequirements.rasterState = rasterState;
}
void VulkanPipelineCache::bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept {
void VulkanPipelineCache::bindRenderPass(VkRenderPass renderPass, uint32_t passKey, int subpassIndex) noexcept {
mPipelineRequirements.renderPass = renderPass;
mPipelineRequirements.subpassIndex = subpassIndex;
mRenderPassKey = passKey;
}
void VulkanPipelineCache::bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept {
@@ -86,9 +86,9 @@ 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;
void bindLayout(VkPipelineLayout layout, uint32_t key) noexcept;
// Creates a new pipeline if necessary and binds it using vkCmdBindPipeline.
void bindPipeline(VulkanCommandBuffer* commands);
@@ -96,7 +96,7 @@ public:
// Each of the following methods are fast and do not make Vulkan calls.
void bindProgram(fvkmemory::resource_ptr<VulkanProgram> program) noexcept;
void bindRasterState(RasterState const& rasterState) noexcept;
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept;
void bindRenderPass(VkRenderPass renderPass, uint32_t passKey, int subpassIndex) noexcept;
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
void bindVertexArray(VkVertexInputAttributeDescription const* attribDesc,
VkVertexInputBindingDescription const* bufferDesc, uint8_t count);
@@ -207,11 +207,14 @@ private:
// Current requirements for the pipeline layout, pipeline, and descriptor sets.
PipelineKey mPipelineRequirements = {};
utils::CString mProgramName;
uint64_t mVertexShaderHash;
uint64_t mFragmentShaderHash;
uint32_t mPipelineLayoutKey;
uint32_t mRenderPassKey;
// Current bindings for the pipeline and descriptor sets.
PipelineKey mBoundPipeline = {};
[[maybe_unused]] VulkanContext const& mContext;
};
} // namespace filament::backend
@@ -15,6 +15,7 @@
*/
#include "VulkanPipelineLayoutCache.h"
#include "VulkanDescriptorSetLayoutSerializer.h"
namespace filament::backend {
@@ -23,7 +24,11 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
fvkmemory::resource_ptr<VulkanProgram> program) {
PipelineLayoutKey key = {};
uint8_t descSetLayoutCount = 0;
key.descSetLayouts = descriptorSetLayouts;
DescriptorSetLayoutHashArray hashArray;
for (uint32_t i = 0; i < descriptorSetLayouts.size(); ++i) {
hashArray[i] = mDescriptorSetCache->getKey(descriptorSetLayouts[i]);
}
key.descSetLayouts = hashArray;
for (auto layoutHandle: descriptorSetLayouts) {
if (layoutHandle == VK_NULL_HANDLE) {
break;
@@ -62,11 +67,14 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.pNext = nullptr,
.setLayoutCount = (uint32_t) descSetLayoutCount,
.pSetLayouts = key.descSetLayouts.data(),
.pSetLayouts = descriptorSetLayouts.data(),
.pushConstantRangeCount = pushConstantRangeCount,
.pPushConstantRanges = pushConstantRanges,
};
PipelineLayoutKeyHashFn hashFunc;
uint32_t hashedKey = hashFunc(key);
VulkanPipelineLayoutSerializer pipelineSer(info, mDescriptorSetCache, hashedKey);
VkPipelineLayout layout;
vkCreatePipelineLayout(mDevice, &info, VKALLOC, &layout);
@@ -74,6 +82,8 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
.handle = layout,
.lastUsed = mTimestamp++,
};
mPipelineToKey[layout] = hashedKey;
return layout;
}
@@ -18,6 +18,7 @@
#define TNT_FILAMENT_BACKEND_VULKANPIPELINELAYOUTCACHE_H
#include "VulkanHandles.h"
#include "VulkanDescriptorSetLayoutCache.h"
#include <bluevk/BlueVK.h>
@@ -30,10 +31,11 @@ namespace filament::backend {
class VulkanPipelineLayoutCache {
public:
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
using DescriptorSetLayoutHashArray = VulkanDescriptorSetLayout::DescriptorSetLayoutHashArray;
VulkanPipelineLayoutCache(VkDevice device)
VulkanPipelineLayoutCache(VkDevice device, VulkanDescriptorSetLayoutCache* cache)
: mDevice(device),
mTimestamp(0) {}
mTimestamp(0), mDescriptorSetCache(cache) {}
void terminate() noexcept;
@@ -45,7 +47,7 @@ public:
};
struct PipelineLayoutKey {
DescriptorSetLayoutArray descSetLayouts = {}; // 8 * 4
DescriptorSetLayoutHashArray descSetLayouts = {}; // 8 * 4
PushConstantKey pushConstant[Program::SHADER_TYPE_COUNT] = {}; // 2 * 3
uint16_t padding = 0;
};
@@ -58,6 +60,14 @@ public:
// are described in the program.
VkPipelineLayout getLayout(DescriptorSetLayoutArray const& descriptorSetLayouts,
fvkmemory::resource_ptr<VulkanProgram> program);
uint32_t getKey(VkPipelineLayout layout) {
uint32_t key = 0;
auto iter = mPipelineToKey.find(layout);
if (iter != mPipelineToKey.end()) {
key = iter->second;
}
return key;
}
private:
using Timestamp = uint64_t;
@@ -79,6 +89,8 @@ private:
VkDevice mDevice;
Timestamp mTimestamp;
PipelineLayoutMap mPipelineLayouts;
std::map<VkPipelineLayout, uint32_t> mPipelineToKey;
VulkanDescriptorSetLayoutCache* mDescriptorSetCache;
};
} // filament::backend
@@ -0,0 +1,251 @@
/*
* Copyright (C) 2022 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 <fstream>
#include "VulkanPipelineStateSerializer.h"
#include <utils/Log.h>
#include <utils/Panic.h>
#include "VulkanConstants.h"
#include "VulkanHandles.h"
#if defined(__clang__)
// Vulkan functions often immediately dereference pointers, so it's fine to pass in a pointer
// to a stack-allocated variable.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreturn-stack-address"
#endif
using namespace bluevk;
namespace filament::backend {
VulkanPipelineStateSerializer::VulkanPipelineStateSerializer(const utils::CString& name) {
mBuffer << "{" << std::endl;
mBuffer << "\"program_name\":" << "\"" << name.c_str() << "\"" << "," << std::endl;
}
VulkanPipelineStateSerializer::~VulkanPipelineStateSerializer() {
//Shader Modules
mBuffer << "\"shader_modules\":[" << std::endl;
for (uint32_t i = 0; i < mShaderStages.size(); ++i) {
mBuffer << "{" << std::endl;
mBuffer << "\"stage\":" << mShaderStages[i].stage << "," << std::endl;
mBuffer << "\"key\":" << mShaderStages[i].module << "," << std::endl;
mBuffer << "\"name\":" << "\"" << mShaderStages[i].mName << "\"" << std::endl;
mBuffer << "}";
if (i < mShaderStages.size() - 1) {
mBuffer << ",";
}
mBuffer << std::endl;
}
mBuffer << "]," << std::endl;
//Vertex Attribute
mBuffer << "\"vertex_attributes\":[" << std::endl;
for (uint32_t i = 0; i < mVtxAttribs.size(); ++i) {
mBuffer << "{" << std::endl;
mBuffer << "\"location\":" << mVtxAttribs[i].location << "," << std::endl;
mBuffer << "\"binding\":" << mVtxAttribs[i].binding << "," << std::endl;
mBuffer << "\"format\":" << mVtxAttribs[i].format << "," << std::endl;
mBuffer << "\"offset\":" << mVtxAttribs[i].offset << std::endl;
mBuffer << "}";
if (i < mVtxAttribs.size() - 1) {
mBuffer << ",";
}
mBuffer << std::endl;
}
mBuffer << "]," << std::endl;
// Vertex Bindings
mBuffer << "\"vertex_bindings\":[" << std::endl;
for (uint32_t i = 0; i < mVtxBindings.size(); ++i) {
mBuffer << "{" << std::endl;
mBuffer << "\"binding\":" << mVtxBindings[i].binding << "," << std::endl;
mBuffer << "\"stride\":" << mVtxBindings[i].stride << "," << std::endl;
mBuffer << "\"input_rate\":" << mVtxBindings[i].inputRate << std::endl;
mBuffer << "}";
if (i < mVtxBindings.size() - 1) {
mBuffer << ",";
}
mBuffer << std::endl;
}
mBuffer << "]" << std::endl;
mBuffer << "}" << std::endl;
std::ofstream file(mFileName);
if (file.is_open()) {
file << mBuffer.str();
}
file.close();
}
void VulkanPipelineStateSerializer::setPipelineLayoutKey(uint32_t pipelineLayoutKey) {
mBuffer << "\"layout\":" << pipelineLayoutKey << "," << std::endl;
}
void VulkanPipelineStateSerializer::setRenderPassKey(uint32_t renderPassKey) {
mBuffer << "\"render_pass\":" << renderPassKey << "," << std::endl;
}
void VulkanPipelineStateSerializer::setID(uint32_t key) {
std::stringstream temp;
temp << "pipeline_" << key << ".json";
temp >> mFileName;
}
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
const VkPipelineInputAssemblyStateCreateInfo& inputAsm) {
mBuffer << "\"topology\":" << inputAsm.topology << "," << std::endl;
return *this;
}
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
const VkPipelineViewportStateCreateInfo& viewport) {
mBuffer << "\"viewport_count\":" << viewport.viewportCount << "," << std::endl;
mBuffer << "\"scissor_count\":" << viewport.scissorCount << "," << std::endl;
return *this;
}
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
const VkPipelineRasterizationStateCreateInfo& rasterState) {
mBuffer << "\"raster_states\":{" << std::endl;
mBuffer << "\"depth_clamp_enable\":" << rasterState.depthClampEnable << "," << std::endl;
mBuffer << "\"polygon_mode\":" << rasterState.polygonMode << "," << std::endl;
mBuffer << "\"cull_mode\":" << rasterState.cullMode << "," << std::endl;
mBuffer << "\"front_face\":" << rasterState.frontFace << "," << std::endl;
mBuffer << "\"depth_bias_enable\":" << rasterState.depthBiasEnable << "," << std::endl;
mBuffer << "\"depth_bias_constant_factor\":" << rasterState.depthBiasConstantFactor << ","
<< std::endl;
mBuffer << "\"depth_bias_clamp\":" << rasterState.depthBiasClamp << "," << std::endl;
mBuffer << "\"depth_bias_slope_factor\":" << rasterState.depthBiasSlopeFactor << "," << std::endl;
mBuffer << "\"line_width\":" << rasterState.lineWidth << std::endl;
mBuffer << "}," << std::endl;
return *this;
}
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
const VkPipelineDepthStencilStateCreateInfo& depthStencil) {
mBuffer << "\"depth_stencil_states\":{" << std::endl;
mBuffer << "\"depth_test_enable\":" << depthStencil.depthTestEnable << "," << std::endl;
mBuffer << "\"depth_write_enable\":" << depthStencil.depthWriteEnable << "," << std::endl;
mBuffer << "\"depth_compare_op\":" << depthStencil.depthCompareOp << "," << std::endl;
mBuffer << "\"depth_bounds_test_enable\":" << depthStencil.depthBoundsTestEnable << ","
<< std::endl;
mBuffer << "\"min_depth_bounds\":" << depthStencil.minDepthBounds << "," << std::endl;
mBuffer << "\"max_depth_bounds\":" << depthStencil.maxDepthBounds << "," << std::endl;
mBuffer << "\"front\":{" << std::endl;
mBuffer << "\"fail_op\":" << depthStencil.front.failOp << "," << std::endl;
mBuffer << "\"pass_op\":" << depthStencil.front.passOp << "," << std::endl;
mBuffer << "\"depth_fail_op\":" << depthStencil.front.depthFailOp << "," << std::endl;
mBuffer << "\"compare_op\":" << depthStencil.front.compareOp << "," << std::endl;
mBuffer << "\"compare_mask\":" << depthStencil.front.compareMask << "," << std::endl;
mBuffer << "\"write_mask\":" << depthStencil.front.writeMask << "," << std::endl;
mBuffer << "\"reference\":" << depthStencil.front.reference << std::endl;
mBuffer << "}," << std::endl;
mBuffer << "\"back\":{" << std::endl;
mBuffer << "\"fail_op\":" << depthStencil.back.failOp << "," << std::endl;
mBuffer << "\"pass_op\":" << depthStencil.back.passOp << "," << std::endl;
mBuffer << "\"depth_fail_op\":" << depthStencil.back.depthFailOp << "," << std::endl;
mBuffer << "\"compare_op\":" << depthStencil.back.compareOp << "," << std::endl;
mBuffer << "\"compare_mask\":" << depthStencil.back.compareMask << "," << std::endl;
mBuffer << "\"write_mask\":" << depthStencil.back.writeMask << "," << std::endl;
mBuffer << "\"reference\":" << depthStencil.back.reference << std::endl;
mBuffer << "}" << std::endl;
mBuffer << "}," << std::endl;
return *this;
}
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
const VkPipelineMultisampleStateCreateInfo& multiSample) {
mBuffer << "\"multisample_states\":{" << std::endl;
mBuffer << "\"rasterization_samples\":" << multiSample.rasterizationSamples << "," << std::endl;
mBuffer << "\"sample_shading_enable\":" << multiSample.sampleShadingEnable << "," << std::endl;
mBuffer << "\"min_sample_shading\":" << multiSample.minSampleShading << "," << std::endl;
mBuffer << "\"alpha_to_coverage_enable\":" << multiSample.alphaToCoverageEnable << ","
<< std::endl;
mBuffer << "\"alpha_to_one_enable\":" << multiSample.alphaToOneEnable << std::endl;
mBuffer << "}," << std::endl;
return *this;
}
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
const VkPipelineDynamicStateCreateInfo& dyStates) {
mBuffer << "\"dynamic_states\":[" << std::endl;
for (uint32_t i = 0; i < dyStates.dynamicStateCount; ++i) {
mBuffer << dyStates.pDynamicStates[i];
if (i < dyStates.dynamicStateCount - 1) {
mBuffer << ",";
}
mBuffer << std::endl;
}
mBuffer << "]," << std::endl;
return *this;
}
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
const VkPipelineColorBlendStateCreateInfo& blendStateInfo) {
mBuffer << "\"color_blend_state_attachments\":[" << std::endl;
for (uint32_t i = 0; i < blendStateInfo.attachmentCount; ++i) {
mBuffer << "{" << std::endl;
mBuffer << "\"blend_enable\":" << blendStateInfo.pAttachments[i].blendEnable << ","
<< std::endl;
mBuffer << "\"src_color_blend_factor\":" << blendStateInfo.pAttachments[i].srcColorBlendFactor
<< ","
<< std::endl;
mBuffer << "\"dst_color_blend_factor\":" << blendStateInfo.pAttachments[i].dstColorBlendFactor
<< ","
<< std::endl;
mBuffer << "\"color_blend_op\":" << blendStateInfo.pAttachments[i].colorBlendOp << ","
<< std::endl;
mBuffer << "\"src_alpha_blend_factor\":" << blendStateInfo.pAttachments[i].srcAlphaBlendFactor
<< ","
<< std::endl;
mBuffer << "\"dst_alpha_blend_factor\":" << blendStateInfo.pAttachments[i].dstAlphaBlendFactor
<< ","
<< std::endl;
mBuffer << "\"alpha_blend_op\":" << blendStateInfo.pAttachments[i].alphaBlendOp << ","
<< std::endl;
mBuffer << "\"color_write_mask\":" << blendStateInfo.pAttachments[i].colorWriteMask
<< std::endl;
mBuffer << "}";
if (i < blendStateInfo.attachmentCount - 1) {
mBuffer << ",";
}
mBuffer << std::endl;
}
mBuffer << "]," << std::endl;
return *this;
}
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
const ShaderStageInfo& shaderStages) {
mShaderStages.push_back(shaderStages);
return *this;
}
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
const VkVertexInputAttributeDescription& attrib) {
mVtxAttribs.push_back(attrib);
return *this;
}
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
const VkVertexInputBindingDescription& binding) {
mVtxBindings.push_back(binding);
return *this;
}
}
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2022 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_VULKANPIPELINESTATESERIALIZER_H
#define TNT_FILAMENT_BACKEND_VULKANPIPELINESTATESERIALIZER_H
#include "VulkanCommands.h"
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
#include <bluevk/BlueVK.h>
#include <iostream>
#include <type_traits>
#include <sstream>
#include <vector>
namespace filament::backend {
// VulkanPipelineStateSerializer stores to a file the pipeline states.
class VulkanPipelineStateSerializer {
public:
VulkanPipelineStateSerializer(VulkanPipelineStateSerializer const&) = delete;
VulkanPipelineStateSerializer& operator=(VulkanPipelineStateSerializer const&) = delete;
VulkanPipelineStateSerializer(const utils::CString& name);
~VulkanPipelineStateSerializer();
void setID(uint32_t key);
void setPipelineLayoutKey(uint32_t pipelineLayoutKey);
void setRenderPassKey(uint32_t renderPassKey);
VulkanPipelineStateSerializer& operator<<(
const VkPipelineInputAssemblyStateCreateInfo& inputAsm);
VulkanPipelineStateSerializer& operator<<(const VkPipelineViewportStateCreateInfo& viewport);
VulkanPipelineStateSerializer& operator<<(const VkPipelineDynamicStateCreateInfo& dyStates);
VulkanPipelineStateSerializer& operator<<(
const VkPipelineRasterizationStateCreateInfo& rasterState);
VulkanPipelineStateSerializer& operator<<(
const VkPipelineDepthStencilStateCreateInfo& depthStencil);
VulkanPipelineStateSerializer& operator<<(const VkPipelineMultisampleStateCreateInfo& multiSample);
VulkanPipelineStateSerializer& operator<<(
const VkPipelineColorBlendStateCreateInfo& blendStateInfo);
struct ShaderStageInfo {
VkShaderStageFlagBits stage;
uint64_t module;
std::string mName;
};
VulkanPipelineStateSerializer& operator<<(const ShaderStageInfo& shaderStages);
VulkanPipelineStateSerializer& operator<<(
const VkVertexInputAttributeDescription& attrib);
VulkanPipelineStateSerializer& operator<<(const VkVertexInputBindingDescription& binding);
private:
std::string mFileName;
std::stringstream mBuffer;
//temp data
utils::CString mProgramName;
std::vector<ShaderStageInfo> mShaderStages;
std::vector<VkVertexInputAttributeDescription> mVtxAttribs;
std::vector<VkVertexInputBindingDescription> mVtxBindings;
};
} // namespace filament::backend
#endif //TNT_FILAMENT_BACKEND_VULKANPIPELINESTATESERIALIZER_H
@@ -0,0 +1,157 @@
/*
* Copyright (C) 2022 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 <fstream>
#include "VulkanRenderPassStateSerializer.h"
#include <utils/Log.h>
#include <utils/Panic.h>
#include "VulkanConstants.h"
#include "VulkanHandles.h"
#if defined(__clang__)
// Vulkan functions often immediately dereference pointers, so it's fine to pass in a pointer
// to a stack-allocated variable.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreturn-stack-address"
#endif
using namespace bluevk;
namespace filament::backend {
VulkanRenderPassStateSerializer::VulkanRenderPassStateSerializer(
const VkRenderPassCreateInfo& info, uint32_t key) {
std::stringstream filename;
filename << "render_pass_" << key << ".json";
std::ofstream file(filename.str());
if (file.is_open()) {
std::stringstream buffer;
buffer << "{" << std::endl;
buffer << "\"attachments\":[" << std::endl;
for (uint32_t i = 0; i < info.attachmentCount; ++i) {
buffer << "{" << std::endl;
buffer << "\"flags\":" << info.pAttachments[i].flags << "," << std::endl;
buffer << "\"format\":" << info.pAttachments[i].format << "," << std::endl;
buffer << "\"samples\":" << info.pAttachments[i].samples << "," << std::endl;
buffer << "\"load_op\":" << info.pAttachments[i].loadOp << "," << std::endl;
buffer << "\"store_op\":" << info.pAttachments[i].storeOp << "," << std::endl;
buffer << "\"stencil_load_op\":" << info.pAttachments[i].stencilLoadOp << "," << std::endl;
buffer << "\"stencil_store_op\":" << info.pAttachments[i].stencilStoreOp << "," << std::endl;
buffer << "\"initial_layout\":" << info.pAttachments[i].initialLayout << "," << std::endl;
buffer << "\"final_layout\":" << info.pAttachments[i].finalLayout << std::endl;
buffer << "}";
if (i < info.attachmentCount - 1) {
buffer << ",";
}
buffer << std::endl;
}
buffer << "]," << std::endl;
buffer << "\"subpasses\":[" << std::endl;
for (uint32_t i = 0; i < info.subpassCount; ++i) {
buffer << "{" << std::endl;
buffer << "\"flags\":" << info.pSubpasses[i].flags << "," << std::endl;
buffer << "\"pipeline_bind_point\":" << info.pSubpasses[i].pipelineBindPoint;
//------------depth stencil-------------------
if (info.pSubpasses[i].pDepthStencilAttachment) {
buffer << "," << std::endl;
buffer << "\"depth_stencil_attachment\":{" << std::endl;
buffer << "\"attachement\":"
<< info.pSubpasses[i].pDepthStencilAttachment->attachment << ","
<< std::endl;
buffer << "\"layout\":" << info.pSubpasses[i].pDepthStencilAttachment->layout
<< std::endl;
buffer << "}";
}
//------------Resolve-------------------
if (info.pSubpasses[i].pResolveAttachments) {
buffer << "," << std::endl;
buffer << "\"resolve_attachments\":[" << std::endl;
for (uint32_t j = 0; j < info.pSubpasses[i].colorAttachmentCount; ++j) {
buffer << "{" << std::endl;
buffer << "\"attachement\":"
<< info.pSubpasses[i].pResolveAttachments[j].attachment << ","
<< std::endl;
buffer << "\"layout\":" << info.pSubpasses[i].pResolveAttachments[j].layout
<< std::endl;
buffer << "}" << std::endl;
if (j < info.pSubpasses[i].colorAttachmentCount - 1) {
buffer << ",";
}
buffer << std::endl;
}
buffer << "]";
}
//------------Color-------------------
if (info.pSubpasses[i].pColorAttachments) {
buffer << "," << std::endl;
buffer << "\"color_attachments\":[" << std::endl;
for (uint32_t j = 0; j < info.pSubpasses[i].colorAttachmentCount; ++j) {
buffer << "{" << std::endl;
buffer << "\"attachement\":"
<< info.pSubpasses[i].pColorAttachments[j].attachment << ","
<< std::endl;
buffer << "\"layout\":" << info.pSubpasses[i].pColorAttachments[j].layout
<< std::endl;
buffer << "}" << std::endl;
if (j < info.pSubpasses[i].colorAttachmentCount - 1) {
buffer << ",";
}
buffer << std::endl;
}
buffer << "]";
}
//------------Input-------------------
if (info.pSubpasses[i].pInputAttachments) {
buffer << "," << std::endl;
buffer << "\"input_attachments\":[" << std::endl;
for (uint32_t j = 0; j < info.pSubpasses[i].inputAttachmentCount; ++j) {
buffer << "{" << std::endl;
buffer << "\"attachement\":"
<< info.pSubpasses[i].pInputAttachments[j].attachment << ","
<< std::endl;
buffer << "\"layout\":" << info.pSubpasses[i].pInputAttachments[j].layout
<< std::endl;
buffer << "}" << std::endl;
if (j < info.pSubpasses[i].inputAttachmentCount - 1) {
buffer << ",";
}
buffer << std::endl;
}
buffer << "]" << std::endl;
}
buffer << "}";
if (i < info.subpassCount - 1) {
buffer << ",";
}
buffer << std::endl;
}
buffer << "]" << std::endl;
buffer << "}";
file << buffer.str();
}
file.close();
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2022 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_VULKANRENDERPASSSTATESERIALIZER_H
#define TNT_FILAMENT_BACKEND_VULKANRENDERPASSSTATESERIALIZER_H
#include "VulkanCommands.h"
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
#include <bluevk/BlueVK.h>
#include <iostream>
#include <type_traits>
#include <sstream>
#include <vector>
namespace filament::backend {
class VulkanRenderPassStateSerializer {
public:
VulkanRenderPassStateSerializer(VulkanRenderPassStateSerializer const&) = delete;
VulkanRenderPassStateSerializer& operator=(VulkanRenderPassStateSerializer const&) = delete;
VulkanRenderPassStateSerializer(const VkRenderPassCreateInfo& info, uint32_t key);
};
} // namespace filament::backend
#endif //TNT_FILAMENT_BACKEND_VULKANRENDERPASSSTATESERIALIZER_H
@@ -21,12 +21,14 @@
#include <utils/Panic.h>
#include "VulkanSamplerStateSerializer.h"
using namespace bluevk;
namespace filament::backend {
VulkanSamplerCache::VulkanSamplerCache(VkDevice device)
: mDevice(device) {}
VulkanSamplerCache::VulkanSamplerCache(VkDevice device, VulkanYcbcrConversionCache* conversionCache)
: mDevice(device), mConversionCache(conversionCache) {}
VkSampler VulkanSamplerCache::getSampler(Params params) {
auto iter = mCache.find(params);
@@ -57,11 +59,16 @@ VkSampler VulkanSamplerCache::getSampler(Params params) {
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
.unnormalizedCoordinates = VK_FALSE,
};
VulkanSamplerStateSerializer samplerSer(params,
params.conversion != VK_NULL_HANDLE ? mConversionCache->getKey(params.conversion) : 0);
VkSampler sampler;
VkResult result = vkCreateSampler(mDevice, &samplerInfo, VKALLOC, &sampler);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create sampler."
<< " error=" << static_cast<int32_t>(result);
mCache.insert({ params, sampler });
SamplerHashFn hashFn;
mSamplerToKey[sampler] = hashFn(params);
return sampler;
}
@@ -17,9 +17,11 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANSAMPLERCACHE_H
#define TNT_FILAMENT_BACKEND_VULKANSAMPLERCACHE_H
#include "VulkanYcbcrConversionCache.h"
#include <backend/DriverEnums.h>
#include <utils/Hash.h>
#include <map>
#include <bluevk/BlueVK.h>
#include <tsl/robin_map.h>
@@ -37,11 +39,20 @@ public:
static_assert(sizeof(Params) == 16);
explicit VulkanSamplerCache(VkDevice device);
explicit VulkanSamplerCache(VkDevice device, VulkanYcbcrConversionCache* conversionCache);
VkSampler getSampler(Params params);
uint32_t getKey(VkSampler sampler) {
uint32_t key = 0;
auto iter = mSamplerToKey.find(sampler);
if (iter != mSamplerToKey.end()) {
key = iter->second;
}
return key;
}
void terminate() noexcept;
private:
VkDevice mDevice;
VulkanYcbcrConversionCache* mConversionCache;
struct SamplerEqualTo {
bool operator()(Params lhs, Params rhs) const noexcept {
@@ -51,6 +62,7 @@ private:
};
using SamplerHashFn = utils::hash::MurmurHashFn<Params>;
tsl::robin_map<Params, VkSampler, SamplerHashFn, SamplerEqualTo> mCache;
std::map<VkSampler, uint32_t> mSamplerToKey;
};
} // namespace filament::backend
@@ -0,0 +1,64 @@
#include "VulkanSamplerStateSerializer.h"
#include <fstream>
#include <sstream>
#include <utils/Hash.h>
namespace filament::backend {
VulkanYcbcrConversionSerializer::VulkanYcbcrConversionSerializer(
const VulkanYcbcrConversionCache::Params& params) {
using ConversionHashFn = utils::hash::MurmurHashFn<VulkanYcbcrConversionCache::Params>;
ConversionHashFn hashFn;
uint32_t key = hashFn(params);
std::stringstream filename;
filename << "ycbcr_conv_" << key << ".json";
std::ofstream file(filename.str());
if (file.is_open()) {
std::stringstream buffer;
auto const& chroma = params.conversion;
buffer << "{" << std::endl;
buffer << "\"format\":" << params.format << "," << std::endl;
buffer << "\"external_format\":" << params.externalFormat << "," << std::endl;
buffer << "\"ycbcr_model\":" << uint8_t(chroma.ycbcrModel) << "," << std::endl;
buffer << "\"ycbcr_range\":" << uint8_t(chroma.ycbcrRange) << "," << std::endl;
buffer << "\"swizzle_array\":[" << uint8_t(chroma.r) << "," << uint8_t(chroma.g) << ","
<< uint8_t(chroma.b) << "," << uint8_t(chroma.a) << "]," << std::endl;
buffer << "\"x_chroma_offset\":" << uint8_t(chroma.xChromaOffset) << "," << std::endl;
buffer << "\"y_chroma_offset\":" << uint8_t(chroma.yChromaOffset) << "," << std::endl;
buffer << "\"chroma_filter\":" << uint8_t(chroma.chromaFilter) << std::endl;
buffer << "}" << std::endl;
file << buffer.str();
}
file.close();
}
VulkanSamplerStateSerializer::VulkanSamplerStateSerializer(const VulkanSamplerCache::Params& params, uint32_t ycbcr_conv_key) {
using ConversionHashFn = utils::hash::MurmurHashFn<VulkanSamplerCache::Params>;
ConversionHashFn hashFn;
uint32_t key = hashFn(params);
std::stringstream filename;
filename << "sampler_" << key << ".json";
std::ofstream file(filename.str());
if (file.is_open()) {
std::stringstream buffer;
auto const& samplerParams = params.sampler;
buffer << "{" << std::endl;
buffer << "\"filter_mag\":" << uint32_t(samplerParams.filterMag) << "," << std::endl;
buffer << "\"filter_min\":" << uint32_t(samplerParams.filterMin) << "," << std::endl;
buffer << "\"wrap_s\":" << uint32_t(samplerParams.wrapS) << "," << std::endl;
buffer << "\"wrap_t\":" << uint32_t(samplerParams.wrapT) << "," << std::endl;
buffer << "\"wrap_r\":" << uint32_t(samplerParams.wrapR) << "," << std::endl;
buffer << "\"anisotropy_log2\":" << uint32_t(samplerParams.anisotropyLog2) << "," << std::endl;
buffer << "\"compare_mode\":" << uint32_t(samplerParams.compareMode) << "," << std::endl;
buffer << "\"compare_func\":" << uint32_t(samplerParams.compareFunc) << "," << std::endl;
buffer << "\"ycbcr_conv_key\":" << ycbcr_conv_key << std::endl;
buffer << "}" << std::endl;
file << buffer.str();
}
file.close();
}
} // namespace filament::backend
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2022 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_VULKANSAMPLERSTATESERIALIZER_H
#define TNT_FILAMENT_BACKEND_VULKANSAMPLERSTATESERIALIZER_H
#include <backend/DriverEnums.h>
#include "VulkanYcbcrConversionCache.h"
#include "VulkanSamplerCache.h"
#include <bluevk/BlueVK.h>
#include <vector>
#include <iostream>
#include <sstream>
namespace filament::backend {
class VulkanYcbcrConversionSerializer {
public:
VulkanYcbcrConversionSerializer(VulkanYcbcrConversionSerializer const&) = delete;
VulkanYcbcrConversionSerializer& operator=(VulkanYcbcrConversionSerializer const&) = delete;
VulkanYcbcrConversionSerializer(const VulkanYcbcrConversionCache::Params& params);
};
// VulkanPipelineStateSerializer stores to a file the pipeline states.
class VulkanSamplerStateSerializer {
public:
VulkanSamplerStateSerializer(VulkanSamplerStateSerializer const&) = delete;
VulkanSamplerStateSerializer& operator=(VulkanSamplerStateSerializer const&) = delete;
VulkanSamplerStateSerializer(const VulkanSamplerCache::Params& params, uint32_t ycbcr_conv_key);
};
}
#endif //TNT_FILAMENT_BACKEND_VULKANSAMPLERSTATESERIALIZER_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;
}
@@ -15,6 +15,7 @@
*/
#include "VulkanYcbcrConversionCache.h"
#include "VulkanSamplerStateSerializer.h"
#include "vulkan/VulkanConstants.h"
#include "vulkan/utils/Conversion.h"
@@ -53,6 +54,7 @@ VkSamplerYcbcrConversion VulkanYcbcrConversionCache::getConversion(
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset),
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
};
VulkanYcbcrConversionSerializer ycbcr_ser(params);
// We could put this in the platform class, but that seems like a bit of an overkill
#if defined(__ANDROID__)
@@ -73,6 +75,8 @@ VkSamplerYcbcrConversion VulkanYcbcrConversionCache::getConversion(
<< " error=" << static_cast<int32_t>(result);
mCache.insert({ params, conversion });
ConversionHashFn hashFn;
mConversionToKey[conversion] = hashFn(params);
return conversion;
}
@@ -25,6 +25,7 @@
#include <bluevk/BlueVK.h>
#include <tsl/robin_map.h>
#include <map>
namespace filament::backend {
@@ -40,6 +41,14 @@ public:
explicit VulkanYcbcrConversionCache(VkDevice device);
VkSamplerYcbcrConversion getConversion(Params params);
uint32_t getKey(VkSamplerYcbcrConversion conv) {
uint32_t key = 0;
auto iter = mConversionToKey.find(conv);
if (iter != mConversionToKey.end()) {
key = iter->second;
}
return key;
}
void terminate() noexcept;
private:
@@ -54,6 +63,7 @@ private:
};
using ConversionHashFn = utils::hash::MurmurHashFn<Params>;
tsl::robin_map<Params, VkSamplerYcbcrConversion, ConversionHashFn, ConversionEqualTo> mCache;
std::map<VkSamplerYcbcrConversion, uint32_t> mConversionToKey;
};
} // namespace filament::backend
@@ -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;
}
}
@@ -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

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