569 lines
27 KiB
C#
569 lines
27 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using SharpEmu.ShaderCompiler.Metal;
|
|
using SharpEmu.ShaderCompiler.Resources;
|
|
using SharpEmu.ShaderCompiler.Vulkan;
|
|
using Xunit;
|
|
using static SharpEmu.ShaderCompiler.Tests.Resources.ResourceTestProgram;
|
|
|
|
namespace SharpEmu.ShaderCompiler.Tests.Resources;
|
|
|
|
public sealed class DirectImageTableTests
|
|
{
|
|
internal static Gen5ShaderProgram CreateWaveIndexedDescriptorProgram()
|
|
{
|
|
return Program(
|
|
ScalarLoad(0, 0, 16, immediateOffset: 0x80),
|
|
Sop1(8, "SFF1I32B32", 18, Gen5Operand.Scalar(16)),
|
|
Sop2(12, "SMulI32", 106, Gen5Operand.Scalar(18), new Gen5Operand(Gen5OperandKind.LiteralConstant, 0x90)),
|
|
MoveVectorFromScalar(16, 34, 18),
|
|
Vop2(20, "VLshlrevB32", 15, Operand(4), Gen5Operand.Vector(34)),
|
|
Vop3(24, "VLshlAddU32", 16, Gen5Operand.Vector(15), Operand(3), Gen5Operand.Vector(15)),
|
|
Sop1(32, "SBitset0B32", 16, Gen5Operand.Scalar(18)),
|
|
Vop2(36, "VAddI32", 15, new Gen5Operand(Gen5OperandKind.LiteralConstant, 0x40), Gen5Operand.Vector(16)),
|
|
GlobalMemory(40, "GlobalLoadDword", 0, 15, 22, 0),
|
|
ReadFirstLane(48, 106, 22),
|
|
Sop2(52, "SLshlB32", 106, Gen5Operand.Scalar(106), Operand(5)),
|
|
ScalarLoad(56, 0, 4, 8, immediateOffset: 0x100, dynamicOffsetRegister: 106),
|
|
Image(64, "ImageLoad", 4, dmask: 1, vectorAddress: 1),
|
|
EndProgram(72));
|
|
}
|
|
|
|
internal static Gen5ShaderProgram CreateWaveIndexedReadLaneProgram(bool selfAddressed, bool restoreExec = true)
|
|
{
|
|
var instructions = new List<Gen5ShaderInstruction>
|
|
{
|
|
ScalarLoad(0, 0, 16, immediateOffset: 0x80),
|
|
Sop1(8, "SFF1I32B32", 18, Gen5Operand.Scalar(16)),
|
|
Sop2(12, "SMulI32", 19, Gen5Operand.Scalar(18), new Gen5Operand(Gen5OperandKind.LiteralConstant, 0x90)),
|
|
MoveVectorFromScalar(16, 34, 18),
|
|
Vop2(20, "VLshlrevB32", 15, Operand(4), Gen5Operand.Vector(34)),
|
|
Vop3(24, "VLshlAddU32", selfAddressed ? 15u : 16u, Gen5Operand.Vector(15), Operand(3), Gen5Operand.Vector(15)),
|
|
Sop2(32, "SLshlB32", 20, Operand(1), Gen5Operand.Scalar(18)),
|
|
Sop2(36, "SXorB32", 16, Gen5Operand.Scalar(20), Gen5Operand.Scalar(16)),
|
|
Vop2(40, "VAddI32", 15, new Gen5Operand(Gen5OperandKind.LiteralConstant, 0x40), Gen5Operand.Vector(selfAddressed ? 15u : 16u)),
|
|
GlobalMemory(44, "GlobalLoadDword", 0, 15, 22, 0),
|
|
Sop1(52, "SMovB64", 12, Gen5Operand.Scalar(126)),
|
|
Sop1(56, "SFF1I32B64", 24, Gen5Operand.Scalar(12)),
|
|
new(60, Gen5ShaderEncoding.Vop3, "VReadlaneB32", [0u, 0u],
|
|
[Gen5Operand.Vector(22), Gen5Operand.Scalar(24), Gen5Operand.Scalar(24)], [Gen5Operand.Scalar(106)], null),
|
|
new(68, Gen5ShaderEncoding.Vop3, "VCmpEqU32", [0u, 0u],
|
|
[Gen5Operand.Scalar(106), Gen5Operand.Vector(22)], [Gen5Operand.Scalar(14)], new Gen5Vop3Control(0, 0, 0, false, 0, 14)),
|
|
Sop1(76, "SAndSaveexecB64", 28, Gen5Operand.Scalar(14)),
|
|
Branch(80, "SCbranchExecz", 8),
|
|
Sop2(84, "SLshlB32", 106, Gen5Operand.Scalar(106), Operand(5)),
|
|
Sop2(88, "SAddI32", 107, Gen5Operand.Scalar(106), Operand(16)),
|
|
ScalarLoad(92, 0, 4, 4, immediateOffset: 0x100, dynamicOffsetRegister: 106),
|
|
ScalarLoad(100, 0, 8, 4, immediateOffset: 0x100, dynamicOffsetRegister: 107),
|
|
Image(108, "ImageLoad", 4, dmask: 1, vectorAddress: 1),
|
|
Sop2(116, "SAndn2B64", 12, Gen5Operand.Scalar(12), Gen5Operand.Scalar(14)),
|
|
restoreExec ? Sop1(120, "SMovB64", 126, Gen5Operand.Scalar(28)) : Nop(120),
|
|
Branch(124, "SCbranchScc1", -18),
|
|
EndProgram(128),
|
|
};
|
|
return Program([.. instructions]);
|
|
}
|
|
|
|
public static Gen5ShaderProgram CreateProgram(uint mask = 1, bool split = true, bool bitScan = true)
|
|
{
|
|
var instructions = new List<Gen5ShaderInstruction>
|
|
{
|
|
Vop2(0, "VAddU32", 12, Operand(mask), Gen5Operand.Vector(0)),
|
|
ReadFirstLane(4, 18, 12),
|
|
Sop1(8, bitScan ? "SFF1I32B32" : "SMovB32", 19, Gen5Operand.Scalar(18)),
|
|
Sop2(12, "SLshlB32", 106, Gen5Operand.Scalar(19), Operand(5)),
|
|
new(16, Gen5ShaderEncoding.Sopk, "SAddkI32", [0xB7EA0158], [new(Gen5OperandKind.LiteralConstant, 344)], [Gen5Operand.Scalar(106)], null),
|
|
Sop2(20, "SAddI32", 107, Gen5Operand.Scalar(106), Operand(16)),
|
|
ScalarLoad(24, 0, 4, split ? 4u : 8u, dynamicOffsetRegister: 106),
|
|
};
|
|
if (split) instructions.Add(ScalarLoad(32, 0, 8, 4, dynamicOffsetRegister: 107));
|
|
instructions.AddRange([
|
|
MoveScalar(40, 106, 999),
|
|
MoveVector(44, 1, 0), MoveVector(48, 2, 0),
|
|
Image(52, "ImageLoad", 4, dmask: 1, vectorAddress: 1),
|
|
MoveScalar(60, 20, 0), MoveScalar(64, 21, 0), MoveScalar(68, 22, 64), MoveScalar(72, 23, 0),
|
|
BufferAccess(76, "BufferStoreDword", 20, vectorData: 4), EndProgram(84),
|
|
]);
|
|
return Program([.. instructions]);
|
|
}
|
|
|
|
public static bool ReadDescriptor(ulong address, out uint word)
|
|
{
|
|
word = 0;
|
|
if (address < 0x1000 + 312 || address >= 0x1000 + 344 + 32 * 32) return false;
|
|
var offset = address - 0x1000 - 312;
|
|
var record = offset / 32;
|
|
var component = offset % 32 / 4;
|
|
word = component switch
|
|
{
|
|
0 => (record & 1) == 0 ? 0x2000u : 0x1000u,
|
|
1 => 20u << 20,
|
|
3 => 0xFACu | (9u << 28),
|
|
_ => 0,
|
|
};
|
|
return true;
|
|
}
|
|
|
|
public static Gen5ShaderProgram CreateGuardedProgram(uint mask = 1, bool split = true)
|
|
{
|
|
var program = CreateProgram(mask, split);
|
|
return program with
|
|
{
|
|
Instructions = program.Instructions.Where(instruction => instruction.Pc < 8)
|
|
.Concat([
|
|
Sopc(8, "SCmpLgU32", Operand(0), Gen5Operand.Scalar(18)),
|
|
Branch(12, "SCbranchScc0", 19),
|
|
])
|
|
.Concat(program.Instructions.Where(instruction => instruction.Pc >= 8)
|
|
.Select(instruction => instruction with { Pc = instruction.Pc + 8 })).ToArray(),
|
|
};
|
|
}
|
|
|
|
public static (ShaderResourcePlan Plan, ResourceSnapshot Snapshot, ShaderCompileRequest Request) PrepareDirect(uint mask = 1, bool split = true, bool guarded = false)
|
|
{
|
|
var program = guarded ? CreateGuardedProgram(mask, split) : CreateProgram(mask, split);
|
|
var plan = ShaderResourcePlan.Extract(program, ShaderStage.Compute, Hash, 0, 2);
|
|
var snapshot = new ResourceSnapshot();
|
|
var specialization = new ResourceSpecialization();
|
|
Assert.True(ResourceMaterializer.Materialize(plan, Inputs([0x1000, 0], readCleanMemory: ReadDescriptor), ref snapshot, ref specialization));
|
|
var resources = ResourceMaterializer.ApplyTo(plan, specialization);
|
|
var layout = BindingLayout.Allocate(resources.Info, BindingLayout.CollectUserDataRegisters(program, 0, 2),
|
|
false, ShaderCompileRequest.RequiresFlattenedTable(plan, resources), false);
|
|
return (plan, snapshot, new ShaderCompileRequest(plan, resources, layout) { LocalSizeX = 1, ThreadCountX = 1 });
|
|
}
|
|
|
|
[Fact]
|
|
public void WaveIndexedDescriptorTableMaterializesOnlyActiveMaskKeys()
|
|
{
|
|
var plan = ShaderResourcePlan.Extract(CreateWaveIndexedDescriptorProgram(), ShaderStage.Compute, Hash, 0, 2);
|
|
var selector = plan.DescriptorSources[(int)plan.Info.Images[0].Source].IndirectImage!;
|
|
Assert.True(selector.Dense);
|
|
Assert.Equal(0u, selector.KeyBound);
|
|
Assert.Equal(new WaveIndexedImageSelector(0x80, 0x40, 0x90), selector.WaveIndexed);
|
|
|
|
var snapshot = new ResourceSnapshot();
|
|
var specialization = new ResourceSpecialization();
|
|
Assert.True(ResourceMaterializer.Materialize(plan, Inputs([0x1000, 0], readCleanMemory: ReadWaveIndexedMemory), ref snapshot, ref specialization));
|
|
Assert.Equal(2, snapshot.Images.Length);
|
|
}
|
|
|
|
[Fact]
|
|
public void CandidateWithAnUnsupportedFormatIsMaterializedAsNull()
|
|
{
|
|
var plan = ShaderResourcePlan.Extract(CreateWaveIndexedDescriptorProgram(), ShaderStage.Compute, Hash, 0, 2);
|
|
bool Read(ulong address, out uint word)
|
|
{
|
|
var success = ReadWaveIndexedMemory(address, out word);
|
|
if (address == 0x1000 + 0x100 + 5 * 32 + 4) word = 139u << 20;
|
|
return success;
|
|
}
|
|
|
|
var snapshot = new ResourceSnapshot();
|
|
var specialization = new ResourceSpecialization();
|
|
Assert.True(ResourceMaterializer.Materialize(plan, Inputs([0x1000, 0], readCleanMemory: Read), ref snapshot, ref specialization, out var failure), $"materialize {failure}");
|
|
Assert.Equal(2, snapshot.Images.Length);
|
|
Assert.Single(snapshot.Images, image => image.All(word => word == 0));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public void ReadLaneWaterfallSelectsWaveIndexedDescriptors(bool selfAddressed)
|
|
{
|
|
var program = CreateWaveIndexedReadLaneProgram(selfAddressed);
|
|
var plan = ShaderResourcePlan.Extract(program, ShaderStage.Compute, Hash, 0, 2);
|
|
var selector = plan.DescriptorSources[(int)plan.Info.Images[0].Source].IndirectImage!;
|
|
Assert.True(selector.Dense, "dense");
|
|
Assert.Equal(0x100u, selector.TableOffset);
|
|
Assert.Equal(new WaveIndexedImageSelector(0x80, 0x40, 0x90), selector.WaveIndexed);
|
|
Assert.True(Assert.Single(plan.IndirectImages).KeyIsAddressOffset, "address-offset key");
|
|
|
|
var snapshot = new ResourceSnapshot();
|
|
var specialization = new ResourceSpecialization();
|
|
Assert.True(ResourceMaterializer.Materialize(plan, Inputs([0x1000, 0], readCleanMemory: ReadWaveIndexedMemory), ref snapshot, ref specialization, out var failure), $"materialize {failure}");
|
|
Assert.Equal(2, snapshot.Images.Length);
|
|
var resources = ResourceMaterializer.ApplyTo(plan, specialization);
|
|
var layout = BindingLayout.Allocate(resources.Info, BindingLayout.CollectUserDataRegisters(program, 0, 2),
|
|
false, ShaderCompileRequest.RequiresFlattenedTable(plan, resources), false);
|
|
var request = new ShaderCompileRequest(plan, resources, layout) { LocalSizeX = 64, ThreadCountX = 64 };
|
|
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out _, out var error), error);
|
|
}
|
|
|
|
[Fact]
|
|
public void ReadLaneWithoutRestoredExecutionIsNotWaveIndexed()
|
|
{
|
|
Assert.Throws<ResourcePlanException>(() =>
|
|
ShaderResourcePlan.Extract(CreateWaveIndexedReadLaneProgram(selfAddressed: true, restoreExec: false), ShaderStage.Compute, Hash, 0, 2));
|
|
}
|
|
|
|
private static bool ReadWaveIndexedMemory(ulong address, out uint word)
|
|
{
|
|
word = 0;
|
|
if (address == 0x1000 + 0x80)
|
|
{
|
|
word = (1u << 1) | (1u << 4);
|
|
return true;
|
|
}
|
|
|
|
if (address == 0x1000 + 0x40 + 0x90 || address == 0x1000 + 0x40 + 4 * 0x90)
|
|
{
|
|
word = address == 0x1000 + 0x40 + 0x90 ? 2u : 5u;
|
|
return true;
|
|
}
|
|
|
|
if (address < 0x1000 + 0x100 || address >= 0x1000 + 0x100 + 6 * 32)
|
|
return false;
|
|
var relative = address - 0x1000 - 0x100;
|
|
var record = relative / 32;
|
|
if (record is not (2 or 5))
|
|
return false;
|
|
word = (relative % 32 / 4) switch
|
|
{
|
|
0 => (record & 1) == 0 ? 0x2000u : 0x1000u,
|
|
1 => 20u << 20,
|
|
3 => 0xFACu | (9u << 28),
|
|
_ => 0,
|
|
};
|
|
return true;
|
|
}
|
|
|
|
public static (ResourceSnapshot Snapshot, ShaderCompileRequest Request) PrepareMixedDimensions(uint mask, bool arrayFirst)
|
|
{
|
|
var program = CreateGuardedProgram(mask);
|
|
program = program with
|
|
{
|
|
Instructions = program.Instructions.Select(instruction => instruction.Pc switch
|
|
{
|
|
48 => MoveVector(48, 3, 1),
|
|
60 => Image(60, "ImageLoad", 4, dimension: 5, dmask: 1, vectorAddress: 1),
|
|
_ => instruction,
|
|
}).ToArray(),
|
|
};
|
|
bool Read(ulong address, out uint word)
|
|
{
|
|
word = 0;
|
|
if (address < 0x1000 + 344 || address >= 0x1000 + 344 + 32 * 32) return false;
|
|
var offset = address - 0x1000 - 344;
|
|
var record = offset / 32;
|
|
if (record is not (0 or 31)) return true;
|
|
var array = (record == 0) == arrayFirst;
|
|
word = (offset % 32 / 4) switch
|
|
{
|
|
0 => array ? 0x1000u : 0x2000u,
|
|
1 => 20u << 20,
|
|
3 => 0xFACu | ((array ? 13u : 9u) << 28),
|
|
4 => array ? 1u : 0u,
|
|
_ => 0,
|
|
};
|
|
return true;
|
|
}
|
|
var plan = ShaderResourcePlan.Extract(program, ShaderStage.Compute, Hash, 0, 2);
|
|
var snapshot = new ResourceSnapshot();
|
|
var specialization = new ResourceSpecialization();
|
|
Assert.True(ResourceMaterializer.Materialize(plan, Inputs([0x1000, 0], readCleanMemory: Read), ref snapshot, ref specialization));
|
|
var resources = ResourceMaterializer.ApplyTo(plan, specialization);
|
|
var layout = BindingLayout.Allocate(resources.Info, BindingLayout.CollectUserDataRegisters(program, 0, 2),
|
|
false, ShaderCompileRequest.RequiresFlattenedTable(plan, resources), false);
|
|
return (snapshot, new ShaderCompileRequest(plan, resources, layout) { LocalSizeX = 1, ThreadCountX = 1 });
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public void MixedDimensionsRetainEveryCandidateAndUseSeparateBindings(bool arrayFirst)
|
|
{
|
|
var (snapshot, request) = PrepareMixedDimensions(1, arrayFirst);
|
|
Assert.Equal(3, snapshot.Images.Length);
|
|
var images = request.Resources.Info.Images;
|
|
Assert.Equal(3, images[0].IndirectResources.Count);
|
|
Assert.Contains(images, image => image.Dimension == ImageDimension.Dim2D);
|
|
Assert.Contains(images, image => image.Dimension == ImageDimension.Dim2DArray);
|
|
var mapping = (int)images[0].IndirectMappingOffset;
|
|
Assert.Equal(32u, snapshot.FlattenedResourceTable[mapping]);
|
|
Assert.Equal(344u + 31 * 32, snapshot.FlattenedResourceTable[mapping + 63]);
|
|
Assert.Equal(2u, snapshot.FlattenedResourceTable[mapping + 64]);
|
|
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out _, out var error), error);
|
|
Assert.False(Gen5MslTranslator.TryCompileProgram(request, out _, out error));
|
|
Assert.Contains("not supported on Metal", error);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public void DirectTableRetainsAllBitScanResultsAndCompiles(bool split)
|
|
{
|
|
var (plan, snapshot, request) = PrepareDirect(split: split);
|
|
Assert.Single(plan.IndirectImages);
|
|
Assert.True(plan.IndirectImages[0].KeyIsAddressOffset);
|
|
Assert.False(plan.Info.UsesDeviceAddresses);
|
|
Assert.Empty(plan.DynamicReads);
|
|
var selector = plan.DescriptorSources[(int)plan.Info.Images[0].Source].IndirectImage!;
|
|
Assert.Equal(33, selector.DirectCandidates!.Count);
|
|
Assert.Contains(selector.DirectCandidates, candidate => candidate.Offset == 312);
|
|
Assert.Contains(selector.DirectCandidates, candidate => candidate.Offset == 344 + 31 * 32);
|
|
Assert.Equal(2, snapshot.Images.Length);
|
|
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out _, out var spirvError), spirvError);
|
|
Assert.True(Gen5MslTranslator.TryCompileProgram(request, out _, out var metalError), metalError);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(32)]
|
|
[InlineData(33)]
|
|
public void DirectImageCapacityPreservesTheLimitAndPublishedState(int distinctCount)
|
|
{
|
|
var (plan, snapshot, _) = PrepareDirect();
|
|
var specialization = new ResourceSpecialization();
|
|
var previousSnapshot = snapshot;
|
|
var previousSpecialization = specialization;
|
|
bool Read(ulong address, out uint word)
|
|
{
|
|
var success = ReadDescriptor(address, out word);
|
|
if (success && (address - 0x1000 - 312) % 32 == 0)
|
|
word = 0x2000 + (uint)(((address - 0x1000 - 312) / 32) % (ulong)distinctCount);
|
|
return success;
|
|
}
|
|
|
|
var success = ResourceMaterializer.Materialize(plan, Inputs([0x1000, 0], readCleanMemory: Read),
|
|
ref snapshot, ref specialization, out var failure);
|
|
Assert.Equal(distinctCount <= ShaderResourceInfo.MaxImages, success);
|
|
if (success)
|
|
{
|
|
Assert.Equal(ResourceMaterializationFailure.None, failure);
|
|
Assert.Equal(distinctCount, snapshot.Images.Length);
|
|
}
|
|
else
|
|
{
|
|
Assert.Equal(ResourceMaterializationFailure.ImageCapacityExceeded, failure);
|
|
Assert.Same(previousSnapshot, snapshot);
|
|
Assert.Same(previousSpecialization, specialization);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void UnboundedDirectTableIsStillRejected()
|
|
{
|
|
Assert.Throws<ResourcePlanException>(() => ShaderResourcePlan.Extract(CreateProgram(bitScan: false), ShaderStage.Compute, Hash, 0, 2));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false, false)]
|
|
[InlineData(false, true)]
|
|
[InlineData(true, false)]
|
|
[InlineData(true, true)]
|
|
public void NonZeroGuardExcludesTheUnreachableDescriptor(bool split, bool equality)
|
|
{
|
|
var program = CreateGuardedProgram(split: split);
|
|
if (equality)
|
|
program = program with
|
|
{
|
|
Instructions = program.Instructions.Select(instruction => instruction.Pc switch
|
|
{
|
|
8 => Sopc(8, "SCmpEqI32", Gen5Operand.Scalar(18), Operand(0)),
|
|
12 => Branch(12, "SCbranchScc1", 19),
|
|
_ => instruction,
|
|
}).ToArray(),
|
|
};
|
|
var plan = ShaderResourcePlan.Extract(program, ShaderStage.Compute, Hash, 0, 2);
|
|
var selector = plan.DescriptorSources[(int)plan.Info.Images[0].Source].IndirectImage!;
|
|
Assert.True(selector.Dense);
|
|
Assert.Equal(32u, selector.KeyBound);
|
|
Assert.Equal(344u, selector.TableOffset);
|
|
Assert.True(Assert.Single(plan.IndirectImages).KeyIsAddressOffset);
|
|
bool Read(ulong address, out uint word)
|
|
{
|
|
Assert.True(address >= 0x1000 + 344);
|
|
return ReadDescriptor(address, out word);
|
|
}
|
|
var snapshot = new ResourceSnapshot();
|
|
var specialization = new ResourceSpecialization();
|
|
Assert.True(ResourceMaterializer.Materialize(plan, Inputs([0x1000, 0], readCleanMemory: Read), ref snapshot, ref specialization));
|
|
var (_, _, request) = PrepareDirect(split: split, guarded: true);
|
|
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out _, out var spirvError), spirvError);
|
|
Assert.True(Gen5MslTranslator.TryCompileProgram(request, out _, out var metalError), metalError);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(600u, 8u)]
|
|
[InlineData(444u, 3u)]
|
|
[InlineData(312u, 32u)]
|
|
[InlineData(1400u, 32u)]
|
|
public void DenseTableEndsBeforeASamplerLoadedFromTheSameBase(uint samplerOffset, uint expectedBound)
|
|
{
|
|
var program = CreateGuardedProgram();
|
|
program = program with
|
|
{
|
|
Instructions = program.Instructions.Where(instruction => instruction.Pc != 52).Select(instruction => instruction.Pc switch
|
|
{
|
|
48 => ScalarLoad(48, 0, 12, 4, immediateOffset: (int)samplerOffset),
|
|
60 => Image(60, "ImageSample", 4, samplerRegister: 12, dmask: 1, vectorAddress: 1),
|
|
_ => instruction,
|
|
}).ToArray(),
|
|
};
|
|
var plan = ShaderResourcePlan.Extract(program, ShaderStage.Compute, Hash, 0, 2);
|
|
var selector = plan.DescriptorSources[(int)plan.Info.Images[0].Source].IndirectImage!;
|
|
Assert.True(selector.Dense);
|
|
Assert.Equal(344u, selector.TableOffset);
|
|
Assert.Equal(expectedBound, selector.KeyBound);
|
|
|
|
var highest = 0ul;
|
|
bool Read(ulong address, out uint word)
|
|
{
|
|
if (address >= 0x1000 + samplerOffset && address < 0x1000 + samplerOffset + 16)
|
|
{
|
|
word = 0;
|
|
return true;
|
|
}
|
|
|
|
if (address >= 0x1000 + 344) highest = Math.Max(highest, address);
|
|
return ReadDescriptor(address, out word);
|
|
}
|
|
var snapshot = new ResourceSnapshot();
|
|
var specialization = new ResourceSpecialization();
|
|
Assert.True(ResourceMaterializer.Materialize(plan, Inputs([0x1000, 0], Read, Read), ref snapshot, ref specialization, out var failure), $"materialize {failure}");
|
|
Assert.True(highest < 0x1000 + 344 + expectedBound * 32, $"read 0x{highest:X} beyond the table");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public void LoopMustRecheckTheBitScanInput(bool bypassGuard)
|
|
{
|
|
var program = CreateGuardedProgram();
|
|
program = program with
|
|
{
|
|
Instructions = program.Instructions.Where(instruction => instruction.Pc < 92)
|
|
.Select(instruction => instruction.Pc == 12 ? Branch(12, "SCbranchScc0", 21) : instruction)
|
|
.Concat([
|
|
ReadFirstLane(92, 18, 13),
|
|
Branch(96, "SBranch", bypassGuard ? (short)-21 : (short)-23),
|
|
EndProgram(100),
|
|
]).ToArray(),
|
|
};
|
|
var plan = ShaderResourcePlan.Extract(program, ShaderStage.Compute, Hash, 0, 2);
|
|
var selector = plan.DescriptorSources[(int)plan.Info.Images[0].Source].IndirectImage!;
|
|
if (bypassGuard)
|
|
{
|
|
Assert.Equal(33, selector.DirectCandidates!.Count);
|
|
Assert.Contains(selector.DirectCandidates, candidate => candidate.Offset == 312);
|
|
}
|
|
else
|
|
{
|
|
Assert.True(selector.Dense);
|
|
Assert.Equal(32u, selector.KeyBound);
|
|
Assert.Equal(344u, selector.TableOffset);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("opposite-branch")]
|
|
[InlineData("different-register")]
|
|
[InlineData("guard-bypass")]
|
|
[InlineData("condition-write")]
|
|
[InlineData("input-write")]
|
|
public void UnprovenGuardRetainsTheZeroInputDescriptor(string variation)
|
|
{
|
|
var program = CreateGuardedProgram();
|
|
var instructions = program.Instructions.ToList();
|
|
switch (variation)
|
|
{
|
|
case "opposite-branch":
|
|
instructions[instructions.FindIndex(instruction => instruction.Pc == 12)] = Branch(12, "SCbranchScc1", 19);
|
|
break;
|
|
case "different-register":
|
|
instructions[instructions.FindIndex(instruction => instruction.Pc == 8)] =
|
|
Sopc(8, "SCmpLgU32", Operand(0), Gen5Operand.Scalar(17));
|
|
break;
|
|
case "guard-bypass":
|
|
instructions[0] = Branch(0, "SCbranchScc1", 3);
|
|
break;
|
|
case "condition-write":
|
|
instructions = instructions.Select(instruction => instruction.Pc >= 12
|
|
? instruction with { Pc = instruction.Pc + 4 } : instruction).ToList();
|
|
instructions.Add(Sopc(12, "SCmpEqU32", Operand(0), Operand(0)));
|
|
break;
|
|
case "input-write":
|
|
instructions = instructions.Select(instruction => instruction.Pc >= 16
|
|
? instruction with { Pc = instruction.Pc + 4 } : instruction).ToList();
|
|
instructions[instructions.FindIndex(instruction => instruction.Pc == 12)] = Branch(12, "SCbranchScc0", 20);
|
|
instructions.Add(ReadFirstLane(16, 18, 13));
|
|
break;
|
|
}
|
|
program = program with { Instructions = instructions.OrderBy(instruction => instruction.Pc).ToArray() };
|
|
var plan = ShaderResourcePlan.Extract(program, ShaderStage.Compute, Hash, 0, 2);
|
|
var candidates = plan.DescriptorSources[(int)plan.Info.Images[0].Source].IndirectImage!.DirectCandidates!;
|
|
Assert.Equal(33, candidates.Count);
|
|
Assert.Contains(candidates, candidate => candidate.Offset == 312);
|
|
}
|
|
|
|
[Fact]
|
|
public void DescriptorWordsUsedByArithmeticAreNotRemoved()
|
|
{
|
|
var program = CreateProgram();
|
|
program = program with
|
|
{
|
|
Instructions = program.Instructions.Select(instruction => instruction.Pc == 40
|
|
? Vop1(40, "VMovB32", 15, Gen5Operand.Scalar(4)) : instruction).ToArray(),
|
|
};
|
|
var plan = ShaderResourcePlan.Extract(program, ShaderStage.Compute, Hash, 0, 2);
|
|
Assert.Single(plan.IndirectImages);
|
|
Assert.True(plan.Info.UsesDeviceAddresses);
|
|
Assert.True(plan.Memory.TryGetIndex(24, 0, out var memoryIndex));
|
|
Assert.False(plan.Memory[memoryIndex].PlanningOnly);
|
|
}
|
|
|
|
[Fact]
|
|
public void DirectTableSlotWithAnUnsupportedFormatIsMaterializedAsNull()
|
|
{
|
|
var program = CreateProgram();
|
|
var plan = ShaderResourcePlan.Extract(program, ShaderStage.Compute, Hash, 0, 2);
|
|
var unsupported = true;
|
|
bool Read(ulong address, out uint word)
|
|
{
|
|
var success = ReadDescriptor(address, out word);
|
|
if (unsupported && address == 0x1000 + 344 + 3 * 32 + 4) word = 139u << 20;
|
|
return success;
|
|
}
|
|
|
|
var clean = new ResourceSnapshot();
|
|
var cleanSpecialization = new ResourceSpecialization();
|
|
Assert.True(ResourceMaterializer.Materialize(plan, Inputs([0x1000, 0], readCleanMemory: ReadDescriptor), ref clean, ref cleanSpecialization));
|
|
var snapshot = new ResourceSnapshot();
|
|
var specialization = new ResourceSpecialization();
|
|
Assert.True(ResourceMaterializer.Materialize(plan, Inputs([0x1000, 0], readCleanMemory: Read), ref snapshot, ref specialization, out var failure), $"materialize {failure}");
|
|
Assert.Equal(clean.Images.Length + 1, snapshot.Images.Length);
|
|
Assert.Single(snapshot.Images, image => image.All(word => word == 0));
|
|
unsupported = false;
|
|
Assert.True(ResourceMaterializer.Materialize(plan, Inputs([0x1000, 0], readCleanMemory: Read), ref snapshot, ref specialization, out failure), $"materialize {failure}");
|
|
Assert.Equal(clean.Images.Length, snapshot.Images.Length);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false, false)]
|
|
[InlineData(true, false)]
|
|
[InlineData(false, true)]
|
|
[InlineData(true, true)]
|
|
public void FailedReadOrIncompatibleCandidateDoesNotPublish(bool incompatible, bool guarded)
|
|
{
|
|
var (plan, original, _) = PrepareDirect(guarded: guarded);
|
|
var snapshot = original;
|
|
var specialization = new ResourceSpecialization();
|
|
var originalSpecialization = specialization;
|
|
bool Read(ulong address, out uint word)
|
|
{
|
|
var success = ReadDescriptor(address, out word);
|
|
if (address == 0x1000 + 344 + 12)
|
|
{
|
|
if (!incompatible) return false;
|
|
word = (word & 0x0FFFFFFF) | (11u << 28);
|
|
}
|
|
return success;
|
|
}
|
|
Assert.False(ResourceMaterializer.Materialize(plan, Inputs([0x1000, 0], readCleanMemory: Read), ref snapshot, ref specialization,
|
|
out var failure));
|
|
Assert.Equal(incompatible ? ResourceMaterializationFailure.IncompatibleImageCandidates : ResourceMaterializationFailure.Other, failure);
|
|
Assert.Same(original, snapshot);
|
|
Assert.Same(originalSpecialization, specialization);
|
|
}
|
|
}
|