Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f14ebba87 |
@@ -21,9 +21,8 @@ jobs:
|
|||||||
- name: Run update script
|
- name: Run update script
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
||||||
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
|
||||||
run: |
|
run: |
|
||||||
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py)
|
GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | python3 test/renderdiff/src/commit_msg.py)
|
||||||
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
||||||
if [[ "${GOLDEN_BRANCH}" != "main" ]]; then
|
if [[ "${GOLDEN_BRANCH}" != "main" ]]; then
|
||||||
git config --global user.email "filament.bot@gmail.com"
|
git config --global user.email "filament.bot@gmail.com"
|
||||||
|
|||||||
@@ -134,17 +134,18 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||||
run: |
|
run: |
|
||||||
|
ls ./gltf/Models
|
||||||
TEST_DIR=test/renderdiff
|
TEST_DIR=test/renderdiff
|
||||||
source ${TEST_DIR}/src/preamble.sh
|
source ${TEST_DIR}/src/preamble.sh
|
||||||
set -eux
|
start_
|
||||||
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 ${TEST_DIR}/src/commit_msg.py)
|
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 ${TEST_DIR}/src/commit_msg.py)
|
||||||
bash ${TEST_DIR}/generate.sh
|
bash ${TEST_DIR}/generate.sh && \
|
||||||
python3 ${TEST_DIR}/src/golden_manager.py \
|
python3 ${TEST_DIR}/src/golden_manager.py \
|
||||||
--branch=${GOLDEN_BRANCH} \
|
--branch=${GOLDEN_BRANCH} \
|
||||||
--output=${GOLDEN_OUTPUT_DIR}
|
--output=${GOLDEN_OUTPUT_DIR}
|
||||||
|
|
||||||
# Note that we need to upload the output even if comparison fails, so we undo `set -eux`
|
# Note that we need to upload the output even if comparison fails, so we undo `set -ex`
|
||||||
set +eux
|
end_
|
||||||
|
|
||||||
python3 ${TEST_DIR}/src/compare.py \
|
python3 ${TEST_DIR}/src/compare.py \
|
||||||
--src=${GOLDEN_OUTPUT_DIR} \
|
--src=${GOLDEN_OUTPUT_DIR} \
|
||||||
@@ -157,7 +158,6 @@ jobs:
|
|||||||
cat compare_output.txt >> "$GITHUB_OUTPUT"
|
cat compare_output.txt >> "$GITHUB_OUTPUT"
|
||||||
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
shell: bash
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: presubmit-renderdiff-result
|
name: presubmit-renderdiff-result
|
||||||
|
|||||||
+2
-4
@@ -872,9 +872,7 @@ add_subdirectory(${LIBRARIES}/utils)
|
|||||||
add_subdirectory(${LIBRARIES}/viewer)
|
add_subdirectory(${LIBRARIES}/viewer)
|
||||||
add_subdirectory(${FILAMENT}/shaders)
|
add_subdirectory(${FILAMENT}/shaders)
|
||||||
add_subdirectory(${EXTERNAL}/abseil/tnt)
|
add_subdirectory(${EXTERNAL}/abseil/tnt)
|
||||||
# Add zstd before basisu to force it to use the external zstd target,
|
add_subdirectory(${EXTERNAL}/basisu/tnt)
|
||||||
# preventing a duplicate symbol conflict with its bundled version.
|
|
||||||
add_subdirectory(${EXTERNAL}/zstd/tnt)
|
|
||||||
add_subdirectory(${EXTERNAL}/civetweb/tnt)
|
add_subdirectory(${EXTERNAL}/civetweb/tnt)
|
||||||
add_subdirectory(${EXTERNAL}/imgui/tnt)
|
add_subdirectory(${EXTERNAL}/imgui/tnt)
|
||||||
add_subdirectory(${EXTERNAL}/robin-map/tnt)
|
add_subdirectory(${EXTERNAL}/robin-map/tnt)
|
||||||
@@ -888,7 +886,7 @@ add_subdirectory(${EXTERNAL}/jsmn/tnt)
|
|||||||
add_subdirectory(${EXTERNAL}/stb/tnt)
|
add_subdirectory(${EXTERNAL}/stb/tnt)
|
||||||
add_subdirectory(${EXTERNAL}/getopt)
|
add_subdirectory(${EXTERNAL}/getopt)
|
||||||
add_subdirectory(${EXTERNAL}/perfetto/tnt)
|
add_subdirectory(${EXTERNAL}/perfetto/tnt)
|
||||||
add_subdirectory(${EXTERNAL}/basisu/tnt)
|
add_subdirectory(${EXTERNAL}/zstd/tnt)
|
||||||
|
|
||||||
|
|
||||||
# Note that this has to be placed after mikktspace in order for combine_static_libs to work.
|
# Note that this has to be placed after mikktspace in order for combine_static_libs to work.
|
||||||
|
|||||||
@@ -6,3 +6,7 @@
|
|||||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||||
|
|
||||||
## Release notes for next branch cut
|
## Release notes for next branch cut
|
||||||
|
|
||||||
|
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
|
||||||
|
|
||||||
|
- materials: Make Material Instances' UBO descriptor use dynamic offsets. [⚠️ **Recompile Materials**]
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.google.android.filament:filament-android:1.68.2'
|
implementation 'com.google.android.filament:filament-android:1.67.0'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
|||||||
iOS projects can use CocoaPods to install the latest release:
|
iOS projects can use CocoaPods to install the latest release:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pod 'Filament', '~> 1.68.2'
|
pod 'Filament', '~> 1.67.0'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|||||||
@@ -7,19 +7,6 @@ A new header is inserted each time a *tag* is created.
|
|||||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||||
|
|
||||||
## v1.68.2
|
|
||||||
|
|
||||||
- Support `setPresentationTime` with the Metal backend.
|
|
||||||
|
|
||||||
## v1.68.1
|
|
||||||
|
|
||||||
|
|
||||||
## v1.68.0
|
|
||||||
|
|
||||||
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
|
|
||||||
- Metal: report GPU errors to the platform via `debugUpdateStat` (b/431665753).
|
|
||||||
- materials: Make Material Instances' UBO descriptor use dynamic offsets. [⚠️ **Recompile Materials**]
|
|
||||||
|
|
||||||
## v1.67.1
|
## v1.67.1
|
||||||
|
|
||||||
- Metal: Add support for the `SwapChain::CONFIG_MSAA_4_SAMPLES` flag.
|
- Metal: Add support for the `SwapChain::CONFIG_MSAA_4_SAMPLES` flag.
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ buildscript {
|
|||||||
'kotlin': '2.0.21',
|
'kotlin': '2.0.21',
|
||||||
'kotlin_coroutines': '1.9.0',
|
'kotlin_coroutines': '1.9.0',
|
||||||
'buildTools': '35.0.0',
|
'buildTools': '35.0.0',
|
||||||
'ndk': '29.0.14206865',
|
'ndk': '27.0.11718014',
|
||||||
'androidx_core': '1.13.1',
|
'androidx_core': '1.13.1',
|
||||||
'androidx_annotations': '1.9.0'
|
'androidx_annotations': '1.9.0'
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ using namespace filament;
|
|||||||
|
|
||||||
extern "C" JNIEXPORT jlong JNICALL
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass,
|
Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass,
|
||||||
jlong nativeEngine, jobject buffer_, jint size, jint shBandCount, jint shadowQuality, jint uboBatchingMode) {
|
jlong nativeEngine, jobject buffer_, jint size, jint shBandCount, jint shadowQuality) {
|
||||||
Engine* engine = (Engine*) nativeEngine;
|
Engine* engine = (Engine*) nativeEngine;
|
||||||
AutoBuffer buffer(env, buffer_, size);
|
AutoBuffer buffer(env, buffer_, size);
|
||||||
auto builder = Material::Builder();
|
auto builder = Material::Builder();
|
||||||
@@ -33,7 +33,6 @@ Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass,
|
|||||||
builder.sphericalHarmonicsBandCount(shBandCount);
|
builder.sphericalHarmonicsBandCount(shBandCount);
|
||||||
}
|
}
|
||||||
builder.shadowSamplingQuality((Material::Builder::ShadowSamplingQuality)shadowQuality);
|
builder.shadowSamplingQuality((Material::Builder::ShadowSamplingQuality)shadowQuality);
|
||||||
builder.uboBatching((Material::UboBatchingMode)uboBatchingMode);
|
|
||||||
Material* material = builder
|
Material* material = builder
|
||||||
.package(buffer.getData(), buffer.getSize())
|
.package(buffer.getData(), buffer.getSize())
|
||||||
.build(*engine);
|
.build(*engine);
|
||||||
|
|||||||
@@ -261,20 +261,6 @@ public class Material {
|
|||||||
LOW
|
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 {
|
public static class UserVariantFilterBit {
|
||||||
/** Directional lighting */
|
/** Directional lighting */
|
||||||
public static int DIRECTIONAL_LIGHTING = 0x01;
|
public static int DIRECTIONAL_LIGHTING = 0x01;
|
||||||
@@ -386,7 +372,6 @@ public class Material {
|
|||||||
private int mSize;
|
private int mSize;
|
||||||
private int mShBandCount = 0;
|
private int mShBandCount = 0;
|
||||||
private ShadowSamplingQuality mShadowSamplingQuality = ShadowSamplingQuality.LOW;
|
private ShadowSamplingQuality mShadowSamplingQuality = ShadowSamplingQuality.LOW;
|
||||||
private UboBatchingMode mUboBatchingMode = UboBatchingMode.DEFAULT;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -431,17 +416,6 @@ public class Material {
|
|||||||
return this;
|
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.
|
* Creates and returns the Material object.
|
||||||
*
|
*
|
||||||
@@ -454,7 +428,7 @@ public class Material {
|
|||||||
@NonNull
|
@NonNull
|
||||||
public Material build(@NonNull Engine engine) {
|
public Material build(@NonNull Engine engine) {
|
||||||
long nativeMaterial = nBuilderBuild(engine.getNativeObject(),
|
long nativeMaterial = nBuilderBuild(engine.getNativeObject(),
|
||||||
mBuffer, mSize, mShBandCount, mShadowSamplingQuality.ordinal(), mUboBatchingMode.ordinal());
|
mBuffer, mSize, mShBandCount, mShadowSamplingQuality.ordinal());
|
||||||
if (nativeMaterial == 0) throw new IllegalStateException("Couldn't create Material");
|
if (nativeMaterial == 0) throw new IllegalStateException("Couldn't create Material");
|
||||||
return new Material(nativeMaterial);
|
return new Material(nativeMaterial);
|
||||||
}
|
}
|
||||||
@@ -1120,7 +1094,7 @@ public class Material {
|
|||||||
mNativeObject = 0;
|
mNativeObject = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount, int shadowQuality, int uboBatchingMode);
|
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount, int shadowQuality);
|
||||||
private static native long nCreateInstance(long nativeMaterial);
|
private static native long nCreateInstance(long nativeMaterial);
|
||||||
private static native long nCreateInstanceWithName(long nativeMaterial, @NonNull String name);
|
private static native long nCreateInstanceWithName(long nativeMaterial, @NonNull String name);
|
||||||
private static native long nGetDefaultInstance(long nativeMaterial);
|
private static native long nGetDefaultInstance(long nativeMaterial);
|
||||||
|
|||||||
-35
@@ -16,9 +16,6 @@
|
|||||||
|
|
||||||
package com.google.android.filament.utils
|
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.MotionEvent
|
||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
import android.view.SurfaceView
|
import android.view.SurfaceView
|
||||||
@@ -29,7 +26,6 @@ import com.google.android.filament.android.UiHelper
|
|||||||
import com.google.android.filament.gltfio.*
|
import com.google.android.filament.gltfio.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.nio.Buffer
|
import java.nio.Buffer
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
private const val kNearPlane = 0.05f // 5 cm
|
private const val kNearPlane = 0.05f // 5 cm
|
||||||
private const val kFarPlane = 1000.0f // 1 km
|
private const val kFarPlane = 1000.0f // 1 km
|
||||||
@@ -123,8 +119,6 @@ class ModelViewer(
|
|||||||
private val target = DoubleArray(3)
|
private val target = DoubleArray(3)
|
||||||
private val upward = DoubleArray(3)
|
private val upward = DoubleArray(3)
|
||||||
|
|
||||||
private var debugFrameCallback: ((Bitmap) -> Unit)? = null
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
renderer = engine.createRenderer()
|
renderer = engine.createRenderer()
|
||||||
scene = engine.createScene()
|
scene = engine.createScene()
|
||||||
@@ -311,39 +305,10 @@ class ModelViewer(
|
|||||||
// Render the scene, unless the renderer wants to skip the frame.
|
// Render the scene, unless the renderer wants to skip the frame.
|
||||||
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
||||||
renderer.render(view)
|
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()
|
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) {
|
private fun populateScene(asset: FilamentAsset) {
|
||||||
val rcm = engine.renderableManager
|
val rcm = engine.renderableManager
|
||||||
var count = 0
|
var count = 0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
GROUP=com.google.android.filament
|
GROUP=com.google.android.filament
|
||||||
VERSION_NAME=1.68.2
|
VERSION_NAME=1.67.0
|
||||||
|
|
||||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
# Unless explicitly specified, NDK version will be selected as highest available version within same major release chain
|
||||||
FILAMENT_NDK_VERSION=$(cat `dirname $0`/build/common/versions | grep GITHUB_NDK_VERSION | sed s/GITHUB_NDK_VERSION=//g | cut -f 1 -d ".")
|
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION:-$(cat `dirname $0`/build/common/versions | grep GITHUB_NDK_VERSION | sed s/GITHUB_NDK_VERSION=//g | cut -f 1 -d ".")}
|
||||||
|
|
||||||
# Internal variables
|
# Internal variables
|
||||||
ISSUE_CLEAN=false
|
ISSUE_CLEAN=false
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ if [[ "$GITHUB_WORKFLOW" ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Unless explicitly specified, NDK version will be selected as highest available version within same major release chain
|
# Unless explicitly specified, NDK version will be set to match exactly the required one
|
||||||
FILAMENT_NDK_VERSION=$(cat `dirname $0`/../common/versions | grep GITHUB_NDK_VERSION | sed s/GITHUB_NDK_VERSION=//g)
|
FILAMENT_NDK_VERSION=${GITHUB_NDK_VERSION:-27.0.11718014}
|
||||||
|
|
||||||
(! grep "${FILAMENT_NDK_VERSION}" `dirname $0`/../../android/build.gradle > /dev/null) &&
|
(! 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" &&
|
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_NINJA_VERSION=1.10.2
|
||||||
GITHUB_MESA_VERSION=24.2.1
|
GITHUB_MESA_VERSION=24.2.1
|
||||||
GITHUB_LLVM_VERSION=16
|
GITHUB_LLVM_VERSION=16
|
||||||
GITHUB_NDK_VERSION=29.0.14206865
|
GITHUB_NDK_VERSION=27.0.11718014
|
||||||
GITHUB_EMSDK_VERSION=3.1.60
|
GITHUB_EMSDK_VERSION=3.1.60
|
||||||
GITHUB_VULKANSDK_VERSION=1.4.321.0
|
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
|
# C_FLAGS += -Wl,-pie
|
||||||
# CXX_FLAGS += -lstdc++
|
# CXX_FLAGS += -lstdc++
|
||||||
|
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE -march=armv8-a -mtune=cortex-a78" CACHE STRING "Toolchain CFLAGS")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE -mcpu=cortex-a57" CACHE STRING "Toolchain CFLAGS")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS}" CACHE STRING "Toolchain CXXFLAGS")
|
set(CMAKE_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_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")
|
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
|
# for hardfp: CFLAGS must have -mhard-float
|
||||||
# LDFLAGS must have -Wl,--no-warn-mismatch
|
# LDFLAGS must have -Wl,--no-warn-mismatch
|
||||||
#
|
#
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=neon-vfpv4 -fPIE" CACHE STRING "Toolchain CFLAGS")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mthumb -march=armv7-a -mcpu=cortex-a15 -mfloat-abi=softfp -mfpu=neon-vfpv4 -fPIE" CACHE STRING "Toolchain CFLAGS")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS}" CACHE STRING "Toolchain CXXFLAGS")
|
set(CMAKE_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_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")
|
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 {
|
dependencies {
|
||||||
implementation 'com.google.android.filament:filament-android:1.68.0'
|
implementation 'com.google.android.filament:filament-android:1.66.2'
|
||||||
}
|
}
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||||
@@ -196,7 +196,7 @@ dependencies {
|
|||||||
</div>
|
</div>
|
||||||
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
||||||
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
||||||
<pre><code class="language-shell">pod 'Filament', '~> 1.68.0'
|
<pre><code class="language-shell">pod 'Filament', '~> 1.66.2'
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
+1541
-1570
File diff suppressed because one or more lines are too long
+1033
-1058
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==0.31.0
|
||||||
trio-websocket==0.12.2
|
trio-websocket==0.12.2
|
||||||
typing_extensions==4.15.0
|
typing_extensions==4.15.0
|
||||||
urllib3==2.6.0
|
urllib3==2.5.0
|
||||||
websocket-client==1.9.0
|
websocket-client==1.9.0
|
||||||
wsproto==1.2.0
|
wsproto==1.2.0
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ set(SRCS
|
|||||||
src/Driver.cpp
|
src/Driver.cpp
|
||||||
src/Handle.cpp
|
src/Handle.cpp
|
||||||
src/HandleAllocator.cpp
|
src/HandleAllocator.cpp
|
||||||
src/JobQueue.cpp
|
|
||||||
src/ostream.cpp
|
src/ostream.cpp
|
||||||
src/noop/NoopDriver.cpp
|
src/noop/NoopDriver.cpp
|
||||||
src/noop/PlatformNoop.cpp
|
src/noop/PlatformNoop.cpp
|
||||||
@@ -60,7 +59,6 @@ set(PRIVATE_HDRS
|
|||||||
src/CompilerThreadPool.h
|
src/CompilerThreadPool.h
|
||||||
src/DataReshaper.h
|
src/DataReshaper.h
|
||||||
src/DriverBase.h
|
src/DriverBase.h
|
||||||
src/JobQueue.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
@@ -208,8 +206,6 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
|||||||
src/vulkan/VulkanDriverFactory.h
|
src/vulkan/VulkanDriverFactory.h
|
||||||
src/vulkan/VulkanExternalImageManager.cpp
|
src/vulkan/VulkanExternalImageManager.cpp
|
||||||
src/vulkan/VulkanExternalImageManager.h
|
src/vulkan/VulkanExternalImageManager.h
|
||||||
src/vulkan/VulkanStreamedImageManager.cpp
|
|
||||||
src/vulkan/VulkanStreamedImageManager.h
|
|
||||||
src/vulkan/VulkanFboCache.cpp
|
src/vulkan/VulkanFboCache.cpp
|
||||||
src/vulkan/VulkanFboCache.h
|
src/vulkan/VulkanFboCache.h
|
||||||
src/vulkan/VulkanHandles.cpp
|
src/vulkan/VulkanHandles.cpp
|
||||||
@@ -320,8 +316,6 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
|||||||
src/webgpu/WebGPURenderPrimitive.h
|
src/webgpu/WebGPURenderPrimitive.h
|
||||||
src/webgpu/WebGPURenderTarget.cpp
|
src/webgpu/WebGPURenderTarget.cpp
|
||||||
src/webgpu/WebGPURenderTarget.h
|
src/webgpu/WebGPURenderTarget.h
|
||||||
src/webgpu/WebGPUStagePool.cpp
|
|
||||||
src/webgpu/WebGPUStagePool.h
|
|
||||||
src/webgpu/WebGPUStrings.h
|
src/webgpu/WebGPUStrings.h
|
||||||
src/webgpu/WebGPUSwapChain.cpp
|
src/webgpu/WebGPUSwapChain.cpp
|
||||||
src/webgpu/WebGPUSwapChain.h
|
src/webgpu/WebGPUSwapChain.h
|
||||||
@@ -570,7 +564,6 @@ if (APPLE OR LINUX)
|
|||||||
test/test_ReadPixels.cpp
|
test/test_ReadPixels.cpp
|
||||||
test/test_BufferUpdates.cpp
|
test/test_BufferUpdates.cpp
|
||||||
test/test_Callbacks.cpp
|
test/test_Callbacks.cpp
|
||||||
test/test_JobQueue.cpp
|
|
||||||
test/test_MemoryMappedBuffer.cpp
|
test/test_MemoryMappedBuffer.cpp
|
||||||
test/test_MsaaSwapChain.cpp
|
test/test_MsaaSwapChain.cpp
|
||||||
test/test_MRT.cpp
|
test/test_MRT.cpp
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
|
||||||
|
#define TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
|
||||||
|
|
||||||
|
#include <backend/Handle.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace filament::backend {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of association between a buffer object and a stream.
|
||||||
|
*/
|
||||||
|
enum class BufferObjectStreamAssociationType { TRANSFORM_MATRIX };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A descriptor for a buffer object to stream association.
|
||||||
|
*/
|
||||||
|
class UTILS_PUBLIC BufferObjectStreamDescriptor {
|
||||||
|
public:
|
||||||
|
//! creates an empty descriptor
|
||||||
|
BufferObjectStreamDescriptor() noexcept = default;
|
||||||
|
|
||||||
|
BufferObjectStreamDescriptor(const BufferObjectStreamDescriptor& rhs) = delete;
|
||||||
|
BufferObjectStreamDescriptor& operator=(const BufferObjectStreamDescriptor& rhs) = delete;
|
||||||
|
|
||||||
|
BufferObjectStreamDescriptor(BufferObjectStreamDescriptor&& rhs) = default;
|
||||||
|
BufferObjectStreamDescriptor& operator=(BufferObjectStreamDescriptor&& rhs) = default;
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct StreamDataDescriptor {
|
||||||
|
uint32_t offset;
|
||||||
|
Handle<HwStream> stream;
|
||||||
|
BufferObjectStreamAssociationType associationType;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<StreamDataDescriptor> mStreams;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace filament::backend
|
||||||
|
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
utils::io::ostream& operator<<(utils::io::ostream& out,
|
||||||
|
const filament::backend::BufferObjectStreamDescriptor& b);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
|
||||||
@@ -1713,8 +1713,6 @@ using FrameTimestamps = Platform::FrameTimestamps;
|
|||||||
|
|
||||||
using CompositorTiming = Platform::CompositorTiming;
|
using CompositorTiming = Platform::CompositorTiming;
|
||||||
|
|
||||||
using AsynchronousMode = Platform::AsynchronousMode;
|
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|
||||||
template<> struct utils::EnableBitMaskOperators<filament::backend::ShaderStageFlags>
|
template<> struct utils::EnableBitMaskOperators<filament::backend::ShaderStageFlags>
|
||||||
|
|||||||
@@ -19,17 +19,13 @@
|
|||||||
#ifndef TNT_FILAMENT_BACKEND_PLATFORM_H
|
#ifndef TNT_FILAMENT_BACKEND_PLATFORM_H
|
||||||
#define TNT_FILAMENT_BACKEND_PLATFORM_H
|
#define TNT_FILAMENT_BACKEND_PLATFORM_H
|
||||||
|
|
||||||
#include <utils/CString.h>
|
|
||||||
#include <utils/compiler.h>
|
#include <utils/compiler.h>
|
||||||
#include <utils/Invocable.h>
|
#include <utils/Invocable.h>
|
||||||
#include <utils/Mutex.h>
|
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
@@ -74,9 +70,6 @@ public:
|
|||||||
ExternalImageHandle& operator=(ExternalImageHandle const& rhs) noexcept;
|
ExternalImageHandle& operator=(ExternalImageHandle const& rhs) noexcept;
|
||||||
ExternalImageHandle& operator=(ExternalImageHandle&& 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; }
|
explicit operator bool() const noexcept { return mTarget != nullptr; }
|
||||||
|
|
||||||
ExternalImage* UTILS_NULLABLE get() noexcept { return mTarget; }
|
ExternalImage* UTILS_NULLABLE get() noexcept { return mTarget; }
|
||||||
@@ -255,28 +248,6 @@ public:
|
|||||||
REALTIME,
|
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 {
|
struct DriverConfig {
|
||||||
/**
|
/**
|
||||||
* Size of handle arena in bytes. Setting to 0 indicates default value is to be used.
|
* Size of handle arena in bytes. Setting to 0 indicates default value is to be used.
|
||||||
@@ -346,11 +317,6 @@ public:
|
|||||||
* - VulkanPlatform
|
* - VulkanPlatform
|
||||||
*/
|
*/
|
||||||
bool vulkanEnableStagingBufferBypass = false;
|
bool vulkanEnableStagingBufferBypass = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronous mode for the engine. Defines how asynchronous operations are handled.
|
|
||||||
*/
|
|
||||||
AsynchronousMode asynchronousMode = AsynchronousMode::NONE;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Platform() noexcept;
|
Platform() noexcept;
|
||||||
@@ -542,22 +508,13 @@ public:
|
|||||||
// --------------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------------
|
||||||
// Debugging APIs
|
// Debugging APIs
|
||||||
|
|
||||||
using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key,
|
using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key, uint64_t value)>;
|
||||||
uint64_t intValue, utils::CString stringValue)>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the callback function that the backend can use to update backend-specific statistics
|
* 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
|
* to aid with debugging. This callback is guaranteed to be called on the Filament driver
|
||||||
* thread.
|
* 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
|
* @param debugUpdateStat an Invocable that updates debug statistics
|
||||||
*/
|
*/
|
||||||
void setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept;
|
void setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept;
|
||||||
@@ -576,32 +533,15 @@ public:
|
|||||||
* This function is guaranteed to be called only on a single thread, the Filament driver
|
* This function is guaranteed to be called only on a single thread, the Filament driver
|
||||||
* thread.
|
* thread.
|
||||||
*
|
*
|
||||||
* @param key a null-terminated C-string with the key of the debug statistic
|
* @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
|
* @param value the updated value of key
|
||||||
* callback will be empty)
|
|
||||||
*/
|
*/
|
||||||
void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t intValue);
|
void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t value);
|
||||||
|
|
||||||
/**
|
|
||||||
* 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:
|
private:
|
||||||
std::shared_ptr<InsertBlobFunc> mInsertBlob;
|
InsertBlobFunc mInsertBlob;
|
||||||
std::shared_ptr<RetrieveBlobFunc> mRetrieveBlob;
|
RetrieveBlobFunc mRetrieveBlob;
|
||||||
std::shared_ptr<DebugUpdateStatFunc> mDebugUpdateStat;
|
DebugUpdateStatFunc mDebugUpdateStat;
|
||||||
mutable utils::Mutex mMutex;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace filament
|
} // namespace filament
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
|
#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
|
||||||
#define 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 "AndroidNdk.h"
|
||||||
|
|
||||||
#include <backend/AcquiredImage.h>
|
#include <backend/AcquiredImage.h>
|
||||||
@@ -30,6 +32,8 @@
|
|||||||
|
|
||||||
#include <math/mat3.h>
|
#include <math/mat3.h>
|
||||||
|
|
||||||
|
#include "AndroidNativeWindow.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
@@ -167,10 +171,19 @@ protected:
|
|||||||
SwapChain* drawSwapChain,
|
SwapChain* drawSwapChain,
|
||||||
SwapChain* readSwapChain) override;
|
SwapChain* readSwapChain) override;
|
||||||
|
|
||||||
private:
|
struct SwapChainEGLAndroid : public SwapChainEGL {
|
||||||
struct SwapChainEGLAndroid;
|
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
|
||||||
struct AndroidDetails;
|
void* nativeWindow, uint64_t flags);
|
||||||
|
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
|
||||||
|
uint32_t width, uint32_t height, uint64_t flags);
|
||||||
|
void terminate(PlatformEGLAndroid& platform);
|
||||||
|
bool setPresentFrameId(uint64_t frameId) const noexcept;
|
||||||
|
uint64_t getFrameId(uint64_t frameId) const noexcept;
|
||||||
|
private:
|
||||||
|
AndroidSwapChainHelper mImpl{};
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
// prevent derived classes' implementations to call through
|
// prevent derived classes' implementations to call through
|
||||||
[[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override;
|
[[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override;
|
||||||
[[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override;
|
[[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override;
|
||||||
@@ -190,14 +203,17 @@ private:
|
|||||||
|
|
||||||
int mOSVersion;
|
int mOSVersion;
|
||||||
ExternalStreamManagerAndroid& mExternalStreamManager;
|
ExternalStreamManagerAndroid& mExternalStreamManager;
|
||||||
AndroidDetails& mAndroidDetails;
|
|
||||||
InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded;
|
InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded;
|
||||||
utils::PerformanceHintManager mPerformanceHintManager;
|
utils::PerformanceHintManager mPerformanceHintManager;
|
||||||
utils::PerformanceHintManager::Session mPerformanceHintSession;
|
utils::PerformanceHintManager::Session mPerformanceHintSession;
|
||||||
|
SwapChainEGLAndroid* mCurrentDrawSwapChain{};
|
||||||
|
|
||||||
using clock = std::chrono::high_resolution_clock;
|
using clock = std::chrono::high_resolution_clock;
|
||||||
clock::time_point mStartTimeOfActualWork;
|
clock::time_point mStartTimeOfActualWork;
|
||||||
SwapChainEGLAndroid* mCurrentDrawSwapChain{};
|
AndroidProducerThrottling mProducerThrottling;
|
||||||
bool mAssertNativeWindowIsValid = false;
|
bool mAssertNativeWindowIsValid = false;
|
||||||
|
|
||||||
|
AndroidFrameCallback mAndroidFrameCallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -17,13 +17,12 @@
|
|||||||
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
|
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
|
||||||
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
|
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
|
||||||
|
|
||||||
|
#include "AndroidFrameCallback.h"
|
||||||
#include "AndroidNdk.h"
|
#include "AndroidNdk.h"
|
||||||
|
|
||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
#include <backend/platforms/VulkanPlatform.h>
|
#include <backend/platforms/VulkanPlatform.h>
|
||||||
|
|
||||||
#include <utils/compiler.h>
|
|
||||||
|
|
||||||
#include <android/hardware_buffer.h>
|
#include <android/hardware_buffer.h>
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
@@ -92,8 +91,6 @@ protected:
|
|||||||
VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override;
|
VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct AndroidDetails;
|
|
||||||
|
|
||||||
struct ExternalImageVulkanAndroid : public ExternalImage {
|
struct ExternalImageVulkanAndroid : public ExternalImage {
|
||||||
AHardwareBuffer* aHardwareBuffer = nullptr;
|
AHardwareBuffer* aHardwareBuffer = nullptr;
|
||||||
bool sRGB = false;
|
bool sRGB = false;
|
||||||
@@ -102,7 +99,7 @@ private:
|
|||||||
~ExternalImageVulkanAndroid() override;
|
~ExternalImageVulkanAndroid() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
AndroidDetails& mAndroidDetails;
|
AndroidFrameCallback mAndroidFrameCallback;
|
||||||
int mOSVersion{};
|
int mOSVersion{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "private/backend/Driver.h"
|
#include "private/backend/Driver.h"
|
||||||
|
|
||||||
#include <backend/BufferDescriptor.h>
|
#include <backend/BufferDescriptor.h>
|
||||||
|
#include <backend/BufferObjectStreamDescriptor.h>
|
||||||
#include <backend/CallbackHandler.h>
|
#include <backend/CallbackHandler.h>
|
||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
#include <backend/Handle.h>
|
#include <backend/Handle.h>
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
class BufferDescriptor;
|
class BufferDescriptor;
|
||||||
|
class BufferObjectStreamDescriptor;
|
||||||
class CallbackHandler;
|
class CallbackHandler;
|
||||||
class PixelBufferDescriptor;
|
class PixelBufferDescriptor;
|
||||||
class Program;
|
class Program;
|
||||||
|
|||||||
@@ -392,6 +392,10 @@ DECL_DRIVER_API_N(updateBufferObject,
|
|||||||
backend::BufferDescriptor&&, data,
|
backend::BufferDescriptor&&, data,
|
||||||
uint32_t, byteOffset)
|
uint32_t, byteOffset)
|
||||||
|
|
||||||
|
DECL_DRIVER_API_N(registerBufferObjectStreams,
|
||||||
|
backend::BufferObjectHandle, boh,
|
||||||
|
backend::BufferObjectStreamDescriptor&&, streams)
|
||||||
|
|
||||||
DECL_DRIVER_API_N(updateBufferObjectUnsynchronized,
|
DECL_DRIVER_API_N(updateBufferObjectUnsynchronized,
|
||||||
backend::BufferObjectHandle, boh,
|
backend::BufferObjectHandle, boh,
|
||||||
backend::BufferDescriptor&&, data,
|
backend::BufferDescriptor&&, data,
|
||||||
|
|||||||
@@ -61,16 +61,6 @@ int NativeWindow::enableFrameTimestamps(ANativeWindow* anw, bool enable) {
|
|||||||
return pWindow->perform(anw, ENABLE_FRAME_TIMESTAMPS, 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,
|
int NativeWindow::getCompositorTiming(ANativeWindow* anw,
|
||||||
int64_t* compositeDeadline, int64_t* compositeInterval,
|
int64_t* compositeDeadline, int64_t* compositeInterval,
|
||||||
int64_t* compositeToPresentLatency) {
|
int64_t* compositeToPresentLatency) {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ struct NativeWindow {
|
|||||||
// is valid query enum value
|
// is valid query enum value
|
||||||
enum {
|
enum {
|
||||||
IS_VALID = 17,
|
IS_VALID = 17,
|
||||||
FRAME_TIMESTAMPS_SUPPORTS_PRESENT = 18,
|
|
||||||
GET_NEXT_FRAME_ID = 24,
|
GET_NEXT_FRAME_ID = 24,
|
||||||
ENABLE_FRAME_TIMESTAMPS = 25,
|
ENABLE_FRAME_TIMESTAMPS = 25,
|
||||||
GET_COMPOSITOR_TIMING = 26,
|
GET_COMPOSITOR_TIMING = 26,
|
||||||
@@ -52,7 +51,6 @@ struct NativeWindow {
|
|||||||
|
|
||||||
static int getNextFrameId(ANativeWindow* anw, uint64_t* frameId);
|
static int getNextFrameId(ANativeWindow* anw, uint64_t* frameId);
|
||||||
static int enableFrameTimestamps(ANativeWindow* anw, bool enable);
|
static int enableFrameTimestamps(ANativeWindow* anw, bool enable);
|
||||||
static int frameTimestampsSupportsPresent(ANativeWindow* anw, bool* outSupportsPresent);
|
|
||||||
static int getCompositorTiming(ANativeWindow* anw,
|
static int getCompositorTiming(ANativeWindow* anw,
|
||||||
int64_t* compositeDeadline, int64_t* compositeInterval,
|
int64_t* compositeDeadline, int64_t* compositeInterval,
|
||||||
int64_t* compositeToPresentLatency);
|
int64_t* compositeToPresentLatency);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <backend/platforms/AndroidNdk.h>
|
#include "AndroidNdk.h"
|
||||||
|
|
||||||
#include <android/hardware_buffer.h>
|
#include <android/hardware_buffer.h>
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,6 @@
|
|||||||
|
|
||||||
#include <android/native_window.h>
|
#include <android/native_window.h>
|
||||||
|
|
||||||
#include <utils/compiler.h>
|
|
||||||
#include <utils/Logger.h>
|
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
@@ -39,23 +36,11 @@ bool AndroidSwapChainHelper::setPresentFrameId(
|
|||||||
int const status = NativeWindow::getNextFrameId(anw, &sysFrameId);
|
int const status = NativeWindow::getNextFrameId(anw, &sysFrameId);
|
||||||
if (status == 0) {
|
if (status == 0) {
|
||||||
std::lock_guard const lock(mLock);
|
std::lock_guard const lock(mLock);
|
||||||
// frameIds must be strictly monotonic, if that's not the case (i.e. the new frameId is
|
auto const pos = mFrameIdToSystemFrameId.find(frameId);
|
||||||
// less or equal to the last one in the map), we have to clear the map, because the
|
if (pos && *pos != sysFrameId) {
|
||||||
// map's find() assume sorted keys.
|
// we're trying to associate the same frame id to a different frame!
|
||||||
// This case can happen if two different filament::Renderer are used with the same
|
return false;
|
||||||
// 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
|
// oldest entry is removed
|
||||||
mFrameIdToSystemFrameId.insert(frameId, sysFrameId);
|
mFrameIdToSystemFrameId.insert(frameId, sysFrameId);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,247 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "JobQueue.h"
|
|
||||||
|
|
||||||
#include <utils/compiler.h>
|
|
||||||
#include <utils/debug.h>
|
|
||||||
#include <utils/Panic.h>
|
|
||||||
|
|
||||||
namespace filament::backend {
|
|
||||||
|
|
||||||
JobQueue::JobQueue(PassKey) {}
|
|
||||||
|
|
||||||
JobQueue::JobId JobQueue::push(Job job, JobId const preIssuedJobId/* = InvalidJobId*/) {
|
|
||||||
JobId jobId = preIssuedJobId;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mQueueMutex);
|
|
||||||
if (mIsStopping) {
|
|
||||||
return InvalidJobId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jobId == InvalidJobId) {
|
|
||||||
jobId = genNextJobId();
|
|
||||||
mJobsMap[jobId] = std::move(job);
|
|
||||||
} else {
|
|
||||||
// Use the job ID previously issued by `issueJobId()`
|
|
||||||
auto it = mJobsMap.find(jobId);
|
|
||||||
if (it == mJobsMap.end()) {
|
|
||||||
// Pre-issued job does not exist, either users passed a wrong id (unlikely)
|
|
||||||
// or the job must have been canceled (likely)
|
|
||||||
return InvalidJobId;
|
|
||||||
}
|
|
||||||
FILAMENT_CHECK_PRECONDITION(!static_cast<bool>(it->second))
|
|
||||||
<< "pre-issued job has already been populated";
|
|
||||||
it->second = std::move(job);
|
|
||||||
}
|
|
||||||
mJobOrder.push(jobId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always notify. A ThreadWorker might be waiting.
|
|
||||||
mQueueCondition.notify_one();
|
|
||||||
|
|
||||||
return jobId;
|
|
||||||
}
|
|
||||||
|
|
||||||
JobQueue::Job JobQueue::pop(bool shouldBlock) {
|
|
||||||
std::unique_lock<std::mutex> lock(mQueueMutex);
|
|
||||||
|
|
||||||
decltype(mJobsMap)::iterator it;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (shouldBlock) {
|
|
||||||
// Wait only if we're in blocking mode and the queue is empty
|
|
||||||
mQueueCondition.wait(lock, [this] { return !mJobOrder.empty() || mIsStopping; });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mJobOrder.empty()) {
|
|
||||||
// When `shouldBlock` is true, this means the queue is stopping now.
|
|
||||||
// When `shouldBlock` is false, this means there's no job.
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
JobId jobId = mJobOrder.front();
|
|
||||||
mJobOrder.pop();
|
|
||||||
|
|
||||||
it = mJobsMap.find(jobId);
|
|
||||||
if (it != mJobsMap.end()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If execution reaches this line, the job must have been canceled right after being added.
|
|
||||||
// Therefore, we should continue the loop and attempt to retrieve the next available job.
|
|
||||||
}
|
|
||||||
|
|
||||||
Job job = std::move(it->second);
|
|
||||||
mJobsMap.erase(it);
|
|
||||||
return job;
|
|
||||||
}
|
|
||||||
|
|
||||||
utils::FixedCapacityVector<JobQueue::Job> JobQueue::popBatch(int const maxJobsToPop) {
|
|
||||||
utils::FixedCapacityVector<Job> jobs;
|
|
||||||
|
|
||||||
if (UTILS_UNLIKELY(maxJobsToPop == 0)) {
|
|
||||||
return jobs;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mQueueMutex);
|
|
||||||
if (mJobOrder.empty()) {
|
|
||||||
return jobs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate jobs to take. If maxJobsToPop is negative, we take all jobs.
|
|
||||||
size_t jobsToTake = mJobOrder.size();
|
|
||||||
if (0 < maxJobsToPop && maxJobsToPop < static_cast<int>(jobsToTake)) {
|
|
||||||
jobsToTake = maxJobsToPop;
|
|
||||||
}
|
|
||||||
jobs.reserve(jobsToTake);
|
|
||||||
|
|
||||||
while (0 < jobsToTake && !mJobOrder.empty()) {
|
|
||||||
JobId jobId = mJobOrder.front();
|
|
||||||
mJobOrder.pop();
|
|
||||||
|
|
||||||
auto it = mJobsMap.find(jobId);
|
|
||||||
if (UTILS_UNLIKELY(it == mJobsMap.end())) {
|
|
||||||
// The job was probably canceled.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
jobs.push_back(std::move(it->second));
|
|
||||||
--jobsToTake;
|
|
||||||
mJobsMap.erase(it);
|
|
||||||
}
|
|
||||||
|
|
||||||
return jobs;
|
|
||||||
}
|
|
||||||
|
|
||||||
JobQueue::JobId JobQueue::issueJobId() noexcept {
|
|
||||||
std::lock_guard<std::mutex> lock(mQueueMutex);
|
|
||||||
JobId const jobId = genNextJobId();
|
|
||||||
// Preallocate a job, which serves two main purposes. It provides a valid jobId that can be
|
|
||||||
// checked for integrity when passed to the `push` method, and it enables job cancellation for
|
|
||||||
// tasks that are yet to be pushed.
|
|
||||||
mJobsMap[jobId];
|
|
||||||
return jobId;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool JobQueue::cancel(JobId const jobId) noexcept {
|
|
||||||
std::lock_guard<std::mutex> lock(mQueueMutex);
|
|
||||||
|
|
||||||
auto it = mJobsMap.find(jobId);
|
|
||||||
if (it == mJobsMap.end()) {
|
|
||||||
return false; // Job not found, must have been completed or canceled.
|
|
||||||
}
|
|
||||||
|
|
||||||
mJobsMap.erase(it);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void JobQueue::stop() noexcept {
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mQueueMutex);
|
|
||||||
mIsStopping = true;
|
|
||||||
}
|
|
||||||
mQueueCondition.notify_all(); // Wake up all waiting threads
|
|
||||||
}
|
|
||||||
|
|
||||||
JobQueue::JobId JobQueue::genNextJobId() noexcept {
|
|
||||||
// We assume this method is called within the critical section.
|
|
||||||
JobId newJobId = mNextJobId++;
|
|
||||||
// We assume the job ID won't overflow or wraps around to zero within the application's lifetime.
|
|
||||||
assert_invariant(newJobId != InvalidJobId);
|
|
||||||
return newJobId;
|
|
||||||
}
|
|
||||||
|
|
||||||
JobWorker::~JobWorker() = default;
|
|
||||||
|
|
||||||
void JobWorker::terminate() {
|
|
||||||
// This is called from workers `terminate()`, which may hinder the concurrent use of multiple
|
|
||||||
// workers. Consider removing this line and require the owner/caller to explicitly invoke it to
|
|
||||||
// enable multiple worker instances.
|
|
||||||
if (mQueue) {
|
|
||||||
mQueue->stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AmortizationWorker::AmortizationWorker(JobQueue::Ptr queue, PassKey)
|
|
||||||
: JobWorker(std::move(queue)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
AmortizationWorker::~AmortizationWorker() = default;
|
|
||||||
|
|
||||||
void AmortizationWorker::process(int const jobCount) {
|
|
||||||
if (!mQueue || jobCount == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jobCount == 1) {
|
|
||||||
// Handle single job without vector allocation.
|
|
||||||
if (auto job = mQueue->pop(false)) {
|
|
||||||
job();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle batch (jobCount > 1 or jobCount < 0 for "all pending jobs")
|
|
||||||
utils::FixedCapacityVector<JobQueue::Job> jobs = mQueue->popBatch(jobCount);
|
|
||||||
if (jobs.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& job: jobs) {
|
|
||||||
job();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AmortizationWorker::terminate() {
|
|
||||||
JobWorker::terminate();
|
|
||||||
|
|
||||||
// Drain all pending jobs.
|
|
||||||
process(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadWorker::ThreadWorker(JobQueue::Ptr queue, Config config, PassKey)
|
|
||||||
: JobWorker(std::move(queue)), mConfig(std::move(config)) {
|
|
||||||
mThread = std::thread([this]() {
|
|
||||||
utils::JobSystem::setThreadName(mConfig.name.data());
|
|
||||||
utils::JobSystem::setThreadPriority(mConfig.priority);
|
|
||||||
|
|
||||||
if (mConfig.onBegin) {
|
|
||||||
mConfig.onBegin();
|
|
||||||
}
|
|
||||||
|
|
||||||
while (JobQueue::Job job = mQueue->pop(true)) {
|
|
||||||
job();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mConfig.onEnd) {
|
|
||||||
mConfig.onEnd();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadWorker::~ThreadWorker() = default;
|
|
||||||
|
|
||||||
void ThreadWorker::terminate() {
|
|
||||||
JobWorker::terminate();
|
|
||||||
|
|
||||||
if (mThread.joinable()) {
|
|
||||||
mThread.join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace utils
|
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef TNT_FILAMENT_BACKEND_PRIVATE_JOBQUEUE_H
|
|
||||||
#define TNT_FILAMENT_BACKEND_PRIVATE_JOBQUEUE_H
|
|
||||||
|
|
||||||
#include <utils/FixedCapacityVector.h>
|
|
||||||
#include <utils/Invocable.h>
|
|
||||||
#include <utils/JobSystem.h>
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <thread>
|
|
||||||
#include <memory>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <limits>
|
|
||||||
#include <queue>
|
|
||||||
|
|
||||||
namespace filament::backend {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A thread-safe producer-consumer queue with batching capabilities.
|
|
||||||
*
|
|
||||||
* This class is thread-safe. All public methods can be called from any thread.
|
|
||||||
*
|
|
||||||
* This class is stateless regarding concurrency. The *caller* decides the blocking behavior and/or
|
|
||||||
* batching when they call a 'pop' methods.
|
|
||||||
*
|
|
||||||
* A typical use case looks like this:
|
|
||||||
*
|
|
||||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
* #include "private/backend/JobQueue.h"
|
|
||||||
* using namespace filament::backend;
|
|
||||||
*
|
|
||||||
* JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
* JobWorker::Ptr worker = AmortizationWorker::create(queue);
|
|
||||||
* [ or JobWorker::Ptr worker = ThreadWorker::create(queue, config); ]
|
|
||||||
*
|
|
||||||
* void loop() {
|
|
||||||
* worker->process(2); // for AmortizationWorker
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* void cleanup() {
|
|
||||||
* worker->terminate();
|
|
||||||
* }
|
|
||||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
* JobId id = queue->push([](){ ... });
|
|
||||||
* queue->cancel(id);
|
|
||||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
* JobId preIssuedId = queue->issueJobId();
|
|
||||||
* JobId id = queue->push([](){ ... }, preIssuedId);
|
|
||||||
* assert(id == preIssuedId);
|
|
||||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
*/
|
|
||||||
class JobQueue {
|
|
||||||
struct PassKey {};
|
|
||||||
public:
|
|
||||||
using Job = utils::Invocable<void()>;
|
|
||||||
using JobId = uint32_t;
|
|
||||||
using Ptr = std::shared_ptr<JobQueue>;
|
|
||||||
static constexpr JobId InvalidJobId = std::numeric_limits<JobId>::max();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance of JobQueue. Users should call this to create one.
|
|
||||||
* @return An instance of JobQueue
|
|
||||||
*/
|
|
||||||
static Ptr create() {
|
|
||||||
return std::make_shared<JobQueue>(PassKey{});
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit JobQueue(PassKey); // This can be created only via `create()`
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pushes a new job into queue.
|
|
||||||
*
|
|
||||||
* If the queue is in the process of shutting down (via a call to `stop`), this method does
|
|
||||||
* nothing (a no-op) and returns an invalid job ID.
|
|
||||||
*
|
|
||||||
* @param job The function/lambda to be executed.
|
|
||||||
* @param preIssuedJobId The previously issued job ID where this job is assigned to.
|
|
||||||
* If the value is `InvalidJobId`, a new job ID is generated internally.
|
|
||||||
* @return A new job ID. If a `preIssuedJobId` is provided, that specific ID is returned instead.
|
|
||||||
*/
|
|
||||||
JobId push(Job job, JobId preIssuedJobId = InvalidJobId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the next job from queue.
|
|
||||||
*
|
|
||||||
* This method returns a valid job ID if there are pending jobs in the queue, even if the queue
|
|
||||||
* is currently in the process of shutting down (via a call to `stop`).
|
|
||||||
*
|
|
||||||
* @param shouldBlock If true (typically used by ThreadWorker), waits for a job and returns it.
|
|
||||||
* If false (typically used by AmortizationWorker), tries retrieving a job. But may return an
|
|
||||||
* empty job if there's no pending job.
|
|
||||||
* @return The next job. If an empty job is returned, it has different meaning depending on the
|
|
||||||
* `shouldBlock` value:
|
|
||||||
* - true: Stop processing (shutting down).
|
|
||||||
* - false: No jobs currently in queue.
|
|
||||||
*/
|
|
||||||
Job pop(bool shouldBlock);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a batch of next jobs from queue. Always non-blocking.
|
|
||||||
*
|
|
||||||
* This method returns a valid job ID if there are pending jobs in the queue, even if the queue
|
|
||||||
* is currently in the process of shutting down (via a call to `stop`).
|
|
||||||
*
|
|
||||||
* @param maxJobsToPop The maximum number of jobs to retrieve.
|
|
||||||
* If < 0, retrieves all pending jobs.
|
|
||||||
* @return A FixedCapacityVector<Job> containing the retrieved jobs.
|
|
||||||
* Returns an empty vector if the queue is empty.
|
|
||||||
*/
|
|
||||||
utils::FixedCapacityVector<Job> popBatch(int maxJobsToPop);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a new job ID. This newly generated ID is meant to be used for the `preIssuedJobId`
|
|
||||||
* parameter of `push` method.
|
|
||||||
*
|
|
||||||
* @return A new job ID.
|
|
||||||
*/
|
|
||||||
JobId issueJobId() noexcept;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels a job by its ID.
|
|
||||||
*
|
|
||||||
* @param jobId The job ID to cancel.
|
|
||||||
* @return true if the job was found and cancelled, false otherwise.
|
|
||||||
*/
|
|
||||||
bool cancel(JobId jobId) noexcept;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signals the queue to shut down, after which no further jobs can be added using the `push`
|
|
||||||
* method. but all jobs already pushed can still be processed using `pop`.
|
|
||||||
*/
|
|
||||||
void stop() noexcept;
|
|
||||||
|
|
||||||
private:
|
|
||||||
JobQueue(const JobQueue&) = delete;
|
|
||||||
JobQueue& operator=(const JobQueue&) = delete;
|
|
||||||
|
|
||||||
JobId genNextJobId() noexcept;
|
|
||||||
|
|
||||||
std::mutex mQueueMutex;
|
|
||||||
std::condition_variable mQueueCondition;
|
|
||||||
std::unordered_map<JobId, Job> mJobsMap;
|
|
||||||
std::queue<JobId> mJobOrder;
|
|
||||||
JobId mNextJobId = 0;
|
|
||||||
bool mIsStopping = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract base class for all worker types.
|
|
||||||
*/
|
|
||||||
class JobWorker {
|
|
||||||
public:
|
|
||||||
using Ptr = std::unique_ptr<JobWorker>;
|
|
||||||
|
|
||||||
virtual ~JobWorker();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes a batch of jobs. (For non-threaded workers)
|
|
||||||
* @param jobCount Max jobs to process (<= 0 for all).
|
|
||||||
*/
|
|
||||||
virtual void process(int jobCount) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Terminates the worker.
|
|
||||||
*/
|
|
||||||
virtual void terminate();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
explicit JobWorker(JobQueue::Ptr queue) : mQueue(std::move(queue)) {}
|
|
||||||
|
|
||||||
JobQueue::Ptr mQueue;
|
|
||||||
|
|
||||||
private:
|
|
||||||
JobWorker(const JobWorker&) = delete;
|
|
||||||
JobWorker& operator=(const JobWorker&) = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A non-threaded worker that consumes jobs in batches.
|
|
||||||
*/
|
|
||||||
class AmortizationWorker final : public JobWorker {
|
|
||||||
struct PassKey {};
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Creates an instance of AmortizationWorker. Users should call this to create one.
|
|
||||||
* @return An instance of AmortizationWorker
|
|
||||||
*/
|
|
||||||
static Ptr create(JobQueue::Ptr queue) {
|
|
||||||
return std::make_unique<AmortizationWorker>(std::move(queue), PassKey{});
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit AmortizationWorker(JobQueue::Ptr queue, PassKey); // This can be created only via `create()`
|
|
||||||
|
|
||||||
~AmortizationWorker() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Polls the queue and executes a batch of jobs.
|
|
||||||
*
|
|
||||||
* @param jobCount The max number of jobs to process.
|
|
||||||
* 0 = do nothing.
|
|
||||||
* 1 = pop one (optimized).
|
|
||||||
* > 1 = pop batch.
|
|
||||||
* <= -1 = pop all.
|
|
||||||
*/
|
|
||||||
void process(int jobCount) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signals the queue to stop and drain all pending jobs.
|
|
||||||
* This is safe to call multiple times.
|
|
||||||
*/
|
|
||||||
void terminate() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A threaded worker that consumes jobs one by one, blocking when empty.
|
|
||||||
*/
|
|
||||||
class ThreadWorker final : public JobWorker {
|
|
||||||
struct PassKey {};
|
|
||||||
public:
|
|
||||||
using Priority = utils::JobSystem::Priority;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Config settings for the worker
|
|
||||||
*/
|
|
||||||
struct Config {
|
|
||||||
std::string_view name = "";
|
|
||||||
Priority priority = Priority::NORMAL;
|
|
||||||
utils::Invocable<void()> onBegin; // Executed when the thread worker begins
|
|
||||||
utils::Invocable<void()> onEnd; // Executed when the thread worker ends
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance of ThreadWorker. Users should call this to create one.
|
|
||||||
* @return An instance of ThreadWorker
|
|
||||||
*/
|
|
||||||
static Ptr create(JobQueue::Ptr queue, Config config) {
|
|
||||||
return std::make_unique<ThreadWorker>(std::move(queue), std::move(config), PassKey{});
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadWorker(JobQueue::Ptr queue, Config config, PassKey); // This can be created only via `create()`
|
|
||||||
|
|
||||||
~ThreadWorker() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signals the queue to stop and joins the worker thread.
|
|
||||||
* This is safe to call multiple times.
|
|
||||||
*/
|
|
||||||
void terminate() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Config mConfig;
|
|
||||||
std::thread mThread;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace filament::backend
|
|
||||||
|
|
||||||
#endif // TNT_FILAMENT_BACKEND_PRIVATE_JOBQUEUE_H
|
|
||||||
@@ -139,73 +139,42 @@ bool Platform::queryFrameTimestamps(SwapChain const*, uint64_t, FrameTimestamps*
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept {
|
void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept {
|
||||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
mInsertBlob = std::move(insertBlob);
|
||||||
mInsertBlob = std::make_shared<InsertBlobFunc>(std::move(insertBlob));
|
mRetrieveBlob = std::move(retrieveBlob);
|
||||||
mRetrieveBlob = std::make_shared<RetrieveBlobFunc>(std::move(retrieveBlob));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Platform::hasInsertBlobFunc() const noexcept {
|
bool Platform::hasInsertBlobFunc() const noexcept {
|
||||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
return bool(mInsertBlob);
|
||||||
return mInsertBlob && bool(*mInsertBlob);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Platform::hasRetrieveBlobFunc() const noexcept {
|
bool Platform::hasRetrieveBlobFunc() const noexcept {
|
||||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
return bool(mRetrieveBlob);
|
||||||
return mRetrieveBlob && bool(*mRetrieveBlob);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Platform::insertBlob(void const* key, size_t keySize, void const* value, size_t valueSize) {
|
void Platform::insertBlob(void const* key, size_t keySize, void const* value, size_t valueSize) {
|
||||||
std::shared_ptr<InsertBlobFunc> callback;
|
if (mInsertBlob) {
|
||||||
{
|
mInsertBlob(key, keySize, value, valueSize);
|
||||||
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) {
|
size_t Platform::retrieveBlob(void const* key, size_t keySize, void* value, size_t valueSize) {
|
||||||
std::shared_ptr<RetrieveBlobFunc> callback;
|
if (mRetrieveBlob) {
|
||||||
{
|
return mRetrieveBlob(key, keySize, value, valueSize);
|
||||||
std::unique_lock<decltype(mMutex)> lock(mMutex);
|
|
||||||
callback = mRetrieveBlob;
|
|
||||||
}
|
|
||||||
if (callback) {
|
|
||||||
return (*callback)(key, keySize, value, valueSize);
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Platform::setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept {
|
void Platform::setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept {
|
||||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
mDebugUpdateStat = std::move(debugUpdateStat);
|
||||||
mDebugUpdateStat = std::make_shared<DebugUpdateStatFunc>(std::move(debugUpdateStat));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Platform::hasDebugUpdateStatFunc() const noexcept {
|
bool Platform::hasDebugUpdateStatFunc() const noexcept {
|
||||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
return bool(mDebugUpdateStat);
|
||||||
return mDebugUpdateStat && bool(*mDebugUpdateStat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Platform::debugUpdateStat(const char* key, uint64_t intValue) {
|
void Platform::debugUpdateStat(const char* key, uint64_t value) {
|
||||||
std::shared_ptr<DebugUpdateStatFunc> callback;
|
if (mDebugUpdateStat) {
|
||||||
{
|
mDebugUpdateStat(key, value);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ public:
|
|||||||
size_t size, bool forceGpuBuffer = false);
|
size_t size, bool forceGpuBuffer = false);
|
||||||
~MetalBuffer();
|
~MetalBuffer();
|
||||||
|
|
||||||
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer; }
|
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer || mCpuBuffer; }
|
||||||
|
|
||||||
MetalBuffer(const MetalBuffer& rhs) = delete;
|
MetalBuffer(const MetalBuffer& rhs) = delete;
|
||||||
MetalBuffer& operator=(const MetalBuffer& rhs) = delete;
|
MetalBuffer& operator=(const MetalBuffer& rhs) = delete;
|
||||||
@@ -185,12 +185,14 @@ public:
|
|||||||
* Denotes that this buffer is used for a draw call ensuring that its allocation remains valid
|
* Denotes that this buffer is used for a draw call ensuring that its allocation remains valid
|
||||||
* until the end of the current frame.
|
* until the end of the current frame.
|
||||||
*
|
*
|
||||||
* @return The MTLBuffer representing the current state of the buffer to bind, it never returns
|
* @return The MTLBuffer representing the current state of the buffer to bind, or nil if there
|
||||||
* nil.
|
* is no device allocation.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
id<MTLBuffer> getGpuBufferForDraw() noexcept;
|
id<MTLBuffer> getGpuBufferForDraw() noexcept;
|
||||||
|
|
||||||
|
void* getCpuBuffer() const noexcept { return mCpuBuffer; }
|
||||||
|
|
||||||
void setLabel(const utils::ImmutableCString& label) {
|
void setLabel(const utils::ImmutableCString& label) {
|
||||||
#if FILAMENT_METAL_DEBUG_LABELS
|
#if FILAMENT_METAL_DEBUG_LABELS
|
||||||
if (label.empty()) {
|
if (label.empty()) {
|
||||||
@@ -233,6 +235,7 @@ private:
|
|||||||
UploadStrategy mUploadStrategy;
|
UploadStrategy mUploadStrategy;
|
||||||
TrackedMetalBuffer mBuffer;
|
TrackedMetalBuffer mBuffer;
|
||||||
size_t mBufferSize = 0;
|
size_t mBufferSize = 0;
|
||||||
|
void* mCpuBuffer = nullptr;
|
||||||
MetalContext& mContext;
|
MetalContext& mContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -39,30 +39,34 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
|
|||||||
mUploadStrategy = UploadStrategy::POOL;
|
mUploadStrategy = UploadStrategy::POOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
MTLResourceOptions options = MTLResourceStorageModePrivate;
|
// If the buffer is less than 4K in size and is updated frequently, we don't use an explicit
|
||||||
|
// buffer. Instead, we use immediate command encoder methods like setVertexBytes:length:atIndex:.
|
||||||
|
// This won't work for SSBOs, since they are read/write.
|
||||||
|
|
||||||
// The buffer will be memory mapped for write operations.
|
/*
|
||||||
if (any(usage & BufferUsage::SHARED_WRITE_BIT)) {
|
if (size <= 4 * 1024 && bindingType != BufferObjectBinding::SHADER_STORAGE &&
|
||||||
#if defined(FILAMENT_IOS) || defined(__arm64__) || defined(__aarch64__)
|
usage == BufferUsage::DYNAMIC && !forceGpuBuffer) {
|
||||||
// iOS and Apple Silicon devices use UMA (Unified Memory Architecture), so we use Shared memory.
|
mBuffer = nil;
|
||||||
options = MTLResourceStorageModeShared;
|
mCpuBuffer = malloc(size);
|
||||||
#else
|
return;
|
||||||
// Intel Macs require Managed memory for CPU/GPU synchronization.
|
|
||||||
options = MTLResourceStorageModeManaged;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Otherwise, we allocate a private GPU buffer.
|
||||||
{
|
{
|
||||||
ScopedAllocationTimer timer("generic");
|
ScopedAllocationTimer timer("generic");
|
||||||
mBuffer = { [context.device newBufferWithLength:size options:options],
|
mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate],
|
||||||
TrackedMetalBuffer::Type::GENERIC };
|
TrackedMetalBuffer::Type::GENERIC };
|
||||||
}
|
}
|
||||||
|
|
||||||
// mBuffer might fail to be allocated. Clients can check for this by calling
|
// mBuffer might fail to be allocated. Clients can check for this by calling
|
||||||
// wasAllocationSuccessful().
|
// wasAllocationSuccessful().
|
||||||
}
|
}
|
||||||
|
|
||||||
MetalBuffer::~MetalBuffer() = default;
|
MetalBuffer::~MetalBuffer() {
|
||||||
|
if (mCpuBuffer) {
|
||||||
|
free(mCpuBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MetalBuffer::copyIntoBuffer(
|
void MetalBuffer::copyIntoBuffer(
|
||||||
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
|
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
|
||||||
@@ -79,6 +83,12 @@ void MetalBuffer::copyIntoBuffer(
|
|||||||
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3))
|
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3))
|
||||||
<< "byteOffset must be a multiple of 4, tag=" << getHandleTag();
|
<< "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) {
|
switch (mUploadStrategy) {
|
||||||
case UploadStrategy::BUMP_ALLOCATOR:
|
case UploadStrategy::BUMP_ALLOCATOR:
|
||||||
uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag));
|
uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag));
|
||||||
@@ -96,6 +106,11 @@ void MetalBuffer::copyIntoBufferUnsynchronized(
|
|||||||
}
|
}
|
||||||
|
|
||||||
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw() noexcept {
|
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);
|
assert_invariant(mBuffer);
|
||||||
return mBuffer.get();
|
return mBuffer.get();
|
||||||
}
|
}
|
||||||
@@ -156,6 +171,41 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncod
|
|||||||
offsets:metalOffsets.data()
|
offsets:metalOffsets.data()
|
||||||
withRange:bufferRange];
|
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(
|
void MetalBuffer::uploadWithPoolBuffer(
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
#ifndef TNT_METALCONTEXT_H
|
#ifndef TNT_METALCONTEXT_H
|
||||||
#define TNT_METALCONTEXT_H
|
#define TNT_METALCONTEXT_H
|
||||||
|
|
||||||
#include "MetalErrorQueue.h"
|
|
||||||
#include "MetalResourceTracker.h"
|
#include "MetalResourceTracker.h"
|
||||||
#include "MetalShaderCompiler.h"
|
#include "MetalShaderCompiler.h"
|
||||||
#include "MetalState.h"
|
#include "MetalState.h"
|
||||||
@@ -130,7 +129,6 @@ struct MetalContext {
|
|||||||
id<MTLCommandBuffer> pendingCommandBuffer = nil;
|
id<MTLCommandBuffer> pendingCommandBuffer = nil;
|
||||||
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nil;
|
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nil;
|
||||||
uint32_t currentFrame = 0;
|
uint32_t currentFrame = 0;
|
||||||
MetalErrorQueue commandBufferErrors;
|
|
||||||
|
|
||||||
std::atomic<bool> memorylessLimitsReached = false;
|
std::atomic<bool> memorylessLimitsReached = false;
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,6 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
|
|||||||
|
|
||||||
if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) {
|
if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) {
|
||||||
logMTLCommandBufferError(errorCode);
|
logMTLCommandBufferError(errorCode);
|
||||||
context->commandBufferErrors.push(buffer.error);
|
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)
|
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)
|
||||||
|
|||||||
@@ -61,8 +61,6 @@ public:
|
|||||||
|
|
||||||
MetalContext* getContext() { return mContext; }
|
MetalContext* getContext() { return mContext; }
|
||||||
|
|
||||||
using DriverBase::scheduleDestroy;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
friend class MetalSwapChain;
|
friend class MetalSwapChain;
|
||||||
|
|||||||
@@ -254,18 +254,6 @@ MetalDriver::~MetalDriver() noexcept {
|
|||||||
void MetalDriver::tick(int) {
|
void MetalDriver::tick(int) {
|
||||||
executeTickOps();
|
executeTickOps();
|
||||||
executeDeferredOps();
|
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,
|
void MetalDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||||
@@ -312,8 +300,6 @@ void MetalDriver::execute(std::function<void(void)> const& fn) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MetalDriver::setPresentationTime(int64_t monotonic_clock_ns) {
|
void MetalDriver::setPresentationTime(int64_t monotonic_clock_ns) {
|
||||||
assert_invariant(mContext->currentDrawSwapChain);
|
|
||||||
mContext->currentDrawSwapChain->setPresentationTime(monotonic_clock_ns);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetalDriver::endFrame(uint32_t frameId) {
|
void MetalDriver::endFrame(uint32_t frameId) {
|
||||||
@@ -1146,12 +1132,8 @@ bool MetalDriver::isRenderTargetFormatSupported(TextureFormat format) {
|
|||||||
bool MetalDriver::isFrameBufferFetchSupported() {
|
bool MetalDriver::isFrameBufferFetchSupported() {
|
||||||
// FrameBuffer fetch is achievable via "programmable blending" in Metal, and only supported on
|
// FrameBuffer fetch is achievable via "programmable blending" in Metal, and only supported on
|
||||||
// Apple GPUs with readWriteTextureSupport.
|
// Apple GPUs with readWriteTextureSupport.
|
||||||
// On macOS, framebuffer fetch requires MSL 2.3, which is only available with macOS 11.0.
|
return mContext->highestSupportedGpuFamily.apple >= 1 &&
|
||||||
if (@available(macOS 11.0, *)) {
|
mContext->device.readWriteTextureSupport;
|
||||||
return mContext->highestSupportedGpuFamily.apple >= 1 &&
|
|
||||||
mContext->device.readWriteTextureSupport;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MetalDriver::isFrameBufferFetchMultiSampleSupported() {
|
bool MetalDriver::isFrameBufferFetchMultiSampleSupported() {
|
||||||
@@ -1292,6 +1274,10 @@ void MetalDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&
|
|||||||
scheduleDestroy(std::move(data));
|
scheduleDestroy(std::move(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MetalDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh, BufferObjectStreamDescriptor&& streams) {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
|
||||||
void MetalDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& data,
|
void MetalDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& data,
|
||||||
uint32_t byteOffset) {
|
uint32_t byteOffset) {
|
||||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||||
@@ -2277,10 +2263,7 @@ MemoryMappedBufferHandle MetalDriver::mapBufferS() noexcept {
|
|||||||
void MetalDriver::mapBufferR(MemoryMappedBufferHandle mmbh,
|
void MetalDriver::mapBufferR(MemoryMappedBufferHandle mmbh,
|
||||||
BufferObjectHandle boh, size_t offset,
|
BufferObjectHandle boh, size_t offset,
|
||||||
size_t size, MapBufferAccessFlags access, utils::ImmutableCString&& tag) {
|
size_t size, MapBufferAccessFlags access, utils::ImmutableCString&& tag) {
|
||||||
assert_invariant(boh);
|
construct_handle<MetalMemoryMappedBuffer>(mmbh, boh, offset, size, access);
|
||||||
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));
|
mHandleAllocator.associateTagToHandle(mmbh.getId(), std::move(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2288,16 +2271,21 @@ void MetalDriver::unmapBuffer(MemoryMappedBufferHandle mmbh) {
|
|||||||
if (UTILS_UNLIKELY(!mmbh)) {
|
if (UTILS_UNLIKELY(!mmbh)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
|
|
||||||
mmb->unmap();
|
|
||||||
destruct_handle<MetalMemoryMappedBuffer>(mmbh);
|
destruct_handle<MetalMemoryMappedBuffer>(mmbh);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetalDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t offset,
|
void MetalDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t offset,
|
||||||
BufferDescriptor&& data) {
|
BufferDescriptor&& data) {
|
||||||
auto* mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
|
auto mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
|
||||||
mmb->copy(*this, offset, std::move(data));
|
|
||||||
|
assert_invariant(any(mmb->access & MapBufferAccessFlags::WRITE_BIT));
|
||||||
|
assert_invariant(offset + data.size <= mmb->size);
|
||||||
|
|
||||||
|
// TODO: this isa zero-effort implementation of copyToMemoryMappedBuffer(), where we just
|
||||||
|
// call updateBufferObject(). This could be a fallback implementation for when
|
||||||
|
// shared memory is not available.
|
||||||
|
// On UMA systems, this should just be a memcpy into the memory-mapped buffer.
|
||||||
|
updateBufferObject(mmb->boh, std::move(data), mmb->offset + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// explicit instantiation of the Dispatcher
|
// explicit instantiation of the Dispatcher
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef TNT_FILAMENT_DRIVER_METALERRORQUEUE_H
|
|
||||||
#define TNT_FILAMENT_DRIVER_METALERRORQUEUE_H
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
#include <utils/compiler.h>
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <functional>
|
|
||||||
#include <mutex>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class MetalErrorQueue {
|
|
||||||
public:
|
|
||||||
bool isEmpty() const {
|
|
||||||
return !mHasErrors.load(std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void push(NSError* error) {
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
mErrors.push_back(error);
|
|
||||||
mHasErrors.store(true, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void flush(const std::function<void(NSError*)>& callback) {
|
|
||||||
if (UTILS_LIKELY(isEmpty())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::vector<NSError*> errors;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
std::swap(mErrors, errors);
|
|
||||||
mHasErrors.store(false, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& error: errors) {
|
|
||||||
callback(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<NSError*> mErrors;
|
|
||||||
std::mutex mMutex;
|
|
||||||
|
|
||||||
// Optimization to avoid locking the mutex at each call to flush.
|
|
||||||
std::atomic<bool> mHasErrors;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif // TNT_FILAMENT_DRIVER_METALERRORQUEUE_H
|
|
||||||
@@ -141,8 +141,6 @@ public:
|
|||||||
// FrameScheduledCallback.
|
// FrameScheduledCallback.
|
||||||
void present();
|
void present();
|
||||||
|
|
||||||
void setPresentationTime(int64_t timeNs) { presentationTimeNs = timeNs; }
|
|
||||||
|
|
||||||
NSUInteger getSurfaceWidth() const;
|
NSUInteger getSurfaceWidth() const;
|
||||||
NSUInteger getSurfaceHeight() const;
|
NSUInteger getSurfaceHeight() const;
|
||||||
NSUInteger getSampleCount() const;
|
NSUInteger getSampleCount() const;
|
||||||
@@ -161,7 +159,7 @@ private:
|
|||||||
bool isCaMetalLayer() const { return type == SwapChainType::CAMETALLAYER; }
|
bool isCaMetalLayer() const { return type == SwapChainType::CAMETALLAYER; }
|
||||||
bool isHeadless() const { return type == SwapChainType::HEADLESS; }
|
bool isHeadless() const { return type == SwapChainType::HEADLESS; }
|
||||||
|
|
||||||
void scheduleFrameScheduledCallback(int64_t presentationTimeNs);
|
void scheduleFrameScheduledCallback();
|
||||||
void scheduleFrameCompletedCallback();
|
void scheduleFrameCompletedCallback();
|
||||||
|
|
||||||
MetalAttachment acquireBaseDrawable();
|
MetalAttachment acquireBaseDrawable();
|
||||||
@@ -194,11 +192,6 @@ private:
|
|||||||
|
|
||||||
int64_t abandonedUntilFrame = -1;
|
int64_t abandonedUntilFrame = -1;
|
||||||
|
|
||||||
// If zero, the next presentation should happen as soon as possible.
|
|
||||||
// Otherwise, this is the timestamp when the present should happen.
|
|
||||||
// Resets to 0 after the present.
|
|
||||||
int64_t presentationTimeNs = 0;
|
|
||||||
|
|
||||||
// These fields store a callback to notify the client that a frame is ready for presentation. If
|
// These fields store a callback to notify the client that a frame is ready for presentation. If
|
||||||
// !frameScheduled.callback, then the Metal backend automatically calls presentDrawable when the
|
// !frameScheduled.callback, then the Metal backend automatically calls presentDrawable when the
|
||||||
// frame is committed. Otherwise, the Metal backend will not automatically present the frame.
|
// frame is committed. Otherwise, the Metal backend will not automatically present the frame.
|
||||||
@@ -576,22 +569,14 @@ struct MetalDescriptorSet : public HwDescriptorSet {
|
|||||||
|
|
||||||
|
|
||||||
struct MetalMemoryMappedBuffer : public HwMemoryMappedBuffer {
|
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{};
|
MapBufferAccessFlags access{};
|
||||||
struct {
|
uint32_t size = 0;
|
||||||
MetalBufferObject* bo;
|
uint32_t offset = 0;
|
||||||
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
|
} // namespace backend
|
||||||
|
|||||||
@@ -325,19 +325,11 @@ void MetalSwapChain::present() {
|
|||||||
if (frameCompleted.callback) {
|
if (frameCompleted.callback) {
|
||||||
scheduleFrameCompletedCallback();
|
scheduleFrameCompletedCallback();
|
||||||
}
|
}
|
||||||
const auto timeNs = presentationTimeNs;
|
|
||||||
presentationTimeNs = 0;
|
|
||||||
if (drawable) {
|
if (drawable) {
|
||||||
if (frameScheduled.callback) {
|
if (frameScheduled.callback) {
|
||||||
scheduleFrameScheduledCallback(timeNs);
|
scheduleFrameScheduledCallback();
|
||||||
} else {
|
} else {
|
||||||
if (presentationTimeNs) {
|
[getPendingCommandBuffer(&context) presentDrawable:drawable];
|
||||||
const CFTimeInterval timeSeconds =
|
|
||||||
(CFTimeInterval) presentationTimeNs / 1000000000.0;
|
|
||||||
[getPendingCommandBuffer(&context) presentDrawable:drawable atTime:timeSeconds];
|
|
||||||
} else {
|
|
||||||
[getPendingCommandBuffer(&context) presentDrawable:drawable];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,22 +341,15 @@ public:
|
|||||||
PresentDrawableData& operator=(const PresentDrawableData&) = delete;
|
PresentDrawableData& operator=(const PresentDrawableData&) = delete;
|
||||||
|
|
||||||
static PresentDrawableData* create(id<CAMetalDrawable> drawable,
|
static PresentDrawableData* create(id<CAMetalDrawable> drawable,
|
||||||
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags,
|
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags) {
|
||||||
int64_t presentationTimeNs) {
|
|
||||||
assert_invariant(drawableMutex);
|
assert_invariant(drawableMutex);
|
||||||
assert_invariant(driver);
|
assert_invariant(driver);
|
||||||
return new PresentDrawableData(drawable, drawableMutex, driver, flags, presentationTimeNs);
|
return new PresentDrawableData(drawable, drawableMutex, driver, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void maybePresentAndDestroyAsync(PresentDrawableData* that, bool shouldPresent) {
|
static void maybePresentAndDestroyAsync(PresentDrawableData* that, bool shouldPresent) {
|
||||||
if (shouldPresent) {
|
if (shouldPresent) {
|
||||||
if (that->mPresentationTimeNs) {
|
[that->mDrawable present];
|
||||||
const CFTimeInterval timeSeconds =
|
|
||||||
(CFTimeInterval) that->mPresentationTimeNs / 1000000000.0;
|
|
||||||
[that->mDrawable presentAtTime:timeSeconds];
|
|
||||||
} else {
|
|
||||||
[that->mDrawable present];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (that->mFlags & SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER) {
|
if (that->mFlags & SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER) {
|
||||||
@@ -382,12 +367,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
PresentDrawableData(id<CAMetalDrawable> drawable, std::shared_ptr<std::mutex> drawableMutex,
|
PresentDrawableData(id<CAMetalDrawable> drawable, std::shared_ptr<std::mutex> drawableMutex,
|
||||||
MetalDriver* driver, uint64_t flags, int64_t presentationTimeNs)
|
MetalDriver* driver, uint64_t flags)
|
||||||
: mDrawable(drawable),
|
: mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver), mFlags(flags) {}
|
||||||
mDrawableMutex(drawableMutex),
|
|
||||||
mDriver(driver),
|
|
||||||
mFlags(flags),
|
|
||||||
mPresentationTimeNs(presentationTimeNs) {}
|
|
||||||
|
|
||||||
static void cleanupAndDestroy(PresentDrawableData *that) {
|
static void cleanupAndDestroy(PresentDrawableData *that) {
|
||||||
if (that->mDrawable) {
|
if (that->mDrawable) {
|
||||||
@@ -403,7 +384,6 @@ private:
|
|||||||
std::shared_ptr<std::mutex> mDrawableMutex;
|
std::shared_ptr<std::mutex> mDrawableMutex;
|
||||||
MetalDriver* mDriver = nullptr;
|
MetalDriver* mDriver = nullptr;
|
||||||
uint64_t mFlags = 0;
|
uint64_t mFlags = 0;
|
||||||
int64_t mPresentationTimeNs = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void presentDrawable(bool presentFrame, void* user) {
|
void presentDrawable(bool presentFrame, void* user) {
|
||||||
@@ -411,7 +391,7 @@ void presentDrawable(bool presentFrame, void* user) {
|
|||||||
PresentDrawableData::maybePresentAndDestroyAsync(presentDrawableData, presentFrame);
|
PresentDrawableData::maybePresentAndDestroyAsync(presentDrawableData, presentFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetalSwapChain::scheduleFrameScheduledCallback(int64_t presentationTimeNs) {
|
void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||||
if (!frameScheduled.callback) {
|
if (!frameScheduled.callback) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -420,11 +400,8 @@ void MetalSwapChain::scheduleFrameScheduledCallback(int64_t presentationTimeNs)
|
|||||||
|
|
||||||
struct Callback {
|
struct Callback {
|
||||||
Callback(std::shared_ptr<FrameScheduledCallback> callback, id<CAMetalDrawable> drawable,
|
Callback(std::shared_ptr<FrameScheduledCallback> callback, id<CAMetalDrawable> drawable,
|
||||||
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags,
|
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags)
|
||||||
int64_t presentationTimeNs)
|
: f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver, flags)) {}
|
||||||
: f(callback),
|
|
||||||
data(PresentDrawableData::create(drawable, drawableMutex, driver, flags,
|
|
||||||
presentationTimeNs)) {}
|
|
||||||
std::shared_ptr<FrameScheduledCallback> f;
|
std::shared_ptr<FrameScheduledCallback> f;
|
||||||
// PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later.
|
// PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later.
|
||||||
std::unique_ptr<PresentDrawableData> data;
|
std::unique_ptr<PresentDrawableData> data;
|
||||||
@@ -442,7 +419,7 @@ void MetalSwapChain::scheduleFrameScheduledCallback(int64_t presentationTimeNs)
|
|||||||
uint64_t const flags = frameScheduled.flags;
|
uint64_t const flags = frameScheduled.flags;
|
||||||
ReleasablePointer* callback = [[ReleasablePointer alloc]
|
ReleasablePointer* callback = [[ReleasablePointer alloc]
|
||||||
initWithPointer:new Callback(frameScheduled.callback, drawable, layerDrawableMutex,
|
initWithPointer:new Callback(frameScheduled.callback, drawable, layerDrawableMutex,
|
||||||
context.driver, flags, presentationTimeNs)];
|
context.driver, flags)];
|
||||||
|
|
||||||
backend::CallbackHandler* handler = frameScheduled.handler;
|
backend::CallbackHandler* handler = frameScheduled.handler;
|
||||||
MetalDriver* driver = context.driver;
|
MetalDriver* driver = context.driver;
|
||||||
@@ -1687,44 +1664,5 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
|
|||||||
return buffer.get();
|
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 backend
|
||||||
} // namespace filament
|
} // namespace filament
|
||||||
|
|||||||
@@ -111,22 +111,9 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
|
|||||||
id<MTLLibrary> library = nil;
|
id<MTLLibrary> library = nil;
|
||||||
switch (program.getShaderLanguage()) {
|
switch (program.getShaderLanguage()) {
|
||||||
case ShaderLanguage::MSL: {
|
case ShaderLanguage::MSL: {
|
||||||
|
// By default, Metal uses the most recent language version.
|
||||||
MTLCompileOptions* options = [MTLCompileOptions new];
|
MTLCompileOptions* options = [MTLCompileOptions new];
|
||||||
options.fastMathEnabled = YES;
|
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');
|
assert_invariant(source[source.size() - 1] == '\0');
|
||||||
// the shader string is null terminated and the length includes the null character
|
// the shader string is null terminated and the length includes the null character
|
||||||
|
|||||||
@@ -291,6 +291,8 @@ void NoopDriver::updateBufferObject(Handle<HwBufferObject> ibh, BufferDescriptor
|
|||||||
scheduleDestroy(std::move(p));
|
scheduleDestroy(std::move(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NoopDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh, BufferObjectStreamDescriptor&& streams) { }
|
||||||
|
|
||||||
void NoopDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> ibh, BufferDescriptor&& p,
|
void NoopDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> ibh, BufferDescriptor&& p,
|
||||||
uint32_t byteOffset) {
|
uint32_t byteOffset) {
|
||||||
scheduleDestroy(std::move(p));
|
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 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
|
// - 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.
|
// destroy the protected context when we're done with it.
|
||||||
bool const recreateVaoName = vao != &mDefaultVAO &&
|
bool const recreateVaoName = p != &mDefaultVAO &&
|
||||||
((vao->vao[contextIndex] == 0) ||
|
((vao->vao[contextIndex] == 0) ||
|
||||||
(vao->nameVersion != state.age && contextIndex == 1));
|
(vao->nameVersion != state.age && contextIndex == 1));
|
||||||
if (UTILS_UNLIKELY(recreateVaoName)) {
|
if (UTILS_UNLIKELY(recreateVaoName)) {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
#include <backend/Platform.h>
|
#include <backend/Platform.h>
|
||||||
#include <backend/Program.h>
|
#include <backend/Program.h>
|
||||||
#include <backend/TargetBufferInfo.h>
|
#include <backend/TargetBufferInfo.h>
|
||||||
|
#include <backend/BufferObjectStreamDescriptor.h>
|
||||||
|
|
||||||
#include "private/backend/CommandStream.h"
|
#include "private/backend/CommandStream.h"
|
||||||
#include "private/backend/Dispatcher.h"
|
#include "private/backend/Dispatcher.h"
|
||||||
@@ -2836,6 +2837,38 @@ void OpenGLDriver::updateIndexBuffer(
|
|||||||
CHECK_GL_ERROR()
|
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(
|
void OpenGLDriver::updateBufferObject(
|
||||||
Handle<HwBufferObject> boh, BufferDescriptor&& bd, uint32_t const byteOffset) {
|
Handle<HwBufferObject> boh, BufferDescriptor&& bd, uint32_t const byteOffset) {
|
||||||
DEBUG_MARKER()
|
DEBUG_MARKER()
|
||||||
@@ -2849,6 +2882,19 @@ void OpenGLDriver::updateBufferObject(
|
|||||||
gl.bindVertexArray(nullptr);
|
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())) {
|
if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
|
||||||
assert_invariant(bo->gl.buffer);
|
assert_invariant(bo->gl.buffer);
|
||||||
memcpy(static_cast<uint8_t*>(bo->gl.buffer) + byteOffset, bd.buffer, bd.size);
|
memcpy(static_cast<uint8_t*>(bo->gl.buffer) + byteOffset, bd.buffer, bd.size);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include <backend/Platform.h>
|
#include <backend/Platform.h>
|
||||||
#include <backend/Program.h>
|
#include <backend/Program.h>
|
||||||
#include <backend/TargetBufferInfo.h>
|
#include <backend/TargetBufferInfo.h>
|
||||||
|
#include <backend/BufferObjectStreamDescriptor.h>
|
||||||
|
|
||||||
#include "private/backend/Driver.h"
|
#include "private/backend/Driver.h"
|
||||||
#include "private/backend/HandleAllocator.h"
|
#include "private/backend/HandleAllocator.h"
|
||||||
@@ -393,6 +394,8 @@ private:
|
|||||||
// the must be accessed from the user thread only
|
// the must be accessed from the user thread only
|
||||||
std::vector<GLStream*> mStreamsWithPendingAcquiredImage;
|
std::vector<GLStream*> mStreamsWithPendingAcquiredImage;
|
||||||
|
|
||||||
|
std::unordered_map<GLuint, BufferObjectStreamDescriptor> mStreamUniformDescriptors;
|
||||||
|
|
||||||
void attachStream(GLTexture* t, GLStream* stream);
|
void attachStream(GLTexture* t, GLStream* stream);
|
||||||
void detachStream(GLTexture* t) noexcept;
|
void detachStream(GLTexture* t) noexcept;
|
||||||
void replaceStream(GLTexture* t, GLStream* stream) noexcept;
|
void replaceStream(GLTexture* t, GLStream* stream) noexcept;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#if defined(__ANDROID__) || defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(__EMSCRIPTEN__) || defined(FILAMENT_SUPPORTS_EGL_ON_LINUX)
|
#if defined(__ANDROID__) || defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(__EMSCRIPTEN__)
|
||||||
|
|
||||||
#if defined(__EMSCRIPTEN__)
|
#if defined(__EMSCRIPTEN__)
|
||||||
# include <GLES3/gl3.h>
|
# include <GLES3/gl3.h>
|
||||||
|
|||||||
@@ -28,13 +28,14 @@
|
|||||||
#include <private/backend/VirtualMachineEnv.h>
|
#include <private/backend/VirtualMachineEnv.h>
|
||||||
|
|
||||||
#include "AndroidNativeWindow.h"
|
#include "AndroidNativeWindow.h"
|
||||||
#include "AndroidFrameCallback.h"
|
|
||||||
#include "AndroidSwapChainHelper.h"
|
#include "AndroidSwapChainHelper.h"
|
||||||
#include "ExternalStreamManagerAndroid.h"
|
#include "ExternalStreamManagerAndroid.h"
|
||||||
|
|
||||||
#include <android/api-level.h>
|
#include <android/api-level.h>
|
||||||
#include <android/native_window.h>
|
#include <android/native_window.h>
|
||||||
#include <android/hardware_buffer.h>
|
#include <android/hardware_buffer.h>
|
||||||
|
#include <android/choreographer.h>
|
||||||
|
#include <android/looper.h>
|
||||||
|
|
||||||
#include <utils/android/PerformanceHintManager.h>
|
#include <utils/android/PerformanceHintManager.h>
|
||||||
#include <utils/compiler.h>
|
#include <utils/compiler.h>
|
||||||
@@ -98,27 +99,6 @@ using namespace glext;
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
struct PlatformEGLAndroid::SwapChainEGLAndroid : public SwapChainEGL {
|
|
||||||
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
|
|
||||||
void* nativeWindow, uint64_t flags);
|
|
||||||
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
|
|
||||||
uint32_t width, uint32_t height, uint64_t flags);
|
|
||||||
void terminate(PlatformEGLAndroid& platform);
|
|
||||||
bool setPresentFrameId(uint64_t frameId) const noexcept;
|
|
||||||
uint64_t getFrameId(uint64_t frameId) const noexcept;
|
|
||||||
bool compositorTimingSupported = false;
|
|
||||||
bool frameTimestampsSupported = false;
|
|
||||||
private:
|
|
||||||
AndroidSwapChainHelper mImpl{};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PlatformEGLAndroid::AndroidDetails {
|
|
||||||
AndroidProducerThrottling producerThrottling;
|
|
||||||
AndroidFrameCallback androidFrameCallback;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() {
|
PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() {
|
||||||
// PerformanceHintManager() needs the calling thread to be a Java thread; so we need
|
// PerformanceHintManager() needs the calling thread to be a Java thread; so we need
|
||||||
// to attach this thread to the JVM before we initialize PerformanceHintManager.
|
// to attach this thread to the JVM before we initialize PerformanceHintManager.
|
||||||
@@ -132,20 +112,17 @@ PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmFor
|
|||||||
// ---------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
PlatformEGLAndroid::PlatformEGLAndroid() noexcept
|
PlatformEGLAndroid::PlatformEGLAndroid() noexcept
|
||||||
: mExternalStreamManager(ExternalStreamManagerAndroid::create()),
|
: mExternalStreamManager(ExternalStreamManagerAndroid::create()) {
|
||||||
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
|
|
||||||
mOSVersion = android_get_device_api_level();
|
mOSVersion = android_get_device_api_level();
|
||||||
if (mOSVersion < 0) {
|
if (mOSVersion < 0) {
|
||||||
mOSVersion = __ANDROID_API_FUTURE__;
|
mOSVersion = __ANDROID_API_FUTURE__;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
|
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept = default;
|
||||||
delete &mAndroidDetails;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlatformEGLAndroid::terminate() noexcept {
|
void PlatformEGLAndroid::terminate() noexcept {
|
||||||
mAndroidDetails.androidFrameCallback.terminate();
|
mAndroidFrameCallback.terminate();
|
||||||
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
|
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
|
||||||
PlatformEGL::terminate();
|
PlatformEGL::terminate();
|
||||||
}
|
}
|
||||||
@@ -178,30 +155,20 @@ void PlatformEGLAndroid::beginFrame(
|
|||||||
int64_t const monotonic_clock_ns,
|
int64_t const monotonic_clock_ns,
|
||||||
int64_t refreshIntervalNs,
|
int64_t refreshIntervalNs,
|
||||||
uint32_t const frameId) noexcept {
|
uint32_t const frameId) noexcept {
|
||||||
// if frameId is 0, it means we're not associated to a particular frame, which is the case
|
// associate the user frameid with the system frame id
|
||||||
// for standalone views. And in this case we skip the performance hint (since we wouldn't get
|
setPresentFrameId(mCurrentDrawSwapChain, frameId);
|
||||||
// the right timing anyway as well as the frame info timing collections)
|
|
||||||
|
|
||||||
// associate the user frameid with the system frame id.
|
if (mPerformanceHintSession.isValid()) {
|
||||||
if (frameId && mCurrentDrawSwapChain) {
|
|
||||||
// mCurrentDrawSwapChain could be null if we're called from renderStandaloneView
|
|
||||||
setPresentFrameId(mCurrentDrawSwapChain, frameId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frameId && mPerformanceHintSession.isValid()) {
|
|
||||||
if (refreshIntervalNs <= 0) {
|
if (refreshIntervalNs <= 0) {
|
||||||
// we're not provided with a target time, use the display period, if everything fails,
|
// we're not provided with a target time, use the display period, if everything fails,
|
||||||
// assume 16.67ms
|
// assume 16.67ms
|
||||||
refreshIntervalNs = 16'666'667;
|
refreshIntervalNs = 16'666'667;
|
||||||
|
|
||||||
if (mCurrentDrawSwapChain) {
|
CompositorTiming compositorTiming{};
|
||||||
// mCurrentDrawSwapChain could be null if we're called from renderStandaloneView
|
bool const hasCompositorTiming =
|
||||||
CompositorTiming compositorTiming{};
|
queryCompositorTiming(mCurrentDrawSwapChain, &compositorTiming);
|
||||||
bool const hasCompositorTiming =
|
if (hasCompositorTiming && compositorTiming.compositeInterval > 0) {
|
||||||
queryCompositorTiming(mCurrentDrawSwapChain, &compositorTiming);
|
refreshIntervalNs = compositorTiming.compositeInterval;
|
||||||
if (hasCompositorTiming && compositorTiming.compositeInterval > 0) {
|
|
||||||
refreshIntervalNs = compositorTiming.compositeInterval;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mStartTimeOfActualWork = clock::time_point(std::chrono::nanoseconds(monotonic_clock_ns));
|
mStartTimeOfActualWork = clock::time_point(std::chrono::nanoseconds(monotonic_clock_ns));
|
||||||
@@ -240,9 +207,9 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
|
|||||||
"eglGetNativeClientBufferANDROID"));
|
"eglGetNativeClientBufferANDROID"));
|
||||||
|
|
||||||
if (ext.egl.ANDROID_presentation_time) {
|
if (ext.egl.ANDROID_presentation_time) {
|
||||||
eglPresentationTimeANDROID =
|
eglGetNativeClientBufferANDROID =
|
||||||
PFNEGLPRESENTATIONTIMEANDROIDPROC(eglGetProcAddress(
|
PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC(eglGetProcAddress(
|
||||||
"eglPresentationTimeANDROID"));
|
"eglGetNativeClientBufferANDROID"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ext.egl.ANDROID_get_frame_timestamps) {
|
if (ext.egl.ANDROID_get_frame_timestamps) {
|
||||||
@@ -273,7 +240,7 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
|
|||||||
|
|
||||||
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
|
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
|
||||||
|
|
||||||
mAndroidDetails.androidFrameCallback.init();
|
mAndroidFrameCallback.init();
|
||||||
|
|
||||||
return driver;
|
return driver;
|
||||||
}
|
}
|
||||||
@@ -297,25 +264,15 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
|
|||||||
}
|
}
|
||||||
|
|
||||||
AndroidFrameCallback::Timeline const preferredTimeline{
|
AndroidFrameCallback::Timeline const preferredTimeline{
|
||||||
mAndroidDetails.androidFrameCallback.getPreferredTimeline() };
|
mAndroidFrameCallback.getPreferredTimeline() };
|
||||||
outCompositorTiming->frameTime = preferredTimeline.frameTime;
|
outCompositorTiming->frameTime = preferredTimeline.frameTime;
|
||||||
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
|
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
|
||||||
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
|
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)) {
|
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
|
||||||
EGLSurface const sur = static_cast<SwapChainEGL const *>(swapchain)->sur;
|
EGLSurface const sur = static_cast<SwapChainEGL const *>(swapchain)->sur;
|
||||||
if (sur == EGL_NO_SURFACE) {
|
if (sur == EGL_NO_SURFACE) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<EGLnsecsANDROID, 3> values;
|
std::array<EGLnsecsANDROID, 3> values;
|
||||||
@@ -326,16 +283,26 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
|
|||||||
};
|
};
|
||||||
EGLBoolean const success = eglGetCompositorTimingANDROID(getEglDisplay(), sur,
|
EGLBoolean const success = eglGetCompositorTimingANDROID(getEglDisplay(), sur,
|
||||||
names.size(), names.data(), values.data());
|
names.size(), names.data(), values.data());
|
||||||
if (UTILS_UNLIKELY(!success)) {
|
if (!success) {
|
||||||
// reset current error to EGL_SUCCESS
|
return false;
|
||||||
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;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
// fallback to private APIs
|
||||||
|
auto const anw = static_cast<SwapChainEGL const *>(swapchain)->nativeWindow;
|
||||||
|
int const status = NativeWindow::getCompositorTiming(anw,
|
||||||
|
&outCompositorTiming->compositeDeadline,
|
||||||
|
&outCompositorTiming->compositeInterval,
|
||||||
|
&outCompositorTiming->compositeToPresentLatency);
|
||||||
|
if (status == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlatformEGL::queryCompositorTiming(swapchain, outCompositorTiming);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlatformEGLAndroid::setPresentFrameId(SwapChain const* swapchain,
|
bool PlatformEGLAndroid::setPresentFrameId(SwapChain const* swapchain,
|
||||||
@@ -360,10 +327,6 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!static_cast<SwapChainEGLAndroid const *>(swapchain)->frameTimestampsSupported) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
|
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
|
||||||
EGLSurface const sur = sc->sur;
|
EGLSurface const sur = sc->sur;
|
||||||
if (sur == EGL_NO_SURFACE) {
|
if (sur == EGL_NO_SURFACE) {
|
||||||
@@ -384,9 +347,7 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
|
|||||||
};
|
};
|
||||||
EGLBoolean const success = eglGetFrameTimestampsANDROID(getEglDisplay(), sur, hwFrameId,
|
EGLBoolean const success = eglGetFrameTimestampsANDROID(getEglDisplay(), sur, hwFrameId,
|
||||||
names.size(), names.data(), values.data());
|
names.size(), names.data(), values.data());
|
||||||
if (UTILS_UNLIKELY(!success)) {
|
if (!success) {
|
||||||
// reset current error to EGL_SUCCESS
|
|
||||||
eglGetError();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
outFrameTimestamps->requestedPresentTime = values[0];
|
outFrameTimestamps->requestedPresentTime = values[0];
|
||||||
@@ -400,44 +361,28 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
|
|||||||
outFrameTimestamps->releaseTime = values[8];
|
outFrameTimestamps->releaseTime = values[8];
|
||||||
return true;
|
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);
|
return PlatformEGL::queryFrameTimestamps(swapchain, frameId, outFrameTimestamps);
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform::SwapChain* PlatformEGLAndroid::createSwapChain(void* nativeWindow, uint64_t const flags) {
|
Platform::SwapChain* PlatformEGLAndroid::createSwapChain(void* nativeWindow, uint64_t const flags) {
|
||||||
auto* const sc = new(std::nothrow) SwapChainEGLAndroid(*this, nativeWindow, 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;
|
return sc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,10 +395,6 @@ Platform::SwapChain* PlatformEGLAndroid::createSwapChain(
|
|||||||
void PlatformEGLAndroid::destroySwapChain(SwapChain* swapChain) noexcept {
|
void PlatformEGLAndroid::destroySwapChain(SwapChain* swapChain) noexcept {
|
||||||
if (swapChain) {
|
if (swapChain) {
|
||||||
SwapChainEGLAndroid* const sc = static_cast<SwapChainEGLAndroid*>(swapChain);
|
SwapChainEGLAndroid* const sc = static_cast<SwapChainEGLAndroid*>(swapChain);
|
||||||
if (mCurrentDrawSwapChain == sc) {
|
|
||||||
// don't keep a dangling pointer around
|
|
||||||
mCurrentDrawSwapChain = nullptr;
|
|
||||||
}
|
|
||||||
sc->terminate(*this);
|
sc->terminate(*this);
|
||||||
delete sc;
|
delete sc;
|
||||||
}
|
}
|
||||||
@@ -734,12 +675,12 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage const sou
|
|||||||
|
|
||||||
|
|
||||||
bool PlatformEGLAndroid::isProducerThrottlingControlSupported() const {
|
bool PlatformEGLAndroid::isProducerThrottlingControlSupported() const {
|
||||||
return mAndroidDetails.producerThrottling.isSupported();
|
return mProducerThrottling.isSupported();
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t PlatformEGLAndroid::setProducerThrottlingEnabled(
|
int32_t PlatformEGLAndroid::setProducerThrottlingEnabled(
|
||||||
EGLNativeWindowType const nativeWindow, bool const enabled) const {
|
EGLNativeWindowType const nativeWindow, bool const enabled) const {
|
||||||
return mAndroidDetails.producerThrottling.setProducerThrottlingEnabled(nativeWindow, enabled);
|
return mProducerThrottling.setProducerThrottlingEnabled(nativeWindow, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------
|
||||||
@@ -763,6 +704,8 @@ PlatformEGLAndroid::SwapChainEGLAndroid::SwapChainEGLAndroid(PlatformEGLAndroid
|
|||||||
// we ignore the result, it doesn't matter much if it fails
|
// we ignore the result, it doesn't matter much if it fails
|
||||||
eglSurfaceAttrib(platform.getEglDisplay(), sur, EGL_TIMESTAMPS_ANDROID, EGL_TRUE);
|
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 <backend/platforms/PlatformEGLHeadless.h>
|
||||||
|
|
||||||
#include <bluegl/BlueGL.h>
|
#include "opengl/GLUtils.h"
|
||||||
|
|
||||||
#include <EGL/egl.h>
|
#include <EGL/egl.h>
|
||||||
#include <EGL/eglext.h>
|
#include <EGL/eglext.h>
|
||||||
@@ -35,31 +35,16 @@ PlatformEGLHeadless::PlatformEGLHeadless() noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool PlatformEGLHeadless::isOpenGL() const noexcept {
|
bool PlatformEGLHeadless::isOpenGL() const noexcept {
|
||||||
#if defined(BACKEND_OPENGL_VERSION_GL)
|
return true;
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif // defined(BACKEND_OPENGL_VERSION_GL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
backend::Driver* PlatformEGLHeadless::createDriver(void* sharedContext,
|
backend::Driver* PlatformEGLHeadless::createDriver(void* sharedContext,
|
||||||
const Platform::DriverConfig& driverConfig) {
|
const Platform::DriverConfig& driverConfig) {
|
||||||
auto bindApiHelper = [](EGLenum api, const char* errorString) -> bool {
|
EGLBoolean bindAPI = eglBindAPI(EGL_OPENGL_API);
|
||||||
EGLBoolean bindAPI = eglBindAPI(api);
|
if (UTILS_UNLIKELY(!bindAPI)) {
|
||||||
if (UTILS_UNLIKELY(bindAPI == EGL_FALSE || bindAPI == EGL_BAD_PARAMETER)) {
|
LOG(ERROR) << "eglBindAPI EGL_OPENGL_API failed";
|
||||||
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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
int bindBlueGL = bluegl::bind();
|
int bindBlueGL = bluegl::bind();
|
||||||
if (UTILS_UNLIKELY(bindBlueGL != 0)) {
|
if (UTILS_UNLIKELY(bindBlueGL != 0)) {
|
||||||
LOG(ERROR) << "bluegl bind failed";
|
LOG(ERROR) << "bluegl bind failed";
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <backend/BufferDescriptor.h>
|
#include <backend/BufferDescriptor.h>
|
||||||
|
#include <backend/BufferObjectStreamDescriptor.h>
|
||||||
#include <backend/DescriptorSetOffsetArray.h>
|
#include <backend/DescriptorSetOffsetArray.h>
|
||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
#include <backend/PipelineState.h>
|
#include <backend/PipelineState.h>
|
||||||
@@ -433,6 +434,10 @@ io::ostream& operator<<(io::ostream& out, BufferDescriptor const& b) {
|
|||||||
<< ", user=" << b.getUser() << " }";
|
<< ", 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) {
|
io::ostream& operator<<(io::ostream& out, PixelBufferDescriptor const& b) {
|
||||||
BufferDescriptor const& base = static_cast<BufferDescriptor const&>(b);
|
BufferDescriptor const& base = static_cast<BufferDescriptor const&>(b);
|
||||||
return out << "PixelBufferDescriptor{ " << base
|
return out << "PixelBufferDescriptor{ " << base
|
||||||
|
|||||||
@@ -62,9 +62,8 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c
|
|||||||
// a memcpy if its available.
|
// a memcpy if its available.
|
||||||
bool const isStaticOrShared =
|
bool const isStaticOrShared =
|
||||||
any(mUsage & (BufferUsage::STATIC | BufferUsage::SHARED_WRITE_BIT));
|
any(mUsage & (BufferUsage::STATIC | BufferUsage::SHARED_WRITE_BIT));
|
||||||
bool const useMemcpy =
|
bool const useMemcpy = (isUniformAndAvailable || isStaticOrShared) && isMemcopyable &&
|
||||||
((isUniformAndAvailable && mStagingBufferBypassEnabled) || isStaticOrShared) &&
|
mStagingBufferBypassEnabled;
|
||||||
isMemcopyable;
|
|
||||||
if (useMemcpy) {
|
if (useMemcpy) {
|
||||||
char* dest = static_cast<char*>(mBuffer->getGpuBuffer()->allocationInfo.pMappedData) +
|
char* dest = static_cast<char*>(mBuffer->getGpuBuffer()->allocationInfo.pMappedData) +
|
||||||
byteOffset;
|
byteOffset;
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
#include "VulkanCommands.h"
|
#include "VulkanCommands.h"
|
||||||
#include "VulkanHandles.h"
|
#include "VulkanHandles.h"
|
||||||
#include "VulkanConstants.h"
|
#include "VulkanConstants.h"
|
||||||
#include "utils/compiler.h"
|
|
||||||
|
|
||||||
#include <utils/FixedCapacityVector.h>
|
#include <utils/FixedCapacityVector.h>
|
||||||
#include <utils/Panic.h>
|
#include <utils/Panic.h>
|
||||||
@@ -32,18 +31,6 @@ namespace filament::backend {
|
|||||||
|
|
||||||
namespace {
|
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 DescriptorSetLayoutArray = VulkanDescriptorSetCache::DescriptorSetLayoutArray;
|
||||||
using DescriptorCount = VulkanDescriptorSetCache::DescriptorCount;
|
using DescriptorCount = VulkanDescriptorSetCache::DescriptorCount;
|
||||||
|
|
||||||
@@ -317,7 +304,8 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
|
|||||||
// This code actually binds the descriptor sets.
|
// This code actually binds the descriptor sets.
|
||||||
auto set = updateSets[index];
|
auto set = updateSets[index];
|
||||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||||
VkDescriptorSet const vkset = set->getVkSet();
|
VkDescriptorSet vkset = useExternalSamplers[index] ? set->getExternalSamplerVkSet() :
|
||||||
|
set->getVkSet();
|
||||||
commands->acquire(set);
|
commands->acquire(set);
|
||||||
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
|
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
|
||||||
1, &vkset, set->uniqueDynamicUboCount, set->getOffsets()->data());
|
1, &vkset, set->uniqueDynamicUboCount, set->getOffsets()->data());
|
||||||
@@ -353,39 +341,17 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
|
|||||||
.pBufferInfo = &info,
|
.pBufferInfo = &info,
|
||||||
};
|
};
|
||||||
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
|
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);
|
set->acquire(bufferObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
void VulkanDescriptorSetCache::updateSamplerImpl(VkDescriptorSet vkset, uint8_t binding,
|
||||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
|
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept {
|
||||||
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();
|
VkImageSubresourceRange range = texture->getPrimaryViewRange();
|
||||||
VkImageViewType const expectedType = texture->getViewType();
|
VkImageViewType const expectedType = texture->getViewType();
|
||||||
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
|
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
|
||||||
@@ -410,6 +376,19 @@ void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescr
|
|||||||
.pImageInfo = &info,
|
.pImageInfo = &info,
|
||||||
};
|
};
|
||||||
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
|
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);
|
set->acquire(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,7 +405,7 @@ fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet
|
|||||||
auto const& count = layout->count;
|
auto const& count = layout->count;
|
||||||
auto const vklayout = layout->getVkLayout();
|
auto const vklayout = layout->getVkLayout();
|
||||||
auto set = fvkmemory::resource_ptr<VulkanDescriptorSet>::make(
|
auto set = fvkmemory::resource_ptr<VulkanDescriptorSet>::make(
|
||||||
mResourceManager, handle, layout,
|
mResourceManager, handle, layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
|
||||||
[vkSet, count, vklayout, this](
|
[vkSet, count, vklayout, this](
|
||||||
VulkanDescriptorSet*) { this->manualRecycle(count, vklayout, vkSet); },
|
VulkanDescriptorSet*) { this->manualRecycle(count, vklayout, vkSet); },
|
||||||
vkSet);
|
vkSet);
|
||||||
@@ -449,22 +428,4 @@ void VulkanDescriptorSetCache::manualRecycle(VulkanDescriptorSetLayout::Count co
|
|||||||
|
|
||||||
void VulkanDescriptorSetCache::gc() { mStashedSets = {}; }
|
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
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -55,8 +55,7 @@ public:
|
|||||||
VkDeviceSize size) noexcept;
|
VkDeviceSize size) noexcept;
|
||||||
|
|
||||||
void updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
|
void updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
|
||||||
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler,
|
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept;
|
||||||
VkDescriptorSetLayout externalSamplerLayout = VK_NULL_HANDLE) noexcept;
|
|
||||||
|
|
||||||
void updateSamplerForExternalSamplerSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
|
void updateSamplerForExternalSamplerSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
|
||||||
fvkmemory::resource_ptr<VulkanTexture> texture) noexcept;
|
fvkmemory::resource_ptr<VulkanTexture> texture) noexcept;
|
||||||
@@ -91,8 +90,8 @@ public:
|
|||||||
void resetCachedState() noexcept { mLastBoundInfo = {}; }
|
void resetCachedState() noexcept { mLastBoundInfo = {}; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void copySet(VkDescriptorSet srcSet, VkDescriptorSet destSet,
|
void updateSamplerImpl(VkDescriptorSet set, uint8_t binding,
|
||||||
fvkutils::SamplerBitmask copyBindings) const;
|
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept;
|
||||||
|
|
||||||
class DescriptorInfinitePool;
|
class DescriptorInfinitePool;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
#include "vulkan/memory/ResourcePointer.h"
|
#include "vulkan/memory/ResourcePointer.h"
|
||||||
#include "vulkan/utils/Conversion.h"
|
#include "vulkan/utils/Conversion.h"
|
||||||
#include "vulkan/utils/Definitions.h"
|
#include "vulkan/utils/Definitions.h"
|
||||||
#include "vulkan/utils/Image.h"
|
|
||||||
|
|
||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
#include <backend/platforms/VulkanPlatform.h>
|
#include <backend/platforms/VulkanPlatform.h>
|
||||||
@@ -245,7 +244,6 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
|
|||||||
mQueryManager(mPlatform->getDevice()),
|
mQueryManager(mPlatform->getDevice()),
|
||||||
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
|
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
|
||||||
&mDescriptorSetLayoutCache),
|
&mDescriptorSetLayoutCache),
|
||||||
mStreamedImageManager(&mExternalImageManager),
|
|
||||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
|
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
|
||||||
mIsMSAASwapChainSupported(false), // TODO: support MSAA swapchain
|
mIsMSAASwapChainSupported(false), // TODO: support MSAA swapchain
|
||||||
mStereoscopicType(driverConfig.stereoscopicType) {
|
mStereoscopicType(driverConfig.stereoscopicType) {
|
||||||
@@ -422,14 +420,12 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
|
|||||||
int64_t refreshIntervalNs, uint32_t frameId) {
|
int64_t refreshIntervalNs, uint32_t frameId) {
|
||||||
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
|
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
|
||||||
|
|
||||||
// if frameId is 0, it means we're not associated to a particular frame, which is the case
|
if (mCurrentSwapChain) { // This should be guaranteed
|
||||||
// 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);
|
mPlatform->setPresentFrameId(mCurrentSwapChain->swapChain, frameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if any command have finished and reset all its used resources. The resources
|
// Check if any command have finished and reset all its used resources. The resources
|
||||||
// won't be destroyed but their reference count will be decreased if the command is already
|
// wont be destroyed but their reference count will decreased if the command is already
|
||||||
// completed.
|
// completed.
|
||||||
//
|
//
|
||||||
// This will let us check if any VulkanBuffer is currently in flight or not.
|
// This will let us check if any VulkanBuffer is currently in flight or not.
|
||||||
@@ -483,9 +479,6 @@ void VulkanDriver::updateDescriptorSetTexture(
|
|||||||
if (UTILS_UNLIKELY(mExternalImageManager.isExternallySampledTexture(texture))) {
|
if (UTILS_UNLIKELY(mExternalImageManager.isExternallySampledTexture(texture))) {
|
||||||
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
|
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
|
||||||
mAppState.hasBoundExternalImages = true;
|
mAppState.hasBoundExternalImages = true;
|
||||||
} else if (bool(texture->getStream())) {
|
|
||||||
mStreamedImageManager.bindStreamedTexture(set, binding, texture, params);
|
|
||||||
mAppState.hasBoundExternalImages = true;
|
|
||||||
} else {
|
} else {
|
||||||
VulkanSamplerCache::Params cacheParams = {
|
VulkanSamplerCache::Params cacheParams = {
|
||||||
.sampler = params,
|
.sampler = params,
|
||||||
@@ -493,7 +486,6 @@ void VulkanDriver::updateDescriptorSetTexture(
|
|||||||
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
|
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
|
||||||
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
|
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
|
||||||
mExternalImageManager.clearTextureBinding(set, binding);
|
mExternalImageManager.clearTextureBinding(set, binding);
|
||||||
mStreamedImageManager.unbindStreamedTexture(set, binding);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,7 +497,10 @@ void VulkanDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_
|
|||||||
assert_invariant(any(mmb->access & MapBufferAccessFlags::WRITE_BIT));
|
assert_invariant(any(mmb->access & MapBufferAccessFlags::WRITE_BIT));
|
||||||
assert_invariant(offset + data.size <= mmb->size);
|
assert_invariant(offset + data.size <= mmb->size);
|
||||||
|
|
||||||
// On UMA systems, this is a memcpy into the memory-mapped buffer, otherwise use a staging buffer.
|
// TODO: this isa zero-effort implementation of copyToMemoryMappedBuffer(), where we just
|
||||||
|
// call updateBufferObject(). This could be a fallback implementation for when
|
||||||
|
// shared memory is not available.
|
||||||
|
// On UMA systems, this should just be a memcpy into the memory-mapped buffer.
|
||||||
updateBufferObject(mmb->boh, std::move(data), mmb->offset + offset);
|
updateBufferObject(mmb->boh, std::move(data), mmb->offset + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -926,6 +921,14 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(__ANDROID__)
|
||||||
|
// on Android, disable producer throttling
|
||||||
|
if (mProducerThrottling.isSupported()) {
|
||||||
|
mProducerThrottling.setProducerThrottlingEnabled(
|
||||||
|
static_cast<ANativeWindow*>(nativeWindow), false);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
auto swapChain = resource_ptr<VulkanSwapChain>::make(&mResourceManager, sch, mPlatform,
|
auto swapChain = resource_ptr<VulkanSwapChain>::make(&mResourceManager, sch, mPlatform,
|
||||||
mContext, &mResourceManager, mAllocator, &mCommands, mStagePool, nativeWindow, flags);
|
mContext, &mResourceManager, mAllocator, &mCommands, mStagePool, nativeWindow, flags);
|
||||||
swapChain.inc();
|
swapChain.inc();
|
||||||
@@ -1113,12 +1116,6 @@ void VulkanDriver::destroySwapChain(Handle<HwSwapChain> sch) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroyStream(Handle<HwStream> sh) {
|
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) {
|
void VulkanDriver::destroySync(Handle<HwSync> sh) {
|
||||||
@@ -1156,38 +1153,18 @@ void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream, utils::ImmutableCString tag) {
|
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream, utils::ImmutableCString tag) {
|
||||||
FVK_LOGW << "createStreamNative not supported in Vulkan.";
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwStream> VulkanDriver::createStreamAcquired(utils::ImmutableCString tag) {
|
Handle<HwStream> VulkanDriver::createStreamAcquired(utils::ImmutableCString tag) {
|
||||||
// @TODO This is still not thread-safe. We might have to revisit this question.
|
return {};
|
||||||
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,
|
void VulkanDriver::setAcquiredImage(Handle<HwStream> sh, void* image, const math::mat3f& transform,
|
||||||
CallbackHandler* handler, StreamCallback cb, void* userData) {
|
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) {
|
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) {
|
int64_t VulkanDriver::getStreamTimestamp(Handle<HwStream> sh) {
|
||||||
@@ -1195,70 +1172,6 @@ int64_t VulkanDriver::getStreamTimestamp(Handle<HwStream> sh) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::updateStreams(CommandStream* driver) {
|
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) {
|
void VulkanDriver::destroyFence(Handle<HwFence> fh) {
|
||||||
@@ -1554,13 +1467,17 @@ void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor
|
|||||||
scheduleDestroy(std::move(p));
|
scheduleDestroy(std::move(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VulkanDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh,
|
||||||
|
BufferObjectStreamDescriptor&& streams) {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
|
||||||
void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& bd,
|
void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& bd,
|
||||||
uint32_t byteOffset) {
|
uint32_t byteOffset) {
|
||||||
VulkanCommandBuffer& commands = mCommands.get();
|
VulkanCommandBuffer& commands = mCommands.get();
|
||||||
|
|
||||||
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
|
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
|
||||||
commands.acquire(bo);
|
commands.acquire(bo);
|
||||||
|
|
||||||
bo->loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
|
bo->loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
|
||||||
|
|
||||||
scheduleDestroy(std::move(bd));
|
scheduleDestroy(std::move(bd));
|
||||||
@@ -1628,12 +1545,6 @@ TimerQueryResult VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
|
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) {
|
void VulkanDriver::generateMipmaps(Handle<HwTexture> th) {
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
#include "vulkan/VulkanDescriptorSetCache.h"
|
#include "vulkan/VulkanDescriptorSetCache.h"
|
||||||
#include "vulkan/VulkanDescriptorSetLayoutCache.h"
|
#include "vulkan/VulkanDescriptorSetLayoutCache.h"
|
||||||
#include "vulkan/VulkanExternalImageManager.h"
|
#include "vulkan/VulkanExternalImageManager.h"
|
||||||
#include "vulkan/VulkanStreamedImageManager.h"
|
|
||||||
#include "vulkan/VulkanPipelineLayoutCache.h"
|
#include "vulkan/VulkanPipelineLayoutCache.h"
|
||||||
#include "vulkan/memory/ResourceManager.h"
|
#include "vulkan/memory/ResourceManager.h"
|
||||||
#include "vulkan/memory/ResourcePointer.h"
|
#include "vulkan/memory/ResourcePointer.h"
|
||||||
@@ -49,6 +48,10 @@
|
|||||||
#include <utils/Allocator.h>
|
#include <utils/Allocator.h>
|
||||||
#include <utils/compiler.h>
|
#include <utils/compiler.h>
|
||||||
|
|
||||||
|
#if defined(__ANDROID__)
|
||||||
|
#include "AndroidNativeWindow.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
class VulkanPlatform;
|
class VulkanPlatform;
|
||||||
@@ -158,8 +161,6 @@ private:
|
|||||||
VulkanDescriptorSetCache mDescriptorSetCache;
|
VulkanDescriptorSetCache mDescriptorSetCache;
|
||||||
VulkanQueryManager mQueryManager;
|
VulkanQueryManager mQueryManager;
|
||||||
VulkanExternalImageManager mExternalImageManager;
|
VulkanExternalImageManager mExternalImageManager;
|
||||||
VulkanStreamedImageManager mStreamedImageManager;
|
|
||||||
|
|
||||||
|
|
||||||
// This maps a VulkanSwapchain to a native swapchain. VulkanSwapchain should have a copy of the
|
// This maps a VulkanSwapchain to a native swapchain. VulkanSwapchain should have a copy of the
|
||||||
// Platform::Swapchain pointer, but queryFrameTimestamps() and queryCompositorTiming() are
|
// Platform::Swapchain pointer, but queryFrameTimestamps() and queryCompositorTiming() are
|
||||||
@@ -205,10 +206,9 @@ private:
|
|||||||
bool const mIsSRGBSwapChainSupported;
|
bool const mIsSRGBSwapChainSupported;
|
||||||
bool const mIsMSAASwapChainSupported;
|
bool const mIsMSAASwapChainSupported;
|
||||||
backend::StereoscopicType const mStereoscopicType;
|
backend::StereoscopicType const mStereoscopicType;
|
||||||
|
#if defined(__ANDROID__)
|
||||||
// setAcquiredImage is a DECL_DRIVER_API_SYNCHRONOUS_N which means we don't necessarily have the
|
AndroidProducerThrottling mProducerThrottling;
|
||||||
// data to process it at call time. So we store it and process it during updateStreams.
|
#endif
|
||||||
std::vector<resource_ptr<VulkanStream>> mStreamsWithPendingAcquiredImage;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ namespace filament::backend {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
using Bitmask = fvkutils::UniformBufferBitmask;
|
||||||
|
static_assert(sizeof(Bitmask) * 8 == fvkutils::MAX_DESCRIPTOR_SET_BITMASK_BITS);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void erasep(std::vector<T>& v, std::function<bool(T const&)> f) {
|
void erasep(std::vector<T>& v, std::function<bool(T const&)> f) {
|
||||||
auto newEnd = std::remove_if(v.begin(), v.end(), f);
|
auto newEnd = std::remove_if(v.begin(), v.end(), f);
|
||||||
@@ -47,6 +50,31 @@ ImageData& findImage(std::vector<ImageData>& images,
|
|||||||
return *itr;
|
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
|
}// namespace
|
||||||
|
|
||||||
VulkanExternalImageManager::VulkanExternalImageManager(VulkanPlatform* platform,
|
VulkanExternalImageManager::VulkanExternalImageManager(VulkanPlatform* platform,
|
||||||
@@ -113,9 +141,6 @@ void VulkanExternalImageManager::updateSetAndLayout(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto& imageData = findImage(mImages, bindingInfo.image);
|
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);
|
updateImage(&imageData);
|
||||||
|
|
||||||
auto samplerParams = bindingInfo.samplerParams;
|
auto samplerParams = bindingInfo.samplerParams;
|
||||||
@@ -147,14 +172,38 @@ void VulkanExternalImageManager::updateSetAndLayout(
|
|||||||
std::for_each(samplerAndBindings.begin(), samplerAndBindings.end(),
|
std::for_each(samplerAndBindings.begin(), samplerAndBindings.end(),
|
||||||
[&](auto const& b) { outSamplers.push_back(std::get<1>(b)); });
|
[&](auto const& b) { outSamplers.push_back(std::get<1>(b)); });
|
||||||
|
|
||||||
|
VkDescriptorSetLayout const oldLayout = layout->getExternalSamplerVkLayout();
|
||||||
VkDescriptorSetLayout const newLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
|
VkDescriptorSetLayout const newLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
|
||||||
actualExternalSamplers, outSamplers);
|
actualExternalSamplers, outSamplers);
|
||||||
layout->setExternalSamplerVkLayout(newLayout);
|
|
||||||
|
// Need to copy the set
|
||||||
|
VkDescriptorSet const oldSet = set->getExternalSamplerVkSet();
|
||||||
|
if (oldLayout != newLayout || oldSet == VK_NULL_HANDLE) {
|
||||||
|
// Build a new descriptor set from the new layout
|
||||||
|
VkDescriptorSet const newSet = mDescriptorSetCache->getVkSet(layout->count, newLayout);
|
||||||
|
auto const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
|
||||||
|
auto const samplers = layout->bitmask.sampler & (~actualExternalSamplers);
|
||||||
|
|
||||||
|
// Each bitmask denotes a binding index, and separated into two stages - vertex and buffer
|
||||||
|
// We fold the two stages into just the lower half of the bits to denote a combined set of
|
||||||
|
// bindings.
|
||||||
|
Bitmask const copyBindings = foldBitsInHalf(ubo | samplers);
|
||||||
|
VkDescriptorSet const srcSet = oldSet != VK_NULL_HANDLE ? oldSet : set->getVkSet();
|
||||||
|
copySet(mPlatform->getDevice(), srcSet, newSet, copyBindings);
|
||||||
|
|
||||||
|
set->setExternalSamplerVkSet(newSet,
|
||||||
|
[&descriptorSetCache = mDescriptorSetCache, layoutCount = layout->count, newLayout,
|
||||||
|
newSet](VulkanDescriptorSet*) {
|
||||||
|
descriptorSetCache->manualRecycle(layoutCount, newLayout, newSet);
|
||||||
|
});
|
||||||
|
if (oldLayout != newLayout) {
|
||||||
|
layout->setExternalSamplerVkLayout(newLayout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update the external samplers in the set
|
// Update the external samplers in the set
|
||||||
for (auto& [binding, sampler, image]: samplerAndBindings) {
|
for (auto& [binding, sampler, image]: samplerAndBindings) {
|
||||||
// We cannot call updateSamplerForExternalSamplerSet because some samplers are non NULL
|
mDescriptorSetCache->updateSamplerForExternalSamplerSet(set, binding, image);
|
||||||
// (RGB) and we cannot do a combined update with a NULL sampler.
|
|
||||||
mDescriptorSetCache->updateSampler(set, binding, image, sampler, newLayout);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,25 +261,13 @@ void VulkanExternalImageManager::bindExternallySampledTexture(
|
|||||||
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams) {
|
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams) {
|
||||||
// Should we do duplicate validation here?
|
// Should we do duplicate validation here?
|
||||||
auto& imageData = findImage(mImages, image);
|
auto& imageData = findImage(mImages, image);
|
||||||
auto itr = std::find_if(mSetBindings.begin(), mSetBindings.end(),
|
mSetBindings.push_back({ bindingPoint, imageData.image, set, samplerParams });
|
||||||
[&](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(
|
void VulkanExternalImageManager::addExternallySampledTexture(
|
||||||
fvkmemory::resource_ptr<VulkanTexture> image,
|
fvkmemory::resource_ptr<VulkanTexture> image,
|
||||||
Platform::ExternalImageHandleRef platformHandleRef) {
|
Platform::ExternalImageHandleRef platformHandleRef) {
|
||||||
// By passing VK_NULL_HANDLE which is already there by default.
|
mImages.push_back({ image, platformHandleRef, false });
|
||||||
// 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(
|
void VulkanExternalImageManager::removeExternallySampledTexture(
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class VulkanSamplerCache;
|
|||||||
class VulkanDescriptorSetLayoutCache;
|
class VulkanDescriptorSetLayoutCache;
|
||||||
class VulkanDescriptorSetCache;
|
class VulkanDescriptorSetCache;
|
||||||
|
|
||||||
// Manages the logic of external images and their quirks wrt Vulkan.
|
// Manages the logic of external images and their quirks wrt Vulikan.
|
||||||
class VulkanExternalImageManager {
|
class VulkanExternalImageManager {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|||||||
@@ -40,11 +40,6 @@ namespace filament::backend {
|
|||||||
|
|
||||||
namespace {
|
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 {
|
inline VulkanBufferBinding getBufferObjectBinding(BufferObjectBinding bindingType) noexcept {
|
||||||
switch (bindingType) {
|
switch (bindingType) {
|
||||||
case BufferObjectBinding::VERTEX:
|
case BufferObjectBinding::VERTEX:
|
||||||
@@ -197,70 +192,15 @@ VulkanDescriptorSetLayout::Bitmask VulkanDescriptorSetLayout::Bitmask::fromLayou
|
|||||||
return fromBackendLayout(layout);
|
return fromBackendLayout(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanDescriptorSet::~VulkanDescriptorSet() {
|
// This method will store an age associated with this command buffer into the VulkanBuffer, which
|
||||||
for (auto setBundle: mSets) {
|
// will allow us to determine whether a barrier is necessary or not.
|
||||||
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) {
|
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) {
|
mUboMask.forEachSetBit([this, &commands](size_t index) {
|
||||||
auto& res = mResources[index];
|
auto& res = mResources[index];
|
||||||
fvkmemory::resource_ptr<VulkanBufferObject> bo =
|
fvkmemory::resource_ptr<VulkanBufferObject> bo =
|
||||||
fvkmemory::resource_ptr<VulkanBufferObject>::cast((VulkanBufferObject*) res.get());
|
fvkmemory::resource_ptr<VulkanBufferObject>::cast((VulkanBufferObject*) res.get());
|
||||||
bo->referencedBy(commands);
|
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) {
|
PushConstantDescription::PushConstantDescription(backend::Program const& program) {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
#include "VulkanTexture.h"
|
#include "VulkanTexture.h"
|
||||||
#include "vulkan/VulkanCommands.h"
|
#include "vulkan/VulkanCommands.h"
|
||||||
#include "vulkan/memory/Resource.h"
|
#include "vulkan/memory/Resource.h"
|
||||||
#include "vulkan/memory/ResourcePointer.h"
|
|
||||||
#include "vulkan/utils/Definitions.h"
|
#include "vulkan/utils/Definitions.h"
|
||||||
#include "vulkan/utils/StaticVector.h"
|
#include "vulkan/utils/StaticVector.h"
|
||||||
|
|
||||||
@@ -58,8 +57,6 @@ inline uint8_t collapsedCount(Bitmask const& mask) {
|
|||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
class VulkanDescriptorSetCache;
|
|
||||||
|
|
||||||
struct VulkanBufferObject;
|
struct VulkanBufferObject;
|
||||||
|
|
||||||
struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Resource {
|
struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Resource {
|
||||||
@@ -162,14 +159,38 @@ public:
|
|||||||
using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
|
using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
|
||||||
|
|
||||||
VulkanDescriptorSet(
|
VulkanDescriptorSet(
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout,
|
fvkutils::UniformBufferBitmask const& dynamicUboMask,
|
||||||
OnRecycle&& onRecycleFn, VkDescriptorSet vkSet);
|
uint8_t uniqueDynamicUboCount,
|
||||||
|
OnRecycle&& onRecycleFn, VkDescriptorSet vkSet)
|
||||||
|
: dynamicUboMask(dynamicUboMask),
|
||||||
|
uniqueDynamicUboCount(uniqueDynamicUboCount),
|
||||||
|
mVkSet(vkSet),
|
||||||
|
mOnRecycleFn(std::move(onRecycleFn)) {}
|
||||||
|
|
||||||
// NOLINTNEXTLINE(bugprone-exception-escape)
|
// NOLINTNEXTLINE(bugprone-exception-escape)
|
||||||
~VulkanDescriptorSet();
|
~VulkanDescriptorSet() {
|
||||||
|
if (mOnRecycleFn) {
|
||||||
|
mOnRecycleFn(this);
|
||||||
|
}
|
||||||
|
if (mOnRecycleExternalSamplerFn) {
|
||||||
|
mOnRecycleExternalSamplerFn(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VkDescriptorSet getVkSet() const noexcept {
|
VkDescriptorSet getVkSet() const noexcept {
|
||||||
return mSets[mCurrentSetIndex].vkSet;
|
return mVkSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDescriptorSet getExternalSamplerVkSet() const noexcept {
|
||||||
|
return mExternalSamplerVkSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setExternalSamplerVkSet(VkDescriptorSet vkset, OnRecycle onRecycle) {
|
||||||
|
mExternalSamplerVkSet = vkset;
|
||||||
|
if (mOnRecycleExternalSamplerFn) {
|
||||||
|
mOnRecycleExternalSamplerFn(this);
|
||||||
|
}
|
||||||
|
mOnRecycleExternalSamplerFn = onRecycle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
|
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
|
||||||
@@ -190,33 +211,17 @@ public:
|
|||||||
|
|
||||||
void referencedBy(VulkanCommandBuffer& commands);
|
void referencedBy(VulkanCommandBuffer& commands);
|
||||||
|
|
||||||
bool isBound() const {
|
fvkutils::UniformBufferBitmask const dynamicUboMask;
|
||||||
return bool(mSets[mCurrentSetIndex].fenceStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> getLayout() const { return mLayout; }
|
|
||||||
|
|
||||||
fvkutils::UniformBufferBitmask const& dynamicUboMask;
|
|
||||||
uint8_t const uniqueDynamicUboCount;
|
uint8_t const uniqueDynamicUboCount;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class VulkanDescriptorSetCache;
|
VkDescriptorSet const mVkSet;
|
||||||
|
VkDescriptorSet mExternalSamplerVkSet = VK_NULL_HANDLE;
|
||||||
|
|
||||||
void addNewSet(VkDescriptorSet vkSet, OnRecycle&& onRecycleFn);
|
|
||||||
|
|
||||||
void gc();
|
|
||||||
|
|
||||||
struct InternalVkSet {
|
|
||||||
VkDescriptorSet vkSet = {};
|
|
||||||
OnRecycle onRecycleFn;
|
|
||||||
std::shared_ptr<VulkanCmdFence> fenceStatus;
|
|
||||||
};
|
|
||||||
|
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> mLayout;
|
|
||||||
backend::DescriptorSetOffsetArray mOffsets;
|
backend::DescriptorSetOffsetArray mOffsets;
|
||||||
std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources;
|
std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources;
|
||||||
uint8_t mCurrentSetIndex;
|
OnRecycle mOnRecycleFn;
|
||||||
std::vector<InternalVkSet> mSets;
|
OnRecycle mOnRecycleExternalSamplerFn;
|
||||||
fvkutils::UniformBufferBitmask mUboMask;
|
fvkutils::UniformBufferBitmask mUboMask;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -191,12 +191,9 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
|||||||
.alphaToCoverageEnable = raster.alphaToCoverageEnable,
|
.alphaToCoverageEnable = raster.alphaToCoverageEnable,
|
||||||
.alphaToOneEnable = VK_FALSE,
|
.alphaToOneEnable = VK_FALSE,
|
||||||
};
|
};
|
||||||
bool const enableDepthTest =
|
|
||||||
raster.depthCompareOp != SamplerCompareFunc::A ||
|
|
||||||
raster.depthWriteEnable;
|
|
||||||
VkPipelineDepthStencilStateCreateInfo vkDs = {
|
VkPipelineDepthStencilStateCreateInfo vkDs = {
|
||||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||||
.depthTestEnable = enableDepthTest ? VK_TRUE : VK_FALSE,
|
.depthTestEnable = VK_TRUE,
|
||||||
.depthWriteEnable = raster.depthWriteEnable,
|
.depthWriteEnable = raster.depthWriteEnable,
|
||||||
.depthCompareOp = fvkutils::getCompareOp(raster.depthCompareOp),
|
.depthCompareOp = fvkutils::getCompareOp(raster.depthCompareOp),
|
||||||
.depthBoundsTestEnable = VK_FALSE,
|
.depthBoundsTestEnable = VK_FALSE,
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "VulkanStreamedImageManager.h"
|
|
||||||
|
|
||||||
#include "VulkanDescriptorSetCache.h"
|
|
||||||
#include "VulkanExternalImageManager.h"
|
|
||||||
#include "VulkanDescriptorSetLayoutCache.h"
|
|
||||||
#include "VulkanSamplerCache.h"
|
|
||||||
|
|
||||||
namespace filament::backend {
|
|
||||||
VulkanStreamedImageManager::VulkanStreamedImageManager(VulkanExternalImageManager* manager)
|
|
||||||
: mExternalImageManager(manager) {}
|
|
||||||
|
|
||||||
VulkanStreamedImageManager::~VulkanStreamedImageManager() = default;
|
|
||||||
|
|
||||||
void VulkanStreamedImageManager::terminate() { mStreamedTexturesBindings.clear(); }
|
|
||||||
|
|
||||||
void VulkanStreamedImageManager::bindStreamedTexture(
|
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
|
||||||
uint8_t bindingPoint, fvkmemory::resource_ptr<VulkanTexture> image,
|
|
||||||
SamplerParams samplerParams) {
|
|
||||||
mStreamedTexturesBindings.push_back({ bindingPoint, image, set, samplerParams });
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanStreamedImageManager::unbindStreamedTexture(
|
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
|
||||||
uint8_t bindingPoint) {
|
|
||||||
auto iter = std::remove_if(mStreamedTexturesBindings.begin(), mStreamedTexturesBindings.end(),
|
|
||||||
[&](StreamedTextureBinding& binding) {
|
|
||||||
return ((binding.set == set) && (binding.binding == bindingPoint));
|
|
||||||
});
|
|
||||||
mStreamedTexturesBindings.erase(iter, mStreamedTexturesBindings.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanStreamedImageManager::onStreamAcquireImage(fvkmemory::resource_ptr<VulkanTexture> image,
|
|
||||||
fvkmemory::resource_ptr<VulkanStream> stream) {
|
|
||||||
for (StreamedTextureBinding const& data: mStreamedTexturesBindings) {
|
|
||||||
// Find the right stream
|
|
||||||
if (data.image->getStream() == stream) {
|
|
||||||
mExternalImageManager->bindExternallySampledTexture(data.set, data.binding, image,
|
|
||||||
data.samplerParams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace filament::backend
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANSTREAMEDIMAGEMANAGER_H
|
|
||||||
#define TNT_FILAMENT_BACKEND_CACHING_VULKANSTREAMEDIMAGEMANAGER_H
|
|
||||||
|
|
||||||
#include "VulkanHandles.h"
|
|
||||||
|
|
||||||
#include <backend/DriverEnums.h>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace filament::backend {
|
|
||||||
|
|
||||||
class VulkanExternalImageManager;
|
|
||||||
class VulkanDescriptorSetCache;
|
|
||||||
class VulkanSamplerCache;
|
|
||||||
|
|
||||||
// Manages the logic of streamed images.
|
|
||||||
class VulkanStreamedImageManager {
|
|
||||||
public:
|
|
||||||
VulkanStreamedImageManager(
|
|
||||||
VulkanExternalImageManager* manager);
|
|
||||||
~VulkanStreamedImageManager();
|
|
||||||
void terminate();
|
|
||||||
|
|
||||||
public:
|
|
||||||
void bindStreamedTexture(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t bindingPoint,
|
|
||||||
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams);
|
|
||||||
void unbindStreamedTexture(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
|
||||||
uint8_t bindingPoint);
|
|
||||||
void onStreamAcquireImage(fvkmemory::resource_ptr<VulkanTexture> image,
|
|
||||||
fvkmemory::resource_ptr<VulkanStream> stream);
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct StreamedTextureBinding {
|
|
||||||
uint8_t binding = 0;
|
|
||||||
fvkmemory::resource_ptr<VulkanTexture> image;
|
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set;
|
|
||||||
SamplerParams samplerParams;
|
|
||||||
};
|
|
||||||
// keep track of all the stream bindings
|
|
||||||
std::vector<StreamedTextureBinding> mStreamedTexturesBindings;
|
|
||||||
|
|
||||||
VulkanExternalImageManager* mExternalImageManager;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace filament::backend
|
|
||||||
|
|
||||||
#endif // TNT_FILAMENT_BACKEND_CACHING_VULKANSTREAMEDIMAGEMANAGER_H
|
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
#include "VulkanCommands.h"
|
#include "VulkanCommands.h"
|
||||||
#include "VulkanTexture.h"
|
#include "VulkanTexture.h"
|
||||||
|
|
||||||
#include <utils/debug.h>
|
|
||||||
#include <utils/FixedCapacityVector.h>
|
#include <utils/FixedCapacityVector.h>
|
||||||
#include <utils/Panic.h>
|
#include <utils/Panic.h>
|
||||||
|
|
||||||
@@ -106,11 +105,6 @@ void VulkanSwapChain::update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanSwapChain::present(DriverBase& driver) {
|
void VulkanSwapChain::present(DriverBase& driver) {
|
||||||
// The last acquire failed, so just skip presenting.
|
|
||||||
if (!mAcquired) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mHeadless && mTransitionSwapChainImageLayoutForPresent) {
|
if (!mHeadless && mTransitionSwapChainImageLayoutForPresent) {
|
||||||
VulkanCommandBuffer& commands = mCommands->get();
|
VulkanCommandBuffer& commands = mCommands->get();
|
||||||
VkImageSubresourceRange const subresources{
|
VkImageSubresourceRange const subresources{
|
||||||
@@ -167,15 +161,7 @@ void VulkanSwapChain::acquire(bool& resized) {
|
|||||||
|
|
||||||
VulkanPlatform::ImageSyncData imageSyncData;
|
VulkanPlatform::ImageSyncData imageSyncData;
|
||||||
VkResult const result = mPlatform->acquire(swapChain, &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;
|
mCurrentSwapIndex = imageSyncData.imageIndex;
|
||||||
assert_invariant(mCurrentSwapIndex < mFinishedDrawing.size());
|
|
||||||
mFinishedDrawing[mCurrentSwapIndex] = {};
|
mFinishedDrawing[mCurrentSwapIndex] = {};
|
||||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
|
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
|
||||||
<< "Cannot acquire in swapchain. error=" << static_cast<int32_t>(result);
|
<< "Cannot acquire in swapchain. error=" << static_cast<int32_t>(result);
|
||||||
|
|||||||
@@ -36,43 +36,6 @@ namespace filament::backend {
|
|||||||
|
|
||||||
struct VulkanTexture;
|
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 {
|
struct VulkanTextureState : public fvkmemory::Resource {
|
||||||
VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands, VmaAllocator allocator,
|
VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands, VmaAllocator allocator,
|
||||||
VkDevice device, VkImage image, VkDeviceMemory deviceMemory, VkFormat format,
|
VkDevice device, VkImage image, VkDeviceMemory deviceMemory, VkFormat format,
|
||||||
@@ -111,8 +74,6 @@ private:
|
|||||||
|
|
||||||
// The texture with the sidecar owns the sidecar.
|
// The texture with the sidecar owns the sidecar.
|
||||||
fvkmemory::resource_ptr<VulkanTexture> mSidecarMSAA;
|
fvkmemory::resource_ptr<VulkanTexture> mSidecarMSAA;
|
||||||
// The stream this texture is associated with.
|
|
||||||
fvkmemory::resource_ptr<VulkanStream> mStream;
|
|
||||||
|
|
||||||
VkImage const mTextureImage;
|
VkImage const mTextureImage;
|
||||||
VkDeviceMemory const mTextureImageMemory;
|
VkDeviceMemory const mTextureImageMemory;
|
||||||
@@ -144,7 +105,6 @@ private:
|
|||||||
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
|
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
|
||||||
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
|
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
|
||||||
|
|
||||||
|
|
||||||
friend struct VulkanTexture;
|
friend struct VulkanTexture;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -222,14 +182,6 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
|||||||
return mState->mSidecarMSAA;
|
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 {
|
bool isTransientAttachment() const {
|
||||||
return mState->mUsage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
|
return mState->mUsage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ template ResourceType getTypeEnum<VulkanBuffer>() noexcept;
|
|||||||
template ResourceType getTypeEnum<VulkanSync>() noexcept;
|
template ResourceType getTypeEnum<VulkanSync>() noexcept;
|
||||||
template ResourceType getTypeEnum<VulkanMemoryMappedBuffer>() noexcept;
|
template ResourceType getTypeEnum<VulkanMemoryMappedBuffer>() noexcept;
|
||||||
template ResourceType getTypeEnum<VulkanSemaphore>() noexcept;
|
template ResourceType getTypeEnum<VulkanSemaphore>() noexcept;
|
||||||
template ResourceType getTypeEnum<VulkanStream>() noexcept;
|
|
||||||
|
|
||||||
template<typename D>
|
template<typename D>
|
||||||
ResourceType getTypeEnum() noexcept {
|
ResourceType getTypeEnum() noexcept {
|
||||||
@@ -105,9 +104,6 @@ ResourceType getTypeEnum() noexcept {
|
|||||||
if constexpr (std::is_same_v<D, VulkanSemaphore>) {
|
if constexpr (std::is_same_v<D, VulkanSemaphore>) {
|
||||||
return ResourceType::SEMAPHORE;
|
return ResourceType::SEMAPHORE;
|
||||||
}
|
}
|
||||||
if constexpr (std::is_same_v<D, VulkanStream>) {
|
|
||||||
return ResourceType::STREAM;
|
|
||||||
}
|
|
||||||
return ResourceType::UNDEFINED_TYPE;
|
return ResourceType::UNDEFINED_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,8 +149,6 @@ std::string_view getTypeStr(ResourceType type) {
|
|||||||
return "VulkanMemoryMappedBuffer";
|
return "VulkanMemoryMappedBuffer";
|
||||||
case ResourceType::SEMAPHORE:
|
case ResourceType::SEMAPHORE:
|
||||||
return "Semaphore";
|
return "Semaphore";
|
||||||
case ResourceType::STREAM:
|
|
||||||
return "VulkanStream";
|
|
||||||
case ResourceType::UNDEFINED_TYPE:
|
case ResourceType::UNDEFINED_TYPE:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,8 +55,7 @@ enum class ResourceType : uint8_t {
|
|||||||
SYNC = 17,
|
SYNC = 17,
|
||||||
MEMORY_MAPPED_BUFFER = 18,
|
MEMORY_MAPPED_BUFFER = 18,
|
||||||
SEMAPHORE = 19,
|
SEMAPHORE = 19,
|
||||||
STREAM = 20,
|
UNDEFINED_TYPE = 20, // Must be the last enum because we use it for iterating over the enums.
|
||||||
UNDEFINED_TYPE = 21, // Must be the last enum because we use it for iterating over the enums.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename D>
|
template<typename D>
|
||||||
|
|||||||
@@ -123,9 +123,6 @@ void ResourceManager::destroyWithType(ResourceType type, HandleId id) {
|
|||||||
case ResourceType::SEMAPHORE:
|
case ResourceType::SEMAPHORE:
|
||||||
destruct<VulkanSemaphore>(Handle<VulkanSemaphore>(id));
|
destruct<VulkanSemaphore>(Handle<VulkanSemaphore>(id));
|
||||||
break;
|
break;
|
||||||
case ResourceType::STREAM:
|
|
||||||
destruct<VulkanStream>(Handle<VulkanStream>(id));
|
|
||||||
break;
|
|
||||||
case ResourceType::UNDEFINED_TYPE:
|
case ResourceType::UNDEFINED_TYPE:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <backend/platforms/VulkanPlatformAndroid.h>
|
#include <backend/platforms/VulkanPlatformAndroid.h>
|
||||||
|
|
||||||
#include "vulkan/VulkanConstants.h"
|
#include "vulkan/VulkanConstants.h"
|
||||||
@@ -21,8 +20,6 @@
|
|||||||
#include "vulkan/platform/VulkanPlatformSwapChainImpl.h"
|
#include "vulkan/platform/VulkanPlatformSwapChainImpl.h"
|
||||||
#include "vulkan/utils/Image.h"
|
#include "vulkan/utils/Image.h"
|
||||||
|
|
||||||
#include "AndroidFrameCallback.h"
|
|
||||||
|
|
||||||
#include <private/backend/BackendUtilsAndroid.h>
|
#include <private/backend/BackendUtilsAndroid.h>
|
||||||
|
|
||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
@@ -35,7 +32,6 @@
|
|||||||
#include <android/hardware_buffer.h>
|
#include <android/hardware_buffer.h>
|
||||||
#include <android/native_window.h>
|
#include <android/native_window.h>
|
||||||
|
|
||||||
#include <new>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
using namespace bluevk;
|
using namespace bluevk;
|
||||||
@@ -204,14 +200,6 @@ uint32_t selectMemoryTypeForExternalImage(VkPhysicalDevice physicalDevice, VkDev
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
struct VulkanPlatformAndroid::AndroidDetails {
|
|
||||||
AndroidFrameCallback androidFrameCallback;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() {
|
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() {
|
||||||
if (__builtin_available(android 26, *)) {
|
if (__builtin_available(android 26, *)) {
|
||||||
if (aHardwareBuffer) {
|
if (aHardwareBuffer) {
|
||||||
@@ -220,19 +208,14 @@ VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------
|
VulkanPlatformAndroid::VulkanPlatformAndroid() {
|
||||||
|
|
||||||
VulkanPlatformAndroid::VulkanPlatformAndroid() :
|
|
||||||
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
|
|
||||||
mOSVersion = android_get_device_api_level();
|
mOSVersion = android_get_device_api_level();
|
||||||
if (mOSVersion < 0) {
|
if (mOSVersion < 0) {
|
||||||
mOSVersion = __ANDROID_API_FUTURE__;
|
mOSVersion = __ANDROID_API_FUTURE__;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanPlatformAndroid::~VulkanPlatformAndroid() noexcept {
|
VulkanPlatformAndroid::~VulkanPlatformAndroid() noexcept = default;
|
||||||
delete &mAndroidDetails;
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
|
Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
|
||||||
AHardwareBuffer const* buffer, bool sRGB) noexcept {
|
AHardwareBuffer const* buffer, bool sRGB) noexcept {
|
||||||
@@ -514,14 +497,14 @@ int VulkanPlatformAndroid::getOSVersion() const noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanPlatformAndroid::terminate() {
|
void VulkanPlatformAndroid::terminate() {
|
||||||
mAndroidDetails.androidFrameCallback.terminate();
|
mAndroidFrameCallback.terminate();
|
||||||
VulkanPlatform::terminate();
|
VulkanPlatform::terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
Driver* VulkanPlatformAndroid::createDriver(void* sharedContext, DriverConfig const& driverConfig) {
|
Driver* VulkanPlatformAndroid::createDriver(void* sharedContext, DriverConfig const& driverConfig) {
|
||||||
Driver* driver = VulkanPlatform::createDriver(sharedContext, driverConfig);
|
Driver* driver = VulkanPlatform::createDriver(sharedContext, driverConfig);
|
||||||
if (driver) {
|
if (driver) {
|
||||||
mAndroidDetails.androidFrameCallback.init();
|
mAndroidFrameCallback.init();
|
||||||
}
|
}
|
||||||
return driver;
|
return driver;
|
||||||
}
|
}
|
||||||
@@ -537,19 +520,13 @@ bool VulkanPlatformAndroid::queryCompositorTiming(SwapChain const* swapchain,
|
|||||||
}
|
}
|
||||||
|
|
||||||
AndroidFrameCallback::Timeline const preferredTimeline{
|
AndroidFrameCallback::Timeline const preferredTimeline{
|
||||||
mAndroidDetails.androidFrameCallback.getPreferredTimeline() };
|
mAndroidFrameCallback.getPreferredTimeline() };
|
||||||
outCompositorTiming->frameTime = preferredTimeline.frameTime;
|
outCompositorTiming->frameTime = preferredTimeline.frameTime;
|
||||||
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
|
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
|
||||||
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
|
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);
|
auto vulkanSwapchain = static_cast<VulkanPlatformSwapChainBase const *>(swapchain);
|
||||||
vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
|
return vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VulkanPlatformAndroid::setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept {
|
bool VulkanPlatformAndroid::setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept {
|
||||||
|
|||||||
@@ -165,11 +165,6 @@ VulkanPlatformSurfaceSwapChain::~VulkanPlatformSurfaceSwapChain() {
|
|||||||
VkResult VulkanPlatformSurfaceSwapChain::create() {
|
VkResult VulkanPlatformSurfaceSwapChain::create() {
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
NativeWindow::enableFrameTimestamps(static_cast<ANativeWindow*>(mNativeWindow), true);
|
NativeWindow::enableFrameTimestamps(static_cast<ANativeWindow*>(mNativeWindow), true);
|
||||||
// on Android, disable producer throttling
|
|
||||||
if (mProducerThrottling.isSupported()) {
|
|
||||||
mProducerThrottling.setProducerThrottlingEnabled(
|
|
||||||
static_cast<ANativeWindow*>(mNativeWindow), false);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
VkSurfaceFormatKHR surfaceFormat = {};
|
VkSurfaceFormatKHR surfaceFormat = {};
|
||||||
@@ -361,15 +356,13 @@ bool VulkanPlatformSurfaceSwapChain::queryCompositorTiming(
|
|||||||
CompositorTiming* outCompositorTiming) const {
|
CompositorTiming* outCompositorTiming) const {
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
// fallback to private APIs
|
// fallback to private APIs
|
||||||
if (UTILS_VERY_LIKELY(mNativeWindow)) {
|
int const status = NativeWindow::getCompositorTiming(
|
||||||
int const status = NativeWindow::getCompositorTiming(
|
static_cast<ANativeWindow*>(mNativeWindow),
|
||||||
static_cast<ANativeWindow*>(mNativeWindow),
|
&outCompositorTiming->compositeDeadline,
|
||||||
&outCompositorTiming->compositeDeadline,
|
&outCompositorTiming->compositeInterval,
|
||||||
&outCompositorTiming->compositeInterval,
|
&outCompositorTiming->compositeToPresentLatency);
|
||||||
&outCompositorTiming->compositeToPresentLatency);
|
if (status == 0) {
|
||||||
if (status == 0) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return VulkanPlatformSwapChainBase::queryCompositorTiming(outCompositorTiming);
|
return VulkanPlatformSwapChainBase::queryCompositorTiming(outCompositorTiming);
|
||||||
|
|||||||
@@ -25,8 +25,7 @@
|
|||||||
#include <bluevk/BlueVK.h>
|
#include <bluevk/BlueVK.h>
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
#include "AndroidSwapChainHelper.h"
|
#include <AndroidSwapChainHelper.h>
|
||||||
#include "AndroidNativeWindow.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -123,7 +122,6 @@ private:
|
|||||||
|
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
AndroidSwapChainHelper mImpl{};
|
AndroidSwapChainHelper mImpl{};
|
||||||
AndroidProducerThrottling mProducerThrottling;
|
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,12 +18,6 @@
|
|||||||
|
|
||||||
#include "vulkan/VulkanTexture.h"
|
#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/Panic.h>
|
||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
#include <utils/debug.h>
|
#include <utils/debug.h>
|
||||||
@@ -240,19 +234,6 @@ uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask) {
|
|||||||
return mostSignificantBit((sampleCount - 1) & 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
|
} // namespace filament::backend::fvkutils
|
||||||
|
|
||||||
bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) {
|
bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
#define TNT_FILAMENT_BACKEND_VULKAN_UTILS_IMAGE_H
|
#define TNT_FILAMENT_BACKEND_VULKAN_UTILS_IMAGE_H
|
||||||
|
|
||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
#include <backend/platforms/VulkanPlatform.h>
|
|
||||||
|
|
||||||
#include <utils/Log.h>
|
#include <utils/Log.h>
|
||||||
|
|
||||||
@@ -107,9 +106,6 @@ VkImageAspectFlags getImageAspect(VkFormat format);
|
|||||||
|
|
||||||
uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask);
|
uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask);
|
||||||
|
|
||||||
Platform::ExternalImageHandle createExternalImageFromRaw(filament::backend::VulkanPlatform* platform,
|
|
||||||
void* image,
|
|
||||||
bool sRGB);
|
|
||||||
} // namespace fvkutils
|
} // namespace fvkutils
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
#include "WebGPUConstants.h"
|
#include "WebGPUConstants.h"
|
||||||
#include "WebGPUQueueManager.h"
|
#include "WebGPUQueueManager.h"
|
||||||
#include "WebGPUStagePool.h"
|
|
||||||
|
|
||||||
#include "DriverBase.h"
|
#include "DriverBase.h"
|
||||||
#include <backend/BufferDescriptor.h>
|
#include <backend/BufferDescriptor.h>
|
||||||
@@ -64,9 +63,9 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
|
|||||||
// WebGPU requires that the size of the data copied from the staging buffer to the GPU buffer is a
|
// WebGPU requires that the size of the data copied from the staging buffer to the GPU buffer is a
|
||||||
// multiple of 4. This function handles cases where the buffer descriptor's size is not a multiple
|
// multiple of 4. This function handles cases where the buffer descriptor's size is not a multiple
|
||||||
// of 4 by padding with zeros.
|
// of 4 by padding with zeros.
|
||||||
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor&& bufferDescriptor,
|
||||||
const uint32_t byteOffset, wgpu::Device const& device,
|
const uint32_t byteOffset, wgpu::Device const& device,
|
||||||
WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool) {
|
WebGPUQueueManager* const webGPUQueueManager) {
|
||||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
|
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
|
||||||
<< "updateGPUBuffer called with a null buffer";
|
<< "updateGPUBuffer called with a null buffer";
|
||||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
|
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
|
||||||
@@ -86,27 +85,53 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
|||||||
const size_t stagingBufferSize =
|
const size_t stagingBufferSize =
|
||||||
remainder == 0 ? bufferDescriptor.size : mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
|
remainder == 0 ? bufferDescriptor.size : mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
|
||||||
|
|
||||||
wgpu::Buffer stagingBuffer = webGPUStagePool->acquireBuffer(stagingBufferSize,
|
// create a staging buffer
|
||||||
webGPUQueueManager->getLatestSubmissionState());
|
wgpu::BufferDescriptor descriptor{
|
||||||
|
.label = "Filament WebGPU Staging Buffer",
|
||||||
|
.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
|
||||||
|
.size = stagingBufferSize};
|
||||||
|
wgpu::Buffer stagingBuffer = device.CreateBuffer(&descriptor);
|
||||||
|
|
||||||
void* mappedRange = stagingBuffer.GetMappedRange();
|
struct UserData final {
|
||||||
assert_invariant(mappedRange);
|
uint32_t byteOffset;
|
||||||
|
size_t stagingBufferSize;
|
||||||
memcpy(mappedRange, bufferDescriptor.buffer, bufferDescriptor.size);
|
size_t remainder;
|
||||||
|
BufferDescriptor srcBufferDescriptor;
|
||||||
// Make sure the padded memory is set to 0 to have deterministic behaviors
|
wgpu::Buffer stagingBuffer;
|
||||||
if (remainder != 0) {
|
WebGPUQueueManager* const webGPUQueueManager;
|
||||||
uint8_t* paddingStart = static_cast<uint8_t*>(mappedRange) + bufferDescriptor.size;
|
wgpu::Buffer dstBuffer;
|
||||||
memset(paddingStart, 0, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - remainder);
|
};
|
||||||
}
|
auto userData = std::make_unique<UserData>(UserData{
|
||||||
|
.byteOffset = byteOffset,
|
||||||
stagingBuffer.Unmap();
|
.stagingBufferSize = stagingBufferSize,
|
||||||
|
.remainder = remainder,
|
||||||
// Copy the staging buffer contents to the destination buffer.
|
.srcBufferDescriptor = std::move(bufferDescriptor),
|
||||||
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stagingBuffer, 0, mBuffer,
|
.stagingBuffer = stagingBuffer,
|
||||||
byteOffset,
|
.webGPUQueueManager = webGPUQueueManager,
|
||||||
remainder == 0 ? bufferDescriptor.size
|
.dstBuffer = mBuffer});
|
||||||
: mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS);
|
stagingBuffer.MapAsync(
|
||||||
|
wgpu::MapMode::Write, 0, stagingBufferSize, wgpu::CallbackMode::AllowProcessEvents,
|
||||||
|
[](wgpu::MapAsyncStatus status, const char* message, UserData* userdata) {
|
||||||
|
std::unique_ptr<UserData> data(static_cast<UserData*>(userdata));
|
||||||
|
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
|
||||||
|
void* mappedRange = data->stagingBuffer.GetMappedRange();
|
||||||
|
memcpy(mappedRange, data->srcBufferDescriptor.buffer,
|
||||||
|
data->srcBufferDescriptor.size);
|
||||||
|
if (data->remainder != 0) {
|
||||||
|
uint8_t* paddingStart =
|
||||||
|
static_cast<uint8_t*>(mappedRange) + data->srcBufferDescriptor.size;
|
||||||
|
memset(paddingStart, 0,
|
||||||
|
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - data->remainder);
|
||||||
|
}
|
||||||
|
data->stagingBuffer.Unmap();
|
||||||
|
data->webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(
|
||||||
|
data->stagingBuffer, 0, data->dstBuffer, data->byteOffset,
|
||||||
|
data->stagingBufferSize);
|
||||||
|
} else {
|
||||||
|
FWGPU_LOGE << "Failed to map staging buffer for readPixels: " << message;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
userData.release());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ namespace filament::backend {
|
|||||||
|
|
||||||
class BufferDescriptor;
|
class BufferDescriptor;
|
||||||
class WebGPUQueueManager;
|
class WebGPUQueueManager;
|
||||||
class WebGPUStagePool;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A base class for WebGPU buffer objects, providing common functionality for creating and
|
* A base class for WebGPU buffer objects, providing common functionality for creating and
|
||||||
@@ -40,8 +39,8 @@ public:
|
|||||||
* happen after draw commands encoded in the encoder. Submitting any commands up to this point
|
* happen after draw commands encoded in the encoder. Submitting any commands up to this point
|
||||||
* ensures the calls happen in the expected sequence.
|
* ensures the calls happen in the expected sequence.
|
||||||
*/
|
*/
|
||||||
void updateGPUBuffer(BufferDescriptor const&, uint32_t byteOffset, wgpu::Device const& device,
|
void updateGPUBuffer(BufferDescriptor&&, uint32_t byteOffset, wgpu::Device const& device,
|
||||||
WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool);
|
WebGPUQueueManager* const webGPUQueueManager);
|
||||||
|
|
||||||
[[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; }
|
[[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; }
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
|
|||||||
mAdapter{ mPlatform.requestAdapter(nullptr) },
|
mAdapter{ mPlatform.requestAdapter(nullptr) },
|
||||||
mDevice{ mPlatform.requestDevice(mAdapter) },
|
mDevice{ mPlatform.requestDevice(mAdapter) },
|
||||||
mQueueManager{ mDevice },
|
mQueueManager{ mDevice },
|
||||||
mStagePool{ mDevice },
|
|
||||||
mPipelineLayoutCache{ mDevice },
|
mPipelineLayoutCache{ mDevice },
|
||||||
mPipelineCache{ mDevice },
|
mPipelineCache{ mDevice },
|
||||||
mRenderPassMipmapGenerator{ mDevice, &mQueueManager },
|
mRenderPassMipmapGenerator{ mDevice, &mQueueManager },
|
||||||
@@ -178,9 +177,6 @@ void WebGPUDriver::endFrame(const uint32_t /* frameId */) {
|
|||||||
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
|
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
|
||||||
mCurrentDescriptorSets[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.
|
// If a command encoder is in flight then the encoder is finished and submitted to the GPU queue.
|
||||||
@@ -655,6 +651,11 @@ void WebGPUDriver::setAcquiredImage(Handle<HwStream> sh, void* image, const math
|
|||||||
//todo
|
//todo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebGPUDriver::registerBufferObjectStreams(Handle<HwBufferObject> bufferObjectHandle,
|
||||||
|
BufferObjectStreamDescriptor&& streams) {
|
||||||
|
//todo
|
||||||
|
}
|
||||||
|
|
||||||
void WebGPUDriver::setStreamDimensions(Handle<HwStream> sh, uint32_t width, uint32_t height) {
|
void WebGPUDriver::setStreamDimensions(Handle<HwStream> sh, uint32_t width, uint32_t height) {
|
||||||
//todo
|
//todo
|
||||||
}
|
}
|
||||||
@@ -855,7 +856,7 @@ void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
|
|||||||
// draw calls are made.
|
// draw calls are made.
|
||||||
flush();
|
flush();
|
||||||
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
|
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
|
||||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
|
->updateGPUBuffer(std::move(bufferDescriptor), byteOffset, mDevice, &mQueueManager);
|
||||||
scheduleDestroy(std::move(bufferDescriptor));
|
scheduleDestroy(std::move(bufferDescriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -866,14 +867,14 @@ void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
|
|||||||
// draw calls are made.
|
// draw calls are made.
|
||||||
flush();
|
flush();
|
||||||
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
||||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
|
->updateGPUBuffer(std::move(bufferDescriptor), byteOffset, mDevice, &mQueueManager);
|
||||||
scheduleDestroy(std::move(bufferDescriptor));
|
scheduleDestroy(std::move(bufferDescriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
|
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
|
||||||
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
|
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
|
||||||
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
||||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
|
->updateGPUBuffer(std::move(bufferDescriptor), byteOffset, mDevice, &mQueueManager);
|
||||||
scheduleDestroy(std::move(bufferDescriptor));
|
scheduleDestroy(std::move(bufferDescriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
#include "webgpu/WebGPUPipelineLayoutCache.h"
|
#include "webgpu/WebGPUPipelineLayoutCache.h"
|
||||||
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
|
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
|
||||||
#include "webgpu/WebGPUQueueManager.h"
|
#include "webgpu/WebGPUQueueManager.h"
|
||||||
#include "webgpu/WebGPUStagePool.h"
|
|
||||||
#include "webgpu/utils/AsyncTaskCounter.h"
|
#include "webgpu/utils/AsyncTaskCounter.h"
|
||||||
#include <backend/platforms/WebGPUPlatform.h>
|
#include <backend/platforms/WebGPUPlatform.h>
|
||||||
|
|
||||||
@@ -82,7 +81,6 @@ private:
|
|||||||
wgpu::Device mDevice = nullptr;
|
wgpu::Device mDevice = nullptr;
|
||||||
wgpu::Limits mDeviceLimits = {};
|
wgpu::Limits mDeviceLimits = {};
|
||||||
WebGPUQueueManager mQueueManager;
|
WebGPUQueueManager mQueueManager;
|
||||||
WebGPUStagePool mStagePool;
|
|
||||||
void* mNativeWindow = nullptr;
|
void* mNativeWindow = nullptr;
|
||||||
WebGPUSwapChain* mSwapChain = nullptr;
|
WebGPUSwapChain* mSwapChain = nullptr;
|
||||||
uint64_t mNextFakeHandle = 1;
|
uint64_t mNextFakeHandle = 1;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
@@ -58,13 +59,13 @@ WebGPUQueueManager::WebGPUQueueManager(wgpu::Device const& device)
|
|||||||
WebGPUQueueManager::~WebGPUQueueManager() = default;
|
WebGPUQueueManager::~WebGPUQueueManager() = default;
|
||||||
|
|
||||||
wgpu::CommandEncoder WebGPUQueueManager::getCommandEncoder() {
|
wgpu::CommandEncoder WebGPUQueueManager::getCommandEncoder() {
|
||||||
|
// std::unique_lock<std::mutex> lock(mLock);
|
||||||
if (!mCommandEncoder) {
|
if (!mCommandEncoder) {
|
||||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
|
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
|
||||||
.label = "Filament Command Encoder",
|
.label = "Filament Command Encoder",
|
||||||
};
|
};
|
||||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||||
ASSERT_POSTCONDITION(mCommandEncoder, "Failed to create command encoder.");
|
ASSERT_POSTCONDITION(mCommandEncoder, "Failed to create command encoder.");
|
||||||
mLatestSubmissionState = std::make_shared<WebGPUSubmissionState>();
|
|
||||||
}
|
}
|
||||||
return mCommandEncoder;
|
return mCommandEncoder;
|
||||||
}
|
}
|
||||||
@@ -91,10 +92,14 @@ std::shared_ptr<WebGPUSubmissionState> WebGPUQueueManager::getLatestSubmissionSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUQueueManager::submit() {
|
void WebGPUQueueManager::submit() {
|
||||||
|
// std::unique_lock<std::mutex> lock(mLock);
|
||||||
if (!mCommandEncoder) {
|
if (!mCommandEncoder) {
|
||||||
|
std::cout << "Run Yu: no mCommandEncoder found!\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mLatestSubmissionState = std::make_shared<WebGPUSubmissionState>();
|
||||||
|
|
||||||
wgpu::CommandBufferDescriptor commandBufferDescriptor{
|
wgpu::CommandBufferDescriptor commandBufferDescriptor{
|
||||||
.label = "Filament Command Buffer",
|
.label = "Filament Command Buffer",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ private:
|
|||||||
wgpu::Queue mQueue;
|
wgpu::Queue mQueue;
|
||||||
wgpu::CommandEncoder mCommandEncoder;
|
wgpu::CommandEncoder mCommandEncoder;
|
||||||
std::shared_ptr<WebGPUSubmissionState> mLatestSubmissionState;
|
std::shared_ptr<WebGPUSubmissionState> mLatestSubmissionState;
|
||||||
|
std::mutex mLock;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "WebGPUStagePool.h"
|
|
||||||
|
|
||||||
#include "WebGPUConstants.h"
|
|
||||||
#include "WebGPUQueueManager.h"
|
|
||||||
|
|
||||||
namespace filament::backend {
|
|
||||||
|
|
||||||
WebGPUStagePool::WebGPUStagePool(wgpu::Device const& device) : mDevice(device) {}
|
|
||||||
|
|
||||||
WebGPUStagePool::~WebGPUStagePool() = default;
|
|
||||||
|
|
||||||
wgpu::Buffer WebGPUStagePool::acquireBuffer(size_t requiredSize,
|
|
||||||
std::shared_ptr<WebGPUSubmissionState> submissionState) {
|
|
||||||
wgpu::Buffer buffer;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mMutex);
|
|
||||||
auto iter = mBuffers.lower_bound(requiredSize);
|
|
||||||
if (iter != mBuffers.end()) {
|
|
||||||
buffer = iter->second;
|
|
||||||
mBuffers.erase(iter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!buffer.Get()) {
|
|
||||||
buffer = createNewBuffer(requiredSize);
|
|
||||||
}
|
|
||||||
mInProgress.push_back({submissionState, buffer});
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebGPUStagePool::recycleBuffer(wgpu::Buffer buffer) {
|
|
||||||
struct UserData final {
|
|
||||||
wgpu::Buffer buffer;
|
|
||||||
WebGPUStagePool* webGPUStagePool;
|
|
||||||
};
|
|
||||||
auto userData =
|
|
||||||
std::make_unique<UserData>(UserData{ .buffer = buffer, .webGPUStagePool = this });
|
|
||||||
buffer.MapAsync(wgpu::MapMode::Write, 0, buffer.GetSize(), wgpu::CallbackMode::AllowSpontaneous,
|
|
||||||
[data = std::move(userData)](wgpu::MapAsyncStatus status, const char* message) {
|
|
||||||
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
|
|
||||||
if (!data->webGPUStagePool) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::lock_guard<std::mutex> lock(data->webGPUStagePool->mMutex);
|
|
||||||
data->webGPUStagePool->mBuffers.insert(
|
|
||||||
{ data->buffer.GetSize(), data->buffer });
|
|
||||||
} else {
|
|
||||||
FWGPU_LOGE << "Failed to MapAsync when recycling staging buffer: " << message;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebGPUStagePool::gc() {
|
|
||||||
// We found that MapAsync would sometimes lead to GetMappedRange returning nullptr if the
|
|
||||||
// command using that staging buffer has not finished executing, so here we only recycle those
|
|
||||||
// buffers that are not still being used by any command
|
|
||||||
std::vector<std::pair<std::shared_ptr<WebGPUSubmissionState>, wgpu::Buffer>> stillInProgress;
|
|
||||||
for (auto& [st, buffer]: mInProgress) {
|
|
||||||
if (st->getStatus() == FenceStatus::CONDITION_SATISFIED) {
|
|
||||||
recycleBuffer(buffer);
|
|
||||||
} else {
|
|
||||||
stillInProgress.push_back({st, buffer});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::swap(mInProgress, stillInProgress);
|
|
||||||
}
|
|
||||||
|
|
||||||
wgpu::Buffer WebGPUStagePool::createNewBuffer(size_t bufferSize) {
|
|
||||||
wgpu::BufferDescriptor descriptor{
|
|
||||||
.label = "Filament WebGPU Staging Buffer",
|
|
||||||
.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
|
|
||||||
.size = bufferSize,
|
|
||||||
.mappedAtCreation = true };
|
|
||||||
return mDevice.CreateBuffer(&descriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace filament::backend
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUSTAGEPOOL_H
|
|
||||||
#define TNT_FILAMENT_BACKEND_WEBGPUSTAGEPOOL_H
|
|
||||||
|
|
||||||
#include <webgpu/webgpu_cpp.h>
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace filament::backend {
|
|
||||||
|
|
||||||
struct WebGPUSubmissionState;
|
|
||||||
|
|
||||||
class WebGPUStagePool {
|
|
||||||
public:
|
|
||||||
WebGPUStagePool(wgpu::Device const& device);
|
|
||||||
~WebGPUStagePool();
|
|
||||||
|
|
||||||
wgpu::Buffer acquireBuffer(size_t requiredSize,
|
|
||||||
std::shared_ptr<WebGPUSubmissionState> submissionState);
|
|
||||||
void recycleBuffer(wgpu::Buffer buffer);
|
|
||||||
void gc();
|
|
||||||
|
|
||||||
private:
|
|
||||||
wgpu::Buffer createNewBuffer(size_t bufferSize);
|
|
||||||
std::multimap<uint32_t, wgpu::Buffer> mBuffers;
|
|
||||||
std::vector<std::pair<std::shared_ptr<WebGPUSubmissionState>, wgpu::Buffer>> mInProgress;
|
|
||||||
std::mutex mMutex;
|
|
||||||
|
|
||||||
wgpu::Device mDevice;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace filament::backend
|
|
||||||
|
|
||||||
#endif // TNT_FILAMENT_BACKEND_WEBGPUSTAGEPOOL_H
|
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include "JobQueue.h"
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
using namespace filament::backend;
|
|
||||||
|
|
||||||
TEST(JobQueue, PushAndPop) {
|
|
||||||
JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
int v = 0;
|
|
||||||
|
|
||||||
queue->push([&v]() { v = 1; });
|
|
||||||
JobQueue::Job job = queue->pop(false);
|
|
||||||
ASSERT_TRUE(job);
|
|
||||||
job();
|
|
||||||
EXPECT_EQ(1, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(JobQueue, PopEmpty) {
|
|
||||||
JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
JobQueue::Job job = queue->pop(false);
|
|
||||||
ASSERT_FALSE(job);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(JobQueue, PopBatch) {
|
|
||||||
JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
int v = 0;
|
|
||||||
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
|
|
||||||
auto jobs = queue->popBatch(2);
|
|
||||||
EXPECT_EQ(2, jobs.size());
|
|
||||||
for (auto& job : jobs) {
|
|
||||||
job();
|
|
||||||
}
|
|
||||||
EXPECT_EQ(2, v);
|
|
||||||
|
|
||||||
jobs = queue->popBatch(10);
|
|
||||||
EXPECT_EQ(1, jobs.size());
|
|
||||||
for (auto& job : jobs) {
|
|
||||||
job();
|
|
||||||
}
|
|
||||||
EXPECT_EQ(3, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(JobQueue, PopAll) {
|
|
||||||
JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
int v = 0;
|
|
||||||
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
|
|
||||||
auto jobs = queue->popBatch(-1);
|
|
||||||
EXPECT_EQ(3, jobs.size());
|
|
||||||
for (auto& job : jobs) {
|
|
||||||
job();
|
|
||||||
}
|
|
||||||
EXPECT_EQ(3, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(JobQueue, Cancel) {
|
|
||||||
JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
int v = 0;
|
|
||||||
|
|
||||||
JobQueue::JobId idToCancel = queue->push([&v]() { v = 1; });
|
|
||||||
queue->push([&v]() { v = 2; });
|
|
||||||
|
|
||||||
EXPECT_TRUE(queue->cancel(idToCancel));
|
|
||||||
|
|
||||||
auto jobs = queue->popBatch(-1);
|
|
||||||
EXPECT_EQ(1, jobs.size());
|
|
||||||
jobs[0]();
|
|
||||||
EXPECT_EQ(2, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(JobQueue, CancelInvalid) {
|
|
||||||
JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
EXPECT_FALSE(queue->cancel(123));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(JobQueue, Stop) {
|
|
||||||
JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
int v = 0;
|
|
||||||
queue->push([&v]() { v = 1; });
|
|
||||||
queue->stop();
|
|
||||||
// After stop, we can't push new jobs. This should be a no-op.
|
|
||||||
JobQueue::JobId id = queue->push([&v]() { v = 2; });
|
|
||||||
EXPECT_EQ(JobQueue::InvalidJobId, id);
|
|
||||||
|
|
||||||
auto job = queue->pop(false);
|
|
||||||
EXPECT_TRUE(job);
|
|
||||||
job();
|
|
||||||
EXPECT_EQ(1, v);
|
|
||||||
|
|
||||||
job = queue->pop(false);
|
|
||||||
EXPECT_FALSE(job);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(JobQueue, PreIssuedJobId) {
|
|
||||||
JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
JobQueue::JobId preIssuedId = queue->issueJobId();
|
|
||||||
JobQueue::JobId id = queue->push([]() {}, preIssuedId);
|
|
||||||
EXPECT_EQ(id, preIssuedId);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(JobQueue, MultipleProducersConsumers) {
|
|
||||||
JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
std::atomic_int v = {0};
|
|
||||||
constexpr int NUM_THREADS = 4;
|
|
||||||
constexpr int JOBS_PER_THREAD = 200;
|
|
||||||
|
|
||||||
// Multiple producers
|
|
||||||
std::vector<std::thread> producers;
|
|
||||||
std::atomic_bool doneProducing = false;
|
|
||||||
for (int i = 0; i < NUM_THREADS; ++i) {
|
|
||||||
producers.emplace_back([&]() {
|
|
||||||
for (int j = 0; j < JOBS_PER_THREAD; ++j) {
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiple consumers
|
|
||||||
std::thread blockingConsumer = std::thread([&]() {
|
|
||||||
while (true) {
|
|
||||||
if (auto job = queue->pop(true)) {
|
|
||||||
job();
|
|
||||||
} else {
|
|
||||||
break; // This means the job queue is stopped.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
std::thread nonBlockingConsumer = std::thread([&]() {
|
|
||||||
while (true) {
|
|
||||||
if (auto job = queue->pop(false)) {
|
|
||||||
job();
|
|
||||||
} else {
|
|
||||||
if (doneProducing.load()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
std::this_thread::yield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
std::thread nonBlockingPopBatchConsumer = std::thread([&]() {
|
|
||||||
while (true) {
|
|
||||||
utils::FixedCapacityVector<JobQueue::Job> jobs = queue->popBatch(2);
|
|
||||||
if (!jobs.empty()) {
|
|
||||||
for (auto& job : jobs) {
|
|
||||||
job();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (doneProducing.load()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
std::this_thread::yield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Waiting for producers to complete pushing jobs
|
|
||||||
for (auto& t : producers) {
|
|
||||||
t.join();
|
|
||||||
}
|
|
||||||
doneProducing = true; // signal for non-blocking consumer
|
|
||||||
queue->stop();
|
|
||||||
|
|
||||||
// Waiting for consumers to complete handling jobs
|
|
||||||
blockingConsumer.join();
|
|
||||||
nonBlockingConsumer.join();
|
|
||||||
nonBlockingPopBatchConsumer.join();
|
|
||||||
|
|
||||||
EXPECT_EQ(NUM_THREADS * JOBS_PER_THREAD, v.load());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
TEST(AmortizationWorker, Process) {
|
|
||||||
JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
JobWorker::Ptr worker = AmortizationWorker::create(queue);
|
|
||||||
int v = 0;
|
|
||||||
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
|
|
||||||
worker->process(2);
|
|
||||||
EXPECT_EQ(2, v);
|
|
||||||
|
|
||||||
worker->process(1);
|
|
||||||
EXPECT_EQ(3, v);
|
|
||||||
|
|
||||||
// No pending jobs, so it should be a no-op.
|
|
||||||
worker->process(1);
|
|
||||||
EXPECT_EQ(3, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(AmortizationWorker, ProcessAll) {
|
|
||||||
JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
JobWorker::Ptr worker = AmortizationWorker::create(queue);
|
|
||||||
int v = 0;
|
|
||||||
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
|
|
||||||
worker->process(-1);
|
|
||||||
EXPECT_EQ(3, v);
|
|
||||||
|
|
||||||
// No pending jobs, so it should be a no-op.
|
|
||||||
worker->process(1);
|
|
||||||
EXPECT_EQ(3, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(AmortizationWorker, TerminateDrainsAllJobs) {
|
|
||||||
JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
JobWorker::Ptr worker = AmortizationWorker::create(queue);
|
|
||||||
int v = 0;
|
|
||||||
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
|
|
||||||
// `terminate` should drain all jobs
|
|
||||||
worker->terminate();
|
|
||||||
EXPECT_EQ(2, v);
|
|
||||||
|
|
||||||
// After terminate, pushing new jobs should not work.
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
worker->process(1);
|
|
||||||
EXPECT_EQ(2, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
TEST(ThreadWorker, Process) {
|
|
||||||
JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
JobWorker::Ptr worker = ThreadWorker::create(queue, {});
|
|
||||||
std::atomic_int v = {0};
|
|
||||||
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
|
|
||||||
// `terminate` should drain all jobs
|
|
||||||
worker->terminate();
|
|
||||||
EXPECT_EQ(2, v.load());
|
|
||||||
|
|
||||||
// After terminate, pushing new jobs should not work.
|
|
||||||
queue->push([&v]() { v++; });
|
|
||||||
worker->terminate();
|
|
||||||
EXPECT_EQ(2, v.load());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ThreadWorker, Callbacks) {
|
|
||||||
JobQueue::Ptr queue = JobQueue::create();
|
|
||||||
bool beginCalled = false;
|
|
||||||
bool endCalled = false;
|
|
||||||
|
|
||||||
ThreadWorker::Config config = {
|
|
||||||
.name = "TestThread",
|
|
||||||
.priority = ThreadWorker::Priority::NORMAL,
|
|
||||||
.onBegin = [&beginCalled]() { beginCalled = true; },
|
|
||||||
.onEnd = [&endCalled]() { endCalled = true; }
|
|
||||||
};
|
|
||||||
JobWorker::Ptr worker = ThreadWorker::create(queue, std::move(config));
|
|
||||||
worker->terminate();
|
|
||||||
|
|
||||||
EXPECT_TRUE(beginCalled);
|
|
||||||
EXPECT_TRUE(endCalled);
|
|
||||||
}
|
|
||||||
@@ -192,7 +192,6 @@ public:
|
|||||||
using StereoscopicType = backend::StereoscopicType;
|
using StereoscopicType = backend::StereoscopicType;
|
||||||
using Driver = backend::Driver;
|
using Driver = backend::Driver;
|
||||||
using GpuContextPriority = backend::Platform::GpuContextPriority;
|
using GpuContextPriority = backend::Platform::GpuContextPriority;
|
||||||
using AsynchronousMode = backend::AsynchronousMode;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Config is used to define the memory footprint used by the engine, such as the
|
* Config is used to define the memory footprint used by the engine, such as the
|
||||||
@@ -429,11 +428,6 @@ public:
|
|||||||
* initial memory usage.
|
* initial memory usage.
|
||||||
*/
|
*/
|
||||||
uint32_t sharedUboInitialSizeInBytes = 256 * 64;
|
uint32_t sharedUboInitialSizeInBytes = 256 * 64;
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronous mode for the engine. Defines how asynchronous operations are handled.
|
|
||||||
*/
|
|
||||||
AsynchronousMode asynchronousMode = AsynchronousMode::NONE;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -748,14 +742,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool isStereoSupported(StereoscopicType stereoscopicType) const noexcept;
|
bool isStereoSupported(StereoscopicType stereoscopicType) const noexcept;
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the engine is set up for asynchronous operation. If it returns true, the
|
|
||||||
* asynchronous versions of the APIs are available for use.
|
|
||||||
*
|
|
||||||
* @return true if the engine supports asynchronous operation.
|
|
||||||
*/
|
|
||||||
bool isAsynchronousOperationSupported() const noexcept;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the configuration settings of this Engine.
|
* Retrieves the configuration settings of this Engine.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -69,20 +69,6 @@ public:
|
|||||||
using ShaderModel = backend::ShaderModel;
|
using ShaderModel = backend::ShaderModel;
|
||||||
using SubpassType = backend::SubpassType;
|
using SubpassType = backend::SubpassType;
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines whether a material instance should use UBO batching or not.
|
|
||||||
*/
|
|
||||||
enum class UboBatchingMode {
|
|
||||||
/**
|
|
||||||
* For default, it follows the engine settings.
|
|
||||||
* If UBO batching is enabled on the engine and the material domain is SURFACE, it
|
|
||||||
* turns on the UBO batching. Otherwise, it turns off the UBO batching.
|
|
||||||
*/
|
|
||||||
DEFAULT,
|
|
||||||
//! Disable the Ubo Batching for this material
|
|
||||||
DISABLED,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds information about a material parameter.
|
* Holds information about a material parameter.
|
||||||
*/
|
*/
|
||||||
@@ -179,13 +165,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
Builder& shadowSamplingQuality(ShadowSamplingQuality quality) noexcept;
|
Builder& shadowSamplingQuality(ShadowSamplingQuality quality) noexcept;
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the batching mode of the instances created from this material.
|
|
||||||
* @param uboBatchingMode
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
Builder& uboBatching(UboBatchingMode uboBatchingMode) noexcept;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the Material object and returns a pointer to it.
|
* Creates the Material object and returns a pointer to it.
|
||||||
*
|
*
|
||||||
@@ -371,12 +350,7 @@ public:
|
|||||||
//! Indicates whether an existing parameter is a sampler or not.
|
//! Indicates whether an existing parameter is a sampler or not.
|
||||||
bool isSampler(const char* UTILS_NONNULL name) const noexcept;
|
bool isSampler(const char* UTILS_NONNULL name) const noexcept;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a view of the material source (.mat which is a JSON-ish file) string,
|
|
||||||
* if it has been set. Otherwise, it returns a view of an empty string.
|
|
||||||
* The lifetime of the string_view is tied to the lifetime of the Material.
|
|
||||||
*/
|
|
||||||
std::string_view getSource() const noexcept;
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Gets the name of the transform field associated for the given sampler parameter.
|
* Gets the name of the transform field associated for the given sampler parameter.
|
||||||
|
|||||||
@@ -113,10 +113,8 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Retrieve a history of frame timing information. The maximum frame history size is
|
* Retrieve a history of frame timing information. The maximum frame history size is
|
||||||
* given by getMaxFrameHistorySize().
|
* given by getMaxFrameHistorySize().
|
||||||
* All or part of the history can be lost when using a different SwapChain in beginFrame().
|
|
||||||
* @param historySize requested history size. The returned vector could be smaller.
|
* @param historySize requested history size. The returned vector could be smaller.
|
||||||
* @return A vector of FrameInfo.
|
* @return A vector of FrameInfo.
|
||||||
* @see beginFrame()
|
|
||||||
*/
|
*/
|
||||||
utils::FixedCapacityVector<FrameInfo> getFrameInfoHistory(
|
utils::FixedCapacityVector<FrameInfo> getFrameInfoHistory(
|
||||||
size_t historySize = 1) const noexcept;
|
size_t historySize = 1) const noexcept;
|
||||||
@@ -328,8 +326,6 @@ public:
|
|||||||
* or 0 if unknown. This value should be the timestamp of
|
* or 0 if unknown. This value should be the timestamp of
|
||||||
* the last h/w vsync. It is expressed in the
|
* the last h/w vsync. It is expressed in the
|
||||||
* std::chrono::steady_clock time base.
|
* std::chrono::steady_clock time base.
|
||||||
* On Android this should be the frame time received from
|
|
||||||
* a Choreographer.
|
|
||||||
* @param swapChain A pointer to the SwapChain instance to use.
|
* @param swapChain A pointer to the SwapChain instance to use.
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
@@ -341,8 +337,6 @@ public:
|
|||||||
*
|
*
|
||||||
* @note
|
* @note
|
||||||
* All calls to render() must happen *after* beginFrame().
|
* All calls to render() must happen *after* beginFrame().
|
||||||
* It is recommended to use the same swapChain for every call to beginFrame, failing to do
|
|
||||||
* so can result is losing all or part of the FrameInfo history.
|
|
||||||
*
|
*
|
||||||
* @see
|
* @see
|
||||||
* endFrame()
|
* endFrame()
|
||||||
|
|||||||
@@ -455,10 +455,6 @@ bool Engine::isStereoSupported(StereoscopicType) const noexcept {
|
|||||||
return downcast(this)->isStereoSupported();
|
return downcast(this)->isStereoSupported();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Engine::isAsynchronousOperationSupported() const noexcept {
|
|
||||||
return downcast(this)->isAsynchronousOperationSupported();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Engine::getMaxStereoscopicEyes() noexcept {
|
size_t Engine::getMaxStereoscopicEyes() noexcept {
|
||||||
return FEngine::getMaxStereoscopicEyes();
|
return FEngine::getMaxStereoscopicEyes();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,24 +101,11 @@ void FrameInfoManager::beginFrame(FSwapChain* swapChain, DriverApi& driver,
|
|||||||
auto& history = mFrameTimeHistory;
|
auto& history = mFrameTimeHistory;
|
||||||
// don't exceed the capacity, drop the oldest entry
|
// don't exceed the capacity, drop the oldest entry
|
||||||
if (UTILS_LIKELY(history.size() == history.capacity())) {
|
if (UTILS_LIKELY(history.size() == history.capacity())) {
|
||||||
FrameInfoImpl& frameInfo = history.back();
|
if (!mDisableGpuFrameComplete) {
|
||||||
if (frameInfo.ready.load(std::memory_order_relaxed)) {
|
assert_invariant(history.back().fence);
|
||||||
if (!mDisableGpuFrameComplete) {
|
driver.destroyFence(std::move(history.back().fence));
|
||||||
assert_invariant(frameInfo.fence);
|
|
||||||
driver.destroyFence(std::move(frameInfo.fence));
|
|
||||||
}
|
|
||||||
history.pop_back();
|
|
||||||
} else {
|
|
||||||
// This is a big problem, we ran out of space in the circular queue and that entry
|
|
||||||
// hasn't been processed yet. Because the code below keeps a reference to the
|
|
||||||
// front element of the queue, we can't pop/push. Our only option is to not record
|
|
||||||
// a new entry for this frame, which will create a false skipped frame in the
|
|
||||||
// data.
|
|
||||||
LOG(WARNING) << "FrameInfo's circular queue is full, but the oldest item hasn't "
|
|
||||||
"been processed yet. Skipping this frame, id = " << frameId;
|
|
||||||
mLastBeginFrameSkipped = true;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
history.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new entry
|
// create a new entry
|
||||||
@@ -206,14 +193,6 @@ void FrameInfoManager::beginFrame(FSwapChain* swapChain, DriverApi& driver,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FrameInfoManager::endFrame(DriverApi& driver) noexcept {
|
void FrameInfoManager::endFrame(DriverApi& driver) noexcept {
|
||||||
if (mLastBeginFrameSkipped) {
|
|
||||||
// if we had to skip the last beginFrame(), endFrame() needs to be skipped too
|
|
||||||
// because history.front() now references the wrong frame.
|
|
||||||
// It is guaranteed that if beginFrame() is called, endFrame() will be called too.
|
|
||||||
mLastBeginFrameSkipped = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& front = mFrameTimeHistory.front();
|
auto& front = mFrameTimeHistory.front();
|
||||||
front.endFrame = std::chrono::steady_clock::now();
|
front.endFrame = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
|||||||
@@ -95,8 +95,33 @@ struct FrameInfoImpl : public details::FrameInfo {
|
|||||||
assert_invariant(!fence);
|
assert_invariant(!fence);
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameInfoImpl(FrameInfoImpl& rhs) noexcept = delete;
|
FrameInfoImpl(FrameInfoImpl&& rhs) noexcept :
|
||||||
FrameInfoImpl& operator=(FrameInfoImpl& rhs) noexcept = delete;
|
details::FrameInfo(rhs),
|
||||||
|
frameId(rhs.frameId),
|
||||||
|
beginFrame(rhs.beginFrame),
|
||||||
|
endFrame(rhs.endFrame),
|
||||||
|
backendBeginFrame(rhs.backendBeginFrame),
|
||||||
|
backendEndFrame(rhs.backendEndFrame),
|
||||||
|
gpuFrameComplete(rhs.gpuFrameComplete),
|
||||||
|
vsync(rhs.vsync),
|
||||||
|
fence(rhs.fence),
|
||||||
|
ready(rhs.ready.load())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameInfoImpl& operator=(FrameInfoImpl&& rhs) noexcept {
|
||||||
|
details::FrameInfo::operator=(rhs);
|
||||||
|
frameId = rhs.frameId;
|
||||||
|
beginFrame = rhs.beginFrame;
|
||||||
|
endFrame = rhs.endFrame;
|
||||||
|
backendBeginFrame = rhs.backendBeginFrame;
|
||||||
|
backendEndFrame = rhs.backendEndFrame;
|
||||||
|
gpuFrameComplete = rhs.gpuFrameComplete;
|
||||||
|
vsync = rhs.vsync;
|
||||||
|
fence = rhs.fence;
|
||||||
|
ready.store(rhs.ready.load());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T, size_t CAPACITY>
|
template<typename T, size_t CAPACITY>
|
||||||
@@ -315,7 +340,6 @@ private:
|
|||||||
FrameHistoryQueue mFrameTimeHistory{};
|
FrameHistoryQueue mFrameTimeHistory{};
|
||||||
utils::AsyncJobQueue mJobQueue;
|
utils::AsyncJobQueue mJobQueue;
|
||||||
FSwapChain* mLastSeenSwapChain = nullptr;
|
FSwapChain* mLastSeenSwapChain = nullptr;
|
||||||
bool mLastBeginFrameSkipped = false;
|
|
||||||
bool const mHasTimerQueries = false;
|
bool const mHasTimerQueries = false;
|
||||||
bool const mDisableGpuFrameComplete = false;
|
bool const mDisableGpuFrameComplete = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -146,10 +146,6 @@ bool Material::isSampler(const char* name) const noexcept {
|
|||||||
return downcast(this)->isSampler(name);
|
return downcast(this)->isSampler(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string_view Material::getSource() const noexcept {
|
|
||||||
return downcast(this)->getSource();
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* Material::getParameterTransformName(const char* samplerName) const noexcept {
|
const char* Material::getParameterTransformName(const char* samplerName) const noexcept {
|
||||||
return downcast(this)->getParameterTransformName(samplerName);
|
return downcast(this)->getParameterTransformName(samplerName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,8 +257,6 @@ void MaterialDefinition::processMain() {
|
|||||||
bool const hasFog = !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::FOG));
|
bool const hasFog = !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::FOG));
|
||||||
|
|
||||||
perViewLayoutIndex = ColorPassDescriptorSet::getIndex(isLit, isSSR, hasFog);
|
perViewLayoutIndex = ColorPassDescriptorSet::getIndex(isLit, isSSR, hasFog);
|
||||||
|
|
||||||
mMaterialParser->getSourceShader(&source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaterialDefinition::processBlendingMode() {
|
void MaterialDefinition::processBlendingMode() {
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ struct MaterialDefinition {
|
|||||||
|
|
||||||
utils::CString name;
|
utils::CString name;
|
||||||
uint64_t cacheId = 0;
|
uint64_t cacheId = 0;
|
||||||
utils::CString source;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class MaterialCache;
|
friend class MaterialCache;
|
||||||
|
|||||||
@@ -35,8 +35,6 @@
|
|||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
#include <backend/Program.h>
|
#include <backend/Program.h>
|
||||||
|
|
||||||
#include <zstd.h>
|
|
||||||
|
|
||||||
#include <utils/compiler.h>
|
#include <utils/compiler.h>
|
||||||
#include <utils/CString.h>
|
#include <utils/CString.h>
|
||||||
#include <utils/FixedCapacityVector.h>
|
#include <utils/FixedCapacityVector.h>
|
||||||
@@ -239,40 +237,6 @@ bool MaterialParser::getName(CString* cstring) const noexcept {
|
|||||||
return unflattener.read(cstring);
|
return unflattener.read(cstring);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MaterialParser::getSourceShader(CString* cstring) const noexcept {
|
|
||||||
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialSource);
|
|
||||||
// Source material is optional, treat it as a success.
|
|
||||||
if (start == end) return true;
|
|
||||||
|
|
||||||
Unflattener unflattener(start, end);
|
|
||||||
|
|
||||||
// First get the compressed blob.
|
|
||||||
const char* compressed;
|
|
||||||
size_t compressedSize = 0;
|
|
||||||
if (!unflattener.read(&compressed, &compressedSize)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the bound and decompress it.
|
|
||||||
const size_t decompressBound =
|
|
||||||
ZSTD_getFrameContentSize(compressed, compressedSize);
|
|
||||||
if (ZSTD_isError(decompressBound)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto dst_buffer = std::make_unique<char[]>(decompressBound);
|
|
||||||
const size_t decompressed =
|
|
||||||
ZSTD_decompress(
|
|
||||||
dst_buffer.get(), decompressBound,
|
|
||||||
compressed, compressedSize);
|
|
||||||
if (ZSTD_isError(decompressed)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*cstring = CString { dst_buffer.get(), decompressed };
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MaterialParser::getCacheId(uint64_t* cacheId) const noexcept {
|
bool MaterialParser::getCacheId(uint64_t* cacheId) const noexcept {
|
||||||
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialCacheId);
|
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialCacheId);
|
||||||
if (start == end) return false;
|
if (start == end) return false;
|
||||||
|
|||||||
@@ -145,8 +145,6 @@ public:
|
|||||||
return getMaterialChunk().hasShader(model, variant, stage);
|
return getMaterialChunk().hasShader(model, variant, stage);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getSourceShader(utils::CString* cstring) const noexcept;
|
|
||||||
|
|
||||||
filaflat::MaterialChunk const& getMaterialChunk() const noexcept {
|
filaflat::MaterialChunk const& getMaterialChunk() const noexcept {
|
||||||
return mImpl.mMaterialChunk;
|
return mImpl.mMaterialChunk;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
#include "details/BufferAllocator.h"
|
#include "details/BufferAllocator.h"
|
||||||
|
|
||||||
#include <private/utils/Tracing.h>
|
|
||||||
#include <utils/Panic.h>
|
#include <utils/Panic.h>
|
||||||
#include <utils/debug.h>
|
#include <utils/debug.h>
|
||||||
|
|
||||||
@@ -170,7 +169,6 @@ void BufferAllocator::releaseGpu(AllocationId id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BufferAllocator::releaseFreeSlots() {
|
void BufferAllocator::releaseFreeSlots() {
|
||||||
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
|
|
||||||
if (!mHasPendingFrees) {
|
if (!mHasPendingFrees) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user