Compare commits

..

1 Commits

Author SHA1 Message Date
Matt Hoffman
667da2aabe Create a helper for setting texture uniforms in backend tests.
BUGS=[407799122]
2025-04-14 16:06:03 -05:00
95 changed files with 1243 additions and 243737 deletions

View File

@@ -108,21 +108,9 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Cache Mesa and deps
id: mesa-cache
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
- name: Install python prereqs
run: pip install mako setuptools pyyaml
- name: Run script
run: |
bash test/renderdiff/test.sh
- uses: actions/upload-artifact@v4

15
.gitignore vendored
View File

@@ -18,18 +18,3 @@ test*.json
results
/compile_commands.json
/.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/

View File

@@ -801,7 +801,6 @@ add_subdirectory(${EXTERNAL}/draco/tnt)
add_subdirectory(${EXTERNAL}/jsmn/tnt)
add_subdirectory(${EXTERNAL}/stb/tnt)
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.
add_subdirectory(${LIBRARIES}/geometry)

View File

@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.59.3'
implementation 'com.google.android.filament:filament-android:1.59.1'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.59.3'
pod 'Filament', '~> 1.59.1'
```
## Documentation

View File

@@ -7,12 +7,6 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.59.4
## v1.59.3
## v1.59.2
- Fix build/compile errors when upgrading to MacOS 15.4

View File

@@ -26,10 +26,6 @@ add_library(utils STATIC IMPORTED)
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
${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)
set_target_properties(filabridge PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilabridge.a)
@@ -44,7 +40,6 @@ set_target_properties(shaders PROPERTIES IMPORTED_LOCATION
set(FILAMAT_INCLUDE_DIRS
../../libs/utils/include
../../third_party/perfetto
)
include_directories(${FILAMENT_DIR}/include)
@@ -60,7 +55,6 @@ target_link_libraries(filamat-jni
filabridge
shaders
utils
perfetto
log
smol-v
$<$<STREQUAL:${FILAMENT_SUPPORTS_WEBGPU},ON>:tint>

View File

@@ -21,10 +21,6 @@ add_library(utils STATIC IMPORTED)
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
${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)
set_target_properties(ibl-lite PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libibl-lite.a)
@@ -127,7 +123,6 @@ target_link_libraries(filament-jni
PRIVATE android
PRIVATE jnigraphics
PRIVATE utils
PRIVATE perfetto
# libgeometry is PUBLIC because gltfio uses it.
PUBLIC geometry
@@ -146,7 +141,6 @@ target_include_directories(filament-jni PRIVATE
${FILAMENT_DIR}/include
../../filament/backend/include
../../third_party/robin-map
../../third_party/perfetto
../../libs/utils/include)
# Force a relink when the version script is changed:

View File

@@ -35,10 +35,6 @@ add_library(utils STATIC IMPORTED)
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
${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)
set_target_properties(uberzlib PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libuberzlib.a)
@@ -125,7 +121,6 @@ set(GLTFIO_INCLUDE_DIRS
../../third_party/meshoptimizer/src
../../third_party/robin-map
../../third_party/stb
../../third_party/perfetto
../../libs/utils/include
../../libs/ktxreader/include
)
@@ -134,7 +129,7 @@ add_library(gltfio-jni SHARED ${GLTFIO_SRCS})
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.map)
target_link_libraries(gltfio-jni filament-jni utils perfetto uberzlib log stb ktxreader basis_transcoder zstd uberarchive)
target_link_libraries(gltfio-jni filament-jni utils uberzlib log stb ktxreader basis_transcoder zstd uberarchive)
target_link_libraries(gltfio-jni dracodec meshoptimizer)
target_compile_definitions(gltfio-jni PUBLIC GLTFIO_DRACO_SUPPORTED=1)
target_include_directories(gltfio-jni PRIVATE ${DRACO_DIR}/src)

View File

@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.59.3
VERSION_NAME=1.59.1
POM_DESCRIPTION=Real-time physically based rendering engine for Android.

View File

@@ -259,7 +259,6 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPUHandles.h
src/webgpu/WebGPUSwapChain.cpp
src/webgpu/WebGPUSwapChain.h
src/webgpu/WGPUProgram.cpp
)
if (WIN32)
list(APPEND SRCS src/webgpu/platform/WebGPUPlatformWindows.cpp)
@@ -508,10 +507,8 @@ if (APPLE OR LINUX)
test/Arguments.cpp
test/ImageExpectations.cpp
test/Lifetimes.cpp
test/PlatformRunner.cpp
test/Shader.cpp
test/SharedShaders.cpp
test/Skip.cpp
test/test_FeedbackLoops.cpp
test/test_Blit.cpp
test/test_MissingRequiredAttributes.cpp
@@ -535,9 +532,6 @@ if (APPLE OR LINUX)
filamat
SPIRV
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()
# TODO: Disabling IOS test due to breakage wrt glslang update

View File

@@ -55,9 +55,4 @@ public:
} // namespace filament::backend
#if !defined(NDEBUG)
utils::io::ostream& operator<<(utils::io::ostream& out,
const filament::backend::BufferObjectStreamDescriptor& b);
#endif
#endif // TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H

View File

@@ -84,7 +84,7 @@ void CommandStream::execute(void* buffer) {
Profiler profiler;
if constexpr (SYSTRACE_TAG) {
if (SYSTRACE_TAG) {
if (UTILS_UNLIKELY(mUsePerformanceCounter)) {
// we want to remove all this when tracing is completely disabled
profiler.resetEvents(Profiler::EV_CPU_CYCLES | Profiler::EV_BPU_MISSES);
@@ -100,7 +100,7 @@ void CommandStream::execute(void* buffer) {
}
});
if constexpr (SYSTRACE_TAG) {
if (SYSTRACE_TAG) {
if (UTILS_UNLIKELY(mUsePerformanceCounter)) {
// we want to remove all this when tracing is completely disabled
profiler.stop();

View File

@@ -85,7 +85,6 @@ OpenGLProgram::~OpenGLProgram() noexcept {
delete lazyInitializationData;
ShaderCompilerService::terminate(mToken);
assert_invariant(!mToken);
}
delete [] mUniformsRecords;

View File

@@ -17,15 +17,11 @@
#include "ShaderCompilerService.h"
#include "BlobCacheKey.h"
#include "CallbackManager.h"
#include "CompilerThreadPool.h"
#include "OpenGLBlobCache.h"
#include "OpenGLDriver.h"
#include <iterator>
#include <private/backend/BackendUtils.h>
#include <backend/DriverEnums.h>
#include <backend/Program.h>
#include <utils/compiler.h>
@@ -38,9 +34,9 @@
#include <utils/Panic.h>
#include <utils/Systrace.h>
#include <algorithm>
#include <array>
#include <cctype>
#include <chrono>
#include <mutex>
#include <memory>
#include <string>
@@ -51,7 +47,6 @@
#include <stddef.h>
#include <stdint.h>
#include <string.h>
namespace filament::backend {
@@ -59,17 +54,17 @@ using namespace utils;
// ------------------------------------------------------------------------------------------------
static std::string to_string(bool const b) { return b ? "true" : "false"; }
static std::string to_string(int const i) { return std::to_string(i); }
static std::string to_string(float const f) { return "float(" + std::to_string(f) + ")"; }
static inline std::string to_string(bool b) noexcept { return b ? "true" : "false"; }
static inline std::string to_string(int i) noexcept { return std::to_string(i); }
static inline std::string to_string(float f) noexcept { return "float(" + std::to_string(f) + ")"; }
static void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
GLuint shaderId, CString const& sourceCode) noexcept;
static void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept;
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context, char* source,
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, char* source,
size_t len) noexcept;
static void process_OVR_multiview2(OpenGLContext const& context, int32_t eyeCount, char* source,
static void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount, char* source,
size_t len) noexcept;
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;
@@ -77,15 +72,20 @@ static std::array<std::string_view, 3> splitShaderSource(std::string_view source
// ------------------------------------------------------------------------------------------------
struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
struct ProgramData {
GLuint program{};
shaders_t shaders{};
};
~OpenGLProgramToken() override;
OpenGLProgramToken(ShaderCompilerService& compiler, CString const& name) noexcept
: compiler(compiler), name(name), handle(compiler.issueCallbackHandle()) {
OpenGLProgramToken(ShaderCompilerService& compiler, utils::CString const& name) noexcept
: compiler(compiler), name(name) {
}
ShaderCompilerService& compiler;
CString const& name;
FixedCapacityVector<std::pair<CString, uint8_t>> attributes;
utils::CString const& name;
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes;
shaders_source_t shaderSourceCode;
void* user = nullptr;
struct {
@@ -93,34 +93,48 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
GLuint program = 0;
} gl; // 12 bytes
// Used in THREAD_POOL mode. The job from ThreadPool should call this when the token is ready to
// be used. It sends a signal to the engine thread being blocked upon the `wait` call, so that
// the engine thread resumes its processing with the token.
void signal() noexcept {
// Sets the programData, typically from the compiler thread, and signal the main thread.
// This is similar to std::promise::set_value.
void set(ProgramData const& data) noexcept {
std::unique_lock const l(lock);
programData = data;
signaled = true;
cond.notify_one();
}
// Used in THREAD_POOL mode. The engine thread should call this before accessing token's fields.
// This may block until the token is ready to be used.
// Get the programBinary, wait if necessary.
// This is similar to std::future::get
ProgramData const& get() const noexcept {
std::unique_lock l(lock);
cond.wait(l, [this](){ return signaled; });
return programData;
}
void wait() const noexcept {
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{};
BlobCacheKey key;
// Used for the `THREAD_POOL` mode.
mutable Mutex lock;
mutable Condition cond;
mutable utils::Mutex lock;
mutable utils::Condition cond;
ProgramData programData;
bool signaled = false;
bool canceled = false; // not part of the signaling
};
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() {
compiler.submitCallbackHandle(handle);
}
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() = default;
/* static */ void ShaderCompilerService::setUserData(const program_token_t& token,
void* user) noexcept {
@@ -175,9 +189,9 @@ void ShaderCompilerService::init() noexcept {
if (mMode == Mode::THREAD_POOL) {
// - on Adreno there is a single compiler object. We can't use a pool > 1
// also glProgramBinary blocks if other threads are compiling.
// - on Mali shader compilation can be multithreaded, but program linking happens on
// - on Mali shader compilation can be multi-threaded, but program linking happens on
// a single service thread, so we don't bother using more than one thread either.
// - on PowerVR shader compilation and linking can be multithreaded.
// - on PowerVR shader compilation and linking can be multi-threaded.
// How many threads should we use?
// - 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.
@@ -206,7 +220,7 @@ void ShaderCompilerService::init() noexcept {
mShaderCompilerThreadCount = poolSize;
mCompilerThreadPool.init(mShaderCompilerThreadCount,
[&platform = mDriver.mPlatform, priority] {
[&platform = mDriver.mPlatform, priority]() {
// give the thread a name
JobSystem::setThreadName("CompilerThreadPool");
// run at a slightly lower priority than other filament threads
@@ -214,7 +228,7 @@ void ShaderCompilerService::init() noexcept {
// create a gl context current to this thread
platform.createContext(true);
},
[&platform = mDriver.mPlatform] {
[&platform = mDriver.mPlatform]() {
// release context and thread state
platform.releaseContext();
});
@@ -235,65 +249,119 @@ void ShaderCompilerService::terminate() noexcept {
}
ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
CString const& name, Program&& program) {
utils::CString const& name, Program&& program) {
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);
if (UTILS_UNLIKELY(gl.isES2())) {
token->attributes = std::move(program.getAttributes());
}
// Try retrieving the cached program blob if available.
token->gl.program = mBlobCache.retrieve(&token->key, mDriver.mPlatform, program);
if (token->gl.program) {
return token;
}
// Initiate program compilation.
token->handle = mCallbackManager.get();
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
switch (mMode) {
case Mode::THREAD_POOL: {
// queue a compile job
mCompilerThreadPool.queue(priorityQueue, token,
[this, &gl, program = std::move(program), token]() mutable {
compileShaders(gl, std::move(program.getShadersSource()),
program.getSpecializationConstants(), program.isMultiview(), token);
linkProgram(gl, token);
// Now `token->gl.program` must be populated, so we signal the completion
// of the linking. We don't need to check the result of the program here
// because it'll be done in the engine thread.
token->signal();
// 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
// performing an expensive caching operation still in the pool.
tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
// compile the shaders
shaders_t shaders{};
compileShaders(gl,
std::move(program.getShadersSource()),
program.getSpecializationConstants(),
program.isMultiview(),
shaders,
token->shaderSourceCode);
// link the program
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
OpenGLProgramToken::ProgramData programData;
programData.shaders = shaders;
// We need to query the link status here to guarantee that the
// program is compiled and linked now (we don't want this to be
// deferred to later). We don't care about the result at this point.
GLint status = GL_FALSE;
glGetProgramiv(glProgram, GL_LINK_STATUS, &status);
programData.program = glProgram;
// we don't need to check for success here, it'll be done on the
// main thread side.
token->set(programData);
mCallbackManager.put(token->handle);
// caching must be the last thing we do
if (token->key && status == GL_TRUE) {
// Attempt to cache. This calls glGetProgramBinary.
mBlobCache.insert(mDriver.mPlatform, token->key, glProgram);
}
});
break;
}
case Mode::SYNCHRONOUS:
case Mode::ASYNCHRONOUS: {
compileShaders(gl, std::move(program.getShadersSource()),
program.getSpecializationConstants(), program.isMultiview(), token);
// this cannot fail because we check compilation status after linking the program
// shaders[] is filled with id of shader stages present.
compileShaders(gl,
std::move(program.getShadersSource()),
program.getSpecializationConstants(),
program.isMultiview(),
token->gl.shaders,
token->shaderSourceCode);
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
assert_invariant(mMode != Mode::THREAD_POOL);
if (mMode == Mode::ASYNCHRONOUS) {
// Check link completion if link was initiated.
// don't attempt to link this program if all shaders are not done compiling
GLint status;
if (token->gl.program) {
return isLinkCompleted(token);
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status);
if (status == GL_FALSE) {
return false;
}
} else {
for (auto shader: token->gl.shaders) {
if (shader) {
glGetShaderiv(shader, GL_COMPLETION_STATUS, &status);
if (status == GL_FALSE) {
return false;
}
}
}
}
// Link hasn't been initiated, then check compile completion.
if (!isCompileCompleted(token)) {
}
if (!token->gl.program) {
// link the program, this also cannot fail because status is checked later.
token->gl.program = linkProgram(mDriver.getContext(),
token->gl.shaders, token->attributes);
if (mMode == Mode::ASYNCHRONOUS) {
// wait until the link finishes...
return false;
}
}
if (!token->gl.program) {
linkProgram(mDriver.getContext(), token);
if (mMode == Mode::ASYNCHRONOUS) {
return false;// Wait until the link finishes.
}
assert_invariant(token->gl.program);
mCallbackManager.put(token->handle);
if (token->key) {
// TODO: technically we don't have to cache right now. Is it advantageous to
// do this later, maybe depending on CPU usage?
// attempt to cache if we don't have a thread pool (otherwise it's done
// by the pool).
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
}
return true;
});
break;
@@ -307,7 +375,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
return token;
}
GLuint ShaderCompilerService::getProgram(program_token_t& token) {
GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& token) {
GLuint const program = initialize(token);
assert_invariant(token == nullptr);
#if !FILAMENT_ENABLE_MATDBG
@@ -316,29 +384,43 @@ GLuint ShaderCompilerService::getProgram(program_token_t& token) {
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) {
assert_invariant(token);
assert_invariant(token);// This function should be called when the token is still alive.
token->canceled = true;
bool const isTickOpCanceled = token->compiler.cancelTickOp(token);
if (token->compiler.mMode == Mode::THREAD_POOL) {
auto const job = token->compiler.mCompilerThreadPool.dequeue(token);
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
if (!job) {
// It's likely that the job was already completed. But it may be still being
// executed at this moment. Just try waiting for it to avoid a race.
// The job is being executed right now. We need to wait for it to finish to avoid a
// race.
token->wait();
} else {
// The job has not been executed, but we still need to inform the callback manager in
// order for future callbacks to be successfully called.
token->compiler.mCallbackManager.put(token->handle);
}
} else if (isTickOpCanceled) {
// Since the tick op was canceled, we need to .put the token here.
token->compiler.mCallbackManager.put(token->handle);
}
cleanupProgramAndShaders(token);
for (GLuint& shader: token->gl.shaders) {
if (shader) {
if (token->gl.program) {
glDetachShader(token->gl.program, shader);
}
glDeleteShader(shader);
shader = 0;
}
}
if (token->gl.program) {
glDeleteProgram(token->gl.program);
}
// Cleanup the token.
token->compiler.cancelTickOp(token);
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
token.reset();
}
void ShaderCompilerService::tick() {
@@ -348,16 +430,8 @@ void ShaderCompilerService::tick() {
}
}
CallbackManager::Handle ShaderCompilerService::issueCallbackHandle() const noexcept {
return mCallbackManager.get();
}
void ShaderCompilerService::submitCallbackHandle(CallbackManager::Handle handle) noexcept {
mCallbackManager.put(handle);
}
void ShaderCompilerService::notifyWhenAllProgramsAreReady(
CallbackHandler* handler, CallbackHandler::Callback const callback, void* user) {
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
if (callback) {
mCallbackManager.setCallback(handler, callback, user);
}
@@ -365,137 +439,117 @@ void ShaderCompilerService::notifyWhenAllProgramsAreReady(
// ------------------------------------------------------------------------------------------------
GLuint ShaderCompilerService::initialize(program_token_t& token) {
/* static */ void ShaderCompilerService::getProgramFromCompilerPool(
program_token_t& token) noexcept {
OpenGLProgramToken::ProgramData const& programData{ token->get() };
if (!token->canceled) {
token->gl.shaders = programData.shaders;
token->gl.program = programData.program;
}
}
GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
SYSTRACE_CALL();
if (!token->gl.program) {
switch (mMode) {
case Mode::THREAD_POOL: {
// we need this program right now, remove it from the queue
auto job = mCompilerThreadPool.dequeue(token);
if (job) {
// if we were able to remove it, we execute the job now, otherwise it means
// it's being executed right now.
job();
}
assert_invariant(token);// This function should be called when the token is still alive.
if (!token->canceled) {
token->compiler.cancelTickOp(token);
}
ensureTokenIsReady(token);
assert_invariant(token->gl.program);
// Block until we get the program from the pool. Generally this wouldn't block
// because we just compiled the program above, when executing job.
ShaderCompilerService::getProgramFromCompilerPool(token);
break;
}
// Check status of program linking. If it failed, errors will be logged.
bool const linked = checkLinkStatusAndCleanupShaders(token);
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);
// We panic if it failed to create the program.
FILAMENT_CHECK_POSTCONDITION(linked)
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
token->gl.program =
linkProgram(mDriver.getContext(), token->gl.shaders, token->attributes);
// The program is successfully created. Try caching the program blob. In the THREAD_POOL mode,
// caching is performed in the pool.
if (mMode != Mode::THREAD_POOL) {
tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
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);
}
}
}
GLuint const program = token->gl.program;
// by this point we must have a GL program
assert_invariant(token->gl.program);
// Cleanup the token.
token->compiler.cancelTickOp(token);
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
GLuint program = 0;
// check status of program linking and shader compilation, logs error and free all resources
// in case of error.
bool const success = checkProgramStatus(token);
// Unless we have matdbg, we panic if a program is invalid. Otherwise, we'd get a UB.
// The compilation error has been logged to log.e by this point.
FILAMENT_CHECK_POSTCONDITION(FILAMENT_ENABLE_MATDBG || success)
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
if (UTILS_LIKELY(success)) {
program = token->gl.program;
// no need to keep the shaders around
UTILS_NOUNROLL
for (GLuint& shader: token->gl.shaders) {
if (shader) {
glDetachShader(program, shader);
glDeleteShader(shader);
shader = 0;
}
}
}
// and destroy all temporary init data
token = nullptr;
return program;
}
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,
Program::ShaderSource shadersSource,
FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
bool multiview, program_token_t const& token) noexcept {
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
bool multiview,
shaders_t& outShaders,
UTILS_UNUSED_IN_RELEASE shaders_source_t& outShaderSourceCode) noexcept {
SYSTRACE_CALL();
auto const appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
auto appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
s += "#define SPIRV_CROSS_CONSTANT_ID_" + std::to_string(sc.id) + ' ';
s += std::visit([](auto&& arg) { return to_string(arg); }, sc.value);
s += '\n';
@@ -504,7 +558,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
std::string specializationConstantString;
int32_t numViews = 2;
for (auto const& sc: specializationConstants) {
for (auto const& sc : specializationConstants) {
appendSpecConstantString(specializationConstantString, sc);
if (sc.id == 8) {
// This constant must match
@@ -542,7 +596,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
if (UTILS_LIKELY(!shadersSource[i].empty())) {
Program::ShaderBlob& shader = shadersSource[i];
char* shader_src = reinterpret_cast<char*>(shader.data());
size_t const shader_len = shader.size();
size_t shader_len = shader.size();
// remove GOOGLE_cpp_style_line_directive
process_GOOGLE_cpp_style_line_directive(context, shader_src, shader_len);
@@ -565,191 +619,178 @@ void ShaderCompilerService::executeTickOps() noexcept {
}
std::array<std::string_view, 5> sources = {
version, prolog, specializationConstantString, packingFunctions,
{ body.data(), body.size() - 1 }// null-terminated
version,
prolog,
specializationConstantString,
packingFunctions,
{ body.data(), body.size() - 1 } // null-terminated
};
// Some of the sources may be zero-length. Remove them as to avoid passing lengths of
// zero to glShaderSource(). glShaderSource should work with lengths of zero, but some
// drivers instead interpret zero as a sentinel for a null-terminated string.
auto const partitionPoint = std::stable_partition(sources.begin(), sources.end(),
[](std::string_view s) { return !s.empty(); });
size_t const count = std::distance(sources.begin(), partitionPoint);
auto partitionPoint = std::stable_partition(
sources.begin(), sources.end(), [](std::string_view s) { return !s.empty(); });
size_t count = std::distance(sources.begin(), partitionPoint);
std::array<const char*, 5> shaderStrings;
std::array<GLint, 5> lengths;
for (size_t j = 0; j < count; j++) {
shaderStrings[j] = sources[j].data();
lengths[j] = GLint(sources[j].size());
for (size_t i = 0; i < count; i++) {
shaderStrings[i] = sources[i].data();
lengths[i] = sources[i].size();
}
GLuint const shaderId = glCreateShader(glShaderType);
glShaderSource(shaderId, GLsizei(count), shaderStrings.data(), lengths.data());
glShaderSource(shaderId, count, shaderStrings.data(), lengths.data());
glCompileShader(shaderId);
#ifndef NDEBUG
// for debugging we return the original shader source (without the modifications we
// made here), otherwise the line numbers wouldn't match.
token->shaderSourceCode[i] = { shader_src, shader_len };
outShaderSourceCode[i] = { shader_src, shader_len };
#endif
token->gl.shaders[i] = shaderId;
outShaders[i] = shaderId;
}
}
}
/* static */ bool ShaderCompilerService::isCompileCompleted(program_token_t const& token) noexcept {
GLenum param = GL_COMPLETION_STATUS;
if (UTILS_UNLIKELY(token->compiler.mMode != Mode::ASYNCHRONOUS)) {
param = GL_COMPILE_STATUS;
}
/*
* Create a program from the given shader IDs and links it. This cannot fail because errors
* are checked later. This always returns a valid GL program ID (which doesn't mean the
* program itself is valid).
*/
/* static */ GLuint ShaderCompilerService::linkProgram(OpenGLContext& context,
shaders_t const& shaders,
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept {
for (auto shader: token->gl.shaders) {
if (!shader) {
continue;
}
GLint status;
glGetShaderiv(shader, param, &status);
if (status == GL_FALSE) {
return false;
}
}
return true;
}
/* static */ void ShaderCompilerService::checkCompileStatus(program_token_t const& token) noexcept {
SYSTRACE_CALL();
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();
for (auto const shader: token->gl.shaders) {
for (auto shader : shaders) {
if (shader) {
glAttachShader(program, shader);
}
}
if (UTILS_UNLIKELY(context.isES2())) {
for (auto const& [name, loc]: token->attributes) {
for (auto const& [ name, loc ] : attributes) {
glBindAttribLocation(program, loc, name.c_str());
}
}
glLinkProgram(program);
token->gl.program = program;
return program;
}
/* static */ bool ShaderCompilerService::isLinkCompleted(program_token_t const& token) noexcept {
assert_invariant(token->gl.program);
// ------------------------------------------------------------------------------------------------
GLenum param = GL_COMPLETION_STATUS;
if (UTILS_UNLIKELY(token->compiler.mMode != Mode::ASYNCHRONOUS)) {
param = GL_LINK_STATUS;
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;
}
GLint status;
glGetProgramiv(token->gl.program, param, &status);
return (status == GL_TRUE);
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
return false;
}
/* static */ bool ShaderCompilerService::checkLinkStatusAndCleanupShaders(
program_token_t const& token) noexcept {
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);
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;
if (UTILS_LIKELY(status == GL_TRUE)) {
return true;
}
// No need to keep the shaders around regardless of the result of the program linking.
// only if the link fails, we check the compilation status
UTILS_NOUNROLL
for (GLuint& shader: token->gl.shaders) {
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
const ShaderStage type = static_cast<ShaderStage>(i);
const GLuint shader = token->gl.shaders[i];
if (shader) {
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);
glDeleteShader(shader);
shader = 0;
token->gl.shaders[i] = 0;
}
}
return linked;
}
/* static */ void ShaderCompilerService::tryCachingProgram(OpenGLBlobCache& cache,
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;
}
// log the link error as well
logProgramLinkError(slog.e, token->name.c_str_safe(), token->gl.program);
glDeleteProgram(token->gl.program);
token->gl.program = 0;
return false;
}
// ------------------------------------------------------------------------------------------------
UTILS_NOINLINE
/* static */ void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
GLuint const shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
GLuint shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
{ // scope for the temporary string storage
auto to_string = [](ShaderStage type) -> const char* {
switch (type) {
case ShaderStage::VERTEX:
return "vertex";
case ShaderStage::FRAGMENT:
return "fragment";
case ShaderStage::COMPUTE:
return "compute";
}
return "unknown";
};
auto to_string = [](ShaderStage type) -> const char* {
switch (type) {
case ShaderStage::VERTEX:
return "vertex";
case ShaderStage::FRAGMENT:
return "fragment";
case ShaderStage::COMPUTE:
return "compute";
}
};
{// scope for the temporary string storage
GLint length = 0;
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &length);
@@ -796,8 +837,8 @@ UTILS_NOINLINE
// If usages of the Google-style line directive are present, remove them, as some
// drivers don't allow the quotation marks. This source modification happens in-place.
/* static */ void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context,
char* source, size_t len) noexcept {
/* static */ void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, char* source,
size_t len) noexcept {
if (!context.ext.GOOGLE_cpp_style_line_directive) {
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) {
removeGoogleLineDirectives(source, len);// length is unaffected
@@ -809,13 +850,13 @@ UTILS_NOINLINE
// necessary for OpenGL because OpenGL relies on the number specified in shader files to determine
// the number of views, which is assumed as a single digit, for multiview.
// This source modification happens in-place.
/* static */ void process_OVR_multiview2(OpenGLContext const& context, int32_t const eyeCount,
char* source, size_t const len) noexcept {
/* static */ void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount, char* source,
size_t len) noexcept {
// We don't use regular expression in favor of performance.
if (context.ext.OVR_multiview2) {
const std::string_view shader{ source, len };
constexpr std::string_view layout = "layout";
constexpr std::string_view num_views = "num_views";
const std::string_view layout = "layout";
const std::string_view num_views = "num_views";
size_t found = 0;
while (true) {
found = shader.find(layout, found);
@@ -970,20 +1011,20 @@ mediump vec4 unpackSnorm4x8(highp uint v) {
// - extensions
// - everything else
/* static */ std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept {
auto const version_start = source.find("#version");
auto version_start = source.find("#version");
assert_invariant(version_start != std::string_view::npos);
auto const version_eol = source.find('\n', version_start) + 1;
auto version_eol = source.find('\n', version_start) + 1;
assert_invariant(version_eol != std::string_view::npos);
auto const prolog_start = version_eol;
auto prolog_start = version_eol;
auto prolog_eol = source.rfind("\n#extension");// last #extension line
if (prolog_eol == std::string_view::npos) {
prolog_eol = prolog_start;
} else {
prolog_eol = source.find('\n', prolog_eol + 1) + 1;
}
auto const body_start = prolog_eol;
auto body_start = prolog_eol;
std::string_view const version = source.substr(version_start, version_eol - version_start);
std::string_view const prolog = source.substr(prolog_start, prolog_eol - prolog_start);

View File

@@ -24,23 +24,23 @@
#include "OpenGLBlobCache.h"
#include <backend/CallbackHandler.h>
#include <backend/DriverEnums.h>
#include <backend/Program.h>
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h>
#include <utils/JobSystem.h>
#include <array>
#include <atomic>
#include <condition_variable>
#include <deque>
#include <functional>
#include <memory>
#include <mutex>
#include <tuple>
#include <thread>
#include <utility>
#include <vector>
#include <stdint.h>
namespace filament::backend {
class OpenGLDriver;
@@ -84,7 +84,6 @@ public:
void tick();
// 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);
// stores a user data pointer in the token
@@ -93,12 +92,6 @@ public:
// retrieves the user data pointer stored in the token
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
void notifyWhenAllProgramsAreReady(
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
@@ -106,7 +99,7 @@ public:
private:
struct Job {
template<typename FUNC>
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {} // NOLINT(*-explicit-constructor)
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {}
Job(std::function<bool(Job const& job)> fn,
CallbackHandler* handler, void* user, CallbackHandler::Callback callback)
: fn(std::move(fn)), handler(handler), user(user), callback(callback) {
@@ -135,49 +128,26 @@ private:
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
std::vector<ContainerType> mRunAtNextTickOps;
GLuint initialize(program_token_t& token);
void ensureTokenIsReady(program_token_t const& token);
GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept;
void runAtNextTick(CompilerPriorityQueue priority, program_token_t const& token,
Job job) noexcept;
static void getProgramFromCompilerPool(program_token_t& token) 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;
bool cancelTickOp(program_token_t const& token) noexcept;
// 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;
bool cancelTickOp(program_token_t token) noexcept;
// order of insertion is important
};
} // namespace filament::backend

View File

@@ -15,7 +15,6 @@
*/
#include <backend/BufferDescriptor.h>
#include <backend/BufferObjectStreamDescriptor.h>
#include <backend/DescriptorSetOffsetArray.h>
#include <backend/DriverEnums.h>
#include <backend/PipelineState.h>
@@ -438,10 +437,6 @@ io::ostream& operator<<(io::ostream& out, BufferDescriptor const& b) {
<< ", user=" << b.getUser() << " }";
}
io::ostream& operator<<(io::ostream& out, const BufferObjectStreamDescriptor& b) {
return out << "BufferObjectStreamDescriptor{ streams(" << b.mStreams.size() << ")=... }";
}
io::ostream& operator<<(io::ostream& out, PixelBufferDescriptor const& b) {
BufferDescriptor const& base = static_cast<BufferDescriptor const&>(b);
return out << "PixelBufferDescriptor{ " << base

View File

@@ -152,12 +152,12 @@ static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
#elif FVK_ENABLED(FVK_DEBUG_SYSTRACE)
#include <utils/Systrace.h>
#define FVK_SYSTRACE_CONTEXT() SYSTRACE_CONTEXT()
#define FVK_SYSTRACE_START(marker) SYSTRACE_NAME_BEGIN(marker)
#define FVK_SYSTRACE_END() SYSTRACE_NAME_END()
#define FVK_SYSTRACE_SCOPE() SYSTRACE_CALL()
#define FVK_PROFILE_MARKER(marker) SYSTRACE_CALL()
#define FVK_SYSTRACE_SCOPE() SYSTRACE_NAME(__func__)
#define FVK_PROFILE_MARKER(marker) FVK_SYSTRACE_SCOPE()
#else
#define FVK_SYSTRACE_CONTEXT()

View File

@@ -35,12 +35,7 @@ using namespace bluevk;
namespace filament::backend {
VulkanPipelineCache::VulkanPipelineCache(VkDevice device)
: mDevice(device) {
VkPipelineCacheCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
};
bluevk::vkCreatePipelineCache(mDevice, &createInfo, VKALLOC, &mPipelineCache);
}
: mDevice(device) {}
void VulkanPipelineCache::bindLayout(VkPipelineLayout layout) noexcept {
mPipelineRequirements.layout = layout;
@@ -220,7 +215,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
PipelineCacheEntry cacheEntry = {
.lastUsed = mCurrentTime,
};
VkResult error = vkCreateGraphicsPipelines(mDevice, mPipelineCache, 1, &pipelineCreateInfo,
VkResult error = vkCreateGraphicsPipelines(mDevice, VK_NULL_HANDLE, 1, &pipelineCreateInfo,
VKALLOC, &cacheEntry.handle);
assert_invariant(error == VK_SUCCESS);
if (error != VK_SUCCESS) {
@@ -276,8 +271,6 @@ void VulkanPipelineCache::terminate() noexcept {
}
mPipelines.clear();
mBoundPipeline = {};
vkDestroyPipelineCache(mDevice, mPipelineCache, VKALLOC);
}
void VulkanPipelineCache::gc() noexcept {

View File

@@ -198,10 +198,6 @@ private:
// Immutable state.
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.
PipelineKey mPipelineRequirements = {};

View File

@@ -1,150 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "WebGPUHandles.h"
#include "WebGPUConstants.h"
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <backend/Program.h>
#include <utils/Panic.h>
#include <utils/ostream.h>
#include <webgpu/webgpu_cpp.h>
#include <sstream>
#include <string_view>
#include <vector>
namespace filament::backend {
namespace {
[[nodiscard]] constexpr std::string_view toString(ShaderStage stage) {
switch (stage) {
case ShaderStage::VERTEX:
return "vertex";
case ShaderStage::FRAGMENT:
return "fragment";
case ShaderStage::COMPUTE:
return "compute";
}
}
[[nodiscard]] wgpu::ShaderModule createShaderModule(wgpu::Device& device, const char* programName,
std::array<utils::FixedCapacityVector<uint8_t>, Program::SHADER_TYPE_COUNT> const&
shaderSource,
ShaderStage stage) {
utils::FixedCapacityVector<uint8_t> const& sourceBytes =
shaderSource[static_cast<size_t>(stage)];
if (sourceBytes.empty()) {
return nullptr;// nothing to compile, the shader was not provided
}
wgpu::ShaderModuleWGSLDescriptor wgslDescriptor{};
wgslDescriptor.code = wgpu::StringView(reinterpret_cast<const char*>(sourceBytes.data()));
std::stringstream labelStream;
labelStream << programName << " " << toString(stage) << " shader";
auto label = labelStream.str();
wgpu::ShaderModuleDescriptor descriptor{
.nextInChain = &wgslDescriptor,
.label = label.data()
};
wgpu::ShaderModule module = device.CreateShaderModule(&descriptor);
FILAMENT_CHECK_POSTCONDITION(module != nullptr) << "Failed to create " << descriptor.label;
module.GetCompilationInfo(wgpu::CallbackMode::AllowSpontaneous,
[&descriptor](auto const& status, wgpu::CompilationInfo const* info) {
switch (status) {
case wgpu::CompilationInfoRequestStatus::CallbackCancelled:
FWGPU_LOGW << "Shader compilation info callback cancelled for "
<< descriptor.label << "?" << utils::io::endl;
return;
case wgpu::CompilationInfoRequestStatus::Success:
break;
}
if (info != nullptr) {
std::stringstream errorStream;
int errorCount = 0;
for (size_t msgIndex = 0; msgIndex < info->messageCount; msgIndex++) {
wgpu::CompilationMessage const& message = info->messages[msgIndex];
switch (message.type) {
case wgpu::CompilationMessageType::Info:
FWGPU_LOGI << descriptor.label << ": " << message.message
<< " line#:" << message.lineNum
<< " linePos:" << message.linePos
<< " offset:" << message.offset
<< " length:" << message.length << utils::io::endl;
break;
case wgpu::CompilationMessageType::Warning:
FWGPU_LOGW << "Warning compiling " << descriptor.label << ": "
<< message.message << " line#:" << message.lineNum
<< " linePos:" << message.linePos
<< " offset:" << message.offset
<< " length:" << message.length << utils::io::endl;
break;
case wgpu::CompilationMessageType::Error:
errorCount++;
errorStream << "Error " << errorCount << " : "
<< std::string_view(message.message)
<< " line#:" << message.lineNum
<< " linePos:" << message.linePos
<< " offset:" << message.offset
<< " length:" << message.length << "\n";
break;
}
}
FILAMENT_CHECK_POSTCONDITION(errorCount < 1)
<< errorCount << " error(s) compiling " << descriptor.label << ":\n"
<< errorStream.str();
}
FWGPU_LOGD << descriptor.label << " compiled successfully" << utils::io::endl;
});
return module;
}
std::vector<wgpu::ConstantEntry> convertConstants(
utils::FixedCapacityVector<filament::backend::Program::SpecializationConstant> const&
constantsInfo) {
std::vector<wgpu::ConstantEntry> constants(constantsInfo.size());
for (size_t i = 0; i < constantsInfo.size(); i++) {
filament::backend::Program::SpecializationConstant const& specConstant = constantsInfo[i];
wgpu::ConstantEntry& constantEntry = constants[i];
constantEntry.key = wgpu::StringView(std::to_string(specConstant.id));
if (auto* v = std::get_if<int32_t>(&specConstant.value)) {
constantEntry.value = static_cast<double>(*v);
} else if (auto* f = std::get_if<float>(&specConstant.value)) {
constantEntry.value = static_cast<double>(*f);
} else if (auto* b = std::get_if<bool>(&specConstant.value)) {
constantEntry.value = *b ? 0.0 : 1.0;
}
}
return constants;
}
}// namespace
WGPUProgram::WGPUProgram(wgpu::Device& device, Program& program)
: HwProgram(program.getName()),
vertexShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
ShaderStage::VERTEX)),
fragmentShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
ShaderStage::FRAGMENT)),
computeShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
ShaderStage::COMPUTE)),
constants(convertConstants(program.getSpecializationConstants())) {}
}// namespace filament::backend

View File

@@ -228,14 +228,6 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfi
driverConfig.disableHeapHandleTags) {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printInstanceDetails(mPlatform.getInstance());
#endif
mAdapter = mPlatform.requestAdapter(nullptr);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printAdapterDetails(mAdapter);
#endif
mDevice = mPlatform.requestDevice(mAdapter);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printDeviceDetails(mDevice);
#endif
}
@@ -325,9 +317,6 @@ void WebGPUDriver::destroyTexture(Handle<HwTexture> th) {
}
void WebGPUDriver::destroyProgram(Handle<HwProgram> ph) {
if (ph) {
destructHandle<WGPUProgram>(ph);
}
}
void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
@@ -347,9 +336,6 @@ void WebGPUDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
}
void WebGPUDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> tqh) {
if (tqh) {
destructHandle<WebGPUDescriptorSetLayout>(tqh);
}
}
void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) {
@@ -372,7 +358,7 @@ Handle<HwTexture> WebGPUDriver::importTextureS() noexcept {
}
Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
return allocHandle<WGPUProgram>();
return Handle<HwProgram>((Handle<HwProgram>::HandleId) mNextFakeHandle++);
}
Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
@@ -424,7 +410,8 @@ Handle<HwRenderTarget> WebGPUDriver::createDefaultRenderTargetS() noexcept {
}
Handle<HwDescriptorSetLayout> WebGPUDriver::createDescriptorSetLayoutS() noexcept {
return allocHandle<WebGPUDescriptorSetLayout>();
return Handle<HwDescriptorSetLayout>(
(Handle<HwDescriptorSetLayout>::HandleId) mNextFakeHandle++);
}
Handle<HwTexture> WebGPUDriver::createTextureExternalImageS() noexcept {
@@ -443,7 +430,14 @@ void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
mNativeWindow = nativeWindow;
assert_invariant(!mSwapChain);
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
mAdapter = mPlatform.requestAdapter(surface);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printAdapterDetails(mAdapter);
#endif
mDevice = mPlatform.requestDevice(mAdapter);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printDeviceDetails(mDevice);
#endif
mQueue = mDevice.GetQueue();
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
mSwapChain = constructHandle<WebGPUSwapChain>(sch, std::move(surface), surfaceSize, mAdapter,
@@ -508,9 +502,7 @@ void WebGPUDriver::importTextureR(Handle<HwTexture> th, intptr_t id, SamplerType
void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph, Handle<HwVertexBuffer> vbh,
Handle<HwIndexBuffer> ibh, PrimitiveType pt) {}
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
constructHandle<WGPUProgram>(ph, mDevice, program);
}
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {}
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
assert_invariant(!mDefaultRenderTarget);
@@ -527,9 +519,7 @@ void WebGPUDriver::createFenceR(Handle<HwFence> fh, int) {}
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
void WebGPUDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
backend::DescriptorSetLayout&& info) {
constructHandle<WebGPUDescriptorSetLayout>(dslh, std::move(info), mDevice);
}
backend::DescriptorSetLayout&& info) {}
void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
Handle<HwDescriptorSetLayout> dslh) {}
@@ -807,7 +797,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> src,
scheduleDestroy(std::move(p));
}
void WebGPUDriver::readBufferSubData(Handle<HwBufferObject> boh,
void WebGPUDriver::readBufferSubData(backend::BufferObjectHandle boh,
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
scheduleDestroy(std::move(p));
}
@@ -859,22 +849,22 @@ void WebGPUDriver::resetState(int) {
}
void WebGPUDriver::updateDescriptorSetBuffer(
Handle<HwDescriptorSet> dsh,
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
Handle<HwBufferObject> boh,
backend::BufferObjectHandle boh,
uint32_t offset,
uint32_t size) {
}
void WebGPUDriver::updateDescriptorSetTexture(
Handle<HwDescriptorSet> dsh,
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
Handle<HwTexture> th,
backend::TextureHandle th,
SamplerParams params) {
}
void WebGPUDriver::bindDescriptorSet(
Handle<HwDescriptorSet> dsh,
backend::DescriptorSetHandle dsh,
backend::descriptor_set_t set,
backend::DescriptorSetOffsetArray&& offsets) {
}

View File

@@ -60,93 +60,4 @@ void WGPUVertexBuffer::setBuffer(WGPUBufferObject* bufferObject, uint32_t index)
WGPUBufferObject::WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount)
: HwBufferObject(byteCount),
bufferObjectBinding(bindingType) {}
wgpu::ShaderStage WebGPUDescriptorSetLayout::filamentStageToWGPUStage(ShaderStageFlags fFlags) {
wgpu::ShaderStage retStages = wgpu::ShaderStage::None;
if (any(ShaderStageFlags::VERTEX & fFlags)) {
retStages |= wgpu::ShaderStage::Vertex;
}
if (any(ShaderStageFlags::FRAGMENT & fFlags)) {
retStages |= wgpu::ShaderStage::Fragment;
}
if (any(ShaderStageFlags::COMPUTE & fFlags)) {
retStages |= wgpu::ShaderStage::Compute;
}
return retStages;
}
WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout,
wgpu::Device const& device) {
assert_invariant(device);
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
// debugging. For now, hack an incrementing value.
static int layoutNum = 0;
uint samplerCount =
std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) {
return fEntry.type == DescriptorType::SAMPLER ||
fEntry.type == DescriptorType::SAMPLER_EXTERNAL;
});
std::vector<wgpu::BindGroupLayoutEntry> wEntries;
wEntries.reserve(layout.bindings.size() + samplerCount);
for (auto fEntry: layout.bindings) {
auto& wEntry = wEntries.emplace_back();
wEntry.visibility = filamentStageToWGPUStage(fEntry.stageFlags);
wEntry.binding = fEntry.binding * 2;
switch (fEntry.type) {
// TODO Metal treats these the same. Is this fine?
case DescriptorType::SAMPLER_EXTERNAL:
case DescriptorType::SAMPLER: {
// Sampler binding is 2n+1 due to split.
auto& samplerEntry = wEntries.emplace_back();
samplerEntry.binding = fEntry.binding * 2 + 1;
samplerEntry.visibility = wEntry.visibility;
// We are simply hoping that undefined and defaults suffices here.
samplerEntry.sampler.type = wgpu::SamplerBindingType::Undefined;
wEntry.texture.sampleType = wgpu::TextureSampleType::Undefined;
break;
}
case DescriptorType::UNIFORM_BUFFER: {
wEntry.buffer.hasDynamicOffset =
any(fEntry.flags & DescriptorFlags::DYNAMIC_OFFSET);
wEntry.buffer.type = wgpu::BufferBindingType::Uniform;
// TODO: Ideally we fill minBindingSize
break;
}
case DescriptorType::INPUT_ATTACHMENT: {
// TODO: support INPUT_ATTACHMENT. Metal does not currently.
PANIC_POSTCONDITION("Input Attachment is not supported");
break;
}
case DescriptorType::SHADER_STORAGE_BUFFER: {
// TODO: Vulkan does not support this, can we?
PANIC_POSTCONDITION("Shader storage is not supported");
break;
}
}
// Currently flags are only used to specify dynamic offset.
// UNUSED
// fEntry.count
}
wgpu::BindGroupLayoutDescriptor layoutDescriptor{
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
// debugging. For now, hack an incrementing value.
.label{ "layout_" + std::to_string(++layoutNum) },
.entryCount = wEntries.size(),
.entries = wEntries.data()
};
// TODO Do we need to defer this until we have more info on textures and samplers??
mLayout = device.CreateBindGroupLayout(&layoutDescriptor);
}
WebGPUDescriptorSetLayout::~WebGPUDescriptorSetLayout() {}
}// namespace filament::backend

View File

@@ -28,20 +28,9 @@
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <vector>
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;
// TODO: Currently WGPUVertexBufferInfo is not used by WebGPU for useful task.
// Update the struct when used by WebGPU driver.
@@ -78,18 +67,6 @@ struct WGPUBufferObject : HwBufferObject {
wgpu::Buffer buffer;
const BufferObjectBinding bufferObjectBinding;
};
class WebGPUDescriptorSetLayout : public HwDescriptorSetLayout {
public:
WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, wgpu::Device const& device);
~WebGPUDescriptorSetLayout();
private:
// TODO: If this is useful elsewhere, remove it from this class
// Convert Filament Shader Stage Flags bitmask to webgpu equivilant
static wgpu::ShaderStage filamentStageToWGPUStage(ShaderStageFlags fFlags);
wgpu::BindGroupLayout mLayout;
};
// TODO: Currently WGPUTexture is not used by WebGPU for useful task.
// Update the struct when used by WebGPU driver.

View File

@@ -43,29 +43,24 @@ using namespace image;
namespace test {
Backend BackendTest::sBackend = Backend::NOOP;
OperatingSystem BackendTest::sOperatingSystem = OperatingSystem::OTHER;
bool BackendTest::sIsMobilePlatform = false;
void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform) {
void BackendTest::init(Backend backend, bool isMobilePlatform) {
sBackend = backend;
sOperatingSystem = operatingSystem;
sIsMobilePlatform = isMobilePlatform;
}
BackendTest::BackendTest() : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE,
CONFIG_COMMAND_BUFFERS_SIZE, /*mPaused=*/false) {
initializeDriver();
mImageExpectations.emplace(getDriverApi());
}
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.
if (sBackend == Backend::OPENGL) {
return;
}
flushAndWait();
driver->terminate();
delete driver;
}
@@ -159,16 +154,49 @@ void BackendTest::renderTriangle(
api.endRenderPass();
}
bool BackendTest::matchesEnvironment(Backend backend) {
return sBackend == backend;
}
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);
bool BackendTest::matchesEnvironment(OperatingSystem operatingSystem) {
return sOperatingSystem == operatingSystem;
}
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;
bool BackendTest::matchesEnvironment(OperatingSystem operatingSystem, Backend backend) {
return matchesEnvironment(operatingSystem) && matchesEnvironment(backend);
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));
}
class Environment : public ::testing::Environment {
@@ -182,8 +210,8 @@ public:
}
};
void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc, char* argv[]) {
BackendTest::init(backend, operatingSystem, isMobile);
void initTests(Backend backend, bool isMobile, int& argc, char* argv[]) {
BackendTest::init(backend, isMobile);
::testing::InitGoogleTest(&argc, argv);
::testing::AddGlobalTestEnvironment(new Environment);
}

View File

@@ -25,17 +25,15 @@
#include "private/backend/DriverApi.h"
#include "PlatformRunner.h"
#include "ImageExpectations.h"
namespace test {
class BackendTest : public ::testing::Test {
public:
static void init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform);
static void init(Backend backend, bool isMobilePlatform);
static Backend sBackend;
static OperatingSystem sOperatingSystem;
static bool sIsMobilePlatform;
protected:
@@ -66,14 +64,13 @@ protected:
filament::backend::Handle<filament::backend::HwProgram> program,
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::Driver& getDriver() { return *driver; }
ImageExpectations& getExpectations() { return *mImageExpectations; }
static bool matchesEnvironment(Backend backend);
static bool matchesEnvironment(OperatingSystem operatingSystem);
static bool matchesEnvironment(OperatingSystem operatingSystem, Backend backend);
private:
filament::backend::Driver* driver = nullptr;
@@ -81,10 +78,6 @@ private:
std::unique_ptr<filament::backend::DriverApi> commandStream;
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

View File

@@ -16,6 +16,7 @@
#include "ImageExpectations.h"
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "absl/strings/str_format.h"
#include "utils/Hash.h"
@@ -27,17 +28,14 @@
#ifndef FILAMENT_IOS
#include <imageio/ImageEncoder.h>
#include <imageio/ImageDecoder.h>
#include <image/ColorTransform.h>
#endif
ScreenshotParams::ScreenshotParams(int width, int height, std::string fileName,
uint32_t expectedHash)
: mWidth(width),
mHeight(height),
mExpectedPixelHash(expectedHash),
mFileName(std::move(fileName)) {}
uint32_t expectedPixelHash)
: mWidth(width), mHeight(height), mFileName(std::move(fileName)),
mExpectedPixelHash(expectedPixelHash) {}
int ScreenshotParams::width() const {
return mWidth;
@@ -51,28 +49,24 @@ uint32_t ScreenshotParams::expectedHash() const {
return mExpectedPixelHash;
}
std::string ScreenshotParams::actualDirectoryPath() {
return "images/actual_images";
std::string ScreenshotParams::outputDirectoryPath() const {
return ".";
}
std::string ScreenshotParams::actualFileName() const {
std::string ScreenshotParams::generatedActualFileName() const {
return absl::StrFormat("%s_actual.png", mFileName);
}
std::string ScreenshotParams::actualFilePath() const {
return absl::StrFormat("%s/%s", actualDirectoryPath(), actualFileName());
std::string ScreenshotParams::generatedActualFilePath() const {
return absl::StrFormat("%s/%s", outputDirectoryPath(), generatedActualFileName());
}
std::string ScreenshotParams::expectedDirectoryPath() {
return "images/expected_images";
std::string ScreenshotParams::goldenFileName() const {
return absl::StrFormat("%s_golden.png", mFileName);
}
std::string ScreenshotParams::expectedFileName() const {
return absl::StrFormat("%s.png", mFileName);
}
std::string ScreenshotParams::expectedFilePath() const {
return absl::StrFormat("%s/%s", expectedDirectoryPath(), expectedFileName());
std::string ScreenshotParams::goldenFilePath() const {
return absl::StrFormat("%s/%s", outputDirectoryPath(), goldenFileName());
}
ImageExpectation::ImageExpectation(const char* fileName, int lineNumber,
@@ -97,22 +91,11 @@ void ImageExpectation::evaluate() {
void ImageExpectation::compareImage() const {
bool bytesFilled = mResult.bytesFilled();
// If this fails, it likely means that BackendTest::flushAndWait needs to be called before
// ImageExpectations is evaluated or destroyed.
EXPECT_THAT(bytesFilled, testing::IsTrue())
<< "Render target wasn't copied to the buffer for " << mFileName;
if (bytesFilled) {
// Rather than directly compare the two images compare their hashes because comparing very
// large arrays generates way too much debug output to be useful.
uint32_t actualHash = mResult.hash();
#ifndef FILAMENT_IOS
LoadedPng loadedImage(mParams.expectedFilePath());
uint32_t loadedImageHash = loadedImage.hash();
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.
EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash()));
}
}
@@ -126,13 +109,12 @@ ImageExpectations::~ImageExpectations() {
void ImageExpectations::addExpectation(const char* fileName, int lineNumber,
filament::backend::RenderTargetHandle renderTarget, ScreenshotParams params) {
mExpectations.emplace_back(std::make_unique<ImageExpectation>(fileName, lineNumber, mApi,
std::move(params), renderTarget));
mExpectations.emplace_back(fileName, lineNumber, mApi, std::move(params), renderTarget);
}
void ImageExpectations::evaluate() {
for (auto& expectation: mExpectations) {
expectation->evaluate();
expectation.evaluate();
}
mExpectations.clear();
}
@@ -140,28 +122,32 @@ void ImageExpectations::evaluate() {
RenderTargetDump::RenderTargetDump(filament::backend::DriverApi& api,
filament::backend::RenderTargetHandle renderTarget, const ScreenshotParams& 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;
mInternal->bytes.resize(size);
auto cb = [](void* buffer, size_t size, void* user) {
auto* internal = static_cast<RenderTargetDump::Internal*>(user);
internal->bytesFilled = true;
#ifndef FILAMENT_IOS
image::LinearImage image(internal->params.width(), internal->params.width(), 4);
image = image::toLinearWithAlpha<uint8_t>(internal->params.width(),
internal->params.height(),
internal->params.width() * 4, (uint8_t*)buffer);
std::string filePath = internal->params.actualFilePath();
std::string filePath = internal->params.generatedActualFilePath();
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG, image, "",
filePath);
#endif
internal->bytesFilled = true;
};
filament::backend::PixelBufferDescriptor pb(mInternal->bytes.data(), size,
filament::backend::PixelDataFormat::RGBA, filament::backend::PixelDataType::UBYTE, cb,
(void*)mInternal.get());
api.readPixels(renderTarget, 0, 0, mInternal->params.width(), mInternal->params.height(),
std::move(pb));
#endif
}
RenderTargetDump::~RenderTargetDump() {
@@ -183,30 +169,4 @@ bool RenderTargetDump::bytesFilled() const {
return mInternal->bytesFilled;
}
RenderTargetDump::Internal::Internal(const ScreenshotParams& params) : params(params) {}
LoadedPng::LoadedPng(std::string filePath) : mFilePath(std::move(filePath)) {
#ifndef FILAMENT_IOS
std::ifstream pngStream(mFilePath, std::ios::binary);
image::LinearImage loadedImage = image::ImageDecoder::decode(pngStream, filePath,
image::ImageDecoder::ColorSpace::LINEAR);
size_t valuesInImage = loadedImage.getWidth() * loadedImage.getHeight() *
loadedImage.getChannels();
// The linear image is loaded with each component as [0.0, 1.0] but should be [0, 255], so
// convert them.
mBytes = std::vector<unsigned char>(valuesInImage);
for (int i = 0; i < valuesInImage; ++i) {
mBytes[i] = static_cast<uint8_t>(loadedImage.get<float>()[i] * 255.0f);
}
#endif
// For platforms that don't support the image loading library, leave the loaded data blank.
}
uint32_t LoadedPng::hash() const {
EXPECT_THAT(mBytes, testing::Not(testing::IsEmpty()))
<< "Failed to load expected test result: " << mFilePath;
if (mBytes.empty()) {
return 0;
}
return utils::hash::murmur3((uint32_t*)mBytes.data(), mBytes.size() / 4, 0);
}
RenderTargetDump::Internal::Internal(const ScreenshotParams& params) : params(params) {}

View File

@@ -46,12 +46,11 @@ public:
int height() const;
uint32_t expectedHash() const;
static std::string actualDirectoryPath();
std::string actualFileName() const;
std::string actualFilePath() const;
static std::string expectedDirectoryPath();
std::string expectedFileName() const;
std::string expectedFilePath() const;
std::string outputDirectoryPath() const;
std::string generatedActualFileName() const;
std::string generatedActualFilePath() const;
std::string goldenFileName() const;
std::string goldenFilePath() const;
private:
int mWidth;
@@ -99,17 +98,6 @@ private:
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 {
public:
ImageExpectation(const char* fileName, int lineNumber, filament::backend::DriverApi& api,
@@ -142,8 +130,7 @@ public:
private:
filament::backend::DriverApi& mApi;
// Store expectations in unique pointers because they are self referential.
std::vector<std::unique_ptr<ImageExpectation>> mExpectations;
std::vector<ImageExpectation> mExpectations;
};
#endif //TNT_IMAGE_EXPECTATIONS_H

View File

@@ -1,60 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "PlatformRunner.h"
namespace utils {
template<>
CString to_string<test::Backend>(test::Backend backend) noexcept {
switch (backend) {
case test::Backend::OPENGL: {
return "OpenGL";
}
case test::Backend::VULKAN: {
return "Vulkan";
}
case test::Backend::METAL: {
return "Metal";
}
case test::Backend::WEBGPU: {
return "WebGPU";
}
case test::Backend::NOOP:
default: {
return "No-op";
}
}
}
template<>
CString to_string(test::OperatingSystem os) noexcept {
switch (os) {
case test::OperatingSystem::LINUX: {
return "Linux";
}
case test::OperatingSystem::APPLE: {
return "Apple";
}
case test::OperatingSystem::OTHER:
default: {
return "Other";
}
}
}
} // namespace utils

View File

@@ -19,7 +19,6 @@
#include <stdint.h>
#include <stddef.h>
#include "utils/CString.h"
namespace test {
@@ -35,15 +34,6 @@ enum class Backend : uint8_t {
NOOP = 5,
};
enum class OperatingSystem: uint8_t {
OTHER = 1,
// Also represents android phones.
LINUX = 2,
// Also represents iOS phones.
APPLE = 3,
// TODO: When tests support windows add it here.
};
struct NativeView {
void* ptr = nullptr;
size_t width = 0, height = 0;
@@ -61,10 +51,9 @@ NativeView getNativeView();
* No tests will be run yet.
*
* @param backend The backend to run the tests on.
* @param operatingSystem The operating system the tests are being run on.
* @param isMobile True if the platform is a mobile platform (iOS or Android).
*/
void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc, char* argv[]);
void initTests(Backend backend, bool isMobile, int& argc, char* argv[]);
/**
* Test runners should call runTests when they are ready for tests to be run.
@@ -79,6 +68,6 @@ int runTests();
*/
Backend parseArgumentsForBackend(int argc, char* argv[]);
} // namespace test
}
#endif

View File

@@ -23,6 +23,14 @@ namespace test {
using namespace filament::backend;
filament::backend::descriptor_binding_t TextureBindingConfig::getBinding() const {
return binding.value_or(0);
}
filament::backend::SamplerParams TextureBindingConfig::getParams() const {
return samplerParams.value_or(SamplerParams{});
}
Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) : mCleanup(cleanup) {
utils::FixedCapacityVector<DescriptorSetLayoutBinding> kLayouts(config.uniforms.size());
for (unsigned char i = 0; i < config.uniforms.size(); ++i) {
@@ -57,6 +65,19 @@ Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) : mCleanup
mDescriptorSetLayout = cleanup.add(
api.createDescriptorSetLayout(DescriptorSetLayout{ kLayouts }));
mDefaultDescriptorSet = createDescriptorSet(api);
}
void Shader::updateTextureUniform(filament::backend::DriverApi& api,
TextureBindingConfig config) const {
DescriptorSetHandle descriptorSet = config.descriptorSet.value_or(mDefaultDescriptorSet);
api.updateDescriptorSetTexture(descriptorSet, config.getBinding(), config.textureHandle,
config.getParams());
if (config.alsoBindToSet.has_value()) {
api.bindDescriptorSet(descriptorSet, *config.alsoBindToSet, {});
}
}
filament::backend::DescriptorSetHandle Shader::createDescriptorSet(DriverApi& api) const {

View File

@@ -61,6 +61,18 @@ struct UniformBindingConfig {
ResolvedUniformBindingConfig resolve();
};
struct TextureBindingConfig {
filament::backend::TextureHandle textureHandle;
std::optional<filament::backend::DescriptorSetHandle> descriptorSet;
std::optional<filament::backend::descriptor_binding_t> binding;
std::optional<filament::backend::SamplerParams> samplerParams;
// If present then the call to update the texture will also bind the descriptor set.
std::optional<filament::backend::descriptor_set_t> alsoBindToSet;
filament::backend::descriptor_binding_t getBinding() const;
filament::backend::SamplerParams getParams() const;
};
class Shader {
public:
// All graphics resources have their lifetime controlled by the Cleanup and not this object.
@@ -86,17 +98,20 @@ public:
void bindUniform(filament::backend::DriverApi& api,
filament::backend::Handle<filament::backend::HwBufferObject> hwBuffer,
UniformBindingConfig config) const;
filament::backend::ProgramHandle getProgram() const;
filament::backend::DescriptorSetLayoutHandle getDescriptorSetLayout() const;
void updateTextureUniform(filament::backend::DriverApi& api, TextureBindingConfig config) const;
filament::backend::DescriptorSetHandle createDescriptorSet(
filament::backend::DriverApi& api) const;
filament::backend::ProgramHandle getProgram() const;
filament::backend::DescriptorSetLayoutHandle getDescriptorSetLayout() const;
protected:
Cleanup& mCleanup;
filament::backend::ProgramHandle mProgram;
filament::backend::DescriptorSetLayoutHandle mDescriptorSetLayout;
// Used whenever the caller doesn't provide a descriptor set.
filament::backend::DescriptorSetHandle mDefaultDescriptorSet;
};
template<typename UniformType>
@@ -133,12 +148,8 @@ void Shader::bindUniform(filament::backend::DriverApi& api,
UniformBindingConfig config) const {
auto resolvedConfig = config.resolve<UniformType>();
filament::backend::DescriptorSetHandle descriptorSet;
if (resolvedConfig.descriptorSet.has_value()) {
descriptorSet = *resolvedConfig.descriptorSet;
} else {
descriptorSet = createDescriptorSet(api);
}
filament::backend::DescriptorSetHandle descriptorSet =
resolvedConfig.descriptorSet.value_or(mDefaultDescriptorSet);
api.updateDescriptorSetBuffer(descriptorSet, resolvedConfig.binding, hwBuffer, 0,
resolvedConfig.bufferSize);

View File

@@ -1,96 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Skip.h"
#include <sstream>
namespace test {
SkipEnvironment::SkipEnvironment(test::Backend backend) : backend(backend) {}
SkipEnvironment::SkipEnvironment(test::OperatingSystem os) : os(os) {}
SkipEnvironment::SkipEnvironment(test::OperatingSystem os, test::Backend backend)
: backend(backend),
os(os) {}
bool SkipEnvironment::matches() {
bool backendMatches = !backend.has_value() || *backend == BackendTest::sBackend;
bool osMatches = !os.has_value() || *os == BackendTest::sOperatingSystem;
bool isMobileMatches = !isMobile.has_value() || *isMobile == BackendTest::sIsMobilePlatform;
return backendMatches && osMatches && isMobileMatches;
}
std::string SkipEnvironment::describe() {
std::stringstream result;
if (matches()) {
result << "environment matches because " << describe_actual_environment() << ".";
} else {
result << "environment does not match because " << describe_requirements() << " but "
<< describe_actual_environment() << ".";
}
return result.str();
}
std::string SkipEnvironment::describe_actual_environment() {
bool resultWritten = false;
std::stringstream reality;
if (backend.has_value()) {
reality << "backend was " << utils::to_string(BackendTest::sBackend).c_str();
resultWritten = true;
}
if (os.has_value()) {
if (resultWritten) {
reality << ", and ";
}
reality << "operating system was "
<< utils::to_string(BackendTest::sOperatingSystem).c_str();
resultWritten = true;
}
if (isMobile.has_value()) {
if (resultWritten) {
reality << ", and ";
}
reality << "device " << (BackendTest::sIsMobilePlatform ? "was" : "was not") << " mobile";
resultWritten = true;
}
return reality.str();
}
std::string SkipEnvironment::describe_requirements() {
bool resultWritten = false;
std::stringstream requirement;
if (backend.has_value()) {
requirement << "backend needs to be " << utils::to_string(*backend).c_str();
resultWritten = true;
}
if (os.has_value()) {
if (resultWritten) {
requirement << ", and ";
}
requirement << "operating system needs to be " << utils::to_string(*os).c_str();
resultWritten = true;
}
if (isMobile.has_value() && BackendTest::sIsMobilePlatform != isMobile) {
if (resultWritten) {
requirement << ", and ";
}
requirement << "device needs to " << (*isMobile ? "be" : "not be") << " mobile";
resultWritten = true;
}
return requirement.str();
}
} // namespace test

View File

@@ -1,55 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_SKIP_H
#define TNT_SKIP_H
#include <gtest/gtest.h>
#include "BackendTest.h"
// skipEnvironment must be a test::SkipEnvironment
#define SKIP_IF(skipEnvironment) \
do { \
SkipEnvironment skip(skipEnvironment); \
if (skip.matches()) { \
GTEST_SKIP() << "Skipping test as the " << skip.describe(); \
} \
} while (false)
namespace test {
struct SkipEnvironment {
SkipEnvironment(const SkipEnvironment&) = default;
explicit SkipEnvironment(test::Backend backend);
explicit SkipEnvironment(test::OperatingSystem os);
SkipEnvironment(test::OperatingSystem os, test::Backend backend);
std::optional<test::Backend> backend;
std::optional<test::OperatingSystem> os;
std::optional<bool> isMobile;
bool matches();
// Describes the current state of either matching or mismatching.
std::string describe();
// Describe all the non-null requirements.
std::string describe_requirements();
// Describes the environment's status for all the attributes that are non-null.
std::string describe_actual_environment();
};
} // namespace test
#endif// TNT_SKIP_H

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -51,6 +51,6 @@ int main(int argc, char* argv[]) {
return 1;
}
test::initTests(backend, test::OperatingSystem::LINUX, false, argc, argv);
test::initTests(backend, false, argc, argv);
return test::runTests();
}

View File

@@ -98,7 +98,7 @@ test::NativeView getNativeView() {
int main(int argc, char* argv[]) {
auto backend = test::parseArgumentsForBackend(argc, argv);
test::initTests(backend, test::OperatingSystem::APPLE, false, argc, argv);
test::initTests(backend, false, argc, argv);
AppDelegate* delegate = [AppDelegate new];
delegate.backend = backend;
NSApplication* app = [NSApplication sharedApplication];

View File

@@ -197,10 +197,16 @@ TEST_F(BlitTest, ColorMagnify) {
}
{
RenderFrame frame(api);
EXPECT_IMAGE(dstRenderTargets[0], getExpectations(),
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMagnify", 0x410bdd31));
api.commit(swapChain);
ImageExpectations expectations(api);
{
RenderFrame frame(api);
EXPECT_IMAGE(dstRenderTargets[0], expectations,
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMagnify", 0x410bdd31));
api.commit(swapChain);
}
flushAndWait();
}
}
@@ -254,8 +260,14 @@ TEST_F(BlitTest, ColorMinify) {
{ 0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel },
SamplerMagFilter::LINEAR);
EXPECT_IMAGE(dstRenderTargets[0], getExpectations(),
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f));
{
ImageExpectations expectations(api);
EXPECT_IMAGE(dstRenderTargets[0], expectations,
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f));
flushAndWait();
}
}
TEST_F(BlitTest, ColorResolve) {
@@ -339,8 +351,14 @@ TEST_F(BlitTest, ColorResolve) {
srcRenderTarget, { 0, 0, kSrcTexWidth, kSrcTexHeight },
SamplerMagFilter::NEAREST);
EXPECT_IMAGE(dstRenderTarget, getExpectations(),
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 0xebfac2ef));
{
ImageExpectations expectations(api);
EXPECT_IMAGE(dstRenderTarget, expectations,
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 0xebfac2ef));
flushAndWait();
}
}
TEST_F(BlitTest, Blit2DTextureArray) {
@@ -405,11 +423,17 @@ TEST_F(BlitTest, Blit2DTextureArray) {
}
{
RenderFrame frame(api);
EXPECT_IMAGE(dstRenderTarget, getExpectations(),
ScreenshotParams(kDstTexWidth, kDstTexHeight, "Blit2DTextureArray",
0x8de7d55b));
api.commit(swapChain);
ImageExpectations expectations(api);
{
RenderFrame frame(api);
EXPECT_IMAGE(dstRenderTarget, expectations,
ScreenshotParams(kDstTexWidth, kDstTexHeight, "Blit2DTextureArray",
0x8de7d55b));
api.commit(swapChain);
}
flushAndWait();
}
}
@@ -479,12 +503,18 @@ TEST_F(BlitTest, BlitRegion) {
}
{
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.
// EXPECT_IMAGE(dstRenderTarget, expectations, ScreenshotParams(kDstTexWidth,
// kDstTexHeight, "BlitRegion", 0x74fa34ed));
api.commit(swapChain);
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.
// EXPECT_IMAGE(dstRenderTarget, expectations, ScreenshotParams(kDstTexWidth,
// kDstTexHeight, "BlitRegion", 0x74fa34ed));
api.commit(swapChain);
}
flushAndWait();
}
}
@@ -537,19 +567,25 @@ TEST_F(BlitTest, BlitRegionToSwapChain) {
};
{
RenderFrame frame(api);
ImageExpectations expectations(api);
{
{
RenderFrame frame(api);
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
dstRect, srcRenderTargets[srcLevel],
srcRect, SamplerMagFilter::LINEAR);
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
dstRect, srcRenderTargets[srcLevel],
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

View File

@@ -16,7 +16,6 @@
#include "BackendTest.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
#include "Shader.h"
#include "SharedShaders.h"
@@ -221,9 +220,9 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
renderTarget, swapChain, shader.getProgram(), params);
EXPECT_IMAGE(renderTarget, getExpectations(),
ScreenshotParams(512, 512, "BufferObjectUpdateWithOffset", 91322442));
static const uint32_t expectedHash = 91322442;
readPixelsAndAssertHash(
"BufferObjectUpdateWithOffset", 512, 512, renderTarget, expectedHash, true);
api.flush();
api.commit(swapChain);

View File

@@ -17,7 +17,6 @@
#include "BackendTest.h"
#include "Lifetimes.h"
#include "Skip.h"
using namespace filament;
using namespace filament::backend;
@@ -25,8 +24,6 @@ using namespace filament::backend;
namespace test {
TEST_F(BackendTest, FrameScheduledCallback) {
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL));
auto& api = getDriverApi();
Cleanup cleanup(api);
@@ -84,8 +81,6 @@ TEST_F(BackendTest, FrameScheduledCallback) {
}
TEST_F(BackendTest, FrameCompletedCallback) {
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL));
auto& api = getDriverApi();
Cleanup cleanup(api);

View File

@@ -71,6 +71,8 @@ void main() {
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.
// Note that Filament uses a higher precision format (R11F_G11F_B10F) but this does not seem
// necessary to trigger the bug.
@@ -94,6 +96,25 @@ struct MaterialParams {
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.
// 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.
@@ -177,12 +198,14 @@ TEST_F(BackendTest, FeedbackLoops) {
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
// Each pass needs its own descriptor set.
auto descriptorSet = shader.createDescriptorSet(api);
auto textureView = passCleanup.add(api.createTextureView(texture, sourceLevel, 1));
api.updateDescriptorSetTexture(descriptorSet, 0, textureView, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
shader.updateTextureUniform(api,
TextureBindingConfig{ .textureHandle = textureView,
.descriptorSet = descriptorSet,
.samplerParams = SamplerParams{ .filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST } });
UniformBindingConfig uniformBinding{
.binding = 1,
@@ -238,8 +261,7 @@ TEST_F(BackendTest, FeedbackLoops) {
// NOTE: Calling glReadPixels on any miplevel other than the base level
// seems to be un-reliable on some GPU's.
if (frame == kNumFrames - 1) {
EXPECT_IMAGE(renderTargets[0], getExpectations(),
ScreenshotParams(kTexWidth, kTexHeight, "FeedbackLoops", 0x70695aa1));
dumpScreenshot(api, renderTargets[0]);
}
api.flush();
@@ -250,6 +272,10 @@ TEST_F(BackendTest, FeedbackLoops) {
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

View File

@@ -246,42 +246,42 @@ TEST_F(LoadImageTest, UpdateImage2D) {
std::vector<TestCase> testCases;
// Test basic upload.
testCases.emplace_back("RGBA UBYTE to RGBA8", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8);
testCases.emplace_back("RGBA, UBYTE -> RGBA8", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8);
// Test format conversion.
// TODO: Vulkan crashes with `Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)'
testCases.emplace_back("RGBA FLOAT to RGBA16F", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F);
testCases.emplace_back("RGBA, FLOAT -> RGBA16F", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F);
// Test texture formats not all backends support natively.
// TODO: Vulkan crashes with "VK_FORMAT_R32G32B32_SFLOAT is not supported"
testCases.emplace_back("RGB FLOAT to RGB32F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F);
testCases.emplace_back("RGB FLOAT to RGB16F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB16F);
testCases.emplace_back("RGB, FLOAT -> RGB32F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F);
testCases.emplace_back("RGB, FLOAT -> RGB16F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB16F);
// Test packed format uploads.
// TODO: Vulkan crashes with "Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)"
testCases.emplace_back("RGBA UINT_2_10_10_10_REV to RGB10_A2", PixelDataFormat::RGBA, PixelDataType::UINT_2_10_10_10_REV, TextureFormat::RGB10_A2);
testCases.emplace_back("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 to R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::HALF, TextureFormat::R11F_G11F_B10F);
testCases.emplace_back("RGBA, UINT_2_10_10_10_REV -> RGB10_A2", PixelDataFormat::RGBA, PixelDataType::UINT_2_10_10_10_REV, TextureFormat::RGB10_A2);
testCases.emplace_back("RGB, UINT_10F_11F_11F_REV -> 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);
// Test integer format uploads.
// TODO: These cases fail on OpenGL and Vulkan.
// TODO: These cases now also fail on Metal, but at some point previously worked.
testCases.emplace_back("RGB_INTEGER UBYTE to RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
testCases.emplace_back("RGB_INTEGER USHORT to RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
testCases.emplace_back("RGB_INTEGER INT to RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
testCases.emplace_back("RGB_INTEGER, UBYTE -> 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, INT -> RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
// Test uploads with buffer padding.
// TODO: Vulkan crashes with "Assertion failed: (offset + size <= allocationSize)"
testCases.emplace_back("RGBA UBYTE to RGBA8 (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u);
testCases.emplace_back("RGBA FLOAT to RGBA16F (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F, 64u);
testCases.emplace_back("RGB FLOAT to RGB32F (with buffer padding)", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F, 64u);
testCases.emplace_back("RGBA, UBYTE -> 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("RGB, FLOAT -> RGB32F (with buffer padding)", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F, 64u);
// Upload subregions separately.
// TODO: Vulkan crashes with "Offsets not yet supported"
testCases.emplace_back("RGBA UBYTE to RGBA8 (subregions)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 0u, true);
testCases.emplace_back("RGBA FLOAT to RGBA16F (subregions)", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F, 0u, true);
testCases.emplace_back("RGBA UBYTE to RGBA8 (subregions and buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u, true);
testCases.emplace_back("RGB FLOAT to RGB32F (subregions and buffer padding)", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F, 64u, true);
testCases.emplace_back("RGBA, UBYTE -> 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, UBYTE -> RGBA8 (subregions, 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);
auto& api = getDriverApi();
@@ -329,24 +329,28 @@ TEST_F(LoadImageTest, UpdateImage2D) {
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 512, t.bufferPadding));
}
// Each loop needs its own descriptor set.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
api.bindDescriptorSet(descriptorSet, 1, {});
shader.updateTextureUniform(api,
TextureBindingConfig{ .textureHandle = texture,
.descriptorSet = descriptorSet,
.samplerParams = SamplerParams{ .filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST },
.alsoBindToSet = 1 });
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
defaultRenderTarget, swapChain, shader.getProgram());
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
ScreenshotParams(512, 512, t.name, expectedHash));
readPixelsAndAssertHash(t.name, 512, 512, defaultRenderTarget, expectedHash);
api.commit(swapChain);
api.endFrame(0);
}
api.finish();
api.stopCapture();
flushAndWait();
}
TEST_F(LoadImageTest, UpdateImageSRGB) {
@@ -370,7 +374,7 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
getSamplerTypeName(textureFormat), fragmentTemplate);
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = mVertexShader, .fragmentShader = fragment, .uniforms = {{
"test_tex", DescriptorType::SAMPLER, samplerInfo
"text_tex", DescriptorType::SAMPLER, samplerInfo
}}});
// Create a texture.
@@ -403,23 +407,24 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
api.beginFrame(0, 0, 0);
// Update samplers.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
api.bindDescriptorSet(descriptorSet, 1, {});
shader.updateTextureUniform(api,
TextureBindingConfig{ .textureHandle = texture,
.samplerParams = SamplerParams{ .filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST },
.alsoBindToSet = 1 });
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
defaultRenderTarget, swapChain, shader.getProgram());
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
ScreenshotParams(512, 512, "UpdateImageSRGB", 359858623));
static const uint32_t expectedHash = 359858623;
readPixelsAndAssertHash("UpdateImageSRGB", 512, 512, defaultRenderTarget, expectedHash);
api.flush();
api.commit(swapChain);
api.endFrame(0);
// This ensures all driver commands have finished before exiting the test.
api.finish();
api.stopCapture();
}
@@ -462,23 +467,24 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
api.beginFrame(0, 0, 0);
// Update samplers.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
api.bindDescriptorSet(descriptorSet, 1, {});
shader.updateTextureUniform(api,
TextureBindingConfig{ .textureHandle = texture,
.samplerParams = SamplerParams{ .filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST },
.alsoBindToSet = 1 });
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
defaultRenderTarget, swapChain, shader.getProgram());
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
ScreenshotParams(512, 512, "UpdateImageMipLevel", 3644679986));
static const uint32_t expectedHash = 3644679986;
readPixelsAndAssertHash("UpdateImageMipLevel", 512, 512, defaultRenderTarget, expectedHash);
api.flush();
api.commit(swapChain);
api.endFrame(0);
// This ensures all driver commands have finished before exiting the test.
api.finish();
api.stopCapture();
}
@@ -530,24 +536,27 @@ TEST_F(LoadImageTest, UpdateImage3D) {
api.update3DImage(texture, 0, 0, 0, 0, 512, 512, 4, std::move(descriptor));
{
RenderFrame frame(api);
api.beginFrame(0, 0, 0);
// Update samplers.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture,
{ .filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST });
// Update samplers.
shader.updateTextureUniform(api,
TextureBindingConfig{ .textureHandle = texture,
.samplerParams = SamplerParams{ .filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST },
.alsoBindToSet = 1 });
api.bindDescriptorSet(descriptorSet, 1, {});
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
defaultRenderTarget, swapChain, shader.getProgram());
renderTriangle({ { DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() } },
defaultRenderTarget, swapChain, shader.getProgram());
static const uint32_t expectedHash = 3644679986;
readPixelsAndAssertHash("UpdateImage3D", 512, 512, defaultRenderTarget, expectedHash);
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
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();
}

View File

@@ -171,11 +171,12 @@ TEST_F(BackendTest, TextureViewLod) {
state.rasterState.culling = CullingMode::NONE;
DescriptorSetHandle descriptorSet13 = texturedShader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet13, 0, texture13, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
api.bindDescriptorSet(descriptorSet13, 0, {});
texturedShader.updateTextureUniform(api,
TextureBindingConfig{ .textureHandle = texture13,
.descriptorSet = descriptorSet13,
.samplerParams = SamplerParams{ .filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST },
.alsoBindToSet = 0 });
// Render a triangle to the screen, sampling from mip level 1.
// Because the min level is 1, the result color should be the white triangle drawn in the
@@ -189,11 +190,12 @@ TEST_F(BackendTest, TextureViewLod) {
auto texture22 = cleanup.add(api.createTextureView(texture, 2, 2));
DescriptorSetHandle descriptorSet22 = texturedShader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet22, 0, texture22, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
api.bindDescriptorSet(descriptorSet22, 0, {});
texturedShader.updateTextureUniform(api,
TextureBindingConfig{ .textureHandle = texture22,
.descriptorSet = descriptorSet22,
.samplerParams = SamplerParams{ .filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST },
.alsoBindToSet = 0 });
// Render a second, smaller, triangle, again sampling from mip level 1.
// This triangle should be yellow striped.

View File

@@ -16,10 +16,8 @@
#include "BackendTest.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "Skip.h"
#include "TrianglePrimitive.h"
#include <utils/Hash.h>
@@ -80,8 +78,6 @@ void main() {
})";
TEST_F(BackendTest, PushConstants) {
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL));
auto& api = getDriverApi();
api.startCapture(0);
@@ -152,14 +148,19 @@ TEST_F(BackendTest, PushConstants) {
api.endRenderPass();
EXPECT_IMAGE(renderTarget, getExpectations(),
ScreenshotParams(512, 512, "pushConstants", 1957275826));
readPixelsAndAssertHash("pushConstants", 512, 512, renderTarget, 1957275826, true);
api.commit(swapChain);
api.endFrame(0);
}
api.stopCapture(0);
// Wait for the ReadPixels result to come back.
api.finish();
executeCommands();
getDriver().purge();
}
} // namespace test

