Compare commits
14 Commits
idris/rend
...
gitIgnoreU
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a74936103 | ||
|
|
7967157fbb | ||
|
|
597ced13e1 | ||
|
|
c3542b135e | ||
|
|
ca3ff7e08e | ||
|
|
42c760a92f | ||
|
|
327a537bcc | ||
|
|
cfc4f34c18 | ||
|
|
6dac384bd9 | ||
|
|
d666f8a0ba | ||
|
|
d6ae3a57b5 | ||
|
|
c2155f3f98 | ||
|
|
e2a3637413 | ||
|
|
390df4e42c |
18
.github/workflows/presubmit.yml
vendored
@@ -108,9 +108,21 @@ jobs:
|
|||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
- name: Install python prereqs
|
- name: Cache Mesa and deps
|
||||||
run: pip install mako setuptools pyyaml
|
id: mesa-cache
|
||||||
- name: Run script
|
uses: actions/cache@v4 # Use a specific version
|
||||||
|
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,3 +18,18 @@ 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,6 +801,7 @@ 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.2'
|
implementation 'com.google.android.filament:filament-android:1.59.3'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -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.2'
|
pod 'Filament', '~> 1.59.3'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ 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.3
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ 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)
|
||||||
@@ -40,6 +44,7 @@ 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)
|
||||||
@@ -55,6 +60,7 @@ 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,6 +21,10 @@ 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)
|
||||||
@@ -123,6 +127,7 @@ 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
|
||||||
@@ -141,6 +146,7 @@ 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,6 +35,10 @@ 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)
|
||||||
@@ -121,6 +125,7 @@ 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
|
||||||
)
|
)
|
||||||
@@ -129,7 +134,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 uberzlib log stb ktxreader basis_transcoder zstd uberarchive)
|
target_link_libraries(gltfio-jni filament-jni utils perfetto 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.2
|
VERSION_NAME=1.59.3
|
||||||
|
|
||||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||||
|
|
||||||
|
|||||||
@@ -259,6 +259,7 @@ 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)
|
||||||
@@ -534,6 +535,9 @@ 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
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ void CommandStream::execute(void* buffer) {
|
|||||||
|
|
||||||
Profiler profiler;
|
Profiler profiler;
|
||||||
|
|
||||||
if (SYSTRACE_TAG) {
|
if constexpr (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 (SYSTRACE_TAG) {
|
if constexpr (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,6 +85,7 @@ OpenGLProgram::~OpenGLProgram() noexcept {
|
|||||||
delete lazyInitializationData;
|
delete lazyInitializationData;
|
||||||
|
|
||||||
ShaderCompilerService::terminate(mToken);
|
ShaderCompilerService::terminate(mToken);
|
||||||
|
assert_invariant(!mToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete [] mUniformsRecords;
|
delete [] mUniformsRecords;
|
||||||
|
|||||||
@@ -17,11 +17,15 @@
|
|||||||
#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>
|
||||||
@@ -34,9 +38,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>
|
||||||
@@ -47,6 +51,7 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
@@ -54,17 +59,17 @@ using namespace utils;
|
|||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
static inline std::string to_string(bool b) noexcept { return b ? "true" : "false"; }
|
static std::string to_string(bool const b) { return b ? "true" : "false"; }
|
||||||
static inline std::string to_string(int i) noexcept { return std::to_string(i); }
|
static std::string to_string(int const i) { return std::to_string(i); }
|
||||||
static inline std::string to_string(float f) noexcept { return "float(" + std::to_string(f) + ")"; }
|
static std::string to_string(float const f) { 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& context, char* source,
|
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context, char* source,
|
||||||
size_t len) noexcept;
|
size_t len) noexcept;
|
||||||
static void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount, char* source,
|
static void process_OVR_multiview2(OpenGLContext const& 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;
|
||||||
@@ -72,20 +77,15 @@ 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, utils::CString const& name) noexcept
|
OpenGLProgramToken(ShaderCompilerService& compiler, CString const& name) noexcept
|
||||||
: compiler(compiler), name(name) {
|
: compiler(compiler), name(name), handle(compiler.issueCallbackHandle()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderCompilerService& compiler;
|
ShaderCompilerService& compiler;
|
||||||
utils::CString const& name;
|
CString const& name;
|
||||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes;
|
FixedCapacityVector<std::pair<CString, uint8_t>> attributes;
|
||||||
shaders_source_t shaderSourceCode;
|
shaders_source_t shaderSourceCode;
|
||||||
void* user = nullptr;
|
void* user = nullptr;
|
||||||
struct {
|
struct {
|
||||||
@@ -93,48 +93,34 @@ 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
|
||||||
// Sets the programData, typically from the compiler thread, and signal the main thread.
|
// be used. It sends a signal to the engine thread being blocked upon the `wait` call, so that
|
||||||
// This is similar to std::promise::set_value.
|
// the engine thread resumes its processing with the token.
|
||||||
void set(ProgramData const& data) noexcept {
|
void signal() 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the programBinary, wait if necessary.
|
// Used in THREAD_POOL mode. The engine thread should call this before accessing token's fields.
|
||||||
// This is similar to std::future::get
|
// This may block until the token is ready to be used.
|
||||||
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;
|
|
||||||
mutable utils::Condition cond;
|
|
||||||
ProgramData programData;
|
|
||||||
bool signaled = false;
|
|
||||||
|
|
||||||
bool canceled = false; // not part of the signaling
|
// Used for the `THREAD_POOL` mode.
|
||||||
|
mutable Mutex lock;
|
||||||
|
mutable Condition cond;
|
||||||
|
bool signaled = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() = default;
|
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() {
|
||||||
|
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 {
|
||||||
@@ -189,9 +175,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 multi-threaded, but program linking happens on
|
// - on Mali shader compilation can be multithreaded, 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 multi-threaded.
|
// - on PowerVR shader compilation and linking can be multithreaded.
|
||||||
// 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.
|
||||||
@@ -220,7 +206,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
|
||||||
@@ -228,7 +214,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();
|
||||||
});
|
});
|
||||||
@@ -249,119 +235,65 @@ void ShaderCompilerService::terminate() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||||
utils::CString const& name, Program&& program) {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
token->handle = mCallbackManager.get();
|
// Initiate program compilation.
|
||||||
|
|
||||||
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 {
|
||||||
// compile the shaders
|
compileShaders(gl, std::move(program.getShadersSource()),
|
||||||
shaders_t shaders{};
|
program.getSpecializationConstants(), program.isMultiview(), token);
|
||||||
compileShaders(gl,
|
linkProgram(gl, token);
|
||||||
std::move(program.getShadersSource()),
|
// Now `token->gl.program` must be populated, so we signal the completion
|
||||||
program.getSpecializationConstants(),
|
// of the linking. We don't need to check the result of the program here
|
||||||
program.isMultiview(),
|
// because it'll be done in the engine thread.
|
||||||
shaders,
|
token->signal();
|
||||||
token->shaderSourceCode);
|
// We try caching the program blob after sending the signal. This allows us
|
||||||
|
// to unblock the engine thread as soon as the token is ready while
|
||||||
// link the program
|
// performing an expensive caching operation still in the pool.
|
||||||
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
|
tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
|
||||||
|
|
||||||
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: {
|
||||||
// this cannot fail because we check compilation status after linking the program
|
compileShaders(gl, std::move(program.getShadersSource()),
|
||||||
// shaders[] is filled with id of shader stages present.
|
program.getSpecializationConstants(), program.isMultiview(), token);
|
||||||
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) {
|
||||||
// don't attempt to link this program if all shaders are not done compiling
|
// Check link completion if link was initiated.
|
||||||
GLint status;
|
|
||||||
if (token->gl.program) {
|
if (token->gl.program) {
|
||||||
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status);
|
return isLinkCompleted(token);
|
||||||
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) {
|
||||||
assert_invariant(token->gl.program);
|
linkProgram(mDriver.getContext(), token);
|
||||||
|
if (mMode == Mode::ASYNCHRONOUS) {
|
||||||
mCallbackManager.put(token->handle);
|
return false;// Wait until the link finishes.
|
||||||
|
}
|
||||||
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;
|
||||||
@@ -375,7 +307,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& token) {
|
GLuint ShaderCompilerService::getProgram(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
|
||||||
@@ -384,43 +316,29 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
|
|||||||
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);
|
|
||||||
|
|
||||||
token->canceled = true;
|
assert_invariant(token);// This function should be called when the token is still alive.
|
||||||
|
|
||||||
bool const isTickOpCanceled = token->compiler.cancelTickOp(token);
|
|
||||||
|
|
||||||
if (token->compiler.mMode == Mode::THREAD_POOL) {
|
if (token->compiler.mMode == Mode::THREAD_POOL) {
|
||||||
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
|
auto const job = token->compiler.mCompilerThreadPool.dequeue(token);
|
||||||
if (!job) {
|
if (!job) {
|
||||||
// The job is being executed right now. We need to wait for it to finish to avoid a
|
// It's likely that the job was already completed. But it may be still being
|
||||||
// race.
|
// executed at this moment. Just try waiting for it to avoid a 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
token.reset();
|
cleanupProgramAndShaders(token);
|
||||||
|
|
||||||
|
// Cleanup the token.
|
||||||
|
token->compiler.cancelTickOp(token);
|
||||||
|
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderCompilerService::tick() {
|
void ShaderCompilerService::tick() {
|
||||||
@@ -430,8 +348,16 @@ 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 callback, void* user) {
|
CallbackHandler* handler, CallbackHandler::Callback const callback, void* user) {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
mCallbackManager.setCallback(handler, callback, user);
|
mCallbackManager.setCallback(handler, callback, user);
|
||||||
}
|
}
|
||||||
@@ -439,117 +365,137 @@ void ShaderCompilerService::notifyWhenAllProgramsAreReady(
|
|||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
/* static */ void ShaderCompilerService::getProgramFromCompilerPool(
|
GLuint ShaderCompilerService::initialize(program_token_t& token) {
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!token->canceled) {
|
assert_invariant(token);// This function should be called when the token is still alive.
|
||||||
token->compiler.cancelTickOp(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block until we get the program from the pool. Generally this wouldn't block
|
ensureTokenIsReady(token);
|
||||||
// because we just compiled the program above, when executing job.
|
|
||||||
ShaderCompilerService::getProgramFromCompilerPool(token);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Mode::ASYNCHRONOUS: {
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
token->gl.program =
|
|
||||||
linkProgram(mDriver.getContext(), token->gl.shaders, token->attributes);
|
|
||||||
|
|
||||||
assert_invariant(token->gl.program);
|
|
||||||
|
|
||||||
mCallbackManager.put(token->handle);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// by this point we must have a GL program
|
|
||||||
assert_invariant(token->gl.program);
|
assert_invariant(token->gl.program);
|
||||||
|
|
||||||
GLuint program = 0;
|
// Check status of program linking. If it failed, errors will be logged.
|
||||||
|
bool const linked = checkLinkStatusAndCleanupShaders(token);
|
||||||
|
|
||||||
// check status of program linking and shader compilation, logs error and free all resources
|
// We panic if it failed to create the program.
|
||||||
// in case of error.
|
FILAMENT_CHECK_POSTCONDITION(linked)
|
||||||
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";
|
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
|
||||||
|
|
||||||
if (UTILS_LIKELY(success)) {
|
// The program is successfully created. Try caching the program blob. In the THREAD_POOL mode,
|
||||||
program = token->gl.program;
|
// caching is performed in the pool.
|
||||||
// no need to keep the shaders around
|
if (mMode != Mode::THREAD_POOL) {
|
||||||
UTILS_NOUNROLL
|
tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
|
||||||
for (GLuint& shader: token->gl.shaders) {
|
|
||||||
if (shader) {
|
|
||||||
glDetachShader(program, shader);
|
|
||||||
glDeleteShader(shader);
|
|
||||||
shader = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// and destroy all temporary init data
|
GLuint const program = token->gl.program;
|
||||||
token = nullptr;
|
|
||||||
|
// Cleanup the token.
|
||||||
|
token->compiler.cancelTickOp(token);
|
||||||
|
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
|
||||||
|
|
||||||
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,
|
||||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||||
bool multiview,
|
bool multiview, program_token_t const& token) noexcept {
|
||||||
shaders_t& outShaders,
|
|
||||||
UTILS_UNUSED_IN_RELEASE shaders_source_t& outShaderSourceCode) noexcept {
|
|
||||||
|
|
||||||
SYSTRACE_CALL();
|
SYSTRACE_CALL();
|
||||||
|
|
||||||
auto appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
|
auto const 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';
|
||||||
@@ -558,7 +504,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) 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
|
||||||
@@ -596,7 +542,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) 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 shader_len = shader.size();
|
size_t const 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);
|
||||||
@@ -619,178 +565,191 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::array<std::string_view, 5> sources = {
|
std::array<std::string_view, 5> sources = {
|
||||||
version,
|
version, prolog, specializationConstantString, packingFunctions,
|
||||||
prolog,
|
{ body.data(), body.size() - 1 }// null-terminated
|
||||||
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 partitionPoint = std::stable_partition(
|
auto const partitionPoint = std::stable_partition(sources.begin(), sources.end(),
|
||||||
sources.begin(), sources.end(), [](std::string_view s) { return !s.empty(); });
|
[](std::string_view s) { return !s.empty(); });
|
||||||
size_t count = std::distance(sources.begin(), partitionPoint);
|
size_t const 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 i = 0; i < count; i++) {
|
for (size_t j = 0; j < count; j++) {
|
||||||
shaderStrings[i] = sources[i].data();
|
shaderStrings[j] = sources[j].data();
|
||||||
lengths[i] = sources[i].size();
|
lengths[j] = GLint(sources[j].size());
|
||||||
}
|
}
|
||||||
|
|
||||||
GLuint const shaderId = glCreateShader(glShaderType);
|
GLuint const shaderId = glCreateShader(glShaderType);
|
||||||
glShaderSource(shaderId, count, shaderStrings.data(), lengths.data());
|
glShaderSource(shaderId, GLsizei(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.
|
||||||
outShaderSourceCode[i] = { shader_src, shader_len };
|
token->shaderSourceCode[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 {
|
||||||
* Create a program from the given shader IDs and links it. This cannot fail because errors
|
GLenum param = GL_COMPLETION_STATUS;
|
||||||
* are checked later. This always returns a valid GL program ID (which doesn't mean the
|
if (UTILS_UNLIKELY(token->compiler.mMode != Mode::ASYNCHRONOUS)) {
|
||||||
* program itself is valid).
|
param = GL_COMPILE_STATUS;
|
||||||
*/
|
}
|
||||||
/* 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 shader : shaders) {
|
for (auto const shader: token->gl.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 ] : attributes) {
|
for (auto const& [name, loc]: token->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 {
|
||||||
|
|
||||||
void ShaderCompilerService::runAtNextTick(CompilerPriorityQueue priority,
|
|
||||||
const program_token_t& 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 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();
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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();
|
|
||||||
|
|
||||||
assert_invariant(token->gl.program);
|
assert_invariant(token->gl.program);
|
||||||
|
|
||||||
GLint status;
|
GLenum param = GL_COMPLETION_STATUS;
|
||||||
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
|
if (UTILS_UNLIKELY(token->compiler.mMode != Mode::ASYNCHRONOUS)) {
|
||||||
if (UTILS_LIKELY(status == GL_TRUE)) {
|
param = GL_LINK_STATUS;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// only if the link fails, we check the compilation status
|
GLint status;
|
||||||
|
glGetProgramiv(token->gl.program, param, &status);
|
||||||
|
return (status == GL_TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* static */ bool ShaderCompilerService::checkLinkStatusAndCleanupShaders(
|
||||||
|
program_token_t const& token) noexcept {
|
||||||
|
SYSTRACE_CALL();
|
||||||
|
assert_invariant(token->gl.program);
|
||||||
|
|
||||||
|
bool linked = true;
|
||||||
|
GLint status;
|
||||||
|
// GL_LINK_STATUS may block until the link is completed.
|
||||||
|
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
|
||||||
|
if (UTILS_UNLIKELY(status != GL_TRUE)) {
|
||||||
|
// Something went wrong. Log the error message.
|
||||||
|
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.
|
||||||
UTILS_NOUNROLL
|
UTILS_NOUNROLL
|
||||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
for (GLuint& shader: token->gl.shaders) {
|
||||||
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);
|
||||||
token->gl.shaders[i] = 0;
|
shader = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// log the link error as well
|
return linked;
|
||||||
logProgramLinkError(slog.e, token->name.c_str_safe(), token->gl.program);
|
}
|
||||||
glDeleteProgram(token->gl.program);
|
|
||||||
token->gl.program = 0;
|
/* static */ void ShaderCompilerService::tryCachingProgram(OpenGLBlobCache& cache,
|
||||||
return false;
|
OpenGLPlatform& platform, program_token_t const& token) noexcept {
|
||||||
|
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 shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
|
GLuint const shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
|
||||||
|
|
||||||
auto to_string = [](ShaderStage type) -> const char* {
|
{ // scope for the temporary string storage
|
||||||
switch (type) {
|
auto to_string = [](ShaderStage type) -> const char* {
|
||||||
case ShaderStage::VERTEX:
|
switch (type) {
|
||||||
return "vertex";
|
case ShaderStage::VERTEX:
|
||||||
case ShaderStage::FRAGMENT:
|
return "vertex";
|
||||||
return "fragment";
|
case ShaderStage::FRAGMENT:
|
||||||
case ShaderStage::COMPUTE:
|
return "fragment";
|
||||||
return "compute";
|
case ShaderStage::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);
|
||||||
|
|
||||||
@@ -837,8 +796,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& context, char* source,
|
/* static */ void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context,
|
||||||
size_t len) noexcept {
|
char* source, 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
|
||||||
@@ -850,13 +809,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& context, int32_t eyeCount, char* source,
|
/* static */ void process_OVR_multiview2(OpenGLContext const& context, int32_t const eyeCount,
|
||||||
size_t len) noexcept {
|
char* source, size_t const 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 };
|
||||||
const std::string_view layout = "layout";
|
constexpr std::string_view layout = "layout";
|
||||||
const std::string_view num_views = "num_views";
|
constexpr 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);
|
||||||
@@ -1011,20 +970,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 version_start = source.find("#version");
|
auto const version_start = source.find("#version");
|
||||||
assert_invariant(version_start != std::string_view::npos);
|
assert_invariant(version_start != std::string_view::npos);
|
||||||
|
|
||||||
auto version_eol = source.find('\n', version_start) + 1;
|
auto const 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 prolog_start = version_eol;
|
auto const 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 body_start = prolog_eol;
|
auto const 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 <atomic>
|
#include <array>
|
||||||
#include <condition_variable>
|
|
||||||
#include <deque>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
class OpenGLDriver;
|
class OpenGLDriver;
|
||||||
@@ -84,6 +84,7 @@ 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
|
||||||
@@ -92,6 +93,12 @@ 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);
|
||||||
@@ -99,7 +106,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
struct Job {
|
struct Job {
|
||||||
template<typename FUNC>
|
template<typename FUNC>
|
||||||
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {}
|
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {} // NOLINT(*-explicit-constructor)
|
||||||
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) {
|
||||||
@@ -128,26 +135,49 @@ 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(ShaderCompilerService::program_token_t& token) noexcept;
|
GLuint initialize(program_token_t& token);
|
||||||
|
void ensureTokenIsReady(program_token_t const& token);
|
||||||
|
|
||||||
static void getProgramFromCompilerPool(program_token_t& token) noexcept;
|
void runAtNextTick(CompilerPriorityQueue priority, program_token_t const& token,
|
||||||
|
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 token) noexcept;
|
bool cancelTickOp(program_token_t const& 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
|
||||||
|
|||||||
@@ -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_NAME(__func__)
|
#define FVK_SYSTRACE_SCOPE() SYSTRACE_CALL()
|
||||||
#define FVK_PROFILE_MARKER(marker) FVK_SYSTRACE_SCOPE()
|
#define FVK_PROFILE_MARKER(marker) SYSTRACE_CALL()
|
||||||
|
|
||||||
#else
|
#else
|
||||||
#define FVK_SYSTRACE_CONTEXT()
|
#define FVK_SYSTRACE_CONTEXT()
|
||||||
|
|||||||
@@ -35,7 +35,12 @@ 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;
|
||||||
@@ -215,7 +220,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
|||||||
PipelineCacheEntry cacheEntry = {
|
PipelineCacheEntry cacheEntry = {
|
||||||
.lastUsed = mCurrentTime,
|
.lastUsed = mCurrentTime,
|
||||||
};
|
};
|
||||||
VkResult error = vkCreateGraphicsPipelines(mDevice, VK_NULL_HANDLE, 1, &pipelineCreateInfo,
|
VkResult error = vkCreateGraphicsPipelines(mDevice, mPipelineCache, 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) {
|
||||||
@@ -271,6 +276,8 @@ void VulkanPipelineCache::terminate() noexcept {
|
|||||||
}
|
}
|
||||||
mPipelines.clear();
|
mPipelines.clear();
|
||||||
mBoundPipeline = {};
|
mBoundPipeline = {};
|
||||||
|
|
||||||
|
vkDestroyPipelineCache(mDevice, mPipelineCache, VKALLOC);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanPipelineCache::gc() noexcept {
|
void VulkanPipelineCache::gc() noexcept {
|
||||||
|
|||||||
@@ -198,6 +198,10 @@ 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 = {};
|
||||||
|
|
||||||
|
|||||||
150
filament/backend/src/webgpu/WGPUProgram.cpp
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
@@ -325,6 +325,9 @@ 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) {
|
||||||
@@ -369,7 +372,7 @@ Handle<HwTexture> WebGPUDriver::importTextureS() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
|
Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
|
||||||
return Handle<HwProgram>((Handle<HwProgram>::HandleId) mNextFakeHandle++);
|
return allocHandle<WGPUProgram>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
|
Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
|
||||||
@@ -464,21 +467,13 @@ void WebGPUDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t wi
|
|||||||
uint32_t height, uint64_t flags) {}
|
uint32_t height, uint64_t flags) {}
|
||||||
|
|
||||||
void WebGPUDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vbih, uint8_t bufferCount,
|
void WebGPUDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vbih, uint8_t bufferCount,
|
||||||
uint8_t attributeCount, AttributeArray attributes) {
|
uint8_t attributeCount, AttributeArray attributes) {}
|
||||||
constructHandle<WGPUVertexBufferInfo>(vbih, bufferCount, attributeCount, attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint32_t vertexCount,
|
void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint32_t vertexCount,
|
||||||
Handle<HwVertexBufferInfo> vbih) {
|
Handle<HwVertexBufferInfo> vbih) {}
|
||||||
auto* vertexBufferInfo = handleCast<WGPUVertexBufferInfo>(vbih);
|
|
||||||
constructHandle<WGPUVertexBuffer>(vbh, mDevice, vertexCount,vertexBufferInfo->bufferCount, vbih);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebGPUDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
|
void WebGPUDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
|
||||||
uint32_t indexCount, BufferUsage usage) {
|
uint32_t indexCount, BufferUsage usage) {}
|
||||||
auto elementSize = (uint8_t)getElementTypeSize(elementType);
|
|
||||||
constructHandle<WGPUIndexBuffer>(ibh, mDevice, elementSize, indexCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebGPUDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
|
void WebGPUDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
|
||||||
BufferObjectBinding bindingType, BufferUsage usage) {}
|
BufferObjectBinding bindingType, BufferUsage usage) {}
|
||||||
@@ -511,21 +506,12 @@ void WebGPUDriver::importTextureR(Handle<HwTexture> th, intptr_t id, SamplerType
|
|||||||
uint32_t depth, TextureUsage usage) {}
|
uint32_t depth, TextureUsage usage) {}
|
||||||
|
|
||||||
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) {}
|
||||||
assert_invariant(mDevice);
|
|
||||||
|
|
||||||
auto* renderPrimitive = handleCast<WGPURenderPrimitive>(rph);
|
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||||
auto* vertexBuffer = handleCast<WGPUVertexBuffer>(vbh);
|
constructHandle<WGPUProgram>(ph, mDevice, program);
|
||||||
auto* indexBuffer = handleCast<WGPUIndexBuffer>(ibh);
|
|
||||||
// auto* vertexBufferInfo = handleCast<WGPUVertexBufferInfo>(vertexBuffer->vbih);
|
|
||||||
// renderPrimitive->setBuffers(vertexBufferInfo, vertexBuffer, indexBuffer);
|
|
||||||
renderPrimitive->vertexBuffer = vertexBuffer;
|
|
||||||
renderPrimitive->indexBuffer = indexBuffer;
|
|
||||||
renderPrimitive->type = pt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {}
|
|
||||||
|
|
||||||
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
|
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
|
||||||
assert_invariant(!mDefaultRenderTarget);
|
assert_invariant(!mDefaultRenderTarget);
|
||||||
mDefaultRenderTarget = constructHandle<WGPURenderTarget>(rth);
|
mDefaultRenderTarget = constructHandle<WGPURenderTarget>(rth);
|
||||||
@@ -542,7 +528,7 @@ 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);
|
constructHandle<WebGPUDescriptorSetLayout>(dslh, std::move(info), mDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
||||||
@@ -734,7 +720,6 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
|||||||
};
|
};
|
||||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||||
assert_invariant(mCommandEncoder);
|
assert_invariant(mCommandEncoder);
|
||||||
|
|
||||||
// TODO: Remove this code once WebGPU pipeline is implemented
|
// TODO: Remove this code once WebGPU pipeline is implemented
|
||||||
static float red = 1.0f;
|
static float red = 1.0f;
|
||||||
if (red - 0.01 > 0) {
|
if (red - 0.01 > 0) {
|
||||||
@@ -822,7 +807,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> src,
|
|||||||
scheduleDestroy(std::move(p));
|
scheduleDestroy(std::move(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
void WebGPUDriver::readBufferSubData(Handle<HwBufferObject> 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));
|
||||||
}
|
}
|
||||||
@@ -848,70 +833,6 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||||
// VulkanCommandBuffer* commands = mCurrentRenderPass.commandBuffer;
|
|
||||||
// VkCommandBuffer cmdbuffer = commands->buffer();
|
|
||||||
// auto prim = resource_ptr<VulkanRenderPrimitive>::cast(&mResourceManager, rph);
|
|
||||||
auto* renderPrimitive = handleCast<WGPURenderPrimitive>(rph);
|
|
||||||
// commands->acquire(prim);
|
|
||||||
|
|
||||||
// This *must* match the VulkanVertexBufferInfo that was bound in bindPipeline(). But we want
|
|
||||||
// to allow to call this before bindPipeline(), so the validation can only happen in draw()
|
|
||||||
auto vbi = handleCast<WGPUVertexBufferInfo>(renderPrimitive->vertexBuffer->vbih);
|
|
||||||
(void) vbi;
|
|
||||||
|
|
||||||
// mRenderPassEncoder.SetVertexBuffer(uint32_t slot, Buffer const& buffer = nullptr, uint64_t offset = 0, uint64_t size = kWholeSize);
|
|
||||||
mRenderPassEncoder.SetIndexBuffer(renderPrimitive->indexBuffer->buffer, renderPrimitive->indexBuffer->indexFormat);
|
|
||||||
|
|
||||||
// uint32_t const bufferCount = vbi->getAttributeCount();
|
|
||||||
// VkDeviceSize const* offsets = vbi->getOffsets();
|
|
||||||
// VkBuffer const* buffers = prim->vertexBuffer->getVkBuffers();
|
|
||||||
//
|
|
||||||
// // Next bind the vertex buffers and index buffer. One potential performance improvement is to
|
|
||||||
// // avoid rebinding these if they are already bound, but since we do not (yet) support subranges
|
|
||||||
// // it would be rare for a client to make consecutive draw calls with the same render primitive.
|
|
||||||
// vkCmdBindVertexBuffers(cmdbuffer, 0, bufferCount, buffers, offsets);
|
|
||||||
// vkCmdBindIndexBuffer(cmdbuffer, prim->indexBuffer->buffer.getGpuBuffer(), 0,
|
|
||||||
// prim->indexBuffer->indexType);
|
|
||||||
|
|
||||||
|
|
||||||
// METAL
|
|
||||||
// if (UTILS_UNLIKELY(mContext->currentRenderPassAbandoned)) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder != nullptr)
|
|
||||||
// << "bindRenderPrimitive() without a valid command encoder.";
|
|
||||||
//
|
|
||||||
// // Bind the user vertex buffers.
|
|
||||||
// MetalBuffer* vertexBuffers[MAX_VERTEX_BUFFER_COUNT] = {};
|
|
||||||
// size_t vertexBufferOffsets[MAX_VERTEX_BUFFER_COUNT] = {};
|
|
||||||
// size_t maxBufferIndex = 0;
|
|
||||||
//
|
|
||||||
// MetalRenderPrimitive const* const primitive = handle_cast<MetalRenderPrimitive>(rph);
|
|
||||||
// MetalVertexBufferInfo const* const vbi =
|
|
||||||
// handle_cast<MetalVertexBufferInfo>(primitive->vertexBuffer->vbih);
|
|
||||||
//
|
|
||||||
// mContext->currentRenderPrimitive = rph;
|
|
||||||
//
|
|
||||||
// auto vb = primitive->vertexBuffer;
|
|
||||||
// for (auto m : vbi->bufferMapping) {
|
|
||||||
// assert_invariant(
|
|
||||||
// m.bufferArgumentIndex >= USER_VERTEX_BUFFER_BINDING_START &&
|
|
||||||
// m.bufferArgumentIndex < USER_VERTEX_BUFFER_BINDING_START + MAX_VERTEX_BUFFER_COUNT);
|
|
||||||
// size_t const vertexBufferIndex = m.bufferArgumentIndex - USER_VERTEX_BUFFER_BINDING_START;
|
|
||||||
// vertexBuffers[vertexBufferIndex] = vb->buffers[m.sourceBufferIndex];
|
|
||||||
// maxBufferIndex = std::max(maxBufferIndex, vertexBufferIndex);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// const auto bufferCount = maxBufferIndex + 1;
|
|
||||||
// MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder,
|
|
||||||
// USER_VERTEX_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX, vertexBuffers,
|
|
||||||
// vertexBufferOffsets, bufferCount);
|
|
||||||
//
|
|
||||||
// // Bind the zero buffer, used for missing vertex attributes.
|
|
||||||
// static const char bytes[16] = { 0 };
|
|
||||||
// [mContext->currentRenderPassEncoder setVertexBytes:bytes
|
|
||||||
// length:16
|
|
||||||
// atIndex:ZERO_VERTEX_BUFFER_BINDING];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
void WebGPUDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||||
@@ -938,22 +859,22 @@ void WebGPUDriver::resetState(int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::updateDescriptorSetBuffer(
|
void WebGPUDriver::updateDescriptorSetBuffer(
|
||||||
backend::DescriptorSetHandle dsh,
|
Handle<HwDescriptorSet> dsh,
|
||||||
backend::descriptor_binding_t binding,
|
backend::descriptor_binding_t binding,
|
||||||
backend::BufferObjectHandle boh,
|
Handle<HwBufferObject> boh,
|
||||||
uint32_t offset,
|
uint32_t offset,
|
||||||
uint32_t size) {
|
uint32_t size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::updateDescriptorSetTexture(
|
void WebGPUDriver::updateDescriptorSetTexture(
|
||||||
backend::DescriptorSetHandle dsh,
|
Handle<HwDescriptorSet> dsh,
|
||||||
backend::descriptor_binding_t binding,
|
backend::descriptor_binding_t binding,
|
||||||
backend::TextureHandle th,
|
Handle<HwTexture> th,
|
||||||
SamplerParams params) {
|
SamplerParams params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::bindDescriptorSet(
|
void WebGPUDriver::bindDescriptorSet(
|
||||||
backend::DescriptorSetHandle dsh,
|
Handle<HwDescriptorSet> dsh,
|
||||||
backend::descriptor_set_t set,
|
backend::descriptor_set_t set,
|
||||||
backend::DescriptorSetOffsetArray&& offsets) {
|
backend::DescriptorSetOffsetArray&& offsets) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ private:
|
|||||||
template<typename D, typename B>
|
template<typename D, typename B>
|
||||||
void destructHandle(Handle<B>& handle) noexcept {
|
void destructHandle(Handle<B>& handle) noexcept {
|
||||||
auto* p = mHandleAllocator.handle_cast<D*>(handle);
|
auto* p = mHandleAllocator.handle_cast<D*>(handle);
|
||||||
mHandleAllocator.deallocate(handle, p);
|
return mHandleAllocator.deallocate(handle, p);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,126 +27,18 @@ wgpu::Buffer createIndexBuffer(wgpu::Device const& device, uint8_t elementSize,
|
|||||||
.mappedAtCreation = false };
|
.mappedAtCreation = false };
|
||||||
return device.CreateBuffer(&descriptor);
|
return device.CreateBuffer(&descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
wgpu::VertexFormat getVertexFormat(filament::backend::ElementType type, bool normalized, bool integer) {
|
|
||||||
using ElementType = filament::backend::ElementType;
|
|
||||||
// using VertexFormat = wgpu::VertexFormat;
|
|
||||||
if (normalized) {
|
|
||||||
switch (type) {
|
|
||||||
// Single Component Types
|
|
||||||
case ElementType::BYTE: return wgpu::VertexFormat::Snorm8;
|
|
||||||
case ElementType::UBYTE: return wgpu::VertexFormat::Unorm8;
|
|
||||||
case ElementType::SHORT: return wgpu::VertexFormat::Snorm16;
|
|
||||||
case ElementType::USHORT: return wgpu::VertexFormat::Unorm16;
|
|
||||||
// Two Component Types
|
|
||||||
case ElementType::BYTE2: return wgpu::VertexFormat::Snorm8x2;
|
|
||||||
case ElementType::UBYTE2: return wgpu::VertexFormat::Unorm8x2;
|
|
||||||
case ElementType::SHORT2: return wgpu::VertexFormat::Snorm16x2;
|
|
||||||
case ElementType::USHORT2: return wgpu::VertexFormat::Unorm16x2;
|
|
||||||
// Three Component Types
|
|
||||||
// There is no vertex format type for 3 byte data in webgpu. Use
|
|
||||||
// 4 byte signed normalized type and ignore the last byte.
|
|
||||||
// TODO: This is to be verified.
|
|
||||||
case ElementType::BYTE3: return wgpu::VertexFormat::Snorm8x4; // NOT MINSPEC
|
|
||||||
case ElementType::UBYTE3: return wgpu::VertexFormat::Unorm8x4; // NOT MINSPEC
|
|
||||||
case ElementType::SHORT3: return wgpu::VertexFormat::Snorm16x4; // NOT MINSPEC
|
|
||||||
case ElementType::USHORT3: return wgpu::VertexFormat::Unorm16x4; // NOT MINSPEC
|
|
||||||
// Four Component Types
|
|
||||||
case ElementType::BYTE4: return wgpu::VertexFormat::Snorm8x4;
|
|
||||||
case ElementType::UBYTE4: return wgpu::VertexFormat::Unorm8x4;
|
|
||||||
case ElementType::SHORT4: return wgpu::VertexFormat::Snorm16x4;
|
|
||||||
case ElementType::USHORT4: return wgpu::VertexFormat::Unorm8x4;
|
|
||||||
default:
|
|
||||||
FILAMENT_CHECK_POSTCONDITION(false) << "Normalized format does not exist.";
|
|
||||||
return wgpu::VertexFormat::Float32x3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch (type) {
|
|
||||||
// Single Component Types
|
|
||||||
// There is no direct alternative for SSCALED in webgpu. Convert them to Float32 directly.
|
|
||||||
// This will result in increased memory on the cpu side.
|
|
||||||
// TODO: Is Float16 acceptable instead with some potential accuracy errors?
|
|
||||||
case ElementType::BYTE: return integer ? wgpu::VertexFormat::Sint8 : wgpu::VertexFormat::Float32;
|
|
||||||
case ElementType::UBYTE: return integer ? wgpu::VertexFormat::Uint8 : wgpu::VertexFormat::Float32;
|
|
||||||
case ElementType::SHORT: return integer ? wgpu::VertexFormat::Sint16 : wgpu::VertexFormat::Float32;
|
|
||||||
case ElementType::USHORT: return integer ? wgpu::VertexFormat::Uint16 : wgpu::VertexFormat::Float32;
|
|
||||||
case ElementType::HALF: return wgpu::VertexFormat::Float16;
|
|
||||||
case ElementType::INT: return wgpu::VertexFormat::Sint32;
|
|
||||||
case ElementType::UINT: return wgpu::VertexFormat::Uint32;
|
|
||||||
case ElementType::FLOAT: return wgpu::VertexFormat::Float32;
|
|
||||||
// Two Component Types
|
|
||||||
case ElementType::BYTE2: return integer ? wgpu::VertexFormat::Sint8x2 : wgpu::VertexFormat::Float32x2;
|
|
||||||
case ElementType::UBYTE2: return integer ? wgpu::VertexFormat::Uint8x2 : wgpu::VertexFormat::Float32x2;
|
|
||||||
case ElementType::SHORT2: return integer ? wgpu::VertexFormat::Sint16x2 : wgpu::VertexFormat::Float32x2;
|
|
||||||
case ElementType::USHORT2: return integer ? wgpu::VertexFormat::Uint16x2 : wgpu::VertexFormat::Float32x2;
|
|
||||||
case ElementType::HALF2: return wgpu::VertexFormat::Float16x2;
|
|
||||||
case ElementType::FLOAT2: return wgpu::VertexFormat::Float32x2;
|
|
||||||
// Three Component Types
|
|
||||||
case ElementType::BYTE3: return wgpu::VertexFormat::Sint8x4; // NOT MINSPEC
|
|
||||||
case ElementType::UBYTE3: return wgpu::VertexFormat::Uint8x4; // NOT MINSPEC
|
|
||||||
case ElementType::SHORT3: return wgpu::VertexFormat::Sint16x4; // NOT MINSPEC
|
|
||||||
case ElementType::USHORT3: return wgpu::VertexFormat::Uint16x4; // NOT MINSPEC
|
|
||||||
case ElementType::HALF3: return wgpu::VertexFormat::Float16x4; // NOT MINSPEC
|
|
||||||
case ElementType::FLOAT3: return wgpu::VertexFormat::Float32x3;
|
|
||||||
// Four Component Types
|
|
||||||
case ElementType::BYTE4: return integer ? wgpu::VertexFormat::Sint8x4 : wgpu::VertexFormat::Float32x4;
|
|
||||||
case ElementType::UBYTE4: return integer ? wgpu::VertexFormat::Uint8x4 : wgpu::VertexFormat::Float32x4;
|
|
||||||
case ElementType::SHORT4: return integer ? wgpu::VertexFormat::Sint16x4 : wgpu::VertexFormat::Float32x4;
|
|
||||||
case ElementType::USHORT4: return integer ? wgpu::VertexFormat::Uint16x4 : wgpu::VertexFormat::Float32x4;
|
|
||||||
case ElementType::HALF4: return wgpu::VertexFormat::Float16x4;
|
|
||||||
case ElementType::FLOAT4: return wgpu::VertexFormat::Float32x4;
|
|
||||||
}
|
|
||||||
FILAMENT_CHECK_POSTCONDITION(false) << "Vertex format should always be defined.";
|
|
||||||
return wgpu::VertexFormat::Float32x3;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
WGPUVertexBufferInfo::WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
|
|
||||||
AttributeArray const& attributes)
|
|
||||||
: HwVertexBufferInfo(bufferCount, attributeCount),
|
|
||||||
mVertexBufferLayout(bufferCount),
|
|
||||||
mAttributes(bufferCount) {
|
|
||||||
for (uint32_t attribIndex = 0; attribIndex < attributes.size(); attribIndex++) {
|
|
||||||
Attribute attrib = attributes[attribIndex];
|
|
||||||
// Ignore the attributes which are not bind to vertex buffers.
|
|
||||||
if (attrib.buffer == Attribute::BUFFER_UNUSED) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_invariant(attrib.buffer < bufferCount);
|
|
||||||
bool const isInteger = attrib.flags & Attribute::FLAG_INTEGER_TARGET;
|
|
||||||
bool const isNormalized = attrib.flags & Attribute::FLAG_NORMALIZED;
|
|
||||||
wgpu::VertexFormat vertexFormat = getVertexFormat(attrib.type, isNormalized, isInteger);
|
|
||||||
|
|
||||||
mAttributes[attrib.buffer].push_back({
|
|
||||||
.format = vertexFormat,
|
|
||||||
.offset = attrib.offset,
|
|
||||||
.shaderLocation = attribIndex,
|
|
||||||
});
|
|
||||||
mVertexBufferLayout[attrib.buffer] = {
|
|
||||||
.arrayStride = attrib.stride,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint32_t bufferIndex = 0; bufferIndex < bufferCount; bufferIndex++) {
|
|
||||||
mVertexBufferLayout[bufferIndex] = {
|
|
||||||
.attributeCount = mAttributes[bufferIndex].size(),
|
|
||||||
.attributes = mAttributes[bufferIndex].data(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WGPUIndexBuffer::WGPUIndexBuffer(wgpu::Device const& device, uint8_t elementSize,
|
WGPUIndexBuffer::WGPUIndexBuffer(wgpu::Device const& device, uint8_t elementSize,
|
||||||
uint32_t indexCount)
|
uint32_t indexCount)
|
||||||
: buffer(createIndexBuffer(device, elementSize, indexCount)),
|
: buffer(createIndexBuffer(device, elementSize, indexCount)) {}
|
||||||
indexFormat(elementSize == 2 ? wgpu::IndexFormat::Uint16 : wgpu::IndexFormat::Uint32) {}
|
|
||||||
|
|
||||||
|
|
||||||
WGPUVertexBuffer::WGPUVertexBuffer(wgpu::Device const &device, uint32_t vextexCount, uint32_t bufferCount,
|
WGPUVertexBuffer::WGPUVertexBuffer(wgpu::Device const &device, uint32_t vextexCount, uint32_t bufferCount,
|
||||||
Handle<HwVertexBufferInfo> vbih)
|
Handle<WGPUVertexBufferInfo> vbih)
|
||||||
: HwVertexBuffer(vextexCount),
|
: HwVertexBuffer(vextexCount),
|
||||||
vbih(vbih),
|
vbih(vbih),
|
||||||
buffers(bufferCount) {
|
buffers(bufferCount) {
|
||||||
@@ -184,14 +76,13 @@ wgpu::ShaderStage WebGPUDescriptorSetLayout::filamentStageToWGPUStage(ShaderStag
|
|||||||
}
|
}
|
||||||
|
|
||||||
WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout,
|
WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout,
|
||||||
wgpu::Device const* device) {
|
wgpu::Device const& device) {
|
||||||
assert_invariant(device->Get());
|
assert_invariant(device);
|
||||||
|
|
||||||
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
|
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
|
||||||
// debugging. For now, hack an incrementing value.
|
// debugging. For now, hack an incrementing value.
|
||||||
static int layoutNum = 0;
|
static int layoutNum = 0;
|
||||||
|
|
||||||
|
|
||||||
uint samplerCount =
|
uint samplerCount =
|
||||||
std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) {
|
std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) {
|
||||||
return fEntry.type == DescriptorType::SAMPLER ||
|
return fEntry.type == DescriptorType::SAMPLER ||
|
||||||
@@ -255,13 +146,7 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
|||||||
.entries = wEntries.data()
|
.entries = wEntries.data()
|
||||||
};
|
};
|
||||||
// TODO Do we need to defer this until we have more info on textures and samplers??
|
// TODO Do we need to defer this until we have more info on textures and samplers??
|
||||||
mLayout = device->CreateBindGroupLayout(&layoutDescriptor);
|
mLayout = device.CreateBindGroupLayout(&layoutDescriptor);
|
||||||
}
|
}
|
||||||
WebGPUDescriptorSetLayout::~WebGPUDescriptorSetLayout() {}
|
WebGPUDescriptorSetLayout::~WebGPUDescriptorSetLayout() {}
|
||||||
|
|
||||||
void WGPURenderPrimitive::setBuffers(WGPUVertexBufferInfo const* const vbi,
|
|
||||||
WGPUVertexBuffer* vertexBuffer, WGPUIndexBuffer* indexBuffer) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}// namespace filament::backend
|
}// namespace filament::backend
|
||||||
|
|||||||
@@ -24,49 +24,42 @@
|
|||||||
#include <backend/Handle.h>
|
#include <backend/Handle.h>
|
||||||
|
|
||||||
#include <utils/FixedCapacityVector.h>
|
#include <utils/FixedCapacityVector.h>
|
||||||
// #include <utils/StructureOfArrays.h>
|
|
||||||
|
|
||||||
#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.
|
||||||
// VertexBufferInfo contains layout info for Vertex Buffer based on WebGPU structs. In WebGPU each
|
// Update the struct when used by WebGPU driver.
|
||||||
// VertexBufferLayout is associated with a single vertex buffer. So number of mVertexBufferLayout
|
|
||||||
// is equal to bufferCount. Each VertexBufferLayout can contain multiple VertexAttribute. Bind index
|
|
||||||
// of vertex buffer is implicitly calculated by the position of VertexBufferLayout in an array.
|
|
||||||
struct WGPUVertexBufferInfo : public HwVertexBufferInfo {
|
struct WGPUVertexBufferInfo : public HwVertexBufferInfo {
|
||||||
|
|
||||||
WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
|
WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
|
||||||
AttributeArray const& attributes);
|
AttributeArray const& attributes)
|
||||||
inline wgpu::VertexBufferLayout const* getVertexBufferLayout() const {
|
: HwVertexBufferInfo(bufferCount, attributeCount),
|
||||||
return mVertexBufferLayout.data();
|
attributes(attributes) {}
|
||||||
}
|
AttributeArray attributes;
|
||||||
inline uint32_t getVertexBufferLayoutSize() const {
|
|
||||||
return mVertexBufferLayout.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline wgpu::VertexAttribute const* getVertexAttributeForIndex(uint32_t index) const {
|
|
||||||
return mAttributes[index].data();
|
|
||||||
}
|
|
||||||
inline uint32_t getVertexAttributeSize(uint32_t index) const {
|
|
||||||
return mAttributes[index].size();
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
std::vector<wgpu::VertexBufferLayout> mVertexBufferLayout;
|
|
||||||
std::vector<std::vector<wgpu::VertexAttribute>> mAttributes;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WGPUVertexBuffer : public HwVertexBuffer {
|
struct WGPUVertexBuffer : public HwVertexBuffer {
|
||||||
WGPUVertexBuffer(wgpu::Device const &device, uint32_t vextexCount, uint32_t bufferCount,
|
WGPUVertexBuffer(wgpu::Device const &device, uint32_t vextexCount, uint32_t bufferCount,
|
||||||
Handle<HwVertexBufferInfo> vbih);
|
Handle<WGPUVertexBufferInfo> vbih);
|
||||||
|
|
||||||
void setBuffer(WGPUBufferObject *bufferObject, uint32_t index);
|
void setBuffer(WGPUBufferObject *bufferObject, uint32_t index);
|
||||||
|
|
||||||
Handle<HwVertexBufferInfo> vbih;
|
Handle<WGPUVertexBufferInfo> vbih;
|
||||||
utils::FixedCapacityVector<wgpu::Buffer> buffers;
|
utils::FixedCapacityVector<wgpu::Buffer> buffers;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,10 +68,9 @@ struct WGPUIndexBuffer : public HwIndexBuffer {
|
|||||||
uint32_t indexCount);
|
uint32_t indexCount);
|
||||||
|
|
||||||
wgpu::Buffer buffer;
|
wgpu::Buffer buffer;
|
||||||
wgpu::IndexFormat indexFormat;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Currently WGPUBufferObject 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.
|
||||||
struct WGPUBufferObject : HwBufferObject {
|
struct WGPUBufferObject : HwBufferObject {
|
||||||
WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount);
|
WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount);
|
||||||
@@ -86,10 +78,9 @@ struct WGPUBufferObject : HwBufferObject {
|
|||||||
wgpu::Buffer buffer;
|
wgpu::Buffer buffer;
|
||||||
const BufferObjectBinding bufferObjectBinding;
|
const BufferObjectBinding bufferObjectBinding;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WebGPUDescriptorSetLayout : public HwDescriptorSetLayout {
|
class WebGPUDescriptorSetLayout : public HwDescriptorSetLayout {
|
||||||
public:
|
public:
|
||||||
WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, wgpu::Device const* device);
|
WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, wgpu::Device const& device);
|
||||||
~WebGPUDescriptorSetLayout();
|
~WebGPUDescriptorSetLayout();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -55,14 +55,17 @@ void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool is
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@@ -156,51 +159,6 @@ void BackendTest::renderTriangle(
|
|||||||
api.endRenderPass();
|
api.endRenderPass();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BackendTest::readPixelsAndAssertHash(const char* testName, size_t width, size_t height,
|
|
||||||
Handle<HwRenderTarget> rt, uint32_t expectedHash, bool exportScreenshot) {
|
|
||||||
void* buffer = calloc(1, width * height * 4);
|
|
||||||
|
|
||||||
struct Capture {
|
|
||||||
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;
|
|
||||||
|
|
||||||
PixelBufferDescriptor pbd(buffer, width * height * 4, PixelDataFormat::RGBA, PixelDataType::UBYTE,
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BackendTest::matchesEnvironment(Backend backend) {
|
bool BackendTest::matchesEnvironment(Backend backend) {
|
||||||
return sBackend == backend;
|
return sBackend == backend;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "private/backend/DriverApi.h"
|
#include "private/backend/DriverApi.h"
|
||||||
|
|
||||||
#include "PlatformRunner.h"
|
#include "PlatformRunner.h"
|
||||||
|
#include "ImageExpectations.h"
|
||||||
|
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
@@ -65,17 +66,14 @@ 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(Backend backend);
|
||||||
static bool matchesEnvironment(OperatingSystem operatingSystem);
|
static bool matchesEnvironment(OperatingSystem operatingSystem);
|
||||||
static bool matchesEnvironment(OperatingSystem operatingSystem, Backend backend);
|
static bool matchesEnvironment(OperatingSystem operatingSystem, Backend backend);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
filament::backend::Driver* driver = nullptr;
|
filament::backend::Driver* driver = nullptr;
|
||||||
@@ -83,6 +81,10 @@ 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,7 +16,6 @@
|
|||||||
|
|
||||||
#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"
|
||||||
@@ -28,14 +27,17 @@
|
|||||||
#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 expectedPixelHash)
|
uint32_t expectedHash)
|
||||||
: mWidth(width), mHeight(height), mFileName(std::move(fileName)),
|
: mWidth(width),
|
||||||
mExpectedPixelHash(expectedPixelHash) {}
|
mHeight(height),
|
||||||
|
mExpectedPixelHash(expectedHash),
|
||||||
|
mFileName(std::move(fileName)) {}
|
||||||
|
|
||||||
int ScreenshotParams::width() const {
|
int ScreenshotParams::width() const {
|
||||||
return mWidth;
|
return mWidth;
|
||||||
@@ -49,24 +51,28 @@ uint32_t ScreenshotParams::expectedHash() const {
|
|||||||
return mExpectedPixelHash;
|
return mExpectedPixelHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ScreenshotParams::outputDirectoryPath() const {
|
std::string ScreenshotParams::actualDirectoryPath() {
|
||||||
return ".";
|
return "images/actual_images";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ScreenshotParams::generatedActualFileName() const {
|
std::string ScreenshotParams::actualFileName() const {
|
||||||
return absl::StrFormat("%s_actual.png", mFileName);
|
return absl::StrFormat("%s_actual.png", mFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ScreenshotParams::generatedActualFilePath() const {
|
std::string ScreenshotParams::actualFilePath() const {
|
||||||
return absl::StrFormat("%s/%s", outputDirectoryPath(), generatedActualFileName());
|
return absl::StrFormat("%s/%s", actualDirectoryPath(), actualFileName());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ScreenshotParams::goldenFileName() const {
|
std::string ScreenshotParams::expectedDirectoryPath() {
|
||||||
return absl::StrFormat("%s_golden.png", mFileName);
|
return "images/expected_images";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ScreenshotParams::goldenFilePath() const {
|
std::string ScreenshotParams::expectedFileName() const {
|
||||||
return absl::StrFormat("%s/%s", outputDirectoryPath(), goldenFileName());
|
return absl::StrFormat("%s.png", mFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
@@ -91,11 +97,22 @@ 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();
|
||||||
EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash()));
|
#ifndef FILAMENT_IOS
|
||||||
|
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.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,12 +126,13 @@ 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(fileName, lineNumber, mApi, std::move(params), renderTarget);
|
mExpectations.emplace_back(std::make_unique<ImageExpectation>(fileName, lineNumber, mApi,
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
@@ -122,32 +140,28 @@ 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.generatedActualFilePath();
|
std::string filePath = internal->params.actualFilePath();
|
||||||
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);
|
||||||
internal->bytesFilled = true;
|
#endif
|
||||||
};
|
};
|
||||||
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() {
|
||||||
@@ -169,4 +183,30 @@ 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,11 +46,12 @@ public:
|
|||||||
int height() const;
|
int height() const;
|
||||||
uint32_t expectedHash() const;
|
uint32_t expectedHash() const;
|
||||||
|
|
||||||
std::string outputDirectoryPath() const;
|
static std::string actualDirectoryPath();
|
||||||
std::string generatedActualFileName() const;
|
std::string actualFileName() const;
|
||||||
std::string generatedActualFilePath() const;
|
std::string actualFilePath() const;
|
||||||
std::string goldenFileName() const;
|
static std::string expectedDirectoryPath();
|
||||||
std::string goldenFilePath() const;
|
std::string expectedFileName() const;
|
||||||
|
std::string expectedFilePath() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int mWidth;
|
int mWidth;
|
||||||
@@ -98,6 +99,17 @@ 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,
|
||||||
@@ -130,7 +142,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
filament::backend::DriverApi& mApi;
|
filament::backend::DriverApi& mApi;
|
||||||
std::vector<ImageExpectation> mExpectations;
|
// Store expectations in unique pointers because they are self referential.
|
||||||
|
std::vector<std::unique_ptr<ImageExpectation>> mExpectations;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //TNT_IMAGE_EXPECTATIONS_H
|
#endif //TNT_IMAGE_EXPECTATIONS_H
|
||||||
|
|||||||
BIN
filament/backend/test/expected_images/Blit2DTextureArray.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
BIN
filament/backend/test/expected_images/ColorMagnify.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
filament/backend/test/expected_images/ColorMinify.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
filament/backend/test/expected_images/ColorResolve.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
filament/backend/test/expected_images/DepthAndStencilBuffer.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
filament/backend/test/expected_images/FeedbackLoops.png
Normal file
|
After Width: | Height: | Size: 982 B |
BIN
filament/backend/test/expected_images/RGB FLOAT to RGB16F.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
BIN
filament/backend/test/expected_images/RGB FLOAT to RGB32F.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
BIN
filament/backend/test/expected_images/RGBA FLOAT to RGBA16F.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
BIN
filament/backend/test/expected_images/RGBA UBYTE to RGBA8.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
BIN
filament/backend/test/expected_images/RenderExternalImage.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
BIN
filament/backend/test/expected_images/StencilBuffer.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
BIN
filament/backend/test/expected_images/UpdateImage3D.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
filament/backend/test/expected_images/UpdateImageMipLevel.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
filament/backend/test/expected_images/UpdateImageSRGB.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
filament/backend/test/expected_images/scissor.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
@@ -197,16 +197,10 @@ TEST_F(BlitTest, ColorMagnify) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ImageExpectations expectations(api);
|
RenderFrame frame(api);
|
||||||
|
EXPECT_IMAGE(dstRenderTargets[0], getExpectations(),
|
||||||
{
|
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMagnify", 0x410bdd31));
|
||||||
RenderFrame frame(api);
|
api.commit(swapChain);
|
||||||
EXPECT_IMAGE(dstRenderTargets[0], expectations,
|
|
||||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMagnify", 0x410bdd31));
|
|
||||||
api.commit(swapChain);
|
|
||||||
}
|
|
||||||
|
|
||||||
flushAndWait();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,14 +254,8 @@ 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(),
|
||||||
ImageExpectations expectations(api);
|
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f));
|
||||||
|
|
||||||
EXPECT_IMAGE(dstRenderTargets[0], expectations,
|
|
||||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f));
|
|
||||||
|
|
||||||
flushAndWait();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(BlitTest, ColorResolve) {
|
TEST_F(BlitTest, ColorResolve) {
|
||||||
@@ -351,14 +339,8 @@ TEST_F(BlitTest, ColorResolve) {
|
|||||||
srcRenderTarget, { 0, 0, kSrcTexWidth, kSrcTexHeight },
|
srcRenderTarget, { 0, 0, kSrcTexWidth, kSrcTexHeight },
|
||||||
SamplerMagFilter::NEAREST);
|
SamplerMagFilter::NEAREST);
|
||||||
|
|
||||||
{
|
EXPECT_IMAGE(dstRenderTarget, getExpectations(),
|
||||||
ImageExpectations expectations(api);
|
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 0xebfac2ef));
|
||||||
|
|
||||||
EXPECT_IMAGE(dstRenderTarget, expectations,
|
|
||||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 0xebfac2ef));
|
|
||||||
|
|
||||||
flushAndWait();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(BlitTest, Blit2DTextureArray) {
|
TEST_F(BlitTest, Blit2DTextureArray) {
|
||||||
@@ -423,17 +405,11 @@ TEST_F(BlitTest, Blit2DTextureArray) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ImageExpectations expectations(api);
|
RenderFrame frame(api);
|
||||||
|
EXPECT_IMAGE(dstRenderTarget, getExpectations(),
|
||||||
{
|
ScreenshotParams(kDstTexWidth, kDstTexHeight, "Blit2DTextureArray",
|
||||||
RenderFrame frame(api);
|
0x8de7d55b));
|
||||||
EXPECT_IMAGE(dstRenderTarget, expectations,
|
api.commit(swapChain);
|
||||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "Blit2DTextureArray",
|
|
||||||
0x8de7d55b));
|
|
||||||
api.commit(swapChain);
|
|
||||||
}
|
|
||||||
|
|
||||||
flushAndWait();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,18 +479,12 @@ TEST_F(BlitTest, BlitRegion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ImageExpectations expectations(api);
|
RenderFrame frame(api);
|
||||||
|
// TODO: for some reason, this test has very, very slight (as in one pixel) differences
|
||||||
{
|
// between OpenGL and Metal. So disable golden checking for now.
|
||||||
RenderFrame frame(api);
|
// EXPECT_IMAGE(dstRenderTarget, expectations, ScreenshotParams(kDstTexWidth,
|
||||||
// TODO: for some reason, this test has very, very slight (as in one pixel) differences
|
// kDstTexHeight, "BlitRegion", 0x74fa34ed));
|
||||||
// between OpenGL and Metal. So disable golden checking for now.
|
api.commit(swapChain);
|
||||||
// EXPECT_IMAGE(dstRenderTarget, expectations, ScreenshotParams(kDstTexWidth,
|
|
||||||
// kDstTexHeight, "BlitRegion", 0x74fa34ed));
|
|
||||||
api.commit(swapChain);
|
|
||||||
}
|
|
||||||
|
|
||||||
flushAndWait();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,25 +537,19 @@ TEST_F(BlitTest, BlitRegionToSwapChain) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
ImageExpectations expectations(api);
|
RenderFrame frame(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,6 +16,7 @@
|
|||||||
|
|
||||||
#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"
|
||||||
@@ -220,9 +221,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;
|
|
||||||
readPixelsAndAssertHash(
|
EXPECT_IMAGE(renderTarget, getExpectations(),
|
||||||
"BufferObjectUpdateWithOffset", 512, 512, renderTarget, expectedHash, true);
|
ScreenshotParams(512, 512, "BufferObjectUpdateWithOffset", 91322442));
|
||||||
|
|
||||||
api.flush();
|
api.flush();
|
||||||
api.commit(swapChain);
|
api.commit(swapChain);
|
||||||
|
|||||||
@@ -71,8 +71,6 @@ 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.
|
||||||
@@ -96,25 +94,6 @@ 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.
|
||||||
@@ -259,7 +238,8 @@ 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) {
|
||||||
dumpScreenshot(api, renderTargets[0]);
|
EXPECT_IMAGE(renderTargets[0], getExpectations(),
|
||||||
|
ScreenshotParams(kTexWidth, kTexHeight, "FeedbackLoops", 0x70695aa1));
|
||||||
}
|
}
|
||||||
|
|
||||||
api.flush();
|
api.flush();
|
||||||
@@ -270,10 +250,6 @@ 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 -> RGBA8", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8);
|
testCases.emplace_back("RGBA UBYTE to 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 -> RGBA16F", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F);
|
testCases.emplace_back("RGBA FLOAT to 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 -> RGB32F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F);
|
testCases.emplace_back("RGB FLOAT to RGB32F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F);
|
||||||
testCases.emplace_back("RGB, FLOAT -> RGB16F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB16F);
|
testCases.emplace_back("RGB FLOAT to 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 -> RGB10_A2", PixelDataFormat::RGBA, PixelDataType::UINT_2_10_10_10_REV, TextureFormat::RGB10_A2);
|
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("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 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, HALF -> R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::HALF, TextureFormat::R11F_G11F_B10F);
|
testCases.emplace_back("RGB HALF to 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 -> RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
|
testCases.emplace_back("RGB_INTEGER UBYTE to RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
|
||||||
testCases.emplace_back("RGB_INTEGER, USHORT -> RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
|
testCases.emplace_back("RGB_INTEGER USHORT to RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
|
||||||
testCases.emplace_back("RGB_INTEGER, INT -> RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
|
testCases.emplace_back("RGB_INTEGER INT to 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 -> RGBA8 (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u);
|
testCases.emplace_back("RGBA UBYTE to RGBA8 (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u);
|
||||||
testCases.emplace_back("RGBA, FLOAT -> RGBA16F (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F, 64u);
|
testCases.emplace_back("RGBA FLOAT to RGBA16F (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F, 64u);
|
||||||
testCases.emplace_back("RGB, FLOAT -> RGB32F (with buffer padding)", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F, 64u);
|
testCases.emplace_back("RGB FLOAT to 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 -> RGBA8 (subregions)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 0u, true);
|
testCases.emplace_back("RGBA UBYTE to RGBA8 (subregions)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 0u, true);
|
||||||
testCases.emplace_back("RGBA, FLOAT -> RGBA16F (subregions)", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F, 0u, true);
|
testCases.emplace_back("RGBA FLOAT to RGBA16F (subregions)", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F, 0u, true);
|
||||||
testCases.emplace_back("RGBA, UBYTE -> RGBA8 (subregions, buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u, true);
|
testCases.emplace_back("RGBA UBYTE to RGBA8 (subregions and buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u, true);
|
||||||
testCases.emplace_back("RGB, FLOAT -> RGB32F (subregions, buffer padding)", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F, 64u, true);
|
testCases.emplace_back("RGB FLOAT to RGB32F (subregions and buffer padding)", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F, 64u, true);
|
||||||
|
|
||||||
auto& api = getDriverApi();
|
auto& api = getDriverApi();
|
||||||
|
|
||||||
@@ -339,16 +339,14 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
|||||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||||
defaultRenderTarget, swapChain, shader.getProgram());
|
defaultRenderTarget, swapChain, shader.getProgram());
|
||||||
|
|
||||||
readPixelsAndAssertHash(t.name, 512, 512, defaultRenderTarget, expectedHash);
|
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
|
||||||
|
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) {
|
||||||
@@ -372,7 +370,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 = {{
|
||||||
"text_tex", DescriptorType::SAMPLER, samplerInfo
|
"test_tex", DescriptorType::SAMPLER, samplerInfo
|
||||||
}}});
|
}}});
|
||||||
|
|
||||||
// Create a texture.
|
// Create a texture.
|
||||||
@@ -416,15 +414,12 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
|
|||||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||||
defaultRenderTarget, swapChain, shader.getProgram());
|
defaultRenderTarget, swapChain, shader.getProgram());
|
||||||
|
|
||||||
static const uint32_t expectedHash = 359858623;
|
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
|
||||||
readPixelsAndAssertHash("UpdateImageSRGB", 512, 512, defaultRenderTarget, expectedHash);
|
ScreenshotParams(512, 512, "UpdateImageSRGB", 359858623));
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,15 +473,12 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
|
|||||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||||
defaultRenderTarget, swapChain, shader.getProgram());
|
defaultRenderTarget, swapChain, shader.getProgram());
|
||||||
|
|
||||||
static const uint32_t expectedHash = 3644679986;
|
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
|
||||||
readPixelsAndAssertHash("UpdateImageMipLevel", 512, 512, defaultRenderTarget, expectedHash);
|
ScreenshotParams(512, 512, "UpdateImageMipLevel", 3644679986));
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,29 +530,24 @@ 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);
|
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
||||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
|
api.updateDescriptorSetTexture(descriptorSet, 0, texture,
|
||||||
.filterMag = SamplerMagFilter::LINEAR,
|
{ .filterMag = SamplerMagFilter::LINEAR,
|
||||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST });
|
||||||
});
|
|
||||||
|
|
||||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
api.bindDescriptorSet(descriptorSet, 1, {});
|
||||||
|
|
||||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
renderTriangle({ { DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() } },
|
||||||
defaultRenderTarget, swapChain, shader.getProgram());
|
defaultRenderTarget, swapChain, shader.getProgram());
|
||||||
|
|
||||||
static const uint32_t expectedHash = 3644679986;
|
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
|
||||||
readPixelsAndAssertHash("UpdateImage3D", 512, 512, defaultRenderTarget, expectedHash);
|
ScreenshotParams(512, 512, "UpdateImage3D", 3644679986));
|
||||||
|
}
|
||||||
|
|
||||||
api.flush();
|
|
||||||
api.commit(swapChain);
|
|
||||||
api.endFrame(0);
|
|
||||||
|
|
||||||
// This ensures all driver commands have finished before exiting the test.
|
|
||||||
api.finish();
|
|
||||||
api.stopCapture();
|
api.stopCapture();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
#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 "Skip.h"
|
||||||
@@ -151,19 +152,14 @@ TEST_F(BackendTest, PushConstants) {
|
|||||||
|
|
||||||
api.endRenderPass();
|
api.endRenderPass();
|
||||||
|
|
||||||
readPixelsAndAssertHash("pushConstants", 512, 512, renderTarget, 1957275826, true);
|
EXPECT_IMAGE(renderTarget, getExpectations(),
|
||||||
|
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,6 +16,7 @@
|
|||||||
|
|
||||||
#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"
|
||||||
@@ -200,17 +201,16 @@ 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();
|
api.finish();
|
||||||
|
flushAndWait();
|
||||||
|
|
||||||
executeCommands();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
#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,20 +131,14 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
|||||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||||
api.endRenderPass();
|
api.endRenderPass();
|
||||||
|
|
||||||
readPixelsAndAssertHash("scissor", kSrcTexWidth >> 1, kSrcTexHeight >> 1, fullRenderTarget,
|
EXPECT_IMAGE(fullRenderTarget, getExpectations(),
|
||||||
0xAB3D1C53, true);
|
ScreenshotParams(kSrcTexWidth >> 1, kSrcTexHeight >> 1, "scissor", 0xAB3D1C53));
|
||||||
|
|
||||||
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.
|
||||||
@@ -226,20 +221,14 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
|
|||||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||||
api.endRenderPass();
|
api.endRenderPass();
|
||||||
|
|
||||||
readPixelsAndAssertHash(
|
EXPECT_IMAGE(renderTarget, getExpectations(),
|
||||||
"ScissorViewportEdgeCases", 512, 512, renderTarget, 0x6BF00F31, true);
|
ScreenshotParams(512, 512, "ScissorViewportEdgeCases", 0x6BF00F31));
|
||||||
|
|
||||||
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,6 +16,7 @@
|
|||||||
|
|
||||||
#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"
|
||||||
@@ -129,7 +130,8 @@ TEST_F(BasicStencilBufferTest, StencilBuffer) {
|
|||||||
|
|
||||||
RunTest(renderTarget);
|
RunTest(renderTarget);
|
||||||
|
|
||||||
readPixelsAndAssertHash("StencilBuffer", 512, 512, renderTarget, 0x3B1AEF0F, true);
|
EXPECT_IMAGE(renderTarget, getExpectations(),
|
||||||
|
ScreenshotParams(512, 512, "StencilBuffer", 0x3B1AEF0F));
|
||||||
|
|
||||||
flushAndWait();
|
flushAndWait();
|
||||||
getDriver().purge();
|
getDriver().purge();
|
||||||
@@ -151,7 +153,8 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
|
|||||||
|
|
||||||
RunTest(renderTarget);
|
RunTest(renderTarget);
|
||||||
|
|
||||||
readPixelsAndAssertHash("DepthAndStencilBuffer", 512, 512, renderTarget, 0x3B1AEF0F, true);
|
EXPECT_IMAGE(renderTarget, getExpectations(),
|
||||||
|
ScreenshotParams(512, 512, "DepthAndStencilBuffer", 0x3B1AEF0F));
|
||||||
|
|
||||||
flushAndWait();
|
flushAndWait();
|
||||||
getDriver().purge();
|
getDriver().purge();
|
||||||
@@ -233,7 +236,8 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
|||||||
api.stopCapture(0);
|
api.stopCapture(0);
|
||||||
api.endFrame(0);
|
api.endFrame(0);
|
||||||
|
|
||||||
readPixelsAndAssertHash("StencilBufferAutoResolve", 512, 512, renderTarget1, 0x6CEFAC8F, true);
|
EXPECT_IMAGE(renderTarget1, getExpectations(),
|
||||||
|
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.2"
|
spec.version = "1.59.3"
|
||||||
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.2/filament-v1.59.2-ios.tgz" }
|
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.59.3/filament-v1.59.3-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,6 +50,8 @@
|
|||||||
#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,7 +37,10 @@
|
|||||||
#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,6 +21,8 @@
|
|||||||
#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,6 +112,7 @@ 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,24 +17,23 @@
|
|||||||
#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_NEVER (0)
|
#define SYSTRACE_TAG_FILAMENT (2) // don't change used in makefiles
|
||||||
#define SYSTRACE_TAG_ALWAYS (1<<0)
|
#define SYSTRACE_TAG_JOBSYSTEM (3)
|
||||||
#define SYSTRACE_TAG_FILAMENT (1<<1) // don't change, used in makefiles
|
#define SYSTRACE_TAG_GLTFIO (4)
|
||||||
#define SYSTRACE_TAG_JOBSYSTEM (1<<2)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The SYSTRACE_ macros use SYSTRACE_TAG as a the TAG, which should be defined
|
* The SYSTRACE_ macros use SYSTRACE_TAG as a category, which must be defined
|
||||||
* before this file is included. If not, the SYSTRACE_TAG_ALWAYS tag will be used.
|
* before this file is included.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef SYSTRACE_TAG
|
#ifndef SYSTRACE_TAG
|
||||||
#define SYSTRACE_TAG (SYSTRACE_TAG_ALWAYS)
|
# error SYSTRACE_TAG must be set to SYSTRACE_TAG_{DISABLED|FILAMENT|JOBSYSTEM}
|
||||||
#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__)
|
||||||
@@ -44,7 +43,6 @@
|
|||||||
#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)
|
||||||
@@ -56,6 +54,6 @@
|
|||||||
#define SYSTRACE_VALUE32(name, val)
|
#define SYSTRACE_VALUE32(name, val)
|
||||||
#define SYSTRACE_VALUE64(name, val)
|
#define SYSTRACE_VALUE64(name, val)
|
||||||
|
|
||||||
#endif // ANDROID
|
#endif
|
||||||
|
|
||||||
#endif // TNT_UTILS_SYSTRACE_H
|
#endif // TNT_UTILS_SYSTRACE_H
|
||||||
|
|||||||
@@ -17,226 +17,75 @@
|
|||||||
#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 <atomic>
|
#include <perfetto/perfetto.h>
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <utils/compiler.h>
|
PERFETTO_DEFINE_CATEGORIES_IN_NAMESPACE(systrace,
|
||||||
|
perfetto::Category("filament"),
|
||||||
|
perfetto::Category("jobsystem"),
|
||||||
|
perfetto::Category("gltfio"));
|
||||||
|
|
||||||
// enable tracing
|
PERFETTO_USE_CATEGORIES_FROM_NAMESPACE(systrace);
|
||||||
#define SYSTRACE_ENABLE() ::utils::details::Systrace::enable(SYSTRACE_TAG)
|
|
||||||
|
|
||||||
// disable tracing
|
#if SYSTRACE_TAG == SYSTRACE_TAG_FILAMENT
|
||||||
#define SYSTRACE_DISABLE() ::utils::details::Systrace::disable(SYSTRACE_TAG)
|
# define UTILS_PERFETTO_CATEGORY "filament"
|
||||||
|
#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()
|
||||||
* Creates a Systrace context in the current scope. needed for calling all other systrace
|
#define SYSTRACE_CONTEXT()
|
||||||
* commands below.
|
#define SYSTRACE_NAME(name)
|
||||||
*/
|
#define SYSTRACE_FRAME_ID(frame)
|
||||||
#define SYSTRACE_CONTEXT() ::utils::details::Systrace ___trctx(SYSTRACE_TAG)
|
#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
|
||||||
|
|
||||||
// SYSTRACE_NAME traces the beginning and end of the current scope. To trace
|
#define SYSTRACE_ENABLE()
|
||||||
// the correct start and end times this macro should be declared first in the
|
#define SYSTRACE_CONTEXT()
|
||||||
// scope body.
|
|
||||||
// It also automatically creates a Systrace context
|
|
||||||
#define SYSTRACE_NAME(name) ::utils::details::ScopedTrace ___tracer(SYSTRACE_TAG, name)
|
|
||||||
|
|
||||||
// Denotes that a new frame has started processing.
|
#define SYSTRACE_CALL() \
|
||||||
#define SYSTRACE_FRAME_ID(frame) \
|
auto constexpr FILAMENT_SYSTRACE_FUNCTION = perfetto::StaticString(__FUNCTION__); \
|
||||||
{ /* scope for frame id trace */ \
|
TRACE_EVENT(UTILS_PERFETTO_CATEGORY, FILAMENT_SYSTRACE_FUNCTION)
|
||||||
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_NAME(name) TRACE_EVENT(UTILS_PERFETTO_CATEGORY, nullptr, \
|
||||||
#define SYSTRACE_CALL() SYSTRACE_NAME(__FUNCTION__)
|
[&](perfetto::EventContext ctx) { \
|
||||||
|
ctx.event()->set_name(name); \
|
||||||
|
})
|
||||||
|
|
||||||
#define SYSTRACE_NAME_BEGIN(name) \
|
#define SYSTRACE_NAME_BEGIN(name) TRACE_EVENT_BEGIN(UTILS_PERFETTO_CATEGORY, nullptr, \
|
||||||
___trctx.traceBegin(SYSTRACE_TAG, name)
|
[&](perfetto::EventContext ctx) { \
|
||||||
|
ctx.event()->set_name(name); \
|
||||||
|
})
|
||||||
|
|
||||||
#define SYSTRACE_NAME_END() \
|
#define SYSTRACE_NAME_END() TRACE_EVENT_END(UTILS_PERFETTO_CATEGORY)
|
||||||
___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) \
|
#define SYSTRACE_ASYNC_BEGIN(name, cookie) \
|
||||||
___trctx.asyncBegin(SYSTRACE_TAG, name, cookie)
|
TRACE_EVENT_BEGIN(UTILS_PERFETTO_CATEGORY, name, perfetto::Track(cookie))
|
||||||
|
|
||||||
/**
|
|
||||||
* Trace the end of an asynchronous event.
|
|
||||||
* This should have a corresponding SYSTRACE_ASYNC_BEGIN.
|
|
||||||
*/
|
|
||||||
#define SYSTRACE_ASYNC_END(name, cookie) \
|
#define SYSTRACE_ASYNC_END(name, cookie) \
|
||||||
___trctx.asyncEnd(SYSTRACE_TAG, name, cookie)
|
TRACE_EVENT_END(UTILS_PERFETTO_CATEGORY, perfetto::Track(cookie))
|
||||||
|
|
||||||
|
#define SYSTRACE_FRAME_ID(frame) \
|
||||||
|
TRACE_EVENT_INSTANT(UTILS_PERFETTO_CATEGORY, "frame", "id", frame)
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) \
|
||||||
___trctx.value(SYSTRACE_TAG, name, int32_t(val))
|
TRACE_COUNTER(UTILS_PERFETTO_CATEGORY, name, val)
|
||||||
|
|
||||||
#define SYSTRACE_VALUE64(name, val) \
|
#define SYSTRACE_VALUE64(name, val) \
|
||||||
___trctx.value(SYSTRACE_TAG, name, int64_t(val))
|
TRACE_COUNTER(UTILS_PERFETTO_CATEGORY, name, 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,13 +29,25 @@
|
|||||||
#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.
|
||||||
@@ -93,6 +105,8 @@ 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...
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
@@ -118,50 +132,40 @@ 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 tags) noexcept;
|
static void enable(uint32_t tag) noexcept;
|
||||||
static void disable(uint32_t tags) noexcept;
|
|
||||||
|
|
||||||
inline void traceBegin(uint32_t tag, const char* name) noexcept {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void traceEnd(uint32_t tag, const char* name) noexcept {
|
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, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void asyncBegin(uint32_t tag, const char* name, int32_t cookie) noexcept {
|
void asyncBegin(uint32_t tag, const char* name, int32_t cookie) noexcept {
|
||||||
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void asyncEnd(uint32_t tag, const char* name, int32_t cookie) noexcept {
|
void asyncEnd(uint32_t tag, const char* name, int32_t cookie) noexcept {
|
||||||
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void value(uint32_t tag, const char* name, int32_t value) noexcept {
|
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);
|
||||||
@@ -170,7 +174,7 @@ class Systrace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void value(uint32_t tag, const char* name, int64_t value) noexcept {
|
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);
|
||||||
@@ -179,16 +183,16 @@ class Systrace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void frameId(uint32_t tag, uint32_t frame) noexcept {
|
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 {
|
||||||
@@ -213,25 +217,25 @@ class Systrace {
|
|||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ~ScopedTrace() noexcept {
|
~ScopedTrace() noexcept {
|
||||||
mTrace.traceEnd(mTag, mName);
|
mTrace.traceEnd(mTag, mName);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void value(uint32_t tag, const char* name, int32_t v) noexcept {
|
void value(uint32_t tag, const char* name, int32_t v) noexcept {
|
||||||
mTrace.value(tag, name, v);
|
mTrace.value(tag, name, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void value(uint32_t tag, const char* name, int64_t v) noexcept {
|
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,10 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// 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().
|
||||||
#ifndef SYSTRACE_TAG
|
#define SYSTRACE_TAG SYSTRACE_TAG_DISABLED
|
||||||
//#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,206 +14,26 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <utils/Systrace.h>
|
#include <utils/compiler.h>
|
||||||
#include <utils/Log.h>
|
#include <utils/android/Systrace.h>
|
||||||
|
|
||||||
#include <cinttypes>
|
#include <perfetto/perfetto.h>
|
||||||
|
|
||||||
#include <string.h>
|
PERFETTO_TRACK_EVENT_STATIC_STORAGE_IN_NAMESPACE(systrace);
|
||||||
|
|
||||||
#include <errno.h>
|
namespace {
|
||||||
#include <fcntl.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
namespace utils {
|
class SystraceStaticInitialization {
|
||||||
namespace details {
|
public:
|
||||||
|
SystraceStaticInitialization() {
|
||||||
static pthread_once_t atrace_once_control = PTHREAD_ONCE_INIT;
|
perfetto::TracingInitArgs args;
|
||||||
|
args.backends |= perfetto::kSystemBackend;
|
||||||
template <typename T>
|
perfetto::Tracing::Initialize(args);
|
||||||
static void loadSymbol(T*& pfn, const char *symbol) noexcept {
|
systrace::TrackEvent::Register();
|
||||||
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) {
|
UTILS_UNUSED SystraceStaticInitialization sSystraceStaticInitialization{};
|
||||||
// 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,11 +14,17 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <utils/Systrace.h>
|
#include <utils/darwin/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;
|
||||||
@@ -41,21 +47,24 @@ void Systrace::setup() noexcept {
|
|||||||
pthread_once(&atrace_once_control, init_once);
|
pthread_once(&atrace_once_control, init_once);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Systrace::enable(uint32_t tags) noexcept {
|
void Systrace::enable(uint32_t tag) noexcept {
|
||||||
setup();
|
setup();
|
||||||
sGlobalState.isTracingEnabled.fetch_or(tags, std::memory_order_relaxed);
|
uint32_t const mask = 1 << tag;
|
||||||
|
sGlobalState.isTracingEnabled.fetch_or(mask, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Systrace::disable(uint32_t tags) noexcept {
|
void Systrace::disable(uint32_t tag) noexcept {
|
||||||
sGlobalState.isTracingEnabled.fetch_and(~tags, std::memory_order_relaxed);
|
uint32_t const mask = 1 << tag;
|
||||||
|
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();
|
||||||
return bool((sGlobalState.isTracingEnabled.load(std::memory_order_relaxed) | SYSTRACE_TAG_ALWAYS) & tag);
|
uint32_t const mask = 1 << 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 -e && set -x && prepare_mesa && \
|
set -ex && prepare_mesa && \
|
||||||
mkdir -p ${OUTPUT_DIR} && \
|
mkdir -p ${OUTPUT_DIR} && \
|
||||||
./build.sh -X ${MESA_DIR} -p desktop debug gltf_viewer && \
|
CXX=`which clang++` CC=`which clang` ./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,13 +14,34 @@
|
|||||||
|
|
||||||
#!/usr/bin/bash
|
#!/usr/bin/bash
|
||||||
|
|
||||||
set -xe
|
set -x
|
||||||
|
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_DIR=$(pwd)/mesa
|
MESA_VERSION=${MESA_VERSION-24.2.1}
|
||||||
|
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.
|
||||||
@@ -44,12 +65,16 @@ 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}
|
||||||
fi # [[ "$GITHUB_WORKFLOW" ]]
|
! command -v clang > /dev/null 2>&1 && \
|
||||||
|
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
|
||||||
@@ -57,14 +82,7 @@ elif [[ "$os_name" == "Darwin" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
brew install autoconf automake libx11 libxext libxrandr llvm@${LLVM_VERSION} ninja meson pkg-config libxshmfence
|
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=true 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}
|
||||||
@@ -88,10 +106,8 @@ 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
|
||||||
git clone git://anongit.freedesktop.org/mesa/mesa
|
if [[ "${MESA_DIR}" != "${ORIG_DIR}/mesa" ]]; then
|
||||||
# Due to gitlab mesa outage.
|
|
||||||
if [[ "${MESA_DIR}" != "$(pwd)/mesa" ]]; then
|
|
||||||
mv mesa ${MESA_DIR}
|
mv mesa ${MESA_DIR}
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -100,10 +116,12 @@ 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-24.2.1
|
git checkout mesa-${MESA_VERSION}
|
||||||
|
|
||||||
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"
|
||||||
@@ -118,8 +136,16 @@ 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="$(pwd)/out" -Dglx=xlib -Dosmesa=true -Dgallium-drivers=llvmpipe,swrast -Dvulkan-drivers=swrast
|
meson setup --wipe builddir/ -Dprefix="${MESA_DIR}/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
Normal file
175338
third_party/perfetto/perfetto/perfetto.h
vendored
Normal file
30
third_party/perfetto/tnt/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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.2",
|
"version": "1.59.3",
|
||||||
"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",
|
||||||
|
|||||||