Compare commits
3 Commits
MapAsyncEx
...
mhoff/gltf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdd710c3b0 | ||
|
|
208e936dab | ||
|
|
bbea03224d |
@@ -287,6 +287,9 @@ set(FILAMENT ${CMAKE_CURRENT_SOURCE_DIR})
|
|||||||
# Where our tools are
|
# Where our tools are
|
||||||
set(TOOLS ${CMAKE_CURRENT_SOURCE_DIR}/tools)
|
set(TOOLS ${CMAKE_CURRENT_SOURCE_DIR}/tools)
|
||||||
|
|
||||||
|
# Where our tools are
|
||||||
|
set(TESTS ${CMAKE_CURRENT_SOURCE_DIR}/test)
|
||||||
|
|
||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
# Compiler check
|
# Compiler check
|
||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
@@ -896,6 +899,8 @@ if (IS_HOST_PLATFORM)
|
|||||||
add_subdirectory(${TOOLS}/roughness-prefilter)
|
add_subdirectory(${TOOLS}/roughness-prefilter)
|
||||||
add_subdirectory(${TOOLS}/specular-color)
|
add_subdirectory(${TOOLS}/specular-color)
|
||||||
add_subdirectory(${TOOLS}/uberz)
|
add_subdirectory(${TOOLS}/uberz)
|
||||||
|
|
||||||
|
add_subdirectory(${TESTS}/gltf-comparison)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Generate exported executables for cross-compiled builds (Android, WebGL, and iOS)
|
# Generate exported executables for cross-compiled builds (Android, WebGL, and iOS)
|
||||||
|
|||||||
@@ -21,9 +21,9 @@
|
|||||||
#include "utils/Hash.h"
|
#include "utils/Hash.h"
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#include "BackendTest.h"
|
//#include "BackendTest.h"
|
||||||
#include "backend/PixelBufferDescriptor.h"
|
#include "backend/PixelBufferDescriptor.h"
|
||||||
#include "private/backend/DriverApi.h"
|
//#include "private/backend/DriverApi.h"
|
||||||
|
|
||||||
#ifndef FILAMENT_IOS
|
#ifndef FILAMENT_IOS
|
||||||
|
|
||||||
|
|||||||
150
test/gltf-comparison/CMakeLists.txt
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.19)
|
||||||
|
project(filament C ASM)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
set(ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..)
|
||||||
|
set(GENERATION_ROOT ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
set(RESOURCE_DIR "${GENERATION_ROOT}/generated/resources")
|
||||||
|
set(MATERIAL_DIR "${GENERATION_ROOT}/generated/material")
|
||||||
|
set(TEXTURE_DIR "${GENERATION_ROOT}/generated/texture")
|
||||||
|
set(RESOURCE_BINS)
|
||||||
|
|
||||||
|
# Materials
|
||||||
|
set(MATERIAL_SRCS
|
||||||
|
${ROOT_DIR}/samples/materials/aiDefaultMat.mat
|
||||||
|
${ROOT_DIR}/samples/materials/bakedColor.mat
|
||||||
|
${ROOT_DIR}/samples/materials/bakedTexture.mat
|
||||||
|
${ROOT_DIR}/samples/materials/aoPreview.mat
|
||||||
|
${ROOT_DIR}/samples/materials/arrayTexture.mat
|
||||||
|
${ROOT_DIR}/samples/materials/groundShadow.mat
|
||||||
|
${ROOT_DIR}/samples/materials/heightfield.mat
|
||||||
|
${ROOT_DIR}/samples/materials/image.mat
|
||||||
|
${ROOT_DIR}/samples/materials/mirror.mat
|
||||||
|
${ROOT_DIR}/samples/materials/overdraw.mat
|
||||||
|
${ROOT_DIR}/samples/materials/sandboxCloth.mat
|
||||||
|
${ROOT_DIR}/samples/materials/sandboxLit.mat
|
||||||
|
${ROOT_DIR}/samples/materials/sandboxLitFade.mat
|
||||||
|
${ROOT_DIR}/samples/materials/sandboxLitTransparent.mat
|
||||||
|
${ROOT_DIR}/samples/materials/sandboxLitThinRefraction.mat
|
||||||
|
${ROOT_DIR}/samples/materials/sandboxLitThinRefractionSsr.mat
|
||||||
|
${ROOT_DIR}/samples/materials/sandboxLitSolidRefraction.mat
|
||||||
|
${ROOT_DIR}/samples/materials/sandboxLitSolidRefractionSsr.mat
|
||||||
|
${ROOT_DIR}/samples/materials/sandboxSpecGloss.mat
|
||||||
|
${ROOT_DIR}/samples/materials/sandboxSubsurface.mat
|
||||||
|
${ROOT_DIR}/samples/materials/sandboxUnlit.mat
|
||||||
|
${ROOT_DIR}/samples/materials/texturedLit.mat
|
||||||
|
)
|
||||||
|
|
||||||
|
# Tint does not support setting gl_PointSize, disable the relevant sample if using WebGPU
|
||||||
|
# https://github.com/gpuweb/gpuweb/issues/1190
|
||||||
|
if (NOT FILAMENT_SUPPORTS_WEBGPU)
|
||||||
|
set(MATERIAL_SRCS ${MATERIAL_SRCS} materials/pointSprites.mat)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (CMAKE_CROSSCOMPILING)
|
||||||
|
include(${IMPORT_EXECUTABLES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(MAKE_DIRECTORY ${MATERIAL_DIR})
|
||||||
|
|
||||||
|
set (MATC_FLAGS ${MATC_BASE_FLAGS})
|
||||||
|
if (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "instanced")
|
||||||
|
set (MATC_FLAGS ${MATC_FLAGS} -PstereoscopicType=instanced)
|
||||||
|
add_definitions(-DFILAMENT_SAMPLES_STEREO_TYPE_INSTANCED)
|
||||||
|
elseif (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview")
|
||||||
|
set (MATC_FLAGS ${MATC_FLAGS} -PstereoscopicType=multiview)
|
||||||
|
add_definitions(-DFILAMENT_SAMPLES_STEREO_TYPE_MULTIVIEW)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
foreach (mat_src ${MATERIAL_SRCS})
|
||||||
|
get_filename_component(localname "${mat_src}" NAME_WE)
|
||||||
|
get_filename_component(fullname "${mat_src}" ABSOLUTE)
|
||||||
|
set(output_path "${MATERIAL_DIR}/${localname}.filamat")
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${output_path}
|
||||||
|
COMMAND matc ${MATC_FLAGS} -o ${output_path} ${fullname}
|
||||||
|
MAIN_DEPENDENCY ${mat_src}
|
||||||
|
DEPENDS matc
|
||||||
|
COMMENT "Compiling material ${mat_src} to ${output_path}"
|
||||||
|
)
|
||||||
|
list(APPEND RESOURCE_BINS ${output_path})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# Resources
|
||||||
|
file(MAKE_DIRECTORY ${RESOURCE_DIR})
|
||||||
|
|
||||||
|
get_resgen_vars(${RESOURCE_DIR} resources)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${RESGEN_OUTPUTS}
|
||||||
|
COMMAND resgen ${RESGEN_FLAGS} ${RESOURCE_BINS}
|
||||||
|
DEPENDS resgen ${RESOURCE_BINS}
|
||||||
|
COMMENT "Aggregating resources"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (DEFINED RESGEN_SOURCE_FLAGS)
|
||||||
|
set_source_files_properties(${RESGEN_SOURCE} PROPERTIES COMPILE_FLAGS ${RESGEN_SOURCE_FLAGS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(gltf-resources ${DUMMY_SRC})
|
||||||
|
set_target_properties(gltf-resources PROPERTIES FOLDER Samples/Resources)
|
||||||
|
|
||||||
|
set(GLTF_DEMO_RESOURCES
|
||||||
|
${ROOT_DIR}/third_party/models/DamagedHelmet/DamagedHelmet.glb
|
||||||
|
${MATERIAL_DIR}/groundShadow.filamat
|
||||||
|
${MATERIAL_DIR}/overdraw.filamat
|
||||||
|
)
|
||||||
|
|
||||||
|
get_resgen_vars(${RESOURCE_DIR} gltf_demo)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${RESGEN_OUTPUTS}
|
||||||
|
COMMAND resgen ${RESGEN_FLAGS} ${GLTF_DEMO_RESOURCES}
|
||||||
|
DEPENDS resgen ${GLTF_DEMO_RESOURCES}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (DEFINED RESGEN_SOURCE_FLAGS)
|
||||||
|
set_source_files_properties(${RESGEN_SOURCE} PROPERTIES COMPILE_FLAGS ${RESGEN_SOURCE_FLAGS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_sources(gltf-resources PRIVATE ${RESGEN_SOURCE})
|
||||||
|
|
||||||
|
|
||||||
|
# CMake fails to invoke ar on Windows unless there is at least one C/C++ file in the library.
|
||||||
|
set(DUMMY_SRC "${RESOURCE_DIR}/dummy.c")
|
||||||
|
add_custom_command(OUTPUT ${DUMMY_SRC} COMMAND echo "//" > ${DUMMY_SRC})
|
||||||
|
|
||||||
|
# Test binary
|
||||||
|
set(GLTF_COMPARISON_SOURCES
|
||||||
|
src/test_CompareGLTF.cpp
|
||||||
|
src/GLTFViewer.cpp
|
||||||
|
src/ImageExpectations.cpp
|
||||||
|
src/RunOnMain.cpp
|
||||||
|
src/CrossThreadTask.cpp
|
||||||
|
src/main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
file(COPY glTF_cases DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
|
||||||
|
file(COPY expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(gltf_comparison ${GLTF_COMPARISON_SOURCES})
|
||||||
|
target_include_directories(gltf_comparison PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
target_link_libraries(gltf_comparison
|
||||||
|
gtest
|
||||||
|
filamentapp
|
||||||
|
absl::str_format
|
||||||
|
uberarchive
|
||||||
|
gltf-resources
|
||||||
|
gltfio
|
||||||
|
viewer
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
include(GoogleTest)
|
||||||
|
gtest_discover_tests(gltf_comparison)
|
||||||
BIN
test/gltf-comparison/expected_images/GLTF_Box.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
test/gltf-comparison/expected_images/GLTF_BoxTextured.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
15
test/gltf-comparison/glTF_cases/Models/Box/LICENSE.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# LICENSE file for the model: Box
|
||||||
|
|
||||||
|
All files in this directory tree are licensed as indicated below.
|
||||||
|
|
||||||
|
* All files directly associated with the model including all text, image and binary files:
|
||||||
|
|
||||||
|
* [CC BY 4.0 International]("https://creativecommons.org/licenses/by/4.0/legalcode") [SPDX license identifier: "CC-BY-4.0"]
|
||||||
|
|
||||||
|
* This file and all other metadocumentation files including "metadata.json":
|
||||||
|
|
||||||
|
* [Creative Commons Attribtution 4.0 International]("https://creativecommons.org/licenses/by/4.0/legalcode") [SPDX license identifier: "CC-BY-4.0"]
|
||||||
|
|
||||||
|
Full license text of these licenses are available at the links above
|
||||||
|
|
||||||
|
#### Generated by modelmetadata
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
## Screenshot
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Simple cube model.
|
||||||
31
test/gltf-comparison/glTF_cases/Models/Box/README.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Box
|
||||||
|
|
||||||
|
## Tags
|
||||||
|
|
||||||
|
[core](../../Models-core.md), [testing](../../Models-testing.md)
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
One mesh and one material. Start with this.
|
||||||
|
|
||||||
|
## Operations
|
||||||
|
|
||||||
|
* [Display](https://github.khronos.org/glTF-Sample-Viewer-Release/?model=https://raw.GithubUserContent.com/KhronosGroup/glTF-Sample-Assets/main/./Models/Box/glTF-Binary/Box.glb) in SampleViewer
|
||||||
|
* [Download GLB](https://raw.GithubUserContent.com/KhronosGroup/glTF-Sample-Assets/main/./Models/Box/glTF-Binary/Box.glb)
|
||||||
|
* [Model Directory](./)
|
||||||
|
|
||||||
|
## Screenshot
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Simple cube model.
|
||||||
|
|
||||||
|
## Legal
|
||||||
|
|
||||||
|
© 2017, Cesium. [CC BY 4.0 International](https://creativecommons.org/licenses/by/4.0/legalcode)
|
||||||
|
|
||||||
|
- Cesium for Everything
|
||||||
|
|
||||||
|
#### Assembled by modelmetadata
|
||||||
BIN
test/gltf-comparison/glTF_cases/Models/Box/glTF-Binary/Box.glb
Normal file
BIN
test/gltf-comparison/glTF_cases/Models/Box/glTF-Draco/Box.bin
Normal file
152
test/gltf-comparison/glTF_cases/Models/Box/glTF-Draco/Box.gltf
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
{
|
||||||
|
"asset": {
|
||||||
|
"generator": "COLLADA2GLTF",
|
||||||
|
"version": "2.0"
|
||||||
|
},
|
||||||
|
"scene": 0,
|
||||||
|
"scenes": [
|
||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"matrix": [
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mesh": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meshes": [
|
||||||
|
{
|
||||||
|
"primitives": [
|
||||||
|
{
|
||||||
|
"attributes": {
|
||||||
|
"NORMAL": 1,
|
||||||
|
"POSITION": 2
|
||||||
|
},
|
||||||
|
"indices": 0,
|
||||||
|
"mode": 4,
|
||||||
|
"material": 0,
|
||||||
|
"extensions": {
|
||||||
|
"KHR_draco_mesh_compression": {
|
||||||
|
"bufferView": 0,
|
||||||
|
"attributes": {
|
||||||
|
"NORMAL": 0,
|
||||||
|
"POSITION": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Mesh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessors": [
|
||||||
|
{
|
||||||
|
"componentType": 5123,
|
||||||
|
"count": 36,
|
||||||
|
"max": [
|
||||||
|
23
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"type": "SCALAR"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentType": 5126,
|
||||||
|
"count": 24,
|
||||||
|
"max": [
|
||||||
|
1.007843137254902,
|
||||||
|
1.007843137254902,
|
||||||
|
1.007843137254902
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-1.007843137254902,
|
||||||
|
-1.007843137254902,
|
||||||
|
-1.007843137254902
|
||||||
|
],
|
||||||
|
"type": "VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentType": 5126,
|
||||||
|
"count": 24,
|
||||||
|
"max": [
|
||||||
|
0.5004885197850513,
|
||||||
|
0.5004885197850513,
|
||||||
|
0.5004885197850513
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-0.5004885197850513,
|
||||||
|
-0.5004885197850513,
|
||||||
|
-0.5004885197850513
|
||||||
|
],
|
||||||
|
"type": "VEC3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
{
|
||||||
|
"pbrMetallicRoughness": {
|
||||||
|
"baseColorFactor": [
|
||||||
|
0.800000011920929,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"metallicFactor": 0,
|
||||||
|
"roughnessFactor": 1
|
||||||
|
},
|
||||||
|
"name": "Red",
|
||||||
|
"emissiveFactor": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"alphaMode": "OPAQUE",
|
||||||
|
"doubleSided": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bufferViews": [
|
||||||
|
{
|
||||||
|
"buffer": 0,
|
||||||
|
"byteOffset": 0,
|
||||||
|
"byteLength": 118
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers": [
|
||||||
|
{
|
||||||
|
"name": "Box",
|
||||||
|
"byteLength": 120,
|
||||||
|
"uri": "Box.bin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extensionsRequired": [
|
||||||
|
"KHR_draco_mesh_compression"
|
||||||
|
],
|
||||||
|
"extensionsUsed": [
|
||||||
|
"KHR_draco_mesh_compression"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
{
|
||||||
|
"asset": {
|
||||||
|
"generator": "COLLADA2GLTF",
|
||||||
|
"version": "2.0"
|
||||||
|
},
|
||||||
|
"scene": 0,
|
||||||
|
"scenes": [
|
||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"matrix": [
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
-1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mesh": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meshes": [
|
||||||
|
{
|
||||||
|
"primitives": [
|
||||||
|
{
|
||||||
|
"attributes": {
|
||||||
|
"NORMAL": 1,
|
||||||
|
"POSITION": 2
|
||||||
|
},
|
||||||
|
"indices": 0,
|
||||||
|
"mode": 4,
|
||||||
|
"material": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Mesh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessors": [
|
||||||
|
{
|
||||||
|
"bufferView": 0,
|
||||||
|
"byteOffset": 0,
|
||||||
|
"componentType": 5123,
|
||||||
|
"count": 36,
|
||||||
|
"max": [
|
||||||
|
23
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"type": "SCALAR"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView": 1,
|
||||||
|
"byteOffset": 0,
|
||||||
|
"componentType": 5126,
|
||||||
|
"count": 24,
|
||||||
|
"max": [
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-1.0,
|
||||||
|
-1.0,
|
||||||
|
-1.0
|
||||||
|
],
|
||||||
|
"type": "VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView": 1,
|
||||||
|
"byteOffset": 288,
|
||||||
|
"componentType": 5126,
|
||||||
|
"count": 24,
|
||||||
|
"max": [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-0.5,
|
||||||
|
-0.5,
|
||||||
|
-0.5
|
||||||
|
],
|
||||||
|
"type": "VEC3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
{
|
||||||
|
"pbrMetallicRoughness": {
|
||||||
|
"baseColorFactor": [
|
||||||
|
0.800000011920929,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"metallicFactor": 0.0
|
||||||
|
},
|
||||||
|
"name": "Red"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bufferViews": [
|
||||||
|
{
|
||||||
|
"buffer": 0,
|
||||||
|
"byteOffset": 576,
|
||||||
|
"byteLength": 72,
|
||||||
|
"target": 34963
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer": 0,
|
||||||
|
"byteOffset": 0,
|
||||||
|
"byteLength": 576,
|
||||||
|
"byteStride": 12,
|
||||||
|
"target": 34962
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers": [
|
||||||
|
{
|
||||||
|
"byteLength": 648,
|
||||||
|
"uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAABAAIAAwACAAEABAAFAAYABwAGAAUACAAJAAoACwAKAAkADAANAA4ADwAOAA0AEAARABIAEwASABEAFAAVABYAFwAWABUA"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
142
test/gltf-comparison/glTF_cases/Models/Box/glTF/Box.gltf
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
{
|
||||||
|
"asset": {
|
||||||
|
"generator": "COLLADA2GLTF",
|
||||||
|
"version": "2.0"
|
||||||
|
},
|
||||||
|
"scene": 0,
|
||||||
|
"scenes": [
|
||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"matrix": [
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
-1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mesh": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meshes": [
|
||||||
|
{
|
||||||
|
"primitives": [
|
||||||
|
{
|
||||||
|
"attributes": {
|
||||||
|
"NORMAL": 1,
|
||||||
|
"POSITION": 2
|
||||||
|
},
|
||||||
|
"indices": 0,
|
||||||
|
"mode": 4,
|
||||||
|
"material": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Mesh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessors": [
|
||||||
|
{
|
||||||
|
"bufferView": 0,
|
||||||
|
"byteOffset": 0,
|
||||||
|
"componentType": 5123,
|
||||||
|
"count": 36,
|
||||||
|
"max": [
|
||||||
|
23
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"type": "SCALAR"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView": 1,
|
||||||
|
"byteOffset": 0,
|
||||||
|
"componentType": 5126,
|
||||||
|
"count": 24,
|
||||||
|
"max": [
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-1.0,
|
||||||
|
-1.0,
|
||||||
|
-1.0
|
||||||
|
],
|
||||||
|
"type": "VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView": 1,
|
||||||
|
"byteOffset": 288,
|
||||||
|
"componentType": 5126,
|
||||||
|
"count": 24,
|
||||||
|
"max": [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-0.5,
|
||||||
|
-0.5,
|
||||||
|
-0.5
|
||||||
|
],
|
||||||
|
"type": "VEC3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
{
|
||||||
|
"pbrMetallicRoughness": {
|
||||||
|
"baseColorFactor": [
|
||||||
|
0.800000011920929,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"metallicFactor": 0.0
|
||||||
|
},
|
||||||
|
"name": "Red"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bufferViews": [
|
||||||
|
{
|
||||||
|
"buffer": 0,
|
||||||
|
"byteOffset": 576,
|
||||||
|
"byteLength": 72,
|
||||||
|
"target": 34963
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer": 0,
|
||||||
|
"byteOffset": 0,
|
||||||
|
"byteLength": 576,
|
||||||
|
"byteStride": 12,
|
||||||
|
"target": 34962
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers": [
|
||||||
|
{
|
||||||
|
"byteLength": 648,
|
||||||
|
"uri": "Box0.bin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
test/gltf-comparison/glTF_cases/Models/Box/glTF/Box0.bin
Normal file
25
test/gltf-comparison/glTF_cases/Models/Box/metadata.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"legal": [
|
||||||
|
{
|
||||||
|
"license": "CC-BY 4.0",
|
||||||
|
"licenseUrl": "https://creativecommons.org/licenses/by/4.0/legalcode",
|
||||||
|
"artist": "Cesium",
|
||||||
|
"year": "2017",
|
||||||
|
"owner": "Cesium",
|
||||||
|
"what": "Everything",
|
||||||
|
"text": "CC BY 4.0 International",
|
||||||
|
"spdx": "CC-BY-4.0",
|
||||||
|
"icon": "https://licensebuttons.net/l/by/3.0/88x31.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"core",
|
||||||
|
"testing"
|
||||||
|
],
|
||||||
|
"screenshot": "screenshot/screenshot.png",
|
||||||
|
"name": "Box",
|
||||||
|
"path": "./Models/Box",
|
||||||
|
"summary": "One mesh and one material. Start with this.",
|
||||||
|
"createReadme": true
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,17 @@
|
|||||||
|
# LICENSE file for the model: Box Textured
|
||||||
|
|
||||||
|
All files in this directory tree are licensed as indicated below.
|
||||||
|
|
||||||
|
* All files directly associated with the model including all text, image and binary files:
|
||||||
|
|
||||||
|
* [CC-BY 4.0 International with Trademark Limitations]("") [SPDX license identifier: "LicenseRef-CC-BY-TM"]
|
||||||
|
|
||||||
|
* [Cesium Trademark or Logo]("") [SPDX license identifier: "LicenseRef-LegalMark-Cesium"]
|
||||||
|
|
||||||
|
* This file and all other metadocumentation files including "metadata.json":
|
||||||
|
|
||||||
|
* [Creative Commons Attribtution 4.0 International]("https://creativecommons.org/licenses/by/4.0/legalcode") [SPDX license identifier: "CC-BY-4.0"]
|
||||||
|
|
||||||
|
Full license text of these licenses are available at the links above
|
||||||
|
|
||||||
|
#### Generated by modelmetadata
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
## Screenshot
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Box with a power-of-2 texture, using the Cesium logo.
|
||||||
35
test/gltf-comparison/glTF_cases/Models/BoxTextured/README.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Box Textured
|
||||||
|
|
||||||
|
## Tags
|
||||||
|
|
||||||
|
[core](../../Models-core.md), [issues](../../Models-issues.md), [testing](../../Models-testing.md)
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Box with a power-of-2 texture. [Issues: non-Khronos mark]
|
||||||
|
|
||||||
|
## Operations
|
||||||
|
|
||||||
|
* [Display](https://github.khronos.org/glTF-Sample-Viewer-Release/?model=https://raw.GithubUserContent.com/KhronosGroup/glTF-Sample-Assets/main/./Models/BoxTextured/glTF-Binary/BoxTextured.glb) in SampleViewer
|
||||||
|
* [Download GLB](https://raw.GithubUserContent.com/KhronosGroup/glTF-Sample-Assets/main/./Models/BoxTextured/glTF-Binary/BoxTextured.glb)
|
||||||
|
* [Model Directory](./)
|
||||||
|
|
||||||
|
## Screenshot
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Box with a power-of-2 texture, using the Cesium logo.
|
||||||
|
|
||||||
|
## Legal
|
||||||
|
|
||||||
|
© 2017, Cesium. [CC-BY 4.0 International with Trademark Limitations]()
|
||||||
|
|
||||||
|
- Cesium for Everything
|
||||||
|
|
||||||
|
© 2015, Cesium. [Cesium Trademark or Logo]()
|
||||||
|
|
||||||
|
- Non-copyrightable logo for Cesium logo
|
||||||
|
|
||||||
|
#### Assembled by modelmetadata
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
{
|
||||||
|
"asset": {
|
||||||
|
"generator": "COLLADA2GLTF",
|
||||||
|
"version": "2.0"
|
||||||
|
},
|
||||||
|
"scene": 0,
|
||||||
|
"scenes": [
|
||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"matrix": [
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
-1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mesh": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meshes": [
|
||||||
|
{
|
||||||
|
"primitives": [
|
||||||
|
{
|
||||||
|
"attributes": {
|
||||||
|
"NORMAL": 1,
|
||||||
|
"POSITION": 2,
|
||||||
|
"TEXCOORD_0": 3
|
||||||
|
},
|
||||||
|
"indices": 0,
|
||||||
|
"mode": 4,
|
||||||
|
"material": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Mesh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessors": [
|
||||||
|
{
|
||||||
|
"bufferView": 0,
|
||||||
|
"byteOffset": 0,
|
||||||
|
"componentType": 5123,
|
||||||
|
"count": 36,
|
||||||
|
"max": [
|
||||||
|
23
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"type": "SCALAR"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView": 1,
|
||||||
|
"byteOffset": 0,
|
||||||
|
"componentType": 5126,
|
||||||
|
"count": 24,
|
||||||
|
"max": [
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-1.0,
|
||||||
|
-1.0,
|
||||||
|
-1.0
|
||||||
|
],
|
||||||
|
"type": "VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView": 1,
|
||||||
|
"byteOffset": 288,
|
||||||
|
"componentType": 5126,
|
||||||
|
"count": 24,
|
||||||
|
"max": [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-0.5,
|
||||||
|
-0.5,
|
||||||
|
-0.5
|
||||||
|
],
|
||||||
|
"type": "VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView": 2,
|
||||||
|
"byteOffset": 0,
|
||||||
|
"componentType": 5126,
|
||||||
|
"count": 24,
|
||||||
|
"max": [
|
||||||
|
6.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"type": "VEC2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
{
|
||||||
|
"pbrMetallicRoughness": {
|
||||||
|
"baseColorTexture": {
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"metallicFactor": 0.0
|
||||||
|
},
|
||||||
|
"name": "Texture"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"textures": [
|
||||||
|
{
|
||||||
|
"sampler": 0,
|
||||||
|
"source": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"uri": "CesiumLogoFlat.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"samplers": [
|
||||||
|
{
|
||||||
|
"magFilter": 9729,
|
||||||
|
"minFilter": 9986,
|
||||||
|
"wrapS": 10497,
|
||||||
|
"wrapT": 10497
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bufferViews": [
|
||||||
|
{
|
||||||
|
"buffer": 0,
|
||||||
|
"byteOffset": 768,
|
||||||
|
"byteLength": 72,
|
||||||
|
"target": 34963
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer": 0,
|
||||||
|
"byteOffset": 0,
|
||||||
|
"byteLength": 576,
|
||||||
|
"byteStride": 12,
|
||||||
|
"target": 34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer": 0,
|
||||||
|
"byteOffset": 576,
|
||||||
|
"byteLength": 192,
|
||||||
|
"byteStride": 8,
|
||||||
|
"target": 34962
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers": [
|
||||||
|
{
|
||||||
|
"byteLength": 840,
|
||||||
|
"uri": "BoxTextured0.bin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"legal": [
|
||||||
|
{
|
||||||
|
"license": "LicenseRef-CC-BY-TM",
|
||||||
|
"licenseUrl": "",
|
||||||
|
"artist": "Cesium",
|
||||||
|
"year": "2017",
|
||||||
|
"owner": "Cesium",
|
||||||
|
"what": "Everything",
|
||||||
|
"text": "CC-BY 4.0 International with Trademark Limitations",
|
||||||
|
"spdx": "LicenseRef-CC-BY-TM",
|
||||||
|
"icon": "https://licensebuttons.net/l/by/3.0/88x31.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"license": "LicenseRef-LegalMark-Cesium",
|
||||||
|
"licenseUrl": "",
|
||||||
|
"artist": "Non-copyrightable logo",
|
||||||
|
"year": "2015",
|
||||||
|
"owner": "Cesium",
|
||||||
|
"what": "Cesium logo",
|
||||||
|
"text": "Cesium Trademark or Logo",
|
||||||
|
"spdx": "LicenseRef-LegalMark-Cesium",
|
||||||
|
"icon": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"core",
|
||||||
|
"issues",
|
||||||
|
"testing"
|
||||||
|
],
|
||||||
|
"screenshot": "screenshot/screenshot.png",
|
||||||
|
"name": "Box Textured",
|
||||||
|
"path": "./Models/BoxTextured",
|
||||||
|
"summary": "Box with a power-of-2 texture. [Issues: non-Khronos mark]",
|
||||||
|
"createReadme": true
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
3
test/gltf-comparison/glTF_cases/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Files here are copied directly from https://github.com/KhronosGroup/glTF-Sample-Assets
|
||||||
|
|
||||||
|
Each glTF model includes its own license and attribution in LICENSE.md and metadata.json.
|
||||||
71
test/gltf-comparison/include/CrossThreadTask.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* 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_CROSSTHREADTASK_H
|
||||||
|
#define TNT_CROSSTHREADTASK_H
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
template <typename T, typename... Args>
|
||||||
|
class CrossThreadTask {
|
||||||
|
public:
|
||||||
|
bool queueTask(std::function<T> task);
|
||||||
|
void runTask(Args...);
|
||||||
|
bool isClosed() const;
|
||||||
|
void setClosed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex mMutex;
|
||||||
|
std::optional<std::function<T>> mTask;
|
||||||
|
std::atomic<bool> mClosed = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename... Args>
|
||||||
|
bool CrossThreadTask<T, Args...>::queueTask(std::function<T> task) {
|
||||||
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
|
if (mTask.has_value()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mTask = task;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename... Args>
|
||||||
|
void CrossThreadTask<T, Args...>::runTask(Args... args) {
|
||||||
|
std::optional<std::function<T>> taskToRun;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
|
std::swap(mTask, taskToRun);
|
||||||
|
}
|
||||||
|
if (taskToRun.has_value()) {
|
||||||
|
(*taskToRun)(args...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename... Args>
|
||||||
|
bool CrossThreadTask<T, Args...>::isClosed() const {
|
||||||
|
return mClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename... Args>
|
||||||
|
void CrossThreadTask<T, Args...>::setClosed() {
|
||||||
|
mClosed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // TNT_CROSSTHREADTASK_H
|
||||||
88
test/gltf-comparison/include/GLTFViewer.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* 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_GLTFVIEWER_H
|
||||||
|
#define TNT_GLTFVIEWER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <filamentapp/Config.h>
|
||||||
|
#include <filamentapp/FilamentApp.h>
|
||||||
|
#include <filamentapp/IBL.h>
|
||||||
|
|
||||||
|
#include <filament/Camera.h>
|
||||||
|
#include <filament/Engine.h>
|
||||||
|
#include <filament/IndexBuffer.h>
|
||||||
|
#include <filament/Material.h>
|
||||||
|
#include <filament/MaterialInstance.h>
|
||||||
|
#include <filament/RenderableManager.h>
|
||||||
|
#include <filament/Scene.h>
|
||||||
|
#include <filament/Skybox.h>
|
||||||
|
#include <filament/TransformManager.h>
|
||||||
|
#include <filament/VertexBuffer.h>
|
||||||
|
#include <filament/View.h>
|
||||||
|
|
||||||
|
#include <gltfio/AssetLoader.h>
|
||||||
|
#include <gltfio/FilamentAsset.h>
|
||||||
|
#include <gltfio/ResourceLoader.h>
|
||||||
|
#include <gltfio/TextureProvider.h>
|
||||||
|
|
||||||
|
#include <viewer/ViewerGui.h>
|
||||||
|
|
||||||
|
#include <utils/EntityManager.h>
|
||||||
|
|
||||||
|
class GLTFViewer {
|
||||||
|
public:
|
||||||
|
void setFilename(std::string fileName);
|
||||||
|
|
||||||
|
void runApp(std::function<void(filament::Engine*, filament::View*, filament::Scene*,
|
||||||
|
filament::Renderer*)>
|
||||||
|
postRender);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setup(filament::Engine* engine, filament::View* view, filament::Scene* scene);
|
||||||
|
void cleanup(filament::Engine* engine, filament::View* view, filament::Scene* scene);
|
||||||
|
|
||||||
|
void animate(filament::Engine* engine, filament::View* view, double now);
|
||||||
|
|
||||||
|
void loadAsset(utils::Path filename);
|
||||||
|
void loadResources(utils::Path filename);
|
||||||
|
|
||||||
|
enum MaterialSource {
|
||||||
|
JITSHADER,
|
||||||
|
UBERSHADER,
|
||||||
|
};
|
||||||
|
struct App {
|
||||||
|
filament::Engine* engine;
|
||||||
|
filament::viewer::ViewerGui* viewer;
|
||||||
|
Config config;
|
||||||
|
filament::gltfio::AssetLoader* loader;
|
||||||
|
filament::gltfio::FilamentAsset* asset = nullptr;
|
||||||
|
utils::NameComponentManager* names;
|
||||||
|
filament::gltfio::MaterialProvider* materials;
|
||||||
|
MaterialSource materialSource = UBERSHADER;
|
||||||
|
filament::gltfio::ResourceLoader* resourceLoader = nullptr;
|
||||||
|
filament::gltfio::TextureProvider* stbDecoder = nullptr;
|
||||||
|
filament::gltfio::TextureProvider* ktxDecoder = nullptr;
|
||||||
|
filament::gltfio::FilamentInstance* instance;
|
||||||
|
bool enableMSAA = false;
|
||||||
|
};
|
||||||
|
App mApp;
|
||||||
|
|
||||||
|
std::string mFilename;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TNT_GLTFVIEWER_H
|
||||||
169
test/gltf-comparison/include/ImageExpectations.h
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TNT_IMAGE_EXPECTATIONS_H
|
||||||
|
#define TNT_IMAGE_EXPECTATIONS_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <backend/PixelBufferDescriptor.h>
|
||||||
|
#include <filament/Renderer.h>
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
// Arguments are (Renderer* renderer, ImageExpectations& expectations,
|
||||||
|
// ScreenshotParams screenshotParams)
|
||||||
|
#define EXPECT_IMAGE(renderer, expectations, screenshotParams) \
|
||||||
|
do { \
|
||||||
|
expectations.addExpectation( \
|
||||||
|
__FILE__, \
|
||||||
|
__LINE__, \
|
||||||
|
renderer, \
|
||||||
|
screenshotParams); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores user-provided configuration values for an image expectation
|
||||||
|
*/
|
||||||
|
class ScreenshotParams {
|
||||||
|
public:
|
||||||
|
// TODO(b/422804941): Add a set of environments where this test should use a different golden.
|
||||||
|
ScreenshotParams(int width, int height, std::string fileName,
|
||||||
|
bool isSrgb = false);
|
||||||
|
|
||||||
|
int width() const;
|
||||||
|
int height() const;
|
||||||
|
bool isSrgb() const;
|
||||||
|
|
||||||
|
static std::string actualDirectoryPath();
|
||||||
|
std::string actualFileName() const;
|
||||||
|
std::string actualFilePath() const;
|
||||||
|
static std::string expectedDirectoryPath();
|
||||||
|
std::string expectedFileName() const;
|
||||||
|
std::string expectedFilePath() const;
|
||||||
|
const std::string filePrefix() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int mWidth;
|
||||||
|
int mHeight;
|
||||||
|
bool mIsSrgb;
|
||||||
|
std::string mFileName;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When created adds a command to the GPU pipeline to copy the render target into a buffer and
|
||||||
|
* stores the result.
|
||||||
|
* If this object is destroyed before the GPU pipeline is flushed it will leak memory in order to
|
||||||
|
* avoid the GPU pipeline callback being a use-after-free.
|
||||||
|
*/
|
||||||
|
class RenderTargetDump {
|
||||||
|
public:
|
||||||
|
using ReadPixels = std::function<void(uint32_t, uint32_t, uint32_t, uint32_t,
|
||||||
|
filament::backend::PixelBufferDescriptor)>;
|
||||||
|
static ReadPixels readFromRenderer(filament::Renderer* renderer);
|
||||||
|
|
||||||
|
RenderTargetDump(const ReadPixels& readPixels, const ScreenshotParams& params);
|
||||||
|
RenderTargetDump(RenderTargetDump&& other) = default;
|
||||||
|
RenderTargetDump& operator=(RenderTargetDump&& other) = default;
|
||||||
|
~RenderTargetDump();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should only bue used if BytesFilled returns true.
|
||||||
|
* @return The hash of the stored bytes.
|
||||||
|
*/
|
||||||
|
uint32_t hash() const;
|
||||||
|
/**
|
||||||
|
* Gets the bytes of the render target. The hash should usually be preferable for comparisons
|
||||||
|
* but this is available for debugging.
|
||||||
|
* @return The stored bytes.
|
||||||
|
*/
|
||||||
|
const std::vector<unsigned char>& bytes() const;
|
||||||
|
/**
|
||||||
|
* Thread safe as this is backed by an atomic.
|
||||||
|
* Once this returns true it will never return false.
|
||||||
|
* @return Whether the bytes have actually been copied from the GPU to the buffer.
|
||||||
|
*/
|
||||||
|
bool bytesFilled() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Internal {
|
||||||
|
explicit Internal(const ScreenshotParams& params);
|
||||||
|
ScreenshotParams params;
|
||||||
|
std::atomic<bool> bytesFilled = false;
|
||||||
|
std::vector<unsigned char> bytes;
|
||||||
|
|
||||||
|
uint32_t hash() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// We need a memory location that won't be invalidated to pass to GPU callbacks as they can't
|
||||||
|
// be canceled during the destructor.
|
||||||
|
std::unique_ptr<Internal> mInternal;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LoadedPng {
|
||||||
|
public:
|
||||||
|
explicit LoadedPng(std::string filePath);
|
||||||
|
|
||||||
|
uint32_t hash() const;
|
||||||
|
|
||||||
|
const std::vector<unsigned char>& bytes() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string mFilePath;
|
||||||
|
std::vector<unsigned char> mBytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageExpectation {
|
||||||
|
public:
|
||||||
|
ImageExpectation(const char* fileName, int lineNumber, RenderTargetDump::ReadPixels readPixels,
|
||||||
|
ScreenshotParams params);
|
||||||
|
|
||||||
|
void evaluate();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void compareImage() const;
|
||||||
|
|
||||||
|
bool mEvaluated = false;
|
||||||
|
const char* mFileName;
|
||||||
|
int mLineNumber;
|
||||||
|
ScreenshotParams mParams;
|
||||||
|
RenderTargetDump mResult;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageExpectations {
|
||||||
|
public:
|
||||||
|
explicit ImageExpectations() = default;
|
||||||
|
~ImageExpectations();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not meant to be called directly, use EXPECT_IMAGE to get the file name and line number
|
||||||
|
*/
|
||||||
|
void addExpectation(const char* fileName, int lineNumber,
|
||||||
|
filament::Renderer* renderer, ScreenshotParams params);
|
||||||
|
|
||||||
|
void evaluate();
|
||||||
|
|
||||||
|
static void markImageAsFailure(const std::string& imagePrefix);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Store expectations in unique pointers because they are self referential.
|
||||||
|
std::vector<std::unique_ptr<ImageExpectation>> mExpectations;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
|
||||||
|
#endif //TNT_IMAGE_EXPECTATIONS_H
|
||||||
30
test/gltf-comparison/include/RunOnMain.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* 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_RUNONMAIN_H
|
||||||
|
#define TNT_RUNONMAIN_H
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "CrossThreadTask.h"
|
||||||
|
|
||||||
|
class RunOnMain {
|
||||||
|
public:
|
||||||
|
static CrossThreadTask<void()> sTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TNT_RUNONMAIN_H
|
||||||
17
test/gltf-comparison/src/CrossThreadTask.cpp
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* 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 "CrossThreadTask.h"
|
||||||
176
test/gltf-comparison/src/GLTFViewer.cpp
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
* 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 "GLTFViewer.h"
|
||||||
|
|
||||||
|
#include <filamentapp/Config.h>
|
||||||
|
#include <filamentapp/FilamentApp.h>
|
||||||
|
#include <filamentapp/IBL.h>
|
||||||
|
|
||||||
|
#include <filament/Camera.h>
|
||||||
|
#include <filament/Engine.h>
|
||||||
|
#include <filament/IndexBuffer.h>
|
||||||
|
#include <filament/Material.h>
|
||||||
|
#include <filament/MaterialInstance.h>
|
||||||
|
#include <filament/RenderableManager.h>
|
||||||
|
#include <filament/Scene.h>
|
||||||
|
#include <filament/Skybox.h>
|
||||||
|
#include <filament/TransformManager.h>
|
||||||
|
#include <filament/VertexBuffer.h>
|
||||||
|
#include <filament/View.h>
|
||||||
|
|
||||||
|
#include <utils/EntityManager.h>
|
||||||
|
#include <utils/NameComponentManager.h>
|
||||||
|
|
||||||
|
#include <gltfio/TextureProvider.h>
|
||||||
|
|
||||||
|
//#include "generated/resources/gltf.h"
|
||||||
|
#include "materials/uberarchive.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
using namespace filament;
|
||||||
|
using utils::Entity;
|
||||||
|
using utils::EntityManager;
|
||||||
|
|
||||||
|
|
||||||
|
void GLTFViewer::runApp(std::function<void(filament::Engine*, filament::View*, filament::Scene*,
|
||||||
|
filament::Renderer*)> postRender) {
|
||||||
|
FilamentApp::get().animate(
|
||||||
|
[this](Engine* engine, View* view, double now) { animate(engine, view, now); });
|
||||||
|
|
||||||
|
// TODO: Remove
|
||||||
|
mApp.config.backend = filament::Engine::Backend::METAL;
|
||||||
|
|
||||||
|
FilamentApp::get().run(
|
||||||
|
mApp.config,
|
||||||
|
[this](Engine* engine, View* view, Scene* scene) { setup(engine, view, scene); },
|
||||||
|
[this](Engine* engine, View* view, Scene* scene) { cleanup(engine, view, scene); },
|
||||||
|
FilamentApp::ImGuiCallback(), FilamentApp::PreRenderCallback(), std::move(postRender));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLTFViewer::setup(Engine* engine, filament::View* view, filament::Scene* scene) {
|
||||||
|
if (mApp.enableMSAA) {
|
||||||
|
// TODO Investigate why
|
||||||
|
// Enabling MSAA fixes the "transmission" sample but breaks the others
|
||||||
|
view->setMultiSampleAntiAliasingOptions({ .enabled = true});
|
||||||
|
}
|
||||||
|
mApp.engine = engine;
|
||||||
|
mApp.names = new utils::NameComponentManager(EntityManager::get());
|
||||||
|
mApp.viewer = new viewer::ViewerGui(engine, scene, view);
|
||||||
|
|
||||||
|
// TODO: Support JIT
|
||||||
|
mApp.materials = //(mApp.materialSource == JITSHADER)
|
||||||
|
//? createJitShaderProvider(engine, false /* optimize */,
|
||||||
|
// samples::getJitMaterialVariantFilter(mApp.config.backend))
|
||||||
|
//:
|
||||||
|
filament::gltfio::createUbershaderProvider(engine, UBERARCHIVE_DEFAULT_DATA,
|
||||||
|
UBERARCHIVE_DEFAULT_SIZE);
|
||||||
|
|
||||||
|
mApp.loader = gltfio::AssetLoader::create({ engine, mApp.materials, mApp.names });
|
||||||
|
|
||||||
|
loadAsset(mFilename);
|
||||||
|
loadResources(mFilename);
|
||||||
|
mApp.viewer->setAsset(mApp.asset, mApp.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLTFViewer::cleanup(Engine* engine, filament::View* view, filament::Scene* scene) {
|
||||||
|
mApp.loader->destroyAsset(mApp.asset);
|
||||||
|
mApp.materials->destroyMaterials();
|
||||||
|
|
||||||
|
delete mApp.materials;
|
||||||
|
delete mApp.names;
|
||||||
|
delete mApp.viewer;
|
||||||
|
delete mApp.resourceLoader;
|
||||||
|
delete mApp.stbDecoder;
|
||||||
|
delete mApp.ktxDecoder;
|
||||||
|
|
||||||
|
gltfio::AssetLoader::destroy(&mApp.loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLTFViewer::animate(filament::Engine* engine, filament::View* view, double now) {
|
||||||
|
mApp.resourceLoader->asyncUpdateLoad();
|
||||||
|
|
||||||
|
mApp.viewer->updateRootTransform();
|
||||||
|
mApp.viewer->populateScene();
|
||||||
|
|
||||||
|
mApp.viewer->applyAnimation(now, mApp.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLTFViewer::setFilename(std::string filename) {
|
||||||
|
mFilename = std::move(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::ifstream::pos_type getFileSize(const char* filename) {
|
||||||
|
std::ifstream in(filename, std::ifstream::ate | std::ifstream::binary);
|
||||||
|
return in.tellg();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLTFViewer::loadAsset(utils::Path filename) {
|
||||||
|
// Peek at the file size to allow pre-allocation.
|
||||||
|
long contentSize = static_cast<long>(getFileSize(filename.c_str()));
|
||||||
|
if (contentSize <= 0) {
|
||||||
|
std::cerr << "Unable to open " << filename << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume the glTF file.
|
||||||
|
std::ifstream in(filename.c_str(), std::ifstream::binary | std::ifstream::in);
|
||||||
|
std::vector<uint8_t> buffer(static_cast<unsigned long>(contentSize));
|
||||||
|
if (!in.read((char*) buffer.data(), contentSize)) {
|
||||||
|
std::cerr << "Unable to read " << filename << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the glTF file and create Filament entities.
|
||||||
|
mApp.asset = mApp.loader->createInstancedAsset(buffer.data(), buffer.size(),
|
||||||
|
&(mApp.instance), 1);
|
||||||
|
buffer.clear();
|
||||||
|
buffer.shrink_to_fit();
|
||||||
|
|
||||||
|
if (!mApp.asset) {
|
||||||
|
std::cerr << "Unable to parse " << filename << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLTFViewer::loadResources(utils::Path filename) {
|
||||||
|
// Load external textures and buffers.
|
||||||
|
std::string gltfPath = filename.getAbsolutePath();
|
||||||
|
gltfio::ResourceConfiguration configuration;
|
||||||
|
configuration.engine = mApp.engine;
|
||||||
|
configuration.gltfPath = gltfPath.c_str();
|
||||||
|
configuration.normalizeSkinningWeights = true;
|
||||||
|
if (!mApp.resourceLoader) {
|
||||||
|
mApp.resourceLoader = new gltfio::ResourceLoader(configuration);
|
||||||
|
mApp.stbDecoder = gltfio::createStbProvider(mApp.engine);
|
||||||
|
mApp.ktxDecoder = gltfio::createKtx2Provider(mApp.engine);
|
||||||
|
mApp.resourceLoader->addTextureProvider("image/png", mApp.stbDecoder);
|
||||||
|
mApp.resourceLoader->addTextureProvider("image/jpeg", mApp.stbDecoder);
|
||||||
|
mApp.resourceLoader->addTextureProvider("image/ktx2", mApp.ktxDecoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mApp.resourceLoader->asyncBeginLoad(mApp.asset)) {
|
||||||
|
std::cerr << "Unable to start loading resources for " << filename << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ibl = FilamentApp::get().getIBL();
|
||||||
|
if (ibl) {
|
||||||
|
mApp.viewer->setIndirectLight(ibl->getIndirectLight(), ibl->getSphericalHarmonics());
|
||||||
|
}
|
||||||
|
}
|
||||||
248
test/gltf-comparison/src/ImageExpectations.cpp
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ImageExpectations.h"
|
||||||
|
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "utils/Hash.h"
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include <imageio/ImageEncoder.h>
|
||||||
|
#include <imageio/ImageDecoder.h>
|
||||||
|
#include <image/ColorTransform.h>
|
||||||
|
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
ScreenshotParams::ScreenshotParams(int width, int height, std::string fileName, bool isSrgb)
|
||||||
|
: mWidth(width),
|
||||||
|
mHeight(height),
|
||||||
|
mIsSrgb(isSrgb),
|
||||||
|
mFileName(std::move(fileName)) {}
|
||||||
|
|
||||||
|
int ScreenshotParams::width() const {
|
||||||
|
return mWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenshotParams::height() const {
|
||||||
|
return mHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScreenshotParams::isSrgb() const {
|
||||||
|
return mIsSrgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ScreenshotParams::actualDirectoryPath() {
|
||||||
|
return "images/actual_images";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ScreenshotParams::actualFileName() const {
|
||||||
|
return absl::StrFormat("%s_actual.png", mFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ScreenshotParams::actualFilePath() const {
|
||||||
|
return absl::StrFormat("%s/%s", actualDirectoryPath(), actualFileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ScreenshotParams::expectedDirectoryPath() {
|
||||||
|
return "images/expected_images";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ScreenshotParams::expectedFileName() const {
|
||||||
|
return absl::StrFormat("%s.png", mFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ScreenshotParams::expectedFilePath() const {
|
||||||
|
return absl::StrFormat("%s/%s", expectedDirectoryPath(), expectedFileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string ScreenshotParams::filePrefix() const {
|
||||||
|
// TODO(b/422804941): If there are platform specific goldens, when on those platforms append a
|
||||||
|
// unique platform identifying string to this.
|
||||||
|
return mFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageExpectation::ImageExpectation(const char* fileName, int lineNumber,
|
||||||
|
RenderTargetDump::ReadPixels readPixels, ScreenshotParams params)
|
||||||
|
: mFileName(fileName),
|
||||||
|
mLineNumber(lineNumber),
|
||||||
|
mParams(std::move(params)),
|
||||||
|
mResult(readPixels, mParams) {}
|
||||||
|
|
||||||
|
void ImageExpectation::evaluate() {
|
||||||
|
// Ensure this is only evaluated once.
|
||||||
|
if (mEvaluated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mEvaluated = true;
|
||||||
|
|
||||||
|
// Do the actual image comparison inside a scoped trace with the stored file and line.
|
||||||
|
{
|
||||||
|
testing::ScopedTrace trace(mFileName, mLineNumber, "");
|
||||||
|
compareImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageExpectation::compareImage() const {
|
||||||
|
bool bytesFilled = mResult.bytesFilled();
|
||||||
|
// If this fails, it likely means that BackendTest::flushAndWait needs to be called before
|
||||||
|
// ImageExpectations is evaluated or destroyed.
|
||||||
|
EXPECT_THAT(bytesFilled, testing::IsTrue())
|
||||||
|
<< "Render target wasn't copied to the buffer for " << mFileName;
|
||||||
|
if (bytesFilled) {
|
||||||
|
// Rather than directly compare the two images compare their hashes because comparing very
|
||||||
|
// large arrays generates way too much debug output to be useful.
|
||||||
|
uint32_t actualHash = mResult.hash();
|
||||||
|
#ifndef FILAMENT_IOS
|
||||||
|
LoadedPng loadedImage(mParams.expectedFilePath());
|
||||||
|
uint32_t loadedImageHash = loadedImage.hash();
|
||||||
|
auto compareToImageMatcher = testing::Eq(loadedImageHash);
|
||||||
|
if (!testing::Matches(compareToImageMatcher)(actualHash)) {
|
||||||
|
ImageExpectations::markImageAsFailure(mParams.filePrefix());
|
||||||
|
}
|
||||||
|
EXPECT_THAT(actualHash, compareToImageMatcher) << mParams.expectedFileName();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageExpectations::~ImageExpectations() {
|
||||||
|
// Guarantee that all expectations are evaluated when this leaves scope even if the caller
|
||||||
|
// forgot to manually evaluate them.
|
||||||
|
evaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageExpectations::addExpectation(const char* fileName, int lineNumber,
|
||||||
|
filament::Renderer* renderer, ScreenshotParams params) {
|
||||||
|
mExpectations.emplace_back(std::make_unique<ImageExpectation>(
|
||||||
|
fileName, lineNumber,
|
||||||
|
[renderer](uint32_t x, uint32_t y, uint32_t width, uint32_t height,
|
||||||
|
filament::backend::PixelBufferDescriptor&& pb) {
|
||||||
|
renderer->readPixels(x, y, width, height, std::move(pb));
|
||||||
|
},
|
||||||
|
std::move(params)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageExpectations::evaluate() {
|
||||||
|
for (auto& expectation: mExpectations) {
|
||||||
|
expectation->evaluate();
|
||||||
|
}
|
||||||
|
mExpectations.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageExpectations::markImageAsFailure(const std::string& imagePrefix) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderTargetDump::RenderTargetDump(const RenderTargetDump::ReadPixels& readPixels,
|
||||||
|
const ScreenshotParams& params)
|
||||||
|
: mInternal(std::make_unique<RenderTargetDump::Internal>(params)) {
|
||||||
|
const size_t size = mInternal->params.width() * mInternal->params.height() * 4;
|
||||||
|
mInternal->bytes.resize(size);
|
||||||
|
|
||||||
|
auto cb = [](void* buffer, size_t size, void* user) {
|
||||||
|
auto* internal = static_cast<RenderTargetDump::Internal*>(user);
|
||||||
|
internal->bytesFilled = true;
|
||||||
|
#ifndef FILAMENT_IOS
|
||||||
|
image::LinearImage image;
|
||||||
|
if (internal->params.isSrgb()) {
|
||||||
|
image = image::toLinearWithAlpha<uint8_t>(internal->params.width(),
|
||||||
|
internal->params.height(),
|
||||||
|
internal->params.width() * 4, (uint8_t*)buffer);
|
||||||
|
} else {
|
||||||
|
// The image data is already linear, so pass in transforms that simply go from uint8_t
|
||||||
|
// to float. toLinearWithAlpha divides the float values by uint8_t max so there's no
|
||||||
|
// need to scale it to [0, 1]
|
||||||
|
image = image::toLinearWithAlpha<uint8_t>(
|
||||||
|
internal->params.width(), internal->params.height(),
|
||||||
|
internal->params.width() * 4, (uint8_t*) buffer,
|
||||||
|
[](uint8_t value) -> float { return value; },
|
||||||
|
[](filament::math::float4 rgba) -> filament::math::float4 { return rgba; });
|
||||||
|
}
|
||||||
|
std::string filePath = internal->params.actualFilePath();
|
||||||
|
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
|
||||||
|
// To avoid going from linear -> sRGB -> linear save the PNG as linear.
|
||||||
|
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG_LINEAR, image, "",
|
||||||
|
filePath);
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
filament::backend::PixelBufferDescriptor pb(mInternal->bytes.data(), size,
|
||||||
|
filament::backend::PixelDataFormat::RGBA, filament::backend::PixelDataType::UBYTE, cb,
|
||||||
|
(void*)mInternal.get());
|
||||||
|
readPixels(0, 0, mInternal->params.width(), mInternal->params.height(), std::move(pb));
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderTargetDump::~RenderTargetDump() {
|
||||||
|
// If the GPU callback hasn't been made yet then there's a callback elsewhere that has a copy of
|
||||||
|
// the internal pointer. But there's no guarantee that the callback will be ever made if the GPU
|
||||||
|
// pipeline wasn't run for some reason. So it is necessary to leak the memory.
|
||||||
|
// It would be possible to try to coordinate with the callback to have it clean up the memory,
|
||||||
|
// but if this condition happens there's already an issue with the test case so there's no need.
|
||||||
|
if (!bytesFilled()) {
|
||||||
|
mInternal.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t RenderTargetDump::Internal::hash() const {
|
||||||
|
return utils::hash::murmur3((uint32_t*)bytes.data(), bytes.size() / 4, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t RenderTargetDump::hash() const {
|
||||||
|
return mInternal->hash();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<unsigned char>& RenderTargetDump::bytes() const {
|
||||||
|
return mInternal->bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderTargetDump::bytesFilled() const {
|
||||||
|
return mInternal->bytesFilled;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderTargetDump::Internal::Internal(const ScreenshotParams& params) : params(params) {}
|
||||||
|
|
||||||
|
LoadedPng::LoadedPng(std::string filePath) : mFilePath(std::move(filePath)) {
|
||||||
|
#ifndef FILAMENT_IOS
|
||||||
|
std::ifstream pngStream(mFilePath, std::ios::binary);
|
||||||
|
image::LinearImage loadedImage = image::ImageDecoder::decode(pngStream, filePath,
|
||||||
|
image::ImageDecoder::ColorSpace::LINEAR);
|
||||||
|
size_t valuesInImage = loadedImage.getWidth() * loadedImage.getHeight() *
|
||||||
|
loadedImage.getChannels();
|
||||||
|
// The linear image is loaded with each component as [0.0, 1.0] but should be [0, 255], so
|
||||||
|
// convert them.
|
||||||
|
mBytes = std::vector<unsigned char>(valuesInImage);
|
||||||
|
for (int i = 0; i < valuesInImage; ++i) {
|
||||||
|
mBytes[i] = static_cast<uint8_t>(loadedImage.get<float>()[i] * 255.0f);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// For platforms that don't support the image loading library, leave the loaded data blank.
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t LoadedPng::hash() const {
|
||||||
|
EXPECT_THAT(mBytes, testing::Not(testing::IsEmpty()))
|
||||||
|
<< "Failed to load expected test result: " << mFilePath << ".\n"
|
||||||
|
<< "Did you forget to sync CMake after updating the expected image in the source "
|
||||||
|
"directory?";
|
||||||
|
if (mBytes.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return utils::hash::murmur3((uint32_t*)mBytes.data(), mBytes.size() / 4, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<unsigned char>& LoadedPng::bytes() const {
|
||||||
|
return mBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
19
test/gltf-comparison/src/RunOnMain.cpp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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 "RunOnMain.h"
|
||||||
|
|
||||||
|
CrossThreadTask<void()> RunOnMain::sTask;
|
||||||
40
test/gltf-comparison/src/main.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 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 <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "RunOnMain.h"
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
testing::InitGoogleTest(&argc, argv);
|
||||||
|
|
||||||
|
int testResult = -1;
|
||||||
|
|
||||||
|
std::thread testThread([&](){
|
||||||
|
testResult = RUN_ALL_TESTS();
|
||||||
|
RunOnMain::sTask.setClosed();
|
||||||
|
});
|
||||||
|
|
||||||
|
while (!RunOnMain::sTask.isClosed()) {
|
||||||
|
RunOnMain::sTask.runTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
testThread.join();
|
||||||
|
|
||||||
|
return testResult;
|
||||||
|
}
|
||||||
147
test/gltf-comparison/src/test_CompareGLTF.cpp
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* 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 <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <filamentapp/Config.h>
|
||||||
|
#include <filamentapp/FilamentApp.h>
|
||||||
|
#include <filamentapp/IBL.h>
|
||||||
|
|
||||||
|
#include <filament/Camera.h>
|
||||||
|
#include <filament/Engine.h>
|
||||||
|
#include <filament/IndexBuffer.h>
|
||||||
|
#include <filament/Material.h>
|
||||||
|
#include <filament/MaterialInstance.h>
|
||||||
|
#include <filament/RenderableManager.h>
|
||||||
|
#include <filament/Scene.h>
|
||||||
|
#include <filament/Skybox.h>
|
||||||
|
#include <filament/TransformManager.h>
|
||||||
|
#include <filament/VertexBuffer.h>
|
||||||
|
#include <filament/View.h>
|
||||||
|
|
||||||
|
#include <utils/EntityManager.h>
|
||||||
|
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
|
||||||
|
#include "ImageExpectations.h"
|
||||||
|
#include "RunOnMain.h"
|
||||||
|
#include "GLTFViewer.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
using namespace filament;
|
||||||
|
using utils::Entity;
|
||||||
|
using utils::EntityManager;
|
||||||
|
|
||||||
|
constexpr int kFramesToWait = 1000;
|
||||||
|
|
||||||
|
class CompareGLTFContext {
|
||||||
|
public:
|
||||||
|
CompareGLTFContext();
|
||||||
|
~CompareGLTFContext();
|
||||||
|
|
||||||
|
using Command = std::function<void(Engine*, View*, Scene*, Renderer*)>;
|
||||||
|
|
||||||
|
void postRender(Engine* engine, View* view, Scene* scene, Renderer* renderer);
|
||||||
|
|
||||||
|
void addPostRenderCommand(Command command);
|
||||||
|
|
||||||
|
CrossThreadTask<void(Engine*, View*, Scene*, Renderer*), Engine*, View*, Scene*, Renderer*>
|
||||||
|
mPostRenderCommand;
|
||||||
|
std::atomic<bool> mStarted = false;
|
||||||
|
|
||||||
|
GLTFViewer mViewer;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CompareGLTFTest : public testing::TestWithParam<std::string> {
|
||||||
|
public:
|
||||||
|
test::ImageExpectations mExpectations;
|
||||||
|
|
||||||
|
static void SetUpTestSuite();
|
||||||
|
void SetUp() override;
|
||||||
|
static void TearDownTestSuite();
|
||||||
|
|
||||||
|
static std::optional<CompareGLTFContext> sContext;
|
||||||
|
std::atomic<bool> mTestFinished;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
std::optional<CompareGLTFContext> CompareGLTFTest::sContext;
|
||||||
|
|
||||||
|
void CompareGLTFTest::SetUpTestSuite() {
|
||||||
|
sContext.emplace();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompareGLTFTest::SetUp() {
|
||||||
|
while (!sContext->mStarted) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompareGLTFTest::TearDownTestSuite() {
|
||||||
|
sContext.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
CompareGLTFContext::CompareGLTFContext() {
|
||||||
|
mViewer.setFilename("glTF_cases/Models/Box/glTF/Box.gltf");
|
||||||
|
addPostRenderCommand([this](Engine* engine, View* view, Scene* scene, Renderer* renderer){
|
||||||
|
mStarted = true;
|
||||||
|
});
|
||||||
|
RunOnMain::sTask.queueTask([=,this]() {
|
||||||
|
mViewer.runApp([this](Engine* engine, View* view, Scene* scene, Renderer* renderer){
|
||||||
|
postRender(engine, view, scene, renderer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CompareGLTFContext::~CompareGLTFContext() {
|
||||||
|
std::atomic<bool> appClosed = false;
|
||||||
|
addPostRenderCommand([&, this](Engine* engine, View* view, Scene* scene, Renderer* renderer) {
|
||||||
|
FilamentApp::get().close();
|
||||||
|
appClosed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
while (!appClosed) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompareGLTFContext::addPostRenderCommand(CompareGLTFContext::Command command) {
|
||||||
|
while (!mPostRenderCommand.queueTask(command)) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompareGLTFContext::postRender(Engine* engine, View* view, Scene* scene, Renderer* renderer) {
|
||||||
|
mPostRenderCommand.runTask(engine, view, scene, renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_SUITE_P(GLTFFiles, CompareGLTFTest, testing::Values("BoxTextured", "Box"),
|
||||||
|
[](const testing::TestParamInfo<std::string>& info) { return info.param; });
|
||||||
|
|
||||||
|
TEST_P(CompareGLTFTest, Compare) {
|
||||||
|
sContext->addPostRenderCommand([this](Engine*, View* view, Scene*, Renderer* renderer) {
|
||||||
|
EXPECT_IMAGE(renderer, mExpectations,
|
||||||
|
test::ScreenshotParams(512, 512, absl::StrFormat("GLTF_%s", GetParam().c_str())));
|
||||||
|
|
||||||
|
std::cout << "postRender: " << GetParam() << std::endl;
|
||||||
|
mTestFinished = true;
|
||||||
|
});
|
||||||
|
while (!mTestFinished) {}
|
||||||
|
|
||||||
|
std::atomic<int> counter = 0;
|
||||||
|
auto incrementCounter = [&](Engine*, View* view, Scene*, Renderer* renderer) {
|
||||||
|
counter++;
|
||||||
|
};
|
||||||
|
while (counter < kFramesToWait) {
|
||||||
|
sContext->addPostRenderCommand(incrementCounter);
|
||||||
|
}
|
||||||
|
}
|
||||||