sharpemu/tests/SharpEmu.ShaderCompiler.Tests/Resources/DirectImageTableTests.cs

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);
}
}