650 lines
24 KiB
C#
650 lines
24 KiB
C#
using AcDream.App.Plugins;
|
|
using AcDream.App.Rendering.Gpu;
|
|
using AcDream.App.Rendering.Packs;
|
|
using AcDream.Plugin.Abstractions.Rendering;
|
|
using AcDream.UI.Abstractions.Panels.Settings;
|
|
|
|
namespace AcDream.App.Tests.Rendering.Packs;
|
|
|
|
public sealed class RenderPackAutoRuntimeTests
|
|
{
|
|
[Fact]
|
|
public void AutoStartsMediumAndAtomicallyPublishesPreparedLowCandidateAtBoundary()
|
|
{
|
|
using var fixture = new Fixture();
|
|
fixture.Activate("auto");
|
|
FakeRuntime medium = fixture.Active;
|
|
|
|
Assert.Equal("medium", medium.Preset.Id);
|
|
Assert.Equal("auto", fixture.Controller.Snapshot.Selection.PresetId);
|
|
fixture.ObserveOverBudget(AtmosphericAutoQualityController.DowngradeHysteresisFrames);
|
|
|
|
Assert.Same(medium, fixture.Controller.ActiveRuntime);
|
|
Assert.False(medium.Disposed);
|
|
Assert.Equal(1, fixture.Factory.BuildCount);
|
|
|
|
RenderPackActivationSnapshot changed = fixture.Controller.ApplyAtFrameBoundary(
|
|
new RenderPackActivationExtent(1920, 1080, 2));
|
|
FakeRuntime low = fixture.Active;
|
|
|
|
Assert.Equal(RenderPackActivationState.Active, changed.State);
|
|
Assert.Equal("auto", changed.Selection.PresetId);
|
|
Assert.Equal("low", low.Preset.Id);
|
|
Assert.True(low.Prepared);
|
|
Assert.True(medium.Disposed);
|
|
Assert.Equal(
|
|
["build:medium", "prepare:medium:1280x720x4", "build:low",
|
|
"prepare:low:1920x1080x2", "dispose:medium"],
|
|
fixture.Events);
|
|
Assert.Equal(0, fixture.Controller.Performance.CpuSampleCount);
|
|
Assert.Equal(AtmosphericQualityLevel.Low,
|
|
fixture.Controller.AutoQuality!.Value.Current);
|
|
|
|
RenderPackDiagnosticsSnapshot diagnostics = fixture.Controller.CaptureDiagnostics();
|
|
Assert.Equal("auto", diagnostics.PresetId);
|
|
Assert.Equal("low", diagnostics.EffectiveQuality);
|
|
}
|
|
|
|
[Fact]
|
|
public void Auto_keeps_current_quality_live_while_replacement_prepares_off_side()
|
|
{
|
|
var scheduler = new ControlledPreparationScheduler();
|
|
using var fixture = new Fixture(preparationScheduler: scheduler);
|
|
fixture.Controller.Request(new RenderPackSelectionSettings(
|
|
"auto.test", "1.0.0", "auto"));
|
|
fixture.Controller.ApplyAtFrameBoundary(
|
|
new RenderPackActivationExtent(1280, 720, 4));
|
|
scheduler.CompleteNext();
|
|
fixture.Controller.ApplyAtFrameBoundary(
|
|
new RenderPackActivationExtent(1280, 720, 4));
|
|
FakeRuntime medium = fixture.Active;
|
|
|
|
fixture.ObserveOverBudget(AtmosphericAutoQualityController.DowngradeHysteresisFrames);
|
|
RenderPackActivationSnapshot pending = fixture.Controller.ApplyAtFrameBoundary(
|
|
new RenderPackActivationExtent(1280, 720, 4));
|
|
|
|
Assert.Equal(RenderPackActivationState.CandidatePending, pending.State);
|
|
Assert.Same(medium, fixture.Controller.ActiveRuntime);
|
|
Assert.False(medium.Disposed);
|
|
scheduler.CompleteNext();
|
|
Assert.Same(medium, fixture.Controller.ActiveRuntime);
|
|
|
|
fixture.Controller.ApplyAtFrameBoundary(
|
|
new RenderPackActivationExtent(1280, 720, 4));
|
|
|
|
Assert.Equal("low", fixture.Active.Preset.Id);
|
|
Assert.True(medium.Disposed);
|
|
}
|
|
|
|
[Fact]
|
|
public void WeakHostStartsAutoAtLowAndCannotPromoteIntoUnavailablePresets()
|
|
{
|
|
using var fixture = new Fixture(new RenderPackHostCapabilities(
|
|
Enum.GetValues<RenderCapability>().ToHashSet(),
|
|
4096,
|
|
4,
|
|
64L * 1024 * 1024));
|
|
|
|
fixture.Activate("auto");
|
|
|
|
Assert.Equal("low", fixture.Active.Preset.Id);
|
|
Assert.Equal(AtmosphericQualityLevel.Low, fixture.Controller.AutoQuality!.Value.Current);
|
|
fixture.Active.ResolvedGpuMilliseconds = 0.01;
|
|
for (int i = 0; i < AtmosphericAutoQualityController.UpgradeHysteresisFrames + 1; i++)
|
|
fixture.Observe(0.01, stable: true);
|
|
Assert.Equal(AtmosphericQualityLevel.Low, fixture.Controller.AutoQuality!.Value.Current);
|
|
Assert.Equal(1, fixture.Factory.BuildCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void AutoFailsSafelyToRetailWhenLowPersistentlyExceedsItsDeclaredBudget()
|
|
{
|
|
using var fixture = new Fixture();
|
|
fixture.Activate("auto");
|
|
fixture.ObserveOverBudget(AtmosphericAutoQualityController.DowngradeHysteresisFrames);
|
|
fixture.Controller.ApplyAtFrameBoundary(
|
|
new RenderPackActivationExtent(1280, 720, 4));
|
|
FakeRuntime low = fixture.Active;
|
|
Assert.Equal("low", low.Preset.Id);
|
|
|
|
fixture.ObserveOverBudget(
|
|
1
|
|
+ AtmosphericAutoQualityController.ChangeCooldownFrames
|
|
+ AtmosphericAutoQualityController.DowngradeHysteresisFrames);
|
|
|
|
Assert.Same(low, fixture.Controller.ActiveRuntime);
|
|
Assert.True(fixture.Controller.AutoQuality!.Value.SafeFallbackToRetailRequested);
|
|
|
|
RenderPackActivationSnapshot fallback = fixture.Controller.ApplyAtFrameBoundary(
|
|
new RenderPackActivationExtent(1280, 720, 4));
|
|
|
|
Assert.Equal(RenderPackActivationState.FailedToRetail, fallback.State);
|
|
Assert.Equal(RenderPackSelectionSettings.Retail, fallback.Selection);
|
|
Assert.Null(fixture.Controller.ActiveRuntime);
|
|
Assert.True(low.Disposed);
|
|
Assert.Contains(
|
|
"Low remained over its declared performance budget for 180 stable samples",
|
|
fallback.Reason,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("GPU p99 20.000 ms (budget 12.000 ms)", fallback.Reason);
|
|
Assert.Contains("CPU p99 20.000 ms (budget 3.000 ms)", fallback.Reason);
|
|
Assert.Contains("resident GPU bytes 536870912 (budget 67108864)", fallback.Reason);
|
|
Assert.Null(fixture.Controller.AutoQuality);
|
|
Assert.Equal(0, fixture.Controller.Performance.CpuSampleCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void HostThatCannotSupportLowFailsAutoPreciselyToRetail()
|
|
{
|
|
using var fixture = new Fixture(new RenderPackHostCapabilities(
|
|
Enum.GetValues<RenderCapability>().ToHashSet(),
|
|
4096,
|
|
4,
|
|
32L * 1024 * 1024,
|
|
MemoryPolicyDescription: "test weak-host policy"));
|
|
|
|
fixture.Controller.Request(new RenderPackSelectionSettings(
|
|
"auto.test", "1.0.0", "auto"));
|
|
RenderPackActivationSnapshot snapshot = fixture.Controller.ApplyAtFrameBoundary(
|
|
new RenderPackActivationExtent(1280, 720, 4));
|
|
|
|
Assert.Equal(RenderPackActivationState.FailedToRetail, snapshot.State);
|
|
Assert.Null(fixture.Controller.ActiveRuntime);
|
|
Assert.Contains("cannot support Low", snapshot.Reason, StringComparison.Ordinal);
|
|
Assert.Contains("33554432", snapshot.Reason, StringComparison.Ordinal);
|
|
Assert.Equal(0, fixture.Factory.BuildCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void AutoCandidateResourceFailureDisposesBothCandidatesAndFailsSafelyToRetail()
|
|
{
|
|
using var fixture = new Fixture();
|
|
fixture.Activate("auto");
|
|
FakeRuntime medium = fixture.Active;
|
|
fixture.ObserveOverBudget(AtmosphericAutoQualityController.DowngradeHysteresisFrames);
|
|
fixture.Factory.FailPreparePreset = "low";
|
|
|
|
RenderPackActivationSnapshot failed = fixture.Controller.ApplyAtFrameBoundary(
|
|
new RenderPackActivationExtent(1280, 720, 4));
|
|
|
|
Assert.Equal(RenderPackActivationState.FailedToRetail, failed.State);
|
|
Assert.Null(fixture.Controller.ActiveRuntime);
|
|
Assert.True(medium.Disposed);
|
|
FakeRuntime candidate = fixture.Factory.Runtimes[^1];
|
|
Assert.Equal("low", candidate.Preset.Id);
|
|
Assert.True(candidate.Disposed);
|
|
Assert.Contains("injected low resource failure", failed.Reason, StringComparison.Ordinal);
|
|
Assert.Equal(0, fixture.Controller.Performance.CpuSampleCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnstableFramesAndExplicitPresetsNeverDriveAutomaticChanges()
|
|
{
|
|
using var auto = new Fixture();
|
|
auto.Activate("auto");
|
|
auto.ObserveOverBudget(1000, stable: false);
|
|
Assert.Equal(AtmosphericQualityLevel.Medium,
|
|
auto.Controller.AutoQuality!.Value.Current);
|
|
Assert.Equal(0, auto.Controller.Performance.CpuSampleCount);
|
|
Assert.Equal(1, auto.Factory.BuildCount);
|
|
|
|
using var explicitHigh = new Fixture();
|
|
explicitHigh.Activate("high");
|
|
explicitHigh.ObserveOverBudget(1000);
|
|
explicitHigh.Controller.ApplyAtFrameBoundary(
|
|
new RenderPackActivationExtent(1280, 720, 4));
|
|
Assert.Equal("high", explicitHigh.Active.Preset.Id);
|
|
Assert.Null(explicitHigh.Controller.AutoQuality);
|
|
Assert.Equal(1, explicitHigh.Factory.BuildCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void AutomaticQualityBooleanEnablesAutoFromAnExplicitPreset()
|
|
{
|
|
using var fixture = new Fixture(descriptor: DescriptorWithAutomaticSetting());
|
|
fixture.Controller.Request(new RenderPackSelectionSettings(
|
|
"auto.test",
|
|
"1.0.0",
|
|
"high")
|
|
{
|
|
SettingOverrides = new RenderPackSettingOverrides(
|
|
new Dictionary<string, string> { ["automatic-quality"] = "true" }),
|
|
});
|
|
|
|
RenderPackActivationSnapshot snapshot = fixture.Controller.ApplyAtFrameBoundary(
|
|
new RenderPackActivationExtent(1280, 720, 4));
|
|
|
|
Assert.Equal(RenderPackActivationState.Active, snapshot.State);
|
|
Assert.Equal("high", fixture.Active.Preset.Id);
|
|
Assert.Equal(AtmosphericQualityLevel.High, fixture.Controller.AutoQuality!.Value.Current);
|
|
}
|
|
|
|
[Fact]
|
|
public void AutomaticQualityBooleanCanDisableTheAutomaticSelector()
|
|
{
|
|
using var fixture = new Fixture(descriptor: DescriptorWithAutomaticSetting());
|
|
fixture.Controller.Request(new RenderPackSelectionSettings(
|
|
"auto.test",
|
|
"1.0.0",
|
|
"auto")
|
|
{
|
|
SettingOverrides = new RenderPackSettingOverrides(
|
|
new Dictionary<string, string> { ["automatic-quality"] = "false" }),
|
|
});
|
|
|
|
RenderPackActivationSnapshot snapshot = fixture.Controller.ApplyAtFrameBoundary(
|
|
new RenderPackActivationExtent(1280, 720, 4));
|
|
|
|
Assert.Equal(RenderPackActivationState.Active, snapshot.State);
|
|
Assert.Equal("medium", fixture.Active.Preset.Id);
|
|
Assert.Null(fixture.Controller.AutoQuality);
|
|
}
|
|
|
|
[Fact]
|
|
public void ResourceGenerationChangeResetsWindowBeforeAcceptingNewLayoutSamples()
|
|
{
|
|
using var fixture = new Fixture();
|
|
fixture.Activate("auto");
|
|
fixture.ObserveOverBudget(5);
|
|
Assert.Equal(5, fixture.Controller.Performance.CpuSampleCount);
|
|
|
|
fixture.Active.ResourceGeneration++;
|
|
fixture.ObserveOverBudget(1);
|
|
Assert.Equal(0, fixture.Controller.Performance.CpuSampleCount);
|
|
fixture.ObserveOverBudget(1);
|
|
Assert.Equal(1, fixture.Controller.Performance.CpuSampleCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void DiagnosticResetStartsACompleteFreshPerformanceWindow()
|
|
{
|
|
using var fixture = new Fixture();
|
|
fixture.Activate("high");
|
|
fixture.ObserveOverBudget(4);
|
|
Assert.Equal(4, fixture.Controller.MinimumPerformanceSampleCount);
|
|
|
|
Assert.True(
|
|
fixture.Controller.TryResetPerformanceEvidence(out string error),
|
|
error);
|
|
Assert.Equal(0, fixture.Controller.MinimumPerformanceSampleCount);
|
|
|
|
fixture.ObserveOverBudget(1);
|
|
Assert.Equal(1, fixture.Controller.MinimumPerformanceSampleCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void DiagnosticResetCannotPerturbAutomaticQualityEvidence()
|
|
{
|
|
using var fixture = new Fixture();
|
|
fixture.Activate("auto");
|
|
fixture.ObserveOverBudget(1);
|
|
|
|
Assert.False(
|
|
fixture.Controller.TryResetPerformanceEvidence(out string error));
|
|
Assert.Contains("explicit quality preset", error);
|
|
Assert.Equal(1, fixture.Controller.MinimumPerformanceSampleCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void DiagnosticsSeparatePackAddedCpuAbsoluteReceiverCpuAndInclusiveGpu()
|
|
{
|
|
using var fixture = new Fixture();
|
|
fixture.Activate("high");
|
|
double[] cpu = [1, 2, 3, 4];
|
|
double[] gpu = [4, 6, 8, 10];
|
|
for (int i = 0; i < cpu.Length; i++)
|
|
{
|
|
fixture.Active.ResolvedGpuMilliseconds = gpu[i];
|
|
fixture.Observe(cpu[i], stable: true);
|
|
}
|
|
|
|
RenderPackDiagnosticsSnapshot diagnostics = fixture.Controller.CaptureDiagnostics();
|
|
|
|
Assert.Equal(4, diagnostics.Performance.CpuSampleCount);
|
|
Assert.Equal(4, diagnostics.Performance.AbsoluteReceiverCpuSampleCount);
|
|
Assert.Equal(4, diagnostics.Performance.GpuSampleCount);
|
|
Assert.Equal(2, diagnostics.Performance.IncrementalCpuMillisecondsP50);
|
|
Assert.Equal(4, diagnostics.Performance.IncrementalCpuMillisecondsP95);
|
|
Assert.Equal(4, diagnostics.Performance.IncrementalCpuMillisecondsP99);
|
|
Assert.Equal(0, diagnostics.Performance.AbsoluteReceiverCpuMillisecondsP50);
|
|
Assert.Equal(6, diagnostics.Performance.InclusiveGpuMillisecondsP50);
|
|
Assert.Equal(10, diagnostics.Performance.InclusiveGpuMillisecondsP95);
|
|
Assert.Equal(10, diagnostics.Performance.InclusiveGpuMillisecondsP99);
|
|
Assert.Contains(
|
|
"perf=cpu-added:2.000/4.000/4.000ms,receiver-cpu-absolute:0.000/0.000/0.000ms,gpu-inclusive:6.000/10.000/10.000ms",
|
|
RenderPackDiagnosticsFormatter.Format(diagnostics),
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void AbsoluteReceiverCpuIsDiagnosticOnlyWhileGpuRemainsInclusive()
|
|
{
|
|
using var fixture = new Fixture();
|
|
fixture.Activate("high");
|
|
fixture.Active.ResolvedGpuMilliseconds = 4.5;
|
|
|
|
fixture.Observe(
|
|
cpuMilliseconds: 1.25,
|
|
stable: true,
|
|
receiverCpuMilliseconds: 0.75);
|
|
|
|
RenderPackPerformanceSnapshot performance = fixture.Controller.Performance;
|
|
Assert.Equal(1, performance.CpuSampleCount);
|
|
Assert.Equal(1.25, performance.IncrementalCpuMillisecondsP50);
|
|
Assert.Equal(0.75, performance.AbsoluteReceiverCpuMillisecondsP50);
|
|
Assert.Equal(4.5, performance.InclusiveGpuMillisecondsP50);
|
|
}
|
|
|
|
[Fact]
|
|
public void LargeAbsoluteReceiverCpuCannotForceAutoDownButPackAddedCpuCan()
|
|
{
|
|
using var receiverHeavy = new Fixture();
|
|
receiverHeavy.Activate("auto");
|
|
receiverHeavy.Active.ResolvedGpuMilliseconds = 0.1;
|
|
for (int i = 0; i < AtmosphericAutoQualityController.DowngradeHysteresisFrames; i++)
|
|
{
|
|
receiverHeavy.Observe(
|
|
cpuMilliseconds: 0.1,
|
|
stable: true,
|
|
receiverCpuMilliseconds: 100);
|
|
}
|
|
|
|
Assert.Equal(
|
|
AtmosphericQualityLevel.Medium,
|
|
receiverHeavy.Controller.AutoQuality!.Value.Current);
|
|
Assert.Equal(
|
|
100,
|
|
receiverHeavy.Controller.Performance.AbsoluteReceiverCpuMillisecondsP99);
|
|
Assert.Equal(
|
|
0.1,
|
|
receiverHeavy.Controller.Performance.IncrementalCpuMillisecondsP99);
|
|
|
|
using var packHeavy = new Fixture();
|
|
packHeavy.Activate("auto");
|
|
packHeavy.Active.ResolvedGpuMilliseconds = 0.1;
|
|
for (int i = 0; i < AtmosphericAutoQualityController.DowngradeHysteresisFrames; i++)
|
|
{
|
|
packHeavy.Observe(
|
|
cpuMilliseconds: 10,
|
|
stable: true,
|
|
receiverCpuMilliseconds: 0.1);
|
|
}
|
|
|
|
Assert.Equal(
|
|
AtmosphericQualityLevel.Low,
|
|
packHeavy.Controller.AutoQuality!.Value.Current);
|
|
}
|
|
|
|
[Fact]
|
|
public void StablePerformanceObservationAllocatesNothingAfterWarmup()
|
|
{
|
|
using var fixture = new Fixture();
|
|
fixture.Activate("high");
|
|
for (int i = 0; i < 128; i++)
|
|
fixture.Observe(1, stable: true);
|
|
|
|
long before = GC.GetAllocatedBytesForCurrentThread();
|
|
for (int i = 0; i < 128; i++)
|
|
fixture.Observe(1, stable: true);
|
|
long after = GC.GetAllocatedBytesForCurrentThread();
|
|
|
|
Assert.Equal(0, after - before);
|
|
}
|
|
|
|
private sealed class Fixture : IDisposable
|
|
{
|
|
private readonly BufferedRenderPackRegistry _registry = new();
|
|
private readonly IDisposable _registration;
|
|
|
|
internal Fixture(
|
|
RenderPackHostCapabilities? capabilities = null,
|
|
RenderPackDescriptor? descriptor = null,
|
|
IRenderPackPreparationScheduler? preparationScheduler = null)
|
|
{
|
|
Factory = new FakeFactory(Events);
|
|
_registration = _registry.Register(descriptor ?? Descriptor(), new EmptyAssets());
|
|
Controller = new RenderPackController(
|
|
() => RenderPackCatalog.Build(
|
|
_registry.Snapshot(),
|
|
capabilities ?? RenderPackHostCapabilities.Conformance),
|
|
Factory,
|
|
preparationScheduler: preparationScheduler
|
|
?? InlineRenderPackPreparationScheduler.Instance);
|
|
}
|
|
|
|
internal List<string> Events { get; } = [];
|
|
internal FakeFactory Factory { get; }
|
|
internal RenderPackController Controller { get; }
|
|
internal FakeRuntime Active => Assert.IsType<FakeRuntime>(Controller.ActiveRuntime);
|
|
|
|
internal void Activate(string preset)
|
|
{
|
|
Controller.Request(new RenderPackSelectionSettings("auto.test", "1.0.0", preset));
|
|
RenderPackActivationSnapshot snapshot = Controller.ApplyAtFrameBoundary(
|
|
new RenderPackActivationExtent(1280, 720, 4));
|
|
Assert.True(
|
|
snapshot.State == RenderPackActivationState.Active,
|
|
snapshot.Reason);
|
|
}
|
|
|
|
internal void ObserveOverBudget(int count, bool stable = true)
|
|
{
|
|
Active.ResolvedGpuMilliseconds = 20;
|
|
Active.RetainedGpuBytes = 512L * 1024 * 1024;
|
|
for (int i = 0; i < count; i++)
|
|
Observe(20, stable);
|
|
}
|
|
|
|
internal void Observe(
|
|
double cpuMilliseconds,
|
|
bool stable,
|
|
double receiverCpuMilliseconds = 0d)
|
|
{
|
|
var observation = new RenderPackFramePerformanceObservation(
|
|
cpuMilliseconds,
|
|
stable,
|
|
1280,
|
|
720,
|
|
4,
|
|
receiverCpuMilliseconds);
|
|
Controller.ObserveActiveFrame(in observation);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Controller.Dispose();
|
|
_registration.Dispose();
|
|
_registry.Dispose();
|
|
}
|
|
}
|
|
|
|
private sealed class ControlledPreparationScheduler : IRenderPackPreparationScheduler
|
|
{
|
|
private readonly Queue<(Action Work, TaskCompletionSource Completion)> _pending = [];
|
|
|
|
public Task Schedule(Action preparation)
|
|
{
|
|
var completion = new TaskCompletionSource(
|
|
TaskCreationOptions.RunContinuationsAsynchronously);
|
|
_pending.Enqueue((preparation, completion));
|
|
return completion.Task;
|
|
}
|
|
|
|
internal void CompleteNext()
|
|
{
|
|
(Action work, TaskCompletionSource completion) = _pending.Dequeue();
|
|
try
|
|
{
|
|
work();
|
|
completion.SetResult();
|
|
}
|
|
catch (Exception error)
|
|
{
|
|
completion.SetException(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
private sealed class FakeFactory(List<string> events) : IRenderPackRuntimeFactory
|
|
{
|
|
internal int BuildCount { get; private set; }
|
|
internal string? FailPreparePreset { get; set; }
|
|
internal List<FakeRuntime> Runtimes { get; } = [];
|
|
|
|
public IRenderPackRuntime Build(
|
|
RenderPackDescriptor descriptor,
|
|
ValidatedRenderPackShaderAssets assets,
|
|
RenderQualityPreset preset,
|
|
IReadOnlyDictionary<string, string> userSettingOverrides)
|
|
{
|
|
BuildCount++;
|
|
events.Add("build:" + preset.Id);
|
|
var runtime = new FakeRuntime(
|
|
descriptor,
|
|
preset,
|
|
events,
|
|
string.Equals(FailPreparePreset, preset.Id, StringComparison.Ordinal));
|
|
Runtimes.Add(runtime);
|
|
return runtime;
|
|
}
|
|
}
|
|
|
|
private sealed class FakeRuntime(
|
|
RenderPackDescriptor descriptor,
|
|
RenderQualityPreset preset,
|
|
List<string> events,
|
|
bool failPrepare) :
|
|
IAtmosphericWorldGraphRuntime,
|
|
IRenderPackRuntimePerformanceSource,
|
|
IRenderPackRuntimeDiagnosticsSource
|
|
{
|
|
public RenderPackDescriptor Descriptor { get; } = descriptor;
|
|
public RenderQualityPreset Preset { get; } = preset;
|
|
internal long ResourceGeneration { get; set; }
|
|
internal double ResolvedGpuMilliseconds { get; set; } = 1;
|
|
internal long RetainedGpuBytes { get; set; } = 32L * 1024 * 1024;
|
|
internal bool Prepared { get; private set; }
|
|
internal bool Disposed { get; private set; }
|
|
|
|
public IGpuRenderTarget PrepareWorldTarget(int width, int height, int sampleCount)
|
|
{
|
|
events.Add($"prepare:{Preset.Id}:{width}x{height}x{sampleCount}");
|
|
if (failPrepare)
|
|
throw new InvalidOperationException($"injected {Preset.Id} resource failure");
|
|
Prepared = true;
|
|
ResourceGeneration++;
|
|
return null!;
|
|
}
|
|
|
|
public void RenderPostProcess(IGpuFrame frame, in AtmosphericFrameInputs inputs)
|
|
{
|
|
}
|
|
|
|
public RenderPackRuntimePerformanceMetrics CapturePerformanceMetrics() => new(
|
|
ResourceGeneration,
|
|
HasResolvedGpuMeasurement: true,
|
|
ResolvedGpuMilliseconds,
|
|
RetainedGpuBytes,
|
|
TransientGpuBytes: 4L * 1024 * 1024);
|
|
|
|
public RenderPackRuntimeDiagnostics CaptureDiagnostics() =>
|
|
RenderPackRuntimeDiagnostics.Empty(Preset.Id);
|
|
|
|
public void Dispose()
|
|
{
|
|
if (Disposed)
|
|
return;
|
|
Disposed = true;
|
|
events.Add("dispose:" + Preset.Id);
|
|
}
|
|
}
|
|
|
|
private sealed class EmptyAssets : IRenderPackAssets
|
|
{
|
|
public Stream OpenRead(string assetKey) => Stream.Null;
|
|
}
|
|
|
|
private static RenderPackDescriptor Descriptor() => new(
|
|
"auto.test",
|
|
"Auto Test",
|
|
new Version(1, 0, 0),
|
|
RenderPackApi.Current,
|
|
RenderPackTier.Tier1,
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[
|
|
Preset("low"),
|
|
Preset("medium"),
|
|
Preset("high"),
|
|
Preset("auto") with { AutoEligible = false },
|
|
],
|
|
[],
|
|
null)
|
|
{
|
|
FeatureSummary = "Automatic-quality test render pack.",
|
|
};
|
|
|
|
private static RenderPackDescriptor DescriptorWithAutomaticSetting()
|
|
{
|
|
RenderPackDescriptor descriptor = Descriptor();
|
|
return descriptor with
|
|
{
|
|
QualityPresets = descriptor.QualityPresets.Select(preset =>
|
|
preset.Semantic == RenderQualitySemantic.Automatic
|
|
? preset with
|
|
{
|
|
SettingOverrides =
|
|
[
|
|
new RenderQualitySettingOverride("automatic-quality", "true"),
|
|
],
|
|
}
|
|
: preset).ToArray(),
|
|
Settings =
|
|
[
|
|
new RenderSettingDeclaration(
|
|
"automatic-quality",
|
|
"Automatic quality",
|
|
RenderSettingKind.Boolean,
|
|
"false",
|
|
null,
|
|
null,
|
|
null,
|
|
[])
|
|
{
|
|
Semantic = RenderSettingSemantic.AutomaticQuality,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
private static RenderQualityPreset Preset(string id) => new RenderQualityPreset(
|
|
id,
|
|
char.ToUpperInvariant(id[0]) + id[1..],
|
|
[],
|
|
[],
|
|
[],
|
|
(id switch
|
|
{
|
|
"low" => 64L,
|
|
"high" => 256L,
|
|
_ => 128L,
|
|
}) * 1024 * 1024,
|
|
10,
|
|
12,
|
|
2,
|
|
3)
|
|
{
|
|
Semantic = id switch
|
|
{
|
|
"low" => RenderQualitySemantic.Low,
|
|
"medium" => RenderQualitySemantic.Medium,
|
|
"high" => RenderQualitySemantic.High,
|
|
"auto" => RenderQualitySemantic.Automatic,
|
|
_ => RenderQualitySemantic.Custom,
|
|
},
|
|
};
|
|
}
|