Compare commits
1 Commits
gitIgnoreU
...
mhoff/text
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
667da2aabe |
18
.github/workflows/presubmit.yml
vendored
@@ -108,21 +108,9 @@ jobs:
|
|||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
- name: Cache Mesa and deps
|
- name: Install python prereqs
|
||||||
id: mesa-cache
|
run: pip install mako setuptools pyyaml
|
||||||
uses: actions/cache@v4 # Use a specific version
|
- name: Run script
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
$HOME/Library/Caches/Homebrew
|
|
||||||
mesa
|
|
||||||
key: ${{ runner.os }}-mesa-deps-${{ vars.MESA_VERSION }}
|
|
||||||
- name: Get Mesa
|
|
||||||
id: mesa-prereq
|
|
||||||
env:
|
|
||||||
MESA_VERSION: ${{ vars.MESA_VERSION }}
|
|
||||||
run: |
|
|
||||||
bash test/utils/get_mesa.sh
|
|
||||||
- name: Run Test
|
|
||||||
run: |
|
run: |
|
||||||
bash test/renderdiff/test.sh
|
bash test/renderdiff/test.sh
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
|
|||||||
15
.gitignore
vendored
@@ -18,18 +18,3 @@ test*.json
|
|||||||
results
|
results
|
||||||
/compile_commands.json
|
/compile_commands.json
|
||||||
/.cache
|
/.cache
|
||||||
build/.cmake/
|
|
||||||
build/CMakeCache.txt
|
|
||||||
build/CMakeFiles/
|
|
||||||
build/Makefile
|
|
||||||
build/SPIRV-Tools*
|
|
||||||
build/cmake_install.cmake
|
|
||||||
build/compile_commands.json
|
|
||||||
build/filament/
|
|
||||||
build/include/
|
|
||||||
build/libs/
|
|
||||||
build/mac/ninja
|
|
||||||
build/samples/
|
|
||||||
build/shaders/
|
|
||||||
build/third_party/
|
|
||||||
build/tools/
|
|
||||||
|
|||||||
@@ -801,7 +801,6 @@ add_subdirectory(${EXTERNAL}/draco/tnt)
|
|||||||
add_subdirectory(${EXTERNAL}/jsmn/tnt)
|
add_subdirectory(${EXTERNAL}/jsmn/tnt)
|
||||||
add_subdirectory(${EXTERNAL}/stb/tnt)
|
add_subdirectory(${EXTERNAL}/stb/tnt)
|
||||||
add_subdirectory(${EXTERNAL}/getopt)
|
add_subdirectory(${EXTERNAL}/getopt)
|
||||||
add_subdirectory(${EXTERNAL}/perfetto/tnt)
|
|
||||||
|
|
||||||
# Note that this has to be placed after mikktspace in order for combine_static_libs to work.
|
# Note that this has to be placed after mikktspace in order for combine_static_libs to work.
|
||||||
add_subdirectory(${LIBRARIES}/geometry)
|
add_subdirectory(${LIBRARIES}/geometry)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.google.android.filament:filament-android:1.59.3'
|
implementation 'com.google.android.filament:filament-android:1.59.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:
|
iOS projects can use CocoaPods to install the latest release:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pod 'Filament', '~> 1.59.3'
|
pod 'Filament', '~> 1.59.1'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|||||||
@@ -7,12 +7,6 @@ A new header is inserted each time a *tag* is created.
|
|||||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||||
|
|
||||||
## v1.59.4
|
|
||||||
|
|
||||||
|
|
||||||
## v1.59.3
|
|
||||||
|
|
||||||
|
|
||||||
## v1.59.2
|
## v1.59.2
|
||||||
|
|
||||||
- Fix build/compile errors when upgrading to MacOS 15.4
|
- Fix build/compile errors when upgrading to MacOS 15.4
|
||||||
|
|||||||
@@ -26,10 +26,6 @@ add_library(utils STATIC IMPORTED)
|
|||||||
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
|
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
|
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
|
||||||
|
|
||||||
add_library(perfetto STATIC IMPORTED)
|
|
||||||
set_target_properties(perfetto PROPERTIES IMPORTED_LOCATION
|
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libperfetto.a)
|
|
||||||
|
|
||||||
add_library(filabridge STATIC IMPORTED)
|
add_library(filabridge STATIC IMPORTED)
|
||||||
set_target_properties(filabridge PROPERTIES IMPORTED_LOCATION
|
set_target_properties(filabridge PROPERTIES IMPORTED_LOCATION
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilabridge.a)
|
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilabridge.a)
|
||||||
@@ -44,7 +40,6 @@ set_target_properties(shaders PROPERTIES IMPORTED_LOCATION
|
|||||||
|
|
||||||
set(FILAMAT_INCLUDE_DIRS
|
set(FILAMAT_INCLUDE_DIRS
|
||||||
../../libs/utils/include
|
../../libs/utils/include
|
||||||
../../third_party/perfetto
|
|
||||||
)
|
)
|
||||||
|
|
||||||
include_directories(${FILAMENT_DIR}/include)
|
include_directories(${FILAMENT_DIR}/include)
|
||||||
@@ -60,7 +55,6 @@ target_link_libraries(filamat-jni
|
|||||||
filabridge
|
filabridge
|
||||||
shaders
|
shaders
|
||||||
utils
|
utils
|
||||||
perfetto
|
|
||||||
log
|
log
|
||||||
smol-v
|
smol-v
|
||||||
$<$<STREQUAL:${FILAMENT_SUPPORTS_WEBGPU},ON>:tint>
|
$<$<STREQUAL:${FILAMENT_SUPPORTS_WEBGPU},ON>:tint>
|
||||||
|
|||||||
@@ -21,10 +21,6 @@ add_library(utils STATIC IMPORTED)
|
|||||||
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
|
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
|
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
|
||||||
|
|
||||||
add_library(perfetto STATIC IMPORTED)
|
|
||||||
set_target_properties(perfetto PROPERTIES IMPORTED_LOCATION
|
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libperfetto.a)
|
|
||||||
|
|
||||||
add_library(ibl-lite STATIC IMPORTED)
|
add_library(ibl-lite STATIC IMPORTED)
|
||||||
set_target_properties(ibl-lite PROPERTIES IMPORTED_LOCATION
|
set_target_properties(ibl-lite PROPERTIES IMPORTED_LOCATION
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libibl-lite.a)
|
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libibl-lite.a)
|
||||||
@@ -127,7 +123,6 @@ target_link_libraries(filament-jni
|
|||||||
PRIVATE android
|
PRIVATE android
|
||||||
PRIVATE jnigraphics
|
PRIVATE jnigraphics
|
||||||
PRIVATE utils
|
PRIVATE utils
|
||||||
PRIVATE perfetto
|
|
||||||
|
|
||||||
# libgeometry is PUBLIC because gltfio uses it.
|
# libgeometry is PUBLIC because gltfio uses it.
|
||||||
PUBLIC geometry
|
PUBLIC geometry
|
||||||
@@ -146,7 +141,6 @@ target_include_directories(filament-jni PRIVATE
|
|||||||
${FILAMENT_DIR}/include
|
${FILAMENT_DIR}/include
|
||||||
../../filament/backend/include
|
../../filament/backend/include
|
||||||
../../third_party/robin-map
|
../../third_party/robin-map
|
||||||
../../third_party/perfetto
|
|
||||||
../../libs/utils/include)
|
../../libs/utils/include)
|
||||||
|
|
||||||
# Force a relink when the version script is changed:
|
# Force a relink when the version script is changed:
|
||||||
|
|||||||
@@ -35,10 +35,6 @@ add_library(utils STATIC IMPORTED)
|
|||||||
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
|
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
|
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
|
||||||
|
|
||||||
add_library(perfetto STATIC IMPORTED)
|
|
||||||
set_target_properties(perfetto PROPERTIES IMPORTED_LOCATION
|
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libperfetto.a)
|
|
||||||
|
|
||||||
add_library(uberzlib STATIC IMPORTED)
|
add_library(uberzlib STATIC IMPORTED)
|
||||||
set_target_properties(uberzlib PROPERTIES IMPORTED_LOCATION
|
set_target_properties(uberzlib PROPERTIES IMPORTED_LOCATION
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libuberzlib.a)
|
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libuberzlib.a)
|
||||||
@@ -125,7 +121,6 @@ set(GLTFIO_INCLUDE_DIRS
|
|||||||
../../third_party/meshoptimizer/src
|
../../third_party/meshoptimizer/src
|
||||||
../../third_party/robin-map
|
../../third_party/robin-map
|
||||||
../../third_party/stb
|
../../third_party/stb
|
||||||
../../third_party/perfetto
|
|
||||||
../../libs/utils/include
|
../../libs/utils/include
|
||||||
../../libs/ktxreader/include
|
../../libs/ktxreader/include
|
||||||
)
|
)
|
||||||
@@ -134,7 +129,7 @@ add_library(gltfio-jni SHARED ${GLTFIO_SRCS})
|
|||||||
target_include_directories(gltfio-jni PRIVATE ${GLTFIO_INCLUDE_DIRS})
|
target_include_directories(gltfio-jni PRIVATE ${GLTFIO_INCLUDE_DIRS})
|
||||||
set_target_properties(gltfio-jni PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.symbols)
|
set_target_properties(gltfio-jni PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.symbols)
|
||||||
set_target_properties(gltfio-jni PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.map)
|
set_target_properties(gltfio-jni PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.map)
|
||||||
target_link_libraries(gltfio-jni filament-jni utils perfetto uberzlib log stb ktxreader basis_transcoder zstd uberarchive)
|
target_link_libraries(gltfio-jni filament-jni utils uberzlib log stb ktxreader basis_transcoder zstd uberarchive)
|
||||||
target_link_libraries(gltfio-jni dracodec meshoptimizer)
|
target_link_libraries(gltfio-jni dracodec meshoptimizer)
|
||||||
target_compile_definitions(gltfio-jni PUBLIC GLTFIO_DRACO_SUPPORTED=1)
|
target_compile_definitions(gltfio-jni PUBLIC GLTFIO_DRACO_SUPPORTED=1)
|
||||||
target_include_directories(gltfio-jni PRIVATE ${DRACO_DIR}/src)
|
target_include_directories(gltfio-jni PRIVATE ${DRACO_DIR}/src)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
GROUP=com.google.android.filament
|
GROUP=com.google.android.filament
|
||||||
VERSION_NAME=1.59.3
|
VERSION_NAME=1.59.1
|
||||||
|
|
||||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||||
|
|
||||||
|
|||||||
@@ -259,7 +259,6 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
|||||||
src/webgpu/WebGPUHandles.h
|
src/webgpu/WebGPUHandles.h
|
||||||
src/webgpu/WebGPUSwapChain.cpp
|
src/webgpu/WebGPUSwapChain.cpp
|
||||||
src/webgpu/WebGPUSwapChain.h
|
src/webgpu/WebGPUSwapChain.h
|
||||||
src/webgpu/WGPUProgram.cpp
|
|
||||||
)
|
)
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
list(APPEND SRCS src/webgpu/platform/WebGPUPlatformWindows.cpp)
|
list(APPEND SRCS src/webgpu/platform/WebGPUPlatformWindows.cpp)
|
||||||
@@ -508,10 +507,8 @@ if (APPLE OR LINUX)
|
|||||||
test/Arguments.cpp
|
test/Arguments.cpp
|
||||||
test/ImageExpectations.cpp
|
test/ImageExpectations.cpp
|
||||||
test/Lifetimes.cpp
|
test/Lifetimes.cpp
|
||||||
test/PlatformRunner.cpp
|
|
||||||
test/Shader.cpp
|
test/Shader.cpp
|
||||||
test/SharedShaders.cpp
|
test/SharedShaders.cpp
|
||||||
test/Skip.cpp
|
|
||||||
test/test_FeedbackLoops.cpp
|
test/test_FeedbackLoops.cpp
|
||||||
test/test_Blit.cpp
|
test/test_Blit.cpp
|
||||||
test/test_MissingRequiredAttributes.cpp
|
test/test_MissingRequiredAttributes.cpp
|
||||||
@@ -535,9 +532,6 @@ if (APPLE OR LINUX)
|
|||||||
filamat
|
filamat
|
||||||
SPIRV
|
SPIRV
|
||||||
spirv-cross-glsl)
|
spirv-cross-glsl)
|
||||||
# Create input/output directories for test result images.
|
|
||||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
|
|
||||||
file(COPY test/expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# TODO: Disabling IOS test due to breakage wrt glslang update
|
# TODO: Disabling IOS test due to breakage wrt glslang update
|
||||||
|
|||||||
@@ -55,9 +55,4 @@ public:
|
|||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|
||||||
#if !defined(NDEBUG)
|
|
||||||
utils::io::ostream& operator<<(utils::io::ostream& out,
|
|
||||||
const filament::backend::BufferObjectStreamDescriptor& b);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
|
#endif // TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ void CommandStream::execute(void* buffer) {
|
|||||||
|
|
||||||
Profiler profiler;
|
Profiler profiler;
|
||||||
|
|
||||||
if constexpr (SYSTRACE_TAG) {
|
if (SYSTRACE_TAG) {
|
||||||
if (UTILS_UNLIKELY(mUsePerformanceCounter)) {
|
if (UTILS_UNLIKELY(mUsePerformanceCounter)) {
|
||||||
// we want to remove all this when tracing is completely disabled
|
// we want to remove all this when tracing is completely disabled
|
||||||
profiler.resetEvents(Profiler::EV_CPU_CYCLES | Profiler::EV_BPU_MISSES);
|
profiler.resetEvents(Profiler::EV_CPU_CYCLES | Profiler::EV_BPU_MISSES);
|
||||||
@@ -100,7 +100,7 @@ void CommandStream::execute(void* buffer) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if constexpr (SYSTRACE_TAG) {
|
if (SYSTRACE_TAG) {
|
||||||
if (UTILS_UNLIKELY(mUsePerformanceCounter)) {
|
if (UTILS_UNLIKELY(mUsePerformanceCounter)) {
|
||||||
// we want to remove all this when tracing is completely disabled
|
// we want to remove all this when tracing is completely disabled
|
||||||
profiler.stop();
|
profiler.stop();
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ OpenGLProgram::~OpenGLProgram() noexcept {
|
|||||||
delete lazyInitializationData;
|
delete lazyInitializationData;
|
||||||
|
|
||||||
ShaderCompilerService::terminate(mToken);
|
ShaderCompilerService::terminate(mToken);
|
||||||
assert_invariant(!mToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete [] mUniformsRecords;
|
delete [] mUniformsRecords;
|
||||||
|
|||||||
@@ -17,15 +17,11 @@
|
|||||||
#include "ShaderCompilerService.h"
|
#include "ShaderCompilerService.h"
|
||||||
|
|
||||||
#include "BlobCacheKey.h"
|
#include "BlobCacheKey.h"
|
||||||
#include "CallbackManager.h"
|
|
||||||
#include "CompilerThreadPool.h"
|
|
||||||
#include "OpenGLBlobCache.h"
|
#include "OpenGLBlobCache.h"
|
||||||
#include "OpenGLDriver.h"
|
#include "OpenGLDriver.h"
|
||||||
|
|
||||||
#include <iterator>
|
|
||||||
#include <private/backend/BackendUtils.h>
|
#include <private/backend/BackendUtils.h>
|
||||||
|
|
||||||
#include <backend/DriverEnums.h>
|
|
||||||
#include <backend/Program.h>
|
#include <backend/Program.h>
|
||||||
|
|
||||||
#include <utils/compiler.h>
|
#include <utils/compiler.h>
|
||||||
@@ -38,9 +34,9 @@
|
|||||||
#include <utils/Panic.h>
|
#include <utils/Panic.h>
|
||||||
#include <utils/Systrace.h>
|
#include <utils/Systrace.h>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <chrono>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -51,7 +47,6 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
@@ -59,17 +54,17 @@ using namespace utils;
|
|||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
static std::string to_string(bool const b) { return b ? "true" : "false"; }
|
static inline std::string to_string(bool b) noexcept { return b ? "true" : "false"; }
|
||||||
static std::string to_string(int const i) { return std::to_string(i); }
|
static inline std::string to_string(int i) noexcept { return std::to_string(i); }
|
||||||
static std::string to_string(float const f) { return "float(" + std::to_string(f) + ")"; }
|
static inline std::string to_string(float f) noexcept { return "float(" + std::to_string(f) + ")"; }
|
||||||
|
|
||||||
static void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
|
static void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
|
||||||
GLuint shaderId, CString const& sourceCode) noexcept;
|
GLuint shaderId, CString const& sourceCode) noexcept;
|
||||||
static void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept;
|
static void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept;
|
||||||
|
|
||||||
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context, char* source,
|
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, char* source,
|
||||||
size_t len) noexcept;
|
size_t len) noexcept;
|
||||||
static void process_OVR_multiview2(OpenGLContext const& context, int32_t eyeCount, char* source,
|
static void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount, char* source,
|
||||||
size_t len) noexcept;
|
size_t len) noexcept;
|
||||||
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
|
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
|
||||||
static std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept;
|
static std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept;
|
||||||
@@ -77,15 +72,20 @@ static std::array<std::string_view, 3> splitShaderSource(std::string_view source
|
|||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||||
|
struct ProgramData {
|
||||||
|
GLuint program{};
|
||||||
|
shaders_t shaders{};
|
||||||
|
};
|
||||||
|
|
||||||
~OpenGLProgramToken() override;
|
~OpenGLProgramToken() override;
|
||||||
|
|
||||||
OpenGLProgramToken(ShaderCompilerService& compiler, CString const& name) noexcept
|
OpenGLProgramToken(ShaderCompilerService& compiler, utils::CString const& name) noexcept
|
||||||
: compiler(compiler), name(name), handle(compiler.issueCallbackHandle()) {
|
: compiler(compiler), name(name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderCompilerService& compiler;
|
ShaderCompilerService& compiler;
|
||||||
CString const& name;
|
utils::CString const& name;
|
||||||
FixedCapacityVector<std::pair<CString, uint8_t>> attributes;
|
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes;
|
||||||
shaders_source_t shaderSourceCode;
|
shaders_source_t shaderSourceCode;
|
||||||
void* user = nullptr;
|
void* user = nullptr;
|
||||||
struct {
|
struct {
|
||||||
@@ -93,34 +93,48 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
|||||||
GLuint program = 0;
|
GLuint program = 0;
|
||||||
} gl; // 12 bytes
|
} gl; // 12 bytes
|
||||||
|
|
||||||
// Used in THREAD_POOL mode. The job from ThreadPool should call this when the token is ready to
|
|
||||||
// be used. It sends a signal to the engine thread being blocked upon the `wait` call, so that
|
// Sets the programData, typically from the compiler thread, and signal the main thread.
|
||||||
// the engine thread resumes its processing with the token.
|
// This is similar to std::promise::set_value.
|
||||||
void signal() noexcept {
|
void set(ProgramData const& data) noexcept {
|
||||||
std::unique_lock const l(lock);
|
std::unique_lock const l(lock);
|
||||||
|
programData = data;
|
||||||
signaled = true;
|
signaled = true;
|
||||||
cond.notify_one();
|
cond.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used in THREAD_POOL mode. The engine thread should call this before accessing token's fields.
|
// Get the programBinary, wait if necessary.
|
||||||
// This may block until the token is ready to be used.
|
// This is similar to std::future::get
|
||||||
|
ProgramData const& get() const noexcept {
|
||||||
|
std::unique_lock l(lock);
|
||||||
|
cond.wait(l, [this](){ return signaled; });
|
||||||
|
return programData;
|
||||||
|
}
|
||||||
|
|
||||||
void wait() const noexcept {
|
void wait() const noexcept {
|
||||||
std::unique_lock l(lock);
|
std::unique_lock l(lock);
|
||||||
cond.wait(l, [this] { return signaled; });
|
cond.wait(l, [this](){ return signaled; });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if the programBinary is ready.
|
||||||
|
// This is similar to std::future::wait_for(0s)
|
||||||
|
bool isReady() const noexcept {
|
||||||
|
std::unique_lock l(lock);
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
return cond.wait_for(l, 0s, [this](){ return signaled; });
|
||||||
}
|
}
|
||||||
|
|
||||||
CallbackManager::Handle handle{};
|
CallbackManager::Handle handle{};
|
||||||
BlobCacheKey key;
|
BlobCacheKey key;
|
||||||
|
mutable utils::Mutex lock;
|
||||||
// Used for the `THREAD_POOL` mode.
|
mutable utils::Condition cond;
|
||||||
mutable Mutex lock;
|
ProgramData programData;
|
||||||
mutable Condition cond;
|
|
||||||
bool signaled = false;
|
bool signaled = false;
|
||||||
|
|
||||||
|
bool canceled = false; // not part of the signaling
|
||||||
};
|
};
|
||||||
|
|
||||||
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() {
|
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() = default;
|
||||||
compiler.submitCallbackHandle(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* static */ void ShaderCompilerService::setUserData(const program_token_t& token,
|
/* static */ void ShaderCompilerService::setUserData(const program_token_t& token,
|
||||||
void* user) noexcept {
|
void* user) noexcept {
|
||||||
@@ -175,9 +189,9 @@ void ShaderCompilerService::init() noexcept {
|
|||||||
if (mMode == Mode::THREAD_POOL) {
|
if (mMode == Mode::THREAD_POOL) {
|
||||||
// - on Adreno there is a single compiler object. We can't use a pool > 1
|
// - on Adreno there is a single compiler object. We can't use a pool > 1
|
||||||
// also glProgramBinary blocks if other threads are compiling.
|
// also glProgramBinary blocks if other threads are compiling.
|
||||||
// - on Mali shader compilation can be multithreaded, but program linking happens on
|
// - on Mali shader compilation can be multi-threaded, but program linking happens on
|
||||||
// a single service thread, so we don't bother using more than one thread either.
|
// a single service thread, so we don't bother using more than one thread either.
|
||||||
// - on PowerVR shader compilation and linking can be multithreaded.
|
// - on PowerVR shader compilation and linking can be multi-threaded.
|
||||||
// How many threads should we use?
|
// How many threads should we use?
|
||||||
// - on macOS (M1 MacBook Pro/Ventura) there is global lock around all GL APIs when using
|
// - on macOS (M1 MacBook Pro/Ventura) there is global lock around all GL APIs when using
|
||||||
// a shared context, so parallel shader compilation yields no benefit.
|
// a shared context, so parallel shader compilation yields no benefit.
|
||||||
@@ -206,7 +220,7 @@ void ShaderCompilerService::init() noexcept {
|
|||||||
|
|
||||||
mShaderCompilerThreadCount = poolSize;
|
mShaderCompilerThreadCount = poolSize;
|
||||||
mCompilerThreadPool.init(mShaderCompilerThreadCount,
|
mCompilerThreadPool.init(mShaderCompilerThreadCount,
|
||||||
[&platform = mDriver.mPlatform, priority] {
|
[&platform = mDriver.mPlatform, priority]() {
|
||||||
// give the thread a name
|
// give the thread a name
|
||||||
JobSystem::setThreadName("CompilerThreadPool");
|
JobSystem::setThreadName("CompilerThreadPool");
|
||||||
// run at a slightly lower priority than other filament threads
|
// run at a slightly lower priority than other filament threads
|
||||||
@@ -214,7 +228,7 @@ void ShaderCompilerService::init() noexcept {
|
|||||||
// create a gl context current to this thread
|
// create a gl context current to this thread
|
||||||
platform.createContext(true);
|
platform.createContext(true);
|
||||||
},
|
},
|
||||||
[&platform = mDriver.mPlatform] {
|
[&platform = mDriver.mPlatform]() {
|
||||||
// release context and thread state
|
// release context and thread state
|
||||||
platform.releaseContext();
|
platform.releaseContext();
|
||||||
});
|
});
|
||||||
@@ -235,65 +249,119 @@ void ShaderCompilerService::terminate() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||||
CString const& name, Program&& program) {
|
utils::CString const& name, Program&& program) {
|
||||||
auto& gl = mDriver.getContext();
|
auto& gl = mDriver.getContext();
|
||||||
|
|
||||||
// Create a token. A callback condition (handle) is internally created upon token creation.
|
|
||||||
auto token = std::make_shared<OpenGLProgramToken>(*this, name);
|
auto token = std::make_shared<OpenGLProgramToken>(*this, name);
|
||||||
if (UTILS_UNLIKELY(gl.isES2())) {
|
if (UTILS_UNLIKELY(gl.isES2())) {
|
||||||
token->attributes = std::move(program.getAttributes());
|
token->attributes = std::move(program.getAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try retrieving the cached program blob if available.
|
|
||||||
token->gl.program = mBlobCache.retrieve(&token->key, mDriver.mPlatform, program);
|
token->gl.program = mBlobCache.retrieve(&token->key, mDriver.mPlatform, program);
|
||||||
if (token->gl.program) {
|
if (token->gl.program) {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initiate program compilation.
|
token->handle = mCallbackManager.get();
|
||||||
|
|
||||||
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
||||||
switch (mMode) {
|
switch (mMode) {
|
||||||
case Mode::THREAD_POOL: {
|
case Mode::THREAD_POOL: {
|
||||||
|
// queue a compile job
|
||||||
mCompilerThreadPool.queue(priorityQueue, token,
|
mCompilerThreadPool.queue(priorityQueue, token,
|
||||||
[this, &gl, program = std::move(program), token]() mutable {
|
[this, &gl, program = std::move(program), token]() mutable {
|
||||||
compileShaders(gl, std::move(program.getShadersSource()),
|
// compile the shaders
|
||||||
program.getSpecializationConstants(), program.isMultiview(), token);
|
shaders_t shaders{};
|
||||||
linkProgram(gl, token);
|
compileShaders(gl,
|
||||||
// Now `token->gl.program` must be populated, so we signal the completion
|
std::move(program.getShadersSource()),
|
||||||
// of the linking. We don't need to check the result of the program here
|
program.getSpecializationConstants(),
|
||||||
// because it'll be done in the engine thread.
|
program.isMultiview(),
|
||||||
token->signal();
|
shaders,
|
||||||
// We try caching the program blob after sending the signal. This allows us
|
token->shaderSourceCode);
|
||||||
// to unblock the engine thread as soon as the token is ready while
|
|
||||||
// performing an expensive caching operation still in the pool.
|
// link the program
|
||||||
tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
|
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
|
||||||
|
|
||||||
|
OpenGLProgramToken::ProgramData programData;
|
||||||
|
programData.shaders = shaders;
|
||||||
|
|
||||||
|
// We need to query the link status here to guarantee that the
|
||||||
|
// program is compiled and linked now (we don't want this to be
|
||||||
|
// deferred to later). We don't care about the result at this point.
|
||||||
|
GLint status = GL_FALSE;
|
||||||
|
glGetProgramiv(glProgram, GL_LINK_STATUS, &status);
|
||||||
|
programData.program = glProgram;
|
||||||
|
|
||||||
|
// we don't need to check for success here, it'll be done on the
|
||||||
|
// main thread side.
|
||||||
|
token->set(programData);
|
||||||
|
|
||||||
|
mCallbackManager.put(token->handle);
|
||||||
|
|
||||||
|
// caching must be the last thing we do
|
||||||
|
if (token->key && status == GL_TRUE) {
|
||||||
|
// Attempt to cache. This calls glGetProgramBinary.
|
||||||
|
mBlobCache.insert(mDriver.mPlatform, token->key, glProgram);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Mode::SYNCHRONOUS:
|
case Mode::SYNCHRONOUS:
|
||||||
case Mode::ASYNCHRONOUS: {
|
case Mode::ASYNCHRONOUS: {
|
||||||
compileShaders(gl, std::move(program.getShadersSource()),
|
// this cannot fail because we check compilation status after linking the program
|
||||||
program.getSpecializationConstants(), program.isMultiview(), token);
|
// shaders[] is filled with id of shader stages present.
|
||||||
|
compileShaders(gl,
|
||||||
|
std::move(program.getShadersSource()),
|
||||||
|
program.getSpecializationConstants(),
|
||||||
|
program.isMultiview(),
|
||||||
|
token->gl.shaders,
|
||||||
|
token->shaderSourceCode);
|
||||||
|
|
||||||
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
|
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
|
||||||
assert_invariant(mMode != Mode::THREAD_POOL);
|
assert_invariant(mMode != Mode::THREAD_POOL);
|
||||||
if (mMode == Mode::ASYNCHRONOUS) {
|
if (mMode == Mode::ASYNCHRONOUS) {
|
||||||
// Check link completion if link was initiated.
|
// don't attempt to link this program if all shaders are not done compiling
|
||||||
|
GLint status;
|
||||||
if (token->gl.program) {
|
if (token->gl.program) {
|
||||||
return isLinkCompleted(token);
|
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status);
|
||||||
|
if (status == GL_FALSE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (auto shader: token->gl.shaders) {
|
||||||
|
if (shader) {
|
||||||
|
glGetShaderiv(shader, GL_COMPLETION_STATUS, &status);
|
||||||
|
if (status == GL_FALSE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Link hasn't been initiated, then check compile completion.
|
}
|
||||||
if (!isCompileCompleted(token)) {
|
|
||||||
|
if (!token->gl.program) {
|
||||||
|
// link the program, this also cannot fail because status is checked later.
|
||||||
|
token->gl.program = linkProgram(mDriver.getContext(),
|
||||||
|
token->gl.shaders, token->attributes);
|
||||||
|
if (mMode == Mode::ASYNCHRONOUS) {
|
||||||
|
// wait until the link finishes...
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!token->gl.program) {
|
|
||||||
linkProgram(mDriver.getContext(), token);
|
assert_invariant(token->gl.program);
|
||||||
if (mMode == Mode::ASYNCHRONOUS) {
|
|
||||||
return false;// Wait until the link finishes.
|
mCallbackManager.put(token->handle);
|
||||||
}
|
|
||||||
|
if (token->key) {
|
||||||
|
// TODO: technically we don't have to cache right now. Is it advantageous to
|
||||||
|
// do this later, maybe depending on CPU usage?
|
||||||
|
// attempt to cache if we don't have a thread pool (otherwise it's done
|
||||||
|
// by the pool).
|
||||||
|
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@@ -307,7 +375,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLuint ShaderCompilerService::getProgram(program_token_t& token) {
|
GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& token) {
|
||||||
GLuint const program = initialize(token);
|
GLuint const program = initialize(token);
|
||||||
assert_invariant(token == nullptr);
|
assert_invariant(token == nullptr);
|
||||||
#if !FILAMENT_ENABLE_MATDBG
|
#if !FILAMENT_ENABLE_MATDBG
|
||||||
@@ -316,29 +384,43 @@ GLuint ShaderCompilerService::getProgram(program_token_t& token) {
|
|||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Cancel program compilation. This function is responsible for cleaning up the ongoing
|
|
||||||
* compilation & link process. If the process is already completed by calling `initialize(token)`,
|
|
||||||
* this function is not called.
|
|
||||||
*/
|
|
||||||
/* static */ void ShaderCompilerService::terminate(program_token_t& token) {
|
/* static */ void ShaderCompilerService::terminate(program_token_t& token) {
|
||||||
|
assert_invariant(token);
|
||||||
|
|
||||||
assert_invariant(token);// This function should be called when the token is still alive.
|
token->canceled = true;
|
||||||
|
|
||||||
|
bool const isTickOpCanceled = token->compiler.cancelTickOp(token);
|
||||||
|
|
||||||
if (token->compiler.mMode == Mode::THREAD_POOL) {
|
if (token->compiler.mMode == Mode::THREAD_POOL) {
|
||||||
auto const job = token->compiler.mCompilerThreadPool.dequeue(token);
|
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
|
||||||
if (!job) {
|
if (!job) {
|
||||||
// It's likely that the job was already completed. But it may be still being
|
// The job is being executed right now. We need to wait for it to finish to avoid a
|
||||||
// executed at this moment. Just try waiting for it to avoid a race.
|
// race.
|
||||||
token->wait();
|
token->wait();
|
||||||
|
} else {
|
||||||
|
// The job has not been executed, but we still need to inform the callback manager in
|
||||||
|
// order for future callbacks to be successfully called.
|
||||||
|
token->compiler.mCallbackManager.put(token->handle);
|
||||||
}
|
}
|
||||||
|
} else if (isTickOpCanceled) {
|
||||||
|
// Since the tick op was canceled, we need to .put the token here.
|
||||||
|
token->compiler.mCallbackManager.put(token->handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanupProgramAndShaders(token);
|
for (GLuint& shader: token->gl.shaders) {
|
||||||
|
if (shader) {
|
||||||
|
if (token->gl.program) {
|
||||||
|
glDetachShader(token->gl.program, shader);
|
||||||
|
}
|
||||||
|
glDeleteShader(shader);
|
||||||
|
shader = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (token->gl.program) {
|
||||||
|
glDeleteProgram(token->gl.program);
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup the token.
|
token.reset();
|
||||||
token->compiler.cancelTickOp(token);
|
|
||||||
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderCompilerService::tick() {
|
void ShaderCompilerService::tick() {
|
||||||
@@ -348,16 +430,8 @@ void ShaderCompilerService::tick() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CallbackManager::Handle ShaderCompilerService::issueCallbackHandle() const noexcept {
|
|
||||||
return mCallbackManager.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderCompilerService::submitCallbackHandle(CallbackManager::Handle handle) noexcept {
|
|
||||||
mCallbackManager.put(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderCompilerService::notifyWhenAllProgramsAreReady(
|
void ShaderCompilerService::notifyWhenAllProgramsAreReady(
|
||||||
CallbackHandler* handler, CallbackHandler::Callback const callback, void* user) {
|
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
mCallbackManager.setCallback(handler, callback, user);
|
mCallbackManager.setCallback(handler, callback, user);
|
||||||
}
|
}
|
||||||
@@ -365,137 +439,117 @@ void ShaderCompilerService::notifyWhenAllProgramsAreReady(
|
|||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
GLuint ShaderCompilerService::initialize(program_token_t& token) {
|
/* static */ void ShaderCompilerService::getProgramFromCompilerPool(
|
||||||
|
program_token_t& token) noexcept {
|
||||||
|
OpenGLProgramToken::ProgramData const& programData{ token->get() };
|
||||||
|
if (!token->canceled) {
|
||||||
|
token->gl.shaders = programData.shaders;
|
||||||
|
token->gl.program = programData.program;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||||
SYSTRACE_CALL();
|
SYSTRACE_CALL();
|
||||||
|
if (!token->gl.program) {
|
||||||
|
switch (mMode) {
|
||||||
|
case Mode::THREAD_POOL: {
|
||||||
|
// we need this program right now, remove it from the queue
|
||||||
|
auto job = mCompilerThreadPool.dequeue(token);
|
||||||
|
if (job) {
|
||||||
|
// if we were able to remove it, we execute the job now, otherwise it means
|
||||||
|
// it's being executed right now.
|
||||||
|
job();
|
||||||
|
}
|
||||||
|
|
||||||
assert_invariant(token);// This function should be called when the token is still alive.
|
if (!token->canceled) {
|
||||||
|
token->compiler.cancelTickOp(token);
|
||||||
|
}
|
||||||
|
|
||||||
ensureTokenIsReady(token);
|
// Block until we get the program from the pool. Generally this wouldn't block
|
||||||
assert_invariant(token->gl.program);
|
// because we just compiled the program above, when executing job.
|
||||||
|
ShaderCompilerService::getProgramFromCompilerPool(token);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Check status of program linking. If it failed, errors will be logged.
|
case Mode::ASYNCHRONOUS: {
|
||||||
bool const linked = checkLinkStatusAndCleanupShaders(token);
|
// we force the program link -- which might stall, either here or below in
|
||||||
|
// checkProgramStatus(), but we don't have a choice, we need to use the program now.
|
||||||
|
token->compiler.cancelTickOp(token);
|
||||||
|
|
||||||
// We panic if it failed to create the program.
|
token->gl.program =
|
||||||
FILAMENT_CHECK_POSTCONDITION(linked)
|
linkProgram(mDriver.getContext(), token->gl.shaders, token->attributes);
|
||||||
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
|
|
||||||
|
|
||||||
// The program is successfully created. Try caching the program blob. In the THREAD_POOL mode,
|
assert_invariant(token->gl.program);
|
||||||
// caching is performed in the pool.
|
|
||||||
if (mMode != Mode::THREAD_POOL) {
|
mCallbackManager.put(token->handle);
|
||||||
tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
|
|
||||||
|
if (token->key) {
|
||||||
|
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Mode::SYNCHRONOUS: {
|
||||||
|
// if we don't have a program yet, block until we get it.
|
||||||
|
tick();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Mode::UNDEFINED: {
|
||||||
|
assert_invariant(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GLuint const program = token->gl.program;
|
// by this point we must have a GL program
|
||||||
|
assert_invariant(token->gl.program);
|
||||||
|
|
||||||
// Cleanup the token.
|
GLuint program = 0;
|
||||||
token->compiler.cancelTickOp(token);
|
|
||||||
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
|
// check status of program linking and shader compilation, logs error and free all resources
|
||||||
|
// in case of error.
|
||||||
|
bool const success = checkProgramStatus(token);
|
||||||
|
|
||||||
|
// Unless we have matdbg, we panic if a program is invalid. Otherwise, we'd get a UB.
|
||||||
|
// The compilation error has been logged to log.e by this point.
|
||||||
|
FILAMENT_CHECK_POSTCONDITION(FILAMENT_ENABLE_MATDBG || success)
|
||||||
|
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
|
||||||
|
|
||||||
|
if (UTILS_LIKELY(success)) {
|
||||||
|
program = token->gl.program;
|
||||||
|
// no need to keep the shaders around
|
||||||
|
UTILS_NOUNROLL
|
||||||
|
for (GLuint& shader: token->gl.shaders) {
|
||||||
|
if (shader) {
|
||||||
|
glDetachShader(program, shader);
|
||||||
|
glDeleteShader(shader);
|
||||||
|
shader = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// and destroy all temporary init data
|
||||||
|
token = nullptr;
|
||||||
|
|
||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderCompilerService::ensureTokenIsReady(program_token_t const& token) {
|
|
||||||
if (token->gl.program) {
|
|
||||||
return;// It's ready.
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (mMode) {
|
|
||||||
case Mode::THREAD_POOL: {
|
|
||||||
// We need this program right now, make sure the job is finished.
|
|
||||||
if (auto job = mCompilerThreadPool.dequeue(token)) {
|
|
||||||
job();// The job hasn't started yet, so execute it now.
|
|
||||||
}
|
|
||||||
|
|
||||||
// This may block if the job was already taken by a thread ahead of the `dequeue`
|
|
||||||
// above and currently being executed. Otherwise, the job must have already been
|
|
||||||
// completed by this point from either the code above or the other thread.
|
|
||||||
token->wait();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Mode::ASYNCHRONOUS: {
|
|
||||||
// Technically the shader compilation may not have finished yet. To deal with the case,
|
|
||||||
// ideally, we should wait here until the compilation is finished. However, for now, we
|
|
||||||
// just log warnings here instead of repeatedly checking compile status. If this turns
|
|
||||||
// out to be a real issue later, we would need to consider doing the canonical way.
|
|
||||||
if (!isCompileCompleted(token)) {
|
|
||||||
slog.w << "Shader compilation for OpenGL program " << token->name.c_str_safe()
|
|
||||||
<< " is not completed yet. The following program link may not succeed.";
|
|
||||||
}
|
|
||||||
|
|
||||||
linkProgram(mDriver.getContext(), token);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Mode::SYNCHRONOUS: {
|
|
||||||
// We must not have called the TickOp yet until now. Call now to have
|
|
||||||
// `token->gl.program` ready to use.
|
|
||||||
tick();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Mode::UNDEFINED: {
|
|
||||||
assert_invariant(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
void ShaderCompilerService::runAtNextTick(CompilerPriorityQueue priority,
|
|
||||||
program_token_t const& token, Job job) noexcept {
|
|
||||||
// insert items in order of priority and at the end of the range
|
|
||||||
auto& ops = mRunAtNextTickOps;
|
|
||||||
auto const pos = std::lower_bound(ops.begin(), ops.end(), priority,
|
|
||||||
[](ContainerType const& lhs, CompilerPriorityQueue const priorityQueue) {
|
|
||||||
return std::get<0>(lhs) < priorityQueue;
|
|
||||||
});
|
|
||||||
ops.emplace(pos, priority, token, std::move(job));
|
|
||||||
|
|
||||||
SYSTRACE_CONTEXT();
|
|
||||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", mRunAtNextTickOps.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderCompilerService::cancelTickOp(program_token_t const& token) noexcept {
|
|
||||||
// We do a linear search here, but this is rare, and we know the list is pretty small.
|
|
||||||
auto& ops = mRunAtNextTickOps;
|
|
||||||
auto const pos = std::find_if(ops.begin(), ops.end(), [&](const auto& item) {
|
|
||||||
return std::get<1>(item) == token;
|
|
||||||
});
|
|
||||||
if (pos != ops.end()) {
|
|
||||||
ops.erase(pos);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
SYSTRACE_CONTEXT();
|
|
||||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderCompilerService::executeTickOps() noexcept {
|
|
||||||
auto& ops = mRunAtNextTickOps;
|
|
||||||
auto it = ops.begin();
|
|
||||||
while (it != ops.end()) {
|
|
||||||
Job const& job = std::get<2>(*it);
|
|
||||||
bool const remove = job.fn(job);
|
|
||||||
if (remove) {
|
|
||||||
it = ops.erase(it);
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SYSTRACE_CONTEXT();
|
|
||||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compile shaders in the ShaderSource. This cannot fail because compilation failures are not
|
||||||
|
* checked until after the program is linked.
|
||||||
|
* This always returns the GL shader IDs or zero a shader stage is not present.
|
||||||
|
*/
|
||||||
/* static */ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
/* static */ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||||
Program::ShaderSource shadersSource,
|
Program::ShaderSource shadersSource,
|
||||||
FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||||
bool multiview, program_token_t const& token) noexcept {
|
bool multiview,
|
||||||
|
shaders_t& outShaders,
|
||||||
|
UTILS_UNUSED_IN_RELEASE shaders_source_t& outShaderSourceCode) noexcept {
|
||||||
|
|
||||||
SYSTRACE_CALL();
|
SYSTRACE_CALL();
|
||||||
|
|
||||||
auto const appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
|
auto appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
|
||||||
s += "#define SPIRV_CROSS_CONSTANT_ID_" + std::to_string(sc.id) + ' ';
|
s += "#define SPIRV_CROSS_CONSTANT_ID_" + std::to_string(sc.id) + ' ';
|
||||||
s += std::visit([](auto&& arg) { return to_string(arg); }, sc.value);
|
s += std::visit([](auto&& arg) { return to_string(arg); }, sc.value);
|
||||||
s += '\n';
|
s += '\n';
|
||||||
@@ -504,7 +558,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
|||||||
|
|
||||||
std::string specializationConstantString;
|
std::string specializationConstantString;
|
||||||
int32_t numViews = 2;
|
int32_t numViews = 2;
|
||||||
for (auto const& sc: specializationConstants) {
|
for (auto const& sc : specializationConstants) {
|
||||||
appendSpecConstantString(specializationConstantString, sc);
|
appendSpecConstantString(specializationConstantString, sc);
|
||||||
if (sc.id == 8) {
|
if (sc.id == 8) {
|
||||||
// This constant must match
|
// This constant must match
|
||||||
@@ -542,7 +596,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
|||||||
if (UTILS_LIKELY(!shadersSource[i].empty())) {
|
if (UTILS_LIKELY(!shadersSource[i].empty())) {
|
||||||
Program::ShaderBlob& shader = shadersSource[i];
|
Program::ShaderBlob& shader = shadersSource[i];
|
||||||
char* shader_src = reinterpret_cast<char*>(shader.data());
|
char* shader_src = reinterpret_cast<char*>(shader.data());
|
||||||
size_t const shader_len = shader.size();
|
size_t shader_len = shader.size();
|
||||||
|
|
||||||
// remove GOOGLE_cpp_style_line_directive
|
// remove GOOGLE_cpp_style_line_directive
|
||||||
process_GOOGLE_cpp_style_line_directive(context, shader_src, shader_len);
|
process_GOOGLE_cpp_style_line_directive(context, shader_src, shader_len);
|
||||||
@@ -565,191 +619,178 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::array<std::string_view, 5> sources = {
|
std::array<std::string_view, 5> sources = {
|
||||||
version, prolog, specializationConstantString, packingFunctions,
|
version,
|
||||||
{ body.data(), body.size() - 1 }// null-terminated
|
prolog,
|
||||||
|
specializationConstantString,
|
||||||
|
packingFunctions,
|
||||||
|
{ body.data(), body.size() - 1 } // null-terminated
|
||||||
};
|
};
|
||||||
|
|
||||||
// Some of the sources may be zero-length. Remove them as to avoid passing lengths of
|
// Some of the sources may be zero-length. Remove them as to avoid passing lengths of
|
||||||
// zero to glShaderSource(). glShaderSource should work with lengths of zero, but some
|
// zero to glShaderSource(). glShaderSource should work with lengths of zero, but some
|
||||||
// drivers instead interpret zero as a sentinel for a null-terminated string.
|
// drivers instead interpret zero as a sentinel for a null-terminated string.
|
||||||
auto const partitionPoint = std::stable_partition(sources.begin(), sources.end(),
|
auto partitionPoint = std::stable_partition(
|
||||||
[](std::string_view s) { return !s.empty(); });
|
sources.begin(), sources.end(), [](std::string_view s) { return !s.empty(); });
|
||||||
size_t const count = std::distance(sources.begin(), partitionPoint);
|
size_t count = std::distance(sources.begin(), partitionPoint);
|
||||||
|
|
||||||
std::array<const char*, 5> shaderStrings;
|
std::array<const char*, 5> shaderStrings;
|
||||||
std::array<GLint, 5> lengths;
|
std::array<GLint, 5> lengths;
|
||||||
for (size_t j = 0; j < count; j++) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
shaderStrings[j] = sources[j].data();
|
shaderStrings[i] = sources[i].data();
|
||||||
lengths[j] = GLint(sources[j].size());
|
lengths[i] = sources[i].size();
|
||||||
}
|
}
|
||||||
|
|
||||||
GLuint const shaderId = glCreateShader(glShaderType);
|
GLuint const shaderId = glCreateShader(glShaderType);
|
||||||
glShaderSource(shaderId, GLsizei(count), shaderStrings.data(), lengths.data());
|
glShaderSource(shaderId, count, shaderStrings.data(), lengths.data());
|
||||||
|
|
||||||
glCompileShader(shaderId);
|
glCompileShader(shaderId);
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
// for debugging we return the original shader source (without the modifications we
|
// for debugging we return the original shader source (without the modifications we
|
||||||
// made here), otherwise the line numbers wouldn't match.
|
// made here), otherwise the line numbers wouldn't match.
|
||||||
token->shaderSourceCode[i] = { shader_src, shader_len };
|
outShaderSourceCode[i] = { shader_src, shader_len };
|
||||||
#endif
|
#endif
|
||||||
token->gl.shaders[i] = shaderId;
|
|
||||||
|
outShaders[i] = shaderId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* static */ bool ShaderCompilerService::isCompileCompleted(program_token_t const& token) noexcept {
|
/*
|
||||||
GLenum param = GL_COMPLETION_STATUS;
|
* Create a program from the given shader IDs and links it. This cannot fail because errors
|
||||||
if (UTILS_UNLIKELY(token->compiler.mMode != Mode::ASYNCHRONOUS)) {
|
* are checked later. This always returns a valid GL program ID (which doesn't mean the
|
||||||
param = GL_COMPILE_STATUS;
|
* program itself is valid).
|
||||||
}
|
*/
|
||||||
|
/* static */ GLuint ShaderCompilerService::linkProgram(OpenGLContext& context,
|
||||||
|
shaders_t const& shaders,
|
||||||
|
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept {
|
||||||
|
|
||||||
for (auto shader: token->gl.shaders) {
|
|
||||||
if (!shader) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
GLint status;
|
|
||||||
glGetShaderiv(shader, param, &status);
|
|
||||||
if (status == GL_FALSE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* static */ void ShaderCompilerService::checkCompileStatus(program_token_t const& token) noexcept {
|
|
||||||
SYSTRACE_CALL();
|
SYSTRACE_CALL();
|
||||||
|
|
||||||
UTILS_NOUNROLL
|
|
||||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
|
||||||
const GLuint shader = token->gl.shaders[i];
|
|
||||||
if (!shader) {
|
|
||||||
continue;// We're not using this shader stage.
|
|
||||||
}
|
|
||||||
// GL_COMPILE_STATUS may block until the compilation is completed.
|
|
||||||
GLint status;
|
|
||||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
|
||||||
if (UTILS_LIKELY(status == GL_TRUE)) {
|
|
||||||
continue;// Succeeded in compilation.
|
|
||||||
}
|
|
||||||
// Something went wrong. Log the error message.
|
|
||||||
const ShaderStage type = static_cast<ShaderStage>(i);
|
|
||||||
logCompilationError(slog.e, type, token->name.c_str_safe(), shader,
|
|
||||||
token->shaderSourceCode[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* static */ void ShaderCompilerService::linkProgram(OpenGLContext const& context,
|
|
||||||
program_token_t const& token) noexcept {
|
|
||||||
SYSTRACE_CALL();
|
|
||||||
|
|
||||||
// Shader compilation should be completed by now. Check the status and log errors on failure.
|
|
||||||
checkCompileStatus(token);
|
|
||||||
|
|
||||||
// Link program
|
|
||||||
GLuint const program = glCreateProgram();
|
GLuint const program = glCreateProgram();
|
||||||
for (auto const shader: token->gl.shaders) {
|
for (auto shader : shaders) {
|
||||||
if (shader) {
|
if (shader) {
|
||||||
glAttachShader(program, shader);
|
glAttachShader(program, shader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UTILS_UNLIKELY(context.isES2())) {
|
if (UTILS_UNLIKELY(context.isES2())) {
|
||||||
for (auto const& [name, loc]: token->attributes) {
|
for (auto const& [ name, loc ] : attributes) {
|
||||||
glBindAttribLocation(program, loc, name.c_str());
|
glBindAttribLocation(program, loc, name.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glLinkProgram(program);
|
glLinkProgram(program);
|
||||||
token->gl.program = program;
|
|
||||||
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* static */ bool ShaderCompilerService::isLinkCompleted(program_token_t const& token) noexcept {
|
// ------------------------------------------------------------------------------------------------
|
||||||
assert_invariant(token->gl.program);
|
|
||||||
|
|
||||||
GLenum param = GL_COMPLETION_STATUS;
|
void ShaderCompilerService::runAtNextTick(CompilerPriorityQueue priority,
|
||||||
if (UTILS_UNLIKELY(token->compiler.mMode != Mode::ASYNCHRONOUS)) {
|
const program_token_t& token, Job job) noexcept {
|
||||||
param = GL_LINK_STATUS;
|
// insert items in order of priority and at the end of the range
|
||||||
|
auto& ops = mRunAtNextTickOps;
|
||||||
|
auto const pos = std::lower_bound(ops.begin(), ops.end(), priority,
|
||||||
|
[](ContainerType const& lhs, CompilerPriorityQueue priorityQueue) {
|
||||||
|
return std::get<0>(lhs) < priorityQueue;
|
||||||
|
});
|
||||||
|
ops.emplace(pos, priority, token, std::move(job));
|
||||||
|
|
||||||
|
SYSTRACE_CONTEXT();
|
||||||
|
SYSTRACE_VALUE32("ShaderCompilerService Jobs", mRunAtNextTickOps.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderCompilerService::cancelTickOp(program_token_t token) noexcept {
|
||||||
|
// We do a linear search here, but this is rare, and we know the list is pretty small.
|
||||||
|
auto& ops = mRunAtNextTickOps;
|
||||||
|
auto pos = std::find_if(ops.begin(), ops.end(), [&](const auto& item) {
|
||||||
|
return std::get<1>(item) == token;
|
||||||
|
});
|
||||||
|
if (pos != ops.end()) {
|
||||||
|
ops.erase(pos);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
SYSTRACE_CONTEXT();
|
||||||
GLint status;
|
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
|
||||||
glGetProgramiv(token->gl.program, param, &status);
|
return false;
|
||||||
return (status == GL_TRUE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* static */ bool ShaderCompilerService::checkLinkStatusAndCleanupShaders(
|
void ShaderCompilerService::executeTickOps() noexcept {
|
||||||
program_token_t const& token) noexcept {
|
auto& ops = mRunAtNextTickOps;
|
||||||
|
auto it = ops.begin();
|
||||||
|
while (it != ops.end()) {
|
||||||
|
Job const& job = std::get<2>(*it);
|
||||||
|
bool const remove = job.fn(job);
|
||||||
|
if (remove) {
|
||||||
|
it = ops.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SYSTRACE_CONTEXT();
|
||||||
|
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks a program link status and logs errors and frees resources on failure.
|
||||||
|
* Returns true on success.
|
||||||
|
*/
|
||||||
|
/* static */ bool ShaderCompilerService::checkProgramStatus(program_token_t const& token) noexcept {
|
||||||
|
|
||||||
SYSTRACE_CALL();
|
SYSTRACE_CALL();
|
||||||
|
|
||||||
assert_invariant(token->gl.program);
|
assert_invariant(token->gl.program);
|
||||||
|
|
||||||
bool linked = true;
|
|
||||||
GLint status;
|
GLint status;
|
||||||
// GL_LINK_STATUS may block until the link is completed.
|
|
||||||
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
|
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
|
||||||
if (UTILS_UNLIKELY(status != GL_TRUE)) {
|
if (UTILS_LIKELY(status == GL_TRUE)) {
|
||||||
// Something went wrong. Log the error message.
|
return true;
|
||||||
logProgramLinkError(slog.e, token->name.c_str_safe(), token->gl.program);
|
|
||||||
linked = false;
|
|
||||||
}
|
}
|
||||||
// No need to keep the shaders around regardless of the result of the program linking.
|
|
||||||
|
// only if the link fails, we check the compilation status
|
||||||
UTILS_NOUNROLL
|
UTILS_NOUNROLL
|
||||||
for (GLuint& shader: token->gl.shaders) {
|
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
||||||
|
const ShaderStage type = static_cast<ShaderStage>(i);
|
||||||
|
const GLuint shader = token->gl.shaders[i];
|
||||||
if (shader) {
|
if (shader) {
|
||||||
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||||
|
if (status != GL_TRUE) {
|
||||||
|
logCompilationError(slog.e, type,
|
||||||
|
token->name.c_str_safe(), shader, token->shaderSourceCode[i]);
|
||||||
|
}
|
||||||
glDetachShader(token->gl.program, shader);
|
glDetachShader(token->gl.program, shader);
|
||||||
glDeleteShader(shader);
|
glDeleteShader(shader);
|
||||||
shader = 0;
|
token->gl.shaders[i] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return linked;
|
// log the link error as well
|
||||||
}
|
logProgramLinkError(slog.e, token->name.c_str_safe(), token->gl.program);
|
||||||
|
glDeleteProgram(token->gl.program);
|
||||||
/* static */ void ShaderCompilerService::tryCachingProgram(OpenGLBlobCache& cache,
|
token->gl.program = 0;
|
||||||
OpenGLPlatform& platform, program_token_t const& token) noexcept {
|
return false;
|
||||||
if (!token->key || !token->gl.program) {
|
|
||||||
return; // Invalid params
|
|
||||||
}
|
|
||||||
GLint status = GL_FALSE;
|
|
||||||
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
|
|
||||||
if (status == GL_FALSE) {
|
|
||||||
return;// Link failure
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.insert(platform, token->key, token->gl.program);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* static */ void ShaderCompilerService::cleanupProgramAndShaders(
|
|
||||||
program_token_t const& token) noexcept {
|
|
||||||
for (GLuint& shader: token->gl.shaders) {
|
|
||||||
if (!shader) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (token->gl.program) {
|
|
||||||
glDetachShader(token->gl.program, shader);
|
|
||||||
}
|
|
||||||
glDeleteShader(shader);
|
|
||||||
shader = 0;
|
|
||||||
}
|
|
||||||
if (token->gl.program) {
|
|
||||||
glDeleteProgram(token->gl.program);
|
|
||||||
token->gl.program = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
UTILS_NOINLINE
|
UTILS_NOINLINE
|
||||||
/* static */ void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
|
/* static */ void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
|
||||||
GLuint const shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
|
GLuint shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
|
||||||
|
|
||||||
{ // scope for the temporary string storage
|
auto to_string = [](ShaderStage type) -> const char* {
|
||||||
auto to_string = [](ShaderStage type) -> const char* {
|
switch (type) {
|
||||||
switch (type) {
|
case ShaderStage::VERTEX:
|
||||||
case ShaderStage::VERTEX:
|
return "vertex";
|
||||||
return "vertex";
|
case ShaderStage::FRAGMENT:
|
||||||
case ShaderStage::FRAGMENT:
|
return "fragment";
|
||||||
return "fragment";
|
case ShaderStage::COMPUTE:
|
||||||
case ShaderStage::COMPUTE:
|
return "compute";
|
||||||
return "compute";
|
}
|
||||||
}
|
};
|
||||||
return "unknown";
|
|
||||||
};
|
|
||||||
|
|
||||||
|
{// scope for the temporary string storage
|
||||||
GLint length = 0;
|
GLint length = 0;
|
||||||
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &length);
|
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &length);
|
||||||
|
|
||||||
@@ -796,8 +837,8 @@ UTILS_NOINLINE
|
|||||||
|
|
||||||
// If usages of the Google-style line directive are present, remove them, as some
|
// If usages of the Google-style line directive are present, remove them, as some
|
||||||
// drivers don't allow the quotation marks. This source modification happens in-place.
|
// drivers don't allow the quotation marks. This source modification happens in-place.
|
||||||
/* static */ void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context,
|
/* static */ void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, char* source,
|
||||||
char* source, size_t len) noexcept {
|
size_t len) noexcept {
|
||||||
if (!context.ext.GOOGLE_cpp_style_line_directive) {
|
if (!context.ext.GOOGLE_cpp_style_line_directive) {
|
||||||
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) {
|
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) {
|
||||||
removeGoogleLineDirectives(source, len);// length is unaffected
|
removeGoogleLineDirectives(source, len);// length is unaffected
|
||||||
@@ -809,13 +850,13 @@ UTILS_NOINLINE
|
|||||||
// necessary for OpenGL because OpenGL relies on the number specified in shader files to determine
|
// necessary for OpenGL because OpenGL relies on the number specified in shader files to determine
|
||||||
// the number of views, which is assumed as a single digit, for multiview.
|
// the number of views, which is assumed as a single digit, for multiview.
|
||||||
// This source modification happens in-place.
|
// This source modification happens in-place.
|
||||||
/* static */ void process_OVR_multiview2(OpenGLContext const& context, int32_t const eyeCount,
|
/* static */ void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount, char* source,
|
||||||
char* source, size_t const len) noexcept {
|
size_t len) noexcept {
|
||||||
// We don't use regular expression in favor of performance.
|
// We don't use regular expression in favor of performance.
|
||||||
if (context.ext.OVR_multiview2) {
|
if (context.ext.OVR_multiview2) {
|
||||||
const std::string_view shader{ source, len };
|
const std::string_view shader{ source, len };
|
||||||
constexpr std::string_view layout = "layout";
|
const std::string_view layout = "layout";
|
||||||
constexpr std::string_view num_views = "num_views";
|
const std::string_view num_views = "num_views";
|
||||||
size_t found = 0;
|
size_t found = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
found = shader.find(layout, found);
|
found = shader.find(layout, found);
|
||||||
@@ -970,20 +1011,20 @@ mediump vec4 unpackSnorm4x8(highp uint v) {
|
|||||||
// - extensions
|
// - extensions
|
||||||
// - everything else
|
// - everything else
|
||||||
/* static */ std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept {
|
/* static */ std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept {
|
||||||
auto const version_start = source.find("#version");
|
auto version_start = source.find("#version");
|
||||||
assert_invariant(version_start != std::string_view::npos);
|
assert_invariant(version_start != std::string_view::npos);
|
||||||
|
|
||||||
auto const version_eol = source.find('\n', version_start) + 1;
|
auto version_eol = source.find('\n', version_start) + 1;
|
||||||
assert_invariant(version_eol != std::string_view::npos);
|
assert_invariant(version_eol != std::string_view::npos);
|
||||||
|
|
||||||
auto const prolog_start = version_eol;
|
auto prolog_start = version_eol;
|
||||||
auto prolog_eol = source.rfind("\n#extension");// last #extension line
|
auto prolog_eol = source.rfind("\n#extension");// last #extension line
|
||||||
if (prolog_eol == std::string_view::npos) {
|
if (prolog_eol == std::string_view::npos) {
|
||||||
prolog_eol = prolog_start;
|
prolog_eol = prolog_start;
|
||||||
} else {
|
} else {
|
||||||
prolog_eol = source.find('\n', prolog_eol + 1) + 1;
|
prolog_eol = source.find('\n', prolog_eol + 1) + 1;
|
||||||
}
|
}
|
||||||
auto const body_start = prolog_eol;
|
auto body_start = prolog_eol;
|
||||||
|
|
||||||
std::string_view const version = source.substr(version_start, version_eol - version_start);
|
std::string_view const version = source.substr(version_start, version_eol - version_start);
|
||||||
std::string_view const prolog = source.substr(prolog_start, prolog_eol - prolog_start);
|
std::string_view const prolog = source.substr(prolog_start, prolog_eol - prolog_start);
|
||||||
|
|||||||
@@ -24,23 +24,23 @@
|
|||||||
#include "OpenGLBlobCache.h"
|
#include "OpenGLBlobCache.h"
|
||||||
|
|
||||||
#include <backend/CallbackHandler.h>
|
#include <backend/CallbackHandler.h>
|
||||||
#include <backend/DriverEnums.h>
|
|
||||||
#include <backend/Program.h>
|
#include <backend/Program.h>
|
||||||
|
|
||||||
#include <utils/CString.h>
|
#include <utils/CString.h>
|
||||||
#include <utils/FixedCapacityVector.h>
|
#include <utils/FixedCapacityVector.h>
|
||||||
|
#include <utils/Invocable.h>
|
||||||
#include <utils/JobSystem.h>
|
#include <utils/JobSystem.h>
|
||||||
|
|
||||||
#include <array>
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <deque>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <tuple>
|
#include <thread>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
class OpenGLDriver;
|
class OpenGLDriver;
|
||||||
@@ -84,7 +84,6 @@ public:
|
|||||||
void tick();
|
void tick();
|
||||||
|
|
||||||
// Destroys a valid token and all associated resources. Used to "cancel" a program compilation.
|
// Destroys a valid token and all associated resources. Used to "cancel" a program compilation.
|
||||||
// This function is not called if `initialize(token)` is already invoked.
|
|
||||||
static void terminate(program_token_t& token);
|
static void terminate(program_token_t& token);
|
||||||
|
|
||||||
// stores a user data pointer in the token
|
// stores a user data pointer in the token
|
||||||
@@ -93,12 +92,6 @@ public:
|
|||||||
// retrieves the user data pointer stored in the token
|
// retrieves the user data pointer stored in the token
|
||||||
static void* getUserData(const program_token_t& token) noexcept;
|
static void* getUserData(const program_token_t& token) noexcept;
|
||||||
|
|
||||||
// Issue one callback handle.
|
|
||||||
CallbackManager::Handle issueCallbackHandle() const noexcept;
|
|
||||||
|
|
||||||
// Return a callback handle to the callback manager.
|
|
||||||
void submitCallbackHandle(CallbackManager::Handle handle) noexcept;
|
|
||||||
|
|
||||||
// call the callback when all active programs are ready
|
// call the callback when all active programs are ready
|
||||||
void notifyWhenAllProgramsAreReady(
|
void notifyWhenAllProgramsAreReady(
|
||||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
|
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
|
||||||
@@ -106,7 +99,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
struct Job {
|
struct Job {
|
||||||
template<typename FUNC>
|
template<typename FUNC>
|
||||||
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {} // NOLINT(*-explicit-constructor)
|
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {}
|
||||||
Job(std::function<bool(Job const& job)> fn,
|
Job(std::function<bool(Job const& job)> fn,
|
||||||
CallbackHandler* handler, void* user, CallbackHandler::Callback callback)
|
CallbackHandler* handler, void* user, CallbackHandler::Callback callback)
|
||||||
: fn(std::move(fn)), handler(handler), user(user), callback(callback) {
|
: fn(std::move(fn)), handler(handler), user(user), callback(callback) {
|
||||||
@@ -135,49 +128,26 @@ private:
|
|||||||
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
|
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
|
||||||
std::vector<ContainerType> mRunAtNextTickOps;
|
std::vector<ContainerType> mRunAtNextTickOps;
|
||||||
|
|
||||||
GLuint initialize(program_token_t& token);
|
GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept;
|
||||||
void ensureTokenIsReady(program_token_t const& token);
|
|
||||||
|
|
||||||
void runAtNextTick(CompilerPriorityQueue priority, program_token_t const& token,
|
static void getProgramFromCompilerPool(program_token_t& token) noexcept;
|
||||||
Job job) noexcept;
|
|
||||||
|
static void compileShaders(
|
||||||
|
OpenGLContext& context,
|
||||||
|
Program::ShaderSource shadersSource,
|
||||||
|
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||||
|
bool multiview, shaders_t& outShaders, shaders_source_t& outShaderSourceCode) noexcept;
|
||||||
|
|
||||||
|
static GLuint linkProgram(OpenGLContext& context, shaders_t const& shaders,
|
||||||
|
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept;
|
||||||
|
|
||||||
|
static bool checkProgramStatus(program_token_t const& token) noexcept;
|
||||||
|
|
||||||
|
void runAtNextTick(CompilerPriorityQueue priority,
|
||||||
|
const program_token_t& token, Job job) noexcept;
|
||||||
void executeTickOps() noexcept;
|
void executeTickOps() noexcept;
|
||||||
bool cancelTickOp(program_token_t const& token) noexcept;
|
bool cancelTickOp(program_token_t token) noexcept;
|
||||||
|
// order of insertion is important
|
||||||
// Compile shaders with the given `shaderSource`. `gl.shaders` is always populated with valid
|
|
||||||
// shader IDs after this method. But this doesn't necessarily mean the shaders are successfully
|
|
||||||
// compiled. Errors can be checked by calling `checkCompileStatus` later.
|
|
||||||
static void compileShaders(OpenGLContext& context, Program::ShaderSource shadersSource,
|
|
||||||
utils::FixedCapacityVector<Program::SpecializationConstant> const&
|
|
||||||
specializationConstants,
|
|
||||||
bool multiview, program_token_t const& token) noexcept;
|
|
||||||
|
|
||||||
// Check if the shader compilation is completed. You may want to call this when the extension
|
|
||||||
// `KHR_parallel_shader_compile` is enabled.
|
|
||||||
static bool isCompileCompleted(program_token_t const& token) noexcept;
|
|
||||||
|
|
||||||
// Check compilation status of the shaders and log errors on failure.
|
|
||||||
static void checkCompileStatus(program_token_t const& token) noexcept;
|
|
||||||
|
|
||||||
// Create a program by linking the compiled shaders. `gl.program` is always populated with a
|
|
||||||
// valid program ID after this method. But this doesn't necessarily mean the program is
|
|
||||||
// successfully linked. Errors can be checked by calling `checkLinkStatusAndCleanupShaders`
|
|
||||||
// later.
|
|
||||||
static void linkProgram(OpenGLContext const& context, program_token_t const& token) noexcept;
|
|
||||||
|
|
||||||
// Check if the program link is completed. You may want to call this when the extension
|
|
||||||
// `KHR_parallel_shader_compile` is enabled.
|
|
||||||
static bool isLinkCompleted(program_token_t const& token) noexcept;
|
|
||||||
|
|
||||||
// Check link status of the program and log errors on failure. Return the result of the link.
|
|
||||||
// Also cleanup shaders regardless of the result.
|
|
||||||
static bool checkLinkStatusAndCleanupShaders(program_token_t const& token) noexcept;
|
|
||||||
|
|
||||||
// Try caching the program if we haven't done it yet. Cache it only when the program is valid.
|
|
||||||
static void tryCachingProgram(OpenGLBlobCache& cache, OpenGLPlatform& platform,
|
|
||||||
program_token_t const& token) noexcept;
|
|
||||||
|
|
||||||
// Cleanup GL resources.
|
|
||||||
static void cleanupProgramAndShaders(program_token_t const& token) noexcept;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <backend/BufferDescriptor.h>
|
#include <backend/BufferDescriptor.h>
|
||||||
#include <backend/BufferObjectStreamDescriptor.h>
|
|
||||||
#include <backend/DescriptorSetOffsetArray.h>
|
#include <backend/DescriptorSetOffsetArray.h>
|
||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
#include <backend/PipelineState.h>
|
#include <backend/PipelineState.h>
|
||||||
@@ -438,10 +437,6 @@ io::ostream& operator<<(io::ostream& out, BufferDescriptor const& b) {
|
|||||||
<< ", user=" << b.getUser() << " }";
|
<< ", user=" << b.getUser() << " }";
|
||||||
}
|
}
|
||||||
|
|
||||||
io::ostream& operator<<(io::ostream& out, const BufferObjectStreamDescriptor& b) {
|
|
||||||
return out << "BufferObjectStreamDescriptor{ streams(" << b.mStreams.size() << ")=... }";
|
|
||||||
}
|
|
||||||
|
|
||||||
io::ostream& operator<<(io::ostream& out, PixelBufferDescriptor const& b) {
|
io::ostream& operator<<(io::ostream& out, PixelBufferDescriptor const& b) {
|
||||||
BufferDescriptor const& base = static_cast<BufferDescriptor const&>(b);
|
BufferDescriptor const& base = static_cast<BufferDescriptor const&>(b);
|
||||||
return out << "PixelBufferDescriptor{ " << base
|
return out << "PixelBufferDescriptor{ " << base
|
||||||
|
|||||||
@@ -152,12 +152,12 @@ static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
|
|||||||
#elif FVK_ENABLED(FVK_DEBUG_SYSTRACE)
|
#elif FVK_ENABLED(FVK_DEBUG_SYSTRACE)
|
||||||
|
|
||||||
#include <utils/Systrace.h>
|
#include <utils/Systrace.h>
|
||||||
|
|
||||||
#define FVK_SYSTRACE_CONTEXT() SYSTRACE_CONTEXT()
|
#define FVK_SYSTRACE_CONTEXT() SYSTRACE_CONTEXT()
|
||||||
#define FVK_SYSTRACE_START(marker) SYSTRACE_NAME_BEGIN(marker)
|
#define FVK_SYSTRACE_START(marker) SYSTRACE_NAME_BEGIN(marker)
|
||||||
#define FVK_SYSTRACE_END() SYSTRACE_NAME_END()
|
#define FVK_SYSTRACE_END() SYSTRACE_NAME_END()
|
||||||
#define FVK_SYSTRACE_SCOPE() SYSTRACE_CALL()
|
#define FVK_SYSTRACE_SCOPE() SYSTRACE_NAME(__func__)
|
||||||
#define FVK_PROFILE_MARKER(marker) SYSTRACE_CALL()
|
#define FVK_PROFILE_MARKER(marker) FVK_SYSTRACE_SCOPE()
|
||||||
|
|
||||||
#else
|
#else
|
||||||
#define FVK_SYSTRACE_CONTEXT()
|
#define FVK_SYSTRACE_CONTEXT()
|
||||||
|
|||||||
@@ -35,12 +35,7 @@ using namespace bluevk;
|
|||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
VulkanPipelineCache::VulkanPipelineCache(VkDevice device)
|
VulkanPipelineCache::VulkanPipelineCache(VkDevice device)
|
||||||
: mDevice(device) {
|
: mDevice(device) {}
|
||||||
VkPipelineCacheCreateInfo createInfo = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
|
|
||||||
};
|
|
||||||
bluevk::vkCreatePipelineCache(mDevice, &createInfo, VKALLOC, &mPipelineCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanPipelineCache::bindLayout(VkPipelineLayout layout) noexcept {
|
void VulkanPipelineCache::bindLayout(VkPipelineLayout layout) noexcept {
|
||||||
mPipelineRequirements.layout = layout;
|
mPipelineRequirements.layout = layout;
|
||||||
@@ -220,7 +215,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
|||||||
PipelineCacheEntry cacheEntry = {
|
PipelineCacheEntry cacheEntry = {
|
||||||
.lastUsed = mCurrentTime,
|
.lastUsed = mCurrentTime,
|
||||||
};
|
};
|
||||||
VkResult error = vkCreateGraphicsPipelines(mDevice, mPipelineCache, 1, &pipelineCreateInfo,
|
VkResult error = vkCreateGraphicsPipelines(mDevice, VK_NULL_HANDLE, 1, &pipelineCreateInfo,
|
||||||
VKALLOC, &cacheEntry.handle);
|
VKALLOC, &cacheEntry.handle);
|
||||||
assert_invariant(error == VK_SUCCESS);
|
assert_invariant(error == VK_SUCCESS);
|
||||||
if (error != VK_SUCCESS) {
|
if (error != VK_SUCCESS) {
|
||||||
@@ -276,8 +271,6 @@ void VulkanPipelineCache::terminate() noexcept {
|
|||||||
}
|
}
|
||||||
mPipelines.clear();
|
mPipelines.clear();
|
||||||
mBoundPipeline = {};
|
mBoundPipeline = {};
|
||||||
|
|
||||||
vkDestroyPipelineCache(mDevice, mPipelineCache, VKALLOC);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanPipelineCache::gc() noexcept {
|
void VulkanPipelineCache::gc() noexcept {
|
||||||
|
|||||||
@@ -198,10 +198,6 @@ private:
|
|||||||
// Immutable state.
|
// Immutable state.
|
||||||
VkDevice mDevice = VK_NULL_HANDLE;
|
VkDevice mDevice = VK_NULL_HANDLE;
|
||||||
|
|
||||||
// Vuklan Driver pipeline cache handle. In the cases a pipeline has been evicted by the `gc`,
|
|
||||||
// recreating the same pipeline is cheaper, helping with frame stalling.
|
|
||||||
VkPipelineCache mPipelineCache = VK_NULL_HANDLE;
|
|
||||||
|
|
||||||
// Current requirements for the pipeline layout, pipeline, and descriptor sets.
|
// Current requirements for the pipeline layout, pipeline, and descriptor sets.
|
||||||
PipelineKey mPipelineRequirements = {};
|
PipelineKey mPipelineRequirements = {};
|
||||||
|
|
||||||
|
|||||||
@@ -1,150 +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 "WebGPUHandles.h"
|
|
||||||
|
|
||||||
#include "WebGPUConstants.h"
|
|
||||||
|
|
||||||
#include "DriverBase.h"
|
|
||||||
#include <backend/DriverEnums.h>
|
|
||||||
#include <backend/Program.h>
|
|
||||||
|
|
||||||
#include <utils/Panic.h>
|
|
||||||
#include <utils/ostream.h>
|
|
||||||
|
|
||||||
#include <webgpu/webgpu_cpp.h>
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace filament::backend {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr std::string_view toString(ShaderStage stage) {
|
|
||||||
switch (stage) {
|
|
||||||
case ShaderStage::VERTEX:
|
|
||||||
return "vertex";
|
|
||||||
case ShaderStage::FRAGMENT:
|
|
||||||
return "fragment";
|
|
||||||
case ShaderStage::COMPUTE:
|
|
||||||
return "compute";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] wgpu::ShaderModule createShaderModule(wgpu::Device& device, const char* programName,
|
|
||||||
std::array<utils::FixedCapacityVector<uint8_t>, Program::SHADER_TYPE_COUNT> const&
|
|
||||||
shaderSource,
|
|
||||||
ShaderStage stage) {
|
|
||||||
utils::FixedCapacityVector<uint8_t> const& sourceBytes =
|
|
||||||
shaderSource[static_cast<size_t>(stage)];
|
|
||||||
if (sourceBytes.empty()) {
|
|
||||||
return nullptr;// nothing to compile, the shader was not provided
|
|
||||||
}
|
|
||||||
wgpu::ShaderModuleWGSLDescriptor wgslDescriptor{};
|
|
||||||
wgslDescriptor.code = wgpu::StringView(reinterpret_cast<const char*>(sourceBytes.data()));
|
|
||||||
std::stringstream labelStream;
|
|
||||||
labelStream << programName << " " << toString(stage) << " shader";
|
|
||||||
auto label = labelStream.str();
|
|
||||||
wgpu::ShaderModuleDescriptor descriptor{
|
|
||||||
.nextInChain = &wgslDescriptor,
|
|
||||||
.label = label.data()
|
|
||||||
};
|
|
||||||
wgpu::ShaderModule module = device.CreateShaderModule(&descriptor);
|
|
||||||
FILAMENT_CHECK_POSTCONDITION(module != nullptr) << "Failed to create " << descriptor.label;
|
|
||||||
module.GetCompilationInfo(wgpu::CallbackMode::AllowSpontaneous,
|
|
||||||
[&descriptor](auto const& status, wgpu::CompilationInfo const* info) {
|
|
||||||
switch (status) {
|
|
||||||
case wgpu::CompilationInfoRequestStatus::CallbackCancelled:
|
|
||||||
FWGPU_LOGW << "Shader compilation info callback cancelled for "
|
|
||||||
<< descriptor.label << "?" << utils::io::endl;
|
|
||||||
return;
|
|
||||||
case wgpu::CompilationInfoRequestStatus::Success:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (info != nullptr) {
|
|
||||||
std::stringstream errorStream;
|
|
||||||
int errorCount = 0;
|
|
||||||
for (size_t msgIndex = 0; msgIndex < info->messageCount; msgIndex++) {
|
|
||||||
wgpu::CompilationMessage const& message = info->messages[msgIndex];
|
|
||||||
switch (message.type) {
|
|
||||||
case wgpu::CompilationMessageType::Info:
|
|
||||||
FWGPU_LOGI << descriptor.label << ": " << message.message
|
|
||||||
<< " line#:" << message.lineNum
|
|
||||||
<< " linePos:" << message.linePos
|
|
||||||
<< " offset:" << message.offset
|
|
||||||
<< " length:" << message.length << utils::io::endl;
|
|
||||||
break;
|
|
||||||
case wgpu::CompilationMessageType::Warning:
|
|
||||||
FWGPU_LOGW << "Warning compiling " << descriptor.label << ": "
|
|
||||||
<< message.message << " line#:" << message.lineNum
|
|
||||||
<< " linePos:" << message.linePos
|
|
||||||
<< " offset:" << message.offset
|
|
||||||
<< " length:" << message.length << utils::io::endl;
|
|
||||||
break;
|
|
||||||
case wgpu::CompilationMessageType::Error:
|
|
||||||
errorCount++;
|
|
||||||
errorStream << "Error " << errorCount << " : "
|
|
||||||
<< std::string_view(message.message)
|
|
||||||
<< " line#:" << message.lineNum
|
|
||||||
<< " linePos:" << message.linePos
|
|
||||||
<< " offset:" << message.offset
|
|
||||||
<< " length:" << message.length << "\n";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FILAMENT_CHECK_POSTCONDITION(errorCount < 1)
|
|
||||||
<< errorCount << " error(s) compiling " << descriptor.label << ":\n"
|
|
||||||
<< errorStream.str();
|
|
||||||
}
|
|
||||||
FWGPU_LOGD << descriptor.label << " compiled successfully" << utils::io::endl;
|
|
||||||
});
|
|
||||||
return module;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<wgpu::ConstantEntry> convertConstants(
|
|
||||||
utils::FixedCapacityVector<filament::backend::Program::SpecializationConstant> const&
|
|
||||||
constantsInfo) {
|
|
||||||
std::vector<wgpu::ConstantEntry> constants(constantsInfo.size());
|
|
||||||
for (size_t i = 0; i < constantsInfo.size(); i++) {
|
|
||||||
filament::backend::Program::SpecializationConstant const& specConstant = constantsInfo[i];
|
|
||||||
wgpu::ConstantEntry& constantEntry = constants[i];
|
|
||||||
constantEntry.key = wgpu::StringView(std::to_string(specConstant.id));
|
|
||||||
if (auto* v = std::get_if<int32_t>(&specConstant.value)) {
|
|
||||||
constantEntry.value = static_cast<double>(*v);
|
|
||||||
} else if (auto* f = std::get_if<float>(&specConstant.value)) {
|
|
||||||
constantEntry.value = static_cast<double>(*f);
|
|
||||||
} else if (auto* b = std::get_if<bool>(&specConstant.value)) {
|
|
||||||
constantEntry.value = *b ? 0.0 : 1.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return constants;
|
|
||||||
}
|
|
||||||
|
|
||||||
}// namespace
|
|
||||||
|
|
||||||
WGPUProgram::WGPUProgram(wgpu::Device& device, Program& program)
|
|
||||||
: HwProgram(program.getName()),
|
|
||||||
vertexShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
|
|
||||||
ShaderStage::VERTEX)),
|
|
||||||
fragmentShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
|
|
||||||
ShaderStage::FRAGMENT)),
|
|
||||||
computeShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
|
|
||||||
ShaderStage::COMPUTE)),
|
|
||||||
constants(convertConstants(program.getSpecializationConstants())) {}
|
|
||||||
|
|
||||||
}// namespace filament::backend
|
|
||||||
@@ -228,14 +228,6 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfi
|
|||||||
driverConfig.disableHeapHandleTags) {
|
driverConfig.disableHeapHandleTags) {
|
||||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||||
printInstanceDetails(mPlatform.getInstance());
|
printInstanceDetails(mPlatform.getInstance());
|
||||||
#endif
|
|
||||||
mAdapter = mPlatform.requestAdapter(nullptr);
|
|
||||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
|
||||||
printAdapterDetails(mAdapter);
|
|
||||||
#endif
|
|
||||||
mDevice = mPlatform.requestDevice(mAdapter);
|
|
||||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
|
||||||
printDeviceDetails(mDevice);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,9 +317,6 @@ void WebGPUDriver::destroyTexture(Handle<HwTexture> th) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::destroyProgram(Handle<HwProgram> ph) {
|
void WebGPUDriver::destroyProgram(Handle<HwProgram> ph) {
|
||||||
if (ph) {
|
|
||||||
destructHandle<WGPUProgram>(ph);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
|
void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
|
||||||
@@ -347,9 +336,6 @@ void WebGPUDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> tqh) {
|
void WebGPUDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> tqh) {
|
||||||
if (tqh) {
|
|
||||||
destructHandle<WebGPUDescriptorSetLayout>(tqh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) {
|
void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) {
|
||||||
@@ -372,7 +358,7 @@ Handle<HwTexture> WebGPUDriver::importTextureS() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
|
Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
|
||||||
return allocHandle<WGPUProgram>();
|
return Handle<HwProgram>((Handle<HwProgram>::HandleId) mNextFakeHandle++);
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
|
Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
|
||||||
@@ -424,7 +410,8 @@ Handle<HwRenderTarget> WebGPUDriver::createDefaultRenderTargetS() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwDescriptorSetLayout> WebGPUDriver::createDescriptorSetLayoutS() noexcept {
|
Handle<HwDescriptorSetLayout> WebGPUDriver::createDescriptorSetLayoutS() noexcept {
|
||||||
return allocHandle<WebGPUDescriptorSetLayout>();
|
return Handle<HwDescriptorSetLayout>(
|
||||||
|
(Handle<HwDescriptorSetLayout>::HandleId) mNextFakeHandle++);
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwTexture> WebGPUDriver::createTextureExternalImageS() noexcept {
|
Handle<HwTexture> WebGPUDriver::createTextureExternalImageS() noexcept {
|
||||||
@@ -443,7 +430,14 @@ void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
|
|||||||
mNativeWindow = nativeWindow;
|
mNativeWindow = nativeWindow;
|
||||||
assert_invariant(!mSwapChain);
|
assert_invariant(!mSwapChain);
|
||||||
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
|
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
|
||||||
|
mAdapter = mPlatform.requestAdapter(surface);
|
||||||
|
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||||
|
printAdapterDetails(mAdapter);
|
||||||
|
#endif
|
||||||
|
mDevice = mPlatform.requestDevice(mAdapter);
|
||||||
|
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||||
|
printDeviceDetails(mDevice);
|
||||||
|
#endif
|
||||||
mQueue = mDevice.GetQueue();
|
mQueue = mDevice.GetQueue();
|
||||||
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
|
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
|
||||||
mSwapChain = constructHandle<WebGPUSwapChain>(sch, std::move(surface), surfaceSize, mAdapter,
|
mSwapChain = constructHandle<WebGPUSwapChain>(sch, std::move(surface), surfaceSize, mAdapter,
|
||||||
@@ -508,9 +502,7 @@ void WebGPUDriver::importTextureR(Handle<HwTexture> th, intptr_t id, SamplerType
|
|||||||
void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph, Handle<HwVertexBuffer> vbh,
|
void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph, Handle<HwVertexBuffer> vbh,
|
||||||
Handle<HwIndexBuffer> ibh, PrimitiveType pt) {}
|
Handle<HwIndexBuffer> ibh, PrimitiveType pt) {}
|
||||||
|
|
||||||
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {}
|
||||||
constructHandle<WGPUProgram>(ph, mDevice, program);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
|
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
|
||||||
assert_invariant(!mDefaultRenderTarget);
|
assert_invariant(!mDefaultRenderTarget);
|
||||||
@@ -527,9 +519,7 @@ void WebGPUDriver::createFenceR(Handle<HwFence> fh, int) {}
|
|||||||
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
|
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
|
||||||
|
|
||||||
void WebGPUDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
|
void WebGPUDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
|
||||||
backend::DescriptorSetLayout&& info) {
|
backend::DescriptorSetLayout&& info) {}
|
||||||
constructHandle<WebGPUDescriptorSetLayout>(dslh, std::move(info), mDevice);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
||||||
Handle<HwDescriptorSetLayout> dslh) {}
|
Handle<HwDescriptorSetLayout> dslh) {}
|
||||||
@@ -807,7 +797,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> src,
|
|||||||
scheduleDestroy(std::move(p));
|
scheduleDestroy(std::move(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::readBufferSubData(Handle<HwBufferObject> boh,
|
void WebGPUDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
||||||
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
|
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
|
||||||
scheduleDestroy(std::move(p));
|
scheduleDestroy(std::move(p));
|
||||||
}
|
}
|
||||||
@@ -859,22 +849,22 @@ void WebGPUDriver::resetState(int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::updateDescriptorSetBuffer(
|
void WebGPUDriver::updateDescriptorSetBuffer(
|
||||||
Handle<HwDescriptorSet> dsh,
|
backend::DescriptorSetHandle dsh,
|
||||||
backend::descriptor_binding_t binding,
|
backend::descriptor_binding_t binding,
|
||||||
Handle<HwBufferObject> boh,
|
backend::BufferObjectHandle boh,
|
||||||
uint32_t offset,
|
uint32_t offset,
|
||||||
uint32_t size) {
|
uint32_t size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::updateDescriptorSetTexture(
|
void WebGPUDriver::updateDescriptorSetTexture(
|
||||||
Handle<HwDescriptorSet> dsh,
|
backend::DescriptorSetHandle dsh,
|
||||||
backend::descriptor_binding_t binding,
|
backend::descriptor_binding_t binding,
|
||||||
Handle<HwTexture> th,
|
backend::TextureHandle th,
|
||||||
SamplerParams params) {
|
SamplerParams params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::bindDescriptorSet(
|
void WebGPUDriver::bindDescriptorSet(
|
||||||
Handle<HwDescriptorSet> dsh,
|
backend::DescriptorSetHandle dsh,
|
||||||
backend::descriptor_set_t set,
|
backend::descriptor_set_t set,
|
||||||
backend::DescriptorSetOffsetArray&& offsets) {
|
backend::DescriptorSetOffsetArray&& offsets) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,93 +60,4 @@ void WGPUVertexBuffer::setBuffer(WGPUBufferObject* bufferObject, uint32_t index)
|
|||||||
WGPUBufferObject::WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount)
|
WGPUBufferObject::WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount)
|
||||||
: HwBufferObject(byteCount),
|
: HwBufferObject(byteCount),
|
||||||
bufferObjectBinding(bindingType) {}
|
bufferObjectBinding(bindingType) {}
|
||||||
|
|
||||||
wgpu::ShaderStage WebGPUDescriptorSetLayout::filamentStageToWGPUStage(ShaderStageFlags fFlags) {
|
|
||||||
wgpu::ShaderStage retStages = wgpu::ShaderStage::None;
|
|
||||||
if (any(ShaderStageFlags::VERTEX & fFlags)) {
|
|
||||||
retStages |= wgpu::ShaderStage::Vertex;
|
|
||||||
}
|
|
||||||
if (any(ShaderStageFlags::FRAGMENT & fFlags)) {
|
|
||||||
retStages |= wgpu::ShaderStage::Fragment;
|
|
||||||
}
|
|
||||||
if (any(ShaderStageFlags::COMPUTE & fFlags)) {
|
|
||||||
retStages |= wgpu::ShaderStage::Compute;
|
|
||||||
}
|
|
||||||
return retStages;
|
|
||||||
}
|
|
||||||
|
|
||||||
WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout,
|
|
||||||
wgpu::Device const& device) {
|
|
||||||
assert_invariant(device);
|
|
||||||
|
|
||||||
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
|
|
||||||
// debugging. For now, hack an incrementing value.
|
|
||||||
static int layoutNum = 0;
|
|
||||||
|
|
||||||
uint samplerCount =
|
|
||||||
std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) {
|
|
||||||
return fEntry.type == DescriptorType::SAMPLER ||
|
|
||||||
fEntry.type == DescriptorType::SAMPLER_EXTERNAL;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
std::vector<wgpu::BindGroupLayoutEntry> wEntries;
|
|
||||||
wEntries.reserve(layout.bindings.size() + samplerCount);
|
|
||||||
|
|
||||||
for (auto fEntry: layout.bindings) {
|
|
||||||
auto& wEntry = wEntries.emplace_back();
|
|
||||||
wEntry.visibility = filamentStageToWGPUStage(fEntry.stageFlags);
|
|
||||||
wEntry.binding = fEntry.binding * 2;
|
|
||||||
|
|
||||||
switch (fEntry.type) {
|
|
||||||
// TODO Metal treats these the same. Is this fine?
|
|
||||||
case DescriptorType::SAMPLER_EXTERNAL:
|
|
||||||
case DescriptorType::SAMPLER: {
|
|
||||||
// Sampler binding is 2n+1 due to split.
|
|
||||||
auto& samplerEntry = wEntries.emplace_back();
|
|
||||||
samplerEntry.binding = fEntry.binding * 2 + 1;
|
|
||||||
samplerEntry.visibility = wEntry.visibility;
|
|
||||||
// We are simply hoping that undefined and defaults suffices here.
|
|
||||||
samplerEntry.sampler.type = wgpu::SamplerBindingType::Undefined;
|
|
||||||
wEntry.texture.sampleType = wgpu::TextureSampleType::Undefined;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DescriptorType::UNIFORM_BUFFER: {
|
|
||||||
wEntry.buffer.hasDynamicOffset =
|
|
||||||
any(fEntry.flags & DescriptorFlags::DYNAMIC_OFFSET);
|
|
||||||
wEntry.buffer.type = wgpu::BufferBindingType::Uniform;
|
|
||||||
// TODO: Ideally we fill minBindingSize
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case DescriptorType::INPUT_ATTACHMENT: {
|
|
||||||
// TODO: support INPUT_ATTACHMENT. Metal does not currently.
|
|
||||||
PANIC_POSTCONDITION("Input Attachment is not supported");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case DescriptorType::SHADER_STORAGE_BUFFER: {
|
|
||||||
// TODO: Vulkan does not support this, can we?
|
|
||||||
PANIC_POSTCONDITION("Shader storage is not supported");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Currently flags are only used to specify dynamic offset.
|
|
||||||
|
|
||||||
// UNUSED
|
|
||||||
// fEntry.count
|
|
||||||
}
|
|
||||||
|
|
||||||
wgpu::BindGroupLayoutDescriptor layoutDescriptor{
|
|
||||||
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
|
|
||||||
// debugging. For now, hack an incrementing value.
|
|
||||||
.label{ "layout_" + std::to_string(++layoutNum) },
|
|
||||||
.entryCount = wEntries.size(),
|
|
||||||
.entries = wEntries.data()
|
|
||||||
};
|
|
||||||
// TODO Do we need to defer this until we have more info on textures and samplers??
|
|
||||||
mLayout = device.CreateBindGroupLayout(&layoutDescriptor);
|
|
||||||
}
|
|
||||||
WebGPUDescriptorSetLayout::~WebGPUDescriptorSetLayout() {}
|
|
||||||
}// namespace filament::backend
|
}// namespace filament::backend
|
||||||
|
|||||||
@@ -28,20 +28,9 @@
|
|||||||
#include <webgpu/webgpu_cpp.h>
|
#include <webgpu/webgpu_cpp.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
class WGPUProgram final : public HwProgram {
|
|
||||||
public:
|
|
||||||
WGPUProgram(wgpu::Device&, Program&);
|
|
||||||
|
|
||||||
wgpu::ShaderModule vertexShaderModule = nullptr;
|
|
||||||
wgpu::ShaderModule fragmentShaderModule = nullptr;
|
|
||||||
wgpu::ShaderModule computeShaderModule = nullptr;
|
|
||||||
std::vector<wgpu::ConstantEntry> constants;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WGPUBufferObject;
|
struct WGPUBufferObject;
|
||||||
// TODO: Currently WGPUVertexBufferInfo is not used by WebGPU for useful task.
|
// TODO: Currently WGPUVertexBufferInfo is not used by WebGPU for useful task.
|
||||||
// Update the struct when used by WebGPU driver.
|
// Update the struct when used by WebGPU driver.
|
||||||
@@ -78,18 +67,6 @@ struct WGPUBufferObject : HwBufferObject {
|
|||||||
wgpu::Buffer buffer;
|
wgpu::Buffer buffer;
|
||||||
const BufferObjectBinding bufferObjectBinding;
|
const BufferObjectBinding bufferObjectBinding;
|
||||||
};
|
};
|
||||||
class WebGPUDescriptorSetLayout : public HwDescriptorSetLayout {
|
|
||||||
public:
|
|
||||||
WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, wgpu::Device const& device);
|
|
||||||
~WebGPUDescriptorSetLayout();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// TODO: If this is useful elsewhere, remove it from this class
|
|
||||||
// Convert Filament Shader Stage Flags bitmask to webgpu equivilant
|
|
||||||
static wgpu::ShaderStage filamentStageToWGPUStage(ShaderStageFlags fFlags);
|
|
||||||
|
|
||||||
wgpu::BindGroupLayout mLayout;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Currently WGPUTexture is not used by WebGPU for useful task.
|
// TODO: Currently WGPUTexture is not used by WebGPU for useful task.
|
||||||
// Update the struct when used by WebGPU driver.
|
// Update the struct when used by WebGPU driver.
|
||||||
|
|||||||
@@ -43,29 +43,24 @@ using namespace image;
|
|||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
Backend BackendTest::sBackend = Backend::NOOP;
|
Backend BackendTest::sBackend = Backend::NOOP;
|
||||||
OperatingSystem BackendTest::sOperatingSystem = OperatingSystem::OTHER;
|
|
||||||
bool BackendTest::sIsMobilePlatform = false;
|
bool BackendTest::sIsMobilePlatform = false;
|
||||||
|
|
||||||
void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform) {
|
void BackendTest::init(Backend backend, bool isMobilePlatform) {
|
||||||
sBackend = backend;
|
sBackend = backend;
|
||||||
sOperatingSystem = operatingSystem;
|
|
||||||
sIsMobilePlatform = isMobilePlatform;
|
sIsMobilePlatform = isMobilePlatform;
|
||||||
}
|
}
|
||||||
|
|
||||||
BackendTest::BackendTest() : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE,
|
BackendTest::BackendTest() : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE,
|
||||||
CONFIG_COMMAND_BUFFERS_SIZE, /*mPaused=*/false) {
|
CONFIG_COMMAND_BUFFERS_SIZE, /*mPaused=*/false) {
|
||||||
initializeDriver();
|
initializeDriver();
|
||||||
mImageExpectations.emplace(getDriverApi());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BackendTest::~BackendTest() {
|
BackendTest::~BackendTest() {
|
||||||
// Ensure all graphics commands and callbacks are finished.
|
|
||||||
flushAndWait();
|
|
||||||
mImageExpectations->evaluate();
|
|
||||||
// Note: Don't terminate the driver for OpenGL, as it wipes away the context and removes the buffer from the screen.
|
// Note: Don't terminate the driver for OpenGL, as it wipes away the context and removes the buffer from the screen.
|
||||||
if (sBackend == Backend::OPENGL) {
|
if (sBackend == Backend::OPENGL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
flushAndWait();
|
||||||
driver->terminate();
|
driver->terminate();
|
||||||
delete driver;
|
delete driver;
|
||||||
}
|
}
|
||||||
@@ -159,16 +154,49 @@ void BackendTest::renderTriangle(
|
|||||||
api.endRenderPass();
|
api.endRenderPass();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BackendTest::matchesEnvironment(Backend backend) {
|
void BackendTest::readPixelsAndAssertHash(const char* testName, size_t width, size_t height,
|
||||||
return sBackend == backend;
|
Handle<HwRenderTarget> rt, uint32_t expectedHash, bool exportScreenshot) {
|
||||||
}
|
void* buffer = calloc(1, width * height * 4);
|
||||||
|
|
||||||
bool BackendTest::matchesEnvironment(OperatingSystem operatingSystem) {
|
struct Capture {
|
||||||
return sOperatingSystem == operatingSystem;
|
uint32_t expectedHash;
|
||||||
}
|
char* name;
|
||||||
|
bool exportScreenshot;
|
||||||
|
size_t width, height;
|
||||||
|
};
|
||||||
|
auto* c = new Capture();
|
||||||
|
c->expectedHash = expectedHash;
|
||||||
|
c->name = strdup(testName);
|
||||||
|
c->exportScreenshot = exportScreenshot;
|
||||||
|
c->width = width;
|
||||||
|
c->height = height;
|
||||||
|
|
||||||
bool BackendTest::matchesEnvironment(OperatingSystem operatingSystem, Backend backend) {
|
PixelBufferDescriptor pbd(buffer, width * height * 4, PixelDataFormat::RGBA, PixelDataType::UBYTE,
|
||||||
return matchesEnvironment(operatingSystem) && matchesEnvironment(backend);
|
1, 0, 0, width, [](void* buffer, size_t size, void* user) {
|
||||||
|
auto* c = (Capture*)user;
|
||||||
|
|
||||||
|
// Export a screenshot, if requested.
|
||||||
|
if (c->exportScreenshot) {
|
||||||
|
#ifndef FILAMENT_IOS
|
||||||
|
LinearImage image(c->width, c->height, 4);
|
||||||
|
image = toLinearWithAlpha<uint8_t>(c->width, c->height, c->width * 4,
|
||||||
|
(uint8_t*) buffer);
|
||||||
|
const std::string png = std::string(c->name) + ".png";
|
||||||
|
std::ofstream outputStream(png.c_str(), std::ios::binary | std::ios::trunc);
|
||||||
|
ImageEncoder::encode(outputStream, ImageEncoder::Format::PNG, image, "",
|
||||||
|
png);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash the contents of the buffer and check that they match.
|
||||||
|
uint32_t hash = utils::hash::murmur3((const uint32_t*) buffer, size / 4, 0);
|
||||||
|
ASSERT_EQ(hash, c->expectedHash) << c->name << " failed: hashes do not match." << std::endl;
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
free(c->name);
|
||||||
|
free(c);
|
||||||
|
}, (void*)c);
|
||||||
|
getDriverApi().readPixels(rt, 0, 0, width, height, std::move(pbd));
|
||||||
}
|
}
|
||||||
|
|
||||||
class Environment : public ::testing::Environment {
|
class Environment : public ::testing::Environment {
|
||||||
@@ -182,8 +210,8 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc, char* argv[]) {
|
void initTests(Backend backend, bool isMobile, int& argc, char* argv[]) {
|
||||||
BackendTest::init(backend, operatingSystem, isMobile);
|
BackendTest::init(backend, isMobile);
|
||||||
::testing::InitGoogleTest(&argc, argv);
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
::testing::AddGlobalTestEnvironment(new Environment);
|
::testing::AddGlobalTestEnvironment(new Environment);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,17 +25,15 @@
|
|||||||
#include "private/backend/DriverApi.h"
|
#include "private/backend/DriverApi.h"
|
||||||
|
|
||||||
#include "PlatformRunner.h"
|
#include "PlatformRunner.h"
|
||||||
#include "ImageExpectations.h"
|
|
||||||
|
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
class BackendTest : public ::testing::Test {
|
class BackendTest : public ::testing::Test {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static void init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform);
|
static void init(Backend backend, bool isMobilePlatform);
|
||||||
|
|
||||||
static Backend sBackend;
|
static Backend sBackend;
|
||||||
static OperatingSystem sOperatingSystem;
|
|
||||||
static bool sIsMobilePlatform;
|
static bool sIsMobilePlatform;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -66,14 +64,13 @@ protected:
|
|||||||
filament::backend::Handle<filament::backend::HwProgram> program,
|
filament::backend::Handle<filament::backend::HwProgram> program,
|
||||||
const filament::backend::RenderPassParams& params);
|
const filament::backend::RenderPassParams& params);
|
||||||
|
|
||||||
|
void readPixelsAndAssertHash(const char* testName, size_t width, size_t height,
|
||||||
|
filament::backend::Handle<filament::backend::HwRenderTarget> rt, uint32_t expectedHash,
|
||||||
|
bool exportScreenshot = false);
|
||||||
|
|
||||||
filament::backend::DriverApi& getDriverApi() { return *commandStream; }
|
filament::backend::DriverApi& getDriverApi() { return *commandStream; }
|
||||||
filament::backend::Driver& getDriver() { return *driver; }
|
filament::backend::Driver& getDriver() { return *driver; }
|
||||||
|
|
||||||
ImageExpectations& getExpectations() { return *mImageExpectations; }
|
|
||||||
|
|
||||||
static bool matchesEnvironment(Backend backend);
|
|
||||||
static bool matchesEnvironment(OperatingSystem operatingSystem);
|
|
||||||
static bool matchesEnvironment(OperatingSystem operatingSystem, Backend backend);
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
filament::backend::Driver* driver = nullptr;
|
filament::backend::Driver* driver = nullptr;
|
||||||
@@ -81,10 +78,6 @@ private:
|
|||||||
std::unique_ptr<filament::backend::DriverApi> commandStream;
|
std::unique_ptr<filament::backend::DriverApi> commandStream;
|
||||||
|
|
||||||
filament::backend::Handle<filament::backend::HwBufferObject> uniform;
|
filament::backend::Handle<filament::backend::HwBufferObject> uniform;
|
||||||
|
|
||||||
// This isn't truly optional, it just needs to delay construction until after the driver has
|
|
||||||
// been initialized
|
|
||||||
std::optional<ImageExpectations> mImageExpectations;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
#include "ImageExpectations.h"
|
#include "ImageExpectations.h"
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include "absl/strings/str_format.h"
|
#include "absl/strings/str_format.h"
|
||||||
#include "utils/Hash.h"
|
#include "utils/Hash.h"
|
||||||
@@ -27,17 +28,14 @@
|
|||||||
#ifndef FILAMENT_IOS
|
#ifndef FILAMENT_IOS
|
||||||
|
|
||||||
#include <imageio/ImageEncoder.h>
|
#include <imageio/ImageEncoder.h>
|
||||||
#include <imageio/ImageDecoder.h>
|
|
||||||
#include <image/ColorTransform.h>
|
#include <image/ColorTransform.h>
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ScreenshotParams::ScreenshotParams(int width, int height, std::string fileName,
|
ScreenshotParams::ScreenshotParams(int width, int height, std::string fileName,
|
||||||
uint32_t expectedHash)
|
uint32_t expectedPixelHash)
|
||||||
: mWidth(width),
|
: mWidth(width), mHeight(height), mFileName(std::move(fileName)),
|
||||||
mHeight(height),
|
mExpectedPixelHash(expectedPixelHash) {}
|
||||||
mExpectedPixelHash(expectedHash),
|
|
||||||
mFileName(std::move(fileName)) {}
|
|
||||||
|
|
||||||
int ScreenshotParams::width() const {
|
int ScreenshotParams::width() const {
|
||||||
return mWidth;
|
return mWidth;
|
||||||
@@ -51,28 +49,24 @@ uint32_t ScreenshotParams::expectedHash() const {
|
|||||||
return mExpectedPixelHash;
|
return mExpectedPixelHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ScreenshotParams::actualDirectoryPath() {
|
std::string ScreenshotParams::outputDirectoryPath() const {
|
||||||
return "images/actual_images";
|
return ".";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ScreenshotParams::actualFileName() const {
|
std::string ScreenshotParams::generatedActualFileName() const {
|
||||||
return absl::StrFormat("%s_actual.png", mFileName);
|
return absl::StrFormat("%s_actual.png", mFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ScreenshotParams::actualFilePath() const {
|
std::string ScreenshotParams::generatedActualFilePath() const {
|
||||||
return absl::StrFormat("%s/%s", actualDirectoryPath(), actualFileName());
|
return absl::StrFormat("%s/%s", outputDirectoryPath(), generatedActualFileName());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ScreenshotParams::expectedDirectoryPath() {
|
std::string ScreenshotParams::goldenFileName() const {
|
||||||
return "images/expected_images";
|
return absl::StrFormat("%s_golden.png", mFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ScreenshotParams::expectedFileName() const {
|
std::string ScreenshotParams::goldenFilePath() const {
|
||||||
return absl::StrFormat("%s.png", mFileName);
|
return absl::StrFormat("%s/%s", outputDirectoryPath(), goldenFileName());
|
||||||
}
|
|
||||||
|
|
||||||
std::string ScreenshotParams::expectedFilePath() const {
|
|
||||||
return absl::StrFormat("%s/%s", expectedDirectoryPath(), expectedFileName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageExpectation::ImageExpectation(const char* fileName, int lineNumber,
|
ImageExpectation::ImageExpectation(const char* fileName, int lineNumber,
|
||||||
@@ -97,22 +91,11 @@ void ImageExpectation::evaluate() {
|
|||||||
|
|
||||||
void ImageExpectation::compareImage() const {
|
void ImageExpectation::compareImage() const {
|
||||||
bool bytesFilled = mResult.bytesFilled();
|
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())
|
EXPECT_THAT(bytesFilled, testing::IsTrue())
|
||||||
<< "Render target wasn't copied to the buffer for " << mFileName;
|
<< "Render target wasn't copied to the buffer for " << mFileName;
|
||||||
if (bytesFilled) {
|
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();
|
uint32_t actualHash = mResult.hash();
|
||||||
#ifndef FILAMENT_IOS
|
EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash()));
|
||||||
LoadedPng loadedImage(mParams.expectedFilePath());
|
|
||||||
uint32_t loadedImageHash = loadedImage.hash();
|
|
||||||
EXPECT_THAT(actualHash, testing::Eq(loadedImageHash)) << mParams.expectedFileName();
|
|
||||||
#endif
|
|
||||||
// For builds that can't load PNGs (currently iOS only) use the expected hash.
|
|
||||||
EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash())) << mParams.expectedFileName();
|
|
||||||
// TODO: Add better debug output, such as generating a diff image.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,13 +109,12 @@ ImageExpectations::~ImageExpectations() {
|
|||||||
|
|
||||||
void ImageExpectations::addExpectation(const char* fileName, int lineNumber,
|
void ImageExpectations::addExpectation(const char* fileName, int lineNumber,
|
||||||
filament::backend::RenderTargetHandle renderTarget, ScreenshotParams params) {
|
filament::backend::RenderTargetHandle renderTarget, ScreenshotParams params) {
|
||||||
mExpectations.emplace_back(std::make_unique<ImageExpectation>(fileName, lineNumber, mApi,
|
mExpectations.emplace_back(fileName, lineNumber, mApi, std::move(params), renderTarget);
|
||||||
std::move(params), renderTarget));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageExpectations::evaluate() {
|
void ImageExpectations::evaluate() {
|
||||||
for (auto& expectation: mExpectations) {
|
for (auto& expectation: mExpectations) {
|
||||||
expectation->evaluate();
|
expectation.evaluate();
|
||||||
}
|
}
|
||||||
mExpectations.clear();
|
mExpectations.clear();
|
||||||
}
|
}
|
||||||
@@ -140,28 +122,32 @@ void ImageExpectations::evaluate() {
|
|||||||
RenderTargetDump::RenderTargetDump(filament::backend::DriverApi& api,
|
RenderTargetDump::RenderTargetDump(filament::backend::DriverApi& api,
|
||||||
filament::backend::RenderTargetHandle renderTarget, const ScreenshotParams& params)
|
filament::backend::RenderTargetHandle renderTarget, const ScreenshotParams& params)
|
||||||
: mInternal(std::make_unique<RenderTargetDump::Internal>(params)) {
|
: mInternal(std::make_unique<RenderTargetDump::Internal>(params)) {
|
||||||
|
#ifdef FILAMENT_IOS
|
||||||
|
bytesFilled_ = true;
|
||||||
|
bytes_.resize(size);
|
||||||
|
std::fill(bytes_.begin(), bytes_.end(), 0);
|
||||||
|
#else
|
||||||
const size_t size = mInternal->params.width() * mInternal->params.height() * 4;
|
const size_t size = mInternal->params.width() * mInternal->params.height() * 4;
|
||||||
mInternal->bytes.resize(size);
|
mInternal->bytes.resize(size);
|
||||||
|
|
||||||
auto cb = [](void* buffer, size_t size, void* user) {
|
auto cb = [](void* buffer, size_t size, void* user) {
|
||||||
auto* internal = static_cast<RenderTargetDump::Internal*>(user);
|
auto* internal = static_cast<RenderTargetDump::Internal*>(user);
|
||||||
internal->bytesFilled = true;
|
|
||||||
#ifndef FILAMENT_IOS
|
|
||||||
image::LinearImage image(internal->params.width(), internal->params.width(), 4);
|
image::LinearImage image(internal->params.width(), internal->params.width(), 4);
|
||||||
image = image::toLinearWithAlpha<uint8_t>(internal->params.width(),
|
image = image::toLinearWithAlpha<uint8_t>(internal->params.width(),
|
||||||
internal->params.height(),
|
internal->params.height(),
|
||||||
internal->params.width() * 4, (uint8_t*)buffer);
|
internal->params.width() * 4, (uint8_t*)buffer);
|
||||||
std::string filePath = internal->params.actualFilePath();
|
std::string filePath = internal->params.generatedActualFilePath();
|
||||||
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
|
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
|
||||||
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG, image, "",
|
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG, image, "",
|
||||||
filePath);
|
filePath);
|
||||||
#endif
|
internal->bytesFilled = true;
|
||||||
};
|
};
|
||||||
filament::backend::PixelBufferDescriptor pb(mInternal->bytes.data(), size,
|
filament::backend::PixelBufferDescriptor pb(mInternal->bytes.data(), size,
|
||||||
filament::backend::PixelDataFormat::RGBA, filament::backend::PixelDataType::UBYTE, cb,
|
filament::backend::PixelDataFormat::RGBA, filament::backend::PixelDataType::UBYTE, cb,
|
||||||
(void*)mInternal.get());
|
(void*)mInternal.get());
|
||||||
api.readPixels(renderTarget, 0, 0, mInternal->params.width(), mInternal->params.height(),
|
api.readPixels(renderTarget, 0, 0, mInternal->params.width(), mInternal->params.height(),
|
||||||
std::move(pb));
|
std::move(pb));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderTargetDump::~RenderTargetDump() {
|
RenderTargetDump::~RenderTargetDump() {
|
||||||
@@ -183,30 +169,4 @@ bool RenderTargetDump::bytesFilled() const {
|
|||||||
return mInternal->bytesFilled;
|
return mInternal->bytesFilled;
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderTargetDump::Internal::Internal(const ScreenshotParams& params) : params(params) {}
|
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;
|
|
||||||
if (mBytes.empty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return utils::hash::murmur3((uint32_t*)mBytes.data(), mBytes.size() / 4, 0);
|
|
||||||
}
|
|
||||||
@@ -46,12 +46,11 @@ public:
|
|||||||
int height() const;
|
int height() const;
|
||||||
uint32_t expectedHash() const;
|
uint32_t expectedHash() const;
|
||||||
|
|
||||||
static std::string actualDirectoryPath();
|
std::string outputDirectoryPath() const;
|
||||||
std::string actualFileName() const;
|
std::string generatedActualFileName() const;
|
||||||
std::string actualFilePath() const;
|
std::string generatedActualFilePath() const;
|
||||||
static std::string expectedDirectoryPath();
|
std::string goldenFileName() const;
|
||||||
std::string expectedFileName() const;
|
std::string goldenFilePath() const;
|
||||||
std::string expectedFilePath() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int mWidth;
|
int mWidth;
|
||||||
@@ -99,17 +98,6 @@ private:
|
|||||||
std::unique_ptr<Internal> mInternal;
|
std::unique_ptr<Internal> mInternal;
|
||||||
};
|
};
|
||||||
|
|
||||||
class LoadedPng {
|
|
||||||
public:
|
|
||||||
explicit LoadedPng(std::string filePath);
|
|
||||||
|
|
||||||
uint32_t hash() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string mFilePath;
|
|
||||||
std::vector<unsigned char> mBytes;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ImageExpectation {
|
class ImageExpectation {
|
||||||
public:
|
public:
|
||||||
ImageExpectation(const char* fileName, int lineNumber, filament::backend::DriverApi& api,
|
ImageExpectation(const char* fileName, int lineNumber, filament::backend::DriverApi& api,
|
||||||
@@ -142,8 +130,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
filament::backend::DriverApi& mApi;
|
filament::backend::DriverApi& mApi;
|
||||||
// Store expectations in unique pointers because they are self referential.
|
std::vector<ImageExpectation> mExpectations;
|
||||||
std::vector<std::unique_ptr<ImageExpectation>> mExpectations;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //TNT_IMAGE_EXPECTATIONS_H
|
#endif //TNT_IMAGE_EXPECTATIONS_H
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "PlatformRunner.h"
|
|
||||||
|
|
||||||
namespace utils {
|
|
||||||
|
|
||||||
template<>
|
|
||||||
CString to_string<test::Backend>(test::Backend backend) noexcept {
|
|
||||||
switch (backend) {
|
|
||||||
case test::Backend::OPENGL: {
|
|
||||||
return "OpenGL";
|
|
||||||
}
|
|
||||||
case test::Backend::VULKAN: {
|
|
||||||
return "Vulkan";
|
|
||||||
}
|
|
||||||
case test::Backend::METAL: {
|
|
||||||
return "Metal";
|
|
||||||
}
|
|
||||||
case test::Backend::WEBGPU: {
|
|
||||||
return "WebGPU";
|
|
||||||
}
|
|
||||||
case test::Backend::NOOP:
|
|
||||||
default: {
|
|
||||||
return "No-op";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
|
||||||
CString to_string(test::OperatingSystem os) noexcept {
|
|
||||||
switch (os) {
|
|
||||||
case test::OperatingSystem::LINUX: {
|
|
||||||
return "Linux";
|
|
||||||
}
|
|
||||||
case test::OperatingSystem::APPLE: {
|
|
||||||
return "Apple";
|
|
||||||
}
|
|
||||||
case test::OperatingSystem::OTHER:
|
|
||||||
default: {
|
|
||||||
return "Other";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace utils
|
|
||||||
|
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include "utils/CString.h"
|
|
||||||
|
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
@@ -35,15 +34,6 @@ enum class Backend : uint8_t {
|
|||||||
NOOP = 5,
|
NOOP = 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class OperatingSystem: uint8_t {
|
|
||||||
OTHER = 1,
|
|
||||||
// Also represents android phones.
|
|
||||||
LINUX = 2,
|
|
||||||
// Also represents iOS phones.
|
|
||||||
APPLE = 3,
|
|
||||||
// TODO: When tests support windows add it here.
|
|
||||||
};
|
|
||||||
|
|
||||||
struct NativeView {
|
struct NativeView {
|
||||||
void* ptr = nullptr;
|
void* ptr = nullptr;
|
||||||
size_t width = 0, height = 0;
|
size_t width = 0, height = 0;
|
||||||
@@ -61,10 +51,9 @@ NativeView getNativeView();
|
|||||||
* No tests will be run yet.
|
* No tests will be run yet.
|
||||||
*
|
*
|
||||||
* @param backend The backend to run the tests on.
|
* @param backend The backend to run the tests on.
|
||||||
* @param operatingSystem The operating system the tests are being run on.
|
|
||||||
* @param isMobile True if the platform is a mobile platform (iOS or Android).
|
* @param isMobile True if the platform is a mobile platform (iOS or Android).
|
||||||
*/
|
*/
|
||||||
void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc, char* argv[]);
|
void initTests(Backend backend, bool isMobile, int& argc, char* argv[]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test runners should call runTests when they are ready for tests to be run.
|
* Test runners should call runTests when they are ready for tests to be run.
|
||||||
@@ -79,6 +68,6 @@ int runTests();
|
|||||||
*/
|
*/
|
||||||
Backend parseArgumentsForBackend(int argc, char* argv[]);
|
Backend parseArgumentsForBackend(int argc, char* argv[]);
|
||||||
|
|
||||||
} // namespace test
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ namespace test {
|
|||||||
|
|
||||||
using namespace filament::backend;
|
using namespace filament::backend;
|
||||||
|
|
||||||
|
filament::backend::descriptor_binding_t TextureBindingConfig::getBinding() const {
|
||||||
|
return binding.value_or(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
filament::backend::SamplerParams TextureBindingConfig::getParams() const {
|
||||||
|
return samplerParams.value_or(SamplerParams{});
|
||||||
|
}
|
||||||
|
|
||||||
Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) : mCleanup(cleanup) {
|
Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) : mCleanup(cleanup) {
|
||||||
utils::FixedCapacityVector<DescriptorSetLayoutBinding> kLayouts(config.uniforms.size());
|
utils::FixedCapacityVector<DescriptorSetLayoutBinding> kLayouts(config.uniforms.size());
|
||||||
for (unsigned char i = 0; i < config.uniforms.size(); ++i) {
|
for (unsigned char i = 0; i < config.uniforms.size(); ++i) {
|
||||||
@@ -57,6 +65,19 @@ Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) : mCleanup
|
|||||||
|
|
||||||
mDescriptorSetLayout = cleanup.add(
|
mDescriptorSetLayout = cleanup.add(
|
||||||
api.createDescriptorSetLayout(DescriptorSetLayout{ kLayouts }));
|
api.createDescriptorSetLayout(DescriptorSetLayout{ kLayouts }));
|
||||||
|
|
||||||
|
mDefaultDescriptorSet = createDescriptorSet(api);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shader::updateTextureUniform(filament::backend::DriverApi& api,
|
||||||
|
TextureBindingConfig config) const {
|
||||||
|
DescriptorSetHandle descriptorSet = config.descriptorSet.value_or(mDefaultDescriptorSet);
|
||||||
|
api.updateDescriptorSetTexture(descriptorSet, config.getBinding(), config.textureHandle,
|
||||||
|
config.getParams());
|
||||||
|
|
||||||
|
if (config.alsoBindToSet.has_value()) {
|
||||||
|
api.bindDescriptorSet(descriptorSet, *config.alsoBindToSet, {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filament::backend::DescriptorSetHandle Shader::createDescriptorSet(DriverApi& api) const {
|
filament::backend::DescriptorSetHandle Shader::createDescriptorSet(DriverApi& api) const {
|
||||||
|
|||||||
@@ -61,6 +61,18 @@ struct UniformBindingConfig {
|
|||||||
ResolvedUniformBindingConfig resolve();
|
ResolvedUniformBindingConfig resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TextureBindingConfig {
|
||||||
|
filament::backend::TextureHandle textureHandle;
|
||||||
|
std::optional<filament::backend::DescriptorSetHandle> descriptorSet;
|
||||||
|
std::optional<filament::backend::descriptor_binding_t> binding;
|
||||||
|
std::optional<filament::backend::SamplerParams> samplerParams;
|
||||||
|
// If present then the call to update the texture will also bind the descriptor set.
|
||||||
|
std::optional<filament::backend::descriptor_set_t> alsoBindToSet;
|
||||||
|
|
||||||
|
filament::backend::descriptor_binding_t getBinding() const;
|
||||||
|
filament::backend::SamplerParams getParams() const;
|
||||||
|
};
|
||||||
|
|
||||||
class Shader {
|
class Shader {
|
||||||
public:
|
public:
|
||||||
// All graphics resources have their lifetime controlled by the Cleanup and not this object.
|
// All graphics resources have their lifetime controlled by the Cleanup and not this object.
|
||||||
@@ -86,17 +98,20 @@ public:
|
|||||||
void bindUniform(filament::backend::DriverApi& api,
|
void bindUniform(filament::backend::DriverApi& api,
|
||||||
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
|
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
|
||||||
UniformBindingConfig config) const;
|
UniformBindingConfig config) const;
|
||||||
|
void updateTextureUniform(filament::backend::DriverApi& api, TextureBindingConfig config) const;
|
||||||
filament::backend::ProgramHandle getProgram() const;
|
|
||||||
filament::backend::DescriptorSetLayoutHandle getDescriptorSetLayout() const;
|
|
||||||
|
|
||||||
filament::backend::DescriptorSetHandle createDescriptorSet(
|
filament::backend::DescriptorSetHandle createDescriptorSet(
|
||||||
filament::backend::DriverApi& api) const;
|
filament::backend::DriverApi& api) const;
|
||||||
|
|
||||||
|
filament::backend::ProgramHandle getProgram() const;
|
||||||
|
filament::backend::DescriptorSetLayoutHandle getDescriptorSetLayout() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Cleanup& mCleanup;
|
Cleanup& mCleanup;
|
||||||
filament::backend::ProgramHandle mProgram;
|
filament::backend::ProgramHandle mProgram;
|
||||||
filament::backend::DescriptorSetLayoutHandle mDescriptorSetLayout;
|
filament::backend::DescriptorSetLayoutHandle mDescriptorSetLayout;
|
||||||
|
// Used whenever the caller doesn't provide a descriptor set.
|
||||||
|
filament::backend::DescriptorSetHandle mDefaultDescriptorSet;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename UniformType>
|
template<typename UniformType>
|
||||||
@@ -133,12 +148,8 @@ void Shader::bindUniform(filament::backend::DriverApi& api,
|
|||||||
UniformBindingConfig config) const {
|
UniformBindingConfig config) const {
|
||||||
auto resolvedConfig = config.resolve<UniformType>();
|
auto resolvedConfig = config.resolve<UniformType>();
|
||||||
|
|
||||||
filament::backend::DescriptorSetHandle descriptorSet;
|
filament::backend::DescriptorSetHandle descriptorSet =
|
||||||
if (resolvedConfig.descriptorSet.has_value()) {
|
resolvedConfig.descriptorSet.value_or(mDefaultDescriptorSet);
|
||||||
descriptorSet = *resolvedConfig.descriptorSet;
|
|
||||||
} else {
|
|
||||||
descriptorSet = createDescriptorSet(api);
|
|
||||||
}
|
|
||||||
|
|
||||||
api.updateDescriptorSetBuffer(descriptorSet, resolvedConfig.binding, hwBuffer, 0,
|
api.updateDescriptorSetBuffer(descriptorSet, resolvedConfig.binding, hwBuffer, 0,
|
||||||
resolvedConfig.bufferSize);
|
resolvedConfig.bufferSize);
|
||||||
|
|||||||
@@ -1,96 +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 "Skip.h"
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace test {
|
|
||||||
|
|
||||||
SkipEnvironment::SkipEnvironment(test::Backend backend) : backend(backend) {}
|
|
||||||
SkipEnvironment::SkipEnvironment(test::OperatingSystem os) : os(os) {}
|
|
||||||
SkipEnvironment::SkipEnvironment(test::OperatingSystem os, test::Backend backend)
|
|
||||||
: backend(backend),
|
|
||||||
os(os) {}
|
|
||||||
|
|
||||||
bool SkipEnvironment::matches() {
|
|
||||||
bool backendMatches = !backend.has_value() || *backend == BackendTest::sBackend;
|
|
||||||
bool osMatches = !os.has_value() || *os == BackendTest::sOperatingSystem;
|
|
||||||
bool isMobileMatches = !isMobile.has_value() || *isMobile == BackendTest::sIsMobilePlatform;
|
|
||||||
return backendMatches && osMatches && isMobileMatches;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string SkipEnvironment::describe() {
|
|
||||||
std::stringstream result;
|
|
||||||
if (matches()) {
|
|
||||||
result << "environment matches because " << describe_actual_environment() << ".";
|
|
||||||
} else {
|
|
||||||
result << "environment does not match because " << describe_requirements() << " but "
|
|
||||||
<< describe_actual_environment() << ".";
|
|
||||||
}
|
|
||||||
return result.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string SkipEnvironment::describe_actual_environment() {
|
|
||||||
bool resultWritten = false;
|
|
||||||
std::stringstream reality;
|
|
||||||
if (backend.has_value()) {
|
|
||||||
reality << "backend was " << utils::to_string(BackendTest::sBackend).c_str();
|
|
||||||
resultWritten = true;
|
|
||||||
}
|
|
||||||
if (os.has_value()) {
|
|
||||||
if (resultWritten) {
|
|
||||||
reality << ", and ";
|
|
||||||
}
|
|
||||||
reality << "operating system was "
|
|
||||||
<< utils::to_string(BackendTest::sOperatingSystem).c_str();
|
|
||||||
resultWritten = true;
|
|
||||||
}
|
|
||||||
if (isMobile.has_value()) {
|
|
||||||
if (resultWritten) {
|
|
||||||
reality << ", and ";
|
|
||||||
}
|
|
||||||
reality << "device " << (BackendTest::sIsMobilePlatform ? "was" : "was not") << " mobile";
|
|
||||||
resultWritten = true;
|
|
||||||
}
|
|
||||||
return reality.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string SkipEnvironment::describe_requirements() {
|
|
||||||
bool resultWritten = false;
|
|
||||||
std::stringstream requirement;
|
|
||||||
if (backend.has_value()) {
|
|
||||||
requirement << "backend needs to be " << utils::to_string(*backend).c_str();
|
|
||||||
resultWritten = true;
|
|
||||||
}
|
|
||||||
if (os.has_value()) {
|
|
||||||
if (resultWritten) {
|
|
||||||
requirement << ", and ";
|
|
||||||
}
|
|
||||||
requirement << "operating system needs to be " << utils::to_string(*os).c_str();
|
|
||||||
resultWritten = true;
|
|
||||||
}
|
|
||||||
if (isMobile.has_value() && BackendTest::sIsMobilePlatform != isMobile) {
|
|
||||||
if (resultWritten) {
|
|
||||||
requirement << ", and ";
|
|
||||||
}
|
|
||||||
requirement << "device needs to " << (*isMobile ? "be" : "not be") << " mobile";
|
|
||||||
resultWritten = true;
|
|
||||||
}
|
|
||||||
return requirement.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace test
|
|
||||||
@@ -1,55 +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_SKIP_H
|
|
||||||
#define TNT_SKIP_H
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
#include "BackendTest.h"
|
|
||||||
|
|
||||||
// skipEnvironment must be a test::SkipEnvironment
|
|
||||||
#define SKIP_IF(skipEnvironment) \
|
|
||||||
do { \
|
|
||||||
SkipEnvironment skip(skipEnvironment); \
|
|
||||||
if (skip.matches()) { \
|
|
||||||
GTEST_SKIP() << "Skipping test as the " << skip.describe(); \
|
|
||||||
} \
|
|
||||||
} while (false)
|
|
||||||
|
|
||||||
namespace test {
|
|
||||||
|
|
||||||
struct SkipEnvironment {
|
|
||||||
SkipEnvironment(const SkipEnvironment&) = default;
|
|
||||||
explicit SkipEnvironment(test::Backend backend);
|
|
||||||
explicit SkipEnvironment(test::OperatingSystem os);
|
|
||||||
SkipEnvironment(test::OperatingSystem os, test::Backend backend);
|
|
||||||
|
|
||||||
std::optional<test::Backend> backend;
|
|
||||||
std::optional<test::OperatingSystem> os;
|
|
||||||
std::optional<bool> isMobile;
|
|
||||||
|
|
||||||
bool matches();
|
|
||||||
// Describes the current state of either matching or mismatching.
|
|
||||||
std::string describe();
|
|
||||||
// Describe all the non-null requirements.
|
|
||||||
std::string describe_requirements();
|
|
||||||
// Describes the environment's status for all the attributes that are non-null.
|
|
||||||
std::string describe_actual_environment();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace test
|
|
||||||
|
|
||||||
#endif// TNT_SKIP_H
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 982 B |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -51,6 +51,6 @@ int main(int argc, char* argv[]) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
test::initTests(backend, test::OperatingSystem::LINUX, false, argc, argv);
|
test::initTests(backend, false, argc, argv);
|
||||||
return test::runTests();
|
return test::runTests();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ test::NativeView getNativeView() {
|
|||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
auto backend = test::parseArgumentsForBackend(argc, argv);
|
auto backend = test::parseArgumentsForBackend(argc, argv);
|
||||||
test::initTests(backend, test::OperatingSystem::APPLE, false, argc, argv);
|
test::initTests(backend, false, argc, argv);
|
||||||
AppDelegate* delegate = [AppDelegate new];
|
AppDelegate* delegate = [AppDelegate new];
|
||||||
delegate.backend = backend;
|
delegate.backend = backend;
|
||||||
NSApplication* app = [NSApplication sharedApplication];
|
NSApplication* app = [NSApplication sharedApplication];
|
||||||
|
|||||||
@@ -197,10 +197,16 @@ TEST_F(BlitTest, ColorMagnify) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
RenderFrame frame(api);
|
ImageExpectations expectations(api);
|
||||||
EXPECT_IMAGE(dstRenderTargets[0], getExpectations(),
|
|
||||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMagnify", 0x410bdd31));
|
{
|
||||||
api.commit(swapChain);
|
RenderFrame frame(api);
|
||||||
|
EXPECT_IMAGE(dstRenderTargets[0], expectations,
|
||||||
|
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMagnify", 0x410bdd31));
|
||||||
|
api.commit(swapChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
flushAndWait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,8 +260,14 @@ TEST_F(BlitTest, ColorMinify) {
|
|||||||
{ 0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel },
|
{ 0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel },
|
||||||
SamplerMagFilter::LINEAR);
|
SamplerMagFilter::LINEAR);
|
||||||
|
|
||||||
EXPECT_IMAGE(dstRenderTargets[0], getExpectations(),
|
{
|
||||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f));
|
ImageExpectations expectations(api);
|
||||||
|
|
||||||
|
EXPECT_IMAGE(dstRenderTargets[0], expectations,
|
||||||
|
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f));
|
||||||
|
|
||||||
|
flushAndWait();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(BlitTest, ColorResolve) {
|
TEST_F(BlitTest, ColorResolve) {
|
||||||
@@ -339,8 +351,14 @@ TEST_F(BlitTest, ColorResolve) {
|
|||||||
srcRenderTarget, { 0, 0, kSrcTexWidth, kSrcTexHeight },
|
srcRenderTarget, { 0, 0, kSrcTexWidth, kSrcTexHeight },
|
||||||
SamplerMagFilter::NEAREST);
|
SamplerMagFilter::NEAREST);
|
||||||
|
|
||||||
EXPECT_IMAGE(dstRenderTarget, getExpectations(),
|
{
|
||||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 0xebfac2ef));
|
ImageExpectations expectations(api);
|
||||||
|
|
||||||
|
EXPECT_IMAGE(dstRenderTarget, expectations,
|
||||||
|
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 0xebfac2ef));
|
||||||
|
|
||||||
|
flushAndWait();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(BlitTest, Blit2DTextureArray) {
|
TEST_F(BlitTest, Blit2DTextureArray) {
|
||||||
@@ -405,11 +423,17 @@ TEST_F(BlitTest, Blit2DTextureArray) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
RenderFrame frame(api);
|
ImageExpectations expectations(api);
|
||||||
EXPECT_IMAGE(dstRenderTarget, getExpectations(),
|
|
||||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "Blit2DTextureArray",
|
{
|
||||||
0x8de7d55b));
|
RenderFrame frame(api);
|
||||||
api.commit(swapChain);
|
EXPECT_IMAGE(dstRenderTarget, expectations,
|
||||||
|
ScreenshotParams(kDstTexWidth, kDstTexHeight, "Blit2DTextureArray",
|
||||||
|
0x8de7d55b));
|
||||||
|
api.commit(swapChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
flushAndWait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,12 +503,18 @@ TEST_F(BlitTest, BlitRegion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
RenderFrame frame(api);
|
ImageExpectations expectations(api);
|
||||||
// TODO: for some reason, this test has very, very slight (as in one pixel) differences
|
|
||||||
// between OpenGL and Metal. So disable golden checking for now.
|
{
|
||||||
// EXPECT_IMAGE(dstRenderTarget, expectations, ScreenshotParams(kDstTexWidth,
|
RenderFrame frame(api);
|
||||||
// kDstTexHeight, "BlitRegion", 0x74fa34ed));
|
// TODO: for some reason, this test has very, very slight (as in one pixel) differences
|
||||||
api.commit(swapChain);
|
// between OpenGL and Metal. So disable golden checking for now.
|
||||||
|
// EXPECT_IMAGE(dstRenderTarget, expectations, ScreenshotParams(kDstTexWidth,
|
||||||
|
// kDstTexHeight, "BlitRegion", 0x74fa34ed));
|
||||||
|
api.commit(swapChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
flushAndWait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,19 +567,25 @@ TEST_F(BlitTest, BlitRegionToSwapChain) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
RenderFrame frame(api);
|
ImageExpectations expectations(api);
|
||||||
|
{
|
||||||
|
{
|
||||||
|
RenderFrame frame(api);
|
||||||
|
|
||||||
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
|
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
|
||||||
dstRect, srcRenderTargets[srcLevel],
|
dstRect, srcRenderTargets[srcLevel],
|
||||||
srcRect, SamplerMagFilter::LINEAR);
|
srcRect, SamplerMagFilter::LINEAR);
|
||||||
|
|
||||||
api.commit(swapChain);
|
api.commit(swapChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: for some reason, this test has been disabled. It needs to be tested on all
|
||||||
|
// machines.
|
||||||
|
// EXPECT_IMAGE(dstRenderTarget, expectations,
|
||||||
|
// ScreenshotParams(kDstTexWidth, kDstTexHeight, "BlitRegionToSwapChain", 0x0));
|
||||||
|
}
|
||||||
|
flushAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: for some reason, this test has been disabled. It needs to be tested on all
|
|
||||||
// machines.
|
|
||||||
// EXPECT_IMAGE(dstRenderTarget, expectations,
|
|
||||||
// ScreenshotParams(kDstTexWidth, kDstTexHeight, "BlitRegionToSwapChain", 0x0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
#include "BackendTest.h"
|
#include "BackendTest.h"
|
||||||
|
|
||||||
#include "ImageExpectations.h"
|
|
||||||
#include "Lifetimes.h"
|
#include "Lifetimes.h"
|
||||||
#include "Shader.h"
|
#include "Shader.h"
|
||||||
#include "SharedShaders.h"
|
#include "SharedShaders.h"
|
||||||
@@ -221,9 +220,9 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
|
|||||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||||
renderTarget, swapChain, shader.getProgram(), params);
|
renderTarget, swapChain, shader.getProgram(), params);
|
||||||
|
|
||||||
|
static const uint32_t expectedHash = 91322442;
|
||||||
EXPECT_IMAGE(renderTarget, getExpectations(),
|
readPixelsAndAssertHash(
|
||||||
ScreenshotParams(512, 512, "BufferObjectUpdateWithOffset", 91322442));
|
"BufferObjectUpdateWithOffset", 512, 512, renderTarget, expectedHash, true);
|
||||||
|
|
||||||
api.flush();
|
api.flush();
|
||||||
api.commit(swapChain);
|
api.commit(swapChain);
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
#include "BackendTest.h"
|
#include "BackendTest.h"
|
||||||
|
|
||||||
#include "Lifetimes.h"
|
#include "Lifetimes.h"
|
||||||
#include "Skip.h"
|
|
||||||
|
|
||||||
using namespace filament;
|
using namespace filament;
|
||||||
using namespace filament::backend;
|
using namespace filament::backend;
|
||||||
@@ -25,8 +24,6 @@ using namespace filament::backend;
|
|||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
TEST_F(BackendTest, FrameScheduledCallback) {
|
TEST_F(BackendTest, FrameScheduledCallback) {
|
||||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL));
|
|
||||||
|
|
||||||
auto& api = getDriverApi();
|
auto& api = getDriverApi();
|
||||||
Cleanup cleanup(api);
|
Cleanup cleanup(api);
|
||||||
|
|
||||||
@@ -84,8 +81,6 @@ TEST_F(BackendTest, FrameScheduledCallback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(BackendTest, FrameCompletedCallback) {
|
TEST_F(BackendTest, FrameCompletedCallback) {
|
||||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL));
|
|
||||||
|
|
||||||
auto& api = getDriverApi();
|
auto& api = getDriverApi();
|
||||||
Cleanup cleanup(api);
|
Cleanup cleanup(api);
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ void main() {
|
|||||||
fragColor = textureLod(test_tex, uv, params.sourceLevel);
|
fragColor = textureLod(test_tex, uv, params.sourceLevel);
|
||||||
})";
|
})";
|
||||||
|
|
||||||
|
static uint32_t sPixelHashResult = 0;
|
||||||
|
|
||||||
// Selecting a NPOT texture size seems to exacerbate the bug seen with Intel GPU's.
|
// Selecting a NPOT texture size seems to exacerbate the bug seen with Intel GPU's.
|
||||||
// Note that Filament uses a higher precision format (R11F_G11F_B10F) but this does not seem
|
// Note that Filament uses a higher precision format (R11F_G11F_B10F) but this does not seem
|
||||||
// necessary to trigger the bug.
|
// necessary to trigger the bug.
|
||||||
@@ -94,6 +96,25 @@ struct MaterialParams {
|
|||||||
float unused;
|
float unused;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt) {
|
||||||
|
const size_t size = kTexWidth * kTexHeight * 4;
|
||||||
|
void* buffer = calloc(1, size);
|
||||||
|
auto cb = [](void* buffer, size_t size, void* user) {
|
||||||
|
int w = kTexWidth, h = kTexHeight;
|
||||||
|
const uint32_t* texels = (uint32_t*) buffer;
|
||||||
|
sPixelHashResult = utils::hash::murmur3(texels, size / 4, 0);
|
||||||
|
#ifndef FILAMENT_IOS
|
||||||
|
LinearImage image(w, h, 4);
|
||||||
|
image = toLinearWithAlpha<uint8_t>(w, h, w * 4, (uint8_t*) buffer);
|
||||||
|
std::ofstream pngstrm("feedback.png", std::ios::binary | std::ios::trunc);
|
||||||
|
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", "feedback.png");
|
||||||
|
#endif
|
||||||
|
free(buffer);
|
||||||
|
};
|
||||||
|
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb);
|
||||||
|
dapi.readPixels(rt, 0, 0, kTexWidth, kTexHeight, std::move(pb));
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: This test needs work to get Metal and OpenGL to agree on results.
|
// TODO: This test needs work to get Metal and OpenGL to agree on results.
|
||||||
// The problems are caused by both uploading and rendering into the same texture, since the OpenGL
|
// The problems are caused by both uploading and rendering into the same texture, since the OpenGL
|
||||||
// backend's readPixels does not work correctly with textures that have image data uploaded.
|
// backend's readPixels does not work correctly with textures that have image data uploaded.
|
||||||
@@ -177,12 +198,14 @@ TEST_F(BackendTest, FeedbackLoops) {
|
|||||||
params.viewport.width = kTexWidth >> targetLevel;
|
params.viewport.width = kTexWidth >> targetLevel;
|
||||||
params.viewport.height = kTexHeight >> targetLevel;
|
params.viewport.height = kTexHeight >> targetLevel;
|
||||||
|
|
||||||
|
// Each pass needs its own descriptor set.
|
||||||
auto descriptorSet = shader.createDescriptorSet(api);
|
auto descriptorSet = shader.createDescriptorSet(api);
|
||||||
auto textureView = passCleanup.add(api.createTextureView(texture, sourceLevel, 1));
|
auto textureView = passCleanup.add(api.createTextureView(texture, sourceLevel, 1));
|
||||||
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, {
|
shader.updateTextureUniform(api,
|
||||||
.filterMag = SamplerMagFilter::LINEAR,
|
TextureBindingConfig{ .textureHandle = textureView,
|
||||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
.descriptorSet = descriptorSet,
|
||||||
});
|
.samplerParams = SamplerParams{ .filterMag = SamplerMagFilter::LINEAR,
|
||||||
|
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST } });
|
||||||
|
|
||||||
UniformBindingConfig uniformBinding{
|
UniformBindingConfig uniformBinding{
|
||||||
.binding = 1,
|
.binding = 1,
|
||||||
@@ -238,8 +261,7 @@ TEST_F(BackendTest, FeedbackLoops) {
|
|||||||
// NOTE: Calling glReadPixels on any miplevel other than the base level
|
// NOTE: Calling glReadPixels on any miplevel other than the base level
|
||||||
// seems to be un-reliable on some GPU's.
|
// seems to be un-reliable on some GPU's.
|
||||||
if (frame == kNumFrames - 1) {
|
if (frame == kNumFrames - 1) {
|
||||||
EXPECT_IMAGE(renderTargets[0], getExpectations(),
|
dumpScreenshot(api, renderTargets[0]);
|
||||||
ScreenshotParams(kTexWidth, kTexHeight, "FeedbackLoops", 0x70695aa1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
api.flush();
|
api.flush();
|
||||||
@@ -250,6 +272,10 @@ TEST_F(BackendTest, FeedbackLoops) {
|
|||||||
getDriver().purge();
|
getDriver().purge();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uint32_t expected = 0x70695aa1;
|
||||||
|
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", sPixelHashResult, expected);
|
||||||
|
EXPECT_TRUE(sPixelHashResult == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
|
|||||||
@@ -246,42 +246,42 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
|||||||
std::vector<TestCase> testCases;
|
std::vector<TestCase> testCases;
|
||||||
|
|
||||||
// Test basic upload.
|
// Test basic upload.
|
||||||
testCases.emplace_back("RGBA UBYTE to RGBA8", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8);
|
testCases.emplace_back("RGBA, UBYTE -> RGBA8", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8);
|
||||||
|
|
||||||
// Test format conversion.
|
// Test format conversion.
|
||||||
// TODO: Vulkan crashes with `Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)'
|
// TODO: Vulkan crashes with `Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)'
|
||||||
testCases.emplace_back("RGBA FLOAT to RGBA16F", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F);
|
testCases.emplace_back("RGBA, FLOAT -> RGBA16F", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F);
|
||||||
|
|
||||||
// Test texture formats not all backends support natively.
|
// Test texture formats not all backends support natively.
|
||||||
// TODO: Vulkan crashes with "VK_FORMAT_R32G32B32_SFLOAT is not supported"
|
// TODO: Vulkan crashes with "VK_FORMAT_R32G32B32_SFLOAT is not supported"
|
||||||
testCases.emplace_back("RGB FLOAT to RGB32F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F);
|
testCases.emplace_back("RGB, FLOAT -> RGB32F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F);
|
||||||
testCases.emplace_back("RGB FLOAT to RGB16F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB16F);
|
testCases.emplace_back("RGB, FLOAT -> RGB16F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB16F);
|
||||||
|
|
||||||
// Test packed format uploads.
|
// Test packed format uploads.
|
||||||
// TODO: Vulkan crashes with "Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)"
|
// TODO: Vulkan crashes with "Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)"
|
||||||
testCases.emplace_back("RGBA UINT_2_10_10_10_REV to RGB10_A2", PixelDataFormat::RGBA, PixelDataType::UINT_2_10_10_10_REV, TextureFormat::RGB10_A2);
|
testCases.emplace_back("RGBA, UINT_2_10_10_10_REV -> RGB10_A2", PixelDataFormat::RGBA, PixelDataType::UINT_2_10_10_10_REV, TextureFormat::RGB10_A2);
|
||||||
testCases.emplace_back("RGB UINT_10F_11F_11F_REV to R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::UINT_10F_11F_11F_REV, TextureFormat::R11F_G11F_B10F);
|
testCases.emplace_back("RGB, UINT_10F_11F_11F_REV -> R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::UINT_10F_11F_11F_REV, TextureFormat::R11F_G11F_B10F);
|
||||||
testCases.emplace_back("RGB HALF to R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::HALF, TextureFormat::R11F_G11F_B10F);
|
testCases.emplace_back("RGB, HALF -> R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::HALF, TextureFormat::R11F_G11F_B10F);
|
||||||
|
|
||||||
// Test integer format uploads.
|
// Test integer format uploads.
|
||||||
// TODO: These cases fail on OpenGL and Vulkan.
|
// TODO: These cases fail on OpenGL and Vulkan.
|
||||||
// TODO: These cases now also fail on Metal, but at some point previously worked.
|
// TODO: These cases now also fail on Metal, but at some point previously worked.
|
||||||
testCases.emplace_back("RGB_INTEGER UBYTE to RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
|
testCases.emplace_back("RGB_INTEGER, UBYTE -> RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
|
||||||
testCases.emplace_back("RGB_INTEGER USHORT to RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
|
testCases.emplace_back("RGB_INTEGER, USHORT -> RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
|
||||||
testCases.emplace_back("RGB_INTEGER INT to RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
|
testCases.emplace_back("RGB_INTEGER, INT -> RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
|
||||||
|
|
||||||
// Test uploads with buffer padding.
|
// Test uploads with buffer padding.
|
||||||
// TODO: Vulkan crashes with "Assertion failed: (offset + size <= allocationSize)"
|
// TODO: Vulkan crashes with "Assertion failed: (offset + size <= allocationSize)"
|
||||||
testCases.emplace_back("RGBA UBYTE to RGBA8 (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u);
|
testCases.emplace_back("RGBA, UBYTE -> RGBA8 (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u);
|
||||||
testCases.emplace_back("RGBA FLOAT to RGBA16F (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F, 64u);
|
testCases.emplace_back("RGBA, FLOAT -> RGBA16F (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F, 64u);
|
||||||
testCases.emplace_back("RGB FLOAT to RGB32F (with buffer padding)", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F, 64u);
|
testCases.emplace_back("RGB, FLOAT -> RGB32F (with buffer padding)", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F, 64u);
|
||||||
|
|
||||||
// Upload subregions separately.
|
// Upload subregions separately.
|
||||||
// TODO: Vulkan crashes with "Offsets not yet supported"
|
// TODO: Vulkan crashes with "Offsets not yet supported"
|
||||||
testCases.emplace_back("RGBA UBYTE to RGBA8 (subregions)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 0u, true);
|
testCases.emplace_back("RGBA, UBYTE -> RGBA8 (subregions)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 0u, true);
|
||||||
testCases.emplace_back("RGBA FLOAT to RGBA16F (subregions)", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F, 0u, true);
|
testCases.emplace_back("RGBA, FLOAT -> RGBA16F (subregions)", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F, 0u, true);
|
||||||
testCases.emplace_back("RGBA UBYTE to RGBA8 (subregions and buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u, true);
|
testCases.emplace_back("RGBA, UBYTE -> RGBA8 (subregions, buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u, true);
|
||||||
testCases.emplace_back("RGB FLOAT to RGB32F (subregions and buffer padding)", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F, 64u, true);
|
testCases.emplace_back("RGB, FLOAT -> RGB32F (subregions, buffer padding)", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F, 64u, true);
|
||||||
|
|
||||||
auto& api = getDriverApi();
|
auto& api = getDriverApi();
|
||||||
|
|
||||||
@@ -329,24 +329,28 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
|||||||
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 512, t.bufferPadding));
|
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 512, t.bufferPadding));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Each loop needs its own descriptor set.
|
||||||
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
||||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
|
shader.updateTextureUniform(api,
|
||||||
.filterMag = SamplerMagFilter::NEAREST,
|
TextureBindingConfig{ .textureHandle = texture,
|
||||||
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
|
.descriptorSet = descriptorSet,
|
||||||
|
.samplerParams = SamplerParams{ .filterMag = SamplerMagFilter::NEAREST,
|
||||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST },
|
||||||
|
.alsoBindToSet = 1 });
|
||||||
|
|
||||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||||
defaultRenderTarget, swapChain, shader.getProgram());
|
defaultRenderTarget, swapChain, shader.getProgram());
|
||||||
|
|
||||||
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
|
readPixelsAndAssertHash(t.name, 512, 512, defaultRenderTarget, expectedHash);
|
||||||
ScreenshotParams(512, 512, t.name, expectedHash));
|
|
||||||
|
|
||||||
api.commit(swapChain);
|
api.commit(swapChain);
|
||||||
api.endFrame(0);
|
api.endFrame(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
api.finish();
|
||||||
api.stopCapture();
|
api.stopCapture();
|
||||||
|
|
||||||
|
flushAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(LoadImageTest, UpdateImageSRGB) {
|
TEST_F(LoadImageTest, UpdateImageSRGB) {
|
||||||
@@ -370,7 +374,7 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
|
|||||||
getSamplerTypeName(textureFormat), fragmentTemplate);
|
getSamplerTypeName(textureFormat), fragmentTemplate);
|
||||||
Shader shader(api, cleanup, ShaderConfig{
|
Shader shader(api, cleanup, ShaderConfig{
|
||||||
.vertexShader = mVertexShader, .fragmentShader = fragment, .uniforms = {{
|
.vertexShader = mVertexShader, .fragmentShader = fragment, .uniforms = {{
|
||||||
"test_tex", DescriptorType::SAMPLER, samplerInfo
|
"text_tex", DescriptorType::SAMPLER, samplerInfo
|
||||||
}}});
|
}}});
|
||||||
|
|
||||||
// Create a texture.
|
// Create a texture.
|
||||||
@@ -403,23 +407,24 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
|
|||||||
api.beginFrame(0, 0, 0);
|
api.beginFrame(0, 0, 0);
|
||||||
|
|
||||||
// Update samplers.
|
// Update samplers.
|
||||||
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
shader.updateTextureUniform(api,
|
||||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
|
TextureBindingConfig{ .textureHandle = texture,
|
||||||
.filterMag = SamplerMagFilter::LINEAR,
|
.samplerParams = SamplerParams{ .filterMag = SamplerMagFilter::LINEAR,
|
||||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST },
|
||||||
});
|
.alsoBindToSet = 1 });
|
||||||
|
|
||||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
|
||||||
|
|
||||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||||
defaultRenderTarget, swapChain, shader.getProgram());
|
defaultRenderTarget, swapChain, shader.getProgram());
|
||||||
|
|
||||||
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
|
static const uint32_t expectedHash = 359858623;
|
||||||
ScreenshotParams(512, 512, "UpdateImageSRGB", 359858623));
|
readPixelsAndAssertHash("UpdateImageSRGB", 512, 512, defaultRenderTarget, expectedHash);
|
||||||
|
|
||||||
|
api.flush();
|
||||||
api.commit(swapChain);
|
api.commit(swapChain);
|
||||||
api.endFrame(0);
|
api.endFrame(0);
|
||||||
|
|
||||||
|
// This ensures all driver commands have finished before exiting the test.
|
||||||
|
api.finish();
|
||||||
api.stopCapture();
|
api.stopCapture();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,23 +467,24 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
|
|||||||
api.beginFrame(0, 0, 0);
|
api.beginFrame(0, 0, 0);
|
||||||
|
|
||||||
// Update samplers.
|
// Update samplers.
|
||||||
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
shader.updateTextureUniform(api,
|
||||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
|
TextureBindingConfig{ .textureHandle = texture,
|
||||||
.filterMag = SamplerMagFilter::LINEAR,
|
.samplerParams = SamplerParams{ .filterMag = SamplerMagFilter::LINEAR,
|
||||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST },
|
||||||
});
|
.alsoBindToSet = 1 });
|
||||||
|
|
||||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
|
||||||
|
|
||||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||||
defaultRenderTarget, swapChain, shader.getProgram());
|
defaultRenderTarget, swapChain, shader.getProgram());
|
||||||
|
|
||||||
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
|
static const uint32_t expectedHash = 3644679986;
|
||||||
ScreenshotParams(512, 512, "UpdateImageMipLevel", 3644679986));
|
readPixelsAndAssertHash("UpdateImageMipLevel", 512, 512, defaultRenderTarget, expectedHash);
|
||||||
|
|
||||||
|
api.flush();
|
||||||
api.commit(swapChain);
|
api.commit(swapChain);
|
||||||
api.endFrame(0);
|
api.endFrame(0);
|
||||||
|
|
||||||
|
// This ensures all driver commands have finished before exiting the test.
|
||||||
|
api.finish();
|
||||||
api.stopCapture();
|
api.stopCapture();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,24 +536,27 @@ TEST_F(LoadImageTest, UpdateImage3D) {
|
|||||||
|
|
||||||
api.update3DImage(texture, 0, 0, 0, 0, 512, 512, 4, std::move(descriptor));
|
api.update3DImage(texture, 0, 0, 0, 0, 512, 512, 4, std::move(descriptor));
|
||||||
|
|
||||||
{
|
api.beginFrame(0, 0, 0);
|
||||||
RenderFrame frame(api);
|
|
||||||
|
|
||||||
// Update samplers.
|
// Update samplers.
|
||||||
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
shader.updateTextureUniform(api,
|
||||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture,
|
TextureBindingConfig{ .textureHandle = texture,
|
||||||
{ .filterMag = SamplerMagFilter::LINEAR,
|
.samplerParams = SamplerParams{ .filterMag = SamplerMagFilter::LINEAR,
|
||||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST });
|
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST },
|
||||||
|
.alsoBindToSet = 1 });
|
||||||
|
|
||||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||||
|
defaultRenderTarget, swapChain, shader.getProgram());
|
||||||
|
|
||||||
renderTriangle({ { DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() } },
|
static const uint32_t expectedHash = 3644679986;
|
||||||
defaultRenderTarget, swapChain, shader.getProgram());
|
readPixelsAndAssertHash("UpdateImage3D", 512, 512, defaultRenderTarget, expectedHash);
|
||||||
|
|
||||||
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
|
api.flush();
|
||||||
ScreenshotParams(512, 512, "UpdateImage3D", 3644679986));
|
api.commit(swapChain);
|
||||||
}
|
api.endFrame(0);
|
||||||
|
|
||||||
|
// This ensures all driver commands have finished before exiting the test.
|
||||||
|
api.finish();
|
||||||
api.stopCapture();
|
api.stopCapture();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,11 +171,12 @@ TEST_F(BackendTest, TextureViewLod) {
|
|||||||
state.rasterState.culling = CullingMode::NONE;
|
state.rasterState.culling = CullingMode::NONE;
|
||||||
|
|
||||||
DescriptorSetHandle descriptorSet13 = texturedShader.createDescriptorSet(api);
|
DescriptorSetHandle descriptorSet13 = texturedShader.createDescriptorSet(api);
|
||||||
api.updateDescriptorSetTexture(descriptorSet13, 0, texture13, {
|
texturedShader.updateTextureUniform(api,
|
||||||
.filterMag = SamplerMagFilter::NEAREST,
|
TextureBindingConfig{ .textureHandle = texture13,
|
||||||
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
|
.descriptorSet = descriptorSet13,
|
||||||
|
.samplerParams = SamplerParams{ .filterMag = SamplerMagFilter::NEAREST,
|
||||||
api.bindDescriptorSet(descriptorSet13, 0, {});
|
.filterMin = SamplerMinFilter::NEAREST },
|
||||||
|
.alsoBindToSet = 0 });
|
||||||
|
|
||||||
// Render a triangle to the screen, sampling from mip level 1.
|
// Render a triangle to the screen, sampling from mip level 1.
|
||||||
// Because the min level is 1, the result color should be the white triangle drawn in the
|
// Because the min level is 1, the result color should be the white triangle drawn in the
|
||||||
@@ -189,11 +190,12 @@ TEST_F(BackendTest, TextureViewLod) {
|
|||||||
auto texture22 = cleanup.add(api.createTextureView(texture, 2, 2));
|
auto texture22 = cleanup.add(api.createTextureView(texture, 2, 2));
|
||||||
|
|
||||||
DescriptorSetHandle descriptorSet22 = texturedShader.createDescriptorSet(api);
|
DescriptorSetHandle descriptorSet22 = texturedShader.createDescriptorSet(api);
|
||||||
api.updateDescriptorSetTexture(descriptorSet22, 0, texture22, {
|
texturedShader.updateTextureUniform(api,
|
||||||
.filterMag = SamplerMagFilter::NEAREST,
|
TextureBindingConfig{ .textureHandle = texture22,
|
||||||
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
|
.descriptorSet = descriptorSet22,
|
||||||
|
.samplerParams = SamplerParams{ .filterMag = SamplerMagFilter::NEAREST,
|
||||||
api.bindDescriptorSet(descriptorSet22, 0, {});
|
.filterMin = SamplerMinFilter::NEAREST },
|
||||||
|
.alsoBindToSet = 0 });
|
||||||
|
|
||||||
// Render a second, smaller, triangle, again sampling from mip level 1.
|
// Render a second, smaller, triangle, again sampling from mip level 1.
|
||||||
// This triangle should be yellow striped.
|
// This triangle should be yellow striped.
|
||||||
|
|||||||
@@ -16,10 +16,8 @@
|
|||||||
|
|
||||||
#include "BackendTest.h"
|
#include "BackendTest.h"
|
||||||
|
|
||||||
#include "ImageExpectations.h"
|
|
||||||
#include "Lifetimes.h"
|
#include "Lifetimes.h"
|
||||||
#include "ShaderGenerator.h"
|
#include "ShaderGenerator.h"
|
||||||
#include "Skip.h"
|
|
||||||
#include "TrianglePrimitive.h"
|
#include "TrianglePrimitive.h"
|
||||||
|
|
||||||
#include <utils/Hash.h>
|
#include <utils/Hash.h>
|
||||||
@@ -80,8 +78,6 @@ void main() {
|
|||||||
})";
|
})";
|
||||||
|
|
||||||
TEST_F(BackendTest, PushConstants) {
|
TEST_F(BackendTest, PushConstants) {
|
||||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL));
|
|
||||||
|
|
||||||
auto& api = getDriverApi();
|
auto& api = getDriverApi();
|
||||||
|
|
||||||
api.startCapture(0);
|
api.startCapture(0);
|
||||||
@@ -152,14 +148,19 @@ TEST_F(BackendTest, PushConstants) {
|
|||||||
|
|
||||||
api.endRenderPass();
|
api.endRenderPass();
|
||||||
|
|
||||||
EXPECT_IMAGE(renderTarget, getExpectations(),
|
readPixelsAndAssertHash("pushConstants", 512, 512, renderTarget, 1957275826, true);
|
||||||
ScreenshotParams(512, 512, "pushConstants", 1957275826));
|
|
||||||
|
|
||||||
api.commit(swapChain);
|
api.commit(swapChain);
|
||||||
api.endFrame(0);
|
api.endFrame(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
api.stopCapture(0);
|
api.stopCapture(0);
|
||||||
|
|
||||||
|
// Wait for the ReadPixels result to come back.
|
||||||
|
api.finish();
|
||||||
|
|
||||||
|
executeCommands();
|
||||||
|
getDriver().purge();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
#include "BackendTest.h"
|
#include "BackendTest.h"
|
||||||
|
|
||||||
#include "ImageExpectations.h"
|
|
||||||
#include "Lifetimes.h"
|
#include "Lifetimes.h"
|
||||||
#include "Shader.h"
|
#include "Shader.h"
|
||||||
#include "SharedShaders.h"
|
#include "SharedShaders.h"
|
||||||
@@ -85,14 +84,12 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
|
|||||||
state.rasterState.depthFunc = RasterState::DepthFunc::A;
|
state.rasterState.depthFunc = RasterState::DepthFunc::A;
|
||||||
state.rasterState.culling = CullingMode::NONE;
|
state.rasterState.culling = CullingMode::NONE;
|
||||||
|
|
||||||
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
|
||||||
|
|
||||||
api.startCapture(0);
|
api.startCapture(0);
|
||||||
api.makeCurrent(swapChain, swapChain);
|
api.makeCurrent(swapChain, swapChain);
|
||||||
api.beginFrame(0, 0, 0);
|
api.beginFrame(0, 0, 0);
|
||||||
|
|
||||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {});
|
shader.updateTextureUniform(api,
|
||||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
TextureBindingConfig{ .textureHandle = texture, .alsoBindToSet = 1 });
|
||||||
|
|
||||||
// Render a triangle.
|
// Render a triangle.
|
||||||
api.beginRenderPass(defaultRenderTarget, params);
|
api.beginRenderPass(defaultRenderTarget, params);
|
||||||
@@ -201,16 +198,17 @@ TEST_F(BackendTest, RenderExternalImage) {
|
|||||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||||
api.endRenderPass();
|
api.endRenderPass();
|
||||||
|
|
||||||
|
readPixelsAndAssertHash("RenderExternalImage", 512, 512, defaultRenderTarget, 267229901, true);
|
||||||
|
|
||||||
api.flush();
|
api.flush();
|
||||||
api.commit(swapChain);
|
api.commit(swapChain);
|
||||||
api.endFrame(0);
|
api.endFrame(0);
|
||||||
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
|
|
||||||
ScreenshotParams(512, 512, "RenderExternalImage", 267229901));
|
|
||||||
|
|
||||||
api.stopCapture(0);
|
api.stopCapture(0);
|
||||||
api.finish();
|
|
||||||
flushAndWait();
|
|
||||||
|
|
||||||
|
api.finish();
|
||||||
|
|
||||||
|
executeCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
#include "BackendTest.h"
|
#include "BackendTest.h"
|
||||||
|
|
||||||
#include "ImageExpectations.h"
|
|
||||||
#include "Lifetimes.h"
|
#include "Lifetimes.h"
|
||||||
#include "Shader.h"
|
#include "Shader.h"
|
||||||
#include "SharedShaders.h"
|
#include "SharedShaders.h"
|
||||||
@@ -131,14 +130,20 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
|||||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||||
api.endRenderPass();
|
api.endRenderPass();
|
||||||
|
|
||||||
EXPECT_IMAGE(fullRenderTarget, getExpectations(),
|
readPixelsAndAssertHash("scissor", kSrcTexWidth >> 1, kSrcTexHeight >> 1, fullRenderTarget,
|
||||||
ScreenshotParams(kSrcTexWidth >> 1, kSrcTexHeight >> 1, "scissor", 0xAB3D1C53));
|
0xAB3D1C53, true);
|
||||||
|
|
||||||
api.commit(swapChain);
|
api.commit(swapChain);
|
||||||
api.endFrame(0);
|
api.endFrame(0);
|
||||||
|
|
||||||
api.stopCapture(0);
|
api.stopCapture(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for the ReadPixels result to come back.
|
||||||
|
api.finish();
|
||||||
|
|
||||||
|
executeCommands();
|
||||||
|
getDriver().purge();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that a negative Viewport origin works with scissor.
|
// Verify that a negative Viewport origin works with scissor.
|
||||||
@@ -221,14 +226,20 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
|
|||||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||||
api.endRenderPass();
|
api.endRenderPass();
|
||||||
|
|
||||||
EXPECT_IMAGE(renderTarget, getExpectations(),
|
readPixelsAndAssertHash(
|
||||||
ScreenshotParams(512, 512, "ScissorViewportEdgeCases", 0x6BF00F31));
|
"ScissorViewportEdgeCases", 512, 512, renderTarget, 0x6BF00F31, true);
|
||||||
|
|
||||||
api.commit(swapChain);
|
api.commit(swapChain);
|
||||||
api.endFrame(0);
|
api.endFrame(0);
|
||||||
|
|
||||||
api.stopCapture(0);
|
api.stopCapture(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for the ReadPixels result to come back.
|
||||||
|
api.finish();
|
||||||
|
|
||||||
|
executeCommands();
|
||||||
|
getDriver().purge();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
#include "BackendTest.h"
|
#include "BackendTest.h"
|
||||||
|
|
||||||
#include "ImageExpectations.h"
|
|
||||||
#include "Lifetimes.h"
|
#include "Lifetimes.h"
|
||||||
#include "Shader.h"
|
#include "Shader.h"
|
||||||
#include "SharedShaders.h"
|
#include "SharedShaders.h"
|
||||||
@@ -130,8 +129,7 @@ TEST_F(BasicStencilBufferTest, StencilBuffer) {
|
|||||||
|
|
||||||
RunTest(renderTarget);
|
RunTest(renderTarget);
|
||||||
|
|
||||||
EXPECT_IMAGE(renderTarget, getExpectations(),
|
readPixelsAndAssertHash("StencilBuffer", 512, 512, renderTarget, 0x3B1AEF0F, true);
|
||||||
ScreenshotParams(512, 512, "StencilBuffer", 0x3B1AEF0F));
|
|
||||||
|
|
||||||
flushAndWait();
|
flushAndWait();
|
||||||
getDriver().purge();
|
getDriver().purge();
|
||||||
@@ -153,8 +151,7 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
|
|||||||
|
|
||||||
RunTest(renderTarget);
|
RunTest(renderTarget);
|
||||||
|
|
||||||
EXPECT_IMAGE(renderTarget, getExpectations(),
|
readPixelsAndAssertHash("DepthAndStencilBuffer", 512, 512, renderTarget, 0x3B1AEF0F, true);
|
||||||
ScreenshotParams(512, 512, "DepthAndStencilBuffer", 0x3B1AEF0F));
|
|
||||||
|
|
||||||
flushAndWait();
|
flushAndWait();
|
||||||
getDriver().purge();
|
getDriver().purge();
|
||||||
@@ -236,8 +233,7 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
|||||||
api.stopCapture(0);
|
api.stopCapture(0);
|
||||||
api.endFrame(0);
|
api.endFrame(0);
|
||||||
|
|
||||||
EXPECT_IMAGE(renderTarget1, getExpectations(),
|
readPixelsAndAssertHash("StencilBufferAutoResolve", 512, 512, renderTarget1, 0x6CEFAC8F, true);
|
||||||
ScreenshotParams(512, 512, "StencilBufferAutoResolve", 0x6CEFAC8F));
|
|
||||||
|
|
||||||
flushAndWait();
|
flushAndWait();
|
||||||
getDriver().purge();
|
getDriver().purge();
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
Pod::Spec.new do |spec|
|
Pod::Spec.new do |spec|
|
||||||
spec.name = "Filament"
|
spec.name = "Filament"
|
||||||
spec.version = "1.59.3"
|
spec.version = "1.59.1"
|
||||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||||
spec.homepage = "https://google.github.io/filament"
|
spec.homepage = "https://google.github.io/filament"
|
||||||
spec.authors = "Google LLC."
|
spec.authors = "Google LLC."
|
||||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||||
spec.platform = :ios, "11.0"
|
spec.platform = :ios, "11.0"
|
||||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.59.3/filament-v1.59.3-ios.tgz" }
|
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.59.1/filament-v1.59.1-ios.tgz" }
|
||||||
|
|
||||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||||
spec.pod_target_xcconfig = {
|
spec.pod_target_xcconfig = {
|
||||||
|
|||||||
@@ -50,8 +50,6 @@
|
|||||||
#include <utils/Log.h>
|
#include <utils/Log.h>
|
||||||
#include <utils/Panic.h>
|
#include <utils/Panic.h>
|
||||||
#include <utils/NameComponentManager.h>
|
#include <utils/NameComponentManager.h>
|
||||||
|
|
||||||
#define SYSTRACE_TAG SYSTRACE_TAG_GLTFIO
|
|
||||||
#include <utils/Systrace.h>
|
#include <utils/Systrace.h>
|
||||||
|
|
||||||
#include <tsl/robin_map.h>
|
#include <tsl/robin_map.h>
|
||||||
|
|||||||
@@ -37,10 +37,7 @@
|
|||||||
#include <utils/compiler.h>
|
#include <utils/compiler.h>
|
||||||
#include <utils/JobSystem.h>
|
#include <utils/JobSystem.h>
|
||||||
#include <utils/Log.h>
|
#include <utils/Log.h>
|
||||||
|
|
||||||
#define SYSTRACE_TAG SYSTRACE_TAG_GLTFIO
|
|
||||||
#include <utils/Systrace.h>
|
#include <utils/Systrace.h>
|
||||||
|
|
||||||
#include <utils/Path.h>
|
#include <utils/Path.h>
|
||||||
|
|
||||||
#include <cgltf.h>
|
#include <cgltf.h>
|
||||||
|
|||||||
@@ -21,8 +21,6 @@
|
|||||||
#include "GltfEnums.h"
|
#include "GltfEnums.h"
|
||||||
|
|
||||||
#include <utils/Log.h>
|
#include <utils/Log.h>
|
||||||
|
|
||||||
#define SYSTRACE_TAG SYSTRACE_TAG_GLTFIO
|
|
||||||
#include <utils/Systrace.h>
|
#include <utils/Systrace.h>
|
||||||
|
|
||||||
#define CGLTF_IMPLEMENTATION
|
#define CGLTF_IMPLEMENTATION
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ if (ANDROID)
|
|||||||
target_link_libraries(${TARGET} PUBLIC log)
|
target_link_libraries(${TARGET} PUBLIC log)
|
||||||
target_link_libraries(${TARGET} PRIVATE dl)
|
target_link_libraries(${TARGET} PRIVATE dl)
|
||||||
target_link_libraries(${TARGET} PUBLIC android)
|
target_link_libraries(${TARGET} PUBLIC android)
|
||||||
target_link_libraries(${TARGET} PUBLIC perfetto)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
|
|||||||
@@ -17,23 +17,24 @@
|
|||||||
#ifndef TNT_UTILS_SYSTRACE_H
|
#ifndef TNT_UTILS_SYSTRACE_H
|
||||||
#define TNT_UTILS_SYSTRACE_H
|
#define TNT_UTILS_SYSTRACE_H
|
||||||
|
|
||||||
#define SYSTRACE_TAG_DISABLED (0)
|
|
||||||
#define SYSTRACE_TAG_FILAMENT (2) // don't change used in makefiles
|
#define SYSTRACE_TAG_NEVER (0)
|
||||||
#define SYSTRACE_TAG_JOBSYSTEM (3)
|
#define SYSTRACE_TAG_ALWAYS (1<<0)
|
||||||
#define SYSTRACE_TAG_GLTFIO (4)
|
#define SYSTRACE_TAG_FILAMENT (1<<1) // don't change, used in makefiles
|
||||||
|
#define SYSTRACE_TAG_JOBSYSTEM (1<<2)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The SYSTRACE_ macros use SYSTRACE_TAG as a category, which must be defined
|
* The SYSTRACE_ macros use SYSTRACE_TAG as a the TAG, which should be defined
|
||||||
* before this file is included.
|
* before this file is included. If not, the SYSTRACE_TAG_ALWAYS tag will be used.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef SYSTRACE_TAG
|
#ifndef SYSTRACE_TAG
|
||||||
# error SYSTRACE_TAG must be set to SYSTRACE_TAG_{DISABLED|FILAMENT|JOBSYSTEM}
|
#define SYSTRACE_TAG (SYSTRACE_TAG_ALWAYS)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Systrace on Apple platforms is fragile and adds overhead, should only be enabled in dev builds.
|
// Systrace on Apple platforms is fragile and adds overhead, should only be enabled in dev builds.
|
||||||
#ifndef FILAMENT_APPLE_SYSTRACE
|
#ifndef FILAMENT_APPLE_SYSTRACE
|
||||||
# define FILAMENT_APPLE_SYSTRACE 0
|
#define FILAMENT_APPLE_SYSTRACE 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__ANDROID__)
|
#if defined(__ANDROID__)
|
||||||
@@ -43,6 +44,7 @@
|
|||||||
#else
|
#else
|
||||||
|
|
||||||
#define SYSTRACE_ENABLE()
|
#define SYSTRACE_ENABLE()
|
||||||
|
#define SYSTRACE_DISABLE()
|
||||||
#define SYSTRACE_CONTEXT()
|
#define SYSTRACE_CONTEXT()
|
||||||
#define SYSTRACE_NAME(name)
|
#define SYSTRACE_NAME(name)
|
||||||
#define SYSTRACE_FRAME_ID(frame)
|
#define SYSTRACE_FRAME_ID(frame)
|
||||||
@@ -54,6 +56,6 @@
|
|||||||
#define SYSTRACE_VALUE32(name, val)
|
#define SYSTRACE_VALUE32(name, val)
|
||||||
#define SYSTRACE_VALUE64(name, val)
|
#define SYSTRACE_VALUE64(name, val)
|
||||||
|
|
||||||
#endif
|
#endif // ANDROID
|
||||||
|
|
||||||
#endif // TNT_UTILS_SYSTRACE_H
|
#endif // TNT_UTILS_SYSTRACE_H
|
||||||
|
|||||||
@@ -17,75 +17,226 @@
|
|||||||
#ifndef TNT_UTILS_ANDROID_SYSTRACE_H
|
#ifndef TNT_UTILS_ANDROID_SYSTRACE_H
|
||||||
#define TNT_UTILS_ANDROID_SYSTRACE_H
|
#define TNT_UTILS_ANDROID_SYSTRACE_H
|
||||||
|
|
||||||
#include <perfetto/perfetto.h>
|
#include <atomic>
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
PERFETTO_DEFINE_CATEGORIES_IN_NAMESPACE(systrace,
|
#include <utils/compiler.h>
|
||||||
perfetto::Category("filament"),
|
|
||||||
perfetto::Category("jobsystem"),
|
|
||||||
perfetto::Category("gltfio"));
|
|
||||||
|
|
||||||
PERFETTO_USE_CATEGORIES_FROM_NAMESPACE(systrace);
|
// enable tracing
|
||||||
|
#define SYSTRACE_ENABLE() ::utils::details::Systrace::enable(SYSTRACE_TAG)
|
||||||
|
|
||||||
#if SYSTRACE_TAG == SYSTRACE_TAG_FILAMENT
|
// disable tracing
|
||||||
# define UTILS_PERFETTO_CATEGORY "filament"
|
#define SYSTRACE_DISABLE() ::utils::details::Systrace::disable(SYSTRACE_TAG)
|
||||||
#elif SYSTRACE_TAG == SYSTRACE_TAG_JOBSYSTEM
|
|
||||||
# define UTILS_PERFETTO_CATEGORY "jobsystem"
|
|
||||||
#elif SYSTRACE_TAG == SYSTRACE_TAG_GLTFIO
|
|
||||||
# define UTILS_PERFETTO_CATEGORY "gltfio"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if SYSTRACE_TAG == SYSTRACE_TAG_DISABLED
|
|
||||||
|
|
||||||
#define SYSTRACE_ENABLE()
|
/**
|
||||||
#define SYSTRACE_CONTEXT()
|
* Creates a Systrace context in the current scope. needed for calling all other systrace
|
||||||
#define SYSTRACE_NAME(name)
|
* commands below.
|
||||||
#define SYSTRACE_FRAME_ID(frame)
|
*/
|
||||||
#define SYSTRACE_NAME_BEGIN(name)
|
#define SYSTRACE_CONTEXT() ::utils::details::Systrace ___trctx(SYSTRACE_TAG)
|
||||||
#define SYSTRACE_NAME_END()
|
|
||||||
#define SYSTRACE_CALL()
|
|
||||||
#define SYSTRACE_ASYNC_BEGIN(name, cookie)
|
|
||||||
#define SYSTRACE_ASYNC_END(name, cookie)
|
|
||||||
#define SYSTRACE_VALUE32(name, val)
|
|
||||||
#define SYSTRACE_VALUE64(name, val)
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#define SYSTRACE_ENABLE()
|
// SYSTRACE_NAME traces the beginning and end of the current scope. To trace
|
||||||
#define SYSTRACE_CONTEXT()
|
// the correct start and end times this macro should be declared first in the
|
||||||
|
// scope body.
|
||||||
#define SYSTRACE_CALL() \
|
// It also automatically creates a Systrace context
|
||||||
auto constexpr FILAMENT_SYSTRACE_FUNCTION = perfetto::StaticString(__FUNCTION__); \
|
#define SYSTRACE_NAME(name) ::utils::details::ScopedTrace ___tracer(SYSTRACE_TAG, name)
|
||||||
TRACE_EVENT(UTILS_PERFETTO_CATEGORY, FILAMENT_SYSTRACE_FUNCTION)
|
|
||||||
|
|
||||||
#define SYSTRACE_NAME(name) TRACE_EVENT(UTILS_PERFETTO_CATEGORY, nullptr, \
|
|
||||||
[&](perfetto::EventContext ctx) { \
|
|
||||||
ctx.event()->set_name(name); \
|
|
||||||
})
|
|
||||||
|
|
||||||
#define SYSTRACE_NAME_BEGIN(name) TRACE_EVENT_BEGIN(UTILS_PERFETTO_CATEGORY, nullptr, \
|
|
||||||
[&](perfetto::EventContext ctx) { \
|
|
||||||
ctx.event()->set_name(name); \
|
|
||||||
})
|
|
||||||
|
|
||||||
#define SYSTRACE_NAME_END() TRACE_EVENT_END(UTILS_PERFETTO_CATEGORY)
|
|
||||||
|
|
||||||
#define SYSTRACE_ASYNC_BEGIN(name, cookie) \
|
|
||||||
TRACE_EVENT_BEGIN(UTILS_PERFETTO_CATEGORY, name, perfetto::Track(cookie))
|
|
||||||
|
|
||||||
#define SYSTRACE_ASYNC_END(name, cookie) \
|
|
||||||
TRACE_EVENT_END(UTILS_PERFETTO_CATEGORY, perfetto::Track(cookie))
|
|
||||||
|
|
||||||
|
// Denotes that a new frame has started processing.
|
||||||
#define SYSTRACE_FRAME_ID(frame) \
|
#define SYSTRACE_FRAME_ID(frame) \
|
||||||
TRACE_EVENT_INSTANT(UTILS_PERFETTO_CATEGORY, "frame", "id", frame)
|
{ /* scope for frame id trace */ \
|
||||||
|
char buf[64]; \
|
||||||
|
snprintf(buf, 64, "frame %u", frame); \
|
||||||
|
SYSTRACE_NAME(buf); \
|
||||||
|
}
|
||||||
|
|
||||||
|
// SYSTRACE_CALL is an SYSTRACE_NAME that uses the current function name.
|
||||||
|
#define SYSTRACE_CALL() SYSTRACE_NAME(__FUNCTION__)
|
||||||
|
|
||||||
|
#define SYSTRACE_NAME_BEGIN(name) \
|
||||||
|
___trctx.traceBegin(SYSTRACE_TAG, name)
|
||||||
|
|
||||||
|
#define SYSTRACE_NAME_END() \
|
||||||
|
___trctx.traceEnd(SYSTRACE_TAG)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trace the beginning of an asynchronous event. Unlike ATRACE_BEGIN/ATRACE_END
|
||||||
|
* contexts, asynchronous events do not need to be nested. The name describes
|
||||||
|
* the event, and the cookie provides a unique identifier for distinguishing
|
||||||
|
* simultaneous events. The name and cookie used to begin an event must be
|
||||||
|
* used to end it.
|
||||||
|
*/
|
||||||
|
#define SYSTRACE_ASYNC_BEGIN(name, cookie) \
|
||||||
|
___trctx.asyncBegin(SYSTRACE_TAG, name, cookie)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trace the end of an asynchronous event.
|
||||||
|
* This should have a corresponding SYSTRACE_ASYNC_BEGIN.
|
||||||
|
*/
|
||||||
|
#define SYSTRACE_ASYNC_END(name, cookie) \
|
||||||
|
___trctx.asyncEnd(SYSTRACE_TAG, name, cookie)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traces an integer counter value. name is used to identify the counter.
|
||||||
|
* This can be used to track how a value changes over time.
|
||||||
|
*/
|
||||||
#define SYSTRACE_VALUE32(name, val) \
|
#define SYSTRACE_VALUE32(name, val) \
|
||||||
TRACE_COUNTER(UTILS_PERFETTO_CATEGORY, name, val)
|
___trctx.value(SYSTRACE_TAG, name, int32_t(val))
|
||||||
|
|
||||||
#define SYSTRACE_VALUE64(name, val) \
|
#define SYSTRACE_VALUE64(name, val) \
|
||||||
TRACE_COUNTER(UTILS_PERFETTO_CATEGORY, name, val)
|
___trctx.value(SYSTRACE_TAG, name, int64_t(val))
|
||||||
|
|
||||||
#endif // SYSTRACE_TAG == SYSTRACE_TAG_DISABLED
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// No user serviceable code below...
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace utils {
|
||||||
|
namespace details {
|
||||||
|
|
||||||
|
class UTILS_PUBLIC Systrace {
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum tags {
|
||||||
|
NEVER = SYSTRACE_TAG_NEVER,
|
||||||
|
ALWAYS = SYSTRACE_TAG_ALWAYS,
|
||||||
|
FILAMENT = SYSTRACE_TAG_FILAMENT,
|
||||||
|
JOBSYSTEM = SYSTRACE_TAG_JOBSYSTEM
|
||||||
|
// we could define more TAGS here, as we need them.
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit Systrace(uint32_t tag) noexcept {
|
||||||
|
if (tag) init(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void enable(uint32_t tags) noexcept;
|
||||||
|
static void disable(uint32_t tags) noexcept;
|
||||||
|
|
||||||
|
|
||||||
|
inline void traceBegin(uint32_t tag, const char* name) noexcept {
|
||||||
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
|
beginSection(this, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void traceEnd(uint32_t tag) noexcept {
|
||||||
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
|
endSection(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void asyncBegin(uint32_t tag, const char* name, int32_t cookie) noexcept {
|
||||||
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
|
beginAsyncSection(this, name, cookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void asyncEnd(uint32_t tag, const char* name, int32_t cookie) noexcept {
|
||||||
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
|
endAsyncSection(this, name, cookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void value(uint32_t tag, const char* name, int32_t value) noexcept {
|
||||||
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
|
setCounter(this, name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void value(uint32_t tag, const char* name, int64_t value) noexcept {
|
||||||
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
|
setCounter(this, name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class ScopedTrace;
|
||||||
|
|
||||||
|
// whether tracing is supported at all by the platform
|
||||||
|
|
||||||
|
using ATrace_isEnabled_t = bool (*)();
|
||||||
|
using ATrace_beginSection_t = void (*)(const char* sectionName);
|
||||||
|
using ATrace_endSection_t = void (*)();
|
||||||
|
using ATrace_beginAsyncSection_t = void (*)(const char* sectionName, int32_t cookie);
|
||||||
|
using ATrace_endAsyncSection_t = void (*)(const char* sectionName, int32_t cookie);
|
||||||
|
using ATrace_setCounter_t = void (*)(const char* counterName, int64_t counterValue);
|
||||||
|
|
||||||
|
struct GlobalState {
|
||||||
|
bool isTracingAvailable;
|
||||||
|
std::atomic<uint32_t> isTracingEnabled;
|
||||||
|
int markerFd;
|
||||||
|
|
||||||
|
ATrace_isEnabled_t ATrace_isEnabled;
|
||||||
|
ATrace_beginSection_t ATrace_beginSection;
|
||||||
|
ATrace_endSection_t ATrace_endSection;
|
||||||
|
ATrace_beginAsyncSection_t ATrace_beginAsyncSection;
|
||||||
|
ATrace_endAsyncSection_t ATrace_endAsyncSection;
|
||||||
|
ATrace_setCounter_t ATrace_setCounter;
|
||||||
|
|
||||||
|
void (*beginSection)(Systrace* that, const char* name);
|
||||||
|
void (*endSection)(Systrace* that);
|
||||||
|
void (*beginAsyncSection)(Systrace* that, const char* name, int32_t cookie);
|
||||||
|
void (*endAsyncSection)(Systrace* that, const char* name, int32_t cookie);
|
||||||
|
void (*setCounter)(Systrace* that, const char* name, int64_t value);
|
||||||
|
};
|
||||||
|
|
||||||
|
static GlobalState sGlobalState;
|
||||||
|
|
||||||
|
|
||||||
|
// per-instance versions for better performance
|
||||||
|
ATrace_isEnabled_t ATrace_isEnabled;
|
||||||
|
ATrace_beginSection_t ATrace_beginSection;
|
||||||
|
ATrace_endSection_t ATrace_endSection;
|
||||||
|
ATrace_beginAsyncSection_t ATrace_beginAsyncSection;
|
||||||
|
ATrace_endAsyncSection_t ATrace_endAsyncSection;
|
||||||
|
ATrace_setCounter_t ATrace_setCounter;
|
||||||
|
|
||||||
|
void (*beginSection)(Systrace* that, const char* name);
|
||||||
|
void (*endSection)(Systrace* that);
|
||||||
|
void (*beginAsyncSection)(Systrace* that, const char* name, int32_t cookie);
|
||||||
|
void (*endAsyncSection)(Systrace* that, const char* name, int32_t cookie);
|
||||||
|
void (*setCounter)(Systrace* that, const char* name, int64_t value);
|
||||||
|
|
||||||
|
void init(uint32_t tag) noexcept;
|
||||||
|
|
||||||
|
// cached values for faster access, no need to be initialized
|
||||||
|
bool mIsTracingEnabled;
|
||||||
|
int mMarkerFd = -1;
|
||||||
|
pid_t mPid;
|
||||||
|
|
||||||
|
static void setup() noexcept;
|
||||||
|
static void init_once() noexcept;
|
||||||
|
static bool isTracingEnabled(uint32_t tag) noexcept;
|
||||||
|
|
||||||
|
static void begin_body(int fd, int pid, const char* name) noexcept;
|
||||||
|
static void end_body(int fd, int pid) noexcept;
|
||||||
|
static void async_begin_body(int fd, int pid, const char* name, int32_t cookie) noexcept;
|
||||||
|
static void async_end_body(int fd, int pid, const char* name, int32_t cookie) noexcept;
|
||||||
|
static void int64_body(int fd, int pid, const char* name, int64_t value) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class UTILS_PUBLIC ScopedTrace {
|
||||||
|
public:
|
||||||
|
// we don't inline this because it's relatively heavy due to a global check
|
||||||
|
ScopedTrace(uint32_t tag, const char* name) noexcept: mTrace(tag), mTag(tag) {
|
||||||
|
mTrace.traceBegin(tag, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ~ScopedTrace() noexcept {
|
||||||
|
mTrace.traceEnd(mTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Systrace mTrace;
|
||||||
|
const uint32_t mTag;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace details
|
||||||
|
} // namespace utils
|
||||||
|
|
||||||
#endif // TNT_UTILS_ANDROID_SYSTRACE_H
|
#endif // TNT_UTILS_ANDROID_SYSTRACE_H
|
||||||
|
|||||||
@@ -29,25 +29,13 @@
|
|||||||
#include <utils/compiler.h>
|
#include <utils/compiler.h>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
|
|
||||||
#if SYSTRACE_TAG == SYSTRACE_TAG_DISABLED
|
|
||||||
|
|
||||||
#define SYSTRACE_ENABLE()
|
|
||||||
#define SYSTRACE_CONTEXT()
|
|
||||||
#define SYSTRACE_NAME(name)
|
|
||||||
#define SYSTRACE_FRAME_ID(frame)
|
|
||||||
#define SYSTRACE_NAME_BEGIN(name)
|
|
||||||
#define SYSTRACE_NAME_END()
|
|
||||||
#define SYSTRACE_CALL()
|
|
||||||
#define SYSTRACE_ASYNC_BEGIN(name, cookie)
|
|
||||||
#define SYSTRACE_ASYNC_END(name, cookie)
|
|
||||||
#define SYSTRACE_VALUE32(name, val)
|
|
||||||
#define SYSTRACE_VALUE64(name, val)
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
// enable tracing
|
// enable tracing
|
||||||
#define SYSTRACE_ENABLE() ::utils::details::Systrace::enable(SYSTRACE_TAG)
|
#define SYSTRACE_ENABLE() ::utils::details::Systrace::enable(SYSTRACE_TAG)
|
||||||
|
|
||||||
|
// disable tracing
|
||||||
|
#define SYSTRACE_DISABLE() ::utils::details::Systrace::disable(SYSTRACE_TAG)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a Systrace context in the current scope. needed for calling all other systrace
|
* Creates a Systrace context in the current scope. needed for calling all other systrace
|
||||||
* commands below.
|
* commands below.
|
||||||
@@ -105,8 +93,6 @@ extern thread_local std::stack<const char*> ___tracerSections;
|
|||||||
#define SYSTRACE_VALUE64(name, val) \
|
#define SYSTRACE_VALUE64(name, val) \
|
||||||
___tracer.value(SYSTRACE_TAG, name, int64_t(val))
|
___tracer.value(SYSTRACE_TAG, name, int64_t(val))
|
||||||
|
|
||||||
#endif // SYSTRACE_TAG == SYSTRACE_TAG_DISABLED
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// No user serviceable code below...
|
// No user serviceable code below...
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
@@ -132,40 +118,50 @@ namespace utils {
|
|||||||
namespace details {
|
namespace details {
|
||||||
|
|
||||||
class Systrace {
|
class Systrace {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
enum tags {
|
||||||
|
NEVER = SYSTRACE_TAG_NEVER,
|
||||||
|
ALWAYS = SYSTRACE_TAG_ALWAYS,
|
||||||
|
FILAMENT = SYSTRACE_TAG_FILAMENT,
|
||||||
|
JOBSYSTEM = SYSTRACE_TAG_JOBSYSTEM
|
||||||
|
// we could define more TAGS here, as we need them.
|
||||||
|
};
|
||||||
|
|
||||||
explicit Systrace(uint32_t tag) noexcept {
|
explicit Systrace(uint32_t tag) noexcept {
|
||||||
if (tag) init(tag);
|
if (tag) init(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void enable(uint32_t tag) noexcept;
|
static void enable(uint32_t tags) noexcept;
|
||||||
|
static void disable(uint32_t tags) noexcept;
|
||||||
|
|
||||||
void traceBegin(uint32_t tag, const char* name) noexcept {
|
inline void traceBegin(uint32_t tag, const char* name) noexcept {
|
||||||
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
APPLE_SIGNPOST_EMIT(sGlobalState.systraceLog, OS_SIGNPOST_INTERVAL_BEGIN,
|
APPLE_SIGNPOST_EMIT(sGlobalState.systraceLog, OS_SIGNPOST_INTERVAL_BEGIN,
|
||||||
OS_SIGNPOST_ID_EXCLUSIVE, name, name)
|
OS_SIGNPOST_ID_EXCLUSIVE, name, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void traceEnd(uint32_t tag, const char* name) noexcept {
|
inline void traceEnd(uint32_t tag, const char* name) noexcept {
|
||||||
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
APPLE_SIGNPOST_EMIT(sGlobalState.systraceLog, OS_SIGNPOST_INTERVAL_END,
|
APPLE_SIGNPOST_EMIT(sGlobalState.systraceLog, OS_SIGNPOST_INTERVAL_END,
|
||||||
OS_SIGNPOST_ID_EXCLUSIVE, name, "")
|
OS_SIGNPOST_ID_EXCLUSIVE, name, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void asyncBegin(uint32_t tag, const char* name, int32_t cookie) noexcept {
|
inline void asyncBegin(uint32_t tag, const char* name, int32_t cookie) noexcept {
|
||||||
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void asyncEnd(uint32_t tag, const char* name, int32_t cookie) noexcept {
|
inline void asyncEnd(uint32_t tag, const char* name, int32_t cookie) noexcept {
|
||||||
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void value(uint32_t tag, const char* name, int32_t value) noexcept {
|
inline void value(uint32_t tag, const char* name, int32_t value) noexcept {
|
||||||
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
char buf[64];
|
char buf[64];
|
||||||
snprintf(buf, 64, "%s - %d", name, value);
|
snprintf(buf, 64, "%s - %d", name, value);
|
||||||
@@ -174,7 +170,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void value(uint32_t tag, const char* name, int64_t value) noexcept {
|
inline void value(uint32_t tag, const char* name, int64_t value) noexcept {
|
||||||
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
char buf[64];
|
char buf[64];
|
||||||
snprintf(buf, 64, "%s - %lld", name, value);
|
snprintf(buf, 64, "%s - %lld", name, value);
|
||||||
@@ -183,16 +179,16 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void frameId(uint32_t tag, uint32_t frame) noexcept {
|
inline void frameId(uint32_t tag, uint32_t frame) noexcept {
|
||||||
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
char buf[64];
|
char buf[64]; \
|
||||||
snprintf(buf, 64, "frame %u", frame);
|
snprintf(buf, 64, "frame %u", frame); \
|
||||||
APPLE_SIGNPOST_EMIT(sGlobalState.frameIdLog, OS_SIGNPOST_EVENT,
|
APPLE_SIGNPOST_EMIT(sGlobalState.frameIdLog, OS_SIGNPOST_EVENT,
|
||||||
OS_SIGNPOST_ID_EXCLUSIVE, "frame", buf)
|
OS_SIGNPOST_ID_EXCLUSIVE, "frame", buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class ScopedTrace;
|
friend class ScopedTrace;
|
||||||
|
|
||||||
struct GlobalState {
|
struct GlobalState {
|
||||||
@@ -217,25 +213,25 @@ private:
|
|||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
class ScopedTrace {
|
class ScopedTrace {
|
||||||
public:
|
public:
|
||||||
// we don't inline this because it's relatively heavy due to a global check
|
// we don't inline this because it's relatively heavy due to a global check
|
||||||
ScopedTrace(uint32_t tag, const char* name) noexcept : mTrace(tag), mName(name), mTag(tag) {
|
ScopedTrace(uint32_t tag, const char* name) noexcept : mTrace(tag), mName(name), mTag(tag) {
|
||||||
mTrace.traceBegin(tag, name);
|
mTrace.traceBegin(tag, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
~ScopedTrace() noexcept {
|
inline ~ScopedTrace() noexcept {
|
||||||
mTrace.traceEnd(mTag, mName);
|
mTrace.traceEnd(mTag, mName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void value(uint32_t tag, const char* name, int32_t v) noexcept {
|
inline void value(uint32_t tag, const char* name, int32_t v) noexcept {
|
||||||
mTrace.value(tag, name, v);
|
mTrace.value(tag, name, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
void value(uint32_t tag, const char* name, int64_t v) noexcept {
|
inline void value(uint32_t tag, const char* name, int64_t v) noexcept {
|
||||||
mTrace.value(tag, name, v);
|
mTrace.value(tag, name, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Systrace mTrace;
|
Systrace mTrace;
|
||||||
const char* mName;
|
const char* mName;
|
||||||
const uint32_t mTag;
|
const uint32_t mTag;
|
||||||
|
|||||||
@@ -15,8 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Note: The overhead of SYSTRACE_TAG_JOBSYSTEM is not negligible especially with parallel_for().
|
// Note: The overhead of SYSTRACE_TAG_JOBSYSTEM is not negligible especially with parallel_for().
|
||||||
#define SYSTRACE_TAG SYSTRACE_TAG_DISABLED
|
#ifndef SYSTRACE_TAG
|
||||||
//#define SYSTRACE_TAG SYSTRACE_TAG_JOBSYSTEM
|
//#define SYSTRACE_TAG SYSTRACE_TAG_JOBSYSTEM
|
||||||
|
#define SYSTRACE_TAG SYSTRACE_TAG_NEVER
|
||||||
|
#endif
|
||||||
|
|
||||||
// when SYSTRACE_TAG_JOBSYSTEM is used, enables even heavier systraces
|
// when SYSTRACE_TAG_JOBSYSTEM is used, enables even heavier systraces
|
||||||
#define HEAVY_SYSTRACE 0
|
#define HEAVY_SYSTRACE 0
|
||||||
|
|||||||
@@ -14,26 +14,206 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <utils/compiler.h>
|
#include <utils/Systrace.h>
|
||||||
#include <utils/android/Systrace.h>
|
#include <utils/Log.h>
|
||||||
|
|
||||||
#include <perfetto/perfetto.h>
|
#include <cinttypes>
|
||||||
|
|
||||||
PERFETTO_TRACK_EVENT_STATIC_STORAGE_IN_NAMESPACE(systrace);
|
#include <string.h>
|
||||||
|
|
||||||
namespace {
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
|
||||||
class SystraceStaticInitialization {
|
namespace utils {
|
||||||
public:
|
namespace details {
|
||||||
SystraceStaticInitialization() {
|
|
||||||
perfetto::TracingInitArgs args;
|
|
||||||
args.backends |= perfetto::kSystemBackend;
|
|
||||||
perfetto::Tracing::Initialize(args);
|
|
||||||
systrace::TrackEvent::Register();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
UTILS_UNUSED SystraceStaticInitialization sSystraceStaticInitialization{};
|
static pthread_once_t atrace_once_control = PTHREAD_ONCE_INIT;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static void loadSymbol(T*& pfn, const char *symbol) noexcept {
|
||||||
|
pfn = (T*)dlsym(RTLD_DEFAULT, symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Systrace::GlobalState Systrace::sGlobalState = {};
|
||||||
|
|
||||||
|
void Systrace::init_once() noexcept {
|
||||||
|
GlobalState& s = sGlobalState;
|
||||||
|
|
||||||
|
s.markerFd = -1;
|
||||||
|
|
||||||
|
// API 23
|
||||||
|
loadSymbol(s.ATrace_isEnabled, "ATrace_isEnabled");
|
||||||
|
loadSymbol(s.ATrace_beginSection, "ATrace_beginSection");
|
||||||
|
loadSymbol(s.ATrace_endSection, "ATrace_endSection");
|
||||||
|
// API 29
|
||||||
|
loadSymbol(s.ATrace_beginAsyncSection, "ATrace_beginAsyncSection");
|
||||||
|
loadSymbol(s.ATrace_endAsyncSection, "ATrace_endAsyncSection");
|
||||||
|
loadSymbol(s.ATrace_setCounter, "ATrace_setCounter");
|
||||||
|
|
||||||
|
|
||||||
|
const bool hasBasicAtrace = s.ATrace_isEnabled &&
|
||||||
|
s.ATrace_beginSection &&
|
||||||
|
s.ATrace_endSection;
|
||||||
|
|
||||||
|
const bool hasFullATrace = hasBasicAtrace &&
|
||||||
|
s.ATrace_beginAsyncSection &&
|
||||||
|
s.ATrace_endAsyncSection &&
|
||||||
|
s.ATrace_setCounter;
|
||||||
|
|
||||||
|
if (!hasFullATrace) {
|
||||||
|
s.markerFd = open("/sys/kernel/debug/tracing/trace_marker", O_WRONLY | O_CLOEXEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasBasicAtrace && !hasFullATrace) {
|
||||||
|
// no-op if we don't have all these
|
||||||
|
s.ATrace_beginAsyncSection = [](const char* sectionName, int32_t cookie){};
|
||||||
|
s.ATrace_endAsyncSection = [](const char* sectionName, int32_t cookie){};
|
||||||
|
s.ATrace_setCounter = [](const char* sectionName, int64_t counterValue){};
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool hasLegacySystrace = s.markerFd != -1;
|
||||||
|
|
||||||
|
if (hasLegacySystrace && !hasFullATrace) {
|
||||||
|
// use legacy
|
||||||
|
s.beginSection = [](Systrace* that, const char* name) {
|
||||||
|
begin_body(that->mMarkerFd, that->mPid, name);
|
||||||
|
};
|
||||||
|
s.endSection = [](Systrace* that) {
|
||||||
|
end_body(that->mMarkerFd, that->mPid);
|
||||||
|
};
|
||||||
|
s.beginAsyncSection = [](Systrace* that, const char* name, int32_t cookie) {
|
||||||
|
async_begin_body(that->mMarkerFd, that->mPid, name, cookie);
|
||||||
|
};
|
||||||
|
s.endAsyncSection = [](Systrace* that, const char* name, int32_t cookie) {
|
||||||
|
async_end_body(that->mMarkerFd, that->mPid, name, cookie);
|
||||||
|
};
|
||||||
|
s.setCounter = [](Systrace* that, const char* name, int64_t value) {
|
||||||
|
int64_body(that->mMarkerFd, that->mPid, name, value);
|
||||||
|
};
|
||||||
|
} else if (hasBasicAtrace) {
|
||||||
|
// we have at least basic ATrace
|
||||||
|
s.beginSection = [](Systrace* that, const char* name) {
|
||||||
|
that->ATrace_beginSection(name);
|
||||||
|
};
|
||||||
|
s.endSection = [](Systrace* that) {
|
||||||
|
that->ATrace_endSection();
|
||||||
|
};
|
||||||
|
s.beginAsyncSection = [](Systrace* that, const char* name, int32_t cookie) {
|
||||||
|
that->ATrace_beginAsyncSection(name, cookie);
|
||||||
|
};
|
||||||
|
s.endAsyncSection = [](Systrace* that, const char* name, int32_t cookie) {
|
||||||
|
that->ATrace_endAsyncSection(name, cookie);
|
||||||
|
};
|
||||||
|
s.setCounter = [](Systrace* that, const char* name, int64_t value) {
|
||||||
|
that->ATrace_setCounter(name, value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
s.isTracingAvailable = hasLegacySystrace || hasFullATrace || hasBasicAtrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Systrace::setup() noexcept {
|
||||||
|
pthread_once(&atrace_once_control, init_once);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Systrace::enable(uint32_t tags) noexcept {
|
||||||
|
setup();
|
||||||
|
if (UTILS_LIKELY(sGlobalState.isTracingAvailable)) {
|
||||||
|
sGlobalState.isTracingEnabled.fetch_or(tags, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Systrace::disable(uint32_t tags) noexcept {
|
||||||
|
sGlobalState.isTracingEnabled.fetch_and(~tags, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// unfortunately, this generates quite a bit of code because reading a global is not
|
||||||
|
// trivial. For this reason, we do not inline this method.
|
||||||
|
bool Systrace::isTracingEnabled(uint32_t tag) noexcept {
|
||||||
|
if (tag) {
|
||||||
|
setup();
|
||||||
|
return bool((sGlobalState.isTracingEnabled.load(std::memory_order_relaxed) | SYSTRACE_TAG_ALWAYS) & tag);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void Systrace::init(uint32_t tag) noexcept {
|
||||||
|
// must be called first
|
||||||
|
mIsTracingEnabled = isTracingEnabled(tag);
|
||||||
|
|
||||||
|
// cache static variables for better efficiency
|
||||||
|
GlobalState& s = sGlobalState;
|
||||||
|
ATrace_isEnabled = s.ATrace_isEnabled;
|
||||||
|
ATrace_beginSection = s.ATrace_beginSection;
|
||||||
|
ATrace_endSection = s.ATrace_endSection;
|
||||||
|
ATrace_beginAsyncSection = s.ATrace_beginAsyncSection;
|
||||||
|
ATrace_endAsyncSection = s.ATrace_endAsyncSection;
|
||||||
|
ATrace_setCounter = s.ATrace_setCounter;
|
||||||
|
|
||||||
|
beginSection = s.beginSection;
|
||||||
|
endSection = s.endSection;
|
||||||
|
beginAsyncSection = s.beginAsyncSection;
|
||||||
|
endAsyncSection = s.endAsyncSection;
|
||||||
|
setCounter = s.setCounter;
|
||||||
|
|
||||||
|
mMarkerFd = s.markerFd;
|
||||||
|
|
||||||
|
mPid = getpid();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum size of a message that can be logged to the trace buffer.
|
||||||
|
* Note this message includes a tag, the pid, and the string given as the name.
|
||||||
|
* Names should be kept short to get the most use of the trace buffer.
|
||||||
|
*/
|
||||||
|
#define ATRACE_MESSAGE_LENGTH 512
|
||||||
|
|
||||||
|
#define WRITE_MSG(format_begin, format_end, pid, name, value) { \
|
||||||
|
char buf[ATRACE_MESSAGE_LENGTH]; \
|
||||||
|
int len = snprintf(buf, sizeof(buf), format_begin "%s" format_end, pid, \
|
||||||
|
name, value); \
|
||||||
|
if (len >= (int) sizeof(buf)) { \
|
||||||
|
/* Given the sizeof(buf), and all of the current format buffers, \
|
||||||
|
* it is impossible for name_len to be < 0 if len >= sizeof(buf). */ \
|
||||||
|
int name_len = strlen(name) - (len - sizeof(buf)) - 1; \
|
||||||
|
/* Truncate the name to make the message fit. */ \
|
||||||
|
len = snprintf(buf, sizeof(buf), format_begin "%.*s" format_end, pid, \
|
||||||
|
name_len, name, value); \
|
||||||
|
} \
|
||||||
|
write(fd, buf, len); \
|
||||||
|
}
|
||||||
|
|
||||||
|
void Systrace::begin_body(int fd, int pid, const char* name) noexcept {
|
||||||
|
char buf[ATRACE_MESSAGE_LENGTH];
|
||||||
|
ssize_t len = snprintf(buf, sizeof(buf), "B|%d|%s", pid, name);
|
||||||
|
if (len >= sizeof(buf)) {
|
||||||
|
len = sizeof(buf) - 1;
|
||||||
|
}
|
||||||
|
write(fd, buf, size_t(len));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Systrace::end_body(int fd, int pid) noexcept {
|
||||||
|
const char END_TAG = 'E';
|
||||||
|
write(fd, &END_TAG, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Systrace::async_begin_body(int fd, int pid, const char* name, int32_t cookie) noexcept {
|
||||||
|
WRITE_MSG("S|%d|", "|%" PRId32, pid, name, cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Systrace::async_end_body(int fd, int pid, const char* name, int32_t cookie) noexcept {
|
||||||
|
WRITE_MSG("F|%d|", "|%" PRId32, pid, name, cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Systrace::int64_body(int fd, int pid, const char* name, int64_t value) noexcept {
|
||||||
|
WRITE_MSG("C|%d|", "|%" PRId64, pid, name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace details
|
||||||
|
} // namespace utils
|
||||||
|
|||||||
@@ -14,17 +14,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <utils/darwin/Systrace.h>
|
#include <utils/Systrace.h>
|
||||||
|
#include <utils/Log.h>
|
||||||
#ifndef FILAMENT_APPLE_SYSTRACE
|
|
||||||
# define FILAMENT_APPLE_SYSTRACE 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if FILAMENT_APPLE_SYSTRACE
|
#if FILAMENT_APPLE_SYSTRACE
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <stack>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
static pthread_once_t atrace_once_control = PTHREAD_ONCE_INIT;
|
static pthread_once_t atrace_once_control = PTHREAD_ONCE_INIT;
|
||||||
@@ -47,24 +41,21 @@ void Systrace::setup() noexcept {
|
|||||||
pthread_once(&atrace_once_control, init_once);
|
pthread_once(&atrace_once_control, init_once);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Systrace::enable(uint32_t tag) noexcept {
|
void Systrace::enable(uint32_t tags) noexcept {
|
||||||
setup();
|
setup();
|
||||||
uint32_t const mask = 1 << tag;
|
sGlobalState.isTracingEnabled.fetch_or(tags, std::memory_order_relaxed);
|
||||||
sGlobalState.isTracingEnabled.fetch_or(mask, std::memory_order_relaxed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Systrace::disable(uint32_t tag) noexcept {
|
void Systrace::disable(uint32_t tags) noexcept {
|
||||||
uint32_t const mask = 1 << tag;
|
sGlobalState.isTracingEnabled.fetch_and(~tags, std::memory_order_relaxed);
|
||||||
sGlobalState.isTracingEnabled.fetch_and(~mask, std::memory_order_relaxed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unfortunately, this generates quite a bit of code because reading a global is not
|
// unfortunately, this generates quite a bit of code because reading a global is not
|
||||||
// trivial. For this reason, we do not inline this method.
|
// trivial. For this reason, we do not inline this method.
|
||||||
bool Systrace::isTracingEnabled(uint32_t tag) noexcept {
|
bool Systrace::isTracingEnabled(uint32_t tag) noexcept {
|
||||||
if (tag) {
|
if (tag) {
|
||||||
setup();
|
setup();
|
||||||
uint32_t const mask = 1 << tag;
|
return bool((sGlobalState.isTracingEnabled.load(std::memory_order_relaxed) | SYSTRACE_TAG_ALWAYS) & tag);
|
||||||
return bool(sGlobalState.isTracingEnabled.load(std::memory_order_relaxed) & mask);
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ function prepare_mesa() {
|
|||||||
# - Run the python script that runs the test
|
# - Run the python script that runs the test
|
||||||
# - Zip up the result
|
# - Zip up the result
|
||||||
|
|
||||||
set -ex && prepare_mesa && \
|
set -e && set -x && prepare_mesa && \
|
||||||
mkdir -p ${OUTPUT_DIR} && \
|
mkdir -p ${OUTPUT_DIR} && \
|
||||||
CXX=`which clang++` CC=`which clang` ./build.sh -X ${MESA_DIR} -p desktop debug gltf_viewer && \
|
./build.sh -X ${MESA_DIR} -p desktop debug gltf_viewer && \
|
||||||
python3 ${RENDERDIFF_TEST_DIR}/src/run.py \
|
python3 ${RENDERDIFF_TEST_DIR}/src/run.py \
|
||||||
--gltf_viewer="$(pwd)/out/cmake-debug/samples/gltf_viewer" \
|
--gltf_viewer="$(pwd)/out/cmake-debug/samples/gltf_viewer" \
|
||||||
--test=${RENDERDIFF_TEST_DIR}/tests/presubmit.json \
|
--test=${RENDERDIFF_TEST_DIR}/tests/presubmit.json \
|
||||||
--output_dir=${OUTPUT_DIR} \
|
--output_dir=${OUTPUT_DIR} \
|
||||||
|
|||||||
@@ -14,34 +14,13 @@
|
|||||||
|
|
||||||
#!/usr/bin/bash
|
#!/usr/bin/bash
|
||||||
|
|
||||||
set -x
|
set -xe
|
||||||
if [[ "$GITHUB_WORKFLOW" ]]; then
|
|
||||||
set -e
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
# GITHUB_CLANG_VERSION is set in build/linux/ci-common.sh
|
||||||
os_name=$(uname -s)
|
os_name=$(uname -s)
|
||||||
LLVM_VERSION=16
|
LLVM_VERSION=16
|
||||||
MESA_VERSION=${MESA_VERSION-24.2.1}
|
MESA_DIR=$(pwd)/mesa
|
||||||
ORIG_DIR=$(pwd)
|
|
||||||
MESA_DIR=${MESA_DIR-${ORIG_DIR}/mesa}
|
|
||||||
|
|
||||||
if [[ "$os_name" == "Linux" ]]; then
|
|
||||||
sudo apt install python3-venv
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install python deps
|
|
||||||
python3 -m venv ${ORIG_DIR}/venv
|
|
||||||
source ${ORIG_DIR}/venv/bin/activate
|
|
||||||
|
|
||||||
NEEDED_PYTHON_DEPS=("mako" "setuptools" "pyyaml")
|
|
||||||
for cmd in "${NEEDED_PYTHON_DEPS[@]}"; do
|
|
||||||
if ! python3 -m pip show "${cmd}" >/dev/null 2>&1; then
|
|
||||||
python3 -m pip install ${cmd}
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
deactivate
|
|
||||||
|
|
||||||
# Install system deps
|
|
||||||
if [[ "$os_name" == "Linux" ]]; then
|
if [[ "$os_name" == "Linux" ]]; then
|
||||||
if [[ "$GITHUB_WORKFLOW" ]]; then
|
if [[ "$GITHUB_WORKFLOW" ]]; then
|
||||||
# We only want to do this if it is a CI machine.
|
# We only want to do this if it is a CI machine.
|
||||||
@@ -65,16 +44,12 @@ if [[ "$os_name" == "Linux" ]]; then
|
|||||||
set -e
|
set -e
|
||||||
CURRENT_CLANG_VERSION=$(clang --version | head -n 1 | awk '{ print $4 }' | awk 'BEGIN { FS="\\." } { print $1 }')
|
CURRENT_CLANG_VERSION=$(clang --version | head -n 1 | awk '{ print $4 }' | awk 'BEGIN { FS="\\." } { print $1 }')
|
||||||
GITHUB_CLANG_VERSION=${GITHUB_CLANG_VERSION:-${CURRENT_CLANG_VERSION}}
|
GITHUB_CLANG_VERSION=${GITHUB_CLANG_VERSION:-${CURRENT_CLANG_VERSION}}
|
||||||
GITHUB_CLANG_VERSION=${GITHUB_CLANG_VERSION:-${LLVM_VERSION}}
|
|
||||||
sudo apt-get -y install clang-${GITHUB_CLANG_VERSION} \
|
sudo apt-get -y install clang-${GITHUB_CLANG_VERSION} \
|
||||||
libc++-${GITHUB_CLANG_VERSION}-dev \
|
libc++-${GITHUB_CLANG_VERSION}-dev \
|
||||||
libc++abi-${GITHUB_CLANG_VERSION}-dev \
|
libc++abi-${GITHUB_CLANG_VERSION}-dev \
|
||||||
llvm-${LLVM_VERSION} \
|
llvm-${LLVM_VERSION} \
|
||||||
llvm-${LLVM_VERSION}-{dev,tools,runtime}
|
llvm-${LLVM_VERSION}-{dev,tools,runtime}
|
||||||
! command -v clang > /dev/null 2>&1 && \
|
fi # [[ "$GITHUB_WORKFLOW" ]]
|
||||||
sudo ln -s /usr/bin/clang-${GITHUB_CLANG_VERSION} /usr/bin/clang && \
|
|
||||||
sudo ln -s /usr/bin/clang++-${GITHUB_CLANG_VERSION} /usr/bin/clang++
|
|
||||||
fi # [[ "$GITHUB_WORKFLOW" ]]
|
|
||||||
elif [[ "$os_name" == "Darwin" ]]; then
|
elif [[ "$os_name" == "Darwin" ]]; then
|
||||||
if [[ ! "$GITHUB_WORKFLOW" ]]; then
|
if [[ ! "$GITHUB_WORKFLOW" ]]; then
|
||||||
if [ ! command -v brew > /dev/null 2>&1 ]; then
|
if [ ! command -v brew > /dev/null 2>&1 ]; then
|
||||||
@@ -82,7 +57,14 @@ elif [[ "$os_name" == "Darwin" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=true brew install autoconf automake libx11 libxext libxrandr llvm@${LLVM_VERSION} ninja meson pkg-config libxshmfence
|
brew install autoconf automake libx11 libxext libxrandr llvm@${LLVM_VERSION} ninja meson pkg-config libxshmfence
|
||||||
|
|
||||||
|
NEEDED_PYTHON_DEPS=("mako" "setuptools")
|
||||||
|
for cmd in "${NEEDED_PYTHON_DEPS[@]}"; do
|
||||||
|
if ! pip3 show "${cmd}" >/dev/null 2>&1; then
|
||||||
|
sudo pip3 install ${cmd}
|
||||||
|
fi
|
||||||
|
done
|
||||||
fi # [[ "$os_name" == x ]]
|
fi # [[ "$os_name" == x ]]
|
||||||
|
|
||||||
LOCAL_LDFLAGS=${LDFLAGS}
|
LOCAL_LDFLAGS=${LDFLAGS}
|
||||||
@@ -106,8 +88,10 @@ fi
|
|||||||
if [[ "$CHECKOUT_MESA" = "true" ]]; then
|
if [[ "$CHECKOUT_MESA" = "true" ]]; then
|
||||||
rm -rf ${MESA_DIR}
|
rm -rf ${MESA_DIR}
|
||||||
|
|
||||||
git clone https://gitlab.freedesktop.org/mesa/mesa.git
|
#git clone https://gitlab.freedesktop.org/mesa/mesa.git
|
||||||
if [[ "${MESA_DIR}" != "${ORIG_DIR}/mesa" ]]; then
|
git clone git://anongit.freedesktop.org/mesa/mesa
|
||||||
|
# Due to gitlab mesa outage.
|
||||||
|
if [[ "${MESA_DIR}" != "$(pwd)/mesa" ]]; then
|
||||||
mv mesa ${MESA_DIR}
|
mv mesa ${MESA_DIR}
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -116,12 +100,10 @@ pushd .
|
|||||||
cd ${MESA_DIR}
|
cd ${MESA_DIR}
|
||||||
|
|
||||||
# Need >= 24 to have llvmpipe for swrast. llvmpipe is needed for GL >= 4.1.
|
# Need >= 24 to have llvmpipe for swrast. llvmpipe is needed for GL >= 4.1.
|
||||||
git checkout mesa-${MESA_VERSION}
|
git checkout mesa-24.2.1
|
||||||
|
|
||||||
mkdir -p out
|
mkdir -p out
|
||||||
|
|
||||||
source ${ORIG_DIR}/venv/bin/activate
|
|
||||||
|
|
||||||
if [[ "$os_name" == "Darwin" ]]; then
|
if [[ "$os_name" == "Darwin" ]]; then
|
||||||
LOCAL_LDFLAGS="-L/opt/homebrew/opt/llvm@${LLVM_VERSION}/lib"
|
LOCAL_LDFLAGS="-L/opt/homebrew/opt/llvm@${LLVM_VERSION}/lib"
|
||||||
LOCAL_CPPFLAGS="-I/opt/homebrew/opt/llvm@${LLVM_VERSION}/include -I/opt/homebrew/include"
|
LOCAL_CPPFLAGS="-I/opt/homebrew/opt/llvm@${LLVM_VERSION}/include -I/opt/homebrew/include"
|
||||||
@@ -136,16 +118,8 @@ fi
|
|||||||
# -Dvulkan-drivers=swrast => builds VK software rasterizer
|
# -Dvulkan-drivers=swrast => builds VK software rasterizer
|
||||||
# -Dgallium-drivers=llvmpipe is needed for GL >= 4.1 pipe-screen (see src/gallium/auxiliary/target-helpers/inline_sw_helper.h)
|
# -Dgallium-drivers=llvmpipe is needed for GL >= 4.1 pipe-screen (see src/gallium/auxiliary/target-helpers/inline_sw_helper.h)
|
||||||
CXX=${LOCAL_CXX} CC=${LOCAL_CC} PATH=${LOCAL_PATH} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
|
CXX=${LOCAL_CXX} CC=${LOCAL_CC} PATH=${LOCAL_PATH} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
|
||||||
meson setup --wipe builddir/ -Dprefix="${MESA_DIR}/out" -Dglx=xlib -Dosmesa=true -Dgallium-drivers=llvmpipe,swrast -Dvulkan-drivers=swrast
|
meson setup --wipe builddir/ -Dprefix="$(pwd)/out" -Dglx=xlib -Dosmesa=true -Dgallium-drivers=llvmpipe,swrast -Dvulkan-drivers=swrast
|
||||||
CXX=${LOCAL_CXX} CC=${LOCAL_CC} PATH=${LOCAL_PATH} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
|
CXX=${LOCAL_CXX} CC=${LOCAL_CC} PATH=${LOCAL_PATH} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
|
||||||
meson install -C builddir/
|
meson install -C builddir/
|
||||||
|
|
||||||
# Disable python venv
|
|
||||||
deactivate
|
|
||||||
|
|
||||||
popd
|
popd
|
||||||
|
|
||||||
if [[ "$GITHUB_WORKFLOW" ]]; then
|
|
||||||
set +e
|
|
||||||
fi
|
|
||||||
set +x
|
|
||||||
|
|||||||
66917
third_party/perfetto/perfetto/perfetto.cc
vendored
175338
third_party/perfetto/perfetto/perfetto.h
vendored
30
third_party/perfetto/tnt/CMakeLists.txt
vendored
@@ -1,30 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.19)
|
|
||||||
project(perfetto)
|
|
||||||
|
|
||||||
set(OUR_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..)
|
|
||||||
|
|
||||||
set(TARGET perfetto)
|
|
||||||
set(SRC_DIR ${OUR_DIR}/${TARGET})
|
|
||||||
set(PUBLIC_HDR_DIR ${OUR_DIR})
|
|
||||||
|
|
||||||
set(PUBLIC_HDRS
|
|
||||||
${PUBLIC_HDR_DIR}/${TARGET}/perfetto.h
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library(${TARGET} STATIC ${PUBLIC_HDRS} ${SRC_DIR}/perfetto.cc)
|
|
||||||
|
|
||||||
if (WIN32)
|
|
||||||
# The perfetto library contains many symbols, so it needs the big object
|
|
||||||
# format.
|
|
||||||
target_compile_options(perfetto PRIVATE "/bigobj")
|
|
||||||
# Disable legacy features in windows.h.
|
|
||||||
add_definitions(-DWIN32_LEAN_AND_MEAN -DNOMINMAX)
|
|
||||||
# On Windows we should link to WinSock2.
|
|
||||||
target_link_libraries(${TARGET} ws2_32)
|
|
||||||
endif (WIN32)
|
|
||||||
|
|
||||||
# specify where the public headers of this library are
|
|
||||||
target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR})
|
|
||||||
set_target_properties(${TARGET} PROPERTIES FOLDER ThirdParty)
|
|
||||||
|
|
||||||
install(TARGETS ${TARGET} ARCHIVE DESTINATION lib/${DIST_DIR})
|
|
||||||
8
third_party/perfetto/tnt/README.txt
vendored
@@ -1,8 +0,0 @@
|
|||||||
Useful links:
|
|
||||||
https://perfetto.dev/
|
|
||||||
https://perfetto.dev/docs/instrumentation/tracing-sdk
|
|
||||||
|
|
||||||
Perfetto was fetched as:
|
|
||||||
git clone https://github.com/google/perfetto.git -b v50.1
|
|
||||||
|
|
||||||
Only the sdk/ directory is needed.
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "filament",
|
"name": "filament",
|
||||||
"version": "1.59.3",
|
"version": "1.59.1",
|
||||||
"description": "Real-time physically based rendering engine",
|
"description": "Real-time physically based rendering engine",
|
||||||
"main": "filament.js",
|
"main": "filament.js",
|
||||||
"module": "filament.js",
|
"module": "filament.js",
|
||||||
|
|||||||