Compare commits

...

8 Commits

Author SHA1 Message Date
bridgewaterrobbie
9a74936103 Update Gitignore to ignore generated files. 2025-04-22 14:23:31 -04:00
Powei Feng
7967157fbb renderdiff: fix breakage (#8648)
- Re-enable renderdiff test.
- Cache the mesa directory so that we're not spending time pulling
  and compiling it.
- Move python prereqs into the script and use venv.
2025-04-22 18:23:05 +00:00
Andy Hovingh
597ced13e1 webgpu: initial shader compilation 2025-04-22 12:56:03 -05:00
Sungun Park
c3542b135e Fix crash for ShaderCompilerService (#8626)
This change fixes a crash that occurs in ShaderCompilerService under a
certain condition described below.

Functions are called in the order described below.
1. `ShaderCompilerService::initialize(...)` is called when a new gl
   program is created. There are cases whre the corresponding TickOp may
   be still alive at the end of this method.
2. `OpenGLProgram::~OpenGLProgram()` is called when the program is
   immediately destroyed without being used. This deletes gl.program.
3. `ShaderCompilerService::tick()` is called later, and it references
   the dangling gl object `gl.program` in the lambda function, and it
   crashes.

This change also includes refactoring the class `ShaderCompilerService`
to make code simpler and less error-prone.

TEST = Tested on Desktop, Android, and Web by running sample apps

BUGS = [394319326, 407090622]
2025-04-22 17:31:40 +00:00
Mathias Agopian
ca3ff7e08e Use the Perfetto SDK instead of ATRACE
Perfetto has significantly less overhead. The User facing API is
mostly unchanged:

Here are the differences:

- SYSTRACE_ENABLE() does nothing on ANDROID, initializes systraces on darwin.
- SYSTRACE_DISABLE() is removed.
- A new "gltfio" tag is added.
- SYSTRACE_TAG *must* be defined before including `utils/Systrace.h`
- `utils/Systrace.h` should not be used from a public header
- the new SYSTRACE_TAG_DISABLE disables systrace at compile time


For android a data source MUST be created in the perfetto config:

```
data_sources {
  config {
    name: "track_event"
    track_event_config {
      enabled_categories: ["filament", "jobsystem", "gltfio"]
      disabled_categories: "*"
    }
  }
}
```

This can for example be added to AGI's custom/advanced config.

FIXES=[407572663]
2025-04-22 09:31:29 -07:00
Mathias Agopian
42c760a92f add the Perfetto SDK to libutils
for android NDK projects we also need to add it to the dependent
libraries.
2025-04-22 09:31:29 -07:00
Mathias Agopian
327a537bcc Add the Perfetto SDK to the build 2025-04-22 09:31:29 -07:00
Matthew Hoffman
cfc4f34c18 Use EXPECT_IMAGE in all backend tests. (#8628) 2025-04-21 19:57:40 -05:00
75 changed files with 243216 additions and 1128 deletions

View File

@@ -108,16 +108,27 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install python prereqs
run: pip install mako setuptools pyyaml
- name: Run script
- 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: |
echo "disabled test"
# bash test/renderdiff/test.sh
# - uses: actions/upload-artifact@v4
# with:
# name: presubmit-renderdiff-result
# path: ./out/renderdiff_tests
bash test/utils/get_mesa.sh
- name: Run Test
run: |
bash test/renderdiff/test.sh
- uses: actions/upload-artifact@v4
with:
name: presubmit-renderdiff-result
path: ./out/renderdiff_tests
validate-wgsl-webgpu:
name: validate-wgsl-webgpu

15
.gitignore vendored
View File

@@ -18,3 +18,18 @@ 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,6 +801,7 @@ 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

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

View File

@@ -21,6 +21,10 @@ 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)
@@ -123,6 +127,7 @@ target_link_libraries(filament-jni
PRIVATE android
PRIVATE jnigraphics
PRIVATE utils
PRIVATE perfetto
# libgeometry is PUBLIC because gltfio uses it.
PUBLIC geometry
@@ -141,6 +146,7 @@ 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,6 +35,10 @@ 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)
@@ -121,6 +125,7 @@ set(GLTFIO_INCLUDE_DIRS
../../third_party/meshoptimizer/src
../../third_party/robin-map
../../third_party/stb
../../third_party/perfetto
../../libs/utils/include
../../libs/ktxreader/include
)
@@ -129,7 +134,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 uberzlib log stb ktxreader basis_transcoder zstd uberarchive)
target_link_libraries(gltfio-jni filament-jni utils perfetto uberzlib log stb ktxreader basis_transcoder zstd uberarchive)
target_link_libraries(gltfio-jni dracodec meshoptimizer)
target_compile_definitions(gltfio-jni PUBLIC GLTFIO_DRACO_SUPPORTED=1)
target_include_directories(gltfio-jni PRIVATE ${DRACO_DIR}/src)

View File

@@ -259,6 +259,7 @@ 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)

View File

