sharpemu/tests/SharpEmu.ShaderCompiler.Tests/Resources/BufferCandidateTableSpirvTe...

92 lines
3.8 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Collections.Generic;
using SharpEmu.ShaderCompiler;
using SharpEmu.ShaderCompiler.Resources;
using SharpEmu.ShaderCompiler.Vulkan;
using Xunit;
using static SharpEmu.ShaderCompiler.Tests.Resources.ResourceTestProgram;
namespace SharpEmu.ShaderCompiler.Tests.Resources;
// Lowering of a bounded runtime V# table: the emitted module must bind one native candidate
// per descriptor and dispatch the normal buffer operation through a flattened key mapping.
public sealed class BufferCandidateTableSpirvTests
{
private static Gen5ShaderProgram CandidateProgram(uint guardRegister = 10)
{
var instructions = new List<Gen5ShaderInstruction>();
uint pc = 0;
void Add(Gen5ShaderInstruction instruction)
{
instructions.Add(instruction);
pc += (uint)instruction.Words.Count * sizeof(uint);
}
Add(MoveScalar(pc, 0, 0x3000));
Add(MoveScalar(pc, 1, 16u << 16));
Add(MoveScalar(pc, 2, 4));
Add(MoveScalar(pc, 3, 0));
Add(MoveScalar(pc, 10, 0));
Add(Branch(pc, "SBranch", 0));
var headerPc = pc;
Add(Sop2(pc, "SMulI32", 12, Gen5Operand.Scalar(10), Operand(16)));
Add(ScalarBufferLoad(pc, 0, destination: 16, count: 4, dynamicOffsetRegister: 12));
Add(BufferLoad(pc, 16, formatted: true));
Add(Sop2(pc, "SAddU32", 10, Gen5Operand.Scalar(10), Operand(1)));
Add(Sopc(pc, "SCmpLtU32", Gen5Operand.Scalar(guardRegister), Operand(4)));
var branchPc = pc;
Add(Branch(branchPc, "SCbranchScc1", (short)(((int)headerPc - (int)(branchPc + 4)) / 4)));
Add(EndProgram(pc));
return Program([.. instructions]);
}
private static TestWordMemory CandidateMemory()
{
var memory = new TestWordMemory { Base = 0x3000, Words = new uint[0x2000 / 4], RequireAlignment = true };
for (var index = 0; index < 4; index++)
{
var address = 0x3000 + (ulong)index * 16;
memory.At(address + 0) = 0x4000u + (uint)index * 0x100;
memory.At(address + 4) = 16u << 16;
memory.At(address + 8) = 4;
memory.At(address + 12) = 77u << 12;
}
return memory;
}
private static ShaderCompileRequest CompileRequest(ShaderResourcePlan plan, SpecializedResourceInfo resources)
{
var layout = BindingLayout.Allocate(
resources.Info,
BindingLayout.CollectUserDataRegisters(plan.Graph.Program, plan.UserDataBase, plan.UserDataCount),
BindingLayout.UsesGlobalDataShare(plan.Graph.Program),
ShaderCompileRequest.RequiresFlattenedTable(plan, resources),
BindingLayout.ReadsShaderBase(plan.Graph.Program));
return new ShaderCompileRequest(plan, resources, layout);
}
[Fact]
public void BoundedCandidateTable_CompilesToValidSpirv()
{
var plan = ShaderResourcePlan.Extract(CandidateProgram(), ShaderStage.Compute, Hash, 0, 64);
Assert.Single(plan.BufferCandidateTables);
var memory = CandidateMemory();
var snapshot = new ResourceSnapshot();
var specialization = new ResourceSpecialization();
Assert.True(ResourceMaterializer.Materialize(plan, Inputs([], readCleanMemory: memory.Read), ref snapshot, ref specialization));
var resources = ResourceMaterializer.ApplyTo(plan, specialization);
var request = CompileRequest(plan, resources);
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out var shader, out var error), error);
Assert.NotEmpty(shader.Spirv);
var module = new SpirvModuleInspector(shader.Spirv);
Assert.Contains((0u, BindingLayout.NativeBindingIndex(ShaderStage.Compute, DescriptorBindingKind.FlattenedResourceTable)), module.DescriptorBindings);
}
}