Skip to content
Open
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
8 changes: 4 additions & 4 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2691,7 +2691,7 @@ storage.
|---|---|
| Style | `dart format` |
| Analysis | `flutter analyze` clean across the workspace, no warnings |
| Unit tests | **11340 tests** across 43 packages and 10 applications |
| Unit tests | **11368 tests** across 43 packages and 10 applications |
| Structure rules | 35, `dart run tool/structure.dart`, the first CI step |
| CI | GitHub Actions over `tool/ci.sh`, on `ubuntu-latest`, with no graphics card |

Expand Down Expand Up @@ -2731,7 +2731,7 @@ material parameter — is the case `doc/boundary-0.5.0.md` worked through for
set belongs to whoever builds on the package rather than to the package
itself.

**Golden render tests.** 90 scenes against **four complete independent
**Golden render tests.** 91 scenes against **four complete independent
reference sets** — Impeller, the software rasteriser, WebGL2 and WebGPU — each
held to zero differing pixels against its own set, with a per-channel tolerance
of 8.
Expand Down Expand Up @@ -3143,7 +3143,7 @@ metres. The directional light's cascades fit the view up to that distance and
nothing beyond it casts — a level whose far end matters visually wants the
number raised, and pays for it in texels.

**The web backend draws all ninety golden scenes the way Impeller does**,
**The web backend draws all ninety-one golden scenes the way Impeller does**,
between 0.01% and 0.42% of pixels differing by more than 8 per channel — the
silhouette's worth of disagreement two rasterisers always have. Two of those
numbers fell when the minification filter learned to read a sampler's
Expand Down Expand Up @@ -3259,7 +3259,7 @@ and charge each of them the bytes, and neither is a change to make on somebody
else's behalf. A build that wants it says so in one flag, and the engine's own
example takes the same answer from `?backend=webgpu` in the URL — a query
parameter rather than a define, because the browser golden stand's whole saving
is one dart2js run serving ninety scenes and both browser backends.
is one dart2js run serving ninety-one scenes and both browser backends.

**`flutter3d_shaders` is one text and no two backends take it the same way.**
Impeller compiles the GLSL with `impellerc`; the WebGL2 generator translates it
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,9 @@ Or one package at a time:
(cd packages/flutter3d_physics && dart test) # plain Dart, no Flutter needed
```

There are 11340 tests across forty-three packages and nine applications. The
There are 11368 tests across forty-three packages and nine applications. The
only ones that need a GPU are the Impeller half of the golden set. The other
half is rendered by the software backend, which is what makes 90 scenes
half is rendered by the software backend, which is what makes 91 scenes
checkable in a headless run.

Several steps run in a browser. `flutter test --platform chrome` covers the two
Expand Down
3 changes: 3 additions & 0 deletions packages/flutter3d/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Unreleased

- The `orthographic-shadow` golden scene: posts down a long floor through an
isometric camera, the shadow cascades split by depth (`P7`).

- **`generatedMaterialPathFor`**: where the build hook writes a `.f3dmat`'s
compiled bundle, `flutter3d_generated/<same path>.f3dshaders`.

Expand Down
4 changes: 2 additions & 2 deletions packages/flutter3d/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ What works today:
`CUBICSPLINE` with authored tangents), slerped rotations, an `AnimationPlayer`
with play/pause/seek/speed and once/loop/ping-pong, and the decoded node
hierarchy rebuilt on instantiation so an animated parent carries its subtree;
- 1659 tests covering projection, scene, sorting, debug draw, raycasting,
- 1664 tests covering projection, scene, sorting, debug draw, raycasting,
animation, skinning, lighting, render targets, BVH, LOD, glTF, OBJ and `.f3d`,
plus a real frame drawn through `flutter3d_cpu`'s software rasteriser for the
ones that need one, all without a GPU. The geometry the engine is written in
Expand Down Expand Up @@ -322,7 +322,7 @@ lib/src/engine/assets/ glTF, OBJ and .f3d decoders, isolate loading, ca
example/lib/ the demo, and the frame capture hook
skills/ the conventions, as agent skills — see below
bin/skills.dart what copies them into a project that uses this
test/ 1659 tests, all runnable without a GPU
test/ 1664 tests, all runnable without a GPU
```

