Compare commits
21 Commits
pf/osmesa-
...
pf/ext-sam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
794420ebdf | ||
|
|
0c8df766d0 | ||
|
|
8f58743405 | ||
|
|
af079b42a6 | ||
|
|
7f7bceb970 | ||
|
|
625603d8d4 | ||
|
|
d2d5d62a20 | ||
|
|
5e9be5dd2d | ||
|
|
df897b3fb2 | ||
|
|
8c396caba0 | ||
|
|
6b91f30389 | ||
|
|
de5d0e55af | ||
|
|
6066e3a461 | ||
|
|
ea0bbad636 | ||
|
|
65861b85cf | ||
|
|
1f169d7e75 | ||
|
|
daa359717b | ||
|
|
4fbfd1b8c1 | ||
|
|
67f05c15d0 | ||
|
|
4db3cc521b | ||
|
|
9e6cf3c876 |
26
.github/workflows/presubmit.yml
vendored
@@ -92,27 +92,33 @@ jobs:
|
||||
|
||||
test-renderdiff:
|
||||
name: test-renderdiff
|
||||
runs-on: ubuntu-22.04-4core
|
||||
|
||||
runs-on: macos-14-xlarge
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: ./.github/actions/ubuntu-apt-add-src
|
||||
- name: Set up Homebrew
|
||||
id: set-up-homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install python prereqs
|
||||
run: pip install mako setuptools pyyaml
|
||||
- name: Run script
|
||||
run: |
|
||||
echo "Disabled renderdiff due to Mesa -> Currently planned outage: 2025-03-16 -> 2025-03-22"
|
||||
# source ./build/linux/ci-common.sh && bash test/renderdiff/test.sh
|
||||
bash test/renderdiff/test.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: presubmit-renderdiff-result
|
||||
path: ./out/renderdiff_tests
|
||||
|
||||
validate-wgsl-pipeline:
|
||||
name: validate-wgsl-pipeline
|
||||
runs-on: ubuntu-22.04-4core
|
||||
validate-wgsl-webgpu:
|
||||
name: validate-wgsl-webgpu
|
||||
runs-on: ubuntu-22.04-8core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run build script
|
||||
run: source ./build/linux/ci-common.sh && ./build.sh -W debug test_filamat
|
||||
run: source ./build/linux/ci-common.sh && ./build.sh -W debug test_filamat filament
|
||||
- name: Run test
|
||||
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
|
||||
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
|
||||
|
||||
@@ -139,14 +139,14 @@ else()
|
||||
set(LINUX FALSE)
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
if (NOT FILAMENT_OSMESA_PATH STREQUAL "")
|
||||
if (NOT EXISTS ${FILAMENT_OSMESA_PATH}/)
|
||||
message(FATAL_ERROR "Cannot find specified OSMesa build directory: ${FILAMENT_OSMESA_PATH}")
|
||||
endif()
|
||||
set(FILAMENT_SUPPORTS_OSMESA TRUE)
|
||||
if (NOT FILAMENT_OSMESA_PATH STREQUAL "")
|
||||
if (NOT EXISTS ${FILAMENT_OSMESA_PATH}/)
|
||||
message(FATAL_ERROR "Cannot find specified OSMesa build directory: ${FILAMENT_OSMESA_PATH}")
|
||||
endif()
|
||||
set(FILAMENT_SUPPORTS_OSMESA TRUE)
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
if (FILAMENT_SUPPORTS_WAYLAND)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_WAYLAND)
|
||||
set(FILAMENT_SUPPORTS_X11 FALSE)
|
||||
@@ -184,6 +184,12 @@ if (NOT ANDROID AND NOT WEBGL AND NOT IOS AND NOT FILAMENT_LINUX_IS_MOBILE)
|
||||
set(IS_HOST_PLATFORM TRUE)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
if (FILAMENT_SUPPORTS_OSMESA)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_OSMESA)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
# Link statically against c/c++ lib to avoid missing redistriburable such as
|
||||
# "VCRUNTIME140.dll not found. Try reinstalling the app.", but give users
|
||||
|
||||
@@ -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.0'
|
||||
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.0'
|
||||
pod 'Filament', '~> 1.58.1'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,6 +7,10 @@ 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.58.2
|
||||
|
||||
- engine: Generate 1D instead of 3D LUTs for color grading whenever possible.
|
||||
|
||||
## v1.58.1
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -46,9 +46,8 @@ import static com.google.android.filament.Asserts.assertFloat4In;
|
||||
*
|
||||
* <h1>Performance</h1>
|
||||
*
|
||||
* Creating a new ColorGrading object may be more expensive than other Filament objects as a
|
||||
* 3D LUT may need to be generated. The generation of a 3D LUT, if necessary, may happen on
|
||||
* the CPU.
|
||||
* Creating a new ColorGrading object may be more expensive than other Filament objects as a LUT may
|
||||
* need to be generated. The generation of this LUT, if necessary, may happen on the CPU.
|
||||
*
|
||||
* <h1>Ordering</h1>
|
||||
*
|
||||
@@ -160,6 +159,10 @@ public class ColorGrading {
|
||||
* quality level will use a 32x32x32 10 bit LUT, a high quality will use a 32x32x32 16 bit
|
||||
* LUT, and a ultra quality will use a 64x64x64 16 bit LUT.
|
||||
*
|
||||
* This setting has no effect if generating a 1D LUT.
|
||||
*
|
||||
* This overrides the values set by format() and dimensions().
|
||||
*
|
||||
* The default quality is {@link QualityLevel#MEDIUM}.
|
||||
*
|
||||
* @param qualityLevel The desired quality of the color grading process
|
||||
@@ -175,6 +178,8 @@ public class ColorGrading {
|
||||
* When color grading is implemented using a 3D LUT, this sets the texture format of
|
||||
* of the LUT. This overrides the value set by quality().
|
||||
*
|
||||
* This setting has no effect if generating a 1D LUT.
|
||||
*
|
||||
* The default is INTEGER
|
||||
*
|
||||
* @param format The desired format of the 3D LUT.
|
||||
@@ -190,6 +195,8 @@ public class ColorGrading {
|
||||
* When color grading is implemented using a 3D LUT, this sets the dimension of the LUT.
|
||||
* This overrides the value set by quality().
|
||||
*
|
||||
* This setting has no effect if generating a 1D LUT.
|
||||
*
|
||||
* The default is 32
|
||||
*
|
||||
* @param dim The desired dimension of the LUT. Between 16 and 64.
|
||||
@@ -616,4 +623,3 @@ public class ColorGrading {
|
||||
|
||||
private static native long nBuilderBuild(long nativeBuilder, long nativeEngine);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,6 @@ package com.google.android.filament;
|
||||
* <li>DisplayRangeToneMapper</li>
|
||||
* </ul>
|
||||
* </ul>
|
||||
*
|
||||
* You can create custom tone mapping operators by subclassing ToneMapper.
|
||||
*/
|
||||
public class ToneMapper {
|
||||
private final long mNativeObject;
|
||||
|
||||
@@ -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.0
|
||||
VERSION_NAME=1.58.1
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
12
android/samples/sample-external-image/.gitignore
vendored
Normal file
@@ -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
|
||||
55
android/samples/sample-external-image/build.gradle
Normal file
@@ -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'
|
||||
|
||||
@@ -31,7 +31,7 @@ if [[ "$GITHUB_WORKFLOW" ]]; then
|
||||
sudo apt-get install mesa-common-dev libxi-dev libxxf86vm-dev
|
||||
|
||||
# For dawn
|
||||
sudo apt-get install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev
|
||||
sudo apt-get install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libx11-xcb-dev
|
||||
|
||||
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-${GITHUB_CLANG_VERSION} 100
|
||||
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-${GITHUB_CLANG_VERSION} 100
|
||||
|
||||
@@ -13,7 +13,7 @@ This document is part of the [Filament project](https://github.com/google/filame
|
||||
## Authors
|
||||
|
||||
- [Romain Guy](https://github.com/romainguy), [@romainguy](https://twitter.com/romainguy)
|
||||
- [Mathias Agopian](https://github.com/pixelflinger), [@darthmoosious](https://twitter.com/darthmoosious)
|
||||
- [Mathias Agopian](https://github.com/pixelflinger), [@pixelflinger](https://bsky.app/profile/pixelflinger.bsky.social)
|
||||
|
||||
# Overview
|
||||
|
||||
@@ -78,26 +78,27 @@ in table [standardProperties].
|
||||
Property | Definition
|
||||
-----------------------:|:---------------------
|
||||
**baseColor** | Diffuse albedo for non-metallic surfaces, and specular color for metallic surfaces
|
||||
**metallic** | Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1)
|
||||
**roughness** | Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections
|
||||
**metallic** | Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1)
|
||||
**reflectance** | Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections
|
||||
**sheenColor** | Strength of the sheen layer
|
||||
**sheenRoughness** | Perceived smoothness or roughness of the sheen layer
|
||||
**ambientOcclusion** | Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0
|
||||
**clearCoat** | Strength of the clear coat layer
|
||||
**clearCoatRoughness** | Perceived smoothness or roughness of the clear coat layer
|
||||
**clearCoatNormal** | A detail normal used to perturb the clear coat layer using _bump mapping_ (_normal mapping_)
|
||||
**anisotropy** | Amount of anisotropy in either the tangent or bitangent direction
|
||||
**anisotropyDirection** | Local surface direction in tangent space
|
||||
**ambientOcclusion** | Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0
|
||||
**normal** | A detail normal used to perturb the surface using _bump mapping_ (_normal mapping_)
|
||||
**bentNormal** | A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality
|
||||
**clearCoatNormal** | A detail normal used to perturb the clear coat layer using _bump mapping_ (_normal mapping_)
|
||||
**emissive** | Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass
|
||||
**postLightingColor** | Additional color that can be blended with the result of the lighting computations. See `postLightingBlending`
|
||||
**ior** | Index of refraction, either for refractive objects or as an alternative to reflectance
|
||||
**transmission** | Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is
|
||||
**absorption** | Absorption factor for refractive objects
|
||||
**microThickness** | Thickness of the thin layer of refractive objects
|
||||
**thickness** | Thickness of the solid volume of refractive objects
|
||||
**sheenColor** | Strength of the sheen layer
|
||||
**sheenRoughness** | Perceived smoothness or roughness of the sheen layer
|
||||
**emissive** | Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass
|
||||
**normal** | A detail normal used to perturb the surface using _bump mapping_ (_normal mapping_)
|
||||
**postLightingColor** | Additional color that can be blended with the result of the lighting computations. See `postLightingBlending`
|
||||
**absorption** | Absorption factor for refractive objects
|
||||
**transmission** | Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is
|
||||
**ior** | Index of refraction, either for refractive objects or as an alternative to reflectance
|
||||
**microThickness** | Thickness of the thin layer of refractive objects
|
||||
**bentNormal** | A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality
|
||||
**shadowStrength** | Strength factor between 0 and 1 for all shadows received by this material
|
||||
[Table [standardProperties]: Properties of the standard model]
|
||||
|
||||
The type and range of each property is described in table [standardPropertiesTypes].
|
||||
@@ -1272,6 +1273,9 @@ Description
|
||||
when selecting any shading model that is not `unlit`. See the shader sections of this document
|
||||
for more information on how to access these attributes from the shaders.
|
||||
|
||||
!!! Note: Interaction with custom variables
|
||||
When the `color` attribute is specified, only four custom variables are available instead of five.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
||||
material {
|
||||
parameters : [
|
||||
@@ -1302,7 +1306,7 @@ Type
|
||||
: array of `string`
|
||||
|
||||
Value
|
||||
: Up to 4 strings, each must be a valid GLSL identifier.
|
||||
: Up to 5 strings, each must be a valid GLSL identifier.
|
||||
|
||||
Description
|
||||
: Defines custom interpolants (or variables) that are output by the material's vertex shader.
|
||||
@@ -1318,6 +1322,10 @@ Description
|
||||
particular if `default` is specified the default precision is used is the fragment shader
|
||||
(`mediump`) and in the vertex shader (`highp`).
|
||||
|
||||
!!! Warning: Interaction with required attributes
|
||||
If the `color` attribute is specified in the `required` list, then only four variables can be used
|
||||
instead of five.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
||||
material {
|
||||
name : Skybox,
|
||||
|
||||
@@ -108,8 +108,12 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformCocoaTouchGL.mm)
|
||||
list(APPEND SRCS src/opengl/platforms/CocoaTouchExternalImage.mm)
|
||||
elseif (APPLE)
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformCocoaGL.mm)
|
||||
list(APPEND SRCS src/opengl/platforms/CocoaExternalImage.mm)
|
||||
if (FILAMENT_SUPPORTS_OSMESA)
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformOSMesa.cpp)
|
||||
else()
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformCocoaGL.mm)
|
||||
list(APPEND SRCS src/opengl/platforms/CocoaExternalImage.mm)
|
||||
endif()
|
||||
elseif (WEBGL)
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformWebGL.cpp)
|
||||
elseif (LINUX)
|
||||
@@ -173,10 +177,12 @@ endif()
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/VulkanPlatform.h
|
||||
src/vulkan/caching/VulkanDescriptorSetManager.cpp
|
||||
src/vulkan/caching/VulkanDescriptorSetManager.h
|
||||
src/vulkan/caching/VulkanPipelineLayoutCache.cpp
|
||||
src/vulkan/caching/VulkanPipelineLayoutCache.h
|
||||
src/vulkan/VulkanDescriptorSetCache.cpp
|
||||
src/vulkan/VulkanDescriptorSetCache.h
|
||||
src/vulkan/VulkanDescriptorSetLayoutCache.cpp
|
||||
src/vulkan/VulkanDescriptorSetLayoutCache.h
|
||||
src/vulkan/VulkanPipelineLayoutCache.cpp
|
||||
src/vulkan/VulkanPipelineLayoutCache.h
|
||||
src/vulkan/memory/ResourceManager.cpp
|
||||
src/vulkan/memory/ResourceManager.h
|
||||
src/vulkan/memory/ResourcePointer.h
|
||||
@@ -416,9 +422,12 @@ set(LINUX_LINKER_OPTIMIZATION_FLAGS
|
||||
-Wl,--exclude-libs,bluegl
|
||||
)
|
||||
|
||||
if (LINUX AND FILAMENT_SUPPORTS_OSMESA)
|
||||
set(OSMESA_COMPILE_FLAGS
|
||||
-I${FILAMENT_OSMESA_PATH}/include/GL)
|
||||
if (FILAMENT_SUPPORTS_OSMESA)
|
||||
if (LINUX)
|
||||
set(OSMESA_COMPILE_FLAGS -I${FILAMENT_OSMESA_PATH}/include/GL)
|
||||
elseif (APPLE)
|
||||
set(OSMESA_COMPILE_FLAGS -I${FILAMENT_OSMESA_PATH}/include)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
PlatformCocoaGL();
|
||||
~PlatformCocoaGL() noexcept override;
|
||||
|
||||
ExternalImageHandle createExternalImage(void* cvPixelBuffer) noexcept;
|
||||
ExternalImageHandle UTILS_PUBLIC createExternalImage(void* cvPixelBuffer) noexcept;
|
||||
|
||||
protected:
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
PlatformCocoaTouchGL();
|
||||
~PlatformCocoaTouchGL() noexcept override;
|
||||
|
||||
ExternalImageHandle createExternalImage(void* cvPixelBuffer) noexcept;
|
||||
ExternalImageHandle UTILS_PUBLIC createExternalImage(void* cvPixelBuffer) noexcept;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Platform Interface
|
||||
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
/**
|
||||
* Creates an ExternalImage from a EGLImageKHR
|
||||
*/
|
||||
ExternalImageHandle createExternalImage(EGLImageKHR eglImage) noexcept;
|
||||
ExternalImageHandle UTILS_PUBLIC createExternalImage(EGLImageKHR eglImage) noexcept;
|
||||
|
||||
protected:
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <backend/platforms/PlatformEGL.h>
|
||||
|
||||
#include <utils/android/PerformanceHintManager.h>
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
@@ -43,8 +44,21 @@ public:
|
||||
PlatformEGLAndroid() noexcept;
|
||||
~PlatformEGLAndroid() noexcept override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Creates an ExternalImage from a EGLImageKHR
|
||||
*/
|
||||
ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer, bool sRGB) noexcept;
|
||||
|
||||
struct UTILS_PUBLIC ExternalImageDescAndroid {
|
||||
uint32_t width; // Texture width
|
||||
uint32_t height; // Texture height
|
||||
TextureFormat format;// Texture format
|
||||
TextureUsage usage; // Texture usage flags
|
||||
};
|
||||
|
||||
ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc(ExternalImageHandle externalImage) noexcept;
|
||||
|
||||
protected:
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Platform Interface
|
||||
|
||||
@@ -57,11 +71,6 @@ protected:
|
||||
Driver* createDriver(void* sharedContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept override;
|
||||
|
||||
/**
|
||||
* Creates an ExternalImage from a EGLImageKHR
|
||||
*/
|
||||
ExternalImageHandle createExternalImage(AHardwareBuffer const *buffer, bool sRGB) noexcept;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// OpenGLPlatform Interface
|
||||
|
||||
@@ -95,15 +104,26 @@ protected:
|
||||
*/
|
||||
AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept override;
|
||||
|
||||
bool setExternalImage(ExternalImageHandleRef externalImage, ExternalTexture* texture) noexcept override;
|
||||
OpenGLPlatform::ExternalTexture* createExternalImageTexture() noexcept override;
|
||||
void destroyExternalImageTexture(ExternalTexture* texture) noexcept override;
|
||||
|
||||
struct ExternalImageEGLAndroid : public ExternalImageEGL {
|
||||
AHardwareBuffer* aHardwareBuffer = nullptr;
|
||||
uint32_t width; // Texture width
|
||||
uint32_t height; // Texture height
|
||||
TextureFormat format;// Texture format
|
||||
TextureUsage usage; // Texture usage flags
|
||||
bool sRGB = false;
|
||||
|
||||
protected:
|
||||
~ExternalImageEGLAndroid() override;
|
||||
};
|
||||
|
||||
bool setExternalImage(ExternalImageHandleRef externalImage,
|
||||
ExternalTexture* texture) noexcept override;
|
||||
bool setImage(ExternalImageEGLAndroid const* eglExternalImage,
|
||||
ExternalTexture* texture) noexcept;
|
||||
|
||||
protected:
|
||||
bool makeCurrent(ContextType type,
|
||||
SwapChain* drawSwapChain,
|
||||
|
||||
@@ -21,7 +21,12 @@
|
||||
|
||||
#include "bluegl/BlueGL.h"
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <osmesa.h>
|
||||
#elif defined(__APPLE__)
|
||||
#undef GLAPI
|
||||
#include <GL/osmesa.h>
|
||||
#endif
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
@@ -368,8 +368,16 @@ public:
|
||||
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
|
||||
VkImageViewType viewType, VkComponentMapping swizzle);
|
||||
|
||||
protected:
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
||||
|
||||
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
|
||||
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept;
|
||||
|
||||
private:
|
||||
static ExtensionSet getSwapchainInstanceExtensions();
|
||||
// Platform dependent helper methods
|
||||
static ExtensionSet getSwapchainInstanceExtensionsImpl();
|
||||
|
||||
static ExternalImageMetadata getExternalImageMetadataImpl(ExternalImageHandleRef externalImage,
|
||||
VkDevice device);
|
||||
@@ -386,8 +394,7 @@ private:
|
||||
VkComponentMapping swizzle);
|
||||
|
||||
// Platform dependent helper methods
|
||||
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
|
||||
static SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
static SurfaceBundle createVkSurfaceKHRImpl(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) noexcept;
|
||||
|
||||
friend struct VulkanPlatformPrivate;
|
||||
|
||||
@@ -17,27 +17,55 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
|
||||
|
||||
#include <backend/Platform.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
#include <android/hardware_buffer.h>
|
||||
|
||||
namespace filament::backend::fvkandroid {
|
||||
namespace filament::backend {
|
||||
|
||||
struct ExternalImageVulkanAndroid : public Platform::ExternalImage {
|
||||
AHardwareBuffer* aHardwareBuffer = nullptr;
|
||||
bool sRGB = false;
|
||||
unsigned int width; // Texture width
|
||||
unsigned int height; // Texture height
|
||||
TextureFormat format; // Texture format
|
||||
TextureUsage usage; // Texture usage flags
|
||||
class VulkanPlatformAndroid : public VulkanPlatform {
|
||||
public:
|
||||
Platform::ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer,
|
||||
bool sRGB) noexcept;
|
||||
|
||||
struct UTILS_PUBLIC ExternalImageDescAndroid {
|
||||
uint32_t width; // Texture width
|
||||
uint32_t height; // Texture height
|
||||
TextureFormat format;// Texture format
|
||||
TextureUsage usage; // Texture usage flags
|
||||
};
|
||||
|
||||
ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc(
|
||||
ExternalImageHandleRef externalImage) const noexcept;
|
||||
|
||||
protected:
|
||||
~ExternalImageVulkanAndroid() override;
|
||||
struct ExternalImageVulkanAndroid : public Platform::ExternalImage {
|
||||
AHardwareBuffer* aHardwareBuffer = nullptr;
|
||||
bool sRGB = false;
|
||||
unsigned int width; // Texture width
|
||||
unsigned int height; // Texture height
|
||||
TextureFormat format;// Texture format
|
||||
TextureUsage usage; // Texture usage flags
|
||||
|
||||
protected:
|
||||
~ExternalImageVulkanAndroid() override;
|
||||
};
|
||||
|
||||
virtual ExternalImageMetadata getExternalImageMetadata(ExternalImageHandleRef externalImage);
|
||||
|
||||
using ImageData = VulkanPlatform::ImageData;
|
||||
virtual ImageData createExternalImageData(ExternalImageHandleRef externalImage,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
|
||||
VkImageUsageFlags usage);
|
||||
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
||||
|
||||
using SurfaceBundle = VulkanPlatform::SurfaceBundle;
|
||||
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept;
|
||||
};
|
||||
|
||||
Platform::ExternalImageHandle createExternalImage(AHardwareBuffer const* buffer,
|
||||
bool sRGB) noexcept;
|
||||
}// namespace filament::backend
|
||||
|
||||
} // namespace filament::backend::fvkandroid
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
|
||||
#endif// TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
#include <utils/Systrace.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
// We need to keep this up top for the linux (X11) name collisions.
|
||||
#if defined(FILAMENT_SUPPORTS_WEBGPU)
|
||||
#include "backend/platforms/WebGPUPlatform.h"
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include <sys/system_properties.h>
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
@@ -30,7 +35,11 @@
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
#include <backend/platforms/PlatformCocoaGL.h>
|
||||
#if defined(FILAMENT_SUPPORTS_OSMESA)
|
||||
#include <backend/platforms/PlatformOSMesa.h>
|
||||
#else
|
||||
#include <backend/platforms/PlatformCocoaGL.h>
|
||||
#endif
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
#if defined(FILAMENT_SUPPORTS_X11)
|
||||
@@ -55,7 +64,11 @@
|
||||
#endif
|
||||
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
#include "backend/platforms/VulkanPlatform.h"
|
||||
#if defined(__ANDROID__)
|
||||
#include "backend/platforms/VulkanPlatformAndroid.h"
|
||||
#else
|
||||
#include "backend/platforms/VulkanPlatform.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined (FILAMENT_SUPPORTS_METAL)
|
||||
@@ -65,9 +78,6 @@ filament::backend::Platform* createDefaultMetalPlatform();
|
||||
#endif
|
||||
|
||||
#include "noop/PlatformNoop.h"
|
||||
#if defined(FILAMENT_SUPPORTS_WEBGPU)
|
||||
#include "backend/platforms/WebGPUPlatform.h"
|
||||
#endif
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -104,7 +114,11 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
|
||||
}
|
||||
if (*backend == Backend::VULKAN) {
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
return new VulkanPlatform();
|
||||
#if defined(__ANDROID__)
|
||||
return new VulkanPlatformAndroid();
|
||||
#else
|
||||
return new VulkanPlatform();
|
||||
#endif
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
@@ -132,7 +146,11 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
|
||||
#elif defined(FILAMENT_IOS)
|
||||
return new PlatformCocoaTouchGL();
|
||||
#elif defined(__APPLE__)
|
||||
return new PlatformCocoaGL();
|
||||
#if defined(FILAMENT_SUPPORTS_OSMESA)
|
||||
return new PlatformOSMesa();
|
||||
#else
|
||||
return new PlatformCocoaGL();
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
#if defined(FILAMENT_SUPPORTS_X11)
|
||||
return new PlatformGLX();
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
Driver* PlatformNoop::createDriver(void* const sharedGLContext, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
Driver* PlatformNoop::createDriver(void* sharedGLContext, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
return NoopDriver::create();
|
||||
}
|
||||
|
||||
|
||||
@@ -148,8 +148,8 @@ using namespace utils;
|
||||
namespace filament::backend {
|
||||
|
||||
Driver* OpenGLDriverFactory::create(
|
||||
OpenGLPlatform* const platform,
|
||||
void* const sharedGLContext,
|
||||
OpenGLPlatform* platform,
|
||||
void* sharedGLContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept {
|
||||
return OpenGLDriver::create(platform, sharedGLContext, driverConfig);
|
||||
}
|
||||
@@ -159,10 +159,10 @@ using namespace GLUtils;
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
UTILS_NOINLINE
|
||||
OpenGLDriver* OpenGLDriver::create(OpenGLPlatform* const platform,
|
||||
void* const /*sharedGLContext*/, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
OpenGLDriver* OpenGLDriver::create(OpenGLPlatform* platform,
|
||||
void* /*sharedGLContext*/, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
assert_invariant(platform);
|
||||
OpenGLPlatform* const ec = platform;
|
||||
OpenGLPlatform* ec = platform;
|
||||
|
||||
#if 0
|
||||
// this is useful for development, but too verbose even for debug builds
|
||||
@@ -230,7 +230,7 @@ OpenGLDriver* OpenGLDriver::create(OpenGLPlatform* const platform,
|
||||
constexpr size_t defaultSize = FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
|
||||
Platform::DriverConfig validConfig{ driverConfig };
|
||||
validConfig.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize);
|
||||
OpenGLDriver* const driver = new(std::nothrow) OpenGLDriver(ec, validConfig);
|
||||
OpenGLDriver* driver = new(std::nothrow) OpenGLDriver(ec, validConfig);
|
||||
return driver;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ PlatformCocoaTouchGL::~PlatformCocoaTouchGL() noexcept {
|
||||
delete pImpl;
|
||||
}
|
||||
|
||||
Driver* PlatformCocoaTouchGL::createDriver(void* const sharedGLContext, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
Driver* PlatformCocoaTouchGL::createDriver(void* sharedGLContext, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
EAGLSharegroup* sharegroup = (__bridge EAGLSharegroup*) sharedGLContext;
|
||||
|
||||
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3 sharegroup:sharegroup];
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <backend/platforms/PlatformEGL.h>
|
||||
#include <backend/platforms/PlatformEGLAndroid.h>
|
||||
|
||||
#include <private/backend/BackendUtilsAndroid.h>
|
||||
#include <private/backend/VirtualMachineEnv.h>
|
||||
|
||||
#include "opengl/GLUtils.h"
|
||||
@@ -34,6 +35,8 @@
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
@@ -231,26 +234,133 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
|
||||
return driver;
|
||||
}
|
||||
|
||||
PlatformEGLAndroid::ExternalImageEGLAndroid::~ExternalImageEGLAndroid() = default;
|
||||
PlatformEGLAndroid::ExternalImageEGLAndroid::~ExternalImageEGLAndroid() {
|
||||
if (__builtin_available(android 26, *)) {
|
||||
if (aHardwareBuffer) {
|
||||
AHardwareBuffer_release(aHardwareBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Platform::ExternalImageHandle PlatformEGLAndroid::createExternalImage(AHardwareBuffer const* buffer, bool sRGB) noexcept {
|
||||
auto* const p = new(std::nothrow) ExternalImageEGLAndroid;
|
||||
p->aHardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
|
||||
p->sRGB = sRGB;
|
||||
return ExternalImageHandle{ p };
|
||||
Platform::ExternalImageHandle PlatformEGLAndroid::createExternalImage(AHardwareBuffer const* buffer,
|
||||
bool sRGB) noexcept {
|
||||
if (__builtin_available(android 26, *)) {
|
||||
auto* const p = new (std::nothrow) ExternalImageEGLAndroid;
|
||||
auto hardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
|
||||
AHardwareBuffer_acquire(hardwareBuffer);
|
||||
p->aHardwareBuffer = hardwareBuffer;
|
||||
p->sRGB = sRGB;
|
||||
AHardwareBuffer_Desc hardwareBufferDescription = {};
|
||||
AHardwareBuffer_describe(hardwareBuffer, &hardwareBufferDescription);
|
||||
p->height = hardwareBufferDescription.height;
|
||||
p->width = hardwareBufferDescription.width;
|
||||
auto textureFormat = mapToFilamentFormat(hardwareBufferDescription.format, sRGB);
|
||||
p->usage = mapToFilamentUsage(hardwareBufferDescription.usage, textureFormat);
|
||||
return ExternalImageHandle{ p };
|
||||
}
|
||||
|
||||
return Platform::ExternalImageHandle{};
|
||||
}
|
||||
|
||||
PlatformEGLAndroid::ExternalImageDescAndroid PlatformEGLAndroid::getExternalImageDesc(
|
||||
ExternalImageHandle externalImage) noexcept {
|
||||
auto const* const eglExternalImage =
|
||||
static_cast<ExternalImageEGLAndroid const*>(externalImage.get());
|
||||
ExternalImageDescAndroid metadata = {};
|
||||
if (!eglExternalImage) {
|
||||
return metadata;
|
||||
}
|
||||
metadata.height = eglExternalImage->height;
|
||||
metadata.width = eglExternalImage->width;
|
||||
metadata.format = eglExternalImage->format;
|
||||
metadata.usage = eglExternalImage->usage;
|
||||
return metadata;
|
||||
}
|
||||
|
||||
bool PlatformEGLAndroid::setExternalImage(ExternalImageHandleRef externalImage,
|
||||
UTILS_UNUSED_IN_RELEASE ExternalTexture* texture) noexcept {
|
||||
auto const* const eglExternalImage = static_cast<ExternalImageEGLAndroid const*>(externalImage.get());
|
||||
auto const* const eglExternalImage =
|
||||
static_cast<ExternalImageEGLAndroid const*>(externalImage.get());
|
||||
if (eglExternalImage->aHardwareBuffer) {
|
||||
// TODO: implement PlatformEGLAndroid::setExternalImage w/ AHardwareBuffer
|
||||
return true;
|
||||
return PlatformEGLAndroid::setImage(eglExternalImage, texture);
|
||||
}
|
||||
// not a AHardwareBuffer, fallback to the inherited version
|
||||
return PlatformEGL::setExternalImage(externalImage, texture);
|
||||
}
|
||||
|
||||
OpenGLPlatform::ExternalTexture* PlatformEGLAndroid::createExternalImageTexture() noexcept {
|
||||
ExternalTexture* outTexture = new (std::nothrow) ExternalTexture{};
|
||||
glGenTextures(1, &outTexture->id);
|
||||
return outTexture;
|
||||
}
|
||||
|
||||
void PlatformEGLAndroid::destroyExternalImageTexture(ExternalTexture* texture) noexcept {
|
||||
glDeleteTextures(1, &texture->id);
|
||||
delete texture;
|
||||
}
|
||||
|
||||
bool PlatformEGLAndroid::setImage(ExternalImageEGLAndroid const* eglExternalImage,
|
||||
UTILS_UNUSED_IN_RELEASE ExternalTexture* texture) noexcept {
|
||||
AHardwareBuffer* hardwareBuffer = eglExternalImage->aHardwareBuffer;
|
||||
|
||||
// Get the EGL client buffer from AHardwareBuffer
|
||||
EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(hardwareBuffer);
|
||||
EGLint imageAttrs[] = {
|
||||
EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
|
||||
EGL_NONE, EGL_NONE, // Reserve space
|
||||
EGL_NONE, EGL_NONE, // Reserve space
|
||||
EGL_NONE // Ensure the list always ends with EGL_NONE
|
||||
};
|
||||
int attrIndex = 2;
|
||||
if (eglExternalImage->sRGB) {
|
||||
imageAttrs[attrIndex++] = EGL_GL_COLORSPACE;
|
||||
imageAttrs[attrIndex++] = EGL_GL_COLORSPACE_SRGB;
|
||||
}
|
||||
|
||||
if (static_cast<bool>(eglExternalImage->usage & TextureUsage::PROTECTED)) {
|
||||
imageAttrs[attrIndex++] = EGL_PROTECTED_CONTENT_EXT;
|
||||
imageAttrs[attrIndex++] = EGL_TRUE;
|
||||
}
|
||||
// Create an EGLImage from the client buffer
|
||||
EGLImageKHR eglImage = eglCreateImageKHR(eglGetCurrentDisplay(), EGL_NO_CONTEXT,
|
||||
EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs);
|
||||
if (eglImage == EGL_NO_IMAGE_KHR) {
|
||||
// Handle error
|
||||
slog.e << "Failed to create EGL image" << io::endl;
|
||||
glDeleteTextures(1, &texture->id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create and bind the OpenGL texture
|
||||
GLint prevActiveTexture, prevTexture;
|
||||
glGetIntegerv(GL_ACTIVE_TEXTURE, &prevActiveTexture);
|
||||
glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevTexture);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(texture->target, texture->id);
|
||||
GLenum error = glGetError();
|
||||
if (UTILS_UNLIKELY(error != GL_NO_ERROR)) {
|
||||
slog.e << "Error after glBindTexture: " << error << io::endl;
|
||||
glDeleteTextures(1, &texture->id);
|
||||
eglDestroyImageKHR(eglGetCurrentDisplay(), eglImage);
|
||||
glActiveTexture(prevActiveTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, prevTexture);
|
||||
return false;
|
||||
}
|
||||
glEGLImageTargetTexture2DOES(texture->target, static_cast<GLeglImageOES>(eglImage));
|
||||
error = glGetError();
|
||||
if (UTILS_UNLIKELY(error != GL_NO_ERROR)) {
|
||||
slog.e << "Error after glEGLImageTargetTexture2DOES: " << error << io::endl;
|
||||
glDeleteTextures(1, &texture->id);
|
||||
eglDestroyImageKHR(eglGetCurrentDisplay(), eglImage);
|
||||
glActiveTexture(prevActiveTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, prevTexture);
|
||||
return false;
|
||||
}
|
||||
glActiveTexture(prevActiveTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, prevTexture);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlatformEGLAndroid::setPresentationTime(int64_t presentationTimeInNanosecond) noexcept {
|
||||
EGLSurface currentDrawSurface = eglGetCurrentSurface(EGL_DRAW);
|
||||
if (currentDrawSurface != EGL_NO_SURFACE) {
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace filament::backend {
|
||||
|
||||
using namespace backend;
|
||||
|
||||
Driver* PlatformGLX::createDriver(void* const sharedGLContext,
|
||||
Driver* PlatformGLX::createDriver(void* sharedGLContext,
|
||||
const DriverConfig& driverConfig) noexcept {
|
||||
loadLibraries();
|
||||
// Get the display device
|
||||
|
||||
@@ -22,9 +22,11 @@
|
||||
#include <dlfcn.h>
|
||||
#include <memory>
|
||||
|
||||
#if defined(__linux__)
|
||||
// This is to ensure that linking during compilation will not fail even if
|
||||
// OSMesaGetProcAddress is not linked.
|
||||
__attribute__((weak)) OSMESAproc OSMesaGetProcAddress(char const*);
|
||||
#endif
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -48,20 +50,27 @@ struct OSMesaSwapchain {
|
||||
|
||||
struct OSMesaAPI {
|
||||
private:
|
||||
using CreateContextFunc = OSMesaContext (*)(GLenum format, OSMesaContext);
|
||||
using CreateContextAttribsFunc = OSMesaContext (*)(const int *, OSMesaContext);
|
||||
using DestroyContextFunc = GLboolean (*)(OSMesaContext);
|
||||
using MakeCurrentFunc = GLboolean (*)(OSMesaContext ctx, void* buffer, GLenum type,
|
||||
GLsizei width, GLsizei height);
|
||||
using GetProcAddressFunc = OSMESAproc (*)(const char* funcName);
|
||||
|
||||
public:
|
||||
CreateContextFunc fOSMesaCreateContext;
|
||||
CreateContextAttribsFunc fOSMesaCreateContextAttribs;
|
||||
DestroyContextFunc fOSMesaDestroyContext;
|
||||
MakeCurrentFunc fOSMesaMakeCurrent;
|
||||
GetProcAddressFunc fOSMesaGetProcAddress;
|
||||
|
||||
OSMesaAPI() {
|
||||
constexpr char const* libraryNames[] = {"libOSMesa.so", "libosmesa.so"};
|
||||
static constexpr char const* libraryNames[] = {
|
||||
#if defined(__linux__)
|
||||
"libOSMesa.so",
|
||||
"libosmesa.so",
|
||||
#elif defined(__APPLE__)
|
||||
"libOSMesa.dylib",
|
||||
#endif
|
||||
};
|
||||
for (char const* libName: libraryNames) {
|
||||
mLib = dlopen(libName, RTLD_GLOBAL | RTLD_NOW);
|
||||
if (mLib) {
|
||||
@@ -71,22 +80,24 @@ public:
|
||||
if (mLib) {
|
||||
// Loading from a libosmesa.os
|
||||
fOSMesaGetProcAddress = (GetProcAddressFunc) dlsym(mLib, "OSMesaGetProcAddress");
|
||||
} else {
|
||||
}
|
||||
#if defined(__linux__)
|
||||
else {
|
||||
// Filament is built into a .so
|
||||
fOSMesaGetProcAddress = (GetProcAddressFunc) dlsym(RTLD_LOCAL, "OSMesaGetProcAddress");
|
||||
}
|
||||
|
||||
if (!fOSMesaGetProcAddress) {
|
||||
// Statically linking osmesa
|
||||
fOSMesaGetProcAddress = OSMesaGetProcAddress;
|
||||
}
|
||||
#endif // __linux__
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(fOSMesaGetProcAddress)
|
||||
<< "Unable to link against libOSMesa to create a software GL context";
|
||||
|
||||
fOSMesaCreateContext = (CreateContextFunc) fOSMesaGetProcAddress("OSMesaCreateContext");
|
||||
fOSMesaDestroyContext =
|
||||
(DestroyContextFunc) fOSMesaGetProcAddress("OSMesaDestroyContext");
|
||||
fOSMesaCreateContextAttribs =
|
||||
(CreateContextAttribsFunc) fOSMesaGetProcAddress("OSMesaCreateContextAttribs");
|
||||
fOSMesaDestroyContext = (DestroyContextFunc) fOSMesaGetProcAddress("OSMesaDestroyContext");
|
||||
fOSMesaMakeCurrent = (MakeCurrentFunc) fOSMesaGetProcAddress("OSMesaMakeCurrent");
|
||||
}
|
||||
|
||||
@@ -101,14 +112,24 @@ private:
|
||||
|
||||
}// anonymous namespace
|
||||
|
||||
Driver* PlatformOSMesa::createDriver(void* const sharedGLContext,
|
||||
Driver* PlatformOSMesa::createDriver(void* sharedGLContext,
|
||||
const DriverConfig& driverConfig) noexcept {
|
||||
|
||||
OSMesaAPI* api = new OSMesaAPI();
|
||||
mOsMesaApi = api;
|
||||
|
||||
static constexpr int attribs[] = {
|
||||
OSMESA_FORMAT, GL_RGBA,
|
||||
OSMESA_DEPTH_BITS, 24,
|
||||
OSMESA_STENCIL_BITS, 8,
|
||||
OSMESA_ACCUM_BITS, 0,
|
||||
OSMESA_PROFILE, OSMESA_CORE_PROFILE,
|
||||
0,
|
||||
};
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(sharedGLContext == nullptr)
|
||||
<< "shared GL context is not supported with PlatformOSMesa";
|
||||
mContext = api->fOSMesaCreateContext(GL_RGBA, NULL);
|
||||
mContext = api->fOSMesaCreateContextAttribs(attribs, NULL);
|
||||
|
||||
// We need to do a no-op makecurrent here so that the context will be in a correct state before
|
||||
// any GL calls.
|
||||
|
||||
@@ -75,7 +75,7 @@ struct WGLSwapChain {
|
||||
|
||||
static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs = nullptr;
|
||||
|
||||
Driver* PlatformWGL::createDriver(void* const sharedGLContext,
|
||||
Driver* PlatformWGL::createDriver(void* sharedGLContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept {
|
||||
int result = 0;
|
||||
int pixelFormat = 0;
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace filament::backend {
|
||||
|
||||
using namespace backend;
|
||||
|
||||
Driver* PlatformWebGL::createDriver(void* const sharedGLContext,
|
||||
Driver* PlatformWebGL::createDriver(void* sharedGLContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept {
|
||||
return OpenGLPlatform::createDefaultDriver(this, sharedGLContext, driverConfig);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,35 +14,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "VulkanDescriptorSetManager.h"
|
||||
#include "VulkanDescriptorSetCache.h"
|
||||
|
||||
#include "vulkan/VulkanCommands.h"
|
||||
#include "vulkan/VulkanHandles.h"
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanConstants.h"
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask;
|
||||
using DescriptorCount = VulkanDescriptorSetLayout::Count;
|
||||
using DescriptorSetLayoutArray = VulkanDescriptorSetManager::DescriptorSetLayoutArray;
|
||||
using BitmaskGroupHashFn = utils::hash::MurmurHashFn<BitmaskGroup>;
|
||||
struct BitmaskGroupEqual {
|
||||
bool operator()(BitmaskGroup const& k1, BitmaskGroup const& k2) const {
|
||||
return k1 == k2;
|
||||
}
|
||||
};
|
||||
using DescriptorSetLayoutArray = VulkanDescriptorSetCache::DescriptorSetLayoutArray;
|
||||
|
||||
// We create a pool for each layout as defined by the number of descriptors of each type. For
|
||||
// example, a layout of
|
||||
@@ -199,72 +189,12 @@ struct Equal {
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Bitmask>
|
||||
uint32_t createBindings(VkDescriptorSetLayoutBinding* toBind, uint32_t count, VkDescriptorType type,
|
||||
Bitmask const& mask) {
|
||||
Bitmask alreadySeen;
|
||||
mask.forEachSetBit([&](size_t index) {
|
||||
VkShaderStageFlags stages = 0;
|
||||
uint32_t binding = 0;
|
||||
if (index < fvkutils::getFragmentStageShift<Bitmask>()) {
|
||||
binding = (uint32_t) index;
|
||||
stages |= VK_SHADER_STAGE_VERTEX_BIT;
|
||||
auto fragIndex = index + fvkutils::getFragmentStageShift<Bitmask>();
|
||||
if (mask.test(fragIndex)) {
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
alreadySeen.set(fragIndex);
|
||||
}
|
||||
} else if (!alreadySeen.test(index)) {
|
||||
// We are in fragment stage bits
|
||||
binding = (uint32_t) (index - fvkutils::getFragmentStageShift<Bitmask>());
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
}
|
||||
|
||||
if (stages) {
|
||||
toBind[count++] = {
|
||||
.binding = binding,
|
||||
.descriptorType = type,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = stages,
|
||||
};
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
inline VkDescriptorSetLayout createLayout(VkDevice device, BitmaskGroup const& bitmaskGroup) {
|
||||
// Note that the following *needs* to be static so that VkDescriptorSetLayoutCreateInfo will not
|
||||
// refer to stack memory.
|
||||
VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
|
||||
uint32_t count = 0;
|
||||
|
||||
count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
||||
bitmaskGroup.dynamicUbo);
|
||||
count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmaskGroup.ubo);
|
||||
count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
bitmaskGroup.sampler);
|
||||
count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
|
||||
bitmaskGroup.inputAttachment);
|
||||
|
||||
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
|
||||
VkDescriptorSetLayoutCreateInfo dlinfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.bindingCount = count,
|
||||
.pBindings = toBind,
|
||||
};
|
||||
|
||||
VkDescriptorSetLayout layout;
|
||||
vkCreateDescriptorSetLayout(device, &dlinfo, VKALLOC, &layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// This is an ever-expanding pool of sets where it
|
||||
// 1. Keeps a list of smaller pools of different layout-dimensions.
|
||||
// 2. Will add a pool if existing pool are not compatible with the requested layout o runs out.
|
||||
class VulkanDescriptorSetManager::DescriptorInfinitePool {
|
||||
class VulkanDescriptorSetCache::DescriptorInfinitePool {
|
||||
private:
|
||||
static constexpr uint16_t EXPECTED_SET_COUNT = 10;
|
||||
static constexpr float SET_COUNT_GROWTH_FACTOR = 1.5;
|
||||
@@ -319,61 +249,34 @@ private:
|
||||
std::vector<std::unique_ptr<DescriptorPool>> mPools;
|
||||
};
|
||||
|
||||
class VulkanDescriptorSetManager::DescriptorSetLayoutManager {
|
||||
public:
|
||||
DescriptorSetLayoutManager(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks) {
|
||||
if (auto itr = mVkLayouts.find(bitmasks); itr != mVkLayouts.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
auto vklayout = createLayout(mDevice, bitmasks);
|
||||
mVkLayouts[bitmasks] = vklayout;
|
||||
return vklayout;
|
||||
}
|
||||
|
||||
~DescriptorSetLayoutManager() {
|
||||
for (auto& itr: mVkLayouts) {
|
||||
vkDestroyDescriptorSetLayout(mDevice, itr.second, VKALLOC);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
tsl::robin_map<BitmaskGroup, VkDescriptorSetLayout, BitmaskGroupHashFn, BitmaskGroupEqual>
|
||||
mVkLayouts;
|
||||
};
|
||||
|
||||
VulkanDescriptorSetManager::VulkanDescriptorSetManager(VkDevice device,
|
||||
VulkanDescriptorSetCache::VulkanDescriptorSetCache(VkDevice device,
|
||||
fvkmemory::ResourceManager* resourceManager)
|
||||
: mDevice(device),
|
||||
mResourceManager(resourceManager),
|
||||
mLayoutManager(std::make_unique<DescriptorSetLayoutManager>(device)),
|
||||
mDescriptorPool(std::make_unique<DescriptorInfinitePool>(device)) {}
|
||||
|
||||
VulkanDescriptorSetManager::~VulkanDescriptorSetManager() = default;
|
||||
VulkanDescriptorSetCache::~VulkanDescriptorSetCache() = default;
|
||||
|
||||
void VulkanDescriptorSetManager::terminate() noexcept{
|
||||
mLayoutManager.reset();
|
||||
void VulkanDescriptorSetCache::terminate() noexcept{
|
||||
mDescriptorPool.reset();
|
||||
clearHistory();
|
||||
}
|
||||
|
||||
// bind() is not really binding the set but just stashing until we have all the info
|
||||
// (pipelinelayout).
|
||||
void VulkanDescriptorSetManager::bind(uint8_t setIndex,
|
||||
void VulkanDescriptorSetCache::bind(uint8_t setIndex,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
backend::DescriptorSetOffsetArray&& offsets) {
|
||||
set->setOffsets(std::move(offsets));
|
||||
mStashedSets[setIndex] = set;
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::unbind(uint8_t setIndex) {
|
||||
void VulkanDescriptorSetCache::unbind(uint8_t setIndex) {
|
||||
mStashedSets[setIndex] = {};
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::commit(VulkanCommandBuffer* commands,
|
||||
void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
|
||||
VkPipelineLayout pipelineLayout, fvkutils::DescriptorSetMask const& setMask) {
|
||||
// setMask indicates the set of descriptor sets the driver wants to bind, curMask is the
|
||||
// actual set of sets that *needs* to be bound.
|
||||
@@ -412,7 +315,7 @@ void VulkanDescriptorSetManager::commit(VulkanCommandBuffer* commands,
|
||||
};
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::updateBuffer(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanBufferObject> bufferObject,
|
||||
VkDeviceSize offset, VkDeviceSize size) noexcept {
|
||||
VkDescriptorBufferInfo const info = {
|
||||
@@ -438,7 +341,7 @@ void VulkanDescriptorSetManager::updateBuffer(fvkmemory::resource_ptr<VulkanDesc
|
||||
set->acquire(bufferObject);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
|
||||
VkSampler sampler) noexcept {
|
||||
VkDescriptorImageInfo info{
|
||||
@@ -470,13 +373,13 @@ void VulkanDescriptorSetManager::updateSampler(fvkmemory::resource_ptr<VulkanDes
|
||||
set->acquire(texture);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::updateInputAttachment(
|
||||
void VulkanDescriptorSetCache::updateInputAttachment(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
VulkanAttachment const& attachment) noexcept {
|
||||
// TOOD: fill-in this region
|
||||
// TOOD: fill this in.
|
||||
}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetManager::createSet(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet(
|
||||
Handle<HwDescriptorSet> handle, fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
auto const vkSet = mDescriptorPool->obtainSet(layout);
|
||||
auto const& count = layout->count;
|
||||
@@ -492,12 +395,7 @@ fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetManager::createS
|
||||
});
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::initVkLayout(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
layout->setVkLayout(mLayoutManager->getVkLayout(layout->bitmask));
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::clearHistory() {
|
||||
void VulkanDescriptorSetCache::clearHistory() {
|
||||
mStashedSets = {};
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
|
||||
#define TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
|
||||
#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETCACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETCACHE_H
|
||||
|
||||
#include "vulkan/VulkanHandles.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
#include "vulkan/utils/Definitions.h" // For DescriptorSetMask
|
||||
|
||||
@@ -34,20 +34,16 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
// [GDSR]: Great-Descriptor-Set-Refactor: As of 03/20/24, the Filament frontend is planning to
|
||||
// introduce descriptor set. This PR will arrive before that change is complete. As such, some of
|
||||
// the methods introduced here will be obsolete, and certain logic will be generalized.
|
||||
|
||||
// Abstraction over the pool and the layout cache.
|
||||
class VulkanDescriptorSetManager {
|
||||
// Abstraction over the descriptor set pool.
|
||||
class VulkanDescriptorSetCache {
|
||||
public:
|
||||
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT =
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT;
|
||||
|
||||
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
||||
|
||||
VulkanDescriptorSetManager(VkDevice device, fvkmemory::ResourceManager* resourceManager);
|
||||
~VulkanDescriptorSetManager();
|
||||
VulkanDescriptorSetCache(VkDevice device, fvkmemory::ResourceManager* resourceManager);
|
||||
~VulkanDescriptorSetCache();
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
@@ -72,12 +68,9 @@ public:
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
void initVkLayout(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
void clearHistory();
|
||||
|
||||
private:
|
||||
class DescriptorSetLayoutManager;
|
||||
class DescriptorInfinitePool;
|
||||
|
||||
using DescriptorSetArray =
|
||||
@@ -85,7 +78,6 @@ private:
|
||||
|
||||
VkDevice mDevice;
|
||||
fvkmemory::ResourceManager* mResourceManager;
|
||||
std::unique_ptr<DescriptorSetLayoutManager> mLayoutManager;
|
||||
std::unique_ptr<DescriptorInfinitePool> mDescriptorPool;
|
||||
std::pair<VulkanAttachment, VkDescriptorImageInfo> mInputAttachment;
|
||||
DescriptorSetArray mStashedSets = {};
|
||||
@@ -99,4 +91,4 @@ private:
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
|
||||
#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETCACHE_H
|
||||
110
filament/backend/src/vulkan/VulkanDescriptorSetLayoutCache.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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 "VulkanDescriptorSetLayoutCache.h"
|
||||
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask;
|
||||
|
||||
template<typename Bitmask>
|
||||
uint32_t appendBindings(VkDescriptorSetLayoutBinding* toBind, VkDescriptorType type,
|
||||
Bitmask const& mask) {
|
||||
uint32_t count = 0;
|
||||
Bitmask alreadySeen;
|
||||
mask.forEachSetBit([&](size_t index) {
|
||||
VkShaderStageFlags stages = 0;
|
||||
uint32_t binding = 0;
|
||||
if (index < fvkutils::getFragmentStageShift<Bitmask>()) {
|
||||
binding = (uint32_t) index;
|
||||
stages |= VK_SHADER_STAGE_VERTEX_BIT;
|
||||
auto fragIndex = index + fvkutils::getFragmentStageShift<Bitmask>();
|
||||
if (mask.test(fragIndex)) {
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
alreadySeen.set(fragIndex);
|
||||
}
|
||||
} else if (!alreadySeen.test(index)) {
|
||||
// We are in fragment stage bits
|
||||
binding = (uint32_t) (index - fvkutils::getFragmentStageShift<Bitmask>());
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
}
|
||||
|
||||
if (stages) {
|
||||
toBind[count++] = {
|
||||
.binding = binding,
|
||||
.descriptorType = type,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = stages,
|
||||
};
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
VulkanDescriptorSetLayoutCache::VulkanDescriptorSetLayoutCache(VkDevice device,
|
||||
fvkmemory::ResourceManager* resourceManager)
|
||||
: mDevice(device),
|
||||
mResourceManager(resourceManager) {}
|
||||
|
||||
VulkanDescriptorSetLayoutCache::~VulkanDescriptorSetLayoutCache() = default;
|
||||
|
||||
void VulkanDescriptorSetLayoutCache::terminate() noexcept {
|
||||
for (auto& itr: mVkLayouts) {
|
||||
vkDestroyDescriptorSetLayout(mDevice, itr.second, VKALLOC);
|
||||
}
|
||||
}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> VulkanDescriptorSetLayoutCache::createLayout(
|
||||
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info) {
|
||||
auto layout = fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::make(mResourceManager, handle,
|
||||
info);
|
||||
VkDescriptorSetLayout vklayout = VK_NULL_HANDLE;
|
||||
auto const& bitmasks = layout->bitmask;
|
||||
if (auto itr = mVkLayouts.find(bitmasks); itr != mVkLayouts.end()) {
|
||||
vklayout = itr->second;
|
||||
} else {
|
||||
VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
|
||||
uint32_t count = 0;
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
||||
bitmasks.dynamicUbo);
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmasks.ubo);
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
bitmasks.sampler);
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
|
||||
bitmasks.inputAttachment);
|
||||
|
||||
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
|
||||
VkDescriptorSetLayoutCreateInfo dlinfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.bindingCount = count,
|
||||
.pBindings = toBind,
|
||||
};
|
||||
|
||||
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &vklayout);
|
||||
mVkLayouts[bitmasks] = vklayout;
|
||||
}
|
||||
layout->setVkLayout(vklayout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
63
filament/backend/src/vulkan/VulkanDescriptorSetLayoutCache.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETLAYOUTCACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETLAYOUTCACHE_H
|
||||
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Program.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <utils/bitset.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanDescriptorSetLayoutCache {
|
||||
public:
|
||||
VulkanDescriptorSetLayoutCache(VkDevice device, fvkmemory::ResourceManager* resourceManager);
|
||||
~VulkanDescriptorSetLayoutCache();
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> createLayout(
|
||||
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info);
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
fvkmemory::ResourceManager* mResourceManager;
|
||||
|
||||
using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask;
|
||||
using BitmaskGroupHashFn = utils::hash::MurmurHashFn<BitmaskGroup>;
|
||||
struct BitmaskGroupEqual {
|
||||
bool operator()(BitmaskGroup const& k1, BitmaskGroup const& k2) const { return k1 == k2; }
|
||||
};
|
||||
|
||||
tsl::robin_map<BitmaskGroup, VkDescriptorSetLayout, BitmaskGroupHashFn, BitmaskGroupEqual>
|
||||
mVkLayouts;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETLAYOUTCACHE_H
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "VulkanDriver.h"
|
||||
|
||||
#include "CommandStreamDispatcher.h"
|
||||
#include "DataReshaper.h"
|
||||
#include "SystraceProfile.h"
|
||||
#include "VulkanAsyncHandles.h"
|
||||
#include "VulkanBuffer.h"
|
||||
@@ -35,7 +34,6 @@
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -44,8 +42,6 @@
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
using utils::FixedCapacityVector;
|
||||
|
||||
#if defined(__clang__)
|
||||
// Vulkan functions often immediately dereference pointers, so it's fine to pass in a pointer
|
||||
// to a stack-allocated variable.
|
||||
@@ -219,7 +215,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
mSamplerCache(mPlatform->getDevice()),
|
||||
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
|
||||
mReadPixels(mPlatform->getDevice()),
|
||||
mDescriptorSetManager(mPlatform->getDevice(), &mResourceManager),
|
||||
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager),
|
||||
mDescriptorSetCache(mPlatform->getDevice(), &mResourceManager),
|
||||
mQueryManager(mPlatform->getDevice()),
|
||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
|
||||
mStereoscopicType(driverConfig.stereoscopicType) {
|
||||
@@ -329,7 +326,8 @@ void VulkanDriver::terminate() {
|
||||
mPipelineCache.terminate();
|
||||
mFramebufferCache.reset();
|
||||
mSamplerCache.terminate();
|
||||
mDescriptorSetManager.terminate();
|
||||
mDescriptorSetLayoutCache.terminate();
|
||||
mDescriptorSetCache.terminate();
|
||||
mPipelineLayoutCache.terminate();
|
||||
|
||||
// Before terminating ResourceManager, we must make sure all of the resource_ptrs have been unset.
|
||||
@@ -365,7 +363,7 @@ void VulkanDriver::collectGarbage() {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
// Command buffers need to be submitted and completed before other resources can be gc'd.
|
||||
mCommands.gc();
|
||||
mDescriptorSetManager.clearHistory();
|
||||
mDescriptorSetCache.clearHistory();
|
||||
mStagePool.gc();
|
||||
mFramebufferCache.gc();
|
||||
mPipelineCache.gc();
|
||||
@@ -408,7 +406,7 @@ void VulkanDriver::updateDescriptorSetBuffer(
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||
auto buffer = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
|
||||
mDescriptorSetManager.updateBuffer(set, binding, buffer, offset, size);
|
||||
mDescriptorSetCache.updateBuffer(set, binding, buffer, offset, size);
|
||||
}
|
||||
|
||||
void VulkanDriver::updateDescriptorSetTexture(
|
||||
@@ -421,7 +419,7 @@ void VulkanDriver::updateDescriptorSetTexture(
|
||||
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
|
||||
|
||||
VkSampler const vksampler = mSamplerCache.getSampler(params);
|
||||
mDescriptorSetManager.updateSampler(set, binding, texture, vksampler);
|
||||
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
|
||||
}
|
||||
|
||||
void VulkanDriver::flush(int) {
|
||||
@@ -778,8 +776,7 @@ void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
||||
|
||||
void VulkanDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
|
||||
backend::DescriptorSetLayout&& info) {
|
||||
auto layout = resource_ptr<VulkanDescriptorSetLayout>::make(&mResourceManager, dslh, info);
|
||||
mDescriptorSetManager.initVkLayout(layout);
|
||||
auto layout = mDescriptorSetLayoutCache.createLayout(dslh, std::move(info));
|
||||
layout.inc();
|
||||
}
|
||||
|
||||
@@ -788,7 +785,7 @@ void VulkanDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout =
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, dslh);
|
||||
auto set = mDescriptorSetManager.createSet(dsh, layout);
|
||||
auto set = mDescriptorSetCache.createSet(dsh, layout);
|
||||
set.inc();
|
||||
}
|
||||
|
||||
@@ -1493,7 +1490,7 @@ void VulkanDriver::nextSubpass(int) {
|
||||
|
||||
if (mCurrentRenderPass.params.subpassMask & 0x1) {
|
||||
VulkanAttachment& subpassInput = renderTarget->getColor0();
|
||||
mDescriptorSetManager.updateInputAttachment({}, subpassInput);
|
||||
mDescriptorSetCache.updateInputAttachment({}, subpassInput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1820,9 +1817,9 @@ void VulkanDriver::bindDescriptorSet(
|
||||
backend::DescriptorSetOffsetArray&& offsets) {
|
||||
if (dsh) {
|
||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||
mDescriptorSetManager.bind(setIndex, set, std::move(offsets));
|
||||
mDescriptorSetCache.bind(setIndex, set, std::move(offsets));
|
||||
} else {
|
||||
mDescriptorSetManager.unbind(setIndex);
|
||||
mDescriptorSetCache.unbind(setIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1830,7 +1827,7 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
VkCommandBuffer cmdbuffer = mCurrentRenderPass.commandBuffer->buffer();
|
||||
|
||||
mDescriptorSetManager.commit(mCurrentRenderPass.commandBuffer,
|
||||
mDescriptorSetCache.commit(mCurrentRenderPass.commandBuffer,
|
||||
mBoundPipeline.pipelineLayout,
|
||||
mBoundPipeline.descriptorSetMask);
|
||||
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
#include "VulkanSamplerCache.h"
|
||||
#include "VulkanStagePool.h"
|
||||
#include "VulkanQueryManager.h"
|
||||
#include "vulkan/caching/VulkanDescriptorSetManager.h"
|
||||
#include "vulkan/caching/VulkanPipelineLayoutCache.h"
|
||||
#include "vulkan/VulkanDescriptorSetCache.h"
|
||||
#include "vulkan/VulkanDescriptorSetLayoutCache.h"
|
||||
#include "vulkan/VulkanPipelineLayoutCache.h"
|
||||
#include "vulkan/memory/ResourceManager.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
#include "vulkan/utils/Definitions.h"
|
||||
@@ -137,7 +138,8 @@ private:
|
||||
VulkanSamplerCache mSamplerCache;
|
||||
VulkanBlitter mBlitter;
|
||||
VulkanReadPixels mReadPixels;
|
||||
VulkanDescriptorSetManager mDescriptorSetManager;
|
||||
VulkanDescriptorSetLayoutCache mDescriptorSetLayoutCache;
|
||||
VulkanDescriptorSetCache mDescriptorSetCache;
|
||||
VulkanQueryManager mQueryManager;
|
||||
|
||||
// This is necessary for us to write to push constants after binding a pipeline.
|
||||
|
||||
@@ -15,15 +15,12 @@
|
||||
*/
|
||||
|
||||
#include "VulkanPipelineCache.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "caching/VulkanDescriptorSetManager.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "vulkan/utils/Conversion.h"
|
||||
|
||||
#if defined(__clang__)
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANPIPELINELAYOUTCACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANPIPELINELAYOUTCACHE_H
|
||||
|
||||
#include <vulkan/VulkanHandles.h>
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
#include <utils/Hash.h>
|
||||
@@ -1000,6 +1000,15 @@ VkImageView VulkanPlatform::createExternalImageView(SamplerYcbcrConversion chrom
|
||||
return createExternalImageViewImpl(mImpl->mDevice, chroma, internalFormat, image, range,
|
||||
viewType, swizzle);
|
||||
}
|
||||
|
||||
ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const {
|
||||
return getSwapchainInstanceExtensionsImpl();
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) const noexcept {
|
||||
return createVkSurfaceKHRImpl(nativeWindow, instance, flags);
|
||||
}
|
||||
#undef SWAPCHAIN_RET_FUNC
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -13,10 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
#include <backend/platforms/VulkanPlatformAndroid.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/platforms/VulkanPlatformAndroid.h>
|
||||
#include <private/backend/BackendUtilsAndroid.h>
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
@@ -157,7 +156,7 @@ VulkanPlatform::ImageData allocateExternalImage(AHardwareBuffer* buffer, VkDevic
|
||||
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
|
||||
};
|
||||
|
||||
VkImageCreateInfo imageInfo{.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
VkImageCreateInfo imageInfo{ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
|
||||
imageInfo.pNext = &externalCreateInfo;
|
||||
imageInfo.format = metadata.format;
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
@@ -200,14 +199,12 @@ VulkanPlatform::ImageData allocateExternalImage(AHardwareBuffer* buffer, VkDevic
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
}// namespace
|
||||
|
||||
namespace fvkandroid {
|
||||
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() = default;
|
||||
|
||||
ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() = default;
|
||||
|
||||
Platform::ExternalImageHandle createExternalImage(AHardwareBuffer const* buffer,
|
||||
bool sRGB) noexcept {
|
||||
Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
|
||||
AHardwareBuffer const* buffer, bool sRGB) noexcept {
|
||||
if (__builtin_available(android 26, *)) {
|
||||
AHardwareBuffer_Desc hardwareBufferDescription = {};
|
||||
AHardwareBuffer_describe(buffer, &hardwareBufferDescription);
|
||||
@@ -226,12 +223,23 @@ Platform::ExternalImageHandle createExternalImage(AHardwareBuffer const* buffer,
|
||||
return Platform::ExternalImageHandle{};
|
||||
}
|
||||
|
||||
} // namespace fvkandroid
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device) {
|
||||
VulkanPlatformAndroid::ExternalImageDescAndroid VulkanPlatformAndroid::getExternalImageDesc(
|
||||
ExternalImageHandleRef externalImage) const noexcept {
|
||||
auto const* fvkExternalImage =
|
||||
static_cast<fvkandroid::ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
|
||||
return {
|
||||
.width = fvkExternalImage->width,
|
||||
.height = fvkExternalImage->height,
|
||||
.format = fvkExternalImage->format,
|
||||
.usage = fvkExternalImage->usage,
|
||||
};
|
||||
}
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMetadata(
|
||||
ExternalImageHandleRef externalImage) {
|
||||
auto const* fvkExternalImage =
|
||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
|
||||
ExternalImageMetadata metadata;
|
||||
AHardwareBuffer* buffer = fvkExternalImage->aHardwareBuffer;
|
||||
@@ -245,7 +253,7 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataIm
|
||||
std::tie(metadata.format, metadata.usage) =
|
||||
getVKFormatAndUsage(bufferDesc, fvkExternalImage->sRGB);
|
||||
}
|
||||
|
||||
|
||||
metadata.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
|
||||
VkAndroidHardwareBufferFormatPropertiesANDROID formatInfo = {
|
||||
@@ -256,11 +264,11 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataIm
|
||||
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
|
||||
.pNext = &formatInfo,
|
||||
};
|
||||
VkResult result = vkGetAndroidHardwareBufferPropertiesANDROID(device, buffer, &properties);
|
||||
VkResult result = vkGetAndroidHardwareBufferPropertiesANDROID(getDevice(), buffer, &properties);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkGetAndroidHardwareBufferProperties failed with error="
|
||||
<< static_cast<int32_t>(result);
|
||||
|
||||
<< "vkGetAndroidHardwareBufferProperties failed with error="
|
||||
<< static_cast<int32_t>(result);
|
||||
|
||||
VkFormat bufferPropertiesFormat = transformVkFormat(formatInfo.format, fvkExternalImage->sRGB);
|
||||
FILAMENT_CHECK_POSTCONDITION(metadata.format == bufferPropertiesFormat)
|
||||
<< "mismatched image format( " << metadata.format << ") and queried format("
|
||||
@@ -271,14 +279,14 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataIm
|
||||
return metadata;
|
||||
}
|
||||
|
||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
VulkanPlatformAndroid::ImageData VulkanPlatformAndroid::createExternalImageData(
|
||||
ExternalImageHandleRef externalImage, const ExternalImageMetadata& metadata,
|
||||
uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
auto const* fvkExternalImage =
|
||||
static_cast<fvkandroid::ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
ImageData data = allocateExternalImage(fvkExternalImage->aHardwareBuffer, device, metadata,
|
||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
ImageData data = allocateExternalImage(fvkExternalImage->aHardwareBuffer, getDevice(), metadata,
|
||||
memoryTypeIndex, usage);
|
||||
VkResult result = vkBindImageMemory(device, data.first, data.second, 0);
|
||||
VkResult result = vkBindImageMemory(getDevice(), data.first, data.second, 0);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkBindImageMemory error=" << static_cast<int32_t>(result);
|
||||
return data;
|
||||
@@ -397,14 +405,14 @@ VkSampler VulkanPlatform::createExternalSamplerImpl(
|
||||
return sampler;
|
||||
}
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() {
|
||||
VulkanPlatform::ExtensionSet VulkanPlatformAndroid::getSwapchainInstanceExtensions() const {
|
||||
return {
|
||||
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
|
||||
};
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatformAndroid::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) const noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
VkExtent2D extent;
|
||||
|
||||
@@ -416,6 +424,26 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
|
||||
vkCreateAndroidSurfaceKHR(instance, &createInfo, VKALLOC, (VkSurfaceKHR*) &surface);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkCreateAndroidSurfaceKHR with error=" << static_cast<int32_t>(result);
|
||||
return {surface, extent};
|
||||
return { surface, extent };
|
||||
}
|
||||
|
||||
// Deprecated platform dependent helper methods
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { return {}; }
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device) {
|
||||
return ExternalImageMetadata{};
|
||||
}
|
||||
|
||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
return ImageData{};
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
return SurfaceBundle{};
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -52,7 +52,7 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() {
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
|
||||
ExtensionSet const ret = {
|
||||
#if defined(__APPLE__)
|
||||
VK_MVK_MACOS_SURFACE_EXTENSION_NAME, // TODO: replace with VK_EXT_metal_surface
|
||||
@@ -87,7 +87,7 @@ VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWindow,
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
#if defined(__APPLE__)
|
||||
|
||||
@@ -108,7 +108,7 @@ VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() {
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
|
||||
VulkanPlatform::ExtensionSet const ret = {
|
||||
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
||||
VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
|
||||
@@ -126,7 +126,7 @@ VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWindow,
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
|
||||
|
||||
@@ -651,11 +651,11 @@ uint8_t WebGPUDriver::getMaxDrawBuffers() {
|
||||
return MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT;
|
||||
}
|
||||
|
||||
size_t WebGPUDriver::getMaxUniformBufferSize(SamplerType) {
|
||||
size_t WebGPUDriver::getMaxUniformBufferSize() {
|
||||
return 16384u;
|
||||
}
|
||||
|
||||
size_t WebGPUDriver::getMaxTextureSize() {
|
||||
size_t WebGPUDriver::getMaxTextureSize(SamplerType target) {
|
||||
return 2048u;
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
|
||||
return device;
|
||||
}
|
||||
|
||||
Driver* WebGPUPlatform::createDriver(void* const sharedContext,
|
||||
Driver* WebGPUPlatform::createDriver(void* sharedContext,
|
||||
const Platform::DriverConfig& /*driverConfig*/) noexcept {
|
||||
if (sharedContext) {
|
||||
FWGPU_LOGW << "sharedContext is ignored/unused in the WebGPU backend. A non-null "
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
@@ -118,7 +120,9 @@ wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t flags)
|
||||
if (useXcb) {
|
||||
wgpu::SurfaceSourceXCBWindow surfaceSourceXcb{};
|
||||
surfaceSourceXcb.connection = g_x11.connection;
|
||||
surfaceSourceXcb.window = reinterpret_cast<uint32_t>(nativeWindow);
|
||||
|
||||
// TODO: this looks really wrong, please fix!!
|
||||
surfaceSourceXcb.window = *((uint32_t*) nativeWindow);
|
||||
wgpu::SurfaceDescriptor surfaceDescriptor{
|
||||
.nextInChain = &surfaceSourceXcb,
|
||||
.label = "linux_xcb_surface"
|
||||
|
||||
@@ -64,9 +64,8 @@ class ColorSpace;
|
||||
* Performance
|
||||
* ===========
|
||||
*
|
||||
* Creating a new ColorGrading object may be more expensive than other Filament objects as a
|
||||
* 3D LUT may need to be generated. The generation of a 3D LUT, if necessary, may happen on
|
||||
* the CPU.
|
||||
* Creating a new ColorGrading object may be more expensive than other Filament objects as a LUT may
|
||||
* need to be generated. The generation of this LUT, if necessary, may happen on the CPU.
|
||||
*
|
||||
* Ordering
|
||||
* ========
|
||||
@@ -155,6 +154,9 @@ public:
|
||||
* 3D texture. For instance, a low quality level will use a 16x16x16 10 bit LUT, a medium
|
||||
* quality level will use a 32x32x32 10 bit LUT, a high quality will use a 32x32x32 16 bit
|
||||
* LUT, and a ultra quality will use a 64x64x64 16 bit LUT.
|
||||
*
|
||||
* This setting has no effect if generating a 1D LUT.
|
||||
*
|
||||
* This overrides the values set by format() and dimensions().
|
||||
*
|
||||
* The default quality is medium.
|
||||
@@ -169,6 +171,8 @@ public:
|
||||
* When color grading is implemented using a 3D LUT, this sets the texture format of
|
||||
* of the LUT. This overrides the value set by quality().
|
||||
*
|
||||
* This setting has no effect if generating a 1D LUT.
|
||||
*
|
||||
* The default is INTEGER
|
||||
*
|
||||
* @param format The desired format of the 3D LUT.
|
||||
@@ -181,6 +185,8 @@ public:
|
||||
* When color grading is implemented using a 3D LUT, this sets the dimension of the LUT.
|
||||
* This overrides the value set by quality().
|
||||
*
|
||||
* This setting has no effect if generating a 1D LUT.
|
||||
*
|
||||
* The default is 32
|
||||
*
|
||||
* @param dim The desired dimension of the LUT. Between 16 and 64.
|
||||
|
||||
@@ -68,6 +68,21 @@ struct UTILS_PUBLIC ToneMapper {
|
||||
* function applied ("linear")
|
||||
*/
|
||||
virtual math::float3 operator()(math::float3 c) const noexcept = 0;
|
||||
|
||||
/**
|
||||
* If true, then this function holds that f(x) = vec3(f(x.r), f(x.g), f(x.b))
|
||||
*
|
||||
* This may be used to indicate that the color grading's LUT only requires a 1D texture instead
|
||||
* of a 3D texture, potentially saving a significant amount of memory and generation time.
|
||||
*/
|
||||
virtual bool isOneDimensional() const noexcept { return false; }
|
||||
|
||||
/**
|
||||
* True if this tonemapper only works in low-dynamic-range.
|
||||
*
|
||||
* This may be used to indicate that the color grading's LUT doesn't need to be log encoded.
|
||||
*/
|
||||
virtual bool isLDR() const noexcept { return false; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -79,6 +94,8 @@ struct UTILS_PUBLIC LinearToneMapper final : public ToneMapper {
|
||||
~LinearToneMapper() noexcept final;
|
||||
|
||||
math::float3 operator()(math::float3 c) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return true; }
|
||||
bool isLDR() const noexcept override { return true; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -91,6 +108,8 @@ struct UTILS_PUBLIC ACESToneMapper final : public ToneMapper {
|
||||
~ACESToneMapper() noexcept final;
|
||||
|
||||
math::float3 operator()(math::float3 c) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return false; }
|
||||
bool isLDR() const noexcept override { return false; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -104,6 +123,8 @@ struct UTILS_PUBLIC ACESLegacyToneMapper final : public ToneMapper {
|
||||
~ACESLegacyToneMapper() noexcept final;
|
||||
|
||||
math::float3 operator()(math::float3 c) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return false; }
|
||||
bool isLDR() const noexcept override { return false; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -117,6 +138,8 @@ struct UTILS_PUBLIC FilmicToneMapper final : public ToneMapper {
|
||||
~FilmicToneMapper() noexcept final;
|
||||
|
||||
math::float3 operator()(math::float3 x) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return true; }
|
||||
bool isLDR() const noexcept override { return false; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -129,6 +152,8 @@ struct UTILS_PUBLIC PBRNeutralToneMapper final : public ToneMapper {
|
||||
~PBRNeutralToneMapper() noexcept final;
|
||||
|
||||
math::float3 operator()(math::float3 x) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return false; }
|
||||
bool isLDR() const noexcept override { return false; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -150,6 +175,8 @@ struct UTILS_PUBLIC AgxToneMapper final : public ToneMapper {
|
||||
~AgxToneMapper() noexcept final;
|
||||
|
||||
math::float3 operator()(math::float3 x) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return false; }
|
||||
bool isLDR() const noexcept override { return false; }
|
||||
|
||||
AgxLook look;
|
||||
};
|
||||
@@ -194,6 +221,8 @@ struct UTILS_PUBLIC GenericToneMapper final : public ToneMapper {
|
||||
GenericToneMapper& operator=(GenericToneMapper&& rhs) noexcept;
|
||||
|
||||
math::float3 operator()(math::float3 x) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return true; }
|
||||
bool isLDR() const noexcept override { return false; }
|
||||
|
||||
/** Returns the contrast of the curve as a strictly positive value. */
|
||||
float getContrast() const noexcept;
|
||||
@@ -256,6 +285,8 @@ struct UTILS_PUBLIC DisplayRangeToneMapper final : public ToneMapper {
|
||||
~DisplayRangeToneMapper() noexcept override;
|
||||
|
||||
math::float3 operator()(math::float3 c) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return false; }
|
||||
bool isLDR() const noexcept override { return false; }
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -2306,36 +2306,11 @@ void PostProcessManager::colorGradingPrepareSubpass(DriverApi& driver,
|
||||
const FColorGrading* colorGrading, ColorGradingConfig const& colorGradingConfig,
|
||||
VignetteOptions const& vignetteOptions, uint32_t const width, uint32_t const height) noexcept {
|
||||
|
||||
float4 const vignetteParameters = getVignetteParameters(vignetteOptions, width, height);
|
||||
|
||||
auto const& material = getPostProcessMaterial("colorGradingAsSubpass");
|
||||
FMaterialInstance* const mi = PostProcessMaterial::getMaterialInstance(mEngine, material);
|
||||
|
||||
mi->setParameter("lut", colorGrading->getHwHandle(), {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR,
|
||||
.wrapS = SamplerWrapMode::CLAMP_TO_EDGE,
|
||||
.wrapT = SamplerWrapMode::CLAMP_TO_EDGE,
|
||||
.wrapR = SamplerWrapMode::CLAMP_TO_EDGE,
|
||||
.anisotropyLog2 = 0
|
||||
});
|
||||
const float lutDimension = float(colorGrading->getDimension());
|
||||
mi->setParameter("lutSize", float2{
|
||||
0.5f / lutDimension, (lutDimension - 1.0f) / lutDimension,
|
||||
});
|
||||
|
||||
const float temporalNoise = mUniformDistribution(mEngine.getRandomEngine());
|
||||
|
||||
mi->setParameter("vignette", vignetteParameters);
|
||||
mi->setParameter("vignetteColor", vignetteOptions.color);
|
||||
mi->setParameter("dithering", colorGradingConfig.dithering);
|
||||
mi->setParameter("outputLuminance", colorGradingConfig.outputLuminance);
|
||||
mi->setParameter("temporalNoise", temporalNoise);
|
||||
auto& material = getPostProcessMaterial("colorGradingAsSubpass");
|
||||
FMaterialInstance* const mi =
|
||||
configureColorGradingMaterial(material, colorGrading, colorGradingConfig,
|
||||
vignetteOptions, width, height);
|
||||
mi->commit(driver);
|
||||
|
||||
// load both variants
|
||||
material.getMaterial(mEngine, PostProcessVariant::OPAQUE);
|
||||
material.getMaterial(mEngine, PostProcessVariant::TRANSLUCENT);
|
||||
}
|
||||
|
||||
void PostProcessManager::colorGradingSubpass(DriverApi& driver,
|
||||
@@ -2487,22 +2462,14 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
|
||||
auto const& out = resources.getRenderPassInfo();
|
||||
|
||||
auto const& material = getPostProcessMaterial("colorGrading");
|
||||
|
||||
PostProcessVariant const variant = colorGradingConfig.translucent ?
|
||||
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE;
|
||||
auto const& input = resources.getDescriptor(data.input);
|
||||
auto const& output = resources.getDescriptor(data.output);
|
||||
|
||||
auto& material = getPostProcessMaterial("colorGrading");
|
||||
FMaterialInstance* const mi =
|
||||
PostProcessMaterial::getMaterialInstance(mEngine, material, variant);
|
||||
configureColorGradingMaterial(material, colorGrading, colorGradingConfig,
|
||||
vignetteOptions, output.width, output.height);
|
||||
|
||||
mi->setParameter("lut", colorGrading->getHwHandle(), {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR
|
||||
});
|
||||
const float lutDimension = float(colorGrading->getDimension());
|
||||
mi->setParameter("lutSize", float2{
|
||||
0.5f / lutDimension, (lutDimension - 1.0f) / lutDimension,
|
||||
});
|
||||
mi->setParameter("colorBuffer", colorTexture, { /* shader uses texelFetch */ });
|
||||
mi->setParameter("bloomBuffer", bloomTexture, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
@@ -2534,19 +2501,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
bloomParameters.y = 1.0f - bloomParameters.x;
|
||||
}
|
||||
|
||||
auto const& input = resources.getDescriptor(data.input);
|
||||
auto const& output = resources.getDescriptor(data.output);
|
||||
float4 const vignetteParameters = getVignetteParameters(
|
||||
vignetteOptions, output.width, output.height);
|
||||
|
||||
const float temporalNoise = mUniformDistribution(mEngine.getRandomEngine());
|
||||
|
||||
mi->setParameter("dithering", colorGradingConfig.dithering);
|
||||
mi->setParameter("bloom", bloomParameters);
|
||||
mi->setParameter("vignette", vignetteParameters);
|
||||
mi->setParameter("vignetteColor", vignetteOptions.color);
|
||||
mi->setParameter("outputLuminance", colorGradingConfig.outputLuminance);
|
||||
mi->setParameter("temporalNoise", temporalNoise);
|
||||
mi->setParameter("viewport", float4{
|
||||
float(vp.left) / input.width,
|
||||
float(vp.bottom) / input.height,
|
||||
@@ -2554,7 +2509,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
float(vp.height) / input.height
|
||||
});
|
||||
|
||||
commitAndRenderFullScreenQuad(driver, out, mi, variant);
|
||||
commitAndRenderFullScreenQuad(driver, out, mi,
|
||||
colorGradingConfig.translucent
|
||||
? PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2692,11 +2649,70 @@ void PostProcessManager::configureTemporalAntiAliasingMaterial(
|
||||
setConstantParameter(ma, "varianceGamma", taaOptions.varianceGamma);
|
||||
if (dirty) {
|
||||
ma->invalidate();
|
||||
// TODO: call Material::compile(), we can't si that now because it works only
|
||||
// TODO: call Material::compile(), we can't do that now because it works only
|
||||
// with surface materials
|
||||
}
|
||||
}
|
||||
|
||||
FMaterialInstance* PostProcessManager::configureColorGradingMaterial(
|
||||
PostProcessMaterial& material, FColorGrading const* colorGrading,
|
||||
ColorGradingConfig const& colorGradingConfig, VignetteOptions const& vignetteOptions,
|
||||
uint32_t const width, uint32_t const height) noexcept {
|
||||
FMaterial* const ma = material.getMaterial(mEngine);
|
||||
bool dirty = false;
|
||||
|
||||
auto setConstantParameter =
|
||||
[&dirty](FMaterial* const material, std::string_view const name, auto value) noexcept {
|
||||
auto id = material->getSpecializationConstantId(name);
|
||||
if (id.has_value()) {
|
||||
if (material->setConstant(id.value(), value)) {
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setConstantParameter(ma, "isOneDimensional", colorGrading->isOneDimensional());
|
||||
setConstantParameter(ma, "isLDR", colorGrading->isLDR());
|
||||
|
||||
if (dirty) {
|
||||
ma->invalidate();
|
||||
// TODO: call Material::compile(), we can't do that now because it works only
|
||||
// with surface materials
|
||||
}
|
||||
|
||||
PostProcessVariant const variant = colorGradingConfig.translucent ?
|
||||
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE;
|
||||
FMaterialInstance* const mi =
|
||||
PostProcessMaterial::getMaterialInstance(mEngine, material, variant);
|
||||
|
||||
const SamplerParams params = {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR,
|
||||
.wrapS = SamplerWrapMode::CLAMP_TO_EDGE,
|
||||
.wrapT = SamplerWrapMode::CLAMP_TO_EDGE,
|
||||
.wrapR = SamplerWrapMode::CLAMP_TO_EDGE,
|
||||
.anisotropyLog2 = 0
|
||||
};
|
||||
|
||||
mi->setParameter("lut", colorGrading->getHwHandle(), params);
|
||||
|
||||
const float lutDimension = float(colorGrading->getDimension());
|
||||
mi->setParameter("lutSize", float2{
|
||||
0.5f / lutDimension, (lutDimension - 1.0f) / lutDimension,
|
||||
});
|
||||
|
||||
const float temporalNoise = mUniformDistribution(mEngine.getRandomEngine());
|
||||
|
||||
float4 const vignetteParameters = getVignetteParameters(vignetteOptions, width, height);
|
||||
mi->setParameter("vignette", vignetteParameters);
|
||||
mi->setParameter("vignetteColor", vignetteOptions.color);
|
||||
mi->setParameter("dithering", colorGradingConfig.dithering);
|
||||
mi->setParameter("outputLuminance", colorGradingConfig.outputLuminance);
|
||||
mi->setParameter("temporalNoise", temporalNoise);
|
||||
|
||||
return mi;
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphId<FrameGraphTexture> const depth,
|
||||
|
||||
@@ -401,6 +401,13 @@ public:
|
||||
FMaterialInstance const* mi,
|
||||
PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept;
|
||||
|
||||
// Sets the necessary spec constants and uniforms common to both colorGrading.mat and
|
||||
// colorGradingAsSubpass.mat.
|
||||
FMaterialInstance* configureColorGradingMaterial(
|
||||
PostProcessMaterial& material, FColorGrading const* colorGrading,
|
||||
ColorGradingConfig const& colorGradingConfig, VignetteOptions const& vignetteOptions,
|
||||
uint32_t const width, uint32_t const height) noexcept;
|
||||
|
||||
private:
|
||||
backend::RenderPrimitiveHandle mFullScreenQuadRph;
|
||||
backend::VertexBufferInfoHandle mFullScreenQuadVbih;
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <tuple>
|
||||
|
||||
namespace filament {
|
||||
@@ -581,8 +580,11 @@ static float3 luminanceScaling(float3 x,
|
||||
// Quality
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static std::tuple<TextureFormat, PixelDataFormat, PixelDataType>
|
||||
selectLutTextureParams(ColorGrading::LutFormat const lutFormat) noexcept {
|
||||
static std::tuple<TextureFormat, PixelDataFormat, PixelDataType> selectLutTextureParams(
|
||||
ColorGrading::LutFormat const lutFormat, const bool isOneDimensional) noexcept {
|
||||
if (isOneDimensional) {
|
||||
return { TextureFormat::R16F, PixelDataFormat::R, PixelDataType::HALF };
|
||||
}
|
||||
// We use RGBA16F for high quality modes instead of RGB16F because RGB16F
|
||||
// is not supported everywhere
|
||||
switch (lutFormat) {
|
||||
@@ -658,27 +660,43 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) {
|
||||
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
Config c;
|
||||
// This lock protects the data inside Config, which is written to by the Filament thread,
|
||||
// and read from multiple Job threads.
|
||||
Mutex configLock;
|
||||
{
|
||||
std::lock_guard<Mutex> const lock(configLock);
|
||||
c.lutDimension = builder->dimension;
|
||||
c.adaptationTransform = adaptationTransform(builder->whiteBalance);
|
||||
c.colorGradingIn = selectColorGradingTransformIn(builder->toneMapping);
|
||||
c.colorGradingOut = selectColorGradingTransformOut(builder->toneMapping);
|
||||
c.colorGradingLuminance = selectColorGradingLuminance(builder->toneMapping);
|
||||
c.oetf = selectOETF(builder->outputColorSpace);
|
||||
// XXX: The following two conditions also only hold true as long as the input and output color
|
||||
// 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();
|
||||
mIsLDR = mIsOneDimensional && builder->toneMapper->isLDR();
|
||||
|
||||
const Config config = {
|
||||
mIsOneDimensional ? 512u : builder->dimension,
|
||||
adaptationTransform(builder->whiteBalance),
|
||||
selectColorGradingTransformIn(builder->toneMapping),
|
||||
selectColorGradingTransformOut(builder->toneMapping),
|
||||
selectColorGradingLuminance(builder->toneMapping),
|
||||
selectOETF(builder->outputColorSpace),
|
||||
};
|
||||
|
||||
mDimension = config.lutDimension;
|
||||
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t depth;
|
||||
if (mIsOneDimensional) {
|
||||
width = config.lutDimension;
|
||||
height = 1;
|
||||
depth = 1;
|
||||
} else {
|
||||
width = config.lutDimension;
|
||||
height = config.lutDimension;
|
||||
depth = config.lutDimension;
|
||||
}
|
||||
|
||||
mDimension = c.lutDimension;
|
||||
|
||||
size_t lutElementCount = c.lutDimension * c.lutDimension * c.lutDimension;
|
||||
size_t elementSize = sizeof(half4);
|
||||
size_t lutElementCount = width * height * depth;
|
||||
size_t elementSize = mIsOneDimensional ? sizeof(half) : sizeof(half4);
|
||||
void* data = malloc(lutElementCount * elementSize);
|
||||
|
||||
auto [textureFormat, format, type] = selectLutTextureParams(builder->format);
|
||||
auto [textureFormat, format, type] =
|
||||
selectLutTextureParams(builder->format, mIsOneDimensional);
|
||||
assert_invariant(FTexture::isTextureFormatSupported(engine, textureFormat));
|
||||
assert_invariant(FTexture::validatePixelFormatAndType(textureFormat, format, type));
|
||||
|
||||
@@ -688,170 +706,185 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) {
|
||||
converted = malloc(lutElementCount * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
auto hdrColorAt = [builder, config](size_t r, size_t g, size_t b) {
|
||||
float3 v = float3{r, g, b} * (1.0f / float(config.lutDimension - 1u));
|
||||
|
||||
// LogC encoding
|
||||
v = LogC_to_linear(v);
|
||||
|
||||
// Kill negative values near 0.0f due to imprecision in the log conversion
|
||||
v = max(v, 0.0f);
|
||||
|
||||
if (builder->hasAdjustments) {
|
||||
// Exposure
|
||||
v = adjustExposure(v, builder->exposure);
|
||||
|
||||
// Purkinje shift ("low-light" vision)
|
||||
v = scotopicAdaptation(v, builder->nightAdaptation);
|
||||
}
|
||||
|
||||
// Move to color grading color space
|
||||
v = config.colorGradingIn * v;
|
||||
|
||||
if (builder->hasAdjustments) {
|
||||
// White balance
|
||||
v = chromaticAdaptation(v, config.adaptationTransform);
|
||||
|
||||
// Kill negative values before the next transforms
|
||||
v = max(v, 0.0f);
|
||||
|
||||
// Channel mixer
|
||||
v = channelMixer(v, builder->outRed, builder->outGreen, builder->outBlue);
|
||||
|
||||
// Shadows/mid-tones/highlights
|
||||
v = tonalRanges(v, config.colorGradingLuminance,
|
||||
builder->shadows, builder->midtones, builder->highlights,
|
||||
builder->tonalRanges);
|
||||
|
||||
// The adjustments below behave better in log space
|
||||
v = linear_to_LogC(v);
|
||||
|
||||
// ASC CDL
|
||||
v = colorDecisionList(v, builder->slope, builder->offset, builder->power);
|
||||
|
||||
// Contrast in log space
|
||||
v = contrast(v, builder->contrast);
|
||||
|
||||
// Back to linear space
|
||||
v = LogC_to_linear(v);
|
||||
|
||||
// Vibrance in linear space
|
||||
v = vibrance(v, config.colorGradingLuminance, builder->vibrance);
|
||||
|
||||
// Saturation in linear space
|
||||
v = saturation(v, config.colorGradingLuminance, builder->saturation);
|
||||
|
||||
// Kill negative values before curves
|
||||
v = max(v, 0.0f);
|
||||
|
||||
// RGB curves
|
||||
v = curves(v,
|
||||
builder->shadowGamma, builder->midPoint, builder->highlightScale);
|
||||
}
|
||||
|
||||
// Tone mapping
|
||||
if (builder->luminanceScaling) {
|
||||
v = luminanceScaling(v, *builder->toneMapper, config.colorGradingLuminance);
|
||||
} else {
|
||||
v = (*builder->toneMapper)(v);
|
||||
}
|
||||
|
||||
// Go back to display color space
|
||||
v = config.colorGradingOut * v;
|
||||
|
||||
// Apply gamut mapping
|
||||
if (builder->gamutMapping) {
|
||||
// TODO: This should depend on the output color space
|
||||
v = gamutMapping_sRGB(v);
|
||||
}
|
||||
|
||||
// TODO: We should convert to the output color space if we use a working
|
||||
// color space that's not sRGB
|
||||
// TODO: Allow the user to customize the output color space
|
||||
|
||||
// We need to clamp for the output transfer function
|
||||
v = saturate(v);
|
||||
|
||||
// Apply OETF
|
||||
v = config.oetf(v);
|
||||
|
||||
return v;
|
||||
};
|
||||
|
||||
//auto now = std::chrono::steady_clock::now();
|
||||
|
||||
// Multithreadedly generate the tone mapping 3D look-up table using 32 jobs
|
||||
// Slices are 8 KiB (128 cache lines) apart.
|
||||
// This takes about 3-6ms on Android in Release
|
||||
JobSystem& js = engine.getJobSystem();
|
||||
auto *slices = js.createJob();
|
||||
for (size_t b = 0; b < c.lutDimension; b++) {
|
||||
auto *job = js.createJob(slices,
|
||||
[data, converted, b, &c, &configLock, builder](JobSystem&, JobSystem::Job*) {
|
||||
Config config;
|
||||
{
|
||||
std::lock_guard<Mutex> lock(configLock);
|
||||
config = c;
|
||||
if (mIsOneDimensional) {
|
||||
half* UTILS_RESTRICT p = (half*) data;
|
||||
if (mIsLDR) {
|
||||
for (size_t rgb = 0; rgb < config.lutDimension; rgb++) {
|
||||
float3 v = float3(rgb) * (1.0f / float(config.lutDimension - 1u));
|
||||
|
||||
v = (*builder->toneMapper)(float3(v));
|
||||
|
||||
// We need to clamp for the output transfer function
|
||||
v = saturate(v);
|
||||
|
||||
// Apply OETF
|
||||
v = config.oetf(v);
|
||||
|
||||
*p++ = half(v.r);
|
||||
}
|
||||
half4* UTILS_RESTRICT p = (half4*) data + b * config.lutDimension * config.lutDimension;
|
||||
for (size_t g = 0; g < config.lutDimension; g++) {
|
||||
for (size_t r = 0; r < config.lutDimension; r++) {
|
||||
float3 v = float3{r, g, b} * (1.0f / float(config.lutDimension - 1u));
|
||||
|
||||
// LogC encoding
|
||||
v = LogC_to_linear(v);
|
||||
|
||||
// Kill negative values near 0.0f due to imprecision in the log conversion
|
||||
v = max(v, 0.0f);
|
||||
|
||||
if (builder->hasAdjustments) {
|
||||
// Exposure
|
||||
v = adjustExposure(v, builder->exposure);
|
||||
|
||||
// Purkinje shift ("low-light" vision)
|
||||
v = scotopicAdaptation(v, builder->nightAdaptation);
|
||||
} else {
|
||||
for (size_t rgb = 0; rgb < config.lutDimension; rgb++) {
|
||||
*p++ = half(hdrColorAt(rgb, rgb, rgb).r);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Multithreadedly generate the tone mapping 3D look-up table using 32 jobs
|
||||
// Slices are 8 KiB (128 cache lines) apart.
|
||||
// This takes about 3-6ms on Android in Release
|
||||
JobSystem& js = engine.getJobSystem();
|
||||
auto *slices = js.createJob();
|
||||
for (size_t b = 0; b < config.lutDimension; b++) {
|
||||
auto* job = js.createJob(slices,
|
||||
[data, converted, b, &config, builder, &hdrColorAt](
|
||||
JobSystem&, JobSystem::Job*) {
|
||||
half4* UTILS_RESTRICT p =
|
||||
(half4*) data + b * config.lutDimension * config.lutDimension;
|
||||
for (size_t g = 0; g < config.lutDimension; g++) {
|
||||
for (size_t r = 0; r < config.lutDimension; r++) {
|
||||
*p++ = half4{hdrColorAt(r, g, b), 0.0f};
|
||||
}
|
||||
|
||||
// Move to color grading color space
|
||||
v = c.colorGradingIn * v;
|
||||
|
||||
if (builder->hasAdjustments) {
|
||||
// White balance
|
||||
v = chromaticAdaptation(v, config.adaptationTransform);
|
||||
|
||||
// Kill negative values before the next transforms
|
||||
v = max(v, 0.0f);
|
||||
|
||||
// Channel mixer
|
||||
v = channelMixer(v, builder->outRed, builder->outGreen, builder->outBlue);
|
||||
|
||||
// Shadows/mid-tones/highlights
|
||||
v = tonalRanges(v, c.colorGradingLuminance,
|
||||
builder->shadows, builder->midtones, builder->highlights,
|
||||
builder->tonalRanges);
|
||||
|
||||
// The adjustments below behave better in log space
|
||||
v = linear_to_LogC(v);
|
||||
|
||||
// ASC CDL
|
||||
v = colorDecisionList(v, builder->slope, builder->offset, builder->power);
|
||||
|
||||
// Contrast in log space
|
||||
v = contrast(v, builder->contrast);
|
||||
|
||||
// Back to linear space
|
||||
v = LogC_to_linear(v);
|
||||
|
||||
// Vibrance in linear space
|
||||
v = vibrance(v, c.colorGradingLuminance, builder->vibrance);
|
||||
|
||||
// Saturation in linear space
|
||||
v = saturation(v, c.colorGradingLuminance, builder->saturation);
|
||||
|
||||
// Kill negative values before curves
|
||||
v = max(v, 0.0f);
|
||||
|
||||
// RGB curves
|
||||
v = curves(v,
|
||||
builder->shadowGamma, builder->midPoint, builder->highlightScale);
|
||||
}
|
||||
|
||||
// Tone mapping
|
||||
if (builder->luminanceScaling) {
|
||||
v = luminanceScaling(v, *builder->toneMapper, c.colorGradingLuminance);
|
||||
} else {
|
||||
v = (*builder->toneMapper)(v);
|
||||
}
|
||||
|
||||
// Go back to display color space
|
||||
v = c.colorGradingOut * v;
|
||||
|
||||
// Apply gamut mapping
|
||||
if (builder->gamutMapping) {
|
||||
// TODO: This should depend on the output color space
|
||||
v = gamutMapping_sRGB(v);
|
||||
}
|
||||
|
||||
// TODO: We should convert to the output color space if we use a working
|
||||
// color space that's not sRGB
|
||||
// TODO: Allow the user to customize the output color space
|
||||
|
||||
// We need to clamp for the output transfer function
|
||||
v = saturate(v);
|
||||
|
||||
// Apply OETF
|
||||
v = c.oetf(v);
|
||||
|
||||
*p++ = half4{v, 0.0f};
|
||||
}
|
||||
}
|
||||
|
||||
if (converted) {
|
||||
uint32_t* const UTILS_RESTRICT dst = (uint32_t*) converted +
|
||||
b * config.lutDimension * config.lutDimension;
|
||||
half4* UTILS_RESTRICT src = (half4*) data +
|
||||
b * config.lutDimension * config.lutDimension;
|
||||
// we use a vectorize width of 8 because, on ARMv8 it allows the compiler to write eight
|
||||
// 32-bits results in one go.
|
||||
const size_t count = (config.lutDimension * config.lutDimension) & ~0x7u; // tell the compiler that we're a multiple of 8
|
||||
if (converted) {
|
||||
uint32_t* const UTILS_RESTRICT dst = (uint32_t*) converted +
|
||||
b * config.lutDimension * config.lutDimension;
|
||||
half4* UTILS_RESTRICT src = (half4*) data +
|
||||
b * config.lutDimension * config.lutDimension;
|
||||
// we use a vectorize width of 8 because, on ARMv8 it allows the compiler to
|
||||
// write eight 32-bits results in one go.
|
||||
const size_t count = (config.lutDimension * config.lutDimension) & ~0x7u; // tell the compiler that we're a multiple of 8
|
||||
#if defined(__clang__)
|
||||
#pragma clang loop vectorize_width(8)
|
||||
#pragma clang loop vectorize_width(8)
|
||||
#endif
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
float4 v{src[i]};
|
||||
uint32_t pr = uint32_t(std::floor(v.x * 1023.0f + 0.5f));
|
||||
uint32_t pg = uint32_t(std::floor(v.y * 1023.0f + 0.5f));
|
||||
uint32_t pb = uint32_t(std::floor(v.z * 1023.0f + 0.5f));
|
||||
dst[i] = (pb << 20u) | (pg << 10u) | pr;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
float4 v{src[i]};
|
||||
uint32_t pr = uint32_t(std::floor(v.x * 1023.0f + 0.5f));
|
||||
uint32_t pg = uint32_t(std::floor(v.y * 1023.0f + 0.5f));
|
||||
uint32_t pb = uint32_t(std::floor(v.z * 1023.0f + 0.5f));
|
||||
dst[i] = (pb << 20u) | (pg << 10u) | pr;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
js.run(job);
|
||||
}
|
||||
|
||||
});
|
||||
js.run(job);
|
||||
// TODO: Should we do a runAndRetain() and defer the wait() + texture creation until
|
||||
// getHwHandle() is invoked?
|
||||
js.runAndWait(slices);
|
||||
}
|
||||
|
||||
// TODO: Should we do a runAndRetain() and defer the wait() + texture creation until
|
||||
// getHwHandle() is invoked?
|
||||
js.runAndWait(slices);
|
||||
|
||||
//std::chrono::duration<float, std::milli> duration = std::chrono::steady_clock::now() - now;
|
||||
//slog.d << "LUT generation time: " << duration.count() << " ms" << io::endl;
|
||||
|
||||
mLutHandle = driver.createTexture(
|
||||
SamplerType::SAMPLER_3D,
|
||||
1,
|
||||
textureFormat,
|
||||
1,
|
||||
c.lutDimension,
|
||||
c.lutDimension,
|
||||
c.lutDimension,
|
||||
TextureUsage::DEFAULT
|
||||
);
|
||||
|
||||
if (converted) {
|
||||
free(data);
|
||||
data = converted;
|
||||
elementSize = sizeof(uint32_t);
|
||||
}
|
||||
|
||||
// Create texture.
|
||||
mLutHandle = driver.createTexture(SamplerType::SAMPLER_3D, 1, textureFormat, 1,
|
||||
width, height, depth, TextureUsage::DEFAULT);
|
||||
|
||||
driver.update3DImage(mLutHandle, 0,
|
||||
0, 0, 0,
|
||||
c.lutDimension, c.lutDimension, c.lutDimension,
|
||||
width, height, depth,
|
||||
PixelBufferDescriptor{
|
||||
data, lutElementCount * elementSize,format, type,
|
||||
[](void* buffer, size_t, void*) { free(buffer); }
|
||||
}
|
||||
);
|
||||
data, lutElementCount * elementSize, format, type,
|
||||
[](void* buffer, size_t, void*) { free(buffer); }
|
||||
});
|
||||
}
|
||||
|
||||
FColorGrading::~FColorGrading() noexcept = default;
|
||||
|
||||
@@ -42,12 +42,15 @@ public:
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
backend::TextureHandle getHwHandle() const noexcept { return mLutHandle; }
|
||||
|
||||
uint32_t getDimension() const noexcept { return mDimension; }
|
||||
bool isOneDimensional() const noexcept { return mIsOneDimensional; }
|
||||
bool isLDR() const noexcept { return mIsLDR; }
|
||||
|
||||
private:
|
||||
backend::TextureHandle mLutHandle;
|
||||
uint32_t mDimension;
|
||||
bool mIsOneDimensional;
|
||||
bool mIsLDR;
|
||||
};
|
||||
|
||||
FILAMENT_DOWNCAST(ColorGrading)
|
||||
|
||||
@@ -153,9 +153,10 @@ Texture::Builder& Texture::Builder::name(const char* name, size_t const len) noe
|
||||
}
|
||||
|
||||
Texture* Texture::Builder::build(Engine& engine) {
|
||||
FILAMENT_CHECK_PRECONDITION(Texture::isTextureFormatSupported(engine, mImpl->mFormat))
|
||||
<< "Texture format " << uint16_t(mImpl->mFormat) << " not supported on this platform";
|
||||
|
||||
if (mImpl->mTarget != SamplerType::SAMPLER_EXTERNAL) {
|
||||
FILAMENT_CHECK_PRECONDITION(Texture::isTextureFormatSupported(engine, mImpl->mFormat))
|
||||
<< "Texture format " << uint16_t(mImpl->mFormat) << " not supported on this platform";
|
||||
}
|
||||
const bool isProtectedTexturesSupported =
|
||||
downcast(engine).getDriverApi().isProtectedTexturesSupported();
|
||||
const bool useProtectedMemory = bool(mImpl->mUsage & TextureUsage::PROTECTED);
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
vec3 colorGrade(mediump sampler3D lut, const vec3 x) {
|
||||
vec3 LogC_to_linear(const vec3 x) {
|
||||
// Alexa LogC EI 1000
|
||||
const float a = 5.555556;
|
||||
const float b = 0.047996;
|
||||
const float c = 0.244161 / log2(10.0);
|
||||
const float d = 0.386036;
|
||||
vec3 logc = c * log2(a * x + b) + d;
|
||||
|
||||
// Remap to sample pixel centers
|
||||
logc = materialParams.lutSize.x + logc * materialParams.lutSize.y;
|
||||
|
||||
return textureLod(lut, logc, 0.0).rgb;
|
||||
return c * log2(a * x + b) + d;
|
||||
}
|
||||
|
||||
vec3 colorGrade3D(mediump sampler3D lut, const vec3 v) {
|
||||
return textureLod(lut, v, 0.0).rgb;
|
||||
}
|
||||
|
||||
vec3 colorGrade1D(mediump sampler3D lut, const vec3 v) {
|
||||
return vec3(
|
||||
textureLod(lut, vec3(v.r, 0.5, 0.5), 0.0).r,
|
||||
textureLod(lut, vec3(v.g, 0.5, 0.5), 0.0).r,
|
||||
textureLod(lut, vec3(v.b, 0.5, 0.5), 0.0).r);
|
||||
}
|
||||
|
||||
vec3 colorGrade(mediump sampler3D lut, vec3 v) {
|
||||
if (!materialConstants_isLDR) {
|
||||
v = LogC_to_linear(v);
|
||||
}
|
||||
// Remap to sample pixel centers.
|
||||
v = materialParams.lutSize.x + v * materialParams.lutSize.y;
|
||||
return materialConstants_isOneDimensional
|
||||
? colorGrade1D(lut, v) : colorGrade3D(lut, v);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,16 @@ material {
|
||||
precision : high
|
||||
}
|
||||
],
|
||||
constants : [
|
||||
{
|
||||
type : bool,
|
||||
name : isOneDimensional
|
||||
},
|
||||
{
|
||||
type : bool,
|
||||
name : isLDR
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
vertex
|
||||
],
|
||||
@@ -154,7 +164,7 @@ void postProcess(inout PostProcessInputs postProcess) {
|
||||
}
|
||||
|
||||
// Color grading
|
||||
color.rgb = colorGrade(materialParams_lut, color.rgb);
|
||||
color.rgb = colorGrade(materialParams_lut, color.rgb);
|
||||
|
||||
// output in premultiplied alpha
|
||||
#if !POST_PROCESS_OPAQUE
|
||||
|
||||
@@ -32,6 +32,16 @@ material {
|
||||
name : vignetteColor
|
||||
}
|
||||
],
|
||||
constants : [
|
||||
{
|
||||
type : bool,
|
||||
name : isOneDimensional
|
||||
},
|
||||
{
|
||||
type : bool,
|
||||
name : isLDR
|
||||
}
|
||||
],
|
||||
subpasses : [
|
||||
{
|
||||
type : subpassInput,
|
||||
@@ -103,7 +113,7 @@ void postProcess(inout PostProcessInputs postProcess) {
|
||||
}
|
||||
|
||||
// Color grading
|
||||
color.rgb = colorGrade(materialParams_lut, color.rgb);
|
||||
color.rgb = colorGrade(materialParams_lut, color.rgb);
|
||||
|
||||
// output in premultiplied alpha
|
||||
#if !POST_PROCESS_OPAQUE
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.58.0"
|
||||
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.0/filament-v1.58.0-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 = {
|
||||
|
||||
@@ -31,13 +31,17 @@ if (WIN32)
|
||||
set(SRCS ${SRCS} src/BlueGLCoreWindowsImpl.S)
|
||||
endif()
|
||||
elseif (APPLE AND NOT IOS)
|
||||
set(SRCS ${SRCS} src/BlueGLDarwin.cpp)
|
||||
if (FILAMENT_SUPPORTS_OSMESA)
|
||||
set(SRCS ${SRCS} src/BlueGLOSMesa.cpp)
|
||||
else()
|
||||
set(SRCS ${SRCS} src/BlueGLDarwin.cpp)
|
||||
endif()
|
||||
set(SRCS ${SRCS} src/BlueGLCoreDarwinUniversalImpl.S)
|
||||
elseif(LINUX)
|
||||
if(FILAMENT_SUPPORTS_EGL_ON_LINUX)
|
||||
if (FILAMENT_SUPPORTS_EGL_ON_LINUX)
|
||||
set(SRCS ${SRCS} src/BlueGLLinuxEGL.cpp)
|
||||
elseif(FILAMENT_SUPPORTS_OSMESA)
|
||||
set(SRCS ${SRCS} src/BlueGLLinuxOSMesa.cpp)
|
||||
elseif (FILAMENT_SUPPORTS_OSMESA)
|
||||
set(SRCS ${SRCS} src/BlueGLOSMesa.cpp)
|
||||
else()
|
||||
set(SRCS ${SRCS} src/BlueGLLinux.cpp)
|
||||
endif()
|
||||
@@ -53,7 +57,11 @@ include_directories(${PUBLIC_HDR_DIR})
|
||||
add_library(${TARGET} STATIC ${PUBLIC_HDRS} ${SRCS})
|
||||
|
||||
if(FILAMENT_SUPPORTS_OSMESA)
|
||||
target_compile_options(${TARGET} PRIVATE -I${FILAMENT_OSMESA_PATH}/include/GL)
|
||||
if (APPLE)
|
||||
target_compile_options(${TARGET} PRIVATE -I${FILAMENT_OSMESA_PATH}/include)
|
||||
else()
|
||||
target_compile_options(${TARGET} PRIVATE -I${FILAMENT_OSMESA_PATH}/include/GL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# specify where the public headers of this library are
|
||||
|
||||
@@ -17,42 +17,64 @@
|
||||
#include <dlfcn.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
#include <osmesa.h>
|
||||
|
||||
// This is to ensure that linking during compilation will not fail even if
|
||||
// OSMesaGetProcAddress is not linked.
|
||||
__attribute__((weak)) OSMESAproc OSMesaGetProcAddress(char const*);
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include <GL/osmesa.h>
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
#if defined(__linux__)
|
||||
#endif
|
||||
|
||||
namespace bluegl {
|
||||
|
||||
namespace {
|
||||
using ProcAddressFunc = void*(*)(char const* funcName);
|
||||
}
|
||||
|
||||
// This is to ensure that linking during compilation will not fail even if
|
||||
// OSMesaGetProcAddress is not linked.
|
||||
__attribute__((weak)) OSMESAproc OSMesaGetProcAddress(char const*);
|
||||
|
||||
struct Driver {
|
||||
ProcAddressFunc OSMesaGetProcAddress;
|
||||
void* library;
|
||||
} g_driver = {nullptr, nullptr};
|
||||
|
||||
bool initBinder() {
|
||||
constexpr char const* libraryNames[] = {"libOSMesa.so", "libosmesa.so"};
|
||||
static constexpr char const* libraryNames[] = {
|
||||
#if defined(__linux__)
|
||||
"libOSMesa.so",
|
||||
"libosmesa.so",
|
||||
#elif defined(__APPLE__)
|
||||
"libOSMesa.dylib",
|
||||
#endif
|
||||
};
|
||||
for (char const* name: libraryNames) {
|
||||
g_driver.library = dlopen(name, RTLD_GLOBAL | RTLD_NOW);
|
||||
if (g_driver.library) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!g_driver.library) {
|
||||
// The library has been linked explicitly during compile.
|
||||
g_driver.OSMesaGetProcAddress = (ProcAddressFunc) dlsym(RTLD_LOCAL, "OSMesaGetProcAddress");
|
||||
} else {
|
||||
if (g_driver.library) {
|
||||
// Linking against a libosmesa.so.
|
||||
g_driver.OSMesaGetProcAddress =
|
||||
(ProcAddressFunc) dlsym(g_driver.library, "OSMesaGetProcAddress");
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
else {
|
||||
// If Filament was built as a dynamic library.
|
||||
g_driver.OSMesaGetProcAddress = (ProcAddressFunc) dlsym(RTLD_LOCAL, "OSMesaGetProcAddress");
|
||||
}
|
||||
if (!g_driver.OSMesaGetProcAddress) {
|
||||
// If statically linking OSMesa.
|
||||
g_driver.OSMesaGetProcAddress = (ProcAddressFunc) OSMesaGetProcAddress;
|
||||
}
|
||||
#endif
|
||||
|
||||
return g_driver.OSMesaGetProcAddress;
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
## Capabilities
|
||||
|
||||
fgviewer is a library and web application for real-time visualization of the frame graph in Filament.
|
||||
fgviewer is a library and web application for real-time visualization of the frame graph in Filament.
|
||||
It displays active passes and resource usage, providing insights into the rendering pipeline.
|
||||
|
||||
## Setup for Desktop
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
utils::CString name;
|
||||
utils::CString value;
|
||||
};
|
||||
|
||||
|
||||
Resource(ResourceId id, utils::CString name,
|
||||
std::vector<Property> properties);
|
||||
|
||||
|
||||
@@ -17,14 +17,22 @@
|
||||
|
||||
#include "ApiHandler.h"
|
||||
|
||||
#include <__chrono/duration.h>
|
||||
#include <fgviewer/DebugServer.h>
|
||||
#include <fgviewer/JsonWriter.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <CivetServer.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace filament::fgviewer {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -21,36 +21,45 @@
|
||||
|
||||
#include <CivetServer.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Hash.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace filament::fgviewer {
|
||||
|
||||
// If set to 0, this serves HTML from a resgen resource. Use 1 only during local development, which
|
||||
// serves files directly from the source code tree.
|
||||
#define SERVE_FROM_SOURCE_TREE 0
|
||||
|
||||
#if SERVE_FROM_SOURCE_TREE
|
||||
|
||||
namespace {
|
||||
std::string const BASE_URL = "libs/fgviewer/web";
|
||||
} // anonymous
|
||||
|
||||
#else
|
||||
|
||||
#include "fgviewer_resources.h"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace {
|
||||
|
||||
struct Asset {
|
||||
std::string_view mime;
|
||||
std::string_view data;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string_view, Asset> ASSET_MAP;
|
||||
|
||||
} // anonymous
|
||||
|
||||
#endif // SERVE_FROM_SOURCE_TREE
|
||||
|
||||
namespace filament::fgviewer {
|
||||
|
||||
using namespace utils;
|
||||
|
||||
std::string_view const DebugServer::kSuccessHeader =
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
|
||||
#include <fgviewer/FrameGraphInfo.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::fgviewer {
|
||||
|
||||
FrameGraphInfo::FrameGraphInfo(utils::CString viewName):
|
||||
|
||||
@@ -14,11 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <fgviewer/JsonWriter.h>
|
||||
#include <fgviewer/FrameGraphInfo.h>
|
||||
#include <fgviewer/JsonWriter.h>
|
||||
|
||||
#include <iomanip>
|
||||
#include <utils/CString.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::fgviewer {
|
||||
|
||||
|
||||
@@ -223,8 +223,8 @@ class FrameGraphSidePanel extends LitElement {
|
||||
return html`
|
||||
<menu-section title="${title}">
|
||||
${this.framegraphs.map(({ fgid, name }) => html`
|
||||
<div class="framegraph"
|
||||
@click="${() => this._handleFrameGraphClick(fgid)}"
|
||||
<div class="framegraph"
|
||||
@click="${() => this._handleFrameGraphClick(fgid)}"
|
||||
data-id="${fgid}">
|
||||
${fgid === this.selectedFrameGraph ? '● ' : ''}${name}
|
||||
</div>
|
||||
@@ -440,15 +440,15 @@ class FrameGraphTable extends LitElement {
|
||||
return html`
|
||||
<tr id="resource-${resourceIndex}">
|
||||
<th class="sticky-col resource ${selectedStyle}" @click="${onClickResource}">
|
||||
${hasSubresources
|
||||
${hasSubresources
|
||||
? html`
|
||||
<span class="toggle-icon"
|
||||
@click="${(e) => { e.stopPropagation(); this._toggleCollapse(resourceIndex); }}">
|
||||
${isExpanded ? '▼' : '▶'}
|
||||
</span>`
|
||||
</span>`
|
||||
: nothing}
|
||||
${resource.name}
|
||||
${hasSubresources && isExpanded ? nothing : html`(${subresourceIds.length})`}
|
||||
${hasSubresources && !isExpanded ? html`(${subresourceIds.length})` : nothing}
|
||||
</th>
|
||||
${this._renderResourceUsage(allPasses, resourceIds, DEFAULT_COLOR)}
|
||||
</tr>
|
||||
@@ -592,12 +592,12 @@ class FrameGraphViewer extends LitElement {
|
||||
return html`
|
||||
<framegraph-sidepanel id="sidepanel"
|
||||
?connected="${this.connected}"
|
||||
selected-framegraph="${this.selectedFrameGraph}"
|
||||
selected-framegraph="${this.selectedFrameGraph}"
|
||||
selected-resource="${this.selectedResourceId}">
|
||||
</framegraph-sidepanel>
|
||||
<framegraph-table id="table"
|
||||
<framegraph-table id="table"
|
||||
?connected="${this.connected}"
|
||||
selected-framegraph="${this.selectedFrameGraph}"
|
||||
selected-framegraph="${this.selectedFrameGraph}"
|
||||
selected-resource="${this.selectedResourceId}">
|
||||
</framegraph-table>
|
||||
`;
|
||||
|
||||