Compare commits

..
Author SHA1 Message Date
bridgewaterrobbie 3041a7ccc0 Include webgpu matc arg in android builds when appropriate. 2025-04-07 14:36:37 -04:00
Syed Idris Shah 06f8d073da Add Command, RenderPass encoders and other pieces of webgpu 2025-04-04 13:14:35 -04:00
Syed Idris Shah e5b8f91859 Use HandleAllocator for webgpu to implement createVertexBufferInfoS 2025-04-04 13:12:57 -04:00
Andy Hovingh 70f2cda9b6 remove unnecessary third party includes from dawn (Filament already brings them in) 2025-04-03 20:22:28 -04:00
Andy Hovingh c3e4894d49 fix Mac OS 15.4 compile errors: minor changes to reflect webgpu and tint API changes 2025-04-03 20:22:28 -04:00
Andy Hovingh a128d9143a fix Mac OS 15.4 compile errors: ignore clang nullability-extension warnings/errors only for Macs 2025-04-03 20:22:28 -04:00
Andy Hovingh 2cf10999f4 fix Mac OS 15.4 compile errors: upgrade spirv-tools dependency to match dawn (6add4e478f8802d3bbd100120e5ffc6f725ec9fe) 2025-04-03 20:22:28 -04:00
Andy Hovingh e0e44da185 fix Mac OS 15.4 compile errors: upgrade spirv-headers dependency to match dawn (8e82b7cfeca98baae9a01a53511483da7194f854) 2025-04-03 20:22:28 -04:00
Andy Hovingh ce62e0ef06 fix Mac OS 15.4 compile errors: upgrade glslang dependency to match dawn (e57f993cff981c8c3ffd38967e030f04d13781a9) 2025-04-03 20:22:28 -04:00
Andy Hovingh 826b885d0e fix Mac OS 15.4 compile errors: upgrade dawn dependency (chromium/7106 tag/version) 2025-04-03 20:22:28 -04:00
Andy Hovingh 5b9a24edf0 fix Mac OS 15.4 compile errors: upgrade abseil dependency (version Abseil LTS 20250127.1) 2025-04-03 20:22:28 -04:00
Andy Hovingh b651f63e02 update release notes 2025-04-03 20:22:28 -04:00
Andy Hovingh c48273df3c fix Mac OS 15.4 compile errors: invoke printSurfaceConfiguration to avoid an unused error 2025-04-03 20:22:28 -04:00
Andy Hovingh 8397a15914 fix Mac OS 15.4 compile errors: upgrade libpng dependency (v1.6.47) 2025-04-03 20:22:28 -04:00
Andy Hovingh d76567b4a8 fix Mac OS 15.4 compile errors: upgrade libz dependency (v1.3.1) 2025-04-03 20:22:28 -04:00
Konrad Piascik ce62b169cb Fix Windows webGPU build 2025-04-03 14:12:17 -04:00
Powei Feng 9244f8ee21 vk: add YcbcrConversion to VulkanTexture (#8588)
- Refactor VulkanTextureState and VulkanTexture so that most
  state fields are consts.
- Add a field for VkYcbcrConversion.
- Clear the image view cache when YcbcrConversion changes
- Make sure that getAttachmentView and getView are better defined
  and called.
2025-04-03 17:30:40 +00:00
bridgewaterrobbie 3c7a871e7e Avoid running the inline pass without merge return on WebGPU 2025-04-02 17:52:27 -04:00
Jeremy Nelson 31d698e9ca remove resize and refactor GetCurrentTexture
remove resize and refactor GetCurrentTexture

remove resize and refactor GetCurrentTexture
2025-04-02 15:35:50 -05:00
Andy Hovingh 769b0db858 webgpu: select color format, present mode, and composite alpha mode from surface capabilities 2025-04-02 15:35:50 -05:00
Jeremy Nelson b40064b1ab Changed class name to WebGPUSwapChain and comments for handle allocator 2025-04-02 15:35:50 -05:00
Jeremy Nelson 889a62d617 Create WebGPUSurface Class 2025-04-02 15:35:50 -05:00
Mathias Agopian 784b3ce565 Fix subpasses with custom render targets
When Color grading is using the optimized subpass mode, it could fail
if it was the last pass. This is because in that case it renders
directly into the custom rendertarget which doesn't have MRTs (which
is needed). The fix is to issue an extra blit.

