Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
794420ebdf |
@@ -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-24.04-32core'
|
||||
runs-on: ubuntu-22.04-32core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
build-linux:
|
||||
name: build-linux
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
runs-on: ubuntu-22.04-16core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
build-android:
|
||||
name: build-android
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
runs-on: ubuntu-22.04-16core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
runs-on: ubuntu-22.04-16core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
|
||||
validate-docs:
|
||||
name: validate-docs
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
runs-on: ubuntu-22.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-24.04-8core'
|
||||
runs-on: ubuntu-22.04-8core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
runs-on: ubuntu-22.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-24.04-16core'
|
||||
runs-on: ubuntu-22.04-16core
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'android'
|
||||
|
||||
steps:
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
runs-on: ubuntu-22.04-16core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
@@ -7,3 +7,6 @@ for next branch cut* header.
|
||||
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**]
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.58.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.58.1'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.58.2'
|
||||
pod 'Filament', '~> 1.58.1'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,10 +7,6 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.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.
|
||||
|
||||
@@ -94,7 +94,7 @@ buildscript {
|
||||
|
||||
ext.versions = [
|
||||
'jdk': 17,
|
||||
'minSdk': 21,
|
||||
'minSdk': 26,
|
||||
'targetSdk': 34,
|
||||
'compileSdk': 34,
|
||||
'kotlin': '2.0.21',
|
||||
@@ -125,7 +125,7 @@ buildscript {
|
||||
ext.cmakeArgs = [
|
||||
"--no-warn-unused-cli",
|
||||
"-DANDROID_PIE=ON",
|
||||
"-DANDROID_PLATFORM=21",
|
||||
"-DANDROID_PLATFORM=26",
|
||||
"-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 versions.minSdk
|
||||
minSdkVersion 26
|
||||
targetSdkVersion versions.targetSdk
|
||||
|
||||
externalNativeBuild {
|
||||
|
||||
@@ -4,6 +4,8 @@ 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)
|
||||
@@ -30,6 +32,10 @@ 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")
|
||||
|
||||
@@ -57,7 +63,8 @@ target_include_directories(filament-utils-jni PRIVATE
|
||||
..
|
||||
../../filament/backend/include
|
||||
${IMAGEIO_DIR}/include
|
||||
../../libs/utils/include)
|
||||
../../libs/utils/include
|
||||
../../libs/bluevk/include)
|
||||
|
||||
set_target_properties(filament-utils-jni PROPERTIES LINK_DEPENDS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libfilament-utils-jni.symbols)
|
||||
@@ -71,4 +78,6 @@ target_link_libraries(filament-utils-jni
|
||||
image
|
||||
ktxreader
|
||||
viewer
|
||||
bluevk
|
||||
android
|
||||
)
|
||||
|
||||
@@ -12,6 +12,10 @@ 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,12 +14,21 @@
|
||||
* 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"
|
||||
@@ -29,6 +38,8 @@ 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);
|
||||
|
||||
@@ -79,6 +90,37 @@ 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) {
|
||||
@@ -108,5 +150,14 @@ 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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Romain Guy
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.utils
|
||||
|
||||
import android.hardware.HardwareBuffer
|
||||
import com.google.android.filament.Engine
|
||||
import com.google.android.filament.Texture
|
||||
|
||||
object ExternalImage {
|
||||
fun setOnTexture(
|
||||
engine: Engine,
|
||||
texture: Texture,
|
||||
buffer: HardwareBuffer,
|
||||
srgb: Boolean,
|
||||
) {
|
||||
val nativeEngine = engine.nativeObject
|
||||
val nativeTexture = texture.nativeObject
|
||||
val l = nSetExternalImageOnTexture(nativeEngine, nativeTexture, buffer, srgb)
|
||||
}
|
||||
|
||||
private external fun nSetExternalImageOnTexture(
|
||||
nativeEngine: Long,
|
||||
nativeTexture: Long,
|
||||
buffer: HardwareBuffer,
|
||||
srgb: Boolean,
|
||||
): Long
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.58.2
|
||||
VERSION_NAME=1.58.1
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
/.idea/caches
|
||||
/.idea/gradle.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
/src/main/assets
|
||||
.externalNativeBuild
|
||||
@@ -0,0 +1,55 @@
|
||||
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')
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
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).")
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
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).")
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
/*
|
||||
* 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)
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
/*
|
||||
* 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)
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<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>
|
||||
@@ -0,0 +1,171 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">External Image</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Hello Camera</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -18,5 +18,6 @@ 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'
|
||||
|
||||
@@ -200,9 +200,8 @@ WEBGPU_ANDROID_GRADLE_OPTION=""
|
||||
EGL_ON_LINUX_OPTION="-DFILAMENT_SUPPORTS_EGL_ON_LINUX=OFF"
|
||||
|
||||
MATDBG_OPTION="-DFILAMENT_ENABLE_MATDBG=OFF"
|
||||
MATDBG_GRADLE_OPTION=""
|
||||
FGVIEWER_OPTION="-DFILAMENT_ENABLE_FGVIEWER=OFF"
|
||||
FGVIEWER_GRADLE_OPTION=""
|
||||
MATDBG_GRADLE_OPTION=""
|
||||
|
||||
MATOPT_OPTION=""
|
||||
MATOPT_GRADLE_OPTION=""
|
||||
@@ -534,7 +533,6 @@ 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 \
|
||||
@@ -586,7 +584,6 @@ 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 \
|
||||
|
||||
@@ -494,9 +494,7 @@ 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
|
||||
@@ -512,7 +510,6 @@ if (APPLE OR LINUX)
|
||||
test/test_Handles.cpp
|
||||
)
|
||||
set(BACKEND_TEST_LIBS
|
||||
absl::str_format
|
||||
backend
|
||||
getopt
|
||||
gtest
|
||||
@@ -528,7 +525,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 PUBLIC ${BACKEND_TEST_LIBS})
|
||||
target_link_libraries(backend_test PRIVATE ${BACKEND_TEST_LIBS})
|
||||
|
||||
set(BACKEND_TEST_DEPS
|
||||
OSDependent
|
||||
|
||||
@@ -256,18 +256,16 @@ struct DescriptorSetLayoutBinding {
|
||||
DescriptorFlags flags = DescriptorFlags::NONE;
|
||||
uint16_t count = 0;
|
||||
|
||||
// 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;
|
||||
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.stageFlags == rhs.stageFlags &&
|
||||
// lhs.externalSamplerDataIndex == rhs.externalSamplerDataIndex;
|
||||
lhs.stageFlags == rhs.stageFlags &&
|
||||
lhs.externalSamplerDataIndex == rhs.externalSamplerDataIndex;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1140,9 +1138,7 @@ static_assert(sizeof(ExternalSamplerDatum) == 12);
|
||||
|
||||
struct DescriptorSetLayout {
|
||||
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
|
||||
|
||||
// TODO: uncomment when needed
|
||||
// utils::FixedCapacityVector<ExternalSamplerDatum> externalSamplerData;
|
||||
utils::FixedCapacityVector<ExternalSamplerDatum> externalSamplerData;
|
||||
};
|
||||
|
||||
//! blending equation function
|
||||
|
||||
@@ -1177,17 +1177,9 @@ size_t MetalDriver::getMaxUniformBufferSize() {
|
||||
return 256 * 1024 * 1024; // TODO: return the actual size instead of hardcoding the minspec
|
||||
}
|
||||
|
||||
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::getMaxTextureSize(SamplerType) {
|
||||
// TODO: return the actual size instead of hardcoding the minspec
|
||||
return 2048;
|
||||
}
|
||||
|
||||
size_t MetalDriver::getMaxArrayTextureLayers() {
|
||||
|
||||
@@ -241,9 +241,7 @@ size_t NoopDriver::getMaxUniformBufferSize() {
|
||||
}
|
||||
|
||||
size_t NoopDriver::getMaxTextureSize(SamplerType target) {
|
||||
// NoopDriver is being actively used for other purposes. This needs to be resolved before we
|
||||
// can change it to 2048. b/406832484
|
||||
return 16384u;
|
||||
return 2048u;
|
||||
}
|
||||
|
||||
size_t NoopDriver::getMaxArrayTextureLayers() {
|
||||
|
||||
@@ -566,11 +566,7 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
|
||||
} else if (strstr(renderer, "AMD") ||
|
||||
strstr(renderer, "ATI")) {
|
||||
// AMD/ATI GPU
|
||||
} else if (strstr(renderer, "Mozilla")) {
|
||||
bugs->disable_invalidate_framebuffer = true;
|
||||
}
|
||||
|
||||
if (strstr(vendor, "Mesa")) {
|
||||
} else if (strstr(vendor, "Mesa")) {
|
||||
// Seen on
|
||||
// [Mesa],
|
||||
// [llvmpipe (LLVM 17.0.6, 256 bits)],
|
||||
@@ -578,6 +574,8 @@ 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.
|
||||
@@ -589,19 +587,6 @@ 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)) {
|
||||
|
||||
@@ -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 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.
|
||||
// Mesa sometimes clears 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
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE | FVK_DEBUG_FORWARDED_FLAG)
|
||||
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE | FVK_DEBUG_FORWARDED_FLAG | FVK_DEBUG_VALIDATION)
|
||||
#else
|
||||
#define FVK_DEBUG_FLAGS 0
|
||||
#endif
|
||||
|
||||
@@ -1057,7 +1057,7 @@ bool VulkanDriver::isDepthStencilBlitSupported(TextureFormat format) {
|
||||
formats.end();
|
||||
}
|
||||
|
||||
bool VulkanDriver::isProtectedTexturesSupported() { return isProtectedContentSupported(); }
|
||||
bool VulkanDriver::isProtectedTexturesSupported() { return false; }
|
||||
|
||||
bool VulkanDriver::isDepthClampSupported() {
|
||||
return mContext.isDepthClampSupported();
|
||||
@@ -1134,28 +1134,24 @@ math::float2 VulkanDriver::getClipSpaceParams() {
|
||||
}
|
||||
|
||||
uint8_t VulkanDriver::getMaxDrawBuffers() {
|
||||
return mContext.getPhysicalDeviceLimits().maxColorAttachments;
|
||||
return MRT::MIN_SUPPORTED_RENDER_TARGET_COUNT; // TODO: query real value
|
||||
}
|
||||
|
||||
size_t VulkanDriver::getMaxUniformBufferSize() {
|
||||
return mContext.getPhysicalDeviceLimits().maxUniformBufferRange;
|
||||
// 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;
|
||||
}
|
||||
|
||||
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::getMaxTextureSize(SamplerType) {
|
||||
// TODO: return the actual size instead of hardcoded value
|
||||
return 2048;
|
||||
}
|
||||
|
||||
size_t VulkanDriver::getMaxArrayTextureLayers() {
|
||||
return mContext.getPhysicalDeviceLimits().maxImageArrayLayers;
|
||||
// TODO: return the actual size instead of hardcoded value
|
||||
return 256;
|
||||
}
|
||||
|
||||
void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
|
||||
|
||||
@@ -736,6 +736,10 @@ 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);
|
||||
@@ -755,10 +759,6 @@ 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));
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "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) {}
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_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
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#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
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_SHADER_H
|
||||
#define TNT_SHADER_H
|
||||
|
||||
#include "Lifetimes.h"
|
||||
|
||||
namespace test {
|
||||
|
||||
// All describing a shader that should be created.
|
||||
struct ShaderConfig {
|
||||
std::string vertexShader;
|
||||
std::string fragmentShader;
|
||||
std::vector<utils::CString> uniformNames;
|
||||
};
|
||||
|
||||
// All values describing a uniform.
|
||||
struct ResolvedUniformBindingConfig {
|
||||
size_t dataSize;
|
||||
size_t bufferSize;
|
||||
uint32_t byteOffset;
|
||||
filament::backend::descriptor_set_t set;
|
||||
filament::backend::descriptor_binding_t binding;
|
||||
};
|
||||
|
||||
// An equivalent to ResolvedUniformBindingConfig with all fields optional.
|
||||
// resolve can be called to create a ResolvedBindingConfig with default values
|
||||
// use in place of nullopt values.
|
||||
struct UniformBindingConfig {
|
||||
std::optional<size_t> dataSize;
|
||||
std::optional<size_t> bufferSize;
|
||||
std::optional<uint32_t> byteOffset;
|
||||
std::optional<filament::backend::descriptor_set_t> set;
|
||||
std::optional<filament::backend::descriptor_binding_t> binding;
|
||||
|
||||
template<typename UniformType>
|
||||
ResolvedUniformBindingConfig resolve();
|
||||
};
|
||||
|
||||
class Shader {
|
||||
public:
|
||||
// All graphics resources have their lifetime controlled by the Cleanup and not this object.
|
||||
// Shader must not outlive either the DriverApi or the Cleanup.
|
||||
Shader(filament::backend::DriverApi& api, Cleanup& cleanup, ShaderConfig config);
|
||||
|
||||
// Uploads a uniform with the default config.
|
||||
template<typename UniformType>
|
||||
void uploadUniform(filament::backend::DriverApi& api,
|
||||
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
|
||||
UniformType uniforms) const;
|
||||
// Binds a uniform with the default config.
|
||||
template<typename UniformType>
|
||||
void bindUniform(filament::backend::DriverApi& api,
|
||||
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer) const;
|
||||
// Uploads a uniform, with UniformBindingConfig providing the arguments.
|
||||
template<typename UniformType>
|
||||
void uploadUniform(filament::backend::DriverApi& api,
|
||||
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
|
||||
UniformBindingConfig config, UniformType uniforms) const;
|
||||
// Binds a uniform, with UniformBindingConfig providing the arguments.
|
||||
template<typename UniformType>
|
||||
void bindUniform(filament::backend::DriverApi& api,
|
||||
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
|
||||
UniformBindingConfig config) const;
|
||||
|
||||
filament::backend::ProgramHandle getProgram() const;
|
||||
filament::backend::DescriptorSetLayoutHandle getDescriptorSetLayout() const;
|
||||
filament::backend::DescriptorSetHandle getDescriptorSet() const;
|
||||
|
||||
protected:
|
||||
filament::backend::ProgramHandle mProgram;
|
||||
filament::backend::DescriptorSetLayoutHandle mDescriptorSetLayout;
|
||||
filament::backend::DescriptorSetHandle mDescriptorSet;
|
||||
};
|
||||
|
||||
template<typename UniformType>
|
||||
ResolvedUniformBindingConfig UniformBindingConfig::resolve() {
|
||||
auto resolvedDataSize = dataSize.value_or(sizeof(UniformType));
|
||||
return ResolvedUniformBindingConfig{
|
||||
.dataSize = resolvedDataSize,
|
||||
.bufferSize = bufferSize.value_or(resolvedDataSize),
|
||||
.byteOffset = byteOffset.value_or(0),
|
||||
.set = set.value_or(1),
|
||||
.binding = binding.value_or(0)
|
||||
};
|
||||
}
|
||||
|
||||
template<typename UniformType>
|
||||
void Shader::uploadUniform(filament::backend::DriverApi& api,
|
||||
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
|
||||
UniformBindingConfig config, UniformType uniforms) const {
|
||||
auto resolvedConfig = config.resolve<UniformType>();
|
||||
|
||||
UniformType* tmp = new UniformType(uniforms);
|
||||
auto cb = [](void* buffer, size_t size, void* user) {
|
||||
UniformType* sp = (UniformType*)buffer;
|
||||
delete sp;
|
||||
};
|
||||
filament::backend::BufferDescriptor bd(tmp, resolvedConfig.dataSize, cb);
|
||||
api.updateBufferObject(hwBuffer, std::move(bd), resolvedConfig.byteOffset);
|
||||
}
|
||||
|
||||
template<typename UniformType>
|
||||
void Shader::bindUniform(filament::backend::DriverApi& api,
|
||||
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
|
||||
UniformBindingConfig config) const {
|
||||
auto resolvedConfig = config.resolve<UniformType>();
|
||||
|
||||
api.updateDescriptorSetBuffer(getDescriptorSet(), resolvedConfig.binding, hwBuffer, 0,
|
||||
resolvedConfig.bufferSize);
|
||||
api.bindDescriptorSet(getDescriptorSet(), resolvedConfig.set, {});
|
||||
}
|
||||
|
||||
template<typename UniformType>
|
||||
void Shader::uploadUniform(filament::backend::DriverApi& api,
|
||||
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
|
||||
UniformType uniforms) const {
|
||||
uploadUniform(api, hwBuffer, UniformBindingConfig{}, uniforms);
|
||||
}
|
||||
|
||||
template<typename UniformType>
|
||||
void Shader::bindUniform(filament::backend::DriverApi& api,
|
||||
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer) const {
|
||||
bindUniform<UniformType>(api, hwBuffer, UniformBindingConfig{});
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
#endif //TNT_SHADER_H
|
||||
@@ -16,9 +16,8 @@
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include "ImageExpectations.h"
|
||||
#include "Lifetimes.h"
|
||||
#include "Shader.h"
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
#include <utils/Hash.h>
|
||||
@@ -27,10 +26,8 @@
|
||||
#include <fstream>
|
||||
|
||||
#ifndef FILAMENT_IOS
|
||||
|
||||
#include <imageio/ImageEncoder.h>
|
||||
#include <image/ColorTransform.h>
|
||||
|
||||
#endif
|
||||
|
||||
namespace test {
|
||||
@@ -40,21 +37,6 @@ 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;
|
||||
@@ -74,12 +56,49 @@ void main() {
|
||||
fragColor = params.color;
|
||||
})";
|
||||
|
||||
Shader BlitTest::createShader() {
|
||||
return Shader(getDriverApi(), mCleanup, ShaderConfig{
|
||||
.vertexShader = triangleVs,
|
||||
.fragmentShader = triangleFs,
|
||||
.uniformNames = { "Params" },
|
||||
});
|
||||
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);
|
||||
}
|
||||
|
||||
static uint32_t toUintColor(float4 color) {
|
||||
@@ -97,7 +116,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;
|
||||
@@ -121,7 +140,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 };
|
||||
@@ -149,7 +168,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;
|
||||
@@ -157,7 +176,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++) {
|
||||
@@ -175,9 +194,10 @@ 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(BlitTest, ColorMagnify) {
|
||||
TEST_F(BackendTest, ColorMagnify) {
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
Cleanup cleanup(getDriverApi());
|
||||
cleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
constexpr int kSrcTexWidth = 256;
|
||||
constexpr int kSrcTexHeight = 256;
|
||||
@@ -188,40 +208,37 @@ TEST_F(BlitTest, 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 = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a source texture.
|
||||
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> const srcTexture = cleanup.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 = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> const dstTexture = cleanup.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] = 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 }, {},
|
||||
{}));
|
||||
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 }, {}, {}));
|
||||
}
|
||||
|
||||
// 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.
|
||||
{
|
||||
@@ -229,23 +246,29 @@ TEST_F(BlitTest, ColorMagnify) {
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// Grab a screenshot.
|
||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "ColorMagnify.png" };
|
||||
{
|
||||
ImageExpectations expectations(api);
|
||||
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
EXPECT_IMAGE(dstRenderTargets[0], expectations,
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMagnify", 0x410bdd31));
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
flushAndWait();
|
||||
RenderFrame frame(api);
|
||||
dumpScreenshot(api, dstRenderTargets[0], ¶ms);
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// 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(BlitTest, ColorMinify) {
|
||||
TEST_F(BackendTest, ColorMinify) {
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
Cleanup cleanup(getDriverApi());
|
||||
cleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
constexpr int kSrcTexWidth = 1024;
|
||||
constexpr int kSrcTexHeight = 1024;
|
||||
@@ -256,55 +279,57 @@ TEST_F(BlitTest, 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 = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a source texture.
|
||||
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> const srcTexture = cleanup.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 = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> const dstTexture = cleanup.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] = 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 }, {},
|
||||
{}));
|
||||
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 }, {}, {}));
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
EXPECT_IMAGE(dstRenderTargets[0], expectations,
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f));
|
||||
// Grab a screenshot.
|
||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "ColorMinify.png" };
|
||||
dumpScreenshot(api, dstRenderTargets[0], ¶ms);
|
||||
|
||||
flushAndWait();
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
TEST_F(BlitTest, ColorResolve) {
|
||||
TEST_F(BackendTest, ColorResolve) {
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(getDriverApi());
|
||||
|
||||
constexpr int kSrcTexWidth = 256;
|
||||
constexpr int kSrcTexHeight = 256;
|
||||
@@ -313,29 +338,49 @@ TEST_F(BlitTest, ColorResolve) {
|
||||
constexpr auto kColorTexFormat = TextureFormat::RGBA8;
|
||||
constexpr int kSampleCount = 4;
|
||||
|
||||
Shader shader = createShader();
|
||||
// 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));
|
||||
|
||||
// Create a VertexBuffer, IndexBuffer, and RenderPrimitive.
|
||||
TrianglePrimitive const triangle(api);
|
||||
|
||||
// Create 4-sample texture.
|
||||
Handle<HwTexture> const srcColorTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, 1, kColorTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight,
|
||||
1,
|
||||
TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> const srcColorTexture = cleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, 1, kColorTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::COLOR_ATTACHMENT));
|
||||
|
||||
// Create 1-sample texture.
|
||||
Handle<HwTexture> const dstColorTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, 1, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> const dstColorTexture = cleanup.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 = mCleanup.add(api.createRenderTarget(
|
||||
Handle<HwRenderTarget> const srcRenderTarget = cleanup.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 = mCleanup.add(api.createRenderTarget(
|
||||
Handle<HwRenderTarget> const dstRenderTarget = cleanup.add(api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR, kDstTexWidth, kDstTexHeight, 1, 0,
|
||||
{{ dstColorTexture }}, {}, {}));
|
||||
|
||||
@@ -349,52 +394,58 @@ TEST_F(BlitTest, ColorResolve) {
|
||||
params.viewport.height = kSrcTexHeight;
|
||||
|
||||
PipelineState state = {};
|
||||
state.program = shader.getProgram();
|
||||
state.pipelineLayout.setLayout[1] = { shader.getDescriptorSetLayout() };
|
||||
state.program = program;
|
||||
state.pipelineLayout.setLayout[1] = { descriptorSetLayout };
|
||||
state.rasterState.colorWrite = true;
|
||||
state.rasterState.depthWrite = false;
|
||||
state.rasterState.depthFunc = RasterState::DepthFunc::A;
|
||||
state.rasterState.culling = CullingMode::NONE;
|
||||
|
||||
auto ubuffer = mCleanup.add(api.createBufferObject(sizeof(MaterialParams),
|
||||
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(MaterialParams),
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
|
||||
// Draw red triangle into srcRenderTarget.
|
||||
shader.uploadUniform(api, ubuffer, MaterialParams{
|
||||
.color = float4(1, 0, 0, 1),
|
||||
.scale = float4(1, 1, 0.5, 0),
|
||||
uploadUniforms(api, ubuffer, {
|
||||
.color = float4(1, 0, 0, 1),
|
||||
.scale = float4(1, 1, 0.5, 0),
|
||||
});
|
||||
shader.bindUniform<MaterialParams>(api, ubuffer);
|
||||
|
||||
api.updateDescriptorSetBuffer(descriptorSet, 0, ubuffer, 0, sizeof(MaterialParams));
|
||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
||||
|
||||
// FIXME: on Metal this triangle is not drawn. Can't understand why.
|
||||
{
|
||||
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);
|
||||
|
||||
{
|
||||
ImageExpectations expectations(api);
|
||||
// Grab a screenshot.
|
||||
ScreenshotParams sparams{ kDstTexWidth, kDstTexHeight, "ColorResolve.png" };
|
||||
dumpScreenshot(api, dstRenderTarget, &sparams);
|
||||
|
||||
EXPECT_IMAGE(dstRenderTarget, expectations,
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 0xebfac2ef));
|
||||
// Wait for the ReadPixels result to come back.
|
||||
flushAndWait();
|
||||
|
||||
flushAndWait();
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
TEST_F(BlitTest, Blit2DTextureArray) {
|
||||
TEST_F(BackendTest, Blit2DTextureArray) {
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
Cleanup cleanup(getDriverApi());
|
||||
cleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
api.startCapture(0);
|
||||
mCleanup.addPostCall([&]() { api.stopCapture(0); });
|
||||
cleanup.addPostCall([&]() { api.stopCapture(0); });
|
||||
|
||||
constexpr int kSrcTexWidth = 256;
|
||||
constexpr int kSrcTexHeight = 256;
|
||||
@@ -409,40 +460,33 @@ TEST_F(BlitTest, 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 = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a source texture.
|
||||
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D_ARRAY, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth,
|
||||
kSrcTexHeight, kSrcTexDepth,
|
||||
Handle<HwTexture> srcTexture = cleanup.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 = mCleanup.add(api.createTexture(
|
||||
Handle<HwTexture> dstTexture = cleanup.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 = 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 }, {}, {}));
|
||||
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 }, {}, {}));
|
||||
|
||||
// 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.
|
||||
{
|
||||
@@ -450,24 +494,29 @@ TEST_F(BlitTest, Blit2DTextureArray) {
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// Grab a screenshot.
|
||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "Blit2DTextureArray.png" };
|
||||
{
|
||||
ImageExpectations expectations(api);
|
||||
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
EXPECT_IMAGE(dstRenderTarget, expectations,
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "Blit2DTextureArray",
|
||||
0x8de7d55b));
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
flushAndWait();
|
||||
RenderFrame frame(api);
|
||||
dumpScreenshot(api, dstRenderTarget, ¶ms);
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// 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(BlitTest, BlitRegion) {
|
||||
TEST_F(BackendTest, BlitRegion) {
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
Cleanup cleanup(getDriverApi());
|
||||
cleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
constexpr int kSrcTexWidth = 1024;
|
||||
constexpr int kSrcTexHeight = 1024;
|
||||
@@ -480,48 +529,47 @@ TEST_F(BlitTest, 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 = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a source texture.
|
||||
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> srcTexture = cleanup.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 = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> dstTexture = cleanup.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 =
|
||||
mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR, srcRect.width,
|
||||
srcRect.height, 1, 0, { srcTexture, kSrcLevel, 0 }, {}, {}));
|
||||
cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR, srcRect.width,
|
||||
srcRect.height, 1, 0, {srcTexture, kSrcLevel, 0}, {}, {}));
|
||||
Handle<HwRenderTarget> dstRenderTarget =
|
||||
mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR, kDstTexWidth >> kDstLevel,
|
||||
kDstTexHeight >> kDstLevel, 1, 0, { dstTexture, kDstLevel, 0 }, {}, {}));
|
||||
cleanup.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.
|
||||
@@ -530,25 +578,33 @@ TEST_F(BlitTest, BlitRegion) {
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// Grab a screenshot.
|
||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "BlitRegion.png" };
|
||||
{
|
||||
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();
|
||||
RenderFrame frame(api);
|
||||
dumpScreenshot(api, dstRenderTarget, ¶ms);
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// 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(BlitTest, BlitRegionToSwapChain) {
|
||||
TEST_F(BackendTest, BlitRegionToSwapChain) {
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
Cleanup cleanup(getDriverApi());
|
||||
cleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
constexpr int kSrcTexWidth = 1024;
|
||||
constexpr int kSrcTexHeight = 1024;
|
||||
@@ -558,15 +614,15 @@ TEST_F(BlitTest, BlitRegionToSwapChain) {
|
||||
constexpr int kNumLevels = 3;
|
||||
|
||||
// Create a SwapChain and make it current.
|
||||
auto swapChain = mCleanup.add(createSwapChain());
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
Handle<HwRenderTarget> dstRenderTarget = mCleanup.add(api.createDefaultRenderTarget());
|
||||
Handle<HwRenderTarget> dstRenderTarget = cleanup.add(api.createDefaultRenderTarget());
|
||||
|
||||
// Create a source texture.
|
||||
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> srcTexture = cleanup.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);
|
||||
@@ -574,46 +630,42 @@ TEST_F(BlitTest, BlitRegionToSwapChain) {
|
||||
// Create a RenderTarget for each texture's miplevel.
|
||||
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
|
||||
for (uint8_t level = 0; level < kNumLevels; level++) {
|
||||
srcRenderTargets[level] = mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
|
||||
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {},
|
||||
{}));
|
||||
srcRenderTargets[level] = cleanup.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,
|
||||
};
|
||||
|
||||
{
|
||||
ImageExpectations expectations(api);
|
||||
{
|
||||
{
|
||||
RenderFrame frame(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);
|
||||
|
||||
api.commit(swapChain);
|
||||
}
|
||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "BlitRegionToSwapChain.png" };
|
||||
dumpScreenshot(api, dstRenderTarget, ¶ms);
|
||||
|
||||
// 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();
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// Wait for the ReadPixels result to come back.
|
||||
api.finish();
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include "Lifetimes.h"
|
||||
#include "Shader.h"
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
@@ -80,28 +78,8 @@ 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
|
||||
};
|
||||
|
||||
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) {
|
||||
TEST_F(BackendTest, VertexBufferUpdate) {
|
||||
const bool largeBuffers = false;
|
||||
|
||||
// If updateIndices is true, then even-numbered triangles will have their indices set to
|
||||
@@ -112,15 +90,31 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
|
||||
// executeCommands().
|
||||
{
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// Create a platform-specific SwapChain and make it current.
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
auto swapChain = createSwapChain();
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
Shader shader = createShader();
|
||||
// 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));
|
||||
|
||||
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
|
||||
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);
|
||||
|
||||
// To test large buffers (which exercise a different code path) create an extra large
|
||||
// buffer. Only the first 3 vertices will be used.
|
||||
@@ -134,29 +128,38 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
|
||||
params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
|
||||
PipelineState state;
|
||||
state.program = shader.getProgram();
|
||||
state.pipelineLayout.setLayout[1] = { shader.getDescriptorSetLayout() };
|
||||
state.program = program;
|
||||
state.pipelineLayout.setLayout[1] = { descriptorSetLayout };
|
||||
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 = cleanup.add(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 = api.createBufferObject(sizeof(MaterialParams) + 64,
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::STATIC);
|
||||
|
||||
shader.bindUniform<MaterialParams>(api, ubuffer, kBindingConfig);
|
||||
api.updateDescriptorSetBuffer(descriptorSet, 0, ubuffer, 0, sizeof(MaterialParams) + 64);
|
||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
||||
|
||||
api.startCapture(0);
|
||||
|
||||
// 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 }
|
||||
});
|
||||
// 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);
|
||||
}
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
@@ -199,6 +202,13 @@ TEST_F(BufferUpdatesTest, 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();
|
||||
@@ -206,38 +216,62 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
|
||||
|
||||
// This test renders two triangles in two separate draw calls. Between the draw calls, a uniform
|
||||
// buffer object is partially updated.
|
||||
TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
|
||||
TEST_F(BackendTest, BufferObjectUpdateWithOffset) {
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// Create a platform-specific SwapChain and make it current.
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
auto swapChain = createSwapChain();
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program.
|
||||
Shader shader = createShader();
|
||||
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);
|
||||
|
||||
|
||||
// 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 = cleanup.add(api.createBufferObject(sizeof(MaterialParams) + 64,
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
|
||||
auto ubuffer = api.createBufferObject(sizeof(MaterialParams) + 64,
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::STATIC);
|
||||
|
||||
shader.bindUniform<MaterialParams>(api, ubuffer, kBindingConfig);
|
||||
api.updateDescriptorSetBuffer(descriptorSet, 0, ubuffer, 0, sizeof(MaterialParams) + 64);
|
||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
||||
|
||||
// Create a render target.
|
||||
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}}, {}, {}));
|
||||
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}}, {}, {});
|
||||
|
||||
// Upload uniforms for the first triangle.
|
||||
// Upload the uniform, but with an offset to accommodate the padding in the shader's
|
||||
// uniform definition.
|
||||
shader.uploadUniform(api, ubuffer, kBindingConfig, MaterialParams{
|
||||
{
|
||||
MaterialParams params {
|
||||
.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;
|
||||
@@ -246,27 +280,29 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
|
||||
params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
params.viewport.height = 512;
|
||||
params.viewport.width = 512;
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||
renderTarget, swapChain, shader.getProgram(), params);
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
|
||||
renderTarget, swapChain, program, 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.
|
||||
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 }
|
||||
);
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
params.flags.clear = TargetBufferFlags::NONE;
|
||||
params.flags.discardStart = TargetBufferFlags::NONE;
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||
renderTarget, swapChain, shader.getProgram(), params);
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }},
|
||||
renderTarget, swapChain, program, params);
|
||||
|
||||
static const uint32_t expectedHash = 91322442;
|
||||
readPixelsAndAssertHash(
|
||||
@@ -276,6 +312,14 @@ TEST_F(BufferUpdatesTest, 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();
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include "Lifetimes.h"
|
||||
|
||||
using namespace filament;
|
||||
using namespace filament::backend;
|
||||
|
||||
@@ -25,14 +23,13 @@ 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 = cleanup.add(createSwapChain());
|
||||
auto swapChain = createSwapChain();
|
||||
|
||||
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createDefaultRenderTarget());
|
||||
Handle<HwRenderTarget> renderTarget = api.createDefaultRenderTarget();
|
||||
|
||||
int callbackCountA = 0;
|
||||
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountA](PresentCallable callable) {
|
||||
@@ -82,10 +79,9 @@ TEST_F(BackendTest, FrameScheduledCallback) {
|
||||
|
||||
TEST_F(BackendTest, FrameCompletedCallback) {
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// Create a SwapChain.
|
||||
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||
auto swapChain = api.createSwapChainHeadless(256, 256, 0);
|
||||
|
||||
int callbackCountA = 0;
|
||||
api.setFrameCompletedCallback(swapChain, nullptr,
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include "Lifetimes.h"
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
@@ -130,13 +129,12 @@ 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 = cleanup.add(createSwapChain());
|
||||
auto swapChain = createSwapChain();
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program.
|
||||
@@ -157,10 +155,10 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
{ "test_tex", DescriptorType::SAMPLER, 0 },
|
||||
{ "Params", DescriptorType::UNIFORM_BUFFER, 1 }
|
||||
});
|
||||
program = cleanup.add(api.createProgram(std::move(prog)));
|
||||
program = api.createProgram(std::move(prog));
|
||||
}
|
||||
|
||||
DescriptorSetLayoutHandle descriptorSetLayout = cleanup.add(api.createDescriptorSetLayout({
|
||||
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
|
||||
{{
|
||||
DescriptorType::SAMPLER,
|
||||
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
|
||||
@@ -170,27 +168,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 = cleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage));
|
||||
Handle<HwTexture> const texture = api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage);
|
||||
|
||||
// Create ubo
|
||||
auto ubuffer = cleanup.add(api.createBufferObject(sizeof(MaterialParams),
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
|
||||
auto ubuffer = 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] = cleanup.add(api.createRenderTarget( TargetBufferFlags::COLOR,
|
||||
kTexWidth >> level, kTexHeight >> level, 1, 0, { texture, level, 0 }, {}, {}));
|
||||
renderTargets[level] = api.createRenderTarget( TargetBufferFlags::COLOR,
|
||||
kTexWidth >> level, kTexHeight >> level, 1, 0, { texture, level, 0 }, {}, {});
|
||||
}
|
||||
|
||||
// Fill the base level of the texture with interesting colors.
|
||||
@@ -228,13 +226,12 @@ 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 = passCleanup.add(api.createTextureView(texture, sourceLevel, 1));
|
||||
DescriptorSetHandle descriptorSet = passCleanup.add(api.createDescriptorSet(descriptorSetLayout));
|
||||
auto textureView = api.createTextureView(texture, sourceLevel, 1);
|
||||
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
@@ -250,6 +247,8 @@ 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
|
||||
@@ -257,13 +256,12 @@ 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 = passCleanup.add(api.createTextureView(texture, sourceLevel, 1));
|
||||
DescriptorSetHandle descriptorSet = passCleanup.add(api.createDescriptorSet(descriptorSetLayout));
|
||||
auto textureView = api.createTextureView(texture, sourceLevel, 1);
|
||||
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
@@ -279,6 +277,8 @@ 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,6 +296,15 @@ 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;
|
||||
|
||||
@@ -16,10 +16,9 @@
|
||||
|
||||
#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>
|
||||
@@ -296,12 +295,10 @@ 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 = cleanup.add(createSwapChain());
|
||||
auto swapChain = createSwapChain();
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
|
||||
auto defaultRenderTarget = api.createDefaultRenderTarget(0);
|
||||
|
||||
// Create a program.
|
||||
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
|
||||
@@ -318,21 +315,21 @@ TEST_F(BackendTest, UpdateImage2D) {
|
||||
Program prog = shaderGen.getProgram(api);
|
||||
prog.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
|
||||
|
||||
ProgramHandle const program = cleanup.add(api.createProgram(std::move(prog)));
|
||||
ProgramHandle const program = api.createProgram(std::move(prog));
|
||||
|
||||
DescriptorSetLayoutHandle descriptorSetLayout = cleanup.add(api.createDescriptorSetLayout({
|
||||
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
|
||||
{{
|
||||
DescriptorType::SAMPLER,
|
||||
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
|
||||
DescriptorFlags::NONE, 0
|
||||
}}}));
|
||||
}}});
|
||||
|
||||
DescriptorSetHandle descriptorSet = cleanup.add(api.createDescriptorSet(descriptorSetLayout));
|
||||
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
|
||||
|
||||
// Create a Texture.
|
||||
auto usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
|
||||
Handle<HwTexture> const texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
t.textureFormat, 1, 512, 512, 1u, usage));
|
||||
Handle<HwTexture> const texture = api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
t.textureFormat, 1, 512, 512, 1u, usage);
|
||||
|
||||
// Upload some pixel data.
|
||||
if (t.uploadSubregions) {
|
||||
@@ -362,6 +359,13 @@ 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();
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include "Lifetimes.h"
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
@@ -61,28 +60,25 @@ 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 = cleanup.add(createSwapChain());
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
auto swapChain = createSwapChain();
|
||||
getDriverApi().makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program.
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
|
||||
Program p = shaderGen.getProgram(api);
|
||||
auto program = cleanup.add(api.createProgram(std::move(p)));
|
||||
Program p = shaderGen.getProgram(getDriverApi());
|
||||
auto program = getDriverApi().createProgram(std::move(p));
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
TrianglePrimitive triangle(getDriverApi());
|
||||
|
||||
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
|
||||
auto defaultRenderTarget = getDriverApi().createDefaultRenderTarget(0);
|
||||
|
||||
// Create two Textures.
|
||||
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
|
||||
Handle<HwTexture> textureA = cleanup.add(api.createTexture(
|
||||
Handle<HwTexture> textureA = getDriverApi().createTexture(
|
||||
SamplerType::SAMPLER_2D, // target
|
||||
1, // levels
|
||||
TextureFormat::RGBA8, // format
|
||||
@@ -90,8 +86,8 @@ TEST_F(BackendTest, MRT) {
|
||||
512, // width
|
||||
512, // height
|
||||
1, // depth
|
||||
usage)); // usage
|
||||
Handle<HwTexture> textureB = cleanup.add(api.createTexture(
|
||||
usage); // usage
|
||||
Handle<HwTexture> textureB = getDriverApi().createTexture(
|
||||
SamplerType::SAMPLER_2D, // target
|
||||
1, // levels
|
||||
TextureFormat::RGBA8, // format
|
||||
@@ -99,10 +95,10 @@ TEST_F(BackendTest, MRT) {
|
||||
512, // width
|
||||
512, // height
|
||||
1, // depth
|
||||
usage)); // usage
|
||||
usage); // usage
|
||||
|
||||
// Create a RenderTarget with two attachments.
|
||||
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createRenderTarget(
|
||||
Handle<HwRenderTarget> renderTarget = getDriverApi().createRenderTarget(
|
||||
TargetBufferFlags::COLOR0 | TargetBufferFlags::COLOR1,
|
||||
// The width and height must match the width and height of the respective mip
|
||||
// level (at least for OpenGL).
|
||||
@@ -112,7 +108,7 @@ TEST_F(BackendTest, MRT) {
|
||||
0, // layerCount
|
||||
{{textureA },{textureB }}, // color
|
||||
{}, // depth
|
||||
{})); // stencil
|
||||
{}); // stencil
|
||||
|
||||
RenderPassParams params = {};
|
||||
fullViewport(params);
|
||||
@@ -128,21 +124,25 @@ TEST_F(BackendTest, MRT) {
|
||||
state.rasterState.depthFunc = RasterState::DepthFunc::A;
|
||||
state.rasterState.culling = CullingMode::NONE;
|
||||
|
||||
api.startCapture(0);
|
||||
getDriverApi().startCapture(0);
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
getDriverApi().makeCurrent(swapChain, swapChain);
|
||||
getDriverApi().beginFrame(0, 0, 0);
|
||||
|
||||
// Draw a triangle.
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.endRenderPass();
|
||||
getDriverApi().beginRenderPass(renderTarget, params);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
getDriverApi().endRenderPass();
|
||||
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
getDriverApi().flush();
|
||||
getDriverApi().commit(swapChain);
|
||||
getDriverApi().endFrame(0);
|
||||
|
||||
api.stopCapture(0);
|
||||
getDriverApi().stopCapture(0);
|
||||
|
||||
getDriverApi().destroyProgram(program);
|
||||
getDriverApi().destroySwapChain(swapChain);
|
||||
getDriverApi().destroyRenderTarget(defaultRenderTarget);
|
||||
}
|
||||
|
||||
executeCommands();
|
||||
|
||||
@@ -17,10 +17,9 @@
|
||||
|
||||
#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>
|
||||
@@ -79,13 +78,12 @@ 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 = cleanup.add(createSwapChain());
|
||||
auto swapChain = createSwapChain();
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program that draws only white.
|
||||
@@ -94,7 +92,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 = cleanup.add(api.createProgram(std::move(p)));
|
||||
whiteProgram = api.createProgram(std::move(p));
|
||||
}
|
||||
|
||||
// Create a program that samples a texture.
|
||||
@@ -108,19 +106,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 = cleanup.add(api.createProgram(std::move(p)));
|
||||
textureProgram = api.createProgram(std::move(p));
|
||||
}
|
||||
|
||||
DescriptorSetLayoutHandle descriptorSetLayout = cleanup.add(api.createDescriptorSetLayout({
|
||||
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
|
||||
{{
|
||||
DescriptorType::SAMPLER,
|
||||
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
|
||||
DescriptorFlags::NONE, 0
|
||||
}}}));
|
||||
}}});
|
||||
|
||||
DescriptorSetHandle descriptorSet[2];
|
||||
descriptorSet[0] = cleanup.add(api.createDescriptorSet(descriptorSetLayout));
|
||||
descriptorSet[1] = cleanup.add(api.createDescriptorSet(descriptorSetLayout));
|
||||
descriptorSet[0] = api.createDescriptorSet(descriptorSetLayout);
|
||||
descriptorSet[1] = api.createDescriptorSet(descriptorSetLayout);
|
||||
|
||||
// Create a texture that has 4 mip levels. Each level is a different color.
|
||||
// Level 0: 128x128 (red)
|
||||
@@ -129,9 +127,9 @@ TEST_F(BackendTest, TextureViewLod) {
|
||||
// Level 3: 16x16 (yellow)
|
||||
const size_t kTextureSize = 128;
|
||||
const size_t kMipLevels = 4;
|
||||
Handle<HwTexture> texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, kMipLevels,
|
||||
Handle<HwTexture> texture = 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;
|
||||
@@ -165,13 +163,13 @@ TEST_F(BackendTest, TextureViewLod) {
|
||||
// Level 1: 64x64 (green) <-- base
|
||||
// Level 2: 32x32 (blue) <--- white triangle rendered
|
||||
// Level 3: 16x16 (yellow) <-- max
|
||||
auto texture13 = cleanup.add(api.createTextureView(texture, 1, 3));
|
||||
auto texture13 = 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 = cleanup.add(api.createRenderTarget(
|
||||
Handle<HwRenderTarget> renderTarget = api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR, 32, 32, 1, 0,
|
||||
{texture, 2 /* level */, 0 /* layer */}, {}, {}));
|
||||
{texture, 2 /* level */, 0 /* layer */}, {}, {});
|
||||
{
|
||||
RenderPassParams params = {};
|
||||
fullViewport(params);
|
||||
@@ -187,7 +185,7 @@ TEST_F(BackendTest, TextureViewLod) {
|
||||
api.endRenderPass();
|
||||
}
|
||||
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = api.createDefaultRenderTarget(0);
|
||||
|
||||
RenderPassParams params = {};
|
||||
fullViewport(params);
|
||||
@@ -219,7 +217,7 @@ TEST_F(BackendTest, TextureViewLod) {
|
||||
api.endRenderPass();
|
||||
|
||||
// Adjust the base mip to 2.
|
||||
auto texture22 = cleanup.add(api.createTextureView(texture, 2, 2));
|
||||
auto texture22 = api.createTextureView(texture, 2, 2);
|
||||
|
||||
api.updateDescriptorSetTexture(descriptorSet[1], 0, texture22, {
|
||||
.filterMag = SamplerMagFilter::NEAREST,
|
||||
@@ -246,6 +244,18 @@ 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,7 +16,6 @@
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include "Lifetimes.h"
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
@@ -71,20 +70,18 @@ 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 = cleanup.add(createSwapChain());
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
auto swapChain = createSwapChain();
|
||||
getDriverApi().makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program.
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
|
||||
Program p = shaderGen.getProgram(api);
|
||||
auto program = cleanup.add(api.createProgram(std::move(p)));
|
||||
Program p = shaderGen.getProgram(getDriverApi());
|
||||
auto program = getDriverApi().createProgram(std::move(p));
|
||||
|
||||
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
|
||||
auto defaultRenderTarget = getDriverApi().createDefaultRenderTarget(0);
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
TrianglePrimitive triangle(getDriverApi());
|
||||
|
||||
RenderPassParams params = {};
|
||||
fullViewport(params);
|
||||
@@ -100,21 +97,25 @@ TEST_F(BackendTest, MissingRequiredAttributes) {
|
||||
state.rasterState.depthFunc = RasterState::DepthFunc::A;
|
||||
state.rasterState.culling = CullingMode::NONE;
|
||||
|
||||
api.startCapture(0);
|
||||
getDriverApi().startCapture(0);
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
getDriverApi().makeCurrent(swapChain, swapChain);
|
||||
getDriverApi().beginFrame(0, 0, 0);
|
||||
|
||||
// Render a triangle.
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.endRenderPass();
|
||||
getDriverApi().beginRenderPass(defaultRenderTarget, params);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
getDriverApi().endRenderPass();
|
||||
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
getDriverApi().flush();
|
||||
getDriverApi().commit(swapChain);
|
||||
getDriverApi().endFrame(0);
|
||||
|
||||
api.stopCapture(0);
|
||||
getDriverApi().stopCapture(0);
|
||||
|
||||
getDriverApi().destroyProgram(program);
|
||||
getDriverApi().destroySwapChain(swapChain);
|
||||
getDriverApi().destroyRenderTarget(defaultRenderTarget);
|
||||
}
|
||||
|
||||
executeCommands();
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include "Lifetimes.h"
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
@@ -81,21 +80,20 @@ TEST_F(BackendTest, PushConstants) {
|
||||
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 = cleanup.add(createSwapChain());
|
||||
auto swapChain = createSwapChain();
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program.
|
||||
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
|
||||
Program p = shaderGen.getProgram(api);
|
||||
ProgramHandle program = cleanup.add(api.createProgram(std::move(p)));
|
||||
ProgramHandle program = api.createProgram(std::move(p));
|
||||
|
||||
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createDefaultRenderTarget());
|
||||
Handle<HwRenderTarget> renderTarget = api.createDefaultRenderTarget();
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
|
||||
@@ -152,6 +150,10 @@ TEST_F(BackendTest, PushConstants) {
|
||||
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// Cleanup.
|
||||
api.destroySwapChain(swapChain);
|
||||
api.destroyRenderTarget(renderTarget);
|
||||
}
|
||||
|
||||
api.stopCapture(0);
|
||||
|
||||
@@ -16,10 +16,9 @@
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include "BackendTestUtils.h"
|
||||
#include "Lifetimes.h"
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
#include "BackendTestUtils.h"
|
||||
|
||||
#include <utils/Hash.h>
|
||||
|
||||
@@ -237,20 +236,17 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
|
||||
TestCase const testCases[] = { t0, t2, t3, t4, t5, t6, t7, t8 };
|
||||
|
||||
DriverApi& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// Create programs.
|
||||
Handle<HwProgram> programFloat, programUint;
|
||||
{
|
||||
ShaderGenerator shaderGen(vertex, fragmentFloat, sBackend, sIsMobilePlatform);
|
||||
Program p = shaderGen.getProgram(api);
|
||||
programFloat = cleanup.add(api.createProgram(std::move(p)));
|
||||
Program p = shaderGen.getProgram(getDriverApi());
|
||||
programFloat = getDriverApi().createProgram(std::move(p));
|
||||
}
|
||||
{
|
||||
ShaderGenerator shaderGen(vertex, fragmentUint, sBackend, sIsMobilePlatform);
|
||||
Program p = shaderGen.getProgram(api);
|
||||
programUint = cleanup.add(api.createProgram(std::move(p)));
|
||||
Program p = shaderGen.getProgram(getDriverApi());
|
||||
programUint = getDriverApi().createProgram(std::move(p));
|
||||
}
|
||||
|
||||
|
||||
@@ -259,35 +255,35 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
// Create a platform-specific SwapChain and make it current.
|
||||
Handle<HwSwapChain> swapChain;
|
||||
if (t.useDefaultRT) {
|
||||
swapChain = cleanup.add(createSwapChain());
|
||||
swapChain = createSwapChain();
|
||||
} else {
|
||||
swapChain = cleanup.add(api.createSwapChainHeadless(t.getRenderTargetSize(),
|
||||
t.getRenderTargetSize(), 0));
|
||||
swapChain = getDriverApi().createSwapChainHeadless(t.getRenderTargetSize(),
|
||||
t.getRenderTargetSize(), 0);
|
||||
}
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
getDriverApi().makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a Texture and RenderTarget to render into.
|
||||
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
|
||||
Handle<HwTexture> const texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
Handle<HwTexture> const texture = getDriverApi().createTexture(SamplerType::SAMPLER_2D,
|
||||
t.mipLevels, t.textureFormat, 1, renderTargetBaseSize, renderTargetBaseSize, 1,
|
||||
usage));
|
||||
usage);
|
||||
|
||||
Handle<HwRenderTarget> renderTarget;
|
||||
if (t.useDefaultRT) {
|
||||
// The width and height must match the width and height of the respective mip
|
||||
// level (at least for OpenGL).
|
||||
renderTarget = cleanup.add(api.createDefaultRenderTarget());
|
||||
renderTarget = getDriverApi().createDefaultRenderTarget();
|
||||
} else {
|
||||
// The width and height must match the width and height of the respective mip
|
||||
// level (at least for OpenGL).
|
||||
renderTarget = cleanup.add(api.createRenderTarget(
|
||||
renderTarget = getDriverApi().createRenderTarget(
|
||||
TargetBufferFlags::COLOR, t.getRenderTargetSize(),
|
||||
t.getRenderTargetSize(), t.samples, 0, {{ texture, uint8_t(t.mipLevel) }}, {},
|
||||
{}));
|
||||
{});
|
||||
}
|
||||
|
||||
TrianglePrimitive const triangle(api);
|
||||
TrianglePrimitive const triangle(getDriverApi());
|
||||
|
||||
RenderPassParams params = {};
|
||||
fullViewport(params);
|
||||
@@ -298,11 +294,11 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
params.viewport.height = t.getRenderTargetSize();
|
||||
params.viewport.width = t.getRenderTargetSize();
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
getDriverApi().makeCurrent(swapChain, swapChain);
|
||||
getDriverApi().beginFrame(0, 0, 0);
|
||||
|
||||
// Render a white triangle over blue.
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
getDriverApi().beginRenderPass(renderTarget, params);
|
||||
|
||||
PipelineState state;
|
||||
state.program = programFloat;
|
||||
@@ -313,21 +309,21 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
state.rasterState.depthWrite = false;
|
||||
state.rasterState.depthFunc = RasterState::DepthFunc::A;
|
||||
state.rasterState.culling = CullingMode::NONE;
|
||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
|
||||
api.endRenderPass();
|
||||
getDriverApi().endRenderPass();
|
||||
|
||||
if (t.mipLevel > 0) {
|
||||
Cleanup localCleanup(api);
|
||||
// Render red to the first mip level to check that the backend is actually reading the
|
||||
// correct mip.
|
||||
RenderPassParams p = params;
|
||||
Handle<HwRenderTarget> mipLevelOneRT = localCleanup.add(api.createRenderTarget(
|
||||
Handle<HwRenderTarget> mipLevelOneRT = getDriverApi().createRenderTarget(
|
||||
TargetBufferFlags::COLOR, renderTargetBaseSize, renderTargetBaseSize, 1, 0,
|
||||
{{ texture }}, {}, {}));
|
||||
{{ texture }}, {}, {});
|
||||
p.clearColor = {1.f, 0.f, 0.f, 1.f};
|
||||
api.beginRenderPass(mipLevelOneRT, p);
|
||||
api.endRenderPass();
|
||||
getDriverApi().beginRenderPass(mipLevelOneRT, p);
|
||||
getDriverApi().endRenderPass();
|
||||
getDriverApi().destroyRenderTarget(mipLevelOneRT);
|
||||
}
|
||||
|
||||
// Read pixels.
|
||||
@@ -351,19 +347,26 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
free(buffer);
|
||||
}, (void*) &t);
|
||||
|
||||
api.readPixels(renderTarget, t.readRect.x, t.readRect.y, t.readRect.width,
|
||||
getDriverApi().readPixels(renderTarget, t.readRect.x, t.readRect.y, t.readRect.width,
|
||||
t.readRect.height, std::move(descriptor));
|
||||
|
||||
// Now render red over what was just rendered. This ensures that readPixels captures the
|
||||
// state of rendering between render passes.
|
||||
params.clearColor = {1.f, 0.f, 0.f, 1.f};
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
api.endRenderPass();
|
||||
getDriverApi().beginRenderPass(renderTarget, params);
|
||||
getDriverApi().endRenderPass();
|
||||
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
getDriverApi().commit(swapChain);
|
||||
getDriverApi().endFrame(0);
|
||||
|
||||
getDriverApi().destroySwapChain(swapChain);
|
||||
getDriverApi().destroyRenderTarget(renderTarget);
|
||||
getDriverApi().destroyTexture(texture);
|
||||
}
|
||||
|
||||
getDriverApi().destroyProgram(programFloat);
|
||||
getDriverApi().destroyProgram(programUint);
|
||||
|
||||
// This ensures all driver commands have finished before exiting the test.
|
||||
flushAndWait();
|
||||
}
|
||||
@@ -372,21 +375,18 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
|
||||
const size_t renderTargetSize = 2000;
|
||||
const int iterationCount = 100;
|
||||
|
||||
DriverApi& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// Create a platform-specific SwapChain and make it current.
|
||||
auto swapChain = cleanup.add(api.createSwapChainHeadless(renderTargetSize, renderTargetSize, 0));
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
auto swapChain = getDriverApi().createSwapChainHeadless(renderTargetSize, renderTargetSize, 0);
|
||||
getDriverApi().makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program.
|
||||
ShaderGenerator shaderGen(vertex, fragmentFloat, sBackend, sIsMobilePlatform);
|
||||
Program p = shaderGen.getProgram(api);
|
||||
auto program = cleanup.add(api.createProgram(std::move(p)));
|
||||
Program p = shaderGen.getProgram(getDriverApi());
|
||||
auto program = getDriverApi().createProgram(std::move(p));
|
||||
|
||||
// Create a Texture and RenderTarget to render into.
|
||||
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
|
||||
Handle<HwTexture> texture = cleanup.add(api.createTexture(
|
||||
Handle<HwTexture> texture = getDriverApi().createTexture(
|
||||
SamplerType::SAMPLER_2D, // target
|
||||
1, // levels
|
||||
TextureFormat::RGBA8, // format
|
||||
@@ -394,9 +394,9 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
|
||||
renderTargetSize, // width
|
||||
renderTargetSize, // height
|
||||
1, // depth
|
||||
usage)); // usage
|
||||
usage); // usage
|
||||
|
||||
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createRenderTarget(
|
||||
Handle<HwRenderTarget> renderTarget = getDriverApi().createRenderTarget(
|
||||
TargetBufferFlags::COLOR,
|
||||
renderTargetSize, // width
|
||||
renderTargetSize, // height
|
||||
@@ -404,9 +404,9 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
|
||||
0, // layerCount
|
||||
{{ texture }}, // color
|
||||
{}, // depth
|
||||
{})); // stencil
|
||||
{}); // stencil
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
TrianglePrimitive triangle(getDriverApi());
|
||||
|
||||
RenderPassParams params = {};
|
||||
fullViewport(params);
|
||||
@@ -433,13 +433,13 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
|
||||
printf("Executing test %d / %d\n", iteration, iterationCount);
|
||||
}
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
getDriverApi().makeCurrent(swapChain, swapChain);
|
||||
getDriverApi().beginFrame(0, 0, 0);
|
||||
|
||||
// Render some content, just so we don't read back uninitialized data.
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.endRenderPass();
|
||||
getDriverApi().beginRenderPass(renderTarget, params);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
getDriverApi().endRenderPass();
|
||||
|
||||
PixelBufferDescriptor descriptor(buffer, renderTargetSize * renderTargetSize * 4,
|
||||
PixelDataFormat::RGBA, PixelDataType::UBYTE, 1, 0, 0, renderTargetSize,
|
||||
@@ -448,9 +448,9 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
|
||||
test->readPixelsFinished = true;
|
||||
}, this);
|
||||
|
||||
api.readPixels(renderTarget, 0, 0, renderTargetSize, renderTargetSize, std::move(descriptor));
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
getDriverApi().readPixels(renderTarget, 0, 0, renderTargetSize, renderTargetSize, std::move(descriptor));
|
||||
getDriverApi().commit(swapChain);
|
||||
getDriverApi().endFrame(0);
|
||||
|
||||
flushAndWait();
|
||||
getDriver().purge();
|
||||
@@ -460,7 +460,11 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
|
||||
|
||||
free(buffer);
|
||||
|
||||
api.finish();
|
||||
getDriverApi().destroyProgram(program);
|
||||
getDriverApi().destroySwapChain(swapChain);
|
||||
getDriverApi().destroyRenderTarget(renderTarget);
|
||||
getDriverApi().destroyTexture(texture);
|
||||
getDriverApi().finish();
|
||||
executeCommands();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include "Lifetimes.h"
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
@@ -67,11 +66,10 @@ using namespace filament::backend;
|
||||
// Rendering an external image without setting any data should not crash.
|
||||
TEST_F(BackendTest, RenderExternalImageWithoutSet) {
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
auto swapChain = createSwapChain();
|
||||
|
||||
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
|
||||
SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, Precision::HIGH, false };
|
||||
@@ -84,22 +82,22 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
|
||||
// Create a program that samples a texture.
|
||||
Program p = shaderGen.getProgram(api);
|
||||
p.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
|
||||
backend::Handle<HwProgram> program = cleanup.add(api.createProgram(std::move(p)));
|
||||
DescriptorSetLayoutHandle descriptorSetLayout = cleanup.add(api.createDescriptorSetLayout({
|
||||
backend::Handle<HwProgram> program = api.createProgram(std::move(p));
|
||||
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
|
||||
{{
|
||||
DescriptorType::SAMPLER,
|
||||
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
|
||||
DescriptorFlags::NONE, 0
|
||||
}}}));
|
||||
}}});
|
||||
|
||||
DescriptorSetHandle descriptorSet = cleanup.add(api.createDescriptorSet(descriptorSetLayout));
|
||||
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
|
||||
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = api.createDefaultRenderTarget(0);
|
||||
|
||||
// Create a texture that will be backed by an external image.
|
||||
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
|
||||
const NativeView& view = getNativeView();
|
||||
backend::Handle<HwTexture> texture = cleanup.add(api.createTexture(
|
||||
backend::Handle<HwTexture> texture = api.createTexture(
|
||||
SamplerType::SAMPLER_EXTERNAL, // target
|
||||
1, // levels
|
||||
TextureFormat::RGBA8, // format
|
||||
@@ -107,7 +105,7 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
|
||||
view.width, // width
|
||||
view.height, // height
|
||||
1, // depth
|
||||
usage)); // usage
|
||||
usage); // usage
|
||||
|
||||
RenderPassParams params = {};
|
||||
fullViewport(params);
|
||||
@@ -142,6 +140,15 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
|
||||
|
||||
api.stopCapture(0);
|
||||
|
||||
// Delete our resources.
|
||||
api.destroyDescriptorSet(descriptorSet);
|
||||
api.destroyDescriptorSetLayout(descriptorSetLayout);
|
||||
api.destroyTexture(texture);
|
||||
|
||||
// Destroy frame resources.
|
||||
api.destroyProgram(program);
|
||||
api.destroyRenderTarget(defaultRenderTarget);
|
||||
|
||||
api.finish();
|
||||
|
||||
executeCommands();
|
||||
@@ -149,11 +156,10 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
|
||||
|
||||
TEST_F(BackendTest, RenderExternalImage) {
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
auto swapChain = createSwapChain();
|
||||
|
||||
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
|
||||
SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, Precision::HIGH, false };
|
||||
@@ -166,18 +172,18 @@ TEST_F(BackendTest, RenderExternalImage) {
|
||||
// Create a program that samples a texture.
|
||||
Program p = shaderGen.getProgram(api);
|
||||
p.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}});
|
||||
auto program = cleanup.add(api.createProgram(std::move(p)));
|
||||
auto program = api.createProgram(std::move(p));
|
||||
|
||||
DescriptorSetLayoutHandle descriptorSetLayout = cleanup.add(api.createDescriptorSetLayout({
|
||||
DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({
|
||||
{{
|
||||
DescriptorType::SAMPLER,
|
||||
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0,
|
||||
DescriptorFlags::NONE, 0
|
||||
}}}));
|
||||
}}});
|
||||
|
||||
DescriptorSetHandle descriptorSet = cleanup.add(api.createDescriptorSet(descriptorSetLayout));
|
||||
DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout);
|
||||
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = api.createDefaultRenderTarget(0);
|
||||
|
||||
// require users to create two Filament textures and have two material parameters
|
||||
// add a "plane" parameter to setExternalImage
|
||||
@@ -221,8 +227,8 @@ TEST_F(BackendTest, RenderExternalImage) {
|
||||
|
||||
api.setupExternalImage(pixBuffer);
|
||||
backend::Handle<HwTexture> texture =
|
||||
cleanup.add(api.createTextureExternalImage(SamplerType::SAMPLER_EXTERNAL,
|
||||
TextureFormat::RGBA8, 1024, 1024, usage, pixBuffer));
|
||||
api.createTextureExternalImage(SamplerType::SAMPLER_EXTERNAL,
|
||||
TextureFormat::RGBA8, 1024, 1024, usage, pixBuffer);
|
||||
|
||||
// We're now free to release the buffer.
|
||||
CVBufferRelease(pixBuffer);
|
||||
@@ -263,6 +269,15 @@ TEST_F(BackendTest, RenderExternalImage) {
|
||||
|
||||
api.stopCapture(0);
|
||||
|
||||
// Delete our resources.
|
||||
api.destroyDescriptorSet(descriptorSet);
|
||||
api.destroyDescriptorSetLayout(descriptorSetLayout);
|
||||
api.destroyTexture(texture);
|
||||
|
||||
// Destroy frame resources.
|
||||
api.destroyProgram(program);
|
||||
api.destroyRenderTarget(defaultRenderTarget);
|
||||
|
||||
api.finish();
|
||||
|
||||
executeCommands();
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include "Lifetimes.h"
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
@@ -56,7 +55,6 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
constexpr int kSrcRtHeight = 384;
|
||||
|
||||
api.startCapture(0);
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// color texture (mip level 1) 512x512 depth texture (mip level 0) 512x512
|
||||
// +----------------------------------------+ +------------------------------------------+
|
||||
@@ -82,21 +80,21 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
// executeCommands().
|
||||
{
|
||||
// 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 = api.createSwapChainHeadless(256, 256, 0);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program.
|
||||
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
|
||||
Program p = shaderGen.getProgram(api);
|
||||
ProgramHandle program = cleanup.add(api.createProgram(std::move(p)));
|
||||
ProgramHandle program = api.createProgram(std::move(p));
|
||||
|
||||
// Create source color and depth textures.
|
||||
Handle<HwTexture> srcTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, kNumLevels,
|
||||
Handle<HwTexture> srcTexture = api.createTexture(SamplerType::SAMPLER_2D, kNumLevels,
|
||||
kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> depthTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT);
|
||||
Handle<HwTexture> depthTexture = api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::DEPTH16, 1, 512, 512, 1,
|
||||
TextureUsage::DEPTH_ATTACHMENT));
|
||||
TextureUsage::DEPTH_ATTACHMENT);
|
||||
|
||||
// Render into the bottom-left quarter of the texture.
|
||||
Viewport srcRect = {
|
||||
@@ -114,13 +112,13 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
|
||||
// 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(
|
||||
Handle<HwRenderTarget> srcRenderTarget = api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, kSrcRtHeight, kSrcRtHeight, 1, 0,
|
||||
{srcTexture, kSrcLevel, 0}, {depthTexture, 0, 0}, {}));
|
||||
{srcTexture, kSrcLevel, 0}, {depthTexture, 0, 0}, {});
|
||||
|
||||
Handle<HwRenderTarget> fullRenderTarget = cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
|
||||
Handle<HwRenderTarget> fullRenderTarget = api.createRenderTarget(TargetBufferFlags::COLOR,
|
||||
kSrcTexHeight >> kSrcLevel, kSrcTexWidth >> kSrcLevel, 1, 0,
|
||||
{srcTexture, kSrcLevel, 0}, {}, {}));
|
||||
{srcTexture, kSrcLevel, 0}, {}, {});
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
|
||||
@@ -152,6 +150,11 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
api.endFrame(0);
|
||||
|
||||
api.stopCapture(0);
|
||||
|
||||
// Cleanup.
|
||||
api.destroyTexture(srcTexture);
|
||||
api.destroySwapChain(swapChain);
|
||||
api.destroyRenderTarget(srcRenderTarget);
|
||||
}
|
||||
|
||||
// Wait for the ReadPixels result to come back.
|
||||
@@ -166,24 +169,23 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
|
||||
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. We don't really use it so the res doesn't matter.
|
||||
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||
auto swapChain = api.createSwapChainHeadless(256, 256, 0);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program.
|
||||
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
|
||||
Program p = shaderGen.getProgram(api);
|
||||
ProgramHandle program = cleanup.add(api.createProgram(std::move(p)));
|
||||
ProgramHandle program = api.createProgram(std::move(p));
|
||||
|
||||
// Create a source color textures.
|
||||
Handle<HwTexture> srcTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
Handle<HwTexture> srcTexture = api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 512, 512, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT);
|
||||
|
||||
// Render into the bottom-left quarter of the texture, checking 3 special cases.
|
||||
// 1. negative viewport left/bottom
|
||||
@@ -205,9 +207,9 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
|
||||
Viewport scissor = {0, 0, (uint32_t)std::numeric_limits<int32_t>::max(),
|
||||
(uint32_t)std::numeric_limits<int32_t>::max()};
|
||||
|
||||
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createRenderTarget(
|
||||
Handle<HwRenderTarget> renderTarget = api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR, 512, 512, 1, 0,
|
||||
{srcTexture, 0, 0}, {}, {}));
|
||||
{srcTexture, 0, 0}, {}, {});
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
|
||||
@@ -247,6 +249,11 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
|
||||
api.endFrame(0);
|
||||
|
||||
api.stopCapture(0);
|
||||
|
||||
// Cleanup.
|
||||
api.destroyTexture(srcTexture);
|
||||
api.destroySwapChain(swapChain);
|
||||
api.destroyRenderTarget(renderTarget);
|
||||
}
|
||||
|
||||
// Wait for the ReadPixels result to come back.
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include "Lifetimes.h"
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
@@ -64,23 +63,20 @@ namespace test {
|
||||
class BasicStencilBufferTest : public BackendTest {
|
||||
public:
|
||||
|
||||
Handle<HwSwapChain> mSwapChain;
|
||||
ProgramHandle mProgram;
|
||||
Cleanup mCleanup;
|
||||
|
||||
BasicStencilBufferTest() : mCleanup(getDriverApi()) {}
|
||||
Handle<HwSwapChain> swapChain;
|
||||
ProgramHandle program;
|
||||
|
||||
void SetUp() override {
|
||||
auto& api = getDriverApi();
|
||||
|
||||
// Create a platform-specific SwapChain and make it current.
|
||||
mSwapChain = mCleanup.add(createSwapChain());
|
||||
api.makeCurrent(mSwapChain, mSwapChain);
|
||||
swapChain = createSwapChain();
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program.
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
|
||||
Program p = shaderGen.getProgram(api);
|
||||
mProgram = mCleanup.add(api.createProgram(std::move(p)));
|
||||
program = api.createProgram(std::move(p));
|
||||
}
|
||||
|
||||
void RunTest(Handle<HwRenderTarget> renderTarget) {
|
||||
@@ -107,13 +103,13 @@ public:
|
||||
params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
|
||||
PipelineState ps = {};
|
||||
ps.program = mProgram;
|
||||
ps.program = program;
|
||||
ps.rasterState.colorWrite = false;
|
||||
ps.rasterState.depthWrite = false;
|
||||
ps.stencilState.stencilWrite = true;
|
||||
ps.stencilState.front.stencilOpDepthStencilPass = StencilOperation::INCR;
|
||||
|
||||
api.makeCurrent(mSwapChain, mSwapChain);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
@@ -133,27 +129,29 @@ public:
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
api.commit(mSwapChain);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
auto& api = getDriverApi();
|
||||
api.destroyProgram(program);
|
||||
api.destroySwapChain(swapChain);
|
||||
flushAndWait();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BasicStencilBufferTest, StencilBuffer) {
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// Create two textures: a color and a stencil, and an associated RenderTarget.
|
||||
auto colorTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT));
|
||||
auto stencilTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::STENCIL8, 1, 512, 512, 1, TextureUsage::STENCIL_ATTACHMENT));
|
||||
auto renderTarget = cleanup.add(api.createRenderTarget(
|
||||
auto colorTexture = api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT);
|
||||
auto stencilTexture = api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::STENCIL8, 1, 512, 512, 1, TextureUsage::STENCIL_ATTACHMENT);
|
||||
auto renderTarget = getDriverApi().createRenderTarget(
|
||||
TargetBufferFlags::COLOR0 | TargetBufferFlags::STENCIL, 512, 512, 1, 0,
|
||||
{{colorTexture}}, {}, {{stencilTexture}}));
|
||||
{{colorTexture}}, {}, {{stencilTexture}});
|
||||
|
||||
RunTest(renderTarget);
|
||||
|
||||
@@ -161,20 +159,23 @@ TEST_F(BasicStencilBufferTest, StencilBuffer) {
|
||||
|
||||
flushAndWait();
|
||||
getDriver().purge();
|
||||
|
||||
api.destroyTexture(colorTexture);
|
||||
api.destroyTexture(stencilTexture);
|
||||
api.destroyRenderTarget(renderTarget);
|
||||
}
|
||||
|
||||
TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// Create two textures: a color and a stencil, and an associated RenderTarget.
|
||||
auto colorTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT));
|
||||
auto depthStencilTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::DEPTH24_STENCIL8, 1, 512, 512, 1, TextureUsage::STENCIL_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT));
|
||||
auto renderTarget = cleanup.add(api.createRenderTarget(
|
||||
auto colorTexture = api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT);
|
||||
auto depthStencilTexture = api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::DEPTH24_STENCIL8, 1, 512, 512, 1, TextureUsage::STENCIL_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT);
|
||||
auto renderTarget = getDriverApi().createRenderTarget(
|
||||
TargetBufferFlags::COLOR0 | TargetBufferFlags::STENCIL, 512, 512, 1, 0,
|
||||
{{colorTexture}}, {depthStencilTexture}, {{depthStencilTexture}}));
|
||||
{{colorTexture}}, {depthStencilTexture}, {{depthStencilTexture}});
|
||||
|
||||
RunTest(renderTarget);
|
||||
|
||||
@@ -182,27 +183,30 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
|
||||
|
||||
flushAndWait();
|
||||
getDriver().purge();
|
||||
|
||||
api.destroyTexture(colorTexture);
|
||||
api.destroyTexture(depthStencilTexture);
|
||||
api.destroyRenderTarget(renderTarget);
|
||||
}
|
||||
|
||||
TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// Create two textures: a single-sampled color and a MSAA stencil texture.
|
||||
// We also create two RenderTargets, one for each pass:
|
||||
// Pass 0: Render a triangle only into the MSAA stencil buffer.
|
||||
// Pass 1: Render a triangle into (an auto-created) MSAA color buffer using the stencil test.
|
||||
// Performs an auto-resolve on the color.
|
||||
auto colorTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE));
|
||||
auto depthStencilTextureMSAA = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::DEPTH24_STENCIL8, 4, 512, 512, 1, TextureUsage::STENCIL_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT));
|
||||
auto renderTarget0 = cleanup.add(api.createRenderTarget(
|
||||
auto colorTexture = api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE);
|
||||
auto depthStencilTextureMSAA = api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::DEPTH24_STENCIL8, 4, 512, 512, 1, TextureUsage::STENCIL_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT);
|
||||
auto renderTarget0 = getDriverApi().createRenderTarget(
|
||||
TargetBufferFlags::DEPTH_AND_STENCIL, 512, 512, 4, 0,
|
||||
{{}}, {depthStencilTextureMSAA}, {depthStencilTextureMSAA}));
|
||||
auto renderTarget1 = cleanup.add(api.createRenderTarget(
|
||||
{{}}, {depthStencilTextureMSAA}, {depthStencilTextureMSAA});
|
||||
auto renderTarget1 = getDriverApi().createRenderTarget(
|
||||
TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH_AND_STENCIL, 512, 512, 4, 0,
|
||||
{{colorTexture}}, {depthStencilTextureMSAA}, {depthStencilTextureMSAA}));
|
||||
{{colorTexture}}, {depthStencilTextureMSAA}, {depthStencilTextureMSAA});
|
||||
|
||||
api.startCapture(0);
|
||||
|
||||
@@ -226,13 +230,13 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
||||
params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
|
||||
PipelineState ps = {};
|
||||
ps.program = mProgram;
|
||||
ps.program = program;
|
||||
ps.rasterState.colorWrite = false;
|
||||
ps.rasterState.depthWrite = false;
|
||||
ps.stencilState.stencilWrite = true;
|
||||
ps.stencilState.front.stencilOpDepthStencilPass = StencilOperation::INCR;
|
||||
|
||||
api.makeCurrent(mSwapChain, mSwapChain);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
|
||||
api.beginRenderPass(renderTarget0, params);
|
||||
@@ -254,7 +258,7 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
api.commit(mSwapChain);
|
||||
api.commit(swapChain);
|
||||
api.stopCapture(0);
|
||||
api.endFrame(0);
|
||||
|
||||
@@ -262,6 +266,11 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
||||
|
||||
flushAndWait();
|
||||
getDriver().purge();
|
||||
|
||||
api.destroyTexture(colorTexture);
|
||||
api.destroyTexture(depthStencilTextureMSAA);
|
||||
api.destroyRenderTarget(renderTarget0);
|
||||
api.destroyRenderTarget(renderTarget1);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <backend/BufferDescriptor.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/StaticString.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
@@ -91,21 +90,9 @@ public:
|
||||
* @param name A string to identify this BufferObject
|
||||
* @param len Length of name, should be less than or equal to 128
|
||||
* @return This Builder, for chaining calls.
|
||||
* @deprecated Use name(utils::StaticString const&) instead.
|
||||
*/
|
||||
UTILS_DEPRECATED
|
||||
Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this BufferObject for debugging purposes.
|
||||
*
|
||||
* name will show in error messages and should be kept as short as possible.
|
||||
*
|
||||
* @param name A string literal to identify this BufferObject
|
||||
* @return This Builder, for chaining calls.
|
||||
*/
|
||||
Builder& name(utils::StaticString const& name) noexcept;
|
||||
|
||||
/**
|
||||
* Creates the BufferObject and returns a pointer to it. After creation, the buffer
|
||||
* object is uninitialized. Use BufferObject::setBuffer() to initialize it.
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/PrivateImplementation.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/StaticString.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -62,17 +61,11 @@ UTILS_PUBLIC void builderMakeName(utils::CString& outName, const char* name, siz
|
||||
template <typename Builder>
|
||||
class UTILS_PUBLIC BuilderNameMixin {
|
||||
public:
|
||||
UTILS_DEPRECATED
|
||||
Builder& name(const char* name, size_t len) noexcept {
|
||||
builderMakeName(mName, name, len);
|
||||
return static_cast<Builder&>(*this);
|
||||
}
|
||||
|
||||
Builder& name(utils::StaticString const& name) noexcept {
|
||||
builderMakeName(mName, name.data(), name.size());
|
||||
return static_cast<Builder&>(*this);
|
||||
}
|
||||
|
||||
utils::CString const& getName() const noexcept { return mName; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <backend/BufferDescriptor.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/StaticString.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
@@ -96,21 +95,9 @@ public:
|
||||
* @param name A string to identify this IndexBuffer
|
||||
* @param len Length of name, should be less than or equal to 128
|
||||
* @return This Builder, for chaining calls.
|
||||
* @deprecated Use name(utils::StaticString const&) instead.
|
||||
*/
|
||||
UTILS_DEPRECATED
|
||||
Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this IndexBuffer for debugging purposes.
|
||||
*
|
||||
* name will show in error messages and should be kept as short as possible.
|
||||
*
|
||||
* @param name A string literal to identify this IndexBuffer
|
||||
* @return This Builder, for chaining calls.
|
||||
*/
|
||||
Builder& name(utils::StaticString const& name) noexcept;
|
||||
|
||||
/**
|
||||
* Creates the IndexBuffer object and returns a pointer to it. After creation, the index
|
||||
* buffer is uninitialized. Use IndexBuffer::setBuffer() to initialize the IndexBuffer.
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <filament/Engine.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/StaticString.h>
|
||||
|
||||
#include <math/mathfwd.h>
|
||||
|
||||
@@ -83,21 +82,9 @@ public:
|
||||
* @param name A string to identify this InstanceBuffer
|
||||
* @param len Length of name, should be less than or equal to 128
|
||||
* @return This Builder, for chaining calls.
|
||||
* @deprecated Use name(utils::StaticString const&) instead.
|
||||
*/
|
||||
UTILS_DEPRECATED
|
||||
Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this InstanceBuffer for debugging purposes.
|
||||
*
|
||||
* name will show in error messages and should be kept as short as possible.
|
||||
*
|
||||
* @param name A string literal to identify this InstanceBuffer
|
||||
* @return This Builder, for chaining calls.
|
||||
*/
|
||||
Builder& name(utils::StaticString const& name) noexcept;
|
||||
|
||||
/**
|
||||
* Creates the InstanceBuffer object and returns a pointer to it.
|
||||
*/
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include <filament/Engine.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/StaticString.h>
|
||||
|
||||
#include <math/mathfwd.h>
|
||||
|
||||
@@ -76,21 +75,9 @@ public:
|
||||
* @param name A string to identify this MorphTargetBuffer
|
||||
* @param len Length of name, should be less than or equal to 128
|
||||
* @return This Builder, for chaining calls.
|
||||
* @deprecated Use name(utils::StaticString const&) instead.
|
||||
*/
|
||||
UTILS_DEPRECATED
|
||||
Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this MorphTargetBuffer for debugging purposes.
|
||||
*
|
||||
* name will show in error messages and should be kept as short as possible.
|
||||
*
|
||||
* @param name A string literal to identify this MorphTargetBuffer
|
||||
* @return This Builder, for chaining calls.
|
||||
*/
|
||||
Builder& name(utils::StaticString const& name) noexcept;
|
||||
|
||||
/**
|
||||
* Creates the MorphTargetBuffer object and returns a pointer to it.
|
||||
*
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include <filament/RenderableManager.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/StaticString.h>
|
||||
|
||||
#include <math/mathfwd.h>
|
||||
|
||||
@@ -82,21 +81,9 @@ public:
|
||||
* @param name A string to identify this SkinningBuffer
|
||||
* @param len Length of name, should be less than or equal to 128
|
||||
* @return This Builder, for chaining calls.
|
||||
* @deprecated Use name(utils::StaticString const&) instead.
|
||||
*/
|
||||
UTILS_DEPRECATED
|
||||
Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this SkinningBuffer for debugging purposes.
|
||||
*
|
||||
* name will show in error messages and should be kept as short as possible.
|
||||
*
|
||||
* @param name A string literal to identify this SkinningBuffer
|
||||
* @return This Builder, for chaining calls.
|
||||
*/
|
||||
Builder& name(utils::StaticString const& name) noexcept;
|
||||
|
||||
/**
|
||||
* Creates the SkinningBuffer object and returns a pointer to it.
|
||||
*
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <backend/CallbackHandler.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/StaticString.h>
|
||||
|
||||
#include <math/mat3.h>
|
||||
|
||||
@@ -151,21 +150,9 @@ public:
|
||||
* @param name A string to identify this Stream
|
||||
* @param len Length of name, should be less than or equal to 128
|
||||
* @return This Builder, for chaining calls.
|
||||
* @deprecated Use name(utils::StaticString const&) instead.
|
||||
*/
|
||||
UTILS_DEPRECATED
|
||||
Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this Stream for debugging purposes.
|
||||
*
|
||||
* name will show in error messages and should be kept as short as possible.
|
||||
*
|
||||
* @param name A string literal to identify this Stream
|
||||
* @return This Builder, for chaining calls.
|
||||
*/
|
||||
Builder& name(utils::StaticString const& name) noexcept;
|
||||
|
||||
/**
|
||||
* Creates the Stream object and returns a pointer to it.
|
||||
*
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/StaticString.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
@@ -230,20 +229,8 @@ public:
|
||||
* @param name A string to identify this Texture
|
||||
* @param len Length of name, should be less than or equal to 128
|
||||
* @return This Builder, for chaining calls.
|
||||
* @deprecated Use name(utils::StaticString const&) instead.
|
||||
*/
|
||||
UTILS_DEPRECATED
|
||||
Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this Texture for debugging purposes.
|
||||
*
|
||||
* name will show in error messages and should be kept as short as possible.
|
||||
*
|
||||
* @param name A string literal to identify this Texture
|
||||
* @return This Builder, for chaining calls.
|
||||
*/
|
||||
Builder& name(utils::StaticString const& name) noexcept;
|
||||
Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept;
|
||||
|
||||
/**
|
||||
* Creates an external texture. The content must be set using setExternalImage().
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/StaticString.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -171,21 +170,9 @@ public:
|
||||
* @param name A string to identify this VertexBuffer
|
||||
* @param len Length of name, should be less than or equal to 128
|
||||
* @return This Builder, for chaining calls.
|
||||
* @deprecated Use name(utils::StaticString const&) instead.
|
||||
*/
|
||||
UTILS_DEPRECATED
|
||||
Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept;
|
||||
|
||||
/**
|
||||
* Associate an optional name with this VertexBuffer for debugging purposes.
|
||||
*
|
||||
* name will show in error messages and should be kept as short as possible.
|
||||
*
|
||||
* @param name A string literal to identify this VertexBuffer
|
||||
* @return This Builder, for chaining calls.
|
||||
*/
|
||||
Builder& name(utils::StaticString const& name) noexcept;
|
||||
|
||||
/**
|
||||
* Creates the VertexBuffer object and returns a pointer to it.
|
||||
*
|
||||
|
||||
@@ -2341,9 +2341,6 @@ void PostProcessManager::customResolvePrepareSubpass(DriverApi& driver, CustomRe
|
||||
}
|
||||
|
||||
void PostProcessManager::customResolveSubpass(DriverApi& driver) noexcept {
|
||||
|
||||
bindPostProcessDescriptorSet(driver);
|
||||
|
||||
FEngine const& engine = mEngine;
|
||||
Handle<HwRenderPrimitive> const& fullScreenRenderPrimitive = engine.getFullScreenRenderPrimitive();
|
||||
auto const& material = getPostProcessMaterial("customResolveAsSubpass");
|
||||
@@ -3039,7 +3036,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleSGSR1(FrameGraph& fg, bool sourceHasLuminance,
|
||||
DynamicResolutionOptions dsrOptions, FrameGraphId<FrameGraphTexture> const input,
|
||||
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc) noexcept {
|
||||
filament::Viewport const&, FrameGraphTexture::Descriptor const& outDesc) noexcept {
|
||||
|
||||
struct QuadBlitData {
|
||||
FrameGraphId<FrameGraphTexture> input;
|
||||
@@ -3055,7 +3052,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleSGSR1(FrameGraph& fg,
|
||||
.attachments = { .color = { data.output } },
|
||||
.clearFlags = TargetBufferFlags::DEPTH });
|
||||
},
|
||||
[this, vp, sourceHasLuminance](FrameGraphResources const& resources,
|
||||
[this, sourceHasLuminance](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) {
|
||||
bindPostProcessDescriptorSet(driver);
|
||||
|
||||
@@ -3081,17 +3078,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleSGSR1(FrameGraph& fg,
|
||||
});
|
||||
|
||||
mi->setParameter("viewport", float4{
|
||||
float(vp.left) / inputDesc.width,
|
||||
float(vp.bottom) / inputDesc.height,
|
||||
float(vp.width) / inputDesc.width,
|
||||
float(vp.height) / inputDesc.height
|
||||
});
|
||||
|
||||
mi->setParameter("viewportInfo", float4{
|
||||
1.0f / inputDesc.width,
|
||||
1.0f / inputDesc.height,
|
||||
float(inputDesc.width),
|
||||
float(inputDesc.height)
|
||||
float(inputDesc.height),
|
||||
});
|
||||
|
||||
mi->commit(driver);
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "FilamentAPI-impl.h"
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/StaticString.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
@@ -52,10 +51,6 @@ BufferObject::Builder& BufferObject::Builder::name(const char* name, size_t cons
|
||||
return BuilderNameMixin::name(name, len);
|
||||
}
|
||||
|
||||
BufferObject::Builder& BufferObject::Builder::name(utils::StaticString const& name) noexcept {
|
||||
return BuilderNameMixin::name(name);
|
||||
}
|
||||
|
||||
BufferObject* BufferObject::Builder::build(Engine& engine) {
|
||||
return downcast(engine).createBufferObject(*this);
|
||||
}
|
||||
|
||||
@@ -664,8 +664,7 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) {
|
||||
// spaces are the same, but we currently don't check that. We must revise these conditions if we
|
||||
// ever handle this case.
|
||||
mIsOneDimensional = !builder->hasAdjustments && !builder->luminanceScaling
|
||||
&& builder->toneMapper->isOneDimensional()
|
||||
&& engine.features.engine.color_grading.use_1d_lut;
|
||||
&& builder->toneMapper->isOneDimensional();
|
||||
mIsLDR = mIsOneDimensional && builder->toneMapper->isLDR();
|
||||
|
||||
const Config config = {
|
||||
|
||||
@@ -687,9 +687,6 @@ public:
|
||||
|
||||
struct {
|
||||
struct {
|
||||
struct {
|
||||
bool use_1d_lut = false;
|
||||
} color_grading;
|
||||
struct {
|
||||
bool use_shadow_atlas = false;
|
||||
} shadows;
|
||||
@@ -722,9 +719,6 @@ public:
|
||||
{ "backend.opengl.assert_native_window_is_valid",
|
||||
"Asserts that the ANativeWindow is valid when rendering starts.",
|
||||
&features.backend.opengl.assert_native_window_is_valid, true },
|
||||
{ "engine.color_grading.use_1d_lut",
|
||||
"Uses a 1D LUT for color grading.",
|
||||
&features.engine.color_grading.use_1d_lut, false },
|
||||
{ "engine.shadows.use_shadow_atlas",
|
||||
"Uses an array of atlases to store shadow maps.",
|
||||
&features.engine.shadows.use_shadow_atlas, false },
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "FilamentAPI-impl.h"
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/StaticString.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
@@ -52,10 +51,6 @@ IndexBuffer::Builder& IndexBuffer::Builder::name(const char* name, size_t const
|
||||
return BuilderNameMixin::name(name, len);
|
||||
}
|
||||
|
||||
IndexBuffer::Builder& IndexBuffer::Builder::name(utils::StaticString const& name) noexcept {
|
||||
return BuilderNameMixin::name(name);
|
||||
}
|
||||
|
||||
IndexBuffer* IndexBuffer::Builder::build(Engine& engine) {
|
||||
return downcast(engine).createIndexBuffer(*this);
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
|
||||
#include "FilamentAPI-impl.h"
|
||||
|
||||
#include <utils/StaticString.h>
|
||||
|
||||
#include <math/mat3.h>
|
||||
#include <math/vec3.h>
|
||||
|
||||
@@ -56,10 +54,6 @@ InstanceBuffer::Builder& InstanceBuffer::Builder::name(const char* name, size_t
|
||||
return BuilderNameMixin::name(name, len);
|
||||
}
|
||||
|
||||
InstanceBuffer::Builder& InstanceBuffer::Builder::name(utils::StaticString const& name) noexcept {
|
||||
return BuilderNameMixin::name(name);
|
||||
}
|
||||
|
||||
InstanceBuffer* InstanceBuffer::Builder::build(Engine& engine) {
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mInstanceCount >= 1) << "instanceCount must be >= 1.";
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mInstanceCount <= engine.getMaxAutomaticInstances())
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <math/norm.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/StaticString.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
@@ -60,10 +59,6 @@ MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::name(const char* name, s
|
||||
return BuilderNameMixin::name(name, len);
|
||||
}
|
||||
|
||||
MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::name(utils::StaticString const& name) noexcept {
|
||||
return BuilderNameMixin::name(name);
|
||||
}
|
||||
|
||||
MorphTargetBuffer* MorphTargetBuffer::Builder::build(Engine& engine) {
|
||||
return downcast(engine).createMorphTargetBuffer(*this);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <math/mat4.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/StaticString.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
@@ -65,10 +64,6 @@ SkinningBuffer::Builder& SkinningBuffer::Builder::name(const char* name, size_t
|
||||
return BuilderNameMixin::name(name, len);
|
||||
}
|
||||
|
||||
SkinningBuffer::Builder& SkinningBuffer::Builder::name(utils::StaticString const& name) noexcept {
|
||||
return BuilderNameMixin::name(name);
|
||||
}
|
||||
|
||||
SkinningBuffer* SkinningBuffer::Builder::build(Engine& engine) {
|
||||
return downcast(engine).createSkinningBuffer(*this);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <backend/PixelBufferDescriptor.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/StaticString.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <filament/Stream.h>
|
||||
|
||||
@@ -66,10 +65,6 @@ Stream::Builder& Stream::Builder::name(const char* name, size_t const len) noexc
|
||||
return BuilderNameMixin::name(name, len);
|
||||
}
|
||||
|
||||
Stream::Builder& Stream::Builder::name(utils::StaticString const& name) noexcept {
|
||||
return BuilderNameMixin::name(name);
|
||||
}
|
||||
|
||||
Stream* Stream::Builder::build(Engine& engine) {
|
||||
return downcast(engine).createStream(*this);
|
||||
}
|
||||
|
||||
@@ -41,8 +41,6 @@
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/StaticString.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Panic.h>
|
||||
@@ -154,10 +152,6 @@ Texture::Builder& Texture::Builder::name(const char* name, size_t const len) noe
|
||||
return BuilderNameMixin::name(name, len);
|
||||
}
|
||||
|
||||
Texture::Builder& Texture::Builder::name(StaticString const& name) noexcept {
|
||||
return BuilderNameMixin::name(name);
|
||||
}
|
||||
|
||||
Texture* Texture::Builder::build(Engine& engine) {
|
||||
if (mImpl->mTarget != SamplerType::SAMPLER_EXTERNAL) {
|
||||
FILAMENT_CHECK_PRECONDITION(Texture::isTextureFormatSupported(engine, mImpl->mFormat))
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/StaticString.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/ostream.h>
|
||||
@@ -153,10 +152,6 @@ VertexBuffer::Builder& VertexBuffer::Builder::name(const char* name, size_t cons
|
||||
return BuilderNameMixin::name(name, len);
|
||||
}
|
||||
|
||||
VertexBuffer::Builder& VertexBuffer::Builder::name(utils::StaticString const& name) noexcept {
|
||||
return BuilderNameMixin::name(name);
|
||||
}
|
||||
|
||||
VertexBuffer* VertexBuffer::Builder::build(Engine& engine) {
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mVertexCount > 0) << "vertexCount cannot be 0";
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mBufferCount > 0) << "bufferCount cannot be 0";
|
||||
|
||||
@@ -88,12 +88,7 @@ void minLeftMaxRight(inout vec4 a, inout vec4 b, inout vec4 c, inout vec4 d, ino
|
||||
|
||||
// - we can't use a function because offset must be a constant expression
|
||||
// - we can't use line continuation because that's not supported on desktop
|
||||
#ifdef TARGET_WEBGPU_ENVIRONMENT
|
||||
//WebGPU can not handle texelFetchOffset without an error about "ConstOffset is only permitted...".
|
||||
#define makeSample(ij, o) vec4(texelFetch(materialParams_dof, ij+o, 0).rgb, texelFetch(materialParams_alpha, ij+o, 0).r)
|
||||
#else
|
||||
#define makeSample(ij, o) vec4(texelFetchOffset(materialParams_dof, ij, 0, o).rgb, texelFetchOffset(materialParams_alpha, ij, 0, o).r)
|
||||
#endif
|
||||
|
||||
vec4 median(ivec2 ij) {
|
||||
vec4 v[9];
|
||||
|
||||
@@ -10,11 +10,6 @@ material {
|
||||
type : float4,
|
||||
name : viewport,
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : viewportInfo,
|
||||
precision: high
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
@@ -27,8 +22,8 @@ material {
|
||||
|
||||
vertex {
|
||||
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
|
||||
postProcess.vertex.xy = materialParams.viewport.xy + postProcess.normalizedUV * materialParams.viewport.zw;
|
||||
postProcess.vertex.xy = uvToRenderTargetUV(postProcess.vertex.xy);
|
||||
postProcess.normalizedUV = uvToRenderTargetUV(postProcess.normalizedUV);
|
||||
postProcess.vertex.xy = postProcess.normalizedUV;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +38,7 @@ fragment {
|
||||
#define gather textureGather
|
||||
#else
|
||||
vec4 gather(const mediump sampler2D color, highp vec2 p, const int comp) {
|
||||
highp ivec2 i = ivec2(p * materialParams.viewportInfo.zw - 0.5);
|
||||
highp ivec2 i = ivec2(p * materialParams.viewport.zw - 0.5);
|
||||
vec4 d;
|
||||
d[0] = texelFetchOffset(color, i, 0, ivec2(0, 1))[comp];
|
||||
d[1] = texelFetchOffset(color, i, 0, ivec2(1, 1))[comp];
|
||||
@@ -79,7 +74,7 @@ fragment {
|
||||
#include "sgsr1_shader_mobile.fs"
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
highp vec4 vp[1] = vec4[1]( materialParams.viewportInfo );
|
||||
highp vec4 vp[1] = vec4[1]( materialParams.viewport );
|
||||
postProcess.color = sgsr(materialParams_color, variable_vertex.xy, vp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.58.2"
|
||||
spec.version = "1.58.1"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.58.2/filament-v1.58.2-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.58.1/filament-v1.58.1-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
namespace filament {
|
||||
|
||||
// update this when a new version of filament wouldn't work with older materials
|
||||
static constexpr size_t MATERIAL_VERSION = 59;
|
||||
static constexpr size_t MATERIAL_VERSION = 57;
|
||||
|
||||
/**
|
||||
* Supported shading models
|
||||
|
||||
@@ -518,113 +518,28 @@ void GLSLPostProcessor::spirvToMsl(const SpirvBlob* spirv, std::string* outMsl,
|
||||
}
|
||||
}
|
||||
|
||||
void GLSLPostProcessor::rebindImageSamplerForWGSL(std::vector<uint32_t>& spirv) {
|
||||
constexpr size_t HEADER_SIZE = 5;
|
||||
size_t const dataSize = spirv.size();
|
||||
uint32_t* data = spirv.data();
|
||||
|
||||
struct Descriptor {
|
||||
uint32_t binding = 0xFFFFFFFF;
|
||||
bool fixed = false;
|
||||
};
|
||||
|
||||
std::unordered_map<uint32_t, Descriptor> descriptors;
|
||||
auto getDesc = [&](uint32_t var) -> Descriptor& {
|
||||
using itertype = std::unordered_map<uint32_t, Descriptor>::iterator;
|
||||
itertype itr;
|
||||
if (itr = descriptors.find(var); itr == descriptors.end()) {
|
||||
itr = descriptors.insert({var, {}}).first;
|
||||
}
|
||||
return itr->second;
|
||||
};
|
||||
|
||||
auto pass = [&](uint32_t targetOp, std::function<void(uint32_t)> f) {
|
||||
for (uint32_t cursor = HEADER_SIZE, cursorEnd = dataSize; cursor < cursorEnd;) {
|
||||
uint32_t const firstWord = data[cursor];
|
||||
uint32_t const wordCount = firstWord >> 16;
|
||||
uint32_t const op = firstWord & 0x0000FFFF;
|
||||
if (targetOp == op) {
|
||||
f(cursor+1);
|
||||
}
|
||||
cursor += wordCount;
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t samplerTypeId = 0;
|
||||
|
||||
// First we find the ID used to identify the sampler type and the image type.
|
||||
pass(spv::Op::OpTypeSampler, [&](uint32_t pos) {
|
||||
samplerTypeId = data[pos];
|
||||
});
|
||||
|
||||
// Next we identify all the descriptors and record their set id and binding.
|
||||
pass(spv::Op::OpDecorate, [&](uint32_t pos) {
|
||||
uint32_t const type = data[pos + 1];
|
||||
if (type == spv::Decoration::DecorationBinding) {
|
||||
uint32_t const targetVar = data[pos];
|
||||
uint32_t const binding = data[pos + 2];
|
||||
auto& desc = getDesc(targetVar);
|
||||
desc.binding = binding;
|
||||
// Note these decorations do not need to be written to the output.
|
||||
} else if (type == spv::Decoration::DecorationDescriptorSet) {
|
||||
uint32_t const targetVar = data[pos];
|
||||
uint32_t const set = data[pos + 2];
|
||||
auto& desc = getDesc(targetVar);
|
||||
}
|
||||
});
|
||||
|
||||
// Offset samplers by 2n+1, and everything else by 2n
|
||||
pass(spv::Op::OpLoad, [&](uint32_t pos) {
|
||||
uint32_t const retType = data[pos];
|
||||
uint32_t const targetVar = data[pos + 2];
|
||||
auto& desc = getDesc(targetVar);
|
||||
if (retType == samplerTypeId) {
|
||||
if (!desc.fixed) {
|
||||
//desc.binding = (desc.binding * 2) +1;
|
||||
desc.binding = (desc.binding ) +100;
|
||||
|
||||
desc.fixed = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!desc.fixed) {
|
||||
//desc.binding *=2;
|
||||
// desc.fixed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Write out the offsetted bindings
|
||||
pass(spv::Op::OpDecorate, [&](uint32_t pos) {
|
||||
uint32_t const type = data[pos + 1];
|
||||
if (type == spv::Decoration::DecorationBinding) {
|
||||
uint32_t const targetVar = data[pos];
|
||||
auto const &desc = getDesc(targetVar);
|
||||
data[pos + 2] = desc.binding;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
bool GLSLPostProcessor::spirvToWgsl(SpirvBlob *spirv, std::string *outWsl) {
|
||||
#if FILAMENT_SUPPORTS_WEBGPU
|
||||
//We need to run some opt-passes at all times to transpile to WGSL
|
||||
auto optimizer = createEmptyOptimizer();
|
||||
optimizer->RegisterPass(CreateSplitCombinedImageSamplerPass());
|
||||
optimizeSpirv(optimizer, *spirv);
|
||||
// We need to remove dead code for our variant-filter workaround for WebGPU to work
|
||||
// This is especially relevant for removing the push constants that morphing uses when not disabled
|
||||
spv::spirvbin_t remapper(0);
|
||||
remapper.remap(*spirv, spv::spirvbin_base_t::DCE_ALL);
|
||||
|
||||
//After splitting the image samplers, we need to remap the bindings to separate them.
|
||||
rebindImageSamplerForWGSL(*spirv);
|
||||
|
||||
//Allow non-uniform derivitives due to our nested shaders. See https://github.com/gpuweb/gpuweb/issues/3479
|
||||
const tint::spirv::reader::Options readerOpts{true};
|
||||
//Currently no options we want to use
|
||||
const tint::spirv::reader::Options readerOpts{};
|
||||
tint::wgsl::writer::Options writerOpts{};
|
||||
|
||||
tint::Program tintRead = tint::spirv::reader::Read(*spirv, readerOpts);
|
||||
|
||||
if (tintRead.Diagnostics().ContainsErrors()) {
|
||||
slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl;
|
||||
return false;
|
||||
//TODO remove this if block once combined image sampler conversion works.
|
||||
if (tintRead.Diagnostics().Str().rfind("error: WGSL does not support combined image-samplers") != std::string::npos) {
|
||||
slog.w << "This tint reader error is currently ignored during WebGPU bringup: " << tintRead.Diagnostics().Str() << io::endl;
|
||||
}
|
||||
else {
|
||||
slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
tint::Result<tint::wgsl::writer::Output> wgslOut = tint::wgsl::writer::Generate(tintRead,writerOpts);
|
||||
@@ -972,8 +887,10 @@ bool GLSLPostProcessor::fullOptimization(const TShader& tShader,
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createEmptyOptimizer() {
|
||||
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
|
||||
MaterialBuilder::Optimization optimization, Config const& config) {
|
||||
auto optimizer = std::make_shared<spvtools::Optimizer>(SPV_ENV_UNIVERSAL_1_3);
|
||||
|
||||
optimizer->SetMessageConsumer([](spv_message_level_t level,
|
||||
const char* source, const spv_position_t& position, const char* message) {
|
||||
if (!filterSpvOptimizerMessage(level)) {
|
||||
@@ -982,12 +899,6 @@ std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createEmptyOptimizer() {
|
||||
slog.e << stringifySpvOptimizerMessage(level, source, position, message)
|
||||
<< io::endl;
|
||||
});
|
||||
return optimizer;
|
||||
}
|
||||
|
||||
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
|
||||
MaterialBuilder::Optimization optimization, Config const& config) {
|
||||
auto optimizer = createEmptyOptimizer();
|
||||
|
||||
if (optimization == MaterialBuilder::Optimization::SIZE) {
|
||||
// When optimizing for size, we don't run the SPIR-V through any size optimization passes
|
||||
@@ -1012,7 +923,7 @@ std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
|
||||
return optimizer;
|
||||
}
|
||||
|
||||
void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) {
|
||||
void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) const {
|
||||
if (!optimizer->Run(spirv.data(), spirv.size(), &spirv)) {
|
||||
slog.e << "SPIR-V optimizer pass failed" << io::endl;
|
||||
return;
|
||||
@@ -1067,8 +978,7 @@ void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config c
|
||||
RegisterPass(CreateWrapOpKillPass());
|
||||
RegisterPass(CreateDeadBranchElimPass());
|
||||
RegisterPass(CreateMergeReturnPass(), MaterialBuilder::TargetApi::METAL);
|
||||
//TODO Do not run inline pass without merge return pass until we sort out issues
|
||||
RegisterPass(CreateInlineExhaustivePass(), MaterialBuilder::TargetApi::ALL & ~MaterialBuilder::TargetApi::WEBGPU);
|
||||
RegisterPass(CreateInlineExhaustivePass());
|
||||
RegisterPass(CreateAggressiveDCEPass());
|
||||
RegisterPass(CreatePrivateToLocalPass());
|
||||
RegisterPass(CreateLocalSingleBlockLoadStoreElimPass());
|
||||
@@ -1112,8 +1022,7 @@ void GLSLPostProcessor::registerSizePasses(Optimizer& optimizer, Config const& c
|
||||
|
||||
RegisterPass(CreateWrapOpKillPass());
|
||||
RegisterPass(CreateDeadBranchElimPass());
|
||||
//TODO Do not run inline pass without merge return pass until we sort out issues
|
||||
RegisterPass(CreateInlineExhaustivePass(), MaterialBuilder::TargetApi::ALL & ~MaterialBuilder::TargetApi::WEBGPU);
|
||||
RegisterPass(CreateInlineExhaustivePass());
|
||||
RegisterPass(CreateEliminateDeadFunctionsPass());
|
||||
RegisterPass(CreatePrivateToLocalPass());
|
||||
RegisterPass(CreateScalarReplacementPass(0));
|
||||
|
||||
@@ -114,20 +114,14 @@ private:
|
||||
* Retrieve an optimizer instance tuned for the given optimization level and shader configuration.
|
||||
*/
|
||||
using OptimizerPtr = std::shared_ptr<spvtools::Optimizer>;
|
||||
|
||||
static OptimizerPtr createOptimizer(
|
||||
MaterialBuilder::Optimization optimization,
|
||||
Config const& config);
|
||||
|
||||
static OptimizerPtr createEmptyOptimizer();
|
||||
|
||||
static void registerSizePasses(spvtools::Optimizer& optimizer, Config const& config);
|
||||
|
||||
static void registerPerformancePasses(spvtools::Optimizer& optimizer, Config const& config);
|
||||
|
||||
static void optimizeSpirv(OptimizerPtr optimizer, SpirvBlob &spirv);
|
||||
|
||||
static void rebindImageSamplerForWGSL(std::vector<uint32_t>& spirv);
|
||||
void optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) const;
|
||||
|
||||
void fixupClipDistance(SpirvBlob& spirv, GLSLPostProcessor::Config const& config) const;
|
||||
|
||||
|
||||