The GLSL is not here. Every shader this package draws with lives in
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter3d/example/lib/src/spike/backend_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import 'golden_store.dart';
///
/// Read from the URL, exactly the way the golden scene and the record/compare
/// direction are read, and for exactly the same arithmetic: one dart2js run
/// serves ninety scenes because the scene is a query parameter, and one
/// serves ninety-one scenes because the scene is a query parameter, and one
/// dart2js run serves the browser backends because this is one too. A
/// `--dart-define` here would be a build per backend, which is the whole saving
/// of the browser golden stand spent on a single word.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ final class GoldenRunner {
scene,
// The store's answer wins where it has one. A desktop run takes the
// direction from the environment and a browser run from the URL, for the
// same reason on both: one build has to serve ninety scenes in both
// same reason on both: one build has to serve ninety-one scenes in both
// directions, and anything the compiler sees is another build.
update:
updateOverride ??
Expand Down
10 changes: 10 additions & 0 deletions packages/flutter3d/example/lib/src/spike/golden_scenes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,16 @@ final List<GoldenScene> kGoldenScenes = <GoldenScene>[
configure: GoldenStages.orthographicMetalSettings,
),

// P7. Posts down a long floor through an orthographic camera, the shadow
// cascades split by depth across what it sees.
const GoldenScene(
name: 'orthographic-shadow',
source: 'Cube',
bloom: false,
ground: false,
stage: GoldenStages.orthographicShadow,
),