View File

@@ -16,7 +16,6 @@
#include "BackendTest.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
#include "Shader.h"
#include "SharedShaders.h"
@@ -85,14 +84,12 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
state.rasterState.depthFunc = RasterState::DepthFunc::A;
state.rasterState.culling = CullingMode::NONE;
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.startCapture(0);
api.makeCurrent(swapChain, swapChain);
api.beginFrame(0, 0, 0);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {});
api.bindDescriptorSet(descriptorSet, 1, {});
shader.updateTextureUniform(api,
TextureBindingConfig{ .textureHandle = texture, .alsoBindToSet = 1 });
// Render a triangle.
api.beginRenderPass(defaultRenderTarget, params);
@@ -201,16 +198,17 @@ TEST_F(BackendTest, RenderExternalImage) {
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
readPixelsAndAssertHash("RenderExternalImage", 512, 512, defaultRenderTarget, 267229901, true);
api.flush();
api.commit(swapChain);
api.endFrame(0);
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
ScreenshotParams(512, 512, "RenderExternalImage", 267229901));
api.stopCapture(0);
api.finish();
flushAndWait();
api.finish();
executeCommands();
}
} // namespace test

View File

@@ -16,7 +16,6 @@
#include "BackendTest.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
#include "Shader.h"
#include "SharedShaders.h"
@@ -131,14 +130,20 @@ TEST_F(BackendTest, ScissorViewportRegion) {
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
EXPECT_IMAGE(fullRenderTarget, getExpectations(),
ScreenshotParams(kSrcTexWidth >> 1, kSrcTexHeight >> 1, "scissor", 0xAB3D1C53));
readPixelsAndAssertHash("scissor", kSrcTexWidth >> 1, kSrcTexHeight >> 1, fullRenderTarget,
0xAB3D1C53, true);
api.commit(swapChain);
api.endFrame(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.
@@ -221,14 +226,20 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
EXPECT_IMAGE(renderTarget, getExpectations(),
ScreenshotParams(512, 512, "ScissorViewportEdgeCases", 0x6BF00F31));
readPixelsAndAssertHash(
"ScissorViewportEdgeCases", 512, 512, renderTarget, 0x6BF00F31, true);
api.commit(swapChain);
api.endFrame(0);
api.stopCapture(0);
}
// Wait for the ReadPixels result to come back.
api.finish();
executeCommands();
getDriver().purge();
}
} // namespace test

