perf: Better realistic collision perf + CI benches (#3842)
This PR basically takes as much allocation out of the realistic collision hot loop as possible. This is done by inlining the vector/matrix/line clipping etc so far less readible.
CI runners will be underwhelming in general, but relative movement is still good.
<img width="720" height="320" alt="edge-floor-realistic-200" src="https://github.com/user-attachments/assets/0ca9a9c3-7e56-4783-ab39-695b43399dcd" />
<img width="720" height="320" alt="sleeping-pile-wake" src="https://github.com/user-attachments/assets/6730b959-70c3-4618-a6a5-46c686412ca6" />
<img width="720" height="320" alt="stack-realistic-100-settle" src="https://github.com/user-attachments/assets/fd7976e7-655e-4ba4-9af5-ded3a480ed12" />
<img width="720" height="320" alt="stack-realistic-300-rest" src="https://github.com/user-attachments/assets/d6e26984-1628-4d18-b428-9fa55d5f2b7f" />
<img width="720" height="320" alt="stack-realistic-300-settle" src="https://github.com/user-attachments/assets/859ee921-1783-4c6d-b7c5-2110ad2c38f1" />
<img width="1240" height="360" alt="summary" src="https://github.com/user-attachments/assets/099f0886-1540-44da-9eed-7373f94a25e7" />
Final report
### Excalibur benchmark
Baseline: `npm:excalibur@latest` (0.32.0) ยท Candidate: `../build/dist/excalibur.js` (0.33.0-alpha.14+816d29e) ยท 2 interleaved run(s), best run shown with the minโmax of run medians ยท ms/frame
| Test | Baseline median | Baseline p95 | Candidate median | Candidate p95 | ฮ median | ฮ p95 | Extras (candidate) | |
|---|---:|---:|---:|---:|---:|---:|---|---|
| actors-1000 | 4.90 <sub>(4.90โ6.15)</sub> | 11.62 | 4.90 <sub>(4.90โ5.50)</sub> | 10.60 | +0.0% | -8.7% | actors=1000 | |
| actors-4000 | 39.05 <sub>(39.05โ39.05)</sub> | 64.41 | 33.35 <sub>(33.35โ40.25)</sub> | 66.93 | -14.6% | +3.9% | actors=4000 | |
| stack-realistic-100-settle | 8.20 <sub>(8.20โ8.30)</sub> | 19.80 | 6.00 <sub>(6.00โ7.05)</sub> | 14.03 | -26.8% | -29.2% | sleepingAtEnd=0, firstFrameAllAsleep=-1 | ๐ |
| stack-realistic-300-settle | ERROR | | 18.10 | | | | page errors: [Fatal] : RangeError: Invalid array length | โ |
| stack-realistic-300-rest | ERROR | | 3.30 | | | | page errors: [Fatal] : RangeError: Invalid array length | โ |
| arcade-1000-bouncing | 28.50 <sub>(28.50โ28.70)</sub> | 45.70 | 23.40 <sub>(23.40โ24.35)</sub> | 40.51 | -17.9% | -11.4% | outside=14 | |
| edge-floor-realistic-200 | 9.70 <sub>(9.70โ10.25)</sub> | 18.10 | 3.80 <sub>(3.80โ3.95)</sub> | 12.80 | -60.8% | -29.3% | sleepingAtEnd=200, fellThrough=0 | ๐ |
| sleeping-pile-wake | 26.40 <sub>(26.40โ27.45)</sub> | 48.70 | 14.70 <sub>(14.70โ14.70)</sub> | 31.70 | -44.3% | -34.9% | sleepingAtEnd=0, spawned=5 | ๐ |
| bunnymark-2000 | 99.20 <sub>(99.20โ99.65)</sub> | 102.30 | 99.20 <sub>(99.20โ100.10)</sub> | 102.03 | -0.0% | -0.3% | bunnies=2000, drawCalls=1 | |
| bunnymark-5000 | 231.80 <sub>(231.80โ232.70)</sub> | 238.51 | 231.90 <sub>(231.90โ233.10)</sub> | 238.55 | +0.0% | +0.0% | bunnies=5000, drawCalls=1 | |
No test is more than 20% slower than the baseline. Results are informational, shared CI runners are noisy.
Also ironed out some correctness issues with stale geometry that would cause simulation instability (already present in v0.32 sadly, see the stack-realistic-300 boxes row). Roughly speaking the solver would blow up impulse on subsequent substeps because the geometry was out of date and it'd try harder and harder to correct.
Additionally there was more savings by skipping sleeping contacts from the solver, we still wake them when necessary but we don't want to apply impulse to things that are asleep. The contact islands now make sure there are now mixed states. Either all awake or all asleep.
Bonus points! Updated and added our legacy benchmarking tool so we can apples to apples compare with older releases yay science!
perf: Better realistic collision perf + CI benches (#3842)
This PR basically takes as much allocation out of the realistic collision hot loop as possible. This is done by inlining the vector/matrix/line clipping etc so far less readible.
CI runners will be underwhelming in general, but relative movement is still good.
<img width="720" height="320" alt="edge-floor-realistic-200" src="https://github.com/user-attachments/assets/0ca9a9c3-7e56-4783-ab39-695b43399dcd" />
<img width="720" height="320" alt="sleeping-pile-wake" src="https://github.com/user-attachments/assets/6730b959-70c3-4618-a6a5-46c686412ca6" />
<img width="720" height="320" alt="stack-realistic-100-settle" src="https://github.com/user-attachments/assets/fd7976e7-655e-4ba4-9af5-ded3a480ed12" />
<img width="720" height="320" alt="stack-realistic-300-rest" src="https://github.com/user-attachments/assets/d6e26984-1628-4d18-b428-9fa55d5f2b7f" />
<img width="720" height="320" alt="stack-realistic-300-settle" src="https://github.com/user-attachments/assets/859ee921-1783-4c6d-b7c5-2110ad2c38f1" />
<img width="1240" height="360" alt="summary" src="https://github.com/user-attachments/assets/099f0886-1540-44da-9eed-7373f94a25e7" />
Final report
### Excalibur benchmark
Baseline: `npm:excalibur@latest` (0.32.0) ยท Candidate: `../build/dist/excalibur.js` (0.33.0-alpha.14+816d29e) ยท 2 interleaved run(s), best run shown with the minโmax of run medians ยท ms/frame
| Test | Baseline median | Baseline p95 | Candidate median | Candidate p95 | ฮ median | ฮ p95 | Extras (candidate) | |
|---|---:|---:|---:|---:|---:|---:|---|---|
| actors-1000 | 4.90 <sub>(4.90โ6.15)</sub> | 11.62 | 4.90 <sub>(4.90โ5.50)</sub> | 10.60 | +0.0% | -8.7% | actors=1000 | |
| actors-4000 | 39.05 <sub>(39.05โ39.05)</sub> | 64.41 | 33.35 <sub>(33.35โ40.25)</sub> | 66.93 | -14.6% | +3.9% | actors=4000 | |
| stack-realistic-100-settle | 8.20 <sub>(8.20โ8.30)</sub> | 19.80 | 6.00 <sub>(6.00โ7.05)</sub> | 14.03 | -26.8% | -29.2% | sleepingAtEnd=0, firstFrameAllAsleep=-1 | ๐ |
| stack-realistic-300-settle | ERROR | | 18.10 | | | | page errors: [Fatal] : RangeError: Invalid array length | โ |
| stack-realistic-300-rest | ERROR | | 3.30 | | | | page errors: [Fatal] : RangeError: Invalid array length | โ |
| arcade-1000-bouncing | 28.50 <sub>(28.50โ28.70)</sub> | 45.70 | 23.40 <sub>(23.40โ24.35)</sub> | 40.51 | -17.9% | -11.4% | outside=14 | |
| edge-floor-realistic-200 | 9.70 <sub>(9.70โ10.25)</sub> | 18.10 | 3.80 <sub>(3.80โ3.95)</sub> | 12.80 | -60.8% | -29.3% | sleepingAtEnd=200, fellThrough=0 | ๐ |
| sleeping-pile-wake | 26.40 <sub>(26.40โ27.45)</sub> | 48.70 | 14.70 <sub>(14.70โ14.70)</sub> | 31.70 | -44.3% | -34.9% | sleepingAtEnd=0, spawned=5 | ๐ |
| bunnymark-2000 | 99.20 <sub>(99.20โ99.65)</sub> | 102.30 | 99.20 <sub>(99.20โ100.10)</sub> | 102.03 | -0.0% | -0.3% | bunnies=2000, drawCalls=1 | |
| bunnymark-5000 | 231.80 <sub>(231.80โ232.70)</sub> | 238.51 | 231.90 <sub>(231.90โ233.10)</sub> | 238.55 | +0.0% | +0.0% | bunnies=5000, drawCalls=1 | |
No test is more than 20% slower than the baseline. Results are informational, shared CI runners are noisy.
Also ironed out some correctness issues with stale geometry that would cause simulation instability (already present in v0.32 sadly, see the stack-realistic-300 boxes row). Roughly speaking the solver would blow up impulse on subsequent substeps because the geometry was out of date and it'd try harder and harder to correct.
Additionally there was more savings by skipping sleeping contacts from the solver, we still wake them when necessary but we don't want to apply impulse to things that are asleep. The contact islands now make sure there are now mixed states. Either all awake or all asleep.
Bonus points! Updated and added our legacy benchmarking tool so we can apples to apples compare with older releases yay science!
perf: Better realistic collision perf + CI benches (#3842)
This PR basically takes as much allocation out of the realistic collision hot loop as possible. This is done by inlining the vector/matrix/line clipping etc so far less readible.
CI runners will be underwhelming in general, but relative movement is still good.
<img width="720" height="320" alt="edge-floor-realistic-200" src="https://github.com/user-attachments/assets/0ca9a9c3-7e56-4783-ab39-695b43399dcd" />
<img width="720" height="320" alt="sleeping-pile-wake" src="https://github.com/user-attachments/assets/6730b959-70c3-4618-a6a5-46c686412ca6" />
<img width="720" height="320" alt="stack-realistic-100-settle" src="https://github.com/user-attachments/assets/fd7976e7-655e-4ba4-9af5-ded3a480ed12" />
<img width="720" height="320" alt="stack-realistic-300-rest" src="https://github.com/user-attachments/assets/d6e26984-1628-4d18-b428-9fa55d5f2b7f" />
<img width="720" height="320" alt="stack-realistic-300-settle" src="https://github.com/user-attachments/assets/859ee921-1783-4c6d-b7c5-2110ad2c38f1" />
<img width="1240" height="360" alt="summary" src="https://github.com/user-attachments/assets/099f0886-1540-44da-9eed-7373f94a25e7" />
Final report
### Excalibur benchmark
Baseline: `npm:excalibur@latest` (0.32.0) ยท Candidate: `../build/dist/excalibur.js` (0.33.0-alpha.14+816d29e) ยท 2 interleaved run(s), best run shown with the minโmax of run medians ยท ms/frame
| Test | Baseline median | Baseline p95 | Candidate median | Candidate p95 | ฮ median | ฮ p95 | Extras (candidate) | |
|---|---:|---:|---:|---:|---:|---:|---|---|
| actors-1000 | 4.90 <sub>(4.90โ6.15)</sub> | 11.62 | 4.90 <sub>(4.90โ5.50)</sub> | 10.60 | +0.0% | -8.7% | actors=1000 | |
| actors-4000 | 39.05 <sub>(39.05โ39.05)</sub> | 64.41 | 33.35 <sub>(33.35โ40.25)</sub> | 66.93 | -14.6% | +3.9% | actors=4000 | |
| stack-realistic-100-settle | 8.20 <sub>(8.20โ8.30)</sub> | 19.80 | 6.00 <sub>(6.00โ7.05)</sub> | 14.03 | -26.8% | -29.2% | sleepingAtEnd=0, firstFrameAllAsleep=-1 | ๐ |
| stack-realistic-300-settle | ERROR | | 18.10 | | | | page errors: [Fatal] : RangeError: Invalid array length | โ |
| stack-realistic-300-rest | ERROR | | 3.30 | | | | page errors: [Fatal] : RangeError: Invalid array length | โ |
| arcade-1000-bouncing | 28.50 <sub>(28.50โ28.70)</sub> | 45.70 | 23.40 <sub>(23.40โ24.35)</sub> | 40.51 | -17.9% | -11.4% | outside=14 | |
| edge-floor-realistic-200 | 9.70 <sub>(9.70โ10.25)</sub> | 18.10 | 3.80 <sub>(3.80โ3.95)</sub> | 12.80 | -60.8% | -29.3% | sleepingAtEnd=200, fellThrough=0 | ๐ |
| sleeping-pile-wake | 26.40 <sub>(26.40โ27.45)</sub> | 48.70 | 14.70 <sub>(14.70โ14.70)</sub> | 31.70 | -44.3% | -34.9% | sleepingAtEnd=0, spawned=5 | ๐ |
| bunnymark-2000 | 99.20 <sub>(99.20โ99.65)</sub> | 102.30 | 99.20 <sub>(99.20โ100.10)</sub> | 102.03 | -0.0% | -0.3% | bunnies=2000, drawCalls=1 | |
| bunnymark-5000 | 231.80 <sub>(231.80โ232.70)</sub> | 238.51 | 231.90 <sub>(231.90โ233.10)</sub> | 238.55 | +0.0% | +0.0% | bunnies=5000, drawCalls=1 | |
No test is more than 20% slower than the baseline. Results are informational, shared CI runners are noisy.
Also ironed out some correctness issues with stale geometry that would cause simulation instability (already present in v0.32 sadly, see the stack-realistic-300 boxes row). Roughly speaking the solver would blow up impulse on subsequent substeps because the geometry was out of date and it'd try harder and harder to correct.
Additionally there was more savings by skipping sleeping contacts from the solver, we still wake them when necessary but we don't want to apply impulse to things that are asleep. The contact islands now make sure there are now mixed states. Either all awake or all asleep.
Bonus points! Updated and added our legacy benchmarking tool so we can apples to apples compare with older releases yay science!
perf: Better realistic collision perf + CI benches (#3842)
This PR basically takes as much allocation out of the realistic collision hot loop as possible. This is done by inlining the vector/matrix/line clipping etc so far less readible.
CI runners will be underwhelming in general, but relative movement is still good.
<img width="720" height="320" alt="edge-floor-realistic-200" src="https://github.com/user-attachments/assets/0ca9a9c3-7e56-4783-ab39-695b43399dcd" />
<img width="720" height="320" alt="sleeping-pile-wake" src="https://github.com/user-attachments/assets/6730b959-70c3-4618-a6a5-46c686412ca6" />
<img width="720" height="320" alt="stack-realistic-100-settle" src="https://github.com/user-attachments/assets/fd7976e7-655e-4ba4-9af5-ded3a480ed12" />
<img width="720" height="320" alt="stack-realistic-300-rest" src="https://github.com/user-attachments/assets/d6e26984-1628-4d18-b428-9fa55d5f2b7f" />
<img width="720" height="320" alt="stack-realistic-300-settle" src="https://github.com/user-attachments/assets/859ee921-1783-4c6d-b7c5-2110ad2c38f1" />
<img width="1240" height="360" alt="summary" src="https://github.com/user-attachments/assets/099f0886-1540-44da-9eed-7373f94a25e7" />
Final report
### Excalibur benchmark
Baseline: `npm:excalibur@latest` (0.32.0) ยท Candidate: `../build/dist/excalibur.js` (0.33.0-alpha.14+816d29e) ยท 2 interleaved run(s), best run shown with the minโmax of run medians ยท ms/frame
| Test | Baseline median | Baseline p95 | Candidate median | Candidate p95 | ฮ median | ฮ p95 | Extras (candidate) | |
|---|---:|---:|---:|---:|---:|---:|---|---|
| actors-1000 | 4.90 <sub>(4.90โ6.15)</sub> | 11.62 | 4.90 <sub>(4.90โ5.50)</sub> | 10.60 | +0.0% | -8.7% | actors=1000 | |
| actors-4000 | 39.05 <sub>(39.05โ39.05)</sub> | 64.41 | 33.35 <sub>(33.35โ40.25)</sub> | 66.93 | -14.6% | +3.9% | actors=4000 | |
| stack-realistic-100-settle | 8.20 <sub>(8.20โ8.30)</sub> | 19.80 | 6.00 <sub>(6.00โ7.05)</sub> | 14.03 | -26.8% | -29.2% | sleepingAtEnd=0, firstFrameAllAsleep=-1 | ๐ |
| stack-realistic-300-settle | ERROR | | 18.10 | | | | page errors: [Fatal] : RangeError: Invalid array length | โ |
| stack-realistic-300-rest | ERROR | | 3.30 | | | | page errors: [Fatal] : RangeError: Invalid array length | โ |
| arcade-1000-bouncing | 28.50 <sub>(28.50โ28.70)</sub> | 45.70 | 23.40 <sub>(23.40โ24.35)</sub> | 40.51 | -17.9% | -11.4% | outside=14 | |
| edge-floor-realistic-200 | 9.70 <sub>(9.70โ10.25)</sub> | 18.10 | 3.80 <sub>(3.80โ3.95)</sub> | 12.80 | -60.8% | -29.3% | sleepingAtEnd=200, fellThrough=0 | ๐ |
| sleeping-pile-wake | 26.40 <sub>(26.40โ27.45)</sub> | 48.70 | 14.70 <sub>(14.70โ14.70)</sub> | 31.70 | -44.3% | -34.9% | sleepingAtEnd=0, spawned=5 | ๐ |
| bunnymark-2000 | 99.20 <sub>(99.20โ99.65)</sub> | 102.30 | 99.20 <sub>(99.20โ100.10)</sub> | 102.03 | -0.0% | -0.3% | bunnies=2000, drawCalls=1 | |
| bunnymark-5000 | 231.80 <sub>(231.80โ232.70)</sub> | 238.51 | 231.90 <sub>(231.90โ233.10)</sub> | 238.55 | +0.0% | +0.0% | bunnies=5000, drawCalls=1 | |
No test is more than 20% slower than the baseline. Results are informational, shared CI runners are noisy.
Also ironed out some correctness issues with stale geometry that would cause simulation instability (already present in v0.32 sadly, see the stack-realistic-300 boxes row). Roughly speaking the solver would blow up impulse on subsequent substeps because the geometry was out of date and it'd try harder and harder to correct.
Additionally there was more savings by skipping sleeping contacts from the solver, we still wake them when necessary but we don't want to apply impulse to things that are asleep. The contact islands now make sure there are now mixed states. Either all awake or all asleep.
Bonus points! Updated and added our legacy benchmarking tool so we can apples to apples compare with older releases yay science!
perf: Better realistic collision perf + CI benches (#3842)
This PR basically takes as much allocation out of the realistic collision hot loop as possible. This is done by inlining the vector/matrix/line clipping etc so far less readible.
CI runners will be underwhelming in general, but relative movement is still good.
<img width="720" height="320" alt="edge-floor-realistic-200" src="https://github.com/user-attachments/assets/0ca9a9c3-7e56-4783-ab39-695b43399dcd" />
<img width="720" height="320" alt="sleeping-pile-wake" src="https://github.com/user-attachments/assets/6730b959-70c3-4618-a6a5-46c686412ca6" />
<img width="720" height="320" alt="stack-realistic-100-settle" src="https://github.com/user-attachments/assets/fd7976e7-655e-4ba4-9af5-ded3a480ed12" />
<img width="720" height="320" alt="stack-realistic-300-rest" src="https://github.com/user-attachments/assets/d6e26984-1628-4d18-b428-9fa55d5f2b7f" />
<img width="720" height="320" alt="stack-realistic-300-settle" src="https://github.com/user-attachments/assets/859ee921-1783-4c6d-b7c5-2110ad2c38f1" />
<img width="1240" height="360" alt="summary" src="https://github.com/user-attachments/assets/099f0886-1540-44da-9eed-7373f94a25e7" />
Final report
### Excalibur benchmark
Baseline: `npm:excalibur@latest` (0.32.0) ยท Candidate: `../build/dist/excalibur.js` (0.33.0-alpha.14+816d29e) ยท 2 interleaved run(s), best run shown with the minโmax of run medians ยท ms/frame
| Test | Baseline median | Baseline p95 | Candidate median | Candidate p95 | ฮ median | ฮ p95 | Extras (candidate) | |
|---|---:|---:|---:|---:|---:|---:|---|---|
| actors-1000 | 4.90 <sub>(4.90โ6.15)</sub> | 11.62 | 4.90 <sub>(4.90โ5.50)</sub> | 10.60 | +0.0% | -8.7% | actors=1000 | |
| actors-4000 | 39.05 <sub>(39.05โ39.05)</sub> | 64.41 | 33.35 <sub>(33.35โ40.25)</sub> | 66.93 | -14.6% | +3.9% | actors=4000 | |
| stack-realistic-100-settle | 8.20 <sub>(8.20โ8.30)</sub> | 19.80 | 6.00 <sub>(6.00โ7.05)</sub> | 14.03 | -26.8% | -29.2% | sleepingAtEnd=0, firstFrameAllAsleep=-1 | ๐ |
| stack-realistic-300-settle | ERROR | | 18.10 | | | | page errors: [Fatal] : RangeError: Invalid array length | โ |
| stack-realistic-300-rest | ERROR | | 3.30 | | | | page errors: [Fatal] : RangeError: Invalid array length | โ |
| arcade-1000-bouncing | 28.50 <sub>(28.50โ28.70)</sub> | 45.70 | 23.40 <sub>(23.40โ24.35)</sub> | 40.51 | -17.9% | -11.4% | outside=14 | |
| edge-floor-realistic-200 | 9.70 <sub>(9.70โ10.25)</sub> | 18.10 | 3.80 <sub>(3.80โ3.95)</sub> | 12.80 | -60.8% | -29.3% | sleepingAtEnd=200, fellThrough=0 | ๐ |
| sleeping-pile-wake | 26.40 <sub>(26.40โ27.45)</sub> | 48.70 | 14.70 <sub>(14.70โ14.70)</sub> | 31.70 | -44.3% | -34.9% | sleepingAtEnd=0, spawned=5 | ๐ |
| bunnymark-2000 | 99.20 <sub>(99.20โ99.65)</sub> | 102.30 | 99.20 <sub>(99.20โ100.10)</sub> | 102.03 | -0.0% | -0.3% | bunnies=2000, drawCalls=1 | |
| bunnymark-5000 | 231.80 <sub>(231.80โ232.70)</sub> | 238.51 | 231.90 <sub>(231.90โ233.10)</sub> | 238.55 | +0.0% | +0.0% | bunnies=5000, drawCalls=1 | |
No test is more than 20% slower than the baseline. Results are informational, shared CI runners are noisy.
Also ironed out some correctness issues with stale geometry that would cause simulation instability (already present in v0.32 sadly, see the stack-realistic-300 boxes row). Roughly speaking the solver would blow up impulse on subsequent substeps because the geometry was out of date and it'd try harder and harder to correct.
Additionally there was more savings by skipping sleeping contacts from the solver, we still wake them when necessary but we don't want to apply impulse to things that are asleep. The contact islands now make sure there are now mixed states. Either all awake or all asleep.
Bonus points! Updated and added our legacy benchmarking tool so we can apples to apples compare with older releases yay science!
feat: Shader Pipelines (#3828)
This PR adds Shader pipeline capabilities to Materials/PostProcessors!
https://github.com/user-attachments/assets/256bdf2c-c09d-40db-b5cd-0e0e1776729c
1. You can stack fragment shaders on each other and re-use them to mix/match effects
- Built in uniforms are passed through each pass
- We support lists of glsl fragment strings or ShaderPasses, and ShaderPipelines that can wrap up multipass effects into a convient package
3. This enables effects like Blur/Bloom/Glow (built in ShaderPipelines) that are only easily possible with multipass workflows
Example using multiple strings
```typescript
// 3. Custom 2-pass pipeline from bare glsl strings
var redOnly = ex.glsl`
in vec2 v_uv;
uniform sampler2D u_image;
out vec4 fragColor;
void main() {
vec4 color = texture(u_image, v_uv);
fragColor = vec4(color.r + color.g + color.b, 0.0, 0.0, color.a);
}`;
var pulseBlue = ex.glsl`
in vec2 v_uv;
uniform sampler2D u_image;
uniform float u_time_ms;
out vec4 fragColor;
void main() {
vec4 color = texture(u_image, v_uv);
float pulse = (sin(u_time_ms / 300.0) + 1.0) / 2.0;
fragColor = vec4(color.r, 0.0, pulse * color.r, color.a);
}`;
var tinted = new ex.Actor({ pos: ex.vec(480, 150) });
tinted.graphics.use(swordImage.toSprite());
tinted.graphics.material = new ex.Material({
name: 'tint-pipeline',
graphicsContext,
passes: [redOnly, pulseBlue]
});
```
Built in effects!
```typescript
var bloomEffect = new ex.BloomEffect({ graphicsContext, threshold: 0.3, intensity: 1.5 });
var blooming = new ex.Actor({ pos: ex.vec(780, 150) });
blooming.graphics.use(swordImage.toSprite());
blooming.graphics.material = new ex.Material({
name: 'bloom-material',
graphicsContext,
passes: bloomEffect,
padding: 32
});
game.add(blooming);
```
Accidental bug fixes
- The time based uniforms in post processors were counting N times based on the number of postprocessors
This was re-implemented resurrected from my old branch back onto main
* https://github.com/excaliburjs/Excalibur/compare/main...feat/shader-pipelines
Huge thanks to Andrew Adamson's tutorials and code samples that provided the inspiration for the framebuffer technique
* https://youtu.be/bAq9Zk7BAWo?si=f4h0rBBR9T5yaS2W
* https://github.com/scriptfoundry/WebGL2-Videos-Materials/blob/main/25.framebuffer.basics.js
chore: migrate tests to vitest (#3381)
managed to migrate the engine leak & memory reporters, although it took a bit of rigamarole. Mainly because, as opposed to karma, the reporter runs in node and not the browser. So I have to track the data needed separately in some global hooks _within_ the browser environment, which the reporter then reads and creates the logs if needed.
However, it seems the memory tracking is regularly reporting >1 mb, so I'm not sure if it's tracking properly or if things have changed with the new browsers. My only theory is that because the timing where memory is read is during an `afterEach` _before_ the test's `afterEach`, it's running before any potential cleanup. The timing of this is not something that's easy to control, unfortunately. I did try to prove this theory by doing the memory analysis on the next test's `beforeEach`, but it didnt seem to change the results, so it may not be an issue.
Implementation is done in `src/spec/vitest/__reporters__/memory.ts` and `src/spec/vitest/__reporters/memory.setup.ts`
---
I noticed CouroutineSpec was an offender for >10mb memory, which spins up additional engines. I added a short 100ms wait after the engine.dispose and the memory usage did drop, so it does seem like this is prone to scheduled garbage collecting.
---
I was able to run the garbage collector (if exposed, currently only on chrome) and this makes the reports more accurate
perf: Better realistic collision perf + CI benches (#3842)
This PR basically takes as much allocation out of the realistic collision hot loop as possible. This is done by inlining the vector/matrix/line clipping etc so far less readible.
CI runners will be underwhelming in general, but relative movement is still good.
<img width="720" height="320" alt="edge-floor-realistic-200" src="https://github.com/user-attachments/assets/0ca9a9c3-7e56-4783-ab39-695b43399dcd" />
<img width="720" height="320" alt="sleeping-pile-wake" src="https://github.com/user-attachments/assets/6730b959-70c3-4618-a6a5-46c686412ca6" />
<img width="720" height="320" alt="stack-realistic-100-settle" src="https://github.com/user-attachments/assets/fd7976e7-655e-4ba4-9af5-ded3a480ed12" />
<img width="720" height="320" alt="stack-realistic-300-rest" src="https://github.com/user-attachments/assets/d6e26984-1628-4d18-b428-9fa55d5f2b7f" />
<img width="720" height="320" alt="stack-realistic-300-settle" src="https://github.com/user-attachments/assets/859ee921-1783-4c6d-b7c5-2110ad2c38f1" />
<img width="1240" height="360" alt="summary" src="https://github.com/user-attachments/assets/099f0886-1540-44da-9eed-7373f94a25e7" />
Final report
### Excalibur benchmark
Baseline: `npm:excalibur@latest` (0.32.0) ยท Candidate: `../build/dist/excalibur.js` (0.33.0-alpha.14+816d29e) ยท 2 interleaved run(s), best run shown with the minโmax of run medians ยท ms/frame
| Test | Baseline median | Baseline p95 | Candidate median | Candidate p95 | ฮ median | ฮ p95 | Extras (candidate) | |
|---|---:|---:|---:|---:|---:|---:|---|---|
| actors-1000 | 4.90 <sub>(4.90โ6.15)</sub> | 11.62 | 4.90 <sub>(4.90โ5.50)</sub> | 10.60 | +0.0% | -8.7% | actors=1000 | |
| actors-4000 | 39.05 <sub>(39.05โ39.05)</sub> | 64.41 | 33.35 <sub>(33.35โ40.25)</sub> | 66.93 | -14.6% | +3.9% | actors=4000 | |
| stack-realistic-100-settle | 8.20 <sub>(8.20โ8.30)</sub> | 19.80 | 6.00 <sub>(6.00โ7.05)</sub> | 14.03 | -26.8% | -29.2% | sleepingAtEnd=0, firstFrameAllAsleep=-1 | ๐ |
| stack-realistic-300-settle | ERROR | | 18.10 | | | | page errors: [Fatal] : RangeError: Invalid array length | โ |
| stack-realistic-300-rest | ERROR | | 3.30 | | | | page errors: [Fatal] : RangeError: Invalid array length | โ |
| arcade-1000-bouncing | 28.50 <sub>(28.50โ28.70)</sub> | 45.70 | 23.40 <sub>(23.40โ24.35)</sub> | 40.51 | -17.9% | -11.4% | outside=14 | |
| edge-floor-realistic-200 | 9.70 <sub>(9.70โ10.25)</sub> | 18.10 | 3.80 <sub>(3.80โ3.95)</sub> | 12.80 | -60.8% | -29.3% | sleepingAtEnd=200, fellThrough=0 | ๐ |
| sleeping-pile-wake | 26.40 <sub>(26.40โ27.45)</sub> | 48.70 | 14.70 <sub>(14.70โ14.70)</sub> | 31.70 | -44.3% | -34.9% | sleepingAtEnd=0, spawned=5 | ๐ |
| bunnymark-2000 | 99.20 <sub>(99.20โ99.65)</sub> | 102.30 | 99.20 <sub>(99.20โ100.10)</sub> | 102.03 | -0.0% | -0.3% | bunnies=2000, drawCalls=1 | |
| bunnymark-5000 | 231.80 <sub>(231.80โ232.70)</sub> | 238.51 | 231.90 <sub>(231.90โ233.10)</sub> | 238.55 | +0.0% | +0.0% | bunnies=5000, drawCalls=1 | |
No test is more than 20% slower than the baseline. Results are informational, shared CI runners are noisy.
Also ironed out some correctness issues with stale geometry that would cause simulation instability (already present in v0.32 sadly, see the stack-realistic-300 boxes row). Roughly speaking the solver would blow up impulse on subsequent substeps because the geometry was out of date and it'd try harder and harder to correct.
Additionally there was more savings by skipping sleeping contacts from the solver, we still wake them when necessary but we don't want to apply impulse to things that are asleep. The contact islands now make sure there are now mixed states. Either all awake or all asleep.
Bonus points! Updated and added our legacy benchmarking tool so we can apples to apples compare with older releases yay science!
chore: migrate tests to vitest (#3381)
managed to migrate the engine leak & memory reporters, although it took a bit of rigamarole. Mainly because, as opposed to karma, the reporter runs in node and not the browser. So I have to track the data needed separately in some global hooks _within_ the browser environment, which the reporter then reads and creates the logs if needed.
However, it seems the memory tracking is regularly reporting >1 mb, so I'm not sure if it's tracking properly or if things have changed with the new browsers. My only theory is that because the timing where memory is read is during an `afterEach` _before_ the test's `afterEach`, it's running before any potential cleanup. The timing of this is not something that's easy to control, unfortunately. I did try to prove this theory by doing the memory analysis on the next test's `beforeEach`, but it didnt seem to change the results, so it may not be an issue.
Implementation is done in `src/spec/vitest/__reporters__/memory.ts` and `src/spec/vitest/__reporters/memory.setup.ts`
---
I noticed CouroutineSpec was an offender for >10mb memory, which spins up additional engines. I added a short 100ms wait after the engine.dispose and the memory usage did drop, so it does seem like this is prone to scheduled garbage collecting.
---
I was able to run the garbage collector (if exposed, currently only on chrome) and this makes the reports more accurate
perf: Better realistic collision perf + CI benches (#3842)
This PR basically takes as much allocation out of the realistic collision hot loop as possible. This is done by inlining the vector/matrix/line clipping etc so far less readible.
CI runners will be underwhelming in general, but relative movement is still good.
<img width="720" height="320" alt="edge-floor-realistic-200" src="https://github.com/user-attachments/assets/0ca9a9c3-7e56-4783-ab39-695b43399dcd" />
<img width="720" height="320" alt="sleeping-pile-wake" src="https://github.com/user-attachments/assets/6730b959-70c3-4618-a6a5-46c686412ca6" />
<img width="720" height="320" alt="stack-realistic-100-settle" src="https://github.com/user-attachments/assets/fd7976e7-655e-4ba4-9af5-ded3a480ed12" />
<img width="720" height="320" alt="stack-realistic-300-rest" src="https://github.com/user-attachments/assets/d6e26984-1628-4d18-b428-9fa55d5f2b7f" />
<img width="720" height="320" alt="stack-realistic-300-settle" src="https://github.com/user-attachments/assets/859ee921-1783-4c6d-b7c5-2110ad2c38f1" />
<img width="1240" height="360" alt="summary" src="https://github.com/user-attachments/assets/099f0886-1540-44da-9eed-7373f94a25e7" />
Final report
### Excalibur benchmark
Baseline: `npm:excalibur@latest` (0.32.0) ยท Candidate: `../build/dist/excalibur.js` (0.33.0-alpha.14+816d29e) ยท 2 interleaved run(s), best run shown with the minโmax of run medians ยท ms/frame
| Test | Baseline median | Baseline p95 | Candidate median | Candidate p95 | ฮ median | ฮ p95 | Extras (candidate) | |
|---|---:|---:|---:|---:|---:|---:|---|---|
| actors-1000 | 4.90 <sub>(4.90โ6.15)</sub> | 11.62 | 4.90 <sub>(4.90โ5.50)</sub> | 10.60 | +0.0% | -8.7% | actors=1000 | |
| actors-4000 | 39.05 <sub>(39.05โ39.05)</sub> | 64.41 | 33.35 <sub>(33.35โ40.25)</sub> | 66.93 | -14.6% | +3.9% | actors=4000 | |
| stack-realistic-100-settle | 8.20 <sub>(8.20โ8.30)</sub> | 19.80 | 6.00 <sub>(6.00โ7.05)</sub> | 14.03 | -26.8% | -29.2% | sleepingAtEnd=0, firstFrameAllAsleep=-1 | ๐ |
| stack-realistic-300-settle | ERROR | | 18.10 | | | | page errors: [Fatal] : RangeError: Invalid array length | โ |
| stack-realistic-300-rest | ERROR | | 3.30 | | | | page errors: [Fatal] : RangeError: Invalid array length | โ |
| arcade-1000-bouncing | 28.50 <sub>(28.50โ28.70)</sub> | 45.70 | 23.40 <sub>(23.40โ24.35)</sub> | 40.51 | -17.9% | -11.4% | outside=14 | |
| edge-floor-realistic-200 | 9.70 <sub>(9.70โ10.25)</sub> | 18.10 | 3.80 <sub>(3.80โ3.95)</sub> | 12.80 | -60.8% | -29.3% | sleepingAtEnd=200, fellThrough=0 | ๐ |
| sleeping-pile-wake | 26.40 <sub>(26.40โ27.45)</sub> | 48.70 | 14.70 <sub>(14.70โ14.70)</sub> | 31.70 | -44.3% | -34.9% | sleepingAtEnd=0, spawned=5 | ๐ |
| bunnymark-2000 | 99.20 <sub>(99.20โ99.65)</sub> | 102.30 | 99.20 <sub>(99.20โ100.10)</sub> | 102.03 | -0.0% | -0.3% | bunnies=2000, drawCalls=1 | |
| bunnymark-5000 | 231.80 <sub>(231.80โ232.70)</sub> | 238.51 | 231.90 <sub>(231.90โ233.10)</sub> | 238.55 | +0.0% | +0.0% | bunnies=5000, drawCalls=1 | |
No test is more than 20% slower than the baseline. Results are informational, shared CI runners are noisy.
Also ironed out some correctness issues with stale geometry that would cause simulation instability (already present in v0.32 sadly, see the stack-realistic-300 boxes row). Roughly speaking the solver would blow up impulse on subsequent substeps because the geometry was out of date and it'd try harder and harder to correct.
Additionally there was more savings by skipping sleeping contacts from the solver, we still wake them when necessary but we don't want to apply impulse to things that are asleep. The contact islands now make sure there are now mixed states. Either all awake or all asleep.
Bonus points! Updated and added our legacy benchmarking tool so we can apples to apples compare with older releases yay science!
chore: split out vitest tests into visual & unit (#3508)
This splits out our vitest test suite into `unit` and `visual` tests. `unit` will run on chromium, while `visual` will run on chrome. I've removed firefox/webkit browsers as we were not running these in CI, and there's a discussion to be had about whether they even make sense to use in unit tests, or are they actually only valuable for visual tests. If the latter, we need to figure out how to not rely on Chrome for consistent screenshotting.
Tests that are visual simply need to contain `@visual` somewhere in it's test name or `describe` name. Unit tests will run for all tests without `@visual`, while visual tests will run all that contain `@visual`. I had considered splitting them out into different files under different folders, but it's nice to re-use the same setup/teardown logic. Otherwise I had to copy & paste all of that in both places.
I also made some other tweaks to our vitest config:
- I've removed the conditional exemption of `--disable-gpu` flag for Chromium on macOS, which although slower it ensures the visual tests pass. I suppose this is because it uses a software renderer.
- Changed the test pooling from `thread` to the default. I never saw a difference in speed but in theory the default (`fork`) is supposed to be faster
- Enabled fileParallelism for tests. I had run into WebGL context issues before, but now they seem to be gone, so this should lead to faster tests
- I've switched from Chrome to actual Chromium for the visual tests, and then lowered the tolerance on the TextSpec so that it could pass both in Windows/Ubuntu CI as well as macOS locally. I'm fine to undo this, but a lower tolerance on font tests might make sense to do anyway and this frees us from relying on Windows+Chrome to run these tests.