sharpemu/tests/SharpEmu.Libs.Tests/VideoOut/PresenterDispatchThreadLimi...

98 lines
4.7 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Gpu;
using SharpEmu.Libs.Gpu.Pipelines;
using SharpEmu.Libs.Gpu.Rendering;
using SharpEmu.Libs.Gpu.Vulkan;
using SharpEmu.Libs.Tests.Gpu.Images;
using SharpEmu.Libs.Tests.Gpu.Scheduling;
using SharpEmu.Libs.Tests.Gpu.Vulkan;
using SharpEmu.ShaderCompiler;
using SharpEmu.ShaderCompiler.Resources;
using SharpEmu.ShaderCompiler.Vulkan;
using Silk.NET.Vulkan;
using Xunit;
using static SharpEmu.ShaderCompiler.Tests.Resources.ResourceTestProgram;
namespace SharpEmu.Libs.Tests.VideoOut;
[Collection(SchedulingStateCollection.Name)]
public sealed class PresenterDispatchThreadLimitTests(HeadlessVulkanFixture fixture)
: IClassFixture<HeadlessVulkanFixture>
{
[Theory]
[InlineData(0u)]
[InlineData(32u)]
public void BufferBindingPreservesThreadLimitsForQueuedDispatches(uint pushDataStart)
{
var vulkan = fixture.Vulkan;
if (!GatePrerequisites.Ready(vulkan, shaderInt64: true)) return;
using var presenter = new PresenterUnderTest(vulkan!);
presenter.SetField("_minStorageBufferOffsetAlignment", 256UL);
presenter.LoadRenderingCommands();
var harness = presenter.Harness;
var allocation = harness.MapBacked(0x10000, GuestPageProtection.Read | GuestPageProtection.Write);
var address = allocation + sizeof(uint);
harness.Write(allocation, BitConverter.GetBytes(0xABCD1234u));
harness.Write(address, new byte[sizeof(uint)]);
var decoded = Program(MoveVector(0, 4, 1), BufferAtomicAdd(8, 0), EndProgram(16));
var plan = Extract(decoded, userDataCount: 4);
uint[] userData = [(uint)address, (uint)(address >> 32), sizeof(uint), 0];
var snapshot = new ResourceSnapshot();
var specialization = new ResourceSpecialization();
Assert.True(ResourceMaterializer.Materialize(plan, Inputs(userData), ref snapshot, ref specialization));
var resources = ResourceMaterializer.ApplyTo(plan, specialization);
var layout = BindingLayout.Allocate(resources.Info, [0, 1, 2, 3], false, false, false,
pushDataStart, usesDispatchThreadLimits: true);
Assert.Equal(pushDataStart == 0, layout.UsesPushData);
var request = new ShaderCompileRequest(plan, resources, layout)
{
LocalSizeX = 64, LocalSizeY = 2, LocalSizeZ = 2,
};
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out var shader, out var error), error);
var stageInfo = new ShaderProgramInfo
{
Stage = ShaderStageKind.Compute,
Hash = Hash,
UserDataCount = 4,
Resources = resources,
Bindings = layout,
};
DispatchThreadLimits[] limits = [new(0, 3, 3), new(1, 1, 1), new(63, 2, 2), new(64, 2, 2), new(65, 3, 3)];
var buffer = presenter.Run(() =>
{
var pipelineHost = (IShaderPipelineHost)presenter.Instance;
var program = new ShaderProgram(1, pipelineHost.CreateShaderModule(
new VulkanCompiledGuestShader(shader.Spirv), ShaderStage.Compute, Hash, 1));
var pipeline = pipelineHost.CreateComputePipeline(new ComputePipelineDescription
{
Program = program, Stage = stageInfo,
Input = new ComputeInputInfo { ThreadsX = 64, ThreadsY = 2, ThreadsZ = 2 },
});
foreach (var limit in limits)
{
var stage = new ShaderStageResources(stageInfo, snapshot) { ThreadLimits = limit };
using var preparation = presenter.RenderHost.BeginPreparation();
var prepared = presenter.RenderHost.PrepareBindings(stage);
presenter.RenderHost.BindResources(prepared);
presenter.RenderHost.CommitBindings(PipelineBindPoint.Compute, in pipeline, [prepared]);
presenter.RenderHost.ShaderWriteHazardBarrier();
presenter.RenderHost.BindPipeline(PipelineBindPoint.Compute, in pipeline);
presenter.RenderHost.Dispatch(2, 2, 2);
presenter.RenderHost.ShaderAccessBarrier();
}
presenter.RenderHost.ResetBindings();
return harness.Cache.GetBuffer(harness.Cache.FindBuffer(address, sizeof(uint)));
});
var result = harness.ReadBack(buffer, buffer.Offset(allocation), 2 * sizeof(uint));
Assert.Equal(0xABCD1234u, BitConverter.ToUInt32(result, 0));
Assert.Equal(limits.Sum(limit => (long)limit.X * limit.Y * limit.Z),
(long)BitConverter.ToUInt32(result, sizeof(uint)));
harness.Shutdown();
vulkan!.AssertNoValidationMessages();
}
}