Add external flame render receipts
This commit is contained in:
parent
d6aba37faf
commit
ee83fb4844
8 changed files with 344 additions and 7 deletions
|
|
@ -40,8 +40,10 @@ This is not full flame parity. It is the first ruler on the desk.
|
|||
`spherical`, and `bubble` variation weights.
|
||||
2. DONE: add exact point parity for `linear`, `spherical`, and `bubble`,
|
||||
plus a deterministic histogram checksum for the mixed fixture.
|
||||
3. Add deterministic histogram parity against a local external FLAM3/Apophysis
|
||||
compatible renderer, with generated receipts under `artifacts/parity`.
|
||||
3. DONE: add an optional external FLAM3-compatible render receipt path. When
|
||||
`flam3-render` is available, Aquarium renders the fixture to binary PPM and
|
||||
records image-shape, non-black pixel count, RGB checksum, and luminance
|
||||
checksum under `artifacts/fractal-flame-reference`.
|
||||
4. Lower proven flame definitions into GPU program rows. Only then should the
|
||||
live reservoir renderer use them as visual test scenes.
|
||||
|
||||
|
|
@ -65,6 +67,25 @@ Default receipt:
|
|||
This receipt is Aquarium's local CPU parser/evaluator checksum. It is not yet
|
||||
an external FLAM3/Apophysis renderer receipt.
|
||||
|
||||
## External Renderer Receipt
|
||||
|
||||
FLAM3 stays outside the repo and outside runtime binaries. If a local
|
||||
`flam3-render` executable is installed, run:
|
||||
|
||||
```powershell
|
||||
.\scripts\fractal-flame-reference-render.ps1 -Flam3Render C:\path\to\flam3-render.exe
|
||||
```
|
||||
|
||||
The script follows FLAM3's documented environment-variable interface: `in`
|
||||
selects the flame file, `out` selects the output image, `format=ppm` requests a
|
||||
plain reference image, `seed` fixes the random sequence, and `qs` controls
|
||||
quality scale. Aquarium then hashes the PPM through
|
||||
`tools/Aquarium.Fractal.Receipt --reference-ppm`.
|
||||
|
||||
This is the first external renderer receipt. It proves the fixture can be
|
||||
rendered by the reference family and gives Aquarium a stable image artifact to
|
||||
compare as the local flame subset grows.
|
||||
|
||||
## Sources
|
||||
|
||||
- Apophysis 7x repository: https://github.com/wanily/apophysis7x
|
||||
|
|
|
|||
62
scripts/fractal-flame-reference-render.ps1
Normal file
62
scripts/fractal-flame-reference-render.ps1
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
param(
|
||||
[string]$Flam3Render = $env:AQUARIUM_FLAM3_RENDER,
|
||||
[string]$Flame = "tests\Aquarium.Engine.Fractal.Tests\Fixtures\Apophysis\linear-spherical-bubble.flame",
|
||||
[string]$Seed = "0x0BADC0DE",
|
||||
[string]$QualityScale = "1",
|
||||
[string]$OutputDirectory = "artifacts\fractal-flame-reference"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Flam3Render)) {
|
||||
throw "Set -Flam3Render or AQUARIUM_FLAM3_RENDER to a local flam3-render executable. Aquarium does not vendor FLAM3 or Apophysis."
|
||||
}
|
||||
|
||||
$rendererCommand = Get-Command $Flam3Render -ErrorAction SilentlyContinue
|
||||
if ($null -eq $rendererCommand) {
|
||||
throw "Could not find flam3-render executable: $Flam3Render"
|
||||
}
|
||||
|
||||
$renderer = $rendererCommand.Source
|
||||
$flameInput = if ([System.IO.Path]::IsPathRooted($Flame)) { $Flame } else { Join-Path $repoRoot $Flame }
|
||||
$flamePath = (Resolve-Path $flameInput).Path
|
||||
$outDir = Join-Path $repoRoot $OutputDirectory
|
||||
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
|
||||
|
||||
$stamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$imagePath = Join-Path $outDir "flam3-reference-$stamp.ppm"
|
||||
|
||||
$previous = @{
|
||||
in = $env:in
|
||||
out = $env:out
|
||||
format = $env:format
|
||||
seed = $env:seed
|
||||
qs = $env:qs
|
||||
verbose = $env:verbose
|
||||
}
|
||||
|
||||
try {
|
||||
$env:in = $flamePath
|
||||
$env:out = $imagePath
|
||||
$env:format = "ppm"
|
||||
$env:seed = $Seed
|
||||
$env:qs = $QualityScale
|
||||
$env:verbose = "0"
|
||||
& $renderer
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "flam3-render failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$env:in = $previous.in
|
||||
$env:out = $previous.out
|
||||
$env:format = $previous.format
|
||||
$env:seed = $previous.seed
|
||||
$env:qs = $previous.qs
|
||||
$env:verbose = $previous.verbose
|
||||
}
|
||||
|
||||
dotnet run --project (Join-Path $repoRoot "tools\Aquarium.Fractal.Receipt\Aquarium.Fractal.Receipt.csproj") -c Release -- `
|
||||
--reference-ppm $imagePath `
|
||||
--out $outDir
|
||||
149
src/Aquarium.Engine.Fractal/Grammar/FractalPpmImageReceipt.cs
Normal file
149
src/Aquarium.Engine.Fractal/Grammar/FractalPpmImageReceipt.cs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
namespace Aquarium.Engine.Fractal.Grammar;
|
||||
|
||||
public readonly record struct FractalPpmImageReceipt(
|
||||
int Width,
|
||||
int Height,
|
||||
int MaxChannelValue,
|
||||
int PixelCount,
|
||||
int NonBlackPixelCount,
|
||||
ulong RgbChecksum,
|
||||
ulong LuminanceChecksum);
|
||||
|
||||
public static class FractalPpmImageReceiptBuilder
|
||||
{
|
||||
public static FractalPpmImageReceipt Build(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
var cursor = 0;
|
||||
var magic = ReadToken(bytes, ref cursor);
|
||||
if (!magic.SequenceEqual("P6"u8))
|
||||
{
|
||||
throw new FormatException("Only binary P6 PPM reference renders are supported.");
|
||||
}
|
||||
|
||||
var width = ParsePositiveInt(ReadToken(bytes, ref cursor), "width");
|
||||
var height = ParsePositiveInt(ReadToken(bytes, ref cursor), "height");
|
||||
var maxChannelValue = ParsePositiveInt(ReadToken(bytes, ref cursor), "max channel value");
|
||||
if (maxChannelValue > 255)
|
||||
{
|
||||
throw new FormatException("Only 8-bit PPM reference renders are supported.");
|
||||
}
|
||||
|
||||
ConsumeSingleWhitespace(bytes, ref cursor);
|
||||
var expectedBytes = checked(width * height * 3);
|
||||
if (bytes.Length - cursor < expectedBytes)
|
||||
{
|
||||
throw new FormatException("PPM reference render ended before all RGB pixels were present.");
|
||||
}
|
||||
|
||||
var rgbChecksum = 1469598103934665603UL;
|
||||
var luminanceChecksum = 1469598103934665603UL;
|
||||
var nonBlack = 0;
|
||||
var pixels = bytes.Slice(cursor, expectedBytes);
|
||||
for (var index = 0; index < pixels.Length; index += 3)
|
||||
{
|
||||
var r = pixels[index];
|
||||
var g = pixels[index + 1];
|
||||
var b = pixels[index + 2];
|
||||
rgbChecksum = Step(rgbChecksum, r);
|
||||
rgbChecksum = Step(rgbChecksum, g);
|
||||
rgbChecksum = Step(rgbChecksum, b);
|
||||
var luminance = (byte)(((r * 54) + (g * 183) + (b * 19)) >> 8);
|
||||
luminanceChecksum = Step(luminanceChecksum, luminance);
|
||||
if ((r | g | b) != 0)
|
||||
{
|
||||
nonBlack++;
|
||||
}
|
||||
}
|
||||
|
||||
return new FractalPpmImageReceipt(
|
||||
width,
|
||||
height,
|
||||
maxChannelValue,
|
||||
width * height,
|
||||
nonBlack,
|
||||
rgbChecksum,
|
||||
luminanceChecksum);
|
||||
}
|
||||
|
||||
private static ulong Step(ulong hash, byte value)
|
||||
{
|
||||
hash ^= value;
|
||||
hash *= 1099511628211UL;
|
||||
return hash;
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<byte> ReadToken(ReadOnlySpan<byte> bytes, ref int cursor)
|
||||
{
|
||||
SkipWhitespaceAndComments(bytes, ref cursor);
|
||||
var start = cursor;
|
||||
while (cursor < bytes.Length && !IsWhitespace(bytes[cursor]) && bytes[cursor] != (byte)'#')
|
||||
{
|
||||
cursor++;
|
||||
}
|
||||
|
||||
if (cursor == start)
|
||||
{
|
||||
throw new FormatException("PPM reference render header ended unexpectedly.");
|
||||
}
|
||||
|
||||
return bytes[start..cursor];
|
||||
}
|
||||
|
||||
private static void SkipWhitespaceAndComments(ReadOnlySpan<byte> bytes, ref int cursor)
|
||||
{
|
||||
while (cursor < bytes.Length)
|
||||
{
|
||||
if (IsWhitespace(bytes[cursor]))
|
||||
{
|
||||
cursor++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bytes[cursor] != (byte)'#')
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (cursor < bytes.Length && bytes[cursor] != (byte)'\n')
|
||||
{
|
||||
cursor++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConsumeSingleWhitespace(ReadOnlySpan<byte> bytes, ref int cursor)
|
||||
{
|
||||
if (cursor >= bytes.Length || !IsWhitespace(bytes[cursor]))
|
||||
{
|
||||
throw new FormatException("PPM reference render header is missing the pixel-data separator.");
|
||||
}
|
||||
|
||||
cursor++;
|
||||
}
|
||||
|
||||
private static bool IsWhitespace(byte value)
|
||||
{
|
||||
return value is (byte)' ' or (byte)'\t' or (byte)'\r' or (byte)'\n';
|
||||
}
|
||||
|
||||
private static int ParsePositiveInt(ReadOnlySpan<byte> token, string name)
|
||||
{
|
||||
var value = 0;
|
||||
foreach (var digit in token)
|
||||
{
|
||||
if (digit is < (byte)'0' or > (byte)'9')
|
||||
{
|
||||
throw new FormatException($"PPM {name} is not an integer.");
|
||||
}
|
||||
|
||||
value = checked((value * 10) + digit - (byte)'0');
|
||||
}
|
||||
|
||||
if (value <= 0)
|
||||
{
|
||||
throw new FormatException($"PPM {name} must be positive.");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
@ -148,7 +148,7 @@ renderer_state:
|
|||
- Zyphos camera composition targets the selected domain's tracked center while preserving the parent domain as orbit/composition anchor. Parent pivots remain part of navigation, but selected planetary/tile domains must not be pushed offscreen by aiming the camera at their parent.
|
||||
- Fractal reference parity starts before the renderer: FractalOwnershipTree can carry affine IFS definitions from `.aquageo`, FractalAffineIfsChaosGame generates deterministic point sets, and ApophysisReferenceParityTests compares a minimal Apophysis/FLAM3-style `.flame` linear Sierpinski fixture against an equivalent `.aquageo` candidate. Apophysis/FLAM3 are test oracles, not runtime code.
|
||||
- The Apophysis/FLAM3 reference subset now has a runtime parser/evaluator boundary: FractalFlameFileParser reads `.flame` xforms with weight, color, affine coefficients, and `linear`/`spherical`/`bubble` variation weights; FractalFlameChaosGame produces deterministic point sets; FractalPointHistogramBuilder produces fixed histogram checksums for parity receipts.
|
||||
- tools/Aquarium.Fractal.Receipt can now run a CPU flame histogram receipt with `--flame`; scripts/fractal-flame-receipt.ps1 emits the default linear/spherical/bubble fixture checksum 0xAEB1C81B under artifacts/fractal-flame-receipts. This is a local parser/evaluator receipt, not external renderer parity yet.
|
||||
- tools/Aquarium.Fractal.Receipt can now run a CPU flame histogram receipt with `--flame`; scripts/fractal-flame-receipt.ps1 emits the default linear/spherical/bubble fixture checksum 0xAEB1C81B under artifacts/fractal-flame-receipts. It can also hash binary PPM renders with `--reference-ppm`; scripts/fractal-flame-reference-render.ps1 drives a local external `flam3-render` through FLAM3's documented env vars and records image-shape/non-black/checksum receipts under artifacts/fractal-flame-reference without vendoring GPL renderer code.
|
||||
- D3D12 capacity logging now reports active fractal reservoir resident MiB, splat count, visible debug splat count, updates/pass, and candidates/update so the GPU cache has a receipt in normal runtime logs, not just in the standalone benchmark.
|
||||
- The consolidated fractal brush architecture plan defines the intended pipeline: client intent -> domain roots -> semantic IFS brush grammar -> ownership tree -> node summaries -> contribution cache/residency scheduler -> backend packets -> renderer passes -> debug/evidence, with explicit module boundaries and mockable unit-test seams.
|
||||
- The Perfect Machine architecture lives in docs/perfect-machine-architecture.md. It is the current waterfall design authority for fast 2D SDF splats, 3D SDF splats, 2D-projected-to-3D surface splats, IFS `.aquageo` structural probes, Mimir-style camera/audio evidence candidates, ReSTIR/GRIS reservoir reuse, heuristic occupancy graph updates, resource-tier residency, and TAA guide-buffer integration.
|
||||
|
|
|
|||
|
|
@ -105,8 +105,8 @@
|
|||
"The first visible fractal reservoir consumer is a tangent-frame surface patch renderer, not a final terrain resolve. It draws a larger live subset of GPU-resident splats, projects patch corners from world space, and shades from radial normals plus SDF/PBR/radiosity reservoirs. If the result still looks abstract, the next coherent fix is clustered surface resolve/ray query or coordinate authority, not more decorative billboard tuning.",
|
||||
"Zyphos camera composition must target the selected spatial domain's tracked center while keeping the parent as orbit/composition anchor. Looking directly at the parent made planetary mode aim at the binary midpoint and left Zyphos offscreen, which made renderer failures look like art failures.",
|
||||
"Do not use Zyphos as the first proof of fractal DSL correctness. Start with reference fractals. Apophysis/FLAM3-style `.flame` fixtures are the parity pressure for Aquarium `.aquageo`; GPL/open-source renderers stay external/test-only. The first gate is linear Sierpinski: exact fixed-xform sequence parity and chaos-game moment bounds before any GPU visualization claim.",
|
||||
"The local Apophysis/FLAM3 subset parser owns only a small proven flame shape: xform weight/color, affine coefficients, and `linear`, `spherical`, and `bubble` variation weights. `FractalFlameChaosGame` and `FractalPointHistogramBuilder` provide deterministic CPU receipts. Next step is external FLAM3/Apophysis rendered histogram parity before GPU lowering.",
|
||||
"Use `scripts/fractal-flame-receipt.ps1` for the local CPU flame receipt. The default mixed linear/spherical/bubble fixture uses 8192 samples, 64 burn-in, 64x64 bounds -8,-8,8,8, seed 0x0BADC0DE, and checksum 0xAEB1C81B. Treat this as parser/evaluator proof only; external FLAM3/Apophysis renderer parity is still the next gate.",
|
||||
"The local Apophysis/FLAM3 subset parser owns only a small proven flame shape: xform weight/color, affine coefficients, and `linear`, `spherical`, and `bubble` variation weights. `FractalFlameChaosGame` and `FractalPointHistogramBuilder` provide deterministic CPU receipts. External renderer proof stays optional and outside runtime through FLAM3-compatible PPM receipt hashing.",
|
||||
"Use `scripts/fractal-flame-receipt.ps1` for the local CPU flame receipt. The default mixed linear/spherical/bubble fixture uses 8192 samples, 64 burn-in, 64x64 bounds -8,-8,8,8, seed 0x0BADC0DE, and checksum 0xAEB1C81B. Use `scripts/fractal-flame-reference-render.ps1` with a local `flam3-render` executable to produce external PPM image receipts under artifacts/fractal-flame-reference; Aquarium does not vendor Apophysis or FLAM3.",
|
||||
"Zyphos' primary planetary terrain test path is now the fractal reservoir surface. The direct SDF planet and Umbros proxy shaders are removed from the render plan; only the parent star remains as a direct SDF body/light. If the planet looks wrong, fix the reservoir lowering/shading path instead of letting the old SDF terrain hide it.",
|
||||
"The GPU splat receipt harness lives at tools/Aquarium.Fractal.Receipt and scripts/fractal-splat-receipt.ps1. The live receipt allocates one 80-byte packed SDF splat UAV plus three distinct 64-byte reservoir UAVs for SDF envelopes, PBR material envelopes, and radiosity; those reservoirs are sampled in independent compute passes under a stochastic update budget. On the local NVIDIA GTX 1070 it kept 2,000,000 splats and 2,000,000 reservoirs per layer resident. The fallback hash-fractal receipt updated 50,000 entries per layer with 2 candidates/update at 4.471 ms/frame, 223.7 FPS equivalent. The authored precomposed selected-cut receipt used depth 48, 4 compact transforms, and 15,000 updates per layer at 4.908 ms/frame, 203.7 FPS equivalent, with full reservoir coverage in 133.3 frames. D3D12FractalSplatRender consumes the raw splat, SDF, PBR, and radiosity buffers through separate graphics SRVs for the visible debug subset. This proves compute-side GPU residency, budgeted temporal convergence, authored-program splat population decoupled from domain ancestry depth, and first layered GPU consumption; final terrain/material/radiosity shading is still future work.",
|
||||
"FractalProjectedSdfSplatCompiler is the Phase G 2D-projected-to-3D lowering seam: it converts resident SignedDistance2D page payload samples into compact 3D SDF splats through an explicit page-local Vector2 -> Vector3 mapping, surface normal, support radius, and splat budget. Zyphos caches a bounded first set with a flat local mapping until cube-sphere/torus mappers are added.",
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
## Current Slice
|
||||
|
||||
Aquarium's active focus is clean engine architecture and shiny Epiphany agent
|
||||
visuals.
|
||||
Aquarium's active focus is the fractal reference gate for the Perfect Machine:
|
||||
prove compact IFS/flame fixtures numerically before asking Zyphos visuals to
|
||||
carry architectural truth.
|
||||
|
||||
Keep the engine/client boundary strict:
|
||||
|
||||
|
|
@ -39,6 +40,10 @@ The renderer work that matters next:
|
|||
packet is `Aquarium.Engine.Fractal` plus tests, cube-face/tile keys,
|
||||
identity/tangent projections, area distortion sampler, face-edge tests, and
|
||||
a projection report.
|
||||
- Use `research/rendering/fractal-reference-oracles.md` for Apophysis/FLAM3
|
||||
parity. Local parser/evaluator receipts are not external renderer proof.
|
||||
`scripts/fractal-flame-reference-render.ps1` is the optional FLAM3-compatible
|
||||
PPM receipt path when a local `flam3-render` binary is installed.
|
||||
- Use `docs/zyphos-eusocial-sync.md` before evolving Zyphos world content.
|
||||
Eusocial Interbeing owns canon in `E:\Projects\Eusocial Interbeing`; Zyphos
|
||||
owns render constraints and feeds design questions back through the vault's
|
||||
|
|
|
|||
|
|
@ -106,6 +106,49 @@ public sealed class ApophysisReferenceParityTests
|
|||
Assert.Equal(0xAEB1C81Bu, checksum);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PpmReferenceReceiptReadsExternalRendererOutputShape()
|
||||
{
|
||||
var ppm = new byte[]
|
||||
{
|
||||
(byte)'P', (byte)'6', (byte)'\n',
|
||||
(byte)'#', (byte)' ', (byte)'F', (byte)'L', (byte)'A', (byte)'M', (byte)'3', (byte)'\n',
|
||||
(byte)'2', (byte)' ', (byte)'2', (byte)'\n',
|
||||
(byte)'2', (byte)'5', (byte)'5', (byte)'\n',
|
||||
0, 0, 0,
|
||||
255, 0, 0,
|
||||
0, 128, 0,
|
||||
0, 0, 64,
|
||||
};
|
||||
|
||||
var receipt = FractalPpmImageReceiptBuilder.Build(ppm);
|
||||
|
||||
Assert.Equal(2, receipt.Width);
|
||||
Assert.Equal(2, receipt.Height);
|
||||
Assert.Equal(4, receipt.PixelCount);
|
||||
Assert.Equal(3, receipt.NonBlackPixelCount);
|
||||
Assert.Equal(0x605A_BDE5_13A2_D19AUL, receipt.RgbChecksum);
|
||||
Assert.Equal(0xD56A_A5A0_E36F_3C2BUL, receipt.LuminanceChecksum);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PpmReferenceReceiptDoesNotTreatPixelWhitespaceAsHeader()
|
||||
{
|
||||
var ppm = new byte[]
|
||||
{
|
||||
(byte)'P', (byte)'6', (byte)'\n',
|
||||
(byte)'1', (byte)' ', (byte)'1', (byte)'\n',
|
||||
(byte)'2', (byte)'5', (byte)'5', (byte)'\n',
|
||||
10, 13, 32,
|
||||
};
|
||||
|
||||
var receipt = FractalPpmImageReceiptBuilder.Build(ppm);
|
||||
|
||||
Assert.Equal(1, receipt.PixelCount);
|
||||
Assert.Equal(1, receipt.NonBlackPixelCount);
|
||||
Assert.Equal(0xDD2C_094F_57F3_A456UL, receipt.RgbChecksum);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<ReferenceAffineTransform> LoadLinearApophysisFixture(string path)
|
||||
{
|
||||
var document = XDocument.Load(path);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ using Vortice.Direct3D12;
|
|||
using Vortice.DXGI;
|
||||
|
||||
var options = ReceiptOptions.Parse(args);
|
||||
if (options.ReferencePpmPath is not null)
|
||||
{
|
||||
RunReferencePpmReceipt(options);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.FlamePath is not null)
|
||||
{
|
||||
RunFlameHistogramReceipt(options);
|
||||
|
|
@ -48,6 +54,34 @@ Console.WriteLine($"cpu submit+wait ms/frame: {receipt.CpuSubmitAndWaitMilliseco
|
|||
Console.WriteLine($"readback checksum: 0x{receipt.ReadbackChecksum:X16}");
|
||||
Console.WriteLine($"receipt: {receiptPath}");
|
||||
|
||||
static void RunReferencePpmReceipt(ReceiptOptions options)
|
||||
{
|
||||
var ppmBytes = File.ReadAllBytes(options.ReferencePpmPath!);
|
||||
var image = FractalPpmImageReceiptBuilder.Build(ppmBytes);
|
||||
var receipt = new ReferencePpmReceipt(
|
||||
Path.GetFullPath(options.ReferencePpmPath!),
|
||||
image.Width,
|
||||
image.Height,
|
||||
image.MaxChannelValue,
|
||||
image.PixelCount,
|
||||
image.NonBlackPixelCount,
|
||||
image.RgbChecksum,
|
||||
image.LuminanceChecksum);
|
||||
|
||||
Directory.CreateDirectory(options.OutputDirectory);
|
||||
var receiptPath = Path.Combine(options.OutputDirectory, $"fractal-reference-ppm-receipt-{DateTimeOffset.Now:yyyyMMdd-HHmmss}.json");
|
||||
File.WriteAllText(receiptPath, JsonSerializer.Serialize(receipt, new JsonSerializerOptions { WriteIndented = true }));
|
||||
|
||||
Console.WriteLine("=== Aquarium External Flame Reference PPM Receipt ===");
|
||||
Console.WriteLine($"source: {receipt.SourcePath}");
|
||||
Console.WriteLine($"image: {receipt.Width}x{receipt.Height}");
|
||||
Console.WriteLine($"pixels: {receipt.PixelCount:N0}");
|
||||
Console.WriteLine($"non-black pixels: {receipt.NonBlackPixelCount:N0}");
|
||||
Console.WriteLine($"rgb checksum: 0x{receipt.RgbChecksum:X16}");
|
||||
Console.WriteLine($"luminance checksum: 0x{receipt.LuminanceChecksum:X16}");
|
||||
Console.WriteLine($"receipt: {receiptPath}");
|
||||
}
|
||||
|
||||
static void RunFlameHistogramReceipt(ReceiptOptions options)
|
||||
{
|
||||
var flameSource = File.ReadAllText(options.FlamePath!);
|
||||
|
|
@ -428,6 +462,7 @@ internal sealed record ReceiptOptions(
|
|||
string ShaderPath,
|
||||
string OutputDirectory,
|
||||
string? FlamePath,
|
||||
string? ReferencePpmPath,
|
||||
int HistogramSamples,
|
||||
int HistogramBurnIn,
|
||||
int HistogramWidth,
|
||||
|
|
@ -449,6 +484,7 @@ internal sealed record ReceiptOptions(
|
|||
Path.Combine("src", "Aquarium.Engine", "Render", "Shaders", "D3D12FractalReservoirCompute.hlsl"),
|
||||
Path.Combine("artifacts", "fractal-splat-receipts"),
|
||||
null,
|
||||
null,
|
||||
8192,
|
||||
64,
|
||||
64,
|
||||
|
|
@ -472,6 +508,7 @@ internal sealed record ReceiptOptions(
|
|||
"--shader" => options with { ShaderPath = Next() },
|
||||
"--out" => options with { OutputDirectory = Next() },
|
||||
"--flame" => options with { FlamePath = Next() },
|
||||
"--reference-ppm" => options with { ReferencePpmPath = Next() },
|
||||
"--histogram-samples" => options with { HistogramSamples = int.Parse(Next()) },
|
||||
"--histogram-burn-in" => options with { HistogramBurnIn = int.Parse(Next()) },
|
||||
"--histogram-size" => ParseHistogramSize(options, Next()),
|
||||
|
|
@ -480,6 +517,16 @@ internal sealed record ReceiptOptions(
|
|||
};
|
||||
}
|
||||
|
||||
if (options.ReferencePpmPath is not null)
|
||||
{
|
||||
if (!File.Exists(options.ReferencePpmPath))
|
||||
{
|
||||
throw new FileNotFoundException("Reference PPM file was not found.", options.ReferencePpmPath);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
if (options.FlamePath is not null)
|
||||
{
|
||||
if (!File.Exists(options.FlamePath))
|
||||
|
|
@ -557,6 +604,16 @@ internal sealed record FlameHistogramReceipt(
|
|||
int OccupiedBins,
|
||||
uint Checksum);
|
||||
|
||||
internal sealed record ReferencePpmReceipt(
|
||||
string SourcePath,
|
||||
int Width,
|
||||
int Height,
|
||||
int MaxChannelValue,
|
||||
int PixelCount,
|
||||
int NonBlackPixelCount,
|
||||
ulong RgbChecksum,
|
||||
ulong LuminanceChecksum);
|
||||
|
||||
internal sealed record GpuFractalSplatReceipt(
|
||||
string Adapter,
|
||||
int SplatCount,
|
||||
|
|
|
|||
Loading…
Reference in a new issue