Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/actions/install-mesa/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: 'Install Mesa'
description: "Install the runner release's lavapipe and select it as the only Vulkan driver"
runs:
using: 'composite'
steps:
# Distro Mesa rather than gfx-rs/ci-build: those builds have no window-system support
# (-Dplatforms=), so they can't create the X11 surfaces sketches render to.
- name: Install Mesa
shell: bash
run: |
set -e
sudo apt-get install -y --no-install-recommends mesa-vulkan-drivers libvulkan1
icd=$(dpkg -L mesa-vulkan-drivers | grep -E '/lvp_icd[^/]*\.json$' | head -1)
if [ -z "$icd" ]; then
echo "::error::no lavapipe ICD in mesa-vulkan-drivers"
dpkg -L mesa-vulkan-drivers | grep -E '\.json$'
exit 1
fi
echo "Using $icd"
echo "VK_DRIVER_FILES=$icd" >> "$GITHUB_ENV"
dpkg-query -W mesa-vulkan-drivers
65 changes: 65 additions & 0 deletions .github/workflows/visual-comment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Visual comment

# Posts visual.yml's PR summary as a sticky comment. Runs from the base repository so it can
# write to PRs from forks; it never checks out PR code and treats the artifact as untrusted.

on:
workflow_run:
workflows: [ Visual ]
types: [ completed ]

permissions: {}

jobs:
comment:
if: >-
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion != 'cancelled' &&
github.event.workflow_run.conclusion != 'skipped'
runs-on: ubuntu-latest
permissions:
actions: read
pull-requests: write
steps:
- uses: actions/download-artifact@v8
id: download
continue-on-error: true
with:
name: visual-comment
path: comment
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}

- name: Validate
id: pr
if: steps.download.outcome == 'success'
env:
GH_TOKEN: ${{ github.token }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
pr=$(cat comment/pr)
notable=$(cat comment/notable)
[[ "$pr" =~ ^[0-9]+$ ]] || { echo "invalid PR number"; exit 1; }
[[ "$notable" == true || "$notable" == false ]] || { echo "invalid notable flag"; exit 1; }
pr_head=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$pr" --jq .head.sha)
[[ "$pr_head" == "$HEAD_SHA" ]] || { echo "PR #$pr is not at $HEAD_SHA; skipping"; exit 0; }
echo "number=$pr" >> "$GITHUB_OUTPUT"
echo "notable=$notable" >> "$GITHUB_OUTPUT"

- name: Post comment
if: steps.pr.outputs.notable == 'true'
uses: marocchino/sticky-pull-request-comment@v3.0.5
with:
header: visual-regression
number_force: ${{ steps.pr.outputs.number }}
path: comment/comment.md

# A clean run only refreshes an earlier comment, so passing PRs stay quiet.
- name: Update comment
if: steps.pr.outputs.notable == 'false'
uses: marocchino/sticky-pull-request-comment@v3.0.5
with:
header: visual-regression
number_force: ${{ steps.pr.outputs.number }}
path: comment/comment.md
only_update: true
145 changes: 145 additions & 0 deletions .github/workflows/visual.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
name: Visual

# Renders tests/visual/cases.toml under lavapipe

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
types: [ opened, synchronize, reopened, labeled, unlabeled ]

permissions:
contents: read
actions: read

concurrency:
group: visual-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
CARGO_PROFILE_RELEASE_DEBUG: 0
WGPU_BACKEND: vulkan
WGPU_ADAPTER_NAME: llvmpipe
# Catch reads of uninitialized GPU memory instead of rendering whatever was there
LVP_POISON_MEMORY: "true"
# llvmpipe JITs for the host CPU
LP_NATIVE_VECTOR_WIDTH: "256"
ODIFF_VERSION: 4.5.0

jobs:
visual:
name: Visual regression
# Only re-run on label changes that involve the override label.
if: >-
github.event_name == 'push' ||
(github.event.action != 'labeled' && github.event.action != 'unlabeled') ||
github.event.label.name == 'deliberate-rendering-change'
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
with:
submodules: true
# PR checkouts are merge commits; HEAD^1 is the main commit they're compared against.
fetch-depth: 2
persist-credentials: false

- uses: ./.github/actions/setup

- uses: ./.github/actions/install-mesa

- uses: astral-sh/setup-uv@v6

- name: Install xvfb and odiff
run: |
sudo apt-get install -y --no-install-recommends xvfb
npm install -g "odiff-bin@$ODIFF_VERSION"

- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-visual-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-visual-
${{ runner.os }}-cargo-

- name: Render
run: xvfb-run -a -s "-screen 0 1920x1080x24" python3 tests/visual/visual.py render --out out/actual

- uses: actions/upload-artifact@v7
with:
name: visual-screenshots
path: out/actual
retention-days: ${{ github.event_name == 'push' && 90 || 14 }}

- name: Fetch baseline from main
if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ github.token }}
run: |
sha=$(git rev-parse HEAD^1)
latest() {
gh run list -R "$GITHUB_REPOSITORY" --workflow visual.yml --branch main --event push \
--commit "$sha" --limit 1 --json databaseId,status,conclusion --jq '.[0] // empty'
}
run=$(latest)
if [ -n "$run" ] && [ "$(jq -r .status <<< "$run")" != completed ]; then
id=$(jq -r .databaseId <<< "$run")
echo "Waiting for main's run $id to publish screenshots"
gh run watch "$id" -R "$GITHUB_REPOSITORY" --interval 30 > /dev/null || true
run=$(latest)
fi
if [ "$(jq -r '.conclusion // empty' <<< "$run")" = success ] &&
gh run download "$(jq -r .databaseId <<< "$run")" -R "$GITHUB_REPOSITORY" -n visual-screenshots -D out/baseline; then
echo "Using screenshots from run $(jq -r .databaseId <<< "$run")"
else
echo "::notice::No screenshots published for main at $sha; cases are reported as new"
fi

- name: Compare
id: compare
if: github.event_name == 'pull_request'
env:
ALLOW_CHANGES: ${{ contains(github.event.pull_request.labels.*.name, 'deliberate-rendering-change') && '--allow-changes' || '' }}
run: |
mkdir -p out/baseline
python3 tests/visual/visual.py compare --baseline out/baseline --actual out/actual --out out/report $ALLOW_CHANGES

- uses: actions/upload-artifact@v7
id: report
if: always() && hashFiles('out/report/report.html') != ''
with:
path: out/report/report.html
archive: false
retention-days: 14

- name: Summarize
if: always() && hashFiles('out/report/summary.md') != ''
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
REPORT_URL: ${{ steps.report.outputs.artifact-url }}
run: |
mkdir -p out/comment
{
cat out/report/summary.md
echo
echo "[Open the visual report]($REPORT_URL) · [workflow run]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID)"
} > out/comment/comment.md
cat out/comment/comment.md >> "$GITHUB_STEP_SUMMARY"
echo "$PR_NUMBER" > out/comment/pr
jq '[.results[] | select(.status != "pass")] | length > 0' out/report/results.json > out/comment/notable

- uses: actions/upload-artifact@v7
if: always() && hashFiles('out/comment/comment.md') != ''
with:
name: visual-comment
path: out/comment
retention-days: 1
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ processing_glfw = { workspace = true }
rand = { workspace = true }

[target.'cfg(target_os = "linux")'.dev-dependencies]
processing_glfw = { workspace = true, features = ["wayland"] }
processing_glfw = { workspace = true, features = ["wayland", "x11"] }

## TODO: Remove these patches once we've moved back to depending on upstream bevy
[patch."https://github.com/bevyengine/bevy"]
Expand Down
Loading
Loading