Compare commits

..

37 Commits

Author SHA1 Message Date
Powei Feng
794420ebdf add external sampler example 2025-04-08 14:14:12 -07:00
alizahlalani
0c8df766d0 make createExternalImage UTILS_PUBLIC across platforms for future proofing (#8547) 2025-03-24 11:18:38 -07:00
Mathias Agopian
8f58743405 support up to five custom varyings
Five custom variables (varyings) are now available on the condition that
the `color` attribute is not requested.

FIXES=[404930099]
2025-03-24 09:38:31 -07:00
Syed Idris Shah
af079b42a6 Make decl/def of createDriver api consistent
Replace "void* const" with "void*" as const here serves no purpose because of following reasons:
  1. void* const in function parameter has a limited and local effect on the code.
  2. clang tidy would complain and report error if const is added to the function declaration.
  3. const is dropped later in the call stack making the code incosistent.
2025-03-21 13:44:10 -04:00
rafadevai
7f7bceb970 vk: New method to describe external image in Vulkan Android (#8551)
To match the GL platform add a new function to describe
an external image.

Also make `ExternalImageVulkanAndroid` and
`getExternalImageMetadata` protected, so only the
backend has access to it.
2025-03-21 17:09:29 +00:00
Powei Feng
625603d8d4 vk: split layout cache from descriptorSet cache (#8554)
This is just splitting the layout cache into its own class and
files.
2025-03-21 16:33:35 +00:00
Doris Wu
d2d5d62a20 Some cleanups (#8558)
* Clean up includes

* Fix the logic
2025-03-22 00:17:22 +08:00
Doris Wu
5e9be5dd2d Remove trailing whitespace (#8556) 2025-03-21 02:10:05 +00:00
Powei Feng
df897b3fb2 osmesa: Enable Mesa/OSMesa rendering for mac (#8530)
This commit enables local running of the renderdiff tests and also
for github workflows.
2025-03-20 22:37:23 +00:00
Powei Feng
8c396caba0 vk: removing caching/ directory (#8553)
Move the files out of that directory for consistency. (Should have
never created the directory in the first place).

Also did some small clean-ups of removing old comments and no
longer needed includes.
2025-03-20 22:03:08 +00:00
Mathias Agopian
6b91f30389 Materials can now specify a shadow attenuation factor (#8540)
* Materials can now specify a shadow strength factor

Materials have a new property: shadowStrength that can be used to
attenuate all shadows received by this material. e.g.:

```
void material(inout MaterialInputs material) {
  prepareMaterial(material);
  material. shadowStrength = 0.1;
}
```

FIXES=[391663042]

Co-authored-by: Powei Feng <powei@google.com>

---------

Co-authored-by: Powei Feng <powei@google.com>
2025-03-20 14:03:52 -07:00
Powei Feng
de5d0e55af webgpu: enable webgpu build for test on linux (#8549) 2025-03-20 20:57:49 +00:00
Benjamin Doherty
6066e3a461 Release Filament 1.58.1 2025-03-19 16:17:13 -07:00
rafadevai
ea0bbad636 vk: Refactor Vulkan android into its own class (#8538) 2025-03-19 20:41:44 +00:00
Eliza Velasquez
65861b85cf 1dlut: fix psychadelic lookup tables 2025-03-19 12:15:35 -07:00
Eliza Velasquez
1f169d7e75 1dlut: fix iOS build 2025-03-19 12:15:35 -07:00
Eliza Velasquez
daa359717b 1dlut: mathias feedback 2025-03-19 12:15:35 -07:00
Eliza Velasquez
4fbfd1b8c1 Generate 1D LUTs for color grading when possible
This introduces two new methods to `ToneMapper`: `isOneDimensional()` and
`isLDR()`. `ColorGrading` references these values along with other parameters
passed to the builder to determine if we can get away with only generating a
one-dimensional LUT. Meanwhile, `PostProcessManager` takes care of setting the
new spec constants and uniforms for `colorGrading.mat` and
`colorGradingAsSubpass.mat`.
2025-03-19 12:15:35 -07:00
Syed Idris Shah
67f05c15d0 Fix WebGPU compilation error
WebGPU is broken because of 3abddc4584 change
2025-03-19 13:25:09 -04:00
Doris Wu
4db3cc521b Move includes outside fgviewer namespace (#8543) 2025-03-19 13:42:34 +08:00
alizahlalani
9e6cf3c876 Refactored external image handling in PlatformEGLAndroid (#8512) 2025-03-18 23:46:31 +00:00
Powei Feng
b64548267e vk: disable transient texture for depth resolve (#8534)
Depth resolve is handled via a custom shader and therefore cannot
use the transient texture path.

This is fixes the black screen bug when MSAA is turned on and
when using the latest MoltenVK (1.2.11).
2025-03-18 17:51:59 +00:00
Randy Reyes
5c811844ff webgpu: fix spacing 2025-03-18 10:25:10 -07:00
Randy Reyes
e6b18b4560 webgpu: enable unpacking WGSL code in filamat 2025-03-18 10:25:10 -07:00
Powei Feng
1571ea846b vk: minor swizzle code clean-up (#8535) 2025-03-18 17:09:55 +00:00
Matthew Hoffman
78c6456fbe Update gtest to 1.16 (#8528) 2025-03-17 16:31:48 -05:00
bridgewaterrobbie
84cce8cfdf Run dead code elimination for WGSL even if all other optimizations are disabled 2025-03-17 15:16:19 -04:00
bridgewaterrobbie
49b7c7169f Avoid building point size demo and material if WebGPU is enabled. 2025-03-17 15:16:19 -04:00
Powei Feng
dbfe2dbc5e Disable renderdiff for breakage (#8500)
Disable renderdiff for breakage
2025-03-17 11:47:19 -07:00
Serge Metral
0ec60de287 vk: External sampler platform code (#8500)
Adds enums for enabling external sampler / immutable samplers. No actual implementation yet.
2025-03-17 09:51:34 -07:00
rafadevai
7eb756ca96 vk: Fix Vulkan Validation errors when importing an AHB (#8509)
- Make sure we setup a valid sample count of 1.
https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-VkImageCreateInfo-samples-parameter
- Select a proper memory type based on the list
provided by the `metadata.memoryTypeBits`
https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-VkMemoryAllocateInfo-memoryTypeIndex-02385
2025-03-16 01:56:42 +00:00
Mathias Agopian
3abddc4584 APIs to query the texture limit sizes.
Assert textures dimensions are in range when creating a Texture object.

FIXES=[398901038]
2025-03-15 16:32:29 -07:00
bridgewaterrobbie
35661a1974 Add appropriate matc flags for WebGPU when enabled
Currently WebGPU does not support skinning nor stereo, so they also must be disabled if targeting WebGPU.
2025-03-14 13:17:23 -04:00
bridgewaterrobbie
606a1259f6 Avoid generating subpass code if using WebGPU
Subpasses should not be used anyways due to the result of WebGPUDriver::isFrameBufferFetchSupported. However, we need to make sure the GLSL is not generated for WebGPU, or Tint is unable to convert.
2025-03-14 13:17:23 -04:00
Powei Feng
6479a5bc12 github: change wgsl presubmit to use linux (#8525) 2025-03-14 16:59:11 +00:00
Andy Hovingh
c95f9ed7e0 WebGPU: backend components created and conditionally logged
Initial groundwork in creating WebGPU backend components, namely
the instance, adapter, device, and queue.
If configured to do so, the backend will print out details about
these components.
The samples/hellotriangle.cpp was slightly modified to include a
webgpu option which allows for exercising the above, but does not
yet draw anything to the screen/window.
NOTE: This has only been sanity tested with hello triangle
on Mac OS and the Android emulator at this time, NOT IOS,
Windows, or Linux yet.
2025-03-13 19:45:48 -05:00
Powei Feng
e3b7861aa2 webgpu: combine tint static libs into single lib (#8520) 2025-03-13 17:57:25 +00:00
448 changed files with 46271 additions and 46817 deletions

View File

@@ -1,15 +1,7 @@
;;; Directory Local Variables -*- no-byte-compile: t -*-
;;; For more information see (info "(emacs) Directory Variables")
((c++-mode . ((eval . (progn
(make-local-variable 'eglot-ignored-server-capabilities)
(add-to-list 'eglot-ignored-server-capabilities
:documentOnTypeFormattingProvider)))
(c-file-style . "filament")
((c++-mode . ((c-file-style . "filament")
(apheleia-inhibit . t)))
(c-mode . ((eval . (progn
(make-local-variable 'eglot-ignored-server-capabilities)
(add-to-list 'eglot-ignored-server-capabilities
:documentOnTypeFormattingProvider)))
(c-file-style . "filament")
(c-mode . ((c-file-style . "filament")
(apheleia-inhibit . t))))

View File

@@ -5,7 +5,7 @@ end_of_line = lf
insert_final_newline = true
charset = utf-8
[*.{c,cpp,h,inc,kt,java,js,md,fs,vs}]
[*.{c,cpp,h,inc,kt,java,js,md}]
indent_style = space
indent_size = 4
max_line_length = 100

View File

@@ -92,26 +92,33 @@ jobs:
test-renderdiff:
name: test-renderdiff
runs-on: ubuntu-22.04-16core
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: |
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: macos-14-xlarge
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: ./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*

View File

@@ -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
@@ -601,6 +607,14 @@ if (FILAMENT_SUPPORTS_METAL)
set(MATC_API_FLAGS ${MATC_API_FLAGS} -a metal)
endif()
# With WebGPU, push constants are not supported. Skinning uses them.
# WebGPU has a proposal to add push constants at https://github.com/gpuweb/gpuweb/blob/main/proposals/push-constants.md
# With WebGPU, Tint does not support ClipDistance which is used in Stereo. Mentioned in comment
# https://github.com/google/dawn/blob/855d17b08abdf02f9142bf5a8f14d0ea088810a4/src/tint/lang/spirv/reader/ast_parser/function.cc#L4434
if (FILAMENT_SUPPORTS_WEBGPU)
set(MATC_API_FLAGS ${MATC_API_FLAGS} -a webgpu --variant-filter=skinning,stereo)
endif()
# Disable ESSL 1.0 code generation.
if (NOT FILAMENT_ENABLE_FEATURE_LEVEL_0)
set(MATC_API_FLAGS ${MATC_API_FLAGS} -1)

View File

@@ -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**]

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -12,12 +12,12 @@ if (FILAMENT_SUPPORTS_VULKAN)
endif()
if (FILAMENT_SUPPORTS_WEBGPU)
add_library(webgpu_dawn STATIC IMPORTED)
set_target_properties(webgpu_dawn PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libwebgpu_dawn.a)
# See third_party/dawn/tnt/CMakeLists.txt and libs/filamat/CMakeLists.txt
add_library(tint STATIC IMPORTED)
set_target_properties(tint PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libtint_combined.a)
endif()
add_library(${FILAMAT_FLAVOR} STATIC IMPORTED)
set_target_properties(${FILAMAT_FLAVOR} PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/lib${FILAMAT_FLAVOR}.a)
@@ -57,5 +57,5 @@ target_link_libraries(filamat-jni
utils
log
smol-v
$<$<STREQUAL:${FILAMENT_SUPPORTS_WEBGPU},ON>:webgpu_dawn> # for tint
$<$<STREQUAL:${FILAMENT_SUPPORTS_WEBGPU},ON>:tint>
)

View File

@@ -29,10 +29,6 @@ add_library(filaflat STATIC IMPORTED)
set_target_properties(filaflat PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilaflat.a)
add_library(filamat STATIC IMPORTED)
set_target_properties(filamat PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilamat.a)
add_library(geometry STATIC IMPORTED)
set_target_properties(geometry PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libgeometry.a)
@@ -59,11 +55,11 @@ add_library(smol-v STATIC IMPORTED)
set_target_properties(smol-v PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libsmol-v.a)
if (FILAMENT_ENABLE_FGVIEWER)
add_library(fgviewer STATIC IMPORTED)
set_target_properties(fgviewer PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfgviewer.a)
endif()
if (FILAMENT_ENABLE_FGVIEWER)
add_library(fgviewer STATIC IMPORTED)
set_target_properties(fgviewer PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfgviewer.a)
endif()
if (FILAMENT_ENABLE_MATDBG)
add_library(matdbg STATIC IMPORTED)

View File

@@ -68,6 +68,21 @@ Java_com_google_android_filament_Texture_nIsTextureSwizzleSupported(JNIEnv*, jcl
return (jboolean) Texture::isTextureSwizzleSupported(*engine);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nGetMaxTextureSize(JNIEnv *, jclass,
jlong nativeEngine, jint sampler) {
Engine *engine = (Engine *) nativeEngine;
return Texture::getMaxTextureSize(*engine, (Texture::Sampler)sampler);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nGetMaxArrayTextureLayers(JNIEnv *, jclass,
jlong nativeEngine) {
Engine *engine = (Engine *) nativeEngine;
return Texture::getMaxArrayTextureLayers(*engine);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Texture_nValidatePixelFormatAndType(JNIEnv*, jclass,
jint internalFormat, jint pixelDataFormat, jint pixelDataType) {

View File

@@ -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);
}

View File

@@ -691,6 +691,24 @@ public class Texture {
pixelDataType.ordinal());
}
/**
* @param engine {@link Engine}
* @param type Texture sampler type
* @return The maximum size in texels of a texture of type \p type. At least 2048 for
* 2D textures, 256 for 3D textures
*/
public static int getMaxTextureSize(@NonNull Engine engine, Sampler type) {
return nGetMaxTextureSize(engine.getNativeObject(), type.ordinal());
}
/**
* @param engine {@link Engine}
* @return The maximum number of layers supported by texture arrays. At least 256.
*/
public static int getMaxArrayTextureLayers(@NonNull Engine engine) {
return nGetMaxArrayTextureLayers(engine.getNativeObject());
}
/**
* Use <code>Builder</code> to construct a <code>Texture</code> object instance.
*/
@@ -1289,6 +1307,8 @@ public class Texture {
private static native boolean nIsTextureFormatSupported(long nativeEngine, int internalFormat);
private static native boolean nIsTextureFormatMipmappable(long nativeEngine, int internalFormat);
private static native boolean nIsTextureSwizzleSupported(long nativeEngine);
private static native int nGetMaxTextureSize(long nativeObject, int ordinal);
private static native int nGetMaxArrayTextureLayers(long nativeObject);
private static native boolean nValidatePixelFormatAndType(int internalFormat, int pixelDataFormat,
int pixelDataType);

View File

@@ -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;

View File

@@ -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
)

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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
}

View File

@@ -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.

View 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

View 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')
}

View File

@@ -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>

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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).")
}
*/
}

View File

@@ -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).")
}
*/
}

View File

@@ -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)
// }
}
}

View File

@@ -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)
// }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -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>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">External Image</string>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Hello Camera</string>
</resources>

View File

@@ -0,0 +1,8 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -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'

View File

@@ -30,6 +30,9 @@ if [[ "$GITHUB_WORKFLOW" ]]; then
sudo apt-get install clang-$GITHUB_CLANG_VERSION libc++-$GITHUB_CLANG_VERSION-dev libc++abi-$GITHUB_CLANG_VERSION-dev
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 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
fi

View File

@@ -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,

View File

@@ -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
@@ -242,11 +248,31 @@ endif()
if (FILAMENT_SUPPORTS_WEBGPU)
list(APPEND SRCS
include/backend/platforms/WebGPUPlatform.h
src/webgpu/platform/WebGPUPlatform.cpp
src/webgpu/WebGPUConstants.h
src/webgpu/WebGPUDriver.cpp
src/webgpu/WebGPUDriver.h
src/webgpu/WebGPUPlatform.cpp
src/webgpu/WebGPUPlatform.h
)
if (WIN32)
list(APPEND SRCS src/webgpu/platform/WebGPUPlatformWindows.cpp)
elseif (LINUX)
list(APPEND SRCS src/webgpu/platform/WebGPUPlatformLinux.cpp)
elseif (APPLE OR IOS)
list(APPEND SRCS src/webgpu/platform/WebGPUPlatformApple.mm)
elseif (ANDROID)
list(APPEND SRCS src/webgpu/platform/WebGPUPlatformAndroid.cpp)
endif()
if (TNT_DEV)
set(FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING_DEFAULT ON)
else()
set(FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING_DEFAULT OFF)
endif()
option(FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING
"Enable immediate_error_handling for the WebGPU backend Dawn implementation"
${FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING_DEFAULT})
endif()
if (ANDROID)
@@ -396,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)
@@ -433,6 +462,10 @@ if (FILAMENT_SUPPORTS_METAL)
target_compile_definitions(${TARGET} PRIVATE $<$<BOOL:${FILAMENT_METAL_PROFILING}>:FILAMENT_METAL_PROFILING>)
endif()
if (FILAMENT_SUPPORTS_WEBGPU)
target_compile_definitions(${TARGET} PRIVATE $<$<BOOL:${FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING}>:FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING>)
endif()
target_link_libraries(${TARGET} PRIVATE
${OSMESA_LINKER_FLAGS}
$<$<AND:$<PLATFORM_ID:Linux>,$<CONFIG:Release>>:${LINUX_LINKER_OPTIMIZATION_FLAGS}>

View File

@@ -126,6 +126,10 @@ static_assert(MAX_VERTEX_BUFFER_COUNT <= MAX_VERTEX_ATTRIBUTE_COUNT,
static constexpr size_t CONFIG_UNIFORM_BINDING_COUNT = 9; // This is guaranteed by OpenGL ES.
static constexpr size_t CONFIG_SAMPLER_BINDING_COUNT = 4; // This is guaranteed by OpenGL ES.
static constexpr uint8_t EXTERNAL_SAMPLER_DATA_INDEX_UNUSED =
uint8_t(-1);// Case where the descriptor set binding isnt using any external sampler state
// and therefore doesn't have a valid entry.
/**
* Defines the backend's feature levels.
*/
@@ -252,21 +256,19 @@ struct DescriptorSetLayoutBinding {
DescriptorFlags flags = DescriptorFlags::NONE;
uint16_t count = 0;
uint8_t externalSamplerDataIndex = EXTERNAL_SAMPLER_DATA_INDEX_UNUSED;
friend inline bool operator==(
DescriptorSetLayoutBinding const& lhs,
DescriptorSetLayoutBinding const& rhs) noexcept {
return lhs.type == rhs.type &&
lhs.flags == rhs.flags &&
lhs.count == rhs.count &&
lhs.stageFlags == rhs.stageFlags;
lhs.stageFlags == rhs.stageFlags &&
lhs.externalSamplerDataIndex == rhs.externalSamplerDataIndex;
}
};
struct DescriptorSetLayout {
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
};
/**
* Bitmask for selecting render buffers
*/
@@ -968,8 +970,28 @@ enum class SamplerCompareFunc : uint8_t {
N //!< Never. The depth / stencil test always fails.
};
//! this API is copied from (and only applies to) the Vulkan spec.
//! These specify YUV to RGB conversions.
enum class SamplerYcbcrModelConversion : uint8_t {
RGB_IDENTITY = 0,
YCBCR_IDENTITY = 1,
YCBCR_709 = 2,
YCBCR_601 = 3,
YCBCR_2020 = 4,
};
enum class SamplerYcbcrRange : uint8_t {
ITU_FULL = 0,
ITU_NARROW = 1,
};
enum class ChromaLocation : uint8_t {
COSITED_EVEN = 0,
MIDPOINT = 1,
};
//! Sampler parameters
struct SamplerParams { // NOLINT
struct SamplerParams { // NOLINT
SamplerMagFilter filterMag : 1; //!< magnification filter (NEAREST)
SamplerMinFilter filterMin : 3; //!< minification filter (NEAREST)
SamplerWrapMode wrapS : 2; //!< s-coordinate wrap mode (CLAMP_TO_EDGE)
@@ -1024,6 +1046,7 @@ private:
return SamplerParams::LessThan{}(lhs, rhs);
}
};
static_assert(sizeof(SamplerParams) == 4);
// The limitation to 64-bits max comes from how we store a SamplerParams in our JNI code
@@ -1031,6 +1054,93 @@ static_assert(sizeof(SamplerParams) == 4);
static_assert(sizeof(SamplerParams) <= sizeof(uint64_t),
"SamplerParams must be no more than 64 bits");
//! Sampler parameters
struct SamplerYcbcrConversion {// NOLINT
SamplerYcbcrModelConversion ycbcrModel : 4;
TextureSwizzle r : 4;
TextureSwizzle g : 4;
TextureSwizzle b : 4;
TextureSwizzle a : 4;
SamplerYcbcrRange ycbcrRange : 1;
ChromaLocation xChromaOffset : 1;
ChromaLocation yChromaOffset : 1;
SamplerMagFilter chromaFilter : 1;
uint8_t padding;
struct Hasher {
size_t operator()(const SamplerYcbcrConversion p) const noexcept {
// we don't use std::hash<> here, so we don't have to include <functional>
return *reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&p));
}
};
struct EqualTo {
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
assert_invariant(lhs.padding == 0);
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs == *pRhs;
}
};
struct LessThan {
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
assert_invariant(lhs.padding == 0);
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs < *pRhs;
}
};
private:
friend inline bool operator == (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
noexcept {
return SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
}
friend inline bool operator != (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
noexcept {
return !SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
}
friend inline bool operator < (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
noexcept {
return SamplerYcbcrConversion::LessThan{}(lhs, rhs);
}
};
static_assert(sizeof(SamplerYcbcrConversion) == 4);
static_assert(sizeof(SamplerYcbcrConversion) <= sizeof(uint64_t),
"SamplerYcbcrConversion must be no more than 64 bits");
struct ExternalSamplerDatum {
ExternalSamplerDatum(SamplerYcbcrConversion ycbcr, SamplerParams spm, uint32_t extFmt)
: YcbcrConversion(ycbcr),
samplerParams(spm),
externalFormat(extFmt) {}
bool operator==(ExternalSamplerDatum const& rhs) const {
return (YcbcrConversion == rhs.YcbcrConversion && samplerParams == rhs.samplerParams &&
externalFormat == rhs.externalFormat);
}
struct EqualTo {
bool operator()(const ExternalSamplerDatum& lhs,
const ExternalSamplerDatum& rhs) const noexcept {
return (lhs.YcbcrConversion == rhs.YcbcrConversion &&
lhs.samplerParams == rhs.samplerParams &&
lhs.externalFormat == rhs.externalFormat);
}
};
SamplerYcbcrConversion YcbcrConversion;
SamplerParams samplerParams;
uint32_t externalFormat;
};
// No implicit padding allowed due to it being a hash key.
static_assert(sizeof(ExternalSamplerDatum) == 12);
struct DescriptorSetLayout {
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
utils::FixedCapacityVector<ExternalSamplerDatum> externalSamplerData;
};
//! blending equation function
enum class BlendEquation : uint8_t {
ADD, //!< the fragment is added to the color buffer

View File

@@ -33,7 +33,7 @@ public:
PlatformCocoaGL();
~PlatformCocoaGL() noexcept override;
ExternalImageHandle createExternalImage(void* cvPixelBuffer) noexcept;
ExternalImageHandle UTILS_PUBLIC createExternalImage(void* cvPixelBuffer) noexcept;
protected:
// --------------------------------------------------------------------------------------------

View File

@@ -32,7 +32,7 @@ public:
PlatformCocoaTouchGL();
~PlatformCocoaTouchGL() noexcept override;
ExternalImageHandle createExternalImage(void* cvPixelBuffer) noexcept;
ExternalImageHandle UTILS_PUBLIC createExternalImage(void* cvPixelBuffer) noexcept;
// --------------------------------------------------------------------------------------------
// Platform Interface

View File

@@ -50,7 +50,7 @@ public:
/**
* Creates an ExternalImage from a EGLImageKHR
*/
ExternalImageHandle createExternalImage(EGLImageKHR eglImage) noexcept;
ExternalImageHandle UTILS_PUBLIC createExternalImage(EGLImageKHR eglImage) noexcept;
protected:
// --------------------------------------------------------------------------------------------

View File

@@ -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,

View File

@@ -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>

View File

@@ -18,6 +18,7 @@
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORM_H
#include <backend/Platform.h>
#include <backend/DriverEnums.h>
#include <bluevk/BlueVK.h>
@@ -317,6 +318,11 @@ public:
*/
uint32_t layers;
/**
* The numbers of samples per texel
*/
VkSampleCountFlagBits samples;
/**
* The format of the external image
*/
@@ -351,20 +357,44 @@ public:
using ImageData = std::pair<VkImage, VkDeviceMemory>;
virtual ImageData createExternalImageData(ExternalImageHandleRef externalImage,
const ExternalImageMetadata& metadata);
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
VkImageUsageFlags usage);
virtual VkSampler createExternalSampler(SamplerYcbcrConversion chroma,
SamplerParams sampler,
uint32_t internalFormat);
virtual VkImageView createExternalImageView(SamplerYcbcrConversion chroma,
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
VkImageViewType viewType, VkComponentMapping swizzle);
protected:
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);
static ImageData createExternalImageDataImpl(ExternalImageHandleRef externalImage,
VkDevice device, const ExternalImageMetadata& metadata);
VkDevice device, const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
VkImageUsageFlags usage);
static VkSampler createExternalSamplerImpl(VkDevice device,
SamplerYcbcrConversion chroma, SamplerParams sampler,
uint32_t internalFormat);
static VkImageView createExternalImageViewImpl(VkDevice device,
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
VkImageSubresourceRange range, VkImageViewType viewType,
VkComponentMapping swizzle);
// Platform dependent helper methods
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;

View File

@@ -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

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORM_H
#define TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORM_H
#include <backend/Platform.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
namespace filament::backend {
/**
* A Platform interface, handling the environment-specific concerns, e.g. OS, for creating a WebGPU
* driver (backend).
*/
class WebGPUPlatform final : public Platform {
public:
WebGPUPlatform();
~WebGPUPlatform() override = default;
[[nodiscard]] int getOSVersion() const noexcept final { return 0; }
[[nodiscard]] wgpu::Instance& getInstance() noexcept { return mInstance; }
// either returns a valid surface or panics
[[nodiscard]] wgpu::Surface createSurface(void* nativeWindow, uint64_t flags);
// either returns a valid adapter or panics
[[nodiscard]] wgpu::Adapter requestAdapter(wgpu::Surface const& surface);
// either returns a valid device or panics
[[nodiscard]] wgpu::Device requestDevice(wgpu::Adapter const& adapter);
protected:
[[nodiscard]] Driver* createDriver(void* sharedContext,
const Platform::DriverConfig& driverConfig) noexcept override;
private:
// we may consider having the driver own this in the future
wgpu::Instance mInstance;
};
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORM_H

View File

@@ -362,6 +362,8 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedTexturesSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthClampSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
DECL_DRIVER_API_SYNCHRONOUS_N(size_t, getMaxTextureSize, backend::SamplerType, target)
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxArrayTextureLayers)
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
DECL_DRIVER_API_SYNCHRONOUS_N(void, setupExternalImage2, backend::Platform::ExternalImageHandleRef, image)
DECL_DRIVER_API_SYNCHRONOUS_N(void, setupExternalImage, void*, image)

View File

@@ -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 "webgpu/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();

View File

@@ -1177,6 +1177,16 @@ size_t MetalDriver::getMaxUniformBufferSize() {
return 256 * 1024 * 1024; // TODO: return the actual size instead of hardcoding the minspec
}
size_t MetalDriver::getMaxTextureSize(SamplerType) {
// TODO: return the actual size instead of hardcoding the minspec
return 2048;
}
size_t MetalDriver::getMaxArrayTextureLayers() {
// TODO: return the actual size instead of hardcoding the minspec
return 256;
}
void MetalDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& data,
uint32_t byteOffset) {
FILAMENT_CHECK_PRECONDITION(data.buffer)

View File

@@ -240,6 +240,14 @@ size_t NoopDriver::getMaxUniformBufferSize() {
return 16384u;
}
size_t NoopDriver::getMaxTextureSize(SamplerType target) {
return 2048u;
}
size_t NoopDriver::getMaxArrayTextureLayers() {
return 256u;
}
void NoopDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
uint32_t byteOffset) {
scheduleDestroy(std::move(p));

View File

@@ -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();
}

View File

@@ -94,18 +94,22 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
}
#endif
OpenGLContext::initExtensions(&ext, state.major, state.minor);
initExtensions(&ext, state.major, state.minor);
OpenGLContext::initProcs(&procs, ext, state.major, state.minor);
initProcs(&procs, ext, state.major, state.minor);
OpenGLContext::initBugs(&bugs, ext, state.major, state.minor,
initBugs(&bugs, ext, state.major, state.minor,
state.vendor, state.renderer, state.version, state.shader);
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &gets.max_renderbuffer_size);
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &gets.max_texture_image_units);
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gets.max_combined_texture_image_units);
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gets.max_texture_size);
glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &gets.max_cubemap_texture_size);
glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &gets.max_3d_texture_size);
glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &gets.max_array_texture_layers);
mFeatureLevel = OpenGLContext::resolveFeatureLevel(state.major, state.minor, ext, gets, bugs);
mFeatureLevel = resolveFeatureLevel(state.major, state.minor, ext, gets, bugs);
#ifdef BACKEND_OPENGL_VERSION_GLES
mShaderModel = ShaderModel::MOBILE;
@@ -177,6 +181,14 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
<< gets.max_anisotropy << '\n'
<< "GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS = "
<< gets.max_combined_texture_image_units << '\n'
<< "GL_MAX_TEXTURE_SIZE = "
<< gets.max_texture_size << '\n'
<< "GL_MAX_CUBE_MAP_TEXTURE_SIZE = "
<< gets.max_cubemap_texture_size << '\n'
<< "GL_MAX_3D_TEXTURE_SIZE = "
<< gets.max_3d_texture_size << '\n'
<< "GL_MAX_ARRAY_TEXTURE_LAYERS = "
<< gets.max_array_texture_layers << '\n'
<< "GL_MAX_DRAW_BUFFERS = "
<< gets.max_draw_buffers << '\n'
<< "GL_MAX_RENDERBUFFER_SIZE = "

View File

@@ -203,6 +203,10 @@ public:
GLint max_renderbuffer_size;
GLint max_samples;
GLint max_texture_image_units;
GLint max_texture_size;
GLint max_cubemap_texture_size;
GLint max_3d_texture_size;
GLint max_array_texture_layers;
GLint max_transform_feedback_separate_attribs;
GLint max_uniform_block_size;
GLint max_uniform_buffer_bindings;

View File

@@ -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;
}
@@ -2460,6 +2460,26 @@ size_t OpenGLDriver::getMaxUniformBufferSize() {
return mContext.gets.max_uniform_block_size;
}
size_t OpenGLDriver::getMaxTextureSize(SamplerType target) {
switch (target) {
case SamplerType::SAMPLER_2D:
case SamplerType::SAMPLER_2D_ARRAY:
case SamplerType::SAMPLER_EXTERNAL:
return mContext.gets.max_texture_size;
case SamplerType::SAMPLER_CUBEMAP:
return mContext.gets.max_cubemap_texture_size;
case SamplerType::SAMPLER_3D:
return mContext.gets.max_3d_texture_size;
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
return mContext.gets.max_cubemap_texture_size;
}
return 0;
}
size_t OpenGLDriver::getMaxArrayTextureLayers() {
return mContext.gets.max_array_texture_layers;
}
// ------------------------------------------------------------------------------------------------
// Swap chains
// ------------------------------------------------------------------------------------------------

View File

@@ -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];

View File

@@ -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) {

View File

@@ -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

View File

@@ -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.

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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 = {};
}

View File

@@ -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

View 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

View 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

View File

@@ -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) {
@@ -554,26 +552,43 @@ void VulkanDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwText
texture.inc();
}
void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th,
backend::SamplerType target, backend::TextureFormat format,
uint32_t width, uint32_t height, backend::TextureUsage usage,
void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::SamplerType target,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
Platform::ExternalImageHandleRef externalImage) {
FVK_SYSTRACE_SCOPE();
const auto& metadata = mPlatform->getExternalImageMetadata(externalImage);
if (metadata.isProtected) {
usage |= backend::TextureUsage::PROTECTED;
}
VkImageUsageFlags vkUsage = metadata.usage;
if (any(usage & TextureUsage::BLIT_SRC)) {
vkUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
if (any(usage & (TextureUsage::BLIT_DST & TextureUsage::UPLOADABLE))) {
vkUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
assert_invariant(width == metadata.width);
assert_invariant(height == metadata.height);
assert_invariant(fvkutils::getVkFormat(format) == metadata.format);
const auto& data = mPlatform->createExternalImageData(externalImage, metadata);
VkMemoryPropertyFlags const requiredMemoryFlags = any(usage & TextureUsage::UPLOADABLE)
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
uint32_t const memoryTypeIndex =
mContext.selectMemoryType(metadata.memoryTypeBits, requiredMemoryFlags);
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex != VK_MAX_MEMORY_TYPES)
<< "failed to find a valid memory type for external image memory.";
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th,
mPlatform->getDevice(), mAllocator, &mResourceManager, &mCommands, data.first, data.second, metadata.format,
1, metadata.width, metadata.height, /*depth=*/1, usage, mStagePool);
const auto& data =
mPlatform->createExternalImageData(externalImage, metadata, memoryTypeIndex, vkUsage);
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mPlatform->getDevice(),
mAllocator, &mResourceManager, &mCommands, data.first, data.second, metadata.format,
metadata.samples, metadata.width, metadata.height, metadata.layerCount, usage,
mStagePool);
texture.inc();
}
@@ -761,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();
}
@@ -771,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();
}
@@ -1130,6 +1144,16 @@ size_t VulkanDriver::getMaxUniformBufferSize() {
return 32768;
}
size_t VulkanDriver::getMaxTextureSize(SamplerType) {
// TODO: return the actual size instead of hardcoded value
return 2048;
}
size_t VulkanDriver::getMaxArrayTextureLayers() {
// TODO: return the actual size instead of hardcoded value
return 256;
}
void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
Handle<HwBufferObject> boh) {
auto vb = resource_ptr<VulkanVertexBuffer>::cast(&mResourceManager, vbh);
@@ -1466,7 +1490,7 @@ void VulkanDriver::nextSubpass(int) {
if (mCurrentRenderPass.params.subpassMask & 0x1) {
VulkanAttachment& subpassInput = renderTarget->getColor0();
mDescriptorSetManager.updateInputAttachment({}, subpassInput);
mDescriptorSetCache.updateInputAttachment({}, subpassInput);
}
}
@@ -1793,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);
}
}
@@ -1803,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);