View File

@@ -16,7 +16,6 @@
#include "BackendTest.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
#include "Shader.h"
#include "SharedShaders.h"
@@ -130,8 +129,7 @@ TEST_F(BasicStencilBufferTest, StencilBuffer) {
RunTest(renderTarget);
EXPECT_IMAGE(renderTarget, getExpectations(),
ScreenshotParams(512, 512, "StencilBuffer", 0x3B1AEF0F));
readPixelsAndAssertHash("StencilBuffer", 512, 512, renderTarget, 0x3B1AEF0F, true);
flushAndWait();
getDriver().purge();
@@ -153,8 +151,7 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
RunTest(renderTarget);
EXPECT_IMAGE(renderTarget, getExpectations(),
ScreenshotParams(512, 512, "DepthAndStencilBuffer", 0x3B1AEF0F));
readPixelsAndAssertHash("DepthAndStencilBuffer", 512, 512, renderTarget, 0x3B1AEF0F, true);
flushAndWait();
getDriver().purge();
@@ -236,8 +233,7 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
api.stopCapture(0);
api.endFrame(0);
EXPECT_IMAGE(renderTarget1, getExpectations(),
ScreenshotParams(512, 512, "StencilBufferAutoResolve", 0x6CEFAC8F));
readPixelsAndAssertHash("StencilBufferAutoResolve", 512, 512, renderTarget1, 0x6CEFAC8F, true);
flushAndWait();
getDriver().purge();

View File

@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.59.3"
spec.version = "1.59.1"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.59.3/filament-v1.59.3-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.59.1/filament-v1.59.1-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {

View File

@@ -50,8 +50,6 @@
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/NameComponentManager.h>
#define SYSTRACE_TAG SYSTRACE_TAG_GLTFIO
#include <utils/Systrace.h>
#include <tsl/robin_map.h>

View File

@@ -37,10 +37,7 @@
#include <utils/compiler.h>
#include <utils/JobSystem.h>
#include <utils/Log.h>
#define SYSTRACE_TAG SYSTRACE_TAG_GLTFIO
#include <utils/Systrace.h>
#include <utils/Path.h>
#include <cgltf.h>

View File

@@ -21,8 +21,6 @@
#include "GltfEnums.h"
#include <utils/Log.h>
#define SYSTRACE_TAG SYSTRACE_TAG_GLTFIO
#include <utils/Systrace.h>
#define CGLTF_IMPLEMENTATION

View File

@@ -112,7 +112,6 @@ if (ANDROID)
target_link_libraries(${TARGET} PUBLIC log)
target_link_libraries(${TARGET} PRIVATE dl)
target_link_libraries(${TARGET} PUBLIC android)
target_link_libraries(${TARGET} PUBLIC perfetto)
endif()
if (WIN32)

View File

@@ -17,23 +17,24 @@
#ifndef TNT_UTILS_SYSTRACE_H
#define TNT_UTILS_SYSTRACE_H
#define SYSTRACE_TAG_DISABLED (0)
#define SYSTRACE_TAG_FILAMENT (2) // don't change used in makefiles
#define SYSTRACE_TAG_JOBSYSTEM (3)
#define SYSTRACE_TAG_GLTFIO (4)
#define SYSTRACE_TAG_NEVER (0)
#define SYSTRACE_TAG_ALWAYS (1<<0)
#define SYSTRACE_TAG_FILAMENT (1<<1) // don't change, used in makefiles
#define SYSTRACE_TAG_JOBSYSTEM (1<<2)
/*
* The SYSTRACE_ macros use SYSTRACE_TAG as a category, which must be defined
* before this file is included.
* The SYSTRACE_ macros use SYSTRACE_TAG as a the TAG, which should be defined
* before this file is included. If not, the SYSTRACE_TAG_ALWAYS tag will be used.
*/
#ifndef SYSTRACE_TAG
# error SYSTRACE_TAG must be set to SYSTRACE_TAG_{DISABLED|FILAMENT|JOBSYSTEM}
#define SYSTRACE_TAG (SYSTRACE_TAG_ALWAYS)
#endif
// Systrace on Apple platforms is fragile and adds overhead, should only be enabled in dev builds.
#ifndef FILAMENT_APPLE_SYSTRACE
# define FILAMENT_APPLE_SYSTRACE 0
#define FILAMENT_APPLE_SYSTRACE 0
#endif
#if defined(__ANDROID__)
@@ -43,6 +44,7 @@
#else
#define SYSTRACE_ENABLE()
#define SYSTRACE_DISABLE()
#define SYSTRACE_CONTEXT()
#define SYSTRACE_NAME(name)
#define SYSTRACE_FRAME_ID(frame)
@@ -54,6 +56,6 @@
#define SYSTRACE_VALUE32(name, val)
#define SYSTRACE_VALUE64(name, val)
#endif
#endif // ANDROID
#endif // TNT_UTILS_SYSTRACE_H

View File

@@ -17,75 +17,226 @@
#ifndef TNT_UTILS_ANDROID_SYSTRACE_H
#define TNT_UTILS_ANDROID_SYSTRACE_H
#include <perfetto/perfetto.h>
#include <atomic>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
PERFETTO_DEFINE_CATEGORIES_IN_NAMESPACE(systrace,
perfetto::Category("filament"),
perfetto::Category("jobsystem"),
perfetto::Category("gltfio"));
#include <utils/compiler.h>
PERFETTO_USE_CATEGORIES_FROM_NAMESPACE(systrace);
// enable tracing
#define SYSTRACE_ENABLE() ::utils::details::Systrace::enable(SYSTRACE_TAG)
#if SYSTRACE_TAG == SYSTRACE_TAG_FILAMENT
# 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
// disable tracing
#define SYSTRACE_DISABLE() ::utils::details::Systrace::disable(SYSTRACE_TAG)
#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)
/**
* Creates a Systrace context in the current scope. needed for calling all other systrace
* commands below.
*/
#define SYSTRACE_CONTEXT() ::utils::details::Systrace ___trctx(SYSTRACE_TAG)
#else
#define SYSTRACE_ENABLE()
#define SYSTRACE_CONTEXT()
#define SYSTRACE_CALL() \
auto constexpr FILAMENT_SYSTRACE_FUNCTION = perfetto::StaticString(__FUNCTION__); \
TRACE_EVENT(UTILS_PERFETTO_CATEGORY, FILAMENT_SYSTRACE_FUNCTION)
#define SYSTRACE_NAME(name) TRACE_EVENT(UTILS_PERFETTO_CATEGORY, nullptr, \
[&](perfetto::EventContext ctx) { \
ctx.event()->set_name(name); \
})
#define SYSTRACE_NAME_BEGIN(name) TRACE_EVENT_BEGIN(UTILS_PERFETTO_CATEGORY, nullptr, \
[&](perfetto::EventContext ctx) { \
ctx.event()->set_name(name); \
})
#define SYSTRACE_NAME_END() TRACE_EVENT_END(UTILS_PERFETTO_CATEGORY)
#define SYSTRACE_ASYNC_BEGIN(name, cookie) \
TRACE_EVENT_BEGIN(UTILS_PERFETTO_CATEGORY, name, perfetto::Track(cookie))
#define SYSTRACE_ASYNC_END(name, cookie) \
TRACE_EVENT_END(UTILS_PERFETTO_CATEGORY, perfetto::Track(cookie))
// SYSTRACE_NAME traces the beginning and end of the current scope. To trace
// the correct start and end times this macro should be declared first in the
// 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_FRAME_ID(frame) \
TRACE_EVENT_INSTANT(UTILS_PERFETTO_CATEGORY, "frame", "id", frame)
{ /* scope for frame id trace */ \
char buf[64]; \
snprintf(buf, 64, "frame %u", frame); \
SYSTRACE_NAME(buf); \
}
// SYSTRACE_CALL is an SYSTRACE_NAME that uses the current function name.
#define SYSTRACE_CALL() SYSTRACE_NAME(__FUNCTION__)
#define SYSTRACE_NAME_BEGIN(name) \
___trctx.traceBegin(SYSTRACE_TAG, name)
#define SYSTRACE_NAME_END() \
___trctx.traceEnd(SYSTRACE_TAG)
/**
* Trace the beginning of an asynchronous event. Unlike ATRACE_BEGIN/ATRACE_END
* contexts, asynchronous events do not need to be nested. The name describes
* the event, and the cookie provides a unique identifier for distinguishing
* simultaneous events. The name and cookie used to begin an event must be
* used to end it.
*/
#define SYSTRACE_ASYNC_BEGIN(name, cookie) \
___trctx.asyncBegin(SYSTRACE_TAG, name, cookie)
/**
* Trace the end of an asynchronous event.
* This should have a corresponding SYSTRACE_ASYNC_BEGIN.
*/
#define SYSTRACE_ASYNC_END(name, cookie) \
___trctx.asyncEnd(SYSTRACE_TAG, name, cookie)
/**
* Traces an integer counter value. name is used to identify the counter.
* This can be used to track how a value changes over time.
*/
#define SYSTRACE_VALUE32(name, val) \
TRACE_COUNTER(UTILS_PERFETTO_CATEGORY, name, val)
___trctx.value(SYSTRACE_TAG, name, int32_t(val))
#define SYSTRACE_VALUE64(name, val) \
TRACE_COUNTER(UTILS_PERFETTO_CATEGORY, name, val)
___trctx.value(SYSTRACE_TAG, name, int64_t(val))
#endif // SYSTRACE_TAG == SYSTRACE_TAG_DISABLED
// ------------------------------------------------------------------------------------------------
// No user serviceable code below...
// ------------------------------------------------------------------------------------------------
namespace utils {
namespace details {
class UTILS_PUBLIC Systrace {
public:
enum tags {
NEVER = SYSTRACE_TAG_NEVER,
ALWAYS = SYSTRACE_TAG_ALWAYS,
FILAMENT = SYSTRACE_TAG_FILAMENT,
JOBSYSTEM = SYSTRACE_TAG_JOBSYSTEM
// we could define more TAGS here, as we need them.
};
explicit Systrace(uint32_t tag) noexcept {
if (tag) init(tag);
}
static void enable(uint32_t tags) noexcept;
static void disable(uint32_t tags) noexcept;
inline void traceBegin(uint32_t tag, const char* name) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
beginSection(this, name);
}
}
inline void traceEnd(uint32_t tag) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
endSection(this);
}
}
inline void asyncBegin(uint32_t tag, const char* name, int32_t cookie) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
beginAsyncSection(this, name, cookie);
}
}
inline void asyncEnd(uint32_t tag, const char* name, int32_t cookie) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
endAsyncSection(this, name, cookie);
}
}
inline void value(uint32_t tag, const char* name, int32_t value) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
setCounter(this, name, value);
}
}
inline void value(uint32_t tag, const char* name, int64_t value) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
setCounter(this, name, value);
}
}
private:
friend class ScopedTrace;
// whether tracing is supported at all by the platform
using ATrace_isEnabled_t = bool (*)();
using ATrace_beginSection_t = void (*)(const char* sectionName);
using ATrace_endSection_t = void (*)();
using ATrace_beginAsyncSection_t = void (*)(const char* sectionName, int32_t cookie);
using ATrace_endAsyncSection_t = void (*)(const char* sectionName, int32_t cookie);
using ATrace_setCounter_t = void (*)(const char* counterName, int64_t counterValue);
struct GlobalState {
bool isTracingAvailable;
std::atomic<uint32_t> isTracingEnabled;
int markerFd;
ATrace_isEnabled_t ATrace_isEnabled;
ATrace_beginSection_t ATrace_beginSection;
ATrace_endSection_t ATrace_endSection;
ATrace_beginAsyncSection_t ATrace_beginAsyncSection;
ATrace_endAsyncSection_t ATrace_endAsyncSection;
ATrace_setCounter_t ATrace_setCounter;
void (*beginSection)(Systrace* that, const char* name);
void (*endSection)(Systrace* that);
void (*beginAsyncSection)(Systrace* that, const char* name, int32_t cookie);
void (*endAsyncSection)(Systrace* that, const char* name, int32_t cookie);
void (*setCounter)(Systrace* that, const char* name, int64_t value);
};
static GlobalState sGlobalState;
// per-instance versions for better performance
ATrace_isEnabled_t ATrace_isEnabled;
ATrace_beginSection_t ATrace_beginSection;
ATrace_endSection_t ATrace_endSection;
ATrace_beginAsyncSection_t ATrace_beginAsyncSection;
ATrace_endAsyncSection_t ATrace_endAsyncSection;
ATrace_setCounter_t ATrace_setCounter;
void (*beginSection)(Systrace* that, const char* name);
void (*endSection)(Systrace* that);
void (*beginAsyncSection)(Systrace* that, const char* name, int32_t cookie);
void (*endAsyncSection)(Systrace* that, const char* name, int32_t cookie);
void (*setCounter)(Systrace* that, const char* name, int64_t value);
void init(uint32_t tag) noexcept;
// cached values for faster access, no need to be initialized
bool mIsTracingEnabled;
int mMarkerFd = -1;
pid_t mPid;
static void setup() noexcept;
static void init_once() noexcept;
static bool isTracingEnabled(uint32_t tag) noexcept;
static void begin_body(int fd, int pid, const char* name) noexcept;
static void end_body(int fd, int pid) noexcept;
static void async_begin_body(int fd, int pid, const char* name, int32_t cookie) noexcept;
static void async_end_body(int fd, int pid, const char* name, int32_t cookie) noexcept;
static void int64_body(int fd, int pid, const char* name, int64_t value) noexcept;
};
// ------------------------------------------------------------------------------------------------
class UTILS_PUBLIC ScopedTrace {
public:
// we don't inline this because it's relatively heavy due to a global check
ScopedTrace(uint32_t tag, const char* name) noexcept: mTrace(tag), mTag(tag) {
mTrace.traceBegin(tag, name);
}
inline ~ScopedTrace() noexcept {
mTrace.traceEnd(mTag);
}
private:
Systrace mTrace;
const uint32_t mTag;
};
} // namespace details
} // namespace utils
#endif // TNT_UTILS_ANDROID_SYSTRACE_H

