sharpemu/tests/SharpEmu.ShaderCompiler.Tests/Gen5DualSourceBlendTests.cs

87 lines
4.0 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using SharpEmu.ShaderCompiler;
using SharpEmu.ShaderCompiler.Resources;
using SharpEmu.ShaderCompiler.Tests.Resources;
using SharpEmu.ShaderCompiler.Vulkan;
using Xunit;
namespace SharpEmu.ShaderCompiler.Tests;
// Dual-source blending reads a second color from the MRT1 export. It is declared at target
// 0's location with index 1 and is not a render target of its own.
public sealed class Gen5DualSourceBlendTests
{
private static Gen5ShaderInstruction Export(uint pc, uint target, bool done) =>
new(pc, Gen5ShaderEncoding.Exp, "Exp", [0u, 0u],
[Gen5Operand.Vector(0), Gen5Operand.Vector(1), Gen5Operand.Vector(2), Gen5Operand.Vector(3)], [],
new Gen5ExportControl(target, 15, false, done, false));
private static ShaderCompileRequest Request(params Gen5PixelOutputBinding[] outputs)
{
var program = ResourceTestProgram.Program(Export(0, 0, false), Export(8, 1, true), ResourceTestProgram.EndProgram(16));
var (plan, resources, layout) = ResourceTestProgram.Prepare(program, ShaderStage.Pixel, userDataCount: 0);
return new ShaderCompileRequest(plan, resources, layout) { PixelOutputs = outputs };
}
private static IEnumerable<(uint Op, uint[] Args)> Instructions(byte[] bytes)
{
for (var offset = 20; offset < bytes.Length;)
{
var header = BinaryPrimitives.ReadUInt32LittleEndian(bytes.AsSpan(offset));
var count = (int)(header >> 16);
var args = new uint[count - 1];
for (var index = 0; index < args.Length; index++)
args[index] = BinaryPrimitives.ReadUInt32LittleEndian(bytes.AsSpan(offset + 4 * (index + 1)));
yield return (header & 0xFFFF, args);
offset += count * 4;
}
}
[Fact]
public void SecondSource_IsLocationZeroIndexOne()
{
var request = Request(
new Gen5PixelOutputBinding(0, 0, Gen5PixelOutputKind.Float),
new Gen5PixelOutputBinding(1, 0, Gen5PixelOutputKind.Float) { ExportTarget = 1, Index = 1 });
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out var shader, out var error), error);
const uint Decorate = 71, Location = 30, Index = 32;
var decorations = Instructions(shader.Spirv).Where(i => i.Op == Decorate).ToArray();
var second = Assert.Single(decorations, i => i.Args[1] == Index && i.Args[2] == 1).Args[0];
Assert.Contains(decorations, i => i.Args[0] == second && i.Args[1] == Location && i.Args[2] == 0);
Assert.Equal(2, decorations.Count(i => i.Args[1] == Location && i.Args[2] == 0));
}
[Fact]
public void DualSourceAlpha_ExportsAlphaAtLocationZeroIndexOne()
{
var request = Request(
new Gen5PixelOutputBinding(0, 0, Gen5PixelOutputKind.Float, new Gen5ColorComponentMapping(0x1B))
{
DualSourceAlpha = true,
});
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out var shader, out var error), error);
const uint Decorate = 71, Location = 30, Index = 32;
var decorations = Instructions(shader.Spirv).Where(i => i.Op == Decorate).ToArray();
var alpha = Assert.Single(decorations, i => i.Args[1] == Index && i.Args[2] == 1).Args[0];
Assert.Contains(decorations, i => i.Args[0] == alpha && i.Args[1] == Location && i.Args[2] == 0);
Assert.Equal(2, decorations.Count(i => i.Args[1] == Location && i.Args[2] == 0));
}
[Fact]
public void SecondSource_NeedsASingleTarget()
{
var request = Request(
new Gen5PixelOutputBinding(0, 0, Gen5PixelOutputKind.Float),
new Gen5PixelOutputBinding(2, 1, Gen5PixelOutputKind.Float),
new Gen5PixelOutputBinding(1, 0, Gen5PixelOutputKind.Float) { ExportTarget = 1, Index = 1 });
Assert.False(Gen5SpirvTranslator.TryCompileProgram(request, out _, out var error));
Assert.Contains("second source", error, StringComparison.Ordinal);
}
}