View File

@@ -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.

View File

@@ -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__)

View File

@@ -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>

View File

@@ -23,79 +23,6 @@ using namespace bluevk;
namespace filament::backend {
constexpr inline VkSamplerAddressMode getWrapMode(SamplerWrapMode mode) noexcept {
switch (mode) {
case SamplerWrapMode::REPEAT:
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
case SamplerWrapMode::CLAMP_TO_EDGE:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
case SamplerWrapMode::MIRRORED_REPEAT:
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
}
}
constexpr inline VkFilter getFilter(SamplerMinFilter filter) noexcept {
switch (filter) {
case SamplerMinFilter::NEAREST:
return VK_FILTER_NEAREST;
case SamplerMinFilter::LINEAR:
return VK_FILTER_LINEAR;
case SamplerMinFilter::NEAREST_MIPMAP_NEAREST:
return VK_FILTER_NEAREST;
case SamplerMinFilter::LINEAR_MIPMAP_NEAREST:
return VK_FILTER_LINEAR;
case SamplerMinFilter::NEAREST_MIPMAP_LINEAR:
return VK_FILTER_NEAREST;
case SamplerMinFilter::LINEAR_MIPMAP_LINEAR:
return VK_FILTER_LINEAR;
}
}
constexpr inline VkFilter getFilter(SamplerMagFilter filter) noexcept {
switch (filter) {
case SamplerMagFilter::NEAREST:
return VK_FILTER_NEAREST;
case SamplerMagFilter::LINEAR:
return VK_FILTER_LINEAR;
}
}
constexpr inline VkSamplerMipmapMode getMipmapMode(SamplerMinFilter filter) noexcept {
switch (filter) {
case SamplerMinFilter::NEAREST:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
case SamplerMinFilter::LINEAR:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
case SamplerMinFilter::NEAREST_MIPMAP_NEAREST:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
case SamplerMinFilter::LINEAR_MIPMAP_NEAREST:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
case SamplerMinFilter::NEAREST_MIPMAP_LINEAR:
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
case SamplerMinFilter::LINEAR_MIPMAP_LINEAR:
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
}
}
constexpr inline float getMaxLod(SamplerMinFilter filter) noexcept {
switch (filter) {
case SamplerMinFilter::NEAREST:
case SamplerMinFilter::LINEAR:
// The Vulkan spec recommends a max LOD of 0.25 to "disable" mipmapping.
// See "Mapping of OpenGL to Vulkan filter modes" in the VK Spec.
return 0.25f;
case SamplerMinFilter::NEAREST_MIPMAP_NEAREST:
case SamplerMinFilter::LINEAR_MIPMAP_NEAREST:
case SamplerMinFilter::NEAREST_MIPMAP_LINEAR:
case SamplerMinFilter::LINEAR_MIPMAP_LINEAR:
return VK_LOD_CLAMP_NONE;
}
}
constexpr inline VkBool32 getCompareEnable(SamplerCompareMode mode) noexcept {
return mode == SamplerCompareMode::NONE ? VK_FALSE : VK_TRUE;
}
VulkanSamplerCache::VulkanSamplerCache(VkDevice device)
: mDevice(device) {}
@@ -106,18 +33,18 @@ VkSampler VulkanSamplerCache::getSampler(SamplerParams params) noexcept {
}
VkSamplerCreateInfo samplerInfo {
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.magFilter = getFilter(params.filterMag),
.minFilter = getFilter(params.filterMin),
.mipmapMode = getMipmapMode(params.filterMin),
.addressModeU = getWrapMode(params.wrapS),
.addressModeV = getWrapMode(params.wrapT),
.addressModeW = getWrapMode(params.wrapR),
.anisotropyEnable = params.anisotropyLog2 == 0 ? 0u : 1u,
.magFilter = fvkutils::getFilter(params.filterMag),
.minFilter = fvkutils::getFilter(params.filterMin),
.mipmapMode = fvkutils::getMipmapMode(params.filterMin),
.addressModeU = fvkutils::getWrapMode(params.wrapS),
.addressModeV = fvkutils::getWrapMode(params.wrapT),
.addressModeW = fvkutils::getWrapMode(params.wrapR),
.anisotropyEnable = params.anisotropyLog2 == 0 ? VK_FALSE : VK_TRUE,
.maxAnisotropy = (float)(1u << params.anisotropyLog2),
.compareEnable = getCompareEnable(params.compareMode),
.compareEnable = fvkutils::getCompareEnable(params.compareMode),
.compareOp = fvkutils::getCompareOp(params.compareFunc),
.minLod = 0.0f,
.maxLod = getMaxLod(params.filterMin),
.maxLod = fvkutils::getMaxLod(params.filterMin),
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
.unnormalizedCoordinates = VK_FALSE
};

View File

@@ -55,63 +55,61 @@ VkComponentMapping composeSwizzle(VkComponentMapping const& prev, VkComponentMap
VK_COMPONENT_SWIZZLE_A,
};
auto const compose = [](VkComponentSwizzle out, VkComponentMapping const& prev,
uint8_t channelIndex) {
// We need to first change all identities to its equivalent channel.
if (out == VK_COMPONENT_SWIZZLE_IDENTITY) {
out = IDENTITY[channelIndex];
auto const compose = [](VkComponentMapping const& prev,
VkComponentMapping const& next) -> VkComponentMapping {
VkComponentSwizzle vals[4] = { next.r, next.g, next.b, next.a };
for (auto& out: vals) {
switch (out) {
case VK_COMPONENT_SWIZZLE_R:
out = prev.r;
break;
case VK_COMPONENT_SWIZZLE_G:
out = prev.g;
break;
case VK_COMPONENT_SWIZZLE_B:
out = prev.b;
break;
case VK_COMPONENT_SWIZZLE_A:
out = prev.a;
break;
// Do not modify the value
case VK_COMPONENT_SWIZZLE_IDENTITY:
case VK_COMPONENT_SWIZZLE_ZERO:
case VK_COMPONENT_SWIZZLE_ONE:
// Below is not exposed in Vulkan's API, but needs to be there for compilation.
case VK_COMPONENT_SWIZZLE_MAX_ENUM:
break;
}
}
switch (out) {
case VK_COMPONENT_SWIZZLE_R:
out = prev.r;
break;
case VK_COMPONENT_SWIZZLE_G:
out = prev.g;
break;
case VK_COMPONENT_SWIZZLE_B:
out = prev.b;
break;
case VK_COMPONENT_SWIZZLE_A:
out = prev.a;
break;
case VK_COMPONENT_SWIZZLE_IDENTITY:
case VK_COMPONENT_SWIZZLE_ZERO:
case VK_COMPONENT_SWIZZLE_ONE:
return out;
// Below is not exposed in Vulkan's API, but needs to be there for compilation.
case VK_COMPONENT_SWIZZLE_MAX_ENUM:
break;
}
// If the result correctly corresponds to the identity, just return identity.
if (IDENTITY[channelIndex] == out) {
return VK_COMPONENT_SWIZZLE_IDENTITY;
}
return out;
return { vals[0], vals[1], vals[2], vals[3] };
};
auto const identityToChannel = [](VkComponentSwizzle val, uint8_t channelIndex) {
if (val != VK_COMPONENT_SWIZZLE_IDENTITY) {
return val;
auto const identityToChannel = [](VkComponentMapping const& mapping) -> VkComponentMapping {
VkComponentSwizzle vals[4] = { mapping.r, mapping.g, mapping.b, mapping.a };
for (uint8_t i = 0; i < 4; i++) {
if (vals[i] != VK_COMPONENT_SWIZZLE_IDENTITY) {
continue;
}
vals[i] = IDENTITY[i];
}
return IDENTITY[channelIndex];
return { vals[0], vals[1], vals[2], vals[3] };
};
auto const channelToIdentity = [](VkComponentMapping const& mapping) -> VkComponentMapping {
VkComponentSwizzle vals[4] = { mapping.r, mapping.g, mapping.b, mapping.a };
for (uint8_t i = 0; i < 4; i++) {
if (IDENTITY[i] != vals[i]) {
continue;
}
vals[i] = VK_COMPONENT_SWIZZLE_IDENTITY;
}
return { vals[0], vals[1], vals[2], vals[3] };
};
// We make sure all all identities are mapped into respective channels so that actual channel
// mapping will be passed onto the output.
VkComponentMapping const prevExplicit = {
identityToChannel(prev.r, 0),
identityToChannel(prev.g, 1),
identityToChannel(prev.b, 2),
identityToChannel(prev.a, 3),
};
// Note that the channel index corresponds to the VkComponentMapping struct layout.
return {
compose(next.r, prevExplicit, 0),
compose(next.g, prevExplicit, 1),
compose(next.b, prevExplicit, 2),
compose(next.a, prevExplicit, 3),
};
VkComponentMapping const prevExplicit = identityToChannel(prev);
VkComponentMapping const nextExplicit = identityToChannel(next);
return channelToIdentity(compose(prevExplicit, nextExplicit));
}
inline VulkanLayout getDefaultLayoutImpl(TextureUsage usage) {
@@ -153,7 +151,6 @@ uint8_t getLayerCountFromDepth(uint32_t const depth) {
return getLayerCount(getSamplerTypeFromDepth(depth), depth);
}
} // anonymous namespace
VulkanTextureState::VulkanTextureState(VkDevice device, VmaAllocator allocator,
@@ -249,12 +246,19 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
// Determine if we can use the transient usage flag combined with lazily allocated memory.
const bool useTransientAttachment =
// Lazily allocated memory is available.
context.isLazilyAllocatedMemorySupported() &&
// Usage consists of attachment flags only.
none(tusage & ~TextureUsage::ALL_ATTACHMENTS) &&
// Usage contains at least one attachment flag.
any(tusage & TextureUsage::ALL_ATTACHMENTS);
// Lazily allocated memory is available.
context.isLazilyAllocatedMemorySupported() &&
// Usage consists of attachment flags only.
none(tusage & ~TextureUsage::ALL_ATTACHMENTS) &&
// Usage contains at least one attachment flag.
any(tusage & TextureUsage::ALL_ATTACHMENTS) &&
// Depth resolve cannot use transient attachment because it uses a custom shader.
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
// restriction.
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
// and above to be able to support stencil resolve (along with depth).
!(any(usage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
mState->mIsTransientAttachment = useTransientAttachment;
const VkImageUsageFlags transientFlag =

View File

@@ -983,10 +983,32 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadata(
}
VulkanPlatform::ImageData VulkanPlatform::createExternalImageData(
ExternalImageHandleRef externalImage, const ExternalImageMetadata& metadata) {
return createExternalImageDataImpl(externalImage, mImpl->mDevice, metadata);
ExternalImageHandleRef externalImage, const ExternalImageMetadata& metadata,
uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
return createExternalImageDataImpl(externalImage, mImpl->mDevice, metadata, memoryTypeIndex,
usage);
}
VkSampler VulkanPlatform::createExternalSampler(SamplerYcbcrConversion chroma,
SamplerParams sampler, uint32_t internalFormat) {
return createExternalSamplerImpl(mImpl->mDevice, chroma, sampler, internalFormat);
}
VkImageView VulkanPlatform::createExternalImageView(SamplerYcbcrConversion chroma,
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
VkImageViewType viewType, VkComponentMapping swizzle) {
return createExternalImageViewImpl(mImpl->mDevice, chroma, internalFormat, image, range,
viewType, swizzle);
}
ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const {
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

View File

@@ -13,15 +13,16 @@
* 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"
#include <utils/Panic.h>
#include "vulkan/utils/Image.h"
#include "vulkan/utils/Conversion.h"
#include <bluevk/BlueVK.h>
@@ -136,7 +137,8 @@ std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer
}
VulkanPlatform::ImageData allocateExternalImage(AHardwareBuffer* buffer, VkDevice device,
VulkanPlatform::ExternalImageMetadata const& metadata) {
VulkanPlatform::ExternalImageMetadata const& metadata, uint32_t memoryTypeIndex,
VkImageUsageFlags usage) {
VulkanPlatform::ImageData data;
// if external format we need to specifiy it in the allocation
@@ -145,8 +147,8 @@ VulkanPlatform::ImageData allocateExternalImage(AHardwareBuffer* buffer, VkDevic
const VkExternalFormatANDROID externalFormat = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
.pNext = nullptr,
.externalFormat = metadata
.externalFormat,// pass down the format (external means we don't have it VK defined)
// pass down the format (external means we don't have it VK defined)
.externalFormat = metadata.externalFormat,
};
const VkExternalMemoryImageCreateInfo externalCreateInfo = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
@@ -154,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;
@@ -165,10 +167,8 @@ VulkanPlatform::ImageData allocateExternalImage(AHardwareBuffer* buffer, VkDevic
};
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = metadata.layers;
imageInfo.usage = metadata.usage;
// In the unprotected case add R/W capabilities
if (metadata.isProtected == false)
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
imageInfo.samples = metadata.samples;
imageInfo.usage = usage;
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &data.first);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
@@ -186,10 +186,12 @@ VulkanPlatform::ImageData allocateExternalImage(AHardwareBuffer* buffer, VkDevic
.image = data.first,
.buffer = VK_NULL_HANDLE,
};
VkMemoryAllocateInfo allocInfo = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
VkMemoryAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &memoryDedicatedAllocateInfo,
.allocationSize = metadata.allocationSize,
.memoryTypeIndex = metadata.memoryTypeBits};
.memoryTypeIndex = memoryTypeIndex,
};
result = vkAllocateMemory(device, &allocInfo, VKALLOC, &data.second);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkAllocateMemory failed with error=" << static_cast<int32_t>(result);
@@ -197,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);
@@ -223,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;
@@ -242,10 +253,8 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataIm
std::tie(metadata.format, metadata.usage) =
getVKFormatAndUsage(bufferDesc, fvkExternalImage->sRGB);
}
// In the unprotected case add R/W capabilities
if (metadata.isProtected == false) {
metadata.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
metadata.samples = VK_SAMPLE_COUNT_1_BIT;
VkAndroidHardwareBufferFormatPropertiesANDROID formatInfo = {
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID,
@@ -255,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("
@@ -270,26 +279,140 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataIm
return metadata;
}
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
ExternalImageHandleRef externalImage, VkDevice device,
const ExternalImageMetadata& metadata) {
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);
VkResult result = vkBindImageMemory(device, data.first, data.second, 0);
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
ImageData data = allocateExternalImage(fvkExternalImage->aHardwareBuffer, getDevice(), metadata,
memoryTypeIndex, usage);
VkResult result = vkBindImageMemory(getDevice(), data.first, data.second, 0);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkBindImageMemory error=" << static_cast<int32_t>(result);
<< "vkBindImageMemory error=" << static_cast<int32_t>(result);
return data;
}
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() {
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device, SamplerYcbcrConversion chroma,
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
VkImageViewType viewType, VkComponentMapping swizzle){
VkExternalFormatANDROID externalFormat = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
.pNext = nullptr,
.externalFormat = internalFormat,
};
TextureSwizzle const swizzleArray[] = {chroma.r, chroma.g, chroma.b, chroma.a};
VkSamplerYcbcrConversionCreateInfo conversionInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
.pNext = &externalFormat,
.format = VK_FORMAT_UNDEFINED,
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
.components = fvkutils::getSwizzleMap(swizzleArray),
.xChromaOffset = fvkutils::getChromaLocation(chroma.xChromaOffset),
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset),
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
};
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
VkResult result = vkCreateSamplerYcbcrConversion(device, &conversionInfo,
nullptr, &conversion);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "Unable to create Ycbcr Conversion."
<< " error=" << static_cast<int32_t>(result);
VkSamplerYcbcrConversionInfo samplerYcbcrConversionInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
.pNext = nullptr,
.conversion = conversion,
};
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = &samplerYcbcrConversionInfo,
.flags = 0,
.image = image,
.viewType = viewType,
.format = VK_FORMAT_UNDEFINED,
.components = swizzle,
.subresourceRange = range,
};
VkImageView imageView;
result = vkCreateImageView(device, &viewInfo, VKALLOC, &imageView);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "Unable to create VkImageView."
<< " error=" << static_cast<int32_t>(result);
return imageView;
}
VkSampler VulkanPlatform::createExternalSamplerImpl(
VkDevice device, SamplerYcbcrConversion chroma, SamplerParams params,
uint32_t internalFormat) {
VkExternalFormatANDROID externalFormat = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
.pNext = nullptr,
.externalFormat = internalFormat,
};
TextureSwizzle const swizzleArray[] = {chroma.r, chroma.g, chroma.b, chroma.a};
VkSamplerYcbcrConversionCreateInfo conversionInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
.pNext = &externalFormat,
.format = VK_FORMAT_UNDEFINED,
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
.components = fvkutils::getSwizzleMap(swizzleArray),
.xChromaOffset = fvkutils::getChromaLocation(chroma.xChromaOffset),
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset),
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
};
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
VkResult result = vkCreateSamplerYcbcrConversion(device, &conversionInfo,
nullptr, &conversion);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "Unable to create Ycbcr Conversion."
<< " error=" << static_cast<int32_t>(result);
VkSamplerYcbcrConversionInfo samplerYcbcrConversionInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
.pNext = nullptr,
.conversion = conversion,
};
VkSamplerCreateInfo samplerInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.pNext = &samplerYcbcrConversionInfo,
.magFilter = fvkutils::getFilter(params.filterMag),
.minFilter = fvkutils::getFilter(params.filterMin),
.mipmapMode = fvkutils::getMipmapMode(params.filterMin),
.addressModeU = fvkutils::getWrapMode(params.wrapS),
.addressModeV = fvkutils::getWrapMode(params.wrapT),
.addressModeW = fvkutils::getWrapMode(params.wrapR),
.anisotropyEnable = params.anisotropyLog2 == 0 ? VK_FALSE : VK_TRUE,
.maxAnisotropy = (float)(1u << params.anisotropyLog2),
.compareEnable = fvkutils::getCompareEnable(params.compareMode),
.compareOp = fvkutils::getCompareOp(params.compareFunc),
.minLod = 0.0f,
.maxLod = fvkutils::getMaxLod(params.filterMin),
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
.unnormalizedCoordinates = VK_FALSE,
};
VkSampler sampler;
result = vkCreateSampler(device, &samplerInfo, VKALLOC, &sampler);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "Unable to create sampler."
<< " error=" << static_cast<int32_t>(result);
return sampler;
}
VulkanPlatform::ExtensionSet VulkanPlatformAndroid::getSwapchainInstanceExtensions() const {
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;
@@ -301,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

View File

@@ -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
@@ -70,11 +70,24 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataIm
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
ExternalImageHandleRef externalImage, VkDevice device,
const ExternalImageMetadata& metadata) {
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
return {};
}
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWindow,
VkSampler VulkanPlatform::createExternalSamplerImpl(VkDevice device,
SamplerYcbcrConversion chroma,
SamplerParams sampler,
uint32_t internalFormat) {
return VK_NULL_HANDLE;
}
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
VkImageSubresourceRange range, VkImageViewType viewType, VkComponentMapping swizzle) {
return VK_NULL_HANDLE;
}
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
VkInstance instance, uint64_t flags) noexcept {
VkSurfaceKHR surface;
#if defined(__APPLE__)

View File

@@ -91,11 +91,24 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataIm
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
ExternalImageHandleRef externalImage, VkDevice device,
const ExternalImageMetadata& metadata) {
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
return {};
}
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() {
VkSampler VulkanPlatform::createExternalSamplerImpl(VkDevice device,
SamplerYcbcrConversion chroma,
SamplerParams sampler,
uint32_t internalFormat) {
return VK_NULL_HANDLE;
}
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
VkImageSubresourceRange range, VkImageViewType viewType, VkComponentMapping swizzle) {
return VK_NULL_HANDLE;
}
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
VulkanPlatform::ExtensionSet const ret = {
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
@@ -113,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;

View File

@@ -588,6 +588,79 @@ VkComponentMapping getSwizzleMap(TextureSwizzle const swizzle[4]) {
return map;
}
VkFilter getFilter(SamplerMinFilter filter) {
switch (filter) {
case SamplerMinFilter::NEAREST:
return VK_FILTER_NEAREST;
case SamplerMinFilter::LINEAR:
return VK_FILTER_LINEAR;
case SamplerMinFilter::NEAREST_MIPMAP_NEAREST:
return VK_FILTER_NEAREST;
case SamplerMinFilter::LINEAR_MIPMAP_NEAREST:
return VK_FILTER_LINEAR;
case SamplerMinFilter::NEAREST_MIPMAP_LINEAR:
return VK_FILTER_NEAREST;
case SamplerMinFilter::LINEAR_MIPMAP_LINEAR:
return VK_FILTER_LINEAR;
}
}
VkFilter getFilter(SamplerMagFilter filter) {
switch (filter) {
case SamplerMagFilter::NEAREST:
return VK_FILTER_NEAREST;
case SamplerMagFilter::LINEAR:
return VK_FILTER_LINEAR;
}
}
VkSamplerMipmapMode getMipmapMode(SamplerMinFilter filter) {
switch (filter) {
case SamplerMinFilter::NEAREST:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
case SamplerMinFilter::LINEAR:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
case SamplerMinFilter::NEAREST_MIPMAP_NEAREST:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
case SamplerMinFilter::LINEAR_MIPMAP_NEAREST:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
case SamplerMinFilter::NEAREST_MIPMAP_LINEAR:
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
case SamplerMinFilter::LINEAR_MIPMAP_LINEAR:
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
}
}
VkSamplerAddressMode getWrapMode(SamplerWrapMode mode) {
switch (mode) {
case SamplerWrapMode::REPEAT:
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
case SamplerWrapMode::CLAMP_TO_EDGE:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
case SamplerWrapMode::MIRRORED_REPEAT:
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
}
}
VkBool32 getCompareEnable(SamplerCompareMode mode) {
return mode == SamplerCompareMode::NONE ? VK_FALSE : VK_TRUE;
}
float getMaxLod(SamplerMinFilter filter) {
switch (filter) {
case SamplerMinFilter::NEAREST:
case SamplerMinFilter::LINEAR:
// The Vulkan spec recommends a max LOD of 0.25 to "disable" mipmapping.
// See "Mapping of OpenGL to Vulkan filter modes" in the VK Spec.
return 0.25f;
case SamplerMinFilter::NEAREST_MIPMAP_NEAREST:
case SamplerMinFilter::LINEAR_MIPMAP_NEAREST:
case SamplerMinFilter::NEAREST_MIPMAP_LINEAR:
case SamplerMinFilter::LINEAR_MIPMAP_LINEAR:
return VK_LOD_CLAMP_NONE;
}
}
VkShaderStageFlags getShaderStageFlags(ShaderStageFlags stageFlags) {
VkShaderStageFlags flags = 0x0;
if (any(stageFlags & ShaderStageFlags::VERTEX)) flags |= VK_SHADER_STAGE_VERTEX_BIT;

View File

@@ -56,6 +56,19 @@ uint32_t getComponentCount(VkFormat format);
VkComponentMapping getSwizzleMap(TextureSwizzle const swizzle[4]);
VkShaderStageFlags getShaderStageFlags(ShaderStageFlags stageFlags);
// Needed by the Platform for external sampler creation
VkFilter getFilter(SamplerMinFilter filter);
VkFilter getFilter(SamplerMagFilter filter);
VkSamplerMipmapMode getMipmapMode(SamplerMinFilter filter);
VkSamplerAddressMode getWrapMode(SamplerWrapMode mode);
VkBool32 getCompareEnable(SamplerCompareMode mode);
float getMaxLod(SamplerMinFilter filter);
// Ycbcr related functions
VkSamplerYcbcrModelConversion getYcbcrModelConversion(SamplerYcbcrModelConversion model);
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range);
VkChromaLocation getChromaLocation(ChromaLocation loc);
inline VkImageViewType getViewType(SamplerType target) {
switch (target) {
case SamplerType::SAMPLER_CUBEMAP:

View File

@@ -193,6 +193,49 @@ uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask) {
return mostSignificantBit((sampleCount - 1) & mask);
}
VkSamplerYcbcrModelConversion getYcbcrModelConversion(
SamplerYcbcrModelConversion model) {
switch (model) {
case SamplerYcbcrModelConversion::RGB_IDENTITY:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY;
case SamplerYcbcrModelConversion::YCBCR_IDENTITY:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY;
case SamplerYcbcrModelConversion::YCBCR_709:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709;
case SamplerYcbcrModelConversion::YCBCR_601:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601;
case SamplerYcbcrModelConversion::YCBCR_2020:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range) {
switch (range) {
case SamplerYcbcrRange::ITU_FULL:
return VK_SAMPLER_YCBCR_RANGE_ITU_FULL;
case SamplerYcbcrRange::ITU_NARROW:
return VK_SAMPLER_YCBCR_RANGE_ITU_NARROW;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
VkChromaLocation getChromaLocation(ChromaLocation loc) {
switch (loc) {
case ChromaLocation::COSITED_EVEN:
return VK_CHROMA_LOCATION_COSITED_EVEN;
case ChromaLocation::MIDPOINT:
return VK_CHROMA_LOCATION_MIDPOINT;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
} // namespace filament::backend::fvkutils
bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) {

View File

@@ -0,0 +1,66 @@
/*
* 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_WEBGPUCONSTANTS_H
#define TNT_FILAMENT_BACKEND_WEBGPUCONSTANTS_H
#include <utils/Log.h>
#include <cstdint>
// FWGPU is short for Filament WebGPU
// turn on runtime validation, namely for debugging, that would normally not run (for release)
#define FWGPU_DEBUG_VALIDATION 0x00000001
// print/log system component details to the console, e.g. about the
// instance, surface, adapter, device, etc.
#define FWGPU_PRINT_SYSTEM 0x00000002
// Set this to enable logging "only" to one output stream. This is useful in the case where we want
// to debug with print statements and want ordered logging (e.g slog.i and slog.e will not appear in
// order of calls).
#define FWGPU_DEBUG_FORCE_LOG_TO_I 0x00000004
// Useful default combinations
#define FWGPU_DEBUG_EVERYTHING 0xFFFFFFFF
#if defined(FILAMENT_BACKEND_DEBUG_FLAG)
#define FWGPU_DEBUG_FORWARDED_FLAG (FILAMENT_BACKEND_DEBUG_FLAG & FWGPU_DEBUG_EVERYTHING)
#else
#define FWGPU_DEBUG_FORWARDED_FLAG 0
#endif
#ifndef NDEBUG
#define FWGPU_DEBUG_FLAGS FWGPU_DEBUG_FORWARDED_FLAG
#else
#define FWGPU_DEBUG_FLAGS 0
#endif
#define FWGPU_ENABLED(flags) (((FWGPU_DEBUG_FLAGS) & (flags)) == (flags))
#if FWGPU_ENABLED(FWGPU_DEBUG_FORCE_LOG_TO_I)
#define FWGPU_LOGI (utils::slog.i)
#define FWGPU_LOGD FWGPU_LOGI
#define FWGPU_LOGE FWGPU_LOGI
#define FWGPU_LOGW FWGPU_LOGI
#else
#define FWGPU_LOGE (utils::slog.e)
#define FWGPU_LOGW (utils::slog.w)
#define FWGPU_LOGD (utils::slog.d)
#define FWGPU_LOGI (utils::slog.i)
#endif
#endif// TNT_FILAMENT_BACKEND_WEBGPUCONSTANTS_H

View File

@@ -14,21 +14,254 @@
* limitations under the License.
*/
#include "webgpu/WebGPUDriver.h"
#include "webgpu/WebGPUConstants.h"
#include <backend/platforms/WebGPUPlatform.h>
#include "CommandStreamDispatcher.h"
#include "DriverBase.h"
#include "private/backend/Dispatcher.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include "webgpu/WebGPUDriver.h"
#include "CommandStreamDispatcher.h"
#include <math/mat3.h>
#include <utils/CString.h>
#include <utils/ostream.h>
#include <stdint.h>
#include <dawn/webgpu_cpp_print.h>
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <sstream>
#include <string_view>
#include <utility>
#include <variant>
namespace filament::backend {
Driver* WebGPUDriver::create() {
return new WebGPUDriver();
namespace {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printInstanceDetails(wgpu::Instance const& instance) {
wgpu::SupportedWGSLLanguageFeatures supportedWGSLLanguageFeatures{};
if (!instance.GetWGSLLanguageFeatures(&supportedWGSLLanguageFeatures)) {
FWGPU_LOGW << "Failed to get WebGPU instance supported WGSL language features"
<< utils::io::endl;
} else {
FWGPU_LOGI << "WebGPU instance supported WGSL language features ("
<< supportedWGSLLanguageFeatures.featureCount << "):" << utils::io::endl;
if (supportedWGSLLanguageFeatures.featureCount > 0 &&
supportedWGSLLanguageFeatures.features != nullptr) {
std::for_each(supportedWGSLLanguageFeatures.features,
supportedWGSLLanguageFeatures.features +
supportedWGSLLanguageFeatures.featureCount,
[](wgpu::WGSLLanguageFeatureName const featureName) {
std::stringstream nameStream{};
nameStream << featureName;
FWGPU_LOGI << " " << nameStream.str() << utils::io::endl;
});
}
}
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printLimit(std::string_view name, const std::variant<uint32_t, uint64_t> value) {
FWGPU_LOGI << " " << name.data() << ": ";
bool undefined = true;
if (std::holds_alternative<uint32_t>(value)) {
if (std::get<uint32_t>(value) != WGPU_LIMIT_U32_UNDEFINED) {
undefined = false;
FWGPU_LOGI << std::get<uint32_t>(value);
}
} else if (std::holds_alternative<uint64_t>(value)) {
if (std::get<uint64_t>(value) != WGPU_LIMIT_U64_UNDEFINED) {
undefined = false;
FWGPU_LOGI << std::get<uint64_t>(value);
}
}
if (undefined) {
FWGPU_LOGI << "UNDEFINED";
}
FWGPU_LOGI << utils::io::endl;
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printLimits(wgpu::Limits const& limits) {
printLimit("maxTextureDimension1D", limits.maxTextureDimension1D);
printLimit("maxTextureDimension2D", limits.maxTextureDimension2D);
printLimit("maxTextureDimension3D", limits.maxTextureDimension3D);
printLimit("maxTextureArrayLayers", limits.maxTextureArrayLayers);
printLimit("maxBindGroups", limits.maxBindGroups);
printLimit("maxBindGroupsPlusVertexBuffers", limits.maxBindGroupsPlusVertexBuffers);
printLimit("maxBindingsPerBindGroup", limits.maxBindingsPerBindGroup);
printLimit("maxDynamicUniformBuffersPerPipelineLayout",
limits.maxDynamicUniformBuffersPerPipelineLayout);
printLimit("maxDynamicStorageBuffersPerPipelineLayout",
limits.maxDynamicStorageBuffersPerPipelineLayout);
printLimit("maxSampledTexturesPerShaderStage", limits.maxSampledTexturesPerShaderStage);
printLimit("maxSamplersPerShaderStage", limits.maxSamplersPerShaderStage);
printLimit("maxStorageBuffersPerShaderStage", limits.maxStorageBuffersPerShaderStage);
printLimit("maxStorageTexturesPerShaderStage", limits.maxStorageTexturesPerShaderStage);
printLimit("maxUniformBuffersPerShaderStage", limits.maxUniformBuffersPerShaderStage);
printLimit("maxUniformBufferBindingSize", limits.maxUniformBufferBindingSize);
printLimit("maxStorageBufferBindingSize", limits.maxStorageBufferBindingSize);
printLimit("minUniformBufferOffsetAlignment", limits.minUniformBufferOffsetAlignment);
printLimit("minStorageBufferOffsetAlignment", limits.minStorageBufferOffsetAlignment);
printLimit("maxVertexBuffers", limits.maxVertexBuffers);
printLimit("maxBufferSize", limits.maxBufferSize);
printLimit("maxVertexAttributes", limits.maxVertexAttributes);
printLimit("maxVertexBufferArrayStride", limits.maxVertexBufferArrayStride);
printLimit("maxInterStageShaderComponents", limits.maxInterStageShaderComponents);
printLimit("maxInterStageShaderVariables", limits.maxInterStageShaderVariables);
printLimit("maxColorAttachments", limits.maxColorAttachments);
printLimit("maxColorAttachmentBytesPerSample", limits.maxColorAttachmentBytesPerSample);
printLimit("maxComputeWorkgroupStorageSize", limits.maxComputeWorkgroupStorageSize);
printLimit("maxComputeInvocationsPerWorkgroup", limits.maxComputeInvocationsPerWorkgroup);
printLimit("maxComputeWorkgroupSizeX", limits.maxComputeWorkgroupSizeX);
printLimit("maxComputeWorkgroupSizeY", limits.maxComputeWorkgroupSizeY);
printLimit("maxComputeWorkgroupSizeZ", limits.maxComputeWorkgroupSizeZ);
printLimit("maxComputeWorkgroupsPerDimension", limits.maxComputeWorkgroupsPerDimension);
printLimit("maxStorageBuffersInVertexStage", limits.maxStorageBuffersInVertexStage);
printLimit("maxStorageTexturesInVertexStage", limits.maxStorageTexturesInVertexStage);
printLimit("maxStorageBuffersInFragmentStage", limits.maxStorageBuffersInFragmentStage);
printLimit("maxStorageTexturesInFragmentStage", limits.maxStorageTexturesInFragmentStage);
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printAdapterDetails(wgpu::Adapter const& adapter) {
wgpu::DawnAdapterPropertiesPowerPreference powerPreferenceProperties{};
wgpu::AdapterInfo adapterInfo{};
adapterInfo.nextInChain = &powerPreferenceProperties;
if (!adapter.GetInfo(&adapterInfo)) {
FWGPU_LOGW << "Failed to get WebGPU adapter info" << utils::io::endl;
} else {
std::stringstream backendTypeStream{};
backendTypeStream << adapterInfo.backendType;
std::stringstream adapterTypeStream{};
adapterTypeStream << adapterInfo.adapterType;
std::stringstream powerPreferenceStream{};
powerPreferenceStream << powerPreferenceProperties.powerPreference;
FWGPU_LOGI << "WebGPU adapter info:" << utils::io::endl;
FWGPU_LOGI << " vendor: " << adapterInfo.vendor.data << utils::io::endl;
FWGPU_LOGI << " architecture: " << adapterInfo.architecture.data << utils::io::endl;
FWGPU_LOGI << " device: " << adapterInfo.device.data << utils::io::endl;
FWGPU_LOGI << " description: " << adapterInfo.description.data << utils::io::endl;
FWGPU_LOGI << " backend type: " << backendTypeStream.str().data() << utils::io::endl;
FWGPU_LOGI << " adapter type: " << adapterTypeStream.str().data() << utils::io::endl;
FWGPU_LOGI << " device ID: " << adapterInfo.deviceID << utils::io::endl;
FWGPU_LOGI << " vendor ID: " << adapterInfo.vendorID << utils::io::endl;
FWGPU_LOGI << " subgroup min size: " << adapterInfo.subgroupMinSize << utils::io::endl;
FWGPU_LOGI << " subgroup max size: " << adapterInfo.subgroupMaxSize << utils::io::endl;
FWGPU_LOGI << " compatibility mode: " << bool(adapterInfo.compatibilityMode)
<< utils::io::endl;
FWGPU_LOGI << " power preference: " << powerPreferenceStream.str() << utils::io::endl;
}
wgpu::SupportedFeatures supportedFeatures{};
adapter.GetFeatures(&supportedFeatures);
FWGPU_LOGI << "WebGPU adapter supported features (" << supportedFeatures.featureCount
<< "):" << utils::io::endl;
if (supportedFeatures.featureCount > 0 && supportedFeatures.features != nullptr) {
std::for_each(supportedFeatures.features,
supportedFeatures.features + supportedFeatures.featureCount,
[](wgpu::FeatureName const name) {
std::stringstream nameStream{};
nameStream << name;
FWGPU_LOGI << " " << nameStream.str().data() << utils::io::endl;
});
}
wgpu::SupportedLimits supportedLimits{};
if (!adapter.GetLimits(&supportedLimits)) {
FWGPU_LOGW << "Failed to get WebGPU adapter supported limits" << utils::io::endl;
} else {
FWGPU_LOGI << "WebGPU adapter supported limits:" << utils::io::endl;
printLimits(supportedLimits.limits);
}
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabilities) {
std::stringstream usages_stream{};
usages_stream << capabilities.usages;
FWGPU_LOGI << "WebGPU surface capabilities:" << utils::io::endl;
FWGPU_LOGI << " surface usages: " << usages_stream.str().data() << utils::io::endl;
FWGPU_LOGI << " surface formats (" << capabilities.formatCount << "):" << utils::io::endl;
if (capabilities.formatCount > 0 && capabilities.formats != nullptr) {
std::for_each(capabilities.formats, capabilities.formats + capabilities.formatCount,
[](wgpu::TextureFormat const format) {
std::stringstream format_stream{};
format_stream << format;
FWGPU_LOGI << " " << format_stream.str().data() << utils::io::endl;
});
}
FWGPU_LOGI << " surface present modes (" << capabilities.presentModeCount
<< "):" << utils::io::endl;
if (capabilities.presentModeCount > 0 && capabilities.presentModes != nullptr) {
std::for_each(capabilities.presentModes,
capabilities.presentModes + capabilities.presentModeCount,
[](wgpu::PresentMode const presentMode) {
std::stringstream present_mode_stream{};
present_mode_stream << presentMode;
FWGPU_LOGI << " " << present_mode_stream.str().data() << utils::io::endl;
});
}
FWGPU_LOGI << " surface alpha modes (" << capabilities.alphaModeCount
<< "):" << utils::io::endl;
if (capabilities.alphaModeCount > 0 && capabilities.alphaModes != nullptr) {
std::for_each(capabilities.alphaModes,
capabilities.alphaModes + capabilities.alphaModeCount,
[](wgpu::CompositeAlphaMode const alphaMode) {
std::stringstream alpha_mode_stream{};
alpha_mode_stream << alphaMode;
FWGPU_LOGI << " " << alpha_mode_stream.str().data() << utils::io::endl;
});
}
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printDeviceDetails(wgpu::Device const& device) {
wgpu::SupportedFeatures supportedFeatures{};
device.GetFeatures(&supportedFeatures);
FWGPU_LOGI << "WebGPU device supported features (" << supportedFeatures.featureCount
<< "):" << utils::io::endl;
if (supportedFeatures.featureCount > 0 && supportedFeatures.features != nullptr) {
std::for_each(supportedFeatures.features,
supportedFeatures.features + supportedFeatures.featureCount,
[](wgpu::FeatureName const name) {
std::stringstream nameStream{};
nameStream << name;
FWGPU_LOGI << " " << nameStream.str().data() << utils::io::endl;
});
}
wgpu::SupportedLimits supportedLimits{};
if (!device.GetLimits(&supportedLimits)) {
FWGPU_LOGW << "Failed to get WebGPU supported device limits" << utils::io::endl;
} else {
FWGPU_LOGI << "WebGPU device supported limits:" << utils::io::endl;
printLimits(supportedLimits.limits);
}
}
#endif
}// namespace
Driver* WebGPUDriver::create(WebGPUPlatform& platform) noexcept {
return new WebGPUDriver(platform);
}
WebGPUDriver::WebGPUDriver() noexcept = default;
WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform) noexcept
: mPlatform(platform) {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printInstanceDetails(mPlatform.getInstance());
#endif
}
WebGPUDriver::~WebGPUDriver() noexcept = default;
@@ -123,6 +356,188 @@ void WebGPUDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> tqh)
void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) {
}
Handle<HwSwapChain> WebGPUDriver::createSwapChainS() noexcept {
return Handle<HwSwapChain>((Handle<HwSwapChain>::HandleId) mNextFakeHandle++);
}
Handle<HwSwapChain> WebGPUDriver::createSwapChainHeadlessS() noexcept {
return Handle<HwSwapChain>((Handle<HwSwapChain>::HandleId) mNextFakeHandle++);
}
Handle<HwTexture> WebGPUDriver::createTextureS() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
}
Handle<HwTexture> WebGPUDriver::importTextureS() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
}
Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
return Handle<HwProgram>((Handle<HwProgram>::HandleId) mNextFakeHandle++);
}
Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
return Handle<HwFence>((Handle<HwFence>::HandleId) mNextFakeHandle++);
}
Handle<HwTimerQuery> WebGPUDriver::createTimerQueryS() noexcept {
return Handle<HwTimerQuery>((Handle<HwTimerQuery>::HandleId) mNextFakeHandle++);
}
Handle<HwIndexBuffer> WebGPUDriver::createIndexBufferS() noexcept {
return Handle<HwIndexBuffer>((Handle<HwIndexBuffer>::HandleId) mNextFakeHandle++);
}
Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
}
Handle<HwBufferObject> WebGPUDriver::createBufferObjectS() noexcept {
return Handle<HwBufferObject>((Handle<HwBufferObject>::HandleId) mNextFakeHandle++);
}
Handle<HwRenderTarget> WebGPUDriver::createRenderTargetS() noexcept {
return Handle<HwRenderTarget>((Handle<HwRenderTarget>::HandleId) mNextFakeHandle++);
}
Handle<HwVertexBuffer> WebGPUDriver::createVertexBufferS() noexcept {
return Handle<HwVertexBuffer>((Handle<HwVertexBuffer>::HandleId) mNextFakeHandle++);
}
Handle<HwDescriptorSet> WebGPUDriver::createDescriptorSetS() noexcept {
return Handle<HwDescriptorSet>((Handle<HwDescriptorSet>::HandleId) mNextFakeHandle++);
}
Handle<HwRenderPrimitive> WebGPUDriver::createRenderPrimitiveS() noexcept {
return Handle<HwRenderPrimitive>((Handle<HwRenderPrimitive>::HandleId) mNextFakeHandle++);
}
Handle<HwVertexBufferInfo> WebGPUDriver::createVertexBufferInfoS() noexcept {
return Handle<HwVertexBufferInfo>((Handle<HwVertexBufferInfo>::HandleId) mNextFakeHandle++);
}
Handle<HwTexture> WebGPUDriver::createTextureViewSwizzleS() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
}
Handle<HwRenderTarget> WebGPUDriver::createDefaultRenderTargetS() noexcept {
return Handle<HwRenderTarget>((Handle<HwRenderTarget>::HandleId) mNextFakeHandle++);
}
Handle<HwDescriptorSetLayout> WebGPUDriver::createDescriptorSetLayoutS() noexcept {
return Handle<HwDescriptorSetLayout>(
(Handle<HwDescriptorSetLayout>::HandleId) mNextFakeHandle++);
}
Handle<HwTexture> WebGPUDriver::createTextureExternalImageS() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
}
Handle<HwTexture> WebGPUDriver::createTextureExternalImage2S() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
}
Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
}
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
mSurface= mPlatform.createSurface(nativeWindow, flags);
mAdapter = mPlatform.requestAdapter(mSurface);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printAdapterDetails(mAdapter);
wgpu::SurfaceCapabilities surfaceCapabilities{};
if (!mSurface.GetCapabilities(mAdapter, &surfaceCapabilities)) {
FWGPU_LOGW << "Failed to get WebGPU surface capabilities" << utils::io::endl;
} else {
printSurfaceCapabilitiesDetails(surfaceCapabilities);
}
#endif
mDevice = mPlatform.requestDevice(mAdapter);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printDeviceDetails(mDevice);
#endif
mQueue = mDevice.GetQueue();
// TODO configure the surface (maybe before or after creating the swapchain?
// how do we get the surface extent?)
// TODO actually create the swapchain
FWGPU_LOGW << "WebGPU support is still essentially a no-op at this point in development (only "
"background components have been instantiated/selected, such as surface/screen, "
"graphics device/GPU, etc.), thus nothing is being drawn to the screen."
<< utils::io::endl;
#if !FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) && !defined(NDEBUG)
FWGPU_LOGI << "If the FILAMENT_BACKEND_DEBUG_FLAG variable were set with the " << utils::io::hex
<< FWGPU_PRINT_SYSTEM << utils::io::dec
<< " bit flag on during build time the application would print system details "
"about the selected graphics device, surface, etc. To see this try "
"rebuilding Filament with that flag, e.g. ./build.sh -x "
<< FWGPU_PRINT_SYSTEM << " ..." << utils::io::endl;
#endif
}
void WebGPUDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
uint32_t height, uint64_t flags) {}
void WebGPUDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vbih, uint8_t bufferCount,
uint8_t attributeCount, AttributeArray attributes) {}
void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint32_t vertexCount,
Handle<HwVertexBufferInfo> vbih) {}
void WebGPUDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
uint32_t indexCount, BufferUsage usage) {}
void WebGPUDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage) {}
void WebGPUDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage usage) {}
void WebGPUDriver::createTextureViewR(Handle<HwTexture> th, Handle<HwTexture> srch,
uint8_t baseLevel, uint8_t levelCount) {}
void WebGPUDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwTexture> srch,
backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b,
backend::TextureSwizzle a) {}
void WebGPUDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::SamplerType target,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
Platform::ExternalImageHandleRef externalImage) {}
void WebGPUDriver::createTextureExternalImageR(Handle<HwTexture> th, backend::SamplerType target,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
void* externalImage) {}
void WebGPUDriver::createTextureExternalImagePlaneR(Handle<HwTexture> th,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
void* image, uint32_t plane) {}
void WebGPUDriver::importTextureR(Handle<HwTexture> th, intptr_t id, SamplerType target,
uint8_t levels, TextureFormat format, uint8_t samples, uint32_t w, uint32_t h,
uint32_t depth, TextureUsage usage) {}
void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph, Handle<HwVertexBuffer> vbh,
Handle<HwIndexBuffer> ibh, PrimitiveType pt) {}
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {}
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {}
void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> rth, TargetBufferFlags targets,
uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount, MRT color,
TargetBufferInfo depth, TargetBufferInfo stencil) {}
void WebGPUDriver::createFenceR(Handle<HwFence> fh, int) {}
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
void WebGPUDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
backend::DescriptorSetLayout&& info) {}
void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
Handle<HwDescriptorSetLayout> dslh) {}
Handle<HwStream> WebGPUDriver::createStreamNative(void* nativeStream) {
return {};
}
@@ -240,6 +655,14 @@ size_t WebGPUDriver::getMaxUniformBufferSize() {
return 16384u;
}
size_t WebGPUDriver::getMaxTextureSize(SamplerType target) {
return 2048u;
}
size_t WebGPUDriver::getMaxArrayTextureLayers() {
return 256u;
}
void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
uint32_t byteOffset) {
scheduleDestroy(std::move(p));

View File

@@ -17,26 +17,44 @@
#ifndef TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
#define TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
#include "private/backend/Driver.h"
#include <backend/platforms/WebGPUPlatform.h>
#include "DriverBase.h"
#include "private/backend/Dispatcher.h"
#include "private/backend/Driver.h"
#include <backend/DriverEnums.h>
#include <utils/compiler.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
namespace filament::backend {
/**
* WebGPU backend (driver) implementation
*/
class WebGPUDriver final : public DriverBase {
WebGPUDriver() noexcept;
~WebGPUDriver() noexcept override;
Dispatcher getDispatcher() const noexcept final;
public:
static Driver* create();
~WebGPUDriver() noexcept override;
[[nodiscard]] Dispatcher getDispatcher() const noexcept final;
[[nodiscard]] static Driver* create(WebGPUPlatform& platform) noexcept;
private:
ShaderModel getShaderModel() const noexcept final;
ShaderLanguage getShaderLanguage() const noexcept final;
explicit WebGPUDriver(WebGPUPlatform& platform) noexcept;
[[nodiscard]] ShaderModel getShaderModel() const noexcept final;
[[nodiscard]] ShaderLanguage getShaderLanguage() const noexcept final;
uint64_t nextFakeHandle = 1;
// the platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
// handled in the WebGPUPlatform
WebGPUPlatform& mPlatform;
wgpu::Surface mSurface = nullptr;
wgpu::Adapter mAdapter = nullptr;
wgpu::Device mDevice = nullptr;
wgpu::Queue mQueue = nullptr;
uint64_t mNextFakeHandle = 1;
/*
* Driver interface
@@ -45,20 +63,19 @@ private:
template<typename T>
friend class ConcreteDispatcher;
#define DECL_DRIVER_API(methodName, paramsDecl, params) \
#define DECL_DRIVER_API(methodName, paramsDecl, params) \
UTILS_ALWAYS_INLINE inline void methodName(paramsDecl);
#define DECL_DRIVER_API_SYNCHRONOUS(RetType, methodName, paramsDecl, params) \
#define DECL_DRIVER_API_SYNCHRONOUS(RetType, methodName, paramsDecl, params) \
RetType methodName(paramsDecl) override;
#define DECL_DRIVER_API_RETURN(RetType, methodName, paramsDecl, params) \
RetType methodName##S() noexcept override { \
return RetType((RetType::HandleId)nextFakeHandle++); } \
UTILS_ALWAYS_INLINE inline void methodName##R(RetType, paramsDecl) { }
#define DECL_DRIVER_API_RETURN(RetType, methodName, paramsDecl, params) \
RetType methodName##S() noexcept override; \
UTILS_ALWAYS_INLINE inline void methodName##R(RetType, paramsDecl);
#include "private/backend/DriverAPI.inc"
};
} // namespace filament
}// namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
#endif// TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H

View File

@@ -1,36 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "webgpu/WebGPUPlatform.h"
#include "webgpu/WebGPUDriver.h"
#include "utils/Log.h"
namespace filament::backend {
Driver* WebGPUPlatform::createDriver(void* const sharedContext , const Platform::DriverConfig& driverConfig) noexcept {
wgpu::InstanceDescriptor instance_descriptor {};
wgpu::Instance instance = wgpu::CreateInstance(&instance_descriptor);
if (instance) {
utils::slog.i << " WebGPU instance created\n";
} else {
utils::slog.e << " WebGPU instance failed to create\n";
return nullptr;
}
return WebGPUDriver::create();
}
} // namespace filament

View File

@@ -1,42 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_WEBGPUPLATFORM_H
#define TNT_FILAMENT_BACKEND_WEBGPUPLATFORM_H
#include <backend/DriverEnums.h>
#include <backend/Platform.h>
#include "webgpu/webgpu_cpp.h"
namespace filament::backend {
class WebGPUPlatform
final : public Platform {
public:
int getOSVersion() const noexcept final { return 0; }
~WebGPUPlatform() noexcept override = default;
protected:
Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) noexcept override;
};
} // namespace filament
#endif // TNT_FILAMENT_BACKEND_WEBGPUPLATFORM_H

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