Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edfdc7fe44 | ||
|
|
eb483501f0 | ||
|
|
c54fe53936 | ||
|
|
b915e2f35f | ||
|
|
c24e5089c4 | ||
|
|
87dbd59275 | ||
|
|
3c02eb9a63 | ||
|
|
0bb96f0b62 | ||
|
|
06c0667636 | ||
|
|
c873552eaf | ||
|
|
38cc77807b | ||
|
|
ded1cb1b09 | ||
|
|
653b2e859c | ||
|
|
7fd24c38d7 | ||
|
|
d4166e3a84 | ||
|
|
c9f3d40993 | ||
|
|
3cf76a837c | ||
|
|
d1781fd66c | ||
|
|
577f1f83ca | ||
|
|
7956a28c71 | ||
|
|
e264dbad98 | ||
|
|
ef14e9c90e | ||
|
|
6c27340d22 | ||
|
|
3302c1ef4b | ||
|
|
c3ae2cdc8f | ||
|
|
e52c6d8e53 | ||
|
|
fdcf82f17a | ||
|
|
60f5c4d32c | ||
|
|
42ce76e285 | ||
|
|
f903683c92 | ||
|
|
546ba48043 | ||
|
|
c81af315b2 | ||
|
|
ea64db4218 |
@@ -12,7 +12,7 @@ jobs:
|
|||||||
name: build-android
|
name: build-android
|
||||||
# We intentially use a larger runner here to enable larger disk space
|
# We intentially use a larger runner here to enable larger disk space
|
||||||
# (standard linux runner will fail on disk space and faster build time).
|
# (standard linux runner will fail on disk space and faster build time).
|
||||||
runs-on: ubuntu-22.04-32core
|
runs-on: 'ubuntu-24.04-32core'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build-linux:
|
build-linux:
|
||||||
name: build-linux
|
name: build-linux
|
||||||
runs-on: ubuntu-22.04-16core
|
runs-on: 'ubuntu-24.04-16core'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
|
|
||||||
build-android:
|
build-android:
|
||||||
name: build-android
|
name: build-android
|
||||||
runs-on: ubuntu-22.04-16core
|
runs-on: 'ubuntu-24.04-16core'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
|
|
||||||
build-web:
|
build-web:
|
||||||
name: build-web
|
name: build-web
|
||||||
runs-on: ubuntu-22.04-16core
|
runs-on: 'ubuntu-24.04-16core'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
|
|
||||||
validate-docs:
|
validate-docs:
|
||||||
name: validate-docs
|
name: validate-docs
|
||||||
runs-on: ubuntu-22.04-4core
|
runs-on: 'ubuntu-24.04-4core'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -114,7 +114,7 @@ jobs:
|
|||||||
|
|
||||||
validate-wgsl-webgpu:
|
validate-wgsl-webgpu:
|
||||||
name: validate-wgsl-webgpu
|
name: validate-wgsl-webgpu
|
||||||
runs-on: ubuntu-22.04-8core
|
runs-on: 'ubuntu-24.04-8core'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
|
|
||||||
build-web:
|
build-web:
|
||||||
name: build-web
|
name: build-web
|
||||||
runs-on: ubuntu-22.04-16core
|
runs-on: 'ubuntu-24.04-16core'
|
||||||
if: github.event_name == 'release' || github.event.inputs.platform == 'web'
|
if: github.event_name == 'release' || github.event.inputs.platform == 'web'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -103,7 +103,7 @@ jobs:
|
|||||||
|
|
||||||
build-android:
|
build-android:
|
||||||
name: build-android
|
name: build-android
|
||||||
runs-on: ubuntu-22.04-16core
|
runs-on: 'ubuntu-24.04-16core'
|
||||||
if: github.event_name == 'release' || github.event.inputs.platform == 'android'
|
if: github.event_name == 'release' || github.event.inputs.platform == 'android'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build-web:
|
build-web:
|
||||||
name: build-web
|
name: build-web
|
||||||
runs-on: ubuntu-22.04-16core
|
runs-on: 'ubuntu-24.04-16core'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
|
|||||||
@@ -7,6 +7,3 @@ for next branch cut* header.
|
|||||||
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
|
||||||
|
|
||||||
- materials: five custom variables (varyings) are now available on the condition that the `color` attribute is not requested (b/404930099). [⚠️ **New Material Version**]
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.google.android.filament:filament-android:1.58.1'
|
implementation 'com.google.android.filament:filament-android:1.59.1'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
|||||||
iOS projects can use CocoaPods to install the latest release:
|
iOS projects can use CocoaPods to install the latest release:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pod 'Filament', '~> 1.58.1'
|
pod 'Filament', '~> 1.59.1'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ 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.59.1
|
||||||
|
|
||||||
|
|
||||||
|
## v1.59.0
|
||||||
|
|
||||||
|
- materials: five custom variables (varyings) are now available on the condition that the `color` attribute is not requested (b/404930099). [⚠️ **New Material Version**]
|
||||||
|
|
||||||
## v1.58.2
|
## v1.58.2
|
||||||
|
|
||||||
- engine: Generate 1D instead of 3D LUTs for color grading whenever possible.
|
- engine: Generate 1D instead of 3D LUTs for color grading whenever possible.
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ buildscript {
|
|||||||
|
|
||||||
ext.versions = [
|
ext.versions = [
|
||||||
'jdk': 17,
|
'jdk': 17,
|
||||||
'minSdk': 26,
|
'minSdk': 21,
|
||||||
'targetSdk': 34,
|
'targetSdk': 34,
|
||||||
'compileSdk': 34,
|
'compileSdk': 34,
|
||||||
'kotlin': '2.0.21',
|
'kotlin': '2.0.21',
|
||||||
@@ -125,7 +125,7 @@ buildscript {
|
|||||||
ext.cmakeArgs = [
|
ext.cmakeArgs = [
|
||||||
"--no-warn-unused-cli",
|
"--no-warn-unused-cli",
|
||||||
"-DANDROID_PIE=ON",
|
"-DANDROID_PIE=ON",
|
||||||
"-DANDROID_PLATFORM=26",
|
"-DANDROID_PLATFORM=21",
|
||||||
"-DANDROID_STL=c++_static",
|
"-DANDROID_STL=c++_static",
|
||||||
"-DFILAMENT_DIST_DIR=${filamentPath}".toString(),
|
"-DFILAMENT_DIST_DIR=${filamentPath}".toString(),
|
||||||
"-DFILAMENT_SUPPORTS_VULKAN=${excludeVulkan ? 'OFF' : 'ON'}".toString(),
|
"-DFILAMENT_SUPPORTS_VULKAN=${excludeVulkan ? 'OFF' : 'ON'}".toString(),
|
||||||
@@ -200,7 +200,7 @@ subprojects {
|
|||||||
ndkVersion versions.ndk
|
ndkVersion versions.ndk
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 26
|
minSdkVersion versions.minSdk
|
||||||
targetSdkVersion versions.targetSdk
|
targetSdkVersion versions.targetSdk
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ project(filament-utils-android)
|
|||||||
set(FILAMENT_DIR ${FILAMENT_DIST_DIR})
|
set(FILAMENT_DIR ${FILAMENT_DIST_DIR})
|
||||||
set(IMAGEIO_DIR ../../libs/imageio)
|
set(IMAGEIO_DIR ../../libs/imageio)
|
||||||
|
|
||||||
set(CMAKE_SYSTEM_VERSION 26)
|
|
||||||
|
|
||||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../gltfio-android ${CMAKE_CURRENT_BINARY_DIR}/gltfio-android)
|
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../gltfio-android ${CMAKE_CURRENT_BINARY_DIR}/gltfio-android)
|
||||||
|
|
||||||
add_library(camutils STATIC IMPORTED)
|
add_library(camutils STATIC IMPORTED)
|
||||||
@@ -32,10 +30,6 @@ add_library(iblprefilter STATIC IMPORTED)
|
|||||||
set_target_properties(iblprefilter PROPERTIES IMPORTED_LOCATION
|
set_target_properties(iblprefilter PROPERTIES IMPORTED_LOCATION
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilament-iblprefilter.a)
|
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilament-iblprefilter.a)
|
||||||
|
|
||||||
add_library(bluevk STATIC IMPORTED)
|
|
||||||
set_target_properties(bluevk PROPERTIES IMPORTED_LOCATION
|
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libbluevk.a)
|
|
||||||
|
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libfilament-utils-jni.map")
|
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libfilament-utils-jni.map")
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
|
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
|
||||||
|
|
||||||
@@ -63,8 +57,7 @@ target_include_directories(filament-utils-jni PRIVATE
|
|||||||
..
|
..
|
||||||
../../filament/backend/include
|
../../filament/backend/include
|
||||||
${IMAGEIO_DIR}/include
|
${IMAGEIO_DIR}/include
|
||||||
../../libs/utils/include
|
../../libs/utils/include)
|
||||||
../../libs/bluevk/include)
|
|
||||||
|
|
||||||
set_target_properties(filament-utils-jni PROPERTIES LINK_DEPENDS
|
set_target_properties(filament-utils-jni PROPERTIES LINK_DEPENDS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/libfilament-utils-jni.symbols)
|
${CMAKE_CURRENT_SOURCE_DIR}/libfilament-utils-jni.symbols)
|
||||||
@@ -78,6 +71,4 @@ target_link_libraries(filament-utils-jni
|
|||||||
image
|
image
|
||||||
ktxreader
|
ktxreader
|
||||||
viewer
|
viewer
|
||||||
bluevk
|
|
||||||
android
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdkVersion 26
|
|
||||||
}
|
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
// No need to package up the following shared libs, which arise as a side effect of our
|
// No need to package up the following shared libs, which arise as a side effect of our
|
||||||
// externalNativeBuild dependencies. When clients pick and choose from project-level gradle
|
// externalNativeBuild dependencies. When clients pick and choose from project-level gradle
|
||||||
|
|||||||
@@ -14,21 +14,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include <android/hardware_buffer.h>
|
|
||||||
#include <android/hardware_buffer_jni.h>
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
|
||||||
#include <filament/Engine.h>
|
#include <filament/Engine.h>
|
||||||
#include <filament/IndirectLight.h>
|
#include <filament/IndirectLight.h>
|
||||||
#include <filament/Skybox.h>
|
#include <filament/Skybox.h>
|
||||||
|
|
||||||
#include <backend/Platform.h>
|
|
||||||
#include <backend/platforms/PlatformEGLAndroid.h>
|
|
||||||
#include <backend/platforms/VulkanPlatformAndroid.h>
|
|
||||||
|
|
||||||
#include <utils/Log.h>
|
|
||||||
|
|
||||||
#include <ktxreader/Ktx1Reader.h>
|
#include <ktxreader/Ktx1Reader.h>
|
||||||
|
|
||||||
#include "common/NioUtils.h"
|
#include "common/NioUtils.h"
|
||||||
@@ -38,8 +29,6 @@ using namespace filament::math;
|
|||||||
using namespace image;
|
using namespace image;
|
||||||
using namespace ktxreader;
|
using namespace ktxreader;
|
||||||
|
|
||||||
using namespace filament::backend;
|
|
||||||
|
|
||||||
jlong nCreateHDRTexture(JNIEnv* env, jclass,
|
jlong nCreateHDRTexture(JNIEnv* env, jclass,
|
||||||
jlong nativeEngine, jobject javaBuffer, jint remaining, jint internalFormat);
|
jlong nativeEngine, jobject javaBuffer, jint remaining, jint internalFormat);
|
||||||
|
|
||||||
@@ -90,37 +79,6 @@ static jboolean nGetSphericalHarmonics(JNIEnv* env, jclass, jobject javaBuffer,
|
|||||||
return success ? JNI_TRUE : JNI_FALSE;
|
return success ? JNI_TRUE : JNI_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static jlong nSetExternalImageOnTexture(JNIEnv* env, jclass, jlong nativeEngine, jlong nativeTexture,
|
|
||||||
jobject hardwareBuffer, jboolean srgb) {
|
|
||||||
utils::slog.e <<"--------- jni nSetExternalImageOnTexture" << utils::io::endl;
|
|
||||||
Engine* engine = (Engine*) nativeEngine;
|
|
||||||
Texture* texture = (Texture*) nativeTexture;
|
|
||||||
|
|
||||||
Platform* platform = engine->getPlatform();
|
|
||||||
AHardwareBuffer* nativeBuffer = nullptr;
|
|
||||||
if (__builtin_available(android 26, *)) {
|
|
||||||
nativeBuffer = AHardwareBuffer_fromHardwareBuffer(env, hardwareBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
utils::slog.e <<"--------- jni nSetExternalImageOnTexture buf=" << nativeBuffer << utils::io::endl;
|
|
||||||
|
|
||||||
if (!nativeBuffer) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (engine->getBackend() == Backend::OPENGL) {
|
|
||||||
PlatformEGLAndroid* eglPlatform = (PlatformEGLAndroid*) platform;
|
|
||||||
auto ref = eglPlatform->createExternalImage(nativeBuffer, srgb == JNI_TRUE);
|
|
||||||
texture->setExternalImage(*engine, ref);
|
|
||||||
} else if (engine->getBackend() == Backend::VULKAN) {
|
|
||||||
VulkanPlatformAndroid* vulkanPlatform = (VulkanPlatformAndroid*) platform;
|
|
||||||
auto ref = vulkanPlatform->createExternalImage(nativeBuffer, srgb == JNI_TRUE);
|
|
||||||
texture->setExternalImage(*engine, ref);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
|
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
|
||||||
JNIEnv* env;
|
JNIEnv* env;
|
||||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||||
@@ -150,14 +108,5 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
|
|||||||
rc = env->RegisterNatives(hdrloaderClass, hdrMethods, sizeof(hdrMethods) / sizeof(JNINativeMethod));
|
rc = env->RegisterNatives(hdrloaderClass, hdrMethods, sizeof(hdrMethods) / sizeof(JNINativeMethod));
|
||||||
if (rc != JNI_OK) return rc;
|
if (rc != JNI_OK) return rc;
|
||||||
|
|
||||||
jclass loaderClass = env->FindClass("com/google/android/filament/utils/ExternalImage");
|
|
||||||
if (loaderClass == nullptr) return JNI_ERR;
|
|
||||||
static const JNINativeMethod methods[] = {
|
|
||||||
{ (char*) "nSetExternalImageOnTexture", (char*) "(JJLandroid/hardware/HardwareBuffer;Z)J",
|
|
||||||
reinterpret_cast<void*>(nSetExternalImageOnTexture) },
|
|
||||||
};
|
|
||||||
rc = env->RegisterNatives(loaderClass, methods, sizeof(methods) / sizeof(JNINativeMethod));
|
|
||||||
if (rc != JNI_OK) return rc;
|
|
||||||
|
|
||||||
return JNI_VERSION_1_6;
|
return JNI_VERSION_1_6;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 Romain Guy
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.android.filament.utils
|
|
||||||
|
|
||||||
import android.hardware.HardwareBuffer
|
|
||||||
import com.google.android.filament.Engine
|
|
||||||
import com.google.android.filament.Texture
|
|
||||||
|
|
||||||
object ExternalImage {
|
|
||||||
fun setOnTexture(
|
|
||||||
engine: Engine,
|
|
||||||
texture: Texture,
|
|
||||||
buffer: HardwareBuffer,
|
|
||||||
srgb: Boolean,
|
|
||||||
) {
|
|
||||||
val nativeEngine = engine.nativeObject
|
|
||||||
val nativeTexture = texture.nativeObject
|
|
||||||
val l = nSetExternalImageOnTexture(nativeEngine, nativeTexture, buffer, srgb)
|
|
||||||
}
|
|
||||||
|
|
||||||
private external fun nSetExternalImageOnTexture(
|
|
||||||
nativeEngine: Long,
|
|
||||||
nativeTexture: Long,
|
|
||||||
buffer: HardwareBuffer,
|
|
||||||
srgb: Boolean,
|
|
||||||
): Long
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
GROUP=com.google.android.filament
|
GROUP=com.google.android.filament
|
||||||
VERSION_NAME=1.58.1
|
VERSION_NAME=1.59.1
|
||||||
|
|
||||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
*.iml
|
|
||||||
.gradle
|
|
||||||
/local.properties
|
|
||||||
/.idea/workspace.xml
|
|
||||||
/.idea/libraries
|
|
||||||
/.idea/caches
|
|
||||||
/.idea/gradle.xml
|
|
||||||
.DS_Store
|
|
||||||
/build
|
|
||||||
/captures
|
|
||||||
/src/main/assets
|
|
||||||
.externalNativeBuild
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'com.android.application'
|
|
||||||
id 'kotlin-android'
|
|
||||||
id 'filament-tools-plugin'
|
|
||||||
}
|
|
||||||
|
|
||||||
project.ext.isSample = true
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
jvmToolchain(versions.jdk)
|
|
||||||
}
|
|
||||||
|
|
||||||
filamentTools {
|
|
||||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
|
||||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
|
||||||
}
|
|
||||||
|
|
||||||
clean.doFirst {
|
|
||||||
delete "src/main/assets"
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace 'com.google.android.filament.externalimg'
|
|
||||||
|
|
||||||
compileSdkVersion versions.compileSdk
|
|
||||||
defaultConfig {
|
|
||||||
applicationId "com.google.android.filament.externalimg"
|
|
||||||
minSdkVersion 26
|
|
||||||
targetSdkVersion versions.targetSdk
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
|
|
||||||
// is not configuration-cache friendly yet; this is only useful for Play publication
|
|
||||||
dependenciesInfo {
|
|
||||||
includeInApk = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use the .filamat extension for materials compiled with matc
|
|
||||||
// Telling aapt to not compress them allows to load them efficiently
|
|
||||||
aaptOptions {
|
|
||||||
noCompress 'filamat', 'ktx'
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility versions.jdk
|
|
||||||
targetCompatibility versions.jdk
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation deps.kotlin
|
|
||||||
implementation deps.androidx.core
|
|
||||||
implementation project(':filament-android')
|
|
||||||
implementation project(':filament-utils-android')
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Copyright (C) 2019 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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.camera" />
|
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
|
||||||
|
|
||||||
<application
|
|
||||||
android:allowBackup="true"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:supportsRtl="true"
|
|
||||||
android:theme="@style/AppTheme">
|
|
||||||
<activity
|
|
||||||
android:exported="true"
|
|
||||||
android:name=".MainActivity">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.android.filament.externalimg
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.graphics.SurfaceTexture
|
|
||||||
import android.hardware.camera2.*
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.HandlerThread
|
|
||||||
import android.util.Log
|
|
||||||
import android.util.Size
|
|
||||||
import android.view.Surface
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.graphics.ImageFormat
|
|
||||||
import android.hardware.HardwareBuffer
|
|
||||||
import android.media.ImageReader
|
|
||||||
import android.opengl.Matrix
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Looper
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
|
|
||||||
import com.google.android.filament.*
|
|
||||||
|
|
||||||
import java.util.concurrent.Semaphore
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toy class that handles all interaction with the Android camera2 API.
|
|
||||||
* Sets the "textureTransform" and "videoTexture" parameters on the given Filament material.
|
|
||||||
*/
|
|
||||||
class CameraHelper(val activity: Activity, private val filamentEngine: Engine, private val filamentMaterial: MaterialInstance) {
|
|
||||||
private lateinit var cameraId: String
|
|
||||||
private lateinit var captureRequest: CaptureRequest
|
|
||||||
|
|
||||||
private val cameraOpenCloseLock = Semaphore(1)
|
|
||||||
private var backgroundHandler: Handler? = null
|
|
||||||
private var backgroundThread: HandlerThread? = null
|
|
||||||
private var cameraDevice: CameraDevice? = null
|
|
||||||
private var captureSession: CameraCaptureSession? = null
|
|
||||||
private var resolution = Size(640, 480)
|
|
||||||
private var filamentTexture: Texture? = null
|
|
||||||
private var filamentStream: Stream? = null
|
|
||||||
private val imageReader = ImageReader.newInstance(
|
|
||||||
resolution.width,
|
|
||||||
resolution.height,
|
|
||||||
ImageFormat.PRIVATE,
|
|
||||||
kImageReaderMaxImages,
|
|
||||||
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
|
|
||||||
|
|
||||||
@Suppress("deprecation")
|
|
||||||
private val display = if (Build.VERSION.SDK_INT >= 30) {
|
|
||||||
Api30Impl.getDisplay(activity)
|
|
||||||
} else {
|
|
||||||
activity.windowManager.defaultDisplay!!
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(30)
|
|
||||||
class Api30Impl {
|
|
||||||
companion object {
|
|
||||||
fun getDisplay(context: Context) = context.display!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val cameraCallback = object : CameraDevice.StateCallback() {
|
|
||||||
override fun onOpened(cameraDevice: CameraDevice) {
|
|
||||||
cameraOpenCloseLock.release()
|
|
||||||
this@CameraHelper.cameraDevice = cameraDevice
|
|
||||||
createCaptureSession()
|
|
||||||
}
|
|
||||||
override fun onDisconnected(cameraDevice: CameraDevice) {
|
|
||||||
cameraOpenCloseLock.release()
|
|
||||||
cameraDevice.close()
|
|
||||||
this@CameraHelper.cameraDevice = null
|
|
||||||
}
|
|
||||||
override fun onError(cameraDevice: CameraDevice, error: Int) {
|
|
||||||
onDisconnected(cameraDevice)
|
|
||||||
this@CameraHelper.activity.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the latest image (if any) from ImageReader and passes its HardwareBuffer to Filament.
|
|
||||||
*/
|
|
||||||
fun pushExternalImageToFilament() {
|
|
||||||
val stream = filamentStream
|
|
||||||
if (stream != null) {
|
|
||||||
imageReader.acquireLatestImage()?.also {
|
|
||||||
stream.setAcquiredImage(it.hardwareBuffer, Handler(Looper.getMainLooper())) {
|
|
||||||
it.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the front-facing Android camera, requests permission, and sets up a listener that will
|
|
||||||
* start a capture session as soon as the camera is ready.
|
|
||||||
*/
|
|
||||||
fun openCamera() {
|
|
||||||
val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
|
||||||
try {
|
|
||||||
for (cameraId in manager.cameraIdList) {
|
|
||||||
val characteristics = manager.getCameraCharacteristics(cameraId)
|
|
||||||
val cameraDirection = characteristics.get(CameraCharacteristics.LENS_FACING)
|
|
||||||
if (cameraDirection != null && cameraDirection == CameraCharacteristics.LENS_FACING_FRONT) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cameraId = cameraId
|
|
||||||
Log.i(kLogTag, "Selected camera $cameraId.")
|
|
||||||
|
|
||||||
val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue
|
|
||||||
resolution = map.getOutputSizes(SurfaceTexture::class.java)[0]
|
|
||||||
Log.i(kLogTag, "Highest resolution is $resolution.")
|
|
||||||
}
|
|
||||||
} catch (e: CameraAccessException) {
|
|
||||||
Log.e(kLogTag, e.toString())
|
|
||||||
} catch (e: NullPointerException) {
|
|
||||||
Log.e(kLogTag, "Camera2 API is not supported on this device.")
|
|
||||||
}
|
|
||||||
|
|
||||||
val permission = ContextCompat.checkSelfPermission(this.activity, Manifest.permission.CAMERA)
|
|
||||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
activity.requestPermissions(arrayOf(Manifest.permission.CAMERA), kRequestCameraPermission)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
|
|
||||||
throw RuntimeException("Time out waiting to lock camera opening.")
|
|
||||||
}
|
|
||||||
manager.openCamera(cameraId, cameraCallback, backgroundHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onResume() {
|
|
||||||
backgroundThread = HandlerThread("CameraBackground").also { it.start() }
|
|
||||||
backgroundHandler = Handler(backgroundThread?.looper!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onPause() {
|
|
||||||
backgroundThread?.quitSafely()
|
|
||||||
try {
|
|
||||||
backgroundThread?.join()
|
|
||||||
backgroundThread = null
|
|
||||||
backgroundHandler = null
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
Log.e(kLogTag, e.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onRequestPermissionsResult(requestCode: Int, grantResults: IntArray): Boolean {
|
|
||||||
if (requestCode == kRequestCameraPermission) {
|
|
||||||
if (grantResults.size != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
Log.e(kLogTag, "Unable to obtain camera position.")
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createCaptureSession() {
|
|
||||||
filamentStream?.apply { filamentEngine.destroyStream(this) }
|
|
||||||
|
|
||||||
// [Re]create the Filament Stream object that gets bound to the Texture.
|
|
||||||
filamentStream = Stream.Builder().build(filamentEngine)
|
|
||||||
|
|
||||||
// Create the Filament Texture object if we haven't done so already.
|
|
||||||
if (filamentTexture == null) {
|
|
||||||
filamentTexture = Texture.Builder()
|
|
||||||
.sampler(Texture.Sampler.SAMPLER_EXTERNAL)
|
|
||||||
.format(Texture.InternalFormat.RGB8)
|
|
||||||
.build(filamentEngine)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are texturing a front-facing square shape so we need to generate a matrix that transforms (u, v, 0, 1)
|
|
||||||
// into a new UV coordinate according to the screen rotation and the aspect ratio of the camera image.
|
|
||||||
val aspectRatio = resolution.width.toFloat() / resolution.height.toFloat()
|
|
||||||
val textureTransform = FloatArray(16)
|
|
||||||
Matrix.setIdentityM(textureTransform, 0)
|
|
||||||
when (display.rotation) {
|
|
||||||
Surface.ROTATION_0 -> {
|
|
||||||
Matrix.translateM(textureTransform, 0, 1.0f, 0.0f, 0.0f)
|
|
||||||
Matrix.rotateM(textureTransform, 0, 90.0f, 0.0f, 0.0f, 1.0f)
|
|
||||||
Matrix.translateM(textureTransform, 0, 1.0f, 0.0f, 0.0f)
|
|
||||||
Matrix.scaleM(textureTransform, 0, -1.0f, 1.0f / aspectRatio, 1.0f)
|
|
||||||
}
|
|
||||||
Surface.ROTATION_90 -> {
|
|
||||||
Matrix.translateM(textureTransform, 0, 1.0f, 1.0f, 0.0f)
|
|
||||||
Matrix.rotateM(textureTransform, 0, 180.0f, 0.0f, 0.0f, 1.0f)
|
|
||||||
Matrix.translateM(textureTransform, 0, 1.0f, 0.0f, 0.0f)
|
|
||||||
Matrix.scaleM(textureTransform, 0, -1.0f / aspectRatio, 1.0f, 1.0f)
|
|
||||||
}
|
|
||||||
Surface.ROTATION_270 -> {
|
|
||||||
Matrix.translateM(textureTransform, 0, 1.0f, 0.0f, 0.0f)
|
|
||||||
Matrix.scaleM(textureTransform, 0, -1.0f / aspectRatio, 1.0f, 1.0f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect the Stream to the Texture and the Texture to the MaterialInstance.
|
|
||||||
val sampler = TextureSampler(TextureSampler.MinFilter.LINEAR, TextureSampler.MagFilter.LINEAR, TextureSampler.WrapMode.CLAMP_TO_EDGE)
|
|
||||||
filamentTexture!!.setExternalStream(filamentEngine, filamentStream!!)
|
|
||||||
filamentMaterial.setParameter("videoTexture", filamentTexture!!, sampler)
|
|
||||||
filamentMaterial.setParameter("textureTransform", MaterialInstance.FloatElement.MAT4, textureTransform, 0, 1)
|
|
||||||
|
|
||||||
// Start the capture session. You could also use TEMPLATE_PREVIEW here.
|
|
||||||
val captureRequestBuilder = cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_RECORD)
|
|
||||||
captureRequestBuilder.addTarget(imageReader.surface)
|
|
||||||
|
|
||||||
cameraDevice?.createCaptureSession(listOf(imageReader.surface),
|
|
||||||
object : CameraCaptureSession.StateCallback() {
|
|
||||||
override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
|
|
||||||
if (cameraDevice == null) return
|
|
||||||
captureSession = cameraCaptureSession
|
|
||||||
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
|
|
||||||
captureRequest = captureRequestBuilder.build()
|
|
||||||
captureSession!!.setRepeatingRequest(captureRequest, null, backgroundHandler)
|
|
||||||
Log.i(kLogTag, "Created CaptureRequest.")
|
|
||||||
}
|
|
||||||
override fun onConfigureFailed(session: CameraCaptureSession) {
|
|
||||||
Log.e(kLogTag, "onConfigureFailed")
|
|
||||||
}
|
|
||||||
}, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val kLogTag = "CameraHelper"
|
|
||||||
private const val kRequestCameraPermission = 1
|
|
||||||
private const val kImageReaderMaxImages = 7
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.android.filament.externalimg
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.graphics.SurfaceTexture
|
|
||||||
import android.hardware.camera2.*
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.HandlerThread
|
|
||||||
import android.util.Log
|
|
||||||
import android.util.Size
|
|
||||||
import android.view.Surface
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.graphics.ImageFormat
|
|
||||||
import android.hardware.HardwareBuffer
|
|
||||||
import android.media.ImageReader
|
|
||||||
import android.opengl.Matrix
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Looper
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
|
|
||||||
import com.google.android.filament.*
|
|
||||||
|
|
||||||
import java.util.concurrent.Semaphore
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toy class that handles all interaction with the Android camera2 API.
|
|
||||||
* Sets the "textureTransform" and "videoTexture" parameters on the given Filament material.
|
|
||||||
*/
|
|
||||||
class CameraHelper(val activity: Activity, private val filamentEngine: Engine, private val filamentMaterial: MaterialInstance) {
|
|
||||||
private lateinit var cameraId: String
|
|
||||||
private lateinit var captureRequest: CaptureRequest
|
|
||||||
|
|
||||||
private val cameraOpenCloseLock = Semaphore(1)
|
|
||||||
private var backgroundHandler: Handler? = null
|
|
||||||
private var backgroundThread: HandlerThread? = null
|
|
||||||
private var cameraDevice: CameraDevice? = null
|
|
||||||
private var captureSession: CameraCaptureSession? = null
|
|
||||||
private var resolution = Size(640, 480)
|
|
||||||
private var filamentTexture: Texture? = null
|
|
||||||
private var filamentStream: Stream? = null
|
|
||||||
private val imageReader = ImageReader.newInstance(
|
|
||||||
resolution.width,
|
|
||||||
resolution.height,
|
|
||||||
ImageFormat.PRIVATE,
|
|
||||||
kImageReaderMaxImages,
|
|
||||||
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
|
|
||||||
|
|
||||||
@Suppress("deprecation")
|
|
||||||
private val display = if (Build.VERSION.SDK_INT >= 30) {
|
|
||||||
Api30Impl.getDisplay(activity)
|
|
||||||
} else {
|
|
||||||
activity.windowManager.defaultDisplay!!
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(30)
|
|
||||||
class Api30Impl {
|
|
||||||
companion object {
|
|
||||||
fun getDisplay(context: Context) = context.display!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val cameraCallback = object : CameraDevice.StateCallback() {
|
|
||||||
override fun onOpened(cameraDevice: CameraDevice) {
|
|
||||||
cameraOpenCloseLock.release()
|
|
||||||
this@CameraHelper.cameraDevice = cameraDevice
|
|
||||||
createCaptureSession()
|
|
||||||
}
|
|
||||||
override fun onDisconnected(cameraDevice: CameraDevice) {
|
|
||||||
cameraOpenCloseLock.release()
|
|
||||||
cameraDevice.close()
|
|
||||||
this@CameraHelper.cameraDevice = null
|
|
||||||
}
|
|
||||||
override fun onError(cameraDevice: CameraDevice, error: Int) {
|
|
||||||
onDisconnected(cameraDevice)
|
|
||||||
this@CameraHelper.activity.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the latest image (if any) from ImageReader and passes its HardwareBuffer to Filament.
|
|
||||||
*/
|
|
||||||
fun pushExternalImageToFilament() {
|
|
||||||
val stream = filamentStream
|
|
||||||
if (stream != null) {
|
|
||||||
imageReader.acquireLatestImage()?.also {
|
|
||||||
stream.setAcquiredImage(it.hardwareBuffer, Handler(Looper.getMainLooper())) {
|
|
||||||
it.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the front-facing Android camera, requests permission, and sets up a listener that will
|
|
||||||
* start a capture session as soon as the camera is ready.
|
|
||||||
*/
|
|
||||||
fun openCamera() {
|
|
||||||
val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
|
||||||
try {
|
|
||||||
for (cameraId in manager.cameraIdList) {
|
|
||||||
val characteristics = manager.getCameraCharacteristics(cameraId)
|
|
||||||
val cameraDirection = characteristics.get(CameraCharacteristics.LENS_FACING)
|
|
||||||
if (cameraDirection != null && cameraDirection == CameraCharacteristics.LENS_FACING_FRONT) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cameraId = cameraId
|
|
||||||
Log.i(kLogTag, "Selected camera $cameraId.")
|
|
||||||
|
|
||||||
val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue
|
|
||||||
resolution = map.getOutputSizes(SurfaceTexture::class.java)[0]
|
|
||||||
Log.i(kLogTag, "Highest resolution is $resolution.")
|
|
||||||
}
|
|
||||||
} catch (e: CameraAccessException) {
|
|
||||||
Log.e(kLogTag, e.toString())
|
|
||||||
} catch (e: NullPointerException) {
|
|
||||||
Log.e(kLogTag, "Camera2 API is not supported on this device.")
|
|
||||||
}
|
|
||||||
|
|
||||||
val permission = ContextCompat.checkSelfPermission(this.activity, Manifest.permission.CAMERA)
|
|
||||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
activity.requestPermissions(arrayOf(Manifest.permission.CAMERA), kRequestCameraPermission)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
|
|
||||||
throw RuntimeException("Time out waiting to lock camera opening.")
|
|
||||||
}
|
|
||||||
manager.openCamera(cameraId, cameraCallback, backgroundHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onResume() {
|
|
||||||
backgroundThread = HandlerThread("CameraBackground").also { it.start() }
|
|
||||||
backgroundHandler = Handler(backgroundThread?.looper!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onPause() {
|
|
||||||
backgroundThread?.quitSafely()
|
|
||||||
try {
|
|
||||||
backgroundThread?.join()
|
|
||||||
backgroundThread = null
|
|
||||||
backgroundHandler = null
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
Log.e(kLogTag, e.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onRequestPermissionsResult(requestCode: Int, grantResults: IntArray): Boolean {
|
|
||||||
if (requestCode == kRequestCameraPermission) {
|
|
||||||
if (grantResults.size != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
Log.e(kLogTag, "Unable to obtain camera position.")
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createCaptureSession() {
|
|
||||||
filamentStream?.apply { filamentEngine.destroyStream(this) }
|
|
||||||
|
|
||||||
// [Re]create the Filament Stream object that gets bound to the Texture.
|
|
||||||
filamentStream = Stream.Builder().build(filamentEngine)
|
|
||||||
|
|
||||||
// Create the Filament Texture object if we haven't done so already.
|
|
||||||
if (filamentTexture == null) {
|
|
||||||
filamentTexture = Texture.Builder()
|
|
||||||
.sampler(Texture.Sampler.SAMPLER_EXTERNAL)
|
|
||||||
.format(Texture.InternalFormat.RGB8)
|
|
||||||
.build(filamentEngine)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are texturing a front-facing square shape so we need to generate a matrix that transforms (u, v, 0, 1)
|
|
||||||
// into a new UV coordinate according to the screen rotation and the aspect ratio of the camera image.
|
|
||||||
val aspectRatio = resolution.width.toFloat() / resolution.height.toFloat()
|
|
||||||
val textureTransform = FloatArray(16)
|
|
||||||
Matrix.setIdentityM(textureTransform, 0)
|
|
||||||
when (display.rotation) {
|
|
||||||
Surface.ROTATION_0 -> {
|
|
||||||
Matrix.translateM(textureTransform, 0, 1.0f, 0.0f, 0.0f)
|
|
||||||
Matrix.rotateM(textureTransform, 0, 90.0f, 0.0f, 0.0f, 1.0f)
|
|
||||||
Matrix.translateM(textureTransform, 0, 1.0f, 0.0f, 0.0f)
|
|
||||||
Matrix.scaleM(textureTransform, 0, -1.0f, 1.0f / aspectRatio, 1.0f)
|
|
||||||
}
|
|
||||||
Surface.ROTATION_90 -> {
|
|
||||||
Matrix.translateM(textureTransform, 0, 1.0f, 1.0f, 0.0f)
|
|
||||||
Matrix.rotateM(textureTransform, 0, 180.0f, 0.0f, 0.0f, 1.0f)
|
|
||||||
Matrix.translateM(textureTransform, 0, 1.0f, 0.0f, 0.0f)
|
|
||||||
Matrix.scaleM(textureTransform, 0, -1.0f / aspectRatio, 1.0f, 1.0f)
|
|
||||||
}
|
|
||||||
Surface.ROTATION_270 -> {
|
|
||||||
Matrix.translateM(textureTransform, 0, 1.0f, 0.0f, 0.0f)
|
|
||||||
Matrix.scaleM(textureTransform, 0, -1.0f / aspectRatio, 1.0f, 1.0f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect the Stream to the Texture and the Texture to the MaterialInstance.
|
|
||||||
val sampler = TextureSampler(TextureSampler.MinFilter.LINEAR, TextureSampler.MagFilter.LINEAR, TextureSampler.WrapMode.CLAMP_TO_EDGE)
|
|
||||||
filamentTexture!!.setExternalStream(filamentEngine, filamentStream!!)
|
|
||||||
filamentMaterial.setParameter("videoTexture", filamentTexture!!, sampler)
|
|
||||||
filamentMaterial.setParameter("textureTransform", MaterialInstance.FloatElement.MAT4, textureTransform, 0, 1)
|
|
||||||
|
|
||||||
// Start the capture session. You could also use TEMPLATE_PREVIEW here.
|
|
||||||
val captureRequestBuilder = cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_RECORD)
|
|
||||||
captureRequestBuilder.addTarget(imageReader.surface)
|
|
||||||
|
|
||||||
cameraDevice?.createCaptureSession(listOf(imageReader.surface),
|
|
||||||
object : CameraCaptureSession.StateCallback() {
|
|
||||||
override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
|
|
||||||
if (cameraDevice == null) return
|
|
||||||
captureSession = cameraCaptureSession
|
|
||||||
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
|
|
||||||
captureRequest = captureRequestBuilder.build()
|
|
||||||
captureSession!!.setRepeatingRequest(captureRequest, null, backgroundHandler)
|
|
||||||
Log.i(kLogTag, "Created CaptureRequest.")
|
|
||||||
}
|
|
||||||
override fun onConfigureFailed(session: CameraCaptureSession) {
|
|
||||||
Log.e(kLogTag, "onConfigureFailed")
|
|
||||||
}
|
|
||||||
}, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val kLogTag = "CameraHelper"
|
|
||||||
private const val kRequestCameraPermission = 1
|
|
||||||
private const val kImageReaderMaxImages = 7
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
import android.graphics.*
|
|
||||||
import android.hardware.HardwareBuffer
|
|
||||||
import android.media.Image
|
|
||||||
import android.media.ImageReader
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.HandlerThread
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.Surface
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import java.util.concurrent.TimeoutException
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
object CanvasToHardwareBufferUtil {
|
|
||||||
private const val TAG = "CanvasToHardwareBufferKt"
|
|
||||||
private const val IMAGE_READER_TIMEOUT_MS = 3000L // Timeout for waiting buffer
|
|
||||||
|
|
||||||
fun drawToHardwareBuffer(
|
|
||||||
width: Int,
|
|
||||||
height: Int,
|
|
||||||
): HardwareBuffer? {
|
|
||||||
if (width <= 0 || height <= 0) {
|
|
||||||
Log.e(TAG, "Invalid dimensions: width=$width, height=$height")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
var handlerThread: HandlerThread? = null
|
|
||||||
var imageReader: ImageReader? = null
|
|
||||||
var surface: Surface? = null // Keep track for logging/debugging if needed
|
|
||||||
// Use var as it's assigned within the try block after future completion
|
|
||||||
var receivedHardwareBuffer: HardwareBuffer? = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. Setup HandlerThread for ImageReader callbacks
|
|
||||||
handlerThread = HandlerThread("ImageReaderThreadKt").apply { start() }
|
|
||||||
val imageReaderHandler = Handler(handlerThread.looper)
|
|
||||||
|
|
||||||
// 2. Use CompletableFuture to wait for the buffer from the listener
|
|
||||||
val bufferFuture = CompletableFuture<HardwareBuffer>()
|
|
||||||
|
|
||||||
// 3. Create ImageReader
|
|
||||||
val usageFlags =
|
|
||||||
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or
|
|
||||||
HardwareBuffer.USAGE_GPU_COLOR_OUTPUT or
|
|
||||||
HardwareBuffer.USAGE_CPU_READ_RARELY // Adjust as needed
|
|
||||||
|
|
||||||
imageReader =
|
|
||||||
ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1, usageFlags)
|
|
||||||
|
|
||||||
// 4. Set Listener to capture the buffer
|
|
||||||
imageReader.setOnImageAvailableListener({ reader ->
|
|
||||||
var image: Image? = null
|
|
||||||
var hardwareBuffer: HardwareBuffer? = null
|
|
||||||
try {
|
|
||||||
// Use `use` block for automatic image.close()
|
|
||||||
image = reader.acquireLatestImage()
|
|
||||||
if (image == null) {
|
|
||||||
Log.w(TAG, "ImageReader listener fired but no image available.")
|
|
||||||
// Complete exceptionally if buffer wasn't already completed.
|
|
||||||
bufferFuture.completeExceptionally(
|
|
||||||
RuntimeException("ImageReader listener fired but no image available"),
|
|
||||||
)
|
|
||||||
return@setOnImageAvailableListener
|
|
||||||
}
|
|
||||||
|
|
||||||
hardwareBuffer = image.hardwareBuffer
|
|
||||||
if (hardwareBuffer != null) {
|
|
||||||
// IMPORTANT: Don't close the HardwareBuffer here!
|
|
||||||
// Transfer ownership via the CompletableFuture.
|
|
||||||
if (!bufferFuture.isDone) { // Avoid completing more than once
|
|
||||||
bufferFuture.complete(hardwareBuffer)
|
|
||||||
} else {
|
|
||||||
// Future was already completed (maybe exceptionally), close this buffer
|
|
||||||
Log.w(TAG, "Future already done, closing redundant HardwareBuffer")
|
|
||||||
hardwareBuffer.close()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Failed to get HardwareBuffer from Image.")
|
|
||||||
if (!bufferFuture.isDone) {
|
|
||||||
bufferFuture.completeExceptionally(
|
|
||||||
RuntimeException("Failed to get HardwareBuffer from Image"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Error in ImageReader listener", e)
|
|
||||||
if (!bufferFuture.isDone) {
|
|
||||||
bufferFuture.completeExceptionally(e) // Propagate error
|
|
||||||
}
|
|
||||||
// If we got the buffer but failed elsewhere, ensure it's closed
|
|
||||||
hardwareBuffer?.takeUnless { it.isClosed }?.close()
|
|
||||||
} finally {
|
|
||||||
// image?.close() // Handled by acquiring reader itself or image.use{} if used
|
|
||||||
image?.close() // Close image if not using `use` or if error before `use` finishes
|
|
||||||
}
|
|
||||||
}, imageReaderHandler)
|
|
||||||
|
|
||||||
// 5. Get the Surface to draw onto
|
|
||||||
surface =
|
|
||||||
imageReader.surface
|
|
||||||
?: throw RuntimeException("Failed to get Surface from ImageReader")
|
|
||||||
|
|
||||||
// 6. Lock Canvas and Draw
|
|
||||||
val canvas: Canvas? = surface.lockHardwareCanvas() // Use hardware accelerated canvas
|
|
||||||
if (canvas != null) {
|
|
||||||
try {
|
|
||||||
// --- Your Drawing Code Here ---
|
|
||||||
val paint =
|
|
||||||
Paint().apply {
|
|
||||||
isAntiAlias = true // Good practice
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blue background
|
|
||||||
paint.color = Color.BLUE
|
|
||||||
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
|
|
||||||
|
|
||||||
// White text
|
|
||||||
paint.color = Color.WHITE
|
|
||||||
paint.textSize = 40f
|
|
||||||
paint.textAlign = Paint.Align.CENTER
|
|
||||||
canvas.drawText(
|
|
||||||
"Hello HardwareBuffer! (Kotlin)",
|
|
||||||
width / 2f,
|
|
||||||
height / 2f,
|
|
||||||
paint,
|
|
||||||
)
|
|
||||||
// --- End Drawing Code ---
|
|
||||||
} finally {
|
|
||||||
// 7. Unlock Canvas and Post
|
|
||||||
surface.unlockCanvasAndPost(canvas)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw RuntimeException("Failed to lock Hardware Canvas")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8. Wait for the listener to provide the HardwareBuffer
|
|
||||||
try {
|
|
||||||
// Wait for the buffer; this blocks the current thread.
|
|
||||||
receivedHardwareBuffer =
|
|
||||||
bufferFuture.get(IMAGE_READER_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
|
||||||
// Ownership of receivedHardwareBuffer is now transferred to the caller
|
|
||||||
} catch (timeout: TimeoutException) {
|
|
||||||
Log.e(TAG, "Timeout waiting for HardwareBuffer from ImageReader listener")
|
|
||||||
bufferFuture.cancel(true) // Attempt to cancel listener processing
|
|
||||||
throw timeout // Re-throw
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Failed to draw to HardwareBuffer", e)
|
|
||||||
// Ensure buffer is closed if acquired but an error occurred before returning it
|
|
||||||
receivedHardwareBuffer?.takeUnless { it.isClosed }?.close()
|
|
||||||
return null // Indicate failure
|
|
||||||
} finally {
|
|
||||||
// 9. Cleanup
|
|
||||||
try {
|
|
||||||
imageReader?.close() // Also releases the Surface implicitly
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Error closing ImageReader", e)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
handlerThread?.quitSafely()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Error quitting HandlerThread", e)
|
|
||||||
}
|
|
||||||
// Note: Do NOT close receivedHardwareBuffer here if returning successfully.
|
|
||||||
// The caller is responsible for closing the returned buffer.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the buffer; caller MUST close it.
|
|
||||||
return receivedHardwareBuffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Example Usage (must be called from appropriate context/thread, like a coroutine) ---
|
|
||||||
/*
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
suspend fun exampleUsage() = withContext(Dispatchers.IO) { // Run blocking code off main thread
|
|
||||||
val myBuffer: HardwareBuffer? = CanvasToHardwareBufferUtil.drawToHardwareBuffer(640, 480)
|
|
||||||
|
|
||||||
// Use the 'use' extension function for automatic closing
|
|
||||||
myBuffer?.use { buffer ->
|
|
||||||
// ... Use the buffer (e.g., create an EGLImage, pass to Vulkan, etc.) ...
|
|
||||||
Log.d(TAG, "Successfully created HardwareBuffer: $buffer. Now using it...")
|
|
||||||
// buffer.close() // 'use' handles this automatically
|
|
||||||
|
|
||||||
} ?: run {
|
|
||||||
Log.e(TAG, "Failed to create HardwareBuffer.")
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG,"HardwareBuffer processing finished (buffer closed if obtained).")
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
import android.graphics.*
|
|
||||||
import android.hardware.HardwareBuffer
|
|
||||||
import android.media.Image
|
|
||||||
import android.media.ImageReader
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.HandlerThread
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.Surface
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import java.util.concurrent.TimeoutException
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
object CanvasToHardwareBufferUtil {
|
|
||||||
|
|
||||||
private const val TAG = "CanvasToHardwareBufferKt"
|
|
||||||
private const val IMAGE_READER_TIMEOUT_MS = 3000L // Timeout for waiting buffer
|
|
||||||
|
|
||||||
fun drawToHardwareBuffer(width: Int, height: Int): HardwareBuffer? {
|
|
||||||
if (width <= 0 || height <= 0) {
|
|
||||||
Log.e(TAG, "Invalid dimensions: width=$width, height=$height")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
var handlerThread: HandlerThread? = null
|
|
||||||
var imageReader: ImageReader? = null
|
|
||||||
var surface: Surface? = null // Keep track for logging/debugging if needed
|
|
||||||
// Use var as it's assigned within the try block after future completion
|
|
||||||
var receivedHardwareBuffer: HardwareBuffer? = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. Setup HandlerThread for ImageReader callbacks
|
|
||||||
handlerThread = HandlerThread("ImageReaderThreadKt").apply { start() }
|
|
||||||
val imageReaderHandler = Handler(handlerThread.looper)
|
|
||||||
|
|
||||||
// 2. Use CompletableFuture to wait for the buffer from the listener
|
|
||||||
val bufferFuture = CompletableFuture<HardwareBuffer>()
|
|
||||||
|
|
||||||
// 3. Create ImageReader
|
|
||||||
val usageFlags = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or
|
|
||||||
HardwareBuffer.USAGE_GPU_COLOR_OUTPUT or
|
|
||||||
HardwareBuffer.USAGE_CPU_READ_RARELY // Adjust as needed
|
|
||||||
|
|
||||||
imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1, usageFlags)
|
|
||||||
|
|
||||||
// 4. Set Listener to capture the buffer
|
|
||||||
imageReader.setOnImageAvailableListener({ reader ->
|
|
||||||
var image: Image? = null
|
|
||||||
var hardwareBuffer: HardwareBuffer? = null
|
|
||||||
try {
|
|
||||||
// Use `use` block for automatic image.close()
|
|
||||||
image = reader.acquireLatestImage()
|
|
||||||
if (image == null) {
|
|
||||||
Log.w(TAG, "ImageReader listener fired but no image available.")
|
|
||||||
// Complete exceptionally if buffer wasn't already completed.
|
|
||||||
bufferFuture.completeExceptionally(RuntimeException("ImageReader listener fired but no image available"))
|
|
||||||
return@setOnImageAvailableListener
|
|
||||||
}
|
|
||||||
|
|
||||||
hardwareBuffer = image.hardwareBuffer
|
|
||||||
if (hardwareBuffer != null) {
|
|
||||||
// IMPORTANT: Don't close the HardwareBuffer here!
|
|
||||||
// Transfer ownership via the CompletableFuture.
|
|
||||||
if (!bufferFuture.isDone) { // Avoid completing more than once
|
|
||||||
bufferFuture.complete(hardwareBuffer)
|
|
||||||
} else {
|
|
||||||
// Future was already completed (maybe exceptionally), close this buffer
|
|
||||||
Log.w(TAG, "Future already done, closing redundant HardwareBuffer")
|
|
||||||
hardwareBuffer.close()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Failed to get HardwareBuffer from Image.")
|
|
||||||
if (!bufferFuture.isDone) {
|
|
||||||
bufferFuture.completeExceptionally(RuntimeException("Failed to get HardwareBuffer from Image"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Error in ImageReader listener", e)
|
|
||||||
if (!bufferFuture.isDone) {
|
|
||||||
bufferFuture.completeExceptionally(e) // Propagate error
|
|
||||||
}
|
|
||||||
// If we got the buffer but failed elsewhere, ensure it's closed
|
|
||||||
hardwareBuffer?.takeUnless { it.isClosed }?.close()
|
|
||||||
} finally {
|
|
||||||
// image?.close() // Handled by acquiring reader itself or image.use{} if used
|
|
||||||
image?.close() // Close image if not using `use` or if error before `use` finishes
|
|
||||||
}
|
|
||||||
}, imageReaderHandler)
|
|
||||||
|
|
||||||
// 5. Get the Surface to draw onto
|
|
||||||
surface = imageReader.surface ?: throw RuntimeException("Failed to get Surface from ImageReader")
|
|
||||||
|
|
||||||
// 6. Lock Canvas and Draw
|
|
||||||
val canvas: Canvas? = surface.lockHardwareCanvas() // Use hardware accelerated canvas
|
|
||||||
if (canvas != null) {
|
|
||||||
try {
|
|
||||||
// --- Your Drawing Code Here ---
|
|
||||||
val paint = Paint().apply {
|
|
||||||
isAntiAlias = true // Good practice
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blue background
|
|
||||||
paint.color = Color.BLUE
|
|
||||||
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
|
|
||||||
|
|
||||||
// White text
|
|
||||||
paint.color = Color.WHITE
|
|
||||||
paint.textSize = 40f
|
|
||||||
paint.textAlign = Paint.Align.CENTER
|
|
||||||
canvas.drawText("Hello HardwareBuffer! (Kotlin)", width / 2f, height / 2f, paint)
|
|
||||||
// --- End Drawing Code ---
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
// 7. Unlock Canvas and Post
|
|
||||||
surface.unlockCanvasAndPost(canvas)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw RuntimeException("Failed to lock Hardware Canvas")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8. Wait for the listener to provide the HardwareBuffer
|
|
||||||
try {
|
|
||||||
// Wait for the buffer; this blocks the current thread.
|
|
||||||
receivedHardwareBuffer = bufferFuture.get(IMAGE_READER_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
|
||||||
// Ownership of receivedHardwareBuffer is now transferred to the caller
|
|
||||||
} catch(timeout: TimeoutException) {
|
|
||||||
Log.e(TAG, "Timeout waiting for HardwareBuffer from ImageReader listener")
|
|
||||||
bufferFuture.cancel(true) // Attempt to cancel listener processing
|
|
||||||
throw timeout // Re-throw
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Failed to draw to HardwareBuffer", e)
|
|
||||||
// Ensure buffer is closed if acquired but an error occurred before returning it
|
|
||||||
receivedHardwareBuffer?.takeUnless { it.isClosed }?.close()
|
|
||||||
return null // Indicate failure
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
// 9. Cleanup
|
|
||||||
try {
|
|
||||||
imageReader?.close() // Also releases the Surface implicitly
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Error closing ImageReader", e)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
handlerThread?.quitSafely()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Error quitting HandlerThread", e)
|
|
||||||
}
|
|
||||||
// Note: Do NOT close receivedHardwareBuffer here if returning successfully.
|
|
||||||
// The caller is responsible for closing the returned buffer.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the buffer; caller MUST close it.
|
|
||||||
return receivedHardwareBuffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Example Usage (must be called from appropriate context/thread, like a coroutine) ---
|
|
||||||
/*
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
suspend fun exampleUsage() = withContext(Dispatchers.IO) { // Run blocking code off main thread
|
|
||||||
val myBuffer: HardwareBuffer? = CanvasToHardwareBufferUtil.drawToHardwareBuffer(640, 480)
|
|
||||||
|
|
||||||
// Use the 'use' extension function for automatic closing
|
|
||||||
myBuffer?.use { buffer ->
|
|
||||||
// ... Use the buffer (e.g., create an EGLImage, pass to Vulkan, etc.) ...
|
|
||||||
Log.d(TAG, "Successfully created HardwareBuffer: $buffer. Now using it...")
|
|
||||||
// buffer.close() // 'use' handles this automatically
|
|
||||||
|
|
||||||
} ?: run {
|
|
||||||
Log.e(TAG, "Failed to create HardwareBuffer.")
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG,"HardwareBuffer processing finished (buffer closed if obtained).")
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@@ -1,469 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.android.filament.externalimg
|
|
||||||
|
|
||||||
import android.animation.ValueAnimator
|
|
||||||
import android.app.Activity
|
|
||||||
import android.hardware.HardwareBuffer
|
|
||||||
import android.opengl.Matrix
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.Choreographer
|
|
||||||
import android.view.Surface
|
|
||||||
import android.view.SurfaceView
|
|
||||||
import android.view.animation.LinearInterpolator
|
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
|
|
||||||
import com.google.android.filament.*
|
|
||||||
import com.google.android.filament.RenderableManager.*
|
|
||||||
import com.google.android.filament.VertexBuffer.*
|
|
||||||
import com.google.android.filament.android.DisplayHelper
|
|
||||||
import com.google.android.filament.android.FilamentHelper
|
|
||||||
import com.google.android.filament.android.UiHelper
|
|
||||||
import com.google.android.filament.utils.ExternalImage
|
|
||||||
import com.google.android.filament.utils.Utils
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
import java.nio.channels.Channels
|
|
||||||
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
|
|
||||||
class MainActivity : Activity(), ActivityCompat.OnRequestPermissionsResultCallback {
|
|
||||||
companion object {
|
|
||||||
init {
|
|
||||||
Filament.init()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var TAG = "filament.externalimg"
|
|
||||||
|
|
||||||
private lateinit var surfaceView: SurfaceView
|
|
||||||
private lateinit var uiHelper: UiHelper
|
|
||||||
private lateinit var displayHelper: DisplayHelper
|
|
||||||
private lateinit var choreographer: Choreographer
|
|
||||||
|
|
||||||
private lateinit var engine: Engine
|
|
||||||
private lateinit var renderer: Renderer
|
|
||||||
private lateinit var scene: Scene
|
|
||||||
private lateinit var view: View
|
|
||||||
|
|
||||||
// This is the Filament camera, not the phone camera. :)
|
|
||||||
private lateinit var camera: Camera
|
|
||||||
|
|
||||||
private var filamentTexture: Texture? = null
|
|
||||||
|
|
||||||
// Other Filament objects:
|
|
||||||
private lateinit var material: Material
|
|
||||||
private lateinit var materialInstance: MaterialInstance
|
|
||||||
private lateinit var vertexBuffer: VertexBuffer
|
|
||||||
private lateinit var indexBuffer: IndexBuffer
|
|
||||||
|
|
||||||
// Filament entity representing a renderable object
|
|
||||||
@Entity private var renderable = 0
|
|
||||||
@Entity private var light = 0
|
|
||||||
|
|
||||||
private var myCount : Int = 0
|
|
||||||
|
|
||||||
// A swap chain is Filament's representation of a surface
|
|
||||||
private var swapChain: SwapChain? = null
|
|
||||||
|
|
||||||
// Performs the rendering and schedules new frames
|
|
||||||
private val frameScheduler = FrameCallback()
|
|
||||||
|
|
||||||
private val animator = ValueAnimator.ofFloat(0.0f, 50.0f)
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
Utils.init()
|
|
||||||
|
|
||||||
surfaceView = SurfaceView(this)
|
|
||||||
setContentView(surfaceView)
|
|
||||||
|
|
||||||
choreographer = Choreographer.getInstance()
|
|
||||||
|
|
||||||
displayHelper = DisplayHelper(this)
|
|
||||||
|
|
||||||
setupSurfaceView()
|
|
||||||
setupFilament()
|
|
||||||
setupView()
|
|
||||||
setupScene()
|
|
||||||
|
|
||||||
// ExternalImage.setOnTexture(engine,, texture, buffer, srgb)
|
|
||||||
|
|
||||||
// cameraHelper = CameraHelper(this, engine, materialInstance)
|
|
||||||
// cameraHelper.openCamera()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupSurfaceView() {
|
|
||||||
uiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK)
|
|
||||||
uiHelper.renderCallback = SurfaceCallback()
|
|
||||||
uiHelper.attachTo(surfaceView)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupFilament() {
|
|
||||||
engine = Engine.create()
|
|
||||||
renderer = engine.createRenderer()
|
|
||||||
scene = engine.createScene()
|
|
||||||
view = engine.createView()
|
|
||||||
camera = engine.createCamera(engine.entityManager.create())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupView() {
|
|
||||||
scene.skybox = Skybox.Builder().color(0.035f, 0.035f, 0.035f, 1.0f).build(engine)
|
|
||||||
view.camera = camera
|
|
||||||
view.scene = scene
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupScene() {
|
|
||||||
loadMaterial()
|
|
||||||
setupMaterial()
|
|
||||||
createMesh()
|
|
||||||
|
|
||||||
// To create a renderable we first create a generic entity
|
|
||||||
renderable = EntityManager.get().create()
|
|
||||||
|
|
||||||
// We then create a renderable component on that entity
|
|
||||||
// A renderable is made of several primitives; in this case we declare only 1
|
|
||||||
// If we wanted each face of the cube to have a different material, we could
|
|
||||||
// declare 6 primitives (1 per face) and give each of them a different material
|
|
||||||
// instance, setup with different parameters
|
|
||||||
RenderableManager.Builder(1)
|
|
||||||
// Overall bounding box of the renderable
|
|
||||||
.boundingBox(Box(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f))
|
|
||||||
// Sets the mesh data of the first primitive, 6 faces of 6 indices each
|
|
||||||
.geometry(0, PrimitiveType.TRIANGLES, vertexBuffer, indexBuffer, 0, 6 * 6)
|
|
||||||
// Sets the material of the first primitive
|
|
||||||
.material(0, materialInstance)
|
|
||||||
.build(engine, renderable)
|
|
||||||
|
|
||||||
// Add the entity to the scene to render it
|
|
||||||
scene.addEntity(renderable)
|
|
||||||
|
|
||||||
// We now need a light, let's create a directional light
|
|
||||||
light = EntityManager.get().create()
|
|
||||||
|
|
||||||
// Create a color from a temperature (5,500K)
|
|
||||||
val (r, g, b) = Colors.cct(5_500.0f)
|
|
||||||
LightManager.Builder(LightManager.Type.DIRECTIONAL)
|
|
||||||
.color(r, g, b)
|
|
||||||
// Intensity of the sun in lux on a clear day
|
|
||||||
.intensity(110_000.0f)
|
|
||||||
// The direction is normalized on our behalf
|
|
||||||
.direction(0.0f, -0.5f, -1.0f)
|
|
||||||
.castShadows(true)
|
|
||||||
.build(engine, light)
|
|
||||||
|
|
||||||
// Add the entity to the scene to light it
|
|
||||||
scene.addEntity(light)
|
|
||||||
|
|
||||||
// Set the exposure on the camera, this exposure follows the sunny f/16 rule
|
|
||||||
// Since we've defined a light that has the same intensity as the sun, it
|
|
||||||
// guarantees a proper exposure
|
|
||||||
camera.setExposure(16.0f, 1.0f / 125.0f, 100.0f)
|
|
||||||
|
|
||||||
// Move the camera back to see the object
|
|
||||||
camera.lookAt(0.0, 0.0, 6.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
|
|
||||||
|
|
||||||
startAnimation()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadMaterial() {
|
|
||||||
readUncompressedAsset("materials/lit.filamat").let {
|
|
||||||
material = Material.Builder().payload(it, it.remaining()).build(engine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupMaterial() {
|
|
||||||
materialInstance = material.createInstance()
|
|
||||||
materialInstance.setParameter("baseColor", Colors.RgbType.SRGB, 1.0f, 0.85f, 0.57f)
|
|
||||||
materialInstance.setParameter("roughness", 0.3f)
|
|
||||||
|
|
||||||
val textureTransform = FloatArray(16)
|
|
||||||
Matrix.setIdentityM(textureTransform, 0)
|
|
||||||
|
|
||||||
filamentTexture = Texture.Builder()
|
|
||||||
.sampler(Texture.Sampler.SAMPLER_EXTERNAL)
|
|
||||||
.width(400)
|
|
||||||
.height(400)
|
|
||||||
.format(Texture.InternalFormat.RGBA8)
|
|
||||||
.build(engine)
|
|
||||||
|
|
||||||
val sampler = TextureSampler(TextureSampler.MinFilter.LINEAR, TextureSampler.MagFilter.LINEAR, TextureSampler.WrapMode.CLAMP_TO_EDGE)
|
|
||||||
materialInstance.setParameter("videoTexture", filamentTexture!!, sampler)
|
|
||||||
materialInstance.setParameter("textureTransform", MaterialInstance.FloatElement.MAT4, textureTransform, 0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createMesh() {
|
|
||||||
val floatSize = 4
|
|
||||||
val shortSize = 2
|
|
||||||
// A vertex is a position + a tangent frame:
|
|
||||||
// 3 floats for XYZ position, 4 floats for normal+tangents (quaternion)
|
|
||||||
val vertexSize = 3 * floatSize + 4 * floatSize
|
|
||||||
|
|
||||||
// Define a vertex and a function to put a vertex in a ByteBuffer
|
|
||||||
@Suppress("ArrayInDataClass")
|
|
||||||
data class Vertex(val x: Float, val y: Float, val z: Float, val tangents: FloatArray)
|
|
||||||
fun ByteBuffer.put(v: Vertex): ByteBuffer {
|
|
||||||
putFloat(v.x)
|
|
||||||
putFloat(v.y)
|
|
||||||
putFloat(v.z)
|
|
||||||
v.tangents.forEach { putFloat(it) }
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6 faces, 4 vertices per face
|
|
||||||
val vertexCount = 6 * 4
|
|
||||||
|
|
||||||
// Create tangent frames, one per face
|
|
||||||
val tfPX = FloatArray(4)
|
|
||||||
val tfNX = FloatArray(4)
|
|
||||||
val tfPY = FloatArray(4)
|
|
||||||
val tfNY = FloatArray(4)
|
|
||||||
val tfPZ = FloatArray(4)
|
|
||||||
val tfNZ = FloatArray(4)
|
|
||||||
|
|
||||||
MathUtils.packTangentFrame( 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, tfPX)
|
|
||||||
MathUtils.packTangentFrame( 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, -1.0f, 0.0f, 0.0f, tfNX)
|
|
||||||
MathUtils.packTangentFrame(-1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, tfPY)
|
|
||||||
MathUtils.packTangentFrame(-1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, tfNY)
|
|
||||||
MathUtils.packTangentFrame( 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, tfPZ)
|
|
||||||
MathUtils.packTangentFrame( 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, tfNZ)
|
|
||||||
|
|
||||||
val vertexData = ByteBuffer.allocate(vertexCount * vertexSize)
|
|
||||||
// It is important to respect the native byte order
|
|
||||||
.order(ByteOrder.nativeOrder())
|
|
||||||
// Face -Z
|
|
||||||
.put(Vertex(-1.5f, -1.5f, -1.0f, tfNZ))
|
|
||||||
.put(Vertex(-1.5f, 1.5f, -1.0f, tfNZ))
|
|
||||||
.put(Vertex( 1.5f, 1.5f, -1.0f, tfNZ))
|
|
||||||
.put(Vertex( 1.5f, -1.5f, -1.0f, tfNZ))
|
|
||||||
// Face +X
|
|
||||||
.put(Vertex( 1.5f, -1.5f, -1.0f, tfPX))
|
|
||||||
.put(Vertex( 1.5f, 1.5f, -1.0f, tfPX))
|
|
||||||
.put(Vertex( 1.0f, 1.0f, 1.0f, tfPX))
|
|
||||||
.put(Vertex( 1.0f, -1.0f, 1.0f, tfPX))
|
|
||||||
// Face +Z
|
|
||||||
.put(Vertex(-1.0f, -1.0f, 1.0f, tfPZ))
|
|
||||||
.put(Vertex( 1.0f, -1.0f, 1.0f, tfPZ))
|
|
||||||
.put(Vertex( 1.0f, 1.0f, 1.0f, tfPZ))
|
|
||||||
.put(Vertex(-1.0f, 1.0f, 1.0f, tfPZ))
|
|
||||||
// Face -X
|
|
||||||
.put(Vertex(-1.0f, -1.0f, 1.0f, tfNX))
|
|
||||||
.put(Vertex(-1.0f, 1.0f, 1.0f, tfNX))
|
|
||||||
.put(Vertex(-1.5f, 1.5f, -1.0f, tfNX))
|
|
||||||
.put(Vertex(-1.5f, -1.5f, -1.0f, tfNX))
|
|
||||||
// Face -Y
|
|
||||||
.put(Vertex(-1.0f, -1.0f, 1.0f, tfNY))
|
|
||||||
.put(Vertex(-1.5f, -1.5f, -1.0f, tfNY))
|
|
||||||
.put(Vertex( 1.5f, -1.5f, -1.0f, tfNY))
|
|
||||||
.put(Vertex( 1.0f, -1.0f, 1.0f, tfNY))
|
|
||||||
// Face +Y
|
|
||||||
.put(Vertex(-1.5f, 1.5f, -1.0f, tfPY))
|
|
||||||
.put(Vertex(-1.0f, 1.0f, 1.0f, tfPY))
|
|
||||||
.put(Vertex( 1.0f, 1.0f, 1.0f, tfPY))
|
|
||||||
.put(Vertex( 1.5f, 1.5f, -1.0f, tfPY))
|
|
||||||
// Make sure the cursor is pointing in the right place in the byte buffer
|
|
||||||
.flip()
|
|
||||||
|
|
||||||
// Declare the layout of our mesh
|
|
||||||
vertexBuffer = VertexBuffer.Builder()
|
|
||||||
.bufferCount(1)
|
|
||||||
.vertexCount(vertexCount)
|
|
||||||
// Because we interleave position and color data we must specify offset and stride
|
|
||||||
// We could use de-interleaved data by declaring two buffers and giving each
|
|
||||||
// attribute a different buffer index
|
|
||||||
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, vertexSize)
|
|
||||||
.attribute(VertexAttribute.TANGENTS, 0, AttributeType.FLOAT4, 3 * floatSize, vertexSize)
|
|
||||||
.build(engine)
|
|
||||||
|
|
||||||
// Feed the vertex data to the mesh
|
|
||||||
// We only set 1 buffer because the data is interleaved
|
|
||||||
vertexBuffer.setBufferAt(engine, 0, vertexData)
|
|
||||||
|
|
||||||
// Create the indices
|
|
||||||
val indexData = ByteBuffer.allocate(6 * 2 * 3 * shortSize)
|
|
||||||
.order(ByteOrder.nativeOrder())
|
|
||||||
repeat(6) {
|
|
||||||
val i = (it * 4).toShort()
|
|
||||||
indexData
|
|
||||||
.putShort(i).putShort((i + 1).toShort()).putShort((i + 2).toShort())
|
|
||||||
.putShort(i).putShort((i + 2).toShort()).putShort((i + 3).toShort())
|
|
||||||
}
|
|
||||||
indexData.flip()
|
|
||||||
|
|
||||||
// 6 faces, 2 triangles per face,
|
|
||||||
indexBuffer = IndexBuffer.Builder()
|
|
||||||
.indexCount(vertexCount * 2)
|
|
||||||
.bufferType(IndexBuffer.Builder.IndexType.USHORT)
|
|
||||||
.build(engine)
|
|
||||||
indexBuffer.setBuffer(engine, indexData)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startAnimation() {
|
|
||||||
// Animate the triangle
|
|
||||||
animator.interpolator = LinearInterpolator()
|
|
||||||
animator.duration = 6000
|
|
||||||
animator.repeatMode = ValueAnimator.RESTART
|
|
||||||
animator.repeatCount = ValueAnimator.INFINITE
|
|
||||||
animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
|
|
||||||
val transformMatrix = FloatArray(16)
|
|
||||||
override fun onAnimationUpdate(animator: ValueAnimator) {
|
|
||||||
val t = animator.animatedValue as Float
|
|
||||||
val radians = sin(t) * 3.0f * PI.toFloat()
|
|
||||||
Matrix.setRotateM(transformMatrix, 0, radians, 0.0f, 1.0f, 0.0f)
|
|
||||||
val tcm = engine.transformManager
|
|
||||||
tcm.setTransform(tcm.getInstance(renderable), transformMatrix)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
animator.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
choreographer.postFrameCallback(frameScheduler)
|
|
||||||
animator.start()
|
|
||||||
// cameraHelper.onResume()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
choreographer.removeFrameCallback(frameScheduler)
|
|
||||||
animator.cancel()
|
|
||||||
// cameraHelper.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
|
|
||||||
// Stop the animation and any pending frame
|
|
||||||
choreographer.removeFrameCallback(frameScheduler)
|
|
||||||
animator.cancel()
|
|
||||||
|
|
||||||
// Always detach the surface before destroying the engine
|
|
||||||
uiHelper.detach()
|
|
||||||
|
|
||||||
// Cleanup all resources
|
|
||||||
engine.destroyEntity(light)
|
|
||||||
engine.destroyEntity(renderable)
|
|
||||||
engine.destroyRenderer(renderer)
|
|
||||||
engine.destroyVertexBuffer(vertexBuffer)
|
|
||||||
engine.destroyIndexBuffer(indexBuffer)
|
|
||||||
engine.destroyMaterialInstance(materialInstance)
|
|
||||||
engine.destroyMaterial(material)
|
|
||||||
engine.destroyView(view)
|
|
||||||
engine.destroyScene(scene)
|
|
||||||
engine.destroyCameraComponent(camera.entity)
|
|
||||||
|
|
||||||
// Engine.destroyEntity() destroys Filament related resources only
|
|
||||||
// (components), not the entity itself
|
|
||||||
val entityManager = EntityManager.get()
|
|
||||||
entityManager.destroy(light)
|
|
||||||
entityManager.destroy(renderable)
|
|
||||||
entityManager.destroy(camera.entity)
|
|
||||||
|
|
||||||
// Destroying the engine will free up any resource you may have forgotten
|
|
||||||
// to destroy, but it's recommended to do the cleanup properly
|
|
||||||
engine.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class FrameCallback : Choreographer.FrameCallback {
|
|
||||||
override fun doFrame(frameTimeNanos: Long) {
|
|
||||||
// Schedule the next frame
|
|
||||||
choreographer.postFrameCallback(this)
|
|
||||||
|
|
||||||
// This check guarantees that we have a swap chain
|
|
||||||
if (uiHelper.isReadyToRender) {
|
|
||||||
|
|
||||||
if (myCount < 1) {
|
|
||||||
val mybuffer: HardwareBuffer? = CanvasToHardwareBufferUtil.drawToHardwareBuffer(400, 400)
|
|
||||||
mybuffer?.use { buffer : HardwareBuffer ->
|
|
||||||
// ... Use the buffer (e.g., create an EGLImage, pass to Vulkan, etc.) ...
|
|
||||||
Log.d(TAG, "Successfully created HardwareBuffer: $buffer. Now using it...")
|
|
||||||
// buffer.close() // 'use' handles this automatically
|
|
||||||
ExternalImage.setOnTexture(engine, filamentTexture!!, buffer, false)
|
|
||||||
|
|
||||||
} ?: run {
|
|
||||||
Log.e(TAG, "Failed to create HardwareBuffer.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
myCount ++;
|
|
||||||
|
|
||||||
// cameraHelper.pushExternalImageToFilament()
|
|
||||||
|
|
||||||
// If beginFrame() returns false you should skip the frame
|
|
||||||
// This means you are sending frames too quickly to the GPU
|
|
||||||
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
|
||||||
renderer.render(view)
|
|
||||||
renderer.endFrame()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class SurfaceCallback : UiHelper.RendererCallback {
|
|
||||||
override fun onNativeWindowChanged(surface: Surface) {
|
|
||||||
swapChain?.let { engine.destroySwapChain(it) }
|
|
||||||
swapChain = engine.createSwapChain(surface)
|
|
||||||
displayHelper.attach(renderer, surfaceView.display)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetachedFromSurface() {
|
|
||||||
displayHelper.detach()
|
|
||||||
swapChain?.let {
|
|
||||||
engine.destroySwapChain(it)
|
|
||||||
// Required to ensure we don't return before Filament is done executing the
|
|
||||||
// destroySwapChain command, otherwise Android might destroy the Surface
|
|
||||||
// too early
|
|
||||||
engine.flushAndWait()
|
|
||||||
swapChain = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResized(width: Int, height: Int) {
|
|
||||||
val aspect = width.toDouble() / height.toDouble()
|
|
||||||
camera.setProjection(45.0, aspect, 0.1, 20.0, Camera.Fov.VERTICAL)
|
|
||||||
|
|
||||||
view.viewport = Viewport(0, 0, width, height)
|
|
||||||
|
|
||||||
FilamentHelper.synchronizePendingFrames(engine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun readUncompressedAsset(@Suppress("SameParameterValue") assetName: String): ByteBuffer {
|
|
||||||
assets.openFd(assetName).use { fd ->
|
|
||||||
val input = fd.createInputStream()
|
|
||||||
val dst = ByteBuffer.allocate(fd.length.toInt())
|
|
||||||
|
|
||||||
val src = Channels.newChannel(input)
|
|
||||||
src.read(dst)
|
|
||||||
src.close()
|
|
||||||
|
|
||||||
return dst.apply { rewind() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
|
||||||
// if (!cameraHelper.onRequestPermissionsResult(requestCode, grantResults)) {
|
|
||||||
// this.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,446 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.android.filament.externalimg
|
|
||||||
|
|
||||||
import android.animation.ValueAnimator
|
|
||||||
import android.app.Activity
|
|
||||||
import android.hardware.HardwareBuffer
|
|
||||||
import android.opengl.Matrix
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.Choreographer
|
|
||||||
import android.view.Surface
|
|
||||||
import android.view.SurfaceView
|
|
||||||
import android.view.animation.LinearInterpolator
|
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
|
|
||||||
import com.google.android.filament.*
|
|
||||||
import com.google.android.filament.RenderableManager.*
|
|
||||||
import com.google.android.filament.VertexBuffer.*
|
|
||||||
import com.google.android.filament.android.DisplayHelper
|
|
||||||
import com.google.android.filament.android.FilamentHelper
|
|
||||||
import com.google.android.filament.android.UiHelper
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
import java.nio.channels.Channels
|
|
||||||
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
|
|
||||||
class MainActivity : Activity(), ActivityCompat.OnRequestPermissionsResultCallback {
|
|
||||||
companion object {
|
|
||||||
init {
|
|
||||||
Filament.init()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var TAG = "filament.externalimg"
|
|
||||||
|
|
||||||
private lateinit var surfaceView: SurfaceView
|
|
||||||
private lateinit var uiHelper: UiHelper
|
|
||||||
private lateinit var displayHelper: DisplayHelper
|
|
||||||
private lateinit var choreographer: Choreographer
|
|
||||||
|
|
||||||
private lateinit var engine: Engine
|
|
||||||
private lateinit var renderer: Renderer
|
|
||||||
private lateinit var scene: Scene
|
|
||||||
private lateinit var view: View
|
|
||||||
|
|
||||||
// This is the Filament camera, not the phone camera. :)
|
|
||||||
private lateinit var camera: Camera
|
|
||||||
|
|
||||||
// Other Filament objects:
|
|
||||||
private lateinit var material: Material
|
|
||||||
private lateinit var materialInstance: MaterialInstance
|
|
||||||
private lateinit var vertexBuffer: VertexBuffer
|
|
||||||
private lateinit var indexBuffer: IndexBuffer
|
|
||||||
|
|
||||||
// Filament entity representing a renderable object
|
|
||||||
@Entity private var renderable = 0
|
|
||||||
@Entity private var light = 0
|
|
||||||
|
|
||||||
private var myCount : Int = 0
|
|
||||||
|
|
||||||
// A swap chain is Filament's representation of a surface
|
|
||||||
private var swapChain: SwapChain? = null
|
|
||||||
|
|
||||||
// Performs the rendering and schedules new frames
|
|
||||||
private val frameScheduler = FrameCallback()
|
|
||||||
|
|
||||||
private val animator = ValueAnimator.ofFloat(0.0f, 50.0f)
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
surfaceView = SurfaceView(this)
|
|
||||||
setContentView(surfaceView)
|
|
||||||
|
|
||||||
choreographer = Choreographer.getInstance()
|
|
||||||
|
|
||||||
displayHelper = DisplayHelper(this)
|
|
||||||
|
|
||||||
setupSurfaceView()
|
|
||||||
setupFilament()
|
|
||||||
setupView()
|
|
||||||
setupScene()
|
|
||||||
|
|
||||||
// cameraHelper = CameraHelper(this, engine, materialInstance)
|
|
||||||
// cameraHelper.openCamera()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupSurfaceView() {
|
|
||||||
uiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK)
|
|
||||||
uiHelper.renderCallback = SurfaceCallback()
|
|
||||||
uiHelper.attachTo(surfaceView)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupFilament() {
|
|
||||||
engine = Engine.create()
|
|
||||||
renderer = engine.createRenderer()
|
|
||||||
scene = engine.createScene()
|
|
||||||
view = engine.createView()
|
|
||||||
camera = engine.createCamera(engine.entityManager.create())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupView() {
|
|
||||||
scene.skybox = Skybox.Builder().color(0.035f, 0.035f, 0.035f, 1.0f).build(engine)
|
|
||||||
view.camera = camera
|
|
||||||
view.scene = scene
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupScene() {
|
|
||||||
loadMaterial()
|
|
||||||
setupMaterial()
|
|
||||||
createMesh()
|
|
||||||
|
|
||||||
// To create a renderable we first create a generic entity
|
|
||||||
renderable = EntityManager.get().create()
|
|
||||||
|
|
||||||
// We then create a renderable component on that entity
|
|
||||||
// A renderable is made of several primitives; in this case we declare only 1
|
|
||||||
// If we wanted each face of the cube to have a different material, we could
|
|
||||||
// declare 6 primitives (1 per face) and give each of them a different material
|
|
||||||
// instance, setup with different parameters
|
|
||||||
RenderableManager.Builder(1)
|
|
||||||
// Overall bounding box of the renderable
|
|
||||||
.boundingBox(Box(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f))
|
|
||||||
// Sets the mesh data of the first primitive, 6 faces of 6 indices each
|
|
||||||
.geometry(0, PrimitiveType.TRIANGLES, vertexBuffer, indexBuffer, 0, 6 * 6)
|
|
||||||
// Sets the material of the first primitive
|
|
||||||
.material(0, materialInstance)
|
|
||||||
.build(engine, renderable)
|
|
||||||
|
|
||||||
// Add the entity to the scene to render it
|
|
||||||
scene.addEntity(renderable)
|
|
||||||
|
|
||||||
// We now need a light, let's create a directional light
|
|
||||||
light = EntityManager.get().create()
|
|
||||||
|
|
||||||
// Create a color from a temperature (5,500K)
|
|
||||||
val (r, g, b) = Colors.cct(5_500.0f)
|
|
||||||
LightManager.Builder(LightManager.Type.DIRECTIONAL)
|
|
||||||
.color(r, g, b)
|
|
||||||
// Intensity of the sun in lux on a clear day
|
|
||||||
.intensity(110_000.0f)
|
|
||||||
// The direction is normalized on our behalf
|
|
||||||
.direction(0.0f, -0.5f, -1.0f)
|
|
||||||
.castShadows(true)
|
|
||||||
.build(engine, light)
|
|
||||||
|
|
||||||
// Add the entity to the scene to light it
|
|
||||||
scene.addEntity(light)
|
|
||||||
|
|
||||||
// Set the exposure on the camera, this exposure follows the sunny f/16 rule
|
|
||||||
// Since we've defined a light that has the same intensity as the sun, it
|
|
||||||
// guarantees a proper exposure
|
|
||||||
camera.setExposure(16.0f, 1.0f / 125.0f, 100.0f)
|
|
||||||
|
|
||||||
// Move the camera back to see the object
|
|
||||||
camera.lookAt(0.0, 0.0, 6.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
|
|
||||||
|
|
||||||
startAnimation()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadMaterial() {
|
|
||||||
readUncompressedAsset("materials/lit.filamat").let {
|
|
||||||
material = Material.Builder().payload(it, it.remaining()).build(engine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupMaterial() {
|
|
||||||
materialInstance = material.createInstance()
|
|
||||||
materialInstance.setParameter("baseColor", Colors.RgbType.SRGB, 1.0f, 0.85f, 0.57f)
|
|
||||||
materialInstance.setParameter("roughness", 0.3f)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createMesh() {
|
|
||||||
val floatSize = 4
|
|
||||||
val shortSize = 2
|
|
||||||
// A vertex is a position + a tangent frame:
|
|
||||||
// 3 floats for XYZ position, 4 floats for normal+tangents (quaternion)
|
|
||||||
val vertexSize = 3 * floatSize + 4 * floatSize
|
|
||||||
|
|
||||||
// Define a vertex and a function to put a vertex in a ByteBuffer
|
|
||||||
@Suppress("ArrayInDataClass")
|
|
||||||
data class Vertex(val x: Float, val y: Float, val z: Float, val tangents: FloatArray)
|
|
||||||
fun ByteBuffer.put(v: Vertex): ByteBuffer {
|
|
||||||
putFloat(v.x)
|
|
||||||
putFloat(v.y)
|
|
||||||
putFloat(v.z)
|
|
||||||
v.tangents.forEach { putFloat(it) }
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6 faces, 4 vertices per face
|
|
||||||
val vertexCount = 6 * 4
|
|
||||||
|
|
||||||
// Create tangent frames, one per face
|
|
||||||
val tfPX = FloatArray(4)
|
|
||||||
val tfNX = FloatArray(4)
|
|
||||||
val tfPY = FloatArray(4)
|
|
||||||
val tfNY = FloatArray(4)
|
|
||||||
val tfPZ = FloatArray(4)
|
|
||||||
val tfNZ = FloatArray(4)
|
|
||||||
|
|
||||||
MathUtils.packTangentFrame( 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, tfPX)
|
|
||||||
MathUtils.packTangentFrame( 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, -1.0f, 0.0f, 0.0f, tfNX)
|
|
||||||
MathUtils.packTangentFrame(-1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, tfPY)
|
|
||||||
MathUtils.packTangentFrame(-1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, tfNY)
|
|
||||||
MathUtils.packTangentFrame( 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, tfPZ)
|
|
||||||
MathUtils.packTangentFrame( 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, tfNZ)
|
|
||||||
|
|
||||||
val vertexData = ByteBuffer.allocate(vertexCount * vertexSize)
|
|
||||||
// It is important to respect the native byte order
|
|
||||||
.order(ByteOrder.nativeOrder())
|
|
||||||
// Face -Z
|
|
||||||
.put(Vertex(-1.5f, -1.5f, -1.0f, tfNZ))
|
|
||||||
.put(Vertex(-1.5f, 1.5f, -1.0f, tfNZ))
|
|
||||||
.put(Vertex( 1.5f, 1.5f, -1.0f, tfNZ))
|
|
||||||
.put(Vertex( 1.5f, -1.5f, -1.0f, tfNZ))
|
|
||||||
// Face +X
|
|
||||||
.put(Vertex( 1.5f, -1.5f, -1.0f, tfPX))
|
|
||||||
.put(Vertex( 1.5f, 1.5f, -1.0f, tfPX))
|
|
||||||
.put(Vertex( 1.0f, 1.0f, 1.0f, tfPX))
|
|
||||||
.put(Vertex( 1.0f, -1.0f, 1.0f, tfPX))
|
|
||||||
// Face +Z
|
|
||||||
.put(Vertex(-1.0f, -1.0f, 1.0f, tfPZ))
|
|
||||||
.put(Vertex( 1.0f, -1.0f, 1.0f, tfPZ))
|
|
||||||
.put(Vertex( 1.0f, 1.0f, 1.0f, tfPZ))
|
|
||||||
.put(Vertex(-1.0f, 1.0f, 1.0f, tfPZ))
|
|
||||||
// Face -X
|
|
||||||
.put(Vertex(-1.0f, -1.0f, 1.0f, tfNX))
|
|
||||||
.put(Vertex(-1.0f, 1.0f, 1.0f, tfNX))
|
|
||||||
.put(Vertex(-1.5f, 1.5f, -1.0f, tfNX))
|
|
||||||
.put(Vertex(-1.5f, -1.5f, -1.0f, tfNX))
|
|
||||||
// Face -Y
|
|
||||||
.put(Vertex(-1.0f, -1.0f, 1.0f, tfNY))
|
|
||||||
.put(Vertex(-1.5f, -1.5f, -1.0f, tfNY))
|
|
||||||
.put(Vertex( 1.5f, -1.5f, -1.0f, tfNY))
|
|
||||||
.put(Vertex( 1.0f, -1.0f, 1.0f, tfNY))
|
|
||||||
// Face +Y
|
|
||||||
.put(Vertex(-1.5f, 1.5f, -1.0f, tfPY))
|
|
||||||
.put(Vertex(-1.0f, 1.0f, 1.0f, tfPY))
|
|
||||||
.put(Vertex( 1.0f, 1.0f, 1.0f, tfPY))
|
|
||||||
.put(Vertex( 1.5f, 1.5f, -1.0f, tfPY))
|
|
||||||
// Make sure the cursor is pointing in the right place in the byte buffer
|
|
||||||
.flip()
|
|
||||||
|
|
||||||
// Declare the layout of our mesh
|
|
||||||
vertexBuffer = VertexBuffer.Builder()
|
|
||||||
.bufferCount(1)
|
|
||||||
.vertexCount(vertexCount)
|
|
||||||
// Because we interleave position and color data we must specify offset and stride
|
|
||||||
// We could use de-interleaved data by declaring two buffers and giving each
|
|
||||||
// attribute a different buffer index
|
|
||||||
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, vertexSize)
|
|
||||||
.attribute(VertexAttribute.TANGENTS, 0, AttributeType.FLOAT4, 3 * floatSize, vertexSize)
|
|
||||||
.build(engine)
|
|
||||||
|
|
||||||
// Feed the vertex data to the mesh
|
|
||||||
// We only set 1 buffer because the data is interleaved
|
|
||||||
vertexBuffer.setBufferAt(engine, 0, vertexData)
|
|
||||||
|
|
||||||
// Create the indices
|
|
||||||
val indexData = ByteBuffer.allocate(6 * 2 * 3 * shortSize)
|
|
||||||
.order(ByteOrder.nativeOrder())
|
|
||||||
repeat(6) {
|
|
||||||
val i = (it * 4).toShort()
|
|
||||||
indexData
|
|
||||||
.putShort(i).putShort((i + 1).toShort()).putShort((i + 2).toShort())
|
|
||||||
.putShort(i).putShort((i + 2).toShort()).putShort((i + 3).toShort())
|
|
||||||
}
|
|
||||||
indexData.flip()
|
|
||||||
|
|
||||||
// 6 faces, 2 triangles per face,
|
|
||||||
indexBuffer = IndexBuffer.Builder()
|
|
||||||
.indexCount(vertexCount * 2)
|
|
||||||
.bufferType(IndexBuffer.Builder.IndexType.USHORT)
|
|
||||||
.build(engine)
|
|
||||||
indexBuffer.setBuffer(engine, indexData)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startAnimation() {
|
|
||||||
// Animate the triangle
|
|
||||||
animator.interpolator = LinearInterpolator()
|
|
||||||
animator.duration = 6000
|
|
||||||
animator.repeatMode = ValueAnimator.RESTART
|
|
||||||
animator.repeatCount = ValueAnimator.INFINITE
|
|
||||||
animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
|
|
||||||
val transformMatrix = FloatArray(16)
|
|
||||||
override fun onAnimationUpdate(animator: ValueAnimator) {
|
|
||||||
val t = animator.animatedValue as Float
|
|
||||||
val radians = sin(t) * 3.0f * PI.toFloat()
|
|
||||||
Matrix.setRotateM(transformMatrix, 0, radians, 0.0f, 1.0f, 0.0f)
|
|
||||||
val tcm = engine.transformManager
|
|
||||||
tcm.setTransform(tcm.getInstance(renderable), transformMatrix)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
animator.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
choreographer.postFrameCallback(frameScheduler)
|
|
||||||
animator.start()
|
|
||||||
// cameraHelper.onResume()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
choreographer.removeFrameCallback(frameScheduler)
|
|
||||||
animator.cancel()
|
|
||||||
// cameraHelper.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
|
|
||||||
// Stop the animation and any pending frame
|
|
||||||
choreographer.removeFrameCallback(frameScheduler)
|
|
||||||
animator.cancel()
|
|
||||||
|
|
||||||
// Always detach the surface before destroying the engine
|
|
||||||
uiHelper.detach()
|
|
||||||
|
|
||||||
// Cleanup all resources
|
|
||||||
engine.destroyEntity(light)
|
|
||||||
engine.destroyEntity(renderable)
|
|
||||||
engine.destroyRenderer(renderer)
|
|
||||||
engine.destroyVertexBuffer(vertexBuffer)
|
|
||||||
engine.destroyIndexBuffer(indexBuffer)
|
|
||||||
engine.destroyMaterialInstance(materialInstance)
|
|
||||||
engine.destroyMaterial(material)
|
|
||||||
engine.destroyView(view)
|
|
||||||
engine.destroyScene(scene)
|
|
||||||
engine.destroyCameraComponent(camera.entity)
|
|
||||||
|
|
||||||
// Engine.destroyEntity() destroys Filament related resources only
|
|
||||||
// (components), not the entity itself
|
|
||||||
val entityManager = EntityManager.get()
|
|
||||||
entityManager.destroy(light)
|
|
||||||
entityManager.destroy(renderable)
|
|
||||||
entityManager.destroy(camera.entity)
|
|
||||||
|
|
||||||
// Destroying the engine will free up any resource you may have forgotten
|
|
||||||
// to destroy, but it's recommended to do the cleanup properly
|
|
||||||
engine.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class FrameCallback : Choreographer.FrameCallback {
|
|
||||||
override fun doFrame(frameTimeNanos: Long) {
|
|
||||||
// Schedule the next frame
|
|
||||||
choreographer.postFrameCallback(this)
|
|
||||||
|
|
||||||
// This check guarantees that we have a swap chain
|
|
||||||
if (uiHelper.isReadyToRender) {
|
|
||||||
|
|
||||||
if (myCount < 1) {
|
|
||||||
val mybuffer: HardwareBuffer? = CanvasToHardwareBufferUtil.drawToHardwareBuffer(400, 400)
|
|
||||||
mybuffer?.use { buffer : HardwareBuffer ->
|
|
||||||
// ... Use the buffer (e.g., create an EGLImage, pass to Vulkan, etc.) ...
|
|
||||||
Log.d(TAG, "Successfully created HardwareBuffer: $buffer. Now using it...")
|
|
||||||
// buffer.close() // 'use' handles this automatically
|
|
||||||
|
|
||||||
} ?: run {
|
|
||||||
Log.e(TAG, "Failed to create HardwareBuffer.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
myCount ++;
|
|
||||||
|
|
||||||
// cameraHelper.pushExternalImageToFilament()
|
|
||||||
|
|
||||||
// If beginFrame() returns false you should skip the frame
|
|
||||||
// This means you are sending frames too quickly to the GPU
|
|
||||||
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
|
||||||
renderer.render(view)
|
|
||||||
renderer.endFrame()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class SurfaceCallback : UiHelper.RendererCallback {
|
|
||||||
override fun onNativeWindowChanged(surface: Surface) {
|
|
||||||
swapChain?.let { engine.destroySwapChain(it) }
|
|
||||||
swapChain = engine.createSwapChain(surface)
|
|
||||||
displayHelper.attach(renderer, surfaceView.display)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetachedFromSurface() {
|
|
||||||
displayHelper.detach()
|
|
||||||
swapChain?.let {
|
|
||||||
engine.destroySwapChain(it)
|
|
||||||
// Required to ensure we don't return before Filament is done executing the
|
|
||||||
// destroySwapChain command, otherwise Android might destroy the Surface
|
|
||||||
// too early
|
|
||||||
engine.flushAndWait()
|
|
||||||
swapChain = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResized(width: Int, height: Int) {
|
|
||||||
val aspect = width.toDouble() / height.toDouble()
|
|
||||||
camera.setProjection(45.0, aspect, 0.1, 20.0, Camera.Fov.VERTICAL)
|
|
||||||
|
|
||||||
view.viewport = Viewport(0, 0, width, height)
|
|
||||||
|
|
||||||
FilamentHelper.synchronizePendingFrames(engine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun readUncompressedAsset(@Suppress("SameParameterValue") assetName: String): ByteBuffer {
|
|
||||||
assets.openFd(assetName).use { fd ->
|
|
||||||
val input = fd.createInputStream()
|
|
||||||
val dst = ByteBuffer.allocate(fd.length.toInt())
|
|
||||||
|
|
||||||
val src = Channels.newChannel(input)
|
|
||||||
src.read(dst)
|
|
||||||
src.close()
|
|
||||||
|
|
||||||
return dst.apply { rewind() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
|
||||||
// if (!cameraHelper.onRequestPermissionsResult(requestCode, grantResults)) {
|
|
||||||
// this.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
// Simple lit material that defines 3 parameters:
|
|
||||||
// - baseColor
|
|
||||||
// - roughness
|
|
||||||
// - metallic
|
|
||||||
//
|
|
||||||
// These parameters can be used by the application to change the appearance of the material.
|
|
||||||
//
|
|
||||||
// This source material must be compiled to a binary material using the matc tool.
|
|
||||||
// The command used to compile this material is:
|
|
||||||
// matc -p mobile -a opengl -o app/src/main/assets/lit.filamat app/src/materials/lit.mat
|
|
||||||
//
|
|
||||||
// See build.gradle for an example of how to compile materials automatically
|
|
||||||
// Please refer to the documentation for more information about matc and the materials system.
|
|
||||||
|
|
||||||
material {
|
|
||||||
name : lit,
|
|
||||||
|
|
||||||
// Dynamic lighting is enabled on this material
|
|
||||||
shadingModel : lit,
|
|
||||||
|
|
||||||
// We don't need to declare a "requires" array, lit materials
|
|
||||||
// always requires the "tangents" vertex attribute (the normal
|
|
||||||
// is required for lighting, tangent/bitangent for normal mapping
|
|
||||||
// and anisotropy)
|
|
||||||
|
|
||||||
// Custom vertex shader outputs
|
|
||||||
variables : [
|
|
||||||
uv
|
|
||||||
],
|
|
||||||
|
|
||||||
// List of parameters exposed by this material
|
|
||||||
parameters : [
|
|
||||||
// The color must be passed in linear space, not sRGB
|
|
||||||
{
|
|
||||||
type : float3,
|
|
||||||
name : baseColor
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type : float,
|
|
||||||
name : roughness
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type : samplerExternal,
|
|
||||||
name : videoTexture
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type : mat4,
|
|
||||||
name : textureTransform
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
vertex {
|
|
||||||
void materialVertex(inout MaterialVertexInputs material) {
|
|
||||||
material.uv = 0.5 * (getPosition() + vec4(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment {
|
|
||||||
void material(inout MaterialInputs material) {
|
|
||||||
prepareMaterial(material);
|
|
||||||
material.roughness = materialParams.roughness;
|
|
||||||
material.metallic = 0.0;
|
|
||||||
|
|
||||||
// Apply the video stream to the +Z face on the cube.
|
|
||||||
if (variable_uv.z >= 1.0) {
|
|
||||||
vec2 uv = (materialParams.textureTransform * vec4(variable_uv.xy, 0, 1)).xy;
|
|
||||||
material.baseColor.rgb = inverseTonemapSRGB(texture(materialParams_videoTexture, uv).rgb);
|
|
||||||
} else {
|
|
||||||
material.baseColor.rgb = materialParams.baseColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportHeight="108"
|
|
||||||
android:viewportWidth="108">
|
|
||||||
<path
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
|
||||||
android:strokeColor="#00000000"
|
|
||||||
android:strokeWidth="1">
|
|
||||||
<aapt:attr name="android:fillColor">
|
|
||||||
<gradient
|
|
||||||
android:endX="78.5885"
|
|
||||||
android:endY="90.9159"
|
|
||||||
android:startX="48.7653"
|
|
||||||
android:startY="61.0927"
|
|
||||||
android:type="linear">
|
|
||||||
<item
|
|
||||||
android:color="#44000000"
|
|
||||||
android:offset="0.0"/>
|
|
||||||
<item
|
|
||||||
android:color="#00000000"
|
|
||||||
android:offset="1.0"/>
|
|
||||||
</gradient>
|
|
||||||
</aapt:attr>
|
|
||||||
</path>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFF"
|
|
||||||
android:fillType="nonZero"
|
|
||||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
|
||||||
android:strokeColor="#00000000"
|
|
||||||
android:strokeWidth="1"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportHeight="108"
|
|
||||||
android:viewportWidth="108">
|
|
||||||
<path
|
|
||||||
android:fillColor="#26A69A"
|
|
||||||
android:pathData="M0,0h108v108h-108z"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M9,0L9,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,0L19,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,0L29,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,0L39,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,0L49,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,0L59,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,0L69,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,0L79,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M89,0L89,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M99,0L99,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,9L108,9"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,19L108,19"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,29L108,29"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,39L108,39"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,49L108,49"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,59L108,59"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,69L108,69"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,79L108,79"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,89L108,89"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,99L108,99"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,29L89,29"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,39L89,39"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,49L89,49"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,59L89,59"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,69L89,69"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,79L89,79"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,19L29,89"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,19L39,89"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,19L49,89"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,19L59,89"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,19L69,89"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,19L79,89"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
|
||||||
</adaptive-icon>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
|
||||||
</adaptive-icon>
|
|
||||||
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="colorPrimary">#3F51B5</color>
|
|
||||||
<color name="colorPrimaryDark">#303F9F</color>
|
|
||||||
<color name="colorAccent">#FF4081</color>
|
|
||||||
</resources>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<string name="app_name">External Image</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<string name="app_name">Hello Camera</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<resources>
|
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
|
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
@@ -18,6 +18,5 @@ include ':samples:sample-stream-test'
|
|||||||
include ':samples:sample-texture-view'
|
include ':samples:sample-texture-view'
|
||||||
include ':samples:sample-textured-object'
|
include ':samples:sample-textured-object'
|
||||||
include ':samples:sample-transparent-view'
|
include ':samples:sample-transparent-view'
|
||||||
include ':samples:sample-external-image'
|
|
||||||
|
|
||||||
rootProject.name = 'filament'
|
rootProject.name = 'filament'
|
||||||
|
|||||||
@@ -200,8 +200,9 @@ WEBGPU_ANDROID_GRADLE_OPTION=""
|
|||||||
EGL_ON_LINUX_OPTION="-DFILAMENT_SUPPORTS_EGL_ON_LINUX=OFF"
|
EGL_ON_LINUX_OPTION="-DFILAMENT_SUPPORTS_EGL_ON_LINUX=OFF"
|
||||||
|
|
||||||
MATDBG_OPTION="-DFILAMENT_ENABLE_MATDBG=OFF"
|
MATDBG_OPTION="-DFILAMENT_ENABLE_MATDBG=OFF"
|
||||||
FGVIEWER_OPTION="-DFILAMENT_ENABLE_FGVIEWER=OFF"
|
|
||||||
MATDBG_GRADLE_OPTION=""
|
MATDBG_GRADLE_OPTION=""
|
||||||
|
FGVIEWER_OPTION="-DFILAMENT_ENABLE_FGVIEWER=OFF"
|
||||||
|
FGVIEWER_GRADLE_OPTION=""
|
||||||
|
|
||||||
MATOPT_OPTION=""
|
MATOPT_OPTION=""
|
||||||
MATOPT_GRADLE_OPTION=""
|
MATOPT_GRADLE_OPTION=""
|
||||||
@@ -533,6 +534,7 @@ function build_android {
|
|||||||
${VULKAN_ANDROID_GRADLE_OPTION} \
|
${VULKAN_ANDROID_GRADLE_OPTION} \
|
||||||
${WEBGPU_ANDROID_GRADLE_OPTION} \
|
${WEBGPU_ANDROID_GRADLE_OPTION} \
|
||||||
${MATDBG_GRADLE_OPTION} \
|
${MATDBG_GRADLE_OPTION} \
|
||||||
|
${FGVIEWER_GRADLE_OPTION} \
|
||||||
${MATOPT_GRADLE_OPTION} \
|
${MATOPT_GRADLE_OPTION} \
|
||||||
:filament-android:assembleDebug \
|
:filament-android:assembleDebug \
|
||||||
:gltfio-android:assembleDebug \
|
:gltfio-android:assembleDebug \
|
||||||
@@ -584,6 +586,7 @@ function build_android {
|
|||||||
${VULKAN_ANDROID_GRADLE_OPTION} \
|
${VULKAN_ANDROID_GRADLE_OPTION} \
|
||||||
${WEBGPU_ANDROID_GRADLE_OPTION} \
|
${WEBGPU_ANDROID_GRADLE_OPTION} \
|
||||||
${MATDBG_GRADLE_OPTION} \
|
${MATDBG_GRADLE_OPTION} \
|
||||||
|
${FGVIEWER_GRADLE_OPTION} \
|
||||||
${MATOPT_GRADLE_OPTION} \
|
${MATOPT_GRADLE_OPTION} \
|
||||||
:filament-android:assembleRelease \
|
:filament-android:assembleRelease \
|
||||||
:gltfio-android:assembleRelease \
|
:gltfio-android:assembleRelease \
|
||||||
|
|||||||
@@ -177,29 +177,6 @@ endif()
|
|||||||
if (FILAMENT_SUPPORTS_VULKAN)
|
if (FILAMENT_SUPPORTS_VULKAN)
|
||||||
list(APPEND SRCS
|
list(APPEND SRCS
|
||||||
include/backend/platforms/VulkanPlatform.h
|
include/backend/platforms/VulkanPlatform.h
|
||||||
src/vulkan/VulkanDescriptorSetCache.cpp
|
|
||||||
src/vulkan/VulkanDescriptorSetCache.h
|
|
||||||
src/vulkan/VulkanDescriptorSetLayoutCache.cpp
|
|
||||||
src/vulkan/VulkanDescriptorSetLayoutCache.h
|
|
||||||
src/vulkan/VulkanPipelineLayoutCache.cpp
|
|
||||||
src/vulkan/VulkanPipelineLayoutCache.h
|
|
||||||
src/vulkan/memory/ResourceManager.cpp
|
|
||||||
src/vulkan/memory/ResourceManager.h
|
|
||||||
src/vulkan/memory/ResourcePointer.h
|
|
||||||
src/vulkan/memory/Resource.cpp
|
|
||||||
src/vulkan/memory/Resource.h
|
|
||||||
src/vulkan/platform/VulkanPlatform.cpp
|
|
||||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
|
|
||||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
|
|
||||||
src/vulkan/utils/Conversion.cpp
|
|
||||||
src/vulkan/utils/Conversion.h
|
|
||||||
src/vulkan/utils/Definitions.h
|
|
||||||
src/vulkan/utils/Helper.h
|
|
||||||
src/vulkan/utils/Image.h
|
|
||||||
src/vulkan/utils/Image.cpp
|
|
||||||
src/vulkan/utils/Spirv.h
|
|
||||||
src/vulkan/utils/Spirv.cpp
|
|
||||||
src/vulkan/utils/StaticVector.h
|
|
||||||
src/vulkan/VulkanAsyncHandles.h
|
src/vulkan/VulkanAsyncHandles.h
|
||||||
src/vulkan/VulkanBlitter.cpp
|
src/vulkan/VulkanBlitter.cpp
|
||||||
src/vulkan/VulkanBlitter.h
|
src/vulkan/VulkanBlitter.h
|
||||||
@@ -210,29 +187,56 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
|||||||
src/vulkan/VulkanConstants.h
|
src/vulkan/VulkanConstants.h
|
||||||
src/vulkan/VulkanContext.cpp
|
src/vulkan/VulkanContext.cpp
|
||||||
src/vulkan/VulkanContext.h
|
src/vulkan/VulkanContext.h
|
||||||
|
src/vulkan/VulkanDescriptorSetCache.cpp
|
||||||
|
src/vulkan/VulkanDescriptorSetCache.h
|
||||||
|
src/vulkan/VulkanDescriptorSetLayoutCache.cpp
|
||||||
|
src/vulkan/VulkanDescriptorSetLayoutCache.h
|
||||||
src/vulkan/VulkanDriver.cpp
|
src/vulkan/VulkanDriver.cpp
|
||||||
src/vulkan/VulkanDriver.h
|
src/vulkan/VulkanDriver.h
|
||||||
src/vulkan/VulkanDriverFactory.h
|
src/vulkan/VulkanDriverFactory.h
|
||||||
|
src/vulkan/VulkanExternalImageManager.cpp
|
||||||
|
src/vulkan/VulkanExternalImageManager.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
|
||||||
src/vulkan/VulkanHandles.h
|
src/vulkan/VulkanHandles.h
|
||||||
src/vulkan/VulkanMemory.h
|
|
||||||
src/vulkan/VulkanMemory.cpp
|
src/vulkan/VulkanMemory.cpp
|
||||||
|
src/vulkan/VulkanMemory.h
|
||||||
src/vulkan/VulkanPipelineCache.cpp
|
src/vulkan/VulkanPipelineCache.cpp
|
||||||
src/vulkan/VulkanPipelineCache.h
|
src/vulkan/VulkanPipelineCache.h
|
||||||
|
src/vulkan/VulkanPipelineLayoutCache.cpp
|
||||||
|
src/vulkan/VulkanPipelineLayoutCache.h
|
||||||
src/vulkan/VulkanQueryManager.cpp
|
src/vulkan/VulkanQueryManager.cpp
|
||||||
src/vulkan/VulkanQueryManager.h
|
src/vulkan/VulkanQueryManager.h
|
||||||
|
src/vulkan/VulkanReadPixels.cpp
|
||||||
|
src/vulkan/VulkanReadPixels.h
|
||||||
src/vulkan/VulkanSamplerCache.cpp
|
src/vulkan/VulkanSamplerCache.cpp
|
||||||
src/vulkan/VulkanSamplerCache.h
|
src/vulkan/VulkanSamplerCache.h
|
||||||
src/vulkan/VulkanStagePool.cpp
|
src/vulkan/VulkanStagePool.cpp
|
||||||
src/vulkan/VulkanStagePool.h
|
src/vulkan/VulkanStagePool.h
|
||||||
src/vulkan/VulkanSwapChain.cpp
|
src/vulkan/VulkanSwapChain.cpp
|
||||||
src/vulkan/VulkanSwapChain.h
|
src/vulkan/VulkanSwapChain.h
|
||||||
src/vulkan/VulkanReadPixels.cpp
|
|
||||||
src/vulkan/VulkanReadPixels.h
|
|
||||||
src/vulkan/VulkanTexture.cpp
|
src/vulkan/VulkanTexture.cpp
|
||||||
src/vulkan/VulkanTexture.h
|
src/vulkan/VulkanTexture.h
|
||||||
|
src/vulkan/VulkanYcbcrConversionCache.cpp
|
||||||
|
src/vulkan/VulkanYcbcrConversionCache.h
|
||||||
|
src/vulkan/memory/Resource.cpp
|
||||||
|
src/vulkan/memory/Resource.h
|
||||||
|
src/vulkan/memory/ResourceManager.cpp
|
||||||
|
src/vulkan/memory/ResourceManager.h
|
||||||
|
src/vulkan/memory/ResourcePointer.h
|
||||||
|
src/vulkan/platform/VulkanPlatform.cpp
|
||||||
|
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
|
||||||
|
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
|
||||||
|
src/vulkan/utils/Conversion.cpp
|
||||||
|
src/vulkan/utils/Conversion.h
|
||||||
|
src/vulkan/utils/Definitions.h
|
||||||
|
src/vulkan/utils/Helper.h
|
||||||
|
src/vulkan/utils/Image.cpp
|
||||||
|
src/vulkan/utils/Image.h
|
||||||
|
src/vulkan/utils/Spirv.cpp
|
||||||
|
src/vulkan/utils/Spirv.h
|
||||||
|
src/vulkan/utils/StaticVector.h
|
||||||
)
|
)
|
||||||
if (LINUX OR WIN32)
|
if (LINUX OR WIN32)
|
||||||
list(APPEND SRCS src/vulkan/platform/VulkanPlatformLinuxWindows.cpp)
|
list(APPEND SRCS src/vulkan/platform/VulkanPlatformLinuxWindows.cpp)
|
||||||
@@ -494,7 +498,9 @@ if (APPLE OR LINUX)
|
|||||||
test/ShaderGenerator.cpp
|
test/ShaderGenerator.cpp
|
||||||
test/TrianglePrimitive.cpp
|
test/TrianglePrimitive.cpp
|
||||||
test/Arguments.cpp
|
test/Arguments.cpp
|
||||||
|
test/ImageExpectations.cpp
|
||||||
test/Lifetimes.cpp
|
test/Lifetimes.cpp
|
||||||
|
test/Shader.cpp
|
||||||
test/test_FeedbackLoops.cpp
|
test/test_FeedbackLoops.cpp
|
||||||
test/test_Blit.cpp
|
test/test_Blit.cpp
|
||||||
test/test_MissingRequiredAttributes.cpp
|
test/test_MissingRequiredAttributes.cpp
|
||||||
@@ -510,6 +516,7 @@ if (APPLE OR LINUX)
|
|||||||
test/test_Handles.cpp
|
test/test_Handles.cpp
|
||||||
)
|
)
|
||||||
set(BACKEND_TEST_LIBS
|
set(BACKEND_TEST_LIBS
|
||||||
|
absl::str_format
|
||||||
backend
|
backend
|
||||||
getopt
|
getopt
|
||||||
gtest
|
gtest
|
||||||
@@ -525,7 +532,7 @@ if (APPLE AND NOT IOS)
|
|||||||
list(APPEND BACKEND_TEST_SRC
|
list(APPEND BACKEND_TEST_SRC
|
||||||
test/test_RenderExternalImage.cpp)
|
test/test_RenderExternalImage.cpp)
|
||||||
add_library(backend_test STATIC ${BACKEND_TEST_SRC})
|
add_library(backend_test STATIC ${BACKEND_TEST_SRC})
|
||||||
target_link_libraries(backend_test PRIVATE ${BACKEND_TEST_LIBS})
|
target_link_libraries(backend_test PUBLIC ${BACKEND_TEST_LIBS})
|
||||||
|
|
||||||
set(BACKEND_TEST_DEPS
|
set(BACKEND_TEST_DEPS
|
||||||
OSDependent
|
OSDependent
|
||||||
|
|||||||
@@ -256,16 +256,18 @@ struct DescriptorSetLayoutBinding {
|
|||||||
DescriptorFlags flags = DescriptorFlags::NONE;
|
DescriptorFlags flags = DescriptorFlags::NONE;
|
||||||
uint16_t count = 0;
|
uint16_t count = 0;
|
||||||
|
|
||||||
uint8_t externalSamplerDataIndex = EXTERNAL_SAMPLER_DATA_INDEX_UNUSED;
|
// TODO: uncomment when needed. Note that this class is used as hash key. We need to ensure
|
||||||
|
// no uninitialized padding bytes.
|
||||||
|
// uint8_t externalSamplerDataIndex = EXTERNAL_SAMPLER_DATA_INDEX_UNUSED;
|
||||||
|
|
||||||
friend inline bool operator==(
|
friend inline bool operator==(DescriptorSetLayoutBinding const& lhs,
|
||||||
DescriptorSetLayoutBinding const& lhs,
|
|
||||||
DescriptorSetLayoutBinding const& rhs) noexcept {
|
DescriptorSetLayoutBinding const& rhs) noexcept {
|
||||||
return lhs.type == rhs.type &&
|
return lhs.type == rhs.type &&
|
||||||
lhs.flags == rhs.flags &&
|
lhs.flags == rhs.flags &&
|
||||||
lhs.count == rhs.count &&
|
lhs.count == rhs.count &&
|
||||||
lhs.stageFlags == rhs.stageFlags &&
|
lhs.stageFlags == rhs.stageFlags;
|
||||||
lhs.externalSamplerDataIndex == rhs.externalSamplerDataIndex;
|
// lhs.stageFlags == rhs.stageFlags &&
|
||||||
|
// lhs.externalSamplerDataIndex == rhs.externalSamplerDataIndex;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1138,7 +1140,9 @@ static_assert(sizeof(ExternalSamplerDatum) == 12);
|
|||||||
|
|
||||||
struct DescriptorSetLayout {
|
struct DescriptorSetLayout {
|
||||||
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
|
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
|
||||||
utils::FixedCapacityVector<ExternalSamplerDatum> externalSamplerData;
|
|
||||||
|
// TODO: uncomment when needed
|
||||||
|
// utils::FixedCapacityVector<ExternalSamplerDatum> externalSamplerData;
|
||||||
};
|
};
|
||||||
|
|
||||||
//! blending equation function
|
//! blending equation function
|
||||||
|
|||||||
@@ -298,6 +298,16 @@ public:
|
|||||||
VkQueue getProtectedGraphicsQueue() const noexcept;
|
VkQueue getProtectedGraphicsQueue() const noexcept;
|
||||||
|
|
||||||
struct ExternalImageMetadata {
|
struct ExternalImageMetadata {
|
||||||
|
/**
|
||||||
|
* The Filament texture format.
|
||||||
|
*/
|
||||||
|
TextureFormat filamentFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Filament texture usage.
|
||||||
|
*/
|
||||||
|
TextureUsage filamentUsage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The width of the external image
|
* The width of the external image
|
||||||
*/
|
*/
|
||||||
@@ -308,11 +318,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
uint32_t height;
|
uint32_t height;
|
||||||
|
|
||||||
/**
|
|
||||||
* The layerCount of the external image
|
|
||||||
*/
|
|
||||||
uint32_t layerCount;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The layer count of the external image
|
* The layer count of the external image
|
||||||
*/
|
*/
|
||||||
@@ -328,11 +333,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
VkFormat format;
|
VkFormat format;
|
||||||
|
|
||||||
/**
|
|
||||||
* An external buffer can be protected. This tells you if it is.
|
|
||||||
*/
|
|
||||||
bool isProtected;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of external format (opaque int) if used.
|
* The type of external format (opaque int) if used.
|
||||||
*/
|
*/
|
||||||
@@ -352,21 +352,44 @@ public:
|
|||||||
* Heap information
|
* Heap information
|
||||||
*/
|
*/
|
||||||
uint32_t memoryTypeBits;
|
uint32_t memoryTypeBits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ycbcr conversion components
|
||||||
|
*/
|
||||||
|
VkComponentMapping ycbcrConversionComponents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ycbcr model
|
||||||
|
*/
|
||||||
|
VkSamplerYcbcrModelConversion ycbcrModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ycbcr range
|
||||||
|
*/
|
||||||
|
VkSamplerYcbcrRange ycbcrRange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ycbcr x chroma offset
|
||||||
|
*/
|
||||||
|
VkChromaLocation xChromaOffset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ycbcr y chroma offset
|
||||||
|
*/
|
||||||
|
VkChromaLocation yChromaOffset;
|
||||||
};
|
};
|
||||||
virtual ExternalImageMetadata getExternalImageMetadata(ExternalImageHandleRef externalImage);
|
|
||||||
|
|
||||||
|
// Note that the image metadata might change per-frame, hence we need a method for extracting
|
||||||
|
// it.
|
||||||
|
virtual ExternalImageMetadata extractExternalImageMetadata(ExternalImageHandleRef image) const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
using ImageData = std::pair<VkImage, VkDeviceMemory>;
|
using ImageData = std::pair<VkImage, VkDeviceMemory>;
|
||||||
virtual ImageData createExternalImageData(ExternalImageHandleRef externalImage,
|
virtual ImageData createVkImageFromExternal(ExternalImageHandleRef image) const {
|
||||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
|
return { VK_NULL_HANDLE, VK_NULL_HANDLE };
|
||||||
VkImageUsageFlags usage);
|
}
|
||||||
|
|
||||||
virtual VkSampler createExternalSampler(SamplerYcbcrConversion chroma,
|
|
||||||
SamplerParams sampler,
|
|
||||||
uint32_t internalFormat);
|
|
||||||
|
|
||||||
virtual VkImageView createExternalImageView(SamplerYcbcrConversion chroma,
|
|
||||||
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
|
|
||||||
VkImageViewType viewType, VkComponentMapping swizzle);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
||||||
@@ -379,20 +402,6 @@ private:
|
|||||||
// Platform dependent helper methods
|
// Platform dependent helper methods
|
||||||
static ExtensionSet getSwapchainInstanceExtensionsImpl();
|
static ExtensionSet getSwapchainInstanceExtensionsImpl();
|
||||||
|
|
||||||
static ExternalImageMetadata getExternalImageMetadataImpl(ExternalImageHandleRef externalImage,
|
|
||||||
VkDevice device);
|
|
||||||
|
|
||||||
static ImageData createExternalImageDataImpl(ExternalImageHandleRef externalImage,
|
|
||||||
VkDevice device, const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
|
|
||||||
VkImageUsageFlags usage);
|
|
||||||
static VkSampler createExternalSamplerImpl(VkDevice device,
|
|
||||||
SamplerYcbcrConversion chroma, SamplerParams sampler,
|
|
||||||
uint32_t internalFormat);
|
|
||||||
static VkImageView createExternalImageViewImpl(VkDevice device,
|
|
||||||
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
|
|
||||||
VkImageSubresourceRange range, VkImageViewType viewType,
|
|
||||||
VkComponentMapping swizzle);
|
|
||||||
|
|
||||||
// Platform dependent helper methods
|
// Platform dependent helper methods
|
||||||
static SurfaceBundle createVkSurfaceKHRImpl(void* nativeWindow, VkInstance instance,
|
static SurfaceBundle createVkSurfaceKHRImpl(void* nativeWindow, VkInstance instance,
|
||||||
uint64_t flags) noexcept;
|
uint64_t flags) noexcept;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace filament::backend {
|
|||||||
|
|
||||||
class VulkanPlatformAndroid : public VulkanPlatform {
|
class VulkanPlatformAndroid : public VulkanPlatform {
|
||||||
public:
|
public:
|
||||||
Platform::ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer,
|
ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer,
|
||||||
bool sRGB) noexcept;
|
bool sRGB) noexcept;
|
||||||
|
|
||||||
struct UTILS_PUBLIC ExternalImageDescAndroid {
|
struct UTILS_PUBLIC ExternalImageDescAndroid {
|
||||||
@@ -39,31 +39,26 @@ public:
|
|||||||
ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc(
|
ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc(
|
||||||
ExternalImageHandleRef externalImage) const noexcept;
|
ExternalImageHandleRef externalImage) const noexcept;
|
||||||
|
|
||||||
|
virtual ExternalImageMetadata extractExternalImageMetadata(
|
||||||
|
ExternalImageHandleRef image) const override;
|
||||||
|
|
||||||
|
virtual ImageData createVkImageFromExternal(ExternalImageHandleRef image) const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
virtual ExtensionSet getSwapchainInstanceExtensions() const override;
|
||||||
|
|
||||||
|
using SurfaceBundle = VulkanPlatform::SurfaceBundle;
|
||||||
|
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||||
|
uint64_t flags) const noexcept override;
|
||||||
|
|
||||||
|
private:
|
||||||
struct ExternalImageVulkanAndroid : public Platform::ExternalImage {
|
struct ExternalImageVulkanAndroid : public Platform::ExternalImage {
|
||||||
AHardwareBuffer* aHardwareBuffer = nullptr;
|
AHardwareBuffer* aHardwareBuffer = nullptr;
|
||||||
bool sRGB = false;
|
bool sRGB = false;
|
||||||
unsigned int width; // Texture width
|
|
||||||
unsigned int height; // Texture height
|
|
||||||
TextureFormat format;// Texture format
|
|
||||||
TextureUsage usage; // Texture usage flags
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
~ExternalImageVulkanAndroid() override;
|
~ExternalImageVulkanAndroid() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual ExternalImageMetadata getExternalImageMetadata(ExternalImageHandleRef externalImage);
|
|
||||||
|
|
||||||
using ImageData = VulkanPlatform::ImageData;
|
|
||||||
virtual ImageData createExternalImageData(ExternalImageHandleRef externalImage,
|
|
||||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
|
|
||||||
VkImageUsageFlags usage);
|
|
||||||
|
|
||||||
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
|
||||||
|
|
||||||
using SurfaceBundle = VulkanPlatform::SurfaceBundle;
|
|
||||||
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
|
||||||
uint64_t flags) const noexcept;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}// namespace filament::backend
|
}// namespace filament::backend
|
||||||
|
|||||||
@@ -40,9 +40,11 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#define HandleAllocatorGL HandleAllocator<32, 96, 184> // ~4520 / pool / MiB
|
#define HandleAllocatorGL HandleAllocator<32, 96, 184> // ~4520 / pool / MiB
|
||||||
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB
|
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB
|
||||||
#define HandleAllocatorMTL HandleAllocator<32, 64, 552> // ~1660 / pool / MiB
|
#define HandleAllocatorMTL HandleAllocator<32, 64, 552> // ~1660 / pool / MiB
|
||||||
|
// TODO WebGPU examine right size of handles
|
||||||
|
#define HandleAllocatorWGPU HandleAllocator<64, 160, 552> // ~1820 / pool / MiB
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
|
|||||||
@@ -211,4 +211,8 @@ template class HandleAllocatorVK;
|
|||||||
template class HandleAllocatorMTL;
|
template class HandleAllocatorMTL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined (FILAMENT_SUPPORTS_WEBGPU)
|
||||||
|
template class HandleAllocatorWGPU;
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -1177,9 +1177,17 @@ size_t MetalDriver::getMaxUniformBufferSize() {
|
|||||||
return 256 * 1024 * 1024; // TODO: return the actual size instead of hardcoding the minspec
|
return 256 * 1024 * 1024; // TODO: return the actual size instead of hardcoding the minspec
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t MetalDriver::getMaxTextureSize(SamplerType) {
|
size_t MetalDriver::getMaxTextureSize(SamplerType type) {
|
||||||
// TODO: return the actual size instead of hardcoding the minspec
|
if (type == SamplerType::SAMPLER_3D) {
|
||||||
return 2048;
|
return 2048;
|
||||||
|
}
|
||||||
|
if (mContext->highestSupportedGpuFamily.apple >= 3 ||
|
||||||
|
mContext->highestSupportedGpuFamily.mac >= 2) {
|
||||||
|
return 16384;
|
||||||
|
} else if (mContext->highestSupportedGpuFamily.apple >= 2) {
|
||||||
|
return 8192;
|
||||||
|
}
|
||||||
|
return 4096;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t MetalDriver::getMaxArrayTextureLayers() {
|
size_t MetalDriver::getMaxArrayTextureLayers() {
|
||||||
|
|||||||
@@ -241,7 +241,9 @@ size_t NoopDriver::getMaxUniformBufferSize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t NoopDriver::getMaxTextureSize(SamplerType target) {
|
size_t NoopDriver::getMaxTextureSize(SamplerType target) {
|
||||||
return 2048u;
|
// NoopDriver is being actively used for other purposes. This needs to be resolved before we
|
||||||
|
// can change it to 2048. b/406832484
|
||||||
|
return 16384u;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t NoopDriver::getMaxArrayTextureLayers() {
|
size_t NoopDriver::getMaxArrayTextureLayers() {
|
||||||
|
|||||||
@@ -566,7 +566,11 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
|
|||||||
} else if (strstr(renderer, "AMD") ||
|
} else if (strstr(renderer, "AMD") ||
|
||||||
strstr(renderer, "ATI")) {
|
strstr(renderer, "ATI")) {
|
||||||
// AMD/ATI GPU
|
// AMD/ATI GPU
|
||||||
} else if (strstr(vendor, "Mesa")) {
|
} else if (strstr(renderer, "Mozilla")) {
|
||||||
|
bugs->disable_invalidate_framebuffer = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr(vendor, "Mesa")) {
|
||||||
// Seen on
|
// Seen on
|
||||||
// [Mesa],
|
// [Mesa],
|
||||||
// [llvmpipe (LLVM 17.0.6, 256 bits)],
|
// [llvmpipe (LLVM 17.0.6, 256 bits)],
|
||||||
@@ -574,8 +578,6 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
|
|||||||
// [4.50]
|
// [4.50]
|
||||||
// not known which version are affected
|
// not known which version are affected
|
||||||
bugs->rebind_buffer_after_deletion = true;
|
bugs->rebind_buffer_after_deletion = true;
|
||||||
} else if (strstr(renderer, "Mozilla")) {
|
|
||||||
bugs->disable_invalidate_framebuffer = true;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// When running under ANGLE, it's a different set of workaround that we need.
|
// When running under ANGLE, it's a different set of workaround that we need.
|
||||||
@@ -587,6 +589,19 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strstr(vendor, "Mozilla")) {
|
||||||
|
// Seen on
|
||||||
|
// [Mozilla],
|
||||||
|
// [GeForce GTX 980, or similar]
|
||||||
|
// or [ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0), or similar]
|
||||||
|
// or anything else,
|
||||||
|
// [OpenGL ES 3.0 (WebGL 2.0)],
|
||||||
|
// [OpenGL ES GLSL ES 3.00 (WebGL GLSL ES 3.00)]
|
||||||
|
// For Mozilla, the issue appears to be observed regardless of whether the renderer is
|
||||||
|
// ANGLE or not. (b/376125497)
|
||||||
|
bugs->rebind_buffer_after_deletion = true;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||||
# ifndef FILAMENT_IOS // FILAMENT_IOS is guaranteed to have ES3.x
|
# ifndef FILAMENT_IOS // FILAMENT_IOS is guaranteed to have ES3.x
|
||||||
if (UTILS_UNLIKELY(major == 2)) {
|
if (UTILS_UNLIKELY(major == 2)) {
|
||||||
|
|||||||
@@ -320,8 +320,8 @@ public:
|
|||||||
// a glFinish. So we must delay the destruction until we know the GPU is finished.
|
// a glFinish. So we must delay the destruction until we know the GPU is finished.
|
||||||
bool delay_fbo_destruction;
|
bool delay_fbo_destruction;
|
||||||
|
|
||||||
// Mesa sometimes clears the generic buffer binding when *another* buffer is destroyed,
|
// Mesa and Mozilla(web) sometimes clear the generic buffer binding when *another* buffer
|
||||||
// if that other buffer is bound on an *indexed* buffer binding.
|
// is destroyed, if that other buffer is bound on an *indexed* buffer binding.
|
||||||
bool rebind_buffer_after_deletion;
|
bool rebind_buffer_after_deletion;
|
||||||
|
|
||||||
// Force feature level 0. Typically used for low end ES3 devices with significant driver
|
// Force feature level 0. Typically used for low end ES3 devices with significant driver
|
||||||
|
|||||||
@@ -17,9 +17,6 @@
|
|||||||
#include "VulkanBlitter.h"
|
#include "VulkanBlitter.h"
|
||||||
#include "VulkanCommands.h"
|
#include "VulkanCommands.h"
|
||||||
#include "VulkanContext.h"
|
#include "VulkanContext.h"
|
||||||
#include "VulkanFboCache.h"
|
|
||||||
#include "VulkanHandles.h"
|
|
||||||
#include "VulkanSamplerCache.h"
|
|
||||||
#include "VulkanTexture.h"
|
#include "VulkanTexture.h"
|
||||||
#include "vulkan/utils/Image.h"
|
#include "vulkan/utils/Image.h"
|
||||||
|
|
||||||
|
|||||||
@@ -24,13 +24,6 @@
|
|||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
class VulkanBuffer;
|
|
||||||
class VulkanFboCache;
|
|
||||||
class VulkanPipelineCache;
|
|
||||||
class VulkanSamplerCache;
|
|
||||||
|
|
||||||
struct VulkanProgram;
|
|
||||||
|
|
||||||
class VulkanBlitter {
|
class VulkanBlitter {
|
||||||
public:
|
public:
|
||||||
VulkanBlitter(VkPhysicalDevice physicalDevice, VulkanCommands* commands) noexcept;
|
VulkanBlitter(VkPhysicalDevice physicalDevice, VulkanCommands* commands) noexcept;
|
||||||
|
|||||||
@@ -93,7 +93,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE | FVK_DEBUG_FORWARDED_FLAG | FVK_DEBUG_VALIDATION)
|
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE | FVK_DEBUG_FORWARDED_FLAG)
|
||||||
#else
|
#else
|
||||||
#define FVK_DEBUG_FLAGS 0
|
#define FVK_DEBUG_FLAGS 0
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ VkImageView VulkanAttachment::getImageView() {
|
|||||||
assert_invariant(texture);
|
assert_invariant(texture);
|
||||||
VkImageSubresourceRange range = getSubresourceRange();
|
VkImageSubresourceRange range = getSubresourceRange();
|
||||||
if (range.layerCount > 1) {
|
if (range.layerCount > 1) {
|
||||||
return texture->getMultiviewAttachmentView(range);
|
return texture->getAttachmentView(range, VK_IMAGE_VIEW_TYPE_2D_ARRAY);
|
||||||
}
|
}
|
||||||
return texture->getAttachmentView(range);
|
return texture->getAttachmentView(range, VK_IMAGE_VIEW_TYPE_2D);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VulkanAttachment::isDepth() const {
|
bool VulkanAttachment::isDepth() const {
|
||||||
@@ -68,11 +68,11 @@ bool VulkanAttachment::isDepth() const {
|
|||||||
VkImageSubresourceRange VulkanAttachment::getSubresourceRange() const {
|
VkImageSubresourceRange VulkanAttachment::getSubresourceRange() const {
|
||||||
assert_invariant(texture);
|
assert_invariant(texture);
|
||||||
return {
|
return {
|
||||||
.aspectMask = texture->getImageAspect(),
|
.aspectMask = texture->getImageAspect(),
|
||||||
.baseMipLevel = uint32_t(level),
|
.baseMipLevel = uint32_t(level),
|
||||||
.levelCount = 1,
|
.levelCount = 1,
|
||||||
.baseArrayLayer = uint32_t(layer),
|
.baseArrayLayer = uint32_t(layer),
|
||||||
.layerCount = layerCount,
|
.layerCount = layerCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
#ifndef TNT_FILAMENT_BACKEND_VULKANCONTEXT_H
|
#ifndef TNT_FILAMENT_BACKEND_VULKANCONTEXT_H
|
||||||
#define TNT_FILAMENT_BACKEND_VULKANCONTEXT_H
|
#define TNT_FILAMENT_BACKEND_VULKANCONTEXT_H
|
||||||
|
|
||||||
#include "VulkanConstants.h"
|
|
||||||
#include "vulkan/utils/Image.h"
|
#include "vulkan/utils/Image.h"
|
||||||
#include "vulkan/utils/Definitions.h"
|
#include "vulkan/utils/Definitions.h"
|
||||||
|
|
||||||
@@ -30,8 +29,6 @@
|
|||||||
|
|
||||||
#include <bluevk/BlueVK.h>
|
#include <bluevk/BlueVK.h>
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
VK_DEFINE_HANDLE(VmaAllocator)
|
VK_DEFINE_HANDLE(VmaAllocator)
|
||||||
VK_DEFINE_HANDLE(VmaPool)
|
VK_DEFINE_HANDLE(VmaPool)
|
||||||
|
|
||||||
@@ -73,13 +70,11 @@ struct VulkanRenderPass {
|
|||||||
// context are stored in VulkanPlatform.
|
// context are stored in VulkanPlatform.
|
||||||
struct VulkanContext {
|
struct VulkanContext {
|
||||||
public:
|
public:
|
||||||
inline uint32_t selectMemoryType(uint32_t flags, VkFlags reqs) const {
|
static uint32_t selectMemoryType(VkPhysicalDeviceMemoryProperties const& memoryProperties,
|
||||||
if ((reqs & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) {
|
uint32_t flags, VkFlags reqs) {
|
||||||
assert_invariant(isProtectedMemorySupported() == true);
|
|
||||||
}
|
|
||||||
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
|
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
|
||||||
if (flags & 1) {
|
if (flags & 1) {
|
||||||
if ((mMemoryProperties.memoryTypes[i].propertyFlags & reqs) == reqs) {
|
if ((memoryProperties.memoryTypes[i].propertyFlags & reqs) == reqs) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,6 +83,13 @@ public:
|
|||||||
return (uint32_t) VK_MAX_MEMORY_TYPES;
|
return (uint32_t) VK_MAX_MEMORY_TYPES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline uint32_t selectMemoryType(uint32_t flags, VkFlags reqs) const {
|
||||||
|
if ((reqs & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) {
|
||||||
|
assert_invariant(isProtectedMemorySupported());
|
||||||
|
}
|
||||||
|
return selectMemoryType(mMemoryProperties, flags, reqs);
|
||||||
|
}
|
||||||
|
|
||||||
inline fvkutils::VkFormatList const& getAttachmentDepthStencilFormats() const {
|
inline fvkutils::VkFormatList const& getAttachmentDepthStencilFormats() const {
|
||||||
return mDepthStencilFormats;
|
return mDepthStencilFormats;
|
||||||
}
|
}
|
||||||
@@ -121,7 +123,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline bool isMultiviewEnabled() const noexcept {
|
inline bool isMultiviewEnabled() const noexcept {
|
||||||
return mMultiviewEnabled;
|
return mPhysicalDeviceVk11Features.multiview == VK_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool isClipDistanceSupported() const noexcept {
|
inline bool isClipDistanceSupported() const noexcept {
|
||||||
@@ -145,6 +147,9 @@ private:
|
|||||||
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
|
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
|
||||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
|
||||||
};
|
};
|
||||||
|
VkPhysicalDeviceVulkan11Features mPhysicalDeviceVk11Features = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
|
||||||
|
};
|
||||||
VkPhysicalDeviceFeatures2 mPhysicalDeviceFeatures = {
|
VkPhysicalDeviceFeatures2 mPhysicalDeviceFeatures = {
|
||||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
||||||
};
|
};
|
||||||
@@ -157,7 +162,6 @@ private:
|
|||||||
};
|
};
|
||||||
bool mDebugMarkersSupported = false;
|
bool mDebugMarkersSupported = false;
|
||||||
bool mDebugUtilsSupported = false;
|
bool mDebugUtilsSupported = false;
|
||||||
bool mMultiviewEnabled = false;
|
|
||||||
bool mLazilyAllocatedMemorySupported = false;
|
bool mLazilyAllocatedMemorySupported = false;
|
||||||
bool mProtectedMemorySupported = false;
|
bool mProtectedMemorySupported = false;
|
||||||
|
|
||||||
|
|||||||
@@ -260,7 +260,6 @@ VulkanDescriptorSetCache::~VulkanDescriptorSetCache() = default;
|
|||||||
|
|
||||||
void VulkanDescriptorSetCache::terminate() noexcept{
|
void VulkanDescriptorSetCache::terminate() noexcept{
|
||||||
mDescriptorPool.reset();
|
mDescriptorPool.reset();
|
||||||
clearHistory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// bind() is not really binding the set but just stashing until we have all the info
|
// bind() is not really binding the set but just stashing until we have all the info
|
||||||
@@ -283,18 +282,25 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
|
|||||||
fvkutils::DescriptorSetMask curMask = setMask;
|
fvkutils::DescriptorSetMask curMask = setMask;
|
||||||
|
|
||||||
auto& updateSets = mStashedSets;
|
auto& updateSets = mStashedSets;
|
||||||
auto& lastBoundSets = mLastBoundInfo.boundSets;
|
bool const pipelineLayoutIsSame = mLastBoundInfo.pipelineLayout == pipelineLayout;
|
||||||
|
|
||||||
setMask.forEachSetBit([&](size_t index) {
|
if (pipelineLayoutIsSame) {
|
||||||
if (!updateSets[index] || updateSets[index] == lastBoundSets[index]) {
|
auto& lastBoundSets = mLastBoundInfo.boundSets;
|
||||||
curMask.unset(index);
|
setMask.forEachSetBit([&](size_t index) {
|
||||||
|
if (!updateSets[index] || updateSets[index] == lastBoundSets[index]) {
|
||||||
|
curMask.unset(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (curMask.none() &&
|
||||||
|
mLastBoundInfo.setMask == setMask && mLastBoundInfo.boundSets == updateSets) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
|
setMask.forEachSetBit([&](size_t index) {
|
||||||
if (curMask.none() &&
|
if (!updateSets[index]) {
|
||||||
(mLastBoundInfo.pipelineLayout == pipelineLayout && mLastBoundInfo.setMask == setMask &&
|
curMask.unset(index);
|
||||||
mLastBoundInfo.boundSets == updateSets)) {
|
}
|
||||||
return;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) {
|
curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) {
|
||||||
@@ -302,7 +308,7 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
|
|||||||
auto set = updateSets[index];
|
auto set = updateSets[index];
|
||||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||||
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
|
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
|
||||||
1, &set->vkSet, set->uniqueDynamicUboCount, set->getOffsets()->data());
|
1, &set->getVkSet(), set->uniqueDynamicUboCount, set->getOffsets()->data());
|
||||||
commands->acquire(set);
|
commands->acquire(set);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -330,8 +336,7 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
|
|||||||
}
|
}
|
||||||
VkWriteDescriptorSet const descriptorWrite = {
|
VkWriteDescriptorSet const descriptorWrite = {
|
||||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||||
.pNext = nullptr,
|
.dstSet = set->getVkSet(),
|
||||||
.dstSet = set->vkSet,
|
|
||||||
.dstBinding = binding,
|
.dstBinding = binding,
|
||||||
.descriptorCount = 1,
|
.descriptorCount = 1,
|
||||||
.descriptorType = type,
|
.descriptorType = type,
|
||||||
@@ -344,26 +349,25 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
|
|||||||
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
|
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
|
||||||
VkSampler sampler) noexcept {
|
VkSampler sampler) noexcept {
|
||||||
VkDescriptorImageInfo info{
|
VkImageSubresourceRange range = texture->getPrimaryViewRange();
|
||||||
.sampler = sampler,
|
|
||||||
};
|
|
||||||
VkImageSubresourceRange const 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) &&
|
||||||
expectedType == VK_IMAGE_VIEW_TYPE_2D) {
|
expectedType == VK_IMAGE_VIEW_TYPE_2D) {
|
||||||
// If the sampler is part of a mipmapped depth texture, where one of the level *can* be
|
// If the sampler is part of a mipmapped depth texture, where one of the level *can* be
|
||||||
// an attachment, then the sampler for this texture has the same view properties as a
|
// an attachment, then the range for this view has exactly one level and one layer.
|
||||||
// view for an attachment. Therefore, we can use getAttachmentView to get a
|
range.levelCount = 1;
|
||||||
// corresponding VkImageView.
|
range.layerCount = 1;
|
||||||
info.imageView = texture->getAttachmentView(range);
|
|
||||||
} else {
|
|
||||||
info.imageView = texture->getViewForType(range, expectedType);
|
|
||||||
}
|
}
|
||||||
info.imageLayout = fvkutils::getVkLayout(texture->getDefaultLayout());
|
VkDescriptorImageInfo info{
|
||||||
|
.sampler = sampler,
|
||||||
|
.imageView = texture->getView(range),
|
||||||
|
.imageLayout = fvkutils::getVkLayout(texture->getDefaultLayout()),
|
||||||
|
};
|
||||||
|
|
||||||
VkWriteDescriptorSet const descriptorWrite = {
|
VkWriteDescriptorSet const descriptorWrite = {
|
||||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.dstSet = set->vkSet,
|
.dstSet = set->getVkSet(),
|
||||||
.dstBinding = binding,
|
.dstBinding = binding,
|
||||||
.descriptorCount = 1,
|
.descriptorCount = 1,
|
||||||
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||||
@@ -381,10 +385,10 @@ void VulkanDescriptorSetCache::updateInputAttachment(
|
|||||||
|
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet(
|
fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet(
|
||||||
Handle<HwDescriptorSet> handle, fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
Handle<HwDescriptorSet> handle, fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||||
auto const vkSet = mDescriptorPool->obtainSet(layout);
|
auto const vkSet = getVkSet(layout);
|
||||||
auto const& count = layout->count;
|
auto const& count = layout->count;
|
||||||
auto const vklayout = layout->getVkLayout();
|
auto const vklayout = layout->getVkLayout();
|
||||||
return fvkmemory::resource_ptr<VulkanDescriptorSet>::make(mResourceManager, handle, vkSet,
|
auto set = fvkmemory::resource_ptr<VulkanDescriptorSet>::make(mResourceManager, handle,
|
||||||
layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
|
layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
|
||||||
[vkSet, count, vklayout, this](VulkanDescriptorSet*) {
|
[vkSet, count, vklayout, this](VulkanDescriptorSet*) {
|
||||||
// Note that mDescriptorPool could be gone due to terminate (when the backend shuts
|
// Note that mDescriptorPool could be gone due to terminate (when the backend shuts
|
||||||
@@ -393,10 +397,20 @@ fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet
|
|||||||
mDescriptorPool->recycle(count, vklayout, vkSet);
|
mDescriptorPool->recycle(count, vklayout, vkSet);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
set->setVkSet(vkSet);
|
||||||
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDescriptorSetCache::clearHistory() {
|
VkDescriptorSet VulkanDescriptorSetCache::getVkSet(
|
||||||
mStashedSets = {};
|
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||||
|
return mDescriptorPool->obtainSet(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VulkanDescriptorSetCache::manualRecyle(VulkanDescriptorSetLayout::Count const& count,
|
||||||
|
VkDescriptorSetLayout vklayout, VkDescriptorSet vkSet) {
|
||||||
|
mDescriptorPool->recycle(count, vklayout, vkSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanDescriptorSetCache::gc() { mStashedSets = {}; }
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ public:
|
|||||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT;
|
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT;
|
||||||
|
|
||||||
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
||||||
|
using DescriptorSetArray =
|
||||||
|
std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>, UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||||
|
|
||||||
VulkanDescriptorSetCache(VkDevice device, fvkmemory::ResourceManager* resourceManager);
|
VulkanDescriptorSetCache(VkDevice device, fvkmemory::ResourceManager* resourceManager);
|
||||||
~VulkanDescriptorSetCache();
|
~VulkanDescriptorSetCache();
|
||||||
@@ -68,14 +70,21 @@ public:
|
|||||||
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
|
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||||
|
|
||||||
void clearHistory();
|
// This method is only meant to be used with external samplers (or internally within this
|
||||||
|
// class).
|
||||||
|
VkDescriptorSet getVkSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||||
|
|
||||||
|
// This method is only meant to be used with external samplers.
|
||||||
|
void manualRecyle(VulkanDescriptorSetLayout::Count const& count, VkDescriptorSetLayout vklayout,
|
||||||
|
VkDescriptorSet vkSet);
|
||||||
|
|
||||||
|
DescriptorSetArray const& getBoundSets() const { return mStashedSets; }
|
||||||
|
|
||||||
|
void gc();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class DescriptorInfinitePool;
|
class DescriptorInfinitePool;
|
||||||
|
|
||||||
using DescriptorSetArray =
|
|
||||||
std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>, UNIQUE_DESCRIPTOR_SET_COUNT>;
|
|
||||||
|
|
||||||
VkDevice mDevice;
|
VkDevice mDevice;
|
||||||
fvkmemory::ResourceManager* mResourceManager;
|
fvkmemory::ResourceManager* mResourceManager;
|
||||||
std::unique_ptr<DescriptorInfinitePool> mDescriptorPool;
|
std::unique_ptr<DescriptorInfinitePool> mDescriptorPool;
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
#include "VulkanHandles.h"
|
#include "VulkanHandles.h"
|
||||||
|
|
||||||
|
#include <utils/Hash.h>
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -58,6 +60,56 @@ uint32_t appendBindings(VkDescriptorSetLayoutBinding* toBind, VkDescriptorType t
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t appendSamplerBindings(VkDescriptorSetLayoutBinding* toBind,
|
||||||
|
fvkutils::SamplerBitmask const& mask, fvkutils::SamplerBitmask const& external,
|
||||||
|
utils::FixedCapacityVector<VkSampler> const& immutableSamplers) {
|
||||||
|
using Bitmask = fvkutils::SamplerBitmask;
|
||||||
|
uint32_t count = 0;
|
||||||
|
Bitmask alreadySeen;
|
||||||
|
uint8_t immutableIndex = 0;
|
||||||
|
size_t const immutableSamplerCount = immutableSamplers.size();
|
||||||
|
mask.forEachSetBit([&](size_t index) {
|
||||||
|
VkShaderStageFlags stages = 0;
|
||||||
|
uint32_t binding = 0;
|
||||||
|
if (index < fvkutils::getFragmentStageShift<Bitmask>()) {
|
||||||
|
binding = (uint32_t) index;
|
||||||
|
stages |= VK_SHADER_STAGE_VERTEX_BIT;
|
||||||
|
auto fragIndex = index + fvkutils::getFragmentStageShift<Bitmask>();
|
||||||
|
if (mask.test(fragIndex)) {
|
||||||
|
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||||
|
alreadySeen.set(fragIndex);
|
||||||
|
}
|
||||||
|
} else if (!alreadySeen.test(index)) {
|
||||||
|
// We are in fragment stage bits
|
||||||
|
binding = (uint32_t) (index - fvkutils::getFragmentStageShift<Bitmask>());
|
||||||
|
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stages) {
|
||||||
|
toBind[count++] = {
|
||||||
|
.binding = binding,
|
||||||
|
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||||
|
.descriptorCount = 1,
|
||||||
|
.stageFlags = stages,
|
||||||
|
.pImmutableSamplers = external[index] && immutableSamplerCount > immutableIndex
|
||||||
|
? &immutableSamplers[immutableIndex++]
|
||||||
|
: nullptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t computeImmutableSamplerHash(utils::FixedCapacityVector<VkSampler> const& samplers) {
|
||||||
|
size_t const size = samplers.size();
|
||||||
|
if (size == 0) {
|
||||||
|
return 0;
|
||||||
|
} else if (size == 1) {
|
||||||
|
return (uint64_t) samplers[0];
|
||||||
|
}
|
||||||
|
return utils::hash::murmur3((uint32_t*) samplers.data(), samplers.size() * 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
VulkanDescriptorSetLayoutCache::VulkanDescriptorSetLayoutCache(VkDevice device,
|
VulkanDescriptorSetLayoutCache::VulkanDescriptorSetLayoutCache(VkDevice device,
|
||||||
@@ -73,37 +125,44 @@ void VulkanDescriptorSetLayoutCache::terminate() noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
|
||||||
|
VulkanDescriptorSetLayout::Bitmask const& bitmasks,
|
||||||
|
utils::FixedCapacityVector<VkSampler> immutableSamplers) {
|
||||||
|
LayoutKey key = {
|
||||||
|
.bitmask = bitmasks,
|
||||||
|
.immutableSamplerHash = computeImmutableSamplerHash(immutableSamplers),
|
||||||
|
};
|
||||||
|
if (auto itr = mVkLayouts.find(key); itr != mVkLayouts.end()) {
|
||||||
|
return itr->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
|
||||||
|
uint32_t count = 0;
|
||||||
|
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
||||||
|
bitmasks.dynamicUbo);
|
||||||
|
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmasks.ubo);
|
||||||
|
count += appendSamplerBindings(&toBind[count], bitmasks.sampler, bitmasks.externalSampler,
|
||||||
|
immutableSamplers);
|
||||||
|
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
|
||||||
|
bitmasks.inputAttachment);
|
||||||
|
|
||||||
|
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
|
||||||
|
VkDescriptorSetLayoutCreateInfo dlinfo = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||||
|
.bindingCount = count,
|
||||||
|
.pBindings = toBind,
|
||||||
|
};
|
||||||
|
VkDescriptorSetLayout vklayout;
|
||||||
|
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &vklayout);
|
||||||
|
mVkLayouts[key] = vklayout;
|
||||||
|
return vklayout;
|
||||||
|
}
|
||||||
|
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> VulkanDescriptorSetLayoutCache::createLayout(
|
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> VulkanDescriptorSetLayoutCache::createLayout(
|
||||||
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info) {
|
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info) {
|
||||||
auto layout = fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::make(mResourceManager, handle,
|
auto layout = fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::make(mResourceManager, handle,
|
||||||
info);
|
info);
|
||||||
VkDescriptorSetLayout vklayout = VK_NULL_HANDLE;
|
layout->setVkLayout(getVkLayout(layout->bitmask));
|
||||||
auto const& bitmasks = layout->bitmask;
|
|
||||||
if (auto itr = mVkLayouts.find(bitmasks); itr != mVkLayouts.end()) {
|
|
||||||
vklayout = itr->second;
|
|
||||||
} else {
|
|
||||||
VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
|
|
||||||
uint32_t count = 0;
|
|
||||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
|
||||||
bitmasks.dynamicUbo);
|
|
||||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmasks.ubo);
|
|
||||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
|
||||||
bitmasks.sampler);
|
|
||||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
|
|
||||||
bitmasks.inputAttachment);
|
|
||||||
|
|
||||||
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
|
|
||||||
VkDescriptorSetLayoutCreateInfo dlinfo = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.bindingCount = count,
|
|
||||||
.pBindings = toBind,
|
|
||||||
};
|
|
||||||
|
|
||||||
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &vklayout);
|
|
||||||
mVkLayouts[bitmasks] = vklayout;
|
|
||||||
}
|
|
||||||
layout->setVkLayout(vklayout);
|
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,11 @@
|
|||||||
#include <backend/TargetBufferInfo.h>
|
#include <backend/TargetBufferInfo.h>
|
||||||
|
|
||||||
#include <utils/bitset.h>
|
#include <utils/bitset.h>
|
||||||
|
#include <utils/FixedCapacityVector.h>
|
||||||
|
|
||||||
#include <bluevk/BlueVK.h>
|
#include <bluevk/BlueVK.h>
|
||||||
#include <tsl/robin_map.h>
|
#include <tsl/robin_map.h>
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
class VulkanDescriptorSetLayoutCache {
|
class VulkanDescriptorSetLayoutCache {
|
||||||
@@ -41,21 +40,34 @@ public:
|
|||||||
|
|
||||||
void terminate() noexcept;
|
void terminate() noexcept;
|
||||||
|
|
||||||
|
// Just a wrapper around getVkLayout()
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> createLayout(
|
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> createLayout(
|
||||||
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info);
|
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info);
|
||||||
|
|
||||||
|
// This method is meant to be used with external samplers
|
||||||
|
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks,
|
||||||
|
utils::FixedCapacityVector<VkSampler> immutableSamplers = {});
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VkDevice mDevice;
|
VkDevice mDevice;
|
||||||
fvkmemory::ResourceManager* mResourceManager;
|
fvkmemory::ResourceManager* mResourceManager;
|
||||||
|
|
||||||
using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask;
|
struct LayoutKey {
|
||||||
using BitmaskGroupHashFn = utils::hash::MurmurHashFn<BitmaskGroup>;
|
// this describes the layout using bitset.
|
||||||
struct BitmaskGroupEqual {
|
VulkanDescriptorSetLayout::Bitmask bitmask = {};
|
||||||
bool operator()(BitmaskGroup const& k1, BitmaskGroup const& k2) const { return k1 == k2; }
|
// number of immutable samplers can be arbitrary; so we hash them into 64-bit.
|
||||||
|
uint64_t immutableSamplerHash = 0;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(LayoutKey) == 48);
|
||||||
|
|
||||||
|
using LayoutKeyHashFn = utils::hash::MurmurHashFn<LayoutKey>;
|
||||||
|
struct LayoutKeyEqual {
|
||||||
|
bool operator()(LayoutKey const& k1, LayoutKey const& k2) const {
|
||||||
|
return k1.bitmask == k2.bitmask && k1.immutableSamplerHash == k2.immutableSamplerHash;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tsl::robin_map<BitmaskGroup, VkDescriptorSetLayout, BitmaskGroupHashFn, BitmaskGroupEqual>
|
tsl::robin_map<LayoutKey, VkDescriptorSetLayout, LayoutKeyHashFn, LayoutKeyEqual> mVkLayouts;
|
||||||
mVkLayouts;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -25,10 +25,12 @@
|
|||||||
#include "VulkanHandles.h"
|
#include "VulkanHandles.h"
|
||||||
#include "VulkanMemory.h"
|
#include "VulkanMemory.h"
|
||||||
#include "VulkanTexture.h"
|
#include "VulkanTexture.h"
|
||||||
|
#include "vulkan/VulkanSamplerCache.h"
|
||||||
#include "vulkan/memory/ResourceManager.h"
|
#include "vulkan/memory/ResourceManager.h"
|
||||||
#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/vulkan_core.h"
|
||||||
|
|
||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
#include <backend/platforms/VulkanPlatform.h>
|
#include <backend/platforms/VulkanPlatform.h>
|
||||||
@@ -198,9 +200,7 @@ Dispatcher VulkanDriver::getDispatcher() const noexcept {
|
|||||||
VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& context,
|
VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& context,
|
||||||
Platform::DriverConfig const& driverConfig) noexcept
|
Platform::DriverConfig const& driverConfig) noexcept
|
||||||
: mPlatform(platform),
|
: mPlatform(platform),
|
||||||
mResourceManager(
|
mResourceManager(driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck,
|
||||||
driverConfig.handleArenaSize,
|
|
||||||
driverConfig.disableHandleUseAfterFreeCheck,
|
|
||||||
driverConfig.disableHeapHandleTags),
|
driverConfig.disableHeapHandleTags),
|
||||||
mAllocator(createAllocator(mPlatform->getInstance(), mPlatform->getPhysicalDevice(),
|
mAllocator(createAllocator(mPlatform->getInstance(), mPlatform->getPhysicalDevice(),
|
||||||
mPlatform->getDevice())),
|
mPlatform->getDevice())),
|
||||||
@@ -212,12 +212,15 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
|||||||
mPipelineCache(mPlatform->getDevice()),
|
mPipelineCache(mPlatform->getDevice()),
|
||||||
mStagePool(mAllocator, &mCommands),
|
mStagePool(mAllocator, &mCommands),
|
||||||
mFramebufferCache(mPlatform->getDevice()),
|
mFramebufferCache(mPlatform->getDevice()),
|
||||||
|
mYcbcrConversionCache(mPlatform->getDevice()),
|
||||||
mSamplerCache(mPlatform->getDevice()),
|
mSamplerCache(mPlatform->getDevice()),
|
||||||
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
|
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
|
||||||
mReadPixels(mPlatform->getDevice()),
|
mReadPixels(mPlatform->getDevice()),
|
||||||
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager),
|
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager),
|
||||||
mDescriptorSetCache(mPlatform->getDevice(), &mResourceManager),
|
mDescriptorSetCache(mPlatform->getDevice(), &mResourceManager),
|
||||||
mQueryManager(mPlatform->getDevice()),
|
mQueryManager(mPlatform->getDevice()),
|
||||||
|
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
|
||||||
|
&mDescriptorSetLayoutCache),
|
||||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
|
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
|
||||||
mStereoscopicType(driverConfig.stereoscopicType) {
|
mStereoscopicType(driverConfig.stereoscopicType) {
|
||||||
|
|
||||||
@@ -310,7 +313,7 @@ void VulkanDriver::terminate() {
|
|||||||
|
|
||||||
mCurrentSwapChain = {};
|
mCurrentSwapChain = {};
|
||||||
mDefaultRenderTarget = {};
|
mDefaultRenderTarget = {};
|
||||||
mBoundPipeline = {};
|
mPipelineState = {};
|
||||||
|
|
||||||
mQueryManager.terminate();
|
mQueryManager.terminate();
|
||||||
|
|
||||||
@@ -322,6 +325,10 @@ void VulkanDriver::terminate() {
|
|||||||
|
|
||||||
mCommands.terminate();
|
mCommands.terminate();
|
||||||
|
|
||||||
|
// Must come before samplerCache, ycbcrConversionCache, descriptorSetCache,
|
||||||
|
// descriptorSetLayoutCache
|
||||||
|
mExternalImageManager.terminate();
|
||||||
|
|
||||||
mStagePool.terminate();
|
mStagePool.terminate();
|
||||||
mPipelineCache.terminate();
|
mPipelineCache.terminate();
|
||||||
mFramebufferCache.reset();
|
mFramebufferCache.reset();
|
||||||
@@ -363,7 +370,7 @@ void VulkanDriver::collectGarbage() {
|
|||||||
FVK_SYSTRACE_SCOPE();
|
FVK_SYSTRACE_SCOPE();
|
||||||
// Command buffers need to be submitted and completed before other resources can be gc'd.
|
// Command buffers need to be submitted and completed before other resources can be gc'd.
|
||||||
mCommands.gc();
|
mCommands.gc();
|
||||||
mDescriptorSetCache.clearHistory();
|
mDescriptorSetCache.gc();
|
||||||
mStagePool.gc();
|
mStagePool.gc();
|
||||||
mFramebufferCache.gc();
|
mFramebufferCache.gc();
|
||||||
mPipelineCache.gc();
|
mPipelineCache.gc();
|
||||||
@@ -378,6 +385,10 @@ 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);
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
|
|
||||||
|
if (mAppState.hasExternalSamplers()) {
|
||||||
|
mExternalImageManager.onBeginFrame();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
|
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
|
||||||
@@ -418,8 +429,16 @@ void VulkanDriver::updateDescriptorSetTexture(
|
|||||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||||
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
|
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
|
||||||
|
|
||||||
VkSampler const vksampler = mSamplerCache.getSampler(params);
|
if (mExternalImageManager.isExternallySampledTexture(texture)) {
|
||||||
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
|
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
|
||||||
|
mAppState.hasBoundExternalImages = true;
|
||||||
|
} else {
|
||||||
|
VulkanSamplerCache::Params cacheParams = {
|
||||||
|
.sampler = params,
|
||||||
|
};
|
||||||
|
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
|
||||||
|
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::flush(int) {
|
void VulkanDriver::flush(int) {
|
||||||
@@ -556,39 +575,35 @@ void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::S
|
|||||||
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
|
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
|
||||||
Platform::ExternalImageHandleRef externalImage) {
|
Platform::ExternalImageHandleRef externalImage) {
|
||||||
FVK_SYSTRACE_SCOPE();
|
FVK_SYSTRACE_SCOPE();
|
||||||
const auto& metadata = mPlatform->getExternalImageMetadata(externalImage);
|
auto const& metadata = mPlatform->extractExternalImageMetadata(externalImage);
|
||||||
if (metadata.isProtected) {
|
|
||||||
usage |= backend::TextureUsage::PROTECTED;
|
// In theory the following are reasonable expectations, but in practice it's hard for client's
|
||||||
|
// to match up the dimensions of the texture with that of the AHB.
|
||||||
|
// assert_invariant(width == metadata.width);
|
||||||
|
// assert_invariant(height == metadata.height);
|
||||||
|
// assert_invariant(format == metadata.filamentFormat);
|
||||||
|
// assert_invariant(fvkutils::getVkFormat(format) == metadata.format);
|
||||||
|
|
||||||
|
VkImage vkimg;
|
||||||
|
VkDeviceMemory deviceMemory;
|
||||||
|
std::tie(vkimg, deviceMemory) = mPlatform->createVkImageFromExternal(externalImage);
|
||||||
|
|
||||||
|
VkSamplerYcbcrConversion conversion =
|
||||||
|
mExternalImageManager.getVkSamplerYcbcrConversion(metadata);
|
||||||
|
|
||||||
|
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mContext,
|
||||||
|
mPlatform->getDevice(), mAllocator, &mResourceManager, &mCommands, vkimg, deviceMemory,
|
||||||
|
metadata.format, conversion, metadata.samples, metadata.width, metadata.height,
|
||||||
|
metadata.layers, usage, mStagePool);
|
||||||
|
|
||||||
|
if (conversion != VK_NULL_HANDLE) {
|
||||||
|
mExternalImageManager.addExternallySampledTexture(texture, externalImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
VkImageUsageFlags vkUsage = metadata.usage;
|
// Unlike uploaded textures or swapchains, we need to explicit transition this
|
||||||
if (any(usage & TextureUsage::BLIT_SRC)) {
|
// texture into the read layout.
|
||||||
vkUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
auto& commands = mCommands.get();
|
||||||
}
|
texture->transitionLayout(&commands, texture->getPrimaryViewRange(), VulkanLayout::READ_ONLY);
|
||||||
|
|
||||||
if (any(usage & (TextureUsage::BLIT_DST & TextureUsage::UPLOADABLE))) {
|
|
||||||
vkUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_invariant(width == metadata.width);
|
|
||||||
assert_invariant(height == metadata.height);
|
|
||||||
assert_invariant(fvkutils::getVkFormat(format) == metadata.format);
|
|
||||||
|
|
||||||
VkMemoryPropertyFlags const requiredMemoryFlags = any(usage & TextureUsage::UPLOADABLE)
|
|
||||||
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
|
|
||||||
: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
|
||||||
uint32_t const memoryTypeIndex =
|
|
||||||
mContext.selectMemoryType(metadata.memoryTypeBits, requiredMemoryFlags);
|
|
||||||
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex != VK_MAX_MEMORY_TYPES)
|
|
||||||
<< "failed to find a valid memory type for external image memory.";
|
|
||||||
|
|
||||||
const auto& data =
|
|
||||||
mPlatform->createExternalImageData(externalImage, metadata, memoryTypeIndex, vkUsage);
|
|
||||||
|
|
||||||
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mPlatform->getDevice(),
|
|
||||||
mAllocator, &mResourceManager, &mCommands, data.first, data.second, metadata.format,
|
|
||||||
metadata.samples, metadata.width, metadata.height, metadata.layerCount, usage,
|
|
||||||
mStagePool);
|
|
||||||
|
|
||||||
texture.inc();
|
texture.inc();
|
||||||
}
|
}
|
||||||
@@ -597,13 +612,14 @@ void VulkanDriver::createTextureExternalImageR(Handle<HwTexture> th, backend::Sa
|
|||||||
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
|
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
|
||||||
void* externalImage) {
|
void* externalImage) {
|
||||||
FVK_SYSTRACE_SCOPE();
|
FVK_SYSTRACE_SCOPE();
|
||||||
|
assert_invariant(false && "Not supported in Vulkan backend");
|
||||||
// not supported in this backend
|
// not supported in this backend
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createTextureExternalImagePlaneR(Handle<HwTexture> th,
|
void VulkanDriver::createTextureExternalImagePlaneR(Handle<HwTexture> th,
|
||||||
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
|
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
|
||||||
void* image, uint32_t plane) {
|
void* image, uint32_t plane) {
|
||||||
|
assert_invariant(false && "Not supported in Vulkan backend");
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
|
void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
|
||||||
@@ -611,6 +627,7 @@ void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
|
|||||||
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
||||||
TextureUsage usage) {
|
TextureUsage usage) {
|
||||||
// not supported in this backend
|
// not supported in this backend
|
||||||
|
assert_invariant(false && "Not supported in Vulkan backend");
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
|
void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
|
||||||
@@ -619,6 +636,8 @@ void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
|
|||||||
}
|
}
|
||||||
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
|
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
|
||||||
texture.dec();
|
texture.dec();
|
||||||
|
|
||||||
|
mExternalImageManager.removeExternallySampledTexture(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||||
@@ -787,6 +806,11 @@ void VulkanDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
|||||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, dslh);
|
fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, dslh);
|
||||||
auto set = mDescriptorSetCache.createSet(dsh, layout);
|
auto set = mDescriptorSetCache.createSet(dsh, layout);
|
||||||
set.inc();
|
set.inc();
|
||||||
|
|
||||||
|
if (layout->hasExternalSamplers()) {
|
||||||
|
mAppState.hasExternalSamplerLayouts = true;
|
||||||
|
mExternalImageManager.addDescriptorSet(layout, set);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwVertexBufferInfo> VulkanDriver::createVertexBufferInfoS() noexcept {
|
Handle<HwVertexBufferInfo> VulkanDriver::createVertexBufferInfoS() noexcept {
|
||||||
@@ -906,11 +930,19 @@ void VulkanDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
|||||||
void VulkanDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> dslh) {
|
void VulkanDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> dslh) {
|
||||||
auto layout = resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, dslh);
|
auto layout = resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, dslh);
|
||||||
layout.dec();
|
layout.dec();
|
||||||
|
|
||||||
|
if (layout->hasExternalSamplers()) {
|
||||||
|
mExternalImageManager.removeDescriptorSetLayout(layout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
|
void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
|
||||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||||
set.dec();
|
set.dec();
|
||||||
|
|
||||||
|
if (mAppState.hasExternalSamplers()) {
|
||||||
|
mExternalImageManager.removeDescriptorSet(set);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream) {
|
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream) {
|
||||||
@@ -1057,7 +1089,7 @@ bool VulkanDriver::isDepthStencilBlitSupported(TextureFormat format) {
|
|||||||
formats.end();
|
formats.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VulkanDriver::isProtectedTexturesSupported() { return false; }
|
bool VulkanDriver::isProtectedTexturesSupported() { return isProtectedContentSupported(); }
|
||||||
|
|
||||||
bool VulkanDriver::isDepthClampSupported() {
|
bool VulkanDriver::isDepthClampSupported() {
|
||||||
return mContext.isDepthClampSupported();
|
return mContext.isDepthClampSupported();
|
||||||
@@ -1134,24 +1166,28 @@ math::float2 VulkanDriver::getClipSpaceParams() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint8_t VulkanDriver::getMaxDrawBuffers() {
|
uint8_t VulkanDriver::getMaxDrawBuffers() {
|
||||||
return MRT::MIN_SUPPORTED_RENDER_TARGET_COUNT; // TODO: query real value
|
return mContext.getPhysicalDeviceLimits().maxColorAttachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t VulkanDriver::getMaxUniformBufferSize() {
|
size_t VulkanDriver::getMaxUniformBufferSize() {
|
||||||
// TODO: return the actual size instead of hardcoded value
|
return mContext.getPhysicalDeviceLimits().maxUniformBufferRange;
|
||||||
// TODO: devices that return less than 32768 should be rejected. This represents only 3%
|
|
||||||
// of android devices.
|
|
||||||
return 32768;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t VulkanDriver::getMaxTextureSize(SamplerType) {
|
size_t VulkanDriver::getMaxTextureSize(SamplerType type) {
|
||||||
// TODO: return the actual size instead of hardcoded value
|
switch (type) {
|
||||||
return 2048;
|
case SamplerType::SAMPLER_2D:
|
||||||
|
return mContext.getPhysicalDeviceLimits().maxImageDimension2D;
|
||||||
|
case SamplerType::SAMPLER_3D:
|
||||||
|
return mContext.getPhysicalDeviceLimits().maxImageDimension3D;
|
||||||
|
case SamplerType::SAMPLER_CUBEMAP:
|
||||||
|
return mContext.getPhysicalDeviceLimits().maxImageDimensionCube;
|
||||||
|
default:
|
||||||
|
return mContext.getPhysicalDeviceLimits().maxImageDimension1D;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t VulkanDriver::getMaxArrayTextureLayers() {
|
size_t VulkanDriver::getMaxArrayTextureLayers() {
|
||||||
// TODO: return the actual size instead of hardcoded value
|
return mContext.getPhysicalDeviceLimits().maxImageArrayLayers;
|
||||||
return 256;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
|
void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
|
||||||
@@ -1467,7 +1503,6 @@ void VulkanDriver::endRenderPass(int) {
|
|||||||
// pipeline barrier between framebuffer writes and shader reads.
|
// pipeline barrier between framebuffer writes and shader reads.
|
||||||
rt->emitBarriersEndRenderPass(*mCurrentRenderPass.commandBuffer);
|
rt->emitBarriersEndRenderPass(*mCurrentRenderPass.commandBuffer);
|
||||||
|
|
||||||
mRenderPassFboInfo = {};
|
|
||||||
mCurrentRenderPass.renderTarget = {};
|
mCurrentRenderPass.renderTarget = {};
|
||||||
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
|
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
|
||||||
|
|
||||||
@@ -1527,10 +1562,10 @@ void VulkanDriver::commit(Handle<HwSwapChain> sch) {
|
|||||||
|
|
||||||
void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||||
backend::PushConstantVariant value) {
|
backend::PushConstantVariant value) {
|
||||||
assert_invariant(mBoundPipeline.program && "Expect a program when writing to push constants");
|
assert_invariant(mPipelineState.program && "Expect a program when writing to push constants");
|
||||||
assert_invariant(mCurrentRenderPass.commandBuffer && "Should be called within a renderpass");
|
assert_invariant(mCurrentRenderPass.commandBuffer && "Should be called within a renderpass");
|
||||||
mBoundPipeline.program->writePushConstant(mCurrentRenderPass.commandBuffer->buffer(),
|
mPipelineState.program->writePushConstant(mCurrentRenderPass.commandBuffer->buffer(),
|
||||||
mBoundPipeline.pipelineLayout, stage, index, value);
|
mPipelineState.pipelineLayout, stage, index, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::insertEventMarker(char const* string) {
|
void VulkanDriver::insertEventMarker(char const* string) {
|
||||||
@@ -1709,6 +1744,27 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||||
|
// We need to determine whether to delay bindning until draw().
|
||||||
|
mPipelineState.bindInDraw.first = false;
|
||||||
|
if (mAppState.hasExternalSamplers()) {
|
||||||
|
auto& layouts = pipelineState.pipelineLayout.setLayout;
|
||||||
|
auto haveExternalSamplers = [&](auto hwHandle) {
|
||||||
|
if (!hwHandle) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto layout =
|
||||||
|
resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, hwHandle);
|
||||||
|
return layout->hasExternalSamplers();
|
||||||
|
};
|
||||||
|
if (std::any_of(layouts.begin(), layouts.end(), haveExternalSamplers)) {
|
||||||
|
mPipelineState.bindInDraw = { true, pipelineState };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bindPipelineImpl(pipelineState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanDriver::bindPipelineImpl(PipelineState const& pipelineState) {
|
||||||
FVK_SYSTRACE_SCOPE();
|
FVK_SYSTRACE_SCOPE();
|
||||||
auto commands = mCurrentRenderPass.commandBuffer;
|
auto commands = mCurrentRenderPass.commandBuffer;
|
||||||
auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager,
|
auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager,
|
||||||
@@ -1777,10 +1833,11 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
|||||||
|
|
||||||
constexpr uint8_t descriptorSetMaskTable[4] = {0x1, 0x3, 0x7, 0xF};
|
constexpr uint8_t descriptorSetMaskTable[4] = {0x1, 0x3, 0x7, 0xF};
|
||||||
|
|
||||||
mBoundPipeline = {
|
mPipelineState = {
|
||||||
.program = program,
|
.program = program,
|
||||||
.pipelineLayout = pipelineLayout,
|
.pipelineLayout = pipelineLayout,
|
||||||
.descriptorSetMask = fvkutils::DescriptorSetMask(descriptorSetMaskTable[layoutCount]),
|
.descriptorSetMask = fvkutils::DescriptorSetMask(descriptorSetMaskTable[layoutCount]),
|
||||||
|
.bindInDraw = {false, {}},
|
||||||
};
|
};
|
||||||
|
|
||||||
mPipelineCache.bindLayout(pipelineLayout);
|
mPipelineCache.bindLayout(pipelineLayout);
|
||||||
@@ -1827,14 +1884,24 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
|||||||
FVK_SYSTRACE_SCOPE();
|
FVK_SYSTRACE_SCOPE();
|
||||||
VkCommandBuffer cmdbuffer = mCurrentRenderPass.commandBuffer->buffer();
|
VkCommandBuffer cmdbuffer = mCurrentRenderPass.commandBuffer->buffer();
|
||||||
|
|
||||||
|
if (mAppState.hasExternalSamplers()) {
|
||||||
|
auto const& [bindInDraw, pipelineSt] = mPipelineState.bindInDraw;
|
||||||
|
bool const hasUpdated =
|
||||||
|
mExternalImageManager.prepareBindSets(mDescriptorSetCache.getBoundSets());
|
||||||
|
if (bindInDraw || hasUpdated) {
|
||||||
|
bindPipelineImpl(pipelineSt);
|
||||||
|
}
|
||||||
|
mPipelineState.bindInDraw.first = false;
|
||||||
|
}
|
||||||
|
|
||||||
mDescriptorSetCache.commit(mCurrentRenderPass.commandBuffer,
|
mDescriptorSetCache.commit(mCurrentRenderPass.commandBuffer,
|
||||||
mBoundPipeline.pipelineLayout,
|
mPipelineState.pipelineLayout,
|
||||||
mBoundPipeline.descriptorSetMask);
|
mPipelineState.descriptorSetMask);
|
||||||
|
|
||||||
// Finally, make the actual draw call. TODO: support subranges
|
// Finally, make the actual draw call. TODO: support subranges
|
||||||
const uint32_t firstIndex = indexOffset;
|
uint32_t const firstIndex = indexOffset;
|
||||||
const int32_t vertexOffset = 0;
|
constexpr int32_t vertexOffset = 0;
|
||||||
const uint32_t firstInstId = 0;
|
constexpr uint32_t firstInstId = 0;
|
||||||
|
|
||||||
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
|
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,10 @@
|
|||||||
#include "VulkanSamplerCache.h"
|
#include "VulkanSamplerCache.h"
|
||||||
#include "VulkanStagePool.h"
|
#include "VulkanStagePool.h"
|
||||||
#include "VulkanQueryManager.h"
|
#include "VulkanQueryManager.h"
|
||||||
|
#include "VulkanYcbcrConversionCache.h"
|
||||||
#include "vulkan/VulkanDescriptorSetCache.h"
|
#include "vulkan/VulkanDescriptorSetCache.h"
|
||||||
#include "vulkan/VulkanDescriptorSetLayoutCache.h"
|
#include "vulkan/VulkanDescriptorSetLayoutCache.h"
|
||||||
|
#include "vulkan/VulkanExternalImageManager.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"
|
||||||
@@ -118,6 +120,7 @@ private:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void collectGarbage();
|
void collectGarbage();
|
||||||
|
void bindPipelineImpl(PipelineState const& pipelineState);
|
||||||
|
|
||||||
VulkanPlatform* mPlatform = nullptr;
|
VulkanPlatform* mPlatform = nullptr;
|
||||||
fvkmemory::ResourceManager mResourceManager;
|
fvkmemory::ResourceManager mResourceManager;
|
||||||
@@ -135,27 +138,36 @@ private:
|
|||||||
VulkanPipelineCache mPipelineCache;
|
VulkanPipelineCache mPipelineCache;
|
||||||
VulkanStagePool mStagePool;
|
VulkanStagePool mStagePool;
|
||||||
VulkanFboCache mFramebufferCache;
|
VulkanFboCache mFramebufferCache;
|
||||||
|
VulkanYcbcrConversionCache mYcbcrConversionCache;
|
||||||
VulkanSamplerCache mSamplerCache;
|
VulkanSamplerCache mSamplerCache;
|
||||||
VulkanBlitter mBlitter;
|
VulkanBlitter mBlitter;
|
||||||
VulkanReadPixels mReadPixels;
|
VulkanReadPixels mReadPixels;
|
||||||
VulkanDescriptorSetLayoutCache mDescriptorSetLayoutCache;
|
VulkanDescriptorSetLayoutCache mDescriptorSetLayoutCache;
|
||||||
VulkanDescriptorSetCache mDescriptorSetCache;
|
VulkanDescriptorSetCache mDescriptorSetCache;
|
||||||
VulkanQueryManager mQueryManager;
|
VulkanQueryManager mQueryManager;
|
||||||
|
VulkanExternalImageManager mExternalImageManager;
|
||||||
|
|
||||||
// This is necessary for us to write to push constants after binding a pipeline.
|
// This is necessary for us to write to push constants after binding a pipeline.
|
||||||
struct {
|
struct {
|
||||||
|
// For push constant
|
||||||
resource_ptr<VulkanProgram> program;
|
resource_ptr<VulkanProgram> program;
|
||||||
|
// For push commiting dynamic ubos in draw()
|
||||||
VkPipelineLayout pipelineLayout;
|
VkPipelineLayout pipelineLayout;
|
||||||
fvkutils::DescriptorSetMask descriptorSetMask;
|
fvkutils::DescriptorSetMask descriptorSetMask;
|
||||||
} mBoundPipeline = {};
|
|
||||||
|
|
||||||
// We need to store information about a render pass to enable better barriers at the end of a
|
std::pair<bool, PipelineState> bindInDraw = {false, {}};
|
||||||
// renderpass.
|
} mPipelineState = {};
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
using AttachmentArray =
|
// This tracks whether the app has seen external samplers bound to a the descriptor set.
|
||||||
fvkutils::StaticVector<VulkanAttachment, MAX_RENDERTARGET_ATTACHMENT_TEXTURES>;
|
// This will force bindPipeline to take a slow path.
|
||||||
AttachmentArray attachments;
|
bool hasExternalSamplerLayouts = false;
|
||||||
} mRenderPassFboInfo = {};
|
bool hasBoundExternalImages = false;
|
||||||
|
|
||||||
|
bool hasExternalSamplers() const noexcept {
|
||||||
|
return hasExternalSamplerLayouts && hasBoundExternalImages;
|
||||||
|
}
|
||||||
|
} mAppState;
|
||||||
|
|
||||||
bool const mIsSRGBSwapChainSupported;
|
bool const mIsSRGBSwapChainSupported;
|
||||||
backend::StereoscopicType const mStereoscopicType;
|
backend::StereoscopicType const mStereoscopicType;
|
||||||
|
|||||||
@@ -0,0 +1,280 @@
|
|||||||
|
/*
|
||||||
|
* 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 "VulkanExternalImageManager.h"
|
||||||
|
|
||||||
|
#include "VulkanDescriptorSetCache.h"
|
||||||
|
#include "VulkanDescriptorSetLayoutCache.h"
|
||||||
|
#include "VulkanSamplerCache.h"
|
||||||
|
#include "VulkanYcbcrConversionCache.h"
|
||||||
|
#include "vulkan/memory/ResourcePointer.h"
|
||||||
|
#include "vulkan/utils/Conversion.h"
|
||||||
|
|
||||||
|
#include <backend/platforms/VulkanPlatform.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace filament::backend {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void erasep(std::vector<T>& v, std::function<bool(T const&)> f) {
|
||||||
|
auto newEnd = std::remove_if(v.begin(), v.end(), f);
|
||||||
|
v.erase(newEnd, v.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous
|
||||||
|
|
||||||
|
VulkanExternalImageManager::VulkanExternalImageManager(VulkanPlatform* platform,
|
||||||
|
VulkanSamplerCache* samplerCache, VulkanYcbcrConversionCache* ycbcrConversionCache,
|
||||||
|
VulkanDescriptorSetCache* setCache, VulkanDescriptorSetLayoutCache* layoutCache)
|
||||||
|
: mPlatform(platform),
|
||||||
|
mSamplerCache(samplerCache),
|
||||||
|
mYcbcrConversionCache(ycbcrConversionCache),
|
||||||
|
mDescriptorSetCache(setCache),
|
||||||
|
mDescriptorSetLayoutCache(layoutCache) {
|
||||||
|
}
|
||||||
|
|
||||||
|
VulkanExternalImageManager::~VulkanExternalImageManager() = default;
|
||||||
|
|
||||||
|
void VulkanExternalImageManager::terminate() {
|
||||||
|
mSetAndLayouts.clear();
|
||||||
|
mSetBindings.clear();
|
||||||
|
mImages.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanExternalImageManager::onBeginFrame() {
|
||||||
|
std::for_each(mImages.begin(), mImages.end(), [](ImageData& image) {
|
||||||
|
image.hasBeenValidated = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanExternalImageManager::prepareBindSets(SetArray const& sets) {
|
||||||
|
bool hasUpdated = false;
|
||||||
|
for (auto set: sets) {
|
||||||
|
if (!set) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (auto itr = std::find_if(mSetAndLayouts.begin(), mSetAndLayouts.end(),
|
||||||
|
[&](auto const& setAndLayout) { return setAndLayout.first == set; });
|
||||||
|
itr != mSetAndLayouts.end()) {
|
||||||
|
hasUpdated = updateSetAndLayout(itr->first, itr->second) || hasUpdated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanExternalImageManager::updateSetAndLayout(
|
||||||
|
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||||
|
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||||
|
auto findImage = [&](fvkmemory::resource_ptr<VulkanTexture> texture) -> ImageData* {
|
||||||
|
auto itr = std::find_if(mImages.begin(), mImages.end(), [&](ImageData const& data) {
|
||||||
|
return data.ptr == texture;
|
||||||
|
});
|
||||||
|
assert_invariant(itr != mImages.end());
|
||||||
|
return &(*itr);
|
||||||
|
};
|
||||||
|
|
||||||
|
//std::vector<std::pair<uint8_t, ImageData*>> externalImages;
|
||||||
|
utils::FixedCapacityVector<std::pair<uint8_t, VkSampler>> samplerAndBindings;
|
||||||
|
samplerAndBindings.reserve(MAX_SAMPLER_COUNT);
|
||||||
|
|
||||||
|
bool hasImageUpdates = false;
|
||||||
|
for (auto& bindingInfo : mSetBindings) {
|
||||||
|
if (bindingInfo.set != set) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto imageData = findImage(bindingInfo.image);
|
||||||
|
hasImageUpdates = updateImage(imageData) || hasImageUpdates;
|
||||||
|
|
||||||
|
auto samplerParams = bindingInfo.samplerParams;
|
||||||
|
// according to spec, these must match chromaFilter
|
||||||
|
// https://registry.khronos.org/vulkan/specs/latest/man/html/VkSamplerCreateInfo.html#VUID-VkSamplerCreateInfo-minFilter-01645
|
||||||
|
samplerParams.filterMag = SamplerMagFilter::NEAREST;
|
||||||
|
samplerParams.filterMin = SamplerMinFilter::NEAREST;
|
||||||
|
|
||||||
|
auto sampler = mSamplerCache->getSampler({
|
||||||
|
.sampler = samplerParams,
|
||||||
|
.conversion = imageData->conversion,
|
||||||
|
});
|
||||||
|
samplerAndBindings.push_back({ bindingInfo.binding, sampler });
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to sort by binding number
|
||||||
|
std::sort(samplerAndBindings.begin(), samplerAndBindings.end());
|
||||||
|
|
||||||
|
utils::FixedCapacityVector<VkSampler> outSamplers;
|
||||||
|
outSamplers.reserve(MAX_SAMPLER_COUNT);
|
||||||
|
std::for_each(samplerAndBindings.begin(), samplerAndBindings.end(),
|
||||||
|
[&](auto const& b) { outSamplers.push_back(b.second); });
|
||||||
|
|
||||||
|
VkDescriptorSetLayout const oldLayout = layout->getVkLayout();
|
||||||
|
VkDescriptorSetLayout const newLayout =
|
||||||
|
mDescriptorSetLayoutCache->getVkLayout(layout->bitmask, outSamplers);
|
||||||
|
bool const hasLayoutUpdate = oldLayout != newLayout;
|
||||||
|
layout->setVkLayout(newLayout);
|
||||||
|
|
||||||
|
assert_invariant(
|
||||||
|
(!hasImageUpdates && !hasLayoutUpdate) ||
|
||||||
|
(hasImageUpdates && hasLayoutUpdate));
|
||||||
|
|
||||||
|
if (!hasLayoutUpdate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto foldBitsInHalf = [](auto bitset) {
|
||||||
|
constexpr size_t BITMASK_LOWER_BITS_LEN = sizeof(bitset) * 4;
|
||||||
|
decltype(bitset) outBitset;
|
||||||
|
bitset.forEachSetBit([&](size_t index) { outBitset.set(index % BITMASK_LOWER_BITS_LEN); });
|
||||||
|
return outBitset;
|
||||||
|
};
|
||||||
|
// We need to build a new descriptor set from the new layout
|
||||||
|
VkDescriptorSet oldSet = set->getVkSet();
|
||||||
|
VkDescriptorSet newSet = mDescriptorSetCache->getVkSet(layout);
|
||||||
|
|
||||||
|
using Bitmask = fvkutils::UniformBufferBitmask;
|
||||||
|
static_assert(sizeof(Bitmask) * 8 == fvkutils::MAX_DESCRIPTOR_SET_BITMASK_BITS);
|
||||||
|
|
||||||
|
auto const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
|
||||||
|
auto const samplers = layout->bitmask.sampler & (~layout->bitmask.externalSampler);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// TODO: fix the size for better memory
|
||||||
|
std::vector<VkCopyDescriptorSet> copies;
|
||||||
|
copyBindings.forEachSetBit([&](size_t index) {
|
||||||
|
copies.push_back({
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET,
|
||||||
|
.srcSet = oldSet,
|
||||||
|
.srcBinding = (uint32_t) index,
|
||||||
|
.dstSet = newSet,
|
||||||
|
.dstBinding = (uint32_t) index,
|
||||||
|
.descriptorCount = 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
vkUpdateDescriptorSets(mPlatform->getDevice(), 0, nullptr, copies.size(), copies.data());
|
||||||
|
|
||||||
|
set->setVkSet(newSet);
|
||||||
|
|
||||||
|
// We need to release the vkset, which is no longer used, back into the pool.
|
||||||
|
mDescriptorSetCache->manualRecyle(layout->count, oldLayout, oldSet);
|
||||||
|
|
||||||
|
// We need to update the external samplers in the set
|
||||||
|
for (auto& bindingInfo: mSetBindings) {
|
||||||
|
if (bindingInfo.set != set) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mDescriptorSetCache->updateSampler(set, bindingInfo.binding, bindingInfo.image,
|
||||||
|
VK_NULL_HANDLE);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSamplerYcbcrConversion VulkanExternalImageManager::getVkSamplerYcbcrConversion(
|
||||||
|
VulkanPlatform::ExternalImageMetadata const& metadata) {
|
||||||
|
// This external image does not require external sampler (YUV conversion).
|
||||||
|
if (metadata.externalFormat == 0) {
|
||||||
|
return VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
VulkanYcbcrConversionCache::Params ycbcrParams = {
|
||||||
|
.conversion = {
|
||||||
|
.ycbcrModel = fvkutils::getYcbcrModelConversionFilament(metadata.ycbcrModel),
|
||||||
|
.r = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.r, 0),
|
||||||
|
.g = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.g, 1),
|
||||||
|
.b = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.b, 2),
|
||||||
|
.a = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.a, 3),
|
||||||
|
.ycbcrRange = fvkutils::getYcbcrRangeFilament(metadata.ycbcrRange),
|
||||||
|
.xChromaOffset = fvkutils::getChromaLocationFilament(metadata.xChromaOffset),
|
||||||
|
.yChromaOffset = fvkutils::getChromaLocationFilament(metadata.yChromaOffset),
|
||||||
|
|
||||||
|
// Unclear where to get the chromaFilter, we just assume it's nearest.
|
||||||
|
.chromaFilter = SamplerMagFilter::NEAREST,
|
||||||
|
},
|
||||||
|
.format = metadata.filamentFormat,
|
||||||
|
.externalFormat = metadata.externalFormat,
|
||||||
|
};
|
||||||
|
return mYcbcrConversionCache->getConversion(ycbcrParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanExternalImageManager::updateImage(ImageData* image) {
|
||||||
|
if (image->hasBeenValidated) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
image->hasBeenValidated = true;
|
||||||
|
|
||||||
|
auto metadata = mPlatform->extractExternalImageMetadata(image->platformHandle);
|
||||||
|
auto vkYcbcr = getVkSamplerYcbcrConversion(metadata);
|
||||||
|
if (vkYcbcr == image->conversion) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
image->ptr->setYcbcrConversion(vkYcbcr, metadata.externalFormat != 0);
|
||||||
|
image->conversion = vkYcbcr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanExternalImageManager::addDescriptorSet(
|
||||||
|
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout,
|
||||||
|
fvkmemory::resource_ptr<VulkanDescriptorSet> set) {
|
||||||
|
mSetAndLayouts.push_back({set, layout});
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanExternalImageManager::removeDescriptorSet(
|
||||||
|
fvkmemory::resource_ptr<VulkanDescriptorSet> inSet) {
|
||||||
|
erasep<SetAndLayout>(mSetAndLayouts,
|
||||||
|
[&](auto const& setLayout) { return (setLayout.first == inSet); });
|
||||||
|
erasep<SetBindingInfo>(mSetBindings,
|
||||||
|
[&](auto const& bindingInfo) { return (bindingInfo.set == inSet); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanExternalImageManager::removeDescriptorSetLayout(
|
||||||
|
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> inLayout) {
|
||||||
|
erasep<SetAndLayout>(mSetAndLayouts,
|
||||||
|
[&](auto const& setLayout) { return (setLayout.second == inLayout); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanExternalImageManager::bindExternallySampledTexture(
|
||||||
|
fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t bindingPoint,
|
||||||
|
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams) {
|
||||||
|
// Should we do duplicate validation here?
|
||||||
|
mSetBindings.push_back({ bindingPoint, image, set, samplerParams });
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanExternalImageManager::addExternallySampledTexture(
|
||||||
|
fvkmemory::resource_ptr<VulkanTexture> image,
|
||||||
|
Platform::ExternalImageHandleRef platformHandleRef) {
|
||||||
|
mImages.push_back({ image, platformHandleRef, false });
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanExternalImageManager::removeExternallySampledTexture(
|
||||||
|
fvkmemory::resource_ptr<VulkanTexture> image) {
|
||||||
|
erasep<SetBindingInfo>(mSetBindings,
|
||||||
|
[&](auto const& bindingInfo) { return (bindingInfo.image == image); });
|
||||||
|
erasep<ImageData>(mImages, [&](auto const& imageData) { return imageData.ptr == image; });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanExternalImageManager::isExternallySampledTexture(
|
||||||
|
fvkmemory::resource_ptr<VulkanTexture> image) const {
|
||||||
|
return std::find_if(mImages.begin(), mImages.end(),
|
||||||
|
[&](auto const& imageData) { return imageData.ptr == image; }) != mImages.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namesapce filament::backend
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* 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_VULKANEXTERNALIMAGEMANAGER_H
|
||||||
|
#define TNT_FILAMENT_BACKEND_CACHING_VULKANEXTERNALIMAGEMANAGER_H
|
||||||
|
|
||||||
|
#include "VulkanHandles.h"
|
||||||
|
|
||||||
|
#include <backend/DriverEnums.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace filament::backend {
|
||||||
|
|
||||||
|
class VulkanYcbcrConversionCache;
|
||||||
|
class VulkanSamplerCache;
|
||||||
|
class VulkanDescriptorSetLayoutCache;
|
||||||
|
class VulkanDescriptorSetCache;
|
||||||
|
|
||||||
|
// Manages the logic of external images and their quirks wrt Vulikan.
|
||||||
|
class VulkanExternalImageManager {
|
||||||
|
public:
|
||||||
|
|
||||||
|
VulkanExternalImageManager(
|
||||||
|
VulkanPlatform* platform,
|
||||||
|
VulkanSamplerCache* samplerCache,
|
||||||
|
VulkanYcbcrConversionCache* ycbcrConversionCache,
|
||||||
|
VulkanDescriptorSetCache* setCache,
|
||||||
|
VulkanDescriptorSetLayoutCache* layoutCache);
|
||||||
|
|
||||||
|
~VulkanExternalImageManager();
|
||||||
|
|
||||||
|
void terminate();
|
||||||
|
|
||||||
|
void onBeginFrame();
|
||||||
|
|
||||||
|
using SetArray = std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>,
|
||||||
|
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||||
|
|
||||||
|
// This sets the currently bound layouts objects for the pipeline
|
||||||
|
bool prepareBindSets(SetArray const& layouts);
|
||||||
|
|
||||||
|
void addDescriptorSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout,
|
||||||
|
fvkmemory::resource_ptr<VulkanDescriptorSet> set);
|
||||||
|
|
||||||
|
void removeDescriptorSetLayout(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||||
|
|
||||||
|
void removeDescriptorSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
|
||||||
|
|
||||||
|
void bindExternallySampledTexture(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||||
|
uint8_t bindingPoint, fvkmemory::resource_ptr<VulkanTexture> image,
|
||||||
|
SamplerParams samplerParams);
|
||||||
|
|
||||||
|
void addExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> image,
|
||||||
|
Platform::ExternalImageHandleRef platformHandleRef);
|
||||||
|
|
||||||
|
void removeExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> image);
|
||||||
|
|
||||||
|
bool isExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> image) const;
|
||||||
|
|
||||||
|
VkSamplerYcbcrConversion getVkSamplerYcbcrConversion(
|
||||||
|
VulkanPlatform::ExternalImageMetadata const& metadata);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
struct ImageData {
|
||||||
|
fvkmemory::resource_ptr<VulkanTexture> ptr;
|
||||||
|
Platform::ExternalImageHandle platformHandle;
|
||||||
|
bool hasBeenValidated = false; // indicates whether the image has been validated *this frame*
|
||||||
|
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool updateSetAndLayout(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||||
|
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||||
|
bool updateImage(ImageData* imageData);
|
||||||
|
|
||||||
|
VulkanPlatform* mPlatform;
|
||||||
|
VulkanSamplerCache* mSamplerCache;
|
||||||
|
VulkanYcbcrConversionCache* mYcbcrConversionCache;
|
||||||
|
VulkanDescriptorSetCache* mDescriptorSetCache;
|
||||||
|
VulkanDescriptorSetLayoutCache* mDescriptorSetLayoutCache;
|
||||||
|
|
||||||
|
using SetAndLayout = std::pair<fvkmemory::resource_ptr<VulkanDescriptorSet>,
|
||||||
|
fvkmemory::resource_ptr<VulkanDescriptorSetLayout>>;
|
||||||
|
|
||||||
|
struct SetBindingInfo {
|
||||||
|
uint8_t binding = 0;
|
||||||
|
fvkmemory::resource_ptr<VulkanTexture> image;
|
||||||
|
fvkmemory::resource_ptr<VulkanDescriptorSet> set;
|
||||||
|
SamplerParams samplerParams;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use vectors instead of hash maps because we only expect small number of entries.
|
||||||
|
std::vector<SetAndLayout> mSetAndLayouts;
|
||||||
|
std::vector<SetBindingInfo> mSetBindings;
|
||||||
|
std::vector<ImageData> mImages;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // filament::backend
|
||||||
|
|
||||||
|
#endif // TNT_FILAMENT_BACKEND_CACHING_VULKANEXTERNALIMAGEMANAGER_H
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
#include <backend/platforms/VulkanPlatform.h>
|
#include <backend/platforms/VulkanPlatform.h>
|
||||||
|
|
||||||
|
#include <utils/compiler.h> // UTILS_FALLTHROUGH
|
||||||
#include <utils/Panic.h> // ASSERT_POSTCONDITION
|
#include <utils/Panic.h> // ASSERT_POSTCONDITION
|
||||||
|
|
||||||
using namespace bluevk;
|
using namespace bluevk;
|
||||||
@@ -89,8 +90,9 @@ BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// TODO: properly handle external sampler
|
|
||||||
case DescriptorType::SAMPLER_EXTERNAL:
|
case DescriptorType::SAMPLER_EXTERNAL:
|
||||||
|
fromStageFlags(binding.stageFlags, binding.binding, mask.externalSampler);
|
||||||
|
UTILS_FALLTHROUGH;
|
||||||
case DescriptorType::SAMPLER: {
|
case DescriptorType::SAMPLER: {
|
||||||
fromStageFlags(binding.stageFlags, binding.binding, mask.sampler);
|
fromStageFlags(binding.stageFlags, binding.binding, mask.sampler);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -66,18 +66,21 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
|
|||||||
|
|
||||||
// The bitmask representation of a set layout.
|
// The bitmask representation of a set layout.
|
||||||
struct Bitmask {
|
struct Bitmask {
|
||||||
// TODO: better utiltize the space below and use bitset instead.
|
|
||||||
fvkutils::UniformBufferBitmask ubo; // 8 bytes
|
fvkutils::UniformBufferBitmask ubo; // 8 bytes
|
||||||
fvkutils::UniformBufferBitmask dynamicUbo; // 8 bytes
|
fvkutils::UniformBufferBitmask dynamicUbo; // 8 bytes
|
||||||
fvkutils::SamplerBitmask sampler; // 8 bytes
|
fvkutils::SamplerBitmask sampler; // 8 bytes
|
||||||
fvkutils::InputAttachmentBitmask inputAttachment; // 8 bytes
|
fvkutils::InputAttachmentBitmask inputAttachment; // 8 bytes
|
||||||
|
|
||||||
|
// This is a subset of the bitmask.sampler field.
|
||||||
|
fvkutils::SamplerBitmask externalSampler; // 8 bytes
|
||||||
|
|
||||||
bool operator==(Bitmask const& right) const {
|
bool operator==(Bitmask const& right) const {
|
||||||
return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler &&
|
return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler &&
|
||||||
inputAttachment == right.inputAttachment;
|
inputAttachment == right.inputAttachment &&
|
||||||
|
externalSampler == right.externalSampler;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
static_assert(sizeof(Bitmask) == 32);
|
static_assert(sizeof(Bitmask) == 40);
|
||||||
|
|
||||||
// This is a convenience struct to quickly check layout compatibility in terms of descriptor set
|
// This is a convenience struct to quickly check layout compatibility in terms of descriptor set
|
||||||
// pools.
|
// pools.
|
||||||
@@ -119,10 +122,16 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
|
|||||||
|
|
||||||
VulkanDescriptorSetLayout(DescriptorSetLayout const& layout);
|
VulkanDescriptorSetLayout(DescriptorSetLayout const& layout);
|
||||||
|
|
||||||
|
// Note that we don't destroy the vklayout. This is done by the layout cache.
|
||||||
~VulkanDescriptorSetLayout() = default;
|
~VulkanDescriptorSetLayout() = default;
|
||||||
|
|
||||||
VkDescriptorSetLayout getVkLayout() const { return mVkLayout; }
|
VkDescriptorSetLayout const& getVkLayout() const noexcept { return mVkLayout; }
|
||||||
void setVkLayout(VkDescriptorSetLayout vklayout) { mVkLayout = vklayout; }
|
|
||||||
|
// It is possible to have the layout switch out due to AHardwarebuffer (external image) format
|
||||||
|
// changes.
|
||||||
|
void setVkLayout(VkDescriptorSetLayout vklayout) noexcept { mVkLayout = vklayout; }
|
||||||
|
|
||||||
|
bool hasExternalSamplers() const noexcept { return bitmask.externalSampler.count() > 0; }
|
||||||
|
|
||||||
Bitmask const bitmask;
|
Bitmask const bitmask;
|
||||||
Count const count;
|
Count const count;
|
||||||
@@ -137,12 +146,11 @@ public:
|
|||||||
// can use to repackage the vk handle.
|
// can use to repackage the vk handle.
|
||||||
using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
|
using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
|
||||||
|
|
||||||
VulkanDescriptorSet(VkDescriptorSet rawSet,
|
VulkanDescriptorSet(
|
||||||
fvkutils::UniformBufferBitmask const& dynamicUboMask,
|
fvkutils::UniformBufferBitmask const& dynamicUboMask,
|
||||||
uint8_t uniqueDynamicUboCount,
|
uint8_t uniqueDynamicUboCount,
|
||||||
OnRecycle&& onRecycleFn)
|
OnRecycle&& onRecycleFn)
|
||||||
: vkSet(rawSet),
|
: dynamicUboMask(dynamicUboMask),
|
||||||
dynamicUboMask(dynamicUboMask),
|
|
||||||
uniqueDynamicUboCount(uniqueDynamicUboCount),
|
uniqueDynamicUboCount(uniqueDynamicUboCount),
|
||||||
mOnRecycleFn(std::move(onRecycleFn)) {}
|
mOnRecycleFn(std::move(onRecycleFn)) {}
|
||||||
|
|
||||||
@@ -152,6 +160,13 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VkDescriptorSet const& getVkSet() const noexcept {
|
||||||
|
return mVkSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that the only case where you'd set it more than once is with external images/samplers.
|
||||||
|
void setVkSet(VkDescriptorSet vkset) noexcept { mVkSet = vkset; }
|
||||||
|
|
||||||
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
|
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
|
||||||
mOffsets = std::move(offsets);
|
mOffsets = std::move(offsets);
|
||||||
}
|
}
|
||||||
@@ -163,11 +178,11 @@ public:
|
|||||||
void acquire(fvkmemory::resource_ptr<VulkanTexture> texture);
|
void acquire(fvkmemory::resource_ptr<VulkanTexture> texture);
|
||||||
void acquire(fvkmemory::resource_ptr<VulkanBufferObject> buffer);
|
void acquire(fvkmemory::resource_ptr<VulkanBufferObject> buffer);
|
||||||
|
|
||||||
VkDescriptorSet const vkSet;
|
|
||||||
fvkutils::UniformBufferBitmask const dynamicUboMask;
|
fvkutils::UniformBufferBitmask const dynamicUboMask;
|
||||||
uint8_t const uniqueDynamicUboCount;
|
uint8_t const uniqueDynamicUboCount;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
VkDescriptorSet mVkSet = VK_NULL_HANDLE;
|
||||||
backend::DescriptorSetOffsetArray mOffsets;
|
backend::DescriptorSetOffsetArray mOffsets;
|
||||||
std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources;
|
std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources;
|
||||||
OnRecycle mOnRecycleFn;
|
OnRecycle mOnRecycleFn;
|
||||||
|
|||||||
@@ -18,24 +18,18 @@
|
|||||||
#define TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
|
#define TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
|
||||||
|
|
||||||
#include "VulkanCommands.h"
|
#include "VulkanCommands.h"
|
||||||
#include "VulkanMemory.h"
|
|
||||||
|
|
||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
#include <backend/TargetBufferInfo.h>
|
#include <backend/TargetBufferInfo.h>
|
||||||
|
|
||||||
#include "backend/Program.h"
|
|
||||||
|
|
||||||
#include <bluevk/BlueVK.h>
|
#include <bluevk/BlueVK.h>
|
||||||
|
|
||||||
#include <utils/bitset.h>
|
#include <utils/bitset.h>
|
||||||
#include <utils/compiler.h>
|
#include <utils/compiler.h>
|
||||||
#include <utils/Hash.h>
|
#include <utils/Hash.h>
|
||||||
|
|
||||||
#include <list>
|
|
||||||
#include <tsl/robin_map.h>
|
#include <tsl/robin_map.h>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "vulkan/VulkanSamplerCache.h"
|
#include "VulkanSamplerCache.h"
|
||||||
|
|
||||||
|
#include "VulkanConstants.h"
|
||||||
#include "vulkan/utils/Conversion.h"
|
#include "vulkan/utils/Conversion.h"
|
||||||
|
#include "vulkan/vulkan_core.h"
|
||||||
|
|
||||||
#include <utils/Panic.h>
|
#include <utils/Panic.h>
|
||||||
|
|
||||||
@@ -26,41 +29,48 @@ namespace filament::backend {
|
|||||||
VulkanSamplerCache::VulkanSamplerCache(VkDevice device)
|
VulkanSamplerCache::VulkanSamplerCache(VkDevice device)
|
||||||
: mDevice(device) {}
|
: mDevice(device) {}
|
||||||
|
|
||||||
VkSampler VulkanSamplerCache::getSampler(SamplerParams params) noexcept {
|
VkSampler VulkanSamplerCache::getSampler(Params params) noexcept {
|
||||||
auto iter = mCache.find(params);
|
auto iter = mCache.find(params);
|
||||||
if (UTILS_LIKELY(iter != mCache.end())) {
|
if (UTILS_LIKELY(iter != mCache.end())) {
|
||||||
return iter->second;
|
return iter->second;
|
||||||
}
|
}
|
||||||
VkSamplerCreateInfo samplerInfo {
|
VkSamplerYcbcrConversionInfo ycbcrConversion = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
|
||||||
|
.conversion = params.conversion,
|
||||||
|
};
|
||||||
|
|
||||||
|
auto const& samplerParams = params.sampler;
|
||||||
|
VkSamplerCreateInfo samplerInfo{
|
||||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||||
.magFilter = fvkutils::getFilter(params.filterMag),
|
.pNext = params.conversion != VK_NULL_HANDLE ? &ycbcrConversion : VK_NULL_HANDLE,
|
||||||
.minFilter = fvkutils::getFilter(params.filterMin),
|
.magFilter = fvkutils::getFilter(samplerParams.filterMag),
|
||||||
.mipmapMode = fvkutils::getMipmapMode(params.filterMin),
|
.minFilter = fvkutils::getFilter(samplerParams.filterMin),
|
||||||
.addressModeU = fvkutils::getWrapMode(params.wrapS),
|
.mipmapMode = fvkutils::getMipmapMode(samplerParams.filterMin),
|
||||||
.addressModeV = fvkutils::getWrapMode(params.wrapT),
|
.addressModeU = fvkutils::getWrapMode(samplerParams.wrapS),
|
||||||
.addressModeW = fvkutils::getWrapMode(params.wrapR),
|
.addressModeV = fvkutils::getWrapMode(samplerParams.wrapT),
|
||||||
.anisotropyEnable = params.anisotropyLog2 == 0 ? VK_FALSE : VK_TRUE,
|
.addressModeW = fvkutils::getWrapMode(samplerParams.wrapR),
|
||||||
.maxAnisotropy = (float)(1u << params.anisotropyLog2),
|
.anisotropyEnable = samplerParams.anisotropyLog2 == 0 ? VK_FALSE : VK_TRUE,
|
||||||
.compareEnable = fvkutils::getCompareEnable(params.compareMode),
|
.maxAnisotropy = (float) (1u << samplerParams.anisotropyLog2),
|
||||||
.compareOp = fvkutils::getCompareOp(params.compareFunc),
|
.compareEnable = fvkutils::getCompareEnable(samplerParams.compareMode),
|
||||||
|
.compareOp = fvkutils::getCompareOp(samplerParams.compareFunc),
|
||||||
.minLod = 0.0f,
|
.minLod = 0.0f,
|
||||||
.maxLod = fvkutils::getMaxLod(params.filterMin),
|
.maxLod = fvkutils::getMaxLod(samplerParams.filterMin),
|
||||||
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
|
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
|
||||||
.unnormalizedCoordinates = VK_FALSE
|
.unnormalizedCoordinates = VK_FALSE,
|
||||||
};
|
};
|
||||||
VkSampler sampler;
|
VkSampler sampler;
|
||||||
VkResult result = vkCreateSampler(mDevice, &samplerInfo, VKALLOC, &sampler);
|
VkResult result = vkCreateSampler(mDevice, &samplerInfo, VKALLOC, &sampler);
|
||||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create sampler."
|
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create sampler."
|
||||||
<< " error=" << static_cast<int32_t>(result);
|
<< " error=" << static_cast<int32_t>(result);
|
||||||
mCache.insert({params, sampler});
|
mCache.insert({ params, sampler });
|
||||||
return sampler;
|
return sampler;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanSamplerCache::terminate() noexcept {
|
void VulkanSamplerCache::terminate() noexcept {
|
||||||
for (auto pair : mCache) {
|
for (auto pair: mCache) {
|
||||||
vkDestroySampler(mDevice, pair.second, VKALLOC);
|
vkDestroySampler(mDevice, pair.second, VKALLOC);
|
||||||
}
|
}
|
||||||
mCache.clear();
|
mCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace filament::backend
|
}// namespace filament::backend
|
||||||
|
|||||||
@@ -17,8 +17,11 @@
|
|||||||
#ifndef TNT_FILAMENT_BACKEND_VULKANSAMPLERCACHE_H
|
#ifndef TNT_FILAMENT_BACKEND_VULKANSAMPLERCACHE_H
|
||||||
#define TNT_FILAMENT_BACKEND_VULKANSAMPLERCACHE_H
|
#define TNT_FILAMENT_BACKEND_VULKANSAMPLERCACHE_H
|
||||||
|
|
||||||
#include "VulkanContext.h"
|
#include <backend/DriverEnums.h>
|
||||||
|
|
||||||
|
#include <utils/Hash.h>
|
||||||
|
|
||||||
|
#include <bluevk/BlueVK.h>
|
||||||
#include <tsl/robin_map.h>
|
#include <tsl/robin_map.h>
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
@@ -26,12 +29,28 @@ namespace filament::backend {
|
|||||||
// Simple manager for VkSampler objects.
|
// Simple manager for VkSampler objects.
|
||||||
class VulkanSamplerCache {
|
class VulkanSamplerCache {
|
||||||
public:
|
public:
|
||||||
|
struct Params {
|
||||||
|
SamplerParams sampler = {};
|
||||||
|
uint32_t padding = 0;
|
||||||
|
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(Params) == 16);
|
||||||
|
|
||||||
explicit VulkanSamplerCache(VkDevice device);
|
explicit VulkanSamplerCache(VkDevice device);
|
||||||
VkSampler getSampler(SamplerParams params) noexcept;
|
VkSampler getSampler(Params params) noexcept;
|
||||||
void terminate() noexcept;
|
void terminate() noexcept;
|
||||||
private:
|
private:
|
||||||
VkDevice mDevice;
|
VkDevice mDevice;
|
||||||
tsl::robin_map<SamplerParams, VkSampler, SamplerParams::Hasher, SamplerParams::EqualTo> mCache;
|
|
||||||
|
struct SamplerEqualTo {
|
||||||
|
bool operator()(Params lhs, Params rhs) const noexcept {
|
||||||
|
SamplerParams::EqualTo equal;
|
||||||
|
return equal(lhs.sampler, rhs.sampler) && lhs.conversion == rhs.conversion;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using SamplerHashFn = utils::hash::MurmurHashFn<Params>;
|
||||||
|
tsl::robin_map<Params, VkSampler, SamplerHashFn, SamplerEqualTo> mCache;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
|
|||||||
VulkanCommands* commands, VulkanStagePool& stagePool, void* nativeWindow, uint64_t flags,
|
VulkanCommands* commands, VulkanStagePool& stagePool, void* nativeWindow, uint64_t flags,
|
||||||
VkExtent2D extent)
|
VkExtent2D extent)
|
||||||
: mPlatform(platform),
|
: mPlatform(platform),
|
||||||
|
mContext(context),
|
||||||
mResourceManager(resourceManager),
|
mResourceManager(resourceManager),
|
||||||
mCommands(commands),
|
mCommands(commands),
|
||||||
mAllocator(allocator),
|
mAllocator(allocator),
|
||||||
@@ -77,16 +78,16 @@ void VulkanSwapChain::update() {
|
|||||||
}
|
}
|
||||||
for (auto const color: bundle.colors) {
|
for (auto const color: bundle.colors) {
|
||||||
auto colorTexture = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager,
|
auto colorTexture = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager,
|
||||||
device, mAllocator, mResourceManager, mCommands, color, VK_NULL_HANDLE,
|
mContext, device, mAllocator, mResourceManager, mCommands, color, VK_NULL_HANDLE,
|
||||||
bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height, bundle.layerCount, colorUsage,
|
bundle.colorFormat, VK_NULL_HANDLE /*ycrcb */, 1, bundle.extent.width,
|
||||||
mStagePool);
|
bundle.extent.height, bundle.layerCount, colorUsage, mStagePool);
|
||||||
mColors.push_back(colorTexture);
|
mColors.push_back(colorTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
mDepth = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager, device,
|
mDepth = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager, mContext, device,
|
||||||
mAllocator, mResourceManager, mCommands, bundle.depth, VK_NULL_HANDLE,
|
mAllocator, mResourceManager, mCommands, bundle.depth, VK_NULL_HANDLE,
|
||||||
bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height, bundle.layerCount, depthUsage,
|
bundle.depthFormat, VK_NULL_HANDLE /*ycrcb */, 1, bundle.extent.width,
|
||||||
mStagePool);
|
bundle.extent.height, bundle.layerCount, depthUsage, mStagePool);
|
||||||
|
|
||||||
mExtent = bundle.extent;
|
mExtent = bundle.extent;
|
||||||
mLayerCount = bundle.layerCount;
|
mLayerCount = bundle.layerCount;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "DriverBase.h"
|
#include "DriverBase.h"
|
||||||
|
|
||||||
|
#include "VulkanConstants.h"
|
||||||
#include "VulkanContext.h"
|
#include "VulkanContext.h"
|
||||||
#include "VulkanTexture.h"
|
#include "VulkanTexture.h"
|
||||||
#include "vulkan/memory/Resource.h"
|
#include "vulkan/memory/Resource.h"
|
||||||
@@ -28,7 +29,6 @@
|
|||||||
#include <bluevk/BlueVK.h>
|
#include <bluevk/BlueVK.h>
|
||||||
#include <utils/FixedCapacityVector.h>
|
#include <utils/FixedCapacityVector.h>
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
using namespace bluevk;
|
using namespace bluevk;
|
||||||
|
|
||||||
@@ -83,6 +83,7 @@ private:
|
|||||||
void update();
|
void update();
|
||||||
|
|
||||||
VulkanPlatform* mPlatform;
|
VulkanPlatform* mPlatform;
|
||||||
|
VulkanContext const& mContext;
|
||||||
fvkmemory::ResourceManager* mResourceManager;
|
fvkmemory::ResourceManager* mResourceManager;
|
||||||
VulkanCommands* mCommands;
|
VulkanCommands* mCommands;
|
||||||
VmaAllocator mAllocator;
|
VmaAllocator mAllocator;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "VulkanCommands.h"
|
#include "VulkanCommands.h"
|
||||||
|
#include "VulkanContext.h"
|
||||||
#include "VulkanMemory.h"
|
#include "VulkanMemory.h"
|
||||||
#include "VulkanTexture.h"
|
#include "VulkanTexture.h"
|
||||||
#include "vulkan/memory/ResourcePointer.h"
|
#include "vulkan/memory/ResourcePointer.h"
|
||||||
@@ -54,7 +55,6 @@ VkComponentMapping composeSwizzle(VkComponentMapping const& prev, VkComponentMap
|
|||||||
VK_COMPONENT_SWIZZLE_B,
|
VK_COMPONENT_SWIZZLE_B,
|
||||||
VK_COMPONENT_SWIZZLE_A,
|
VK_COMPONENT_SWIZZLE_A,
|
||||||
};
|
};
|
||||||
|
|
||||||
auto const compose = [](VkComponentMapping const& prev,
|
auto const compose = [](VkComponentMapping const& prev,
|
||||||
VkComponentMapping const& next) -> VkComponentMapping {
|
VkComponentMapping const& next) -> VkComponentMapping {
|
||||||
VkComponentSwizzle vals[4] = { next.r, next.g, next.b, next.a };
|
VkComponentSwizzle vals[4] = { next.r, next.g, next.b, next.a };
|
||||||
@@ -151,35 +151,152 @@ uint8_t getLayerCountFromDepth(uint32_t const depth) {
|
|||||||
return getLayerCount(getSamplerTypeFromDepth(depth), depth);
|
return getLayerCount(getSamplerTypeFromDepth(depth), depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VkImageUsageFlags getUsage(VulkanContext const& context, uint8_t samples,
|
||||||
|
VkPhysicalDevice physicalDevice, VkFormat vkFormat, TextureUsage tusage) {
|
||||||
|
VkImageUsageFlags usage = {};
|
||||||
|
if (any(tusage & TextureUsage::BLIT_SRC)) {
|
||||||
|
usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||||
|
}
|
||||||
|
if (any(tusage & TextureUsage::BLIT_DST)) {
|
||||||
|
usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if we can use the transient usage flag combined with lazily allocated memory.
|
||||||
|
const bool useTransientAttachment =
|
||||||
|
// Lazily allocated memory is available.
|
||||||
|
context.isLazilyAllocatedMemorySupported() &&
|
||||||
|
// Usage consists of attachment flags only.
|
||||||
|
none(tusage & ~TextureUsage::ALL_ATTACHMENTS) &&
|
||||||
|
// Usage contains at least one attachment flag.
|
||||||
|
any(tusage & TextureUsage::ALL_ATTACHMENTS) &&
|
||||||
|
// Depth resolve cannot use transient attachment because it uses a custom shader.
|
||||||
|
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
|
||||||
|
// restriction.
|
||||||
|
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
|
||||||
|
// and above to be able to support stencil resolve (along with depth).
|
||||||
|
!(any(tusage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
|
||||||
|
|
||||||
|
const VkImageUsageFlags transientFlag =
|
||||||
|
useTransientAttachment ? VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : 0U;
|
||||||
|
|
||||||
|
if (any(tusage & TextureUsage::SAMPLEABLE)) {
|
||||||
|
|
||||||
|
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||||
|
if (physicalDevice != VK_NULL_HANDLE) {
|
||||||
|
// Validate that the format is actually sampleable.
|
||||||
|
VkFormatProperties props;
|
||||||
|
vkGetPhysicalDeviceFormatProperties(physicalDevice, vkFormat, &props);
|
||||||
|
if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
|
||||||
|
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << mState->mVkFormat << " is not "
|
||||||
|
"sampleable with optimal tiling." << utils::io::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||||
|
}
|
||||||
|
if (any(tusage & TextureUsage::COLOR_ATTACHMENT)) {
|
||||||
|
usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | transientFlag;
|
||||||
|
if (any(tusage & TextureUsage::SUBPASS_INPUT)) {
|
||||||
|
usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (any(tusage & TextureUsage::STENCIL_ATTACHMENT)) {
|
||||||
|
usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag;
|
||||||
|
}
|
||||||
|
if (any(tusage & TextureUsage::UPLOADABLE)) {
|
||||||
|
usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||||
|
}
|
||||||
|
if (any(tusage & TextureUsage::DEPTH_ATTACHMENT)) {
|
||||||
|
usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag;
|
||||||
|
|
||||||
|
// Depth resolves uses a custom shader and therefore needs to be sampleable.
|
||||||
|
if (samples > 1) {
|
||||||
|
usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return usage;
|
||||||
|
}
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
VulkanTextureState::VulkanTextureState(VkDevice device, VmaAllocator allocator,
|
VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands,
|
||||||
VulkanCommands* commands, VulkanStagePool& stagePool, VkFormat format,
|
VmaAllocator allocator, VkDevice device, VkImage image, VkDeviceMemory deviceMemory,
|
||||||
VkImageViewType viewType, uint8_t levels, uint8_t layerCount, VulkanLayout defaultLayout,
|
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount,
|
||||||
|
VkSamplerYcbcrConversion ycbcrConversion, bool isExternalFormat, VkImageUsageFlags usage,
|
||||||
bool isProtected)
|
bool isProtected)
|
||||||
: mVkFormat(format),
|
: mStagePool(stagePool),
|
||||||
mViewType(viewType),
|
|
||||||
mFullViewRange{fvkutils::getImageAspect(format), 0, levels, 0, layerCount},
|
|
||||||
mDefaultLayout(defaultLayout),
|
|
||||||
mIsProtected(isProtected),
|
|
||||||
mStagePool(stagePool),
|
|
||||||
mDevice(device),
|
|
||||||
mAllocator(allocator),
|
|
||||||
mCommands(commands),
|
mCommands(commands),
|
||||||
mIsTransientAttachment(false) {}
|
mAllocator(allocator),
|
||||||
|
mDevice(device),
|
||||||
|
mTextureImage(image),
|
||||||
|
mTextureImageMemory(deviceMemory),
|
||||||
|
mVkFormat(format),
|
||||||
|
mViewType(viewType),
|
||||||
|
mFullViewRange{ fvkutils::getImageAspect(format), 0, levels, 0, layerCount },
|
||||||
|
mYcbcr{ ycbcrConversion, isExternalFormat },
|
||||||
|
mDefaultLayout(getDefaultLayoutImpl(usage)),
|
||||||
|
mUsage(usage),
|
||||||
|
mIsProtected(isProtected) {}
|
||||||
|
|
||||||
// Constructor for internally passed VkImage
|
VulkanTextureState::~VulkanTextureState() {
|
||||||
VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator,
|
if (mTextureImageMemory != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyImage(mDevice, mTextureImage, VKALLOC);
|
||||||
|
vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC);
|
||||||
|
}
|
||||||
|
clearCachedImageViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanTextureState::clearCachedImageViews() noexcept {
|
||||||
|
for (auto entry: mCachedImageViews) {
|
||||||
|
vkDestroyImageView(mDevice, entry.second, VKALLOC);
|
||||||
|
}
|
||||||
|
mCachedImageViews.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageView VulkanTextureState::getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
|
||||||
|
VkComponentMapping swizzle) {
|
||||||
|
ImageViewKey const key{ range, viewType, swizzle };
|
||||||
|
if (auto iter = mCachedImageViews.find(key); iter != mCachedImageViews.end()) {
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSamplerYcbcrConversionInfo conversionInfo = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
|
||||||
|
.conversion = mYcbcr.conversion,
|
||||||
|
};
|
||||||
|
|
||||||
|
VkImageViewCreateInfo viewInfo = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||||
|
.pNext = mYcbcr.conversion != VK_NULL_HANDLE ? &conversionInfo : nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.image = mTextureImage,
|
||||||
|
.viewType = viewType,
|
||||||
|
.format = mYcbcr.isExternalFormat ? VK_FORMAT_UNDEFINED : mVkFormat,
|
||||||
|
.components = swizzle,
|
||||||
|
.subresourceRange = range,
|
||||||
|
};
|
||||||
|
VkImageView imageView;
|
||||||
|
vkCreateImageView(mDevice, &viewInfo, VKALLOC, &imageView);
|
||||||
|
mCachedImageViews.emplace(key, imageView);
|
||||||
|
return imageView;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor for internally passed VkImage - including swapchain images and external images.
|
||||||
|
VulkanTexture::VulkanTexture(VulkanContext const& context, VkDevice device, VmaAllocator allocator,
|
||||||
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, VkImage image,
|
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, VkImage image,
|
||||||
VkDeviceMemory memory, VkFormat format, uint8_t samples, uint32_t width,
|
VkDeviceMemory memory, VkFormat format, VkSamplerYcbcrConversion conversion,
|
||||||
uint32_t height, uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool)
|
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage tusage,
|
||||||
: HwTexture(getSamplerTypeFromDepth(depth), 1, samples, width, height, depth, TextureFormat::UNUSED,
|
VulkanStagePool& stagePool)
|
||||||
tusage),
|
: HwTexture(getSamplerTypeFromDepth(depth), 1, samples, width, height, depth,
|
||||||
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, device,
|
TextureFormat::UNUSED, tusage),
|
||||||
allocator, commands, stagePool, format, fvkutils::getViewType(SamplerType::SAMPLER_2D),
|
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, stagePool,
|
||||||
/*mipLevels=*/1, getLayerCountFromDepth(depth), getDefaultLayoutImpl(tusage), any(usage & TextureUsage::PROTECTED))) {
|
commands, allocator, device, image, memory, format,
|
||||||
mState->mTextureImage = image;
|
fvkutils::getViewType(SamplerType::SAMPLER_2D),
|
||||||
mState->mTextureImageMemory = memory;
|
/*mipLevels=*/1, getLayerCountFromDepth(depth), conversion,
|
||||||
|
/*isExternalFormat=*/false,
|
||||||
|
getUsage(context, samples, VK_NULL_HANDLE, format, tusage),
|
||||||
|
any(usage & TextureUsage::PROTECTED))) {
|
||||||
mPrimaryViewRange = mState->mFullViewRange;
|
mPrimaryViewRange = mState->mFullViewRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,21 +306,19 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
|||||||
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, SamplerType target,
|
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, SamplerType target,
|
||||||
uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h,
|
uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h,
|
||||||
uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool)
|
uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool)
|
||||||
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage),
|
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage) {
|
||||||
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, device,
|
|
||||||
allocator, commands, stagePool, fvkutils::getVkFormat(tformat),
|
|
||||||
fvkutils::getViewType(target), levels, getLayerCount(target, depth),
|
|
||||||
VulkanLayout::UNDEFINED, any(usage & TextureUsage::PROTECTED))) {
|
|
||||||
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
|
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
|
||||||
|
VkFormat const vkFormat = fvkutils::getVkFormat(tformat);
|
||||||
|
bool const isProtected = any(tusage & TextureUsage::PROTECTED);
|
||||||
VkImageCreateInfo imageInfo{
|
VkImageCreateInfo imageInfo{
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||||
.imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,
|
.imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,
|
||||||
.format = mState->mVkFormat,
|
.format = vkFormat,
|
||||||
.extent = {w, h, depth},
|
.extent = {w, h, depth},
|
||||||
.mipLevels = levels,
|
.mipLevels = levels,
|
||||||
.arrayLayers = 1,
|
.arrayLayers = 1,
|
||||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||||
.usage = 0,
|
.usage = getUsage(context, samples, physicalDevice, vkFormat, tusage),
|
||||||
};
|
};
|
||||||
if (target == SamplerType::SAMPLER_3D && any(tusage & TextureUsage::ALL_ATTACHMENTS)) {
|
if (target == SamplerType::SAMPLER_3D && any(tusage & TextureUsage::ALL_ATTACHMENTS)) {
|
||||||
if (context.isImageView2DOn3DImageSupported()) {
|
if (context.isImageView2DOn3DImageSupported()) {
|
||||||
@@ -233,79 +348,17 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
|||||||
imageInfo.arrayLayers = depth * 6;
|
imageInfo.arrayLayers = depth * 6;
|
||||||
imageInfo.extent.depth = 1;
|
imageInfo.extent.depth = 1;
|
||||||
}
|
}
|
||||||
if (any(usage & TextureUsage::PROTECTED)) {
|
if (isProtected) {
|
||||||
imageInfo.flags |= VK_IMAGE_CREATE_PROTECTED_BIT;
|
imageInfo.flags |= VK_IMAGE_CREATE_PROTECTED_BIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (any(usage & TextureUsage::BLIT_SRC)) {
|
|
||||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
|
||||||
}
|
|
||||||
if (any(usage & TextureUsage::BLIT_DST)) {
|
|
||||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if we can use the transient usage flag combined with lazily allocated memory.
|
|
||||||
const bool useTransientAttachment =
|
|
||||||
// Lazily allocated memory is available.
|
|
||||||
context.isLazilyAllocatedMemorySupported() &&
|
|
||||||
// Usage consists of attachment flags only.
|
|
||||||
none(tusage & ~TextureUsage::ALL_ATTACHMENTS) &&
|
|
||||||
// Usage contains at least one attachment flag.
|
|
||||||
any(tusage & TextureUsage::ALL_ATTACHMENTS) &&
|
|
||||||
// Depth resolve cannot use transient attachment because it uses a custom shader.
|
|
||||||
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
|
|
||||||
// restriction.
|
|
||||||
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
|
|
||||||
// and above to be able to support stencil resolve (along with depth).
|
|
||||||
!(any(usage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
|
|
||||||
|
|
||||||
mState->mIsTransientAttachment = useTransientAttachment;
|
|
||||||
|
|
||||||
const VkImageUsageFlags transientFlag =
|
|
||||||
useTransientAttachment ? VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : 0U;
|
|
||||||
|
|
||||||
if (any(usage & TextureUsage::SAMPLEABLE)) {
|
|
||||||
|
|
||||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
|
||||||
// Validate that the format is actually sampleable.
|
|
||||||
VkFormatProperties props;
|
|
||||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, mState->mVkFormat, &props);
|
|
||||||
if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
|
|
||||||
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << mState->mVkFormat << " is not "
|
|
||||||
"sampleable with optimal tiling." << utils::io::endl;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
||||||
}
|
|
||||||
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
|
|
||||||
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | transientFlag;
|
|
||||||
if (any(usage & TextureUsage::SUBPASS_INPUT)) {
|
|
||||||
imageInfo.usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (any(usage & TextureUsage::STENCIL_ATTACHMENT)) {
|
|
||||||
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag;
|
|
||||||
}
|
|
||||||
if (any(usage & TextureUsage::UPLOADABLE)) {
|
|
||||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
||||||
}
|
|
||||||
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
|
|
||||||
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag;
|
|
||||||
|
|
||||||
// Depth resolves uses a custom shader and therefore needs to be sampleable.
|
|
||||||
if (samples > 1) {
|
|
||||||
imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constrain the sample count according to the sample count masks in VkPhysicalDeviceProperties.
|
// Constrain the sample count according to the sample count masks in VkPhysicalDeviceProperties.
|
||||||
// Note that VulkanRenderTarget holds a single MSAA count, so we play it safe if this is used as
|
// Note that VulkanRenderTarget holds a single MSAA count, so we play it safe if this is used as
|
||||||
// any kind of attachment (color or depth).
|
// any kind of attachment (color or depth).
|
||||||
const auto& limits = context.getPhysicalDeviceLimits();
|
auto const& limits = context.getPhysicalDeviceLimits();
|
||||||
if (imageInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) {
|
if (imageInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) {
|
||||||
samples = fvkutils::reduceSampleCount(samples,
|
samples = fvkutils::reduceSampleCount(samples,
|
||||||
fvkutils::isVkDepthFormat(mState->mVkFormat)
|
fvkutils::isVkDepthFormat(vkFormat)
|
||||||
? limits.sampledImageDepthSampleCounts
|
? limits.sampledImageDepthSampleCounts
|
||||||
: limits.sampledImageColorSampleCounts);
|
: limits.sampledImageColorSampleCounts);
|
||||||
}
|
}
|
||||||
@@ -319,33 +372,36 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
|||||||
this->samples = samples;
|
this->samples = samples;
|
||||||
imageInfo.samples = (VkSampleCountFlagBits) samples;
|
imageInfo.samples = (VkSampleCountFlagBits) samples;
|
||||||
|
|
||||||
VkResult result = vkCreateImage(mState->mDevice, &imageInfo, VKALLOC, &mState->mTextureImage);
|
VkImage textureImage;
|
||||||
|
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &textureImage);
|
||||||
if (result != VK_SUCCESS || FVK_ENABLED(FVK_DEBUG_TEXTURE)) {
|
if (result != VK_SUCCESS || FVK_ENABLED(FVK_DEBUG_TEXTURE)) {
|
||||||
FVK_LOGD << "vkCreateImage: "
|
FVK_LOGD << "vkCreateImage: "
|
||||||
<< "image = " << mState->mTextureImage << ", "
|
<< "image = " << textureImage << ", "
|
||||||
<< "result = " << result << ", "
|
<< "result = " << result << ", "
|
||||||
<< "handle = " << utils::io::hex << mState->mTextureImage << utils::io::dec << ", "
|
<< "handle = " << utils::io::hex << textureImage << utils::io::dec << ", "
|
||||||
<< "extent = " << w << "x" << h << "x"<< depth << ", "
|
<< "extent = " << w << "x" << h << "x"<< depth << ", "
|
||||||
<< "mipLevels = " << int(levels) << ", "
|
<< "mipLevels = " << int(levels) << ", "
|
||||||
<< "TextureUsage = " << static_cast<int>(usage) << ", "
|
<< "TextureUsage = " << static_cast<int>(tusage) << ", "
|
||||||
<< "usage = " << imageInfo.usage << ", "
|
<< "usage = " << imageInfo.usage << ", "
|
||||||
<< "samples = " << imageInfo.samples << ", "
|
<< "samples = " << imageInfo.samples << ", "
|
||||||
<< "type = " << imageInfo.imageType << ", "
|
<< "type = " << imageInfo.imageType << ", "
|
||||||
<< "flags = " << imageInfo.flags << ", "
|
<< "flags = " << imageInfo.flags << ", "
|
||||||
<< "target = " << static_cast<int>(target) <<", "
|
<< "target = " << static_cast<int>(target) <<", "
|
||||||
<< "format = " << mState->mVkFormat << utils::io::endl;
|
<< "format = " << vkFormat << utils::io::endl;
|
||||||
}
|
}
|
||||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create image."
|
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create image."
|
||||||
<< " error=" << static_cast<int32_t>(result);
|
<< " error=" << static_cast<int32_t>(result);
|
||||||
|
|
||||||
// Allocate memory for the VkImage and bind it.
|
// Allocate memory for the VkImage and bind it.
|
||||||
VkMemoryRequirements memReqs = {};
|
VkMemoryRequirements memReqs;
|
||||||
vkGetImageMemoryRequirements(mState->mDevice, mState->mTextureImage, &memReqs);
|
vkGetImageMemoryRequirements(device, textureImage, &memReqs);
|
||||||
|
|
||||||
const VkFlags requiredMemoryFlags =
|
bool const useTransientAttachment = imageInfo.usage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
|
||||||
|
|
||||||
|
VkFlags const requiredMemoryFlags =
|
||||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
|
||||||
(useTransientAttachment ? VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT : 0U) |
|
(useTransientAttachment ? VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT : 0U) |
|
||||||
(mState->mIsProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT : 0U);
|
(isProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT : 0U);
|
||||||
uint32_t memoryTypeIndex
|
uint32_t memoryTypeIndex
|
||||||
= context.selectMemoryType(memReqs.memoryTypeBits, requiredMemoryFlags);
|
= context.selectMemoryType(memReqs.memoryTypeBits, requiredMemoryFlags);
|
||||||
|
|
||||||
@@ -357,21 +413,22 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
|||||||
.allocationSize = memReqs.size,
|
.allocationSize = memReqs.size,
|
||||||
.memoryTypeIndex = memoryTypeIndex,
|
.memoryTypeIndex = memoryTypeIndex,
|
||||||
};
|
};
|
||||||
result = vkAllocateMemory(mState->mDevice, &allocInfo, nullptr, &mState->mTextureImageMemory);
|
VkDeviceMemory textureImageMemory;
|
||||||
|
result = vkAllocateMemory(device, &allocInfo, nullptr, &textureImageMemory);
|
||||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to allocate image memory."
|
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to allocate image memory."
|
||||||
<< " error=" << static_cast<int32_t>(result);
|
<< " error=" << static_cast<int32_t>(result);
|
||||||
result = vkBindImageMemory(mState->mDevice, mState->mTextureImage, mState->mTextureImageMemory,
|
result = vkBindImageMemory(device, textureImage, textureImageMemory, 0);
|
||||||
0);
|
|
||||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to bind image."
|
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to bind image."
|
||||||
<< " error=" << static_cast<int32_t>(result);
|
<< " error=" << static_cast<int32_t>(result);
|
||||||
|
|
||||||
|
mState = fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, stagePool,
|
||||||
|
commands, allocator, device, textureImage, textureImageMemory, vkFormat,
|
||||||
|
fvkutils::getViewType(target), levels, getLayerCount(target, depth),
|
||||||
|
VK_NULL_HANDLE /* ycbcrConversion */, false /*isExternalFormat*/, imageInfo.usage,
|
||||||
|
isProtected);
|
||||||
|
|
||||||
// Spec out the "primary" VkImageView that shaders use to sample from the image.
|
// Spec out the "primary" VkImageView that shaders use to sample from the image.
|
||||||
mPrimaryViewRange = mState->mFullViewRange;
|
mPrimaryViewRange = mState->mFullViewRange;
|
||||||
|
|
||||||
// Go ahead and create the primary image view.
|
|
||||||
getImageView(mPrimaryViewRange, mState->mViewType, mSwizzle);
|
|
||||||
|
|
||||||
mState->mDefaultLayout = getDefaultLayoutImpl(imageInfo.usage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor for creating a texture view
|
// Constructor for creating a texture view
|
||||||
@@ -380,8 +437,8 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
|||||||
fvkmemory::resource_ptr<VulkanTexture> src, uint8_t baseLevel,
|
fvkmemory::resource_ptr<VulkanTexture> src, uint8_t baseLevel,
|
||||||
uint8_t levelCount)
|
uint8_t levelCount)
|
||||||
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
|
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
|
||||||
src->format, src->usage) {
|
src->format, src->usage),
|
||||||
mState = src->mState;
|
mState(src->mState) {
|
||||||
mPrimaryViewRange = src->mPrimaryViewRange;
|
mPrimaryViewRange = src->mPrimaryViewRange;
|
||||||
mPrimaryViewRange.baseMipLevel = src->mPrimaryViewRange.baseMipLevel + baseLevel;
|
mPrimaryViewRange.baseMipLevel = src->mPrimaryViewRange.baseMipLevel + baseLevel;
|
||||||
mPrimaryViewRange.levelCount = levelCount;
|
mPrimaryViewRange.levelCount = levelCount;
|
||||||
@@ -392,21 +449,10 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
|||||||
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
||||||
fvkmemory::resource_ptr<VulkanTexture> src, VkComponentMapping swizzle)
|
fvkmemory::resource_ptr<VulkanTexture> src, VkComponentMapping swizzle)
|
||||||
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
|
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
|
||||||
src->format, src->usage) {
|
src->format, src->usage),
|
||||||
mState = src->mState;
|
mState(src->mState),
|
||||||
mPrimaryViewRange = src->mPrimaryViewRange;
|
mPrimaryViewRange(src->mPrimaryViewRange),
|
||||||
mSwizzle = composeSwizzle(src->mSwizzle, swizzle);
|
mSwizzle(composeSwizzle(src->mSwizzle, swizzle)) {}
|
||||||
}
|
|
||||||
|
|
||||||
VulkanTextureState::~VulkanTextureState() {
|
|
||||||
if (mTextureImageMemory != VK_NULL_HANDLE) {
|
|
||||||
vkDestroyImage(mDevice, mTextureImage, VKALLOC);
|
|
||||||
vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC);
|
|
||||||
}
|
|
||||||
for (auto entry: mCachedImageViews) {
|
|
||||||
vkDestroyImageView(mDevice, entry.second, VKALLOC);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
|
void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
|
||||||
uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel) {
|
uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel) {
|
||||||
@@ -513,20 +559,20 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
|
|||||||
commands.acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
|
commands.acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
|
||||||
|
|
||||||
// TODO: support blit-based format conversion for 3D images and cubemaps.
|
// TODO: support blit-based format conversion for 3D images and cubemaps.
|
||||||
const int layer = 0;
|
constexpr int layer = 0;
|
||||||
|
|
||||||
const VkOffset3D rect[2] { {0, 0, 0}, {int32_t(width), int32_t(height), 1} };
|
VkOffset3D const rect[2] { {0, 0, 0}, {int32_t(width), int32_t(height), 1} };
|
||||||
|
|
||||||
const VkImageAspectFlags aspect = getImageAspect();
|
VkImageAspectFlags const aspect = getImageAspect();
|
||||||
|
|
||||||
const VkImageBlit blitRegions[1] = {{
|
VkImageBlit const blitRegions[1] = {{
|
||||||
.srcSubresource = { aspect, 0, 0, 1 },
|
.srcSubresource = { aspect, 0, 0, 1 },
|
||||||
.srcOffsets = { rect[0], rect[1] },
|
.srcOffsets = { rect[0], rect[1] },
|
||||||
.dstSubresource = { aspect, uint32_t(miplevel), layer, 1 },
|
.dstSubresource = { aspect, uint32_t(miplevel), layer, 1 },
|
||||||
.dstOffsets = { rect[0], rect[1] }
|
.dstOffsets = { rect[0], rect[1] }
|
||||||
}};
|
}};
|
||||||
|
|
||||||
const VkImageSubresourceRange range = { aspect, miplevel, 1, layer, 1 };
|
VkImageSubresourceRange const range = { aspect, miplevel, 1, layer, 1 };
|
||||||
|
|
||||||
VulkanLayout const newLayout = VulkanLayout::TRANSFER_DST;
|
VulkanLayout const newLayout = VulkanLayout::TRANSFER_DST;
|
||||||
VulkanLayout const oldLayout = getLayout(layer, miplevel);
|
VulkanLayout const oldLayout = getLayout(layer, miplevel);
|
||||||
@@ -542,41 +588,21 @@ VulkanLayout VulkanTexture::getDefaultLayout() const {
|
|||||||
return mState->mDefaultLayout;
|
return mState->mDefaultLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
|
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange const& range, VkImageViewType type) {
|
||||||
range.levelCount = 1;
|
assert_invariant(mState->mYcbcr.conversion == VK_NULL_HANDLE &&
|
||||||
range.layerCount = 1;
|
"We are not yet supporting external image as attachments.");
|
||||||
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D, {});
|
if (type == VK_IMAGE_VIEW_TYPE_2D) {
|
||||||
}
|
VkImageSubresourceRange copy = range;
|
||||||
|
copy.levelCount = 1;
|
||||||
VkImageView VulkanTexture::getMultiviewAttachmentView(VkImageSubresourceRange range) {
|
copy.layerCount = 1;
|
||||||
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D_ARRAY, {});
|
return mState->getImageView(copy, type, {});
|
||||||
}
|
} else {
|
||||||
|
return mState->getImageView(range, type, {});
|
||||||
VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range, VkImageViewType type) {
|
|
||||||
return getImageView(range, type, mSwizzle);
|
|
||||||
}
|
|
||||||
|
|
||||||
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
|
|
||||||
VkComponentMapping swizzle) {
|
|
||||||
VulkanTextureState::ImageViewKey const key{ range, viewType, swizzle };
|
|
||||||
auto iter = mState->mCachedImageViews.find(key);
|
|
||||||
if (iter != mState->mCachedImageViews.end()) {
|
|
||||||
return iter->second;
|
|
||||||
}
|
}
|
||||||
VkImageViewCreateInfo viewInfo = {
|
}
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
|
||||||
.pNext = nullptr,
|
VkImageView VulkanTexture::getView(VkImageSubresourceRange const& range) {
|
||||||
.flags = 0,
|
return mState->getImageView(range, mState->mViewType, mSwizzle);
|
||||||
.image = mState->mTextureImage,
|
|
||||||
.viewType = viewType,
|
|
||||||
.format = mState->mVkFormat,
|
|
||||||
.components = swizzle,
|
|
||||||
.subresourceRange = range,
|
|
||||||
};
|
|
||||||
VkImageView imageView;
|
|
||||||
vkCreateImageView(mState->mDevice, &viewInfo, VKALLOC, &imageView);
|
|
||||||
mState->mCachedImageViews.emplace(key, imageView);
|
|
||||||
return imageView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VkImageAspectFlags VulkanTexture::getImageAspect() const {
|
VkImageAspectFlags VulkanTexture::getImageAspect() const {
|
||||||
@@ -738,6 +764,21 @@ void VulkanTexture::setLayout(VkImageSubresourceRange const& range, VulkanLayout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VulkanTexture::setYcbcrConversion(VkSamplerYcbcrConversion conversion, bool isExternalFormat) {
|
||||||
|
// Note that this comparison is valid because we only ever create VkSamplerYcbcrConversion from
|
||||||
|
// a cache. So for each set of parameters, there is exactly one conversion (similar to
|
||||||
|
// samplers).
|
||||||
|
VulkanTextureState::Ycbcr ycbcr = {
|
||||||
|
.conversion = conversion,
|
||||||
|
.isExternalFormat = isExternalFormat,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mState->mYcbcr != ycbcr) {
|
||||||
|
mState->mYcbcr = ycbcr;
|
||||||
|
mState->clearCachedImageViews();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VulkanLayout VulkanTexture::getLayout(uint32_t layer, uint32_t level) const {
|
VulkanLayout VulkanTexture::getLayout(uint32_t layer, uint32_t level) const {
|
||||||
assert_invariant(level <= 0xffff && layer <= 0xffff);
|
assert_invariant(level <= 0xffff && layer <= 0xffff);
|
||||||
const uint32_t key = (layer << 16) | level;
|
const uint32_t key = (layer << 16) | level;
|
||||||
|
|||||||
@@ -19,7 +19,10 @@
|
|||||||
|
|
||||||
#include "DriverBase.h"
|
#include "DriverBase.h"
|
||||||
|
|
||||||
#include "VulkanBuffer.h"
|
#include "VulkanCommands.h"
|
||||||
|
#include "VulkanConstants.h"
|
||||||
|
#include "VulkanMemory.h"
|
||||||
|
#include "VulkanStagePool.h"
|
||||||
#include "vulkan/memory/Resource.h"
|
#include "vulkan/memory/Resource.h"
|
||||||
#include "vulkan/memory/ResourcePointer.h"
|
#include "vulkan/memory/ResourcePointer.h"
|
||||||
#include "vulkan/utils/Image.h"
|
#include "vulkan/utils/Image.h"
|
||||||
@@ -31,10 +34,14 @@
|
|||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
|
struct VulkanTexture;
|
||||||
|
|
||||||
struct VulkanTextureState : public fvkmemory::Resource {
|
struct VulkanTextureState : public fvkmemory::Resource {
|
||||||
VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
|
VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands, VmaAllocator allocator,
|
||||||
VulkanStagePool& stagePool, VkFormat format, VkImageViewType viewType, uint8_t levels,
|
VkDevice device, VkImage image, VkDeviceMemory deviceMemory, VkFormat format,
|
||||||
uint8_t layerCount, VulkanLayout defaultLayout, bool isProtected);
|
VkImageViewType viewType, uint8_t levels, uint8_t layerCount,
|
||||||
|
VkSamplerYcbcrConversion ycbcrConversion, bool isExternalFormat,
|
||||||
|
VkImageUsageFlags usage, bool isProtected);
|
||||||
|
|
||||||
~VulkanTextureState();
|
~VulkanTextureState();
|
||||||
|
|
||||||
@@ -57,29 +64,50 @@ struct VulkanTextureState : public fvkmemory::Resource {
|
|||||||
// No implicit padding allowed due to it being a hash key.
|
// No implicit padding allowed due to it being a hash key.
|
||||||
static_assert(sizeof(ImageViewKey) == 40);
|
static_assert(sizeof(ImageViewKey) == 40);
|
||||||
|
|
||||||
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
|
VkImageView getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
|
||||||
|
VkComponentMapping swizzle);
|
||||||
|
private:
|
||||||
|
void clearCachedImageViews() noexcept;
|
||||||
|
VulkanStagePool& mStagePool;
|
||||||
|
VulkanCommands* const mCommands;
|
||||||
|
VmaAllocator const mAllocator;
|
||||||
|
VkDevice const mDevice;
|
||||||
|
|
||||||
// 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;
|
||||||
VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE;
|
|
||||||
|
|
||||||
|
VkImage const mTextureImage;
|
||||||
|
VkDeviceMemory const mTextureImageMemory;
|
||||||
VkFormat const mVkFormat;
|
VkFormat const mVkFormat;
|
||||||
VkImageViewType const mViewType;
|
VkImageViewType const mViewType;
|
||||||
VkImageSubresourceRange const mFullViewRange;
|
VkImageSubresourceRange const mFullViewRange;
|
||||||
VkImage mTextureImage = VK_NULL_HANDLE;
|
|
||||||
VulkanLayout mDefaultLayout;
|
|
||||||
|
|
||||||
bool mIsProtected = false;
|
// Note that this parameter is not constant due to the fact that AHB can force a change in the
|
||||||
|
// conversion matrix per-frame.
|
||||||
|
struct Ycbcr {
|
||||||
|
VkSamplerYcbcrConversion conversion;
|
||||||
|
bool isExternalFormat;
|
||||||
|
|
||||||
|
bool operator==(Ycbcr const& other) const {
|
||||||
|
return conversion == other.conversion && isExternalFormat == other.isExternalFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(Ycbcr const& other) const {
|
||||||
|
return !((*this) == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
} mYcbcr;
|
||||||
|
|
||||||
|
VulkanLayout const mDefaultLayout;
|
||||||
|
VkImageUsageFlags const mUsage;
|
||||||
|
bool const mIsProtected;
|
||||||
|
|
||||||
// Track the image layout of each subresource using a sparse range map.
|
// Track the image layout of each subresource using a sparse range map.
|
||||||
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
|
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
|
||||||
|
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
|
||||||
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
|
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
|
||||||
VulkanStagePool& mStagePool;
|
|
||||||
VkDevice mDevice;
|
friend struct VulkanTexture;
|
||||||
VmaAllocator mAllocator;
|
|
||||||
VulkanCommands* mCommands;
|
|
||||||
bool mIsTransientAttachment;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
||||||
@@ -91,10 +119,10 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
|||||||
VulkanStagePool& stagePool);
|
VulkanStagePool& stagePool);
|
||||||
|
|
||||||
// Specialized constructor for internally created textures (e.g. from a swap chain)
|
// Specialized constructor for internally created textures (e.g. from a swap chain)
|
||||||
// The texture will never destroy the given VkImage, but it does manages its subresources.
|
VulkanTexture(VulkanContext const& context, VkDevice device, VmaAllocator allocator,
|
||||||
VulkanTexture(VkDevice device, VmaAllocator allocator,
|
|
||||||
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, VkImage image,
|
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, VkImage image,
|
||||||
VkDeviceMemory memory, VkFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
|
VkDeviceMemory memory, VkFormat format, VkSamplerYcbcrConversion conversion,
|
||||||
|
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
|
||||||
TextureUsage tusage, VulkanStagePool& stagePool);
|
TextureUsage tusage, VulkanStagePool& stagePool);
|
||||||
|
|
||||||
// Constructor for creating a texture view for wrt specific mip range
|
// Constructor for creating a texture view for wrt specific mip range
|
||||||
@@ -113,11 +141,6 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
|||||||
void updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
|
void updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
|
||||||
uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel);
|
uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel);
|
||||||
|
|
||||||
// Returns the primary image view, which is used for shader sampling.
|
|
||||||
VkImageView getPrimaryImageView() {
|
|
||||||
return getImageView(mPrimaryViewRange, mState->mViewType, mSwizzle);
|
|
||||||
}
|
|
||||||
|
|
||||||
VkImageViewType getViewType() const {
|
VkImageViewType getViewType() const {
|
||||||
return mState->mViewType;
|
return mState->mViewType;
|
||||||
}
|
}
|
||||||
@@ -132,20 +155,11 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
|||||||
// time of the call.
|
// time of the call.
|
||||||
VulkanLayout getDefaultLayout() const;
|
VulkanLayout getDefaultLayout() const;
|
||||||
|
|
||||||
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
|
// Gets or creates a cached VkImageView for a subresource that can be used as a render
|
||||||
// target attachment. Unlike the primary image view, this always has type VK_IMAGE_VIEW_TYPE_2D
|
// target attachment. Unlike the primary image view, this always the identity swizzle.
|
||||||
// and the identity swizzle.
|
VkImageView getAttachmentView(VkImageSubresourceRange const& range, VkImageViewType type);
|
||||||
VkImageView getAttachmentView(VkImageSubresourceRange range);
|
|
||||||
|
|
||||||
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
|
VkImageView getView(VkImageSubresourceRange const& range);
|
||||||
// target attachment when rendering with multiview.
|
|
||||||
VkImageView getMultiviewAttachmentView(VkImageSubresourceRange range);
|
|
||||||
|
|
||||||
// This is a workaround for the first few frames where we're waiting for the texture to actually
|
|
||||||
// be uploaded. In that case, we bind the sampler to an empty texture, but the corresponding
|
|
||||||
// imageView needs to be of the right type. Hence, we provide an option to indicate the
|
|
||||||
// view type. Swizzle option does not matter in this case.
|
|
||||||
VkImageView getViewForType(VkImageSubresourceRange const& range, VkImageViewType type);
|
|
||||||
|
|
||||||
VkFormat getVkFormat() const {
|
VkFormat getVkFormat() const {
|
||||||
return mState->mVkFormat;
|
return mState->mVkFormat;
|
||||||
@@ -165,7 +179,7 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isTransientAttachment() const {
|
bool isTransientAttachment() const {
|
||||||
return mState->mIsTransientAttachment;
|
return mState->mUsage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getIsProtected() const {
|
bool getIsProtected() const {
|
||||||
@@ -192,6 +206,11 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
|||||||
// manually (outside of calls to transitionLayout).
|
// manually (outside of calls to transitionLayout).
|
||||||
void setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout);
|
void setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout);
|
||||||
|
|
||||||
|
// This is used in the case of external images and external samplers. AHB might update the
|
||||||
|
// conversion per-frame. This implies that we need to invalidate the view cache when that
|
||||||
|
// happens.
|
||||||
|
void setYcbcrConversion(VkSamplerYcbcrConversion conversion, bool isExternal);
|
||||||
|
|
||||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||||
void print() const;
|
void print() const;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* 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 "VulkanYcbcrConversionCache.h"
|
||||||
|
|
||||||
|
#include "vulkan/VulkanConstants.h"
|
||||||
|
#include "vulkan/utils/Conversion.h"
|
||||||
|
|
||||||
|
#include <utils/Panic.h>
|
||||||
|
|
||||||
|
#if defined(__ANDROID__)
|
||||||
|
#include <vulkan/vulkan_android.h> // for VkExternalFormatANDROID
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace bluevk;
|
||||||
|
|
||||||
|
namespace filament::backend {
|
||||||
|
|
||||||
|
VulkanYcbcrConversionCache::VulkanYcbcrConversionCache(VkDevice device)
|
||||||
|
: mDevice(device) {}
|
||||||
|
|
||||||
|
VkSamplerYcbcrConversion VulkanYcbcrConversionCache::getConversion(
|
||||||
|
VulkanYcbcrConversionCache::Params params) noexcept {
|
||||||
|
auto iter = mCache.find(params);
|
||||||
|
if (UTILS_LIKELY(iter != mCache.end())) {
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const& chroma = params.conversion;
|
||||||
|
TextureSwizzle const swizzleArray[] = { chroma.r, chroma.g, chroma.b, chroma.a };
|
||||||
|
VkSamplerYcbcrConversionCreateInfo conversionInfo = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
|
||||||
|
.format = fvkutils::getVkFormat(params.format),
|
||||||
|
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
|
||||||
|
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
|
||||||
|
.components = fvkutils::getSwizzleMap(swizzleArray),
|
||||||
|
.xChromaOffset = fvkutils::getChromaLocation(chroma.xChromaOffset),
|
||||||
|
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset),
|
||||||
|
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
|
||||||
|
};
|
||||||
|
|
||||||
|
// We could put this in the platform class, but that seems like a bit of an overkill
|
||||||
|
#if defined(__ANDROID__)
|
||||||
|
VkExternalFormatANDROID externalFormat = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
|
||||||
|
.externalFormat = params.externalFormat,
|
||||||
|
};
|
||||||
|
if (params.externalFormat) {
|
||||||
|
conversionInfo.pNext = &externalFormat;
|
||||||
|
conversionInfo.format = VK_FORMAT_UNDEFINED;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
VkSamplerYcbcrConversion conversion;
|
||||||
|
VkResult result =
|
||||||
|
vkCreateSamplerYcbcrConversion(mDevice, &conversionInfo, nullptr, &conversion);
|
||||||
|
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create Ycbcr Conversion."
|
||||||
|
<< " error=" << static_cast<int32_t>(result);
|
||||||
|
|
||||||
|
mCache.insert({ params, conversion });
|
||||||
|
return conversion;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanYcbcrConversionCache::terminate() noexcept {
|
||||||
|
for (auto& [param, conv]: mCache) {
|
||||||
|
vkDestroySamplerYcbcrConversion(mDevice, conv, VKALLOC);
|
||||||
|
}
|
||||||
|
mCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace filament::backend
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
|
||||||
|
#define TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
|
||||||
|
|
||||||
|
#include <backend/DriverEnums.h>
|
||||||
|
|
||||||
|
#include <utils/Hash.h>
|
||||||
|
|
||||||
|
#include <bluevk/BlueVK.h>
|
||||||
|
#include <tsl/robin_map.h>
|
||||||
|
|
||||||
|
namespace filament::backend {
|
||||||
|
|
||||||
|
// Simple manager for VkSamplerYcbcrConversion objects.
|
||||||
|
class VulkanYcbcrConversionCache {
|
||||||
|
public:
|
||||||
|
struct Params {
|
||||||
|
SamplerYcbcrConversion conversion = {};
|
||||||
|
TextureFormat format = {};
|
||||||
|
uint16_t padding = 0;
|
||||||
|
uint64_t externalFormat = 0;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(Params) == 16);
|
||||||
|
|
||||||
|
explicit VulkanYcbcrConversionCache(VkDevice device);
|
||||||
|
VkSamplerYcbcrConversion getConversion(Params params) noexcept;
|
||||||
|
void terminate() noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkDevice mDevice;
|
||||||
|
|
||||||
|
struct ConversionEqualTo {
|
||||||
|
bool operator()(Params lhs, Params rhs) const noexcept {
|
||||||
|
SamplerYcbcrConversion::EqualTo equal;
|
||||||
|
return equal(lhs.conversion, rhs.conversion) &&
|
||||||
|
lhs.externalFormat == rhs.externalFormat;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using ConversionHashFn = utils::hash::MurmurHashFn<Params>;
|
||||||
|
tsl::robin_map<Params, VkSamplerYcbcrConversion, ConversionHashFn, ConversionEqualTo> mCache;
|
||||||
|
};
|
||||||
|
|
||||||
|
}// namespace filament::backend
|
||||||
|
|
||||||
|
#endif// TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
|
||||||
@@ -132,6 +132,10 @@ public:
|
|||||||
return id() == other.id() && type() == other.type();
|
return id() == other.id() && type() == other.type();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool operator!=(resource_ptr<D> const& other) const {
|
||||||
|
return !((*this) == other);
|
||||||
|
}
|
||||||
|
|
||||||
inline explicit operator bool() const {
|
inline explicit operator bool() const {
|
||||||
return bool(mRef);
|
return bool(mRef);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
|
|||||||
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
|
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
|
||||||
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
|
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
|
||||||
VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
|
VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
|
||||||
|
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
|
||||||
#endif
|
#endif
|
||||||
// MoltenVk is the only non-conformant implementation we're interested in.
|
// MoltenVk is the only non-conformant implementation we're interested in.
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
@@ -326,7 +327,9 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||||
VkPhysicalDeviceFeatures2 const& features, uint32_t graphicsQueueFamilyIndex,
|
VkPhysicalDeviceFeatures2 const& features,
|
||||||
|
VkPhysicalDeviceVulkan11Features const& vk11Features,
|
||||||
|
uint32_t graphicsQueueFamilyIndex,
|
||||||
uint32_t protectedGraphicsQueueFamilyIndex, ExtensionSet const& deviceExtensions,
|
uint32_t protectedGraphicsQueueFamilyIndex, ExtensionSet const& deviceExtensions,
|
||||||
bool requestImageView2DOn3DImage) {
|
bool requestImageView2DOn3DImage) {
|
||||||
VkDevice device;
|
VkDevice device;
|
||||||
@@ -359,14 +362,28 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
|||||||
|
|
||||||
// We could simply enable all supported features, but since that may have performance
|
// We could simply enable all supported features, but since that may have performance
|
||||||
// consequences let's just enable the features we need.
|
// consequences let's just enable the features we need.
|
||||||
VkPhysicalDeviceFeatures enabledFeatures{
|
VkPhysicalDeviceFeatures enabledFeatures = {
|
||||||
.depthClamp = features.features.depthClamp,
|
.depthClamp = features.features.depthClamp,
|
||||||
.samplerAnisotropy = features.features.samplerAnisotropy,
|
.samplerAnisotropy = features.features.samplerAnisotropy,
|
||||||
.textureCompressionETC2 = features.features.textureCompressionETC2,
|
.textureCompressionETC2 = features.features.textureCompressionETC2,
|
||||||
.textureCompressionBC = features.features.textureCompressionBC,
|
.textureCompressionBC = features.features.textureCompressionBC,
|
||||||
.shaderClipDistance = features.features.shaderClipDistance,
|
.shaderClipDistance = features.features.shaderClipDistance,
|
||||||
};
|
};
|
||||||
deviceCreateInfo.pEnabledFeatures = &enabledFeatures;
|
|
||||||
|
VkPhysicalDeviceFeatures2 enabledFeatures2 = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
||||||
|
.features = enabledFeatures,
|
||||||
|
};
|
||||||
|
chainStruct(&deviceCreateInfo, &enabledFeatures2);
|
||||||
|
|
||||||
|
VkPhysicalDeviceVulkan11Features enabledVk11Features = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
|
||||||
|
.multiview = vk11Features.multiview,
|
||||||
|
#if defined(__ANDROID__)
|
||||||
|
.samplerYcbcrConversion = vk11Features.samplerYcbcrConversion,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
chainStruct(&deviceCreateInfo, &enabledVk11Features);
|
||||||
|
|
||||||
deviceCreateInfo.enabledExtensionCount = (uint32_t) requestExtensions.size();
|
deviceCreateInfo.enabledExtensionCount = (uint32_t) requestExtensions.size();
|
||||||
deviceCreateInfo.ppEnabledExtensionNames = requestExtensions.data();
|
deviceCreateInfo.ppEnabledExtensionNames = requestExtensions.data();
|
||||||
@@ -383,7 +400,7 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
|||||||
|
|
||||||
VkPhysicalDeviceMultiviewFeaturesKHR multiview = {
|
VkPhysicalDeviceMultiviewFeaturesKHR multiview = {
|
||||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR,
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR,
|
||||||
.multiview = VK_TRUE,
|
.multiview = vk11Features.multiview,
|
||||||
.multiviewGeometryShader = VK_FALSE,
|
.multiviewGeometryShader = VK_FALSE,
|
||||||
.multiviewTessellationShader = VK_FALSE,
|
.multiviewTessellationShader = VK_FALSE,
|
||||||
};
|
};
|
||||||
@@ -734,12 +751,9 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
|||||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
|
||||||
};
|
};
|
||||||
chainStruct(&context.mPhysicalDeviceFeatures, &queryProtectedMemoryFeatures);
|
chainStruct(&context.mPhysicalDeviceFeatures, &queryProtectedMemoryFeatures);
|
||||||
|
chainStruct(&context.mPhysicalDeviceFeatures, &context.mPhysicalDeviceVk11Features);
|
||||||
chainStruct(&context.mPhysicalDeviceProperties, &protectedMemoryProperties);
|
chainStruct(&context.mPhysicalDeviceProperties, &protectedMemoryProperties);
|
||||||
|
|
||||||
// We know we need to allocate the protected version of the VK objects
|
|
||||||
context.mProtectedMemorySupported =
|
|
||||||
static_cast<bool>(queryProtectedMemoryFeatures.protectedMemory);
|
|
||||||
|
|
||||||
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
|
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
|
||||||
// physicalDeviceFeatures, graphicsQueueFamilyIndex.
|
// physicalDeviceFeatures, graphicsQueueFamilyIndex.
|
||||||
vkGetPhysicalDeviceProperties2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
|
vkGetPhysicalDeviceProperties2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
|
||||||
@@ -759,6 +773,10 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
|||||||
mImpl->mGraphicsQueueIndex = 0;
|
mImpl->mGraphicsQueueIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We know we need to allocate the protected version of the VK objects
|
||||||
|
context.mProtectedMemorySupported =
|
||||||
|
static_cast<bool>(queryProtectedMemoryFeatures.protectedMemory);
|
||||||
|
|
||||||
if (context.mProtectedMemorySupported) {
|
if (context.mProtectedMemorySupported) {
|
||||||
mImpl->mProtectedGraphicsQueueFamilyIndex = identifyGraphicsQueueFamilyIndex(
|
mImpl->mProtectedGraphicsQueueFamilyIndex = identifyGraphicsQueueFamilyIndex(
|
||||||
mImpl->mPhysicalDevice, (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_PROTECTED_BIT));
|
mImpl->mPhysicalDevice, (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_PROTECTED_BIT));
|
||||||
@@ -795,10 +813,10 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mImpl->mDevice == VK_NULL_HANDLE) {
|
if (mImpl->mDevice == VK_NULL_HANDLE) {
|
||||||
mImpl->mDevice =
|
mImpl->mDevice = createLogicalDevice(mImpl->mPhysicalDevice,
|
||||||
createLogicalDevice(mImpl->mPhysicalDevice, context.mPhysicalDeviceFeatures,
|
context.mPhysicalDeviceFeatures, context.mPhysicalDeviceVk11Features,
|
||||||
mImpl->mGraphicsQueueFamilyIndex, mImpl->mProtectedGraphicsQueueFamilyIndex,
|
mImpl->mGraphicsQueueFamilyIndex, mImpl->mProtectedGraphicsQueueFamilyIndex,
|
||||||
deviceExts, requestPortabilitySubsetImageView2DOn3DImage);
|
deviceExts, requestPortabilitySubsetImageView2DOn3DImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_invariant(mImpl->mDevice != VK_NULL_HANDLE);
|
assert_invariant(mImpl->mDevice != VK_NULL_HANDLE);
|
||||||
@@ -826,12 +844,10 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
|||||||
if (!mImpl->mSharedContext) {
|
if (!mImpl->mSharedContext) {
|
||||||
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||||
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
|
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
|
||||||
context.mMultiviewEnabled = setContains(deviceExts, VK_KHR_MULTIVIEW_EXTENSION_NAME);
|
|
||||||
} else {
|
} else {
|
||||||
VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext;
|
VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext;
|
||||||
context.mDebugUtilsSupported = scontext->debugUtilsSupported;
|
context.mDebugUtilsSupported = scontext->debugUtilsSupported;
|
||||||
context.mDebugMarkersSupported = scontext->debugMarkersSupported;
|
context.mDebugMarkersSupported = scontext->debugMarkersSupported;
|
||||||
context.mMultiviewEnabled = scontext->multiviewSupported;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the availability of lazily allocated memory
|
// Check the availability of lazily allocated memory
|
||||||
@@ -977,30 +993,6 @@ VkQueue VulkanPlatform::getProtectedGraphicsQueue() const noexcept {
|
|||||||
return mImpl->mProtectedGraphicsQueue;
|
return mImpl->mProtectedGraphicsQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadata(
|
|
||||||
ExternalImageHandleRef externalImage) {
|
|
||||||
return getExternalImageMetadataImpl(externalImage, mImpl->mDevice);
|
|
||||||
}
|
|
||||||
|
|
||||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageData(
|
|
||||||
ExternalImageHandleRef externalImage, const ExternalImageMetadata& metadata,
|
|
||||||
uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
|
||||||
return createExternalImageDataImpl(externalImage, mImpl->mDevice, metadata, memoryTypeIndex,
|
|
||||||
usage);
|
|
||||||
}
|
|
||||||
|
|
||||||
VkSampler VulkanPlatform::createExternalSampler(SamplerYcbcrConversion chroma,
|
|
||||||
SamplerParams sampler, uint32_t internalFormat) {
|
|
||||||
return createExternalSamplerImpl(mImpl->mDevice, chroma, sampler, internalFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
VkImageView VulkanPlatform::createExternalImageView(SamplerYcbcrConversion chroma,
|
|
||||||
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
|
|
||||||
VkImageViewType viewType, VkComponentMapping swizzle) {
|
|
||||||
return createExternalImageViewImpl(mImpl->mDevice, chroma, internalFormat, image, range,
|
|
||||||
viewType, swizzle);
|
|
||||||
}
|
|
||||||
|
|
||||||
ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const {
|
ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const {
|
||||||
return getSwapchainInstanceExtensionsImpl();
|
return getSwapchainInstanceExtensionsImpl();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,14 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
#include <backend/platforms/VulkanPlatformAndroid.h>
|
#include <backend/platforms/VulkanPlatformAndroid.h>
|
||||||
|
|
||||||
|
#include "vulkan/VulkanConstants.h"
|
||||||
|
#include "vulkan/VulkanContext.h"
|
||||||
|
#include "vulkan/vulkan_core.h"
|
||||||
|
|
||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
#include <private/backend/BackendUtilsAndroid.h>
|
#include <private/backend/BackendUtilsAndroid.h>
|
||||||
|
|
||||||
#include "vulkan/VulkanConstants.h"
|
|
||||||
|
|
||||||
#include <utils/Panic.h>
|
#include <utils/Panic.h>
|
||||||
#include "vulkan/utils/Image.h"
|
|
||||||
#include "vulkan/utils/Conversion.h"
|
|
||||||
|
|
||||||
#include <bluevk/BlueVK.h>
|
#include <bluevk/BlueVK.h>
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ VkFormat transformVkFormat(VkFormat format, bool sRGB) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isProtectedFromUsage(uint64_t usage) {
|
bool isProtectedFromUsage(uint64_t usage) {
|
||||||
return (usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) ? true : false;
|
return usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer_Desc& desc,
|
std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer_Desc& desc,
|
||||||
@@ -120,7 +120,9 @@ std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer
|
|||||||
usage = 0;
|
usage = 0;
|
||||||
if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE) {
|
if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE) {
|
||||||
usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||||
usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
|
||||||
|
// We shouldn't be using external samplers as input attachments
|
||||||
|
// usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
||||||
}
|
}
|
||||||
if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER) {
|
if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER) {
|
||||||
if (isDepthFormat) {
|
if (isDepthFormat) {
|
||||||
@@ -136,87 +138,38 @@ std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer
|
|||||||
return { format, usage };
|
return { format, usage };
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanPlatform::ImageData allocateExternalImage(AHardwareBuffer* buffer, VkDevice device,
|
std::pair<TextureFormat, TextureUsage> getFilamentFormatAndUsage(const AHardwareBuffer_Desc& desc,
|
||||||
VulkanPlatform::ExternalImageMetadata const& metadata, uint32_t memoryTypeIndex,
|
bool sRGB) {
|
||||||
VkImageUsageFlags usage) {
|
auto const format = mapToFilamentFormat(desc.format, sRGB);
|
||||||
VulkanPlatform::ImageData data;
|
return {
|
||||||
|
format,
|
||||||
// if external format we need to specifiy it in the allocation
|
mapToFilamentUsage(desc.usage, format),
|
||||||
const bool useExternalFormat = metadata.format == VK_FORMAT_UNDEFINED;
|
|
||||||
|
|
||||||
const VkExternalFormatANDROID externalFormat = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
|
|
||||||
.pNext = nullptr,
|
|
||||||
// pass down the format (external means we don't have it VK defined)
|
|
||||||
.externalFormat = metadata.externalFormat,
|
|
||||||
};
|
};
|
||||||
const VkExternalMemoryImageCreateInfo externalCreateInfo = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
|
|
||||||
.pNext = useExternalFormat ? &externalFormat : nullptr,
|
|
||||||
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
|
|
||||||
};
|
|
||||||
|
|
||||||
VkImageCreateInfo imageInfo{ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
|
|
||||||
imageInfo.pNext = &externalCreateInfo;
|
|
||||||
imageInfo.format = metadata.format;
|
|
||||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
|
||||||
imageInfo.extent = {
|
|
||||||
metadata.width,
|
|
||||||
metadata.height,
|
|
||||||
1u,
|
|
||||||
};
|
|
||||||
imageInfo.mipLevels = 1;
|
|
||||||
imageInfo.arrayLayers = metadata.layers;
|
|
||||||
imageInfo.samples = metadata.samples;
|
|
||||||
imageInfo.usage = usage;
|
|
||||||
|
|
||||||
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &data.first);
|
|
||||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
|
||||||
<< "vkCreateImage failed with error=" << static_cast<int32_t>(result);
|
|
||||||
|
|
||||||
// Allocate the memory
|
|
||||||
VkImportAndroidHardwareBufferInfoANDROID androidHardwareBufferInfo = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.buffer = buffer,
|
|
||||||
};
|
|
||||||
VkMemoryDedicatedAllocateInfo memoryDedicatedAllocateInfo = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
|
|
||||||
.pNext = &androidHardwareBufferInfo,
|
|
||||||
.image = data.first,
|
|
||||||
.buffer = VK_NULL_HANDLE,
|
|
||||||
};
|
|
||||||
VkMemoryAllocateInfo allocInfo = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
|
||||||
.pNext = &memoryDedicatedAllocateInfo,
|
|
||||||
.allocationSize = metadata.allocationSize,
|
|
||||||
.memoryTypeIndex = memoryTypeIndex,
|
|
||||||
};
|
|
||||||
result = vkAllocateMemory(device, &allocInfo, VKALLOC, &data.second);
|
|
||||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
|
||||||
<< "vkAllocateMemory failed with error=" << static_cast<int32_t>(result);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}// namespace
|
}// namespace
|
||||||
|
|
||||||
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() = default;
|
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() {
|
||||||
|
if (__builtin_available(android 26, *)) {
|
||||||
|
if (aHardwareBuffer) {
|
||||||
|
AHardwareBuffer_release(aHardwareBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
|
Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
|
||||||
AHardwareBuffer const* buffer, bool sRGB) noexcept {
|
AHardwareBuffer const* buffer, bool sRGB) noexcept {
|
||||||
if (__builtin_available(android 26, *)) {
|
if (__builtin_available(android 26, *)) {
|
||||||
|
auto bufferImpl = const_cast<AHardwareBuffer*>(buffer);
|
||||||
|
AHardwareBuffer_acquire(bufferImpl);
|
||||||
|
|
||||||
AHardwareBuffer_Desc hardwareBufferDescription = {};
|
AHardwareBuffer_Desc hardwareBufferDescription = {};
|
||||||
AHardwareBuffer_describe(buffer, &hardwareBufferDescription);
|
AHardwareBuffer_describe(buffer, &hardwareBufferDescription);
|
||||||
|
|
||||||
auto* const p = new (std::nothrow) ExternalImageVulkanAndroid;
|
auto* const p = new (std::nothrow) ExternalImageVulkanAndroid;
|
||||||
p->aHardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
|
p->aHardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
|
||||||
p->sRGB = sRGB;
|
p->sRGB = sRGB;
|
||||||
p->height = hardwareBufferDescription.height;
|
|
||||||
p->width = hardwareBufferDescription.width;
|
|
||||||
TextureFormat textureFormat = mapToFilamentFormat(hardwareBufferDescription.format, sRGB);
|
|
||||||
p->format = textureFormat;
|
|
||||||
p->usage = mapToFilamentUsage(hardwareBufferDescription.usage, textureFormat);
|
|
||||||
return Platform::ExternalImageHandle{ p };
|
return Platform::ExternalImageHandle{ p };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,23 +178,20 @@ Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
|
|||||||
|
|
||||||
VulkanPlatformAndroid::ExternalImageDescAndroid VulkanPlatformAndroid::getExternalImageDesc(
|
VulkanPlatformAndroid::ExternalImageDescAndroid VulkanPlatformAndroid::getExternalImageDesc(
|
||||||
ExternalImageHandleRef externalImage) const noexcept {
|
ExternalImageHandleRef externalImage) const noexcept {
|
||||||
auto const* fvkExternalImage =
|
auto metadata = extractExternalImageMetadata(externalImage);
|
||||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
.width = fvkExternalImage->width,
|
.width = metadata.width,
|
||||||
.height = fvkExternalImage->height,
|
.height = metadata.height,
|
||||||
.format = fvkExternalImage->format,
|
.format = metadata.filamentFormat,
|
||||||
.usage = fvkExternalImage->usage,
|
.usage = metadata.filamentUsage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMetadata(
|
VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::extractExternalImageMetadata(
|
||||||
ExternalImageHandleRef externalImage) {
|
ExternalImageHandleRef image) const {
|
||||||
auto const* fvkExternalImage =
|
auto const* fvkExternalImage = static_cast<ExternalImageVulkanAndroid const*>(image.get());
|
||||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
|
||||||
|
|
||||||
ExternalImageMetadata metadata;
|
ExternalImageMetadata metadata = {};
|
||||||
AHardwareBuffer* buffer = fvkExternalImage->aHardwareBuffer;
|
AHardwareBuffer* buffer = fvkExternalImage->aHardwareBuffer;
|
||||||
if (__builtin_available(android 26, *)) {
|
if (__builtin_available(android 26, *)) {
|
||||||
AHardwareBuffer_Desc bufferDesc;
|
AHardwareBuffer_Desc bufferDesc;
|
||||||
@@ -249,16 +199,30 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMet
|
|||||||
metadata.width = bufferDesc.width;
|
metadata.width = bufferDesc.width;
|
||||||
metadata.height = bufferDesc.height;
|
metadata.height = bufferDesc.height;
|
||||||
metadata.layers = bufferDesc.layers;
|
metadata.layers = bufferDesc.layers;
|
||||||
metadata.isProtected = isProtectedFromUsage(bufferDesc.usage);
|
|
||||||
std::tie(metadata.format, metadata.usage) =
|
std::tie(metadata.format, metadata.usage) =
|
||||||
getVKFormatAndUsage(bufferDesc, fvkExternalImage->sRGB);
|
getVKFormatAndUsage(bufferDesc, fvkExternalImage->sRGB);
|
||||||
}
|
std::tie(metadata.filamentFormat, metadata.filamentUsage) =
|
||||||
|
getFilamentFormatAndUsage(bufferDesc, fvkExternalImage->sRGB);
|
||||||
|
|
||||||
|
if (isProtectedFromUsage(bufferDesc.usage)) {
|
||||||
|
metadata.filamentUsage |= TextureUsage::PROTECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: The following seems unnecessary. we should be able to discern directly from the
|
||||||
|
// bufferDesc.
|
||||||
|
if (any(metadata.filamentUsage & TextureUsage::BLIT_SRC)) {
|
||||||
|
metadata.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (any(metadata.filamentUsage & (TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE))) {
|
||||||
|
metadata.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
metadata.samples = VK_SAMPLE_COUNT_1_BIT;
|
metadata.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
|
||||||
|
|
||||||
VkAndroidHardwareBufferFormatPropertiesANDROID formatInfo = {
|
VkAndroidHardwareBufferFormatPropertiesANDROID formatInfo = {
|
||||||
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID,
|
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID,
|
||||||
.pNext = nullptr,
|
|
||||||
};
|
};
|
||||||
VkAndroidHardwareBufferPropertiesANDROID properties = {
|
VkAndroidHardwareBufferPropertiesANDROID properties = {
|
||||||
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
|
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
|
||||||
@@ -268,141 +232,116 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMet
|
|||||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||||
<< "vkGetAndroidHardwareBufferProperties failed with error="
|
<< "vkGetAndroidHardwareBufferProperties failed with error="
|
||||||
<< static_cast<int32_t>(result);
|
<< static_cast<int32_t>(result);
|
||||||
|
|
||||||
VkFormat bufferPropertiesFormat = transformVkFormat(formatInfo.format, fvkExternalImage->sRGB);
|
VkFormat bufferPropertiesFormat = transformVkFormat(formatInfo.format, fvkExternalImage->sRGB);
|
||||||
FILAMENT_CHECK_POSTCONDITION(metadata.format == bufferPropertiesFormat)
|
FILAMENT_CHECK_POSTCONDITION(metadata.format == bufferPropertiesFormat)
|
||||||
<< "mismatched image format( " << metadata.format << ") and queried format("
|
<< "mismatched image format( " << metadata.format << ") and queried format("
|
||||||
<< bufferPropertiesFormat << ") for external image (AHB)";
|
<< bufferPropertiesFormat << ") for external image (AHB)";
|
||||||
metadata.externalFormat = formatInfo.externalFormat;
|
metadata.externalFormat = formatInfo.externalFormat;
|
||||||
|
|
||||||
|
// Choose either externalFormat > 0 or metadata.format and prefer the latter.
|
||||||
|
if (metadata.externalFormat > 0 && metadata.format != VK_FORMAT_UNDEFINED) {
|
||||||
|
// See VUID-VkImageCreateInfo-pNext-09457
|
||||||
|
metadata.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||||
|
metadata.externalFormat = 0;
|
||||||
|
}
|
||||||
|
|
||||||
metadata.allocationSize = properties.allocationSize;
|
metadata.allocationSize = properties.allocationSize;
|
||||||
metadata.memoryTypeBits = properties.memoryTypeBits;
|
metadata.memoryTypeBits = properties.memoryTypeBits;
|
||||||
|
|
||||||
|
metadata.ycbcrConversionComponents = formatInfo.samplerYcbcrConversionComponents;
|
||||||
|
metadata.ycbcrModel = formatInfo.suggestedYcbcrModel;
|
||||||
|
metadata.ycbcrRange = formatInfo.suggestedYcbcrRange;
|
||||||
|
metadata.xChromaOffset = formatInfo.suggestedXChromaOffset;
|
||||||
|
metadata.yChromaOffset = formatInfo.suggestedYChromaOffset;
|
||||||
|
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanPlatformAndroid::ImageData VulkanPlatformAndroid::createExternalImageData(
|
VulkanPlatformAndroid::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
|
||||||
ExternalImageHandleRef externalImage, const ExternalImageMetadata& metadata,
|
ExternalImageHandleRef externalImage) const {
|
||||||
uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
auto const& metadata = extractExternalImageMetadata(externalImage);
|
||||||
|
|
||||||
auto const* fvkExternalImage =
|
auto const* fvkExternalImage =
|
||||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||||
ImageData data = allocateExternalImage(fvkExternalImage->aHardwareBuffer, getDevice(), metadata,
|
AHardwareBuffer* buffer = fvkExternalImage->aHardwareBuffer;
|
||||||
memoryTypeIndex, usage);
|
|
||||||
VkResult result = vkBindImageMemory(getDevice(), data.first, data.second, 0);
|
|
||||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
|
||||||
<< "vkBindImageMemory error=" << static_cast<int32_t>(result);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device, SamplerYcbcrConversion chroma,
|
// if external format we need to specifiy it in the allocation
|
||||||
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
|
bool const useExternalFormat = metadata.format == VK_FORMAT_UNDEFINED;
|
||||||
VkImageViewType viewType, VkComponentMapping swizzle){
|
|
||||||
VkExternalFormatANDROID externalFormat = {
|
VkExternalFormatANDROID const externalFormat = {
|
||||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
|
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.externalFormat = internalFormat,
|
.externalFormat = metadata.externalFormat,
|
||||||
|
};
|
||||||
|
VkExternalMemoryImageCreateInfo const externalCreateInfo = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
|
||||||
|
.pNext = useExternalFormat ? &externalFormat : nullptr,
|
||||||
|
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
|
||||||
};
|
};
|
||||||
|
|
||||||
TextureSwizzle const swizzleArray[] = {chroma.r, chroma.g, chroma.b, chroma.a};
|
VkImageCreateInfo const imageInfo{
|
||||||
VkSamplerYcbcrConversionCreateInfo conversionInfo = {
|
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
|
.pNext = &externalCreateInfo,
|
||||||
.pNext = &externalFormat,
|
.flags = useExternalFormat ? VK_IMAGE_CREATE_ALIAS_BIT : 0u,
|
||||||
.format = VK_FORMAT_UNDEFINED,
|
.imageType = VK_IMAGE_TYPE_2D,
|
||||||
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
|
.format = metadata.format,
|
||||||
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
|
.extent = {
|
||||||
.components = fvkutils::getSwizzleMap(swizzleArray),
|
metadata.width,
|
||||||
.xChromaOffset = fvkutils::getChromaLocation(chroma.xChromaOffset),
|
metadata.height,
|
||||||
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset),
|
1u,
|
||||||
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
|
},
|
||||||
|
.mipLevels = 1,
|
||||||
|
.arrayLayers = metadata.layers,
|
||||||
|
.samples = metadata.samples,
|
||||||
|
.usage = metadata.usage,
|
||||||
};
|
};
|
||||||
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
|
||||||
VkResult result = vkCreateSamplerYcbcrConversion(device, &conversionInfo,
|
VkDevice const device = getDevice();
|
||||||
nullptr, &conversion);
|
|
||||||
|
VkImage image;
|
||||||
|
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &image);
|
||||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||||
<< "Unable to create Ycbcr Conversion."
|
<< "vkCreateImage failed with error=" << static_cast<int32_t>(result);
|
||||||
<< " error=" << static_cast<int32_t>(result);
|
|
||||||
|
|
||||||
VkSamplerYcbcrConversionInfo samplerYcbcrConversionInfo = {
|
// Allocate the memory
|
||||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
|
VkImportAndroidHardwareBufferInfoANDROID const androidHardwareBufferInfo = {
|
||||||
.pNext = nullptr,
|
.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
|
||||||
.conversion = conversion,
|
.buffer = buffer,
|
||||||
};
|
};
|
||||||
|
VkMemoryDedicatedAllocateInfo const memoryDedicatedAllocateInfo = {
|
||||||
VkImageViewCreateInfo viewInfo = {
|
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
.pNext = &androidHardwareBufferInfo,
|
||||||
.pNext = &samplerYcbcrConversionInfo,
|
|
||||||
.flags = 0,
|
|
||||||
.image = image,
|
.image = image,
|
||||||
.viewType = viewType,
|
.buffer = VK_NULL_HANDLE,
|
||||||
.format = VK_FORMAT_UNDEFINED,
|
|
||||||
.components = swizzle,
|
|
||||||
.subresourceRange = range,
|
|
||||||
};
|
};
|
||||||
VkImageView imageView;
|
|
||||||
result = vkCreateImageView(device, &viewInfo, VKALLOC, &imageView);
|
VkPhysicalDeviceMemoryProperties memoryProperties;
|
||||||
|
vkGetPhysicalDeviceMemoryProperties(getPhysicalDevice(), &memoryProperties);
|
||||||
|
|
||||||
|
|
||||||
|
VkMemoryPropertyFlags const requiredMemoryFlags =
|
||||||
|
any(metadata.filamentUsage & TextureUsage::UPLOADABLE)
|
||||||
|
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
|
||||||
|
: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||||
|
|
||||||
|
uint32_t const memoryTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
|
||||||
|
metadata.memoryTypeBits, requiredMemoryFlags);
|
||||||
|
|
||||||
|
VkMemoryAllocateInfo const allocInfo = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||||
|
.pNext = &memoryDedicatedAllocateInfo,
|
||||||
|
.allocationSize = metadata.allocationSize,
|
||||||
|
.memoryTypeIndex = memoryTypeIndex,
|
||||||
|
};
|
||||||
|
VkDeviceMemory memory;
|
||||||
|
result = vkAllocateMemory(device, &allocInfo, VKALLOC, &memory);
|
||||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||||
<< "Unable to create VkImageView."
|
<< "vkAllocateMemory failed with error=" << static_cast<int32_t>(result);
|
||||||
<< " error=" << static_cast<int32_t>(result);
|
|
||||||
|
|
||||||
return imageView;
|
result = vkBindImageMemory(getDevice(), image, memory, 0);
|
||||||
}
|
|
||||||
|
|
||||||
VkSampler VulkanPlatform::createExternalSamplerImpl(
|
|
||||||
VkDevice device, SamplerYcbcrConversion chroma, SamplerParams params,
|
|
||||||
uint32_t internalFormat) {
|
|
||||||
VkExternalFormatANDROID externalFormat = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.externalFormat = internalFormat,
|
|
||||||
};
|
|
||||||
|
|
||||||
TextureSwizzle const swizzleArray[] = {chroma.r, chroma.g, chroma.b, chroma.a};
|
|
||||||
VkSamplerYcbcrConversionCreateInfo conversionInfo = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
|
|
||||||
.pNext = &externalFormat,
|
|
||||||
.format = VK_FORMAT_UNDEFINED,
|
|
||||||
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
|
|
||||||
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
|
|
||||||
.components = fvkutils::getSwizzleMap(swizzleArray),
|
|
||||||
.xChromaOffset = fvkutils::getChromaLocation(chroma.xChromaOffset),
|
|
||||||
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset),
|
|
||||||
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
|
|
||||||
};
|
|
||||||
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
|
||||||
VkResult result = vkCreateSamplerYcbcrConversion(device, &conversionInfo,
|
|
||||||
nullptr, &conversion);
|
|
||||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||||
<< "Unable to create Ycbcr Conversion."
|
<< "vkBindImageMemory error=" << static_cast<int32_t>(result);
|
||||||
<< " error=" << static_cast<int32_t>(result);
|
return { image, memory };
|
||||||
|
|
||||||
VkSamplerYcbcrConversionInfo samplerYcbcrConversionInfo = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.conversion = conversion,
|
|
||||||
};
|
|
||||||
|
|
||||||
VkSamplerCreateInfo samplerInfo = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
|
||||||
.pNext = &samplerYcbcrConversionInfo,
|
|
||||||
.magFilter = fvkutils::getFilter(params.filterMag),
|
|
||||||
.minFilter = fvkutils::getFilter(params.filterMin),
|
|
||||||
.mipmapMode = fvkutils::getMipmapMode(params.filterMin),
|
|
||||||
.addressModeU = fvkutils::getWrapMode(params.wrapS),
|
|
||||||
.addressModeV = fvkutils::getWrapMode(params.wrapT),
|
|
||||||
.addressModeW = fvkutils::getWrapMode(params.wrapR),
|
|
||||||
.anisotropyEnable = params.anisotropyLog2 == 0 ? VK_FALSE : VK_TRUE,
|
|
||||||
.maxAnisotropy = (float)(1u << params.anisotropyLog2),
|
|
||||||
.compareEnable = fvkutils::getCompareEnable(params.compareMode),
|
|
||||||
.compareOp = fvkutils::getCompareOp(params.compareFunc),
|
|
||||||
.minLod = 0.0f,
|
|
||||||
.maxLod = fvkutils::getMaxLod(params.filterMin),
|
|
||||||
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
|
|
||||||
.unnormalizedCoordinates = VK_FALSE,
|
|
||||||
};
|
|
||||||
VkSampler sampler;
|
|
||||||
result = vkCreateSampler(device, &samplerInfo, VKALLOC, &sampler);
|
|
||||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
|
||||||
<< "Unable to create sampler."
|
|
||||||
<< " error=" << static_cast<int32_t>(result);
|
|
||||||
return sampler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanPlatform::ExtensionSet VulkanPlatformAndroid::getSwapchainInstanceExtensions() const {
|
VulkanPlatform::ExtensionSet VulkanPlatformAndroid::getSwapchainInstanceExtensions() const {
|
||||||
@@ -430,20 +369,9 @@ VulkanPlatform::SurfaceBundle VulkanPlatformAndroid::createVkSurfaceKHR(void* na
|
|||||||
// Deprecated platform dependent helper methods
|
// Deprecated platform dependent helper methods
|
||||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { return {}; }
|
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { return {}; }
|
||||||
|
|
||||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
|
|
||||||
ExternalImageHandleRef externalImage, VkDevice device) {
|
|
||||||
return ExternalImageMetadata{};
|
|
||||||
}
|
|
||||||
|
|
||||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
|
|
||||||
ExternalImageHandleRef externalImage, VkDevice device,
|
|
||||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
|
||||||
return ImageData{};
|
|
||||||
}
|
|
||||||
|
|
||||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||||
VkInstance instance, uint64_t flags) noexcept {
|
VkInstance instance, uint64_t flags) noexcept {
|
||||||
return SurfaceBundle{};
|
return SurfaceBundle{};
|
||||||
}
|
}
|
||||||
|
|
||||||
}// namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -63,30 +63,6 @@ VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl(
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
|
|
||||||
ExternalImageHandleRef externalImage, VkDevice device) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
|
|
||||||
ExternalImageHandleRef externalImage, VkDevice device,
|
|
||||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
VkSampler VulkanPlatform::createExternalSamplerImpl(VkDevice device,
|
|
||||||
SamplerYcbcrConversion chroma,
|
|
||||||
SamplerParams sampler,
|
|
||||||
uint32_t internalFormat) {
|
|
||||||
return VK_NULL_HANDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
|
|
||||||
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
|
|
||||||
VkImageSubresourceRange range, VkImageViewType viewType, VkComponentMapping swizzle) {
|
|
||||||
return VK_NULL_HANDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||||
VkInstance instance, uint64_t flags) noexcept {
|
VkInstance instance, uint64_t flags) noexcept {
|
||||||
VkSurfaceKHR surface;
|
VkSurfaceKHR surface;
|
||||||
|
|||||||
@@ -84,30 +84,6 @@ using namespace bluevk;
|
|||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
|
|
||||||
ExternalImageHandleRef externalImage, VkDevice device) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
|
|
||||||
ExternalImageHandleRef externalImage, VkDevice device,
|
|
||||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
VkSampler VulkanPlatform::createExternalSamplerImpl(VkDevice device,
|
|
||||||
SamplerYcbcrConversion chroma,
|
|
||||||
SamplerParams sampler,
|
|
||||||
uint32_t internalFormat) {
|
|
||||||
return VK_NULL_HANDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
|
|
||||||
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
|
|
||||||
VkImageSubresourceRange range, VkImageViewType viewType, VkComponentMapping swizzle) {
|
|
||||||
return VK_NULL_HANDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
|
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
|
||||||
VulkanPlatform::ExtensionSet const ret = {
|
VulkanPlatform::ExtensionSet const ret = {
|
||||||
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
||||||
|
|||||||
@@ -18,12 +18,12 @@
|
|||||||
#define TNT_FILAMENT_BACKEND_VULKANSWAPCHAIN_IMPL_H
|
#define TNT_FILAMENT_BACKEND_VULKANSWAPCHAIN_IMPL_H
|
||||||
|
|
||||||
#include "vulkan/VulkanContext.h"
|
#include "vulkan/VulkanContext.h"
|
||||||
|
#include "vulkan/VulkanConstants.h"
|
||||||
|
|
||||||
#include <backend/platforms/VulkanPlatform.h>
|
#include <backend/platforms/VulkanPlatform.h>
|
||||||
|
|
||||||
#include <bluevk/BlueVK.h>
|
#include <bluevk/BlueVK.h>
|
||||||
|
|
||||||
#include <tuple>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
using namespace bluevk;
|
using namespace bluevk;
|
||||||
|
|||||||
@@ -668,4 +668,111 @@ VkShaderStageFlags getShaderStageFlags(ShaderStageFlags stageFlags) {
|
|||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VkSamplerYcbcrModelConversion getYcbcrModelConversion(
|
||||||
|
SamplerYcbcrModelConversion model) {
|
||||||
|
switch (model) {
|
||||||
|
case SamplerYcbcrModelConversion::RGB_IDENTITY:
|
||||||
|
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY;
|
||||||
|
case SamplerYcbcrModelConversion::YCBCR_IDENTITY:
|
||||||
|
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY;
|
||||||
|
case SamplerYcbcrModelConversion::YCBCR_709:
|
||||||
|
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709;
|
||||||
|
case SamplerYcbcrModelConversion::YCBCR_601:
|
||||||
|
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601;
|
||||||
|
case SamplerYcbcrModelConversion::YCBCR_2020:
|
||||||
|
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020;
|
||||||
|
default:
|
||||||
|
assert_invariant(false &&
|
||||||
|
"Unknown data type, conversion is not supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range) {
|
||||||
|
switch (range) {
|
||||||
|
case SamplerYcbcrRange::ITU_FULL:
|
||||||
|
return VK_SAMPLER_YCBCR_RANGE_ITU_FULL;
|
||||||
|
case SamplerYcbcrRange::ITU_NARROW:
|
||||||
|
return VK_SAMPLER_YCBCR_RANGE_ITU_NARROW;
|
||||||
|
default:
|
||||||
|
assert_invariant(false &&
|
||||||
|
"Unknown data type, conversion is not supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkChromaLocation getChromaLocation(ChromaLocation loc) {
|
||||||
|
switch (loc) {
|
||||||
|
case ChromaLocation::COSITED_EVEN:
|
||||||
|
return VK_CHROMA_LOCATION_COSITED_EVEN;
|
||||||
|
case ChromaLocation::MIDPOINT:
|
||||||
|
return VK_CHROMA_LOCATION_MIDPOINT;
|
||||||
|
default:
|
||||||
|
assert_invariant(false &&
|
||||||
|
"Unknown data type, conversion is not supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SamplerYcbcrModelConversion getYcbcrModelConversionFilament(VkSamplerYcbcrModelConversion model) {
|
||||||
|
switch (model) {
|
||||||
|
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY:
|
||||||
|
return SamplerYcbcrModelConversion::RGB_IDENTITY;
|
||||||
|
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY:
|
||||||
|
return SamplerYcbcrModelConversion::YCBCR_IDENTITY;
|
||||||
|
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709:
|
||||||
|
return SamplerYcbcrModelConversion::YCBCR_709;
|
||||||
|
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601:
|
||||||
|
return SamplerYcbcrModelConversion::YCBCR_601;
|
||||||
|
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020:
|
||||||
|
return SamplerYcbcrModelConversion::YCBCR_2020;
|
||||||
|
default:
|
||||||
|
assert_invariant(false && "Unknown data type, conversion is not supported.");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SamplerYcbcrRange getYcbcrRangeFilament(VkSamplerYcbcrRange range) {
|
||||||
|
switch (range) {
|
||||||
|
case VK_SAMPLER_YCBCR_RANGE_ITU_FULL:
|
||||||
|
return SamplerYcbcrRange::ITU_FULL;
|
||||||
|
case VK_SAMPLER_YCBCR_RANGE_ITU_NARROW:
|
||||||
|
return SamplerYcbcrRange::ITU_NARROW;
|
||||||
|
default:
|
||||||
|
assert_invariant(false && "Unknown data type, conversion is not supported.");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChromaLocation getChromaLocationFilament(VkChromaLocation loc) {
|
||||||
|
switch (loc) {
|
||||||
|
case VK_CHROMA_LOCATION_COSITED_EVEN:
|
||||||
|
return ChromaLocation::COSITED_EVEN;
|
||||||
|
case VK_CHROMA_LOCATION_MIDPOINT:
|
||||||
|
return ChromaLocation::MIDPOINT;
|
||||||
|
default:
|
||||||
|
assert_invariant(false && "Unknown data type, conversion is not supported.");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureSwizzle getSwizzleFilament(VkComponentSwizzle c, uint8_t rgbaIndex) {
|
||||||
|
switch (c) {
|
||||||
|
case VK_COMPONENT_SWIZZLE_ZERO:
|
||||||
|
return TextureSwizzle::SUBSTITUTE_ZERO;
|
||||||
|
case VK_COMPONENT_SWIZZLE_ONE:
|
||||||
|
return TextureSwizzle::SUBSTITUTE_ONE;
|
||||||
|
case VK_COMPONENT_SWIZZLE_IDENTITY:
|
||||||
|
return (TextureSwizzle) (((uint8_t) TextureSwizzle::CHANNEL_0) + rgbaIndex);
|
||||||
|
case VK_COMPONENT_SWIZZLE_R:
|
||||||
|
return TextureSwizzle::CHANNEL_0;
|
||||||
|
case VK_COMPONENT_SWIZZLE_G:
|
||||||
|
return TextureSwizzle::CHANNEL_1;
|
||||||
|
case VK_COMPONENT_SWIZZLE_B:
|
||||||
|
return TextureSwizzle::CHANNEL_2;
|
||||||
|
case VK_COMPONENT_SWIZZLE_A:
|
||||||
|
return TextureSwizzle::CHANNEL_3;
|
||||||
|
default:
|
||||||
|
assert_invariant(false && "Unknown data type, conversion is not supported.");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace filament::backend::fvkutils
|
} // namespace filament::backend::fvkutils
|
||||||
|
|||||||
@@ -69,6 +69,12 @@ VkSamplerYcbcrModelConversion getYcbcrModelConversion(SamplerYcbcrModelConversio
|
|||||||
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range);
|
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range);
|
||||||
VkChromaLocation getChromaLocation(ChromaLocation loc);
|
VkChromaLocation getChromaLocation(ChromaLocation loc);
|
||||||
|
|
||||||
|
// Ycbcr related functions
|
||||||
|
SamplerYcbcrModelConversion getYcbcrModelConversionFilament(VkSamplerYcbcrModelConversion model);
|
||||||
|
SamplerYcbcrRange getYcbcrRangeFilament(VkSamplerYcbcrRange range);
|
||||||
|
ChromaLocation getChromaLocationFilament(VkChromaLocation loc);
|
||||||
|
TextureSwizzle getSwizzleFilament(VkComponentSwizzle c, uint8_t rgbaIndex);
|
||||||
|
|
||||||
inline VkImageViewType getViewType(SamplerType target) {
|
inline VkImageViewType getViewType(SamplerType target) {
|
||||||
switch (target) {
|
switch (target) {
|
||||||
case SamplerType::SAMPLER_CUBEMAP:
|
case SamplerType::SAMPLER_CUBEMAP:
|
||||||
|
|||||||
@@ -348,6 +348,10 @@ using SamplerBitmask = utils::bitset64;
|
|||||||
// general.
|
// general.
|
||||||
using InputAttachmentBitmask = utils::bitset64;
|
using InputAttachmentBitmask = utils::bitset64;
|
||||||
|
|
||||||
|
constexpr uint8_t MAX_DESCRIPTOR_SET_BITMASK_BITS =
|
||||||
|
std::max(std::max(sizeof(UniformBufferBitmask), sizeof(SamplerBitmask)),
|
||||||
|
sizeof(InputAttachmentBitmask)) * 8;
|
||||||
|
|
||||||
template<typename Bitmask>
|
template<typename Bitmask>
|
||||||
static constexpr uint8_t getVertexStageShift() noexcept {
|
static constexpr uint8_t getVertexStageShift() noexcept {
|
||||||
// We assume the bottom half of bits are for vertex stages.
|
// We assume the bottom half of bits are for vertex stages.
|
||||||
|
|||||||
@@ -193,49 +193,6 @@ uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask) {
|
|||||||
return mostSignificantBit((sampleCount - 1) & mask);
|
return mostSignificantBit((sampleCount - 1) & mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
VkSamplerYcbcrModelConversion getYcbcrModelConversion(
|
|
||||||
SamplerYcbcrModelConversion model) {
|
|
||||||
switch (model) {
|
|
||||||
case SamplerYcbcrModelConversion::RGB_IDENTITY:
|
|
||||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY;
|
|
||||||
case SamplerYcbcrModelConversion::YCBCR_IDENTITY:
|
|
||||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY;
|
|
||||||
case SamplerYcbcrModelConversion::YCBCR_709:
|
|
||||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709;
|
|
||||||
case SamplerYcbcrModelConversion::YCBCR_601:
|
|
||||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601;
|
|
||||||
case SamplerYcbcrModelConversion::YCBCR_2020:
|
|
||||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020;
|
|
||||||
default:
|
|
||||||
assert_invariant(false &&
|
|
||||||
"Unknown data type, conversion is not supported.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range) {
|
|
||||||
switch (range) {
|
|
||||||
case SamplerYcbcrRange::ITU_FULL:
|
|
||||||
return VK_SAMPLER_YCBCR_RANGE_ITU_FULL;
|
|
||||||
case SamplerYcbcrRange::ITU_NARROW:
|
|
||||||
return VK_SAMPLER_YCBCR_RANGE_ITU_NARROW;
|
|
||||||
default:
|
|
||||||
assert_invariant(false &&
|
|
||||||
"Unknown data type, conversion is not supported.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VkChromaLocation getChromaLocation(ChromaLocation loc) {
|
|
||||||
switch (loc) {
|
|
||||||
case ChromaLocation::COSITED_EVEN:
|
|
||||||
return VK_CHROMA_LOCATION_COSITED_EVEN;
|
|
||||||
case ChromaLocation::MIDPOINT:
|
|
||||||
return VK_CHROMA_LOCATION_MIDPOINT;
|
|
||||||
default:
|
|
||||||
assert_invariant(false &&
|
|
||||||
"Unknown data type, conversion is not supported.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace filament::backend::fvkutils
|
} // namespace filament::backend::fvkutils
|
||||||
|
|
||||||
bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) {
|
bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) {
|
||||||
|
|||||||
@@ -252,12 +252,19 @@ void printDeviceDetails(wgpu::Device const& device) {
|
|||||||
|
|
||||||
}// namespace
|
}// namespace
|
||||||
|
|
||||||
Driver* WebGPUDriver::create(WebGPUPlatform& platform) noexcept {
|
Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept {
|
||||||
return new WebGPUDriver(platform);
|
constexpr size_t defaultSize = FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
|
||||||
|
Platform::DriverConfig validConfig {driverConfig};
|
||||||
|
validConfig.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize);
|
||||||
|
return new WebGPUDriver(platform, validConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform) noexcept
|
WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept
|
||||||
: mPlatform(platform) {
|
: mPlatform(platform),
|
||||||
|
mHandleAllocator("Handles",
|
||||||
|
driverConfig.handleArenaSize,
|
||||||
|
driverConfig.disableHandleUseAfterFreeCheck,
|
||||||
|
driverConfig.disableHeapHandleTags) {
|
||||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||||
printInstanceDetails(mPlatform.getInstance());
|
printInstanceDetails(mPlatform.getInstance());
|
||||||
#endif
|
#endif
|
||||||
@@ -385,7 +392,7 @@ Handle<HwTimerQuery> WebGPUDriver::createTimerQueryS() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwIndexBuffer> WebGPUDriver::createIndexBufferS() noexcept {
|
Handle<HwIndexBuffer> WebGPUDriver::createIndexBufferS() noexcept {
|
||||||
return Handle<HwIndexBuffer>((Handle<HwIndexBuffer>::HandleId) mNextFakeHandle++);
|
return allocHandle<HwIndexBuffer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept {
|
Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "DriverBase.h"
|
#include "DriverBase.h"
|
||||||
#include "private/backend/Dispatcher.h"
|
#include "private/backend/Dispatcher.h"
|
||||||
#include "private/backend/Driver.h"
|
#include "private/backend/Driver.h"
|
||||||
|
#include "private/backend/HandleAllocator.h"
|
||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
|
|
||||||
#include <utils/compiler.h>
|
#include <utils/compiler.h>
|
||||||
@@ -30,6 +31,10 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#ifndef FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB
|
||||||
|
# define FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB 8
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,10 +45,10 @@ public:
|
|||||||
~WebGPUDriver() noexcept override;
|
~WebGPUDriver() noexcept override;
|
||||||
|
|
||||||
[[nodiscard]] Dispatcher getDispatcher() const noexcept final;
|
[[nodiscard]] Dispatcher getDispatcher() const noexcept final;
|
||||||
[[nodiscard]] static Driver* create(WebGPUPlatform& platform) noexcept;
|
[[nodiscard]] static Driver* create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit WebGPUDriver(WebGPUPlatform& platform) noexcept;
|
explicit WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
|
||||||
[[nodiscard]] ShaderModel getShaderModel() const noexcept final;
|
[[nodiscard]] ShaderModel getShaderModel() const noexcept final;
|
||||||
[[nodiscard]] ShaderLanguage getShaderLanguage() const noexcept final;
|
[[nodiscard]] ShaderLanguage getShaderLanguage() const noexcept final;
|
||||||
|
|
||||||
@@ -74,8 +79,20 @@ private:
|
|||||||
UTILS_ALWAYS_INLINE inline void methodName##R(RetType, paramsDecl);
|
UTILS_ALWAYS_INLINE inline void methodName##R(RetType, paramsDecl);
|
||||||
|
|
||||||
#include "private/backend/DriverAPI.inc"
|
#include "private/backend/DriverAPI.inc"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Memory management
|
||||||
|
*/
|
||||||
|
|
||||||
|
HandleAllocatorWGPU mHandleAllocator;
|
||||||
|
|
||||||
|
template<typename D>
|
||||||
|
Handle<D> allocHandle() {
|
||||||
|
return mHandleAllocator.allocate<D>();
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}// namespace filament::backend
|
}// namespace filament::backend
|
||||||
|
|
||||||
#endif// TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
|
#endif // TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
|
||||||
|
|||||||
@@ -144,13 +144,13 @@ wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Driver* WebGPUPlatform::createDriver(void* sharedContext,
|
Driver* WebGPUPlatform::createDriver(void* sharedContext,
|
||||||
const Platform::DriverConfig& /*driverConfig*/) noexcept {
|
const Platform::DriverConfig& driverConfig) noexcept {
|
||||||
if (sharedContext) {
|
if (sharedContext) {
|
||||||
FWGPU_LOGW << "sharedContext is ignored/unused in the WebGPU backend. A non-null "
|
FWGPU_LOGW << "sharedContext is ignored/unused in the WebGPU backend. A non-null "
|
||||||
"sharedContext was provided, but it will be ignored."
|
"sharedContext was provided, but it will be ignored."
|
||||||
<< utils::io::endl;
|
<< utils::io::endl;
|
||||||
}
|
}
|
||||||
return WebGPUDriver::create(*this);
|
return WebGPUDriver::create(*this, driverConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebGPUPlatform::WebGPUPlatform()
|
WebGPUPlatform::WebGPUPlatform()
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* 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 "ImageExpectations.h"
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "utils/Hash.h"
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "backend/PixelBufferDescriptor.h"
|
||||||
|
#include "private/backend/DriverApi.h"
|
||||||
|
|
||||||
|
#ifndef FILAMENT_IOS
|
||||||
|
|
||||||
|
#include <imageio/ImageEncoder.h>
|
||||||
|
#include <image/ColorTransform.h>
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ScreenshotParams::ScreenshotParams(int width, int height, std::string fileName,
|
||||||
|
uint32_t expectedPixelHash)
|
||||||
|
: mWidth(width), mHeight(height), mFileName(std::move(fileName)),
|
||||||
|
mExpectedPixelHash(expectedPixelHash) {}
|
||||||
|
|
||||||
|
int ScreenshotParams::width() const {
|
||||||
|
return mWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenshotParams::height() const {
|
||||||
|
return mHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ScreenshotParams::expectedHash() const {
|
||||||
|
return mExpectedPixelHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ScreenshotParams::outputDirectoryPath() const {
|
||||||
|
return ".";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ScreenshotParams::generatedActualFileName() const {
|
||||||
|
return absl::StrFormat("%s_actual.png", mFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ScreenshotParams::generatedActualFilePath() const {
|
||||||
|
return absl::StrFormat("%s/%s", outputDirectoryPath(), generatedActualFileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ScreenshotParams::goldenFileName() const {
|
||||||
|
return absl::StrFormat("%s_golden.png", mFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ScreenshotParams::goldenFilePath() const {
|
||||||
|
return absl::StrFormat("%s/%s", outputDirectoryPath(), goldenFileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageExpectation::ImageExpectation(const char* fileName, int lineNumber,
|
||||||
|
filament::backend::DriverApi& api, ScreenshotParams params,
|
||||||
|
filament::backend::RenderTargetHandle renderTarget)
|
||||||
|
: mFileName(fileName), mLineNumber(lineNumber), mParams(std::move(params)),
|
||||||
|
mResult(api, renderTarget, mParams) {}
|
||||||
|
|
||||||
|
void ImageExpectation::evaluate() {
|
||||||
|
// Ensure this is only evaluated once.
|
||||||
|
if (mEvaluated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mEvaluated = true;
|
||||||
|
|
||||||
|
// Do the actual image comparison inside a scoped trace with the stored file and line.
|
||||||
|
{
|
||||||
|
testing::ScopedTrace trace(mFileName, mLineNumber, "");
|
||||||
|
compareImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageExpectation::compareImage() const {
|
||||||
|
bool bytesFilled = mResult.bytesFilled();
|
||||||
|
EXPECT_THAT(bytesFilled, testing::IsTrue())
|
||||||
|
<< "Render target wasn't copied to the buffer for " << mFileName;
|
||||||
|
if (bytesFilled) {
|
||||||
|
uint32_t actualHash = mResult.hash();
|
||||||
|
EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageExpectations::ImageExpectations(filament::backend::DriverApi& api) : mApi(api) {}
|
||||||
|
|
||||||
|
ImageExpectations::~ImageExpectations() {
|
||||||
|
// Guarantee that all expectations are evaluated when this leaves scope even if the caller
|
||||||
|
// forgot to manually evaluate them.
|
||||||
|
evaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageExpectations::addExpectation(const char* fileName, int lineNumber,
|
||||||
|
filament::backend::RenderTargetHandle renderTarget, ScreenshotParams params) {
|
||||||
|
mExpectations.emplace_back(fileName, lineNumber, mApi, std::move(params), renderTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageExpectations::evaluate() {
|
||||||
|
for (auto& expectation: mExpectations) {
|
||||||
|
expectation.evaluate();
|
||||||
|
}
|
||||||
|
mExpectations.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderTargetDump::RenderTargetDump(filament::backend::DriverApi& api,
|
||||||
|
filament::backend::RenderTargetHandle renderTarget, const ScreenshotParams& params)
|
||||||
|
: mInternal(std::make_unique<RenderTargetDump::Internal>(params)) {
|
||||||
|
#ifdef FILAMENT_IOS
|
||||||
|
bytesFilled_ = true;
|
||||||
|
bytes_.resize(size);
|
||||||
|
std::fill(bytes_.begin(), bytes_.end(), 0);
|
||||||
|
#else
|
||||||
|
const size_t size = mInternal->params.width() * mInternal->params.height() * 4;
|
||||||
|
mInternal->bytes.resize(size);
|
||||||
|
|
||||||
|
auto cb = [](void* buffer, size_t size, void* user) {
|
||||||
|
auto* internal = static_cast<RenderTargetDump::Internal*>(user);
|
||||||
|
image::LinearImage image(internal->params.width(), internal->params.width(), 4);
|
||||||
|
image = image::toLinearWithAlpha<uint8_t>(internal->params.width(),
|
||||||
|
internal->params.height(),
|
||||||
|
internal->params.width() * 4, (uint8_t*)buffer);
|
||||||
|
std::string filePath = internal->params.generatedActualFilePath();
|
||||||
|
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
|
||||||
|
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG, image, "",
|
||||||
|
filePath);
|
||||||
|
internal->bytesFilled = true;
|
||||||
|
};
|
||||||
|
filament::backend::PixelBufferDescriptor pb(mInternal->bytes.data(), size,
|
||||||
|
filament::backend::PixelDataFormat::RGBA, filament::backend::PixelDataType::UBYTE, cb,
|
||||||
|
(void*)mInternal.get());
|
||||||
|
api.readPixels(renderTarget, 0, 0, mInternal->params.width(), mInternal->params.height(),
|
||||||
|
std::move(pb));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderTargetDump::~RenderTargetDump() {
|
||||||
|
// If the GPU callback hasn't been made yet then there's a callback elsewhere that has a copy of
|
||||||
|
// the internal pointer. But there's no guarantee that the callback will be ever made if the GPU
|
||||||
|
// pipeline wasn't run for some reason. So it is necessary to leak the memory.
|
||||||
|
// It would be possible to try to coordinate with the callback to have it clean up the memory,
|
||||||
|
// but if this condition happens there's already an issue with the test case so there's no need.
|
||||||
|
if (!bytesFilled()) {
|
||||||
|
mInternal.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t RenderTargetDump::hash() const {
|
||||||
|
return utils::hash::murmur3((uint32_t*)mInternal->bytes.data(), mInternal->bytes.size() / 4, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderTargetDump::bytesFilled() const {
|
||||||
|
return mInternal->bytesFilled;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderTargetDump::Internal::Internal(const ScreenshotParams& params) : params(params) {}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* 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_IMAGE_EXPECTATIONS_H
|
||||||
|
#define TNT_IMAGE_EXPECTATIONS_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "backend/Handle.h"
|
||||||
|
#include "backend/DriverApiForward.h"
|
||||||
|
|
||||||
|
// Arguments are (RenderTargetHandle renderTarget, ImageExpectations& expectations,
|
||||||
|
// ScreenshotParams screenshotParams)
|
||||||
|
#define EXPECT_IMAGE(renderTarget, expectations, screenshotParams) \
|
||||||
|
do { \
|
||||||
|
expectations.addExpectation( \
|
||||||
|
__FILE__, \
|
||||||
|
__LINE__, \
|
||||||
|
renderTarget, \
|
||||||
|
screenshotParams); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores user-provided configuration values for an image expectation
|
||||||
|
*/
|
||||||
|
class ScreenshotParams {
|
||||||
|
public:
|
||||||
|
ScreenshotParams(int width, int height, std::string fileName, uint32_t expectedPixelHash);
|
||||||
|
|
||||||
|
int width() const;
|
||||||
|
int height() const;
|
||||||
|
uint32_t expectedHash() const;
|
||||||
|
|
||||||
|
std::string outputDirectoryPath() const;
|
||||||
|
std::string generatedActualFileName() const;
|
||||||
|
std::string generatedActualFilePath() const;
|
||||||
|
std::string goldenFileName() const;
|
||||||
|
std::string goldenFilePath() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int mWidth;
|
||||||
|
int mHeight;
|
||||||
|
uint32_t mExpectedPixelHash;
|
||||||
|
std::string mFileName;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When created adds a command to the GPU pipeline to copy the render target into a buffer and
|
||||||
|
* stores the result.
|
||||||
|
* If this object is destroyed before the GPU pipeline is flushed it will leak memory in order to
|
||||||
|
* avoid the GPU pipeline callback being a use-after-free.
|
||||||
|
*/
|
||||||
|
class RenderTargetDump {
|
||||||
|
public:
|
||||||
|
RenderTargetDump(filament::backend::DriverApi& api,
|
||||||
|
filament::backend::RenderTargetHandle renderTarget, const ScreenshotParams& params);
|
||||||
|
RenderTargetDump(RenderTargetDump&& other) = default;
|
||||||
|
RenderTargetDump& operator=(RenderTargetDump&& other) = default;
|
||||||
|
~RenderTargetDump();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should only bue used if BytesFilled returns true.
|
||||||
|
* @return The hash of the stored bytes.
|
||||||
|
*/
|
||||||
|
uint32_t hash() const;
|
||||||
|
/**
|
||||||
|
* Thread safe as this is backed by an atomic.
|
||||||
|
* Once this returns true it will never return false.
|
||||||
|
* @return Whether the bytes have actually been copied from the GPU to the buffer.
|
||||||
|
*/
|
||||||
|
bool bytesFilled() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Internal {
|
||||||
|
explicit Internal(const ScreenshotParams& params);
|
||||||
|
ScreenshotParams params;
|
||||||
|
std::atomic<bool> bytesFilled = false;
|
||||||
|
std::vector<unsigned char> bytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
// We need a memory location that won't be invalidated to pass to GPU callbacks as they can't
|
||||||
|
// be canceled during the destructor.
|
||||||
|
std::unique_ptr<Internal> mInternal;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageExpectation {
|
||||||
|
public:
|
||||||
|
ImageExpectation(const char* fileName, int lineNumber, filament::backend::DriverApi& api,
|
||||||
|
ScreenshotParams params, filament::backend::RenderTargetHandle renderTarget);
|
||||||
|
|
||||||
|
void evaluate();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void compareImage() const;
|
||||||
|
|
||||||
|
bool mEvaluated = false;
|
||||||
|
const char* mFileName;
|
||||||
|
int mLineNumber;
|
||||||
|
ScreenshotParams mParams;
|
||||||
|
RenderTargetDump mResult;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageExpectations {
|
||||||
|
public:
|
||||||
|
explicit ImageExpectations(filament::backend::DriverApi& api);
|
||||||
|
~ImageExpectations();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not meant to be called directly, use EXPECT_IMAGE to get the file name and line number
|
||||||
|
*/
|
||||||
|
void addExpectation(const char* fileName, int lineNumber,
|
||||||
|
filament::backend::RenderTargetHandle renderTarget, ScreenshotParams params);
|
||||||
|
|
||||||
|
void evaluate();
|
||||||
|
|
||||||
|
private:
|
||||||
|
filament::backend::DriverApi& mApi;
|
||||||
|
std::vector<ImageExpectation> mExpectations;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //TNT_IMAGE_EXPECTATIONS_H
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Shader.h"
|
||||||
|
|
||||||
|
#include "BackendTest.h"
|
||||||
|
#include "ShaderGenerator.h"
|
||||||
|
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
using namespace filament::backend;
|
||||||
|
|
||||||
|
Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) {
|
||||||
|
utils::FixedCapacityVector<DescriptorSetLayoutBinding> kLayouts(config.uniformNames.size());
|
||||||
|
for (unsigned char i = 0; i < config.uniformNames.size(); ++i) {
|
||||||
|
kLayouts[i] =
|
||||||
|
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, i };
|
||||||
|
};
|
||||||
|
|
||||||
|
filamat::DescriptorSets descriptors;
|
||||||
|
for (unsigned char i = 0; i < config.uniformNames.size(); ++i) {
|
||||||
|
descriptors[i + 1] = {{ config.uniformNames[i], kLayouts[i], {}}};
|
||||||
|
}
|
||||||
|
ShaderGenerator shaderGen(
|
||||||
|
std::move(config.vertexShader), std::move(config.fragmentShader), BackendTest::sBackend,
|
||||||
|
BackendTest::sIsMobilePlatform, std::move(descriptors));
|
||||||
|
Program prog = shaderGen.getProgram(api);
|
||||||
|
for (unsigned char i = 0; i < config.uniformNames.size(); ++i) {
|
||||||
|
prog.descriptorBindings(1, {{ config.uniformNames[i], DescriptorType::UNIFORM_BUFFER, i }});
|
||||||
|
}
|
||||||
|
mProgram = cleanup.add(api.createProgram(std::move(prog)));
|
||||||
|
|
||||||
|
mDescriptorSetLayout = cleanup.add(
|
||||||
|
api.createDescriptorSetLayout(DescriptorSetLayout{ kLayouts }));
|
||||||
|
|
||||||
|
mDescriptorSet = cleanup.add(api.createDescriptorSet(mDescriptorSetLayout));
|
||||||
|
}
|
||||||
|
|
||||||
|
filament::backend::ProgramHandle Shader::getProgram() const {
|
||||||
|
return mProgram;
|
||||||
|
}
|
||||||
|
|
||||||
|
filament::backend::DescriptorSetLayoutHandle Shader::getDescriptorSetLayout() const {
|
||||||
|
return mDescriptorSetLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
filament::backend::DescriptorSetHandle Shader::getDescriptorSet() const {
|
||||||
|
return mDescriptorSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* 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_SHADER_H
|
||||||
|
#define TNT_SHADER_H
|
||||||
|
|
||||||
|
#include "Lifetimes.h"
|
||||||
|
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
// All describing a shader that should be created.
|
||||||
|
struct ShaderConfig {
|
||||||
|
std::string vertexShader;
|
||||||
|
std::string fragmentShader;
|
||||||
|
std::vector<utils::CString> uniformNames;
|
||||||
|
};
|
||||||
|
|
||||||
|
// All values describing a uniform.
|
||||||
|
struct ResolvedUniformBindingConfig {
|
||||||
|
size_t dataSize;
|
||||||
|
size_t bufferSize;
|
||||||
|
uint32_t byteOffset;
|
||||||
|
filament::backend::descriptor_set_t set;
|
||||||
|
filament::backend::descriptor_binding_t binding;
|
||||||
|
};
|
||||||
|
|
||||||
|
// An equivalent to ResolvedUniformBindingConfig with all fields optional.
|
||||||
|
// resolve can be called to create a ResolvedBindingConfig with default values
|
||||||
|
// use in place of nullopt values.
|
||||||
|
struct UniformBindingConfig {
|
||||||
|
std::optional<size_t> dataSize;
|
||||||
|
std::optional<size_t> bufferSize;
|
||||||
|
std::optional<uint32_t> byteOffset;
|
||||||
|
std::optional<filament::backend::descriptor_set_t> set;
|
||||||
|
std::optional<filament::backend::descriptor_binding_t> binding;
|
||||||
|
|
||||||
|
template<typename UniformType>
|
||||||
|
ResolvedUniformBindingConfig resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
class Shader {
|
||||||
|
public:
|
||||||
|
// All graphics resources have their lifetime controlled by the Cleanup and not this object.
|
||||||
|
// Shader must not outlive either the DriverApi or the Cleanup.
|
||||||
|
Shader(filament::backend::DriverApi& api, Cleanup& cleanup, ShaderConfig config);
|
||||||
|
|
||||||
|
// Uploads a uniform with the default config.
|
||||||
|
template<typename UniformType>
|
||||||
|
void uploadUniform(filament::backend::DriverApi& api,
|
||||||
|
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
|
||||||
|
UniformType uniforms) const;
|
||||||
|
// Binds a uniform with the default config.
|
||||||
|
template<typename UniformType>
|
||||||
|
void bindUniform(filament::backend::DriverApi& api,
|
||||||
|
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer) const;
|
||||||
|
// Uploads a uniform, with UniformBindingConfig providing the arguments.
|
||||||
|
template<typename UniformType>
|
||||||
|
void uploadUniform(filament::backend::DriverApi& api,
|
||||||
|
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
|
||||||
|
UniformBindingConfig config, UniformType uniforms) const;
|
||||||
|
// Binds a uniform, with UniformBindingConfig providing the arguments.
|
||||||
|
template<typename UniformType>
|
||||||
|
void bindUniform(filament::backend::DriverApi& api,
|
||||||
|
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
|
||||||
|
UniformBindingConfig config) const;
|
||||||
|
|
||||||
|
filament::backend::ProgramHandle getProgram() const;
|
||||||
|
filament::backend::DescriptorSetLayoutHandle getDescriptorSetLayout() const;
|
||||||
|
filament::backend::DescriptorSetHandle getDescriptorSet() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
filament::backend::ProgramHandle mProgram;
|
||||||
|
filament::backend::DescriptorSetLayoutHandle mDescriptorSetLayout;
|
||||||
|
filament::backend::DescriptorSetHandle mDescriptorSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename UniformType>
|
||||||
|
ResolvedUniformBindingConfig UniformBindingConfig::resolve() {
|
||||||
|
auto resolvedDataSize = dataSize.value_or(sizeof(UniformType));
|
||||||
|
return ResolvedUniformBindingConfig{
|
||||||
|
.dataSize = resolvedDataSize,
|
||||||
|
.bufferSize = bufferSize.value_or(resolvedDataSize),
|
||||||
|
.byteOffset = byteOffset.value_or(0),
|
||||||
|
.set = set.value_or(1),
|
||||||
|
.binding = binding.value_or(0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename UniformType>
|
||||||
|
void Shader::uploadUniform(filament::backend::DriverApi& api,
|
||||||
|
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
|
||||||
|
UniformBindingConfig config, UniformType uniforms) const {
|
||||||
|
auto resolvedConfig = config.resolve<UniformType>();
|
||||||
|
|
||||||
|
UniformType* tmp = new UniformType(uniforms);
|
||||||
|
auto cb = [](void* buffer, size_t size, void* user) {
|
||||||
|
UniformType* sp = (UniformType*)buffer;
|
||||||
|
delete sp;
|
||||||
|
};
|
||||||
|
filament::backend::BufferDescriptor bd(tmp, resolvedConfig.dataSize, cb);
|
||||||
|
api.updateBufferObject(hwBuffer, std::move(bd), resolvedConfig.byteOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename UniformType>
|
||||||
|
void Shader::bindUniform(filament::backend::DriverApi& api,
|
||||||
|
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
|
||||||
|
UniformBindingConfig config) const {
|
||||||
|
auto resolvedConfig = config.resolve<UniformType>();
|
||||||
|
|
||||||
|
api.updateDescriptorSetBuffer(getDescriptorSet(), resolvedConfig.binding, hwBuffer, 0,
|
||||||
|
resolvedConfig.bufferSize);
|
||||||
|
api.bindDescriptorSet(getDescriptorSet(), resolvedConfig.set, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename UniformType>
|
||||||
|
void Shader::uploadUniform(filament::backend::DriverApi& api,
|
||||||
|
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
|
||||||
|
UniformType uniforms) const {
|
||||||
|
uploadUniform(api, hwBuffer, UniformBindingConfig{}, uniforms);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename UniformType>
|
||||||
|
void Shader::bindUniform(filament::backend::DriverApi& api,
|
||||||
|
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer) const {
|
||||||
|
bindUniform<UniformType>(api, hwBuffer, UniformBindingConfig{});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
|
||||||
|
#endif //TNT_SHADER_H
|
||||||
@@ -16,8 +16,9 @@
|
|||||||
|
|
||||||
#include "BackendTest.h"
|
#include "BackendTest.h"
|
||||||
|
|
||||||
|
#include "ImageExpectations.h"
|
||||||
#include "Lifetimes.h"
|
#include "Lifetimes.h"
|
||||||
#include "ShaderGenerator.h"
|
#include "Shader.h"
|
||||||
#include "TrianglePrimitive.h"
|
#include "TrianglePrimitive.h"
|
||||||
|
|
||||||
#include <utils/Hash.h>
|
#include <utils/Hash.h>
|
||||||
@@ -26,8 +27,10 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#ifndef FILAMENT_IOS
|
#ifndef FILAMENT_IOS
|
||||||
|
|
||||||
#include <imageio/ImageEncoder.h>
|
#include <imageio/ImageEncoder.h>
|
||||||
#include <image/ColorTransform.h>
|
#include <image/ColorTransform.h>
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace test {
|
namespace test {
|
||||||
@@ -37,6 +40,21 @@ using namespace filament::backend;
|
|||||||
using namespace filament::math;
|
using namespace filament::math;
|
||||||
using namespace utils;
|
using namespace utils;
|
||||||
|
|
||||||
|
struct MaterialParams {
|
||||||
|
float4 color;
|
||||||
|
float4 scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BlitTest : public BackendTest {
|
||||||
|
public:
|
||||||
|
BlitTest() : mCleanup(getDriverApi()) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Shader createShader();
|
||||||
|
|
||||||
|
Cleanup mCleanup;
|
||||||
|
};
|
||||||
|
|
||||||
static const char* const triangleVs = R"(#version 450 core
|
static const char* const triangleVs = R"(#version 450 core
|
||||||
layout(location = 0) in vec4 mesh_position;
|
layout(location = 0) in vec4 mesh_position;
|
||||||
layout(binding = 0, set = 1) uniform Params { highp vec4 color; highp vec4 scale; } params;
|
layout(binding = 0, set = 1) uniform Params { highp vec4 color; highp vec4 scale; } params;
|
||||||
@@ -56,49 +74,12 @@ void main() {
|
|||||||
fragColor = params.color;
|
fragColor = params.color;
|
||||||
})";
|
})";
|
||||||
|
|
||||||
struct MaterialParams {
|
Shader BlitTest::createShader() {
|
||||||
float4 color;
|
return Shader(getDriverApi(), mCleanup, ShaderConfig{
|
||||||
float4 scale;
|
.vertexShader = triangleVs,
|
||||||
};
|
.fragmentShader = triangleFs,
|
||||||
|
.uniformNames = { "Params" },
|
||||||
struct ScreenshotParams {
|
});
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
const char* filename;
|
|
||||||
uint32_t pixelHashResult;
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef FILAMENT_IOS
|
|
||||||
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt, ScreenshotParams* params) {}
|
|
||||||
#else
|
|
||||||
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt, ScreenshotParams* params) {
|
|
||||||
using namespace image;
|
|
||||||
const size_t size = params->width * params->height * 4;
|
|
||||||
void* buffer = calloc(1, size);
|
|
||||||
auto cb = [](void* buffer, size_t size, void* user) {
|
|
||||||
ScreenshotParams* params = (ScreenshotParams*) user;
|
|
||||||
int w = params->width, h = params->height;
|
|
||||||
const uint32_t* texels = (uint32_t*) buffer;
|
|
||||||
params->pixelHashResult = utils::hash::murmur3(texels, size / 4, 0);
|
|
||||||
LinearImage image(w, h, 4);
|
|
||||||
image = toLinearWithAlpha<uint8_t>(w, h, w * 4, (uint8_t*) buffer);
|
|
||||||
std::ofstream pngstrm(params->filename, std::ios::binary | std::ios::trunc);
|
|
||||||
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", params->filename);
|
|
||||||
};
|
|
||||||
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb,
|
|
||||||
(void*) params);
|
|
||||||
dapi.readPixels(rt, 0, 0, params->width, params->height, std::move(pb));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void uploadUniforms(DriverApi& dapi, Handle<HwBufferObject> ubh, MaterialParams params) {
|
|
||||||
MaterialParams* tmp = new MaterialParams(params);
|
|
||||||
auto cb = [](void* buffer, size_t size, void* user) {
|
|
||||||
MaterialParams* sp = (MaterialParams*) buffer;
|
|
||||||
delete sp;
|
|
||||||
};
|
|
||||||
BufferDescriptor bd(tmp, sizeof(MaterialParams), cb);
|
|
||||||
dapi.updateBufferObject(ubh, std::move(bd), 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t toUintColor(float4 color) {
|
static uint32_t toUintColor(float4 color) {
|
||||||
@@ -116,7 +97,7 @@ static void createBitmap(DriverApi& dapi, Handle<HwTexture> texture, int baseWid
|
|||||||
const int width = baseWidth >> level;
|
const int width = baseWidth >> level;
|
||||||
const int height = baseHeight >> level;
|
const int height = baseHeight >> level;
|
||||||
const size_t size0 = height * width * 4;
|
const size_t size0 = height * width * 4;
|
||||||
uint8_t* buffer0 = (uint8_t*) calloc(size0, 1);
|
uint8_t* buffer0 = (uint8_t*)calloc(size0, 1);
|
||||||
PixelBufferDescriptor pb(buffer0, size0, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb);
|
PixelBufferDescriptor pb(buffer0, size0, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb);
|
||||||
|
|
||||||
const float3 foreground = color;
|
const float3 foreground = color;
|
||||||
@@ -140,7 +121,7 @@ static void createBitmap(DriverApi& dapi, Handle<HwTexture> texture, int baseWid
|
|||||||
// | ........
|
// | ........
|
||||||
// low addresses
|
// low addresses
|
||||||
// This is because OpenGL automatically flips image data when uploading into the texture.
|
// This is because OpenGL automatically flips image data when uploading into the texture.
|
||||||
uint32_t* texels = (uint32_t*) buffer0;
|
uint32_t* texels = (uint32_t*)buffer0;
|
||||||
for (int row = 0; row < height; row++) {
|
for (int row = 0; row < height; row++) {
|
||||||
for (int col = 0; col < width; col++) {
|
for (int col = 0; col < width; col++) {
|
||||||
float2 uv = { (col - width / 2.0f) / width, (row - height / 2.0f) / height };
|
float2 uv = { (col - width / 2.0f) / width, (row - height / 2.0f) / height };
|
||||||
@@ -168,7 +149,7 @@ static void createFaces(DriverApi& dapi, Handle<HwTexture> texture, int baseWidt
|
|||||||
const int width = baseWidth >> level;
|
const int width = baseWidth >> level;
|
||||||
const int height = baseHeight >> level;
|
const int height = baseHeight >> level;
|
||||||
const size_t size0 = height * width * 4 * 6;
|
const size_t size0 = height * width * 4 * 6;
|
||||||
uint8_t* buffer0 = (uint8_t*) calloc(size0, 1);
|
uint8_t* buffer0 = (uint8_t*)calloc(size0, 1);
|
||||||
PixelBufferDescriptor pb(buffer0, size0, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb);
|
PixelBufferDescriptor pb(buffer0, size0, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb);
|
||||||
|
|
||||||
const float3 foreground = color;
|
const float3 foreground = color;
|
||||||
@@ -176,7 +157,7 @@ static void createFaces(DriverApi& dapi, Handle<HwTexture> texture, int baseWidt
|
|||||||
const float radius = 0.25f;
|
const float radius = 0.25f;
|
||||||
|
|
||||||
// Draw a circle on a yellow background.
|
// Draw a circle on a yellow background.
|
||||||
uint32_t* texels = (uint32_t*) buffer0;
|
uint32_t* texels = (uint32_t*)buffer0;
|
||||||
for (int face = 0; face < 6; face++) {
|
for (int face = 0; face < 6; face++) {
|
||||||
for (int row = 0; row < height; row++) {
|
for (int row = 0; row < height; row++) {
|
||||||
for (int col = 0; col < width; col++) {
|
for (int col = 0; col < width; col++) {
|
||||||
@@ -194,10 +175,9 @@ static void createFaces(DriverApi& dapi, Handle<HwTexture> texture, int baseWidt
|
|||||||
dapi.update3DImage(texture, level, 0, 0, 0, width, height, 6, std::move(pb));
|
dapi.update3DImage(texture, level, 0, 0, 0, width, height, 6, std::move(pb));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(BackendTest, ColorMagnify) {
|
TEST_F(BlitTest, ColorMagnify) {
|
||||||
auto& api = getDriverApi();
|
auto& api = getDriverApi();
|
||||||
Cleanup cleanup(getDriverApi());
|
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||||
cleanup.addPostCall([&]() { executeCommands(); });
|
|
||||||
|
|
||||||
constexpr int kSrcTexWidth = 256;
|
constexpr int kSrcTexWidth = 256;
|
||||||
constexpr int kSrcTexHeight = 256;
|
constexpr int kSrcTexHeight = 256;
|
||||||
@@ -208,37 +188,40 @@ TEST_F(BackendTest, ColorMagnify) {
|
|||||||
constexpr int kNumLevels = 3;
|
constexpr int kNumLevels = 3;
|
||||||
|
|
||||||
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
|
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
|
||||||
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||||
api.makeCurrent(swapChain, swapChain);
|
api.makeCurrent(swapChain, swapChain);
|
||||||
|
|
||||||
// Create a source texture.
|
// Create a source texture.
|
||||||
Handle<HwTexture> const srcTexture = cleanup.add(api.createTexture(
|
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(
|
||||||
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||||
const bool flipY = sBackend == Backend::OPENGL;
|
const bool flipY = sBackend == Backend::OPENGL;
|
||||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
|
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
|
||||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
|
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
|
||||||
|
|
||||||
// Create a destination texture.
|
// Create a destination texture.
|
||||||
Handle<HwTexture> const dstTexture = cleanup.add(api.createTexture(
|
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(
|
||||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||||
|
|
||||||
// Create a RenderTarget for each texture's miplevel.
|
// Create a RenderTarget for each texture's miplevel.
|
||||||
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
|
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
|
||||||
Handle<HwRenderTarget> dstRenderTargets[kNumLevels];
|
Handle<HwRenderTarget> dstRenderTargets[kNumLevels];
|
||||||
for (uint8_t level = 0; level < kNumLevels; level++) {
|
for (uint8_t level = 0; level < kNumLevels; level++) {
|
||||||
srcRenderTargets[level] = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
|
srcRenderTargets[level] = mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
|
||||||
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {}, {}));
|
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {},
|
||||||
dstRenderTargets[level] = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
|
{}));
|
||||||
kDstTexWidth >> level, kDstTexHeight >> level, 1, 0, { dstTexture, level, 0 }, {}, {}));
|
dstRenderTargets[level] = mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
|
||||||
|
kDstTexWidth >> level, kDstTexHeight >> level, 1, 0, { dstTexture, level, 0 }, {},
|
||||||
|
{}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do a "magnify" blit from level 1 of the source RT to the level 0 of the destination RT.
|
// Do a "magnify" blit from level 1 of the source RT to the level 0 of the destination RT.
|
||||||
const int srcLevel = 1;
|
const int srcLevel = 1;
|
||||||
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTargets[0],
|
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTargets[0],
|
||||||
{0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTargets[srcLevel],
|
{ 0, 0, kDstTexWidth, kDstTexHeight }, srcRenderTargets[srcLevel],
|
||||||
{0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel}, SamplerMagFilter::LINEAR);
|
{ 0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel },
|
||||||
|
SamplerMagFilter::LINEAR);
|
||||||
|
|
||||||
// Push through an empty frame to allow the texture to upload and the blit to execute.
|
// Push through an empty frame to allow the texture to upload and the blit to execute.
|
||||||
{
|
{
|
||||||
@@ -246,29 +229,23 @@ TEST_F(BackendTest, ColorMagnify) {
|
|||||||
api.commit(swapChain);
|
api.commit(swapChain);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grab a screenshot.
|
|
||||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "ColorMagnify.png" };
|
|
||||||
{
|
{
|
||||||
RenderFrame frame(api);
|
ImageExpectations expectations(api);
|
||||||
dumpScreenshot(api, dstRenderTargets[0], ¶ms);
|
|
||||||
api.commit(swapChain);
|
{
|
||||||
|
RenderFrame frame(api);
|
||||||
|
EXPECT_IMAGE(dstRenderTargets[0], expectations,
|
||||||
|
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMagnify", 0x410bdd31));
|
||||||
|
api.commit(swapChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
flushAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the ReadPixels result to come back.
|
|
||||||
api.finish();
|
|
||||||
executeCommands();
|
|
||||||
getDriver().purge();
|
|
||||||
|
|
||||||
// Check if the image matches perfectly to our golden run.
|
|
||||||
const uint32_t expected = 0x410bdd31;
|
|
||||||
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected);
|
|
||||||
EXPECT_TRUE(params.pixelHashResult == expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(BackendTest, ColorMinify) {
|
TEST_F(BlitTest, ColorMinify) {
|
||||||
auto& api = getDriverApi();
|
auto& api = getDriverApi();
|
||||||
Cleanup cleanup(getDriverApi());
|
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||||
cleanup.addPostCall([&]() { executeCommands(); });
|
|
||||||
|
|
||||||
constexpr int kSrcTexWidth = 1024;
|
constexpr int kSrcTexWidth = 1024;
|
||||||
constexpr int kSrcTexHeight = 1024;
|
constexpr int kSrcTexHeight = 1024;
|
||||||
@@ -279,57 +256,55 @@ TEST_F(BackendTest, ColorMinify) {
|
|||||||
constexpr int kNumLevels = 3;
|
constexpr int kNumLevels = 3;
|
||||||
|
|
||||||
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
|
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
|
||||||
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||||
api.makeCurrent(swapChain, swapChain);
|
api.makeCurrent(swapChain, swapChain);
|
||||||
|
|
||||||
// Create a source texture.
|
// Create a source texture.
|
||||||
Handle<HwTexture> const srcTexture = cleanup.add(api.createTexture(
|
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(
|
||||||
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||||
const bool flipY = sBackend == Backend::OPENGL;
|
const bool flipY = sBackend == Backend::OPENGL;
|
||||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
|
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
|
||||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
|
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
|
||||||
|
|
||||||
// Create a destination texture.
|
// Create a destination texture.
|
||||||
Handle<HwTexture> const dstTexture = cleanup.add(api.createTexture(
|
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(
|
||||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||||
|
|
||||||
// Create a RenderTarget for each texture's miplevel.
|
// Create a RenderTarget for each texture's miplevel.
|
||||||
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
|
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
|
||||||
Handle<HwRenderTarget> dstRenderTargets[kNumLevels];
|
Handle<HwRenderTarget> dstRenderTargets[kNumLevels];
|
||||||
for (uint8_t level = 0; level < kNumLevels; level++) {
|
for (uint8_t level = 0; level < kNumLevels; level++) {
|
||||||
srcRenderTargets[level] = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
|
srcRenderTargets[level] = mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
|
||||||
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {}, {}));
|
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {},
|
||||||
dstRenderTargets[level] = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
|
{}));
|
||||||
kDstTexWidth >> level, kDstTexHeight >> level, 1, 0, { dstTexture, level, 0 }, {}, {}));
|
dstRenderTargets[level] = mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
|
||||||
|
kDstTexWidth >> level, kDstTexHeight >> level, 1, 0, { dstTexture, level, 0 }, {},
|
||||||
|
{}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do a "minify" blit from level 1 of the source RT to the level 0 of the destination RT.
|
// Do a "minify" blit from level 1 of the source RT to the level 0 of the destination RT.
|
||||||
const int srcLevel = 1;
|
const int srcLevel = 1;
|
||||||
|
|
||||||
api.blitDEPRECATED(TargetBufferFlags::COLOR0,
|
api.blitDEPRECATED(TargetBufferFlags::COLOR0,
|
||||||
dstRenderTargets[0], {0, 0, kDstTexWidth, kDstTexHeight},
|
dstRenderTargets[0], { 0, 0, kDstTexWidth, kDstTexHeight },
|
||||||
srcRenderTargets[srcLevel], {0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel},
|
srcRenderTargets[srcLevel],
|
||||||
|
{ 0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel },
|
||||||
SamplerMagFilter::LINEAR);
|
SamplerMagFilter::LINEAR);
|
||||||
|
|
||||||
|
{
|
||||||
|
ImageExpectations expectations(api);
|
||||||
|
|
||||||
// Grab a screenshot.
|
EXPECT_IMAGE(dstRenderTargets[0], expectations,
|
||||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "ColorMinify.png" };
|
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f));
|
||||||
dumpScreenshot(api, dstRenderTargets[0], ¶ms);
|
|
||||||
|
|
||||||
// Wait for the ReadPixels result to come back.
|
flushAndWait();
|
||||||
flushAndWait();
|
}
|
||||||
|
|
||||||
// Check if the image matches perfectly to our golden run.
|
|
||||||
const uint32_t expected = 0xf3d9c53f;
|
|
||||||
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected);
|
|
||||||
EXPECT_TRUE(params.pixelHashResult == expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(BackendTest, ColorResolve) {
|
TEST_F(BlitTest, ColorResolve) {
|
||||||
auto& api = getDriverApi();
|
auto& api = getDriverApi();
|
||||||
Cleanup cleanup(getDriverApi());
|
|
||||||
|
|
||||||
constexpr int kSrcTexWidth = 256;
|
constexpr int kSrcTexWidth = 256;
|
||||||
constexpr int kSrcTexHeight = 256;
|
constexpr int kSrcTexHeight = 256;
|
||||||
@@ -338,49 +313,29 @@ TEST_F(BackendTest, ColorResolve) {
|
|||||||
constexpr auto kColorTexFormat = TextureFormat::RGBA8;
|
constexpr auto kColorTexFormat = TextureFormat::RGBA8;
|
||||||
constexpr int kSampleCount = 4;
|
constexpr int kSampleCount = 4;
|
||||||
|
|
||||||
// Create a program.
|
Shader shader = createShader();
|
||||||
ProgramHandle program;
|
|
||||||
{
|
|
||||||
filamat::DescriptorSets descriptors;
|
|
||||||
descriptors[1] = { { "Params",
|
|
||||||
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0 },
|
|
||||||
{} } };
|
|
||||||
ShaderGenerator shaderGen(
|
|
||||||
triangleVs, triangleFs, sBackend, sIsMobilePlatform, std::move(descriptors));
|
|
||||||
Program prog = shaderGen.getProgram(api);
|
|
||||||
prog.descriptorBindings(1, {{ "Params", DescriptorType::UNIFORM_BUFFER, 0 }});
|
|
||||||
program = cleanup.add(api.createProgram(std::move(prog)));
|
|
||||||
}
|
|
||||||
|
|
||||||
DescriptorSetLayoutHandle descriptorSetLayout = cleanup.add(api.createDescriptorSetLayout({
|
|
||||||
{{
|
|
||||||
DescriptorType::UNIFORM_BUFFER,
|
|
||||||
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
|
|
||||||
DescriptorFlags::NONE, 0
|
|
||||||
}}}));
|
|
||||||
|
|
||||||
DescriptorSetHandle descriptorSet = cleanup.add(api.createDescriptorSet(descriptorSetLayout));
|
|
||||||
|
|
||||||
// Create a VertexBuffer, IndexBuffer, and RenderPrimitive.
|
// Create a VertexBuffer, IndexBuffer, and RenderPrimitive.
|
||||||
TrianglePrimitive const triangle(api);
|
TrianglePrimitive const triangle(api);
|
||||||
|
|
||||||
// Create 4-sample texture.
|
// Create 4-sample texture.
|
||||||
Handle<HwTexture> const srcColorTexture = cleanup.add(api.createTexture(
|
Handle<HwTexture> const srcColorTexture = mCleanup.add(api.createTexture(
|
||||||
SamplerType::SAMPLER_2D, 1, kColorTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight, 1,
|
SamplerType::SAMPLER_2D, 1, kColorTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight,
|
||||||
TextureUsage::COLOR_ATTACHMENT));
|
1,
|
||||||
|
TextureUsage::COLOR_ATTACHMENT));
|
||||||
|
|
||||||
// Create 1-sample texture.
|
// Create 1-sample texture.
|
||||||
Handle<HwTexture> const dstColorTexture = cleanup.add(api.createTexture(
|
Handle<HwTexture> const dstColorTexture = mCleanup.add(api.createTexture(
|
||||||
SamplerType::SAMPLER_2D, 1, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
SamplerType::SAMPLER_2D, 1, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||||
|
|
||||||
// Create a 4-sample render target with the 4-sample texture.
|
// Create a 4-sample render target with the 4-sample texture.
|
||||||
Handle<HwRenderTarget> const srcRenderTarget = cleanup.add(api.createRenderTarget(
|
Handle<HwRenderTarget> const srcRenderTarget = mCleanup.add(api.createRenderTarget(
|
||||||
TargetBufferFlags::COLOR, kSrcTexWidth, kSrcTexHeight, kSampleCount, 0,
|
TargetBufferFlags::COLOR, kSrcTexWidth, kSrcTexHeight, kSampleCount, 0,
|
||||||
{{ srcColorTexture }}, {}, {}));
|
{{ srcColorTexture }}, {}, {}));
|
||||||
|
|
||||||
// Create a 1-sample render target with the 1-sample texture.
|
// Create a 1-sample render target with the 1-sample texture.
|
||||||
Handle<HwRenderTarget> const dstRenderTarget = cleanup.add(api.createRenderTarget(
|
Handle<HwRenderTarget> const dstRenderTarget = mCleanup.add(api.createRenderTarget(
|
||||||
TargetBufferFlags::COLOR, kDstTexWidth, kDstTexHeight, 1, 0,
|
TargetBufferFlags::COLOR, kDstTexWidth, kDstTexHeight, 1, 0,
|
||||||
{{ dstColorTexture }}, {}, {}));
|
{{ dstColorTexture }}, {}, {}));
|
||||||
|
|
||||||
@@ -394,58 +349,52 @@ TEST_F(BackendTest, ColorResolve) {
|
|||||||
params.viewport.height = kSrcTexHeight;
|
params.viewport.height = kSrcTexHeight;
|
||||||
|
|
||||||
PipelineState state = {};
|
PipelineState state = {};
|
||||||
state.program = program;
|
state.program = shader.getProgram();
|
||||||
state.pipelineLayout.setLayout[1] = { descriptorSetLayout };
|
state.pipelineLayout.setLayout[1] = { shader.getDescriptorSetLayout() };
|
||||||
state.rasterState.colorWrite = true;
|
state.rasterState.colorWrite = true;
|
||||||
state.rasterState.depthWrite = false;
|
state.rasterState.depthWrite = false;
|
||||||
state.rasterState.depthFunc = RasterState::DepthFunc::A;
|
state.rasterState.depthFunc = RasterState::DepthFunc::A;
|
||||||
state.rasterState.culling = CullingMode::NONE;
|
state.rasterState.culling = CullingMode::NONE;
|
||||||
|
|
||||||
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(MaterialParams),
|
auto ubuffer = mCleanup.add(api.createBufferObject(sizeof(MaterialParams),
|
||||||
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
|
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
|
||||||
// Draw red triangle into srcRenderTarget.
|
// Draw red triangle into srcRenderTarget.
|
||||||
uploadUniforms(api, ubuffer, {
|
shader.uploadUniform(api, ubuffer, MaterialParams{
|
||||||
.color = float4(1, 0, 0, 1),
|
.color = float4(1, 0, 0, 1),
|
||||||
.scale = float4(1, 1, 0.5, 0),
|
.scale = float4(1, 1, 0.5, 0),
|
||||||
});
|
});
|
||||||
|
shader.bindUniform<MaterialParams>(api, ubuffer);
|
||||||
api.updateDescriptorSetBuffer(descriptorSet, 0, ubuffer, 0, sizeof(MaterialParams));
|
|
||||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
|
||||||
|
|
||||||
// FIXME: on Metal this triangle is not drawn. Can't understand why.
|
// FIXME: on Metal this triangle is not drawn. Can't understand why.
|
||||||
{
|
{
|
||||||
RenderFrame frame(api);
|
RenderFrame frame(api);
|
||||||
api.beginRenderPass(srcRenderTarget, params);
|
api.beginRenderPass(srcRenderTarget, params);
|
||||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||||
api.endRenderPass();
|
api.endRenderPass();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the MSAA render target into the single-sample render target.
|
// Resolve the MSAA render target into the single-sample render target.
|
||||||
api.blitDEPRECATED(TargetBufferFlags::COLOR,
|
api.blitDEPRECATED(TargetBufferFlags::COLOR,
|
||||||
dstRenderTarget, {0, 0, kDstTexWidth, kDstTexHeight},
|
dstRenderTarget, { 0, 0, kDstTexWidth, kDstTexHeight },
|
||||||
srcRenderTarget, {0, 0, kSrcTexWidth, kSrcTexHeight},
|
srcRenderTarget, { 0, 0, kSrcTexWidth, kSrcTexHeight },
|
||||||
SamplerMagFilter::NEAREST);
|
SamplerMagFilter::NEAREST);
|
||||||
|
|
||||||
// Grab a screenshot.
|
{
|
||||||
ScreenshotParams sparams{ kDstTexWidth, kDstTexHeight, "ColorResolve.png" };
|
ImageExpectations expectations(api);
|
||||||
dumpScreenshot(api, dstRenderTarget, &sparams);
|
|
||||||
|
|
||||||
// Wait for the ReadPixels result to come back.
|
EXPECT_IMAGE(dstRenderTarget, expectations,
|
||||||
flushAndWait();
|
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 0xebfac2ef));
|
||||||
|
|
||||||
// Check if the image matches perfectly to our golden run.
|
flushAndWait();
|
||||||
const uint32_t expected = 0xebfac2ef;
|
}
|
||||||
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", sparams.pixelHashResult, expected);
|
|
||||||
EXPECT_TRUE(sparams.pixelHashResult == expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(BackendTest, Blit2DTextureArray) {
|
TEST_F(BlitTest, Blit2DTextureArray) {
|
||||||
auto& api = getDriverApi();
|
auto& api = getDriverApi();
|
||||||
Cleanup cleanup(getDriverApi());
|
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||||
cleanup.addPostCall([&]() { executeCommands(); });
|
|
||||||
|
|
||||||
api.startCapture(0);
|
api.startCapture(0);
|
||||||
cleanup.addPostCall([&]() { api.stopCapture(0); });
|
mCleanup.addPostCall([&]() { api.stopCapture(0); });
|
||||||
|
|
||||||
constexpr int kSrcTexWidth = 256;
|
constexpr int kSrcTexWidth = 256;
|
||||||
constexpr int kSrcTexHeight = 256;
|
constexpr int kSrcTexHeight = 256;
|
||||||
@@ -460,33 +409,40 @@ TEST_F(BackendTest, Blit2DTextureArray) {
|
|||||||
constexpr int kDstTexLayer = 0;
|
constexpr int kDstTexLayer = 0;
|
||||||
|
|
||||||
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
|
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
|
||||||
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||||
api.makeCurrent(swapChain, swapChain);
|
api.makeCurrent(swapChain, swapChain);
|
||||||
|
|
||||||
// Create a source texture.
|
// Create a source texture.
|
||||||
Handle<HwTexture> srcTexture = cleanup.add(api.createTexture(
|
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
|
||||||
SamplerType::SAMPLER_2D_ARRAY, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, kSrcTexDepth,
|
SamplerType::SAMPLER_2D_ARRAY, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth,
|
||||||
|
kSrcTexHeight, kSrcTexDepth,
|
||||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||||
const bool flipY = sBackend == Backend::OPENGL;
|
const bool flipY = sBackend == Backend::OPENGL;
|
||||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, kSrcTexLayer, float3(0.5, 0, 0), flipY);
|
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, kSrcTexLayer, float3(0.5, 0, 0),
|
||||||
|
flipY);
|
||||||
|
|
||||||
// Create a destination texture.
|
// Create a destination texture.
|
||||||
Handle<HwTexture> dstTexture = cleanup.add(api.createTexture(
|
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(
|
||||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||||
|
|
||||||
// Create two RenderTargets.
|
// Create two RenderTargets.
|
||||||
const int level = 0;
|
const int level = 0;
|
||||||
Handle<HwRenderTarget> srcRenderTarget = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
|
Handle<HwRenderTarget> srcRenderTarget = mCleanup.add(
|
||||||
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, kSrcTexLayer }, {}, {}));
|
api.createRenderTarget(TargetBufferFlags::COLOR,
|
||||||
Handle<HwRenderTarget> dstRenderTarget = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
|
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0,
|
||||||
kDstTexWidth >> level, kDstTexHeight >> level, 1, 0, { dstTexture, level, kDstTexLayer }, {}, {}));
|
{ srcTexture, level, kSrcTexLayer }, {}, {}));
|
||||||
|
Handle<HwRenderTarget> dstRenderTarget = mCleanup.add(
|
||||||
|
api.createRenderTarget(TargetBufferFlags::COLOR,
|
||||||
|
kDstTexWidth >> level, kDstTexHeight >> level, 1, 0,
|
||||||
|
{ dstTexture, level, kDstTexLayer }, {}, {}));
|
||||||
|
|
||||||
// Do a blit from kSrcTexLayer of the source RT to kDstTexLayer of the destination RT.
|
// Do a blit from kSrcTexLayer of the source RT to kDstTexLayer of the destination RT.
|
||||||
const int srcLevel = 0;
|
const int srcLevel = 0;
|
||||||
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
|
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
|
||||||
{0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTarget,
|
{ 0, 0, kDstTexWidth, kDstTexHeight }, srcRenderTarget,
|
||||||
{0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel}, SamplerMagFilter::LINEAR);
|
{ 0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel },
|
||||||
|
SamplerMagFilter::LINEAR);
|
||||||
|
|
||||||
// Push through an empty frame to allow the texture to upload and the blit to execute.
|
// Push through an empty frame to allow the texture to upload and the blit to execute.
|
||||||
{
|
{
|
||||||
@@ -494,29 +450,24 @@ TEST_F(BackendTest, Blit2DTextureArray) {
|
|||||||
api.commit(swapChain);
|
api.commit(swapChain);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grab a screenshot.
|
|
||||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "Blit2DTextureArray.png" };
|
|
||||||
{
|
{
|
||||||
RenderFrame frame(api);
|
ImageExpectations expectations(api);
|
||||||
dumpScreenshot(api, dstRenderTarget, ¶ms);
|
|
||||||
api.commit(swapChain);
|
{
|
||||||
|
RenderFrame frame(api);
|
||||||
|
EXPECT_IMAGE(dstRenderTarget, expectations,
|
||||||
|
ScreenshotParams(kDstTexWidth, kDstTexHeight, "Blit2DTextureArray",
|
||||||
|
0x8de7d55b));
|
||||||
|
api.commit(swapChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
flushAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the ReadPixels result to come back.
|
|
||||||
api.finish();
|
|
||||||
executeCommands();
|
|
||||||
getDriver().purge();
|
|
||||||
|
|
||||||
// Check if the image matches perfectly to our golden run.
|
|
||||||
const uint32_t expected = 0x8de7d55b;
|
|
||||||
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected);
|
|
||||||
EXPECT_TRUE(params.pixelHashResult == expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(BackendTest, BlitRegion) {
|
TEST_F(BlitTest, BlitRegion) {
|
||||||
auto& api = getDriverApi();
|
auto& api = getDriverApi();
|
||||||
Cleanup cleanup(getDriverApi());
|
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||||
cleanup.addPostCall([&]() { executeCommands(); });
|
|
||||||
|
|
||||||
constexpr int kSrcTexWidth = 1024;
|
constexpr int kSrcTexWidth = 1024;
|
||||||
constexpr int kSrcTexHeight = 1024;
|
constexpr int kSrcTexHeight = 1024;
|
||||||
@@ -529,47 +480,48 @@ TEST_F(BackendTest, BlitRegion) {
|
|||||||
constexpr int kDstLevel = 0;
|
constexpr int kDstLevel = 0;
|
||||||
|
|
||||||
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
|
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
|
||||||
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||||
api.makeCurrent(swapChain, swapChain);
|
api.makeCurrent(swapChain, swapChain);
|
||||||
|
|
||||||
// Create a source texture.
|
// Create a source texture.
|
||||||
Handle<HwTexture> srcTexture = cleanup.add(api.createTexture(
|
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
|
||||||
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||||
const bool flipY = sBackend == Backend::OPENGL;
|
const bool flipY = sBackend == Backend::OPENGL;
|
||||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
|
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
|
||||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
|
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
|
||||||
|
|
||||||
// Create a destination texture.
|
// Create a destination texture.
|
||||||
Handle<HwTexture> dstTexture = cleanup.add(api.createTexture(
|
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(
|
||||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||||
|
|
||||||
// Blit one-quarter of src level 1 to dst level 0.
|
// Blit one-quarter of src level 1 to dst level 0.
|
||||||
Viewport srcRect = {
|
Viewport srcRect = {
|
||||||
.left = 0,
|
.left = 0,
|
||||||
.bottom = 0,
|
.bottom = 0,
|
||||||
.width = (kSrcTexWidth >> kSrcLevel) / 2,
|
.width = (kSrcTexWidth >> kSrcLevel) / 2,
|
||||||
.height = (kSrcTexHeight >> kSrcLevel) / 2,
|
.height = (kSrcTexHeight >> kSrcLevel) / 2,
|
||||||
};
|
};
|
||||||
Viewport dstRect = {
|
Viewport dstRect = {
|
||||||
.left = 10,
|
.left = 10,
|
||||||
.bottom = 10,
|
.bottom = 10,
|
||||||
.width = kDstTexWidth - 10,
|
.width = kDstTexWidth - 10,
|
||||||
.height = kDstTexHeight - 10,
|
.height = kDstTexHeight - 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a RenderTarget for each texture's miplevel.
|
// Create a RenderTarget for each texture's miplevel.
|
||||||
// We purposely set the render target width and height to smaller than the texture, to check
|
// We purposely set the render target width and height to smaller than the texture, to check
|
||||||
// that this case is handled correctly.
|
// that this case is handled correctly.
|
||||||
Handle<HwRenderTarget> srcRenderTarget =
|
Handle<HwRenderTarget> srcRenderTarget =
|
||||||
cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR, srcRect.width,
|
mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR, srcRect.width,
|
||||||
srcRect.height, 1, 0, {srcTexture, kSrcLevel, 0}, {}, {}));
|
srcRect.height, 1, 0, { srcTexture, kSrcLevel, 0 }, {}, {}));
|
||||||
Handle<HwRenderTarget> dstRenderTarget =
|
Handle<HwRenderTarget> dstRenderTarget =
|
||||||
cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR, kDstTexWidth >> kDstLevel,
|
mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR, kDstTexWidth >> kDstLevel,
|
||||||
kDstTexHeight >> kDstLevel, 1, 0, {dstTexture, kDstLevel, 0}, {}, {}));
|
kDstTexHeight >> kDstLevel, 1, 0, { dstTexture, kDstLevel, 0 }, {}, {}));
|
||||||
|
|
||||||
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget, dstRect, srcRenderTarget, srcRect,
|
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget, dstRect, srcRenderTarget,
|
||||||
|
srcRect,
|
||||||
SamplerMagFilter::LINEAR);
|
SamplerMagFilter::LINEAR);
|
||||||
|
|
||||||
// Push through an empty frame to allow the texture to upload and the blit to execute.
|
// Push through an empty frame to allow the texture to upload and the blit to execute.
|
||||||
@@ -578,33 +530,25 @@ TEST_F(BackendTest, BlitRegion) {
|
|||||||
api.commit(swapChain);
|
api.commit(swapChain);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grab a screenshot.
|
|
||||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "BlitRegion.png" };
|
|
||||||
{
|
{
|
||||||
RenderFrame frame(api);
|
ImageExpectations expectations(api);
|
||||||
dumpScreenshot(api, dstRenderTarget, ¶ms);
|
|
||||||
api.commit(swapChain);
|
{
|
||||||
|
RenderFrame frame(api);
|
||||||
|
// TODO: for some reason, this test has very, very slight (as in one pixel) differences
|
||||||
|
// between OpenGL and Metal. So disable golden checking for now.
|
||||||
|
// EXPECT_IMAGE(dstRenderTarget, expectations, ScreenshotParams(kDstTexWidth,
|
||||||
|
// kDstTexHeight, "BlitRegion", 0x74fa34ed));
|
||||||
|
api.commit(swapChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
flushAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the ReadPixels result to come back.
|
|
||||||
api.finish();
|
|
||||||
executeCommands();
|
|
||||||
getDriver().purge();
|
|
||||||
|
|
||||||
// TODO: for some reason, this test has very, very slight (as in one pixel) differences between
|
|
||||||
// OpenGL and Metal. So disable golden checking for now.
|
|
||||||
// Use the compare tool from ImageMagick to see visual differences:
|
|
||||||
// compare -verbose -metric mae BlitRegion_Metal.png BlitRegion_OpenGL.png difference.png
|
|
||||||
//
|
|
||||||
// const uint32_t expected = 0x74fa34ed;
|
|
||||||
// printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected);
|
|
||||||
// EXPECT_TRUE(params.pixelHashResult == expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(BackendTest, BlitRegionToSwapChain) {
|
TEST_F(BlitTest, BlitRegionToSwapChain) {
|
||||||
auto& api = getDriverApi();
|
auto& api = getDriverApi();
|
||||||
Cleanup cleanup(getDriverApi());
|
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||||
cleanup.addPostCall([&]() { executeCommands(); });
|
|
||||||
|
|
||||||
constexpr int kSrcTexWidth = 1024;
|
constexpr int kSrcTexWidth = 1024;
|
||||||
constexpr int kSrcTexHeight = 1024;
|
constexpr int kSrcTexHeight = 1024;
|
||||||
@@ -614,15 +558,15 @@ TEST_F(BackendTest, BlitRegionToSwapChain) {
|
|||||||
constexpr int kNumLevels = 3;
|
constexpr int kNumLevels = 3;
|
||||||
|
|
||||||
// Create a SwapChain and make it current.
|
// Create a SwapChain and make it current.
|
||||||
auto swapChain = cleanup.add(createSwapChain());
|
auto swapChain = mCleanup.add(createSwapChain());
|
||||||
api.makeCurrent(swapChain, swapChain);
|
api.makeCurrent(swapChain, swapChain);
|
||||||
|
|
||||||
Handle<HwRenderTarget> dstRenderTarget = cleanup.add(api.createDefaultRenderTarget());
|
Handle<HwRenderTarget> dstRenderTarget = mCleanup.add(api.createDefaultRenderTarget());
|
||||||
|
|
||||||
// Create a source texture.
|
// Create a source texture.
|
||||||
Handle<HwTexture> srcTexture = cleanup.add(api.createTexture(
|
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
|
||||||
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||||
const bool flipY = sBackend == Backend::OPENGL;
|
const bool flipY = sBackend == Backend::OPENGL;
|
||||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
|
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
|
||||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
|
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
|
||||||
@@ -630,42 +574,46 @@ TEST_F(BackendTest, BlitRegionToSwapChain) {
|
|||||||
// Create a RenderTarget for each texture's miplevel.
|
// Create a RenderTarget for each texture's miplevel.
|
||||||
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
|
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
|
||||||
for (uint8_t level = 0; level < kNumLevels; level++) {
|
for (uint8_t level = 0; level < kNumLevels; level++) {
|
||||||
srcRenderTargets[level] = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
|
srcRenderTargets[level] = mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
|
||||||
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {}, {}));
|
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {},
|
||||||
|
{}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blit one-quarter of src level 1 to dst level 0.
|
// Blit one-quarter of src level 1 to dst level 0.
|
||||||
const int srcLevel = 1;
|
const int srcLevel = 1;
|
||||||
Viewport srcRect = {
|
Viewport srcRect = {
|
||||||
.left = (kSrcTexWidth >> srcLevel) / 2,
|
.left = (kSrcTexWidth >> srcLevel) / 2,
|
||||||
.bottom = (kSrcTexHeight >> srcLevel) / 2,
|
.bottom = (kSrcTexHeight >> srcLevel) / 2,
|
||||||
.width = (kSrcTexWidth >> srcLevel) / 2,
|
.width = (kSrcTexWidth >> srcLevel) / 2,
|
||||||
.height = (kSrcTexHeight >> srcLevel) / 2,
|
.height = (kSrcTexHeight >> srcLevel) / 2,
|
||||||
};
|
};
|
||||||
Viewport dstRect = {
|
Viewport dstRect = {
|
||||||
.left = 10,
|
.left = 10,
|
||||||
.bottom = 10,
|
.bottom = 10,
|
||||||
.width = kDstTexWidth - 10,
|
.width = kDstTexWidth - 10,
|
||||||
.height = kDstTexHeight - 10,
|
.height = kDstTexHeight - 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
RenderFrame frame(api);
|
ImageExpectations expectations(api);
|
||||||
|
{
|
||||||
|
{
|
||||||
|
RenderFrame frame(api);
|
||||||
|
|
||||||
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
|
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
|
||||||
dstRect, srcRenderTargets[srcLevel],
|
dstRect, srcRenderTargets[srcLevel],
|
||||||
srcRect, SamplerMagFilter::LINEAR);
|
srcRect, SamplerMagFilter::LINEAR);
|
||||||
|
|
||||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "BlitRegionToSwapChain.png" };
|
api.commit(swapChain);
|
||||||
dumpScreenshot(api, dstRenderTarget, ¶ms);
|
}
|
||||||
|
|
||||||
api.commit(swapChain);
|
// TODO: for some reason, this test has been disabled. It needs to be tested on all
|
||||||
|
// machines.
|
||||||
|
// EXPECT_IMAGE(dstRenderTarget, expectations,
|
||||||
|
// ScreenshotParams(kDstTexWidth, kDstTexHeight, "BlitRegionToSwapChain", 0x0));
|
||||||
|
}
|
||||||
|
flushAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the ReadPixels result to come back.
|
|
||||||
api.finish();
|
|
||||||
executeCommands();
|
|
||||||
getDriver().purge();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
|
|||||||