@@ -84,7 +84,7 @@ void CommandStream::execute(void* buffer) {
Profiler profiler;
if (SYSTRACE_TAG) {
if constexpr (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 (SYSTRACE_TAG) {
if constexpr (SYSTRACE_TAG) {
if (UTILS_UNLIKELY(mUsePerformanceCounter)) {
// we want to remove all this when tracing is completely disabled
profiler.stop();

View File

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

View File

@@ -17,11 +17,15 @@
#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>
@@ -34,9 +38,9 @@
#include <utils/Panic.h>
#include <utils/Systrace.h>
#include <algorithm>
#include <array>
#include <cctype>
#include <chrono>
#include <mutex>
#include <memory>
#include <string>
@@ -47,6 +51,7 @@
#include <stddef.h>
#include <stdint.h>
#include <string.h>
namespace filament::backend {
@@ -54,17 +59,17 @@ using namespace utils;
// ------------------------------------------------------------------------------------------------
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 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 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& context, char* source,
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context, char* source,
size_t len) noexcept;
static void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount, char* source,
static void process_OVR_multiview2(OpenGLContext const& context, int32_t eyeCount, char* source,
size_t len) noexcept;
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;
@@ -72,20 +77,15 @@ 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, utils::CString const& name) noexcept
: compiler(compiler), name(name) {
OpenGLProgramToken(ShaderCompilerService& compiler, CString const& name) noexcept
: compiler(compiler), name(name), handle(compiler.issueCallbackHandle()) {
}
ShaderCompilerService& compiler;
utils::CString const& name;
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes;
CString const& name;
FixedCapacityVector<std::pair<CString, uint8_t>> attributes;
shaders_source_t shaderSourceCode;
void* user = nullptr;
struct {
@@ -93,48 +93,34 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
GLuint program = 0;
} gl; // 12 bytes
// 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 {
// 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 {
std::unique_lock const l(lock);
programData = data;
signaled = true;
cond.notify_one();
}
// 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;
}
// 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.
void wait() const noexcept {
std::unique_lock l(lock);
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; });
cond.wait(l, [this] { return signaled; });
}
CallbackManager::Handle handle{};
BlobCacheKey key;
mutable utils::Mutex lock;
mutable utils::Condition cond;
ProgramData programData;
bool signaled = false;
bool canceled = false; // not part of the signaling
// Used for the `THREAD_POOL` mode.
mutable Mutex lock;
mutable Condition cond;
bool signaled = false;
};
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() = default;
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() {
compiler.submitCallbackHandle(handle);
}
/* static */ void ShaderCompilerService::setUserData(const program_token_t& token,
void* user) noexcept {
@@ -189,9 +175,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 multi-threaded, but program linking happens on
// - on Mali shader compilation can be multithreaded, but program linking happens on
// a single service thread, so we don't bother using more than one thread either.
// - on PowerVR shader compilation and linking can be multi-threaded.
// - on PowerVR shader compilation and linking can be multithreaded.
// How many threads should we use?
// - 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.
@@ -220,7 +206,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
@@ -228,7 +214,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();
});
@@ -249,119 +235,65 @@ void ShaderCompilerService::terminate() noexcept {
}
ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
utils::CString const& name, Program&& program) {
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;
}
token->handle = mCallbackManager.get();
// Initiate program compilation.
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 {
// 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);
}
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);
});
break;
}
case Mode::SYNCHRONOUS:
case Mode::ASYNCHRONOUS: {
// 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);
compileShaders(gl, std::move(program.getShadersSource()),
program.getSpecializationConstants(), program.isMultiview(), token);
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
assert_invariant(mMode != Mode::THREAD_POOL);
if (mMode == Mode::ASYNCHRONOUS) {
// don't attempt to link this program if all shaders are not done compiling
GLint status;
// Check link completion if link was initiated.
if (token->gl.program) {
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;
}
}
}
return isLinkCompleted(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...
// Link hasn't been initiated, then check compile completion.
if (!isCompileCompleted(token)) {
return false;
}
}
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);
if (!token->gl.program) {
linkProgram(mDriver.getContext(), token);
if (mMode == Mode::ASYNCHRONOUS) {
return false;// Wait until the link finishes.
}
}
return true;
});
break;
@@ -375,7 +307,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
return token;
}
GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& token) {
GLuint ShaderCompilerService::getProgram(program_token_t& token) {
GLuint const program = initialize(token);
assert_invariant(token == nullptr);
#if !FILAMENT_ENABLE_MATDBG
@@ -384,43 +316,29 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
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);
token->canceled = true;
bool const isTickOpCanceled = token->compiler.cancelTickOp(token);
assert_invariant(token);// This function should be called when the token is still alive.
if (token->compiler.mMode == Mode::THREAD_POOL) {
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
auto const job = token->compiler.mCompilerThreadPool.dequeue(token);
if (!job) {
// The job is being executed right now. We need to wait for it to finish to avoid a
// race.
// 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.
token->wait();
} else {
// The job has not been executed, but we still need to inform the callback manager in
// order for future callbacks to be successfully called.
token->compiler.mCallbackManager.put(token->handle);
}
} else if (isTickOpCanceled) {
// Since the tick op was canceled, we need to .put the token here.
token->compiler.mCallbackManager.put(token->handle);
}
for (GLuint& shader: token->gl.shaders) {
if (shader) {
if (token->gl.program) {
glDetachShader(token->gl.program, shader);
}
glDeleteShader(shader);
shader = 0;
}
}
if (token->gl.program) {
glDeleteProgram(token->gl.program);
}
token.reset();
cleanupProgramAndShaders(token);
// Cleanup the token.
token->compiler.cancelTickOp(token);
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
}
void ShaderCompilerService::tick() {
@@ -430,8 +348,16 @@ void ShaderCompilerService::tick() {
}
}
CallbackManager::Handle ShaderCompilerService::issueCallbackHandle() const noexcept {
return mCallbackManager.get();
}
void ShaderCompilerService::submitCallbackHandle(CallbackManager::Handle handle) noexcept {
mCallbackManager.put(handle);
}
void ShaderCompilerService::notifyWhenAllProgramsAreReady(
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
CallbackHandler* handler, CallbackHandler::Callback const callback, void* user) {
if (callback) {
mCallbackManager.setCallback(handler, callback, user);
}
@@ -439,117 +365,137 @@ void ShaderCompilerService::notifyWhenAllProgramsAreReady(
// ------------------------------------------------------------------------------------------------
/* 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) {
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();
}
if (!token->canceled) {
token->compiler.cancelTickOp(token);
}
assert_invariant(token);// This function should be called when the token is still alive.
// 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;
}
case Mode::ASYNCHRONOUS: {
// we force the program link -- which might stall, either here or below in
// checkProgramStatus(), but we don't have a choice, we need to use the program now.
token->compiler.cancelTickOp(token);
token->gl.program =
linkProgram(mDriver.getContext(), token->gl.shaders, token->attributes);
assert_invariant(token->gl.program);
mCallbackManager.put(token->handle);
if (token->key) {
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
}
break;
}
case Mode::SYNCHRONOUS: {
// if we don't have a program yet, block until we get it.
tick();
break;
}
case Mode::UNDEFINED: {
assert_invariant(false);
}
}
}
// by this point we must have a GL program
ensureTokenIsReady(token);
assert_invariant(token->gl.program);
GLuint program = 0;
// Check status of program linking. If it failed, errors will be logged.
bool const linked = checkLinkStatusAndCleanupShaders(token);
// check status of program linking and shader compilation, logs error and free all resources
// 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)
// 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";
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;
}
}
// 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);
}
// and destroy all temporary init data
token = nullptr;
GLuint const program = token->gl.program;
// Cleanup the token.
token->compiler.cancelTickOp(token);
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
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,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
bool multiview,
shaders_t& outShaders,
UTILS_UNUSED_IN_RELEASE shaders_source_t& outShaderSourceCode) noexcept {
FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
bool multiview, program_token_t const& token) noexcept {
SYSTRACE_CALL();
auto appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
auto const appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
s += "#define SPIRV_CROSS_CONSTANT_ID_" + std::to_string(sc.id) + ' ';
s += std::visit([](auto&& arg) { return to_string(arg); }, sc.value);
s += '\n';
@@ -558,7 +504,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) 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
@@ -596,7 +542,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
if (UTILS_LIKELY(!shadersSource[i].empty())) {
Program::ShaderBlob& shader = shadersSource[i];
char* shader_src = reinterpret_cast<char*>(shader.data());
size_t shader_len = shader.size();
size_t const shader_len = shader.size();
// remove GOOGLE_cpp_style_line_directive
process_GOOGLE_cpp_style_line_directive(context, shader_src, shader_len);
@@ -619,178 +565,191 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
}
std::array<std::string_view, 5> sources = {
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 partitionPoint = std::stable_partition(
sources.begin(), sources.end(), [](std::string_view s) { return !s.empty(); });
size_t count = std::distance(sources.begin(), partitionPoint);
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);
std::array<const char*, 5> shaderStrings;
std::array<GLint, 5> lengths;
for (size_t i = 0; i < count; i++) {
shaderStrings[i] = sources[i].data();
lengths[i] = sources[i].size();
for (size_t j = 0; j < count; j++) {
shaderStrings[j] = sources[j].data();
lengths[j] = GLint(sources[j].size());
}
GLuint const shaderId = glCreateShader(glShaderType);
glShaderSource(shaderId, count, shaderStrings.data(), lengths.data());
glShaderSource(shaderId, GLsizei(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.
outShaderSourceCode[i] = { shader_src, shader_len };
token->shaderSourceCode[i] = { shader_src, shader_len };
#endif
outShaders[i] = shaderId;
token->gl.shaders[i] = shaderId;
}
}
}
/*
* 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 {
/* 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;
}
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 shader : shaders) {
for (auto const shader: token->gl.shaders) {
if (shader) {
glAttachShader(program, shader);
}
}
if (UTILS_UNLIKELY(context.isES2())) {
for (auto const& [ name, loc ] : attributes) {
for (auto const& [name, loc]: token->attributes) {
glBindAttribLocation(program, loc, name.c_str());
}
}
glLinkProgram(program);
return program;
token->gl.program = program;
}
// ------------------------------------------------------------------------------------------------
void ShaderCompilerService::runAtNextTick(CompilerPriorityQueue priority,
const program_token_t& token, Job job) noexcept {
// insert items in order of priority and at the end of the range
auto& ops = mRunAtNextTickOps;
auto const pos = std::lower_bound(ops.begin(), ops.end(), priority,
[](ContainerType const& lhs, CompilerPriorityQueue priorityQueue) {
return std::get<0>(lhs) < priorityQueue;
});
ops.emplace(pos, priority, token, std::move(job));
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("ShaderCompilerService Jobs", mRunAtNextTickOps.size());
}
bool ShaderCompilerService::cancelTickOp(program_token_t token) noexcept {
// We do a linear search here, but this is rare, and we know the list is pretty small.
auto& ops = mRunAtNextTickOps;
auto pos = std::find_if(ops.begin(), ops.end(), [&](const auto& item) {
return std::get<1>(item) == token;
});
if (pos != ops.end()) {
ops.erase(pos);
return true;
}
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
return false;
}
void ShaderCompilerService::executeTickOps() noexcept {
auto& ops = mRunAtNextTickOps;
auto it = ops.begin();
while (it != ops.end()) {
Job const& job = std::get<2>(*it);
bool const remove = job.fn(job);
if (remove) {
it = ops.erase(it);
} else {
++it;
}
}
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
}
// ------------------------------------------------------------------------------------------------
/*
* Checks a program link status and logs errors and frees resources on failure.
* Returns true on success.
*/
/* static */ bool ShaderCompilerService::checkProgramStatus(program_token_t const& token) noexcept {
SYSTRACE_CALL();
/* static */ bool ShaderCompilerService::isLinkCompleted(program_token_t const& token) noexcept {
assert_invariant(token->gl.program);
GLint status;
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
if (UTILS_LIKELY(status == GL_TRUE)) {
return true;
GLenum param = GL_COMPLETION_STATUS;
if (UTILS_UNLIKELY(token->compiler.mMode != Mode::ASYNCHRONOUS)) {
param = GL_LINK_STATUS;
}
// only if the link fails, we check the compilation status
GLint status;
glGetProgramiv(token->gl.program, param, &status);
return (status == GL_TRUE);
}
/* static */ bool ShaderCompilerService::checkLinkStatusAndCleanupShaders(
program_token_t const& token) noexcept {
SYSTRACE_CALL();
assert_invariant(token->gl.program);
bool linked = true;
GLint status;
// GL_LINK_STATUS may block until the link is completed.
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
if (UTILS_UNLIKELY(status != GL_TRUE)) {
// Something went wrong. Log the error message.
logProgramLinkError(slog.e, token->name.c_str_safe(), token->gl.program);
linked = false;
}
// No need to keep the shaders around regardless of the result of the program linking.
UTILS_NOUNROLL
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];
for (GLuint& shader: token->gl.shaders) {
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);
token->gl.shaders[i] = 0;
shader = 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;
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;
}
}
// ------------------------------------------------------------------------------------------------
UTILS_NOINLINE
/* static */ void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
GLuint shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
GLuint const shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
auto to_string = [](ShaderStage type) -> const char* {
switch (type) {
case ShaderStage::VERTEX:
return "vertex";
case ShaderStage::FRAGMENT:
return "fragment";
case ShaderStage::COMPUTE:
return "compute";
}
};
{ // 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";
};
{// scope for the temporary string storage
GLint length = 0;
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &length);
@@ -837,8 +796,8 @@ UTILS_NOINLINE
// If usages of the Google-style line directive are present, remove them, as some
// drivers don't allow the quotation marks. This source modification happens in-place.
/* static */ void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, char* source,
size_t len) noexcept {
/* static */ void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& 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
@@ -850,13 +809,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& context, int32_t eyeCount, char* source,
size_t len) noexcept {
/* static */ void process_OVR_multiview2(OpenGLContext const& context, int32_t const eyeCount,
char* source, size_t const len) noexcept {
// We don't use regular expression in favor of performance.
if (context.ext.OVR_multiview2) {
const std::string_view shader{ source, len };
const std::string_view layout = "layout";
const std::string_view num_views = "num_views";
constexpr std::string_view layout = "layout";
constexpr std::string_view num_views = "num_views";
size_t found = 0;
while (true) {
found = shader.find(layout, found);
@@ -1011,20 +970,20 @@ mediump vec4 unpackSnorm4x8(highp uint v) {
// - extensions
// - everything else
/* static */ std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept {
auto version_start = source.find("#version");
auto const version_start = source.find("#version");
assert_invariant(version_start != std::string_view::npos);
auto version_eol = source.find('\n', version_start) + 1;
auto const version_eol = source.find('\n', version_start) + 1;
assert_invariant(version_eol != std::string_view::npos);
auto prolog_start = version_eol;
auto const 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 body_start = prolog_eol;
auto const body_start = prolog_eol;
std::string_view const version = source.substr(version_start, version_eol - version_start);
std::string_view const 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 <atomic>
#include <condition_variable>
#include <deque>
#include <array>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
#include <tuple>
#include <utility>
#include <vector>
#include <stdint.h>
namespace filament::backend {
class OpenGLDriver;
@@ -84,6 +84,7 @@ 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
@@ -92,6 +93,12 @@ 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);
@@ -99,7 +106,7 @@ public:
private:
struct Job {
template<typename FUNC>
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {}
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {} // NOLINT(*-explicit-constructor)
Job(std::function<bool(Job const& job)> fn,
CallbackHandler* handler, void* user, CallbackHandler::Callback callback)
: fn(std::move(fn)), handler(handler), user(user), callback(callback) {
@@ -128,26 +135,49 @@ private:
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
std::vector<ContainerType> mRunAtNextTickOps;
GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept;
GLuint initialize(program_token_t& token);
void ensureTokenIsReady(program_token_t const& token);
static void getProgramFromCompilerPool(program_token_t& token) noexcept;
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 runAtNextTick(CompilerPriorityQueue priority, program_token_t const& token,
Job job) noexcept;
void executeTickOps() noexcept;
bool cancelTickOp(program_token_t token) noexcept;
// order of insertion is important
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;
};
} // namespace filament::backend

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_NAME(__func__)
#define FVK_PROFILE_MARKER(marker) FVK_SYSTRACE_SCOPE()
#define FVK_SYSTRACE_SCOPE() SYSTRACE_CALL()
#define FVK_PROFILE_MARKER(marker) SYSTRACE_CALL()
#else
#define FVK_SYSTRACE_CONTEXT()

View File

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

View File

@@ -325,6 +325,9 @@ void WebGPUDriver::destroyTexture(Handle<HwTexture> th) {
}
void WebGPUDriver::destroyProgram(Handle<HwProgram> ph) {
if (ph) {
destructHandle<WGPUProgram>(ph);
}
}
void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
@@ -369,7 +372,7 @@ Handle<HwTexture> WebGPUDriver::importTextureS() noexcept {
}
Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
return Handle<HwProgram>((Handle<HwProgram>::HandleId) mNextFakeHandle++);
return allocHandle<WGPUProgram>();
}
Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
@@ -505,7 +508,9 @@ 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) {}
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
constructHandle<WGPUProgram>(ph, mDevice, program);
}
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
assert_invariant(!mDefaultRenderTarget);

View File

@@ -28,9 +28,20 @@
#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.

View File

@@ -55,14 +55,17 @@ void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool is
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;
}
@@ -156,51 +159,6 @@ void BackendTest::renderTriangle(
api.endRenderPass();
}
void BackendTest::readPixelsAndAssertHash(const char* testName, size_t width, size_t height,
Handle<HwRenderTarget> rt, uint32_t expectedHash, bool exportScreenshot) {
void* buffer = calloc(1, width * height * 4);
struct Capture {
uint32_t expectedHash;
char* name;
bool exportScreenshot;
size_t width, height;
};
auto* c = new Capture();
c->expectedHash = expectedHash;
c->name = strdup(testName);
c->exportScreenshot = exportScreenshot;
c->width = width;
c->height = height;
PixelBufferDescriptor pbd(buffer, width * height * 4, PixelDataFormat::RGBA, PixelDataType::UBYTE,
1, 0, 0, width, [](void* buffer, size_t size, void* user) {
auto* c = (Capture*)user;
// Export a screenshot, if requested.
if (c->exportScreenshot) {
#ifndef FILAMENT_IOS
LinearImage image(c->width, c->height, 4);
image = toLinearWithAlpha<uint8_t>(c->width, c->height, c->width * 4,
(uint8_t*) buffer);
const std::string png = std::string(c->name) + ".png";
std::ofstream outputStream(png.c_str(), std::ios::binary | std::ios::trunc);
ImageEncoder::encode(outputStream, ImageEncoder::Format::PNG, image, "",
png);
#endif
}
// Hash the contents of the buffer and check that they match.
uint32_t hash = utils::hash::murmur3((const uint32_t*) buffer, size / 4, 0);
ASSERT_EQ(hash, c->expectedHash) << c->name << " failed: hashes do not match." << std::endl;
free(buffer);
free(c->name);
free(c);
}, (void*)c);
getDriverApi().readPixels(rt, 0, 0, width, height, std::move(pbd));
}
bool BackendTest::matchesEnvironment(Backend backend) {
return sBackend == backend;
}

View File

@@ -25,6 +25,7 @@
#include "private/backend/DriverApi.h"
#include "PlatformRunner.h"
#include "ImageExpectations.h"
namespace test {
@@ -65,17 +66,14 @@ 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;
@@ -83,6 +81,10 @@ 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

@@ -97,16 +97,21 @@ 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) {
LoadedPng loadedImage(mParams.expectedFilePath());
// 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));
EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash()));
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.
}
}
@@ -121,12 +126,13 @@ ImageExpectations::~ImageExpectations() {
void ImageExpectations::addExpectation(const char* fileName, int lineNumber,
filament::backend::RenderTargetHandle renderTarget, ScreenshotParams params) {
mExpectations.emplace_back(fileName, lineNumber, mApi, std::move(params), renderTarget);
mExpectations.emplace_back(std::make_unique<ImageExpectation>(fileName, lineNumber, mApi,
std::move(params), renderTarget));
}
void ImageExpectations::evaluate() {
for (auto& expectation: mExpectations) {
expectation.evaluate();
expectation->evaluate();
}
mExpectations.clear();
}
@@ -134,16 +140,13 @@ 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(),
@@ -152,14 +155,13 @@ RenderTargetDump::RenderTargetDump(filament::backend::DriverApi& api,
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG, image, "",
filePath);
internal->bytesFilled = true;
#endif
};
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,9 +185,9 @@ bool RenderTargetDump::bytesFilled() const {
RenderTargetDump::Internal::Internal(const ScreenshotParams& params) : params(params) {}
LoadedPng::LoadedPng(std::string filePath) {
LoadedPng::LoadedPng(std::string filePath) : mFilePath(std::move(filePath)) {
#ifndef FILAMENT_IOS
std::ifstream pngStream(filePath, std::ios::binary);
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() *
@@ -201,7 +203,8 @@ LoadedPng::LoadedPng(std::string filePath) {
}
uint32_t LoadedPng::hash() const {
EXPECT_THAT(mBytes, testing::Not(testing::IsEmpty()));
EXPECT_THAT(mBytes, testing::Not(testing::IsEmpty()))
<< "Failed to load expected test result: " << mFilePath;
if (mBytes.empty()) {
return 0;
}

View File

@@ -106,6 +106,7 @@ public:
uint32_t hash() const;
private:
std::string mFilePath;
std::vector<unsigned char> mBytes;
};
@@ -141,7 +142,8 @@ public:
private:
filament::backend::DriverApi& mApi;
std::vector<ImageExpectation> mExpectations;
// Store expectations in unique pointers because they are self referential.
std::vector<std::unique_ptr<ImageExpectation>> mExpectations;
};
#endif //TNT_IMAGE_EXPECTATIONS_H

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -197,16 +197,10 @@ TEST_F(BlitTest, ColorMagnify) {
}
{
ImageExpectations expectations(api);
{
RenderFrame frame(api);
EXPECT_IMAGE(dstRenderTargets[0], expectations,
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMagnify", 0x410bdd31));
api.commit(swapChain);
}
flushAndWait();
RenderFrame frame(api);
EXPECT_IMAGE(dstRenderTargets[0], getExpectations(),
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMagnify", 0x410bdd31));
api.commit(swapChain);
}
}
@@ -260,14 +254,8 @@ TEST_F(BlitTest, ColorMinify) {
{ 0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel },
SamplerMagFilter::LINEAR);
{
ImageExpectations expectations(api);
EXPECT_IMAGE(dstRenderTargets[0], expectations,
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f));
flushAndWait();
}
EXPECT_IMAGE(dstRenderTargets[0], getExpectations(),
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f));
}
TEST_F(BlitTest, ColorResolve) {
@@ -351,14 +339,8 @@ TEST_F(BlitTest, ColorResolve) {
srcRenderTarget, { 0, 0, kSrcTexWidth, kSrcTexHeight },
SamplerMagFilter::NEAREST);
{
ImageExpectations expectations(api);
EXPECT_IMAGE(dstRenderTarget, expectations,
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 0xebfac2ef));
flushAndWait();
}
EXPECT_IMAGE(dstRenderTarget, getExpectations(),
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 0xebfac2ef));
}
TEST_F(BlitTest, Blit2DTextureArray) {
@@ -423,17 +405,11 @@ TEST_F(BlitTest, Blit2DTextureArray) {
}
{
ImageExpectations expectations(api);
{
RenderFrame frame(api);
EXPECT_IMAGE(dstRenderTarget, expectations,
ScreenshotParams(kDstTexWidth, kDstTexHeight, "Blit2DTextureArray",
0x8de7d55b));
api.commit(swapChain);
}
flushAndWait();
RenderFrame frame(api);
EXPECT_IMAGE(dstRenderTarget, getExpectations(),
ScreenshotParams(kDstTexWidth, kDstTexHeight, "Blit2DTextureArray",
0x8de7d55b));
api.commit(swapChain);
}
}
@@ -503,18 +479,12 @@ TEST_F(BlitTest, BlitRegion) {
}
{
ImageExpectations expectations(api);
{
RenderFrame frame(api);
// TODO: for some reason, this test has very, very slight (as in one pixel) differences
// between OpenGL and Metal. So disable golden checking for now.
// EXPECT_IMAGE(dstRenderTarget, expectations, ScreenshotParams(kDstTexWidth,
// kDstTexHeight, "BlitRegion", 0x74fa34ed));
api.commit(swapChain);
}
flushAndWait();
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);
}
}
@@ -567,25 +537,19 @@ TEST_F(BlitTest, BlitRegionToSwapChain) {
};
{
ImageExpectations expectations(api);
{
{
RenderFrame frame(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);
}
// 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();
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));
}
} // namespace test

