Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43ab60521c | ||
|
|
b7c685647a | ||
|
|
d45a5cc926 | ||
|
|
361ba2afea | ||
|
|
78419cd992 | ||
|
|
cc7361dba5 | ||
|
|
5c0841ff56 | ||
|
|
10af183756 | ||
|
|
56e0e9a424 | ||
|
|
86a500c846 | ||
|
|
5b3f13fc1d | ||
|
|
1b1dfaa57c | ||
|
|
bf8c84bbe5 | ||
|
|
fb179cabbc | ||
|
|
a80ea743e8 | ||
|
|
97f8106909 | ||
|
|
d455899b93 | ||
|
|
63fe439a5e | ||
|
|
7847220aba | ||
|
|
d76ae4395b | ||
|
|
f994cb58ce | ||
|
|
52eb682498 | ||
|
|
5a5168a191 | ||
|
|
e85dfe75c8 | ||
|
|
6beb40b0a1 | ||
|
|
5cb96e5732 | ||
|
|
927aa57a4e | ||
|
|
36e775902d | ||
|
|
53e28f3b33 |
@@ -26,4 +26,4 @@ jobs:
|
||||
path: out/filament-release-darwin.tgz
|
||||
- name: Check public headers
|
||||
run: |
|
||||
build/common/check-headers.sh out/release/filament/include
|
||||
test/check-headers/test.sh out/release/filament/include
|
||||
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
- name: Prerequisites
|
||||
id: prereqs
|
||||
run: |
|
||||
bash test/utils/get_mesa.sh
|
||||
bash build/common/get-mesa.sh
|
||||
pip install tifffile numpy
|
||||
- name: Run Test
|
||||
run: bash test/renderdiff/test.sh
|
||||
@@ -153,8 +153,8 @@ jobs:
|
||||
- name: Run test
|
||||
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
|
||||
|
||||
code-correctness:
|
||||
name: code-correctness
|
||||
test-code-correctness:
|
||||
name: test-code-correctness
|
||||
runs-on: 'macos-14-xlarge'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
@@ -65,13 +65,9 @@ jobs:
|
||||
|
||||
build-mac:
|
||||
name: build-mac
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: macos-14-xlarge
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'desktop'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-14-xlarge, ubuntu-22.04-32core]
|
||||
|
||||
steps:
|
||||
- name: Decide Git ref
|
||||
id: git_ref
|
||||
@@ -167,6 +163,9 @@ jobs:
|
||||
mv out/filamat-android-release.aar out/filamat-${TAG}-android.aar
|
||||
mv out/gltfio-android-release.aar out/gltfio-${TAG}-android.aar
|
||||
mv out/filament-utils-android-release.aar out/filament-utils-${TAG}-android.aar
|
||||
cd out/android-release/filament
|
||||
tar -czf ../../filament-${TAG}-android-native.tgz .
|
||||
cd ../../..
|
||||
- name: Sign sample-gltf-viewer
|
||||
run: |
|
||||
echo "${APK_KEYSTORE_BASE64}" > filament.jks.base64
|
||||
@@ -188,7 +187,7 @@ jobs:
|
||||
script: |
|
||||
const upload = require('./build/common/upload-release-assets');
|
||||
const { TAG } = process.env;
|
||||
const globber = await glob.create(['out/*.aar', 'out/*.apk'].join('\n'));
|
||||
const globber = await glob.create(['out/*.aar', 'out/*.apk', 'out/*.tgz'].join('\n'));
|
||||
await upload({ github, context }, await globber.glob(), TAG);
|
||||
|
||||
build-ios:
|
||||
|
||||
@@ -7,4 +7,3 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.60.1
|
||||
|
||||
|
||||
## v1.60.0
|
||||
|
||||
- materials: remove dependence on per-view descset layout from filamat. [⚠️ **New Material Version**]
|
||||
|
||||
@@ -151,7 +151,7 @@ function print_fgviewer_help {
|
||||
}
|
||||
|
||||
# Unless explicitly specified, NDK version will be selected as highest available version within same major release chain
|
||||
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION:-$(cat `dirname $0`/build/android/ndk.version | cut -f 1 -d ".")}
|
||||
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION:-$(cat `dirname $0`/build/common/versions | grep GITHUB_NDK_VERSION | sed s/GITHUB_NDK_VERSION=//g | cut -f 1 -d ".")}
|
||||
|
||||
# Requirements
|
||||
CMAKE_MAJOR=3
|
||||
@@ -463,16 +463,6 @@ function ensure_android_build {
|
||||
echo "Error: Android NDK side-by-side version ${FILAMENT_NDK_VERSION} or compatible must be installed, exiting"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local cmake_version=$(cmake --version)
|
||||
if [[ "${cmake_version}" =~ ([0-9]+)\.([0-9]+)\.[0-9]+ ]]; then
|
||||
if [[ "${BASH_REMATCH[1]}" -lt "${CMAKE_MAJOR}" ]] || \
|
||||
[[ "${BASH_REMATCH[2]}" -lt "${CMAKE_MINOR}" ]]; then
|
||||
echo "Error: cmake version ${CMAKE_MAJOR}.${CMAKE_MINOR}+ is required," \
|
||||
"${BASH_REMATCH[1]}.${BASH_REMATCH[2]} installed, exiting"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function build_android {
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
#!/usr/bin/bash
|
||||
|
||||
set -x
|
||||
set -e
|
||||
if [[ "$GITHUB_WORKFLOW" ]]; then
|
||||
set -e
|
||||
set -x
|
||||
fi
|
||||
|
||||
OS_NAME=$(uname -s)
|
||||
@@ -35,7 +35,7 @@ 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
|
||||
if ! python3 -m pip show -q "${cmd}" >/dev/null 2>&1; then
|
||||
python3 -m pip install ${cmd}
|
||||
fi
|
||||
done
|
||||
@@ -145,6 +145,6 @@ deactivate
|
||||
popd
|
||||
|
||||
if [[ "$GITHUB_WORKFLOW" ]]; then
|
||||
set +e
|
||||
set +x
|
||||
fi
|
||||
set +x
|
||||
set +e
|
||||
@@ -1023,8 +1023,12 @@ samplerCubemap | Cubemap texture
|
||||
[Table [materialParamsTypes]: Material parameter types]
|
||||
|
||||
Samplers
|
||||
: Sampler types can also specify a `format` which can be either `int` or `float` (defaults to
|
||||
`float`).
|
||||
: Sampler types can specify additional options:
|
||||
|
||||
- `format`: either `int` or `float` (defaults to `float`).
|
||||
- `stages`: array of strings containing the list of shader stages this
|
||||
sampler can be accessed from. Each entry must be either `vertex` or
|
||||
`fragment` (defaults to both).
|
||||
|
||||
Arrays
|
||||
: A parameter can define an array of values by appending `[size]` after the type name, where
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
- [Metal](./notes/metal_debugging.md)
|
||||
- [Vulkan](./notes/vulkan_debugging.md)
|
||||
- [SPIR-V](./notes/spirv_debugging.md)
|
||||
- [Running with ASAN and UBSAN](./notes/asan_ubsan.md)
|
||||
- [Using Instruments on macOS](./notes/instruments.md)
|
||||
- [Libraries](./notes/libs.md)
|
||||
- [bluegl](./dup/bluegl.md)
|
||||
- [bluevk](./dup/bluevk.md)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Running with ASAN/UBSAN
|
||||
|
||||
## Enabling
|
||||
|
||||
When building though build.sh, pass the `-b` flag. This sets the cmake variable
|
||||
`FILAMENT_ENABLE_ASAN_UBSAN=ON` which eventually passes `"-fsanitize=address -fsanitize=undefined"`
|
||||
to all compile and link operations.
|
||||
|
||||
If building through CMake directly, or an IDE like CLion that doesn't use build.sh, instead pass
|
||||
`-DFILAMENT_ENABLE_ASAN_UBSAN=ON` to cmake in order to get the same result.
|
||||
|
||||
## Getting memory leak detection on Mac
|
||||
|
||||
Memory leak detection isn't enabled by default on MacOS. There are two issues to address, first is
|
||||
using a version of clang that supports memory leak detection and second is enabling it at runtime.
|
||||
|
||||
The version of clang distributed by Apple (with a version like "Apple clang version 16.0.0") doesn't
|
||||
currently support leak detection at all. Instead you will need to get or build a different LLVM,
|
||||
such as the one distributed through homebrew and get CMake to use that instead.
|
||||
|
||||
Then during runtime you'll need to have the environment variable `ASAN_OPTIONS` include the option
|
||||
`detect_leaks=1`. Multiple `ASAN_OPTIONS` values are concatenated with `:`.
|
||||
|
||||
## Getting memory leak output in CLion
|
||||
|
||||
### Setting variables
|
||||
|
||||
Under `Settings | Build, Execution, Deployment | Dynamic Analysis Tools | Sanitizers` there is an
|
||||
ASAN Settings field that overrides whatever other `ASAN_OPTIONS` you might set elsewhere, so you
|
||||
must use that instead of setting it through your Run/Debug Configuration.
|
||||
|
||||
To pass `-DFILAMENT_ENABLE_ASAN_UBSAN=ON` to CMake you'll want to create a new CMake Profile and
|
||||
pass it as a CMake argument.
|
||||
|
||||
### Avoiding losing output
|
||||
|
||||
CMake will consume ASAN output and display it through a separate "Sanitizers" tab. Unfortunately
|
||||
certain leak detection errors that interrupt the executable seem to not show up in this tab, but are
|
||||
still removed from the user-visible console output. If this is happening and you need to see the
|
||||
unfiltered console output you'll need to go to `Settings | Build, Execution, Deployment | Dynamic
|
||||
Analysis Tools | Sanitizers` and uncheck "Use visual representation for Sanitizer's output".
|
||||
@@ -0,0 +1,36 @@
|
||||
# Using Instruments on macOS
|
||||
|
||||
When running a binary under Instruments on macOS, you may run into the following issue when
|
||||
launching or attaching to an executable:
|
||||
|
||||
```
|
||||
Failed to gain authorization
|
||||
Recovery Suggestion: Target binary needs to be debuggable and signed with 'get-task-allow'
|
||||
```
|
||||
|
||||
This is a security precaution; the solution is to code sign the binary with the
|
||||
`com.apple.security.get-task-allow` entitlement.
|
||||
|
||||
1. Create an `entitlements.plist` file with the following contents:
|
||||
|
||||
```
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.get-task-allow</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
2. Run the following command:
|
||||
|
||||
```
|
||||
codesign -s - --entitlements entitlements.plist <binary>
|
||||
```
|
||||
|
||||
Replace `<binary>` with the name of the binary, for example: `out/cmake-debug/samples/gltf_viewer`.
|
||||
|
||||
Afterwards, you should be able to successfully launch and attach to the executable using
|
||||
Instruments.
|
||||
@@ -5,18 +5,6 @@ set(TARGET backend)
|
||||
set(PUBLIC_HDR_DIR include)
|
||||
set(GENERATION_ROOT ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# ==================================================================================================
|
||||
# Compilation options
|
||||
# ==================================================================================================
|
||||
#
|
||||
set(BACKEND_SANITIZATION "" CACHE STRING "Sanitization option")
|
||||
set_property(CACHE BACKEND_SANITIZATION PROPERTY STRINGS ";ASAN")
|
||||
|
||||
set(BACKEND_SANITIZERS)
|
||||
if (BACKEND_SANITIZATION STREQUAL "ASAN")
|
||||
set(BACKEND_SANITIZERS -fsanitize=address)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Sources and headers
|
||||
# ==================================================================================================
|
||||
@@ -484,7 +472,6 @@ target_compile_options(${TARGET} PRIVATE
|
||||
${OSMESA_COMPILE_FLAGS}
|
||||
$<$<CONFIG:Release>:${OPTIMIZATION_FLAGS}>
|
||||
$<$<AND:$<PLATFORM_ID:Darwin>,$<CONFIG:Release>>:${DARWIN_OPTIMIZATION_FLAGS}>
|
||||
${BACKEND_SANITIZERS}
|
||||
)
|
||||
|
||||
if (FILAMENT_SUPPORTS_METAL)
|
||||
@@ -495,8 +482,6 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
target_compile_definitions(${TARGET} PRIVATE $<$<BOOL:${FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING}>:FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING>)
|
||||
endif()
|
||||
|
||||
target_link_options(${TARGET} PRIVATE ${BACKEND_SANITIZERS})
|
||||
|
||||
target_link_libraries(${TARGET} PRIVATE
|
||||
${OSMESA_LINKER_FLAGS}
|
||||
$<$<AND:$<PLATFORM_ID:Linux>,$<CONFIG:Release>>:${LINUX_LINKER_OPTIMIZATION_FLAGS}>
|
||||
@@ -566,8 +551,6 @@ if (APPLE AND NOT IOS)
|
||||
test/test_RenderExternalImage.cpp)
|
||||
add_library(backend_test STATIC ${BACKEND_TEST_SRC})
|
||||
target_link_libraries(backend_test PUBLIC ${BACKEND_TEST_LIBS})
|
||||
target_compile_options(backend_test PRIVATE ${BACKEND_SANITIZERS})
|
||||
target_link_options(backend_test PRIVATE ${BACKEND_SANITIZERS})
|
||||
|
||||
set(BACKEND_TEST_DEPS
|
||||
OSDependent
|
||||
@@ -606,7 +589,6 @@ if (APPLE AND NOT IOS)
|
||||
# linker from removing "unused" symbols.
|
||||
target_link_libraries(backend_test_mac PRIVATE -force_load backend_test)
|
||||
set_target_properties(backend_test_mac PROPERTIES FOLDER Tests)
|
||||
target_link_options(backend_test_mac PRIVATE ${BACKEND_SANITIZERS})
|
||||
|
||||
# This is needed after XCode 15.3
|
||||
set_target_properties(backend_test_mac PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
@@ -616,8 +598,6 @@ endif()
|
||||
|
||||
if (LINUX)
|
||||
add_executable(backend_test_linux test/linux_runner.cpp ${BACKEND_TEST_SRC})
|
||||
target_compile_options(backend_test_linux PRIVATE ${BACKEND_SANITIZERS})
|
||||
target_link_options(backend_test_linux PRIVATE ${BACKEND_SANITIZERS})
|
||||
target_link_libraries(backend_test_linux PRIVATE ${BACKEND_TEST_LIBS})
|
||||
set_target_properties(backend_test_linux PROPERTIES FOLDER Tests)
|
||||
endif()
|
||||
|
||||
@@ -20,10 +20,15 @@
|
||||
#define TNT_FILAMENT_BACKEND_BUFFERDESCRIPTOR_H
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace utils::io {
|
||||
class ostream;
|
||||
} // namespace utils::io
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class CallbackHandler;
|
||||
@@ -89,8 +94,8 @@ public:
|
||||
* @param callback A callback used to release the CPU buffer from this BufferDescriptor
|
||||
* @param user An opaque user pointer passed to the callback function when it's called
|
||||
*/
|
||||
BufferDescriptor(void const* buffer, size_t size,
|
||||
Callback callback = nullptr, void* user = nullptr) noexcept
|
||||
BufferDescriptor(void const* buffer, size_t const size,
|
||||
Callback const callback = nullptr, void* user = nullptr) noexcept
|
||||
: buffer(const_cast<void*>(buffer)), size(size), mCallback(callback), mUser(user) {
|
||||
}
|
||||
|
||||
@@ -98,11 +103,12 @@ public:
|
||||
* Creates a BufferDescriptor that references a CPU memory-buffer
|
||||
* @param buffer Memory address of the CPU buffer to reference
|
||||
* @param size Size of the CPU buffer in bytes
|
||||
* @param handler A custom handler for the callback
|
||||
* @param callback A callback used to release the CPU buffer from this BufferDescriptor
|
||||
* @param user An opaque user pointer passed to the callback function when it's called
|
||||
*/
|
||||
BufferDescriptor(void const* buffer, size_t size,
|
||||
CallbackHandler* handler, Callback callback, void* user = nullptr) noexcept
|
||||
BufferDescriptor(void const* buffer, size_t const size,
|
||||
CallbackHandler* handler, Callback const callback, void* user = nullptr) noexcept
|
||||
: buffer(const_cast<void*>(buffer)), size(size),
|
||||
mCallback(callback), mUser(user), mHandler(handler) {
|
||||
}
|
||||
@@ -116,8 +122,9 @@ public:
|
||||
*
|
||||
* @param buffer Memory address of the CPU buffer to reference
|
||||
* @param size Size of the CPU buffer in bytes
|
||||
* @param data A pointer to the data
|
||||
* @param handler Handler to use to dispatch the callback, or nullptr for the default handler
|
||||
* @return a new BufferDescriptor
|
||||
* @return A new BufferDescriptor
|
||||
*/
|
||||
template<typename T, void(T::*method)(void const*, size_t)>
|
||||
static BufferDescriptor make(void const* buffer, size_t size, T* data,
|
||||
@@ -164,7 +171,7 @@ public:
|
||||
* @param callback The new callback function
|
||||
* @param user An opaque user pointer passed to the callbeck function when it's called
|
||||
*/
|
||||
void setCallback(Callback callback, void* user = nullptr) noexcept {
|
||||
void setCallback(Callback const callback, void* user = nullptr) noexcept {
|
||||
this->mCallback = callback;
|
||||
this->mUser = user;
|
||||
this->mHandler = nullptr;
|
||||
@@ -176,7 +183,7 @@ public:
|
||||
* @param callback The new callback function
|
||||
* @param user An opaque user pointer passed to the callbeck function when it's called
|
||||
*/
|
||||
void setCallback(CallbackHandler* handler, Callback callback, void* user = nullptr) noexcept {
|
||||
void setCallback(CallbackHandler* handler, Callback const callback, void* user = nullptr) noexcept {
|
||||
mCallback = callback;
|
||||
mUser = user;
|
||||
mHandler = handler;
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
#include <backend/DriverApiForward.h>
|
||||
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
|
||||
@@ -28,6 +26,10 @@
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
namespace utils::io {
|
||||
class ostream;
|
||||
} // namespace utils::io
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
void* allocateFromCommandStream(DriverApi& driver, size_t size, size_t alignment) noexcept;
|
||||
|
||||
@@ -25,12 +25,11 @@
|
||||
#include <backend/PresentCallable.h>
|
||||
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/StaticString.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <math/vec4.h>
|
||||
|
||||
@@ -41,6 +40,10 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace utils::io {
|
||||
class ostream;
|
||||
} // namespace utils::io
|
||||
|
||||
/**
|
||||
* Types and enums used by filament's driver.
|
||||
*
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_HANDLE_H
|
||||
#define TNT_FILAMENT_BACKEND_HANDLE_H
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
#include <utils/ostream.h>
|
||||
#endif
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <type_traits> // FIXME: STL headers are not allowed in public headers
|
||||
@@ -27,6 +24,10 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace utils::io {
|
||||
class ostream;
|
||||
} // namespace utils::io
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct HwBufferObject;
|
||||
|
||||
@@ -20,12 +20,14 @@
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace utils::io {
|
||||
class ostream;
|
||||
} // namespace utils::io
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
//! \privatesection
|
||||
|
||||
@@ -24,11 +24,14 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace utils::io {
|
||||
class ostream;
|
||||
} // namespace utils::io
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
|
||||
@@ -149,13 +149,6 @@ public:
|
||||
* - PlatformEGLAndroid
|
||||
*/
|
||||
bool assertNativeWindowIsValid = false;
|
||||
|
||||
/**
|
||||
* The action to take if a Drawable cannot be acquired. If true, the
|
||||
* frame is aborted instead of panic. This is only supported for:
|
||||
* - PlatformMetal
|
||||
*/
|
||||
bool metalDisablePanicOnDrawableFailure = false;
|
||||
};
|
||||
|
||||
Platform() noexcept;
|
||||
|
||||
@@ -20,12 +20,10 @@
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
@@ -33,6 +31,10 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace utils::io {
|
||||
class ostream;
|
||||
} // namespace utils::io
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class Program {
|
||||
|
||||
@@ -19,22 +19,26 @@
|
||||
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <utils/ostream.h>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace utils::io {
|
||||
class ostream;
|
||||
} // namespace utils::io
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
//! \privatesection
|
||||
|
||||
struct TargetBufferInfo {
|
||||
// note: the parameters of this constructor are not in the order of this structure's fields
|
||||
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level, uint16_t layer) noexcept
|
||||
: handle(handle), level(level), layer(layer) {
|
||||
TargetBufferInfo(Handle<HwTexture> handle, uint8_t const level, uint16_t const layer) noexcept
|
||||
: handle(std::move(handle)), level(level), layer(layer) {
|
||||
}
|
||||
|
||||
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level) noexcept
|
||||
TargetBufferInfo(Handle<HwTexture> handle, uint8_t const level) noexcept
|
||||
: handle(handle), level(level) {
|
||||
}
|
||||
|
||||
@@ -70,11 +74,11 @@ private:
|
||||
TargetBufferInfo mInfos[MAX_SUPPORTED_RENDER_TARGET_COUNT];
|
||||
|
||||
public:
|
||||
TargetBufferInfo const& operator[](size_t i) const noexcept {
|
||||
TargetBufferInfo const& operator[](size_t const i) const noexcept {
|
||||
return mInfos[i];
|
||||
}
|
||||
|
||||
TargetBufferInfo& operator[](size_t i) noexcept {
|
||||
TargetBufferInfo& operator[](size_t const i) noexcept {
|
||||
return mInfos[i];
|
||||
}
|
||||
|
||||
|
||||
@@ -39,18 +39,40 @@ public:
|
||||
Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) noexcept override;
|
||||
int getOSVersion() const noexcept override { return 0; }
|
||||
|
||||
/**
|
||||
* Optionally initializes the Metal platform by acquiring resources necessary for rendering.
|
||||
*
|
||||
* This method attempts to acquire a Metal device and command queue, returning true if both are
|
||||
* successfully obtained, or false otherwise. Typically, these objects are acquired when
|
||||
* the Metal backend is initialized. This method allows clients to check for their availability
|
||||
* earlier.
|
||||
*
|
||||
* Calling initialize() is optional and safe to do so multiple times. After initialize() returns
|
||||
* true, subsequent calls will continue to return true but have no effect.
|
||||
*
|
||||
* initialize() must be called from the main thread.
|
||||
*
|
||||
* @returns true if the device and command queue have been successfully obtained; false
|
||||
* otherwise.
|
||||
*/
|
||||
bool initialize() noexcept;
|
||||
|
||||
/**
|
||||
* Obtain the preferred Metal device object for the backend to use.
|
||||
*
|
||||
* On desktop platforms, there may be multiple GPUs suitable for rendering, and this method is
|
||||
* free to decide which one to use. On mobile systems with a single GPU, implementations should
|
||||
* simply return the result of MTLCreateSystemDefaultDevice();
|
||||
*
|
||||
* createDevice is called by the Metal backend from the backend thread.
|
||||
*/
|
||||
virtual void createDevice(MetalDevice& outDevice) noexcept;
|
||||
|
||||
/**
|
||||
* Create a command submission queue on the Metal device object.
|
||||
*
|
||||
* createCommandQueue is called by the Metal backend from the backend thread.
|
||||
*
|
||||
* @param device The device which was returned from createDevice()
|
||||
*/
|
||||
virtual void createCommandQueue(
|
||||
@@ -60,6 +82,8 @@ public:
|
||||
* Obtain a MTLCommandBuffer enqueued on this Platform's MTLCommandQueue. The command buffer is
|
||||
* guaranteed to execute before all subsequent command buffers created either by Filament, or
|
||||
* further calls to this method.
|
||||
*
|
||||
* createAndEnqueueCommandBuffer must be called from the main thread.
|
||||
*/
|
||||
void createAndEnqueueCommandBuffer(MetalCommandBuffer& outCommandBuffer) noexcept;
|
||||
|
||||
@@ -68,6 +92,8 @@ public:
|
||||
*
|
||||
* Each frame rendered requires a CAMetalDrawable texture, which is presented on-screen at the
|
||||
* completion of each frame. These are limited and provided round-robin style by the system.
|
||||
*
|
||||
* setDrawableFailureBehavior must be called from the main thread.
|
||||
*/
|
||||
enum class DrawableFailureBehavior : uint8_t {
|
||||
/**
|
||||
|
||||
@@ -27,8 +27,11 @@
|
||||
#include <utils/Hash.h>
|
||||
#include <utils/PrivateImplementation.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
#include <string>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <utility>
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include "MetalContext.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
|
||||
|
||||
@@ -721,15 +721,25 @@ const char* toString(DescriptorFlags flags) {
|
||||
|
||||
void MetalDriver::createDescriptorSetLayoutR(
|
||||
Handle<HwDescriptorSetLayout> dslh, DescriptorSetLayout&& info) {
|
||||
#if FILAMENT_METAL_DEBUG_LOG == 1
|
||||
const char* labelStr = "";
|
||||
std::visit([&labelStr](auto&& arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, utils::CString> || std::is_same_v<T, utils::StaticString>) {
|
||||
labelStr = arg.c_str();
|
||||
}
|
||||
}, info.label);
|
||||
std::sort(info.bindings.begin(), info.bindings.end(),
|
||||
[](const auto& a, const auto& b) { return a.binding < b.binding; });
|
||||
DEBUG_LOG("createDescriptorSetLayoutR(dslh = %d, info = {\n", dslh.getId());
|
||||
DEBUG_LOG("createDescriptorSetLayoutR(dslh = %d, info = { label = %s,\n", dslh.getId(),
|
||||
labelStr);
|
||||
for (size_t i = 0; i < info.bindings.size(); i++) {
|
||||
DEBUG_LOG(" {binding = %d, type = %s, count = %d, stage = %s, flags = %s},\n",
|
||||
info.bindings[i].binding, toString(info.bindings[i].type), info.bindings[i].count,
|
||||
toString(info.bindings[i].stageFlags), toString(info.bindings[i].flags));
|
||||
}
|
||||
DEBUG_LOG("})\n");
|
||||
#endif
|
||||
construct_handle<MetalDescriptorSetLayout>(dslh, std::move(info));
|
||||
}
|
||||
|
||||
|
||||
@@ -1382,6 +1382,9 @@ id<MTLArgumentEncoder> MetalDescriptorSetLayout::getArgumentEncoderSlow(id<MTLDe
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (arguments.count == 0) {
|
||||
return nil;
|
||||
}
|
||||
return [device newArgumentEncoderWithArguments:arguments];
|
||||
}
|
||||
|
||||
@@ -1442,6 +1445,9 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
|
||||
|
||||
id<MTLArgumentEncoder> encoder =
|
||||
layout->getArgumentEncoder(context.device, stage, textureTypes);
|
||||
if (!encoder) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
{
|
||||
ScopedAllocationTimer timer("descriptor_set");
|
||||
|
||||
@@ -20,18 +20,19 @@
|
||||
#include <Metal/Metal.h>
|
||||
|
||||
#include "private/backend/Driver.h"
|
||||
#include "backend/Program.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <memory>
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <utils/Hash.h>
|
||||
#include <utils/Invocable.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
|
||||
|
||||
@@ -24,14 +24,22 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct PlatformMetalImpl {
|
||||
std::mutex mLock; // locks mDevice and mCommandQueue
|
||||
id<MTLDevice> mDevice = nil;
|
||||
id<MTLCommandQueue> mCommandQueue = nil;
|
||||
|
||||
// read form driver thread, read/written to from client thread
|
||||
std::atomic<PlatformMetal::DrawableFailureBehavior> mDrawableFailureBehavior =
|
||||
PlatformMetal::DrawableFailureBehavior::PANIC;
|
||||
|
||||
// These methods must be called with mLock held
|
||||
void createDeviceImpl(MetalDevice& outDevice);
|
||||
void createCommandQueueImpl(MetalDevice& device, MetalCommandQueue& outCommandQueue);
|
||||
};
|
||||
|
||||
Platform* createDefaultMetalPlatform() {
|
||||
@@ -45,13 +53,62 @@ PlatformMetal::~PlatformMetal() noexcept {
|
||||
}
|
||||
|
||||
Driver* PlatformMetal::createDriver(void* /*sharedContext*/, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
pImpl->mDrawableFailureBehavior = driverConfig.metalDisablePanicOnDrawableFailure
|
||||
? DrawableFailureBehavior::ABORT_FRAME
|
||||
: DrawableFailureBehavior::PANIC;
|
||||
return MetalDriverFactory::create(this, driverConfig);
|
||||
}
|
||||
|
||||
|
||||
bool PlatformMetal::initialize() noexcept {
|
||||
std::lock_guard<std::mutex> lock(pImpl->mLock);
|
||||
|
||||
MetalDevice device{};
|
||||
pImpl->createDeviceImpl(device);
|
||||
if (device.device == nil) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MetalCommandQueue commandQueue{};
|
||||
pImpl->createCommandQueueImpl(device, commandQueue);
|
||||
if (commandQueue.commandQueue == nil) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlatformMetal::createDevice(MetalDevice& outDevice) noexcept {
|
||||
std::lock_guard<std::mutex> lock(pImpl->mLock);
|
||||
pImpl->createDeviceImpl(outDevice);
|
||||
}
|
||||
|
||||
void PlatformMetal::createCommandQueue(
|
||||
MetalDevice& device, MetalCommandQueue& outCommandQueue) noexcept {
|
||||
std::lock_guard<std::mutex> lock(pImpl->mLock);
|
||||
pImpl->createCommandQueueImpl(device, outCommandQueue);
|
||||
}
|
||||
|
||||
void PlatformMetal::createAndEnqueueCommandBuffer(MetalCommandBuffer& outCommandBuffer) noexcept {
|
||||
std::lock_guard<std::mutex> lock(pImpl->mLock);
|
||||
id<MTLCommandBuffer> commandBuffer = [pImpl->mCommandQueue commandBuffer];
|
||||
[commandBuffer enqueue];
|
||||
outCommandBuffer.commandBuffer = commandBuffer;
|
||||
}
|
||||
|
||||
void PlatformMetal::setDrawableFailureBehavior(DrawableFailureBehavior behavior) noexcept {
|
||||
pImpl->mDrawableFailureBehavior = behavior;
|
||||
}
|
||||
|
||||
PlatformMetal::DrawableFailureBehavior PlatformMetal::getDrawableFailureBehavior() const noexcept {
|
||||
return pImpl->mDrawableFailureBehavior;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
void PlatformMetalImpl::createDeviceImpl(MetalDevice& outDevice) {
|
||||
if (mDevice) {
|
||||
outDevice.device = mDevice;
|
||||
return;
|
||||
}
|
||||
|
||||
id<MTLDevice> result;
|
||||
|
||||
#if !defined(FILAMENT_IOS)
|
||||
@@ -77,27 +134,17 @@ void PlatformMetal::createDevice(MetalDevice& outDevice) noexcept {
|
||||
<< utils::io::endl;
|
||||
|
||||
outDevice.device = result;
|
||||
mDevice = result;
|
||||
}
|
||||
|
||||
void PlatformMetal::createCommandQueue(
|
||||
MetalDevice& device, MetalCommandQueue& outCommandQueue) noexcept {
|
||||
pImpl->mCommandQueue = [device.device newCommandQueue];
|
||||
pImpl->mCommandQueue.label = @"Filament";
|
||||
outCommandQueue.commandQueue = pImpl->mCommandQueue;
|
||||
}
|
||||
|
||||
void PlatformMetal::createAndEnqueueCommandBuffer(MetalCommandBuffer& outCommandBuffer) noexcept {
|
||||
id<MTLCommandBuffer> commandBuffer = [pImpl->mCommandQueue commandBuffer];
|
||||
[commandBuffer enqueue];
|
||||
outCommandBuffer.commandBuffer = commandBuffer;
|
||||
}
|
||||
|
||||
void PlatformMetal::setDrawableFailureBehavior(DrawableFailureBehavior behavior) noexcept {
|
||||
pImpl->mDrawableFailureBehavior = behavior;
|
||||
}
|
||||
|
||||
PlatformMetal::DrawableFailureBehavior PlatformMetal::getDrawableFailureBehavior() const noexcept {
|
||||
return pImpl->mDrawableFailureBehavior;
|
||||
void PlatformMetalImpl::createCommandQueueImpl(MetalDevice& device, MetalCommandQueue& outCommandQueue) {
|
||||
if (mCommandQueue) {
|
||||
outCommandQueue.commandQueue = mCommandQueue;
|
||||
return;
|
||||
}
|
||||
mCommandQueue = [device.device newCommandQueue];
|
||||
mCommandQueue.label = @"Filament";
|
||||
outCommandQueue.commandQueue = mCommandQueue;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -542,6 +542,13 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
|
||||
} else if (strstr(renderer, "Intel")) {
|
||||
// Intel GPU
|
||||
bugs->vao_doesnt_store_element_array_buffer_binding = true;
|
||||
|
||||
if (strstr(renderer, "Mesa")) {
|
||||
// Mesa Intel driver on Linux/Android
|
||||
// Renderer of the form [Mesa Intel(R) HD Graphics 505 (APL 3)]
|
||||
// b/405252622
|
||||
bugs->disable_invalidate_framebuffer = true;
|
||||
}
|
||||
} else if (strstr(renderer, "PowerVR")) {
|
||||
// PowerVR GPU
|
||||
// On PowerVR (Rogue GE8320) glFlush doesn't seem to do anything, in particular,
|
||||
|
||||
@@ -54,7 +54,7 @@ struct PlatformCocoaGLImpl {
|
||||
CVOpenGLTextureCacheRef mTextureCache = nullptr;
|
||||
std::unique_ptr<CocoaExternalImage::SharedGl> mExternalImageSharedGl;
|
||||
void updateOpenGLContext(NSView *nsView, bool resetView, bool clearView);
|
||||
struct ExternalImageCocoaGL : public Platform::ExternalImage {
|
||||
struct ExternalImageCocoaGL final : public Platform::ExternalImage {
|
||||
CVPixelBufferRef cvBuffer;
|
||||
protected:
|
||||
~ExternalImageCocoaGL() noexcept final;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
@@ -502,7 +503,6 @@ size_t WebGPUDescriptorSet::countEntitiesWithDynamicOffsets() const {
|
||||
return mEntriesByBindingWithDynamicOffsets.count();
|
||||
}
|
||||
|
||||
// From createTextureR
|
||||
WGPUTexture::WGPUTexture(SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
|
||||
uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage,
|
||||
wgpu::Device const& device) noexcept {
|
||||
@@ -516,6 +516,7 @@ WGPUTexture::WGPUTexture(SamplerType target, uint8_t levels, TextureFormat forma
|
||||
// First, the texture aspect, starting with the defaults/basic configuration
|
||||
mUsage = fToWGPUTextureUsage(usage);
|
||||
mFormat = fToWGPUTextureFormat(format);
|
||||
mAspect = fToWGPUTextureViewAspect(usage, format);
|
||||
wgpu::TextureDescriptor textureDescriptor{
|
||||
.label = getUserTextureLabel(target),
|
||||
.usage = mUsage,
|
||||
@@ -560,13 +561,12 @@ WGPUTexture::WGPUTexture(SamplerType target, uint8_t levels, TextureFormat forma
|
||||
mTexView = makeTextureView(0, levels, target);
|
||||
}
|
||||
|
||||
// From createTextureViewR
|
||||
WGPUTexture::WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount) noexcept {
|
||||
mTexture = src->mTexture;
|
||||
mTexView = makeTextureView(baseLevel, levelCount, target);
|
||||
}
|
||||
|
||||
wgpu::TextureUsage WGPUTexture::fToWGPUTextureUsage(const TextureUsage& fUsage) {
|
||||
wgpu::TextureUsage WGPUTexture::fToWGPUTextureUsage(TextureUsage const& fUsage) {
|
||||
wgpu::TextureUsage retUsage = wgpu::TextureUsage::None;
|
||||
|
||||
// Basing this mapping off of VulkanTexture.cpp's getUsage func and suggestions from Gemini
|
||||
@@ -614,8 +614,9 @@ wgpu::TextureUsage WGPUTexture::fToWGPUTextureUsage(const TextureUsage& fUsage)
|
||||
// PROTECTED
|
||||
return retUsage;
|
||||
}
|
||||
wgpu::TextureFormat WGPUTexture::fToWGPUTextureFormat(const TextureFormat& fUsage) {
|
||||
switch (fUsage) {
|
||||
|
||||
wgpu::TextureFormat WGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFormat) {
|
||||
switch (fFormat) {
|
||||
case filament::backend::TextureFormat::R8:
|
||||
return wgpu::TextureFormat::R8Unorm;
|
||||
case filament::backend::TextureFormat::R8_SNORM:
|
||||
@@ -854,24 +855,57 @@ wgpu::TextureFormat WGPUTexture::fToWGPUTextureFormat(const TextureFormat& fUsag
|
||||
}
|
||||
}
|
||||
|
||||
wgpu::TextureAspect WGPUTexture::fToWGPUTextureViewAspect(TextureUsage const& fUsage,
|
||||
TextureFormat const& fFormat) {
|
||||
|
||||
const bool isDepth = any(fUsage & TextureUsage::DEPTH_ATTACHMENT);
|
||||
const bool isStencil = any(fUsage & TextureUsage::STENCIL_ATTACHMENT);
|
||||
const bool isColor = any(fUsage & TextureUsage::COLOR_ATTACHMENT);
|
||||
const bool isSample = (fUsage == TextureUsage::SAMPLEABLE);
|
||||
|
||||
if (isDepth && !isColor && !isStencil) {
|
||||
return wgpu::TextureAspect::DepthOnly;
|
||||
}
|
||||
|
||||
if (isStencil && !isColor && !isDepth) {
|
||||
return wgpu::TextureAspect::StencilOnly;
|
||||
}
|
||||
|
||||
if (fFormat == filament::backend::TextureFormat::DEPTH32F ||
|
||||
fFormat == filament::backend::TextureFormat::DEPTH24 ||
|
||||
fFormat == filament::backend::TextureFormat::DEPTH16) {
|
||||
return wgpu::TextureAspect::DepthOnly;
|
||||
}
|
||||
|
||||
if (fFormat == filament::backend::TextureFormat::STENCIL8) {
|
||||
return wgpu::TextureAspect::StencilOnly;
|
||||
}
|
||||
|
||||
if (fFormat == filament::backend::TextureFormat::DEPTH24_STENCIL8 ||
|
||||
fFormat == filament::backend::TextureFormat::DEPTH32F_STENCIL8) {
|
||||
if (isSample) {
|
||||
return wgpu::TextureAspect::DepthOnly;
|
||||
}
|
||||
}
|
||||
|
||||
return wgpu::TextureAspect::All;
|
||||
}
|
||||
|
||||
wgpu::TextureView WGPUTexture::makeTextureView(const uint8_t& baseLevel, const uint8_t& levelCount,
|
||||
SamplerType target) {
|
||||
// starting with the defaults/basic configuration
|
||||
|
||||
wgpu::TextureViewDescriptor textureViewDescriptor{
|
||||
.label = getUserTextureViewLabel(target),
|
||||
.format = mFormat,
|
||||
// dimension depends on target and is set below
|
||||
.baseMipLevel = baseLevel,
|
||||
.mipLevelCount = levelCount,
|
||||
// baseArrayLayer is required, making a guess
|
||||
// TODO: check if this baseArrayLayer assumption is correct
|
||||
.baseArrayLayer = 0,
|
||||
.arrayLayerCount = mArrayLayerCount,
|
||||
// Have not found an analog to aspect in other drivers, but ALL should be unrestrictive.
|
||||
// TODO Can we make this better?
|
||||
.aspect = wgpu::TextureAspect::All,
|
||||
.aspect = mAspect,
|
||||
.usage = mUsage
|
||||
};
|
||||
// adjust for specific cases
|
||||
|
||||
switch (target) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
textureViewDescriptor.dimension = wgpu::TextureViewDimension::e2D;
|
||||
|
||||
@@ -74,8 +74,8 @@ public:
|
||||
|
||||
private:
|
||||
// TODO: can we do better in terms on heap management.
|
||||
std::vector<wgpu::VertexBufferLayout> mVertexBufferLayout {};
|
||||
std::vector<std::vector<wgpu::VertexAttribute>> mAttributes {};
|
||||
std::vector<wgpu::VertexBufferLayout> mVertexBufferLayout{};
|
||||
std::vector<std::vector<wgpu::VertexAttribute>> mAttributes{};
|
||||
};
|
||||
|
||||
struct WGPUVertexBuffer : public HwVertexBuffer {
|
||||
@@ -145,8 +145,8 @@ public:
|
||||
[[nodiscard]] uint32_t const* setDynamicOffsets(uint32_t const* offsets);
|
||||
[[nodiscard]] bool getIsLocked() const { return mBindGroup != nullptr; }
|
||||
[[nodiscard]] size_t countEntitiesWithDynamicOffsets() const;
|
||||
private:
|
||||
|
||||
private:
|
||||
static wgpu::Buffer sDummyUniformBuffer;
|
||||
static wgpu::Texture sDummyTexture;
|
||||
static wgpu::TextureView sDummyTextureView;
|
||||
@@ -161,10 +161,10 @@ private:
|
||||
// Also storing the wgpu ObjectBase takes care of ownership challenges in theory
|
||||
wgpu::BindGroupLayout mLayout = nullptr;
|
||||
static constexpr uint8_t INVALID_INDEX = MAX_DESCRIPTOR_COUNT + 1;
|
||||
std::array<uint8_t, MAX_DESCRIPTOR_COUNT> mEntryIndexByBinding {};
|
||||
std::array<uint8_t, MAX_DESCRIPTOR_COUNT> mEntryIndexByBinding{};
|
||||
std::vector<wgpu::BindGroupEntry> mEntriesSortedByBinding;
|
||||
std::bitset<MAX_DESCRIPTOR_COUNT> mEntriesByBindingWithDynamicOffsets {};
|
||||
std::bitset<MAX_DESCRIPTOR_COUNT> mEntriesByBindingAdded {};
|
||||
std::bitset<MAX_DESCRIPTOR_COUNT> mEntriesByBindingWithDynamicOffsets{};
|
||||
std::bitset<MAX_DESCRIPTOR_COUNT> mEntriesByBindingAdded{};
|
||||
std::vector<uint32_t> mDynamicOffsets;
|
||||
wgpu::BindGroup mBindGroup = nullptr;
|
||||
};
|
||||
@@ -177,11 +177,14 @@ public:
|
||||
|
||||
WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount) noexcept;
|
||||
|
||||
const wgpu::Texture& getTexture() const { return mTexture; }
|
||||
const wgpu::TextureView& getTexView() const { return mTexView; }
|
||||
[[nodiscard]] const wgpu::Texture& getTexture() const { return mTexture; }
|
||||
[[nodiscard]] const wgpu::TextureView& getTexView() const { return mTexView; }
|
||||
|
||||
// Public to allow checking for support of a texture format
|
||||
static wgpu::TextureFormat fToWGPUTextureFormat(const filament::backend::TextureFormat& fUsage);
|
||||
static wgpu::TextureFormat fToWGPUTextureFormat(
|
||||
filament::backend::TextureFormat const& fFormat);
|
||||
static wgpu::TextureAspect fToWGPUTextureViewAspect(
|
||||
filament::backend::TextureUsage const& fUsage,
|
||||
filament::backend::TextureFormat const& fFormat);
|
||||
|
||||
private:
|
||||
wgpu::TextureView makeTextureView(const uint8_t& baseLevel, const uint8_t& levelCount,
|
||||
@@ -192,9 +195,10 @@ private:
|
||||
wgpu::Texture mTexture = nullptr;
|
||||
wgpu::TextureUsage mUsage = wgpu::TextureUsage::None;
|
||||
wgpu::TextureFormat mFormat = wgpu::TextureFormat::Undefined;
|
||||
wgpu::TextureAspect mAspect = wgpu::TextureAspect::Undefined;
|
||||
uint32_t mArrayLayerCount = 1;
|
||||
wgpu::TextureView mTexView = nullptr;
|
||||
wgpu::TextureUsage fToWGPUTextureUsage(const filament::backend::TextureUsage& fUsage);
|
||||
wgpu::TextureUsage fToWGPUTextureUsage(filament::backend::TextureUsage const& fUsage);
|
||||
};
|
||||
|
||||
struct WGPURenderPrimitive : public HwRenderPrimitive {
|
||||
@@ -252,7 +256,7 @@ private:
|
||||
|
||||
Attachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
|
||||
math::uint2 attachmentSize = {};
|
||||
std::vector<wgpu::RenderPassColorAttachment> colorAttachments {};
|
||||
std::vector<wgpu::RenderPassColorAttachment> colorAttachments{};
|
||||
};
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -211,6 +211,10 @@ uint32_t RenderTargetDump::hash() const {
|
||||
return mInternal->hash();
|
||||
}
|
||||
|
||||
const std::vector<unsigned char>& RenderTargetDump::bytes() const {
|
||||
return mInternal->bytes;
|
||||
}
|
||||
|
||||
bool RenderTargetDump::bytesFilled() const {
|
||||
return mInternal->bytesFilled;
|
||||
}
|
||||
|
||||
@@ -85,6 +85,12 @@ public:
|
||||
* @return The hash of the stored bytes.
|
||||
*/
|
||||
uint32_t hash() const;
|
||||
/**
|
||||
* Gets the bytes of the render target. The hash should usually be preferable for comparisons
|
||||
* but this is available for debugging.
|
||||
* @return The stored bytes.
|
||||
*/
|
||||
const std::vector<unsigned char>& bytes() const;
|
||||
/**
|
||||
* Thread safe as this is backed by an atomic.
|
||||
* Once this returns true it will never return false.
|
||||
|
||||
@@ -32,13 +32,24 @@ do {
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define NONFATAL_FAIL_IF(skipEnvironment, rationale) \
|
||||
do { \
|
||||
SkipEnvironment skip(skipEnvironment); \
|
||||
if (skip.matches()) { \
|
||||
ADD_FAILURE() \
|
||||
<< "Failing test as the " << skip.describe() << "\n" \
|
||||
<< " This test has a known failure where " \
|
||||
<< rationale; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define FAIL_IF(skipEnvironment, rationale) \
|
||||
do { \
|
||||
SkipEnvironment skip(skipEnvironment); \
|
||||
if (skip.matches()) { \
|
||||
GTEST_FAIL() \
|
||||
<< "Failing test as the " << skip.describe() << "\n" \
|
||||
<< " This test should be able to succeed but it needs to fail early because" \
|
||||
<< " This test should be able to succeed but it needs to fail early because " \
|
||||
<< rationale; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
@@ -116,13 +116,23 @@ if __name__ == "__main__":
|
||||
'Remember to sync CMake after running this to '
|
||||
'move the new expected images to the binary '
|
||||
'directory.')
|
||||
parser.add_argument('-r', '--results_path')
|
||||
parser.add_argument('-s', '--source_expected_path', default="./expected_images")
|
||||
parser.add_argument('-r', '--results_path',
|
||||
help='The path with the generated images directory, which should be where '
|
||||
'the test binary was run.')
|
||||
parser.add_argument('-s', '--source_expected_path', default="./expected_images",
|
||||
help='The directory that updated expected images should be written to, '
|
||||
'which should be the source directory copy.')
|
||||
# The mutually exclusive options for how to process the actual images
|
||||
parser.add_argument('-b', '--batch', action='extend', nargs='*')
|
||||
parser.add_argument('-a', '--all', action='store_true')
|
||||
parser.add_argument('-t', '--tests', action='store_true')
|
||||
parser.add_argument('-c', '--compare', action='extend', nargs='*')
|
||||
parser.add_argument('-b', '--batch', action='extend', nargs='*',
|
||||
help='If true copy all actual images to the source expected image '
|
||||
'directory.')
|
||||
parser.add_argument('-a', '--all', action='store_true',
|
||||
help='If true, visually compare all generated images.')
|
||||
parser.add_argument('-t', '--tests', action='store_true',
|
||||
help='If true use a test_detail.xml file that exists in the results_path '
|
||||
'directory to visually compare all images that failed a test.')
|
||||
parser.add_argument('-c', '--compare', action='extend', nargs='*',
|
||||
help='A list of image names to visually compare (without the .png suffix).')
|
||||
|
||||
args = parser.parse_args()
|
||||
if not args.results_path:
|
||||
|
||||
@@ -260,6 +260,8 @@ TEST_F(BlitTest, ColorMinify) {
|
||||
}
|
||||
|
||||
TEST_F(BlitTest, ColorResolve) {
|
||||
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Nothing is drawn, see b/417229577");
|
||||
auto& api = getDriverApi();
|
||||
|
||||
constexpr int kSrcTexWidth = 256;
|
||||
@@ -490,7 +492,7 @@ TEST_F(BlitTest, BlitRegion) {
|
||||
}
|
||||
|
||||
TEST_F(BlitTest, BlitRegionToSwapChain) {
|
||||
FAIL_IF(Backend::VULKAN, "Crashes due to not finding color attachment");
|
||||
FAIL_IF(Backend::VULKAN, "Crashes due to not finding color attachment, see b/417481493");
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Lifetimes.h"
|
||||
#include "Shader.h"
|
||||
#include "SharedShaders.h"
|
||||
#include "Skip.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
namespace test {
|
||||
@@ -159,6 +160,8 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
|
||||
// This test renders two triangles in two separate draw calls. Between the draw calls, a uniform
|
||||
// buffer object is partially updated.
|
||||
TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
|
||||
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"All values including alpha are written as 0, see b/417254943");
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace test {
|
||||
|
||||
TEST_F(BackendTest, FrameScheduledCallback) {
|
||||
SKIP_IF(Backend::OPENGL, "Frame callbacks are unsupported in OpenGL");
|
||||
SKIP_IF(Backend::VULKAN, "Frame callbacks are unsupported in Vulkan, see b/417254479");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
@@ -85,6 +86,7 @@ TEST_F(BackendTest, FrameScheduledCallback) {
|
||||
|
||||
TEST_F(BackendTest, FrameCompletedCallback) {
|
||||
SKIP_IF(Backend::OPENGL, "Frame callbacks are unsupported in OpenGL");
|
||||
SKIP_IF(Backend::VULKAN, "Frame callbacks are unsupported in Vulkan, see b/417254479");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
@@ -99,6 +99,8 @@ struct MaterialParams {
|
||||
// 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.
|
||||
TEST_F(BackendTest, FeedbackLoops) {
|
||||
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Image is unexpectedly darker, see b/417226296");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL),
|
||||
"OpenGL image is upside down due to readPixels failing for texture with uploaded image "
|
||||
"data");
|
||||
|
||||
@@ -212,7 +212,7 @@ static SamplerFormat getSamplerFormat(TextureFormat textureFormat) {
|
||||
}
|
||||
|
||||
TEST_F(LoadImageTest, UpdateImage2D) {
|
||||
FAIL_IF(Backend::VULKAN, "Multiple test cases crash");
|
||||
FAIL_IF(Backend::VULKAN, "Multiple test cases crash, see b/417481434");
|
||||
|
||||
// All of these test cases should result in the same rendered image, and thus the same hash.
|
||||
static const uint32_t expectedHash = 3644679986;
|
||||
@@ -485,6 +485,9 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
|
||||
}
|
||||
|
||||
TEST_F(LoadImageTest, UpdateImage3D) {
|
||||
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Checkerboard not drawn, possibly due to using wrong z value of 3d texture, "
|
||||
"see b/417254499");
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
api.startCapture();
|
||||
|
||||
@@ -81,7 +81,8 @@ void main() {
|
||||
|
||||
TEST_F(BackendTest, PushConstants) {
|
||||
SKIP_IF(Backend::OPENGL, "Push constants not supported on OpenGL");
|
||||
FAIL_IF(Backend::VULKAN, "Crashing due to no program set when setting push constants");
|
||||
FAIL_IF(Backend::VULKAN,
|
||||
"Crashing due to no program set when setting push constants, see b/417477740");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Lifetimes.h"
|
||||
#include "Shader.h"
|
||||
#include "SharedShaders.h"
|
||||
#include "Skip.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
#include <utils/Hash.h>
|
||||
@@ -74,6 +75,8 @@ public:
|
||||
};
|
||||
|
||||
TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Two cases fail, see b/417255941 and b/417255943");
|
||||
// These test scenarios use a known hash of the result pixel buffer to decide pass / fail,
|
||||
// asserting an exact pixel-for-pixel match. So far, rendering on macOS and iPhone have had
|
||||
// deterministic results. Take this test with a grain of salt, however, as other platform / GPU
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Lifetimes.h"
|
||||
#include "Shader.h"
|
||||
#include "SharedShaders.h"
|
||||
#include "Skip.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
#include <utils/Hash.h>
|
||||
@@ -30,6 +31,8 @@ using namespace filament;
|
||||
using namespace filament::backend;
|
||||
|
||||
TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Affected area in wrong corner, see b/417229118");
|
||||
auto& api = getDriverApi();
|
||||
|
||||
constexpr int kSrcTexWidth = 1024;
|
||||
|
||||
@@ -117,6 +117,8 @@ public:
|
||||
};
|
||||
|
||||
TEST_F(BasicStencilBufferTest, StencilBuffer) {
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Stencil not supported, see b/417230776");
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
@@ -139,6 +141,8 @@ TEST_F(BasicStencilBufferTest, StencilBuffer) {
|
||||
}
|
||||
|
||||
TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Stencil not supported, see b/417230776");
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
@@ -162,6 +166,8 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
|
||||
}
|
||||
|
||||
TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Stencil not supported, see b/417230776");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL), "Stencil isn't applied");
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
@@ -317,15 +317,6 @@ public:
|
||||
*/
|
||||
size_t metalUploadBufferSizeBytes = 512 * 1024;
|
||||
|
||||
/**
|
||||
* The action to take if a Drawable cannot be acquired.
|
||||
*
|
||||
* Each frame rendered requires a CAMetalDrawable texture, which is
|
||||
* presented on-screen at the completion of each frame. These are
|
||||
* limited and provided round-robin style by the system.
|
||||
*/
|
||||
bool metalDisablePanicOnDrawableFailure = false;
|
||||
|
||||
/**
|
||||
* Set to `true` to forcibly disable parallel shader compilation in the backend.
|
||||
* Currently only honored by the GL and Metal backends.
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
#include <backend/CallbackHandler.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/PresentCallable.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Invocable.h>
|
||||
@@ -35,7 +34,7 @@ class Engine;
|
||||
/**
|
||||
* A swap chain represents an Operating System's *native* renderable surface.
|
||||
*
|
||||
* Typically it's a native window or a view. Because a SwapChain is initialized from a
|
||||
* Typically, it's a native window or a view. Because a SwapChain is initialized from a
|
||||
* native object, it is given to filament as a `void *`, which must be of the proper type
|
||||
* for each platform filament is running on.
|
||||
*
|
||||
@@ -158,7 +157,7 @@ public:
|
||||
/**
|
||||
* Requests a SwapChain with an alpha channel.
|
||||
*/
|
||||
static const uint64_t CONFIG_TRANSPARENT = backend::SWAP_CHAIN_CONFIG_TRANSPARENT;
|
||||
static constexpr uint64_t CONFIG_TRANSPARENT = backend::SWAP_CHAIN_CONFIG_TRANSPARENT;
|
||||
|
||||
/**
|
||||
* This flag indicates that the swap chain may be used as a source surface
|
||||
@@ -168,13 +167,13 @@ public:
|
||||
* @see
|
||||
* Renderer.copyFrame()
|
||||
*/
|
||||
static const uint64_t CONFIG_READABLE = backend::SWAP_CHAIN_CONFIG_READABLE;
|
||||
static constexpr uint64_t CONFIG_READABLE = backend::SWAP_CHAIN_CONFIG_READABLE;
|
||||
|
||||
/**
|
||||
* Indicates that the native X11 window is an XCB window rather than an XLIB window.
|
||||
* This is ignored on non-Linux platforms and in builds that support only one X11 API.
|
||||
*/
|
||||
static const uint64_t CONFIG_ENABLE_XCB = backend::SWAP_CHAIN_CONFIG_ENABLE_XCB;
|
||||
static constexpr uint64_t CONFIG_ENABLE_XCB = backend::SWAP_CHAIN_CONFIG_ENABLE_XCB;
|
||||
|
||||
/**
|
||||
* Indicates that the native window is a CVPixelBufferRef.
|
||||
@@ -186,7 +185,7 @@ public:
|
||||
* Filament. Filament will call CVPixelBufferRetain during Engine::createSwapChain, and
|
||||
* CVPixelBufferRelease when the swap chain is destroyed.
|
||||
*/
|
||||
static const uint64_t CONFIG_APPLE_CVPIXELBUFFER =
|
||||
static constexpr uint64_t CONFIG_APPLE_CVPIXELBUFFER =
|
||||
backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER;
|
||||
|
||||
/**
|
||||
@@ -303,6 +302,7 @@ public:
|
||||
*
|
||||
* @param handler Handler to dispatch the callback or nullptr for the default handler.
|
||||
* @param callback Callback called when the frame is scheduled.
|
||||
* @param flags
|
||||
*
|
||||
* @remark Only Filament's Metal backend supports PresentCallables and frame callbacks. Other
|
||||
* backends ignore the callback (which will never be called) and proceed normally.
|
||||
@@ -314,7 +314,7 @@ public:
|
||||
FrameScheduledCallback&& callback = {}, uint64_t flags = 0);
|
||||
|
||||
/**
|
||||
* Returns whether or not this SwapChain currently has a FrameScheduledCallback set.
|
||||
* Returns whether this SwapChain currently has a FrameScheduledCallback set.
|
||||
*
|
||||
* @return true, if the last call to setFrameScheduledCallback set a callback
|
||||
*
|
||||
|
||||
@@ -16,13 +16,22 @@
|
||||
|
||||
#include "AtlasAllocator.h"
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/QuadTree.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace utils;
|
||||
|
||||
static inline constexpr std::pair<uint8_t, uint8_t> unmorton(uint16_t const m) noexcept {
|
||||
static constexpr std::pair<uint8_t, uint8_t> unmorton(uint16_t const m) noexcept {
|
||||
uint32_t r = (m | (uint32_t(m) << 15u)) & 0x55555555u;
|
||||
r = (r | (r >> 1u)) & 0x33333333u;
|
||||
r = (r | (r >> 2u)) & 0x0f0f0f0fu;
|
||||
@@ -165,8 +174,8 @@ AtlasAllocator::NodeId AtlasAllocator::allocateInLayer(size_t const maxHeight) n
|
||||
NodeId found{ -1, 0 };
|
||||
QuadTree::traverse(candidate.l, candidate.code,
|
||||
[this, n, &found](NodeId const& curr) -> QuadTree::TraversalResult {
|
||||
size_t const i = index(curr.l, curr.code);
|
||||
Node& node = mQuadTree[i];
|
||||
size_t const j = index(curr.l, curr.code);
|
||||
Node& node = mQuadTree[j];
|
||||
if (curr.l == n) {
|
||||
found = curr;
|
||||
assert_invariant(!node.hasChildren());
|
||||
|
||||
@@ -138,7 +138,6 @@ Engine* FEngine::create(Builder const& builder) {
|
||||
.forceGLES2Context = instance->getConfig().forceGLES2Context,
|
||||
.stereoscopicType = instance->getConfig().stereoscopicType,
|
||||
.assertNativeWindowIsValid = instance->features.backend.opengl.assert_native_window_is_valid,
|
||||
.metalDisablePanicOnDrawableFailure = instance->getConfig().metalDisablePanicOnDrawableFailure,
|
||||
};
|
||||
instance->mDriver = platform->createDriver(sharedContext, driverConfig);
|
||||
|
||||
@@ -734,7 +733,6 @@ int FEngine::loop() {
|
||||
.forceGLES2Context = mConfig.forceGLES2Context,
|
||||
.stereoscopicType = mConfig.stereoscopicType,
|
||||
.assertNativeWindowIsValid = features.backend.opengl.assert_native_window_is_valid,
|
||||
.metalDisablePanicOnDrawableFailure = mConfig.metalDisablePanicOnDrawableFailure,
|
||||
};
|
||||
mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig);
|
||||
|
||||
|
||||
@@ -102,7 +102,8 @@ project in Xcode to see changes take effect.
|
||||
|
||||
## Building iOS Samples with ASan / UBSan
|
||||
|
||||
1. Turn on ASan / UBSan in Filament's top-level CMakeLists.txt by uncommenting the following line:
|
||||
1. Turn on ASan / UBSan in Filament's top-level CMakeLists.txt by passing
|
||||
`-DFILAMENT_ENABLE_ASAN_UBSAN=1` to trigger the following line:
|
||||
|
||||
```
|
||||
set(EXTRA_SANITIZE_OPTIONS "-fsanitize=undefined -fsanitize=address")
|
||||
|
||||
@@ -52,6 +52,7 @@ public:
|
||||
using Precision = backend::Precision;
|
||||
using SamplerParams = backend::SamplerParams;
|
||||
using Binding = backend::descriptor_binding_t;
|
||||
using ShaderStageFlags = backend::ShaderStageFlags;
|
||||
|
||||
struct SamplerInfo { // NOLINT(cppcoreguidelines-pro-type-member-init)
|
||||
utils::CString name; // name of this sampler
|
||||
@@ -61,6 +62,7 @@ public:
|
||||
Format format; // format of this sampler
|
||||
Precision precision; // precision of this sampler
|
||||
bool multisample; // multisample capable
|
||||
ShaderStageFlags stages; // stages the sampler can be accessed from
|
||||
};
|
||||
|
||||
using SamplerInfoList = utils::FixedCapacityVector<SamplerInfo>;
|
||||
@@ -82,6 +84,8 @@ public:
|
||||
Format format; // format of this sampler
|
||||
Precision precision; // precision of this sampler
|
||||
bool multisample = false; // multisample capable
|
||||
ShaderStageFlags stages =
|
||||
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS; // shader stages using this sampler
|
||||
};
|
||||
|
||||
// Give a name to this sampler interface block
|
||||
@@ -91,8 +95,8 @@ public:
|
||||
|
||||
// Add a sampler
|
||||
Builder& add(std::string_view samplerName, Binding binding, Type type, Format format,
|
||||
Precision precision = Precision::MEDIUM,
|
||||
bool multisample = false) noexcept;
|
||||
Precision precision = Precision::MEDIUM, bool multisample = false,
|
||||
ShaderStageFlags stages = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) noexcept;
|
||||
|
||||
// Add multiple samplers
|
||||
Builder& add(std::initializer_list<ListEntry> list) noexcept;
|
||||
|
||||
@@ -49,13 +49,13 @@ SamplerInterfaceBlock::Builder::stageFlags(backend::ShaderStageFlags stageFlags)
|
||||
return *this;
|
||||
}
|
||||
|
||||
SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add(
|
||||
std::string_view samplerName, Binding binding, Type type, Format format,
|
||||
Precision precision, bool multisample) noexcept {
|
||||
SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add(std::string_view samplerName,
|
||||
Binding binding, Type type, Format format, Precision precision, bool multisample,
|
||||
ShaderStageFlags stages) noexcept {
|
||||
mEntries.push_back({
|
||||
{ samplerName.data(), samplerName.size() }, // name
|
||||
{ }, // uniform name
|
||||
binding, type, format, precision, multisample });
|
||||
binding, type, format, precision, multisample, stages });
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ SamplerInterfaceBlock SamplerInterfaceBlock::Builder::build() {
|
||||
SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add(
|
||||
std::initializer_list<ListEntry> list) noexcept {
|
||||
for (auto& e : list) {
|
||||
add(e.name, e.binding, e.type, e.format, e.precision, e.multisample);
|
||||
add(e.name, e.binding, e.type, e.format, e.precision, e.multisample, e.stages);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -91,6 +91,7 @@ SamplerInterfaceBlock::SamplerInterfaceBlock(Builder const& builder) noexcept
|
||||
size_t const i = std::distance(builder.mEntries.data(), &e);
|
||||
SamplerInfo& info = samplersInfoList[i];
|
||||
info = e;
|
||||
info.stages &= builder.mStageFlags;
|
||||
info.uniformName = generateUniformName(mName.c_str(), e.name.c_str());
|
||||
infoMap[{ info.name.data(), info.name.size() }] = i; // info.name.c_str() guaranteed constant
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ using OutputTarget = MaterialBuilder::OutputTarget;
|
||||
using OutputQualifier = MaterialBuilder::VariableQualifier;
|
||||
using OutputType = MaterialBuilder::OutputType;
|
||||
using ConstantType = MaterialBuilder::ConstantType;
|
||||
using ShaderStageType = MaterialBuilder::ShaderStageFlags;
|
||||
|
||||
// Convenience methods to convert std::string to Enum and also iterate over Enum values.
|
||||
class Enums {
|
||||
@@ -79,6 +80,7 @@ private:
|
||||
static std::unordered_map<std::string, OutputQualifier> mStringToOutputQualifier;
|
||||
static std::unordered_map<std::string, OutputType> mStringToOutputType;
|
||||
static std::unordered_map<std::string, ConstantType> mStringToConstantType;
|
||||
static std::unordered_map<std::string, ShaderStageType> mStringToShaderStageType;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
||||
@@ -254,6 +254,7 @@ public:
|
||||
using FeatureLevel = filament::backend::FeatureLevel;
|
||||
using StereoscopicType = filament::backend::StereoscopicType;
|
||||
using ShaderStage = filament::backend::ShaderStage;
|
||||
using ShaderStageFlags = filament::backend::ShaderStageFlags;
|
||||
|
||||
enum class VariableQualifier : uint8_t {
|
||||
OUT
|
||||
@@ -322,9 +323,9 @@ public:
|
||||
*/
|
||||
MaterialBuilder& parameter(const char* name, SamplerType samplerType,
|
||||
SamplerFormat format = SamplerFormat::FLOAT,
|
||||
ParameterPrecision precision = ParameterPrecision::DEFAULT,
|
||||
bool multisample = false,
|
||||
const char* transformName = "") noexcept;
|
||||
ParameterPrecision precision = ParameterPrecision::DEFAULT, bool multisample = false,
|
||||
const char* transformName = "",
|
||||
ShaderStageFlags stages = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) noexcept;
|
||||
|
||||
MaterialBuilder& buffer(filament::BufferInterfaceBlock bib) noexcept;
|
||||
|
||||
@@ -656,8 +657,10 @@ public:
|
||||
Parameter() noexcept: parameterType(INVALID) {}
|
||||
|
||||
// Sampler
|
||||
Parameter(const char* paramName, SamplerType t, SamplerFormat f, ParameterPrecision p, bool ms, const char* tn)
|
||||
: name(paramName), size(1), precision(p), samplerType(t), format(f), parameterType(SAMPLER), multisample(ms), transformName(tn) { }
|
||||
Parameter(const char* paramName, SamplerType t, SamplerFormat f, ParameterPrecision p,
|
||||
bool ms, const char* tn, ShaderStageFlags s)
|
||||
: name(paramName), size(1), precision(p), samplerType(t), format(f),
|
||||
parameterType(SAMPLER), multisample(ms), transformName(tn), stages(s) { }
|
||||
|
||||
// Uniform
|
||||
Parameter(const char* paramName, UniformType t, size_t typeSize, ParameterPrecision p)
|
||||
@@ -676,6 +679,7 @@ public:
|
||||
SamplerFormat format;
|
||||
bool multisample;
|
||||
utils::CString transformName;
|
||||
ShaderStageFlags stages;
|
||||
enum {
|
||||
INVALID,
|
||||
UNIFORM,
|
||||
|
||||
@@ -175,4 +175,15 @@ std::unordered_map<std::string, ConstantType>& Enums::getMap<ConstantType>() noe
|
||||
return mStringToConstantType;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, ShaderStageType> Enums::mStringToShaderStageType = {
|
||||
{ "fragment", ShaderStageType::FRAGMENT },
|
||||
{ "vertex", ShaderStageType::VERTEX },
|
||||
{ "compute", ShaderStageType::COMPUTE },
|
||||
};
|
||||
|
||||
template <>
|
||||
std::unordered_map<std::string, ShaderStageType>& Enums::getMap<ShaderStageType>() noexcept {
|
||||
return mStringToShaderStageType;
|
||||
};
|
||||
|
||||
} // namespace filamat
|
||||
|
||||
@@ -144,12 +144,12 @@ DescriptorSetLayout getPerMaterialDescriptorSet(SamplerInterfaceBlock const& sib
|
||||
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
|
||||
+PerMaterialBindingPoints::MATERIAL_PARAMS, DescriptorFlags::NONE, 0 });
|
||||
|
||||
for (auto const& sampler : samplers) {
|
||||
layout.bindings.push_back(DescriptorSetLayoutBinding {
|
||||
(sampler.type == SamplerInterfaceBlock::Type::SAMPLER_EXTERNAL) ?
|
||||
DescriptorType::SAMPLER_EXTERNAL : DescriptorType::SAMPLER,
|
||||
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, sampler.binding,
|
||||
DescriptorFlags::NONE, 0 });
|
||||
for (auto const& sampler: samplers) {
|
||||
layout.bindings.push_back(DescriptorSetLayoutBinding{
|
||||
(sampler.type == SamplerInterfaceBlock::Type::SAMPLER_EXTERNAL)
|
||||
? DescriptorType::SAMPLER_EXTERNAL
|
||||
: DescriptorType::SAMPLER,
|
||||
sampler.stages, sampler.binding, DescriptorFlags::NONE, 0 });
|
||||
}
|
||||
|
||||
return layout;
|
||||
|
||||
@@ -302,7 +302,8 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, UniformType const
|
||||
|
||||
|
||||
MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType samplerType,
|
||||
SamplerFormat format, ParameterPrecision precision, bool multisample, const char* transformName) noexcept {
|
||||
SamplerFormat format, ParameterPrecision precision, bool multisample,
|
||||
const char* transformName, ShaderStageFlags stages) noexcept {
|
||||
FILAMENT_CHECK_PRECONDITION(!multisample ||
|
||||
(format != SamplerFormat::SHADOW &&
|
||||
(samplerType == SamplerType::SAMPLER_2D ||
|
||||
@@ -311,7 +312,7 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType sample
|
||||
" as long as type is not SHADOW";
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT) << "Too many parameters";
|
||||
mParameters[mParameterCount++] = { name, samplerType, format, precision, multisample, transformName };
|
||||
mParameters[mParameterCount++] = { name, samplerType, format, precision, multisample, transformName, stages };
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -637,8 +638,8 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept {
|
||||
auto const& param = mParameters[i];
|
||||
assert_invariant(!param.isSubpass());
|
||||
if (param.isSampler()) {
|
||||
sbb.add({ param.name.data(), param.name.size() },
|
||||
binding, param.samplerType, param.format, param.precision, param.multisample);
|
||||
sbb.add({ param.name.data(), param.name.size() }, binding, param.samplerType,
|
||||
param.format, param.precision, param.multisample, param.stages);
|
||||
if (!param.transformName.empty()) {
|
||||
ibb.add({{{ param.transformName.data(), param.transformName.size() }, uint8_t(binding),
|
||||
0, UniformType::MAT3, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 }});
|
||||
|
||||
@@ -233,7 +233,7 @@ void MaterialDescriptorSetLayoutChunk::flatten(Flattener& f) {
|
||||
} else {
|
||||
f.writeUint8(uint8_t(DescriptorType::SAMPLER));
|
||||
}
|
||||
f.writeUint8(uint8_t(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT));
|
||||
f.writeUint8(uint8_t(entry.stages));
|
||||
f.writeUint8(entry.binding);
|
||||
f.writeUint8(uint8_t(DescriptorFlags::NONE));
|
||||
f.writeUint16(0);
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <filamat/MaterialBuilder.h>
|
||||
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
using namespace filament;
|
||||
using namespace filament::math;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <geometry/TangentSpaceMesh.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/StructureOfArrays.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <string_view>
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
using namespace utils;
|
||||
using namespace std::literals;
|
||||
|
||||
@@ -65,6 +65,8 @@ set(SRCS
|
||||
src/CyclicBarrier.cpp
|
||||
src/EntityManager.cpp
|
||||
src/EntityManagerImpl.h
|
||||
src/FixedCapacityVectorBase.cpp
|
||||
src/Invocable.cpp
|
||||
src/JobSystem.cpp
|
||||
src/Log.cpp
|
||||
src/NameComponentManager.cpp
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
// NOTE: this header should not include STL headers
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
@@ -31,6 +31,9 @@
|
||||
#include <string.h>
|
||||
|
||||
namespace utils {
|
||||
namespace io {
|
||||
class ostream;
|
||||
}
|
||||
|
||||
//! \privatesection
|
||||
struct hashCStrings {
|
||||
@@ -38,7 +41,7 @@ struct hashCStrings {
|
||||
typedef size_t result_type;
|
||||
result_type operator()(argument_type cstr) const noexcept {
|
||||
size_t hash = 5381;
|
||||
while (int const c = *cstr++) {
|
||||
while (int const c = static_cast<unsigned char>(*cstr++)) {
|
||||
hash = (hash * 33u) ^ size_t(c);
|
||||
}
|
||||
return hash;
|
||||
@@ -97,11 +100,7 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
~CString() noexcept {
|
||||
if (mData) {
|
||||
free(mData - 1);
|
||||
}
|
||||
}
|
||||
~CString() noexcept;
|
||||
|
||||
void swap(CString& other) noexcept {
|
||||
// don't use std::swap(), we don't want an STL dependency in this file
|
||||
@@ -110,8 +109,8 @@ public:
|
||||
other.mCStr = temp;
|
||||
}
|
||||
|
||||
const_pointer c_str() const noexcept { return mCStr; }
|
||||
pointer c_str() noexcept { return mCStr; }
|
||||
const_pointer c_str() const noexcept { return const_cast<CString*>(this)->c_str(); }
|
||||
const_pointer c_str_safe() const noexcept { return mData ? c_str() : ""; }
|
||||
const_pointer data() const noexcept { return c_str(); }
|
||||
pointer data() noexcept { return c_str(); }
|
||||
@@ -119,33 +118,89 @@ public:
|
||||
size_type length() const noexcept { return size(); }
|
||||
bool empty() const noexcept { return size() == 0; }
|
||||
|
||||
iterator begin() noexcept { return mCStr; }
|
||||
iterator begin() noexcept { return c_str(); }
|
||||
iterator end() noexcept { return begin() + length(); }
|
||||
const_iterator begin() const noexcept { return data(); }
|
||||
const_iterator end() const noexcept { return begin() + length(); }
|
||||
const_iterator cbegin() const noexcept { return begin(); }
|
||||
const_iterator cend() const noexcept { return end(); }
|
||||
|
||||
CString& replace(size_type pos, size_type len, const CString& str) noexcept;
|
||||
CString& insert(size_type pos, const CString& str) noexcept { return replace(pos, 0, str); }
|
||||
CString& append(const CString& str) noexcept { return insert(length(), str); }
|
||||
// replace
|
||||
template<size_t N>
|
||||
CString& replace(size_type const pos,
|
||||
size_type const len, const StringLiteral<N>& str) & noexcept {
|
||||
return replace(pos, len, str, N - 1);
|
||||
}
|
||||
|
||||
const_reference operator[](size_type pos) const noexcept {
|
||||
CString& replace(size_type const pos, size_type const len, const CString& str) & noexcept {
|
||||
return replace(pos, len, str.c_str_safe(), str.size());
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
CString&& replace(size_type const pos,
|
||||
size_type const len, const StringLiteral<N>& str) && noexcept {
|
||||
return std::move(replace(pos, len, str));
|
||||
}
|
||||
|
||||
CString&& replace(size_type const pos, size_type const len, const CString& str) && noexcept {
|
||||
return std::move(replace(pos, len, str));
|
||||
}
|
||||
|
||||
// insert
|
||||
template<size_t N>
|
||||
CString& insert(size_type const pos, const StringLiteral<N>& str) & noexcept {
|
||||
return replace(pos, 0, str);
|
||||
}
|
||||
|
||||
CString& insert(size_type const pos, const CString& str) & noexcept {
|
||||
return replace(pos, 0, str);
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
CString&& insert(size_type const pos, const StringLiteral<N>& str) && noexcept {
|
||||
return std::move(*this).replace(pos, 0, str);
|
||||
}
|
||||
|
||||
CString&& insert(size_type const pos, const CString& str) && noexcept {
|
||||
return std::move(*this).replace(pos, 0, str);
|
||||
}
|
||||
|
||||
// append
|
||||
template<size_t N>
|
||||
CString& append(const StringLiteral<N>& str) & noexcept {
|
||||
return insert(length(), str);
|
||||
}
|
||||
|
||||
CString& append(const CString& str) & noexcept {
|
||||
return insert(length(), str);
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
CString&& append(const StringLiteral<N>& str) && noexcept {
|
||||
return std::move(*this).insert(length(), str);
|
||||
}
|
||||
|
||||
CString&& append(const CString& str) && noexcept {
|
||||
return std::move(*this).insert(length(), str);
|
||||
}
|
||||
|
||||
|
||||
const_reference operator[](size_type const pos) const noexcept {
|
||||
assert(pos < size());
|
||||
return begin()[pos];
|
||||
}
|
||||
|
||||
reference operator[](size_type pos) noexcept {
|
||||
reference operator[](size_type const pos) noexcept {
|
||||
assert(pos < size());
|
||||
return begin()[pos];
|
||||
}
|
||||
|
||||
const_reference at(size_type pos) const noexcept {
|
||||
const_reference at(size_type const pos) const noexcept {
|
||||
assert(pos < size());
|
||||
return begin()[pos];
|
||||
}
|
||||
|
||||
reference at(size_type pos) noexcept {
|
||||
reference at(size_type const pos) noexcept {
|
||||
assert(pos < size());
|
||||
return begin()[pos];
|
||||
}
|
||||
@@ -171,7 +226,7 @@ public:
|
||||
}
|
||||
|
||||
// placement new declared as "throw" to avoid the compiler's null-check
|
||||
inline void* operator new(size_t, void* ptr) {
|
||||
void* operator new(size_t, void* ptr) {
|
||||
assert(ptr);
|
||||
return ptr;
|
||||
}
|
||||
@@ -185,6 +240,8 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
CString& replace(size_type pos, size_type len, char const* str, size_t l) & noexcept;
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
friend io::ostream& operator<<(io::ostream& out, const CString& rhs);
|
||||
#endif
|
||||
@@ -225,7 +282,7 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
// implement this for your type for automatic conversion to CString. Failing to do so leads
|
||||
// Implement this for your type for automatic conversion to CString. Failing to do so leads
|
||||
// to a compile-time failure.
|
||||
template<typename T>
|
||||
CString to_string(T value) noexcept;
|
||||
@@ -249,7 +306,7 @@ public:
|
||||
pointer c_str() noexcept { return mData; }
|
||||
|
||||
private:
|
||||
value_type mData[N] = {0};
|
||||
value_type mData[N] = {};
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
|
||||
@@ -23,9 +23,11 @@
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
namespace utils {
|
||||
namespace io {
|
||||
class ostream;
|
||||
}
|
||||
|
||||
/**
|
||||
* CallStack captures the current's thread call stack.
|
||||
@@ -88,23 +90,23 @@ public:
|
||||
|
||||
bool operator <(const CallStack& rhs) const;
|
||||
|
||||
inline bool operator >(const CallStack& rhs) const {
|
||||
bool operator >(const CallStack& rhs) const {
|
||||
return rhs < *this;
|
||||
}
|
||||
|
||||
inline bool operator !=(const CallStack& rhs) const {
|
||||
bool operator !=(const CallStack& rhs) const {
|
||||
return *this < rhs || rhs < *this;
|
||||
}
|
||||
|
||||
inline bool operator >=(const CallStack& rhs) const {
|
||||
bool operator >=(const CallStack& rhs) const {
|
||||
return !operator <(rhs);
|
||||
}
|
||||
|
||||
inline bool operator <=(const CallStack& rhs) const {
|
||||
bool operator <=(const CallStack& rhs) const {
|
||||
return !operator >(rhs);
|
||||
}
|
||||
|
||||
inline bool operator ==(const CallStack& rhs) const {
|
||||
bool operator ==(const CallStack& rhs) const {
|
||||
return !operator !=(rhs);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/compressed_pair.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
@@ -40,6 +40,11 @@
|
||||
|
||||
namespace utils {
|
||||
|
||||
class FixedCapacityVectorBase {
|
||||
protected:
|
||||
UTILS_NORETURN static void capacityCheckFailed(size_t capacity, size_t size);
|
||||
};
|
||||
|
||||
/**
|
||||
* FixedCapacityVector is (almost) a drop-in replacement for std::vector<> except it has a
|
||||
* fixed capacity decided at runtime. The vector storage is never reallocated unless reserve()
|
||||
@@ -56,7 +61,7 @@ namespace utils {
|
||||
* the optional value argument, e.g. FixedCapacityVector<int>(4, 0) or foo.resize(4, 0).
|
||||
*/
|
||||
template<typename T, typename A = std::allocator<T>, bool CapacityCheck = true>
|
||||
class UTILS_PUBLIC FixedCapacityVector {
|
||||
class UTILS_PUBLIC FixedCapacityVector : protected FixedCapacityVectorBase {
|
||||
public:
|
||||
using allocator_type = A;
|
||||
using value_type = T;
|
||||
@@ -266,7 +271,7 @@ public:
|
||||
mSize = 0;
|
||||
}
|
||||
|
||||
void resize(size_type count) {
|
||||
void resize(size_type const count) {
|
||||
assertCapacityForSize(count);
|
||||
if constexpr(std::is_trivially_constructible_v<value_type> &&
|
||||
std::is_trivially_destructible_v<value_type>) {
|
||||
@@ -277,12 +282,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void resize(size_type count, const_reference v) {
|
||||
void resize(size_type const count, const_reference v) {
|
||||
assertCapacityForSize(count);
|
||||
resize_non_trivial(count, v);
|
||||
}
|
||||
|
||||
void swap(FixedCapacityVector& other) {
|
||||
void swap(FixedCapacityVector& other) noexcept {
|
||||
using std::swap;
|
||||
swap(mData, other.mData);
|
||||
swap(mSize, other.mSize);
|
||||
@@ -326,16 +331,16 @@ private:
|
||||
return mCapacityAllocator.second();
|
||||
}
|
||||
|
||||
iterator assertCapacityForSize(size_type s) {
|
||||
iterator assertCapacityForSize(size_type const s) {
|
||||
if constexpr(CapacityCheck || FILAMENT_FORCE_CAPACITY_CHECK) {
|
||||
FILAMENT_CHECK_PRECONDITION(capacity() >= s)
|
||||
<< "capacity exceeded: requested size " << (unsigned long)s
|
||||
<< "u, available capacity " << (unsigned long)capacity() << "u.";
|
||||
if (UTILS_VERY_UNLIKELY(capacity() < s)) {
|
||||
capacityCheckFailed(capacity(), s);
|
||||
}
|
||||
}
|
||||
return end();
|
||||
}
|
||||
|
||||
inline void construct(iterator first, iterator last) noexcept {
|
||||
void construct(iterator const first, iterator const last) noexcept {
|
||||
// we check for triviality here so that the implementation could be non-inline
|
||||
if constexpr(!std::is_trivially_constructible_v<value_type>) {
|
||||
construct_non_trivial(first, last);
|
||||
@@ -358,7 +363,7 @@ private:
|
||||
}
|
||||
|
||||
|
||||
inline void destroy(iterator first, iterator last) noexcept {
|
||||
void destroy(iterator const first, iterator const last) noexcept {
|
||||
// we check for triviality here so that the implementation could be non-inline
|
||||
if constexpr(!std::is_trivially_destructible_v<value_type>) {
|
||||
destroy_non_trivial(first, last);
|
||||
@@ -419,7 +424,7 @@ private:
|
||||
explicit SizeTypeWrapper(TYPE value) noexcept : value(value) { }
|
||||
SizeTypeWrapper& operator=(TYPE rhs) noexcept { value = rhs; return *this; }
|
||||
SizeTypeWrapper& operator=(SizeTypeWrapper& rhs) noexcept = delete;
|
||||
operator TYPE() const noexcept { return value; }
|
||||
operator TYPE() const noexcept { return value; } // NOLINT(*-explicit-constructor)
|
||||
};
|
||||
|
||||
pointer mData{};
|
||||
|
||||
@@ -14,10 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_UTILS_INVOKABLE_H
|
||||
#define TNT_UTILS_INVOKABLE_H
|
||||
|
||||
#include <utils/ostream.h>
|
||||
#ifndef TNT_UTILS_INVOCABLE_H
|
||||
#define TNT_UTILS_INVOCABLE_H
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@@ -25,6 +23,9 @@
|
||||
#include <assert.h>
|
||||
|
||||
namespace utils {
|
||||
namespace io {
|
||||
class ostream;
|
||||
}
|
||||
|
||||
/*
|
||||
* Invocable is a move-only general purpose function wrapper. Instances can
|
||||
@@ -50,11 +51,16 @@ template<typename Fn, typename R, typename... Args>
|
||||
using EnableIfFnMatchesInvocable = std::enable_if_t<true, int>;
|
||||
#endif
|
||||
|
||||
class InvocableBase {
|
||||
protected:
|
||||
static io::ostream& printInvocable(io::ostream& out, const char* name);
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class Invocable;
|
||||
|
||||
template<typename R, typename... Args>
|
||||
class Invocable<R(Args...)> {
|
||||
class Invocable<R(Args...)> : protected InvocableBase {
|
||||
public:
|
||||
// Creates an Invocable that does not contain a functor.
|
||||
// Will evaluate to false.
|
||||
@@ -85,7 +91,7 @@ public:
|
||||
private:
|
||||
#if !defined(NDEBUG)
|
||||
friend io::ostream& operator<<(io::ostream& out, const Invocable&) {
|
||||
return out << "Invocable<>"; // TODO: is there a way to do better here?
|
||||
return printInvocable(out, "Invocable<>"); // TODO: is there a way to do better here?
|
||||
}
|
||||
#endif
|
||||
void* mInvocable = nullptr;
|
||||
@@ -156,4 +162,4 @@ Invocable<R(Args...)>::operator bool() const noexcept {
|
||||
|
||||
} // namespace utils
|
||||
|
||||
#endif // TNT_UTILS_INVOKABLE_H
|
||||
#endif // TNT_UTILS_INVOCABLE_H
|
||||
|
||||
@@ -353,7 +353,7 @@ public:
|
||||
* The TPanic<> class implements the std::exception protocol as well as the Panic
|
||||
* interface common to all exceptions thrown by the framework.
|
||||
*/
|
||||
template <typename T>
|
||||
template <typename>
|
||||
class UTILS_PUBLIC TPanic : public Panic {
|
||||
public:
|
||||
// std::exception protocol
|
||||
@@ -398,12 +398,11 @@ public:
|
||||
* @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC
|
||||
* @see setMode()
|
||||
*/
|
||||
static inline void panic(
|
||||
static void panic(
|
||||
char const* function, char const* file, int line, char const* literal,
|
||||
std::string reason) UTILS_NORETURN;
|
||||
|
||||
protected:
|
||||
|
||||
private:
|
||||
/**
|
||||
* Creates a Panic with extra information about the error-site.
|
||||
* @param function the name of the function where the error was detected
|
||||
@@ -415,11 +414,14 @@ protected:
|
||||
TPanic(char const* function, char const* file, int line, char const* literal,
|
||||
std::string reason);
|
||||
|
||||
friend class PreconditionPanic;
|
||||
friend class PostconditionPanic;
|
||||
friend class ArithmeticPanic;
|
||||
|
||||
protected:
|
||||
~TPanic() override;
|
||||
|
||||
private:
|
||||
void buildMessage();
|
||||
|
||||
char const* const mFile = nullptr; // file where the panic happened
|
||||
char const* const mFunction = nullptr; // function where the panic happened
|
||||
int const mLine = -1; // line where the panic happened
|
||||
@@ -443,7 +445,7 @@ void panicLog(
|
||||
* ASSERT_PRECONDITION uses this Panic to report a precondition failure.
|
||||
* @see ASSERT_PRECONDITION
|
||||
*/
|
||||
class UTILS_PUBLIC PreconditionPanic : public TPanic<PreconditionPanic> {
|
||||
class UTILS_PUBLIC PreconditionPanic final : public TPanic<PreconditionPanic> {
|
||||
// Programming error, can be avoided
|
||||
// e.g.: invalid arguments
|
||||
using TPanic<PreconditionPanic>::TPanic;
|
||||
@@ -457,9 +459,9 @@ class UTILS_PUBLIC PreconditionPanic : public TPanic<PreconditionPanic> {
|
||||
* ASSERT_POSTCONDITION uses this Panic to report a postcondition failure.
|
||||
* @see ASSERT_POSTCONDITION
|
||||
*/
|
||||
class UTILS_PUBLIC PostconditionPanic : public TPanic<PostconditionPanic> {
|
||||
class UTILS_PUBLIC PostconditionPanic final : public TPanic<PostconditionPanic> {
|
||||
// Usually only detectable at runtime
|
||||
// e.g.: dead-lock would occur, arithmetic errors
|
||||
// e.g.: deadlock would occur, arithmetic errors
|
||||
using TPanic<PostconditionPanic>::TPanic;
|
||||
friend class TPanic<PostconditionPanic>;
|
||||
constexpr static auto type = "Postcondition";
|
||||
@@ -471,7 +473,7 @@ class UTILS_PUBLIC PostconditionPanic : public TPanic<PostconditionPanic> {
|
||||
* ASSERT_ARITHMETIC uses this Panic to report an arithmetic (postcondition) failure.
|
||||
* @see ASSERT_ARITHMETIC
|
||||
*/
|
||||
class UTILS_PUBLIC ArithmeticPanic : public TPanic<ArithmeticPanic> {
|
||||
class UTILS_PUBLIC ArithmeticPanic final : public TPanic<ArithmeticPanic> {
|
||||
// A common case of post-condition error
|
||||
// e.g.: underflow, overflow, internal computations errors
|
||||
using TPanic<ArithmeticPanic>::TPanic;
|
||||
@@ -519,11 +521,11 @@ public:
|
||||
|
||||
PanicStream& operator<<(const void* value) noexcept;
|
||||
|
||||
PanicStream& operator<<(const char* string) noexcept;
|
||||
PanicStream& operator<<(const unsigned char* string) noexcept;
|
||||
PanicStream& operator<<(const char* value) noexcept;
|
||||
PanicStream& operator<<(const unsigned char* value) noexcept;
|
||||
|
||||
PanicStream& operator<<(std::string const& s) noexcept;
|
||||
PanicStream& operator<<(std::string_view const& s) noexcept;
|
||||
PanicStream& operator<<(std::string const& value) noexcept;
|
||||
PanicStream& operator<<(std::string_view const& value) noexcept;
|
||||
|
||||
protected:
|
||||
io::sstream mStream;
|
||||
|
||||
@@ -19,11 +19,8 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <type_traits>
|
||||
#include <string_view>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace utils {
|
||||
|
||||
/**
|
||||
@@ -41,7 +38,7 @@ public:
|
||||
using const_iterator = std::string_view::const_iterator;
|
||||
|
||||
// Constructor from string literal
|
||||
template <size_t M>
|
||||
template<unsigned int M>
|
||||
constexpr StaticString(const char (&str)[M]) noexcept : mString(str, M - 1) {} // NOLINT(*-explicit-constructor)
|
||||
|
||||
constexpr StaticString() noexcept = default;
|
||||
@@ -61,7 +58,7 @@ public:
|
||||
return mString[pos];
|
||||
}
|
||||
|
||||
constexpr const_reference at(size_type pos) const {
|
||||
constexpr const_reference at(size_type const pos) const {
|
||||
return mString[pos];
|
||||
}
|
||||
|
||||
|
||||
+25
-16
@@ -20,27 +20,30 @@
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
|
||||
namespace utils {
|
||||
|
||||
UTILS_NOINLINE
|
||||
CString::CString(const char* cstr, size_t length) {
|
||||
CString::CString(const char* cstr, size_t const length) {
|
||||
if (length && cstr) {
|
||||
Data* p = (Data*)malloc(sizeof(Data) + length + 1);
|
||||
p->length = (size_type)length;
|
||||
mCStr = (value_type*)(p + 1);
|
||||
|
||||
Data* const p = static_cast<Data*>(std::malloc(sizeof(Data) + length + 1));
|
||||
p->length = size_type(length);
|
||||
mCStr = reinterpret_cast<value_type*>(p + 1);
|
||||
// we don't use memcpy here to avoid a call to libc, the generated code is pretty good.
|
||||
std::uninitialized_copy_n(cstr, length, mCStr);
|
||||
mCStr[length] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
CString::CString(size_t length) {
|
||||
CString::CString(size_t const length) {
|
||||
if (length) {
|
||||
Data* p = (Data*)malloc(sizeof(Data) + length + 1);
|
||||
p->length = (size_type)length;
|
||||
mCStr = (value_type*)(p + 1);
|
||||
Data* const p = static_cast<Data*>(std::malloc(sizeof(Data) + length + 1));
|
||||
p->length = size_type(length);
|
||||
mCStr = reinterpret_cast<value_type*>(p + 1);
|
||||
std::fill_n(mCStr, length, 0);
|
||||
mCStr[length] = '\0';
|
||||
}
|
||||
@@ -58,24 +61,30 @@ CString& CString::operator=(const CString& rhs) {
|
||||
if (this != &rhs) {
|
||||
auto *const p = mData ? mData - 1 : nullptr;
|
||||
new(this) CString(rhs);
|
||||
free(p);
|
||||
std::free(p);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
CString& CString::replace(size_type pos, size_type len, const CString& str) noexcept {
|
||||
CString::~CString() noexcept {
|
||||
if (mData) {
|
||||
std::free(mData - 1);
|
||||
}
|
||||
}
|
||||
|
||||
CString& CString::replace(size_type const pos, size_type len, char const* str, size_t const l) & noexcept {
|
||||
assert(pos <= size());
|
||||
|
||||
len = std::min(len, size() - pos);
|
||||
|
||||
// The new size of the string, after the replacement.
|
||||
const size_type newSize = size() - len + str.size();
|
||||
const size_type newSize = size() - len + l;
|
||||
|
||||
// Allocate enough memory to hold the new string.
|
||||
Data* p = (Data*) malloc(sizeof(Data) + newSize + 1);
|
||||
Data* const p = static_cast<Data*>(std::malloc(sizeof(Data) + newSize + 1));
|
||||
assert(p);
|
||||
p->length = newSize;
|
||||
value_type* newStr = (value_type*) (p + 1);
|
||||
value_type* newStr = reinterpret_cast<value_type*>(p + 1);
|
||||
|
||||
const value_type* beginning = mCStr;
|
||||
const value_type* replacementStart = mCStr + pos;
|
||||
@@ -84,7 +93,7 @@ CString& CString::replace(size_type pos, size_type len, const CString& str) noex
|
||||
|
||||
value_type* ptr = newStr;
|
||||
ptr = std::uninitialized_copy(beginning, replacementStart, ptr);
|
||||
ptr = std::uninitialized_copy_n(str.c_str_safe(), str.length(), ptr);
|
||||
ptr = std::uninitialized_copy_n(str, l, ptr);
|
||||
ptr = std::uninitialized_copy(replacementEnd, end, ptr);
|
||||
|
||||
// null-terminator
|
||||
@@ -92,14 +101,14 @@ CString& CString::replace(size_type pos, size_type len, const CString& str) noex
|
||||
|
||||
std::swap(mCStr, newStr);
|
||||
if (newStr) {
|
||||
free((Data*) newStr - 1);
|
||||
std::free(reinterpret_cast<Data*>(newStr) - 1);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
io::ostream& operator<<(io::ostream& out, const utils::CString& rhs) {
|
||||
io::ostream& operator<<(io::ostream& out, const CString& rhs) {
|
||||
return out << rhs.c_str_safe();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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 <utils/FixedCapacityVector.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace utils {
|
||||
|
||||
void FixedCapacityVectorBase::capacityCheckFailed(size_t const capacity, size_t const size) {
|
||||
UTILS_ASSUME(capacity < size);
|
||||
FILAMENT_CHECK_PRECONDITION(capacity >= size)
|
||||
<< "capacity exceeded: requested size " << size
|
||||
<< "u, available capacity " << capacity << "u.";
|
||||
|
||||
// In practice, we will never reach this.
|
||||
abort();
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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 <utils/Invocable.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
namespace utils {
|
||||
io::ostream& InvocableBase::printInvocable(io::ostream& out, const char* name) {
|
||||
return out << name;
|
||||
}
|
||||
} // namespace utils
|
||||
+23
-22
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "ostream_.h"
|
||||
|
||||
#include <utils/CallStack.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/ostream.h>
|
||||
@@ -68,7 +69,7 @@ public:
|
||||
getCallback().call(panic);
|
||||
}
|
||||
|
||||
void set(Panic::PanicHandlerCallback handler, void* user) noexcept {
|
||||
void set(Panic::PanicHandlerCallback const handler, void* user) noexcept {
|
||||
std::lock_guard const lock(mLock);
|
||||
mCallBack = { handler, user };
|
||||
}
|
||||
@@ -96,7 +97,7 @@ static std::string sprintfToString(const char* format, va_list args) noexcept {
|
||||
return s;
|
||||
}
|
||||
|
||||
static inline std::string sprintfToString(const char* format, ...) noexcept {
|
||||
static std::string sprintfToString(const char* format, ...) noexcept {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
std::string const s{ sprintfToString(format, args) };
|
||||
@@ -120,14 +121,14 @@ static std::string buildPanicString(
|
||||
|
||||
Panic::~Panic() noexcept = default;
|
||||
|
||||
void Panic::setPanicHandler(PanicHandlerCallback handler, void* user) noexcept {
|
||||
void Panic::setPanicHandler(PanicHandlerCallback const handler, void* user) noexcept {
|
||||
UserPanicHandler::get().set(handler, user);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
template<typename T>
|
||||
TPanic<T>::TPanic(const char* function, const char* file, int line, char const* literal,
|
||||
TPanic<T>::TPanic(const char* function, const char* file, int const line, char const* literal,
|
||||
std::string reason)
|
||||
: mFile(file),
|
||||
mFunction(function),
|
||||
@@ -188,13 +189,13 @@ void TPanic<T>::log() const noexcept {
|
||||
}
|
||||
|
||||
UTILS_ALWAYS_INLINE
|
||||
inline static const char* formatFile(char const* file) noexcept {
|
||||
static const char* formatFile(char const* file) noexcept {
|
||||
const char * p = std::strstr(file, "filament/");
|
||||
return p ? p : file;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void TPanic<T>::panic(char const* function, char const* file, int line, char const* literal,
|
||||
void TPanic<T>::panic(char const* function, char const* file, int const line, char const* literal,
|
||||
const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
@@ -233,7 +234,7 @@ void TPanic<T>::panic(char const* function, char const* file, int line, char con
|
||||
|
||||
namespace details {
|
||||
|
||||
void panicLog(char const* function, char const* file, int line, const char* format, ...) noexcept {
|
||||
void panicLog(char const* function, char const* file, int const line, const char* format, ...) noexcept {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
std::string const reason{ sprintfToString(format, args) };
|
||||
@@ -249,79 +250,79 @@ void panicLog(char const* function, char const* file, int line, const char* form
|
||||
PanicStream::PanicStream(
|
||||
char const* function,
|
||||
char const* file,
|
||||
int line,
|
||||
int const line,
|
||||
char const* condition) noexcept
|
||||
: mFunction(function), mFile(file), mLine(line), mLiteral(condition) {
|
||||
}
|
||||
|
||||
PanicStream::~PanicStream() = default;
|
||||
|
||||
PanicStream& PanicStream::operator<<(short value) noexcept {
|
||||
PanicStream& PanicStream::operator<<(short const value) noexcept {
|
||||
mStream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PanicStream& PanicStream::operator<<(unsigned short value) noexcept {
|
||||
PanicStream& PanicStream::operator<<(unsigned short const value) noexcept {
|
||||
mStream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PanicStream& PanicStream::operator<<(char value) noexcept {
|
||||
PanicStream& PanicStream::operator<<(char const value) noexcept {
|
||||
mStream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PanicStream& PanicStream::operator<<(unsigned char value) noexcept {
|
||||
PanicStream& PanicStream::operator<<(unsigned char const value) noexcept {
|
||||
mStream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PanicStream& PanicStream::operator<<(int value) noexcept {
|
||||
PanicStream& PanicStream::operator<<(int const value) noexcept {
|
||||
mStream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PanicStream& PanicStream::operator<<(unsigned int value) noexcept {
|
||||
PanicStream& PanicStream::operator<<(unsigned int const value) noexcept {
|
||||
mStream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PanicStream& PanicStream::operator<<(long value) noexcept {
|
||||
PanicStream& PanicStream::operator<<(long const value) noexcept {
|
||||
mStream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PanicStream& PanicStream::operator<<(unsigned long value) noexcept {
|
||||
PanicStream& PanicStream::operator<<(unsigned long const value) noexcept {
|
||||
mStream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PanicStream& PanicStream::operator<<(long long int value) noexcept {
|
||||
PanicStream& PanicStream::operator<<(long long int const value) noexcept {
|
||||
mStream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PanicStream& PanicStream::operator<<(unsigned long long int value) noexcept {
|
||||
PanicStream& PanicStream::operator<<(unsigned long long int const value) noexcept {
|
||||
mStream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PanicStream& PanicStream::operator<<(float value) noexcept {
|
||||
PanicStream& PanicStream::operator<<(float const value) noexcept {
|
||||
mStream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PanicStream& PanicStream::operator<<(double value) noexcept {
|
||||
PanicStream& PanicStream::operator<<(double const value) noexcept {
|
||||
mStream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PanicStream& PanicStream::operator<<(long double value) noexcept {
|
||||
PanicStream& PanicStream::operator<<(long double const value) noexcept {
|
||||
mStream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PanicStream& PanicStream::operator<<(bool value) noexcept {
|
||||
PanicStream& PanicStream::operator<<(bool const value) noexcept {
|
||||
mStream << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#define TNT_UTILS_OSTREAM__H
|
||||
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <utility>
|
||||
#include <mutex>
|
||||
|
||||
namespace utils::io {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
using namespace utils;
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ static const Vertex TRIANGLE_VERTICES[3] = {
|
||||
{{cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xff0000ffu},
|
||||
};
|
||||
|
||||
static constexpr uint16_t TRIANGLE_INDICES[3] = { 0, 1, 2 };
|
||||
static constexpr uint16_t TRIANGLE_INDICES[4] = { 0, 1, 2 , 0};
|
||||
|
||||
static void printUsage(char* name) {
|
||||
std::string exec_name(utils::Path(name).getName());
|
||||
@@ -147,11 +147,11 @@ int main(int argc, char** argv) {
|
||||
app.vb->setBufferAt(*engine, 0,
|
||||
VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES, 36, nullptr));
|
||||
app.ib = IndexBuffer::Builder()
|
||||
.indexCount(3)
|
||||
.indexCount(4)
|
||||
.bufferType(IndexBuffer::IndexType::USHORT)
|
||||
.build(*engine);
|
||||
app.ib->setBuffer(*engine,
|
||||
IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, 6, nullptr));
|
||||
IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, 8, nullptr));
|
||||
app.mat = Material::Builder()
|
||||
.package(RESOURCES_BAKEDCOLOR_DATA, RESOURCES_BAKEDCOLOR_SIZE)
|
||||
.build(*engine);
|
||||
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
# 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.
|
||||
|
||||
#!/usr/bin/bash
|
||||
|
||||
source `dirname $0`/src/preamble.sh
|
||||
|
||||
function start_render_() {
|
||||
start_
|
||||
if [[ ! "$GITHUB_WORKFLOW" ]]; then
|
||||
if [ ! -d ${MESA_LIB_DIR} ]; then
|
||||
bash ${BUILD_COMMON_DIR}/get-mesa.sh
|
||||
fi
|
||||
|
||||
# Install python deps
|
||||
python3 -m venv ${VENV_DIR}
|
||||
source ${VENV_DIR}/bin/activate
|
||||
|
||||
NEEDED_PYTHON_DEPS=("numpy" "tifffile")
|
||||
for cmd in "${NEEDED_PYTHON_DEPS[@]}"; do
|
||||
if ! python3 -m pip show -q "${cmd}"; then
|
||||
python3 -m pip install ${cmd}
|
||||
fi
|
||||
done
|
||||
fi
|
||||
mkdir -p ${OUTPUT_DIR}
|
||||
CXX=`which clang++` CC=`which clang` ./build.sh -f -X ${MESA_DIR} -p desktop debug gltf_viewer
|
||||
}
|
||||
|
||||
function end_render_() {
|
||||
if [[ ! "$GITHUB_WORKFLOW" ]]; then
|
||||
deactivate # End python virtual env
|
||||
fi
|
||||
end_
|
||||
}
|
||||
|
||||
# Following steps are taken:
|
||||
# - Get and build mesa
|
||||
# - Build gltf_viewer
|
||||
# - Run a test
|
||||
|
||||
start_render_ && \
|
||||
python3 ${RENDERDIFF_TEST_DIR}/src/render.py \
|
||||
--gltf_viewer="$(pwd)/out/cmake-debug/samples/gltf_viewer" \
|
||||
--test=${RENDERDIFF_TEST_DIR}/tests/presubmit.json \
|
||||
--output_dir=${OUTPUT_DIR} \
|
||||
--opengl_lib=${MESA_LIB_DIR} && \
|
||||
end_render_
|
||||
@@ -0,0 +1,54 @@
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
import pprint
|
||||
import json
|
||||
|
||||
from utils import execute, ArgParseImpl, important_print
|
||||
from image_diff import same_image
|
||||
from results import RESULT_OK, RESULT_FAILED, RESULT_MISSING
|
||||
|
||||
def _compare_goldens(base_dir, comparison_dir):
|
||||
render_results = {}
|
||||
base_files = glob.glob(os.path.join(base_dir, "./**/*.tif"))
|
||||
for golden_file in base_files:
|
||||
base_fname = os.path.abspath(golden_file)
|
||||
test_case = base_fname.replace(f'{os.path.abspath(base_dir)}/', '')
|
||||
comp_fname = os.path.abspath(os.path.join(comparison_dir, test_case))
|
||||
if not os.path.exists(comp_fname):
|
||||
print(f'file name not found: {comp_fname}')
|
||||
render_results[test_case] = RESULT_MISSING
|
||||
continue
|
||||
if not same_image(base_fname, comp_fname):
|
||||
render_results[test_case] = RESULT_FAILED
|
||||
else:
|
||||
render_results[test_case] = RESULT_OK
|
||||
return render_results
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = ArgParseImpl()
|
||||
parser.add_argument('--src', help='Directory of the base of the diff.', required=True)
|
||||
parser.add_argument('--dest', help='Directory of the comparison of the diff.')
|
||||
parser.add_argument('--out', help='Directory of output for the result of the diff.')
|
||||
|
||||
args, _ = parser.parse_known_args(sys.argv[1:])
|
||||
|
||||
dest = args.dest
|
||||
if not dest:
|
||||
print('Assume the default renderdiff output folder')
|
||||
dest = os.path.join(os.getcwd(), './out/renderdiff_tests')
|
||||
assert os.path.exists(dest), f"Destination folder={dest} does not exist."
|
||||
|
||||
results = _compare_goldens(args.src, dest)
|
||||
|
||||
if args.out:
|
||||
assert os.path.exists(arg.out), f"Output folder={dest} does not exist."
|
||||
with open(os.path.join(args.out, "compare_results.json", 'w')) as f:
|
||||
f.write(json.dumps(results))
|
||||
|
||||
failed = [f" {k}" for k in results.keys() if results[k] != RESULT_OK]
|
||||
success_count = len(results) - len(failed)
|
||||
important_print(f'Successfully compared {success_count} / {len(results)} images' +
|
||||
('\nFailed:\n' + ('\n'.join(failed)) if len(failed) > 0 else ''))
|
||||
if len(failed) > 0:
|
||||
exit(1)
|
||||
@@ -1,14 +1,43 @@
|
||||
# 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.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import re
|
||||
import sys
|
||||
|
||||
from utils import execute, ArgParseImpl
|
||||
from utils import execute, ArgParseImpl, mkdir_p
|
||||
|
||||
GOLDENS_DIR = 'renderdiff'
|
||||
|
||||
ACCESS_TYPE_TOKEN = 'token'
|
||||
ACCESS_TYPE_SSH = 'ssh'
|
||||
ACCESS_TYPE_READ_ONLY = 'read-only'
|
||||
|
||||
def _read_git_config(curdir):
|
||||
with open(os.path.join(curdir, './.git/config'), 'r') as f:
|
||||
return f.read()
|
||||
|
||||
def _write_git_config(curdir, config_str):
|
||||
with open(os.path.join(curdir, './.git/config'), 'w') as f:
|
||||
return f.write(config_str)
|
||||
|
||||
class GoldenManager:
|
||||
def __init__(self, working_dir, access_token=None):
|
||||
def __init__(self, working_dir, access_type=ACCESS_TYPE_READ_ONLY, access_token=None):
|
||||
self.working_dir_ = working_dir
|
||||
self.access_token_ = access_token
|
||||
self.access_type_ = access_type
|
||||
assert os.path.isdir(self.working_dir_),\
|
||||
f"working directory {self.working_dir_} does not exist"
|
||||
self._prepare()
|
||||
@@ -16,16 +45,36 @@ class GoldenManager:
|
||||
def _assets_dir(self):
|
||||
return os.path.join(self.working_dir_, "filament-assets")
|
||||
|
||||
# Returns the directory containing the goldens
|
||||
def directory(self):
|
||||
return os.path.join(self._assets_dir(), GOLDENS_DIR)
|
||||
|
||||
def _get_repo_url(self):
|
||||
protocol = ''
|
||||
protocol_separator = ''
|
||||
if self.access_type_ == ACCESS_TYPE_SSH:
|
||||
protocol = 'git@'
|
||||
protocol_separator = ':'
|
||||
else:
|
||||
protocol = 'https://' + \
|
||||
(f'x-access-token:{self.access_token_}@' if self.access_token_ else '')
|
||||
protocol_separator = '/'
|
||||
return f'{protocol}github.com{protocol_separator}google/filament-assets.git'
|
||||
|
||||
def _prepare(self):
|
||||
assets_dir = self._assets_dir()
|
||||
if not os.path.exists(assets_dir):
|
||||
access_token_part = ''
|
||||
if self.access_token_:
|
||||
access_token_part = f'x-access-token:{self.access_token_}@'
|
||||
execute(
|
||||
f'git clone --depth=1 https://{access_token_part}github.com/google/filament-assets.git',
|
||||
cwd=self.working_dir_)
|
||||
f'git clone --depth=1 {self._get_repo_url()}',
|
||||
cwd=self.working_dir_,
|
||||
capture_output=False
|
||||
)
|
||||
else:
|
||||
if self.access_type_ == ACCESS_TYPE_SSH:
|
||||
config = _read_git_config(self._assets_dir())
|
||||
https_url = r'https://github\.com\/google\/filament\.git'
|
||||
config = re.sub(https_url, self._get_repo_url(), config)
|
||||
_write_git_config(self._assets_dir(), config)
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
@@ -41,40 +90,62 @@ class GoldenManager:
|
||||
assets_dir = self._assets_dir()
|
||||
self._git_exec(f'checkout main')
|
||||
self._git_exec(f'merge --no-ff {branch}')
|
||||
if push_to_remote and self.access_token_:
|
||||
if push_to_remote and \
|
||||
(self.access_token_ or self.access_type_ == ACCESS_TYPE_SSH):
|
||||
self._git_exec(f'push origin main')
|
||||
self.update()
|
||||
|
||||
def source_from_and_commit(self, src_dir, commit_msg, branch, push_to_remote=False):
|
||||
def source_from(self, src_dir, commit_msg, branch,
|
||||
updates=[], deletes=[], push_to_remote=False):
|
||||
assets_dir = self._assets_dir()
|
||||
self._git_exec(f'checkout main')
|
||||
# Force create the branch (note will overwrite the old branch)
|
||||
self._git_exec(f'switch -C {branch}')
|
||||
rdiff_dir = os.path.join(assets_dir, GOLDENS_DIR)
|
||||
execute(f'rm -rf {rdiff_dir}')
|
||||
execute(f'mkdir -p {rdiff_dir}')
|
||||
shutil.copytree(src_dir, rdiff_dir, dirs_exist_ok=True)
|
||||
self._git_exec(f'add {GOLDENS_DIR}')
|
||||
if len(updates) == 0 and len(deletes) == 0:
|
||||
shutil.rmtree(rdiff_dir, ignore_errors=True)
|
||||
mkdir_p(rdiff_dir)
|
||||
shutil.copytree(src_dir, rdiff_dir, dirs_exist_ok=True)
|
||||
self._git_exec(f'add {GOLDENS_DIR}')
|
||||
else:
|
||||
for f in deletes:
|
||||
self._git_exec(f'remove {os.path.join(GOLDENS_DIR, f)}')
|
||||
for f in updates:
|
||||
shutil.copy2(
|
||||
os.path.join(src_dir, f),
|
||||
os.path.join(rdiff_dir, f))
|
||||
self._git_exec(f'add {os.path.join(GOLDENS_DIR, f)}')
|
||||
|
||||
TMP_GOLDEN_COMMIT_FILE = '/tmp/golden_commit.txt'
|
||||
|
||||
with open(TMP_GOLDEN_COMMIT_FILE, 'w') as f:
|
||||
f.write(commit_msg)
|
||||
self._git_exec(f'commit -F {TMP_GOLDEN_COMMIT_FILE}')
|
||||
if push_to_remote and self.access_token_:
|
||||
self._git_exec(f'push -f origin ${branch}')
|
||||
self._git_exec(f'commit -a -F {TMP_GOLDEN_COMMIT_FILE}')
|
||||
if push_to_remote and \
|
||||
(self.access_token_ or self.access_type_ == ACCESS_TYPE_SSH):
|
||||
self._git_exec(f'push -f origin {branch}')
|
||||
self.update()
|
||||
|
||||
def download_to(self, dest_dir, branch='main'):
|
||||
self._git_exec(f'checkout {branch}')
|
||||
assets_dir = self._assets_dir()
|
||||
execute(f'mkdir -p {dest_dir}')
|
||||
mkdir_p(dest_dir)
|
||||
rdiff_dir = os.path.join(assets_dir, GOLDENS_DIR)
|
||||
shutil.copytree(rdiff_dir, dest_dir, dirs_exist_ok=True)
|
||||
|
||||
# For testing only
|
||||
# The main entry point will enable download content of a branch to a directory
|
||||
if __name__ == "__main__":
|
||||
parser = ArgParseImpl()
|
||||
parser.add_argument('--branch', type=str, help='Branch of the golden repo', default='main')
|
||||
parser.add_argument('--output', type=str, help='Directory to download to', required=True)
|
||||
|
||||
args, _ = parser.parse_known_args(sys.argv[1:])
|
||||
|
||||
# prepare goldens working directory
|
||||
golden_dir = args.output
|
||||
assert os.path.isdir(golden_dir),\
|
||||
f"Output directory {golden_dir} does not exist"
|
||||
|
||||
# Download the golden repo into the current working directory
|
||||
golden_manager = GoldenManager(os.getcwd())
|
||||
# golden_manager.source_from_and_commit(
|
||||
# os.path.join(os.getcwd(), 'out/renderdiff_tests'),
|
||||
# 'First commit (local)',
|
||||
# branch='branch-test')
|
||||
# golden_manager.merge_to_main('branch-test', push_to_remote=True)
|
||||
# golden_manager.download_to(os.path.join(os.getcwd(), 'tmp/goldens'))
|
||||
golden_manager.download_to(golden_dir, branch=args.branch)
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
# Copyright (C) 2025 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import tifffile
|
||||
import numpy
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# 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.
|
||||
|
||||
#!/usr/bin/bash
|
||||
|
||||
# Sets up the environment for scripts in test/renderdiff/
|
||||
|
||||
OUTPUT_DIR="$(pwd)/out/renderdiff_tests"
|
||||
RENDERDIFF_TEST_DIR="$(pwd)/test/renderdiff"
|
||||
MESA_DIR="$(pwd)/mesa/out/"
|
||||
VENV_DIR="$(pwd)/venv"
|
||||
BUILD_COMMON_DIR="$(pwd)/build/common"
|
||||
|
||||
os_name=$(uname -s)
|
||||
if [[ "$os_name" == "Linux" ]]; then
|
||||
MESA_LIB_DIR="${MESA_DIR}lib/x86_64-linux-gnu"
|
||||
elif [[ "$os_name" == "Darwin" ]]; then
|
||||
MESA_LIB_DIR="${MESA_DIR}lib"
|
||||
else
|
||||
echo "Unsupported platform for renderdiff tests"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function start_() {
|
||||
if [[ "$GITHUB_WORKFLOW" ]]; then
|
||||
set -ex
|
||||
fi
|
||||
}
|
||||
|
||||
function end_() {
|
||||
if [[ "$GITHUB_WORKFLOW" ]]; then
|
||||
set +ex
|
||||
fi
|
||||
}
|
||||
@@ -16,37 +16,25 @@ import sys
|
||||
import os
|
||||
import json
|
||||
import glob
|
||||
import shutil
|
||||
|
||||
from utils import execute, ArgParseImpl
|
||||
from parse_test_json import parse_test_config_from_path
|
||||
from utils import execute, ArgParseImpl, mkdir_p, mv_f, important_print
|
||||
|
||||
import test_config
|
||||
from golden_manager import GoldenManager
|
||||
from image_diff import same_image
|
||||
from results import RESULT_OK, RESULT_FAILED
|
||||
|
||||
def important_print(msg):
|
||||
lines = msg.split('\n')
|
||||
max_len = max([len(l) for l in lines])
|
||||
print('-' * (max_len + 8))
|
||||
for line in lines:
|
||||
diff = max_len - len(line)
|
||||
information = f'--- {line} ' + (' ' * diff) + '---'
|
||||
print(information)
|
||||
print('-' * (max_len + 8))
|
||||
|
||||
RESULT_OK = 'ok'
|
||||
RESULT_FAILED_TO_RENDER = 'failed-to-render'
|
||||
RESULT_FAILED_IMAGE_DIFF = 'failed-image-diff'
|
||||
RESULT_FAILED_NO_GOLDEN = 'failed-no-golden'
|
||||
|
||||
def run_test(gltf_viewer,
|
||||
test_config,
|
||||
output_dir,
|
||||
opengl_lib=None,
|
||||
vk_icd=None):
|
||||
def _render_test_config(gltf_viewer,
|
||||
test_config,
|
||||
output_dir,
|
||||
opengl_lib=None,
|
||||
vk_icd=None):
|
||||
assert os.path.isdir(output_dir), f"output directory {output_dir} does not exist"
|
||||
assert os.access(gltf_viewer, os.X_OK)
|
||||
|
||||
named_output_dir = os.path.join(output_dir, test_config.name)
|
||||
execute(f'mkdir -p {named_output_dir}')
|
||||
mkdir_p(named_output_dir)
|
||||
|
||||
results = []
|
||||
for test in test_config.tests:
|
||||
@@ -82,11 +70,10 @@ def run_test(gltf_viewer,
|
||||
result = RESULT_OK
|
||||
out_tif_basename = f'{out_name}.tif'
|
||||
out_tif_name = f'{named_output_dir}/{out_tif_basename}'
|
||||
execute(f'mv -f {test.name}0.tif {out_tif_name}', capture_output=False)
|
||||
execute(f'mv -f {test.name}0.json {named_output_dir}/{test.name}.json',
|
||||
capture_output=False)
|
||||
mv_f(f'{test.name}0.tif', out_tif_name)
|
||||
mv_f(f'{test.name}0.json', f'{named_output_dir}/{test.name}.json')
|
||||
else:
|
||||
result = RESULT_FAILED_TO_RENDER
|
||||
result = RESULT_FAILED
|
||||
important_print(f'{test_desc} rendering failed with error={out_code}')
|
||||
|
||||
results.append({
|
||||
@@ -96,23 +83,6 @@ def run_test(gltf_viewer,
|
||||
})
|
||||
return named_output_dir, results
|
||||
|
||||
def compare_goldens(render_results, output_dir, goldens):
|
||||
for result in render_results:
|
||||
if result['result'] != RESULT_OK:
|
||||
continue
|
||||
|
||||
out_tif_basename = f"{result['name']}.tif"
|
||||
out_tif_name = f'{output_dir}/{out_tif_basename}'
|
||||
golden_path = goldens.get(out_tif_basename)
|
||||
if not golden_path:
|
||||
result['result'] = RESULT_FAILED_NO_GOLDEN
|
||||
result['result_code'] = 1
|
||||
elif not same_image(golden_path, out_tif_name):
|
||||
result['result'] = RESULT_FAILED_IMAGE_DIFF
|
||||
result['result_code'] = 1
|
||||
|
||||
return render_results
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = ArgParseImpl()
|
||||
parser.add_argument('--test', help='Configuration of the test', required=True)
|
||||
@@ -120,40 +90,26 @@ if __name__ == "__main__":
|
||||
parser.add_argument('--output_dir', help='Output Directory', required=True)
|
||||
parser.add_argument('--opengl_lib', help='Path to the folder containing OpenGL driver lib (for LD_LIBRARY_PATH)')
|
||||
parser.add_argument('--vk_icd', help='Path to VK ICD file')
|
||||
parser.add_argument('--golden_branch', help='Branch of the golden repo to compare against')
|
||||
|
||||
args, _ = parser.parse_known_args(sys.argv[1:])
|
||||
test = parse_test_config_from_path(args.test)
|
||||
test = test_config.parse_from_path(args.test)
|
||||
|
||||
output_dir, results = \
|
||||
run_test(args.gltf_viewer,
|
||||
test,
|
||||
args.output_dir,
|
||||
opengl_lib=args.opengl_lib,
|
||||
vk_icd=args.vk_icd)
|
||||
_render_test_config(args.gltf_viewer,
|
||||
test,
|
||||
args.output_dir,
|
||||
opengl_lib=args.opengl_lib,
|
||||
vk_icd=args.vk_icd)
|
||||
|
||||
# The presence of this argument indicates comparison against a set of goldens.
|
||||
if args.golden_branch:
|
||||
# prepare goldens working directory
|
||||
tmp_golden_dir = '/tmp/renderdiff-goldens'
|
||||
execute(f'mkdir -p {tmp_golden_dir}')
|
||||
|
||||
# Download the golden repo into the current working directory
|
||||
golden_manager = GoldenManager(os.getcwd())
|
||||
golden_manager.download_to(tmp_golden_dir, branch=args.golden_branch)
|
||||
|
||||
goldens = {
|
||||
os.path.basename(fpath) : fpath for fpath in \
|
||||
glob.glob(f'{os.path.join(tmp_golden_dir, test.name)}/**/*.tif', recursive=True)
|
||||
}
|
||||
results = compare_goldens(results, output_dir, goldens)
|
||||
|
||||
|
||||
with open(f'{output_dir}/results.json', 'w') as f:
|
||||
with open(f'{output_dir}/render_results.json', 'w') as f:
|
||||
f.write(json.dumps(results))
|
||||
execute(f'cp {args.test} {output_dir}/test.json')
|
||||
|
||||
shutil.copy2(args.test, f'{output_dir}/test.json')
|
||||
|
||||
failed = [f" {k['name']}" for k in results if k['result'] != RESULT_OK]
|
||||
success_count = len(results) - len(failed)
|
||||
important_print(f'Successfully tested {success_count} / {len(results)}' +
|
||||
('\nFailed:\n' + ('\n'.join(failed)) if len(failed) > 0 else ''))
|
||||
important_print(f'Successfully rendered {success_count} / {len(results)} tests' +
|
||||
('\nFailed:\n' + ('\n'.join(failed)) if len(failed) > 0 else ''))
|
||||
|
||||
if len(failed) > 0:
|
||||
exit(1)
|
||||
@@ -0,0 +1,3 @@
|
||||
RESULT_OK = 'ok'
|
||||
RESULT_FAILED = 'failed'
|
||||
RESULT_MISSING = 'missing'
|
||||
@@ -129,7 +129,7 @@ def _remove_comments_from_json_txt(json_txt):
|
||||
res.append(line)
|
||||
return '\n'.join(res)
|
||||
|
||||
def parse_test_config_from_path(config_path):
|
||||
def parse_from_path(config_path):
|
||||
with open(config_path, 'r') as f:
|
||||
json_txt = json.loads(_remove_comments_from_json_txt(f.read()))
|
||||
return RenderTestConfig(json_txt)
|
||||
@@ -0,0 +1,171 @@
|
||||
# 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.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import glob
|
||||
import time
|
||||
|
||||
from golden_manager import GoldenManager, ACCESS_TYPE_SSH, ACCESS_TYPE_TOKEN
|
||||
from image_diff import same_image
|
||||
|
||||
from utils import execute, ArgParseImpl
|
||||
from utils import prompt_helper, PROMPT_YES, PROMPT_NO
|
||||
|
||||
def line_prompt(prompt, validator=lambda a:True):
|
||||
while True:
|
||||
res = input(f'{prompt} => ').strip()
|
||||
if validator(res):
|
||||
return res
|
||||
return None
|
||||
|
||||
CONFIG_NEW_SRC_DIR = 'goldens_dir'
|
||||
CONFIG_GOLDENS_BRANCH = 'goldens_branch'
|
||||
CONFIG_GOLDENS_UPDATES = 'goldens_updates'
|
||||
CONFIG_GOLDENS_DELETES = 'goldens_deletes'
|
||||
CONFIG_AUTO_COMMIT = 'auto-commit'
|
||||
CONFIG_COMMIT_MSG = 'commit_msg'
|
||||
|
||||
def _get_current_branch():
|
||||
code, res = execute('git branch --show-current')
|
||||
return res.strip()
|
||||
|
||||
def _file_as_str(fpath):
|
||||
with open(fpath, 'r') as f:
|
||||
return f.read()
|
||||
|
||||
def _do_update(golden_manager, config):
|
||||
deletes = config[CONFIG_GOLDENS_DELETES]
|
||||
updates = config[CONFIG_GOLDENS_UPDATES]
|
||||
if len(deletes) == 0 and len(updates) == 0:
|
||||
print('Nothing to update. Exiting...')
|
||||
exit(0)
|
||||
|
||||
branch = config[CONFIG_GOLDENS_BRANCH]
|
||||
src_dir = config[CONFIG_NEW_SRC_DIR]
|
||||
auto_commit = config[CONFIG_AUTO_COMMIT]
|
||||
commit_msg = config[CONFIG_COMMIT_MSG]
|
||||
golden_manager.source_from(src_dir, commit_msg, branch,
|
||||
updates=updates,
|
||||
deletes=deletes,
|
||||
push_to_remote=auto_commit)
|
||||
|
||||
def _get_deletes_updates(update_dir, golden_dir):
|
||||
ret_delete = []
|
||||
ret_update = []
|
||||
for ext in ['tif', 'json']:
|
||||
base = set(glob.glob(f'./**/*.{ext}', root_dir=golden_dir, recursive=True))
|
||||
new = set(glob.glob(f'./**/*.{ext}', root_dir=update_dir, recursive=True))
|
||||
|
||||
delete = list(base - new)
|
||||
update = list(new - base)
|
||||
|
||||
for fpath in base.intersection(new):
|
||||
base_fpath = os.path.join(golden_dir, fpath)
|
||||
new_fpath = os.path.join(update_dir, fpath)
|
||||
if (ext == 'tif' and not same_image(new_fpath, base_fpath)) or \
|
||||
(ext == 'json' and _file_as_str(new_fpath) != _file_as_str(base_fpath)):
|
||||
update.append(fpath)
|
||||
|
||||
ret_update += update
|
||||
ret_delete += delete
|
||||
|
||||
return ret_delete, ret_update
|
||||
|
||||
# Ask a bunch of questions to gather the configuration for the update
|
||||
def _interactive_mode(base_golden_dir):
|
||||
config = {}
|
||||
cur_branch = _get_current_branch()
|
||||
if prompt_helper(
|
||||
f'Generate the new goldens from your local ' \
|
||||
f'Filament branch? (branch={cur_branch})') == PROMPT_YES:
|
||||
code, res = execute('bash ./test/renderdiff/generate.sh',
|
||||
capture_output=False)
|
||||
if code != 0:
|
||||
print('Failed to generate new goldens')
|
||||
exit(1)
|
||||
config[CONFIG_NEW_SRC_DIR] = os.path.join(os.getcwd(), './out/renderdiff_tests/')
|
||||
else:
|
||||
def validator(src_dir):
|
||||
if not os.path.exists(src_dir):
|
||||
print(f'Cannot find directory {src_dir}. Please try again.')
|
||||
return False
|
||||
return True
|
||||
|
||||
config[CONFIG_NEW_SRC_DIR] = line_prompt(
|
||||
'Please provide path of directory containing new goldens',
|
||||
validator)
|
||||
|
||||
if prompt_helper(f'Update new goldens to branch={cur_branch}? '
|
||||
'(Note that this refers to a branch in the goldens repo, not the Filament repo.)'
|
||||
) == PROMPT_YES:
|
||||
config[CONFIG_GOLDENS_BRANCH] = cur_branch
|
||||
else:
|
||||
config[CONFIG_GOLDENS_BRANCH] = line_prompt('Please provide new branch name for update')
|
||||
|
||||
if prompt_helper(f'Provide a commit message?') == PROMPT_YES:
|
||||
config[CONFIG_COMMIT_MSG] = line_prompt('Message:')
|
||||
else:
|
||||
config[CONFIG_COMMIT_MSG] = f'Update {time.time()} from filament ({cur_branch})'
|
||||
|
||||
new_golden_dir = config[CONFIG_NEW_SRC_DIR]
|
||||
deletes, updates = _get_deletes_updates(new_golden_dir, base_golden_dir)
|
||||
if len(deletes) + len(updates) != 0:
|
||||
prompt = 'The following files will be changed:\n' + \
|
||||
'\n'.join([f' {fname} [delete]' for fname in deletes]) + \
|
||||
'\n'.join([f' {fname} [update]' for fname in updates]) + \
|
||||
'\nIs that ok?'
|
||||
if prompt_helper(prompt) == PROMPT_YES:
|
||||
config[CONFIG_GOLDENS_DELETES] = deletes
|
||||
config[CONFIG_GOLDENS_UPDATES] = updates
|
||||
else:
|
||||
# We cannot proceed if user answered no.
|
||||
exit(1)
|
||||
else:
|
||||
config[CONFIG_GOLDENS_DELETES] = []
|
||||
config[CONFIG_GOLDENS_UPDATES] = []
|
||||
|
||||
config[CONFIG_AUTO_COMMIT] = \
|
||||
prompt_helper(f'Commit golden repo changes to remote?') == PROMPT_YES
|
||||
return config
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = ArgParseImpl()
|
||||
parser.add_argument('--branch', help='Branch of the golden repo to write to')
|
||||
parser.add_argument('--source', help='Directory containing the new goldens')
|
||||
parser.add_argument('--commit-msg', help='Message for the commit to the golden repo')
|
||||
parser.add_argument('--golden-repo-token', help='Access token for the golden repo')
|
||||
|
||||
args, _ = parser.parse_known_args(sys.argv[1:])
|
||||
config = {}
|
||||
golden_manager = GoldenManager(
|
||||
os.getcwd(),
|
||||
access_type=ACCESS_TYPE_SSH if not args.golden_repo_token else ACCESS_TYPE_TOKEN,
|
||||
access_token=args.golden_repo_token
|
||||
)
|
||||
base_golden_dir = golden_manager.directory()
|
||||
if args.branch and args.source and args.commit_msg:
|
||||
assert os.path.exists(args.source), f'{args.source} (--source) directory not found'
|
||||
deletes, updates = _get_deletes_updates(args.source, base_golden_dir)
|
||||
config = {
|
||||
CONFIG_AUTO_COMMIT: True,
|
||||
CONFIG_GOLDENS_BRANCH: args.branch,
|
||||
CONFIG_NEW_SRC_DIR: args.source,
|
||||
CONFIG_GOLDENS_UPDATES: updates,
|
||||
CONFIG_GOLDENS_DELETES: deletes,
|
||||
CONFIG_COMMIT_MSG: args.commit_msg,
|
||||
}
|
||||
else:
|
||||
config = _interactive_mode(base_golden_dir)
|
||||
_do_update(golden_manager, config)
|
||||
@@ -16,6 +16,7 @@ import subprocess
|
||||
import os
|
||||
import argparse
|
||||
import sys
|
||||
import pathlib
|
||||
|
||||
def execute(cmd,
|
||||
cwd=None,
|
||||
@@ -66,3 +67,52 @@ class ArgParseImpl(argparse.ArgumentParser):
|
||||
sys.stderr.write('error: %s\n' % message)
|
||||
self.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
PROMPT_YES = 'y'
|
||||
PROMPT_NO = 'n'
|
||||
PROMPT_YES_NO = f'{PROMPT_YES}{PROMPT_NO}'
|
||||
|
||||
class GetCh:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __call__(self):
|
||||
import sys, tty, termios
|
||||
fd = sys.stdin.fileno()
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(sys.stdin.fileno())
|
||||
ch = sys.stdin.read(1)
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
return ch
|
||||
|
||||
getch = GetCh()
|
||||
|
||||
def prompt_helper(prompt_str, keys=PROMPT_YES_NO):
|
||||
while True:
|
||||
print(f'{prompt_str}: [' + ', '.join(keys) + '] => ', end='', flush=True)
|
||||
val = getch()
|
||||
print(val)
|
||||
if val in keys or ord(val) == 3: # If user pressed Ctrl+c
|
||||
if ord(val) == 3:
|
||||
exit(1)
|
||||
return val
|
||||
|
||||
def mkdir_p(path_str):
|
||||
pathlib.Path(path_str).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def mv_f(src_str, dst_str):
|
||||
src = pathlib.Path(src_str)
|
||||
src.replace(dst_str)
|
||||
|
||||
def important_print(msg):
|
||||
lines = msg.split('\n')
|
||||
max_len = max([len(l) for l in lines])
|
||||
print('-' * (max_len + 8))
|
||||
for line in lines:
|
||||
diff = max_len - len(line)
|
||||
information = f'--- {line} ' + (' ' * diff) + '---'
|
||||
print(information)
|
||||
print('-' * (max_len + 8))
|
||||
|
||||
@@ -17,19 +17,29 @@ from utils import execute
|
||||
def get_last_commit():
|
||||
res, o = execute('git log -1')
|
||||
commit, author, date, _, title, *desc = o.split('\n')
|
||||
commit = commit.split(' ')[1]
|
||||
title = title.strip()
|
||||
|
||||
desc = [l.strip() for l in desc[1:]]
|
||||
if len(desc) > 0 and len(desc[0]) == 0:
|
||||
while len(desc) > 0 and len(desc[0]) == 0:
|
||||
desc = desc[1:]
|
||||
|
||||
return (
|
||||
commit.split(' ')[1],
|
||||
title.strip(),
|
||||
commit,
|
||||
title,
|
||||
desc
|
||||
)
|
||||
|
||||
def sanitized_split(line, split_atom='\n'):
|
||||
return list(filter(lambda x: len(x) > 0, map(lambda x: x.strip(), line.split(split_atom))))
|
||||
return list(
|
||||
filter(
|
||||
lambda x: len(x) > 0,
|
||||
map(
|
||||
lambda x: x.strip(),
|
||||
line.split(split_atom)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
RDIFF_UPDATE_GOLDEN_STR = 'RDIFF_UPDATE_GOLDEN'
|
||||
|
||||
|
||||
+10
-34
@@ -14,39 +14,15 @@
|
||||
|
||||
#!/usr/bin/bash
|
||||
|
||||
OUTPUT_DIR="$(pwd)/out/renderdiff_tests"
|
||||
RENDERDIFF_TEST_DIR="$(pwd)/test/renderdiff"
|
||||
TEST_UTILS_DIR="$(pwd)/test/utils"
|
||||
MESA_DIR="$(pwd)/mesa/out/"
|
||||
source `dirname $0`/src/preamble.sh
|
||||
|
||||
os_name=$(uname -s)
|
||||
if [[ "$os_name" == "Linux" ]]; then
|
||||
MESA_LIB_DIR="${MESA_DIR}lib/x86_64-linux-gnu"
|
||||
elif [[ "$os_name" == "Darwin" ]]; then
|
||||
MESA_LIB_DIR="${MESA_DIR}lib"
|
||||
else
|
||||
echo "Unsupported platform for renderdiff tests"
|
||||
exit 1
|
||||
fi
|
||||
GOLDEN_DIR=$(pwd)/golden_images
|
||||
|
||||
function prepare_mesa() {
|
||||
if [ ! -d ${MESA_LIB_DIR} ]; then
|
||||
bash ${TEST_UTILS_DIR}/get_mesa.sh
|
||||
fi
|
||||
}
|
||||
|
||||
# Following steps are taken:
|
||||
# - Get and build mesa
|
||||
# - Build gltf_viewer
|
||||
# - Run the python script that runs the test
|
||||
# - Zip up the result
|
||||
|
||||
set -ex && prepare_mesa && \
|
||||
mkdir -p ${OUTPUT_DIR} && \
|
||||
CXX=`which clang++` CC=`which clang` ./build.sh -f -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} \
|
||||
--opengl_lib=${MESA_LIB_DIR} \
|
||||
--golden_branch=main
|
||||
start_ && \
|
||||
bash `dirname $0`/generate.sh && \
|
||||
mkdir -p ${GOLDEN_DIR} && \
|
||||
python3 ${RENDERDIFF_TEST_DIR}/src/golden_manager.py --output=${GOLDEN_DIR} && \
|
||||
python3 ${RENDERDIFF_TEST_DIR}/src/compare.py \
|
||||
--src=${GOLDEN_DIR} \
|
||||
--dest=${OUTPUT_DIR} && \
|
||||
end_
|
||||
|
||||
+4
-4
@@ -229,10 +229,10 @@ MaybeError ValidateWriteBuffer(const DeviceBase* device,
|
||||
uint64_t size) {
|
||||
DAWN_TRY(device->ValidateObject(buffer));
|
||||
|
||||
// DAWN_INVALID_IF(bufferOffset % 4 != 0, "BufferOffset (%u) is not a multiple of 4.",
|
||||
// bufferOffset);
|
||||
//
|
||||
// DAWN_INVALID_IF(size % 4 != 0, "Size (%u) is not a multiple of 4.", size);
|
||||
DAWN_INVALID_IF(bufferOffset % 4 != 0, "BufferOffset (%u) is not a multiple of 4.",
|
||||
bufferOffset);
|
||||
|
||||
DAWN_INVALID_IF(size % 4 != 0, "Size (%u) is not a multiple of 4.", size);
|
||||
|
||||
uint64_t bufferSize = buffer->GetSize();
|
||||
DAWN_INVALID_IF(bufferOffset > bufferSize || size > (bufferSize - bufferOffset),
|
||||
|
||||
@@ -196,9 +196,43 @@ static bool processParameter(MaterialBuilder& builder, const JsonishObject& json
|
||||
auto typeString = typeValue->toJsonString()->getString();
|
||||
auto nameString = nameValue->toJsonString()->getString();
|
||||
|
||||
const JsonishValue* stagesValue = jsonObject.getValue("stages");
|
||||
using filament::backend::ShaderStageFlags;
|
||||
auto stages = ShaderStageFlags::NONE;
|
||||
if (stagesValue) {
|
||||
if (stagesValue->getType() != JsonishValue::ARRAY) {
|
||||
std::cerr << "parameters: stages must be an ARRAY." << std::endl;
|
||||
return false;
|
||||
}
|
||||
for (auto value: stagesValue->toJsonArray()->getElements()) {
|
||||
if (value->getType() == JsonishValue::Type::STRING) {
|
||||
using namespace std::literals;
|
||||
using Qualifier = filament::BufferInterfaceBlock::Qualifier;
|
||||
auto stageString = value->toJsonString()->getString();
|
||||
if (Enums::isValid<ShaderStageType>(stageString)) {
|
||||
stages |= Enums::toEnum<ShaderStageType>(stageString);
|
||||
} else {
|
||||
std::cerr << "stages: the stage '" << stageString
|
||||
<< "' for parameter with name '" << nameString
|
||||
<< "' is not a valid shader stage." << std::endl;
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
std::cerr << "parameters: stages must be an array of STRINGs." << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t const arraySize = extractArraySize(typeString);
|
||||
|
||||
if (Enums::isValid<UniformType>(typeString)) {
|
||||
if (stages != ShaderStageFlags::NONE) {
|
||||
std::cerr << "parameters: the uniform parameter with name '" << nameString << "'"
|
||||
<< " has shader stages specified. Shader stages are only supported for"
|
||||
<< " samplers." << std::endl;
|
||||
return false;
|
||||
}
|
||||
MaterialBuilder::UniformType const type = Enums::toEnum<UniformType>(typeString);
|
||||
ParameterPrecision precision = ParameterPrecision::DEFAULT;
|
||||
if (precisionValue) {
|
||||
@@ -228,6 +262,11 @@ static bool processParameter(MaterialBuilder& builder, const JsonishObject& json
|
||||
|
||||
auto multisample = multiSampleValue ? multiSampleValue->toJsonBool()->getBool() : false;
|
||||
|
||||
if (stages == ShaderStageFlags::NONE) {
|
||||
// TODO: Infer the default shader stages based on which blocks are present in the
|
||||
// material.
|
||||
stages = ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT;
|
||||
}
|
||||
if (transformNameValue) {
|
||||
if (type != MaterialBuilder::SamplerType::SAMPLER_EXTERNAL) {
|
||||
std::cerr << "parameters: the parameter with name '" << nameString << "'"
|
||||
@@ -237,9 +276,10 @@ static bool processParameter(MaterialBuilder& builder, const JsonishObject& json
|
||||
return false;
|
||||
}
|
||||
auto transformName = transformNameValue->toJsonString()->getString();
|
||||
builder.parameter(nameString.c_str(), type, format, precision, multisample, transformName.c_str());
|
||||
builder.parameter(nameString.c_str(), type, format, precision, multisample,
|
||||
transformName.c_str(), stages);
|
||||
} else {
|
||||
builder.parameter(nameString.c_str(), type, format, precision, multisample);
|
||||
builder.parameter(nameString.c_str(), type, format, precision, multisample, "", stages);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/memalign.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <uberz/ReadableArchive.h>
|
||||
#include <uberz/WritableArchive.h>
|
||||
|
||||
Reference in New Issue
Block a user