Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07356a2949 |
@@ -1,33 +0,0 @@
|
||||
name: 'Get commit message'
|
||||
outputs:
|
||||
msg:
|
||||
value: ${{ steps.action_output.outputs.msg }}
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Find commit message (on push)
|
||||
if: github.event_name == 'push'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "${{ github.event.head_commit.message }}" >> /tmp/commit_msg.txt
|
||||
- name: Checkout code
|
||||
shell: bash
|
||||
id: checkout_code
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
echo "hash=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
git checkout ${{ github.event.pull_request.head.ref }}
|
||||
COMMIT_MESSAGE=$(git log -1)
|
||||
echo "${COMMIT_MESSAGE}" >> /tmp/commit_msg.txt
|
||||
- shell: bash
|
||||
id: action_output
|
||||
run: |
|
||||
DELIMITER="EOF_FILE_CONTENT_$(date +%s)" # Using timestamp to make it more unique
|
||||
echo "msg<<$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
cat /tmp/commit_msg.txt >> "$GITHUB_OUTPUT"
|
||||
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
- name: Uncheckout code
|
||||
shell: bash
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
git checkout ${{ steps.checkout.outputs.hash }}
|
||||
@@ -1,35 +0,0 @@
|
||||
name: 'Post-submit tasks'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
update-renderdiff-goldens:
|
||||
name: update-renderdiff-goldens
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- name: Prerequisites
|
||||
run: pip install tifffile numpy
|
||||
- name: Run update script
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
||||
run: |
|
||||
GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | python3 test/renderdiff/src/commit_msg.py)
|
||||
COMMIT_HASH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | tr -d 'commit ')
|
||||
if [[ "${GOLDEN_BRANCH}" != "main" ]]; then
|
||||
git config --global user.email "filament.bot@gmail.com"
|
||||
git config --global user.name "Filament Bot"
|
||||
git config --global credential.helper cache
|
||||
echo "branch==${GOLDEN_BRANCH}"
|
||||
echo "hash==${COMMIT_HASH}"
|
||||
python3 test/renderdiff/src/update_golden.py --branch=${GOLDEN_BRANCH} \
|
||||
--merge-to-main --filament-tag=${COMMIT_HASH} --golden-repo-token=${GH_TOKEN}
|
||||
fi
|
||||
@@ -3,10 +3,10 @@ name: Presubmit
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-desktop-mac:
|
||||
@@ -41,7 +41,8 @@ jobs:
|
||||
|
||||
build-windows:
|
||||
name: build-windows
|
||||
runs-on: windows-2022-32core
|
||||
runs-on: win-2019-16core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -109,35 +110,31 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Check for manual edits to /docs
|
||||
run: |
|
||||
echo "${{ github.event.pull_request.head.sha }} -- ${{ github.event.pull_request.head.sha }}"
|
||||
# disable for now
|
||||
# bash docs_src/build/presubmit_check.sh ${{ github.event.pull_request.head.sha }}
|
||||
echo "${{ github.event.pull_request.head.sha }} -- ${{ github.event.pull_request.head.sha }}"
|
||||
# disable for now
|
||||
# bash docs_src/build/presubmit_check.sh ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
test-renderdiff:
|
||||
name: test-renderdiff
|
||||
runs-on: macos-14-xlarge
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/mac-prereq
|
||||
- name: Cache Mesa and deps
|
||||
id: mesa-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: mesa
|
||||
key: ${{ runner.os }}-mesa-deps-2-${{ vars.MESA_VERSION }}
|
||||
- name: Prerequisites
|
||||
id: prereqs
|
||||
run: |
|
||||
bash build/common/get-mesa.sh
|
||||
pip install tifffile numpy
|
||||
- name: Run Test
|
||||
run: |
|
||||
echo "${{ steps.get_commit_msg.outputs.msg }}" | bash test/renderdiff/test.sh
|
||||
run: bash test/renderdiff/test.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: presubmit-renderdiff-result
|
||||
|
||||
@@ -226,7 +226,7 @@ jobs:
|
||||
|
||||
build-windows:
|
||||
name: build-windows
|
||||
runs-on: windows-2022-32core
|
||||
runs-on: windows-2019-32core
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'windows'
|
||||
|
||||
steps:
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
build-windows:
|
||||
name: build-windows
|
||||
runs-on: windows-2022-32core
|
||||
runs-on: windows-2019-32core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.61.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.60.1'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.61.1'
|
||||
pod 'Filament', '~> 1.60.1'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,9 +7,6 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.61.1
|
||||
|
||||
|
||||
## v1.61.0
|
||||
|
||||
- materials: sampler now export their type in the material binary [⚠️ **New Material Version**]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.61.1
|
||||
VERSION_NAME=1.60.1
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -41,8 +41,6 @@ for cmd in "${NEEDED_PYTHON_DEPS[@]}"; do
|
||||
done
|
||||
deactivate
|
||||
|
||||
LOCAL_PKG_CONFIG_PATH=
|
||||
|
||||
# Install system deps
|
||||
if [[ "$OS_NAME" == "Linux" ]]; then
|
||||
if [[ "$GITHUB_WORKFLOW" ]]; then
|
||||
@@ -84,9 +82,6 @@ elif [[ "$OS_NAME" == "Darwin" ]]; then
|
||||
fi
|
||||
fi
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=true brew install autoconf automake libx11 libxext libxrandr llvm@${LLVM_VERSION} ninja meson pkg-config libxshmfence
|
||||
|
||||
# For reasons unknown, this is necessary for pkg-config to find homebrew's packages
|
||||
LOCAL_PKG_CONFIG_PATH="/opt/homebrew/lib/pkgconfig:$PKG_CONFIG_PATH"
|
||||
fi # [[ "$OS_NAME" == x ]]
|
||||
|
||||
LOCAL_LDFLAGS=${LDFLAGS}
|
||||
@@ -139,11 +134,9 @@ fi
|
||||
# -Dgallium-drivers=swrast => builds GL software rasterizer
|
||||
# -Dvulkan-drivers=swrast => builds VK software rasterizer
|
||||
# -Dgallium-drivers=llvmpipe is needed for GL >= 4.1 pipe-screen (see src/gallium/auxiliary/target-helpers/inline_sw_helper.h)
|
||||
PKG_CONFIG_PATH=${LOCAL_PKG_CONFIG_PATH} PATH=${LOCAL_PATH} \
|
||||
CXX=${LOCAL_CXX} CC=${LOCAL_CC} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
|
||||
CXX=${LOCAL_CXX} CC=${LOCAL_CC} PATH=${LOCAL_PATH} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
|
||||
meson setup --wipe builddir/ -Dprefix="${MESA_DIR}/out" -Dglx=xlib -Dosmesa=true -Dgallium-drivers=llvmpipe,swrast -Dvulkan-drivers=swrast
|
||||
PKG_CONFIG_PATH=${LOCAL_PKG_CONFIG_PATH} PATH=${LOCAL_PATH} \
|
||||
CXX=${LOCAL_CXX} CC=${LOCAL_CC} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
|
||||
CXX=${LOCAL_CXX} CC=${LOCAL_CC} PATH=${LOCAL_PATH} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
|
||||
meson install -C builddir/
|
||||
|
||||
# Disable python venv
|
||||
|
||||
@@ -47,11 +47,7 @@ if "%RUNNING_LOCALLY%" == "1" (
|
||||
set "PATH=%PATH%;C:\Program Files\7-Zip"
|
||||
)
|
||||
|
||||
:: Outdated windows-2019 pattern
|
||||
:: call "C:\Program Files (x86)\Microsoft Visual Studio\2019\%VISUAL_STUDIO_VERSION%\VC\Auxiliary\Build\vcvars64.bat"
|
||||
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\%VISUAL_STUDIO_VERSION%\VC\Auxiliary\Build\vcvars64.bat"
|
||||
echo Passed vcvars64.bat
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\%VISUAL_STUDIO_VERSION%\VC\Auxiliary\Build\vcvars64.bat"
|
||||
if errorlevel 1 exit /b %errorlevel%
|
||||
|
||||
msbuild /version
|
||||
@@ -111,7 +107,7 @@ cd out\cmake-%variant%
|
||||
if errorlevel 1 exit /b %errorlevel%
|
||||
|
||||
cmake ..\.. ^
|
||||
-G "Visual Studio 17 2022" ^
|
||||
-G "Visual Studio 16 2019" ^
|
||||
-A x64 ^
|
||||
%flag% ^
|
||||
-DCMAKE_INSTALL_PREFIX=..\%variant% ^
|
||||
|
||||
@@ -222,6 +222,56 @@ public:
|
||||
return static_cast<Dp>(p);
|
||||
}
|
||||
|
||||
template<typename Dp, typename B>
|
||||
inline std::enable_if_t<
|
||||
std::is_pointer_v<Dp> && std::is_base_of_v<B, std::remove_pointer_t<Dp>>, int16_t>
|
||||
handle_set_value(Handle<B>& handle, uint8_t v) {
|
||||
assert_invariant(handle);
|
||||
auto [p, tag] = handleToPointer(handle.getId());
|
||||
|
||||
if (isPoolHandle(handle.getId())) {
|
||||
// check for pool handle use-after-free
|
||||
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
|
||||
auto const pNode = static_cast<typename Allocator::Node*>(p);
|
||||
pNode[-1].test_value = v;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
// check for heap handle use-after-free
|
||||
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
|
||||
return -1;
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Dp, typename B>
|
||||
inline std::enable_if_t<
|
||||
std::is_pointer_v<Dp> && std::is_base_of_v<B, std::remove_pointer_t<Dp>>, int16_t>
|
||||
handle_get_value(Handle<B>& handle) {
|
||||
assert_invariant(handle);
|
||||
auto [p, tag] = handleToPointer(handle.getId());
|
||||
|
||||
if (isPoolHandle(handle.getId())) {
|
||||
// check for pool handle use-after-free
|
||||
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
|
||||
auto const pNode = static_cast<typename Allocator::Node*>(p);
|
||||
uint8_t age = pNode[-1].age;
|
||||
uint8_t test_value = pNode[-1].test_value;
|
||||
int16_t ret = (int16_t) ((age << 8) | test_value);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
// check for heap handle use-after-free
|
||||
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
|
||||
return -1;
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
utils::CString getHandleTag(HandleBase::HandleId key) const noexcept;
|
||||
|
||||
template<typename B>
|
||||
@@ -276,7 +326,10 @@ private:
|
||||
class Allocator {
|
||||
friend class HandleAllocator;
|
||||
static constexpr size_t MIN_ALIGNMENT = alignof(std::max_align_t);
|
||||
struct Node { uint8_t age; };
|
||||
struct Node {
|
||||
uint8_t age;
|
||||
uint8_t test_value = 0xff;
|
||||
};
|
||||
// Note: using the `extra` parameter of PoolAllocator<>, even with a 1-byte structure,
|
||||
// generally increases all pool allocations by 8-bytes because of alignment restrictions.
|
||||
template<size_t SIZE>
|
||||
|
||||
@@ -643,6 +643,25 @@ void OpenGLDriver::createVertexBufferInfoR(
|
||||
AttributeArray attributes) {
|
||||
DEBUG_MARKER()
|
||||
construct<GLVertexBufferInfo>(vbih, bufferCount, attributeCount, attributes);
|
||||
|
||||
// test ----
|
||||
int16_t returned_value = handle_set_value<GLVertexBufferInfo*>(vbih, 90);
|
||||
uint8_t age = (uint8_t) (returned_value >> 8);
|
||||
uint8_t test_value = (returned_value & 0xff);
|
||||
switch (test_value) {
|
||||
case -2:
|
||||
case -1:
|
||||
case 0:
|
||||
utils::slog.w << "@@@ createVertexBufferInfoR: unexpected result, age:" << (int) age
|
||||
<< ", test_value:" << (int) test_value << utils::io::endl;
|
||||
break;
|
||||
default:
|
||||
FILAMENT_CHECK_POSTCONDITION(test_value == 1)
|
||||
<< "@@@ createVertexBufferInfoR: shouldn't happen, age:" << (int) age
|
||||
<< ", test_value:" << (int) test_value << utils::io::endl;
|
||||
break;
|
||||
}
|
||||
//----------
|
||||
}
|
||||
|
||||
void OpenGLDriver::createVertexBufferR(
|
||||
@@ -761,6 +780,25 @@ void OpenGLDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||
|
||||
construct<OpenGLProgram>(ph, *this, std::move(program));
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
|
||||
// test ----
|
||||
int16_t returned_value = handle_set_value<OpenGLProgram*>(ph, 100);
|
||||
uint8_t age = (uint8_t) (returned_value >> 8);
|
||||
uint8_t test_value = (returned_value & 0xff);
|
||||
switch (test_value) {
|
||||
case -2:
|
||||
case -1:
|
||||
case 0:
|
||||
utils::slog.w << "@@@ createProgramR: unexpected result, age:" << (int)age << ", test_value:" << (int)test_value
|
||||
<< utils::io::endl;
|
||||
break;
|
||||
default:
|
||||
FILAMENT_CHECK_POSTCONDITION(test_value == 1)
|
||||
<< "@@@ createProgramR: shouldn't happen, age:" << (int)age
|
||||
<< ", test_value:" << (int)test_value << utils::io::endl;
|
||||
break;
|
||||
}
|
||||
//----------
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
@@ -1918,6 +1956,25 @@ void OpenGLDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
void OpenGLDriver::destroyProgram(Handle<HwProgram> ph) {
|
||||
DEBUG_MARKER()
|
||||
if (ph) {
|
||||
// test ----
|
||||
int16_t returned_value = handle_set_value<OpenGLProgram*>(ph, 101);
|
||||
uint8_t age = (uint8_t) (returned_value >> 8);
|
||||
uint8_t test_value = (returned_value & 0xff);
|
||||
switch (test_value) {
|
||||
case -2:
|
||||
case -1:
|
||||
case 0:
|
||||
utils::slog.w << "@@@ destroyProgram: unexpected result, age:" << (int) age
|
||||
<< ", test_value:" << (int) test_value << utils::io::endl;
|
||||
break;
|
||||
default:
|
||||
FILAMENT_CHECK_POSTCONDITION(test_value == 1)
|
||||
<< "@@@ destroyProgram: shouldn't happen, age:" << (int) age
|
||||
<< ", test_value:" << (int) test_value << utils::io::endl;
|
||||
break;
|
||||
}
|
||||
//----------
|
||||
|
||||
OpenGLProgram* p = handle_cast<OpenGLProgram*>(ph);
|
||||
destruct(ph, p);
|
||||
}
|
||||
@@ -2542,12 +2599,7 @@ void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain>
|
||||
[this](size_t index) {
|
||||
for (auto t: mTexturesWithStreamsAttached) {
|
||||
if (t->hwStream->streamType == StreamType::NATIVE) {
|
||||
if (t->externalTexture) {
|
||||
glGenTextures(1, &t->externalTexture->id);
|
||||
t->gl.id = t->externalTexture->id;
|
||||
} else {
|
||||
glGenTextures(1, &t->gl.id);
|
||||
}
|
||||
glGenTextures(1, &t->gl.id);
|
||||
mPlatform.attach(t->hwStream->stream, t->gl.id);
|
||||
mContext.updateTexImage(GL_TEXTURE_EXTERNAL_OES, t->gl.id);
|
||||
}
|
||||
@@ -3032,7 +3084,6 @@ void OpenGLDriver::attachStream(GLTexture* t, GLStream* hwStream) noexcept {
|
||||
switch (hwStream->streamType) {
|
||||
case StreamType::NATIVE:
|
||||
mPlatform.attach(hwStream->stream, t->gl.id);
|
||||
mContext.updateTexImage(GL_TEXTURE_EXTERNAL_OES, t->gl.id);
|
||||
break;
|
||||
case StreamType::ACQUIRED:
|
||||
break;
|
||||
@@ -3061,12 +3112,7 @@ void OpenGLDriver::detachStream(GLTexture* t) noexcept {
|
||||
break;
|
||||
}
|
||||
|
||||
if (t->externalTexture) {
|
||||
glGenTextures(1, &t->externalTexture->id);
|
||||
t->gl.id = t->externalTexture->id;
|
||||
} else {
|
||||
glGenTextures(1, &t->gl.id);
|
||||
}
|
||||
glGenTextures(1, &t->gl.id);
|
||||
|
||||
t->hwStream = nullptr;
|
||||
}
|
||||
@@ -3090,14 +3136,8 @@ void OpenGLDriver::replaceStream(GLTexture* texture, GLStream* newStream) noexce
|
||||
|
||||
switch (newStream->streamType) {
|
||||
case StreamType::NATIVE:
|
||||
if (texture->externalTexture) {
|
||||
glGenTextures(1, &texture->externalTexture->id);
|
||||
texture->gl.id = texture->externalTexture->id;
|
||||
} else {
|
||||
glGenTextures(1, &texture->gl.id);
|
||||
}
|
||||
glGenTextures(1, &texture->gl.id);
|
||||
mPlatform.attach(newStream->stream, texture->gl.id);
|
||||
mContext.updateTexImage(GL_TEXTURE_EXTERNAL_OES, texture->gl.id);
|
||||
break;
|
||||
case StreamType::ACQUIRED:
|
||||
// Just re-use the old texture id.
|
||||
@@ -4083,6 +4123,24 @@ void OpenGLDriver::bindPipeline(PipelineState const& state) {
|
||||
setRasterState(state.rasterState);
|
||||
setStencilState(state.stencilState);
|
||||
gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant);
|
||||
// test ----
|
||||
Handle<HwProgram> pro = state.program;
|
||||
int16_t returned_value = handle_get_value<OpenGLProgram*>(pro);
|
||||
uint8_t age = (uint8_t)(returned_value >> 8);
|
||||
uint8_t test_value = (returned_value & 0xff);
|
||||
switch (test_value) {
|
||||
case -2:
|
||||
case -1:
|
||||
case 0:
|
||||
utils::slog.w << "@@@ bindPipeline: unexpected result, age:" << (int)age << ", test_value:" << (int)test_value << utils::io::endl;
|
||||
break;
|
||||
default:
|
||||
if (test_value != 100) {
|
||||
utils::slog.w << "@@@ bindPipeline: program is not valid OpenGLProgram, age:" << (int)age << ", test_value:" << (int)test_value << utils::io::endl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
//----------
|
||||
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(state.program);
|
||||
mValidProgram = useProgram(p);
|
||||
(*mCurrentPushConstants) = p->getPushConstants();
|
||||
|
||||
@@ -282,6 +282,20 @@ private:
|
||||
return mHandleAllocator.handle_cast<Dp, B>(handle);
|
||||
}
|
||||
|
||||
template<typename Dp, typename B>
|
||||
std::enable_if_t<std::is_pointer_v<Dp> && std::is_base_of_v<B, std::remove_pointer_t<Dp>>,
|
||||
int16_t>
|
||||
handle_set_value(Handle<B>& handle, uint8_t v) {
|
||||
return mHandleAllocator.handle_set_value<Dp, B>(handle, v);
|
||||
}
|
||||
|
||||
template<typename Dp, typename B>
|
||||
std::enable_if_t<std::is_pointer_v<Dp> && std::is_base_of_v<B, std::remove_pointer_t<Dp>>,
|
||||
int16_t>
|
||||
handle_get_value(Handle<B>& handle) {
|
||||
return mHandleAllocator.handle_get_value<Dp, B>(handle);
|
||||
}
|
||||
|
||||
friend class OpenGLProgram;
|
||||
friend class ShaderCompilerService;
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "OpenGLDriver.h"
|
||||
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <private/backend/BackendUtils.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
@@ -111,17 +110,7 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
cond.wait(l, [this] { return signaled; });
|
||||
}
|
||||
|
||||
// This is invoked upon token completion, which occurs after a successful `gl.program`
|
||||
// population or upon cancellation. In either scenario, the callback handle must be submitted
|
||||
// to notify the caller that resource loading has concluded.
|
||||
void trySubmittingCallback() noexcept {
|
||||
if (handle) {
|
||||
compiler.submitCallbackHandle(*handle);
|
||||
handle = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<CallbackManager::Handle> handle{};
|
||||
CallbackManager::Handle handle{};
|
||||
BlobCacheKey key;
|
||||
|
||||
// Used for the `THREAD_POOL` mode.
|
||||
@@ -131,7 +120,7 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
};
|
||||
|
||||
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() {
|
||||
trySubmittingCallback();
|
||||
compiler.submitCallbackHandle(handle);
|
||||
}
|
||||
|
||||
/* static */ void ShaderCompilerService::setUserData(const program_token_t& token,
|
||||
@@ -350,7 +339,7 @@ GLuint ShaderCompilerService::getProgram(program_token_t& token) {
|
||||
|
||||
// Cleanup the token.
|
||||
token->compiler.cancelTickOp(token);
|
||||
token = nullptr; // This will try submitting a callback handle to the callback manager.
|
||||
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
|
||||
}
|
||||
|
||||
void ShaderCompilerService::tick() {
|
||||
@@ -403,7 +392,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) {
|
||||
|
||||
// Cleanup the token.
|
||||
token->compiler.cancelTickOp(token);
|
||||
token = nullptr;
|
||||
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
|
||||
|
||||
return program;
|
||||
}
|
||||
@@ -670,7 +659,6 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
||||
}
|
||||
glLinkProgram(program);
|
||||
token->gl.program = program;
|
||||
token->trySubmittingCallback();
|
||||
}
|
||||
|
||||
/* static */ bool ShaderCompilerService::isLinkCompleted(program_token_t const& token) noexcept {
|
||||
|
||||
@@ -950,12 +950,11 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
pipelineSamples = mCurrentRenderTarget->getSamples();
|
||||
}
|
||||
|
||||
// TODO: We expected this to be a sane check, however it complains when running shadowtest.
|
||||
//if (program->fragmentShaderModule != nullptr) {
|
||||
// FILAMENT_CHECK_POSTCONDITION(!pipelineColorFormats.empty())
|
||||
// << "Render pipeline with fragment shader must have at least one color target "
|
||||
// "format.";
|
||||
//}
|
||||
if (program->fragmentShaderModule != nullptr) {
|
||||
FILAMENT_CHECK_POSTCONDITION(!pipelineColorFormats.empty())
|
||||
<< "Render pipeline with fragment shader must have at least one color target "
|
||||
"format.";
|
||||
}
|
||||
wgpu::RenderPipeline pipeline = createWebGPURenderPipeline(mDevice, *program, *vertexBufferInfo,
|
||||
layout, pipelineState.rasterState, pipelineState.stencilState,
|
||||
pipelineState.polygonOffset, pipelineState.primitiveType, pipelineColorFormats,
|
||||
|
||||
@@ -536,8 +536,8 @@ size_t WebGPUDescriptorSet::countEntitiesWithDynamicOffsets() const {
|
||||
return mEntriesWithDynamicOffsetsCount;
|
||||
}
|
||||
|
||||
WGPUTexture::WGPUTexture(SamplerType samplerTargetType, uint8_t levels, TextureFormat format,
|
||||
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage,
|
||||
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 {
|
||||
assert_invariant(
|
||||
samples == 1 ||
|
||||
@@ -552,8 +552,6 @@ WGPUTexture::WGPUTexture(SamplerType samplerTargetType, uint8_t levels, TextureF
|
||||
mAspect = fToWGPUTextureViewAspect(usage, format);
|
||||
mBlockWidth = filament::backend::getBlockWidth(format);
|
||||
mBlockHeight = filament::backend::getBlockHeight(format);
|
||||
target = samplerTargetType;
|
||||
|
||||
wgpu::TextureDescriptor textureDescriptor{
|
||||
.label = getUserTextureLabel(target),
|
||||
.usage = mUsage,
|
||||
@@ -603,7 +601,6 @@ WGPUTexture::WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount
|
||||
mAspect = src->mAspect;
|
||||
mBlockWidth = src->mBlockWidth;
|
||||
mBlockHeight = src->mBlockHeight;
|
||||
target = src->target;
|
||||
|
||||
mTexureView = makeTextureView(baseLevel, levelCount, target);
|
||||
}
|
||||
@@ -903,7 +900,7 @@ wgpu::TextureAspect WGPUTexture::fToWGPUTextureViewAspect(TextureUsage const& fU
|
||||
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 = any(fUsage & TextureUsage::SAMPLEABLE);
|
||||
const bool isSample = (fUsage == TextureUsage::SAMPLEABLE);
|
||||
|
||||
if (isDepth && !isColor && !isStencil) {
|
||||
return wgpu::TextureAspect::DepthOnly;
|
||||
|
||||
@@ -169,8 +169,8 @@ private:
|
||||
|
||||
class WGPUTexture : public HwTexture {
|
||||
public:
|
||||
WGPUTexture(SamplerType samplerTargetType, uint8_t levels, TextureFormat format,
|
||||
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage,
|
||||
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;
|
||||
|
||||
WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount) noexcept;
|
||||
|
||||
@@ -237,10 +237,9 @@ wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
|
||||
fragmentState.targetCount = colorFormats.size();
|
||||
fragmentState.targets = colorTargets.data();
|
||||
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
// We expect a fragment shader implies at least one color target if it outputs color.
|
||||
// A fragment shader implies at least one color target if it outputs color.
|
||||
// This should be guaranteed by the caller ensuring colorFormats is not empty.
|
||||
// However, this fails on shadowtest.cpp, TODO investigate why
|
||||
// assert_invariant(fragmentState.targetCount > 0);
|
||||
assert_invariant(fragmentState.targetCount > 0);
|
||||
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
|
||||
auto& colorTarget = colorTargets[targetIndex];
|
||||
colorTarget.format = colorFormats[targetIndex];
|
||||
|
||||
@@ -629,8 +629,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
.textureDimension = uint16_t(options.mapSize),
|
||||
.shadowDimension = uint16_t(options.mapSize - 2u),
|
||||
.textureSpaceFlipped = engine.getBackend() == Backend::METAL ||
|
||||
engine.getBackend() == Backend::VULKAN ||
|
||||
engine.getBackend() == Backend::WEBGPU,
|
||||
engine.getBackend() == Backend::VULKAN,
|
||||
.vsm = view.hasVSM()
|
||||
};
|
||||
|
||||
@@ -807,8 +806,7 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engin
|
||||
.textureDimension = uint16_t(options->mapSize),
|
||||
.shadowDimension = uint16_t(options->mapSize - 2u),
|
||||
.textureSpaceFlipped = engine.getBackend() == Backend::METAL ||
|
||||
engine.getBackend() == Backend::VULKAN ||
|
||||
engine.getBackend() == Backend::WEBGPU,
|
||||
engine.getBackend() == Backend::VULKAN,
|
||||
.vsm = view.hasVSM()
|
||||
};
|
||||
|
||||
@@ -900,8 +898,7 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
||||
.textureDimension = uint16_t(options->mapSize),
|
||||
.shadowDimension = uint16_t(options->mapSize), // point-lights don't have a border
|
||||
.textureSpaceFlipped = engine.getBackend() == Backend::METAL ||
|
||||
engine.getBackend() == Backend::VULKAN ||
|
||||
engine.getBackend() == Backend::WEBGPU,
|
||||
engine.getBackend() == Backend::VULKAN,
|
||||
.vsm = view.hasVSM()
|
||||
};
|
||||
|
||||
|
||||
@@ -429,7 +429,7 @@ void FMaterialInstance::fixMissingSamplers() const {
|
||||
FEngine const& engine = mMaterial->getEngine();
|
||||
filament::DescriptorSetLayout const& layout = mMaterial->getDescriptorSetLayout();
|
||||
|
||||
if (pos->format == SamplerFormat::FLOAT) {
|
||||
if (pos->format != SamplerFormat::FLOAT) {
|
||||
// TODO: we only handle missing samplers that are FLOAT
|
||||
switch (pos->type) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.61.1"
|
||||
spec.version = "1.60.1"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.61.1/filament-v1.61.1-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.60.1/filament-v1.60.1-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
namespace filament {
|
||||
|
||||
// update this when a new version of filament wouldn't work with older materials
|
||||
static constexpr size_t MATERIAL_VERSION = 61;
|
||||
static constexpr size_t MATERIAL_VERSION = 60;
|
||||
|
||||
/**
|
||||
* Supported shading models
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
# define FILAMENT_APPLE_SYSTRACE 0
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID__) && FILAMENT_TRACING_ENABLED
|
||||
#if defined(__ANDROID__)
|
||||
# include <private/utils/android/Tracing.h>
|
||||
#elif defined(__APPLE__) && FILAMENT_APPLE_SYSTRACE && FILAMENT_TRACING_ENABLED
|
||||
#elif defined(__APPLE__) && FILAMENT_APPLE_SYSTRACE
|
||||
# include <private/utils/darwin/Tracing.h>
|
||||
#else
|
||||
|
||||
|
||||
@@ -14,13 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO: Clean-up. We shouldn't need this #ifndef here, but a client has requested that perfetto be
|
||||
// disabled due to size increase. In their case, this flag would be defined across targets. Hence
|
||||
// we guard below with an #ifndef.
|
||||
#ifndef FILAMENT_TRACING_ENABLED
|
||||
// Note: The overhead of TRACING is not negligible especially with parallel_for().
|
||||
#define FILAMENT_TRACING_ENABLED false
|
||||
#endif
|
||||
|
||||
// when FILAMENT_TRACING_ENABLED is true, enables even heavier tracing
|
||||
#define HEAVY_TRACING 0
|
||||
@@ -64,7 +59,6 @@
|
||||
// see https://developer.android.com/topic/performance/threads#priority
|
||||
# include <sys/time.h>
|
||||
# include <sys/resource.h>
|
||||
# include <unistd.h>
|
||||
# ifndef ANDROID_PRIORITY_URGENT_DISPLAY
|
||||
# define ANDROID_PRIORITY_URGENT_DISPLAY (-8)
|
||||
# endif
|
||||
|
||||
@@ -19,8 +19,8 @@ void main() {
|
||||
#endif
|
||||
|
||||
// Adjust clip-space
|
||||
#if !defined(TARGET_VULKAN_ENVIRONMENT) && !defined(TARGET_METAL_ENVIRONMENT) && !defined(TARGET_WEBGPU_ENVIRONMENT)
|
||||
// This is not needed in Vulkan, Metal or WebGPU because clipControl is always (1, 0)
|
||||
#if !defined(TARGET_VULKAN_ENVIRONMENT) && !defined(TARGET_METAL_ENVIRONMENT)
|
||||
// This is not needed in Vulkan or Metal because clipControl is always (1, 0)
|
||||
// (We don't use a dot() here because it works around a spirv-opt optimization that in turn
|
||||
// causes a crash on PowerVR, see #5118)
|
||||
inputs.position.z = inputs.position.z * frameUniforms.clipControl.x + inputs.position.w * frameUniforms.clipControl.y;
|
||||
|
||||
@@ -246,8 +246,8 @@ void main() {
|
||||
position.y = -position.y;
|
||||
#endif
|
||||
|
||||
#if !defined(TARGET_VULKAN_ENVIRONMENT) && !defined(TARGET_METAL_ENVIRONMENT) && !defined(TARGET_WEBGPU_ENVIRONMENT)
|
||||
// This is not needed in Vulkan, Metal or WebGPU because clipControl is always (1, 0)
|
||||
#if !defined(TARGET_VULKAN_ENVIRONMENT) && !defined(TARGET_METAL_ENVIRONMENT)
|
||||
// This is not needed in Vulkan or Metal because clipControl is always (1, 0)
|
||||
// (We don't use a dot() here because it workaround a spirv-opt optimization that in turn
|
||||
// causes a crash on PowerVR, see #5118)
|
||||
position.z = position.z * frameUniforms.clipControl.x + position.w * frameUniforms.clipControl.y;
|
||||
|
||||
@@ -7,72 +7,11 @@ machines. To perform software rasterization, these scripts are centered around [
|
||||
rasterizers, but nothing bars us from using another rasterizer like [SwiftShader]. Additionally,
|
||||
we should be able to use GPUs where available (though this is more of a future work).
|
||||
|
||||
The script `render.py` contains the core logic for taking input parameters (such as the test
|
||||
description file) and then running gltf_viewer to produce the renderings.
|
||||
The script `run.py` contains the core logic for taking input parameters (such as the test
|
||||
description file) and then running gltf_viewer to produce the results.
|
||||
|
||||
In the `test` directory is a list of test descriptions that are specified in json. Please see
|
||||
`sample.json` to parse the structure.
|
||||
|
||||
## Running the test locally
|
||||
- To run the same presbumit as [`test-renderdiff`][presubmit-renderdiff], you can do
|
||||
```
|
||||
bash test/renderdiff/test.sh
|
||||
```
|
||||
- This script will generate the renderings based on the current state of your repo.
|
||||
Additionally, it will also compare the generated images with corresponding images in the
|
||||
golden repo.
|
||||
- To just render without running the test, you could use the following script
|
||||
```
|
||||
bash test/renderdiff/generate.sh
|
||||
```
|
||||
|
||||
## Update the golden images
|
||||
The golden images are stored in a github repository: https://github.com/google/filament-assets.
|
||||
Filament team members should have access to write to the repository. A typical flow for updating
|
||||
the goldens is to upload your changed images into **branch** of `filament-assets`. This branch is
|
||||
paired with a PR or commit on the `filament` repo.
|
||||
|
||||
As an example, imagine I am working on a PR, and I've uploaded my change, which is in a branch
|
||||
called `my-pr-branch`, to `filament`. This PR requires updating the golden. We would do it
|
||||
in the following fashion
|
||||
|
||||
### Using a script to update the golden repo
|
||||
|
||||
- Run interactive mode in the `update_golden.py` script.
|
||||
```
|
||||
python3 test/renderdiff/src/update_golden.py
|
||||
```
|
||||
- This will guide you through a series of steps to push the changes to a remote branch
|
||||
on `filament-assets`.
|
||||
|
||||
### Manually updating the golden repo
|
||||
|
||||
- Check out the golden repo
|
||||
```
|
||||
git clone git@github.com:google/filament-assets.git
|
||||
```
|
||||
- Create a branch on the golden repo
|
||||
```
|
||||
cd filament-assets
|
||||
git switch -c my-pr-branch-golden
|
||||
```
|
||||
- Copy the new images to their appropriate place in `filament-assets`
|
||||
- Push the `filament-assets` working branch to remote
|
||||
```
|
||||
git push origin my-pr-branch-golden
|
||||
```
|
||||
- In the commit message of your working branch on `filament`, add the following line
|
||||
```
|
||||
RDIFF_BBRANCH=my-pr-branch-golden
|
||||
```
|
||||
### Manually updating the golden repo
|
||||
|
||||
Doing the above has multiple effects:
|
||||
- The presubmit test [`test-renderdiff`][presubmit-renderdiff] will test against the provided
|
||||
branch of the golden repo (i.e. `my-pr-branch-golden`).
|
||||
- If the PR is merged, then there is another workflow that will merge `my-pr-branch-golden` to
|
||||
the `main` branch of the golden repo.
|
||||
|
||||
[Mesa]: https://docs.mesa3d.org
|
||||
[SwiftShader]: https://github.com/google/swiftshader
|
||||
[presubmit-renderdiff]: https://github.com/google/filament/blob/e85dfe75c86106a05019e13ccdbef67e030af675/.github/workflows/presubmit.yml#L118
|
||||
[SwiftShader]: https://github.com/google/swiftshader
|
||||
@@ -34,6 +34,7 @@ function start_render_() {
|
||||
fi
|
||||
done
|
||||
fi
|
||||
mkdir -p ${OUTPUT_DIR}
|
||||
CXX=`which clang++` CC=`which clang` ./build.sh -f -X ${MESA_DIR} -p desktop debug gltf_viewer
|
||||
}
|
||||
|
||||
@@ -53,6 +54,6 @@ 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=${RENDER_OUTPUT_DIR} \
|
||||
--output_dir=${OUTPUT_DIR} \
|
||||
--opengl_lib=${MESA_LIB_DIR} && \
|
||||
end_render_
|
||||
|
||||
@@ -4,54 +4,26 @@ import sys
|
||||
import pprint
|
||||
import json
|
||||
|
||||
from utils import execute, ArgParseImpl, important_print, mkdir_p
|
||||
from image_diff import same_image, output_image_diff
|
||||
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, out_dir=None):
|
||||
all_files = glob.glob(os.path.join(base_dir, "./**/*.tif"), recursive=True)
|
||||
test_dirs = set(os.path.abspath(os.path.dirname(f)).replace(os.path.abspath(base_dir) + '/', '') \
|
||||
for f in all_files)
|
||||
all_results = []
|
||||
for test_dir in test_dirs:
|
||||
results_meta = {}
|
||||
results = []
|
||||
output_test_dir = None if not out_dir else os.path.join(out_dir, test_dir)
|
||||
if output_test_dir:
|
||||
mkdir_p(output_test_dir)
|
||||
base_test_dir = os.path.abspath(os.path.join(base_dir, test_dir))
|
||||
comp_test_dir = os.path.abspath(os.path.join(comparison_dir, test_dir))
|
||||
results_meta['base_dir'] = base_test_dir
|
||||
results_meta['comparison_dir'] = comp_test_dir
|
||||
for golden_file in \
|
||||
glob.glob(os.path.join(base_test_dir, "*.tif")):
|
||||
base_fname = os.path.abspath(golden_file)
|
||||
test_case = base_fname.replace(f'{base_test_dir}/', '')
|
||||
comp_fname = os.path.join(comp_test_dir, test_case)
|
||||
result = {
|
||||
'name': test_case,
|
||||
}
|
||||
if not os.path.exists(comp_fname):
|
||||
print(f'file name not found: {comp_fname}')
|
||||
result['result'] = RESULT_MISSING
|
||||
elif not same_image(base_fname, comp_fname):
|
||||
result['result'] = RESULT_FAILED
|
||||
if output_test_dir:
|
||||
# just the file name
|
||||
diff_fname = f"{test_case.replace('.tif', '_diff.tif')}"
|
||||
output_image_diff(base_fname, comp_fname, os.path.join(output_test_dir, diff_fname))
|
||||
result['diff'] = diff_fname
|
||||
else:
|
||||
result['result'] = RESULT_OK
|
||||
results.append(result)
|
||||
if output_test_dir:
|
||||
results_meta['results'] = results
|
||||
output_fname = os.path.join(output_test_dir, "compare_results.json")
|
||||
with open(output_fname, 'w') as f:
|
||||
f.write(json.dumps(results_meta, indent=2))
|
||||
important_print(f'Written comparison results for {test_dir} to \n {output_fname}')
|
||||
all_results += results
|
||||
return all_results
|
||||
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()
|
||||
@@ -67,9 +39,14 @@ if __name__ == '__main__':
|
||||
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, out_dir=args.out)
|
||||
results = _compare_goldens(args.src, dest)
|
||||
|
||||
failed = [f" {k['name']}" for k in results if k['result'] != RESULT_OK]
|
||||
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 ''))
|
||||
|
||||
@@ -65,7 +65,7 @@ class GoldenManager:
|
||||
assets_dir = self._assets_dir()
|
||||
if not os.path.exists(assets_dir):
|
||||
execute(
|
||||
f'git clone {self._get_repo_url()}',
|
||||
f'git clone --depth=1 {self._get_repo_url()}',
|
||||
cwd=self.working_dir_,
|
||||
capture_output=False
|
||||
)
|
||||
@@ -83,26 +83,13 @@ class GoldenManager:
|
||||
self._git_exec('rebase')
|
||||
|
||||
def _git_exec(self, cmd):
|
||||
return execute(f'git {cmd}', cwd=self._assets_dir(), capture_output=False)
|
||||
execute(f'git {cmd}', cwd=self._assets_dir(), capture_output=False)
|
||||
|
||||
# tag represent a hash in the filament repo that this merge is associated with
|
||||
def merge_to_main(self, branch, tag, push_to_remote=False):
|
||||
def merge_to_main(self, branch, push_to_remote=False):
|
||||
self.update()
|
||||
assets_dir = self._assets_dir()
|
||||
|
||||
# Update commit message
|
||||
self._git_exec(f'checkout {branch}')
|
||||
code, old_commit = execute(f'git log --format=%B -n 1', cwd=assets_dir)
|
||||
if tag and len(tag) > 0:
|
||||
old_commit += f'\nFILAMENT={tag}'
|
||||
COMMIT_FILE = '/tmp/golden_commit.txt'
|
||||
with open(COMMIT_FILE, 'w') as f:
|
||||
f.write(old_commit)
|
||||
self._git_exec(f'commit --amend -F {COMMIT_FILE}')
|
||||
|
||||
# Do the actual merge
|
||||
self._git_exec(f'checkout main')
|
||||
self._git_exec(f'merge --no-ff --no-edit {branch}')
|
||||
self._git_exec(f'merge --no-ff {branch}')
|
||||
if push_to_remote and \
|
||||
(self.access_token_ or self.access_type_ == ACCESS_TYPE_SSH):
|
||||
self._git_exec(f'push origin main')
|
||||
@@ -122,7 +109,7 @@ class GoldenManager:
|
||||
self._git_exec(f'add {GOLDENS_DIR}')
|
||||
else:
|
||||
for f in deletes:
|
||||
self._git_exec(f'rm {os.path.join(GOLDENS_DIR, f)}')
|
||||
self._git_exec(f'remove {os.path.join(GOLDENS_DIR, f)}')
|
||||
for f in updates:
|
||||
shutil.copy2(
|
||||
os.path.join(src_dir, f),
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
import tifffile
|
||||
import numpy as np
|
||||
import numpy
|
||||
|
||||
def same_image(tiff_file_a, tiff_file_b):
|
||||
try:
|
||||
@@ -27,7 +27,7 @@ def same_image(tiff_file_a, tiff_file_b):
|
||||
return False
|
||||
|
||||
# numpy.array_equal() checks if two arrays have the same shape and elements.
|
||||
if np.array_equal(img1_data, img2_data):
|
||||
if numpy.array_equal(img1_data, img2_data):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -41,27 +41,3 @@ def same_image(tiff_file_a, tiff_file_b):
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred: {e}")
|
||||
return False
|
||||
|
||||
def output_image_diff(tiff_file_a, tiff_file_b, output_path):
|
||||
try:
|
||||
img1_data = tifffile.imread(tiff_file_a)
|
||||
img2_data = tifffile.imread(tiff_file_b)
|
||||
|
||||
# If the dimensions (height, width, number of channels, number of pages/frames)
|
||||
# are different, the images are not the same.
|
||||
if img1_data.shape != img2_data.shape:
|
||||
raise RuntimeError(f"Images {tiff_file_a} and {tiff_file_b} have different shapes: {img1_data.shape} vs {img2_data.shape}")
|
||||
|
||||
diff_img = np.abs(img1_data.astype(np.int16) - img2_data.astype(np.int16))
|
||||
diff_img = diff_img.astype(np.uint8)
|
||||
tifffile.imwrite(output_path, diff_img)
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"Error: One or both files not found ('{file_path1}', '{file_path2}').")
|
||||
return False
|
||||
except tifffile.TiffFileError as e:
|
||||
print(f"Error: One or both files are not valid TIFF files or could not be read. Details: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred: {e}")
|
||||
return False
|
||||
|
||||
@@ -16,9 +16,7 @@
|
||||
|
||||
# Sets up the environment for scripts in test/renderdiff/
|
||||
|
||||
RENDER_OUTPUT_DIR="$(pwd)/out/renderdiff/renders"
|
||||
DIFF_OUTPUT_DIR="$(pwd)/out/renderdiff/diffs"
|
||||
GOLDEN_OUTPUT_DIR="$(pwd)/out/renderdiff/goldens"
|
||||
OUTPUT_DIR="$(pwd)/out/renderdiff_tests"
|
||||
RENDERDIFF_TEST_DIR="$(pwd)/test/renderdiff"
|
||||
MESA_DIR="$(pwd)/mesa/out/"
|
||||
VENV_DIR="$(pwd)/venv"
|
||||
@@ -35,7 +33,6 @@ else
|
||||
fi
|
||||
|
||||
function start_() {
|
||||
mkdir -p ${RENDER_OUTPUT_DIR} ${DIFF_OUTPUT_DIR} ${GOLDEN_OUTPUT_DIR}
|
||||
if [[ "$GITHUB_WORKFLOW" ]]; then
|
||||
set -ex
|
||||
fi
|
||||
|
||||
@@ -102,7 +102,7 @@ if __name__ == "__main__":
|
||||
vk_icd=args.vk_icd)
|
||||
|
||||
with open(f'{output_dir}/render_results.json', 'w') as f:
|
||||
f.write(json.dumps(results, indent=2))
|
||||
f.write(json.dumps(results))
|
||||
|
||||
shutil.copy2(args.test, f'{output_dir}/test.json')
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ CONFIG_NEW_SRC_DIR = 'goldens_dir'
|
||||
CONFIG_GOLDENS_BRANCH = 'goldens_branch'
|
||||
CONFIG_GOLDENS_UPDATES = 'goldens_updates'
|
||||
CONFIG_GOLDENS_DELETES = 'goldens_deletes'
|
||||
CONFIG_PUSH_TO_REMOTE = 'push-to-remote'
|
||||
CONFIG_AUTO_COMMIT = 'auto-commit'
|
||||
CONFIG_COMMIT_MSG = 'commit_msg'
|
||||
|
||||
def _get_current_branch():
|
||||
@@ -54,12 +54,12 @@ def _do_update(golden_manager, config):
|
||||
|
||||
branch = config[CONFIG_GOLDENS_BRANCH]
|
||||
src_dir = config[CONFIG_NEW_SRC_DIR]
|
||||
push_to_remote = config[CONFIG_PUSH_TO_REMOTE]
|
||||
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=push_to_remote)
|
||||
push_to_remote=auto_commit)
|
||||
|
||||
def _get_deletes_updates(update_dir, golden_dir):
|
||||
ret_delete = []
|
||||
@@ -95,8 +95,7 @@ def _interactive_mode(base_golden_dir):
|
||||
if code != 0:
|
||||
print('Failed to generate new goldens')
|
||||
exit(1)
|
||||
# Note that this matches RENDER_OUTPUT_DIR in preamble.sh
|
||||
config[CONFIG_NEW_SRC_DIR] = os.path.join(os.getcwd(), './out/renderdiff/renders')
|
||||
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):
|
||||
@@ -137,23 +136,16 @@ def _interactive_mode(base_golden_dir):
|
||||
config[CONFIG_GOLDENS_DELETES] = []
|
||||
config[CONFIG_GOLDENS_UPDATES] = []
|
||||
|
||||
config[CONFIG_PUSH_TO_REMOTE] = \
|
||||
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('--golden-repo-token', help='Access token for the golden repo')
|
||||
parser.add_argument('--push-to-remote', action="store_true", help='Access token for the golden repo')
|
||||
|
||||
# write-to-branch mode
|
||||
parser.add_argument('--source', help='Directory containing the new goldens')
|
||||
parser.add_argument('--commit-msg', help='Message for the commit to the golden repo')
|
||||
|
||||
# merge-to-main mode (used in postsubmit)
|
||||
parser.add_argument('--merge-to-main', action="store_true", help='Merge to main the given branch')
|
||||
parser.add_argument('--filament-tag', help='Tag to append to the commit message on merge')
|
||||
parser.add_argument('--golden-repo-token', help='Access token for the golden repo')
|
||||
|
||||
args, _ = parser.parse_known_args(sys.argv[1:])
|
||||
config = {}
|
||||
@@ -163,24 +155,17 @@ if __name__ == "__main__":
|
||||
access_token=args.golden_repo_token
|
||||
)
|
||||
base_golden_dir = golden_manager.directory()
|
||||
|
||||
# This is the write-to-branch mode
|
||||
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_PUSH_TO_REMOTE: args.push_to_remote,
|
||||
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,
|
||||
}
|
||||
_do_update(golden_manager, config)
|
||||
# This is the merge-to-main mode
|
||||
elif args.branch and args.merge_to_main and args.filament_tag:
|
||||
golden_manager.merge_to_main(branch=args.branch, tag=args.filament_tag, push_to_remote=True)
|
||||
# Else, we're in interactive mode of write-to-branch (for local execution).
|
||||
else:
|
||||
config = _interactive_mode(base_golden_dir)
|
||||
_do_update(golden_manager, config)
|
||||
_do_update(golden_manager, config)
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
# Copyright (C) 2025 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import flask
|
||||
import pathlib
|
||||
import json
|
||||
|
||||
from utils import ArgParseImpl
|
||||
|
||||
from flask import Flask, request, make_response, send_from_directory
|
||||
|
||||
DIR = pathlib.Path(__file__).parent.absolute()
|
||||
HTML_DIR = os.path.join(DIR, "viewer_html")
|
||||
|
||||
def _create_app(config):
|
||||
app = Flask(__name__)
|
||||
|
||||
client_config = config.copy()
|
||||
base_dir = client_config['base_dir']
|
||||
comparison_dir = client_config['comparison_dir']
|
||||
diff_dir = client_config['diff_dir']
|
||||
|
||||
del client_config['base_dir']
|
||||
del client_config['comparison_dir']
|
||||
del client_config['diff_dir']
|
||||
|
||||
@app.route('/r/', methods=['GET'])
|
||||
def get_r():
|
||||
return json.dumps(client_config)
|
||||
|
||||
@app.route('/g/<path:filepath>', methods=['GET'])
|
||||
def get_g(filepath):
|
||||
return send_from_directory(base_dir, filepath)
|
||||
|
||||
@app.route('/d/<path:filepath>', methods=['GET'])
|
||||
def get_d(filepath):
|
||||
return send_from_directory(diff_dir, filepath)
|
||||
|
||||
@app.route('/c/<path:filepath>', methods=['GET'])
|
||||
def get_c(filepath):
|
||||
return send_from_directory(comparison_dir, filepath)
|
||||
|
||||
@app.route('/<path:filepath>')
|
||||
def get_static_file(filepath):
|
||||
return send_from_directory(HTML_DIR, filepath)
|
||||
|
||||
@app.route('/')
|
||||
def get_index():
|
||||
return send_from_directory(HTML_DIR, 'index.html')
|
||||
|
||||
app.url_map.strict_slashes = False
|
||||
return app
|
||||
|
||||
if __name__ == '__main__':
|
||||
PORT = 8901
|
||||
parser = ArgParseImpl()
|
||||
parser.add_argument('--diff', help='Diff directory', required=True)
|
||||
args, _ = parser.parse_known_args(sys.argv[1:])
|
||||
|
||||
with open(os.path.join(args.diff, 'compare_results.json'), 'r') as f:
|
||||
config = json.loads(f.read())
|
||||
config['diff_dir'] = os.path.abspath(args.diff)
|
||||
|
||||
app = _create_app(config)
|
||||
from waitress import serve
|
||||
|
||||
print(f'Point your browser to http://localhost:{PORT} to see the diff results')
|
||||
serve(app, host="127.0.0.1", port=PORT)
|
||||
@@ -1,299 +0,0 @@
|
||||
// Copyright (C) 2025 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { LitElement, html, css } from "https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js";
|
||||
import './tools.js';
|
||||
import './tiff-viewer.js';
|
||||
|
||||
function getGoldenUrl(testResult) {
|
||||
return '/g/' + testResult.name;
|
||||
}
|
||||
|
||||
function getCompUrl(testResult) {
|
||||
return '/c/' + testResult.name;
|
||||
}
|
||||
|
||||
function getDiffUrl(testResult) {
|
||||
return '/d/' + testResult.diff;
|
||||
}
|
||||
|
||||
const DIFF_VIEW = 'diff'
|
||||
const GOLDEN_VIEW = 'golden'
|
||||
const RENDERED_VIEW = 'rendered'
|
||||
|
||||
class ExpandedPassedResult extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
content: { type: Object, attribute: 'content' },
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.content = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.content) {
|
||||
const url = getGoldenUrl(this.content);
|
||||
return html`<tiff-viewer fileurl="${url}"></tiff-viewer>`;
|
||||
}
|
||||
return html``;
|
||||
}
|
||||
}
|
||||
customElements.define('expanded-passed-result', ExpandedPassedResult);
|
||||
|
||||
class ExpandedFailedResult extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
content: { type: Object, attribute: 'content' },
|
||||
leftViewSelected: { type: String },
|
||||
rightViewSelected: { type: String },
|
||||
};
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.viewer-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.viewer {
|
||||
margin: 0 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.content = null;
|
||||
this.leftViewSelected = GOLDEN_VIEW;
|
||||
this.rightViewSelected = DIFF_VIEW;
|
||||
this.addEventListener('radio-change', (ev) => {
|
||||
if (ev.detail.radioId == 'right') {
|
||||
this.rightViewSelected = ev.detail.value;
|
||||
}
|
||||
if (ev.detail.radioId == 'left') {
|
||||
this.leftViewSelected = ev.detail.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_viewer(name, choices, current) {
|
||||
const url = {
|
||||
[GOLDEN_VIEW]: getGoldenUrl,
|
||||
[RENDERED_VIEW]: getCompUrl,
|
||||
[DIFF_VIEW]: getDiffUrl,
|
||||
}[current](this.content);
|
||||
return html`
|
||||
<div>
|
||||
<radio-button-group id="${name}" .choices=${choices} value=${current}></radio-button-group>
|
||||
<tiff-viewer class="viewer" fileurl="${url}"></tiff-viewer>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.content) {
|
||||
return html``;
|
||||
}
|
||||
const v1 = this._viewer("left", [GOLDEN_VIEW, RENDERED_VIEW], this.leftViewSelected);
|
||||
const v2 = this._viewer("right", [RENDERED_VIEW, DIFF_VIEW], this.rightViewSelected);
|
||||
return html`
|
||||
<div class="viewer-container">
|
||||
${v1}
|
||||
${v2}
|
||||
</div>
|
||||
`;
|
||||
return html``;
|
||||
}
|
||||
}
|
||||
customElements.define('expanded-failed-result', ExpandedFailedResult);
|
||||
|
||||
class App extends LitElement {
|
||||
static styles = css`
|
||||
.test-label {
|
||||
margin-bottom: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.test-item {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid black;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
align-items: center;
|
||||
margin: 5px;
|
||||
}
|
||||
.test-item:hover {
|
||||
box-shadow: 2px 3px 2px 0px rgba(40, 40, 40, .5);
|
||||
cursor: pointer;
|
||||
}
|
||||
.app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding-top: 20px;
|
||||
}
|
||||
.results {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.results-container {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid black;
|
||||
border-radius: 10px;
|
||||
background: #cbf1c9;
|
||||
padding: 5px;
|
||||
align-items: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.container-label {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.failed-tiffs {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.tiff-wrapper {
|
||||
margin: 0 4px;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.tiff-wrapper-label {
|
||||
margin: 4px 0;
|
||||
font-size: 10px;
|
||||
}
|
||||
.dialog-header {
|
||||
font-size: 18px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
static properties = {
|
||||
tests: {type: Array},
|
||||
dialogContent: {type: Object},
|
||||
};
|
||||
|
||||
async _init() {
|
||||
const config = await ((await fetch("/r/")).json());
|
||||
config['results'] = config['results'].sort((a, b) => a.name < b.name ? -1 : (a.name > b.name ? 1 : 0));
|
||||
this.tests = config['results']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.tests = [];
|
||||
this.dialogContent = null;
|
||||
this._init();
|
||||
|
||||
this.addEventListener('dialog-closed', () => {
|
||||
this.dialogContent = null;
|
||||
});
|
||||
}
|
||||
|
||||
updated(props) {
|
||||
if (props.has('dialogContent')) {
|
||||
let dialog = this.shadowRoot.querySelector("#dialog");
|
||||
dialog.open = !!this.dialogContent;
|
||||
}
|
||||
}
|
||||
|
||||
_onClick(testDetail) {
|
||||
this.dialogContent = testDetail;
|
||||
this.shadowRoot.querySelector("#dialog").open = true;
|
||||
}
|
||||
|
||||
render() {
|
||||
let passed = this.tests.filter((t) => t.result == 'ok');
|
||||
let failed = this.tests.filter((t) => t.result != 'ok');
|
||||
const singleTiff = (url) => {
|
||||
return html`<tiff-viewer style="max-width:100px" fileurl="${url}"></tiff-viewer>`;
|
||||
};
|
||||
const buildTiffs = (ts) => ts.map((t) => {
|
||||
const printName = t.name.replace('.tif', '');
|
||||
const tiff = singleTiff(getGoldenUrl(t));
|
||||
return html`
|
||||
<div class="test-item" @click="${()=>this._onClick(t)}">
|
||||
<span class="test-label">${printName}</span>
|
||||
${tiff}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
const buildFailedTiffs = (ts) => ts.map((t) => {
|
||||
const printName = t.name.replace('.tif', '');
|
||||
const goldenUrl = getGoldenUrl(t);
|
||||
const compUrl = getCompUrl(t);
|
||||
const diffUrl = getDiffUrl(t);
|
||||
const wrap = (tif, label) => {
|
||||
return html`
|
||||
<div class="tiff-wrapper">
|
||||
${tif}
|
||||
<div class="tiff-wrapper-label">${label}</div>
|
||||
</div>`;
|
||||
}
|
||||
const tiffs = [
|
||||
[goldenUrl, 'golden'],
|
||||
[compUrl, 'rendered'],
|
||||
[diffUrl, 'diff']
|
||||
].map((a) => [singleTiff(a[0]), a[1]])
|
||||
.map((a) => wrap(...a));
|
||||
return html`
|
||||
<div class="test-item" @click="${()=>this._onClick(t)}">
|
||||
<span class="test-label">${printName}</span>
|
||||
<div class="failed-tiffs">
|
||||
${tiffs}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
const passedTiffs = buildTiffs(passed);
|
||||
const failedTiffs = buildFailedTiffs(failed);
|
||||
|
||||
let dialogSlot = null;
|
||||
let dialogHeader = '';
|
||||
if (this.dialogContent) {
|
||||
dialogSlot = (() => {
|
||||
if (this.dialogContent.result == 'ok') {
|
||||
return html`<expanded-passed-result .content=${this.dialogContent}></expanded-passed-result>`;
|
||||
}
|
||||
return html`<expanded-failed-result .content=${this.dialogContent}></expanded-failed-result>`;
|
||||
})();
|
||||
dialogHeader = this.dialogContent.name;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="app">
|
||||
<div class="results-container" style="background:#f5a5a4">
|
||||
<span class="container-label">Failed</span>
|
||||
<div class="results">
|
||||
${failedTiffs}
|
||||
</div>
|
||||
</div>
|
||||
<div class="results-container">
|
||||
<span class="container-label">Passed</span>
|
||||
<div class="results">
|
||||
${passedTiffs}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<modal-dialog id="dialog">
|
||||
<div class="dialog-header" slot="header">${dialogHeader}</div>
|
||||
${dialogSlot}
|
||||
</modal-dialog>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('diff-app', App);
|
||||
@@ -1,36 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2025 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http:www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Diff Viewer</title>
|
||||
<script type="module" src="./app.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/utif@3.1.0/UTIF.min.js"></script>
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet">
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-family: "Open Sans";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<diff-app></diff-app>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,96 +0,0 @@
|
||||
// Copyright (C) 2025 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { LitElement, html, css } from "https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js";
|
||||
|
||||
// Generated by Gemini with some modifications
|
||||
export class TiffViewer extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
canvas {
|
||||
border: 1px solid #ccc;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
static properties = {
|
||||
fileurl: {type: String, attribute: 'fileurl'},
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.fileurl = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<canvas id="tiffCanvas"></canvas>`;
|
||||
}
|
||||
|
||||
updated(props) {
|
||||
if (props.has('fileurl') && this.fileurl) {
|
||||
this._updateImage(this.fileurl);
|
||||
}
|
||||
}
|
||||
|
||||
async _updateImage(fileurl) {
|
||||
const fileblob = await ((await fetch(this.fileurl)).arrayBuffer());
|
||||
const canvas = this.shadowRoot.getElementById('tiffCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
try {
|
||||
const arrayBuffer = fileblob;
|
||||
const ifds = UTIF.decode(arrayBuffer); // Parse TIFF data
|
||||
|
||||
if (!ifds || ifds.length === 0) {
|
||||
console.error('Could not decode TIFF file. No image data found.');
|
||||
this._clearCanvas();
|
||||
return;
|
||||
}
|
||||
|
||||
// By default, render the first image in the TIFF (IFD)
|
||||
const firstImage = ifds[0];
|
||||
UTIF.decodeImage(arrayBuffer, firstImage, ifds); // Decode the actual pixel data
|
||||
|
||||
const rgba = UTIF.toRGBA8(firstImage); // Convert to RGBA
|
||||
canvas.width = firstImage.width;
|
||||
canvas.height = firstImage.height;
|
||||
|
||||
// The default mode would set alpha to 1 so that RGB differences would be displayed as non-transparent
|
||||
for (let i = 0; i < firstImage.width; i++) {
|
||||
for (let j = 0; j < firstImage.height; j++) {
|
||||
rgba[(j * firstImage.width * 4) + (i * 4) + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
const imageData = new ImageData(new Uint8ClampedArray(rgba), firstImage.width, firstImage.height);
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
} catch (error) {
|
||||
console.error('Error processing TIFF file:', error);
|
||||
this._clearCanvas();
|
||||
}
|
||||
}
|
||||
|
||||
_clearCanvas() {
|
||||
const canvas = this.shadowRoot.getElementById('tiffCanvas');
|
||||
if (canvas) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tiff-viewer', TiffViewer);
|
||||
@@ -1,225 +0,0 @@
|
||||
// Copyright (C) 2025 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { LitElement, html, css } from "https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js";
|
||||
|
||||
// Generated by Gemini
|
||||
export class RadioButtonGroup extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.radio-group-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
label:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
input[type="radio"] {
|
||||
margin-right: 8px;
|
||||
cursor: pointer;
|
||||
/* Custom radio button appearance */
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid #ccc;
|
||||
border-radius: 50%;
|
||||
outline: none;
|
||||
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||
}
|
||||
input[type="radio"]:checked {
|
||||
border-color: #656565;
|
||||
background-color: #656565; /* Optional: fill color when checked */
|
||||
}
|
||||
input[type="radio"]:checked::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin: 3px; /* Adjust to center the dot */
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
input[type="radio"]:focus {
|
||||
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
.label-text {
|
||||
font-size: 1rem;
|
||||
}
|
||||
`;
|
||||
static properties = {
|
||||
/**
|
||||
* An array of strings representing the choices for the radio buttons.
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
choices: { type: Array },
|
||||
|
||||
/**
|
||||
* The name for the radio button group. This is important for accessibility
|
||||
* and ensuring only one radio button in the group can be selected.
|
||||
* @type {string}
|
||||
*/
|
||||
name: { type: String },
|
||||
|
||||
/**
|
||||
* The currently selected value.
|
||||
* @type {string}
|
||||
*/
|
||||
value: { type: String, reflect: true },
|
||||
|
||||
/**
|
||||
* The label or title for the radio group.
|
||||
* @type {string}
|
||||
*/
|
||||
groupLabel: { type: String }
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.choices = [];
|
||||
this.name = 'radio-group'; // Default name
|
||||
this.value = '';
|
||||
this.groupLabel = '';
|
||||
}
|
||||
|
||||
_handleChange(event) {
|
||||
const selectedValue = event.target.value;
|
||||
if (this.value !== selectedValue) {
|
||||
this.value = selectedValue;
|
||||
// Dispatch a custom event with the new value
|
||||
this.dispatchEvent(new CustomEvent('radio-change', {
|
||||
detail: { value: this.value, radioId: this.id },
|
||||
bubbles: true, // Allows the event to bubble up through the DOM
|
||||
composed: true // Allows the event to cross shadow DOM boundaries
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="radio-group-container" role="radiogroup" aria-labelledby="group-label">
|
||||
${this.groupLabel ? html`<span id="group-label" class="group-label">${this.groupLabel}</span>` : ''}
|
||||
${this.choices.map(choice => html`
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name=${this.name}
|
||||
.value=${choice}
|
||||
.checked=${choice === this.value}
|
||||
@change=${this._handleChange}
|
||||
>
|
||||
<span class="label-text">${choice}</span>
|
||||
</label>
|
||||
`)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('radio-button-group', RadioButtonGroup);
|
||||
|
||||
// Generated by Gemini with some modifications
|
||||
class ModalDialog extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: none; /* Hidden by default */
|
||||
}
|
||||
|
||||
:host([open]) {
|
||||
display: block; /* Show when open attribute is present */
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent black */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000; /* Ensure it's on top */
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
min-width: 300px; /* Or your desired width */
|
||||
z-index: 1001; /* Above the backdrop */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 0 15px;
|
||||
}
|
||||
`;
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
open: { type: Boolean, reflect: true },
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.open = false;
|
||||
}
|
||||
|
||||
_handleBackdropClick(event) {
|
||||
// Close only if the click is directly on the backdrop, not on the dialog itself
|
||||
if (event.target === this.shadowRoot.querySelector('.backdrop')) {
|
||||
this.open = false;
|
||||
this.dispatchEvent(new CustomEvent('dialog-closed', { bubbles: true, composed: true }));
|
||||
}
|
||||
}
|
||||
|
||||
_handleDialogClick(event) {
|
||||
// Prevent clicks inside the dialog from bubbling up to the backdrop
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.open) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="backdrop" @click="${this._handleBackdropClick}">
|
||||
<div class="dialog" @click="${this._handleDialogClick}">
|
||||
<slot name="header"><h2>Default Header</h2></slot>
|
||||
<slot>
|
||||
<p>This is the default content of the modal.</p>
|
||||
</slot>
|
||||
<slot name="footer">
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('modal-dialog', ModalDialog);
|
||||
@@ -12,24 +12,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import sys
|
||||
import re
|
||||
from utils import execute
|
||||
|
||||
from utils import execute, ArgParseImpl
|
||||
|
||||
RDIFF_UPDATE_GOLDEN_STR = 'RDIFF_BRANCH'
|
||||
|
||||
def _parse_commit(commit_str):
|
||||
lines = commit_str.split('\n')
|
||||
if len(lines) >= 4:
|
||||
commit, author, date, _, title, *desc = lines
|
||||
else:
|
||||
print(commit_str, file=sys.stderr)
|
||||
return (
|
||||
lines[0],
|
||||
lines[1],
|
||||
'',
|
||||
)
|
||||
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()
|
||||
|
||||
@@ -43,27 +30,28 @@ def _parse_commit(commit_str):
|
||||
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)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
RDIFF_UPDATE_GOLDEN_STR = 'RDIFF_UPDATE_GOLDEN'
|
||||
|
||||
if __name__ == "__main__":
|
||||
RE_STR = rf"{RDIFF_UPDATE_GOLDEN_STR}(?:S)?=[\[]?([a-zA-Z0-9,\s\-\/]+)[\]]?"
|
||||
|
||||
parser = ArgParseImpl()
|
||||
parser.add_argument('--file', help='A file containing the commit message')
|
||||
args, _ = parser.parse_known_args(sys.argv[1:])
|
||||
|
||||
if not args.file:
|
||||
msg = sys.stdin.read()
|
||||
else:
|
||||
with open(args.file, 'r') as f:
|
||||
msg = f.read()
|
||||
RE_STR = f'{RDIFF_UPDATE_GOLDEN_STR}(?:S)?=[\[]?([a-zA-Z0-9,\s]+)[\]]?'
|
||||
|
||||
to_update = []
|
||||
commit, title, description = _parse_commit(msg)
|
||||
commit, title, description = get_last_commit()
|
||||
for line in description:
|
||||
m = re.match(RE_STR, line)
|
||||
if not m:
|
||||
continue
|
||||
print(m.group(1))
|
||||
exit(0)
|
||||
|
||||
# Always default to the main branch
|
||||
print('main')
|
||||
to_update += sanitize_split(m.group(1).replace(',', ' '), ' ')
|
||||
print(','.join(to_update))
|
||||
+8
-17
@@ -16,22 +16,13 @@
|
||||
|
||||
source `dirname $0`/src/preamble.sh
|
||||
|
||||
start_
|
||||
GOLDEN_DIR=$(pwd)/golden_images
|
||||
|
||||
if [[ "$GITHUB_WORKFLOW" ]]; then
|
||||
# The commit message would have been piped as stdin to this script
|
||||
COMMIT_MSG=$(cat)
|
||||
GOLDEN_BRANCH=$(echo "${COMMIT_MSG}" | python3 test/renderdiff/src/commit_msg.py)
|
||||
else
|
||||
GOLDEN_BRANCH=$(git log -1 | python3 test/renderdiff/src/commit_msg.py)
|
||||
fi
|
||||
|
||||
bash `dirname $0`/generate.sh && \
|
||||
python3 ${RENDERDIFF_TEST_DIR}/src/golden_manager.py \
|
||||
--branch=${GOLDEN_BRANCH} \
|
||||
--output=${GOLDEN_OUTPUT_DIR} && \
|
||||
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_OUTPUT_DIR} \
|
||||
--dest=${RENDER_OUTPUT_DIR} \
|
||||
--out=${DIFF_OUTPUT_DIR}
|
||||
end_
|
||||
--src=${GOLDEN_DIR} \
|
||||
--dest=${OUTPUT_DIR} && \
|
||||
end_
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "filament",
|
||||
"version": "1.61.1",
|
||||
"version": "1.60.1",
|
||||
"description": "Real-time physically based rendering engine",
|
||||
"main": "filament.js",
|
||||
"module": "filament.js",
|
||||
|
||||
Reference in New Issue
Block a user