Skip to content

Commit 60df8ac

Browse files
authored
renderdiff: enable webgpu and improvements (#9219)
- Allow for presets to override selected models when presented in order. - Add 'gltf' for model search path - Add a few simple gltf models to the 'base' preset - Improve UI so that missing tests do not generate any html bits. - Add documentation on using the viewer - Add renderdiff documentation to the project webpage. RDIFF_BRANCH=pf/renderdiff-enable-webgpu
1 parent 1fb3d48 commit 60df8ac

File tree

12 files changed

+167
-44
lines changed

12 files changed

+167
-44
lines changed

docs_src/build/duplicates.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,8 @@
7878
},
7979
"docs_src/README.md": {
8080
"dest": "dup/docs.md"
81+
},
82+
"docs_src/test/renderdiff/README.md": {
83+
"dest": "dup/renderdiff.md"
8184
}
8285
}

docs_src/src_mdbook/src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
- [Code coverage analysis](./notes/coverage.md)
2828
- [Performance analysis](./notes/performance_analysis.md)
2929
- [Framegraph](./notes/framegraph.md)
30+
- [Tests](./notes/tests.md)
31+
- [renderdiff](./dup/renderdiff.md)
3032
- [Libraries](./notes/libs.md)
3133
- [bluegl](./dup/bluegl.md)
3234
- [bluevk](./dup/bluevk.md)
361 KB
Loading
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Tests
2+
3+
Filament has a collection of tests that can be run locally. Many of them are used
4+
for in our Continuation Integration flow [on github](https://https://github.com/google/filament/tree/main/.github/workflows).

test/renderdiff/README.md

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,33 @@ description file) and then running gltf_viewer to produce the renderings.
1313
In the `test` directory is a list of test descriptions that are specified in json. Please see
1414
`sample.json` to parse the structure.
1515

16+
## Setting up python
17+
The `renderdiff` project uses `python` extensively. To install the dependencies for producing
18+
renderings, do the following step
19+
- Set up a virtual environment (from the root directory)
20+
```
21+
python3 -m venv venv
22+
. ./venv/bin/activate
23+
```
24+
- Install the rendering dependencies
25+
```
26+
pip install -r test/renderdiff/src/rendering_requirements.txt
27+
```
28+
- Install the viewer depdencies
29+
```
30+
pip install -r test/renderdiff/src/viewer_requirements.txt
31+
```
32+
- For the commands in the following section, do not exit the virtual environment. Once you've
33+
completed all your work, you can exit with
34+
```
35+
deactivate
36+
```
37+
1638
## Running the test locally
1739
- To run the same presbumit as [`test-renderdiff`](presubmit-renderdiff), you can do
1840

1941
```
20-
bash test/renderdiff/test.sh
42+
bash test/renderdiff/local_test.sh
2143
```
2244

2345
- This script will generate the renderings based on the current state of your repo.
@@ -67,7 +89,7 @@ in the following fashion
6789

