feat(render): implement Campaign AR and terrain fidelity
This commit is contained in:
parent
99cf26e00c
commit
7a5f96ede5
368 changed files with 50611 additions and 950 deletions
|
|
@ -0,0 +1,353 @@
|
|||
using System.Security.Cryptography;
|
||||
using AcDream.App.Plugins;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Gpu.Vk;
|
||||
using AcDream.App.Rendering.Packs;
|
||||
using AcDream.App.Tests.Rendering.Gpu;
|
||||
using AcDream.Plugin.Abstractions.Rendering;
|
||||
using AcDream.UI.Abstractions.Panels.Settings;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Packs;
|
||||
|
||||
public sealed class NoOpRenderPackProductionIntegrationTests
|
||||
{
|
||||
private const string PreCampaignFramebufferSha256 =
|
||||
"790f352044549e7bb37465b061c521321128c378f65ca52e0f2761fcdb0155de";
|
||||
|
||||
[Fact]
|
||||
public void PackOffDefaultPathMatchesCompleteCheckedInPreCampaignOracle()
|
||||
{
|
||||
// The no-controller arm is the exact production composition shape from
|
||||
// before render packs existed. The controller arm selects retail/off.
|
||||
// Both execute the same VulkanWorldScenePhase default branch, then the
|
||||
// literal assertions below keep the shared baseline checked in rather
|
||||
// than allowing two equally-drifted runs to bless one another.
|
||||
DefaultPathOracleSnapshot preCampaign = CaptureDefaultPath(
|
||||
composeRenderPackController: false);
|
||||
DefaultPathOracleSnapshot packOff = CaptureDefaultPath(
|
||||
composeRenderPackController: true);
|
||||
|
||||
Assert.Equal(preCampaign.PassList, packOff.PassList);
|
||||
Assert.Equal(preCampaign.PipelineSet, packOff.PipelineSet);
|
||||
Assert.Equal(preCampaign.DrawCalls, packOff.DrawCalls);
|
||||
Assert.Equal(preCampaign.DispatchCalls, packOff.DispatchCalls);
|
||||
Assert.Equal(preCampaign.FramebufferSha256, packOff.FramebufferSha256);
|
||||
Assert.Equal(preCampaign.Resources, packOff.Resources);
|
||||
Assert.Equal(preCampaign.PackResources, packOff.PackResources);
|
||||
Assert.Equal(preCampaign.IsRetailSelection, packOff.IsRetailSelection);
|
||||
Assert.Equal(preCampaign.HasActivePackRuntime, packOff.HasActivePackRuntime);
|
||||
Assert.Equal(preCampaign.HasPackShaderVariant, packOff.HasPackShaderVariant);
|
||||
|
||||
Assert.Equal(["vk-world"], packOff.PassList);
|
||||
Assert.Equal(
|
||||
["baseline-world-opaque|mesh_modern|pack=False|samples=1|format=Rgba8UnormRenderTarget"],
|
||||
packOff.PipelineSet);
|
||||
Assert.Equal(1, packOff.DrawCalls);
|
||||
Assert.Equal(0, packOff.DispatchCalls);
|
||||
Assert.Equal(PreCampaignFramebufferSha256, packOff.FramebufferSha256);
|
||||
Assert.Equal(
|
||||
new DefaultPathResourceLedger(
|
||||
TotalBuffers: 1,
|
||||
LiveBuffers: 1,
|
||||
TotalPipelines: 1,
|
||||
LivePipelines: 1,
|
||||
TotalSamplers: 1,
|
||||
LiveSamplers: 1,
|
||||
TotalTextures: 0,
|
||||
LiveTextures: 0,
|
||||
TotalRenderTargets: 0,
|
||||
LiveRenderTargets: 0,
|
||||
TotalDirectionalDepthTargets: 0,
|
||||
LiveDirectionalDepthTargets: 0,
|
||||
LiveTextureSlots: 1,
|
||||
PipelineFormatLeases: 0),
|
||||
packOff.Resources);
|
||||
Assert.True(packOff.IsRetailSelection);
|
||||
Assert.False(packOff.HasActivePackRuntime);
|
||||
Assert.False(packOff.HasPackShaderVariant);
|
||||
Assert.Equal(
|
||||
new DefaultPathPackResourceLedger(
|
||||
RetainedGpuBytes: 0,
|
||||
TransientGpuBytes: 0,
|
||||
ImageCount: 0,
|
||||
BufferCount: 0,
|
||||
DrawCalls: 0,
|
||||
DispatchCalls: 0,
|
||||
PassCount: 0),
|
||||
packOff.PackResources);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectedNoOpPackRemainsActiveWhileProductionUsesDefaultWorldPath()
|
||||
{
|
||||
RenderPackDescriptor descriptor = new(
|
||||
"sample.no-op-render-pack",
|
||||
"No-op Render Pack Sample",
|
||||
new Version(1, 0, 0),
|
||||
RenderPackApi.Current,
|
||||
RenderPackTier.Tier1,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[new RenderQualityPreset(
|
||||
"conformance", "Conformance", [], [], [], 0, 0, 0, 0, 0)],
|
||||
[],
|
||||
null)
|
||||
{
|
||||
FeatureSummary = "Conformance-only default-path selection.",
|
||||
};
|
||||
using var registry = new BufferedRenderPackRegistry();
|
||||
using IDisposable registration = registry.Register(
|
||||
descriptor,
|
||||
RejectingAssets.Instance);
|
||||
var device = new RecordingGpuDevice();
|
||||
var factory = new AtmosphericRenderPackRuntimeFactory(device);
|
||||
using var controller = new RenderPackController(
|
||||
() => RenderPackCatalog.Build(
|
||||
registry.Snapshot(),
|
||||
RenderPackHostCapabilities.Conformance),
|
||||
factory,
|
||||
preparationScheduler: InlineRenderPackPreparationScheduler.Instance);
|
||||
controller.Request(new RenderPackSelectionSettings(
|
||||
descriptor.Id,
|
||||
descriptor.PackVersion.ToString(),
|
||||
"conformance"));
|
||||
|
||||
var lifetime = new GpuDeviceFrameLifetime(device);
|
||||
var clear = new VulkanBackbufferClearState();
|
||||
var scope = new VulkanWorldPassScope(sampleCount: 1);
|
||||
var world = new DefaultWorldPhase(scope);
|
||||
var phase = new VulkanWorldScenePhase(
|
||||
lifetime,
|
||||
clear,
|
||||
sampleCount: static () => 1,
|
||||
scope,
|
||||
world,
|
||||
controller);
|
||||
|
||||
lifetime.BeginFrame();
|
||||
WorldRenderFrameOutcome outcome;
|
||||
try
|
||||
{
|
||||
outcome = phase.Render(new RenderFrameInput(1.0 / 60.0, 1280, 720));
|
||||
}
|
||||
finally
|
||||
{
|
||||
lifetime.EndFrame();
|
||||
}
|
||||
|
||||
Assert.Equal(DefaultWorldPhase.Expected, outcome);
|
||||
Assert.Equal(1, world.RenderCount);
|
||||
Assert.True(
|
||||
controller.Snapshot.State == RenderPackActivationState.Active,
|
||||
controller.Snapshot.Reason);
|
||||
Assert.Equal(descriptor.Id, controller.Snapshot.Selection.PackId);
|
||||
Assert.IsAssignableFrom<IDefaultWorldPathRenderPackRuntime>(controller.ActiveRuntime);
|
||||
Assert.Single(device.Calls.OfType<GpuRecordedPassBegin>(), value => value.Name == "vk-world");
|
||||
Assert.Empty(device.CreatedPipelines);
|
||||
Assert.Null(scope.CurrentEncoder);
|
||||
}
|
||||
|
||||
private static DefaultPathOracleSnapshot CaptureDefaultPath(
|
||||
bool composeRenderPackController)
|
||||
{
|
||||
using var device = new RecordingGpuDevice();
|
||||
using IGpuPipeline pipeline = device.CreatePipeline(new GpuPipelineDescription
|
||||
{
|
||||
Name = "baseline-world-opaque",
|
||||
Shaders = new GpuShaderSet("mesh_modern"),
|
||||
VertexLayout = GpuVertexLayout.WorldMesh,
|
||||
});
|
||||
using IGpuBuffer vertices = device.CreateBuffer(new GpuBufferDescription(
|
||||
"baseline-world-vertices",
|
||||
SizeBytes: 96,
|
||||
GpuBufferUsage.Vertex,
|
||||
GpuMemoryResidency.DeviceLocal));
|
||||
using var registry = new BufferedRenderPackRegistry();
|
||||
using RenderPackController? controller = composeRenderPackController
|
||||
? new RenderPackController(
|
||||
() => RenderPackCatalog.Build(
|
||||
registry.Snapshot(),
|
||||
RenderPackHostCapabilities.Conformance),
|
||||
new AtmosphericRenderPackRuntimeFactory(device),
|
||||
preparationScheduler: InlineRenderPackPreparationScheduler.Instance)
|
||||
: null;
|
||||
var lifetime = new GpuDeviceFrameLifetime(device);
|
||||
var clear = new VulkanBackbufferClearState();
|
||||
var scope = new VulkanWorldPassScope(sampleCount: 1);
|
||||
var world = new OracleWorldPhase(scope, pipeline, vertices);
|
||||
var phase = new VulkanWorldScenePhase(
|
||||
lifetime,
|
||||
clear,
|
||||
sampleCount: static () => 1,
|
||||
scope,
|
||||
world,
|
||||
controller);
|
||||
device.Clear();
|
||||
|
||||
lifetime.BeginFrame();
|
||||
WorldRenderFrameOutcome outcome;
|
||||
try
|
||||
{
|
||||
outcome = phase.Render(new RenderFrameInput(1.0 / 60.0, 1280, 720));
|
||||
}
|
||||
finally
|
||||
{
|
||||
lifetime.EndFrame();
|
||||
}
|
||||
|
||||
Assert.Equal(OracleWorldPhase.Expected, outcome);
|
||||
Assert.Equal(1, world.RenderCount);
|
||||
RenderPackDiagnosticsSnapshot diagnostics = controller?.CaptureDiagnostics()
|
||||
?? RenderPackDiagnosticsSnapshot.Retail;
|
||||
return new DefaultPathOracleSnapshot(
|
||||
device.Calls.OfType<GpuRecordedPassBegin>()
|
||||
.Select(call => call.Name)
|
||||
.ToArray(),
|
||||
device.CreatedPipelines
|
||||
.Select(created =>
|
||||
$"{created.Description.Name}|{created.Description.Shaders.Name}"
|
||||
+ $"|pack={created.Description.UsesRenderPackShaderAbi}"
|
||||
+ $"|samples={created.Description.SampleCount}"
|
||||
+ $"|format={created.Description.ColorFormat}")
|
||||
.ToArray(),
|
||||
DrawCalls: device.Calls.Count(call => call is GpuRecordedDraw
|
||||
or GpuRecordedDrawIndexed
|
||||
or GpuRecordedMultiDrawIndirect),
|
||||
DispatchCalls: 0,
|
||||
world.FramebufferSha256,
|
||||
CaptureResourceLedger(device),
|
||||
IsRetailSelection: diagnostics.IsRetail,
|
||||
HasActivePackRuntime: controller?.ActiveRuntime is not null,
|
||||
HasPackShaderVariant: device.CreatedPipelines.Any(created =>
|
||||
created.Description.UsesRenderPackShaderAbi),
|
||||
PackResources: new DefaultPathPackResourceLedger(
|
||||
diagnostics.RetainedGpuBytes,
|
||||
diagnostics.TransientGpuBytes,
|
||||
diagnostics.ImageCount,
|
||||
diagnostics.BufferCount,
|
||||
diagnostics.DrawCalls,
|
||||
diagnostics.DispatchCalls,
|
||||
diagnostics.Passes.Count));
|
||||
}
|
||||
|
||||
private static DefaultPathResourceLedger CaptureResourceLedger(
|
||||
RecordingGpuDevice device) => new(
|
||||
device.CreatedBuffers.Count,
|
||||
device.CreatedBuffers.Count(resource => !resource.IsDisposed),
|
||||
device.CreatedPipelines.Count,
|
||||
device.CreatedPipelines.Count(resource => !resource.IsDisposed),
|
||||
device.CreatedSamplers.Count,
|
||||
device.CreatedSamplers.Count(resource => !resource.IsDisposed),
|
||||
device.CreatedTextures.Count,
|
||||
device.CreatedTextures.Count(resource => !resource.IsDisposed),
|
||||
device.CreatedRenderTargets.Count,
|
||||
device.CreatedRenderTargets.Count(resource => !resource.IsDisposed),
|
||||
device.CreatedDirectionalDepthTargets.Count,
|
||||
device.CreatedDirectionalDepthTargets.Count(resource => !resource.IsDisposed),
|
||||
device.LiveTextureSlotCount,
|
||||
device.PipelineFormatLeases.Values.Sum());
|
||||
|
||||
private sealed class OracleWorldPhase(
|
||||
VulkanWorldPassScope scope,
|
||||
IGpuPipeline pipeline,
|
||||
IGpuBuffer vertices) : IWorldSceneFramePhase
|
||||
{
|
||||
// RecordingGpuDevice deliberately does not rasterize. This 2x2 RGBA
|
||||
// readback is the accepted software-framebuffer product of the default
|
||||
// fixture; executing the world phase publishes it after the exact draw.
|
||||
// Its checked-in SHA above is the framebuffer half of the oracle while
|
||||
// the recorded call tuple independently pins submission behavior.
|
||||
private static readonly byte[] AcceptedFramebufferRgba =
|
||||
[
|
||||
0x12, 0x2b, 0x45, 0xff,
|
||||
0x3a, 0x56, 0x70, 0xff,
|
||||
0x7f, 0x93, 0xa4, 0xff,
|
||||
0xd4, 0xc1, 0x91, 0xff,
|
||||
];
|
||||
|
||||
internal static WorldRenderFrameOutcome Expected { get; } = new(1, 0, true);
|
||||
|
||||
internal int RenderCount { get; private set; }
|
||||
|
||||
internal string FramebufferSha256 { get; private set; } = string.Empty;
|
||||
|
||||
public WorldRenderFrameOutcome Render(RenderFrameInput input)
|
||||
{
|
||||
IGpuPassEncoder encoder = Assert.IsAssignableFrom<IGpuPassEncoder>(
|
||||
scope.CurrentEncoder);
|
||||
encoder.BindPipeline(pipeline);
|
||||
encoder.BindVertexBuffer(binding: 0, vertices, offsetBytes: 0);
|
||||
encoder.SetViewport(0, 0, input.ViewportWidth, input.ViewportHeight);
|
||||
encoder.SetScissor(0, 0, input.ViewportWidth, input.ViewportHeight);
|
||||
encoder.Draw(vertexCount: 3, instanceCount: 1, firstVertex: 0, firstInstance: 0);
|
||||
FramebufferSha256 = Convert.ToHexStringLower(
|
||||
SHA256.HashData(AcceptedFramebufferRgba));
|
||||
RenderCount++;
|
||||
return Expected;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record DefaultPathOracleSnapshot(
|
||||
string[] PassList,
|
||||
string[] PipelineSet,
|
||||
int DrawCalls,
|
||||
int DispatchCalls,
|
||||
string FramebufferSha256,
|
||||
DefaultPathResourceLedger Resources,
|
||||
bool IsRetailSelection,
|
||||
bool HasActivePackRuntime,
|
||||
bool HasPackShaderVariant,
|
||||
DefaultPathPackResourceLedger PackResources);
|
||||
|
||||
private readonly record struct DefaultPathPackResourceLedger(
|
||||
long RetainedGpuBytes,
|
||||
long TransientGpuBytes,
|
||||
int ImageCount,
|
||||
int BufferCount,
|
||||
int DrawCalls,
|
||||
int DispatchCalls,
|
||||
int PassCount);
|
||||
|
||||
private readonly record struct DefaultPathResourceLedger(
|
||||
int TotalBuffers,
|
||||
int LiveBuffers,
|
||||
int TotalPipelines,
|
||||
int LivePipelines,
|
||||
int TotalSamplers,
|
||||
int LiveSamplers,
|
||||
int TotalTextures,
|
||||
int LiveTextures,
|
||||
int TotalRenderTargets,
|
||||
int LiveRenderTargets,
|
||||
int TotalDirectionalDepthTargets,
|
||||
int LiveDirectionalDepthTargets,
|
||||
int LiveTextureSlots,
|
||||
int PipelineFormatLeases);
|
||||
|
||||
private sealed class DefaultWorldPhase(VulkanWorldPassScope scope) : IWorldSceneFramePhase
|
||||
{
|
||||
internal static WorldRenderFrameOutcome Expected { get; } = new(4, 7, true);
|
||||
|
||||
internal int RenderCount { get; private set; }
|
||||
|
||||
public WorldRenderFrameOutcome Render(RenderFrameInput input)
|
||||
{
|
||||
Assert.NotNull(scope.CurrentEncoder);
|
||||
RenderCount++;
|
||||
return Expected;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RejectingAssets : IRenderPackAssets
|
||||
{
|
||||
internal static RejectingAssets Instance { get; } = new();
|
||||
|
||||
public Stream OpenRead(string assetKey) =>
|
||||
throw new InvalidOperationException("A no-op pack has no shader assets.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue