acdream/src/AcDream.App/Rendering/Packs/RenderPackResidentBudget.cs
Erik 132395e6f7 fix #425: pack resident budget is a 1080p ceiling that scales with pixel count; an explicit Apply retries a failed selection
Live Holtburg at 2560x1440: the Low preset needed 67,368,164 resident bytes
(screen-sized HDR/depth/ray targets are 44 MB of that) against an absolute
64 MiB ceiling that had only been validated at 1080p, so Options -> Apply
fell back to the default path; every later Apply was then refused by the
controller's failure memo, which treated the user's deliberate choice like
automatic re-activation.

RenderPackResidentBudget.Effective scales the declared 1080p figure by the
viewport's pixel-count ratio (never below 1), still capped by the hardware
MaxPackResidentBytes; both pack graphs use it and the performance-matrix
tool judges its resident column by the same rule (contract test updated).
RenderPackController.Request gains explicitUserChoice, which clears the
memo for that selection; RenderPackSelectionBinding passes it on every
display edge (Apply, including resolution changes) and keeps the memo for
the startup request.

Tests: RenderPackResidentBudgetTests (1080p/720p keep the declared
ceiling, 1440p = 16/9x, 4K = 4x, hardware cap wins, zero extent rejected);
controller explicit-retry; the binding test now proves the user's next
Apply activates once the cause is gone. App hermetic lane 6,068/0 (Release).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 10:00:11 +02:00

55 lines
2.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

namespace AcDream.App.Rendering.Packs;
/// <summary>
/// The one rule for a pack preset's effective resident-GPU ceiling.
/// </summary>
/// <remarks>
/// <para>
/// A preset's <c>MaxResidentGpuBytes</c> is the <b>1080p</b> figure of the
/// Campaign AR budget table (Low 64 / Medium 128 / High 256 MiB). The pack's
/// resident set is dominated by screen-sized images — the HDR world colour +
/// depth targets alone are 12 bytes per pixel, plus the ray/bloom
/// intermediates — so a ceiling that does not scale with pixel count refuses
/// the pack the moment the user picks a larger mode: at 2560×1440 the Low
/// preset's screen targets are 44 MB of a 64 MiB budget, and live Holtburg's
/// scene-dependent shadow command buffers pushed the total to 64.25 MiB.
/// That refusal is what the owner hit at the Campaign VM VM7 gate
/// (2026-08-23, #425): Apply → "needs 67368164 resident GPU bytes; the
/// active pack budget is 67108864" → persisted back to the default path.
/// </para>
/// <para>
/// The effective ceiling is therefore the declared 1080p figure scaled by
/// the pixel-count ratio (never below 1, so smaller modes keep the declared
/// ceiling), and still capped by the hardware's <c>MaxPackResidentBytes</c>.
/// The parts of the resident set that do NOT scale with the screen (shadow
/// depth maps, shadow command/transform buffers) are over-allowed by the same
/// factor; that errs on the side of honouring the user's explicit resolution
/// choice while keeping the bound proportional to what they asked the GPU to
/// draw. <c>tools/run-atmospheric-performance-matrix.ps1</c> judges its
/// resident column with this same rule so the tool and the runtime cannot
/// disagree.
/// </para>
/// </remarks>
internal static class RenderPackResidentBudget
{
internal const long ReferencePixels = 1920L * 1080L;
internal static long Effective(
long declaredBytesAt1080p,
int viewportWidth,
int viewportHeight,
long hardwareCapBytes)
{
ArgumentOutOfRangeException.ThrowIfNegative(declaredBytesAt1080p);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(viewportWidth);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(viewportHeight);
ArgumentOutOfRangeException.ThrowIfNegative(hardwareCapBytes);
long pixels = checked((long)viewportWidth * viewportHeight);
long scaled = pixels <= ReferencePixels
? declaredBytesAt1080p
: checked((long)Math.Ceiling(
(double)declaredBytesAt1080p * pixels / ReferencePixels));
return Math.Min(scaled, hardwareCapBytes);
}
}