Skip to content

task(memory): GPU budget admission policy and unified telemetry #832

Description

@JeanPhilippeKernel

Summary

Complete the production GPU-memory policy around the implemented allocator,
pools, staging ring, deferred frees, and VMA budget sampling. The engine needs
truthful per-heap/per-domain telemetry and deterministic admission decisions;
it must not pretend that unrelated geometry, environment, texture, transient,
and CPU arena values are one global hard budget.

Scope

  • Define a stable per-frame GPU telemetry snapshot from VMA heap budgets plus
    engine-owned counters for geometry streaming, textures, environment lighting,
    staging, and transient render-graph allocations.
  • Define pressure thresholds and explicit admission/retry outcomes per resource
    class. Allocation/cook/bake callers must receive an actionable failure or
    fallback path rather than assert, spin, or leave partial GPU state.
  • Make geometry streaming capacity, environment-lighting quality/budget,
    texture residency, and transient render-graph pressure visible in one
    reporting surface while retaining each subsystem's actual ownership.
  • Specify and implement the policy for device-local heap pressure, unified
    memory, missing VK_EXT_memory_budget, and allocation failure. Quality
    reduction or eviction must be explicit, bounded, and observable.
  • Export diagnostics/metrics suitable for the editor and logs: heap budget and
    usage, domain usage, pressure state, rejected/deferred work, evictions, and
    fallback reason.
  • Add deterministic CPU policy tests. Integrate device-level pressure and
    allocation-failure coverage when Headless VulkanDevice test fixture for RRM/texture-pipeline GPU-level tests #753 supplies its real VulkanDevice
    fixture.

Non-goals

Implementation contract

namespace ZEngine::Core::Memory
{
    enum class GpuPressureState : uint8_t { Normal, Warning, Critical, Exhausted };
    enum class GpuAdmissionResult : uint8_t { Admit, Defer, EvictThenRetry, Reject };
    inline constexpr uint32_t GpuMemoryDomainCount = static_cast<uint32_t>(GpuMemoryDomain::Count);

    struct GpuMemoryPolicyConfig
    {
        float WarningPressure  = 0.90f;
        float CriticalPressure = 0.97f;
    };

    struct GpuMemoryDiagnostic
    {
        GpuAdmissionResult Result = GpuAdmissionResult::Admit;
        GpuMemoryDomain    Domain = GpuMemoryDomain::DeviceGeometry;
        uint64_t           RequestedBytes = 0;
        GpuPressureState   Pressure = GpuPressureState::Normal;
    };

    struct GpuMemoryDomainTelemetry
    {
        GpuMemoryDomain  Domain       = GpuMemoryDomain::DeviceGeometry;
        uint64_t         ReservedBytes = 0;
        uint64_t         ResidentBytes = 0;
        uint64_t         PendingFreeBytes = 0;
    };

    struct GpuHeapTelemetry
    {
        uint64_t         BudgetBytes = 0;
        uint64_t         UsageBytes  = 0;
        GpuPressureState Pressure    = GpuPressureState::Normal;
    };

    struct GpuMemoryTelemetry
    {
        uint64_t                         FrameToken = 0;
        GpuHeapTelemetry                  Heaps[VK_MAX_MEMORY_HEAPS] = {};
        uint32_t                          HeapCount = 0;
        GpuMemoryDomainTelemetry          Domains[GpuMemoryDomainCount] = {};
    };

    struct GpuAdmissionRequest
    {
        GpuMemoryDomain Domain        = GpuMemoryDomain::DeviceGeometry;
        uint64_t        RequestedBytes = 0;
        bool            CanDefer       = false;
        bool            CanEvict       = false;
        cstring         DebugName      = nullptr;
    };

    class GpuMemoryPolicy
    {
    public:
        void Initialize(const GpuMemoryPolicyConfig& config);
        GpuAdmissionResult Evaluate(const GpuMemoryTelemetry& telemetry,
                                    const GpuAdmissionRequest& request,
                                    GpuMemoryDiagnostic* out_diagnostic) const;
    };
}

GpuMemoryTelemetry is a reporting snapshot, not an ownership transfer or a
promise that VMA's global usage can be divided perfectly by engine domain. The
policy evaluates before a new optional allocation is committed; mandatory
allocation failure still propagates a structured error and leaves existing
resources intact. cstring DebugName is borrowed for the call only.

Acceptance criteria

  • Editor/log telemetry identifies heap budget/usage, engine domain usage,
    pressure state, pending frees, and the cause of every deferred/rejected
    request.
  • Geometry, textures, environment lighting, and render-graph transients use
    their declared policy path; no optional workload bypasses pressure handling.
  • Missing memory-budget extension, UMA hardware, critical pressure, and VMA
    allocation failure have deterministic, tested behavior.
  • A failed admission or allocation leaves a valid prior GPU resource and emits
    an actionable diagnostic; no new assertion-only OOM path is introduced.
  • CPU policy tests cover every pressure/admission transition; Headless VulkanDevice test fixture for RRM/texture-pipeline GPU-level tests #753 adds the
    required real-device integration cases when available.

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

P0Production-critical work that blocks foundational engine or editor correctnessenhancementNew feature or request

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions