-
Notifications
You must be signed in to change notification settings - Fork 0
GPU DeepZoom Handoff
Branch: feature/vulkan-compute · Tip at handoff: 468859c ·
Next task: SA/BLA-on-GPU spike (#88) — jump to
§6.
- V6 GPU deep-zoom perturbation (#82) is functionally complete on BOTH backends (Vulkan + D3D11), off by default, gated, tiled for TDR safety, with a perf auto-fallback. Correctness is bit-exact vs the CPU deep path and validated headless.
- No fast-FP64 hardware is available in this environment for a perf sign-off. Both test GPUs are too weak at FP64, so the perf auto-fallback correctly disables the GPU deep path and everything runs on the CPU. The feature is correctness-proven, perf-unproven — it only pays off on a strong-FP64 GPU (workstation / compute card).
- Next work (SA/BLA on GPU, #88) can START on weak hardware because correctness gates are speed-independent; only the eventual perf sign-off needs capable hardware.
Deep-zoom perturbation runs the δ loop in FP64. Consumer GPUs cripple FP64:
| GPU | Backend | FP64 rate | Deep-GPU result |
|---|---|---|---|
| GeForce GT710 (Kepler) | D3D11 (Windows) | 1/24 | Slower than CPU. One un-tiled dispatch tripped the Windows TDR watchdog → DXGI_ERROR_DEVICE_REMOVED (now tiled + perf-fallback → CPU). |
| Intel UHD 630 | Vulkan (Linux) | weak/slow | Perf-fallback disables at very shallow HP zoom and never re-engages (session-disable is intentional). Deep GPU never beneficial here. |
Conclusion: on both available GPUs the CPU deep path (multi-threaded SIMD PT + SA/BLA) is faster than GPU FP64. The GPU path's value is real only on strong-FP64 hardware. Don't chase GPU speedups on these two cards — validate correctness on them, defer perf to capable hardware.
The Linux "engages vs DISABLED — slower" log line is the perf verdict for any given box. On the UHD 630 it says DISABLED.
-
MandelbrotCalculator.UseGpuPerturbation(static, default OFF) — master toggle. - Gate in
CalculateHighPrecision(Engine/Calculators/MandelbrotCalculator.cs):!recycled && UseGpuPerturbation && GpuKernel != null && GpuKernel.SupportsPerturbation && AllowPtRebasing && !UseDdRebaseReference && !ForceScalarPtPath && !tileCap && Zoom <= MaxGpuPerturbZoom && _refOrbitLen >= 1→TryRunGpuPerturbation. -
MaxGpuPerturbZoom = ODZoomThreshold (1e50), conservative. Deep-dc recheck proved single-doubledcis bit-exact vs DD across 1e6→1e40 (--vulkanpturbdc), so this ceiling can lift toward thescale-denormal limit — held pending a strong-FP64 sign-off. -
Enable seams: Vulkan auto-enables on
--renderer vulkanwhenshaderFloat64present (Hosting/AvaloniaShellBootstrap.cs). D3D is behind envFF_GPU_PERTURB=1(deep-only — attaches the kernel but leaves the shallow FP32 GPU path off).
- Shared HLSL
MandelbrotKernelSource.BuildPerturb(): cbuffer reordered doubles-first (64 bytes, clean 8-byte alignment) + newgRowBase; pixel row =gRowBase + tid.y. -
MandelbrotKernelSource.PerturbBandRows(w,h,maxIter)— band height from a 40M iter-pixel budget. - Both backends dispatch the frame in row bands, each its own GPU packet (Vulkan: submit+
QueueWaitIdle; D3D:Flushper band), so no single packet exceeds the ~2 s TDR budget.
- After band 0 (real GPU-synced time), extrapolate
band0 × bandCount. If> PerturbBudgetMs(default 3000, envFF_GPU_PERTURB_BUDGET_MS) → throwGPU-PERTURB-TOO-SLOW. - Calculator catch treats that (and any device-lost) as disable
UseGpuPerturbationfor the session + CPU deep path. This is why weak GPUs "fall off and never re-engage" — by design.
-
Rendering.D3D/MandelbrotKernelSource.cs— shared HLSL + band/budget helpers (compile-linked into Vulkan). -
Rendering.D3D/MandelbrotGpuKernel.cs— D3DRunPerturb(tiling + band-0 perf sync viaCopyResource+Map). -
Rendering.Vulkan/VulkanComputeKernel.cs— VulkanRunPerturb(tiling + band-0 perf viaQueueWaitIdle). -
Engine/Calculators/MandelbrotCalculator.cs— gate,TryRunGpuPerturbation, too-slow/device-lost handling,LastFrameUsedGpuPerturbation(HUD marker),#86file-diagnostics. -
Engine/Interefaces/IGpuKernel.cs—SupportsPerturbation+RunPerturbdefault-interface members. -
Engine/Rendering/FractalRenderHost.cs—DD (GPU)HUD marker; newest-wins present guard;#86trace. -
Rendering.Vulkan.Smoke/Perturb*Probe.cs— the headless gates (below).
dotnet run --project Rendering.Vulkan.Smoke/FracturingFog.Rendering.Vulkan.Smoke.csproj -- --vulkanpturbcalc # end-to-end calc parity: 0/16384 exact
dotnet run --project Rendering.Vulkan.Smoke/FracturingFog.Rendering.Vulkan.Smoke.csproj -- --vulkanpturbprobe # kernel-vs-CPU at noise floor
dotnet run --project Rendering.Vulkan.Smoke/FracturingFog.Rendering.Vulkan.Smoke.csproj -- --vulkanpturbdc # deep-dc precision sweep 1e6→1e50
| Var | Effect |
|---|---|
FF_GPU_PERTURB=1 |
D3D/Windows: opt in to deep GPU perturbation (deep-only). |
FF_GPU_PERTURB_BUDGET_MS |
Perf-fallback threshold, ms (default 3000). Raise to force GPU to stay on for measuring. |
FF_PERTURB_BANDROWS |
Force band height (test hook — stress multi-band; e.g. 8). |
FF_GPU_PERTURB_DEBUG=1 |
Write #86 present/gate trace to %TEMP%/ff_gpu_perturb_86.log (WinExe has no console). |
| # | Title | State |
|---|---|---|
| #82 | V6 GPU perturbation | Core done both backends. Remaining: MaxGpuPerturbZoom lift decision + close-out — both need a strong-FP64 perf sign-off. |
| #88 | SA/BLA on GPU | Deferred — the next task (this handoff). |
| #85 | Resize-during-calc buffer realloc race | Mitigated (cancel before realloc) + not reproducing; proper full calc-thread drain still open, low priority. |
| #84 | LightingFX HUD gone post-Vulkan | Open UI regression, unrelated to compute — quick win, weak HW fine. |
| #44 | V5 macOS via MoltenVK | Stretch — needs a Mac, not FP64. |
Closed this arc: #86 (stale deep frame → was TDR device-removed, fixed by tiling), #87 (perf-fallback, done).
-
#86diagnostics (FF_GPU_PERTURB_DEBUGfile trace inFractalRenderHost+MandelbrotCalculator) are still in — opt-in and harmless, kept as they help debug the SA/BLA spike. Strip when the deep-GPU work is fully signed off.
-
The δ loop is PLAIN DOUBLE, even at extreme zoom — the default CPU
ComputePixelPTRebasedreads only the Hi-limb reference orbit (_refZr/_refZi) + single-roundeddc. QD/OD precision is spent BUILDING the ref orbit + carrying the centre, not in the per-pixel loop. So no in-shader limb (DD/QD) math is needed for the base kernel. (Proven again by--vulkanpturbdc.) -
Parity metric = disagreement FRACTION vs the CPU precision noise floor, NOT
maxΔiter. Filament pixels at the escape-time knife-edge flip by many iters under sub-ULP rounding (boundary chaos) — the CPU-vs-DD oracle disagrees by the same order. Gate on the fraction. (SeeDocs/Deep-Zoom-Perturbation.md.) -
One HLSL source, two compilers:
MandelbrotKernelSourcefeeds FXC (cs_5_0, D3D) and DXC (cs_6_0 -spirv, Vulkan). No[[vk::binding]]— DXC shift flags map registers (-fvk-b/t/u-shift). Bindings:b0=params,t0/t1=refZr/refZi,u0/u1/u2=iter/smooth/finalZD. -
cbuffer layout is doubles-first, 64 bytes — both C# blobs (
PerturbParamsD3D,PerturbParamsBlobVulkan) must match the HLSL byte-for-byte or you get silent garbage. -
D3D kernel shares the renderer's device/immediate-context (via the
GpuKernelFactoryHookdowncast). A long or crashing dispatch therefore takes down presentation too — that's why TDR froze the whole app. A dedicated deferred context / separate compute device would decouple them (noted on #87, not done). -
_d3dGateserialises kernel dispatch withrenderer.Render/UpdateTexture.RunPerturbholds it for the whole tiled render — a slow deep GPU frame blocks present for its duration (another reason weak-FP64 GPU is bad). - Non-degenerate probe centres must have an amplifying orbit (|Z|≈2 range). The parabolic root (-0.75,0) is
degenerate (orbit small, amp≈1, all interior). Canonical deep centre:
-1.9918151296901943… / -5.524…e-6(4+ limbs) — used across the deep-zoom probes.
SA spike CORRECTNESS: GREEN (2026-07-22). In-shader Series Approximation landed and validated on the GT710. Kernel
MandelbrotKernelSource.BuildPerturbSA()(entryCSPerturbSA) +VulkanComputeKernel.RunPerturbSA(8 coefficient SSBOs t2..t9, 80-byte SA UBO) + gate--vulkanpturbsa(PerturbSaProbe). In-shaderFindSkipuses squared magnitudes (HLSL has no doublesqrt); coefficients come from the productionEngine/Math/SeriesApproximation. Results at zoom 1e6, tol 1e-3, refLen 3090:
- (1) GPU-SA vs CPU-SA = 0.141 % (13/9216) — at the #82 GPU-vs-CPU dialect floor (0.119 %). The in-shader FindSkip/EvalDelta + SA-seeded rebased loop reproduces the CPU SA path. This is the correctness proof.
- SA engaged: avg skip k = 32, max 3090, 100 % of pixels skipped ≥16.
- SA effect (SA vs no-SA) = 8.76 % at tol 1e-3 — expected boundary chaos, NOT a bug: a tolerance sweep (
FF_SA_TOL) collapses it to the 0.011 % precision floor at tol 1e-6/1e-9 (tighter tol → smaller skip → less truncation), proving the divergence is genuine SA truncation, correctly controlled. GPU adds 0 % beyond the CPU SA path.What is DONE: the SA-spike correctness (step 1–3 below). What remains: (a) wire into
TryRunGpuPerturbationbehind a sub-toggle (step 4) + the D3D FXC compile ofBuildPerturbSA(Vulkan/DXC proven; FXC unverified); (b) perf sign-off on strong-FP64 HW — GT710 cannot; (c) BLA (heavier, DD coeffs + table, deferred until SA lands — now it has).Gate:
dotnet run --project Rendering.Vulkan.Smoke/... -- --vulkanpturbsa
Goal: add iteration-skipping (SA first, BLA later) to the GPU perturbation kernel so it stops repeating work the CPU elides. Spike-first, exactly like #82: prove bit-exact parity headless before a full build.
-
SA (Series Approximation): dc-independent Taylor series near iter 0, one skip to a start iteration
k+ starting δ. Simplest to port — a few coefficient SSBOs + a per-pixelFindSkip/EvalDelta. CPU ref:SeriesApproximation(Engine/…), used viasa.FindSkip(dcR,dcI,…)/sa.EvalDelta(k,…)inMandelbrotCalculator(see thedcRod/EvalDeltasites). -
BLA (Bivariate Linear Approximation): hierarchical 2^k-step merge table, DD-precision
A_npast ~1e15, in-shader table lookup — heavier, more divergence, needs DD coeffs. Defer until SA lands.
- Divergence: SA/BLA give variable per-pixel skip counts. On SIMT, lanes skipping different amounts diverge and erode the speedup. Options for the spike: (a) accept partial benefit (per-pixel skip, measure), or (b) uniform per-tile skip. Start with (a) for correctness; measure divergence later on strong HW.
- Correctness first: the skip must produce the same escape iteration as the non-skipped loop within the noise floor.
-
New probe
--vulkanpturbsainRendering.Vulkan.Smoke/(copyPerturbCalcProbe/PerturbSpikeProbestructure). Build a reference orbit, run the SA-skipping kernel, compare to the plain perturbation kernel AND to the CPU SA path. Gate = disagreement fraction vs noise floor (reuse the--vulkanpturbprobephilosophy). -
SA variant of the shared kernel — add an
SAsection to a copy ofBuildPerturb()(or aBuildPerturbSA()): upload SA coefficients as SSBO(s), per pixel doFindSkip → k, δ_kthen run the existing rebased loop fromk. Keep it FXC + DXC clean (no[[vk::binding]]; extend the shift-mapped bindings). - Validate on GT710/lavapipe — bit-exact parity is speed-independent, so weak HW is fine here.
- Only after SA parity is green: wire into
TryRunGpuPerturbationbehind a new sub-toggle, and defer perf to strong-FP64 HW.
- CAN: everything correctness — write the kernel, the probe, prove bit-exact parity on GT710/lavapipe.
- CANNOT: decide if SA/BLA-on-GPU is actually faster (perf sign-off) — needs strong-FP64 hardware; the #87 fallback will disable the path on GT710/UHD 630 regardless.
-
Docs/Deep-Zoom-Perturbation.md(perturbation + rebasing + SA/BLA math — do not derive from memory). -
Docs/Technical/Vulkan-Compute-DevelopmentPlan.md§13–§15 (spike + full-build history). - CPU SA/BLA in
Engine/Calculators/MandelbrotCalculator.cs:EnsureSeriesApproximation,EnsureBlaTable,SeriesApproximation,BlaTable, and thesa.FindSkip/EvalDelta+_blaTablecall sites. -
MandelbrotKernelSource.BuildPerturb()— the kernel you're extending.
V6 deep-GPU perturbation done + correctness-proven on both backends; no fast-FP64 HW here for perf sign-off (GT710 + UHD 630 both fall back to CPU by design). SA-on-GPU spike (#88) correctness now GREEN too (
--vulkanpturbsa, GPU-vs-CPU-SA 0.141 % on GT710). Next: wire SA intoTryRunGpuPerturbation+ D3D FXC compile; perf sign-off still needs strong-FP64 HW; then BLA.