sharpemu/tests/SharpEmu.ShaderCompiler.Met.../MslTranslationTests.cs

682 lines
28 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.ShaderCompiler;
using SharpEmu.ShaderCompiler.Resources;
using Xunit;
namespace SharpEmu.ShaderCompiler.Metal.Tests;
/// <summary>
/// Structural checks over the emitted MSL — these run on every platform because
/// translation is pure text generation; only the runtime tests need a Metal device.
/// </summary>
public sealed class MslTranslationTests
{
[Fact]
public void ComputeFixturesResolveDescriptorFormatsBeforeCompilation()
{
foreach (var fixture in new[] { Gen5ComputeFixtures.TypedLoad, Gen5ComputeFixtures.TypedStore })
{
var request = Gen5ComputeFixtures.CreateComputeRequest(fixture);
Assert.NotEmpty(request.Resources.Info.Buffers);
Assert.All(request.Resources.Info.Buffers, buffer =>
{
Assert.Equal(56u, buffer.DescriptorFormat);
Assert.Equal(DescriptorConstants.IdentityDestinationSelect, buffer.DescriptorSwizzle);
});
_ = Gen5ComputeFixtures.CompileRequestOrThrow(fixture);
}
}
[Fact]
public void SadU32UsesUnsignedAbsoluteDifferenceAndAccumulator()
{
var sad = new Gen5ShaderInstruction(
0,
Gen5ShaderEncoding.Vop3,
"VSadU32",
[0, 0],
[Gen5Operand.Vector(0), Gen5Operand.Vector(1), Gen5Operand.Vector(2)],
[Gen5Operand.Vector(3)],
null);
var end = new Gen5ShaderInstruction(
8,
Gen5ShaderEncoding.Sopp,
"SEndpgm",
[0xBF810000],
[],
[],
null);
var request = Gen5ComputeFixtures.RequestOrThrow(
new Gen5ShaderProgram(0, [sad, end]), ShaderStage.Compute, localSizeX: 1);
Assert.True(Gen5MslTranslator.TryCompileProgram(request, out var shader, out var error), error);
Assert.Contains("uint v[256]", shader.Source, StringComparison.Ordinal);
Assert.Contains("((max(v[0], v[1]) - min(v[0], v[1])) + (v[2]))", shader.Source, StringComparison.Ordinal);
}
[Fact]
public void F16CompareUsesHalfOperands()
{
var compare = new Gen5ShaderInstruction(
0,
Gen5ShaderEncoding.Vopc,
"VCmpLtF16",
[],
[Gen5Operand.Vector(0), Gen5Operand.Vector(1)],
[],
null);
var end = new Gen5ShaderInstruction(
4,
Gen5ShaderEncoding.Sopp,
"SEndpgm",
[0xBF810000],
[],
[],
null);
var request = Gen5ComputeFixtures.RequestOrThrow(
new Gen5ShaderProgram(0, [compare, end]), ShaderStage.Compute, localSizeX: 1);
Assert.True(Gen5MslTranslator.TryCompileProgram(request, out var shader, out var error), error);
Assert.Contains("as_type<half>", shader.Source, StringComparison.Ordinal);
Assert.Contains(" < ", shader.Source, StringComparison.Ordinal);
}
[Fact]
public void EveryFixtureTranslates()
{
foreach (var fixture in Gen5ComputeFixtures.All)
{
var shader = Gen5ComputeFixtures.CompileRequestOrThrow(fixture);
Assert.Equal(Gen5MslStage.Compute, shader.Stage);
Assert.Equal("gen5_cs", shader.EntryPoint);
Assert.Contains("kernel void gen5_cs(", shader.Source, StringComparison.Ordinal);
Assert.Contains("while (active)", shader.Source, StringComparison.Ordinal);
}
}
[Fact]
public void ExecMaskedStoresAreGuarded()
{
var shader = Gen5ComputeFixtures.CompileRequestOrThrow(Gen5ComputeFixtures.ExecStore);
// Every buffer store must sit behind the per-lane EXEC guard.
Assert.Contains("if (exec)", shader.Source, StringComparison.Ordinal);
Assert.Contains("sharpemu_store_bytes(b0,", shader.Source, StringComparison.Ordinal);
// s_mov_b32 exec_lo, 0 / -1 must drive the per-lane bool.
Assert.Contains("exec = ((", shader.Source, StringComparison.Ordinal);
}
[Fact]
public void LoopFixtureProducesMultipleDispatcherBlocks()
{
var shader = Gen5ComputeFixtures.CompileRequestOrThrow(Gen5ComputeFixtures.Loop);
// The backward branch splits the program into at least three blocks and
// the conditional branch selects between loop head and fallthrough.
Assert.Contains("case 0u:", shader.Source, StringComparison.Ordinal);
Assert.Contains("case 1u:", shader.Source, StringComparison.Ordinal);
Assert.Contains("case 2u:", shader.Source, StringComparison.Ordinal);
Assert.Contains("pc = (scc) ?", shader.Source, StringComparison.Ordinal);
}
[Fact]
public void DispatcherIsBoundedByDefault()
{
var shader = Gen5ComputeFixtures.CompileRequestOrThrow(Gen5ComputeFixtures.Fmac);
Assert.Contains("if (++steps >=", shader.Source, StringComparison.Ordinal);
}
[Fact]
public void ArgumentBufferCarriesBufferLengthsAndUsesSeparatePushData()
{
var shader = Gen5ComputeFixtures.CompileRequestOrThrow(Gen5ComputeFixtures.ExecStore);
Assert.NotNull(shader.ArgumentLayout);
Assert.Contains("buffer_bytes [[id(", shader.Source, StringComparison.Ordinal);
Assert.Contains("sharpemu_resources [[buffer(0)]]", shader.Source, StringComparison.Ordinal);
Assert.Contains("sharpemu_push_data [[buffer(1)]]", shader.Source, StringComparison.Ordinal);
Assert.DoesNotContain("SharpEmuUniforms", shader.Source, StringComparison.Ordinal);
}
[Fact]
public void PixelStageEmitsFragmentInterface()
{
var shader = Gen5ComputeFixtures.CompilePixelOrThrow();
Assert.Equal(Gen5MslStage.Pixel, shader.Stage);
Assert.Equal("gen5_ps", shader.EntryPoint);
Assert.Equal(1u, shader.AttributeCount);
Assert.Contains("fragment Gen5PsOut gen5_ps(", shader.Source, StringComparison.Ordinal);
Assert.Contains("float4 attr0 [[user(locn0)]];", shader.Source, StringComparison.Ordinal);
Assert.Contains("[[color(0)]]", shader.Source, StringComparison.Ordinal);
Assert.Contains("[[position]]", shader.Source, StringComparison.Ordinal);
// Interpolation reads land in VGPRs; the export writes MRT0 under EXEC
// and inactive lanes discard at the end.
Assert.Contains("as_type<uint>(sharpemu_in.attr0[0])", shader.Source, StringComparison.Ordinal);
Assert.Contains("sharpemu_out.mrt0 = exec ?", shader.Source, StringComparison.Ordinal);
Assert.Contains("discard_fragment();", shader.Source, StringComparison.Ordinal);
}
[Fact]
public void NullValidMaskExportControlsFragmentDiscard()
{
var export = new Gen5ShaderInstruction(
0,
Gen5ShaderEncoding.Exp,
"Exp",
[],
[
Gen5Operand.Vector(0),
Gen5Operand.Vector(1),
Gen5Operand.Vector(2),
Gen5Operand.Vector(3),
],
[],
new Gen5ExportControl(9, 0, false, true, true));
var end = new Gen5ShaderInstruction(
8,
Gen5ShaderEncoding.Sopp,
"SEndpgm",
[0xBF810000],
[],
[],
null);
var request = Gen5ComputeFixtures.RequestOrThrow(
new Gen5ShaderProgram(0, [export, end]), ShaderStage.Pixel, pixelOutputs: []);
Assert.True(Gen5MslTranslator.TryCompileProgram(request, out var shader, out var error), error);
Assert.Contains(
"bool pixel_valid_mask_active = true;",
shader.Source,
StringComparison.Ordinal);
Assert.Contains(
"pixel_valid_mask_active = exec;",
shader.Source,
StringComparison.Ordinal);
Assert.Contains(
"if (!pixel_valid_mask_active)",
shader.Source,
StringComparison.Ordinal);
}
[Fact]
public void PixelOutputKindsSelectTheAttachmentType()
{
var uintShader = Gen5ComputeFixtures.CompilePixelOrThrow(Gen5PixelOutputKind.Uint);
Assert.Contains("uint4 mrt0 [[color(0)]];", uintShader.Source, StringComparison.Ordinal);
var sintShader = Gen5ComputeFixtures.CompilePixelOrThrow(Gen5PixelOutputKind.Sint);
Assert.Contains("int4 mrt0 [[color(0)]];", sintShader.Source, StringComparison.Ordinal);
}
[Fact]
public void IdentityPixelOutputKeepsGuestComponentOrder()
{
var shader = Gen5ComputeFixtures.CompilePixelOrThrow();
Assert.Contains(
"vec<float, 4>(as_type<float>(v[0]), as_type<float>(v[1]), " +
"as_type<float>(v[2]), as_type<float>(v[3]))",
shader.Source,
StringComparison.Ordinal);
}
[Fact]
public void BgraPixelOutputMapsGuestComponentsToPhysicalOrder()
{
var shader = Gen5ComputeFixtures.CompilePixelOrThrow(
componentMapping: new Gen5ColorComponentMapping(0xC6));
Assert.Contains(
"vec<float, 4>(as_type<float>(v[2]), as_type<float>(v[1]), " +
"as_type<float>(v[0]), as_type<float>(v[3]))",
shader.Source,
StringComparison.Ordinal);
}
[Fact]
public void VertexStageEmitsVertexInterface()
{
var shader = Gen5ComputeFixtures.CompileVertexOrThrow();
Assert.Equal(Gen5MslStage.Vertex, shader.Stage);
Assert.Equal("gen5_vs", shader.EntryPoint);
Assert.Equal(1u, shader.AttributeCount);
Assert.Contains("vertex Gen5VsOut gen5_vs(", shader.Source, StringComparison.Ordinal);
Assert.Contains("float4 sharpemu_position [[position]];", shader.Source, StringComparison.Ordinal);
Assert.Contains("float4 param0 [[user(locn0)]];", shader.Source, StringComparison.Ordinal);
Assert.Contains("uint sharpemu_vertex_id [[vertex_id]],", shader.Source, StringComparison.Ordinal);
Assert.Contains("v[5] = sharpemu_vertex_id;", shader.Source, StringComparison.Ordinal);
Assert.Contains("v[8] = sharpemu_instance_id;", shader.Source, StringComparison.Ordinal);
Assert.Contains("sharpemu_out.sharpemu_position = exec ?", shader.Source, StringComparison.Ordinal);
Assert.Contains("sharpemu_out.param0 = exec ?", shader.Source, StringComparison.Ordinal);
Assert.Contains("return sharpemu_out;", shader.Source, StringComparison.Ordinal);
}
[Fact]
public void RequiredVertexOutputsAreZeroFilledDeclarations()
{
// The paired fragment shader reads locations 0..2; the program only
// exports param0, so 1 and 2 must still be declared (zero-filled).
var shader = Gen5ComputeFixtures.CompileVertexOrThrow(requiredVertexOutputCount: 3);
Assert.Equal(3u, shader.AttributeCount);
Assert.Contains("float4 param1 [[user(locn1)]];", shader.Source, StringComparison.Ordinal);
Assert.Contains("float4 param2 [[user(locn2)]];", shader.Source, StringComparison.Ordinal);
}
[Fact]
public void FixedShadersCoverThePresenterSurface()
{
var fullscreen = MslFixedShaders.CreateFullscreenVertex(2);
Assert.Contains("vertex FullscreenOut fullscreen_vs(", fullscreen, StringComparison.Ordinal);
Assert.Contains("float4 attr1 [[user(locn1)]];", fullscreen, StringComparison.Ordinal);
Assert.Contains("tex0.sample(smp0, in.attr0.xy)", MslFixedShaders.CreateCopyFragment(), StringComparison.Ordinal);
Assert.Contains("float4(1.0f, 0.0f, 1.0f, 1.0f)", MslFixedShaders.CreateSolidFragment(1f, 0f, 1f, 1f), StringComparison.Ordinal);
Assert.Contains("return in.attr3;", MslFixedShaders.CreateAttributeFragment(3), StringComparison.Ordinal);
Assert.Contains("fragment void depth_only_fs()", MslFixedShaders.CreateDepthOnlyFragment(), StringComparison.Ordinal);
}
[Theory]
[InlineData(0xFACu, "as_type<float>(v[4]), as_type<float>(v[5]), as_type<float>(v[6]), as_type<float>(v[7])")]
[InlineData(0x9F5u, "as_type<float>(v[7]), as_type<float>(v[4]), as_type<float>(v[5]), as_type<float>(v[6])")]
[InlineData(0xF2Eu, "as_type<float>(v[6]), as_type<float>(v[5]), as_type<float>(v[4]), as_type<float>(v[7])")]
[InlineData(0x3ACu, "as_type<float>(v[4]), as_type<float>(v[5]), as_type<float>(v[6]), 0.0f")]
[InlineData(0xFA4u, "as_type<float>(v[4]), 0.0f, as_type<float>(v[6]), as_type<float>(v[7])")]
public void ImageStoreAppliesInverseDescriptorSwizzle(
uint dstSelect,
string expectedComponents)
{
var shader = CompileImageStore(dstSelect, dmask: 0xF);
Assert.Contains(
$".write(vec<float, 4>({expectedComponents}),",
shader.Source,
StringComparison.Ordinal);
}
[Fact]
public void ImageStoreTreatsZeroDmaskAsX()
{
var shader = CompileImageStore(
Gen5ShaderTranslator.IdentityImageDstSelect,
dmask: 0);
Assert.Contains(
".write(vec<float, 4>(as_type<float>(v[4]), 0.0f, 0.0f, 0.0f),",
shader.Source,
StringComparison.Ordinal);
}
[Fact]
public void ImageStoreMipAppliesInverseDescriptorSwizzle()
{
var shader = CompileImageStore(
0x9F5u,
dmask: 0xF,
opcode: "ImageStoreMip");
Assert.Contains(
".write(vec<float, 4>(as_type<float>(v[7]), as_type<float>(v[4]), as_type<float>(v[5]), as_type<float>(v[6])),",
shader.Source,
StringComparison.Ordinal);
}
[Fact]
public void UintImageStoreUsesUintTextureAndInverseDescriptorSwizzle()
{
var shader = CompileImageStore(
0xF2Eu,
dmask: 0xF,
unifiedFormat: 69u); // FORMAT_16_16_16_16_UINT
Assert.Contains(
"texture2d<uint, access::write>",
shader.Source,
StringComparison.Ordinal);
Assert.Contains(
".write(vec<uint, 4>(v[6], v[5], v[4], v[7]),",
shader.Source,
StringComparison.Ordinal);
}
[Fact]
public void UnsupportedOpcodeFailsLoudlyWithPc()
{
// v_cubeid_f32 is real but outside the phase-1 ALU set: the translator
// must name the opcode and pc instead of emitting wrong code.
var fixture = new Gen5ComputeFixture(
"unsupported",
[
0xD5C40000, 0x04060501, // v_cubeid_f32 v0, v1, v2, v3
0xBF810000, // s_endpgm
],
StoreScalarResourceBase: 0,
StoreBackingBytes: 0);
var exception = Assert.Throws<InvalidOperationException>(
() => Gen5ComputeFixtures.CompileRequestOrThrow(fixture));
Assert.Contains("pc=0x", exception.Message, StringComparison.Ordinal);
}
[Fact]
public void RelativeVectorSourceUsesM0ForDynamicRead()
{
var fixture = new Gen5ComputeFixture(
"relative-vector-source",
[
0x7E6E870C, // v_movrels_b32 v55, v12
0xBF810000, // s_endpgm
],
StoreScalarResourceBase: 0,
StoreBackingBytes: 0);
var shader = Gen5ComputeFixtures.CompileRequestOrThrow(fixture);
Assert.Contains("12u + (s[124])", shader.Source, StringComparison.Ordinal);
Assert.Contains("< 256u ?", shader.Source, StringComparison.Ordinal);
}
[Fact]
public void ScalarBlockerOpcodesCompileWithRdna2Semantics()
{
var fixture = new Gen5ComputeFixture(
"scalar-blockers",
[
0xBF130200, // s_cmp_lg_u64 s[0:1], s[2:3]
0xBE861404, // s_ff1_i32_b64 s6, s[4:5]
0xBEEB106A, // s_bcnt1_i32_b64 s107, s[106:107]
0xBE890908, // s_wqm_b32 s9, s8
0xBF810000, // s_endpgm
],
StoreScalarResourceBase: 0,
StoreBackingBytes: 0);
var shader = Gen5ComputeFixtures.CompileRequestOrThrow(fixture);
Assert.Contains(" != ", shader.Source, StringComparison.Ordinal);
Assert.Contains("(uint)ctz(", shader.Source, StringComparison.Ordinal);
Assert.Contains("(uint)popcount(", shader.Source, StringComparison.Ordinal);
Assert.Contains("& 0x11111111u", shader.Source, StringComparison.Ordinal);
Assert.Contains("* 0xFu", shader.Source, StringComparison.Ordinal);
}
[Fact]
public void DataShareWaveCountersUseOneAtomicPerWave()
{
var fixture = new Gen5ComputeFixture(
"data-share-wave-counters",
[
0xD8F80014, 0x07000000, // ds_append v7 offset:20
0xD8F40014, 0x08000000, // ds_consume v8 offset:20
0xBF810000, // s_endpgm
],
StoreScalarResourceBase: 0,
StoreBackingBytes: 0);
var shader = Gen5ComputeFixtures.CompileRequestOrThrow(fixture);
Assert.Contains("popcount(", shader.Source, StringComparison.Ordinal);
Assert.Contains(">> 16u", shader.Source, StringComparison.Ordinal);
Assert.Contains("& 0xFFFFu", shader.Source, StringComparison.Ordinal);
Assert.Contains("atomic_fetch_add_explicit", shader.Source, StringComparison.Ordinal);
Assert.Contains("atomic_fetch_sub_explicit", shader.Source, StringComparison.Ordinal);
Assert.Contains("simd_broadcast", shader.Source, StringComparison.Ordinal);
}
[Theory]
[InlineData(0xBF970001u)]
[InlineData(0xBF980001u)]
[InlineData(0xBF990001u)]
[InlineData(0xBF9A0001u)]
public void DebugConditionBranchesFallThroughWithoutShaderDebugger(uint branch)
{
var fixture = new Gen5ComputeFixture(
"debug-condition-branch",
[
branch,
0xBF800000, // s_nop 0
0xBF810000, // s_endpgm
],
StoreScalarResourceBase: 0,
StoreBackingBytes: 0);
var shader = Gen5ComputeFixtures.CompileRequestOrThrow(fixture);
Assert.Contains("pc = (false) ?", shader.Source, StringComparison.Ordinal);
}
[Theory]
[InlineData(true, 22u)]
[InlineData(false, 0u)]
public void TypedBufferLoadUsesTheInstructionFormatAndUntypedKeepsTheDescriptorTable(bool typed, uint typedFormat)
{
var load = new Gen5ShaderInstruction(
0,
typed ? Gen5ShaderEncoding.Mtbuf : Gen5ShaderEncoding.Mubuf,
typed ? "TBufferLoadFormatXyzw" : "BufferLoadFormatXyzw",
[0, 0],
[Gen5Operand.Vector(0), Gen5Operand.Scalar(8), Gen5Operand.Source(128, null)],
[Gen5Operand.Vector(4)],
new Gen5BufferMemoryControl(
4,
0,
4,
8,
0,
IndexEnabled: false,
OffsetEnabled: false,
Glc: false,
Slc: false,
Typed: typed,
TypedFormat: typedFormat));
var end = new Gen5ShaderInstruction(8, Gen5ShaderEncoding.Sopp, "SEndpgm", [0xBF810000], [], [], null);
var scalars = new uint[256];
scalars[8] = 0x2000;
scalars[10] = 64;
scalars[11] = 77u << 12;
var shader = CompileMaterialized(new Gen5ShaderProgram(0, [load, end]), scalars[..12]);
if (typed)
{
Assert.DoesNotContain("sharpemu_gfx10_formats[(", shader.Source, StringComparison.Ordinal);
Assert.Contains("sharpemu_format_layout(4u, 0u,", shader.Source, StringComparison.Ordinal);
}
else
{
Assert.Contains("sharpemu_gfx10_formats[(", shader.Source, StringComparison.Ordinal);
Assert.Contains("== 7u ?", shader.Source, StringComparison.Ordinal);
Assert.Contains(": true);", shader.Source, StringComparison.Ordinal);
Assert.DoesNotContain("sharpemu_format_bytes", shader.Source, StringComparison.Ordinal);
}
}
[Fact]
public void TypedBufferStoreEncodesAndPlacesEachComponentInOneElement()
{
// Format 14 is 8_8 unorm: both registers are encoded, packed into one element
// with a two-byte mask, and stored under the descriptor and element bounds.
var shader = CompileTypedBufferAccess("TBufferStoreFormatXy", dwordCount: 2, typedFormat: 14);
Assert.Contains("if (exec && ", shader.Source, StringComparison.Ordinal);
Assert.Contains("sharpemu_format_encode(v[4], 8u, 0u, 3u)", shader.Source, StringComparison.Ordinal);
Assert.Contains("sharpemu_format_encode(v[5], 8u, 0u, 3u)", shader.Source, StringComparison.Ordinal);
Assert.Contains("<< 8u), 0u, 0u, 0u), uint4(0xFFFFu, 0x0u, 0x0u, 0x0u));", shader.Source, StringComparison.Ordinal);
Assert.Contains("2u <= sharpemu_resources.buffer_bytes[0] && ", shader.Source, StringComparison.Ordinal);
Assert.DoesNotContain("sharpemu_store_bytes(b", shader.Source, StringComparison.Ordinal);
}
[Fact]
public void TypedBufferStoreOfWholeDwordsKeepsTheRegisterBits()
{
// Format 77 is 32x4 float: four aligned dword stores, no encoding, one bounds test.
var shader = CompileTypedBufferAccess("TBufferStoreFormatXyzw", dwordCount: 4, typedFormat: 77);
Assert.Contains(", v[4], 4u);", shader.Source, StringComparison.Ordinal);
Assert.Contains("+ 12u), v[7], 4u);", shader.Source, StringComparison.Ordinal);
Assert.Contains("16u <= sharpemu_resources.buffer_bytes[0] && ", shader.Source, StringComparison.Ordinal);
Assert.DoesNotContain("sharpemu_format_encode(v[", shader.Source, StringComparison.Ordinal);
Assert.DoesNotContain("sharpemu_store_element(b", shader.Source, StringComparison.Ordinal);
}
[Theory]
[InlineData("TBufferLoadFormatX", 1u, 4u)]
[InlineData("TBufferLoadFormatXy", 2u, 8u)]
[InlineData("TBufferStoreFormatX", 1u, 4u)]
[InlineData("TBufferStoreFormatXy", 2u, 8u)]
public void TypedBufferBoundsUseOnlyTransferredComponents(string opcode, uint componentCount, uint accessBytes)
{
var shader = CompileTypedBufferAccess(opcode, componentCount, typedFormat: 77);
Assert.Contains($"{accessBytes}u <= sharpemu_resources.buffer_bytes[0] && ", shader.Source, StringComparison.Ordinal);
Assert.DoesNotContain("16u <= sharpemu_resources.buffer_bytes[0] && ", shader.Source, StringComparison.Ordinal);
}
[Fact]
public void FormattedBufferStoreUsesDescriptorFormatForPackedNormals()
{
var shader = CompileTypedBufferAccess("BufferStoreFormatXyzw", 4, 0, typed: false, descriptorFormat: 50);
// 2_10_10_10 is R10G10B10A2: ten-bit X, Y and Z, then the two-bit W.
Assert.Contains("sharpemu_format_encode(v[4], 10u, 0u, 9u)", shader.Source, StringComparison.Ordinal);
Assert.Contains("sharpemu_format_encode(v[5], 10u, 0u, 9u)", shader.Source, StringComparison.Ordinal);
Assert.Contains("sharpemu_format_encode(v[6], 10u, 0u, 9u)", shader.Source, StringComparison.Ordinal);
Assert.Contains("sharpemu_format_encode(v[7], 2u, 0u, 9u)", shader.Source, StringComparison.Ordinal);
Assert.Contains("uint4(0xFFFFFFFFu, 0x0u, 0x0u, 0x0u)", shader.Source, StringComparison.Ordinal);
Assert.DoesNotContain("sharpemu_store_bytes(b", shader.Source, StringComparison.Ordinal);
}
private static Gen5MslShader CompileTypedBufferAccess(string opcode, uint dwordCount, uint typedFormat,
bool typed = true, uint descriptorFormat = 77)
{
var data = new Gen5Operand[dwordCount];
for (var index = 0; index < data.Length; index++)
{
data[index] = Gen5Operand.Vector(4 + (uint)index);
}
var access = new Gen5ShaderInstruction(
0,
typed ? Gen5ShaderEncoding.Mtbuf : Gen5ShaderEncoding.Mubuf,
opcode,
[0, 0],
[Gen5Operand.Vector(0), Gen5Operand.Scalar(8), Gen5Operand.Source(128, null)],
data,
new Gen5BufferMemoryControl(
dwordCount,
0,
4,
8,
0,
IndexEnabled: false,
OffsetEnabled: false,
Glc: false,
Slc: false,
Typed: typed,
TypedFormat: typedFormat));
var end = new Gen5ShaderInstruction(8, Gen5ShaderEncoding.Sopp, "SEndpgm", [0xBF810000], [], [], null);
var scalars = new uint[256];
scalars[8] = 0x2000;
scalars[10] = 64;
scalars[11] = descriptorFormat << 12;
var shader = CompileMaterialized(new Gen5ShaderProgram(0, [access, end]), scalars[..12]);
return shader;
}
[Fact]
public void FormattedVertexFetchZeroFillsMissingComponents()
{
var fetch = new Gen5ShaderInstruction(
0,
Gen5ShaderEncoding.Mubuf,
"BufferLoadFormatXyz",
[],
[],
[],
new Gen5BufferMemoryControl(
3,
5,
0,
0,
0,
IndexEnabled: true,
OffsetEnabled: false,
Glc: false,
Slc: false));
var end = new Gen5ShaderInstruction(
4,
Gen5ShaderEncoding.Sopp,
"SEndpgm",
[],
[],
[],
null);
var program = new Gen5ShaderProgram(0, [fetch, end]);
var plan = ShaderResourcePlan.Extract(program, ShaderStage.Vertex, 1, 0, 0, new HashSet<uint> { 0 });
var resources = ResourceMaterializer.ApplyTo(plan, ResourceSpecialization.Default(plan.Info));
var layout = BindingLayout.Allocate(resources.Info, [], false, false, false);
var request = new ShaderCompileRequest(plan, resources, layout)
{
VertexInputs = [new ShaderVertexInput(0, 0, 2, 7, false, [])],
};
Assert.True(Gen5MslTranslator.TryCompileProgram(request, out var shader, out var error), error);
Assert.Contains("v[2] = 0u;", shader.Source, StringComparison.Ordinal);
}
private static Gen5MslShader CompileImageStore(
uint dstSelect,
uint dmask,
string opcode = "ImageStore",
uint unifiedFormat = 71u)
{
var control = new Gen5ImageControl(
Dmask: dmask,
VectorAddress: 0,
AddressRegisters: [0, 1],
VectorData: 4,
ScalarResource: 8,
ScalarSampler: 16,
Dimension: 1,
IsArray: false,
Glc: false,
Slc: false,
A16: false,
D16: false);
var store = new Gen5ShaderInstruction(
0,
Gen5ShaderEncoding.Mimg,
opcode,
[],
[],
[],
control);
var end = new Gen5ShaderInstruction(
8,
Gen5ShaderEncoding.Sopp,
"SEndpgm",
[0xBF810000],
[],
[],
null);
var userData = new uint[16];
userData[8] = 0x20;
userData[9] = unifiedFormat << 20;
userData[11] = (9u << 28) | dstSelect;
return CompileMaterialized(new Gen5ShaderProgram(0x1_0000_C000, [store, end]), userData);
}
private static Gen5MslShader CompileMaterialized(Gen5ShaderProgram program, uint[] userData)
{
var plan = ShaderResourcePlan.Extract(program, ShaderStage.Compute, 1, 0, (uint)userData.Length);
var snapshot = new ResourceSnapshot();
var specialization = new ResourceSpecialization();
Assert.True(ResourceMaterializer.Materialize(plan, new ResourceRuntimeInputs { UserData = userData },
ref snapshot, ref specialization));
var resources = ResourceMaterializer.ApplyTo(plan, specialization);
var layout = BindingLayout.Allocate(resources.Info,
BindingLayout.CollectUserDataRegisters(program, 0, (uint)userData.Length), false,
ShaderCompileRequest.RequiresFlattenedTable(plan, resources), false);
var request = new ShaderCompileRequest(plan, resources, layout);
Assert.True(Gen5MslTranslator.TryCompileProgram(request, out var shader, out var error), error);
return shader;
}
}