// P6. `shadow-teapot` wiped at the middle: lit on the left, the shading
// normal on the right.
const GoldenScene(
Expand Down
50 changes: 46 additions & 4 deletions packages/flutter3d/example/lib/src/spike/golden_stages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1269,17 +1269,59 @@ abstract final class GoldenStages {
fog: FogSettings(color: Vector3(0.7, 0.75, 0.8), density: 0.03),
);

/// `orthographic-shadow`: a floor eighty metres long running away from an
/// isometric camera forty metres back, with a row of posts and slabs down
/// it from the bottom of the frame to the top. Split by distance from the
/// eye, the near cascades covered air in front of the lens and every
/// shadow came from the whole-floor map; split by depth across what the
/// view sees, the posts at the top of the frame cast as sharp a shadow as
/// the ones at the bottom.
static Future<GoldenStaged> orthographicShadow(GoldenStage stage) async {
final device = stage.device;
stage.sun.setLocalForward(Vector3(-0.6, -1.0, 0.35).normalized());
final stone = Material(
baseColor: Vector4(0.62, 0.6, 0.56, 1.0),
roughness: 0.9,
);
final post = Material(baseColor: Vector4(0.7, 0.3, 0.2, 1.0));
return GoldenStaged(
nodes: <SceneNode>[
_slab(
device,
Vector3(10.0, 0.2, 80.0),
Vector3(0.0, -0.1, 30.0),
stone,
),
for (var i = 0; i < 6; i++) ...<SceneNode>[
_slab(
device,
Vector3(0.3, 2.0, 0.3),
Vector3(-2.5, 1.0, i * 4.0),
post,
),
_slab(
device,
Vector3(1.6, 0.2, 0.6),
Vector3(2.0, 1.8, i * 4.0 + 1.0),
post,
),
],
],
everyFrame: (_, _) => stage.camera
..projection = const OrthographicProjection(height: 14.0, far: 200.0)
..setPosition(-14.0, 26.0, -26.0)
..lookAt(Vector3(0.0, 0.0, 10.0)),
);
}

// ------------------------------------------------------------------ P6

/// `debug-view-split`: the lit teapot left of the middle and its shading
/// normal right of it, in one draw — the wipe `DebugViewSettings.split`
/// makes, and the composite leaving the right half out of the tone curve.
static RenderSettings debugViewSplit(RenderSettings settings) =>
settings.copyWith(
debugView: const DebugViewSettings(
view: DebugView.normal,
split: 0.5,
),
debugView: const DebugViewSettings(view: DebugView.normal, split: 0.5),
);

// ------------------------------------------------------------------ R6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ void reportLine(String message) {
/// The scene named in the page's URL, if any.
///
/// A run-time choice here, where the desktop path takes a compile-time define.
/// The reason is arithmetic: the suite is ninety scenes, and rebuilding
/// The reason is arithmetic: the suite is ninety-one scenes, and rebuilding
/// the bundle for each is seventy-eight dart2js runs to compare seventy-eight
/// pictures. One build and seventy-eight navigations is the same information
/// in a fraction of the time.
Expand All @@ -58,7 +58,7 @@ String? get sceneOverride {
/// Whether this run records rather than compares, from the page's URL.
///
/// A run-time choice for the same reason [sceneOverride] is one: the suite is
/// ninety scenes and rebuilding for each would be seventy-eight dart2js
/// ninety-one scenes and rebuilding for each would be seventy-eight dart2js
/// runs. One build serves both directions, and the URL says which.
bool get updateOverride => Uri.base.queryParameters['update'] == '1';

Expand Down
2 changes: 1 addition & 1 deletion packages/flutter3d/test/ambient_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ void main() {
// approximately, bit for bit.
//
// Mutation: default either end of the hemisphere to anything but white.
// Ninety goldens move at once.
// Ninety-one goldens move at once.
final lit = await _pixels(_ball(), _noSky);
final top = _band(lit, 0.15, 0.35);
final bottom = _band(lit, 0.65, 0.85);
Expand Down
4 changes: 2 additions & 2 deletions packages/flutter3d/test/anisotropy_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
/// which spends the taps and throws away what they bought — contrast drops
/// *below* the trilinear baseline and the first assertion fails; and letting
/// a sampler that asked for one reach the new path at all, which would move a
/// backend ninety golden scenes are recorded on.
/// backend ninety-one golden scenes are recorded on.
///
/// **One they do not catch, said rather than hidden:** taking the taps along
/// the *short* axis instead of the long one passes every line here. The
Expand Down Expand Up @@ -213,7 +213,7 @@ void main() {
});

test('asking for one tap is the old sampler exactly', () async {
// What makes this safe to add to the backend ninety golden scenes are
// What makes this safe to add to the backend ninety-one golden scenes are
// recorded on. Not "close": the same bytes, because with `anisotropy` at
// one the new path is not reached at all.
expect(await _floorAt(1), await _floorAt(1));
Expand Down
4 changes: 2 additions & 2 deletions packages/flutter3d/test/auto_batch_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
/// three backends.** It computes in Dart doubles; Impeller, WebGL and WebGPU
/// compute in 32-bit floats, where those two expressions have far less room
/// before they part. Nothing here can run them, so the setting stays off by
/// default — which is also what keeps the ninety goldens where they are.
/// default — which is also what keeps the ninety-one goldens where they are.
library;

import 'dart:math' as math;
Expand Down Expand Up @@ -191,7 +191,7 @@ void main() {
});

test('nothing is batched unless it is asked for', () async {
// Which is what keeps the ninety goldens where they are: the default
// Which is what keeps the ninety-one goldens where they are: the default
// frame is the frame it always was, to the byte.
final device = _device();
final scene = _field(device, count: 100);
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter3d/test/fog_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ void main() {

test('no fog is byte-identical to the fog nobody asked for', () async {
// The early return at zero density is not an optimisation. The golden sets
// — ninety scenes, zero-pixel threshold, three backends — are all
// — ninety-one scenes, zero-pixel threshold, three backends — are all
// recorded with the default `FogSettings()`, and this is the property that
// lets them stay recorded.
final none = FogSettings();
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion packages/flutter3d/test/grade_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Future<List<int>> _ramp(LookSettings look, {int width = 256}) async {

void main() {
test('every default is an exact identity', () async {
// The promise ninety goldens rest on. Neutral for these three is
// The promise ninety-one goldens rest on. Neutral for these three is
// (0,0,0) added, (1,1,1) as an exponent and (1,1,1) multiplied, and the
// composite has to take all three without moving a byte.
final plain = await _ramp(const LookSettings());
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter3d/test/halation_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ void main() {

test('zero is an exact identity, and is the default', () async {
// The multiplier is one on every channel at zero, so the chain comes out
// byte for byte as it did — which is what ninety goldens need.
// byte for byte as it did — which is what ninety-one goldens need.
expect(const BloomSettings().halation, 0.0);
final a = await _glow(halation: 0.0);
final b = await _glow(halation: 0.0);
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter3d/test/irradiance_bounce_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ double _tint(Uint8List rgba) {
void main() {
test('off, the picture is the bytes it was', () async {
// The clause every recorded frame depends on. A field is null by default,
// so this is the path ninety goldens take.
// so this is the path ninety-one goldens take.
final a = await _draw(field: false, redWall: true);
final b = await _draw(field: false, redWall: true);
expect(a, orderedEquals(b));
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter3d/test/masked_shadow_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ void main() {
test('a caster with no map keeps the shadow it always cast', () async {
// **The half that lets this land.** A material that is not cut out goes
// through the stage it has always gone through, with no sampler in the
// pipeline and no texture bound per draw, so the ninety goldens
// pipeline and no texture bound per draw, so the ninety-one goldens
// recorded against that stage cannot move. Asking for MASK with no map to
// read is the same case: there is nothing to cut out.
final plain = await _frame(masked: false, withTexture: false);
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter3d/test/material_vertex_stage_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ void main() {
// **The half that lets this land**, and the reason the seam is a null field
// rather than a new required one: a material saying nothing about its vertex
// stage goes through the pipeline it has always gone through, which is also
// why the ninety goldens cannot move.
// why the ninety-one goldens cannot move.
final withSeam = await _draw(lighting: _plain);

final device = CpuDevice(
Expand Down
Loading
Loading