flutter3d_testing 1.0.0-rc.1
flutter3d_testing: ^1.0.0-rc.1 copied to clipboard
Renders a flutter3d scene with no GPU and compares it against a reference image, so a game gets pixel regression tests on a machine with no display.
1.0.0-rc.1 #
-
Breaking:
QualityMeasurementis a class with the samesetting,costandflip, so a row can later carry more measures. -
Breaking:
FrameSubjectandRenderedFrameare classes. A replay subject and a frame builder returnFrameSubject(scene:, camera:);RenderedFramekeepspixels,width,heightanddrawCalls. Read the same way; a record could not have grown. -
A frame and a replay are drawn on the device a test names.
renderFrame,testReplayandexpectReplayMatchestakedevice:, aBackendOpener(WebGpuDevice.open,GpuRenderBackend.open, an outside backend's own), and draw there instead of on the software rasteriser: a frame's device is disposed once its pixels are read, a replay's at the end of the run.drawOnce, the half both share, is exported for a test that holds its own device and renderer. Null keeps the software rasteriser. -
Breaking:
renderFrame(clearColor:)takes aLinearColor, linear asRenderView.clearColoris, where it took an sRGBVector4:LinearColor.fromSrgb(r, g, b, a)draws the same picture, and the default is still opaque black. -
Breaking: a replay steps the run through the engine's loop.
ReplaySubjectlostrestore,stepandsave, and hasloop(anEngineLoopbuilt onReplayStart.input, the genre installed and its run set) andgenre(theGenrePluginwhose run the tape recorded) in their place.expectReplayMatchesrestores the tape's start as the genre's part of the loop's snapshots (loop.rewindTo(0, state: genre.loopStateOf(start))), plays the tape as the loop's playback onerunSteps(1)at a time, and digests the genre's part ofloop.capture()at each checkpoint — the one path a rollback takes, rather than a second copy of the game's step written for the test. A tape's start and checkpoints are still the run's own snapshot, so every recorded tape keeps passing. A subject whose loop reads anotherInputState, or lacks the genre, is refused before a step. -
Breaking: a boolean reads as a question, and no
boolis positional.PacingReport.evenisisEven.dart fixcarries the renames. -
Breaking:
PacingReportandreplayPacingspeak seconds (docs/CONTRACTS.md):millisandlimitMillisaresecondsandlimit(0.05 by default), andp50,p99andmaxare seconds.toJsonstill writes milliseconds, under the keys it always had. -
1.0.0 is a promise: strict semver from there. This release candidate already keeps it. A patch fixes bugs and breaks nothing, a minor adds, and a break waits for a major. The whole public API is stable, with no experimental exceptions, and is held to the snapshot in
api/. A deprecated name stays until the next major and for at least six months, and says what replaces it. CONTRIBUTING.md has the rules, and SUPPORT.md says which releases get fixes and on which platforms. -
MaterialProgramStagemoved toflutter3d_app, which a game can depend on; this package carriesflutter_test.
testReplayturns a recorded run into a test that needs no GPU.testReplay('test/tapes/level.f3drun', start: …, digestAt: …, goldensAt: …)registers a test that plays the tape into the game on the software backend. At each step indigestAt(every checkpoint the tape holds, by default) the digest of the game's snapshot must equal the recorded one; a divergence names its step and the last checkpoint that agreed. At each step ingoldensAtthe frame is compared withtest/goldens/<tape>-<step>.png. The game implementsReplaySubject(levelHash,restore,step,save,frame) and gets aReplayStartwith the tape, the input to read and the device to upload to. A step past the end of the tape, a digest where the tape has no checkpoint, a test that checks nothing and a level changed since the recording all fail before the first step.expectReplayMatchesdoes the same for aDemoin memory, andreadTapereads a.f3drunor says why it cannot.
-
Breaking:
FramePacingisPacingReport.flutter3d_corehas aFramePacingof its own, the renderer's pacing policy, and a file that imported both met two types of one name.dart fixrenames it. -
Breaking:
ReplaySubjectis anabstract base class, extended rather than implemented, so a member added in a minor release can arrive with a body.levelHashis null by default.
Its flutter3d_* dependencies ask for ^1.0.0.
0.8.0 #
- Two pictures can be compared the way an eye would.
fliptakes two 8-bit sRGB frames andflipLineartwo in linear light, and both return aFlipResult: the per-pixel error map and its mean, nought for identical and one for as different as green is from blue. It is LDR-FLIP (Andersson, Nilsson, Akenine-Möller, Oskarsson, Åström and Fairchild, HPG 2020), ported line for line from the reference C++ so a value here is the value there, and a test holds it to the error maps that implementation publishes. The viewing condition defaults todefaultFlipPpd, about 67 pixels per degree. A count of moved pixels cannot say how much worse a frame looks: a lower render scale moves nearly every pixel a little and looks almost the same. The reference code's BSD licence is kept inthird_party/flip. expectMatchesGoldentakesflipBudget. Null, the default, compares as before. Given, the mean FLIP error replaces the share of differing pixels, for a frame meant to differ a little everywhere, such as a lower render scale or a stochastic effect against its sorted reference.measureQualityTablemeasures the tableAdaptiveQualityinflutter3d_corechooses from. It renders eachQualitySceneat every setting ofQualitySetting.gridon the device it is handed, gives each setting its own renderer andsettleframes, then takes the fastest ofsamplesinterleaved frames, and returns aQualityMeasurementper setting: its cost relative to full quality and its mean FLIP against it. The time is the passes' GPU time where the device reports it and the renderer's wall clock where it does not.qualityTablesSourcewrites the source offlutter3d_core'stables/quality_tables.dartfrom a full grid perDeviceClass, with a flag for whether each was measured on that class. It is an offline tool: it draws every scene at every setting many times over.replayPacingplays a recorded run a frame at a time and times every frame. A frame is one step of the tape throughonStepand onedrawFrame, back to back with the CPU and GPU halves serialised, which errs long and pins each spike to the frame that caused it.gpuSettledcloses a frame and waits for the device'sonFrameComplete, so the GPU's share is counted. The result,FramePacing, keeps the median, the 99th percentile, the worst frame and its index, and every frame overlimitMillis(50 by default); a frame rate would hide the one stop a player notices.repeats,rewindandwarmUpFramesplay a tape several times from its start or skip a level's first frames.
Its flutter3d_* dependencies ask for ^0.8.0.
0.7.1 #
- A zero tolerance was never byte for byte, and now the README says so.
toleranceis the share of pixels allowed to differ, but a pixel only counted as differing when red, green or blue moved by more than 8 steps, and alpha was not compared at all. The README called that "zero". The defaults stay as they were, andexpectMatchesGoldentakeschannelandalpha, so a test with byte-exact references passeschannel: 0, alpha: true.
Its flutter3d_* dependencies ask for ^0.7.1, and it asks for vector_math ^2.4.3.
0.7.0 #
- Breaking for code that builds a
RenderedFrameby hand. The record gained a fourth field,drawCalls, read off theFrameResultevery render already produced.renderFramefills it, and a test can now assert how many draws a scene cost as well as what it looked like. Code that only readspixels,widthandheightis unaffected; a literal(pixels:, width:, height:)passed where aRenderedFrameis wanted no longer has the type. replayGolden: a recorded run, replayed to one step, held to a picture. It drives anInputStatefrom aDemo's tape, calls the caller'sonSteponce per entry, draws the caller'sframeatatStepthroughrenderFrameand compares withexpectMatchesGolden. ADigestTracealready says whether a replay reached the same numbers; this is for a renderer that changed under numbers that did not. The step is the caller's because a genre's simulation is not this package's to know. A tape that ends beforeatStepthrowsStateError, so a short recording is not reported as a wrong picture.flutter3d_sim^0.7.0is a dependency for it.MaterialProgramStagedraws a material written as source on the software backend. It implementsflutter3d_cpu'sCpuFragmentShaderover aMaterialProgramfrompackage:flutter3d_core/formats.dartthat has been throughspecialiseMaterial, and evaluates the same tree the GLSL emitter writes from. It lives here becauseflutter3d_cpu'slib/may not reach the engine and the engine may not reach a backend.flutter3d_core^0.7.0is a dependency for it.- The archive carries a skill for a coding agent,
skills/flutter3d-testing-pixel-regression/, which a project depending on this package installs withdart run skills@ get. - The floors on
flutter3d,flutter3d_cpuandflutter3d_hardwareare^0.7.0.renderFrame,expectMatchesGoldenand the tolerance of zero are unchanged.
0.6.0 #
- Floors, and no code. Drawing a frame through the software backend and
holding it to a reference image works exactly as it did in 0.5.2. The floors
on
flutter3d,flutter3d_cpuandflutter3d_hardwareare^0.6.0— and they matter more here than in most places, because a golden helper resolved against a different engine than the game under test compares two different renderers and calls the difference a regression.
0.5.2 #
^0.5.2on both halves. This package is the one place the engine and the software backend are named together, so it is where "these two move as a pair" can be written at all — seeflutter3d_backend0.5.2 for what a mismatched pair does with morph targets.
0.5.1 #
- Its floors move to
flutter3dandflutter3d_cpuat 0.5.1, both of them. This package pairs the engine with the software backend, and 0.5.1 changed a contract they share: what the surface buffer's alpha holds, and the members of theFogInfoblock. Either half at 0.5.0 against the other at 0.5.1 draws a picture that is quietly the old one — the software backend reads a missing uniform member as zeros rather than refusing, so nothing says anything. Naming both floors is what closes it in both directions, whichflutter3d_backendcannot do because it does not depend on the engine.
0.5.0 #
Breaking. The frame builder takes one object.
FrameBuilderisFrameSubject Function(FrameRequest), so telling a builder the size it is drawing at, or which backend it is on, does not break every builder anybody has written.
0.4.0 #
- No changes of its own; the version moves with the workspace, whose sibling constraints name a single release. The README's closing section now says what the engine around this package is.
0.3.0 #
- First release.
renderFramedraws a scene through the software backend, with no GPU, no driver and no display;expectMatchesGoldenholds the result to a reference image, recording one when there is none. - The tolerance defaults to zero, because a software rasteriser draws the same scene the same way twice and a test that allows drift has stopped watching it.