View File

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

View File

@@ -71,8 +71,6 @@ 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.
@@ -96,25 +94,6 @@ 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.
@@ -259,7 +238,8 @@ 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) {
dumpScreenshot(api, renderTargets[0]);
EXPECT_IMAGE(renderTargets[0], getExpectations(),
ScreenshotParams(kTexWidth, kTexHeight, "FeedbackLoops", 0x70695aa1));
}
api.flush();
@@ -270,10 +250,6 @@ 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 -> RGBA8", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8);
testCases.emplace_back("RGBA UBYTE to 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 -> RGBA16F", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F);
testCases.emplace_back("RGBA FLOAT to RGBA16F", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F);
// Test texture formats not all backends support natively.
// TODO: Vulkan crashes with "VK_FORMAT_R32G32B32_SFLOAT is not supported"
testCases.emplace_back("RGB, FLOAT -> RGB32F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F);
testCases.emplace_back("RGB, FLOAT -> RGB16F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB16F);
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);
// 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 -> 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);
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);
// 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 -> 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);
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);
// Test uploads with buffer padding.
// TODO: Vulkan crashes with "Assertion failed: (offset + size <= allocationSize)"
testCases.emplace_back("RGBA, UBYTE -> RGBA8 (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u);
testCases.emplace_back("RGBA, 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);
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);
// Upload subregions separately.
// TODO: Vulkan crashes with "Offsets not yet supported"
testCases.emplace_back("RGBA, UBYTE -> RGBA8 (subregions)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 0u, true);
testCases.emplace_back("RGBA, 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);
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);
auto& api = getDriverApi();
@@ -339,16 +339,14 @@ TEST_F(LoadImageTest, UpdateImage2D) {
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
defaultRenderTarget, swapChain, shader.getProgram());
readPixelsAndAssertHash(t.name, 512, 512, defaultRenderTarget, expectedHash);
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
ScreenshotParams(512, 512, t.name, expectedHash));
api.commit(swapChain);
api.endFrame(0);
}
api.finish();
api.stopCapture();
flushAndWait();
}
TEST_F(LoadImageTest, UpdateImageSRGB) {
@@ -372,7 +370,7 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
getSamplerTypeName(textureFormat), fragmentTemplate);
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = mVertexShader, .fragmentShader = fragment, .uniforms = {{
"text_tex", DescriptorType::SAMPLER, samplerInfo
"test_tex", DescriptorType::SAMPLER, samplerInfo
}}});
// Create a texture.
@@ -416,15 +414,12 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
defaultRenderTarget, swapChain, shader.getProgram());
static const uint32_t expectedHash = 359858623;
readPixelsAndAssertHash("UpdateImageSRGB", 512, 512, defaultRenderTarget, expectedHash);
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
ScreenshotParams(512, 512, "UpdateImageSRGB", 359858623));
api.flush();
api.commit(swapChain);
api.endFrame(0);
// This ensures all driver commands have finished before exiting the test.
api.finish();
api.stopCapture();
}
@@ -478,15 +473,12 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
defaultRenderTarget, swapChain, shader.getProgram());
static const uint32_t expectedHash = 3644679986;
readPixelsAndAssertHash("UpdateImageMipLevel", 512, 512, defaultRenderTarget, expectedHash);
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
ScreenshotParams(512, 512, "UpdateImageMipLevel", 3644679986));
api.flush();
api.commit(swapChain);
api.endFrame(0);
// This ensures all driver commands have finished before exiting the test.
api.finish();
api.stopCapture();
}
@@ -538,29 +530,24 @@ TEST_F(LoadImageTest, UpdateImage3D) {
api.update3DImage(texture, 0, 0, 0, 0, 512, 512, 4, std::move(descriptor));
api.beginFrame(0, 0, 0);
{
RenderFrame frame(api);
// Update samplers.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
// Update samplers.
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet, 0, texture,
{ .filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST });
api.bindDescriptorSet(descriptorSet, 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

@@ -16,6 +16,7 @@
#include "BackendTest.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
#include "ShaderGenerator.h"
#include "Skip.h"
@@ -151,19 +152,14 @@ TEST_F(BackendTest, PushConstants) {
api.endRenderPass();
readPixelsAndAssertHash("pushConstants", 512, 512, renderTarget, 1957275826, true);
EXPECT_IMAGE(renderTarget, getExpectations(),
ScreenshotParams(512, 512, "pushConstants", 1957275826));
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,6 +16,7 @@
#include "BackendTest.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
#include "Shader.h"
#include "SharedShaders.h"
@@ -200,17 +201,16 @@ 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();
executeCommands();
}
} // namespace test

View File

@@ -16,6 +16,7 @@
#include "BackendTest.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
#include "Shader.h"
#include "SharedShaders.h"
@@ -130,20 +131,14 @@ TEST_F(BackendTest, ScissorViewportRegion) {
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
readPixelsAndAssertHash("scissor", kSrcTexWidth >> 1, kSrcTexHeight >> 1, fullRenderTarget,
0xAB3D1C53, true);
EXPECT_IMAGE(fullRenderTarget, getExpectations(),
ScreenshotParams(kSrcTexWidth >> 1, kSrcTexHeight >> 1, "scissor", 0xAB3D1C53));
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.
@@ -226,20 +221,14 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
readPixelsAndAssertHash(
"ScissorViewportEdgeCases", 512, 512, renderTarget, 0x6BF00F31, true);
EXPECT_IMAGE(renderTarget, getExpectations(),
ScreenshotParams(512, 512, "ScissorViewportEdgeCases", 0x6BF00F31));
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,6 +16,7 @@
#include "BackendTest.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
#include "Shader.h"
#include "SharedShaders.h"
@@ -129,7 +130,8 @@ TEST_F(BasicStencilBufferTest, StencilBuffer) {
RunTest(renderTarget);
readPixelsAndAssertHash("StencilBuffer", 512, 512, renderTarget, 0x3B1AEF0F, true);
EXPECT_IMAGE(renderTarget, getExpectations(),
ScreenshotParams(512, 512, "StencilBuffer", 0x3B1AEF0F));
flushAndWait();
getDriver().purge();
@@ -151,7 +153,8 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
RunTest(renderTarget);
readPixelsAndAssertHash("DepthAndStencilBuffer", 512, 512, renderTarget, 0x3B1AEF0F, true);
EXPECT_IMAGE(renderTarget, getExpectations(),
ScreenshotParams(512, 512, "DepthAndStencilBuffer", 0x3B1AEF0F));
flushAndWait();
getDriver().purge();
@@ -233,7 +236,8 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
api.stopCapture(0);
api.endFrame(0);
readPixelsAndAssertHash("StencilBufferAutoResolve", 512, 512, renderTarget1, 0x6CEFAC8F, true);
EXPECT_IMAGE(renderTarget1, getExpectations(),
ScreenshotParams(512, 512, "StencilBufferAutoResolve", 0x6CEFAC8F));
flushAndWait();
getDriver().purge();

View File

@@ -50,6 +50,8 @@
#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,7 +37,10 @@
#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,6 +21,8 @@
#include "GltfEnums.h"
#include <utils/Log.h>
#define SYSTRACE_TAG SYSTRACE_TAG_GLTFIO
#include <utils/Systrace.h>
#define CGLTF_IMPLEMENTATION

View File

@@ -112,6 +112,7 @@ 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,24 +17,23 @@
#ifndef TNT_UTILS_SYSTRACE_H
#define TNT_UTILS_SYSTRACE_H
#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)
#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)
/*
* 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.
* The SYSTRACE_ macros use SYSTRACE_TAG as a category, which must be defined
* before this file is included.
*/
#ifndef SYSTRACE_TAG
#define SYSTRACE_TAG (SYSTRACE_TAG_ALWAYS)
# error SYSTRACE_TAG must be set to SYSTRACE_TAG_{DISABLED|FILAMENT|JOBSYSTEM}
#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__)
@@ -44,7 +43,6 @@
#else
#define SYSTRACE_ENABLE()
#define SYSTRACE_DISABLE()
#define SYSTRACE_CONTEXT()
#define SYSTRACE_NAME(name)
#define SYSTRACE_FRAME_ID(frame)
@@ -56,6 +54,6 @@
#define SYSTRACE_VALUE32(name, val)
#define SYSTRACE_VALUE64(name, val)
#endif // ANDROID
#endif
#endif // TNT_UTILS_SYSTRACE_H

View File

@@ -17,226 +17,75 @@
#ifndef TNT_UTILS_ANDROID_SYSTRACE_H
#define TNT_UTILS_ANDROID_SYSTRACE_H
#include <atomic>
#include <perfetto/perfetto.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <utils/compiler.h>
PERFETTO_DEFINE_CATEGORIES_IN_NAMESPACE(systrace,
perfetto::Category("filament"),
perfetto::Category("jobsystem"),
perfetto::Category("gltfio"));
// enable tracing
#define SYSTRACE_ENABLE() ::utils::details::Systrace::enable(SYSTRACE_TAG)
PERFETTO_USE_CATEGORIES_FROM_NAMESPACE(systrace);
// disable tracing
#define SYSTRACE_DISABLE() ::utils::details::Systrace::disable(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
#if SYSTRACE_TAG == SYSTRACE_TAG_DISABLED
/**
* 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)
#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
// 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)
#define SYSTRACE_ENABLE()
#define SYSTRACE_CONTEXT()
// Denotes that a new frame has started processing.
#define SYSTRACE_FRAME_ID(frame) \
{ /* scope for frame id trace */ \
char buf[64]; \
snprintf(buf, 64, "frame %u", frame); \
SYSTRACE_NAME(buf); \
}
#define SYSTRACE_CALL() \
auto constexpr FILAMENT_SYSTRACE_FUNCTION = perfetto::StaticString(__FUNCTION__); \
TRACE_EVENT(UTILS_PERFETTO_CATEGORY, FILAMENT_SYSTRACE_FUNCTION)
// SYSTRACE_CALL is an SYSTRACE_NAME that uses the current function name.
#define SYSTRACE_CALL() SYSTRACE_NAME(__FUNCTION__)
#define SYSTRACE_NAME(name) TRACE_EVENT(UTILS_PERFETTO_CATEGORY, nullptr, \
[&](perfetto::EventContext ctx) { \
ctx.event()->set_name(name); \
})
#define SYSTRACE_NAME_BEGIN(name) \
___trctx.traceBegin(SYSTRACE_TAG, 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() \
___trctx.traceEnd(SYSTRACE_TAG)
#define SYSTRACE_NAME_END() TRACE_EVENT_END(UTILS_PERFETTO_CATEGORY)
/**
* 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_EVENT_BEGIN(UTILS_PERFETTO_CATEGORY, name, perfetto::Track(cookie))
/**
* Trace the end of an asynchronous event.
* This should have a corresponding SYSTRACE_ASYNC_BEGIN.
*/
#define SYSTRACE_ASYNC_END(name, cookie) \
___trctx.asyncEnd(SYSTRACE_TAG, name, cookie)
TRACE_EVENT_END(UTILS_PERFETTO_CATEGORY, perfetto::Track(cookie))
#define SYSTRACE_FRAME_ID(frame) \
TRACE_EVENT_INSTANT(UTILS_PERFETTO_CATEGORY, "frame", "id", frame)
/**
* Traces an integer counter value. name is used to identify the counter.
* This can be used to track how a value changes over time.
*/
#define SYSTRACE_VALUE32(name, val) \
___trctx.value(SYSTRACE_TAG, name, int32_t(val))
TRACE_COUNTER(UTILS_PERFETTO_CATEGORY, name, val)
#define SYSTRACE_VALUE64(name, val) \
___trctx.value(SYSTRACE_TAG, name, int64_t(val))
TRACE_COUNTER(UTILS_PERFETTO_CATEGORY, name, val)
// ------------------------------------------------------------------------------------------------
// 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 // SYSTRACE_TAG == SYSTRACE_TAG_DISABLED
#endif // TNT_UTILS_ANDROID_SYSTRACE_H

View File

@@ -29,13 +29,25 @@
#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.
@@ -93,6 +105,8 @@ 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...
// ------------------------------------------------------------------------------------------------
@@ -118,50 +132,40 @@ namespace utils {
namespace details {
class 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.
};
public:
explicit Systrace(uint32_t tag) noexcept {
if (tag) init(tag);
}
static void enable(uint32_t tags) noexcept;
static void disable(uint32_t tags) noexcept;
static void enable(uint32_t tag) noexcept;
inline void traceBegin(uint32_t tag, const char* name) noexcept {
void traceBegin(uint32_t tag, const char* name) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
APPLE_SIGNPOST_EMIT(sGlobalState.systraceLog, OS_SIGNPOST_INTERVAL_BEGIN,
OS_SIGNPOST_ID_EXCLUSIVE, name, name)
}
}
inline void traceEnd(uint32_t tag, const char* name) noexcept {
void traceEnd(uint32_t tag, const char* name) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
APPLE_SIGNPOST_EMIT(sGlobalState.systraceLog, OS_SIGNPOST_INTERVAL_END,
OS_SIGNPOST_ID_EXCLUSIVE, name, "")
}
}
inline void asyncBegin(uint32_t tag, const char* name, int32_t cookie) noexcept {
void asyncBegin(uint32_t tag, const char* name, int32_t cookie) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
// TODO
}
}
inline void asyncEnd(uint32_t tag, const char* name, int32_t cookie) noexcept {
void asyncEnd(uint32_t tag, const char* name, int32_t cookie) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
// TODO
}
}
inline void value(uint32_t tag, const char* name, int32_t value) noexcept {
void value(uint32_t tag, const char* name, int32_t value) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
char buf[64];
snprintf(buf, 64, "%s - %d", name, value);
@@ -170,7 +174,7 @@ class Systrace {
}
}
inline void value(uint32_t tag, const char* name, int64_t value) noexcept {
void value(uint32_t tag, const char* name, int64_t value) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
char buf[64];
snprintf(buf, 64, "%s - %lld", name, value);
@@ -179,16 +183,16 @@ class Systrace {
}
}
inline void frameId(uint32_t tag, uint32_t frame) noexcept {
void frameId(uint32_t tag, uint32_t frame) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
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 {
@@ -213,25 +217,25 @@ class Systrace {
// ------------------------------------------------------------------------------------------------
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);
}
inline ~ScopedTrace() noexcept {
~ScopedTrace() noexcept {
mTrace.traceEnd(mTag, mName);
}
inline void value(uint32_t tag, const char* name, int32_t v) noexcept {
void value(uint32_t tag, const char* name, int32_t v) noexcept {
mTrace.value(tag, name, v);
}
inline void value(uint32_t tag, const char* name, int64_t v) noexcept {
void value(uint32_t tag, const char* name, int64_t v) noexcept {
mTrace.value(tag, name, v);
}
private:
private:
Systrace mTrace;
const char* mName;
const uint32_t mTag;

View File

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

View File

@@ -14,206 +14,26 @@
* limitations under the License.
*/
#include <utils/Systrace.h>
#include <utils/Log.h>
#include <utils/compiler.h>
#include <utils/android/Systrace.h>
#include <cinttypes>
#include <perfetto/perfetto.h>
#include <string.h>
PERFETTO_TRACK_EVENT_STATIC_STORAGE_IN_NAMESPACE(systrace);
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <dlfcn.h>
namespace {
namespace utils {
namespace details {
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);
class SystraceStaticInitialization {
public:
SystraceStaticInitialization() {
perfetto::TracingInitArgs args;
args.backends |= perfetto::kSystemBackend;
perfetto::Tracing::Initialize(args);
systrace::TrackEvent::Register();
}
};
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){};
}
UTILS_UNUSED SystraceStaticInitialization sSystraceStaticInitialization{};
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,11 +14,17 @@
* limitations under the License.
*/
#include <utils/Systrace.h>
#include <utils/Log.h>
#include <utils/darwin/Systrace.h>
#ifndef FILAMENT_APPLE_SYSTRACE
# define FILAMENT_APPLE_SYSTRACE 0
#endif
#if FILAMENT_APPLE_SYSTRACE
#include <atomic>
#include <stack>
#include <stdint.h>
#include <pthread.h>
static pthread_once_t atrace_once_control = PTHREAD_ONCE_INIT;
@@ -41,21 +47,24 @@ void Systrace::setup() noexcept {
pthread_once(&atrace_once_control, init_once);
}
void Systrace::enable(uint32_t tags) noexcept {
void Systrace::enable(uint32_t tag) noexcept {
setup();
sGlobalState.isTracingEnabled.fetch_or(tags, std::memory_order_relaxed);
uint32_t const mask = 1 << tag;
sGlobalState.isTracingEnabled.fetch_or(mask, std::memory_order_relaxed);
}
void Systrace::disable(uint32_t tags) noexcept {
sGlobalState.isTracingEnabled.fetch_and(~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);
}
// 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();
return bool((sGlobalState.isTracingEnabled.load(std::memory_order_relaxed) | SYSTRACE_TAG_ALWAYS) & tag);
uint32_t const mask = 1 << tag;
return bool(sGlobalState.isTracingEnabled.load(std::memory_order_relaxed) & mask);
}
return false;
}

View File

@@ -41,10 +41,10 @@ function prepare_mesa() {
# - Run the python script that runs the test
# - Zip up the result
set -e && set -x && prepare_mesa && \
set -ex && prepare_mesa && \
mkdir -p ${OUTPUT_DIR} && \
./build.sh -X ${MESA_DIR} -p desktop debug gltf_viewer && \
python3 ${RENDERDIFF_TEST_DIR}/src/run.py \
CXX=`which clang++` CC=`which clang` ./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,13 +14,34 @@
#!/usr/bin/bash
set -xe
set -x
if [[ "$GITHUB_WORKFLOW" ]]; then
set -e
fi
# GITHUB_CLANG_VERSION is set in build/linux/ci-common.sh
os_name=$(uname -s)
LLVM_VERSION=16
MESA_DIR=$(pwd)/mesa
MESA_VERSION=${MESA_VERSION-24.2.1}
ORIG_DIR=$(pwd)
MESA_DIR=${MESA_DIR-${ORIG_DIR}/mesa}
if [[ "$os_name" == "Linux" ]]; then
sudo apt install python3-venv
fi
# Install python deps
python3 -m venv ${ORIG_DIR}/venv
source ${ORIG_DIR}/venv/bin/activate
NEEDED_PYTHON_DEPS=("mako" "setuptools" "pyyaml")
for cmd in "${NEEDED_PYTHON_DEPS[@]}"; do
if ! python3 -m pip show "${cmd}" >/dev/null 2>&1; then
python3 -m pip install ${cmd}
fi
done
deactivate
# Install system deps
if [[ "$os_name" == "Linux" ]]; then
if [[ "$GITHUB_WORKFLOW" ]]; then
# We only want to do this if it is a CI machine.
@@ -44,12 +65,16 @@ 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}
fi # [[ "$GITHUB_WORKFLOW" ]]
! command -v clang > /dev/null 2>&1 && \
sudo ln -s /usr/bin/clang-${GITHUB_CLANG_VERSION} /usr/bin/clang && \
sudo ln -s /usr/bin/clang++-${GITHUB_CLANG_VERSION} /usr/bin/clang++
fi # [[ "$GITHUB_WORKFLOW" ]]
elif [[ "$os_name" == "Darwin" ]]; then
if [[ ! "$GITHUB_WORKFLOW" ]]; then
if [ ! command -v brew > /dev/null 2>&1 ]; then
@@ -57,14 +82,7 @@ elif [[ "$os_name" == "Darwin" ]]; then
exit 1
fi
fi
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
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=true brew install autoconf automake libx11 libxext libxrandr llvm@${LLVM_VERSION} ninja meson pkg-config libxshmfence
fi # [[ "$os_name" == x ]]
LOCAL_LDFLAGS=${LDFLAGS}
@@ -88,10 +106,8 @@ fi
if [[ "$CHECKOUT_MESA" = "true" ]]; then
rm -rf ${MESA_DIR}
#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
git clone https://gitlab.freedesktop.org/mesa/mesa.git
if [[ "${MESA_DIR}" != "${ORIG_DIR}/mesa" ]]; then
mv mesa ${MESA_DIR}
fi
fi
@@ -100,10 +116,12 @@ pushd .
cd ${MESA_DIR}
# Need >= 24 to have llvmpipe for swrast. llvmpipe is needed for GL >= 4.1.
git checkout mesa-24.2.1
git checkout mesa-${MESA_VERSION}
mkdir -p out
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"
@@ -118,8 +136,16 @@ 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="$(pwd)/out" -Dglx=xlib -Dosmesa=true -Dgallium-drivers=llvmpipe,swrast -Dvulkan-drivers=swrast
meson setup --wipe builddir/ -Dprefix="${MESA_DIR}/out" -Dglx=xlib -Dosmesa=true -Dgallium-drivers=llvmpipe,swrast -Dvulkan-drivers=swrast
CXX=${LOCAL_CXX} CC=${LOCAL_CC} PATH=${LOCAL_PATH} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
meson install -C builddir/
# Disable python venv
deactivate
popd
if [[ "$GITHUB_WORKFLOW" ]]; then
set +e
fi
set +x

66917
third_party/perfetto/perfetto/perfetto.cc vendored Normal file

File diff suppressed because it is too large Load Diff

175338
third_party/perfetto/perfetto/perfetto.h vendored Normal file

File diff suppressed because it is too large Load Diff

30
third_party/perfetto/tnt/CMakeLists.txt vendored Normal file
View File

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

8
third_party/perfetto/tnt/README.txt vendored Normal file
View File

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