6890
- Copy the new images to their appropriate place in `filament-assets`
6991
- Push the `filament-assets` working branch to remote
70-
92+
7193
```
7294
git push origin my-pr-branch-golden
7395
```
@@ -78,14 +100,39 @@ in the following fashion
78100
RDIFF_BBRANCH=my-pr-branch-golden
79101
```
80102

81-
### Manually updating the golden repo
82-
83103
Doing the above has multiple effects:
84104
- The presubmit test [`test-renderdiff`][presubmit-renderdiff] will test against the provided
85105
branch of the golden repo (i.e. `my-pr-branch-golden`).
86106
- If the PR is merged, then there is another workflow that will merge `my-pr-branch-golden` to
87107
the `main` branch of the golden repo.
88108

109+
## Viewing test results
110+
We provide a viewer for looking at the result of a test run. The viewer is a webapp that can be used by
111+
pointing your browser to a localhost port. If you input the viewer with a PR or a directory, it will
112+
parse the test result and show the results and the rendered and/or golden images.
113+
114+
![Viewer](docs/images/renderdiff_example.png)
115+
116+
To run the viewer of a test output directory that has been generated locally, you would run the
117+
following
118+
119+
```
120+
python3 test/renderdiff/src/viewer.py --diff=[test output]
121+
```
122+
where `[test output]` is a directory containing the `compare_results.json` of the test run.
123+
For example, it could be `out/renderdiff/diffs/presubmit` for the standard path to the
124+
`presubmit` test output.
125+
126+
To see the results of a Pull Request initiated test run, you would do the following
127+
128+
```
129+
python3 test/renderdiff/src/viewer.py --pr_number=[PR #] --github_token=[github token]
130+
```
131+
132+
where `[PR #]` is the numeric ID of your pull request, and the `[github token]` is an acess token
133+
that you (as a github user) needs to generate ([reference][github_token_ref]).
134+
135+
[github_token_ref]: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
89136
[Mesa]: https://docs.mesa3d.org
90137
[SwiftShader]: https://github.com/google/swiftshader
91138
[presubmit-renderdiff]: https://github.com/google/filament/blob/e85dfe75c86106a05019e13ccdbef67e030af675/.github/workflows/presubmit.yml#L118

test/renderdiff/src/render.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from utils import execute, ArgParseImpl, mkdir_p, mv_f, important_print
2323

2424
import test_config
25-
from golden_manager import GoldenManager
2625
from image_diff import same_image
2726
from results import RESULT_OK, RESULT_FAILED
2827

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Mako==1.3.10
2+
MarkupSafe==3.0.2
3+
numpy==2.3.3
4+
PyYAML==6.0.2
5+
setuptools==80.9.0
6+
tifffile==2025.9.9

test/renderdiff/src/test_config.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,12 @@ def __init__(self, data, existing_models, presets):
7676
given_presets = {p.name: p for p in presets}
7777
assert all((name in given_presets) for name in apply_presets),\
7878
f'used preset {name} which is not in {given_presets}'
79+
80+
# Note that this needs to applied in order. Models will be overwritten.
81+
# Properties will be "added" in order.
7982
for preset in apply_presets:
8083
rendering.update(given_presets[preset].rendering)
81-
preset_models += given_presets[preset].models
84+
preset_models = given_presets[preset].models
8285

8386
assert 'rendering' in data
8487
rendering.update(data['rendering'])

test/renderdiff/src/viewer_html/app.js

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ class ExpandedComparisonResult extends LitElement {
113113
#diffCanvas {
114114
width: 100%;
115115
height: 100%;
116-
margin-top: 35px;
116+
margin-top: 22px;
117117
margin-bottom: 5px;
118118
}
119119
.selector {
@@ -151,33 +151,9 @@ class ExpandedComparisonResult extends LitElement {
151151

152152
_viewer(name, choices, current, viewType) {
153153
const url = viewType == 'rendered' ? getCompUrl(current) : getGoldenUrl(current);
154-
const onSelect = (ev) => {
155-
const testName = ev.target.value;
156-
const test = this.tests.find((t) => t.name == testName);
157-
if (name == 'left') {
158-
this.left = test;
159-
this.leftImageLoaded = false;
160-
} else {
161-
this.right = test;
162-
this.rightImageLoaded = false;
163-
}
164-
this.showDiff = false;
165-
};
166-
167-
const dropdown = () => {
168-
if (this.disableDropdowns) {
169-
return html`<div class="selector">${current.name} (${viewType})</div>`;
170-
}
171-
return html`
172-
<select class="selector" @change=${onSelect}>
173-
${choices.map((c) => html`<option value=${c.name} ?selected=${c.name == current.name}>${c.name}</option>`)}
174-
</select>
175-
`;
176-
}
177-
178154
return html`
179155
<div style="flex: 1; margin: 0 5px;">
180-
${dropdown()}
156+
<div>${current.name}</div>
181157
<tiff-viewer id="viewer-${name}" class="viewer"
182158
name="${current.name}"
183159
fileurl="${url}"></tiff-viewer>
@@ -405,6 +381,9 @@ class App extends LitElement {
405381
selectedTests: {type: Array},
406382
comparisonContent: {type: Object},
407383
compareMode: {type: Boolean},
384+
385+
// This is used to cache urls that are not found
386+
missingFile: {type: Object},
408387
};
409388

410389
async _init() {
@@ -420,12 +399,27 @@ class App extends LitElement {
420399
this.selectedTests = [];
421400
this.comparisonContent = null;
422401
this.compareMode = false;
402+
this.missingFile = {
403+
[getDiffUrl('undefined')]: true,
404+
};
423405
this._init();
424406

425407
this.addEventListener('dialog-closed', () => {
426408
this.dialogContent = null;
427409
this.comparisonContent = null;
428410
});
411+
412+
this.addEventListener('url-hit', (ev) => {
413+
delete this.missingFile[ev.detail.value];
414+
this.missingFile = this.missingFile;
415+
this.requestUpdate();
416+
});
417+
418+
this.addEventListener('url-miss', (ev) => {
419+
this.missingFile[ev.detail.value] = true;
420+
this.missingFile = this.missingFile;
421+
this.requestUpdate();
422+
});
429423
}
430424

431425
updated(props) {
@@ -470,8 +464,24 @@ class App extends LitElement {
470464
}
471465

472466
render() {
473-
let passed = this.tests.filter((t) => t.result == 'ok');
474-
let failed = this.tests.filter((t) => t.result != 'ok');
467+
const sortFn = (a, b) => {
468+
const aparts = a.name.split('.');
469+
const bparts = b.name.split('.');
470+
// 0 = test names
471+
// 1 = backend
472+
// 2 = model
473+
for (let i of [0, 2, 1]) {
474+
if (aparts[i] < bparts[i]) {
475+
return -1;
476+
}
477+
if (aparts[i] > bparts[i]) {
478+
return 1;
479+
}
480+
}
481+
return 0;
482+
};
483+
let passed = this.tests.filter((t) => t.result == 'ok').sort(sortFn);
484+
let failed = this.tests.filter((t) => t.result != 'ok').sort(sortFn);
475485
const singleTiff = (url) => {
476486
return html`<tiff-viewer style="max-width:100px" fileurl="${url}"></tiff-viewer>`;
477487
};
@@ -502,7 +512,8 @@ class App extends LitElement {
502512
[goldenUrl, 'golden'],
503513
[compUrl, 'rendered'],
504514
[diffUrl, 'diff']
505-
].map((a) => [singleTiff(a[0]), a[1]])
515+
].filter((a) => !this.missingFile[a[0]])
516+
.map((a) => [singleTiff(a[0]), a[1]])
506517
.map((a) => wrap(...a));
507518
return html`
508519
<div class="test-item" @click="${(e)=>this._onClick(t, e)}" >

test/renderdiff/src/viewer_html/tiff-viewer.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,20 @@ export class TiffViewer extends LitElement {
3030
static properties = {
3131
fileurl: {type: String, attribute: 'fileurl'},
3232
name: {type: String, attribute: 'name'},
33+
failedToFetch: {type: Boolean },
3334
};
3435

3536
constructor() {
3637
super();
3738
this.fileurl = null;
3839
this.name = null;
40+
this.failedToFetch = false;
3941
}
4042

4143
render() {
44+
if (this.failedToFetch) {
45+
return html``;
46+
}
4247
return html`<canvas id="tiffCanvas"></canvas>`;
4348
}
4449

@@ -49,7 +54,33 @@ export class TiffViewer extends LitElement {
4954
}
5055

5156
async _updateImage(fileurl) {
52-
const fileblob = await ((await fetch(this.fileurl)).arrayBuffer());
57+
this.failedToFetch = false;
58+
let fileblob = null;
59+
try {
60+
let res = await fetch(this.fileurl);
61+
if (!res.ok) {
62+
throw new Error(`Could not find ${this.fileurl}`);
63+
}
64+
fileblob = await res.arrayBuffer();
65+
} catch (error) {
66+
this.failedToFetch = true;
67+
}
68+
if (!fileblob) {
69+
const event = new CustomEvent('url-miss', {
70+
detail: { value: fileurl },
71+
bubbles: true,
72+
composed: true,
73+
});
74+
this.dispatchEvent(event);
75+
return;
76+
} else {
77+
const event = new CustomEvent('url-hit', {
78+
detail: { value: fileurl },
79+
bubbles: true,
80+
composed: true,
81+
});
82+
this.dispatchEvent(event);
83+
}
5384
const canvas = this.shadowRoot.getElementById('tiffCanvas');
5485
const ctx = canvas.getContext('2d');
5586

0 commit comments

Comments
 (0)