Compare commits
83
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4436ffaaab | ||
|
|
c98038b2d7 | ||
|
|
849c4bb8c5 | ||
|
|
ee64322d76 | ||
|
|
79d65a768a | ||
|
|
37610dc8f3 | ||
|
|
2e31dc20e4 | ||
|
|
ae3d98fb47 | ||
|
|
a938a790fa | ||
|
|
71d556dddf | ||
|
|
d61227b2fd | ||
|
|
612a6b00e4 | ||
|
|
b3353b518c | ||
|
|
0b44761d43 | ||
|
|
c7979e4023 | ||
|
|
e00904e100 | ||
|
|
96e2366569 | ||
|
|
e76624862d | ||
|
|
c5c83a61d3 | ||
|
|
ccf0d6205f | ||
|
|
f482c1d702 | ||
|
|
a4c056e822 | ||
|
|
f5071b2aae | ||
|
|
0fa13ee43d | ||
|
|
1d2b8a08fa | ||
|
|
0c06744450 | ||
|
|
4ada29fbc3 | ||
|
|
76182231dd | ||
|
|
8cd6915b9d | ||
|
|
f90de26bc2 | ||
|
|
cc2e661aab | ||
|
|
3a92cdab3a | ||
|
|
b367982c23 | ||
|
|
a4f4dc617e | ||
|
|
2eb0f11d05 | ||
|
|
6694df991c | ||
|
|
8b2bfa7e9d | ||
|
|
7386220325 | ||
|
|
000faeaca7 | ||
|
|
3f9783ac02 | ||
|
|
4e6dc67ae8 | ||
|
|
666140bb73 | ||
|
|
56050de5cd | ||
|
|
772136decd | ||
|
|
f664601c51 | ||
|
|
f06b27b7fb | ||
|
|
ef53ce88d4 | ||
|
|
ef18030e1a | ||
|
|
f07176c0a2 | ||
|
|
15db141c7a | ||
|
|
d4bbb7c591 | ||
|
|
92e620d2ad | ||
|
|
311104da97 | ||
|
|
3127632f96 | ||
|
|
59f611bfde | ||
|
|
2d556bdca2 | ||
|
|
65e7dba8b8 | ||
|
|
6550a63056 | ||
|
|
aa4e2c56b5 | ||
|
|
aa4f1910b8 | ||
|
|
223a4b18a8 | ||
|
|
57ef534acd | ||
|
|
719914fb84 | ||
|
|
080f958da3 | ||
|
|
7547aa3807 | ||
|
|
67a0c6e0e1 | ||
|
|
37cb842993 | ||
|
|
ad27d48fd3 | ||
|
|
3a503976c8 | ||
|
|
ce6fa82026 | ||
|
|
e8349ab5cc | ||
|
|
4773fc4647 | ||
|
|
d47a69a529 | ||
|
|
d3b74e96b4 | ||
|
|
209d3f7550 | ||
|
|
92e65cb3fd | ||
|
|
a4945939de | ||
|
|
086760b307 | ||
|
|
ce1b63ce38 | ||
|
|
b7b8983653 | ||
|
|
d52fb1f4fd | ||
|
|
901c87761b | ||
|
|
081fe6a434 |
@@ -21,8 +21,9 @@ jobs:
|
||||
- name: Run update script
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
||||
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||
run: |
|
||||
GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | python3 test/renderdiff/src/commit_msg.py)
|
||||
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py)
|
||||
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
||||
if [[ "${GOLDEN_BRANCH}" != "main" ]]; then
|
||||
git config --global user.email "filament.bot@gmail.com"
|
||||
|
||||
@@ -134,18 +134,17 @@ jobs:
|
||||
env:
|
||||
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||
run: |
|
||||
ls ./gltf/Models
|
||||
TEST_DIR=test/renderdiff
|
||||
source ${TEST_DIR}/src/preamble.sh
|
||||
start_
|
||||
set -eux
|
||||
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 ${TEST_DIR}/src/commit_msg.py)
|
||||
bash ${TEST_DIR}/generate.sh && \
|
||||
python3 ${TEST_DIR}/src/golden_manager.py \
|
||||
bash ${TEST_DIR}/generate.sh
|
||||
python3 ${TEST_DIR}/src/golden_manager.py \
|
||||
--branch=${GOLDEN_BRANCH} \
|
||||
--output=${GOLDEN_OUTPUT_DIR}
|
||||
|
||||
# Note that we need to upload the output even if comparison fails, so we undo `set -ex`
|
||||
end_
|
||||
# Note that we need to upload the output even if comparison fails, so we undo `set -eux`
|
||||
set +eux
|
||||
|
||||
python3 ${TEST_DIR}/src/compare.py \
|
||||
--src=${GOLDEN_OUTPUT_DIR} \
|
||||
@@ -158,6 +157,7 @@ jobs:
|
||||
cat compare_output.txt >> "$GITHUB_OUTPUT"
|
||||
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
shell: bash
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: presubmit-renderdiff-result
|
||||
@@ -179,7 +179,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- name: Run build script
|
||||
run: ./build.sh -W debug test_filamat filament
|
||||
run: ./build.sh -W debug test_filamat filament gltf_viewer
|
||||
- name: Run test
|
||||
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
|
||||
|
||||
|
||||
+4
-2
@@ -872,7 +872,9 @@ add_subdirectory(${LIBRARIES}/utils)
|
||||
add_subdirectory(${LIBRARIES}/viewer)
|
||||
add_subdirectory(${FILAMENT}/shaders)
|
||||
add_subdirectory(${EXTERNAL}/abseil/tnt)
|
||||
add_subdirectory(${EXTERNAL}/basisu/tnt)
|
||||
# Add zstd before basisu to force it to use the external zstd target,
|
||||
# preventing a duplicate symbol conflict with its bundled version.
|
||||
add_subdirectory(${EXTERNAL}/zstd/tnt)
|
||||
add_subdirectory(${EXTERNAL}/civetweb/tnt)
|
||||
add_subdirectory(${EXTERNAL}/imgui/tnt)
|
||||
add_subdirectory(${EXTERNAL}/robin-map/tnt)
|
||||
@@ -886,7 +888,7 @@ add_subdirectory(${EXTERNAL}/jsmn/tnt)
|
||||
add_subdirectory(${EXTERNAL}/stb/tnt)
|
||||
add_subdirectory(${EXTERNAL}/getopt)
|
||||
add_subdirectory(${EXTERNAL}/perfetto/tnt)
|
||||
add_subdirectory(${EXTERNAL}/zstd/tnt)
|
||||
add_subdirectory(${EXTERNAL}/basisu/tnt)
|
||||
|
||||
|
||||
# Note that this has to be placed after mikktspace in order for combine_static_libs to work.
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.67.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.68.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.67.1'
|
||||
pod 'Filament', '~> 1.68.0'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,6 +7,15 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.68.1
|
||||
|
||||
|
||||
## v1.68.0
|
||||
|
||||
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
|
||||
- Metal: report GPU errors to the platform via `debugUpdateStat` (b/431665753).
|
||||
- materials: Make Material Instances' UBO descriptor use dynamic offsets. [⚠️ **Recompile Materials**]
|
||||
|
||||
## v1.67.1
|
||||
|
||||
- Metal: Add support for the `SwapChain::CONFIG_MSAA_4_SAMPLES` flag.
|
||||
|
||||
@@ -100,7 +100,7 @@ buildscript {
|
||||
'kotlin': '2.0.21',
|
||||
'kotlin_coroutines': '1.9.0',
|
||||
'buildTools': '35.0.0',
|
||||
'ndk': '27.0.11718014',
|
||||
'ndk': '29.0.14206865',
|
||||
'androidx_core': '1.13.1',
|
||||
'androidx_annotations': '1.9.0'
|
||||
]
|
||||
|
||||
@@ -25,7 +25,7 @@ using namespace filament;
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass,
|
||||
jlong nativeEngine, jobject buffer_, jint size, jint shBandCount, jint shadowQuality) {
|
||||
jlong nativeEngine, jobject buffer_, jint size, jint shBandCount, jint shadowQuality, jint uboBatchingMode) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
AutoBuffer buffer(env, buffer_, size);
|
||||
auto builder = Material::Builder();
|
||||
@@ -33,6 +33,7 @@ Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass,
|
||||
builder.sphericalHarmonicsBandCount(shBandCount);
|
||||
}
|
||||
builder.shadowSamplingQuality((Material::Builder::ShadowSamplingQuality)shadowQuality);
|
||||
builder.uboBatching((Material::UboBatchingMode)uboBatchingMode);
|
||||
Material* material = builder
|
||||
.package(buffer.getData(), buffer.getSize())
|
||||
.build(*engine);
|
||||
|
||||
@@ -147,6 +147,15 @@ Java_com_google_android_filament_View_nSetDynamicResolutionOptions(JNIEnv*, jcla
|
||||
view->setDynamicResolutionOptions(options);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nGetLastDynamicResolutionScale(JNIEnv *env, jclass, jlong nativeView, jfloatArray out_) {
|
||||
jfloat* out = env->GetFloatArrayElements(out_, nullptr);
|
||||
View *view = (View *) nativeView;
|
||||
math::float2 result = view->getLastDynamicResolutionScale();
|
||||
std::copy_n(result.v, 2, out);
|
||||
env->ReleaseFloatArrayElements(out_, out, 0);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetShadowType(JNIEnv*, jclass, jlong nativeView, jint type) {
|
||||
View* view = (View*) nativeView;
|
||||
|
||||
@@ -99,6 +99,15 @@ final class Asserts {
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull @Size(min = 2)
|
||||
static float[] assertFloat2(@Nullable float[] out) {
|
||||
if (out == null) out = new float[2];
|
||||
else if (out.length < 2) {
|
||||
throw new ArrayIndexOutOfBoundsException("Array length must be at least 2");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@NonNull @Size(min = 4)
|
||||
static float[] assertFloat4(@Nullable float[] out) {
|
||||
if (out == null) out = new float[4];
|
||||
|
||||
@@ -261,6 +261,20 @@ public class Material {
|
||||
LOW
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines whether a material instance should use UBO batching or not.
|
||||
*/
|
||||
public enum UboBatchingMode {
|
||||
/**
|
||||
* For default, it follows the engine settings.
|
||||
* If UBO batching is enabled on the engine and the material domain is SURFACE, it
|
||||
* turns on the UBO batching. Otherwise, it turns off the UBO batching.
|
||||
*/
|
||||
DEFAULT,
|
||||
/** Disable the Ubo Batching for this material */
|
||||
DISABLED
|
||||
}
|
||||
|
||||
public static class UserVariantFilterBit {
|
||||
/** Directional lighting */
|
||||
public static int DIRECTIONAL_LIGHTING = 0x01;
|
||||
@@ -372,6 +386,7 @@ public class Material {
|
||||
private int mSize;
|
||||
private int mShBandCount = 0;
|
||||
private ShadowSamplingQuality mShadowSamplingQuality = ShadowSamplingQuality.LOW;
|
||||
private UboBatchingMode mUboBatchingMode = UboBatchingMode.DEFAULT;
|
||||
|
||||
|
||||
/**
|
||||
@@ -416,6 +431,17 @@ public class Material {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the batching mode of the instances created from this material.
|
||||
* @param uboBatchingMode
|
||||
* @return Reference to this Builder for chaining calls.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder uboBatching(UboBatchingMode mode) {
|
||||
mUboBatchingMode = mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns the Material object.
|
||||
*
|
||||
@@ -428,7 +454,7 @@ public class Material {
|
||||
@NonNull
|
||||
public Material build(@NonNull Engine engine) {
|
||||
long nativeMaterial = nBuilderBuild(engine.getNativeObject(),
|
||||
mBuffer, mSize, mShBandCount, mShadowSamplingQuality.ordinal());
|
||||
mBuffer, mSize, mShBandCount, mShadowSamplingQuality.ordinal(), mUboBatchingMode.ordinal());
|
||||
if (nativeMaterial == 0) throw new IllegalStateException("Couldn't create Material");
|
||||
return new Material(nativeMaterial);
|
||||
}
|
||||
@@ -1094,7 +1120,7 @@ public class Material {
|
||||
mNativeObject = 0;
|
||||
}
|
||||
|
||||
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount, int shadowQuality);
|
||||
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount, int shadowQuality, int uboBatchingMode);
|
||||
private static native long nCreateInstance(long nativeMaterial);
|
||||
private static native long nCreateInstanceWithName(long nativeMaterial, @NonNull String name);
|
||||
private static native long nGetDefaultInstance(long nativeMaterial);
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.EnumSet;
|
||||
|
||||
import static com.google.android.filament.Asserts.assertFloat3In;
|
||||
import static com.google.android.filament.Asserts.assertFloat4In;
|
||||
import static com.google.android.filament.Colors.LinearColor;
|
||||
|
||||
import com.google.android.filament.proguard.UsedByNative;
|
||||
|
||||
@@ -674,6 +673,21 @@ public class View {
|
||||
return mDynamicResolution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last dynamic resolution scale factor used by this view. This value is updated
|
||||
* when Renderer::render(View*) is called
|
||||
* @param out A 2-float array where the value will be stored, or null in which case the array is
|
||||
* allocated.
|
||||
* @return A 2-float array containing the horizontal and the vertical scale factors
|
||||
* @see Renderer#render(View)
|
||||
*/
|
||||
@NonNull @Size(min = 2)
|
||||
public float[] getLastDynamicResolutionScale(@Nullable @Size(min = 2) float[] out) {
|
||||
out = Asserts.assertFloat2(out);
|
||||
nGetLastDynamicResolutionScale(getNativeObject(), out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the rendering quality for this view (e.g. color precision).
|
||||
*
|
||||
@@ -1317,6 +1331,7 @@ public class View {
|
||||
private static native void nSetDithering(long nativeView, int dithering);
|
||||
private static native int nGetDithering(long nativeView);
|
||||
private static native void nSetDynamicResolutionOptions(long nativeView, boolean enabled, boolean homogeneousScaling, float minScale, float maxScale, float sharpness, int quality);
|
||||
private static native void nGetLastDynamicResolutionScale(long nativeView, float[] out);
|
||||
private static native void nSetRenderQuality(long nativeView, int hdrColorBufferQuality);
|
||||
private static native void nSetDynamicLightingOptions(long nativeView, float zLightNear, float zLightFar);
|
||||
private static native void nSetShadowType(long nativeView, int type);
|
||||
|
||||
+35
@@ -16,6 +16,9 @@
|
||||
|
||||
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
|
||||
@@ -26,6 +29,7 @@ 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
|
||||
@@ -119,6 +123,8 @@ 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()
|
||||
@@ -305,10 +311,39 @@ 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
|
||||
|
||||
+14
@@ -40,7 +40,21 @@ fun loadTexture(engine: Engine, resources: Resources, resourceId: Int, type: Tex
|
||||
options.inPremultiplied = type == TextureType.COLOR
|
||||
|
||||
val bitmap = BitmapFactory.decodeResource(resources, resourceId, options)
|
||||
return buildTexture(engine, bitmap, type)
|
||||
}
|
||||
|
||||
fun loadTexture(engine: Engine, bytes: ByteArray, type: TextureType, offset: Int = 0, length: Int = bytes.size): Texture {
|
||||
val options = BitmapFactory.Options()
|
||||
// Color is the only type of texture we want to pre-multiply with the alpha channel
|
||||
// Pre-multiplication is the default behavior, so we need to turn it off here
|
||||
options.inPremultiplied = type == TextureType.COLOR
|
||||
|
||||
val bitmap = BitmapFactory.decodeByteArray(bytes, offset, length, options)
|
||||
return buildTexture(engine, bitmap, type)
|
||||
}
|
||||
|
||||
|
||||
private fun buildTexture(engine: Engine, bitmap: Bitmap, type: TextureType): Texture {
|
||||
val texture = Texture.Builder()
|
||||
.width(bitmap.width)
|
||||
.height(bitmap.height)
|
||||
|
||||
+1
@@ -22,6 +22,7 @@ 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,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.67.1
|
||||
VERSION_NAME=1.68.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -17,12 +17,20 @@ filamentTools {
|
||||
}
|
||||
|
||||
// don't forget to update MainACtivity.kt when/if changing this.
|
||||
tasks.register('copyMesh', Copy) {
|
||||
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) {
|
||||
from "../../../third_party/models/BusterDrone"
|
||||
into "src/main/assets/models"
|
||||
}
|
||||
|
||||
preBuild.dependsOn copyMesh
|
||||
preBuild.dependsOn copyDamagedHelmetGltf
|
||||
preBuild.dependsOn copyBusterGltf
|
||||
|
||||
clean.doFirst {
|
||||
delete "src/main/assets"
|
||||
|
||||
Binary file not shown.
@@ -158,7 +158,7 @@ function print_fgviewer_help {
|
||||
}
|
||||
|
||||
# Unless explicitly specified, NDK version will be selected as highest available version within same major release chain
|
||||
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION:-$(cat `dirname $0`/build/common/versions | grep GITHUB_NDK_VERSION | sed s/GITHUB_NDK_VERSION=//g | cut -f 1 -d ".")}
|
||||
FILAMENT_NDK_VERSION=$(cat `dirname $0`/build/common/versions | grep GITHUB_NDK_VERSION | sed s/GITHUB_NDK_VERSION=//g | cut -f 1 -d ".")
|
||||
|
||||
# Internal variables
|
||||
ISSUE_CLEAN=false
|
||||
|
||||
@@ -18,8 +18,8 @@ if [[ "$GITHUB_WORKFLOW" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Unless explicitly specified, NDK version will be set to match exactly the required one
|
||||
FILAMENT_NDK_VERSION=${GITHUB_NDK_VERSION:-27.0.11718014}
|
||||
# Unless explicitly specified, NDK version will be selected as highest available version within same major release chain
|
||||
FILAMENT_NDK_VERSION=$(cat `dirname $0`/../common/versions | grep GITHUB_NDK_VERSION | sed s/GITHUB_NDK_VERSION=//g)
|
||||
|
||||
(! grep "${FILAMENT_NDK_VERSION}" `dirname $0`/../../android/build.gradle > /dev/null) &&
|
||||
echo "Mismatch of NDK versions: want ${FILAMENT_NDK_VERSION} and not found in android/build.gradle" &&
|
||||
|
||||
@@ -3,7 +3,7 @@ GITHUB_CMAKE_VERSION=3.22.1
|
||||
GITHUB_NINJA_VERSION=1.10.2
|
||||
GITHUB_MESA_VERSION=24.2.1
|
||||
GITHUB_LLVM_VERSION=16
|
||||
GITHUB_NDK_VERSION=27.0.11718014
|
||||
GITHUB_NDK_VERSION=29.0.14206865
|
||||
GITHUB_EMSDK_VERSION=3.1.60
|
||||
GITHUB_VULKANSDK_VERSION=1.4.321.0
|
||||
GITHUB_GLTF_SAMPLE_ASSETS_COMMIT=d441dfdb87413ff412c620849a649d61789a470f
|
||||
GITHUB_GLTF_SAMPLE_ASSETS_COMMIT=d441dfdb87413ff412c620849a649d61789a470f
|
||||
|
||||
@@ -75,7 +75,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
# C_FLAGS += -Wl,-pie
|
||||
# CXX_FLAGS += -lstdc++
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE -mcpu=cortex-a57" CACHE STRING "Toolchain CFLAGS")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE -march=armv8-a -mtune=cortex-a78" CACHE STRING "Toolchain CFLAGS")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS}" CACHE STRING "Toolchain CXXFLAGS")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIE -pie -static-libstdc++" CACHE STRING "Toolchain LDFLAGS")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libstdc++" CACHE STRING "Toolchain LDFLAGS")
|
||||
|
||||
@@ -87,7 +87,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
# for hardfp: CFLAGS must have -mhard-float
|
||||
# LDFLAGS must have -Wl,--no-warn-mismatch
|
||||
#
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mthumb -march=armv7-a -mcpu=cortex-a15 -mfloat-abi=softfp -mfpu=neon-vfpv4 -fPIE" CACHE STRING "Toolchain CFLAGS")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=neon-vfpv4 -fPIE" CACHE STRING "Toolchain CFLAGS")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS}" CACHE STRING "Toolchain CXXFLAGS")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -march=armv7-a -Wl,--no-warn-mismatch -L${TOOLCHAIN}/arm-linux-androideabi/lib/armv7-a -static-libstdc++ -fPIE -pie" CACHE STRING "Toolchain LDFLAGS")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -march=armv7-a -Wl,--no-warn-mismatch -L${TOOLCHAIN}/arm-linux-androideabi/lib/armv7-a -static-libstdc++" CACHE STRING "Toolchain LDFLAGS")
|
||||
|
||||
+2
-2
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.66.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.68.0'
|
||||
}
|
||||
</code></pre>
|
||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||
@@ -196,7 +196,7 @@ dependencies {
|
||||
</div>
|
||||
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
||||
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.66.2'
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.68.0'
|
||||
</code></pre>
|
||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||
<ul>
|
||||
|
||||
+1575
-1546
File diff suppressed because one or more lines are too long
+1058
-1033
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -10,6 +10,6 @@ sortedcontainers==2.4.0
|
||||
trio==0.31.0
|
||||
trio-websocket==0.12.2
|
||||
typing_extensions==4.15.0
|
||||
urllib3==2.5.0
|
||||
urllib3==2.6.0
|
||||
websocket-client==1.9.0
|
||||
wsproto==1.2.0
|
||||
|
||||
@@ -206,6 +206,8 @@ 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
|
||||
@@ -252,10 +254,21 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
src/vulkan/utils/Spirv.h
|
||||
src/vulkan/utils/StaticVector.h
|
||||
)
|
||||
if (LINUX OR WIN32)
|
||||
list(APPEND SRCS src/vulkan/platform/VulkanPlatformLinuxWindows.cpp)
|
||||
if (LINUX)
|
||||
list(APPEND SRCS
|
||||
src/vulkan/platform/VulkanPlatformLinux.cpp
|
||||
include/backend/platforms/VulkanPlatformLinux.h
|
||||
)
|
||||
elseif (WIN32)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/VulkanPlatformWindows.h
|
||||
src/vulkan/platform/VulkanPlatformWindows.cpp
|
||||
)
|
||||
elseif (APPLE OR IOS)
|
||||
list(APPEND SRCS src/vulkan/platform/VulkanPlatformApple.mm)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/VulkanPlatformApple.h
|
||||
src/vulkan/platform/VulkanPlatformApple.mm
|
||||
)
|
||||
elseif (ANDROID)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/VulkanPlatformAndroid.h
|
||||
@@ -305,6 +318,8 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
src/webgpu/WebGPURenderPrimitive.h
|
||||
src/webgpu/WebGPURenderTarget.cpp
|
||||
src/webgpu/WebGPURenderTarget.h
|
||||
src/webgpu/WebGPUStagePool.cpp
|
||||
src/webgpu/WebGPUStagePool.h
|
||||
src/webgpu/WebGPUStrings.h
|
||||
src/webgpu/WebGPUSwapChain.cpp
|
||||
src/webgpu/WebGPUSwapChain.h
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
|
||||
#define TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
|
||||
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* The type of association between a buffer object and a stream.
|
||||
*/
|
||||
enum class BufferObjectStreamAssociationType { TRANSFORM_MATRIX };
|
||||
|
||||
/**
|
||||
* A descriptor for a buffer object to stream association.
|
||||
*/
|
||||
class UTILS_PUBLIC BufferObjectStreamDescriptor {
|
||||
public:
|
||||
//! creates an empty descriptor
|
||||
BufferObjectStreamDescriptor() noexcept = default;
|
||||
|
||||
BufferObjectStreamDescriptor(const BufferObjectStreamDescriptor& rhs) = delete;
|
||||
BufferObjectStreamDescriptor& operator=(const BufferObjectStreamDescriptor& rhs) = delete;
|
||||
|
||||
BufferObjectStreamDescriptor(BufferObjectStreamDescriptor&& rhs) = default;
|
||||
BufferObjectStreamDescriptor& operator=(BufferObjectStreamDescriptor&& rhs) = default;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
struct StreamDataDescriptor {
|
||||
uint32_t offset;
|
||||
Handle<HwStream> stream;
|
||||
BufferObjectStreamAssociationType associationType;
|
||||
};
|
||||
|
||||
std::vector<StreamDataDescriptor> mStreams;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
utils::io::ostream& operator<<(utils::io::ostream& out,
|
||||
const filament::backend::BufferObjectStreamDescriptor& b);
|
||||
#endif
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
|
||||
@@ -1713,6 +1713,8 @@ using FrameTimestamps = Platform::FrameTimestamps;
|
||||
|
||||
using CompositorTiming = Platform::CompositorTiming;
|
||||
|
||||
using AsynchronousMode = Platform::AsynchronousMode;
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
template<> struct utils::EnableBitMaskOperators<filament::backend::ShaderStageFlags>
|
||||
|
||||
@@ -19,13 +19,17 @@
|
||||
#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 {
|
||||
|
||||
@@ -70,6 +74,9 @@ 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; }
|
||||
@@ -248,6 +255,28 @@ public:
|
||||
REALTIME,
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines how asynchronous operations are handled by the engine.
|
||||
*/
|
||||
enum class AsynchronousMode : uint8_t {
|
||||
/**
|
||||
* Asynchronous operations are disabled. This is the default.
|
||||
*/
|
||||
NONE,
|
||||
|
||||
/**
|
||||
* Attempts to use a dedicated worker thread for asynchronous tasks. If threading is not
|
||||
* supported by the platform, it automatically falls back to using an amortization strategy.
|
||||
*/
|
||||
THREAD_PREFERRED,
|
||||
|
||||
/**
|
||||
* Uses an amortization strategy, processing a small number of asynchronous tasks during
|
||||
* each engine update cycle.
|
||||
*/
|
||||
AMORTIZATION,
|
||||
};
|
||||
|
||||
struct DriverConfig {
|
||||
/**
|
||||
* Size of handle arena in bytes. Setting to 0 indicates default value is to be used.
|
||||
@@ -317,6 +346,11 @@ public:
|
||||
* - VulkanPlatform
|
||||
*/
|
||||
bool vulkanEnableStagingBufferBypass = false;
|
||||
|
||||
/**
|
||||
* Asynchronous mode for the engine. Defines how asynchronous operations are handled.
|
||||
*/
|
||||
AsynchronousMode asynchronousMode = AsynchronousMode::NONE;
|
||||
};
|
||||
|
||||
Platform() noexcept;
|
||||
@@ -508,13 +542,22 @@ public:
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Debugging APIs
|
||||
|
||||
using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key, uint64_t value)>;
|
||||
using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key,
|
||||
uint64_t intValue, utils::CString stringValue)>;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
@@ -533,15 +576,32 @@ 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 value the updated value of key
|
||||
* @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)
|
||||
*/
|
||||
void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t value);
|
||||
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);
|
||||
|
||||
private:
|
||||
InsertBlobFunc mInsertBlob;
|
||||
RetrieveBlobFunc mRetrieveBlob;
|
||||
DebugUpdateStatFunc mDebugUpdateStat;
|
||||
std::shared_ptr<InsertBlobFunc> mInsertBlob;
|
||||
std::shared_ptr<RetrieveBlobFunc> mRetrieveBlob;
|
||||
std::shared_ptr<DebugUpdateStatFunc> mDebugUpdateStat;
|
||||
mutable utils::Mutex mMutex;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
#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>
|
||||
@@ -32,8 +30,6 @@
|
||||
|
||||
#include <math/mat3.h>
|
||||
|
||||
#include "AndroidNativeWindow.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -171,19 +167,10 @@ protected:
|
||||
SwapChain* drawSwapChain,
|
||||
SwapChain* readSwapChain) override;
|
||||
|
||||
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:
|
||||
struct SwapChainEGLAndroid;
|
||||
struct AndroidDetails;
|
||||
|
||||
// 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;
|
||||
@@ -203,17 +190,14 @@ 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;
|
||||
AndroidProducerThrottling mProducerThrottling;
|
||||
SwapChainEGLAndroid* mCurrentDrawSwapChain{};
|
||||
bool mAssertNativeWindowIsValid = false;
|
||||
|
||||
AndroidFrameCallback mAndroidFrameCallback;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -437,22 +437,19 @@ protected:
|
||||
std::shared_ptr<VulkanCmdFence> fenceStatus;
|
||||
};
|
||||
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
||||
|
||||
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const = 0;
|
||||
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept;
|
||||
uint64_t flags) const noexcept = 0;
|
||||
|
||||
virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept;
|
||||
|
||||
/**
|
||||
* Query if transient attachments are supported by the backend.
|
||||
*/
|
||||
bool isTransientAttachmentSupported() const noexcept;
|
||||
|
||||
private:
|
||||
// Platform dependent helper methods
|
||||
static ExtensionSet getSwapchainInstanceExtensionsImpl();
|
||||
|
||||
// Platform dependent helper methods
|
||||
static SurfaceBundle createVkSurfaceKHRImpl(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) noexcept;
|
||||
|
||||
friend struct VulkanPlatformPrivate;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
#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 {
|
||||
@@ -91,6 +92,8 @@ protected:
|
||||
VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override;
|
||||
|
||||
private:
|
||||
struct AndroidDetails;
|
||||
|
||||
struct ExternalImageVulkanAndroid : public ExternalImage {
|
||||
AHardwareBuffer* aHardwareBuffer = nullptr;
|
||||
bool sRGB = false;
|
||||
@@ -99,7 +102,7 @@ private:
|
||||
~ExternalImageVulkanAndroid() override;
|
||||
};
|
||||
|
||||
AndroidFrameCallback mAndroidFrameCallback;
|
||||
AndroidDetails& mAndroidDetails;
|
||||
int mOSVersion{};
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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_PLATFORMS_VULKANPLATFORMAPPLE_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanPlatformApple : public VulkanPlatform {
|
||||
protected:
|
||||
ExtensionSet getSwapchainInstanceExtensions() const override;
|
||||
SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept override;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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_PLATFORMS_VULKANPLATFORMLINUX_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanPlatformLinux : public VulkanPlatform {
|
||||
protected:
|
||||
ExtensionSet getSwapchainInstanceExtensions() const override;
|
||||
SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept override;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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_PLATFORMS_VULKANPLATFORMWINDOWS_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanPlatformWindows : public VulkanPlatform {
|
||||
protected:
|
||||
ExtensionSet getSwapchainInstanceExtensions() const override;
|
||||
SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept override;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "private/backend/Driver.h"
|
||||
|
||||
#include <backend/BufferDescriptor.h>
|
||||
#include <backend/BufferObjectStreamDescriptor.h>
|
||||
#include <backend/CallbackHandler.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
namespace filament::backend {
|
||||
|
||||
class BufferDescriptor;
|
||||
class BufferObjectStreamDescriptor;
|
||||
class CallbackHandler;
|
||||
class PixelBufferDescriptor;
|
||||
class Program;
|
||||
|
||||
@@ -392,10 +392,6 @@ DECL_DRIVER_API_N(updateBufferObject,
|
||||
backend::BufferDescriptor&&, data,
|
||||
uint32_t, byteOffset)
|
||||
|
||||
DECL_DRIVER_API_N(registerBufferObjectStreams,
|
||||
backend::BufferObjectHandle, boh,
|
||||
backend::BufferObjectStreamDescriptor&&, streams)
|
||||
|
||||
DECL_DRIVER_API_N(updateBufferObjectUnsynchronized,
|
||||
backend::BufferObjectHandle, boh,
|
||||
backend::BufferDescriptor&&, data,
|
||||
|
||||
@@ -61,6 +61,16 @@ 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,6 +32,7 @@ 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,
|
||||
@@ -51,6 +52,7 @@ 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);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "AndroidNdk.h"
|
||||
#include <backend/platforms/AndroidNdk.h>
|
||||
|
||||
#include <android/hardware_buffer.h>
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
|
||||
#include <android/native_window.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Logger.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
@@ -36,11 +39,23 @@ bool AndroidSwapChainHelper::setPresentFrameId(
|
||||
int const status = NativeWindow::getNextFrameId(anw, &sysFrameId);
|
||||
if (status == 0) {
|
||||
std::lock_guard const lock(mLock);
|
||||
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;
|
||||
// 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();
|
||||
}
|
||||
|
||||
// oldest entry is removed
|
||||
mFrameIdToSystemFrameId.insert(frameId, sysFrameId);
|
||||
return true;
|
||||
|
||||
@@ -139,42 +139,73 @@ bool Platform::queryFrameTimestamps(SwapChain const*, uint64_t, FrameTimestamps*
|
||||
}
|
||||
|
||||
void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept {
|
||||
mInsertBlob = std::move(insertBlob);
|
||||
mRetrieveBlob = std::move(retrieveBlob);
|
||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||
mInsertBlob = std::make_shared<InsertBlobFunc>(std::move(insertBlob));
|
||||
mRetrieveBlob = std::make_shared<RetrieveBlobFunc>(std::move(retrieveBlob));
|
||||
}
|
||||
|
||||
bool Platform::hasInsertBlobFunc() const noexcept {
|
||||
return bool(mInsertBlob);
|
||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||
return mInsertBlob && bool(*mInsertBlob);
|
||||
}
|
||||
|
||||
bool Platform::hasRetrieveBlobFunc() const noexcept {
|
||||
return bool(mRetrieveBlob);
|
||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||
return mRetrieveBlob && bool(*mRetrieveBlob);
|
||||
}
|
||||
|
||||
void Platform::insertBlob(void const* key, size_t keySize, void const* value, size_t valueSize) {
|
||||
if (mInsertBlob) {
|
||||
mInsertBlob(key, keySize, value, valueSize);
|
||||
std::shared_ptr<InsertBlobFunc> callback;
|
||||
{
|
||||
std::unique_lock<decltype(mMutex)> lock(mMutex);
|
||||
callback = mInsertBlob;
|
||||
}
|
||||
if (callback) {
|
||||
(*callback)(key, keySize, value, valueSize);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Platform::retrieveBlob(void const* key, size_t keySize, void* value, size_t valueSize) {
|
||||
if (mRetrieveBlob) {
|
||||
return mRetrieveBlob(key, keySize, value, valueSize);
|
||||
std::shared_ptr<RetrieveBlobFunc> callback;
|
||||
{
|
||||
std::unique_lock<decltype(mMutex)> lock(mMutex);
|
||||
callback = mRetrieveBlob;
|
||||
}
|
||||
if (callback) {
|
||||
return (*callback)(key, keySize, value, valueSize);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Platform::setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept {
|
||||
mDebugUpdateStat = std::move(debugUpdateStat);
|
||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||
mDebugUpdateStat = std::make_shared<DebugUpdateStatFunc>(std::move(debugUpdateStat));
|
||||
}
|
||||
|
||||
bool Platform::hasDebugUpdateStatFunc() const noexcept {
|
||||
return bool(mDebugUpdateStat);
|
||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||
return mDebugUpdateStat && bool(*mDebugUpdateStat);
|
||||
}
|
||||
|
||||
void Platform::debugUpdateStat(const char* key, uint64_t value) {
|
||||
if (mDebugUpdateStat) {
|
||||
mDebugUpdateStat(key, value);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,8 +67,12 @@
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
#if defined(__ANDROID__)
|
||||
#include "backend/platforms/VulkanPlatformAndroid.h"
|
||||
#else
|
||||
#include "backend/platforms/VulkanPlatform.h"
|
||||
#elif defined(__APPLE__)
|
||||
#include "backend/platforms/VulkanPlatformApple.h"
|
||||
#elif defined(__linux__)
|
||||
#include "backend/platforms/VulkanPlatformLinux.h"
|
||||
#elif defined(WIN32)
|
||||
#include "backend/platforms/VulkanPlatformWindows.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -117,8 +121,14 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
#if defined(__ANDROID__)
|
||||
return new VulkanPlatformAndroid();
|
||||
#elif defined(__APPLE__)
|
||||
return new VulkanPlatformApple();
|
||||
#elif defined(__linux__)
|
||||
return new VulkanPlatformLinux();
|
||||
#elif defined(WIN32)
|
||||
return new VulkanPlatformWindows();
|
||||
#else
|
||||
return new VulkanPlatform();
|
||||
return nullptr;
|
||||
#endif
|
||||
#else
|
||||
return nullptr;
|
||||
|
||||
@@ -165,7 +165,7 @@ public:
|
||||
size_t size, bool forceGpuBuffer = false);
|
||||
~MetalBuffer();
|
||||
|
||||
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer || mCpuBuffer; }
|
||||
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer; }
|
||||
|
||||
MetalBuffer(const MetalBuffer& rhs) = delete;
|
||||
MetalBuffer& operator=(const MetalBuffer& rhs) = delete;
|
||||
@@ -185,14 +185,12 @@ 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, or nil if there
|
||||
* is no device allocation.
|
||||
* @return The MTLBuffer representing the current state of the buffer to bind, it never returns
|
||||
* nil.
|
||||
*
|
||||
*/
|
||||
id<MTLBuffer> getGpuBufferForDraw() noexcept;
|
||||
|
||||
void* getCpuBuffer() const noexcept { return mCpuBuffer; }
|
||||
|
||||
void setLabel(const utils::ImmutableCString& label) {
|
||||
#if FILAMENT_METAL_DEBUG_LABELS
|
||||
if (label.empty()) {
|
||||
@@ -235,7 +233,6 @@ private:
|
||||
UploadStrategy mUploadStrategy;
|
||||
TrackedMetalBuffer mBuffer;
|
||||
size_t mBufferSize = 0;
|
||||
void* mCpuBuffer = nullptr;
|
||||
MetalContext& mContext;
|
||||
};
|
||||
|
||||
|
||||
@@ -39,34 +39,30 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
|
||||
mUploadStrategy = UploadStrategy::POOL;
|
||||
}
|
||||
|
||||
// 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.
|
||||
MTLResourceOptions options = MTLResourceStorageModePrivate;
|
||||
|
||||
/*
|
||||
if (size <= 4 * 1024 && bindingType != BufferObjectBinding::SHADER_STORAGE &&
|
||||
usage == BufferUsage::DYNAMIC && !forceGpuBuffer) {
|
||||
mBuffer = nil;
|
||||
mCpuBuffer = malloc(size);
|
||||
return;
|
||||
// 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
|
||||
}
|
||||
*/
|
||||
|
||||
// Otherwise, we allocate a private GPU buffer.
|
||||
{
|
||||
ScopedAllocationTimer timer("generic");
|
||||
mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate],
|
||||
mBuffer = { [context.device newBufferWithLength:size options:options],
|
||||
TrackedMetalBuffer::Type::GENERIC };
|
||||
}
|
||||
|
||||
// mBuffer might fail to be allocated. Clients can check for this by calling
|
||||
// wasAllocationSuccessful().
|
||||
}
|
||||
|
||||
MetalBuffer::~MetalBuffer() {
|
||||
if (mCpuBuffer) {
|
||||
free(mCpuBuffer);
|
||||
}
|
||||
}
|
||||
MetalBuffer::~MetalBuffer() = default;
|
||||
|
||||
void MetalBuffer::copyIntoBuffer(
|
||||
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
|
||||
@@ -83,12 +79,6 @@ 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));
|
||||
@@ -106,11 +96,6 @@ 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();
|
||||
}
|
||||
@@ -171,41 +156,6 @@ 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,6 +17,7 @@
|
||||
#ifndef TNT_METALCONTEXT_H
|
||||
#define TNT_METALCONTEXT_H
|
||||
|
||||
#include "MetalErrorQueue.h"
|
||||
#include "MetalResourceTracker.h"
|
||||
#include "MetalShaderCompiler.h"
|
||||
#include "MetalState.h"
|
||||
@@ -129,6 +130,7 @@ struct MetalContext {
|
||||
id<MTLCommandBuffer> pendingCommandBuffer = nil;
|
||||
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nil;
|
||||
uint32_t currentFrame = 0;
|
||||
MetalErrorQueue commandBufferErrors;
|
||||
|
||||
std::atomic<bool> memorylessLimitsReached = false;
|
||||
|
||||
|
||||
@@ -153,6 +153,7 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
|
||||
|
||||
if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) {
|
||||
logMTLCommandBufferError(errorCode);
|
||||
context->commandBufferErrors.push(buffer.error);
|
||||
}
|
||||
}];
|
||||
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)
|
||||
|
||||
@@ -61,6 +61,8 @@ public:
|
||||
|
||||
MetalContext* getContext() { return mContext; }
|
||||
|
||||
using DriverBase::scheduleDestroy;
|
||||
|
||||
private:
|
||||
|
||||
friend class MetalSwapChain;
|
||||
|
||||
@@ -254,6 +254,18 @@ 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,
|
||||
@@ -1132,8 +1144,12 @@ bool MetalDriver::isRenderTargetFormatSupported(TextureFormat format) {
|
||||
bool MetalDriver::isFrameBufferFetchSupported() {
|
||||
// FrameBuffer fetch is achievable via "programmable blending" in Metal, and only supported on
|
||||
// Apple GPUs with readWriteTextureSupport.
|
||||
return mContext->highestSupportedGpuFamily.apple >= 1 &&
|
||||
mContext->device.readWriteTextureSupport;
|
||||
// On macOS, framebuffer fetch requires MSL 2.3, which is only available with macOS 11.0.
|
||||
if (@available(macOS 11.0, *)) {
|
||||
return mContext->highestSupportedGpuFamily.apple >= 1 &&
|
||||
mContext->device.readWriteTextureSupport;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetalDriver::isFrameBufferFetchMultiSampleSupported() {
|
||||
@@ -1274,10 +1290,6 @@ void MetalDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&
|
||||
scheduleDestroy(std::move(data));
|
||||
}
|
||||
|
||||
void MetalDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh, BufferObjectStreamDescriptor&& streams) {
|
||||
// Noop
|
||||
}
|
||||
|
||||
void MetalDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& data,
|
||||
uint32_t byteOffset) {
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
@@ -2263,7 +2275,10 @@ MemoryMappedBufferHandle MetalDriver::mapBufferS() noexcept {
|
||||
void MetalDriver::mapBufferR(MemoryMappedBufferHandle mmbh,
|
||||
BufferObjectHandle boh, size_t offset,
|
||||
size_t size, MapBufferAccessFlags access, utils::ImmutableCString&& tag) {
|
||||
construct_handle<MetalMemoryMappedBuffer>(mmbh, boh, offset, size, access);
|
||||
assert_invariant(boh);
|
||||
MetalBufferObject* bo = mHandleAllocator.handle_cast<MetalBufferObject*>(boh);
|
||||
assert_invariant(bo);
|
||||
construct_handle<MetalMemoryMappedBuffer>(mmbh, bo, offset, size, access);
|
||||
mHandleAllocator.associateTagToHandle(mmbh.getId(), std::move(tag));
|
||||
}
|
||||
|
||||
@@ -2271,21 +2286,16 @@ 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);
|
||||
|
||||
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);
|
||||
auto* mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
|
||||
mmb->copy(*this, offset, std::move(data));
|
||||
}
|
||||
|
||||
// explicit instantiation of the Dispatcher
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -569,14 +569,22 @@ 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{};
|
||||
uint32_t size = 0;
|
||||
uint32_t offset = 0;
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace backend
|
||||
|
||||
@@ -1664,5 +1664,44 @@ 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,9 +111,22 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
|
||||
id<MTLLibrary> library = nil;
|
||||
switch (program.getShaderLanguage()) {
|
||||
case ShaderLanguage::MSL: {
|
||||
// By default, Metal uses the most recent language version.
|
||||
MTLCompileOptions* options = [MTLCompileOptions new];
|
||||
options.fastMathEnabled = YES;
|
||||
// The docs for MTLCompileOptions say that Metal uses the most
|
||||
// recent language version if it is not specified, but this is
|
||||
// not always true: it is possible for the environment to be set
|
||||
// up in a way where an older version is used by default. So we
|
||||
// explicitly set the minimum version we require.
|
||||
#if defined(FILAMENT_IOS)
|
||||
options.languageVersion = MTLLanguageVersion2_0;
|
||||
#else
|
||||
if (@available(macOS 11.0, *)) {
|
||||
options.languageVersion = MTLLanguageVersion2_3;
|
||||
} else {
|
||||
options.languageVersion = MTLLanguageVersion2_2;
|
||||
}
|
||||
#endif
|
||||
|
||||
assert_invariant(source[source.size() - 1] == '\0');
|
||||
// the shader string is null terminated and the length includes the null character
|
||||
|
||||
@@ -291,8 +291,6 @@ void NoopDriver::updateBufferObject(Handle<HwBufferObject> ibh, BufferDescriptor
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
|
||||
void NoopDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh, BufferObjectStreamDescriptor&& streams) { }
|
||||
|
||||
void NoopDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> ibh, BufferDescriptor&& p,
|
||||
uint32_t byteOffset) {
|
||||
scheduleDestroy(std::move(p));
|
||||
|
||||
@@ -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 = p != &mDefaultVAO &&
|
||||
bool const recreateVaoName = vao != &mDefaultVAO &&
|
||||
((vao->vao[contextIndex] == 0) ||
|
||||
(vao->nameVersion != state.age && contextIndex == 1));
|
||||
if (UTILS_UNLIKELY(recreateVaoName)) {
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
#include <backend/Platform.h>
|
||||
#include <backend/Program.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
#include <backend/BufferObjectStreamDescriptor.h>
|
||||
|
||||
#include "private/backend/CommandStream.h"
|
||||
#include "private/backend/Dispatcher.h"
|
||||
@@ -2837,38 +2836,6 @@ void OpenGLDriver::updateIndexBuffer(
|
||||
CHECK_GL_ERROR()
|
||||
}
|
||||
|
||||
void OpenGLDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh, BufferObjectStreamDescriptor&& streams) {
|
||||
DEBUG_MARKER()
|
||||
|
||||
GLBufferObject const* bo = handle_cast<GLBufferObject*>(boh);
|
||||
mStreamUniformDescriptors[bo->gl.id] = std::move(streams);
|
||||
}
|
||||
|
||||
|
||||
// specialization for mat3f (which has a different alignment, see std140 layout rules)
|
||||
static void copyMat3f(void* addr, size_t const offset, const mat3f& v) noexcept {
|
||||
struct mat43 {
|
||||
float v[3][4];
|
||||
};
|
||||
|
||||
addr = static_cast<char*>(addr) + offset;
|
||||
mat43& temp = *static_cast<mat43*>(addr);
|
||||
|
||||
temp.v[0][0] = v[0][0];
|
||||
temp.v[0][1] = v[0][1];
|
||||
temp.v[0][2] = v[0][2];
|
||||
|
||||
temp.v[1][0] = v[1][0];
|
||||
temp.v[1][1] = v[1][1];
|
||||
temp.v[1][2] = v[1][2];
|
||||
|
||||
temp.v[2][0] = v[2][0];
|
||||
temp.v[2][1] = v[2][1];
|
||||
temp.v[2][2] = v[2][2];
|
||||
|
||||
// don't store anything in temp.v[][3] because there could be uniforms packed there
|
||||
}
|
||||
|
||||
void OpenGLDriver::updateBufferObject(
|
||||
Handle<HwBufferObject> boh, BufferDescriptor&& bd, uint32_t const byteOffset) {
|
||||
DEBUG_MARKER()
|
||||
@@ -2882,19 +2849,6 @@ void OpenGLDriver::updateBufferObject(
|
||||
gl.bindVertexArray(nullptr);
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(!mStreamUniformDescriptors.empty())) {
|
||||
auto const streamDescriptors = mStreamUniformDescriptors.find(bo->gl.id);
|
||||
if (streamDescriptors != mStreamUniformDescriptors.end()) {
|
||||
for (auto const& [offset, stream, associationType] : streamDescriptors->second.mStreams) {
|
||||
if (associationType == BufferObjectStreamAssociationType::TRANSFORM_MATRIX) {
|
||||
auto transform = getStreamTransformMatrix(stream);
|
||||
copyMat3f(bd.buffer, offset, transform);
|
||||
}
|
||||
}
|
||||
mStreamUniformDescriptors.erase(streamDescriptors);
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
|
||||
assert_invariant(bo->gl.buffer);
|
||||
memcpy(static_cast<uint8_t*>(bo->gl.buffer) + byteOffset, bd.buffer, bd.size);
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
#include <backend/Platform.h>
|
||||
#include <backend/Program.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
#include <backend/BufferObjectStreamDescriptor.h>
|
||||
|
||||
#include "private/backend/Driver.h"
|
||||
#include "private/backend/HandleAllocator.h"
|
||||
@@ -394,8 +393,6 @@ private:
|
||||
// the must be accessed from the user thread only
|
||||
std::vector<GLStream*> mStreamsWithPendingAcquiredImage;
|
||||
|
||||
std::unordered_map<GLuint, BufferObjectStreamDescriptor> mStreamUniformDescriptors;
|
||||
|
||||
void attachStream(GLTexture* t, GLStream* stream);
|
||||
void detachStream(GLTexture* t) noexcept;
|
||||
void replaceStream(GLTexture* t, GLStream* stream) noexcept;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#if defined(__ANDROID__) || defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(__EMSCRIPTEN__)
|
||||
#if defined(__ANDROID__) || defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(__EMSCRIPTEN__) || defined(FILAMENT_SUPPORTS_EGL_ON_LINUX)
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
# include <GLES3/gl3.h>
|
||||
|
||||
@@ -28,14 +28,13 @@
|
||||
#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>
|
||||
@@ -99,6 +98,27 @@ 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.
|
||||
@@ -112,17 +132,20 @@ PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmFor
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
PlatformEGLAndroid::PlatformEGLAndroid() noexcept
|
||||
: mExternalStreamManager(ExternalStreamManagerAndroid::create()) {
|
||||
: mExternalStreamManager(ExternalStreamManagerAndroid::create()),
|
||||
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
|
||||
mOSVersion = android_get_device_api_level();
|
||||
if (mOSVersion < 0) {
|
||||
mOSVersion = __ANDROID_API_FUTURE__;
|
||||
}
|
||||
}
|
||||
|
||||
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept = default;
|
||||
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
|
||||
delete &mAndroidDetails;
|
||||
}
|
||||
|
||||
void PlatformEGLAndroid::terminate() noexcept {
|
||||
mAndroidFrameCallback.terminate();
|
||||
mAndroidDetails.androidFrameCallback.terminate();
|
||||
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
|
||||
PlatformEGL::terminate();
|
||||
}
|
||||
@@ -155,20 +178,30 @@ void PlatformEGLAndroid::beginFrame(
|
||||
int64_t const monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs,
|
||||
uint32_t const frameId) noexcept {
|
||||
// associate the user frameid with the system frame id
|
||||
setPresentFrameId(mCurrentDrawSwapChain, frameId);
|
||||
// if frameId is 0, it means we're not associated to a particular frame, which is the case
|
||||
// for standalone views. And in this case we skip the performance hint (since we wouldn't get
|
||||
// the right timing anyway as well as the frame info timing collections)
|
||||
|
||||
if (mPerformanceHintSession.isValid()) {
|
||||
// associate the user frameid with the system frame id.
|
||||
if (frameId && mCurrentDrawSwapChain) {
|
||||
// mCurrentDrawSwapChain could be null if we're called from renderStandaloneView
|
||||
setPresentFrameId(mCurrentDrawSwapChain, frameId);
|
||||
}
|
||||
|
||||
if (frameId && mPerformanceHintSession.isValid()) {
|
||||
if (refreshIntervalNs <= 0) {
|
||||
// we're not provided with a target time, use the display period, if everything fails,
|
||||
// assume 16.67ms
|
||||
refreshIntervalNs = 16'666'667;
|
||||
|
||||
CompositorTiming compositorTiming{};
|
||||
bool const hasCompositorTiming =
|
||||
queryCompositorTiming(mCurrentDrawSwapChain, &compositorTiming);
|
||||
if (hasCompositorTiming && compositorTiming.compositeInterval > 0) {
|
||||
refreshIntervalNs = compositorTiming.compositeInterval;
|
||||
if (mCurrentDrawSwapChain) {
|
||||
// mCurrentDrawSwapChain could be null if we're called from renderStandaloneView
|
||||
CompositorTiming compositorTiming{};
|
||||
bool const hasCompositorTiming =
|
||||
queryCompositorTiming(mCurrentDrawSwapChain, &compositorTiming);
|
||||
if (hasCompositorTiming && compositorTiming.compositeInterval > 0) {
|
||||
refreshIntervalNs = compositorTiming.compositeInterval;
|
||||
}
|
||||
}
|
||||
}
|
||||
mStartTimeOfActualWork = clock::time_point(std::chrono::nanoseconds(monotonic_clock_ns));
|
||||
@@ -207,9 +240,9 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
|
||||
"eglGetNativeClientBufferANDROID"));
|
||||
|
||||
if (ext.egl.ANDROID_presentation_time) {
|
||||
eglGetNativeClientBufferANDROID =
|
||||
PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC(eglGetProcAddress(
|
||||
"eglGetNativeClientBufferANDROID"));
|
||||
eglPresentationTimeANDROID =
|
||||
PFNEGLPRESENTATIONTIMEANDROIDPROC(eglGetProcAddress(
|
||||
"eglPresentationTimeANDROID"));
|
||||
}
|
||||
|
||||
if (ext.egl.ANDROID_get_frame_timestamps) {
|
||||
@@ -240,7 +273,7 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
|
||||
|
||||
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
|
||||
|
||||
mAndroidFrameCallback.init();
|
||||
mAndroidDetails.androidFrameCallback.init();
|
||||
|
||||
return driver;
|
||||
}
|
||||
@@ -264,15 +297,25 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
|
||||
}
|
||||
|
||||
AndroidFrameCallback::Timeline const preferredTimeline{
|
||||
mAndroidFrameCallback.getPreferredTimeline() };
|
||||
mAndroidDetails.androidFrameCallback.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 false;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::array<EGLnsecsANDROID, 3> values;
|
||||
@@ -283,26 +326,16 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
|
||||
};
|
||||
EGLBoolean const success = eglGetCompositorTimingANDROID(getEglDisplay(), sur,
|
||||
names.size(), names.data(), values.data());
|
||||
if (!success) {
|
||||
return false;
|
||||
if (UTILS_UNLIKELY(!success)) {
|
||||
// reset current error to EGL_SUCCESS
|
||||
eglGetError();
|
||||
} else {
|
||||
outCompositorTiming->compositeDeadline = values[0];
|
||||
outCompositorTiming->compositeInterval = values[1];
|
||||
outCompositorTiming->compositeToPresentLatency = values[2];
|
||||
}
|
||||
outCompositorTiming->compositeDeadline = values[0];
|
||||
outCompositorTiming->compositeInterval = values[1];
|
||||
outCompositorTiming->compositeToPresentLatency = values[2];
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlatformEGLAndroid::setPresentFrameId(SwapChain const* swapchain,
|
||||
@@ -327,6 +360,10 @@ 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) {
|
||||
@@ -347,7 +384,9 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
|
||||
};
|
||||
EGLBoolean const success = eglGetFrameTimestampsANDROID(getEglDisplay(), sur, hwFrameId,
|
||||
names.size(), names.data(), values.data());
|
||||
if (!success) {
|
||||
if (UTILS_UNLIKELY(!success)) {
|
||||
// reset current error to EGL_SUCCESS
|
||||
eglGetError();
|
||||
return false;
|
||||
}
|
||||
outFrameTimestamps->requestedPresentTime = values[0];
|
||||
@@ -361,28 +400,44 @@ 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;
|
||||
}
|
||||
|
||||
@@ -395,6 +450,10 @@ Platform::SwapChain* PlatformEGLAndroid::createSwapChain(
|
||||
void PlatformEGLAndroid::destroySwapChain(SwapChain* swapChain) noexcept {
|
||||
if (swapChain) {
|
||||
SwapChainEGLAndroid* const sc = static_cast<SwapChainEGLAndroid*>(swapChain);
|
||||
if (mCurrentDrawSwapChain == sc) {
|
||||
// don't keep a dangling pointer around
|
||||
mCurrentDrawSwapChain = nullptr;
|
||||
}
|
||||
sc->terminate(*this);
|
||||
delete sc;
|
||||
}
|
||||
@@ -675,12 +734,12 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage const sou
|
||||
|
||||
|
||||
bool PlatformEGLAndroid::isProducerThrottlingControlSupported() const {
|
||||
return mProducerThrottling.isSupported();
|
||||
return mAndroidDetails.producerThrottling.isSupported();
|
||||
}
|
||||
|
||||
int32_t PlatformEGLAndroid::setProducerThrottlingEnabled(
|
||||
EGLNativeWindowType const nativeWindow, bool const enabled) const {
|
||||
return mProducerThrottling.setProducerThrottlingEnabled(nativeWindow, enabled);
|
||||
return mAndroidDetails.producerThrottling.setProducerThrottlingEnabled(nativeWindow, enabled);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
@@ -704,8 +763,6 @@ 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 "opengl/GLUtils.h"
|
||||
#include <bluegl/BlueGL.h>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
@@ -35,16 +35,31 @@ PlatformEGLHeadless::PlatformEGLHeadless() noexcept
|
||||
}
|
||||
|
||||
bool PlatformEGLHeadless::isOpenGL() const noexcept {
|
||||
return true;
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL)
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // defined(BACKEND_OPENGL_VERSION_GL)
|
||||
}
|
||||
|
||||
backend::Driver* PlatformEGLHeadless::createDriver(void* sharedContext,
|
||||
const Platform::DriverConfig& driverConfig) {
|
||||
EGLBoolean bindAPI = eglBindAPI(EGL_OPENGL_API);
|
||||
if (UTILS_UNLIKELY(!bindAPI)) {
|
||||
LOG(ERROR) << "eglBindAPI EGL_OPENGL_API failed";
|
||||
auto bindApiHelper = [](EGLenum api, const char* errorString) -> bool {
|
||||
EGLBoolean bindAPI = eglBindAPI(api);
|
||||
if (UTILS_UNLIKELY(bindAPI == EGL_FALSE || bindAPI == EGL_BAD_PARAMETER)) {
|
||||
logEglError(errorString);
|
||||
eglReleaseThread();
|
||||
return false;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
EGLenum api = isOpenGL() ? EGL_OPENGL_API : EGL_OPENGL_ES_API;
|
||||
const char* apiString = isOpenGL() ? "eglBindAPI EGL_OPENGL_API" : "eglBindAPI EGL_OPENGL_ES_API";
|
||||
if (!bindApiHelper(api, apiString)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int bindBlueGL = bluegl::bind();
|
||||
if (UTILS_UNLIKELY(bindBlueGL != 0)) {
|
||||
LOG(ERROR) << "bluegl bind failed";
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
#include <backend/BufferDescriptor.h>
|
||||
#include <backend/BufferObjectStreamDescriptor.h>
|
||||
#include <backend/DescriptorSetOffsetArray.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/PipelineState.h>
|
||||
@@ -434,10 +433,6 @@ io::ostream& operator<<(io::ostream& out, BufferDescriptor const& b) {
|
||||
<< ", user=" << b.getUser() << " }";
|
||||
}
|
||||
|
||||
io::ostream& operator<<(io::ostream& out, const BufferObjectStreamDescriptor& b) {
|
||||
return out << "BufferObjectStreamDescriptor{ streams(" << b.mStreams.size() << ")=... }";
|
||||
}
|
||||
|
||||
io::ostream& operator<<(io::ostream& out, PixelBufferDescriptor const& b) {
|
||||
BufferDescriptor const& base = static_cast<BufferDescriptor const&>(b);
|
||||
return out << "PixelBufferDescriptor{ " << base
|
||||
|
||||
@@ -62,8 +62,9 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c
|
||||
// a memcpy if its available.
|
||||
bool const isStaticOrShared =
|
||||
any(mUsage & (BufferUsage::STATIC | BufferUsage::SHARED_WRITE_BIT));
|
||||
bool const useMemcpy = (isUniformAndAvailable || isStaticOrShared) && isMemcopyable &&
|
||||
mStagingBufferBypassEnabled;
|
||||
bool const useMemcpy =
|
||||
((isUniformAndAvailable && mStagingBufferBypassEnabled) || isStaticOrShared) &&
|
||||
isMemcopyable;
|
||||
if (useMemcpy) {
|
||||
char* dest = static_cast<char*>(mBuffer->getGpuBuffer()->allocationInfo.pMappedData) +
|
||||
byteOffset;
|
||||
|
||||
@@ -214,4 +214,10 @@ 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,6 +154,10 @@ public:
|
||||
return mStagingBufferBypassEnabled;
|
||||
}
|
||||
|
||||
inline bool pipelineCreationFeedbackSupported() const noexcept {
|
||||
return mPipelineCreationFeedbackSupported;
|
||||
}
|
||||
|
||||
private:
|
||||
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
|
||||
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
|
||||
@@ -181,6 +185,7 @@ private:
|
||||
bool mProtectedMemorySupported = false;
|
||||
bool mIsUnifiedMemoryArchitecture = false;
|
||||
bool mStagingBufferBypassEnabled = false;
|
||||
bool mPipelineCreationFeedbackSupported = false;
|
||||
|
||||
fvkutils::VkFormatList mDepthStencilFormats;
|
||||
fvkutils::VkFormatList mBlittableDepthStencilFormats;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanConstants.h"
|
||||
#include "utils/compiler.h"
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Panic.h>
|
||||
@@ -31,6 +32,18 @@ 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;
|
||||
|
||||
@@ -304,8 +317,7 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
|
||||
// This code actually binds the descriptor sets.
|
||||
auto set = updateSets[index];
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
VkDescriptorSet vkset = useExternalSamplers[index] ? set->getExternalSamplerVkSet() :
|
||||
set->getVkSet();
|
||||
VkDescriptorSet const vkset = set->getVkSet();
|
||||
commands->acquire(set);
|
||||
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
|
||||
1, &vkset, set->uniqueDynamicUboCount, set->getOffsets()->data());
|
||||
@@ -341,17 +353,39 @@ 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::updateSamplerImpl(VkDescriptorSet vkset, uint8_t binding,
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept {
|
||||
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();
|
||||
VkImageSubresourceRange range = texture->getPrimaryViewRange();
|
||||
VkImageViewType const expectedType = texture->getViewType();
|
||||
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
|
||||
@@ -376,19 +410,6 @@ void VulkanDescriptorSetCache::updateSamplerImpl(VkDescriptorSet vkset, uint8_t
|
||||
.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);
|
||||
}
|
||||
|
||||
@@ -405,7 +426,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->bitmask.dynamicUbo, layout->count.dynamicUbo,
|
||||
mResourceManager, handle, layout,
|
||||
[vkSet, count, vklayout, this](
|
||||
VulkanDescriptorSet*) { this->manualRecycle(count, vklayout, vkSet); },
|
||||
vkSet);
|
||||
@@ -428,4 +449,22 @@ 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,7 +55,8 @@ public:
|
||||
VkDeviceSize size) noexcept;
|
||||
|
||||
void updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept;
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler,
|
||||
VkDescriptorSetLayout externalSamplerLayout = VK_NULL_HANDLE) noexcept;
|
||||
|
||||
void updateSamplerForExternalSamplerSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture) noexcept;
|
||||
@@ -90,8 +91,8 @@ public:
|
||||
void resetCachedState() noexcept { mLastBoundInfo = {}; }
|
||||
|
||||
private:
|
||||
void updateSamplerImpl(VkDescriptorSet set, uint8_t binding,
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept;
|
||||
void copySet(VkDescriptorSet srcSet, VkDescriptorSet destSet,
|
||||
fvkutils::SamplerBitmask copyBindings) const;
|
||||
|
||||
class DescriptorInfinitePool;
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#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,7 +232,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
|
||||
mPlatform->getGraphicsQueueFamilyIndex(), mPlatform->getProtectedGraphicsQueue(),
|
||||
mPlatform->getProtectedGraphicsQueueFamilyIndex(), mContext, &mSemaphoreManager),
|
||||
mPipelineLayoutCache(mPlatform->getDevice()),
|
||||
mPipelineCache(mPlatform->getDevice()),
|
||||
mPipelineCache(mPlatform->getDevice(), mContext),
|
||||
mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()),
|
||||
mBufferCache(mContext, mResourceManager, mAllocator),
|
||||
mFramebufferCache(mPlatform->getDevice()),
|
||||
@@ -244,6 +245,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
|
||||
mQueryManager(mPlatform->getDevice()),
|
||||
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
|
||||
&mDescriptorSetLayoutCache),
|
||||
mStreamedImageManager(&mExternalImageManager),
|
||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
|
||||
mIsMSAASwapChainSupported(false), // TODO: support MSAA swapchain
|
||||
mStereoscopicType(driverConfig.stereoscopicType) {
|
||||
@@ -420,12 +422,14 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs, uint32_t frameId) {
|
||||
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
|
||||
|
||||
if (mCurrentSwapChain) { // This should be guaranteed
|
||||
// if frameId is 0, it means we're not associated to a particular frame, which is the case
|
||||
// for standalone views. And in this case we skip the frame info timing collection.
|
||||
if (frameId && mCurrentSwapChain) { // This should be guaranteed
|
||||
mPlatform->setPresentFrameId(mCurrentSwapChain->swapChain, frameId);
|
||||
}
|
||||
|
||||
// Check if any command have finished and reset all its used resources. The resources
|
||||
// wont be destroyed but their reference count will decreased if the command is already
|
||||
// won't be destroyed but their reference count will be decreased if the command is already
|
||||
// completed.
|
||||
//
|
||||
// This will let us check if any VulkanBuffer is currently in flight or not.
|
||||
@@ -479,6 +483,9 @@ 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,
|
||||
@@ -486,6 +493,7 @@ void VulkanDriver::updateDescriptorSetTexture(
|
||||
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
|
||||
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
|
||||
mExternalImageManager.clearTextureBinding(set, binding);
|
||||
mStreamedImageManager.unbindStreamedTexture(set, binding);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,10 +505,7 @@ void VulkanDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_
|
||||
assert_invariant(any(mmb->access & MapBufferAccessFlags::WRITE_BIT));
|
||||
assert_invariant(offset + data.size <= mmb->size);
|
||||
|
||||
// TODO: this isa zero-effort implementation of copyToMemoryMappedBuffer(), where we just
|
||||
// call updateBufferObject(). This could be a fallback implementation for when
|
||||
// shared memory is not available.
|
||||
// On UMA systems, this should just be a memcpy into the memory-mapped buffer.
|
||||
// On UMA systems, this is a memcpy into the memory-mapped buffer, otherwise use a staging buffer.
|
||||
updateBufferObject(mmb->boh, std::move(data), mmb->offset + offset);
|
||||
}
|
||||
|
||||
@@ -921,14 +926,6 @@ 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();
|
||||
@@ -1116,6 +1113,12 @@ 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) {
|
||||
@@ -1153,18 +1156,38 @@ 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) {
|
||||
return {};
|
||||
// @TODO This is still not thread-safe. We might have to revisit this question.
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto handle = mResourceManager.allocHandle<VulkanStream>();
|
||||
auto stream = resource_ptr<VulkanStream>::make(&mResourceManager, handle);
|
||||
stream.inc();
|
||||
return handle;
|
||||
}
|
||||
|
||||
void VulkanDriver::setAcquiredImage(Handle<HwStream> sh, void* image, const math::mat3f& transform,
|
||||
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) {
|
||||
@@ -1172,6 +1195,70 @@ 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) {
|
||||
@@ -1467,17 +1554,13 @@ 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));
|
||||
@@ -1545,6 +1628,12 @@ TimerQueryResult VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint
|
||||
}
|
||||
|
||||
void VulkanDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto t = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
|
||||
assert_invariant(t);
|
||||
auto s = resource_ptr<VulkanStream>::cast(&mResourceManager, sh);
|
||||
assert_invariant(s);
|
||||
t->setStream(s);
|
||||
}
|
||||
|
||||
void VulkanDriver::generateMipmaps(Handle<HwTexture> th) {
|
||||
|
||||
@@ -34,12 +34,14 @@
|
||||
#include "vulkan/VulkanDescriptorSetCache.h"
|
||||
#include "vulkan/VulkanDescriptorSetLayoutCache.h"
|
||||
#include "vulkan/VulkanExternalImageManager.h"
|
||||
#include "vulkan/VulkanStreamedImageManager.h"
|
||||
#include "vulkan/VulkanPipelineLayoutCache.h"
|
||||
#include "vulkan/memory/ResourceManager.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
#include "vulkan/utils/Definitions.h"
|
||||
|
||||
#include "backend/DriverEnums.h"
|
||||
#include "backend/BufferObjectStreamDescriptor.h"
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include "private/backend/Driver.h"
|
||||
@@ -48,10 +50,6 @@
|
||||
#include <utils/Allocator.h>
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include "AndroidNativeWindow.h"
|
||||
#endif
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanPlatform;
|
||||
@@ -161,6 +159,8 @@ private:
|
||||
VulkanDescriptorSetCache mDescriptorSetCache;
|
||||
VulkanQueryManager mQueryManager;
|
||||
VulkanExternalImageManager mExternalImageManager;
|
||||
VulkanStreamedImageManager mStreamedImageManager;
|
||||
|
||||
|
||||
// This maps a VulkanSwapchain to a native swapchain. VulkanSwapchain should have a copy of the
|
||||
// Platform::Swapchain pointer, but queryFrameTimestamps() and queryCompositorTiming() are
|
||||
@@ -206,9 +206,10 @@ private:
|
||||
bool const mIsSRGBSwapChainSupported;
|
||||
bool const mIsMSAASwapChainSupported;
|
||||
backend::StereoscopicType const mStereoscopicType;
|
||||
#if defined(__ANDROID__)
|
||||
AndroidProducerThrottling mProducerThrottling;
|
||||
#endif
|
||||
|
||||
// setAcquiredImage is a DECL_DRIVER_API_SYNCHRONOUS_N which means we don't necessarily have the
|
||||
// data to process it at call time. So we store it and process it during updateStreams.
|
||||
std::vector<resource_ptr<VulkanStream>> mStreamsWithPendingAcquiredImage;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -31,9 +31,6 @@ 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);
|
||||
@@ -50,31 +47,6 @@ 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,
|
||||
@@ -141,6 +113,9 @@ 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;
|
||||
@@ -172,38 +147,14 @@ 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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
layout->setExternalSamplerVkLayout(newLayout);
|
||||
// Update the external samplers in the set
|
||||
for (auto& [binding, sampler, image]: samplerAndBindings) {
|
||||
mDescriptorSetCache->updateSamplerForExternalSamplerSet(set, binding, image);
|
||||
// We cannot call updateSamplerForExternalSamplerSet because some samplers are non NULL
|
||||
// (RGB) and we cannot do a combined update with a NULL sampler.
|
||||
mDescriptorSetCache->updateSampler(set, binding, image, sampler, newLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,13 +212,25 @@ void VulkanExternalImageManager::bindExternallySampledTexture(
|
||||
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams) {
|
||||
// Should we do duplicate validation here?
|
||||
auto& imageData = findImage(mImages, image);
|
||||
mSetBindings.push_back({ bindingPoint, imageData.image, set, samplerParams });
|
||||
auto itr = std::find_if(mSetBindings.begin(), mSetBindings.end(),
|
||||
[&](SetBindingInfo const& binding) {
|
||||
return (binding.set == set && binding.binding == bindingPoint);
|
||||
});
|
||||
if (itr == mSetBindings.end()) {
|
||||
mSetBindings.push_back({ bindingPoint, imageData.image, set, samplerParams });
|
||||
} else {
|
||||
// override the image data in the binding point
|
||||
itr->image = image;
|
||||
itr->samplerParams = samplerParams;
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::addExternallySampledTexture(
|
||||
fvkmemory::resource_ptr<VulkanTexture> image,
|
||||
Platform::ExternalImageHandleRef platformHandleRef) {
|
||||
mImages.push_back({ image, platformHandleRef, false });
|
||||
// By passing VK_NULL_HANDLE which is already there by default.
|
||||
// We make it clear that all default images do NOT have a chroma conversion.
|
||||
mImages.push_back({ image, platformHandleRef, false, VK_NULL_HANDLE });
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::removeExternallySampledTexture(
|
||||
|
||||
@@ -31,7 +31,7 @@ class VulkanSamplerCache;
|
||||
class VulkanDescriptorSetLayoutCache;
|
||||
class VulkanDescriptorSetCache;
|
||||
|
||||
// Manages the logic of external images and their quirks wrt Vulikan.
|
||||
// Manages the logic of external images and their quirks wrt Vulkan.
|
||||
class VulkanExternalImageManager {
|
||||
public:
|
||||
|
||||
|
||||
@@ -40,6 +40,11 @@ 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:
|
||||
@@ -192,15 +197,70 @@ VulkanDescriptorSetLayout::Bitmask VulkanDescriptorSetLayout::Bitmask::fromLayou
|
||||
return fromBackendLayout(layout);
|
||||
}
|
||||
|
||||
// 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.
|
||||
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));
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#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"
|
||||
|
||||
@@ -57,6 +58,8 @@ inline uint8_t collapsedCount(Bitmask const& mask) {
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class VulkanDescriptorSetCache;
|
||||
|
||||
struct VulkanBufferObject;
|
||||
|
||||
struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Resource {
|
||||
@@ -159,38 +162,14 @@ public:
|
||||
using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
|
||||
|
||||
VulkanDescriptorSet(
|
||||
fvkutils::UniformBufferBitmask const& dynamicUboMask,
|
||||
uint8_t uniqueDynamicUboCount,
|
||||
OnRecycle&& onRecycleFn, VkDescriptorSet vkSet)
|
||||
: dynamicUboMask(dynamicUboMask),
|
||||
uniqueDynamicUboCount(uniqueDynamicUboCount),
|
||||
mVkSet(vkSet),
|
||||
mOnRecycleFn(std::move(onRecycleFn)) {}
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout,
|
||||
OnRecycle&& onRecycleFn, VkDescriptorSet vkSet);
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-exception-escape)
|
||||
~VulkanDescriptorSet() {
|
||||
if (mOnRecycleFn) {
|
||||
mOnRecycleFn(this);
|
||||
}
|
||||
if (mOnRecycleExternalSamplerFn) {
|
||||
mOnRecycleExternalSamplerFn(this);
|
||||
}
|
||||
}
|
||||
~VulkanDescriptorSet();
|
||||
|
||||
VkDescriptorSet getVkSet() const noexcept {
|
||||
return mVkSet;
|
||||
}
|
||||
|
||||
VkDescriptorSet getExternalSamplerVkSet() const noexcept {
|
||||
return mExternalSamplerVkSet;
|
||||
}
|
||||
|
||||
void setExternalSamplerVkSet(VkDescriptorSet vkset, OnRecycle onRecycle) {
|
||||
mExternalSamplerVkSet = vkset;
|
||||
if (mOnRecycleExternalSamplerFn) {
|
||||
mOnRecycleExternalSamplerFn(this);
|
||||
}
|
||||
mOnRecycleExternalSamplerFn = onRecycle;
|
||||
return mSets[mCurrentSetIndex].vkSet;
|
||||
}
|
||||
|
||||
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
|
||||
@@ -211,17 +190,33 @@ public:
|
||||
|
||||
void referencedBy(VulkanCommandBuffer& commands);
|
||||
|
||||
fvkutils::UniformBufferBitmask const dynamicUboMask;
|
||||
bool isBound() const {
|
||||
return bool(mSets[mCurrentSetIndex].fenceStatus);
|
||||
}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> getLayout() const { return mLayout; }
|
||||
|
||||
fvkutils::UniformBufferBitmask const& dynamicUboMask;
|
||||
uint8_t const uniqueDynamicUboCount;
|
||||
|
||||
private:
|
||||
VkDescriptorSet const mVkSet;
|
||||
VkDescriptorSet mExternalSamplerVkSet = VK_NULL_HANDLE;
|
||||
friend class VulkanDescriptorSetCache;
|
||||
|
||||
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;
|
||||
OnRecycle mOnRecycleFn;
|
||||
OnRecycle mOnRecycleExternalSamplerFn;
|
||||
uint8_t mCurrentSetIndex;
|
||||
std::vector<InternalVkSet> mSets;
|
||||
fvkutils::UniformBufferBitmask mUboMask;
|
||||
};
|
||||
|
||||
|
||||
@@ -34,8 +34,41 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPipelineCache::VulkanPipelineCache(VkDevice device)
|
||||
: mDevice(device) {
|
||||
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) {
|
||||
VkPipelineCacheCreateInfo createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
|
||||
};
|
||||
@@ -158,9 +191,12 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
.alphaToCoverageEnable = raster.alphaToCoverageEnable,
|
||||
.alphaToOneEnable = VK_FALSE,
|
||||
};
|
||||
bool const enableDepthTest =
|
||||
raster.depthCompareOp != SamplerCompareFunc::A ||
|
||||
raster.depthWriteEnable;
|
||||
VkPipelineDepthStencilStateCreateInfo vkDs = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||
.depthTestEnable = VK_TRUE,
|
||||
.depthTestEnable = enableDepthTest ? VK_TRUE : VK_FALSE,
|
||||
.depthWriteEnable = raster.depthWriteEnable,
|
||||
.depthCompareOp = fvkutils::getCompareOp(raster.depthCompareOp),
|
||||
.depthBoundsTestEnable = VK_FALSE,
|
||||
@@ -216,15 +252,40 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
}
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = ("
|
||||
<< shaderStages[0].module << ", " << shaderStages[1].module << ")";
|
||||
#endif
|
||||
#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
|
||||
PipelineCacheEntry cacheEntry = {
|
||||
.lastUsed = mCurrentTime,
|
||||
};
|
||||
VkResult error = vkCreateGraphicsPipelines(mDevice, mPipelineCache, 1, &pipelineCreateInfo,
|
||||
VKALLOC, &cacheEntry.handle);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" << shaderStages[0].module << ", "
|
||||
<< shaderStages[1].module << ")";
|
||||
|
||||
if (mContext.pipelineCreationFeedbackSupported()) {
|
||||
printPipelineFeedbackInfo(feedbackInfo);
|
||||
}
|
||||
#endif
|
||||
|
||||
assert_invariant(error == VK_SUCCESS);
|
||||
if (error != VK_SUCCESS) {
|
||||
FVK_LOGE << "vkCreateGraphicsPipelines error " << error;
|
||||
|
||||
@@ -86,7 +86,7 @@ public:
|
||||
|
||||
static_assert(sizeof(RasterState) == 16, "RasterState must not have implicit padding.");
|
||||
|
||||
VulkanPipelineCache(VkDevice device);
|
||||
VulkanPipelineCache(VkDevice device, VulkanContext const& context);
|
||||
|
||||
void bindLayout(VkPipelineLayout layout) noexcept;
|
||||
|
||||
@@ -210,6 +210,8 @@ private:
|
||||
|
||||
// Current bindings for the pipeline and descriptor sets.
|
||||
PipelineKey mBoundPipeline = {};
|
||||
|
||||
[[maybe_unused]] VulkanContext const& mContext;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "VulkanStreamedImageManager.h"
|
||||
|
||||
#include "VulkanDescriptorSetCache.h"
|
||||
#include "VulkanExternalImageManager.h"
|
||||
#include "VulkanDescriptorSetLayoutCache.h"
|
||||
#include "VulkanSamplerCache.h"
|
||||
|
||||
namespace filament::backend {
|
||||
VulkanStreamedImageManager::VulkanStreamedImageManager(VulkanExternalImageManager* manager)
|
||||
: mExternalImageManager(manager) {}
|
||||
|
||||
VulkanStreamedImageManager::~VulkanStreamedImageManager() = default;
|
||||
|
||||
void VulkanStreamedImageManager::terminate() { mStreamedTexturesBindings.clear(); }
|
||||
|
||||
void VulkanStreamedImageManager::bindStreamedTexture(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t bindingPoint, fvkmemory::resource_ptr<VulkanTexture> image,
|
||||
SamplerParams samplerParams) {
|
||||
mStreamedTexturesBindings.push_back({ bindingPoint, image, set, samplerParams });
|
||||
}
|
||||
|
||||
void VulkanStreamedImageManager::unbindStreamedTexture(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t bindingPoint) {
|
||||
auto iter = std::remove_if(mStreamedTexturesBindings.begin(), mStreamedTexturesBindings.end(),
|
||||
[&](StreamedTextureBinding& binding) {
|
||||
return ((binding.set == set) && (binding.binding == bindingPoint));
|
||||
});
|
||||
mStreamedTexturesBindings.erase(iter, mStreamedTexturesBindings.end());
|
||||
}
|
||||
|
||||
void VulkanStreamedImageManager::onStreamAcquireImage(fvkmemory::resource_ptr<VulkanTexture> image,
|
||||
fvkmemory::resource_ptr<VulkanStream> stream) {
|
||||
for (StreamedTextureBinding const& data: mStreamedTexturesBindings) {
|
||||
// Find the right stream
|
||||
if (data.image->getStream() == stream) {
|
||||
mExternalImageManager->bindExternallySampledTexture(data.set, data.binding, image,
|
||||
data.samplerParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANSTREAMEDIMAGEMANAGER_H
|
||||
#define TNT_FILAMENT_BACKEND_CACHING_VULKANSTREAMEDIMAGEMANAGER_H
|
||||
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanExternalImageManager;
|
||||
class VulkanDescriptorSetCache;
|
||||
class VulkanSamplerCache;
|
||||
|
||||
// Manages the logic of streamed images.
|
||||
class VulkanStreamedImageManager {
|
||||
public:
|
||||
VulkanStreamedImageManager(
|
||||
VulkanExternalImageManager* manager);
|
||||
~VulkanStreamedImageManager();
|
||||
void terminate();
|
||||
|
||||
public:
|
||||
void bindStreamedTexture(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t bindingPoint,
|
||||
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams);
|
||||
void unbindStreamedTexture(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t bindingPoint);
|
||||
void onStreamAcquireImage(fvkmemory::resource_ptr<VulkanTexture> image,
|
||||
fvkmemory::resource_ptr<VulkanStream> stream);
|
||||
|
||||
private:
|
||||
struct StreamedTextureBinding {
|
||||
uint8_t binding = 0;
|
||||
fvkmemory::resource_ptr<VulkanTexture> image;
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set;
|
||||
SamplerParams samplerParams;
|
||||
};
|
||||
// keep track of all the stream bindings
|
||||
std::vector<StreamedTextureBinding> mStreamedTexturesBindings;
|
||||
|
||||
VulkanExternalImageManager* mExternalImageManager;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_CACHING_VULKANSTREAMEDIMAGEMANAGER_H
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanTexture.h"
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
@@ -105,6 +106,11 @@ void VulkanSwapChain::update() {
|
||||
}
|
||||
|
||||
void VulkanSwapChain::present(DriverBase& driver) {
|
||||
// The last acquire failed, so just skip presenting.
|
||||
if (!mAcquired) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mHeadless && mTransitionSwapChainImageLayoutForPresent) {
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkImageSubresourceRange const subresources{
|
||||
@@ -161,7 +167,15 @@ void VulkanSwapChain::acquire(bool& resized) {
|
||||
|
||||
VulkanPlatform::ImageSyncData imageSyncData;
|
||||
VkResult const result = mPlatform->acquire(swapChain, &imageSyncData);
|
||||
|
||||
if (result != VK_SUCCESS) {
|
||||
// We just don't set mAcquired here so the next present will just skip.
|
||||
FVK_LOGD << "Failed to acquire next image in the swapchain result=" << (int) result;
|
||||
return;
|
||||
}
|
||||
|
||||
mCurrentSwapIndex = imageSyncData.imageIndex;
|
||||
assert_invariant(mCurrentSwapIndex < mFinishedDrawing.size());
|
||||
mFinishedDrawing[mCurrentSwapIndex] = {};
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
|
||||
<< "Cannot acquire in swapchain. error=" << static_cast<int32_t>(result);
|
||||
|
||||
@@ -36,6 +36,43 @@ 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,
|
||||
@@ -74,6 +111,8 @@ 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;
|
||||
@@ -105,6 +144,7 @@ private:
|
||||
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
|
||||
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
|
||||
|
||||
|
||||
friend struct VulkanTexture;
|
||||
};
|
||||
|
||||
@@ -182,6 +222,14 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
||||
return mState->mSidecarMSAA;
|
||||
}
|
||||
|
||||
void setStream(fvkmemory::resource_ptr<VulkanStream> stream) {
|
||||
mState->mStream = stream;
|
||||
}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanStream> getStream() const {
|
||||
return mState->mStream;
|
||||
}
|
||||
|
||||
bool isTransientAttachment() const {
|
||||
return mState->mUsage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ 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 {
|
||||
@@ -104,6 +105,9 @@ 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;
|
||||
}
|
||||
|
||||
@@ -149,6 +153,8 @@ std::string_view getTypeStr(ResourceType type) {
|
||||
return "VulkanMemoryMappedBuffer";
|
||||
case ResourceType::SEMAPHORE:
|
||||
return "Semaphore";
|
||||
case ResourceType::STREAM:
|
||||
return "VulkanStream";
|
||||
case ResourceType::UNDEFINED_TYPE:
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -55,7 +55,8 @@ enum class ResourceType : uint8_t {
|
||||
SYNC = 17,
|
||||
MEMORY_MAPPED_BUFFER = 18,
|
||||
SEMAPHORE = 19,
|
||||
UNDEFINED_TYPE = 20, // Must be the last enum because we use it for iterating over the enums.
|
||||
STREAM = 20,
|
||||
UNDEFINED_TYPE = 21, // Must be the last enum because we use it for iterating over the enums.
|
||||
};
|
||||
|
||||
template<typename D>
|
||||
|
||||
@@ -123,6 +123,9 @@ 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,6 +228,10 @@ 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
|
||||
@@ -865,6 +869,8 @@ 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;
|
||||
@@ -873,13 +879,16 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
|
||||
// Check the availability of lazily allocated memory
|
||||
context.mLazilyAllocatedMemorySupported = false;
|
||||
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;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1022,13 +1031,8 @@ VkExternalFenceHandleTypeFlagBits VulkanPlatform::getFenceExportFlags() const no
|
||||
return static_cast<VkExternalFenceHandleTypeFlagBits>(0);
|
||||
}
|
||||
|
||||
ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const {
|
||||
return getSwapchainInstanceExtensionsImpl();
|
||||
bool VulkanPlatform::isTransientAttachmentSupported() const noexcept {
|
||||
return mImpl->mContext.isLazilyAllocatedMemorySupported();
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) const noexcept {
|
||||
return createVkSurfaceKHRImpl(nativeWindow, instance, flags);
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <backend/platforms/VulkanPlatformAndroid.h>
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
@@ -20,6 +21,8 @@
|
||||
#include "vulkan/platform/VulkanPlatformSwapChainImpl.h"
|
||||
#include "vulkan/utils/Image.h"
|
||||
|
||||
#include "AndroidFrameCallback.h"
|
||||
|
||||
#include <private/backend/BackendUtilsAndroid.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
@@ -32,6 +35,7 @@
|
||||
#include <android/hardware_buffer.h>
|
||||
#include <android/native_window.h>
|
||||
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
using namespace bluevk;
|
||||
@@ -158,7 +162,55 @@ std::pair<TextureFormat, TextureUsage> getFilamentFormatAndUsage(const AHardware
|
||||
};
|
||||
}
|
||||
|
||||
}// namespace
|
||||
uint32_t selectMemoryTypeForExternalImage(VkPhysicalDevice physicalDevice, VkDevice device,
|
||||
VkImage image, uint32_t types, VkFlags requiredMemoryFlags) {
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties;
|
||||
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
|
||||
uint32_t const memoryTypeIndex =
|
||||
VulkanContext::selectMemoryType(memoryProperties, types, requiredMemoryFlags);
|
||||
|
||||
if constexpr (FVK_RENDERDOC_CAPTURE_MODE) {
|
||||
// RenderDoc will replay external resources as non-external.
|
||||
// Adjust properties that will trip it up when replaying, even though these are not valid.
|
||||
// Update memory type index if necessary so that we can replay the capture.
|
||||
|
||||
VkMemoryRequirements imageMemoryRequirements;
|
||||
vkGetImageMemoryRequirements(device, image, &imageMemoryRequirements);
|
||||
|
||||
uint32_t const imageMemoryTypeBits = imageMemoryRequirements.memoryTypeBits;
|
||||
bool const isMemoryTypeSupported = ((1 << memoryTypeIndex) & imageMemoryTypeBits) != 0;
|
||||
if (isMemoryTypeSupported) {
|
||||
return memoryTypeIndex;
|
||||
}
|
||||
|
||||
// Current memory type will not be replayable by RenderDoc.
|
||||
// Attempt to change the memory type index
|
||||
VkMemoryPropertyFlags const kRenderDocFallBackReqs = 0;
|
||||
uint32_t commonMemoryTypeBits = types & imageMemoryTypeBits;
|
||||
uint32_t commonTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
|
||||
commonMemoryTypeBits, kRenderDocFallBackReqs);
|
||||
if (commonMemoryTypeBits && commonTypeIndex != VK_MAX_MEMORY_TYPES) {
|
||||
return commonTypeIndex;
|
||||
}
|
||||
uint32_t imageTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
|
||||
imageMemoryTypeBits, kRenderDocFallBackReqs);
|
||||
if (imageTypeIndex != VK_MAX_MEMORY_TYPES) {
|
||||
return imageTypeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return memoryTypeIndex;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
struct VulkanPlatformAndroid::AndroidDetails {
|
||||
AndroidFrameCallback androidFrameCallback;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() {
|
||||
if (__builtin_available(android 26, *)) {
|
||||
@@ -168,14 +220,19 @@ VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid()
|
||||
}
|
||||
}
|
||||
|
||||
VulkanPlatformAndroid::VulkanPlatformAndroid() {
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
VulkanPlatformAndroid::VulkanPlatformAndroid() :
|
||||
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
|
||||
mOSVersion = android_get_device_api_level();
|
||||
if (mOSVersion < 0) {
|
||||
mOSVersion = __ANDROID_API_FUTURE__;
|
||||
}
|
||||
}
|
||||
|
||||
VulkanPlatformAndroid::~VulkanPlatformAndroid() noexcept = default;
|
||||
VulkanPlatformAndroid::~VulkanPlatformAndroid() noexcept {
|
||||
delete &mAndroidDetails;
|
||||
}
|
||||
|
||||
Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
|
||||
AHardwareBuffer const* buffer, bool sRGB) noexcept {
|
||||
@@ -354,8 +411,6 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
|
||||
.image = image,
|
||||
.buffer = VK_NULL_HANDLE,
|
||||
};
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties;
|
||||
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
|
||||
VkMemoryPropertyFlags requiredMemoryFlags =
|
||||
!isExternal && any(metadata.filamentUsage & TextureUsage::UPLOADABLE)
|
||||
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
|
||||
@@ -365,8 +420,8 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
|
||||
requiredMemoryFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT;
|
||||
}
|
||||
|
||||
uint32_t const memoryTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
|
||||
metadata.memoryTypeBits, requiredMemoryFlags);
|
||||
uint32_t const memoryTypeIndex = selectMemoryTypeForExternalImage(physicalDevice, device,
|
||||
image, metadata.memoryTypeBits, requiredMemoryFlags);
|
||||
|
||||
VkMemoryAllocateInfo const allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
@@ -459,14 +514,14 @@ int VulkanPlatformAndroid::getOSVersion() const noexcept {
|
||||
}
|
||||
|
||||
void VulkanPlatformAndroid::terminate() {
|
||||
mAndroidFrameCallback.terminate();
|
||||
mAndroidDetails.androidFrameCallback.terminate();
|
||||
VulkanPlatform::terminate();
|
||||
}
|
||||
|
||||
Driver* VulkanPlatformAndroid::createDriver(void* sharedContext, DriverConfig const& driverConfig) {
|
||||
Driver* driver = VulkanPlatform::createDriver(sharedContext, driverConfig);
|
||||
if (driver) {
|
||||
mAndroidFrameCallback.init();
|
||||
mAndroidDetails.androidFrameCallback.init();
|
||||
}
|
||||
return driver;
|
||||
}
|
||||
@@ -482,13 +537,19 @@ bool VulkanPlatformAndroid::queryCompositorTiming(SwapChain const* swapchain,
|
||||
}
|
||||
|
||||
AndroidFrameCallback::Timeline const preferredTimeline{
|
||||
mAndroidFrameCallback.getPreferredTimeline() };
|
||||
mAndroidDetails.androidFrameCallback.getPreferredTimeline() };
|
||||
outCompositorTiming->frameTime = preferredTimeline.frameTime;
|
||||
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
|
||||
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
|
||||
outCompositorTiming->compositeDeadline = CompositorTiming::INVALID;
|
||||
outCompositorTiming->compositeInterval = CompositorTiming::INVALID;
|
||||
outCompositorTiming->compositeToPresentLatency = CompositorTiming::INVALID;
|
||||
|
||||
// From this point on, we always return "success" because some timings were returned.
|
||||
|
||||
auto vulkanSwapchain = static_cast<VulkanPlatformSwapChainBase const *>(swapchain);
|
||||
return vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
|
||||
vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanPlatformAndroid::setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept {
|
||||
@@ -502,13 +563,4 @@ bool VulkanPlatformAndroid::queryFrameTimestamps(SwapChain const* swapchain, uin
|
||||
return vulkanSwapchain->queryFrameTimestamps(frameId, outFrameTimestamps);
|
||||
}
|
||||
|
||||
|
||||
// Deprecated platform dependent helper methods
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { return {}; }
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
return SurfaceBundle{};
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -14,10 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
#include <backend/platforms/VulkanPlatformApple.h>
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
#include "vulkan/VulkanDriverFactory.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
@@ -36,15 +35,15 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
|
||||
VulkanPlatform::ExtensionSet VulkanPlatformApple::getSwapchainInstanceExtensions() const {
|
||||
ExtensionSet const ret = {
|
||||
VK_EXT_METAL_SURFACE_EXTENSION_NAME,
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatformApple::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) const noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
CAMetalLayer* mlayer = (__bridge CAMetalLayer*) nativeWindow;
|
||||
FILAMENT_CHECK_POSTCONDITION(mlayer) << "Unable to obtain Metal-backed layer.";
|
||||
|
||||
+5
-32
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
* 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.
|
||||
@@ -14,11 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
#include <backend/platforms/VulkanPlatformLinux.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
#include "vulkan/VulkanDriverFactory.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
@@ -74,17 +73,13 @@
|
||||
void* library = nullptr;
|
||||
} g_x11_vk;
|
||||
}// anonymous namespace
|
||||
#elif defined(WIN32)
|
||||
// No platform specific includes
|
||||
#else
|
||||
// Not a supported Vulkan platform
|
||||
#endif
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
|
||||
VulkanPlatform::ExtensionSet VulkanPlatformLinux::getSwapchainInstanceExtensions() const {
|
||||
VulkanPlatform::ExtensionSet const ret = {
|
||||
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
||||
VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
|
||||
@@ -95,15 +90,13 @@ VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl(
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
VK_KHR_XLIB_SURFACE_EXTENSION_NAME,
|
||||
#endif
|
||||
#elif defined(WIN32)
|
||||
VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
|
||||
#endif
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatformLinux::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) const noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
|
||||
// On certain platforms, the extent of the surface cannot be queried from Vulkan. In those
|
||||
@@ -182,26 +175,6 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativ
|
||||
<< "vkCreateXlibSurfaceKHR error=" << static_cast<int32_t>(result);
|
||||
}
|
||||
#endif
|
||||
#elif defined(WIN32)
|
||||
// On (at least) NVIDIA drivers, the Vulkan implementation (specifically the call to
|
||||
// vkGetPhysicalDeviceSurfaceCapabilitiesKHR()) does not correctly handle the fact that
|
||||
// each native window has its own DPI_AWARENESS_CONTEXT, and erroneously uses the context
|
||||
// of the calling thread. As a workaround, we set the current thread's DPI_AWARENESS_CONTEXT
|
||||
// to that of the native window we've been given. This isn't a perfect solution, because an
|
||||
// application could create swap chains on multiple native windows with varying DPI-awareness,
|
||||
// but even then, at least one of the windows would be guaranteed to work correctly.
|
||||
SetThreadDpiAwarenessContext(GetWindowDpiAwarenessContext((HWND) nativeWindow));
|
||||
|
||||
VkWin32SurfaceCreateInfoKHR const createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
|
||||
.hinstance = GetModuleHandle(nullptr),
|
||||
.hwnd = (HWND) nativeWindow,
|
||||
};
|
||||
VkResult const result = vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr,
|
||||
(VkSurfaceKHR*) &surface);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkCreateWin32SurfaceKHR failed."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
#endif
|
||||
return std::make_tuple(surface, extent);
|
||||
}
|
||||
@@ -165,6 +165,11 @@ VulkanPlatformSurfaceSwapChain::~VulkanPlatformSurfaceSwapChain() {
|
||||
VkResult VulkanPlatformSurfaceSwapChain::create() {
|
||||
#ifdef __ANDROID__
|
||||
NativeWindow::enableFrameTimestamps(static_cast<ANativeWindow*>(mNativeWindow), true);
|
||||
// on Android, disable producer throttling
|
||||
if (mProducerThrottling.isSupported()) {
|
||||
mProducerThrottling.setProducerThrottlingEnabled(
|
||||
static_cast<ANativeWindow*>(mNativeWindow), false);
|
||||
}
|
||||
#endif
|
||||
|
||||
VkSurfaceFormatKHR surfaceFormat = {};
|
||||
@@ -356,13 +361,15 @@ bool VulkanPlatformSurfaceSwapChain::queryCompositorTiming(
|
||||
CompositorTiming* outCompositorTiming) const {
|
||||
#ifdef __ANDROID__
|
||||
// fallback to private APIs
|
||||
int const status = NativeWindow::getCompositorTiming(
|
||||
static_cast<ANativeWindow*>(mNativeWindow),
|
||||
&outCompositorTiming->compositeDeadline,
|
||||
&outCompositorTiming->compositeInterval,
|
||||
&outCompositorTiming->compositeToPresentLatency);
|
||||
if (status == 0) {
|
||||
return true;
|
||||
if (UTILS_VERY_LIKELY(mNativeWindow)) {
|
||||
int const status = NativeWindow::getCompositorTiming(
|
||||
static_cast<ANativeWindow*>(mNativeWindow),
|
||||
&outCompositorTiming->compositeDeadline,
|
||||
&outCompositorTiming->compositeInterval,
|
||||
&outCompositorTiming->compositeToPresentLatency);
|
||||
if (status == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return VulkanPlatformSwapChainBase::queryCompositorTiming(outCompositorTiming);
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <AndroidSwapChainHelper.h>
|
||||
#include "AndroidSwapChainHelper.h"
|
||||
#include "AndroidNativeWindow.h"
|
||||
#endif
|
||||
|
||||
#include <unordered_map>
|
||||
@@ -122,6 +123,7 @@ private:
|
||||
|
||||
#ifdef __ANDROID__
|
||||
AndroidSwapChainHelper mImpl{};
|
||||
AndroidProducerThrottling mProducerThrottling;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 <backend/platforms/VulkanPlatformWindows.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#if defined(WIN32)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatformWindows::getSwapchainInstanceExtensions() const {
|
||||
VulkanPlatform::ExtensionSet const ret = {
|
||||
VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatformWindows::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) const noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
VkExtent2D extent;
|
||||
|
||||
// On (at least) NVIDIA drivers, the Vulkan implementation (specifically the call to
|
||||
// vkGetPhysicalDeviceSurfaceCapabilitiesKHR()) does not correctly handle the fact that
|
||||
// each native window has its own DPI_AWARENESS_CONTEXT, and erroneously uses the context
|
||||
// of the calling thread. As a workaround, we set the current thread's DPI_AWARENESS_CONTEXT
|
||||
// to that of the native window we've been given. This isn't a perfect solution, because an
|
||||
// application could create swap chains on multiple native windows with varying DPI-awareness,
|
||||
// but even then, at least one of the windows would be guaranteed to work correctly.
|
||||
SetThreadDpiAwarenessContext(GetWindowDpiAwarenessContext((HWND) nativeWindow));
|
||||
|
||||
VkWin32SurfaceCreateInfoKHR const createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
|
||||
.hinstance = GetModuleHandle(nullptr),
|
||||
.hwnd = (HWND) nativeWindow,
|
||||
};
|
||||
VkResult const result = vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr,
|
||||
(VkSurfaceKHR*) &surface);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkCreateWin32SurfaceKHR failed."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
return std::make_tuple(surface, VkExtent2D{});
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -18,6 +18,12 @@
|
||||
|
||||
#include "vulkan/VulkanTexture.h"
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include "backend/platforms/VulkanPlatformAndroid.h"
|
||||
#include <android/hardware_buffer.h>
|
||||
#include <android/native_window.h>
|
||||
#endif
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/debug.h>
|
||||
@@ -234,6 +240,19 @@ uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask) {
|
||||
return mostSignificantBit((sampleCount - 1) & mask);
|
||||
}
|
||||
|
||||
filament::backend::Platform::ExternalImageHandle createExternalImageFromRaw(
|
||||
filament::backend::VulkanPlatform* platform, void* image, bool sRGB) {
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
if (__builtin_available(android 26, *)) {
|
||||
return static_cast<filament::backend::VulkanPlatformAndroid*>(platform)
|
||||
->createExternalImage(reinterpret_cast<AHardwareBuffer const*>(image), sRGB);
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
} // namespace filament::backend::fvkutils
|
||||
|
||||
bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define TNT_FILAMENT_BACKEND_VULKAN_UTILS_IMAGE_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
#include <utils/Log.h>
|
||||
|
||||
@@ -106,6 +107,9 @@ VkImageAspectFlags getImageAspect(VkFormat format);
|
||||
|
||||
uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask);
|
||||
|
||||
Platform::ExternalImageHandle createExternalImageFromRaw(filament::backend::VulkanPlatform* platform,
|
||||
void* image,
|
||||
bool sRGB);
|
||||
} // namespace fvkutils
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "WebGPUConstants.h"
|
||||
#include "WebGPUQueueManager.h"
|
||||
#include "WebGPUStagePool.h"
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include <backend/BufferDescriptor.h>
|
||||
@@ -65,7 +66,7 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
|
||||
// of 4 by padding with zeros.
|
||||
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||
const uint32_t byteOffset, wgpu::Device const& device,
|
||||
WebGPUQueueManager* const webGPUQueueManager) {
|
||||
WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool) {
|
||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
|
||||
<< "updateGPUBuffer called with a null buffer";
|
||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
|
||||
@@ -85,15 +86,12 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||
const size_t stagingBufferSize =
|
||||
remainder == 0 ? bufferDescriptor.size : mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
|
||||
|
||||
// create a staging buffer
|
||||
wgpu::BufferDescriptor descriptor{
|
||||
.label = "Filament WebGPU Staging Buffer",
|
||||
.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
|
||||
.size = stagingBufferSize,
|
||||
.mappedAtCreation = true };
|
||||
wgpu::Buffer stagingBuffer = device.CreateBuffer(&descriptor);
|
||||
wgpu::Buffer stagingBuffer = webGPUStagePool->acquireBuffer(stagingBufferSize,
|
||||
webGPUQueueManager->getLatestSubmissionState());
|
||||
|
||||
void* mappedRange = stagingBuffer.GetMappedRange();
|
||||
assert_invariant(mappedRange);
|
||||
|
||||
memcpy(mappedRange, bufferDescriptor.buffer, bufferDescriptor.size);
|
||||
|
||||
// Make sure the padded memory is set to 0 to have deterministic behaviors
|
||||
@@ -106,7 +104,9 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||
|
||||
// Copy the staging buffer contents to the destination buffer.
|
||||
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stagingBuffer, 0, mBuffer,
|
||||
byteOffset, stagingBufferSize);
|
||||
byteOffset,
|
||||
remainder == 0 ? bufferDescriptor.size
|
||||
: mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace filament::backend {
|
||||
|
||||
class BufferDescriptor;
|
||||
class WebGPUQueueManager;
|
||||
class WebGPUStagePool;
|
||||
|
||||
/**
|
||||
* A base class for WebGPU buffer objects, providing common functionality for creating and
|
||||
@@ -40,7 +41,7 @@ public:
|
||||
* ensures the calls happen in the expected sequence.
|
||||
*/
|
||||
void updateGPUBuffer(BufferDescriptor const&, uint32_t byteOffset, wgpu::Device const& device,
|
||||
WebGPUQueueManager* const webGPUQueueManager);
|
||||
WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool);
|
||||
|
||||
[[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; }
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
|
||||
mAdapter{ mPlatform.requestAdapter(nullptr) },
|
||||
mDevice{ mPlatform.requestDevice(mAdapter) },
|
||||
mQueueManager{ mDevice },
|
||||
mStagePool{ mDevice },
|
||||
mPipelineLayoutCache{ mDevice },
|
||||
mPipelineCache{ mDevice },
|
||||
mRenderPassMipmapGenerator{ mDevice, &mQueueManager },
|
||||
@@ -177,6 +178,9 @@ void WebGPUDriver::endFrame(const uint32_t /* frameId */) {
|
||||
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
|
||||
mCurrentDescriptorSets[i] = {};
|
||||
}
|
||||
|
||||
// Garbage collection (if necessary)
|
||||
mStagePool.gc();
|
||||
}
|
||||
|
||||
// If a command encoder is in flight then the encoder is finished and submitted to the GPU queue.
|
||||
@@ -651,11 +655,6 @@ void WebGPUDriver::setAcquiredImage(Handle<HwStream> sh, void* image, const math
|
||||
//todo
|
||||
}
|
||||
|
||||
void WebGPUDriver::registerBufferObjectStreams(Handle<HwBufferObject> bufferObjectHandle,
|
||||
BufferObjectStreamDescriptor&& streams) {
|
||||
//todo
|
||||
}
|
||||
|
||||
void WebGPUDriver::setStreamDimensions(Handle<HwStream> sh, uint32_t width, uint32_t height) {
|
||||
//todo
|
||||
}
|
||||
@@ -856,7 +855,7 @@ void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
|
||||
// draw calls are made.
|
||||
flush();
|
||||
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
|
||||
scheduleDestroy(std::move(bufferDescriptor));
|
||||
}
|
||||
|
||||
@@ -867,14 +866,14 @@ void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
|
||||
// draw calls are made.
|
||||
flush();
|
||||
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
|
||||
scheduleDestroy(std::move(bufferDescriptor));
|
||||
}
|
||||
|
||||
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
|
||||
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
|
||||
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
|
||||
scheduleDestroy(std::move(bufferDescriptor));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "webgpu/WebGPUPipelineLayoutCache.h"
|
||||
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
|
||||
#include "webgpu/WebGPUQueueManager.h"
|
||||
#include "webgpu/WebGPUStagePool.h"
|
||||
#include "webgpu/utils/AsyncTaskCounter.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
@@ -81,6 +82,7 @@ private:
|
||||
wgpu::Device mDevice = nullptr;
|
||||
wgpu::Limits mDeviceLimits = {};
|
||||
WebGPUQueueManager mQueueManager;
|
||||
WebGPUStagePool mStagePool;
|
||||
void* mNativeWindow = nullptr;
|
||||
WebGPUSwapChain* mSwapChain = nullptr;
|
||||
uint64_t mNextFakeHandle = 1;
|
||||
|
||||
@@ -64,6 +64,7 @@ wgpu::CommandEncoder WebGPUQueueManager::getCommandEncoder() {
|
||||
};
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
ASSERT_POSTCONDITION(mCommandEncoder, "Failed to create command encoder.");
|
||||
mLatestSubmissionState = std::make_shared<WebGPUSubmissionState>();
|
||||
}
|
||||
return mCommandEncoder;
|
||||
}
|
||||
@@ -94,8 +95,6 @@ void WebGPUQueueManager::submit() {
|
||||
return;
|
||||
}
|
||||
|
||||
mLatestSubmissionState = std::make_shared<WebGPUSubmissionState>();
|
||||
|
||||
wgpu::CommandBufferDescriptor commandBufferDescriptor{
|
||||
.label = "Filament Command Buffer",
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user