116 lines
5.2 KiB
C#
116 lines
5.2 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using SharpEmu.HLE.Host;
|
|
using SharpEmu.Libs.Gpu.Pipelines;
|
|
using SharpEmu.Libs.Gpu.Rendering;
|
|
using SharpEmu.Libs.Gpu.Vulkan;
|
|
using SharpEmu.Libs.Tests.Gpu.Images;
|
|
using SharpEmu.ShaderCompiler;
|
|
using SharpEmu.ShaderCompiler.Resources;
|
|
using SharpEmu.ShaderCompiler.Vulkan;
|
|
using Xunit;
|
|
using static SharpEmu.Libs.Tests.Gpu.Images.ImageCacheTestSupport;
|
|
using static SharpEmu.ShaderCompiler.Tests.Resources.ResourceTestProgram;
|
|
|
|
namespace SharpEmu.Libs.Tests.VideoOut;
|
|
|
|
public sealed unsafe partial class RenderHostDeviceTests
|
|
{
|
|
[Theory]
|
|
[InlineData(0u, true, 191)]
|
|
[InlineData(1u, true, 64)]
|
|
[InlineData(2u, true, 64)]
|
|
[InlineData(0u, false, 128)]
|
|
[InlineData(1u, false, 0)]
|
|
[InlineData(2u, false, 64)]
|
|
public void InterpolationParameter_DrawReadsSelectedVertex(uint selector, bool custom, int expected)
|
|
{
|
|
VerifyInterpolationDraw(selector, custom, interpolate: false, expected);
|
|
}
|
|
|
|
[Fact]
|
|
public void InterpolationPhases_DrawUsesBarycentricRegisters()
|
|
{
|
|
VerifyInterpolationDraw(2, false, interpolate: true, expected: 96);
|
|
}
|
|
|
|
[Fact]
|
|
public void InterpolationShader_RejectsMissingDeviceFeatureBeforeModuleCreation()
|
|
{
|
|
if (!Ready()) return;
|
|
using var presenter = new PresenterUnderTest(_vulkan);
|
|
presenter.SetField("_supportsFragmentShaderBarycentric", false);
|
|
var shader = new VulkanCompiledGuestShader(CompileInterpolationFragment(2, true, false));
|
|
presenter.Run(() =>
|
|
{
|
|
var error = Assert.Throws<NotSupportedException>(() =>
|
|
((IShaderPipelineHost)presenter.Instance).CreateShaderModule(shader, ShaderStage.Pixel, 1, 1));
|
|
Assert.Contains("fragmentShaderBarycentric", error.Message);
|
|
});
|
|
presenter.Harness.Shutdown();
|
|
}
|
|
|
|
private void VerifyInterpolationDraw(uint selector, bool custom, bool interpolate, int expected)
|
|
{
|
|
if (!Ready()) return;
|
|
if (!_vulkan.SupportsFragmentShaderBarycentric)
|
|
{
|
|
Assert.False(GatePrerequisites.DeviceRequired, "The required device lacks fragmentShaderBarycentric.");
|
|
Console.Error.WriteLine("[TEST][SKIP] The device lacks fragmentShaderBarycentric.");
|
|
return;
|
|
}
|
|
|
|
using var presenter = new PresenterUnderTest(_vulkan);
|
|
presenter.LoadRenderingCommands();
|
|
var harness = presenter.Harness;
|
|
var target = harness.MapBacked(0x10000, ReadWrite);
|
|
var vertices = harness.MapBacked(0x10000, ReadWrite);
|
|
var words = RegisterWords.Color(target, Size, Size);
|
|
harness.Write(vertices, Triangle(-1f, -1f, 3f, -1f, -1f, 3f));
|
|
var provider = new FixedProgramProvider((IShaderPipelineHost)presenter.Instance, vertices,
|
|
interpolationShader: CompileInterpolationFragment(selector, custom, interpolate));
|
|
var executor = new RenderExecutor(presenter.RenderHost, provider);
|
|
presenter.Run(() => executor.DrawAuto(1, Banks(words), Draw()));
|
|
presenter.Run(() => presenter.InvokeMethod("FlushBatchedGuestCommands"));
|
|
harness.Finish();
|
|
var pixels = harness.ReadImageBytes(TargetImage(presenter, words));
|
|
var actual = Pixel(pixels, Size / 2, Size / 2);
|
|
Assert.InRange((int)(actual & 255), Math.Max(0, expected - 1), expected + 1);
|
|
Assert.Equal(255u, actual >> 24);
|
|
harness.Shutdown();
|
|
_vulkan.AssertNoValidationMessages();
|
|
}
|
|
|
|
private static byte[] CompileInterpolationFragment(uint selector, bool custom, bool interpolate)
|
|
{
|
|
var instructions = new List<Gen5ShaderInstruction>();
|
|
void Parameter(string opcode, uint source) => instructions.Add(new Gen5ShaderInstruction(
|
|
(uint)instructions.Count * 4, Gen5ShaderEncoding.Vintrp, opcode, [source],
|
|
[Gen5Operand.Vector(source)], [Gen5Operand.Vector(4)], new Gen5InterpolationControl(0, 0)));
|
|
Parameter("VInterpMovF32", selector);
|
|
if (interpolate)
|
|
{
|
|
Parameter("VInterpP1F32", 0);
|
|
Parameter("VInterpP2F32", 1);
|
|
}
|
|
instructions.Add(MoveVector((uint)instructions.Count * 4, 5, 0x3F800000));
|
|
instructions.Add(new Gen5ShaderInstruction((uint)instructions.Count * 4, Gen5ShaderEncoding.Exp,
|
|
"Exp", [], [Gen5Operand.Vector(4), Gen5Operand.Vector(4), Gen5Operand.Vector(4), Gen5Operand.Vector(5)],
|
|
[], new Gen5ExportControl(0, 15, false, true, true)));
|
|
instructions.Add(EndProgram((uint)instructions.Count * 4));
|
|
var (plan, resources, layout) = Prepare(Program([.. instructions]), ShaderStage.Pixel, userDataCount: 0);
|
|
var request = new ShaderCompileRequest(plan, resources, layout)
|
|
{
|
|
PixelInputAddress = 2,
|
|
PixelInputEnable = 2,
|
|
PixelInputCntl = [custom ? 0x400u : 0u],
|
|
PixelCustomInterpolationMask = custom ? 1u : 0u,
|
|
PixelOutputs = [new Gen5PixelOutputBinding(0, 0, Gen5PixelOutputKind.Float)],
|
|
EnableGraphicsSubgroupOperations = false,
|
|
};
|
|
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out var shader, out var error), error);
|
|
return shader.Spirv;
|
|
}
|
|
}
|