View File

@@ -29,25 +29,13 @@
#include <utils/compiler.h>
#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
#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
* commands below.
@@ -105,8 +93,6 @@ extern thread_local std::stack<const char*> ___tracerSections;
#define SYSTRACE_VALUE64(name, val) \
___tracer.value(SYSTRACE_TAG, name, int64_t(val))
#endif // SYSTRACE_TAG == SYSTRACE_TAG_DISABLED
// ------------------------------------------------------------------------------------------------
// No user serviceable code below...
// ------------------------------------------------------------------------------------------------
@@ -132,40 +118,50 @@ namespace utils {
namespace details {
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 {
if (tag) init(tag);
}
static void enable(uint32_t tag) noexcept;
static void enable(uint32_t tags) noexcept;
static void disable(uint32_t tags) noexcept;
void traceBegin(uint32_t tag, const char* name) noexcept {
inline void traceBegin(uint32_t tag, const char* name) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
APPLE_SIGNPOST_EMIT(sGlobalState.systraceLog, OS_SIGNPOST_INTERVAL_BEGIN,
OS_SIGNPOST_ID_EXCLUSIVE, name, name)
}
}
void traceEnd(uint32_t tag, const char* name) noexcept {
inline void traceEnd(uint32_t tag, const char* name) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
APPLE_SIGNPOST_EMIT(sGlobalState.systraceLog, OS_SIGNPOST_INTERVAL_END,
OS_SIGNPOST_ID_EXCLUSIVE, name, "")
}
}
void asyncBegin(uint32_t tag, const char* name, int32_t cookie) noexcept {
inline void asyncBegin(uint32_t tag, const char* name, int32_t cookie) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
// TODO
}
}
void asyncEnd(uint32_t tag, const char* name, int32_t cookie) noexcept {
inline void asyncEnd(uint32_t tag, const char* name, int32_t cookie) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
// TODO
}
}
void value(uint32_t tag, const char* name, int32_t value) noexcept {
inline void value(uint32_t tag, const char* name, int32_t value) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
char buf[64];
snprintf(buf, 64, "%s - %d", name, value);
@@ -174,7 +170,7 @@ public:
}
}
void value(uint32_t tag, const char* name, int64_t value) noexcept {
inline void value(uint32_t tag, const char* name, int64_t value) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
char buf[64];
snprintf(buf, 64, "%s - %lld", name, value);
@@ -183,16 +179,16 @@ public:
}
}
void frameId(uint32_t tag, uint32_t frame) noexcept {
inline void frameId(uint32_t tag, uint32_t frame) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
char buf[64];
snprintf(buf, 64, "frame %u", frame);
char buf[64]; \
snprintf(buf, 64, "frame %u", frame); \
APPLE_SIGNPOST_EMIT(sGlobalState.frameIdLog, OS_SIGNPOST_EVENT,
OS_SIGNPOST_ID_EXCLUSIVE, "frame", buf)
}
}
private:
private:
friend class ScopedTrace;
struct GlobalState {
@@ -217,25 +213,25 @@ private:
// ------------------------------------------------------------------------------------------------
class ScopedTrace {
public:
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), mName(name), mTag(tag) {
mTrace.traceBegin(tag, name);
}
~ScopedTrace() noexcept {
inline ~ScopedTrace() noexcept {
mTrace.traceEnd(mTag, mName);
}
void value(uint32_t tag, const char* name, int32_t v) noexcept {
inline void value(uint32_t tag, const char* name, int32_t v) noexcept {
mTrace.value(tag, name, v);
}
void value(uint32_t tag, const char* name, int64_t v) noexcept {
inline void value(uint32_t tag, const char* name, int64_t v) noexcept {
mTrace.value(tag, name, v);
}
private:
private:
Systrace mTrace;
const char* mName;
const uint32_t mTag;

View File

@@ -15,8 +15,10 @@
*/
// Note: The overhead of SYSTRACE_TAG_JOBSYSTEM is not negligible especially with parallel_for().
#define SYSTRACE_TAG SYSTRACE_TAG_DISABLED
#ifndef SYSTRACE_TAG
//#define SYSTRACE_TAG SYSTRACE_TAG_JOBSYSTEM
#define SYSTRACE_TAG SYSTRACE_TAG_NEVER
#endif
// when SYSTRACE_TAG_JOBSYSTEM is used, enables even heavier systraces
#define HEAVY_SYSTRACE 0

View File

@@ -14,26 +14,206 @@
* limitations under the License.
*/
#include <utils/compiler.h>
#include <utils/android/Systrace.h>
#include <utils/Systrace.h>
#include <utils/Log.h>
#include <perfetto/perfetto.h>
#include <cinttypes>
PERFETTO_TRACK_EVENT_STATIC_STORAGE_IN_NAMESPACE(systrace);
#include <string.h>
namespace {
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <dlfcn.h>
class SystraceStaticInitialization {
public:
SystraceStaticInitialization() {
perfetto::TracingInitArgs args;
args.backends |= perfetto::kSystemBackend;
perfetto::Tracing::Initialize(args);
systrace::TrackEvent::Register();
}
};
namespace utils {
namespace details {
UTILS_UNUSED SystraceStaticInitialization sSystraceStaticInitialization{};
static pthread_once_t atrace_once_control = PTHREAD_ONCE_INIT;
template <typename T>
static void loadSymbol(T*& pfn, const char *symbol) noexcept {
pfn = (T*)dlsym(RTLD_DEFAULT, symbol);
}
Systrace::GlobalState Systrace::sGlobalState = {};
void Systrace::init_once() noexcept {
GlobalState& s = sGlobalState;
s.markerFd = -1;
// API 23
loadSymbol(s.ATrace_isEnabled, "ATrace_isEnabled");
loadSymbol(s.ATrace_beginSection, "ATrace_beginSection");
loadSymbol(s.ATrace_endSection, "ATrace_endSection");
// API 29
loadSymbol(s.ATrace_beginAsyncSection, "ATrace_beginAsyncSection");
loadSymbol(s.ATrace_endAsyncSection, "ATrace_endAsyncSection");
loadSymbol(s.ATrace_setCounter, "ATrace_setCounter");
const bool hasBasicAtrace = s.ATrace_isEnabled &&
s.ATrace_beginSection &&
s.ATrace_endSection;
const bool hasFullATrace = hasBasicAtrace &&
s.ATrace_beginAsyncSection &&
s.ATrace_endAsyncSection &&
s.ATrace_setCounter;
if (!hasFullATrace) {
s.markerFd = open("/sys/kernel/debug/tracing/trace_marker", O_WRONLY | O_CLOEXEC);
}
if (hasBasicAtrace && !hasFullATrace) {
// no-op if we don't have all these
s.ATrace_beginAsyncSection = [](const char* sectionName, int32_t cookie){};
s.ATrace_endAsyncSection = [](const char* sectionName, int32_t cookie){};
s.ATrace_setCounter = [](const char* sectionName, int64_t counterValue){};
}
const bool hasLegacySystrace = s.markerFd != -1;
if (hasLegacySystrace && !hasFullATrace) {
// use legacy
s.beginSection = [](Systrace* that, const char* name) {
begin_body(that->mMarkerFd, that->mPid, name);
};
s.endSection = [](Systrace* that) {
end_body(that->mMarkerFd, that->mPid);
};
s.beginAsyncSection = [](Systrace* that, const char* name, int32_t cookie) {
async_begin_body(that->mMarkerFd, that->mPid, name, cookie);
};
s.endAsyncSection = [](Systrace* that, const char* name, int32_t cookie) {
async_end_body(that->mMarkerFd, that->mPid, name, cookie);
};
s.setCounter = [](Systrace* that, const char* name, int64_t value) {
int64_body(that->mMarkerFd, that->mPid, name, value);
};
} else if (hasBasicAtrace) {
// we have at least basic ATrace
s.beginSection = [](Systrace* that, const char* name) {
that->ATrace_beginSection(name);
};
s.endSection = [](Systrace* that) {
that->ATrace_endSection();
};
s.beginAsyncSection = [](Systrace* that, const char* name, int32_t cookie) {
that->ATrace_beginAsyncSection(name, cookie);
};
s.endAsyncSection = [](Systrace* that, const char* name, int32_t cookie) {
that->ATrace_endAsyncSection(name, cookie);
};
s.setCounter = [](Systrace* that, const char* name, int64_t value) {
that->ATrace_setCounter(name, value);
};
}
s.isTracingAvailable = hasLegacySystrace || hasFullATrace || hasBasicAtrace;
}
void Systrace::setup() noexcept {
pthread_once(&atrace_once_control, init_once);
}
void Systrace::enable(uint32_t tags) noexcept {
setup();
if (UTILS_LIKELY(sGlobalState.isTracingAvailable)) {
sGlobalState.isTracingEnabled.fetch_or(tags, std::memory_order_relaxed);
}
}
void Systrace::disable(uint32_t tags) noexcept {
sGlobalState.isTracingEnabled.fetch_and(~tags, std::memory_order_relaxed);
}
// unfortunately, this generates quite a bit of code because reading a global is not
// trivial. For this reason, we do not inline this method.
bool Systrace::isTracingEnabled(uint32_t tag) noexcept {
if (tag) {
setup();
return bool((sGlobalState.isTracingEnabled.load(std::memory_order_relaxed) | SYSTRACE_TAG_ALWAYS) & tag);
}
return false;
}
// ------------------------------------------------------------------------------------------------
void Systrace::init(uint32_t tag) noexcept {
// must be called first
mIsTracingEnabled = isTracingEnabled(tag);
// cache static variables for better efficiency
GlobalState& s = sGlobalState;
ATrace_isEnabled = s.ATrace_isEnabled;
ATrace_beginSection = s.ATrace_beginSection;
ATrace_endSection = s.ATrace_endSection;
ATrace_beginAsyncSection = s.ATrace_beginAsyncSection;
ATrace_endAsyncSection = s.ATrace_endAsyncSection;
ATrace_setCounter = s.ATrace_setCounter;
beginSection = s.beginSection;
endSection = s.endSection;
beginAsyncSection = s.beginAsyncSection;
endAsyncSection = s.endAsyncSection;
setCounter = s.setCounter;
mMarkerFd = s.markerFd;
mPid = getpid();
}
// ------------------------------------------------------------------------------------------------
/**
* Maximum size of a message that can be logged to the trace buffer.
* Note this message includes a tag, the pid, and the string given as the name.
* Names should be kept short to get the most use of the trace buffer.
*/
#define ATRACE_MESSAGE_LENGTH 512
#define WRITE_MSG(format_begin, format_end, pid, name, value) { \
char buf[ATRACE_MESSAGE_LENGTH]; \
int len = snprintf(buf, sizeof(buf), format_begin "%s" format_end, pid, \
name, value); \
if (len >= (int) sizeof(buf)) { \
/* Given the sizeof(buf), and all of the current format buffers, \
* it is impossible for name_len to be < 0 if len >= sizeof(buf). */ \
int name_len = strlen(name) - (len - sizeof(buf)) - 1; \
/* Truncate the name to make the message fit. */ \
len = snprintf(buf, sizeof(buf), format_begin "%.*s" format_end, pid, \
name_len, name, value); \
} \
write(fd, buf, len); \
}
void Systrace::begin_body(int fd, int pid, const char* name) noexcept {
char buf[ATRACE_MESSAGE_LENGTH];
ssize_t len = snprintf(buf, sizeof(buf), "B|%d|%s", pid, name);
if (len >= sizeof(buf)) {
len = sizeof(buf) - 1;
}
write(fd, buf, size_t(len));
}
void Systrace::end_body(int fd, int pid) noexcept {
const char END_TAG = 'E';
write(fd, &END_TAG, 1);
}
void Systrace::async_begin_body(int fd, int pid, const char* name, int32_t cookie) noexcept {
WRITE_MSG("S|%d|", "|%" PRId32, pid, name, cookie);
}
void Systrace::async_end_body(int fd, int pid, const char* name, int32_t cookie) noexcept {
WRITE_MSG("F|%d|", "|%" PRId32, pid, name, cookie);
}
void Systrace::int64_body(int fd, int pid, const char* name, int64_t value) noexcept {
WRITE_MSG("C|%d|", "|%" PRId64, pid, name, value);
}
} // namespace details
} // namespace utils

View File

@@ -14,17 +14,11 @@
* limitations under the License.
*/
#include <utils/darwin/Systrace.h>
#ifndef FILAMENT_APPLE_SYSTRACE
# define FILAMENT_APPLE_SYSTRACE 0
#endif
#include <utils/Systrace.h>
#include <utils/Log.h>
#if FILAMENT_APPLE_SYSTRACE
#include <atomic>
#include <stack>
#include <stdint.h>
#include <pthread.h>
static pthread_once_t atrace_once_control = PTHREAD_ONCE_INIT;
@@ -47,24 +41,21 @@ void Systrace::setup() noexcept {
pthread_once(&atrace_once_control, init_once);
}
void Systrace::enable(uint32_t tag) noexcept {
void Systrace::enable(uint32_t tags) noexcept {
setup();
uint32_t const mask = 1 << tag;
sGlobalState.isTracingEnabled.fetch_or(mask, std::memory_order_relaxed);
sGlobalState.isTracingEnabled.fetch_or(tags, std::memory_order_relaxed);
}
void Systrace::disable(uint32_t tag) noexcept {
uint32_t const mask = 1 << tag;
sGlobalState.isTracingEnabled.fetch_and(~mask, 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
// 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();
uint32_t const mask = 1 << tag;
return bool(sGlobalState.isTracingEnabled.load(std::memory_order_relaxed) & mask);
return bool((sGlobalState.isTracingEnabled.load(std::memory_order_relaxed) | SYSTRACE_TAG_ALWAYS) & tag);
}
return false;
}

View File

@@ -41,10 +41,10 @@ function prepare_mesa() {
# - Run the python script that runs the test
# - Zip up the result
set -ex && prepare_mesa && \
set -e && set -x && prepare_mesa && \
mkdir -p ${OUTPUT_DIR} && \
CXX=`which clang++` CC=`which clang` ./build.sh -X ${MESA_DIR} -p desktop debug gltf_viewer && \
python3 ${RENDERDIFF_TEST_DIR}/src/run.py \
./build.sh -X ${MESA_DIR} -p desktop debug gltf_viewer && \
python3 ${RENDERDIFF_TEST_DIR}/src/run.py \
--gltf_viewer="$(pwd)/out/cmake-debug/samples/gltf_viewer" \
--test=${RENDERDIFF_TEST_DIR}/tests/presubmit.json \
--output_dir=${OUTPUT_DIR} \

View File

@@ -14,34 +14,13 @@
#!/usr/bin/bash
set -x
if [[ "$GITHUB_WORKFLOW" ]]; then
set -e
fi
set -xe
# GITHUB_CLANG_VERSION is set in build/linux/ci-common.sh
os_name=$(uname -s)
LLVM_VERSION=16
MESA_VERSION=${MESA_VERSION-24.2.1}
ORIG_DIR=$(pwd)
MESA_DIR=${MESA_DIR-${ORIG_DIR}/mesa}
MESA_DIR=$(pwd)/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 [[ "$GITHUB_WORKFLOW" ]]; then
# We only want to do this if it is a CI machine.
@@ -65,16 +44,12 @@ if [[ "$os_name" == "Linux" ]]; then
set -e
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:-${LLVM_VERSION}}
sudo apt-get -y install clang-${GITHUB_CLANG_VERSION} \
libc++-${GITHUB_CLANG_VERSION}-dev \
libc++abi-${GITHUB_CLANG_VERSION}-dev \
llvm-${LLVM_VERSION} \
llvm-${LLVM_VERSION}-{dev,tools,runtime}
! 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" ]]
fi # [[ "$GITHUB_WORKFLOW" ]]
elif [[ "$os_name" == "Darwin" ]]; then
if [[ ! "$GITHUB_WORKFLOW" ]]; then
if [ ! command -v brew > /dev/null 2>&1 ]; then
@@ -82,7 +57,14 @@ elif [[ "$os_name" == "Darwin" ]]; then
exit 1
fi
fi
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=true brew install autoconf automake libx11 libxext libxrandr llvm@${LLVM_VERSION} ninja meson pkg-config libxshmfence
brew install autoconf automake libx11 libxext libxrandr llvm@${LLVM_VERSION} ninja meson pkg-config libxshmfence
NEEDED_PYTHON_DEPS=("mako" "setuptools")
for cmd in "${NEEDED_PYTHON_DEPS[@]}"; do
if ! pip3 show "${cmd}" >/dev/null 2>&1; then
sudo pip3 install ${cmd}
fi
done
fi # [[ "$os_name" == x ]]
LOCAL_LDFLAGS=${LDFLAGS}
@@ -106,8 +88,10 @@ fi
if [[ "$CHECKOUT_MESA" = "true" ]]; then
rm -rf ${MESA_DIR}
git clone https://gitlab.freedesktop.org/mesa/mesa.git
if [[ "${MESA_DIR}" != "${ORIG_DIR}/mesa" ]]; then
#git clone https://gitlab.freedesktop.org/mesa/mesa.git
git clone git://anongit.freedesktop.org/mesa/mesa
# Due to gitlab mesa outage.
if [[ "${MESA_DIR}" != "$(pwd)/mesa" ]]; then
mv mesa ${MESA_DIR}
fi
fi
@@ -116,12 +100,10 @@ pushd .
cd ${MESA_DIR}
# Need >= 24 to have llvmpipe for swrast. llvmpipe is needed for GL >= 4.1.
git checkout mesa-${MESA_VERSION}
git checkout mesa-24.2.1
mkdir -p out
source ${ORIG_DIR}/venv/bin/activate
if [[ "$os_name" == "Darwin" ]]; then
LOCAL_LDFLAGS="-L/opt/homebrew/opt/llvm@${LLVM_VERSION}/lib"
LOCAL_CPPFLAGS="-I/opt/homebrew/opt/llvm@${LLVM_VERSION}/include -I/opt/homebrew/include"
@@ -136,16 +118,8 @@ fi
# -Dvulkan-drivers=swrast => builds VK software rasterizer
# -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} \
meson setup --wipe builddir/ -Dprefix="${MESA_DIR}/out" -Dglx=xlib -Dosmesa=true -Dgallium-drivers=llvmpipe,swrast -Dvulkan-drivers=swrast
meson setup --wipe builddir/ -Dprefix="$(pwd)/out" -Dglx=xlib -Dosmesa=true -Dgallium-drivers=llvmpipe,swrast -Dvulkan-drivers=swrast
CXX=${LOCAL_CXX} CC=${LOCAL_CC} PATH=${LOCAL_PATH} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
meson install -C builddir/
# Disable python venv
deactivate
popd
if [[ "$GITHUB_WORKFLOW" ]]; then
set +e
fi
set +x

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,30 +0,0 @@
cmake_minimum_required(VERSION 3.19)
project(perfetto)
set(OUR_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..)
set(TARGET perfetto)
set(SRC_DIR ${OUR_DIR}/${TARGET})
set(PUBLIC_HDR_DIR ${OUR_DIR})
set(PUBLIC_HDRS
${PUBLIC_HDR_DIR}/${TARGET}/perfetto.h
)
add_library(${TARGET} STATIC ${PUBLIC_HDRS} ${SRC_DIR}/perfetto.cc)
if (WIN32)
# The perfetto library contains many symbols, so it needs the big object
# format.
target_compile_options(perfetto PRIVATE "/bigobj")
# Disable legacy features in windows.h.
add_definitions(-DWIN32_LEAN_AND_MEAN -DNOMINMAX)
# On Windows we should link to WinSock2.
target_link_libraries(${TARGET} ws2_32)
endif (WIN32)
# specify where the public headers of this library are
target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR})
set_target_properties(${TARGET} PROPERTIES FOLDER ThirdParty)
install(TARGETS ${TARGET} ARCHIVE DESTINATION lib/${DIST_DIR})

View File

@@ -1,8 +0,0 @@
Useful links:
https://perfetto.dev/
https://perfetto.dev/docs/instrumentation/tracing-sdk
Perfetto was fetched as:
git clone https://github.com/google/perfetto.git -b v50.1
Only the sdk/ directory is needed.

View File

@@ -1,6 +1,6 @@
{
"name": "filament",
"version": "1.59.3",
"version": "1.59.1",
"description": "Real-time physically based rendering engine",
"main": "filament.js",
"module": "filament.js",