87 lines
4.0 KiB
C#
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);
|
|
}
|
|
}
|