FIXES=[406343577]
2025-04-01 15:23:34 -07:00
Powei Feng 4f77547ab3 vk: add ycbcr conversion cache (#8557)
Introduce a cache for Ycbcr conversion, which is needed for
external textures on Android.
2025-04-01 22:12:59 +00:00
Nick Fisher 46c89499d0 Dispose debug.server/debug.fgViewerServer when Engine destroyed (#8563) 2025-04-01 20:52:42 +00:00
Sungun Park 57c782685b Add missing header (#8586) 2025-04-01 11:11:19 -07:00
bridgewaterrobbieandPowei Feng bc799c78f6 Update libs/filamat/src/GLSLPostProcessor.cpp per suggestion
Co-authored-by: Powei Feng <powei@google.com>
2025-03-31 19:39:05 -04:00
bridgewaterrobbieandPowei Feng 86e688a2a3 Update libs/filamat/src/GLSLPostProcessor.cpp per suggestion
Co-authored-by: Powei Feng <powei@google.com>
2025-03-31 19:39:05 -04:00
bridgewaterrobbie 3dead231d4 Output failing processed spir-v on failure to read with tint
Also add a build option to ignore such issues if needed during bringup
2025-03-31 19:39:05 -04:00
bridgewaterrobbie fd382ce219 Split and rebind combined image samplers for WebGPU API target 2025-03-31 19:39:05 -04:00
bridgewaterrobbie 46358572b7 Allow non uniform derivitives 2025-03-31 19:39:05 -04:00
bridgewaterrobbie 546b986107 Hack a workaround for OpConstantLoad issue in Tint to work with most spec constants 2025-03-31 19:39:05 -04:00
bridgewaterrobbie 5245141c46 Workaround Tint issues with texelFetchOffset in dofMedian.mat 2025-03-31 19:39:05 -04:00
bridgewaterrobbie 62b182fe04 Update SPIR-V tools to commit ada1771a9f7a125573aa94fe551fdc44b45769bd to pull in splitter pass 2025-03-31 19:39:05 -04:00
Sungun Park 87dbd59275 Release Filament 1.59.0 2025-03-31 22:23:12 +00:00
Juan Caldas 3c02eb9a63 WebGPU initial handle implementation (#8566)
* initial handle pass
2025-03-31 18:03:04 -04:00
Powei Feng 0bb96f0b62 Update MATERIAL_VERSION to 59 2025-03-28 15:38:42 -07:00
Powei Feng 06c0667636 Release Filament 1.58.2 2025-03-28 15:34:06 -07:00
c873552eaf Update runners to ubuntu-24.04 from deprecated ubuntu-20.04 label (#8581)
Co-authored-by: Bill Napier <napier@google.com>
Co-authored-by: Powei Feng <powei@google.com>
2025-03-28 15:30:15 -07:00
Powei Feng 38cc77807b noop: increase texture size (#8574) 2025-03-28 15:29:39 -07:00
Mathias Agopian ded1cb1b09 builders now accept StaticString names
The const char* version of name is node [[deprecated]].

This change is only cosmetic, no implementation is changed. This is
a first step toward eliminating non literal tags.
2025-03-28 15:18:08 -07:00
Mathias Agopian 653b2e859c fix CString::compare() 2025-03-28 15:18:08 -07:00
Mathias Agopian 7fd24c38d7 a new StaticString class 2025-03-28 15:18:08 -07:00
Mathias Agopian d4166e3a84 fix SGSR upscaling when a subpass is used
SGSR upscaling didn't take the viewport into account.
2025-03-28 15:03:36 -07:00
Matthew Hoffman c9f3d40993 Add helper for creating shaders in tests. (#8532)
Add helper for creating shaders in tests.

BUGS=[403541835]
2025-03-28 16:49:21 -05:00
Bill Napier 3cf76a837c Update runners to ubuntu-24.04 from deprecated ubuntu-20.04 label 2025-03-28 13:15:19 -07:00
Matthew Hoffman d1781fd66c Use the backend test cleanup helper. (#8560)
BUGS=[402472830]
2025-03-28 13:44:59 -05:00
Matthew Hoffman 577f1f83ca Add helper for tests to expect image results. (#8518)
Currently still based around using image hashes but will be improved later.

BUGS=[402474472]
2025-03-28 13:20:18 -05:00
Sungun Park 7956a28c71 Fix INVALID_OPERATION error for WebGL (#8576)
Mozilla sometimes clears the generic buffer binding when another buffer
is destroyed, if that other buffer is bound on an indexed buffer
binding.

The fix makes sure the generic buffer binding is unbound from the
context when being destroyed.

BUGS=[376125497]
2025-03-28 17:59:36 +00:00
e264dbad98 Upgrade all self-hosted ubuntu-22.04 runners to ubuntu-24.04. (#8579)
* Update runners to ubuntu-24.04 from deprecated ubuntu-20.04 label

---------

Co-authored-by: Bill Napier <napier@google.com>
Co-authored-by: Powei Feng <powei@google.com>
2025-03-28 17:31:33 +00:00
Mathias Agopian ef14e9c90e fix MSAA custom resolve
we were not binding the proper descriptor set. on gl this is only
an assert, on vulkan it would be a problem if we supported subpasses,
on metal there is no assert.
2025-03-28 10:07:04 -07:00
Bill Napier 6c27340d22 Update runners to ubuntu-24.04 from deprecated ubuntu-20.04 label 2025-03-28 08:43:46 -07:00
Powei Feng 3302c1ef4b Update presubmit.yml 2025-03-28 08:24:39 -07:00
Bill Napier c3ae2cdc8f Update runners to ubuntu-24.04 from deprecated ubuntu-20.04 label 2025-03-28 08:24:39 -07:00
Powei FengandBenjamin Doherty e52c6d8e53 Metal: add max texture size (#8572)
Use highestSupportedGpuFamily to query

---------

Co-authored-by: Benjamin Doherty <bendoherty@google.com>
2025-03-27 21:06:49 +00:00
Powei Feng fdcf82f17a vk: use real device limits (#8564) 2025-03-27 18:03:26 +00:00
Mathias Agopian 60f5c4d32c update remove ui (#8573) 2025-03-27 10:35:37 -07:00
Powei Feng 42ce76e285 Update MaterialVersion to 58 2025-03-26 10:56:33 -07:00
Doris Wu f903683c92 Pass the fgviewer gradle option (#8565) 2025-03-26 14:12:59 +08:00
Powei Feng 546ba48043 Fix test breaking changes with workarounds (#8569)
- Some tests require textures larger than 2048. Change the minimum
  to 4096 for now, and file proper bugs for tests.
- The introduction of uint8_t to DescriptorSetLayoutBinding
  caused unintended padding with using this struct as a hash key.
  We comment out the relevent code for now.
2025-03-25 16:11:35 -07:00
Powei Feng c81af315b2 engine: Add 1D LUT feature flag (#8568)
Note that the flag is default to false while we roll out this
feature.
2025-03-25 15:52:52 -07:00
rafadevai ea64db4218 vk: Fix Vulkan protected memory checks (#8561)
Set the support for isProtectedTexturesSupported
depending if the context supports protected content.

Fix checking if the protected feature is supported
on the host after retrieving the results from querying
the Physical Device.
2025-03-25 20:04:42 +00:00
9456 changed files with 353500 additions and 2044798 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
name: build-android
# We intentially use a larger runner here to enable larger disk space
# (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:
- uses: actions/checkout@v4.1.6
+1 -1
View File
@@ -10,7 +10,7 @@ on:
jobs:
build-linux:
name: build-linux
runs-on: ubuntu-22.04-16core
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
+4 -4
View File
@@ -40,7 +40,7 @@ jobs:
build-android:
name: build-android
runs-on: ubuntu-22.04-16core
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
@@ -69,7 +69,7 @@ jobs:
build-web:
name: build-web
runs-on: ubuntu-22.04-16core
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
@@ -79,7 +79,7 @@ jobs:
validate-docs:
name: validate-docs
runs-on: ubuntu-22.04-4core
runs-on: 'ubuntu-24.04-4core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -114,7 +114,7 @@ jobs:
validate-wgsl-webgpu:
name: validate-wgsl-webgpu
runs-on: ubuntu-22.04-8core
runs-on: 'ubuntu-24.04-8core'
steps:
- uses: actions/checkout@v4.1.6
+2 -2
View File
@@ -70,7 +70,7 @@ jobs:
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'
steps:
@@ -103,7 +103,7 @@ jobs:
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'
steps:
+1 -1
View File
@@ -10,7 +10,7 @@ on:
jobs:
build-web:
name: build-web
runs-on: ubuntu-22.04-16core
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
+3
View File
@@ -334,6 +334,9 @@ if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} /W0 /Zc:__cplusplus")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} -fstrict-aliasing -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations")
if (APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-nullability-extension")
endif()
endif()
if (FILAMENT_USE_EXTERNAL_GLES3)
+1 -2
View File
@@ -8,5 +8,4 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## 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**]
- Fix build/compile errors when upgrading to MacOS 15.4
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.58.1'
implementation 'com.google.android.filament:filament-android:1.59.0'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.58.1'
pod 'Filament', '~> 1.59.0'
```
## Documentation
+7
View File
@@ -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
[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
- engine: Generate 1D instead of 3D LUTs for color grading whenever possible.
+3 -3
View File
@@ -94,7 +94,7 @@ buildscript {
ext.versions = [
'jdk': 17,
'minSdk': 26,
'minSdk': 21,
'targetSdk': 34,
'compileSdk': 34,
'kotlin': '2.0.21',
@@ -125,7 +125,7 @@ buildscript {
ext.cmakeArgs = [
"--no-warn-unused-cli",
"-DANDROID_PIE=ON",
"-DANDROID_PLATFORM=26",
"-DANDROID_PLATFORM=21",
"-DANDROID_STL=c++_static",
"-DFILAMENT_DIST_DIR=${filamentPath}".toString(),
"-DFILAMENT_SUPPORTS_VULKAN=${excludeVulkan ? 'OFF' : 'ON'}".toString(),
@@ -200,7 +200,7 @@ subprojects {
ndkVersion versions.ndk
defaultConfig {
minSdkVersion 26
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
externalNativeBuild {
@@ -143,6 +143,15 @@ abstract class MaterialCompiler extends TaskWithBinary {
matcArgs += ['-a', 'vulkan']
}
def include_webgpu = providers
.gradleProperty("com.google.android.filament.include-webgpu")
.forUseAtConfigurationTime().present
if (!include_webgpu) {
matcArgs += ['-a', 'webgpu']
}
def mat_no_opt = providers
.gradleProperty("com.google.android.filament.matnopt")
.forUseAtConfigurationTime().present
+1 -10
View File
@@ -4,8 +4,6 @@ project(filament-utils-android)
set(FILAMENT_DIR ${FILAMENT_DIST_DIR})
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_library(camutils STATIC IMPORTED)
@@ -32,10 +30,6 @@ add_library(iblprefilter STATIC IMPORTED)
set_target_properties(iblprefilter PROPERTIES IMPORTED_LOCATION
${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 "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
@@ -63,8 +57,7 @@ target_include_directories(filament-utils-jni PRIVATE
..
../../filament/backend/include
${IMAGEIO_DIR}/include
../../libs/utils/include
../../libs/bluevk/include)
../../libs/utils/include)
set_target_properties(filament-utils-jni PROPERTIES LINK_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/libfilament-utils-jni.symbols)
@@ -78,6 +71,4 @@ target_link_libraries(filament-utils-jni
image
ktxreader
viewer
bluevk
android
)
@@ -12,10 +12,6 @@ android {
}
}
defaultConfig {
minSdkVersion 26
}
packagingOptions {
// 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
@@ -14,21 +14,12 @@
* limitations under the License.
*/
#include <android/hardware_buffer.h>
#include <android/hardware_buffer_jni.h>
#include <jni.h>
#include <filament/Engine.h>
#include <filament/IndirectLight.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 "common/NioUtils.h"
@@ -38,8 +29,6 @@ using namespace filament::math;
using namespace image;
using namespace ktxreader;
using namespace filament::backend;
jlong nCreateHDRTexture(JNIEnv* env, jclass,
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;
}
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*) {
JNIEnv* env;
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));
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;
}
@@ -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 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.58.1
VERSION_NAME=1.59.0
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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

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>
-1
View File
@@ -18,6 +18,5 @@ include ':samples:sample-stream-test'
include ':samples:sample-texture-view'
include ':samples:sample-textured-object'
include ':samples:sample-transparent-view'
include ':samples:sample-external-image'
rootProject.name = 'filament'
+4 -1
View File
@@ -200,8 +200,9 @@ WEBGPU_ANDROID_GRADLE_OPTION=""
EGL_ON_LINUX_OPTION="-DFILAMENT_SUPPORTS_EGL_ON_LINUX=OFF"
MATDBG_OPTION="-DFILAMENT_ENABLE_MATDBG=OFF"
FGVIEWER_OPTION="-DFILAMENT_ENABLE_FGVIEWER=OFF"
MATDBG_GRADLE_OPTION=""
FGVIEWER_OPTION="-DFILAMENT_ENABLE_FGVIEWER=OFF"
FGVIEWER_GRADLE_OPTION=""
MATOPT_OPTION=""
MATOPT_GRADLE_OPTION=""
@@ -533,6 +534,7 @@ function build_android {
${VULKAN_ANDROID_GRADLE_OPTION} \
${WEBGPU_ANDROID_GRADLE_OPTION} \
${MATDBG_GRADLE_OPTION} \
${FGVIEWER_GRADLE_OPTION} \
${MATOPT_GRADLE_OPTION} \
:filament-android:assembleDebug \
:gltfio-android:assembleDebug \
@@ -584,6 +586,7 @@ function build_android {
${VULKAN_ANDROID_GRADLE_OPTION} \
${WEBGPU_ANDROID_GRADLE_OPTION} \
${MATDBG_GRADLE_OPTION} \
${FGVIEWER_GRADLE_OPTION} \
${MATOPT_GRADLE_OPTION} \
:filament-android:assembleRelease \
:gltfio-android:assembleRelease \
File diff suppressed because one or more lines are too long
Binary file not shown.
+15 -1
View File
@@ -233,6 +233,8 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanReadPixels.h
src/vulkan/VulkanTexture.cpp
src/vulkan/VulkanTexture.h
src/vulkan/VulkanYcbcrConversionCache.cpp
src/vulkan/VulkanYcbcrConversionCache.h
)
if (LINUX OR WIN32)
list(APPEND SRCS src/vulkan/platform/VulkanPlatformLinuxWindows.cpp)
@@ -253,6 +255,10 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPUConstants.h
src/webgpu/WebGPUDriver.cpp
src/webgpu/WebGPUDriver.h
src/webgpu/WebGPUSwapChain.cpp
src/webgpu/WebGPUSwapChain.h
src/webgpu/WebGPUHandles.cpp
src/webgpu/WebGPUHandles.h
)
if (WIN32)
list(APPEND SRCS src/webgpu/platform/WebGPUPlatformWindows.cpp)
@@ -302,6 +308,11 @@ target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR})
# add this subproject to the Filament folder
set_target_properties(${TARGET} PROPERTIES FOLDER Filament)
# we need to export the headers properly for backend.lib on windows
if (WIN32 AND FILAMENT_SUPPORTS_WEBGPU)
target_compile_definitions(${TARGET} PRIVATE "WGPU_IMPLEMENTATION")
endif()
# ==================================================================================================
# Expose a header-only target to minimize dependencies.
# ==================================================================================================
@@ -494,7 +505,9 @@ if (APPLE OR LINUX)
test/ShaderGenerator.cpp
test/TrianglePrimitive.cpp
test/Arguments.cpp
test/ImageExpectations.cpp
test/Lifetimes.cpp
test/Shader.cpp
test/test_FeedbackLoops.cpp
test/test_Blit.cpp
test/test_MissingRequiredAttributes.cpp
@@ -510,6 +523,7 @@ if (APPLE OR LINUX)
test/test_Handles.cpp
)
set(BACKEND_TEST_LIBS
absl::str_format
backend
getopt
gtest
@@ -525,7 +539,7 @@ if (APPLE AND NOT IOS)
list(APPEND BACKEND_TEST_SRC
test/test_RenderExternalImage.cpp)
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
OSDependent
+10 -6
View File
@@ -256,16 +256,18 @@ struct DescriptorSetLayoutBinding {
DescriptorFlags flags = DescriptorFlags::NONE;
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==(
DescriptorSetLayoutBinding const& lhs,
friend inline bool operator==(DescriptorSetLayoutBinding const& lhs,
DescriptorSetLayoutBinding const& rhs) noexcept {
return lhs.type == rhs.type &&
lhs.flags == rhs.flags &&
lhs.count == rhs.count &&
lhs.stageFlags == rhs.stageFlags &&
lhs.externalSamplerDataIndex == rhs.externalSamplerDataIndex;
lhs.stageFlags == rhs.stageFlags;
// lhs.stageFlags == rhs.stageFlags &&
// lhs.externalSamplerDataIndex == rhs.externalSamplerDataIndex;
}
};
@@ -1138,7 +1140,9 @@ static_assert(sizeof(ExternalSamplerDatum) == 12);
struct DescriptorSetLayout {
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
utils::FixedCapacityVector<ExternalSamplerDatum> externalSamplerData;
// TODO: uncomment when needed
// utils::FixedCapacityVector<ExternalSamplerDatum> externalSamplerData;
};
//! blending equation function
@@ -361,8 +361,7 @@ public:
VkImageUsageFlags usage);
virtual VkSampler createExternalSampler(SamplerYcbcrConversion chroma,
SamplerParams sampler,
uint32_t internalFormat);
SamplerParams sampler, uint32_t internalFormat);
virtual VkImageView createExternalImageView(SamplerYcbcrConversion chroma,
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
@@ -40,9 +40,11 @@
#include <stddef.h>
#include <stdint.h>
#define HandleAllocatorGL HandleAllocator<32, 96, 184> // ~4520 / pool / MiB
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB
#define HandleAllocatorMTL HandleAllocator<32, 64, 552> // ~1660 / pool / MiB
#define HandleAllocatorGL HandleAllocator<32, 96, 184> // ~4520 / pool / MiB
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / 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 {
+4
View File
@@ -211,4 +211,8 @@ template class HandleAllocatorVK;
template class HandleAllocatorMTL;
#endif
#if defined (FILAMENT_SUPPORTS_WEBGPU)
template class HandleAllocatorWGPU;
#endif
} // namespace filament::backend
+11 -3
View File
@@ -1177,9 +1177,17 @@ size_t MetalDriver::getMaxUniformBufferSize() {
return 256 * 1024 * 1024; // TODO: return the actual size instead of hardcoding the minspec
}
size_t MetalDriver::getMaxTextureSize(SamplerType) {
// TODO: return the actual size instead of hardcoding the minspec
return 2048;
size_t MetalDriver::getMaxTextureSize(SamplerType type) {
if (type == SamplerType::SAMPLER_3D) {
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() {
+3 -1
View File
@@ -241,7 +241,9 @@ size_t NoopDriver::getMaxUniformBufferSize() {
}
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() {
+18 -3
View File
@@ -566,7 +566,11 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
} else if (strstr(renderer, "AMD") ||
strstr(renderer, "ATI")) {
// AMD/ATI GPU
} else if (strstr(vendor, "Mesa")) {
} else if (strstr(renderer, "Mozilla")) {
bugs->disable_invalidate_framebuffer = true;
}
if (strstr(vendor, "Mesa")) {
// Seen on
// [Mesa],
// [llvmpipe (LLVM 17.0.6, 256 bits)],
@@ -574,8 +578,6 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
// [4.50]
// not known which version are affected
bugs->rebind_buffer_after_deletion = true;
} else if (strstr(renderer, "Mozilla")) {
bugs->disable_invalidate_framebuffer = true;
}
} else {
// 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
# ifndef FILAMENT_IOS // FILAMENT_IOS is guaranteed to have ES3.x
if (UTILS_UNLIKELY(major == 2)) {
+2 -2
View File
@@ -320,8 +320,8 @@ public:
// a glFinish. So we must delay the destruction until we know the GPU is finished.
bool delay_fbo_destruction;
// Mesa sometimes clears the generic buffer binding when *another* buffer is destroyed,
// if that other buffer is bound on an *indexed* buffer binding.
// Mesa and Mozilla(web) sometimes clear the generic buffer binding when *another* buffer
// is destroyed, if that other buffer is bound on an *indexed* buffer binding.
bool rebind_buffer_after_deletion;
// Force feature level 0. Typically used for low end ES3 devices with significant driver
@@ -17,9 +17,6 @@
#include "VulkanBlitter.h"
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include "VulkanFboCache.h"
#include "VulkanHandles.h"
#include "VulkanSamplerCache.h"
#include "VulkanTexture.h"
#include "vulkan/utils/Image.h"
@@ -24,13 +24,6 @@
namespace filament::backend {
class VulkanBuffer;
class VulkanFboCache;
class VulkanPipelineCache;
class VulkanSamplerCache;
struct VulkanProgram;
class VulkanBlitter {
public:
VulkanBlitter(VkPhysicalDevice physicalDevice, VulkanCommands* commands) noexcept;
@@ -93,7 +93,7 @@
#endif
#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
#define FVK_DEBUG_FLAGS 0
#endif
@@ -56,9 +56,9 @@ VkImageView VulkanAttachment::getImageView() {
assert_invariant(texture);
VkImageSubresourceRange range = getSubresourceRange();
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 {
@@ -68,11 +68,11 @@ bool VulkanAttachment::isDepth() const {
VkImageSubresourceRange VulkanAttachment::getSubresourceRange() const {
assert_invariant(texture);
return {
.aspectMask = texture->getImageAspect(),
.baseMipLevel = uint32_t(level),
.levelCount = 1,
.baseArrayLayer = uint32_t(layer),
.layerCount = layerCount,
.aspectMask = texture->getImageAspect(),
.baseMipLevel = uint32_t(level),
.levelCount = 1,
.baseArrayLayer = uint32_t(layer),
.layerCount = layerCount,
};
}
@@ -17,7 +17,6 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANCONTEXT_H
#define TNT_FILAMENT_BACKEND_VULKANCONTEXT_H
#include "VulkanConstants.h"
#include "vulkan/utils/Image.h"
#include "vulkan/utils/Definitions.h"
@@ -30,8 +29,6 @@
#include <bluevk/BlueVK.h>
#include <memory>
VK_DEFINE_HANDLE(VmaAllocator)
VK_DEFINE_HANDLE(VmaPool)
@@ -260,7 +260,6 @@ VulkanDescriptorSetCache::~VulkanDescriptorSetCache() = default;
void VulkanDescriptorSetCache::terminate() noexcept{
mDescriptorPool.reset();
clearHistory();
}
// bind() is not really binding the set but just stashing until we have all the info
@@ -330,7 +329,6 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
}
VkWriteDescriptorSet const descriptorWrite = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
.dstSet = set->vkSet,
.dstBinding = binding,
.descriptorCount = 1,
@@ -346,20 +344,19 @@ void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescr
VkSampler sampler) noexcept {
VkDescriptorImageInfo info{
.sampler = sampler,
.imageLayout = fvkutils::getVkLayout(texture->getDefaultLayout()),
};
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
VkImageSubresourceRange range = texture->getPrimaryViewRange();
VkImageViewType const expectedType = texture->getViewType();
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
expectedType == VK_IMAGE_VIEW_TYPE_2D) {
// 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
// view for an attachment. Therefore, we can use getAttachmentView to get a
// corresponding VkImageView.
info.imageView = texture->getAttachmentView(range);
} else {
info.imageView = texture->getViewForType(range, expectedType);
// an attachment, then the range for this view has exactly one level and one layer.
range.levelCount = 1;
range.layerCount = 1;
}
info.imageLayout = fvkutils::getVkLayout(texture->getDefaultLayout());
info.imageView = texture->getView(range);
VkWriteDescriptorSet const descriptorWrite = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
@@ -395,7 +392,7 @@ fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet
});
}
void VulkanDescriptorSetCache::clearHistory() {
void VulkanDescriptorSetCache::gc() {
mStashedSets = {};
}
@@ -68,7 +68,7 @@ public:
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
void clearHistory();
void gc();
private:
class DescriptorInfinitePool;
+36 -21
View File
@@ -25,10 +25,12 @@
#include "VulkanHandles.h"
#include "VulkanMemory.h"
#include "VulkanTexture.h"
#include "vulkan/VulkanSamplerCache.h"
#include "vulkan/memory/ResourceManager.h"
#include "vulkan/memory/ResourcePointer.h"
#include "vulkan/utils/Conversion.h"
#include "vulkan/utils/Definitions.h"
#include "vulkan/vulkan_core.h"
#include <backend/DriverEnums.h>
#include <backend/platforms/VulkanPlatform.h>
@@ -212,6 +214,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
mPipelineCache(mPlatform->getDevice()),
mStagePool(mAllocator, &mCommands),
mFramebufferCache(mPlatform->getDevice()),
mYcbcrConversionCache(mPlatform->getDevice()),
mSamplerCache(mPlatform->getDevice()),
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
mReadPixels(mPlatform->getDevice()),
@@ -363,7 +366,7 @@ void VulkanDriver::collectGarbage() {
FVK_SYSTRACE_SCOPE();
// Command buffers need to be submitted and completed before other resources can be gc'd.
mCommands.gc();
mDescriptorSetCache.clearHistory();
mDescriptorSetCache.gc();
mStagePool.gc();
mFramebufferCache.gc();
mPipelineCache.gc();
@@ -418,7 +421,11 @@ void VulkanDriver::updateDescriptorSetTexture(
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
VkSampler const vksampler = mSamplerCache.getSampler(params);
// TODO: YcbcrConversion?
VulkanSamplerCache::Params cacheParams = {
.sampler = params,
};
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
}
@@ -556,7 +563,7 @@ void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::S
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
Platform::ExternalImageHandleRef externalImage) {
FVK_SYSTRACE_SCOPE();
const auto& metadata = mPlatform->getExternalImageMetadata(externalImage);
auto const& metadata = mPlatform->getExternalImageMetadata(externalImage);
if (metadata.isProtected) {
usage |= backend::TextureUsage::PROTECTED;
}
@@ -566,7 +573,7 @@ void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::S
vkUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
if (any(usage & (TextureUsage::BLIT_DST & TextureUsage::UPLOADABLE))) {
if (any(usage & (TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE))) {
vkUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
@@ -582,13 +589,15 @@ void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::S
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex != VK_MAX_MEMORY_TYPES)
<< "failed to find a valid memory type for external image memory.";
const auto& data =
VkImage vkimg;
VkDeviceMemory deviceMemory;
std::tie(vkimg, deviceMemory) =
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);
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mContext,
mPlatform->getDevice(), mAllocator, &mResourceManager, &mCommands, vkimg, deviceMemory,
metadata.format, VK_NULL_HANDLE, metadata.samples, metadata.width, metadata.height,
metadata.layerCount, usage, mStagePool);
texture.inc();
}
@@ -597,13 +606,14 @@ void VulkanDriver::createTextureExternalImageR(Handle<HwTexture> th, backend::Sa
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
void* externalImage) {
FVK_SYSTRACE_SCOPE();
assert_invariant(false && "Not supported in Vulkan backend");
// not supported in this backend
}
void VulkanDriver::createTextureExternalImagePlaneR(Handle<HwTexture> th,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
void* image, uint32_t plane) {
assert_invariant(false && "Not supported in Vulkan backend");
}
void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
@@ -611,6 +621,7 @@ void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage usage) {
// not supported in this backend
assert_invariant(false && "Not supported in Vulkan backend");
}
void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
@@ -1057,7 +1068,7 @@ bool VulkanDriver::isDepthStencilBlitSupported(TextureFormat format) {
formats.end();
}
bool VulkanDriver::isProtectedTexturesSupported() { return false; }
bool VulkanDriver::isProtectedTexturesSupported() { return isProtectedContentSupported(); }
bool VulkanDriver::isDepthClampSupported() {
return mContext.isDepthClampSupported();
@@ -1134,24 +1145,28 @@ math::float2 VulkanDriver::getClipSpaceParams() {
}
uint8_t VulkanDriver::getMaxDrawBuffers() {
return MRT::MIN_SUPPORTED_RENDER_TARGET_COUNT; // TODO: query real value
return mContext.getPhysicalDeviceLimits().maxColorAttachments;
}
size_t VulkanDriver::getMaxUniformBufferSize() {
// TODO: return the actual size instead of hardcoded value
// TODO: devices that return less than 32768 should be rejected. This represents only 3%
// of android devices.
return 32768;
return mContext.getPhysicalDeviceLimits().maxUniformBufferRange;
}
size_t VulkanDriver::getMaxTextureSize(SamplerType) {
// TODO: return the actual size instead of hardcoded value
return 2048;
size_t VulkanDriver::getMaxTextureSize(SamplerType type) {
switch (type) {
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() {
// TODO: return the actual size instead of hardcoded value
return 256;
return mContext.getPhysicalDeviceLimits().maxImageArrayLayers;
}
void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
@@ -27,6 +27,7 @@
#include "VulkanSamplerCache.h"
#include "VulkanStagePool.h"
#include "VulkanQueryManager.h"
#include "VulkanYcbcrConversionCache.h"
#include "vulkan/VulkanDescriptorSetCache.h"
#include "vulkan/VulkanDescriptorSetLayoutCache.h"
#include "vulkan/VulkanPipelineLayoutCache.h"
@@ -135,6 +136,7 @@ private:
VulkanPipelineCache mPipelineCache;
VulkanStagePool mStagePool;
VulkanFboCache mFramebufferCache;
VulkanYcbcrConversionCache mYcbcrConversionCache;
VulkanSamplerCache mSamplerCache;
VulkanBlitter mBlitter;
VulkanReadPixels mReadPixels;
@@ -18,24 +18,18 @@
#define TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
#include "VulkanCommands.h"
#include "VulkanMemory.h"
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
#include "backend/Program.h"
#include <bluevk/BlueVK.h>
#include <utils/bitset.h>
#include <utils/compiler.h>
#include <utils/Hash.h>
#include <list>
#include <tsl/robin_map.h>
#include <type_traits>
#include <vector>
#include <unordered_map>
namespace filament::backend {
@@ -14,7 +14,9 @@
* limitations under the License.
*/
#include "vulkan/VulkanSamplerCache.h"
#include "VulkanSamplerCache.h"
#include "VulkanConstants.h"
#include "vulkan/utils/Conversion.h"
#include <utils/Panic.h>
@@ -26,41 +28,40 @@ namespace filament::backend {
VulkanSamplerCache::VulkanSamplerCache(VkDevice device)
: mDevice(device) {}
VkSampler VulkanSamplerCache::getSampler(SamplerParams params) noexcept {
VkSampler VulkanSamplerCache::getSampler(Params params) noexcept {
auto iter = mCache.find(params);
if (UTILS_LIKELY(iter != mCache.end())) {
return iter->second;
}
VkSamplerCreateInfo samplerInfo {
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.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),
auto const& samplerParams = params.sampler;
VkSamplerCreateInfo samplerInfo{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.magFilter = fvkutils::getFilter(samplerParams.filterMag),
.minFilter = fvkutils::getFilter(samplerParams.filterMin),
.mipmapMode = fvkutils::getMipmapMode(samplerParams.filterMin),
.addressModeU = fvkutils::getWrapMode(samplerParams.wrapS),
.addressModeV = fvkutils::getWrapMode(samplerParams.wrapT),
.addressModeW = fvkutils::getWrapMode(samplerParams.wrapR),
.anisotropyEnable = samplerParams.anisotropyLog2 == 0 ? VK_FALSE : VK_TRUE,
.maxAnisotropy = (float) (1u << samplerParams.anisotropyLog2),
.compareEnable = fvkutils::getCompareEnable(samplerParams.compareMode),
.compareOp = fvkutils::getCompareOp(samplerParams.compareFunc),
.minLod = 0.0f,
.maxLod = fvkutils::getMaxLod(params.filterMin),
.maxLod = fvkutils::getMaxLod(samplerParams.filterMin),
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
.unnormalizedCoordinates = VK_FALSE
};
.unnormalizedCoordinates = VK_FALSE };
VkSampler sampler;
VkResult result = vkCreateSampler(mDevice, &samplerInfo, VKALLOC, &sampler);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create sampler."
<< " error=" << static_cast<int32_t>(result);
mCache.insert({params, sampler});
mCache.insert({ params, sampler });
return sampler;
}
void VulkanSamplerCache::terminate() noexcept {
for (auto pair : mCache) {
for (auto pair: mCache) {
vkDestroySampler(mDevice, pair.second, VKALLOC);
}
mCache.clear();
}
} // namespace filament::backend
}// namespace filament::backend
@@ -17,8 +17,11 @@
#ifndef 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>
namespace filament::backend {
@@ -26,12 +29,28 @@ namespace filament::backend {
// Simple manager for VkSampler objects.
class VulkanSamplerCache {
public:
struct Params {
SamplerParams sampler = {};
uint32_t padding = 0;
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
};
static_assert(sizeof(Params) == 16);
explicit VulkanSamplerCache(VkDevice device);
VkSampler getSampler(SamplerParams params) noexcept;
VkSampler getSampler(Params params) noexcept;
void terminate() noexcept;
private:
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
@@ -32,6 +32,7 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
VulkanCommands* commands, VulkanStagePool& stagePool, void* nativeWindow, uint64_t flags,
VkExtent2D extent)
: mPlatform(platform),
mContext(context),
mResourceManager(resourceManager),
mCommands(commands),
mAllocator(allocator),
@@ -77,16 +78,16 @@ void VulkanSwapChain::update() {
}
for (auto const color: bundle.colors) {
auto colorTexture = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager,
device, mAllocator, mResourceManager, mCommands, color, VK_NULL_HANDLE,
bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height, bundle.layerCount, colorUsage,
mStagePool);
mContext, device, mAllocator, mResourceManager, mCommands, color, VK_NULL_HANDLE,
bundle.colorFormat, VK_NULL_HANDLE /*ycrcb */, 1, bundle.extent.width,
bundle.extent.height, bundle.layerCount, colorUsage, mStagePool);
mColors.push_back(colorTexture);
}
mDepth = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager, device,
mAllocator, mResourceManager, mCommands, bundle.depth, VK_NULL_HANDLE,
bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height, bundle.layerCount, depthUsage,
mStagePool);
mDepth = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager, mContext, device,
mAllocator, mResourceManager, mCommands, bundle.depth, VK_NULL_HANDLE,
bundle.depthFormat, VK_NULL_HANDLE /*ycrcb */, 1, bundle.extent.width,
bundle.extent.height, bundle.layerCount, depthUsage, mStagePool);
mExtent = bundle.extent;
mLayerCount = bundle.layerCount;
@@ -19,6 +19,7 @@
#include "DriverBase.h"
#include "VulkanConstants.h"
#include "VulkanContext.h"
#include "VulkanTexture.h"
#include "vulkan/memory/Resource.h"
@@ -28,7 +29,6 @@
#include <bluevk/BlueVK.h>
#include <utils/FixedCapacityVector.h>
#include <memory>
using namespace bluevk;
@@ -83,6 +83,7 @@ private:
void update();
VulkanPlatform* mPlatform;
VulkanContext const& mContext;
fvkmemory::ResourceManager* mResourceManager;
VulkanCommands* mCommands;
VmaAllocator mAllocator;
+210 -169
View File
@@ -15,6 +15,7 @@
*/
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include "VulkanMemory.h"
#include "VulkanTexture.h"
#include "vulkan/memory/ResourcePointer.h"
@@ -54,7 +55,6 @@ VkComponentMapping composeSwizzle(VkComponentMapping const& prev, VkComponentMap
VK_COMPONENT_SWIZZLE_B,
VK_COMPONENT_SWIZZLE_A,
};
auto const compose = [](VkComponentMapping const& prev,
VkComponentMapping const& next) -> VkComponentMapping {
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);
}
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
VulkanTextureState::VulkanTextureState(VkDevice device, VmaAllocator allocator,
VulkanCommands* commands, VulkanStagePool& stagePool, VkFormat format,
VkImageViewType viewType, uint8_t levels, uint8_t layerCount, VulkanLayout defaultLayout,
VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands,
VmaAllocator allocator, VkDevice device, VkImage image, VkDeviceMemory deviceMemory,
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount,
VkSamplerYcbcrConversion ycbcrConversion, bool isExternalFormat, VkImageUsageFlags usage,
bool isProtected)
: mVkFormat(format),
mViewType(viewType),
mFullViewRange{fvkutils::getImageAspect(format), 0, levels, 0, layerCount},
mDefaultLayout(defaultLayout),
mIsProtected(isProtected),
mStagePool(stagePool),
mDevice(device),
mAllocator(allocator),
: mStagePool(stagePool),
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
VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator,
VulkanTextureState::~VulkanTextureState() {
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,
VkDeviceMemory memory, VkFormat format, uint8_t samples, uint32_t width,
uint32_t height, uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool)
: HwTexture(getSamplerTypeFromDepth(depth), 1, samples, width, height, depth, TextureFormat::UNUSED,
tusage),
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, device,
allocator, commands, stagePool, format, fvkutils::getViewType(SamplerType::SAMPLER_2D),
/*mipLevels=*/1, getLayerCountFromDepth(depth), getDefaultLayoutImpl(tusage), any(usage & TextureUsage::PROTECTED))) {
mState->mTextureImage = image;
mState->mTextureImageMemory = memory;
VkDeviceMemory memory, VkFormat format, VkSamplerYcbcrConversion conversion,
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage tusage,
VulkanStagePool& stagePool)
: HwTexture(getSamplerTypeFromDepth(depth), 1, samples, width, height, depth,
TextureFormat::UNUSED, tusage),
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, stagePool,
commands, allocator, device, image, memory, format,
fvkutils::getViewType(SamplerType::SAMPLER_2D),
/*mipLevels=*/1, getLayerCountFromDepth(depth), conversion,
/*isExternalFormat=*/false,
getUsage(context, samples, VK_NULL_HANDLE, format, tusage),
any(usage & TextureUsage::PROTECTED))) {
mPrimaryViewRange = mState->mFullViewRange;
}
@@ -189,21 +306,19 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, SamplerType target,
uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h,
uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool)
: 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))) {
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage) {
// 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{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,
.format = mState->mVkFormat,
.format = vkFormat,
.extent = {w, h, depth},
.mipLevels = levels,
.arrayLayers = 1,
.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 (context.isImageView2DOn3DImageSupported()) {
@@ -233,79 +348,17 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.arrayLayers = depth * 6;
imageInfo.extent.depth = 1;
}
if (any(usage & TextureUsage::PROTECTED)) {
if (isProtected) {
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.
// 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).
const auto& limits = context.getPhysicalDeviceLimits();
auto const& limits = context.getPhysicalDeviceLimits();
if (imageInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) {
samples = fvkutils::reduceSampleCount(samples,
fvkutils::isVkDepthFormat(mState->mVkFormat)
fvkutils::isVkDepthFormat(vkFormat)
? limits.sampledImageDepthSampleCounts
: limits.sampledImageColorSampleCounts);
}
@@ -319,33 +372,36 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
this->samples = 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)) {
FVK_LOGD << "vkCreateImage: "
<< "image = " << mState->mTextureImage << ", "
<< "image = " << textureImage << ", "
<< "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 << ", "
<< "mipLevels = " << int(levels) << ", "
<< "TextureUsage = " << static_cast<int>(usage) << ", "
<< "TextureUsage = " << static_cast<int>(tusage) << ", "
<< "usage = " << imageInfo.usage << ", "
<< "samples = " << imageInfo.samples << ", "
<< "type = " << imageInfo.imageType << ", "
<< "flags = " << imageInfo.flags << ", "
<< "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."
<< " error=" << static_cast<int32_t>(result);
// Allocate memory for the VkImage and bind it.
VkMemoryRequirements memReqs = {};
vkGetImageMemoryRequirements(mState->mDevice, mState->mTextureImage, &memReqs);
VkMemoryRequirements 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 |
(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
= context.selectMemoryType(memReqs.memoryTypeBits, requiredMemoryFlags);
@@ -357,21 +413,22 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
.allocationSize = memReqs.size,
.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."
<< " error=" << static_cast<int32_t>(result);
result = vkBindImageMemory(mState->mDevice, mState->mTextureImage, mState->mTextureImageMemory,
0);
result = vkBindImageMemory(device, textureImage, textureImageMemory, 0);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to bind image."
<< " 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.
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
@@ -380,8 +437,8 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
fvkmemory::resource_ptr<VulkanTexture> src, uint8_t baseLevel,
uint8_t levelCount)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage) {
mState = src->mState;
src->format, src->usage),
mState(src->mState) {
mPrimaryViewRange = src->mPrimaryViewRange;
mPrimaryViewRange.baseMipLevel = src->mPrimaryViewRange.baseMipLevel + baseLevel;
mPrimaryViewRange.levelCount = levelCount;
@@ -392,21 +449,10 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
fvkmemory::resource_ptr<VulkanTexture> src, VkComponentMapping swizzle)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage) {
mState = src->mState;
mPrimaryViewRange = src->mPrimaryViewRange;
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);
}
}
src->format, src->usage),
mState(src->mState),
mPrimaryViewRange(src->mPrimaryViewRange),
mSwizzle(composeSwizzle(src->mSwizzle, swizzle)) {}
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) {
@@ -513,20 +559,20 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
commands.acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
// 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 },
.srcOffsets = { rect[0], rect[1] },
.dstSubresource = { aspect, uint32_t(miplevel), layer, 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 oldLayout = getLayout(layer, miplevel);
@@ -542,41 +588,21 @@ VulkanLayout VulkanTexture::getDefaultLayout() const {
return mState->mDefaultLayout;
}
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
range.levelCount = 1;
range.layerCount = 1;
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D, {});
}
VkImageView VulkanTexture::getMultiviewAttachmentView(VkImageSubresourceRange range) {
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D_ARRAY, {});
}
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;
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange const& range, VkImageViewType type) {
assert_invariant(mState->mYcbcr.conversion == VK_NULL_HANDLE &&
"We are not yet supporting external image as attachments.");
if (type == VK_IMAGE_VIEW_TYPE_2D) {
VkImageSubresourceRange copy = range;
copy.levelCount = 1;
copy.layerCount = 1;
return mState->getImageView(copy, type, {});
} else {
return mState->getImageView(range, type, {});
}
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.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;
}
VkImageView VulkanTexture::getView(VkImageSubresourceRange const& range) {
return mState->getImageView(range, mState->mViewType, mSwizzle);
}
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 {
assert_invariant(level <= 0xffff && layer <= 0xffff);
const uint32_t key = (layer << 16) | level;
+56 -37
View File
@@ -19,7 +19,10 @@
#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/ResourcePointer.h"
#include "vulkan/utils/Image.h"
@@ -31,10 +34,14 @@
namespace filament::backend {
struct VulkanTexture;
struct VulkanTextureState : public fvkmemory::Resource {
VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool, VkFormat format, VkImageViewType viewType, uint8_t levels,
uint8_t layerCount, VulkanLayout defaultLayout, bool isProtected);
VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands, VmaAllocator allocator,
VkDevice device, VkImage image, VkDeviceMemory deviceMemory, VkFormat format,
VkImageViewType viewType, uint8_t levels, uint8_t layerCount,
VkSamplerYcbcrConversion ycbcrConversion, bool isExternalFormat,
VkImageUsageFlags usage, bool isProtected);
~VulkanTextureState();
@@ -57,29 +64,50 @@ struct VulkanTextureState : public fvkmemory::Resource {
// No implicit padding allowed due to it being a hash key.
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.
fvkmemory::resource_ptr<VulkanTexture> mSidecarMSAA;
VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE;
VkImage const mTextureImage;
VkDeviceMemory const mTextureImageMemory;
VkFormat const mVkFormat;
VkImageViewType const mViewType;
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.
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
VulkanStagePool& mStagePool;
VkDevice mDevice;
VmaAllocator mAllocator;
VulkanCommands* mCommands;
bool mIsTransientAttachment;
friend struct VulkanTexture;
};
struct VulkanTexture : public HwTexture, fvkmemory::Resource {
@@ -91,10 +119,10 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
VulkanStagePool& stagePool);
// 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(VkDevice device, VmaAllocator allocator,
VulkanTexture(VulkanContext const& context, VkDevice device, VmaAllocator allocator,
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);
// 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,
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 {
return mState->mViewType;
}
@@ -132,20 +155,11 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
// time of the call.
VulkanLayout getDefaultLayout() const;
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
// target attachment. Unlike the primary image view, this always has type VK_IMAGE_VIEW_TYPE_2D
// and the identity swizzle.
VkImageView getAttachmentView(VkImageSubresourceRange range);
// 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 the identity swizzle.
VkImageView getAttachmentView(VkImageSubresourceRange const& range, VkImageViewType type);
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
// 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);
VkImageView getView(VkImageSubresourceRange const& range);
VkFormat getVkFormat() const {
return mState->mVkFormat;
@@ -165,7 +179,7 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
}
bool isTransientAttachment() const {
return mState->mIsTransientAttachment;
return mState->mUsage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
}
bool getIsProtected() const {
@@ -192,6 +206,11 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
// manually (outside of calls to transitionLayout).
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)
void print() const;
#endif
@@ -0,0 +1,82 @@
/*
* 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 = 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),
};
#if defined(__ANDROID__)
VkExternalFormatANDROID externalFormat = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
.externalFormat = params.externalFormat,
};
if (params.externalFormat) {
conversionInfo.pNext = &externalFormat;
}
#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,59 @@
/*
* 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 = {};
uint32_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
@@ -736,10 +736,6 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
chainStruct(&context.mPhysicalDeviceFeatures, &queryProtectedMemoryFeatures);
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,
// physicalDeviceFeatures, graphicsQueueFamilyIndex.
vkGetPhysicalDeviceProperties2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
@@ -759,6 +755,10 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
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) {
mImpl->mProtectedGraphicsQueueFamilyIndex = identifyGraphicsQueueFamilyIndex(
mImpl->mPhysicalDevice, (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_PROTECTED_BIT));
@@ -295,9 +295,8 @@ VulkanPlatformAndroid::ImageData VulkanPlatformAndroid::createExternalImageData(
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device, SamplerYcbcrConversion chroma,
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
VkImageViewType viewType, VkComponentMapping swizzle){
VkExternalFormatANDROID externalFormat = {
VkExternalFormatANDROID externalFormat = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
.pNext = nullptr,
.externalFormat = internalFormat,
};
@@ -322,7 +321,6 @@ VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device, Sampler
VkSamplerYcbcrConversionInfo samplerYcbcrConversionInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
.pNext = nullptr,
.conversion = conversion,
};
@@ -350,7 +348,6 @@ VkSampler VulkanPlatform::createExternalSamplerImpl(
uint32_t internalFormat) {
VkExternalFormatANDROID externalFormat = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
.pNext = nullptr,
.externalFormat = internalFormat,
};
@@ -18,12 +18,12 @@
#define TNT_FILAMENT_BACKEND_VULKANSWAPCHAIN_IMPL_H
#include "vulkan/VulkanContext.h"
#include "vulkan/VulkanConstants.h"
#include <backend/platforms/VulkanPlatform.h>
#include <bluevk/BlueVK.h>
#include <tuple>
#include <unordered_map>
using namespace bluevk;
+125 -69
View File
@@ -22,6 +22,7 @@
#include "CommandStreamDispatcher.h"
#include "DriverBase.h"
#include "private/backend/Dispatcher.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
@@ -35,6 +36,7 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <sstream>
#include <string_view>
#include <utility>
@@ -116,7 +118,6 @@ void printLimits(wgpu::Limits const& limits) {
printLimit("maxBufferSize", limits.maxBufferSize);
printLimit("maxVertexAttributes", limits.maxVertexAttributes);
printLimit("maxVertexBufferArrayStride", limits.maxVertexBufferArrayStride);
printLimit("maxInterStageShaderComponents", limits.maxInterStageShaderComponents);
printLimit("maxInterStageShaderVariables", limits.maxInterStageShaderVariables);
printLimit("maxColorAttachments", limits.maxColorAttachments);
printLimit("maxColorAttachmentBytesPerSample", limits.maxColorAttachmentBytesPerSample);
@@ -158,8 +159,6 @@ void printAdapterDetails(wgpu::Adapter const& adapter) {
FWGPU_LOGI << " vendor ID: " << adapterInfo.vendorID << utils::io::endl;
FWGPU_LOGI << " subgroup min size: " << adapterInfo.subgroupMinSize << utils::io::endl;
FWGPU_LOGI << " subgroup max size: " << adapterInfo.subgroupMaxSize << utils::io::endl;
FWGPU_LOGI << " compatibility mode: " << bool(adapterInfo.compatibilityMode)
<< utils::io::endl;
FWGPU_LOGI << " power preference: " << powerPreferenceStream.str() << utils::io::endl;
}
wgpu::SupportedFeatures supportedFeatures{};
@@ -175,55 +174,17 @@ void printAdapterDetails(wgpu::Adapter const& adapter) {
FWGPU_LOGI << " " << nameStream.str().data() << utils::io::endl;
});
}
wgpu::SupportedLimits supportedLimits{};
wgpu::Limits supportedLimits{};
if (!adapter.GetLimits(&supportedLimits)) {
FWGPU_LOGW << "Failed to get WebGPU adapter supported limits" << utils::io::endl;
} else {
FWGPU_LOGI << "WebGPU adapter supported limits:" << utils::io::endl;
printLimits(supportedLimits.limits);
printLimits(supportedLimits);
}
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabilities) {
std::stringstream usages_stream{};
usages_stream << capabilities.usages;
FWGPU_LOGI << "WebGPU surface capabilities:" << utils::io::endl;
FWGPU_LOGI << " surface usages: " << usages_stream.str().data() << utils::io::endl;
FWGPU_LOGI << " surface formats (" << capabilities.formatCount << "):" << utils::io::endl;
if (capabilities.formatCount > 0 && capabilities.formats != nullptr) {
std::for_each(capabilities.formats, capabilities.formats + capabilities.formatCount,
[](wgpu::TextureFormat const format) {
std::stringstream format_stream{};
format_stream << format;
FWGPU_LOGI << " " << format_stream.str().data() << utils::io::endl;
});
}
FWGPU_LOGI << " surface present modes (" << capabilities.presentModeCount
<< "):" << utils::io::endl;
if (capabilities.presentModeCount > 0 && capabilities.presentModes != nullptr) {
std::for_each(capabilities.presentModes,
capabilities.presentModes + capabilities.presentModeCount,
[](wgpu::PresentMode const presentMode) {
std::stringstream present_mode_stream{};
present_mode_stream << presentMode;
FWGPU_LOGI << " " << present_mode_stream.str().data() << utils::io::endl;
});
}
FWGPU_LOGI << " surface alpha modes (" << capabilities.alphaModeCount
<< "):" << utils::io::endl;
if (capabilities.alphaModeCount > 0 && capabilities.alphaModes != nullptr) {
std::for_each(capabilities.alphaModes,
capabilities.alphaModes + capabilities.alphaModeCount,
[](wgpu::CompositeAlphaMode const alphaMode) {
std::stringstream alpha_mode_stream{};
alpha_mode_stream << alphaMode;
FWGPU_LOGI << " " << alpha_mode_stream.str().data() << utils::io::endl;
});
}
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printDeviceDetails(wgpu::Device const& device) {
@@ -240,24 +201,31 @@ void printDeviceDetails(wgpu::Device const& device) {
FWGPU_LOGI << " " << nameStream.str().data() << utils::io::endl;
});
}
wgpu::SupportedLimits supportedLimits{};
wgpu::Limits supportedLimits{};
if (!device.GetLimits(&supportedLimits)) {
FWGPU_LOGW << "Failed to get WebGPU supported device limits" << utils::io::endl;
} else {
FWGPU_LOGI << "WebGPU device supported limits:" << utils::io::endl;
printLimits(supportedLimits.limits);
printLimits(supportedLimits);
}
}
#endif
}// namespace
Driver* WebGPUDriver::create(WebGPUPlatform& platform) noexcept {
return new WebGPUDriver(platform);
Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept {
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
: mPlatform(platform) {
WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept
: mPlatform(platform),
mHandleAllocator("Handles",
driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck,
driverConfig.disableHeapHandleTags) {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printInstanceDetails(mPlatform.getInstance());
#endif
@@ -289,10 +257,14 @@ void WebGPUDriver::terminate() {
}
void WebGPUDriver::tick(int) {
mDevice.Tick();
}
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) {
wgpu::CommandEncoderDescriptor commandEncoderDescriptor{};
commandEncoderDescriptor.nextInChain = nullptr;
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
}
void WebGPUDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
@@ -309,6 +281,12 @@ void WebGPUDriver::setPresentationTime(int64_t monotonic_clock_ns) {
}
void WebGPUDriver::endFrame(uint32_t frameId) {
// FWGPU_LOGW << __FUNCTION__<< "\n";
mQueue.Submit(1, &mCommandBuffer);
mCommandEncoder = nullptr;
mCommandBuffer = nullptr;
mTextureView = nullptr;
mSwapChain->Present();
}
void WebGPUDriver::flush(int) {
@@ -342,6 +320,13 @@ void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
}
void WebGPUDriver::destroySwapChain(Handle<HwSwapChain> sch) {
mSwapChain = nullptr;
// TODO: use webgpu handle allocator from
// https://github.com/google/filament/pull/8566
// if (sch) {
// HwSwapChain* hwSwapChain = handleCast<HwSwapChain*>(sch);
// destruct(sch, hwSwapChain);
// }
}
void WebGPUDriver::destroyStream(Handle<HwStream> sh) {
@@ -357,6 +342,9 @@ void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) {
}
Handle<HwSwapChain> WebGPUDriver::createSwapChainS() noexcept {
// TODO: use webgpu handle allocator from.
// https://github.com/google/filament/pull/8566
// return allocAndConstructHandle<HwSwapChain>();
return Handle<HwSwapChain>((Handle<HwSwapChain>::HandleId) mNextFakeHandle++);
}
@@ -365,7 +353,7 @@ Handle<HwSwapChain> WebGPUDriver::createSwapChainHeadlessS() noexcept {
}
Handle<HwTexture> WebGPUDriver::createTextureS() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
return allocHandle<WGPUTexture>();
}
Handle<HwTexture> WebGPUDriver::importTextureS() noexcept {
@@ -385,23 +373,23 @@ Handle<HwTimerQuery> WebGPUDriver::createTimerQueryS() noexcept {
}
Handle<HwIndexBuffer> WebGPUDriver::createIndexBufferS() noexcept {
return Handle<HwIndexBuffer>((Handle<HwIndexBuffer>::HandleId) mNextFakeHandle++);
return allocHandle<HwIndexBuffer>();
}
Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
return allocHandle<WGPUTexture>();
}
Handle<HwBufferObject> WebGPUDriver::createBufferObjectS() noexcept {
return Handle<HwBufferObject>((Handle<HwBufferObject>::HandleId) mNextFakeHandle++);
return allocHandle<WGPUBufferObject>();
}
Handle<HwRenderTarget> WebGPUDriver::createRenderTargetS() noexcept {
return Handle<HwRenderTarget>((Handle<HwRenderTarget>::HandleId) mNextFakeHandle++);
return allocHandle<WGPURenderTarget>();
}
Handle<HwVertexBuffer> WebGPUDriver::createVertexBufferS() noexcept {
return Handle<HwVertexBuffer>((Handle<HwVertexBuffer>::HandleId) mNextFakeHandle++);
return allocHandle<WGPUVertexBuffer>();
}
Handle<HwDescriptorSet> WebGPUDriver::createDescriptorSetS() noexcept {
@@ -413,15 +401,15 @@ Handle<HwRenderPrimitive> WebGPUDriver::createRenderPrimitiveS() noexcept {
}
Handle<HwVertexBufferInfo> WebGPUDriver::createVertexBufferInfoS() noexcept {
return Handle<HwVertexBufferInfo>((Handle<HwVertexBufferInfo>::HandleId) mNextFakeHandle++);
return allocHandle<WGPUVertexBufferInfo>();
}
Handle<HwTexture> WebGPUDriver::createTextureViewSwizzleS() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
return allocHandle<WGPUTexture>();
}
Handle<HwRenderTarget> WebGPUDriver::createDefaultRenderTargetS() noexcept {
return Handle<HwRenderTarget>((Handle<HwRenderTarget>::HandleId) mNextFakeHandle++);
return allocHandle<WGPURenderTarget>();
}
Handle<HwDescriptorSetLayout> WebGPUDriver::createDescriptorSetLayoutS() noexcept {
@@ -442,25 +430,43 @@ Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
}
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
mSurface= mPlatform.createSurface(nativeWindow, flags);
mAdapter = mPlatform.requestAdapter(mSurface);
// TODO: use webgpu handle allocator from.
// https://github.com/google/filament/pull/8566
// HwSwapChain* hwSwapChain = handleCast<HwSwapChain*>(sch);
assert_invariant(!mSwapChain);
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
mAdapter = mPlatform.requestAdapter(surface);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printAdapterDetails(mAdapter);
wgpu::SurfaceCapabilities surfaceCapabilities{};
if (!mSurface.GetCapabilities(mAdapter, &surfaceCapabilities)) {
FWGPU_LOGW << "Failed to get WebGPU surface capabilities" << utils::io::endl;
} else {
printSurfaceCapabilitiesDetails(surfaceCapabilities);
}
#endif
mDevice = mPlatform.requestDevice(mAdapter);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printDeviceDetails(mDevice);
#endif
mQueue = mDevice.GetQueue();
mSwapChain = std::make_unique<WebGPUSwapChain>(std::move(surface), mAdapter, mDevice, flags);
// TODO configure the surface (maybe before or after creating the swapchain?
// how do we get the surface extent?)
// TODO actually create the swapchain
// auto onQueueWorkDone = [](wgpu::QueueWorkDoneStatus status, void* /* pUserData */) {
// FWGPU_LOGW << "Queued work finished with status: " << status << std::endl;
// };
// mQueue.OnSubmittedWorkDone(wgpu::CallbackMode::WaitAnyOnly, [](wgpu::QueueWorkDoneStatus status, void* pUserdata) {
// FWGPU_LOGW << "Queued work finished with status:\n";
// }, nullptr /* pUserData */);
void* userDataPtr = nullptr;
mQueue.OnSubmittedWorkDone(
wgpu::CallbackMode::AllowProcessEvents,
[](wgpu::QueueWorkDoneStatus status, void* pUserData) {
FWGPU_LOGW << "Queued work finished with status: " << static_cast<int>(status) << "\n";
if (pUserData == nullptr) {
// Expected case
} else {
FWGPU_LOGW << "Unexpected non-null pUserData received.\n";
}
},
userDataPtr /* pUserData */
);
FWGPU_LOGW << "WebGPU support is still essentially a no-op at this point in development (only "
"background components have been instantiated/selected, such as surface/screen, "
"graphics device/GPU, etc.), thus nothing is being drawn to the screen."
@@ -473,6 +479,9 @@ void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
"rebuilding Filament with that flag, e.g. ./build.sh -x "
<< FWGPU_PRINT_SYSTEM << " ..." << utils::io::endl;
#endif
// TODO: use webgpu handle allocator from.
// https://github.com/google/filament/pull/8566
// hwSwapChain->swapChain = mSwapChain.get();
}
void WebGPUDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
@@ -495,7 +504,13 @@ void WebGPUDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
TextureUsage usage) {}
void WebGPUDriver::createTextureViewR(Handle<HwTexture> th, Handle<HwTexture> srch,
uint8_t baseLevel, uint8_t levelCount) {}
uint8_t baseLevel, uint8_t levelCount) {
// FWGPU_LOGW << __FUNCTION__<< "\n";
WGPUTexture const* src = handleCast<WGPUTexture>(srch);
(void) src;
// textures.insert(
// constructHandle<WGPUTexture>(th, src, baseLevel, levelCount));
}
void WebGPUDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwTexture> srch,
backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b,
@@ -522,7 +537,11 @@ void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph, Handle<
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {}
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {}
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
assert_invariant(!mDefaultRenderTarget);
mDefaultRenderTarget = constructHandle<WGPURenderTarget>(rth);
assert_invariant(mDefaultRenderTarget);
}
void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> rth, TargetBufferFlags targets,
uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount, MRT color,
@@ -683,6 +702,11 @@ void WebGPUDriver::resetBufferObject(Handle<HwBufferObject> boh) {
void WebGPUDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
Handle<HwBufferObject> boh) {
auto* vertexBuffer = handleCast<WGPUVertexBuffer>(vbh);
auto* bufferObject = handleCast<WGPUBufferObject>(boh);
assert_invariant(index < vertexBuffer->mBuffers.size());
vertexBuffer->setBuffer(bufferObject, index);
}
void WebGPUDriver::update3DImage(Handle<HwTexture> th,
@@ -715,9 +739,33 @@ void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority,
}
void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassParams& params) {
// FWGPU_LOGW << __FUNCTION__<< "\n";
mTextureView = mSwapChain->GetNextSurfaceTextureView(params.viewport.width, params.viewport.height);
wgpu::RenderPassColorAttachment renderPassColorAttachment = {};
renderPassColorAttachment.view = mTextureView;
renderPassColorAttachment.resolveTarget = nullptr;
renderPassColorAttachment.loadOp = wgpu::LoadOp::Clear;
renderPassColorAttachment.storeOp = wgpu::StoreOp::Store;
renderPassColorAttachment.clearValue = wgpu::Color{1, 0 , 0 , 1};
renderPassColorAttachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
wgpu::RenderPassDescriptor renderPassDescriptor = {};
renderPassDescriptor.nextInChain = nullptr;
renderPassDescriptor.colorAttachmentCount = 1;
renderPassDescriptor.colorAttachments = &renderPassColorAttachment;
renderPassDescriptor.depthStencilAttachment = nullptr;
renderPassDescriptor.timestampWrites = nullptr;
mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor);
mRenderPassEncoder.SetViewport((float)params.viewport.left, (float)params.viewport.bottom,
(float) params.viewport.width, (float) params.viewport.height, params.depthRange.near, params.depthRange.far);
// (float) 1024/*params.viewport.width*/, (float) 640/*params.viewport.height*/, params.depthRange.near, params.depthRange.far);
}
void WebGPUDriver::endRenderPass(int) {
// FWGPU_LOGW << __FUNCTION__<< "\n";
mRenderPassEncoder.End();
mRenderPassEncoder = nullptr;
}
void WebGPUDriver::nextSubpass(int) {
@@ -727,6 +775,12 @@ void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
}
void WebGPUDriver::commit(Handle<HwSwapChain> sch) {
wgpu::CommandBufferDescriptor commandBufferDescriptor = {};
commandBufferDescriptor.nextInChain = nullptr;
mCommandBuffer = mCommandEncoder.Finish(&commandBufferDescriptor);
// mQueue.Submit(1, &mCommandBuffer);
// mCommandBuffer = nullptr;
}
void WebGPUDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
@@ -783,6 +837,8 @@ void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
}
void WebGPUDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
// mRenderPassEncoder.DrawIndexed(indexCount, instanceCount, indexOffset, 0, 0);
}
void WebGPUDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph,
+40 -6
View File
@@ -17,18 +17,27 @@
#ifndef TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
#define TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
#include "webgpu/WebGPUSwapChain.h"
#include <backend/platforms/WebGPUPlatform.h>
#include "DriverBase.h"
#include "private/backend/Dispatcher.h"
#include "private/backend/Driver.h"
#include "private/backend/HandleAllocator.h"
#include <backend/DriverEnums.h>
#include <utils/compiler.h>
#include <webgpu/webgpu_cpp.h>
#include "WebGPUHandles.h"
#include <cstdint>
#include <memory>
#ifndef FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB
# define FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB 8
#endif
namespace filament::backend {
@@ -40,26 +49,30 @@ public:
~WebGPUDriver() noexcept override;
[[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:
explicit WebGPUDriver(WebGPUPlatform& platform) noexcept;
explicit WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
[[nodiscard]] ShaderModel getShaderModel() const noexcept final;
[[nodiscard]] ShaderLanguage getShaderLanguage() const noexcept final;
// the platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
// handled in the WebGPUPlatform
WebGPUPlatform& mPlatform;
wgpu::Surface mSurface = nullptr;
wgpu::Adapter mAdapter = nullptr;
wgpu::Device mDevice = nullptr;
wgpu::Queue mQueue = nullptr;
// TODO consider moving to handle allocator when ready
std::unique_ptr<WebGPUSwapChain> mSwapChain = nullptr;
uint64_t mNextFakeHandle = 1;
wgpu::CommandEncoder mCommandEncoder = nullptr;
wgpu::TextureView mTextureView = nullptr;
wgpu::RenderPassEncoder mRenderPassEncoder = nullptr;
wgpu::CommandBuffer mCommandBuffer = nullptr;
WGPURenderTarget* mDefaultRenderTarget = nullptr;
/*
* Driver interface
*/
template<typename T>
friend class ConcreteDispatcher;
@@ -74,8 +87,29 @@ private:
UTILS_ALWAYS_INLINE inline void methodName##R(RetType, paramsDecl);
#include "private/backend/DriverAPI.inc"
/*
* Memory management
*/
HandleAllocatorWGPU mHandleAllocator;
template<typename D>
Handle<D> allocHandle() {
return mHandleAllocator.allocate<D>();
}
template<typename D, typename B, typename ... ARGS>
D* constructHandle(Handle<B>& handle, ARGS&& ... args) noexcept {
return mHandleAllocator.construct<D>(handle, std::forward<ARGS>(args)...);
}
template<typename D, typename B>
D* handleCast(Handle<B> handle) noexcept {
return mHandleAllocator.handle_cast<D*>(handle);
}
};
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
#endif // TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
@@ -0,0 +1,19 @@
//
// Created by Idris Idris Shah on 3/21/25.
//
#include "WebGPUHandles.h"
namespace filament::backend {
WGPUVertexBuffer::WGPUVertexBuffer(uint32_t vextexCount, uint32_t bufferCount,
Handle<WGPUVertexBufferInfo> vbih)
: HwVertexBuffer(vextexCount),
vbih(vbih),
mBuffers(MAX_VERTEX_BUFFER_COUNT) {}
void WGPUVertexBuffer::setBuffer(WGPUBufferObject* bufferObject, uint32_t index) {}
WGPUBufferObject::WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount)
: HwBufferObject(byteCount),
mBindingType(bindingType) {}
}// namespace filament::backend
+109
View File
@@ -0,0 +1,109 @@
//
// Created by Idris Idris Shah on 3/21/25.
//
#ifndef TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
#define TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <utils/FixedCapacityVector.h>
#include <webgpu/webgpu_cpp.h>
namespace filament::backend {
struct WGPUBufferObject;
struct WGPUVertexBufferInfo : public HwVertexBufferInfo {
WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
AttributeArray const& attributes)
: HwVertexBufferInfo(bufferCount, attributeCount),
attributes(attributes) {}
AttributeArray attributes;
};
struct WGPUVertexBuffer : public HwVertexBuffer {
WGPUVertexBuffer(uint32_t vextexCount, uint32_t bufferCount, Handle<WGPUVertexBufferInfo> vbih);
void setBuffer(WGPUBufferObject* bufferObject, uint32_t index);
Handle<WGPUVertexBufferInfo> vbih;
utils::FixedCapacityVector<wgpu::Buffer> mBuffers;
};
struct WGPUIndexBuffer : public HwIndexBuffer {
WGPUIndexBuffer(BufferUsage usage, uint8_t elementSize, uint32_t indexCount);
wgpu::Buffer buffer;
};
struct WGPUBufferObject : HwBufferObject {
WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount);
wgpu::Buffer mBuffer;
const BufferObjectBinding mBindingType;
};
class WGPUTexture : public HwTexture {
public:
WGPUTexture(SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage) noexcept;
// constructors for creating texture views
WGPUTexture(WGPUTexture const* src, uint8_t baseLevel, uint8_t levelCount) noexcept;
wgpu::Texture texture = nullptr;
};
class WGPURenderTarget : public HwRenderTarget {
public:
class Attachment {
public:
friend class WGPURenderTarget;
Attachment() = default;
Attachment(WGPUTexture* gpuTexture, uint8_t level = 0, uint16_t layer = 0)
: level(level),
layer(layer),
texture(gpuTexture->texture),
mWGPUTexture(gpuTexture) {}
uint8_t level = 0;
uint16_t layer = 0;
private:
wgpu::Texture texture = nullptr;
WGPUTexture* mWGPUTexture = nullptr;
};
WGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples,
Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]);
WGPURenderTarget()
: HwRenderTarget(0, 0),
defaultRenderTarget(true) {}
void setUpRenderPassAttachments(wgpu::RenderPassDescriptor* descriptor,
const RenderPassParams& params);
math::uint2 getAttachmentSize() noexcept;
bool isDefaultRenderTarget() const { return defaultRenderTarget; }
uint8_t getSamples() const { return samples; }
Attachment getDrawColorAttachment(size_t index);
Attachment getReadColorAttachment(size_t index);
private:
static wgpu::LoadOp getLoadAction(const RenderPassParams& params, TargetBufferFlags buffer);
static wgpu::LoadOp getStoreAction(const RenderPassParams& params, TargetBufferFlags buffer);
bool defaultRenderTarget = false;
uint8_t samples = 1;
Attachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
math::uint2 attachmentSize = {};
};
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
@@ -0,0 +1,277 @@
/*
* 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 "webgpu/WebGPUSwapChain.h"
#include "webgpu/WebGPUConstants.h"
#include "backend/DriverEnums.h"
#include <utils/Panic.h>
#include <utils/ostream.h>
#include <dawn/webgpu_cpp_print.h>
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
#include <cstdint>
#include <sstream>
namespace {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabilities) {
std::stringstream usagesStream{};
usagesStream << capabilities.usages;
FWGPU_LOGI << "WebGPU surface capabilities:" << utils::io::endl;
FWGPU_LOGI << " surface usages: " << usagesStream.str().data() << utils::io::endl;
FWGPU_LOGI << " surface formats (" << capabilities.formatCount << "):" << utils::io::endl;
if (capabilities.formatCount > 0 && capabilities.formats != nullptr) {
std::for_each(capabilities.formats, capabilities.formats + capabilities.formatCount,
[](wgpu::TextureFormat const format) {
std::stringstream formatStream{};
formatStream << format;
FWGPU_LOGI << " " << formatStream.str().data() << utils::io::endl;
});
}
FWGPU_LOGI << " surface present modes (" << capabilities.presentModeCount
<< "):" << utils::io::endl;
if (capabilities.presentModeCount > 0 && capabilities.presentModes != nullptr) {
std::for_each(capabilities.presentModes,
capabilities.presentModes + capabilities.presentModeCount,
[](wgpu::PresentMode const presentMode) {
std::stringstream presentModeStream{};
presentModeStream << presentMode;
FWGPU_LOGI << " " << presentModeStream.str().data() << utils::io::endl;
});
}
FWGPU_LOGI << " surface alpha modes (" << capabilities.alphaModeCount
<< "):" << utils::io::endl;
if (capabilities.alphaModeCount > 0 && capabilities.alphaModes != nullptr) {
std::for_each(capabilities.alphaModes,
capabilities.alphaModes + capabilities.alphaModeCount,
[](wgpu::CompositeAlphaMode const alphaMode) {
std::stringstream alphaModeStream{};
alphaModeStream << alphaMode;
FWGPU_LOGI << " " << alphaModeStream.str().data() << utils::io::endl;
});
}
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config) {
std::stringstream formatStream{};
formatStream << config.format;
std::stringstream usageStream{};
usageStream << config.usage;
std::stringstream alphaModeStream{};
alphaModeStream << config.alphaMode;
std::stringstream presentModeStream{};
presentModeStream << config.presentMode;
FWGPU_LOGI << "WebGPU surface configuration:" << utils::io::endl;
FWGPU_LOGI << " surface format: " << formatStream.str() << utils::io::endl;
FWGPU_LOGI << " surface usage: " << usageStream.str() << utils::io::endl;
FWGPU_LOGI << " surface view formats (" << config.viewFormatCount << "):" << utils::io::endl;
if (config.viewFormatCount > 0 && config.viewFormats != nullptr) {
std::for_each(config.viewFormats, config.viewFormats + config.viewFormatCount,
[](wgpu::TextureFormat const viewFormat) {
std::stringstream viewFormatStream{};
viewFormatStream << viewFormat;
FWGPU_LOGI << " " << viewFormatStream.str().data() << utils::io::endl;
});
}
FWGPU_LOGI << " surface alpha mode: " << alphaModeStream.str() << utils::io::endl;
FWGPU_LOGI << " surface width: " << config.width << utils::io::endl;
FWGPU_LOGI << " surface height: " << config.height << utils::io::endl;
FWGPU_LOGI << " surface present mode: " << presentModeStream.str() << utils::io::endl;
}
#endif
wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount,
wgpu::TextureFormat const* availableFormats, bool useSRGBColorSpace) {
const std::array expectedColorFormats =
useSRGBColorSpace ?
std::array{
wgpu::TextureFormat::RGBA8UnormSrgb,
wgpu::TextureFormat::BGRA8UnormSrgb }
: std::array{
wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureFormat::BGRA8Unorm };
auto const firstFoundColorFormat = std::find_first_of(expectedColorFormats.begin(),
expectedColorFormats.end(), availableFormats, availableFormats + availableFormatsCount);
FILAMENT_CHECK_POSTCONDITION(firstFoundColorFormat != expectedColorFormats.end())
<< "Cannot find a suitable WebGPU swapchain "
<< (useSRGBColorSpace ? "sRGB" : "non-standard (e.g. linear) RGB") << " color format";
return *firstFoundColorFormat;
}
wgpu::PresentMode selectPresentMode(size_t availablePresentModesCount,
wgpu::PresentMode const* availablePresentModes) {
// Verify that our chosen present mode is supported. In practice all devices support the FIFO
// mode, but we check for it anyway for completeness. (and to avoid validation warnings)
const wgpu::PresentMode desiredPresentMode = wgpu::PresentMode::Fifo;
FILAMENT_CHECK_POSTCONDITION(
std::any_of(availablePresentModes, availablePresentModes + availablePresentModesCount,
[](const wgpu::PresentMode availablePresentMode) {
return availablePresentMode == desiredPresentMode;
}))
<< "Cannot find a suitable WebGPU swapchain present mode";
return desiredPresentMode;
}
wgpu::CompositeAlphaMode selectAlphaMode(size_t availableAlphaModesCount,
wgpu::CompositeAlphaMode const* availableAlphaModes) {
bool autoAvailable = false;
bool inheritAvailable = false;
bool opaqueAvailable = false;
bool premultipliedAvailable = false;
bool unpremultipliedAvailable = false;
std::for_each(availableAlphaModes, availableAlphaModes + availableAlphaModesCount,
[&](const wgpu::CompositeAlphaMode alphMode) {
switch (alphMode) {
// in practice, the surface capabilities would not list Auto,
// but for completeness and defensive programming we can leverage it
// if it explicitly comes back as available/supported
case wgpu::CompositeAlphaMode::Auto:
autoAvailable = true;
break;
case wgpu::CompositeAlphaMode::Opaque:
opaqueAvailable = true;
break;
case wgpu::CompositeAlphaMode::Premultiplied:
premultipliedAvailable = true;
break;
case wgpu::CompositeAlphaMode::Unpremultiplied:
unpremultipliedAvailable = true;
break;
case wgpu::CompositeAlphaMode::Inherit:
inheritAvailable = true;
break;
}
});
if (autoAvailable || inheritAvailable || opaqueAvailable) {
return wgpu::CompositeAlphaMode::Auto;
} else if (premultipliedAvailable) {
// In practice, we do not expect this to possibly happen, as opaque should be supported,
// which we select first. However, if the underlying system actually does not support
// opaque we allow it to be tested, but warn that this may not likely work as they expect
// (untested territory).
// We prefer premultiplied to unpremuliplied until that assumption should be adjusted.
FWGPU_LOGW << "Auto, Inherit, & Opaque composite alpha modes not supported. Filament has "
"historically used these. Premultiplied alpha composite mode for "
"transparency is being selected as a fallback, but may not work as expected."
<< utils::io::endl;
return wgpu::CompositeAlphaMode::Premultiplied;
} else {
FILAMENT_CHECK_POSTCONDITION(unpremultipliedAvailable)
<< "No available composite alpha modes? Unknown/unhandled composite alpha mode?";
// Again, we don't expect this in practice, but allow if for the same reason as premultiplied.
// We prefer premultiplied to unpremuliplied until that assumption should be adjusted.
FWGPU_LOGW << "Auto, Inherit, & Opaque composite alpha modes not supported. Filament has "
"historically used these. Unpremultiplied alpha composite mode for "
"transparency is being selected as a fallback "
"(premulitipled is not available either), but may not work as expected."
<< utils::io::endl;
return wgpu::CompositeAlphaMode::Unpremultiplied;
}
}
void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device& device,
wgpu::SurfaceCapabilities const& capabilities, bool useSRGBColorSpace) {
config.device = device;
config.usage = wgpu::TextureUsage::RenderAttachment;
config.format =
selectColorFormat(capabilities.formatCount, capabilities.formats, useSRGBColorSpace);
config.presentMode =
selectPresentMode(capabilities.presentModeCount, capabilities.presentModes);
config.alphaMode = selectAlphaMode(capabilities.alphaModeCount, capabilities.alphaModes);
}
}// namespace
namespace filament::backend {
WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Adapter& adapter,
wgpu::Device& device, uint64_t flags)
: mSurface(surface) {
wgpu::SurfaceCapabilities capabilities = {};
if (!mSurface.GetCapabilities(adapter, &capabilities)) {
FWGPU_LOGW << "Failed to get WebGPU surface capabilities" << utils::io::endl;
} else {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printSurfaceCapabilitiesDetails(capabilities);
#endif
}
const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0;
initConfig(mConfig, device, capabilities, useSRGBColorSpace);
}
WebGPUSwapChain::~WebGPUSwapChain() {
if (mConfigured) {
mSurface.Unconfigure();
mConfigured = false;
}
}
void WebGPUSwapChain::GetCurrentTexture(uint32_t width, uint32_t height, wgpu::SurfaceTexture* texture) {
if (width < 1 || height < 1) {
PANIC_LOG("WebGPUSwapChain::GetCurrentTexture: Invalid width and/or height requested.");
return;
}
if (mConfig.width != width || mConfig.height != height || !mConfigured) {
mConfig.width = width;
mConfig.height = height;
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printSurfaceConfiguration(mConfig);
#endif
mSurface.Configure(&mConfig);
mConfigured = true;
return;
}
mSurface.GetCurrentTexture(texture);
}
wgpu::TextureView WebGPUSwapChain::GetNextSurfaceTextureView(uint32_t width, uint32_t height) {
wgpu::SurfaceTexture surfaceTexture;
GetCurrentTexture(width, height, &surfaceTexture);
if (surfaceTexture.status != wgpu::SurfaceGetCurrentTextureStatus::Success) {
return nullptr;
}
// Create a view for this surface texture
wgpu::TextureViewDescriptor textureViewDescriptor;
textureViewDescriptor.nextInChain = nullptr;
textureViewDescriptor.label = "Surface texture view";
textureViewDescriptor.format = surfaceTexture.texture.GetFormat();
textureViewDescriptor.dimension = wgpu::TextureViewDimension::e2D;
textureViewDescriptor.baseMipLevel = 0;
textureViewDescriptor.mipLevelCount = 1;
textureViewDescriptor.baseArrayLayer = 0;
textureViewDescriptor.arrayLayerCount = 1;
textureViewDescriptor.aspect = wgpu::TextureAspect::All;
wgpu::TextureView textureView = surfaceTexture.texture.CreateView(&textureViewDescriptor);
return textureView;
}
void WebGPUSwapChain::Present() {
assert_invariant(mSurface);
mSurface.Present();
}
}// namespace filament::backend
@@ -0,0 +1,46 @@
/*
* 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_WEBGPUSWAPCHAIN_H
#define TNT_FILAMENT_BACKEND_WEBGPUSWAPCHAIN_H
#include <webgpu/webgpu_cpp.h>
#include <backend/Platform.h>
#include <cstdint>
namespace filament::backend {
class WebGPUSwapChain : public Platform::SwapChain {
public:
WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Adapter& adapter, wgpu::Device& device,
uint64_t flags);
~WebGPUSwapChain();
void GetCurrentTexture(uint32_t width, uint32_t height, wgpu::SurfaceTexture*);
wgpu::TextureView GetNextSurfaceTextureView(uint32_t width, uint32_t height);
void Present();
private:
wgpu::Surface mSurface = {};
wgpu::SurfaceConfiguration mConfig = {};
bool mConfigured = false;
};
} // namespace filament::backend
#endif //TNT_FILAMENT_BACKEND_WEBGPUSWAPCHAIN_H
@@ -144,13 +144,13 @@ wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
}
Driver* WebGPUPlatform::createDriver(void* sharedContext,
const Platform::DriverConfig& /*driverConfig*/) noexcept {
const Platform::DriverConfig& driverConfig) noexcept {
if (sharedContext) {
FWGPU_LOGW << "sharedContext is ignored/unused in the WebGPU backend. A non-null "
"sharedContext was provided, but it will be ignored."
<< utils::io::endl;
}
return WebGPUDriver::create(*this);
return WebGPUDriver::create(*this, driverConfig);
}
WebGPUPlatform::WebGPUPlatform()
@@ -22,6 +22,8 @@
#include <cstdint>
#include <Windows.h>
/**
* Windows OS specific implementation aspects of the WebGPU backend
*/
@@ -41,12 +43,12 @@ wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags
wgpu::SurfaceSourceWindowsHWND surfaceSourceWin{};
surfaceSourceWin.hinstance = GetModuleHandle(nullptr);
surfaceSourceWin.hwnd = nativeWindow;
wgpu::SurfaceDescriptor surfaceDescriptor{
const wgpu::SurfaceDescriptor surfaceDescriptor{
.nextInChain = &surfaceSourceWin,
.label = "windows_surface"
};
wgpu::Surface surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface != nullptr) << "Unable to create Windows-backed surface.";
FILAMENT_CHECK_POSTCONDITION(surface.Get() != nullptr) << "Unable to create Windows-backed surface.";
return surface;
}
+172
View File
@@ -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) {}
+136
View File
@@ -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
+64
View File
@@ -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
+143
View File
@@ -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
+208 -260
View File
@@ -16,8 +16,9 @@
#include "BackendTest.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "Shader.h"
#include "TrianglePrimitive.h"
#include <utils/Hash.h>
@@ -26,8 +27,10 @@
#include <fstream>
#ifndef FILAMENT_IOS
#include <imageio/ImageEncoder.h>
#include <image/ColorTransform.h>
#endif
namespace test {
@@ -37,6 +40,21 @@ using namespace filament::backend;
using namespace filament::math;
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
layout(location = 0) in vec4 mesh_position;
layout(binding = 0, set = 1) uniform Params { highp vec4 color; highp vec4 scale; } params;
@@ -56,49 +74,12 @@ void main() {
fragColor = params.color;
})";
struct MaterialParams {
float4 color;
float4 scale;
};
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);
Shader BlitTest::createShader() {
return Shader(getDriverApi(), mCleanup, ShaderConfig{
.vertexShader = triangleVs,
.fragmentShader = triangleFs,
.uniformNames = { "Params" },
});
}
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 height = baseHeight >> level;
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);
const float3 foreground = color;
@@ -140,7 +121,7 @@ static void createBitmap(DriverApi& dapi, Handle<HwTexture> texture, int baseWid
// | ........
// low addresses
// 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 col = 0; col < width; col++) {
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 height = baseHeight >> level;
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);
const float3 foreground = color;
@@ -176,7 +157,7 @@ static void createFaces(DriverApi& dapi, Handle<HwTexture> texture, int baseWidt
const float radius = 0.25f;
// 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 row = 0; row < height; row++) {
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));
}
TEST_F(BackendTest, ColorMagnify) {
TEST_F(BlitTest, ColorMagnify) {
auto& api = getDriverApi();
Cleanup cleanup(getDriverApi());
cleanup.addPostCall([&]() { executeCommands(); });
mCleanup.addPostCall([&]() { executeCommands(); });
constexpr int kSrcTexWidth = 256;
constexpr int kSrcTexHeight = 256;
@@ -208,37 +188,40 @@ TEST_F(BackendTest, ColorMagnify) {
constexpr int kNumLevels = 3;
// 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);
// Create a source texture.
Handle<HwTexture> const srcTexture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
const bool flipY = sBackend == Backend::OPENGL;
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
// Create a destination texture.
Handle<HwTexture> const dstTexture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
// Create a RenderTarget for each texture's miplevel.
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
Handle<HwRenderTarget> dstRenderTargets[kNumLevels];
for (uint8_t level = 0; level < kNumLevels; level++) {
srcRenderTargets[level] = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
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 }, {}, {}));
srcRenderTargets[level] = mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, 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.
const int srcLevel = 1;
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTargets[0],
{0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTargets[srcLevel],
{0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel}, SamplerMagFilter::LINEAR);
{ 0, 0, kDstTexWidth, kDstTexHeight }, srcRenderTargets[srcLevel],
{ 0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel },
SamplerMagFilter::LINEAR);
// 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);
}
// Grab a screenshot.
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "ColorMagnify.png" };
{
RenderFrame frame(api);
dumpScreenshot(api, dstRenderTargets[0], &params);
api.commit(swapChain);
ImageExpectations expectations(api);
{
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();
Cleanup cleanup(getDriverApi());
cleanup.addPostCall([&]() { executeCommands(); });
mCleanup.addPostCall([&]() { executeCommands(); });
constexpr int kSrcTexWidth = 1024;
constexpr int kSrcTexHeight = 1024;
@@ -279,57 +256,55 @@ TEST_F(BackendTest, ColorMinify) {
constexpr int kNumLevels = 3;
// 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);
// Create a source texture.
Handle<HwTexture> const srcTexture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
const bool flipY = sBackend == Backend::OPENGL;
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
// Create a destination texture.
Handle<HwTexture> const dstTexture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
// Create a RenderTarget for each texture's miplevel.
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
Handle<HwRenderTarget> dstRenderTargets[kNumLevels];
for (uint8_t level = 0; level < kNumLevels; level++) {
srcRenderTargets[level] = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
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 }, {}, {}));
srcRenderTargets[level] = mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, 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.
const int srcLevel = 1;
api.blitDEPRECATED(TargetBufferFlags::COLOR0,
dstRenderTargets[0], {0, 0, kDstTexWidth, kDstTexHeight},
srcRenderTargets[srcLevel], {0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel},
dstRenderTargets[0], { 0, 0, kDstTexWidth, kDstTexHeight },
srcRenderTargets[srcLevel],
{ 0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel },
SamplerMagFilter::LINEAR);
{
ImageExpectations expectations(api);
// Grab a screenshot.
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "ColorMinify.png" };
dumpScreenshot(api, dstRenderTargets[0], &params);
EXPECT_IMAGE(dstRenderTargets[0], expectations,
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f));
// Wait for the ReadPixels result to come back.
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);
flushAndWait();
}
}
TEST_F(BackendTest, ColorResolve) {
TEST_F(BlitTest, ColorResolve) {
auto& api = getDriverApi();
Cleanup cleanup(getDriverApi());
constexpr int kSrcTexWidth = 256;
constexpr int kSrcTexHeight = 256;
@@ -338,49 +313,29 @@ TEST_F(BackendTest, ColorResolve) {
constexpr auto kColorTexFormat = TextureFormat::RGBA8;
constexpr int kSampleCount = 4;
// Create a program.
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));
Shader shader = createShader();
// Create a VertexBuffer, IndexBuffer, and RenderPrimitive.
TrianglePrimitive const triangle(api);
// Create 4-sample texture.
Handle<HwTexture> const srcColorTexture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, 1, kColorTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> const srcColorTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, 1, kColorTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight,
1,
TextureUsage::COLOR_ATTACHMENT));
// Create 1-sample texture.
Handle<HwTexture> const dstColorTexture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, 1, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> const dstColorTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, 1, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
// 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,
{{ srcColorTexture }}, {}, {}));
// 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,
{{ dstColorTexture }}, {}, {}));
@@ -394,58 +349,52 @@ TEST_F(BackendTest, ColorResolve) {
params.viewport.height = kSrcTexHeight;
PipelineState state = {};
state.program = program;
state.pipelineLayout.setLayout[1] = { descriptorSetLayout };
state.program = shader.getProgram();
state.pipelineLayout.setLayout[1] = { shader.getDescriptorSetLayout() };
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
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));
// Draw red triangle into srcRenderTarget.
uploadUniforms(api, ubuffer, {
.color = float4(1, 0, 0, 1),
.scale = float4(1, 1, 0.5, 0),
shader.uploadUniform(api, ubuffer, MaterialParams{
.color = float4(1, 0, 0, 1),
.scale = float4(1, 1, 0.5, 0),
});
api.updateDescriptorSetBuffer(descriptorSet, 0, ubuffer, 0, sizeof(MaterialParams));
api.bindDescriptorSet(descriptorSet, 1, {});
shader.bindUniform<MaterialParams>(api, ubuffer);
// FIXME: on Metal this triangle is not drawn. Can't understand why.
{
RenderFrame frame(api);
api.beginRenderPass(srcRenderTarget, params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
}
// Resolve the MSAA render target into the single-sample render target.
api.blitDEPRECATED(TargetBufferFlags::COLOR,
dstRenderTarget, {0, 0, kDstTexWidth, kDstTexHeight},
srcRenderTarget, {0, 0, kSrcTexWidth, kSrcTexHeight},
dstRenderTarget, { 0, 0, kDstTexWidth, kDstTexHeight },
srcRenderTarget, { 0, 0, kSrcTexWidth, kSrcTexHeight },
SamplerMagFilter::NEAREST);
// Grab a screenshot.
ScreenshotParams sparams{ kDstTexWidth, kDstTexHeight, "ColorResolve.png" };
dumpScreenshot(api, dstRenderTarget, &sparams);
{
ImageExpectations expectations(api);
// Wait for the ReadPixels result to come back.
flushAndWait();
EXPECT_IMAGE(dstRenderTarget, expectations,
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 0xebfac2ef));
// Check if the image matches perfectly to our golden run.
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);
flushAndWait();
}
}
TEST_F(BackendTest, Blit2DTextureArray) {
TEST_F(BlitTest, Blit2DTextureArray) {
auto& api = getDriverApi();
Cleanup cleanup(getDriverApi());
cleanup.addPostCall([&]() { executeCommands(); });
mCleanup.addPostCall([&]() { executeCommands(); });
api.startCapture(0);
cleanup.addPostCall([&]() { api.stopCapture(0); });
mCleanup.addPostCall([&]() { api.stopCapture(0); });
constexpr int kSrcTexWidth = 256;
constexpr int kSrcTexHeight = 256;
@@ -460,33 +409,40 @@ TEST_F(BackendTest, Blit2DTextureArray) {
constexpr int kDstTexLayer = 0;
// 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);
// Create a source texture.
Handle<HwTexture> srcTexture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D_ARRAY, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, kSrcTexDepth,
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D_ARRAY, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth,
kSrcTexHeight, kSrcTexDepth,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
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.
Handle<HwTexture> dstTexture = cleanup.add(api.createTexture(
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
// Create two RenderTargets.
const int level = 0;
Handle<HwRenderTarget> srcRenderTarget = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, kSrcTexLayer }, {}, {}));
Handle<HwRenderTarget> dstRenderTarget = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
kDstTexWidth >> level, kDstTexHeight >> level, 1, 0, { dstTexture, level, kDstTexLayer }, {}, {}));
Handle<HwRenderTarget> srcRenderTarget = mCleanup.add(
api.createRenderTarget(TargetBufferFlags::COLOR,
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0,
{ 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.
const int srcLevel = 0;
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
{0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTarget,
{0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel}, SamplerMagFilter::LINEAR);
{ 0, 0, kDstTexWidth, kDstTexHeight }, srcRenderTarget,
{ 0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel },
SamplerMagFilter::LINEAR);
// 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);
}
// Grab a screenshot.
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "Blit2DTextureArray.png" };
{
RenderFrame frame(api);
dumpScreenshot(api, dstRenderTarget, &params);
api.commit(swapChain);
ImageExpectations expectations(api);
{
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();
Cleanup cleanup(getDriverApi());
cleanup.addPostCall([&]() { executeCommands(); });
mCleanup.addPostCall([&]() { executeCommands(); });
constexpr int kSrcTexWidth = 1024;
constexpr int kSrcTexHeight = 1024;
@@ -529,47 +480,48 @@ TEST_F(BackendTest, BlitRegion) {
constexpr int kDstLevel = 0;
// 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);
// Create a source texture.
Handle<HwTexture> srcTexture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
const bool flipY = sBackend == Backend::OPENGL;
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
// Create a destination texture.
Handle<HwTexture> dstTexture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
// Blit one-quarter of src level 1 to dst level 0.
Viewport srcRect = {
.left = 0,
.bottom = 0,
.width = (kSrcTexWidth >> kSrcLevel) / 2,
.height = (kSrcTexHeight >> kSrcLevel) / 2,
.left = 0,
.bottom = 0,
.width = (kSrcTexWidth >> kSrcLevel) / 2,
.height = (kSrcTexHeight >> kSrcLevel) / 2,
};
Viewport dstRect = {
.left = 10,
.bottom = 10,
.width = kDstTexWidth - 10,
.height = kDstTexHeight - 10,
.left = 10,
.bottom = 10,
.width = kDstTexWidth - 10,
.height = kDstTexHeight - 10,
};
// Create a RenderTarget for each texture's miplevel.
// We purposely set the render target width and height to smaller than the texture, to check
// that this case is handled correctly.
Handle<HwRenderTarget> srcRenderTarget =
cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR, srcRect.width,
srcRect.height, 1, 0, {srcTexture, kSrcLevel, 0}, {}, {}));
mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR, srcRect.width,
srcRect.height, 1, 0, { srcTexture, kSrcLevel, 0 }, {}, {}));
Handle<HwRenderTarget> dstRenderTarget =
cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR, kDstTexWidth >> kDstLevel,
kDstTexHeight >> kDstLevel, 1, 0, {dstTexture, kDstLevel, 0}, {}, {}));
mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR, kDstTexWidth >> kDstLevel,
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);
// 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);
}
// Grab a screenshot.
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "BlitRegion.png" };
{
RenderFrame frame(api);
dumpScreenshot(api, dstRenderTarget, &params);
api.commit(swapChain);
ImageExpectations expectations(api);
{
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();
Cleanup cleanup(getDriverApi());
cleanup.addPostCall([&]() { executeCommands(); });
mCleanup.addPostCall([&]() { executeCommands(); });
constexpr int kSrcTexWidth = 1024;
constexpr int kSrcTexHeight = 1024;
@@ -614,15 +558,15 @@ TEST_F(BackendTest, BlitRegionToSwapChain) {
constexpr int kNumLevels = 3;
// Create a SwapChain and make it current.
auto swapChain = cleanup.add(createSwapChain());
auto swapChain = mCleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
Handle<HwRenderTarget> dstRenderTarget = cleanup.add(api.createDefaultRenderTarget());
Handle<HwRenderTarget> dstRenderTarget = mCleanup.add(api.createDefaultRenderTarget());
// Create a source texture.
Handle<HwTexture> srcTexture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
const bool flipY = sBackend == Backend::OPENGL;
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), 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.
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
for (uint8_t level = 0; level < kNumLevels; level++) {
srcRenderTargets[level] = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {}, {}));
srcRenderTargets[level] = mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {},
{}));
}
// Blit one-quarter of src level 1 to dst level 0.
const int srcLevel = 1;
Viewport srcRect = {
.left = (kSrcTexWidth >> srcLevel) / 2,
.bottom = (kSrcTexHeight >> srcLevel) / 2,
.width = (kSrcTexWidth >> srcLevel) / 2,
.height = (kSrcTexHeight >> srcLevel) / 2,
.left = (kSrcTexWidth >> srcLevel) / 2,
.bottom = (kSrcTexHeight >> srcLevel) / 2,
.width = (kSrcTexWidth >> srcLevel) / 2,
.height = (kSrcTexHeight >> srcLevel) / 2,
};
Viewport dstRect = {
.left = 10,
.bottom = 10,
.width = kDstTexWidth - 10,
.height = kDstTexHeight - 10,
.left = 10,
.bottom = 10,
.width = kDstTexWidth - 10,
.height = kDstTexHeight - 10,
};
{
RenderFrame frame(api);
ImageExpectations expectations(api);
{
{
RenderFrame frame(api);
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
dstRect, srcRenderTargets[srcLevel],
srcRect, SamplerMagFilter::LINEAR);
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
dstRect, srcRenderTargets[srcLevel],
srcRect, SamplerMagFilter::LINEAR);
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "BlitRegionToSwapChain.png" };
dumpScreenshot(api, dstRenderTarget, &params);
api.commit(swapChain);
}
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
+70 -114
View File
@@ -16,6 +16,8 @@
#include "BackendTest.h"
#include "Lifetimes.h"
#include "Shader.h"
#include "ShaderGenerator.h"
#include "TrianglePrimitive.h"
@@ -78,8 +80,28 @@ struct MaterialParams {
math::float4 offset;
};
static_assert(sizeof(MaterialParams) == 8 * sizeof(float));
// Uniform config for writing MaterialParams to the shader uniform with 64 bytes of padding.
const UniformBindingConfig kBindingConfig = {
.dataSize = sizeof(MaterialParams),
.bufferSize = sizeof(MaterialParams) + 64,
.byteOffset = 64
};
TEST_F(BackendTest, VertexBufferUpdate) {
class BufferUpdatesTest : public BackendTest {
public:
BufferUpdatesTest() : mCleanup(getDriverApi()) {}
protected:
Shader createShader() {
return Shader(getDriverApi(), mCleanup, ShaderConfig{
vertex, fragment, {"Params"}
});
}
Cleanup mCleanup;
};
TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
const bool largeBuffers = false;
// If updateIndices is true, then even-numbered triangles will have their indices set to
@@ -90,31 +112,15 @@ TEST_F(BackendTest, VertexBufferUpdate) {
// executeCommands().
{
auto& api = getDriverApi();
Cleanup cleanup(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = createSwapChain();
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a program.
filamat::DescriptorSets descriptors;
descriptors[1] = { { "Params",
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, 0 }, {} } };
ShaderGenerator shaderGen(
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
Program p = shaderGen.getProgram(api);
p.descriptorBindings(1, {{ "Params", DescriptorType::UNIFORM_BUFFER, 0 }});
auto program = api.createProgram(std::move(p));
Shader shader = createShader();
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
{{
DescriptorType::UNIFORM_BUFFER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}});
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
auto defaultRenderTarget = api.createDefaultRenderTarget(0);
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
// To test large buffers (which exercise a different code path) create an extra large
// buffer. Only the first 3 vertices will be used.
@@ -128,38 +134,29 @@ TEST_F(BackendTest, VertexBufferUpdate) {
params.flags.discardEnd = TargetBufferFlags::NONE;
PipelineState state;
state.program = program;
state.pipelineLayout.setLayout[1] = { descriptorSetLayout };
state.program = shader.getProgram();
state.pipelineLayout.setLayout[1] = { shader.getDescriptorSetLayout() };
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
state.rasterState.culling = CullingMode::NONE;
// Create a uniform buffer.
// We use STATIC here, even though the buffer is updated, to force the Metal backend to use a
// GPU buffer, which is more interesting to test.
auto ubuffer = api.createBufferObject(sizeof(MaterialParams) + 64,
BufferObjectBinding::UNIFORM, BufferUsage::STATIC);
// We use STATIC here, even though the buffer is updated, to force the Metal backend to use
// a GPU buffer, which is more interesting to test.
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(MaterialParams) + 64,
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
api.updateDescriptorSetBuffer(descriptorSet, 0, ubuffer, 0, sizeof(MaterialParams) + 64);
api.bindDescriptorSet(descriptorSet, 1, {});
shader.bindUniform<MaterialParams>(api, ubuffer, kBindingConfig);
api.startCapture(0);
// Upload uniforms.
{
MaterialParams params {
.color = { 1.0f, 1.0f, 1.0f, 1.0f },
.offset = { 0.0f, 0.0f, 0.0f, 0.0f }
};
auto* tmp = new MaterialParams(params);
auto cb = [](void* buffer, size_t size, void* user) {
auto* sp = (MaterialParams*) buffer;
delete sp;
};
BufferDescriptor bd(tmp, sizeof(MaterialParams), cb);
api.updateBufferObject(ubuffer, std::move(bd), 64);
}
// Upload the uniform, but with an offset to accommodate the padding in the shader's
// uniform definition.
shader.uploadUniform(api, ubuffer, kBindingConfig, MaterialParams{
.color = { 1.0f, 1.0f, 1.0f, 1.0f },
.offset = { 0.0f, 0.0f, 0.0f, 0.0f }
});
api.makeCurrent(swapChain, swapChain);
api.beginFrame(0, 0, 0);
@@ -202,13 +199,6 @@ TEST_F(BackendTest, VertexBufferUpdate) {
api.endFrame(0);
api.stopCapture(0);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyBufferObject(ubuffer);
api.destroyRenderTarget(defaultRenderTarget);
}
executeCommands();
@@ -216,62 +206,38 @@ TEST_F(BackendTest, VertexBufferUpdate) {
// This test renders two triangles in two separate draw calls. Between the draw calls, a uniform
// buffer object is partially updated.
TEST_F(BackendTest, BufferObjectUpdateWithOffset) {
TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
auto& api = getDriverApi();
Cleanup cleanup(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = createSwapChain();
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a program.
filamat::DescriptorSets descriptors;
descriptors[1] = { { "Params",
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, 0 }, {} } };
ShaderGenerator shaderGen(
vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
Program p = shaderGen.getProgram(api);
p.descriptorBindings(1, {{ "Params", DescriptorType::UNIFORM_BUFFER, 0 }});
auto program = api.createProgram(std::move(p));
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
{{
DescriptorType::UNIFORM_BUFFER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}});
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
Shader shader = createShader();
// Create a uniform buffer.
// We use STATIC here, even though the buffer is updated, to force the Metal backend to use a
// GPU buffer, which is more interesting to test.
auto ubuffer = api.createBufferObject(sizeof(MaterialParams) + 64,
BufferObjectBinding::UNIFORM, BufferUsage::STATIC);
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(MaterialParams) + 64,
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
api.updateDescriptorSetBuffer(descriptorSet, 0, ubuffer, 0, sizeof(MaterialParams) + 64);
api.bindDescriptorSet(descriptorSet, 1, {});
shader.bindUniform<MaterialParams>(api, ubuffer, kBindingConfig);
// Create a render target.
auto colorTexture = api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT);
auto renderTarget = api.createRenderTarget(
TargetBufferFlags::COLOR0, 512, 512, 1, 0, {{colorTexture}}, {}, {});
auto colorTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT));
auto renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR0, 512, 512, 1, 0, {{colorTexture}}, {}, {}));
// Upload uniforms for the first triangle.
{
MaterialParams params {
// Upload the uniform, but with an offset to accommodate the padding in the shader's
// uniform definition.
shader.uploadUniform(api, ubuffer, kBindingConfig, MaterialParams{
.color = { 1.0f, 0.0f, 0.5f, 1.0f },
.offset = { 0.0f, 0.0f, 0.0f, 0.0f }
};
auto* tmp = new MaterialParams(params);
auto cb = [](void* buffer, size_t size, void* user) {
auto* sp = (MaterialParams*) buffer;
delete sp;
};
BufferDescriptor bd(tmp, sizeof(MaterialParams), cb);
api.updateBufferObject(ubuffer, std::move(bd), 64);
}
});
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::COLOR;
@@ -280,29 +246,27 @@ TEST_F(BackendTest, BufferObjectUpdateWithOffset) {
params.flags.discardEnd = TargetBufferFlags::NONE;
params.viewport.height = 512;
params.viewport.width = 512;
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
renderTarget, swapChain, program, params);
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
renderTarget, swapChain, shader.getProgram(), params);
// Upload uniforms for the second triangle. To test partial buffer updates, we'll only update
// color.b, color.a, offset.x, and offset.y.
{
MaterialParams params {
.color = { 1.0f, 0.0f, 1.0f, 1.0f },
.offset = { 0.5f, 0.5f, 0.0f, 0.0f }
};
auto* tmp = new MaterialParams(params);
auto cb = [](void* buffer, size_t size, void* user) {
auto* sp = (MaterialParams*) ((char*)buffer - offsetof(MaterialParams, color.b));
delete sp;
};
BufferDescriptor bd((char*)tmp + offsetof(MaterialParams, color.b), sizeof(float) * 4, cb);
api.updateBufferObject(ubuffer, std::move(bd), 64 + offsetof(MaterialParams, color.b));
}
shader.uploadUniform(api, ubuffer, UniformBindingConfig{
.dataSize = sizeof(std::array<float, 4>),
.bufferSize = kBindingConfig.bufferSize,
.byteOffset = *kBindingConfig.byteOffset + offsetof(MaterialParams, color.b),
},
std::array<float, 4>{
// color.b, color.a
1.0f, 1.0f,
// offset.x, offset.y
0.5f, 0.5f }
);
params.flags.clear = TargetBufferFlags::NONE;
params.flags.discardStart = TargetBufferFlags::NONE;
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
renderTarget, swapChain, program, params);
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
renderTarget, swapChain, shader.getProgram(), params);
static const uint32_t expectedHash = 91322442;
readPixelsAndAssertHash(
@@ -312,14 +276,6 @@ TEST_F(BackendTest, BufferObjectUpdateWithOffset) {
api.commit(swapChain);
api.endFrame(0);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyBufferObject(ubuffer);
api.destroyRenderTarget(renderTarget);
api.destroyTexture(colorTexture);
// This ensures all driver commands have finished before exiting the test.
api.finish();
+7 -3
View File
@@ -16,6 +16,8 @@
#include "BackendTest.h"
#include "Lifetimes.h"
using namespace filament;
using namespace filament::backend;
@@ -23,13 +25,14 @@ namespace test {
TEST_F(BackendTest, FrameScheduledCallback) {
auto& api = getDriverApi();
Cleanup cleanup(api);
// Create a SwapChain.
// In order for the frameScheduledCallback to be called, this must be a real SwapChain (not
// headless) so we obtain a drawable.
auto swapChain = createSwapChain();
auto swapChain = cleanup.add(createSwapChain());
Handle<HwRenderTarget> renderTarget = api.createDefaultRenderTarget();
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createDefaultRenderTarget());
int callbackCountA = 0;
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountA](PresentCallable callable) {
@@ -79,9 +82,10 @@ TEST_F(BackendTest, FrameScheduledCallback) {
TEST_F(BackendTest, FrameCompletedCallback) {
auto& api = getDriverApi();
Cleanup cleanup(api);
// Create a SwapChain.
auto swapChain = api.createSwapChainHeadless(256, 256, 0);
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
int callbackCountA = 0;
api.setFrameCompletedCallback(swapChain, nullptr,
+18 -27
View File
@@ -16,6 +16,7 @@
#include "BackendTest.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "TrianglePrimitive.h"
@@ -129,12 +130,13 @@ static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt) {
// backend's readPixels does not work correctly with textures that have image data uploaded.
TEST_F(BackendTest, FeedbackLoops) {
auto& api = getDriverApi();
Cleanup cleanup(api);
// The test is executed within this block scope to force destructors to run before
// executeCommands().
{
// Create a platform-specific SwapChain and make it current.
auto swapChain = createSwapChain();
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a program.
@@ -155,10 +157,10 @@ TEST_F(BackendTest, FeedbackLoops) {
{ "test_tex", DescriptorType::SAMPLER, 0 },
{ "Params", DescriptorType::UNIFORM_BUFFER, 1 }
});
program = api.createProgram(std::move(prog));
program = cleanup.add(api.createProgram(std::move(prog)));
}
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
DescriptorSetLayoutHandle descriptorSetLayout = cleanup.add(api.createDescriptorSetLayout({
{{
DescriptorType::SAMPLER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
@@ -168,27 +170,27 @@ TEST_F(BackendTest, FeedbackLoops) {
DescriptorType::UNIFORM_BUFFER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 1,
DescriptorFlags::NONE, 0
}}});
}}}));
TrianglePrimitive const triangle(getDriverApi());
// Create a texture.
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
Handle<HwTexture> const texture = api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage);
Handle<HwTexture> const texture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage));
// Create ubo
auto ubuffer = api.createBufferObject(sizeof(MaterialParams),
BufferObjectBinding::UNIFORM, BufferUsage::STATIC);
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(MaterialParams),
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
// Create a RenderTarget for each miplevel.
Handle<HwRenderTarget> renderTargets[kNumLevels];
for (uint8_t level = 0; level < kNumLevels; level++) {
slog.i << "Level " << int(level) << ": " <<
(kTexWidth >> level) << "x" << (kTexHeight >> level) << io::endl;
renderTargets[level] = api.createRenderTarget( TargetBufferFlags::COLOR,
kTexWidth >> level, kTexHeight >> level, 1, 0, { texture, level, 0 }, {}, {});
renderTargets[level] = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
kTexWidth >> level, kTexHeight >> level, 1, 0, { texture, level, 0 }, {}, {}));
}
// Fill the base level of the texture with interesting colors.
@@ -226,12 +228,13 @@ TEST_F(BackendTest, FeedbackLoops) {
params.flags.discardStart = TargetBufferFlags::ALL;
state.rasterState.disableBlending();
for (int targetLevel = 1; targetLevel < kNumLevels; targetLevel++) {
Cleanup passCleanup(api);
const uint32_t sourceLevel = targetLevel - 1;
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
auto textureView = api.createTextureView(texture, sourceLevel, 1);
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
auto textureView = passCleanup.add(api.createTextureView(texture, sourceLevel, 1));
DescriptorSetHandle descriptorSet = passCleanup.add(api.createDescriptorSet(descriptorSetLayout));
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
@@ -247,8 +250,6 @@ TEST_F(BackendTest, FeedbackLoops) {
api.beginRenderPass(renderTargets[targetLevel], params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
api.destroyTexture(textureView);
api.destroyDescriptorSet(descriptorSet);
}
// Upsample passes
@@ -256,12 +257,13 @@ TEST_F(BackendTest, FeedbackLoops) {
state.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
state.rasterState.blendFunctionDstRGB = BlendFunction::ONE;
for (int targetLevel = kNumLevels - 2; targetLevel >= 0; targetLevel--) {
Cleanup passCleanup(api);
const uint32_t sourceLevel = targetLevel + 1;
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
auto textureView = api.createTextureView(texture, sourceLevel, 1);
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
auto textureView = passCleanup.add(api.createTextureView(texture, sourceLevel, 1));
DescriptorSetHandle descriptorSet = passCleanup.add(api.createDescriptorSet(descriptorSetLayout));
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
@@ -277,8 +279,6 @@ TEST_F(BackendTest, FeedbackLoops) {
api.beginRenderPass(renderTargets[targetLevel], params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
api.destroyTexture(textureView);
api.destroyDescriptorSet(descriptorSet);
}
// Read back the render target corresponding to the base level.
@@ -296,15 +296,6 @@ TEST_F(BackendTest, FeedbackLoops) {
executeCommands();
getDriver().purge();
}
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyTexture(texture);
api.destroyBufferObject(ubuffer);
for (auto rt : renderTargets) {
api.destroyRenderTarget(rt);
}
}
const uint32_t expected = 0x70695aa1;
+12 -16
View File
@@ -16,9 +16,10 @@
#include "BackendTest.h"
#include "BackendTestUtils.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "TrianglePrimitive.h"
#include "BackendTestUtils.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
@@ -295,10 +296,12 @@ TEST_F(BackendTest, UpdateImage2D) {
// The test is executed within this block scope to force destructors to run before
// executeCommands().
for (const auto& t : testCases) {
Cleanup cleanup(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = createSwapChain();
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
auto defaultRenderTarget = api.createDefaultRenderTarget(0);
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
@@ -315,21 +318,21 @@ TEST_F(BackendTest, UpdateImage2D) {
Program prog = shaderGen.getProgram(api);
prog.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
ProgramHandle const program = api.createProgram(std::move(prog));
ProgramHandle const program = cleanup.add(api.createProgram(std::move(prog)));
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
DescriptorSetLayoutHandle descriptorSetLayout = cleanup.add(api.createDescriptorSetLayout({
{{
DescriptorType::SAMPLER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}});
}}}));
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
DescriptorSetHandle descriptorSet = cleanup.add(api.createDescriptorSet(descriptorSetLayout));
// Create a Texture.
auto usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
Handle<HwTexture> const texture = api.createTexture(SamplerType::SAMPLER_2D, 1,
t.textureFormat, 1, 512, 512, 1u, usage);
Handle<HwTexture> const texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
t.textureFormat, 1, 512, 512, 1u, usage));
// Upload some pixel data.
if (t.uploadSubregions) {
@@ -359,13 +362,6 @@ TEST_F(BackendTest, UpdateImage2D) {
api.commit(swapChain);
api.endFrame(0);
api.destroyDescriptorSet(descriptorSet);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyRenderTarget(defaultRenderTarget);
api.destroyTexture(texture);
}
api.finish();
+26 -26
View File
@@ -16,6 +16,7 @@
#include "BackendTest.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "TrianglePrimitive.h"
@@ -60,25 +61,28 @@ using namespace filament;
using namespace filament::backend;
TEST_F(BackendTest, MRT) {
DriverApi& api = getDriverApi();
Cleanup cleanup(api);
// The test is executed within this block scope to force destructors to run before
// executeCommands().
{
// Create a platform-specific SwapChain and make it current.
auto swapChain = createSwapChain();
getDriverApi().makeCurrent(swapChain, swapChain);
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a program.
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(getDriverApi());
auto program = getDriverApi().createProgram(std::move(p));
Program p = shaderGen.getProgram(api);
auto program = cleanup.add(api.createProgram(std::move(p)));
TrianglePrimitive triangle(getDriverApi());
TrianglePrimitive triangle(api);
auto defaultRenderTarget = getDriverApi().createDefaultRenderTarget(0);
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
// Create two Textures.
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
Handle<HwTexture> textureA = getDriverApi().createTexture(
Handle<HwTexture> textureA = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, // target
1, // levels
TextureFormat::RGBA8, // format
@@ -86,8 +90,8 @@ TEST_F(BackendTest, MRT) {
512, // width
512, // height
1, // depth
usage); // usage
Handle<HwTexture> textureB = getDriverApi().createTexture(
usage)); // usage
Handle<HwTexture> textureB = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, // target
1, // levels
TextureFormat::RGBA8, // format
@@ -95,10 +99,10 @@ TEST_F(BackendTest, MRT) {
512, // width
512, // height
1, // depth
usage); // usage
usage)); // usage
// Create a RenderTarget with two attachments.
Handle<HwRenderTarget> renderTarget = getDriverApi().createRenderTarget(
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR0 | TargetBufferFlags::COLOR1,
// The width and height must match the width and height of the respective mip
// level (at least for OpenGL).
@@ -108,7 +112,7 @@ TEST_F(BackendTest, MRT) {
0, // layerCount
{{textureA },{textureB }}, // color
{}, // depth
{}); // stencil
{})); // stencil
RenderPassParams params = {};
fullViewport(params);
@@ -124,25 +128,21 @@ TEST_F(BackendTest, MRT) {
state.rasterState.depthFunc = RasterState::DepthFunc::A;
state.rasterState.culling = CullingMode::NONE;
getDriverApi().startCapture(0);
api.startCapture(0);
getDriverApi().makeCurrent(swapChain, swapChain);
getDriverApi().beginFrame(0, 0, 0);
api.makeCurrent(swapChain, swapChain);
api.beginFrame(0, 0, 0);
// Draw a triangle.
getDriverApi().beginRenderPass(renderTarget, params);
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
getDriverApi().endRenderPass();
api.beginRenderPass(renderTarget, params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
getDriverApi().flush();
getDriverApi().commit(swapChain);
getDriverApi().endFrame(0);
api.flush();
api.commit(swapChain);
api.endFrame(0);
getDriverApi().stopCapture(0);
getDriverApi().destroyProgram(program);
getDriverApi().destroySwapChain(swapChain);
getDriverApi().destroyRenderTarget(defaultRenderTarget);
api.stopCapture(0);
}
executeCommands();
+17 -27
View File
@@ -17,9 +17,10 @@
#include "BackendTest.h"
#include "BackendTestUtils.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "TrianglePrimitive.h"
#include "BackendTestUtils.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
@@ -78,12 +79,13 @@ using namespace filament::backend;
TEST_F(BackendTest, TextureViewLod) {
auto& api = getDriverApi();
api.startCapture(0);
Cleanup cleanup(api);
// The test is executed within this block scope to force destructors to run before
// executeCommands().
{
// Create a SwapChain and make it current.
auto swapChain = createSwapChain();
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a program that draws only white.
@@ -92,7 +94,7 @@ TEST_F(BackendTest, TextureViewLod) {
ShaderGenerator shaderGen(vertex, whiteFragment, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(api);
p.descriptorBindings(0, {{"backend_test_sib_tex", DescriptorType::SAMPLER, 0}});
whiteProgram = api.createProgram(std::move(p));
whiteProgram = cleanup.add(api.createProgram(std::move(p)));
}
// Create a program that samples a texture.
@@ -106,19 +108,19 @@ TEST_F(BackendTest, TextureViewLod) {
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors));
Program p = shaderGen.getProgram(api);
p.descriptorBindings(0, {{"backend_test_sib_tex", DescriptorType::SAMPLER, 0}});
textureProgram = api.createProgram(std::move(p));
textureProgram = cleanup.add(api.createProgram(std::move(p)));
}
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
DescriptorSetLayoutHandle descriptorSetLayout = cleanup.add(api.createDescriptorSetLayout({
{{
DescriptorType::SAMPLER,
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
DescriptorFlags::NONE, 0
}}});
}}}));
DescriptorSetHandle descriptorSet[2];
descriptorSet[0] = api.createDescriptorSet(descriptorSetLayout);
descriptorSet[1] = api.createDescriptorSet(descriptorSetLayout);
descriptorSet[0] = cleanup.add(api.createDescriptorSet(descriptorSetLayout));
descriptorSet[1] = cleanup.add(api.createDescriptorSet(descriptorSetLayout));
// Create a texture that has 4 mip levels. Each level is a different color.
// Level 0: 128x128 (red)
@@ -127,9 +129,9 @@ TEST_F(BackendTest, TextureViewLod) {
// Level 3: 16x16 (yellow)
const size_t kTextureSize = 128;
const size_t kMipLevels = 4;
Handle<HwTexture> texture = api.createTexture(SamplerType::SAMPLER_2D, kMipLevels,
Handle<HwTexture> texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, kMipLevels,
TextureFormat::RGBA8, 1, kTextureSize, kTextureSize, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::UPLOADABLE);
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::UPLOADABLE));
// Create image data.
auto pixelFormat = PixelDataFormat::RGBA;
@@ -163,13 +165,13 @@ TEST_F(BackendTest, TextureViewLod) {
// Level 1: 64x64 (green) <-- base
// Level 2: 32x32 (blue) <--- white triangle rendered
// Level 3: 16x16 (yellow) <-- max
auto texture13 = api.createTextureView(texture, 1, 3);
auto texture13 = cleanup.add(api.createTextureView(texture, 1, 3));
// Render a white triangle into level 2.
// We specify mip level 2, because minMaxLevels has no effect when rendering into a texture.
Handle<HwRenderTarget> renderTarget = api.createRenderTarget(
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR, 32, 32, 1, 0,
{texture, 2 /* level */, 0 /* layer */}, {}, {});
{texture, 2 /* level */, 0 /* layer */}, {}, {}));
{
RenderPassParams params = {};
fullViewport(params);
@@ -185,7 +187,7 @@ TEST_F(BackendTest, TextureViewLod) {
api.endRenderPass();
}
backend::Handle<HwRenderTarget> defaultRenderTarget = api.createDefaultRenderTarget(0);
backend::Handle<HwRenderTarget> defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
RenderPassParams params = {};
fullViewport(params);
@@ -217,7 +219,7 @@ TEST_F(BackendTest, TextureViewLod) {
api.endRenderPass();
// Adjust the base mip to 2.
auto texture22 = api.createTextureView(texture, 2, 2);
auto texture22 = cleanup.add(api.createTextureView(texture, 2, 2));
api.updateDescriptorSetTexture(descriptorSet[1], 0, texture22, {
.filterMag = SamplerMagFilter::NEAREST,
@@ -244,18 +246,6 @@ TEST_F(BackendTest, TextureViewLod) {
api.endFrame(0);
api.stopCapture(0);
// Cleanup.
api.destroySwapChain(swapChain);
api.destroyRenderTarget(renderTarget);
api.destroyDescriptorSet(descriptorSet[0]);
api.destroyDescriptorSet(descriptorSet[1]);
api.destroyDescriptorSetLayout(descriptorSetLayout);
api.destroyTexture(texture);
api.destroyTexture(texture13);
api.destroyTexture(texture22);
api.destroyProgram(whiteProgram);
api.destroyProgram(textureProgram);
}
api.finish();
@@ -16,6 +16,7 @@
#include "BackendTest.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "TrianglePrimitive.h"
@@ -70,18 +71,20 @@ TEST_F(BackendTest, MissingRequiredAttributes) {
// The test is executed within this block scope to force destructors to run before
// executeCommands().
{
DriverApi& api = getDriverApi();
Cleanup cleanup(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = createSwapChain();
getDriverApi().makeCurrent(swapChain, swapChain);
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a program.
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
Program p = shaderGen.getProgram(getDriverApi());
auto program = getDriverApi().createProgram(std::move(p));
Program p = shaderGen.getProgram(api);
auto program = cleanup.add(api.createProgram(std::move(p)));
auto defaultRenderTarget = getDriverApi().createDefaultRenderTarget(0);
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
TrianglePrimitive triangle(getDriverApi());
TrianglePrimitive triangle(api);
RenderPassParams params = {};
fullViewport(params);
@@ -97,25 +100,21 @@ TEST_F(BackendTest, MissingRequiredAttributes) {
state.rasterState.depthFunc = RasterState::DepthFunc::A;
state.rasterState.culling = CullingMode::NONE;
getDriverApi().startCapture(0);
api.startCapture(0);
getDriverApi().makeCurrent(swapChain, swapChain);
getDriverApi().beginFrame(0, 0, 0);
api.makeCurrent(swapChain, swapChain);
api.beginFrame(0, 0, 0);
// Render a triangle.
getDriverApi().beginRenderPass(defaultRenderTarget, params);
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
getDriverApi().endRenderPass();
api.beginRenderPass(defaultRenderTarget, params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
getDriverApi().flush();
getDriverApi().commit(swapChain);
getDriverApi().endFrame(0);
api.flush();
api.commit(swapChain);
api.endFrame(0);
getDriverApi().stopCapture(0);
getDriverApi().destroyProgram(program);
getDriverApi().destroySwapChain(swapChain);
getDriverApi().destroyRenderTarget(defaultRenderTarget);
api.stopCapture(0);
}
executeCommands();

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