[SpirvTranslator] Shader fixes (#1019)
* [ShaderCompiler] Fix packed 10 and 11 bit buffer format layouts * [SpirvTranslator] Pass W as the pixel position input instead of its reciprocal * [SpirvTranslator] Keep lane spills in per invocation slots for all 64 lanes * [SpirvTranslator] Match the hardware float conversion and NaN rules * [SpirvTranslator] Apply VOP3 sign modifiers to conditional mask sources * [GPU] Download guest DCC metadata before a known target is sampled * [ShaderCompiler] End bit scan image tables at a sampler loaded from the same base * [ShaderCompiler] Treat image table candidates with unsupported formats as null
This commit is contained in:
parent
6fe8b87f48
commit
d4891229e9
|
|
@ -123,6 +123,21 @@ public sealed unsafe partial class GuestImageCache : IGuestImageCache, IGuestIma
|
|||
return GetNullImage(request);
|
||||
}
|
||||
|
||||
var found = LookUpImage(ref request, exactFormat);
|
||||
if (request.Role is ImageRole.Texture or ImageRole.StorageImage)
|
||||
{
|
||||
ref readonly var description = ref _slots[found].Description;
|
||||
if (description.DccSliceSize is var sliceSize and not 0)
|
||||
{
|
||||
SynchronizeGuestDccMetadata(description.Metadata.Range.Address, sliceSize, request.View.BaseLayer, request.View.LayerCount);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
private ResourceSlotIdentifier LookUpImage(ref ImageRequest request, bool exactFormat)
|
||||
{
|
||||
using var held = _lock.Hold();
|
||||
if (TryReuseLookup(ref request, exactFormat, out var reused))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -142,15 +142,15 @@ public static partial class Gen5MslTranslator
|
|||
}
|
||||
case "VCndmaskB32":
|
||||
{
|
||||
// dst = mask-bit(lane) ? src1 : src0. Sources are raw (no
|
||||
// float modifiers), matching the SPIR-V translator; the mask
|
||||
// is VCC for VOP2 and an explicit SGPR operand for VOP3.
|
||||
// dst = mask-bit(lane) ? src1 : src0. VOP3 abs/neg act on the
|
||||
// sign bit, matching the SPIR-V translator; the mask is VCC
|
||||
// for VOP2 and an explicit SGPR operand for VOP3.
|
||||
var mask = instruction.Sources.Count > 2
|
||||
? MaskBitExpression(instruction.Sources[2])
|
||||
: "vcc";
|
||||
StoreVector(
|
||||
DestinationVector(instruction),
|
||||
$"({mask}) ? ({RawSource(instruction, 1)}) : ({RawSource(instruction, 0)})");
|
||||
$"({mask}) ? ({SignModifiedSource(instruction, 1)}) : ({SignModifiedSource(instruction, 0)})");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1776,6 +1776,24 @@ public static partial class Gen5MslTranslator
|
|||
return value;
|
||||
}
|
||||
|
||||
private string SignModifiedSource(Gen5ShaderInstruction instruction, int sourceIndex)
|
||||
{
|
||||
var value = RawSource(instruction, sourceIndex);
|
||||
if (instruction.Control is not Gen5Vop3Control control)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
if ((control.AbsoluteMask & (1u << sourceIndex)) != 0)
|
||||
{
|
||||
value = $"(({value}) & 0x7FFFFFFFu)";
|
||||
}
|
||||
|
||||
return (control.NegateMask & (1u << sourceIndex)) != 0
|
||||
? $"(({value}) ^ 0x80000000u)"
|
||||
: value;
|
||||
}
|
||||
|
||||
/// <summary>64-bit source: SGPR/VGPR pair, sign-extended inline, or zero-extended 32-bit.</summary>
|
||||
private string RawSource64(Gen5ShaderInstruction instruction, int sourceIndex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -117,8 +117,15 @@ public static partial class Gen5SpirvTranslator
|
|||
{
|
||||
var oldValue = LoadV(destination);
|
||||
var sourceValue = GetRawSource(instruction, 0);
|
||||
var selectedLane = BitwiseAnd(GetRawSource(instruction, 1), UInt(_waveLaneCount - 1));
|
||||
if (_laneSpillSlots.Count != 0)
|
||||
var selectedLane = BitwiseAnd(GetRawSource(instruction, 1), UInt(LaneSelectMask));
|
||||
if (TryGetConstantLane(instruction, out var constantLane))
|
||||
{
|
||||
if (_laneSpillSlots.TryGetValue((destination, constantLane), out var slot))
|
||||
{
|
||||
Store(slot, sourceValue);
|
||||
}
|
||||
}
|
||||
else if (_laneSpillSlots.Count != 0)
|
||||
{
|
||||
foreach (var ((slotRegister, slotLane), variable) in _laneSpillSlots)
|
||||
{
|
||||
|
|
@ -157,8 +164,8 @@ public static partial class Gen5SpirvTranslator
|
|||
SpirvOp.Select,
|
||||
_uintType,
|
||||
condition,
|
||||
GetRawSource(instruction, 1),
|
||||
GetRawSource(instruction, 0));
|
||||
GetSignModifiedSource(instruction, 1),
|
||||
GetSignModifiedSource(instruction, 0));
|
||||
break;
|
||||
}
|
||||
case "VCvtU32F32":
|
||||
|
|
@ -190,7 +197,7 @@ public static partial class Gen5SpirvTranslator
|
|||
var source = GetFloatSource(instruction, 0);
|
||||
if (instruction.Opcode == "VCvtRpiI32F32")
|
||||
{
|
||||
source = Ext(9, _floatType, source);
|
||||
source = Ext(8, _floatType, _module.AddInstruction(SpirvOp.FAdd, _floatType, source, Float(0.5f)));
|
||||
}
|
||||
else if (instruction.Opcode == "VCvtFlrI32F32")
|
||||
{
|
||||
|
|
@ -421,7 +428,7 @@ public static partial class Gen5SpirvTranslator
|
|||
var converted = _module.AddInstruction(
|
||||
SpirvOp.ConvertFToU,
|
||||
_uintType,
|
||||
GetFloatSource(instruction, 0));
|
||||
Ext(81, _floatType, GetFloatSource(instruction, 0), Float(0), Float(255)));
|
||||
var offset = ShiftLeftLogical(
|
||||
BitwiseAnd(GetRawSource(instruction, 1), UInt(3)),
|
||||
UInt(3));
|
||||
|
|
@ -678,9 +685,9 @@ public static partial class Gen5SpirvTranslator
|
|||
var c = GetFloat16Source(instruction, 2);
|
||||
var value = instruction.Opcode switch
|
||||
{
|
||||
"VMin3F16" => Ext(37, _floatType, Ext(37, _floatType, a, b), c),
|
||||
"VMax3F16" => Ext(40, _floatType, Ext(40, _floatType, a, b), c),
|
||||
_ => Ext(40, _floatType, Ext(37, _floatType, a, b), Ext(37, _floatType, Ext(40, _floatType, a, b), c)),
|
||||
"VMin3F16" => NanIgnoringMinMax(37, NanIgnoringMinMax(37, a, b), c),
|
||||
"VMax3F16" => NanIgnoringMinMax(40, NanIgnoringMinMax(40, a, b), c),
|
||||
_ => EmitFloatMedian(a, b, c),
|
||||
};
|
||||
result = EmitFloat16Result(instruction, destination, value);
|
||||
break;
|
||||
|
|
@ -1219,18 +1226,9 @@ public static partial class Gen5SpirvTranslator
|
|||
}
|
||||
case "VMed3F32":
|
||||
{
|
||||
var left = GetFloatSource(instruction, 0);
|
||||
var middle = GetFloatSource(instruction, 1);
|
||||
var right = GetFloatSource(instruction, 2);
|
||||
var low = Ext(37, _floatType, left, middle);
|
||||
var high = Ext(40, _floatType, left, middle);
|
||||
result = EmitFloatResult(
|
||||
instruction,
|
||||
Ext(
|
||||
40,
|
||||
_floatType,
|
||||
low,
|
||||
Ext(37, _floatType, high, right)));
|
||||
EmitFloatMedian(GetFloatSource(instruction, 0), GetFloatSource(instruction, 1), GetFloatSource(instruction, 2)));
|
||||
break;
|
||||
}
|
||||
case "VCubeidF32":
|
||||
|
|
@ -3879,6 +3877,24 @@ public static partial class Gen5SpirvTranslator
|
|||
return value;
|
||||
}
|
||||
|
||||
private uint GetSignModifiedSource(Gen5ShaderInstruction instruction, int sourceIndex)
|
||||
{
|
||||
var value = GetRawSource(instruction, sourceIndex);
|
||||
if (instruction.Control is not Gen5Vop3Control control)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
if ((control.AbsoluteMask & (1u << sourceIndex)) != 0)
|
||||
{
|
||||
value = BitwiseAnd(value, UInt(0x7FFFFFFFu));
|
||||
}
|
||||
|
||||
return (control.NegateMask & (1u << sourceIndex)) != 0
|
||||
? BitwiseXor(value, UInt(0x80000000u))
|
||||
: value;
|
||||
}
|
||||
|
||||
private uint ApplyDpp8Source(Gen5Dpp8Control control, uint value)
|
||||
{
|
||||
var lane = GuestWaveLane();
|
||||
|
|
@ -4451,7 +4467,7 @@ public static partial class Gen5SpirvTranslator
|
|||
};
|
||||
if (clamp)
|
||||
{
|
||||
doubleValue = Ext(43, type, doubleValue, Double(0.0), Double(1.0));
|
||||
doubleValue = Ext(81, type, doubleValue, Double(0.0), Double(1.0));
|
||||
}
|
||||
|
||||
var bits = Bitcast(_ulongType, doubleValue);
|
||||
|
|
@ -4485,9 +4501,8 @@ public static partial class Gen5SpirvTranslator
|
|||
EmitFloat16Result(
|
||||
instruction,
|
||||
destination,
|
||||
Ext(
|
||||
NanIgnoringMinMax(
|
||||
operation,
|
||||
_floatType,
|
||||
GetFloat16Source(instruction, 0),
|
||||
GetFloat16Source(instruction, 1)));
|
||||
|
||||
|
|
@ -4515,6 +4530,19 @@ public static partial class Gen5SpirvTranslator
|
|||
GetFloatSource(instruction, 0),
|
||||
GetFloatSource(instruction, 1)));
|
||||
|
||||
private uint EmitFloatMedian(uint a, uint b, uint c)
|
||||
{
|
||||
var low = NanIgnoringMinMax(37, a, b);
|
||||
var high = NanIgnoringMinMax(40, a, b);
|
||||
var median = NanIgnoringMinMax(40, low, NanIgnoringMinMax(37, high, c));
|
||||
var anyNan = _module.AddInstruction(
|
||||
SpirvOp.LogicalOr,
|
||||
_boolType,
|
||||
IsNanBits(a),
|
||||
_module.AddInstruction(SpirvOp.LogicalOr, _boolType, IsNanBits(b), IsNanBits(c)));
|
||||
return _module.AddInstruction(SpirvOp.Select, _floatType, anyNan, NanIgnoringMinMax(37, low, c), median);
|
||||
}
|
||||
|
||||
private uint EmitFloatTernaryExt(
|
||||
Gen5ShaderInstruction instruction,
|
||||
uint operation)
|
||||
|
|
@ -4979,7 +5007,7 @@ public static partial class Gen5SpirvTranslator
|
|||
|
||||
var destination = instruction.Destinations[0].Value;
|
||||
var sourceValue = GetRawSource(instruction, 0);
|
||||
var selectedLane = BitwiseAnd(GetRawSource(instruction, 1), UInt(_waveLaneCount - 1));
|
||||
var selectedLane = BitwiseAnd(GetRawSource(instruction, 1), UInt(LaneSelectMask));
|
||||
|
||||
if (_emulateWave64)
|
||||
{
|
||||
|
|
@ -4987,7 +5015,7 @@ public static partial class Gen5SpirvTranslator
|
|||
// Read it even when the guest execution mask disables that lane.
|
||||
StoreS(destination, BroadcastWave64Lane(sourceValue, selectedLane));
|
||||
}
|
||||
else if (_subgroupInvocationIdInput != 0)
|
||||
else if (_subgroupInvocationIdInput != 0 && _stage == Gen5SpirvStage.Compute)
|
||||
{
|
||||
var broadcast = _module.AddInstruction(
|
||||
SpirvOp.GroupNonUniformBroadcast,
|
||||
|
|
@ -4995,18 +5023,13 @@ public static partial class Gen5SpirvTranslator
|
|||
UInt(3),
|
||||
sourceValue,
|
||||
selectedLane);
|
||||
StoreS(destination, broadcast);
|
||||
StoreS(destination, ReadLaneSpillSlot(instruction, selectedLane, broadcast));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: no subgroup ops, read current lane's value, or the
|
||||
// spill slot that V_WRITELANE filled for the selected lane.
|
||||
if (instruction.Sources[0].Kind == Gen5OperandKind.VectorRegister)
|
||||
{
|
||||
sourceValue = SelectLaneSpillSlot(instruction.Sources[0].Value, selectedLane, sourceValue);
|
||||
}
|
||||
|
||||
StoreS(destination, sourceValue);
|
||||
StoreS(destination, ReadLaneSpillSlot(instruction, selectedLane, sourceValue));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -5129,7 +5152,7 @@ public static partial class Gen5SpirvTranslator
|
|||
};
|
||||
if (control?.Clamp == true)
|
||||
{
|
||||
value = Ext(43, _floatType, value, Float(0), Float(1));
|
||||
value = Ext(81, _floatType, value, Float(0), Float(1));
|
||||
}
|
||||
|
||||
var half = EmitFloatToHalf(Bitcast(_uintType, value));
|
||||
|
|
@ -5170,7 +5193,7 @@ public static partial class Gen5SpirvTranslator
|
|||
};
|
||||
if (clamp)
|
||||
{
|
||||
value = Ext(43, _floatType, value, Float(0), Float(1));
|
||||
value = Ext(81, _floatType, value, Float(0), Float(1));
|
||||
}
|
||||
|
||||
return Bitcast(_uintType, value);
|
||||
|
|
|
|||
|
|
@ -1398,6 +1398,11 @@ public static partial class Gen5SpirvTranslator
|
|||
_floatType,
|
||||
fragCoord,
|
||||
component);
|
||||
if (component == 3)
|
||||
{
|
||||
value = _module.AddInstruction(SpirvOp.FDiv, _floatType, Float(1f), value);
|
||||
}
|
||||
|
||||
StoreV(vgpr, Bitcast(_uintType, value), guardWithExec: false);
|
||||
}
|
||||
|
||||
|
|
@ -7436,11 +7441,12 @@ public static partial class Gen5SpirvTranslator
|
|||
private List<(uint Register, uint Lane)> FindLaneSpillSlots()
|
||||
{
|
||||
var slots = new List<(uint Register, uint Lane)>();
|
||||
if (UsesSubgroupOperations())
|
||||
if (_emulateWave64)
|
||||
{
|
||||
return slots;
|
||||
}
|
||||
|
||||
var ownsLaneZero = !UsesSubgroupOperations();
|
||||
var readRegisters = _request.Program.Instructions
|
||||
.Where(static instruction => instruction.Opcode == "VReadlaneB32" &&
|
||||
instruction.Sources.Count > 0 &&
|
||||
|
|
@ -7453,7 +7459,7 @@ public static partial class Gen5SpirvTranslator
|
|||
TryGetVectorDestination(instruction, out var register) &&
|
||||
readRegisters.Contains(register) &&
|
||||
TryGetConstantLane(instruction, out var lane) &&
|
||||
lane != 0 &&
|
||||
(lane != 0 || !ownsLaneZero) &&
|
||||
!slots.Contains((register, lane)))
|
||||
{
|
||||
slots.Add((register, lane));
|
||||
|
|
@ -7484,10 +7490,28 @@ public static partial class Gen5SpirvTranslator
|
|||
return false;
|
||||
}
|
||||
|
||||
lane = value & (_waveLaneCount - 1);
|
||||
lane = value & LaneSelectMask;
|
||||
return true;
|
||||
}
|
||||
|
||||
private uint LaneSelectMask => _stage == Gen5SpirvStage.Compute ? _waveLaneCount - 1 : 63u;
|
||||
|
||||
private uint ReadLaneSpillSlot(Gen5ShaderInstruction instruction, uint selectedLane, uint value)
|
||||
{
|
||||
if (instruction.Sources[0].Kind != Gen5OperandKind.VectorRegister)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
var register = instruction.Sources[0].Value;
|
||||
if (TryGetConstantLane(instruction, out var lane))
|
||||
{
|
||||
return _laneSpillSlots.TryGetValue((register, lane), out var slot) ? Load(_uintType, slot) : value;
|
||||
}
|
||||
|
||||
return SelectLaneSpillSlot(register, selectedLane, value);
|
||||
}
|
||||
|
||||
// Folds the spill slots of register into value: selected lane == slot lane reads the slot.
|
||||
private uint SelectLaneSpillSlot(uint register, uint selectedLane, uint value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -214,9 +214,9 @@ public static class Gfx10UnifiedFormat
|
|||
(0, 3, 0, 0, 8), // 8_8
|
||||
(0, 4, 0, 0, 32), // 32
|
||||
(0, 5, 0, 0, 16), // 16_16
|
||||
(0, 6, 0, 0, 10), // 10_11_11
|
||||
(0, 7, 0, 0, 11), // 11_11_10
|
||||
(0, 8, 0, 0, 10), // 10_10_10_2
|
||||
(0, 6, 0, 0, 11), // 10_11_11
|
||||
(0, 7, 0, 0, 10), // 11_11_10
|
||||
(0, 8, 0, 0, 2), // 10_10_10_2
|
||||
(0, 9, 0, 0, 10), // 2_10_10_10
|
||||
(0, 10, 0, 0, 8), // 8_8_8_8
|
||||
(0, 11, 0, 0, 32), // 32_32
|
||||
|
|
@ -225,24 +225,24 @@ public static class Gfx10UnifiedFormat
|
|||
(0, 14, 0, 0, 32), // 32_32_32_32
|
||||
(1, 3, 1, 0, 8),
|
||||
(1, 5, 2, 0, 16),
|
||||
(1, 6, 0, 10, 11),
|
||||
(1, 7, 0, 11, 11),
|
||||
(1, 8, 0, 10, 10),
|
||||
(1, 6, 0, 11, 11),
|
||||
(1, 7, 0, 10, 11),
|
||||
(1, 8, 0, 2, 10),
|
||||
(1, 9, 0, 10, 10),
|
||||
(1, 10, 1, 0, 8),
|
||||
(1, 11, 4, 0, 32),
|
||||
(1, 12, 2, 0, 16),
|
||||
(1, 13, 4, 0, 32),
|
||||
(1, 14, 4, 0, 32),
|
||||
(2, 6, 0, 21, 11),
|
||||
(2, 7, 0, 22, 10),
|
||||
(2, 8, 0, 20, 10),
|
||||
(2, 6, 0, 22, 10),
|
||||
(2, 7, 0, 21, 11),
|
||||
(2, 8, 0, 12, 10),
|
||||
(2, 9, 0, 20, 10),
|
||||
(2, 10, 2, 0, 8),
|
||||
(2, 12, 4, 0, 16),
|
||||
(2, 13, 8, 0, 32),
|
||||
(2, 14, 8, 0, 32),
|
||||
(3, 8, 0, 30, 2),
|
||||
(3, 8, 0, 22, 10),
|
||||
(3, 9, 0, 30, 2),
|
||||
(3, 10, 3, 0, 8),
|
||||
(3, 12, 6, 0, 16),
|
||||
|
|
|
|||
|
|
@ -167,8 +167,7 @@ public static class ResourceMaterializer
|
|||
for (var candidateIndex = 0; candidateIndex < descriptors.Count; candidateIndex++)
|
||||
{
|
||||
var descriptor = descriptors[candidateIndex];
|
||||
if (NullImageDescriptor(descriptor.Dwords) || !ValidImageDescriptor(descriptor.Dwords, image.R128) ||
|
||||
!ReservedImageBitsClear(descriptor.Dwords))
|
||||
if (!UsableImageCandidate(descriptor.Dwords, image.R128))
|
||||
descriptor = DescriptorWords.Empty(8);
|
||||
var existing = directTable.Descriptors.FindIndex(candidate => candidate.SameAs(descriptor));
|
||||
if (existing < 0)
|
||||
|
|
@ -531,6 +530,10 @@ public static class ResourceMaterializer
|
|||
return true;
|
||||
}
|
||||
|
||||
private static bool UsableImageCandidate(ReadOnlySpan<uint> candidate, bool r128) =>
|
||||
!NullImageDescriptor(candidate) && ValidImageDescriptor(candidate, r128) && ReservedImageBitsClear(candidate) &&
|
||||
GuestImageFormat.SampledNumericClass(GuestImageFormat.FormatOf(candidate)) != ImageNumericClass.Unsupported;
|
||||
|
||||
private static ulong ScalarBufferSize(ReadOnlySpan<uint> descriptor)
|
||||
{
|
||||
var stride = (descriptor[1] >> 16) & 0x3FFF;
|
||||
|
|
@ -640,8 +643,7 @@ public static class ResourceMaterializer
|
|||
}
|
||||
}
|
||||
|
||||
if (NullImageDescriptor(candidate) || !ValidImageDescriptor(candidate, image.R128) ||
|
||||
!ReservedImageBitsClear(candidate))
|
||||
if (!UsableImageCandidate(candidate, image.R128))
|
||||
{
|
||||
Array.Clear(candidate);
|
||||
}
|
||||
|
|
@ -694,8 +696,7 @@ public static class ResourceMaterializer
|
|||
return false;
|
||||
}
|
||||
|
||||
if (NullImageDescriptor(candidate) || !ValidImageDescriptor(candidate, r128) ||
|
||||
!ReservedImageBitsClear(candidate))
|
||||
if (!UsableImageCandidate(candidate, r128))
|
||||
Array.Clear(candidate);
|
||||
probed.Add(candidate);
|
||||
}
|
||||
|
|
@ -752,7 +753,7 @@ public static class ResourceMaterializer
|
|||
return false;
|
||||
}
|
||||
|
||||
if (NullImageDescriptor(candidate) || !ValidImageDescriptor(candidate, r128) || !ReservedImageBitsClear(candidate))
|
||||
if (!UsableImageCandidate(candidate, r128))
|
||||
Array.Clear(candidate);
|
||||
probed.Add(candidate);
|
||||
offsets.Add(unchecked(indirect.DynamicOffsetBase + (key << 5)));
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ public sealed partial class ResourceTracker
|
|||
return false;
|
||||
|
||||
var key = scaled.Operands[0];
|
||||
var bound = DenseKeyBound(key);
|
||||
var bound = BoundBySamplerLoads(DenseKeyBound(key), heapHandle, tableOffset);
|
||||
var waveIndexed = TryCreateWaveIndexedImageSelector(key, reads);
|
||||
if (bound == 0 && waveIndexed is null)
|
||||
{
|
||||
|
|
@ -158,6 +158,31 @@ public sealed partial class ResourceTracker
|
|||
return true;
|
||||
}
|
||||
|
||||
private uint BoundBySamplerLoads(uint bound, ScalarValue heapHandle, uint tableOffset)
|
||||
{
|
||||
foreach (var access in _plan.Accesses)
|
||||
{
|
||||
if (access?.SamplerHandle is not { } sampler)
|
||||
continue;
|
||||
|
||||
foreach (var operand in sampler.Operands)
|
||||
{
|
||||
var word = ScalarValueEquivalence.ResolveInvariantPhi(_plan.Memory, operand) ?? operand;
|
||||
if (word.Kind == ScalarValueKind.ResourceTableWord && word.Payload < (ulong)_plan.TableReads.Count)
|
||||
word = _plan.TableReads[(int)word.Payload].Value;
|
||||
if (word.Kind != ScalarValueKind.ScalarAddressWord || word.MemoryIndex < 0 || word.MemoryIndex >= _plan.Memory.Count ||
|
||||
!word.Operands[1].IsConstant || !_graph.Equivalent(word.Operands[0], heapHandle))
|
||||
continue;
|
||||
|
||||
var offset = (ulong)word.Operands[1].ConstantU32 + _plan.Memory[word.MemoryIndex].Offset;
|
||||
if (offset > tableOffset)
|
||||
bound = (uint)Math.Min(bound, (offset - tableOffset) >> (int)DenseIndirectImageShift);
|
||||
}
|
||||
}
|
||||
|
||||
return bound;
|
||||
}
|
||||
|
||||
private uint DenseKeyBound(ScalarValue key)
|
||||
{
|
||||
if (key.Kind == ScalarValueKind.Operation && key.Operation == ScalarOperation.FindLowestBit32 &&
|
||||
|
|
|
|||
|
|
@ -156,6 +156,46 @@ public sealed partial class GuestImageCacheTests
|
|||
harness.Shutdown();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void GuestDccMetadataFilledOnTheGpu_IsDownloadedWhenTheTargetIsSampled(bool storage)
|
||||
{
|
||||
if (!GatePrerequisites.Ready(_vulkan)) return;
|
||||
using var harness = new CacheHarness(_vulkan);
|
||||
const ulong SliceSize = 0x1000;
|
||||
var address = harness.MapBacked(0x20000, ReadWrite);
|
||||
var metadata = address + 0x10000;
|
||||
var sampled = LinearRequest(address + 0xc000, 4, Format.R8G8B8A8Unorm, GuestPixelFormat.Bits8_8_8_8UNorm, GuestImageType.Color2D, new Extent3D(1, 1, 1), 1, 4, 1);
|
||||
var target = AsColorTarget(sampled);
|
||||
var targetId = harness.Find(ref target);
|
||||
var image = harness.Image(targetId);
|
||||
image.Description.TileMode = GuestTileMode.RenderTarget;
|
||||
image.Description.Metadata.Kind = MetadataKind.Dcc;
|
||||
image.Description.Metadata.Range = new GuestSpan(metadata, SliceSize);
|
||||
Assert.Equal(SliceSize, image.Description.DccSliceSize);
|
||||
harness.Write(metadata, Enumerable.Repeat((byte)0x55, (int)SliceSize).ToArray());
|
||||
harness.Worker.Run(() =>
|
||||
{
|
||||
Assert.NotNull(harness.Cache.ObtainBuffer(metadata, SliceSize, isWritten: true).Buffer);
|
||||
harness.Cache.FillBuffer(metadata, SliceSize, 0, isGds: false);
|
||||
});
|
||||
Assert.True(harness.Cache.HasGpuDirtyBytes(metadata, SliceSize));
|
||||
Assert.False(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 0, out _, out _));
|
||||
|
||||
sampled.Description.TileMode = GuestTileMode.RenderTarget;
|
||||
if (storage)
|
||||
{
|
||||
sampled = AsStorage(sampled);
|
||||
}
|
||||
|
||||
Assert.Equal(targetId, harness.Find(ref sampled));
|
||||
Assert.False(harness.Cache.HasGpuDirtyBytes(metadata, SliceSize));
|
||||
Assert.True(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 0, out _, out var code));
|
||||
Assert.Equal(0x00, code);
|
||||
harness.Shutdown();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GuestDccClear_ReadsOnlyUniformClearSlices()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Buffers.Binary;
|
||||
using SharpEmu.Libs.Gpu.Buffers;
|
||||
using SharpEmu.Libs.Tests.Gpu.Images;
|
||||
using SharpEmu.ShaderCompiler;
|
||||
using SharpEmu.ShaderCompiler.Resources;
|
||||
using SharpEmu.ShaderCompiler.Vulkan;
|
||||
using Xunit;
|
||||
using static SharpEmu.ShaderCompiler.Tests.Resources.ResourceTestProgram;
|
||||
|
||||
namespace SharpEmu.Libs.Tests.Gpu.Vulkan;
|
||||
|
||||
public sealed class FloatSemanticsDeviceTests(HeadlessVulkanFixture fixture) : IClassFixture<HeadlessVulkanFixture>
|
||||
{
|
||||
private const uint PackBase = 0xAABB_CCDD;
|
||||
private const uint ThreadCount = 32;
|
||||
private const uint RowBytes = 32;
|
||||
|
||||
[Fact]
|
||||
public void ConversionsAndNanOperandsMatchTheHardwareRules()
|
||||
{
|
||||
var vulkan = fixture.Vulkan;
|
||||
if (!GatePrerequisites.Ready(vulkan, shaderInt64: true)) return;
|
||||
var program = Program(
|
||||
Vop1(0, "VCvtF32U32", 1, Gen5Operand.Vector(0)),
|
||||
MoveScalar(4, 8, Bits(0.25f)),
|
||||
MoveScalar(12, 9, Bits(-2f)),
|
||||
MoveScalar(20, 10, Bits(20f)),
|
||||
MoveScalar(28, 11, Bits(-60f)),
|
||||
MoveScalar(36, 12, Bits(float.NaN)),
|
||||
MoveScalar(44, 13, PackBase),
|
||||
MoveScalar(52, 14, Bits(5f)),
|
||||
MoveScalar(60, 15, Bits(10f)),
|
||||
Vop3(68, "VMadF32", 2, Gen5Operand.Vector(1), Gen5Operand.Scalar(8), Gen5Operand.Scalar(9)),
|
||||
Vop3(76, "VMadF32", 3, Gen5Operand.Vector(1), Gen5Operand.Scalar(10), Gen5Operand.Scalar(11)),
|
||||
Vop1(84, "VCvtRpiI32F32", 12, Gen5Operand.Vector(2)),
|
||||
Vop3(88, "VCvtPkU8F32", 13, Gen5Operand.Vector(3), Operand(1), Gen5Operand.Scalar(13)),
|
||||
Vop2(96, "VMaxF32", 14, Gen5Operand.Scalar(12), Gen5Operand.Vector(1)),
|
||||
Vop2(100, "VMinF32", 15, Gen5Operand.Vector(1), Gen5Operand.Scalar(12)),
|
||||
Vop3(104, "VMed3F32", 16, Gen5Operand.Scalar(12), Gen5Operand.Vector(1), Gen5Operand.Scalar(14)),
|
||||
Vop3(112, "VMed3F32", 17, Gen5Operand.Vector(1), Gen5Operand.Scalar(14), Gen5Operand.Scalar(15)),
|
||||
Vop2(120, "VLshlrevB32", 7, Operand(5), Gen5Operand.Vector(0)),
|
||||
BufferAccess(124, "BufferStoreDwordx4", 4, vectorData: 12, dwords: 4, offsetEnabled: true, vectorAddress: 7),
|
||||
BufferAccess(132, "BufferStoreDword", 4, offset: 16, vectorData: 16, offsetEnabled: true, vectorAddress: 7),
|
||||
BufferAccess(140, "BufferStoreDword", 4, offset: 20, vectorData: 17, offsetEnabled: true, vectorAddress: 7),
|
||||
EndProgram(148));
|
||||
var (plan, resources, layout) = Prepare(program);
|
||||
var request = new ShaderCompileRequest(plan, resources, layout)
|
||||
{
|
||||
LocalSizeX = ThreadCount, ThreadCountX = ThreadCount, WaveSize = 32,
|
||||
};
|
||||
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out var shader, out var error), error);
|
||||
using var harness = new ImageTestHarness(vulkan);
|
||||
using var runner = new LayoutComputeRunner(harness, request, shader.Spirv);
|
||||
var result = runner.CreateBuffer(ThreadCount * RowBytes);
|
||||
var registers = new uint[256];
|
||||
registers[6] = ThreadCount * RowBytes;
|
||||
harness.Run(() => runner.Dispatch(registers,
|
||||
new Dictionary<DescriptorBindingKind, GpuBuffer[]> { [DescriptorBindingKind.Buffers] = [result] }, 1));
|
||||
var actual = runner.ReadBack(result, 0, ThreadCount * RowBytes);
|
||||
for (var lane = 0u; lane < ThreadCount; lane++)
|
||||
{
|
||||
var row = actual.AsSpan((int)(lane * RowBytes));
|
||||
var rounded = (int)MathF.Floor(lane * 0.25f - 2f + 0.5f);
|
||||
var packed = (uint)Math.Clamp(lane * 20f - 60f, 0f, 255f);
|
||||
Assert.Equal(rounded, BinaryPrimitives.ReadInt32LittleEndian(row));
|
||||
Assert.Equal((PackBase & ~0xFF00u) | (packed << 8), BinaryPrimitives.ReadUInt32LittleEndian(row[4..]));
|
||||
Assert.Equal(Bits(lane), BinaryPrimitives.ReadUInt32LittleEndian(row[8..]));
|
||||
Assert.Equal(Bits(lane), BinaryPrimitives.ReadUInt32LittleEndian(row[12..]));
|
||||
Assert.Equal(Bits(MathF.Min(lane, 5f)), BinaryPrimitives.ReadUInt32LittleEndian(row[16..]));
|
||||
Assert.Equal(Bits(Math.Clamp(lane, 5f, 10f)), BinaryPrimitives.ReadUInt32LittleEndian(row[20..]));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConditionalMaskAppliesSourceSignModifiers()
|
||||
{
|
||||
var vulkan = fixture.Vulkan;
|
||||
if (!GatePrerequisites.Ready(vulkan, shaderInt64: true)) return;
|
||||
var negateFirst = new Gen5Vop3Control(0, 1, 0, false, 0, null);
|
||||
var absoluteBothNegateSecond = new Gen5Vop3Control(3, 2, 0, false, 0, null);
|
||||
var program = Program(
|
||||
Vop1(0, "VCvtF32U32", 1, Gen5Operand.Vector(0)),
|
||||
MoveScalar(4, 8, Bits(1f)),
|
||||
MoveScalar(12, 9, Bits(-8f)),
|
||||
MoveScalar(20, 20, uint.MaxValue),
|
||||
MoveScalar(28, 21, uint.MaxValue),
|
||||
MoveScalar(36, 22, 0),
|
||||
MoveScalar(44, 23, 0),
|
||||
Vop3(52, "VMadF32", 2, Gen5Operand.Vector(1), Gen5Operand.Scalar(8), Gen5Operand.Scalar(9)),
|
||||
Vop3(60, "VCndmaskB32", 12, Gen5Operand.Vector(2), Gen5Operand.Vector(2), Gen5Operand.Scalar(22)) with { Control = negateFirst },
|
||||
Vop3(68, "VCndmaskB32", 13, Gen5Operand.Vector(2), Gen5Operand.Vector(2), Gen5Operand.Scalar(20)) with { Control = negateFirst },
|
||||
Vop3(76, "VCndmaskB32", 14, Gen5Operand.Vector(2), Gen5Operand.Vector(2), Gen5Operand.Scalar(20)) with { Control = absoluteBothNegateSecond },
|
||||
Vop3(84, "VCndmaskB32", 15, Gen5Operand.Vector(2), Gen5Operand.Vector(2), Gen5Operand.Scalar(22)) with { Control = absoluteBothNegateSecond },
|
||||
Vop2(92, "VLshlrevB32", 7, Operand(5), Gen5Operand.Vector(0)),
|
||||
BufferAccess(96, "BufferStoreDwordx4", 4, vectorData: 12, dwords: 4, offsetEnabled: true, vectorAddress: 7),
|
||||
EndProgram(104));
|
||||
var (plan, resources, layout) = Prepare(program);
|
||||
var request = new ShaderCompileRequest(plan, resources, layout)
|
||||
{
|
||||
LocalSizeX = ThreadCount, ThreadCountX = ThreadCount, WaveSize = 32,
|
||||
};
|
||||
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out var shader, out var error), error);
|
||||
using var harness = new ImageTestHarness(vulkan);
|
||||
using var runner = new LayoutComputeRunner(harness, request, shader.Spirv);
|
||||
var result = runner.CreateBuffer(ThreadCount * RowBytes);
|
||||
var registers = new uint[256];
|
||||
registers[6] = ThreadCount * RowBytes;
|
||||
harness.Run(() => runner.Dispatch(registers,
|
||||
new Dictionary<DescriptorBindingKind, GpuBuffer[]> { [DescriptorBindingKind.Buffers] = [result] }, 1));
|
||||
var actual = runner.ReadBack(result, 0, ThreadCount * RowBytes);
|
||||
for (var lane = 0u; lane < ThreadCount; lane++)
|
||||
{
|
||||
var row = actual.AsSpan((int)(lane * RowBytes));
|
||||
var value = Bits(lane - 8f);
|
||||
Assert.Equal(value ^ 0x8000_0000, BinaryPrimitives.ReadUInt32LittleEndian(row));
|
||||
Assert.Equal(value, BinaryPrimitives.ReadUInt32LittleEndian(row[4..]));
|
||||
Assert.Equal(value | 0x8000_0000, BinaryPrimitives.ReadUInt32LittleEndian(row[8..]));
|
||||
Assert.Equal(value & 0x7FFF_FFFF, BinaryPrimitives.ReadUInt32LittleEndian(row[12..]));
|
||||
}
|
||||
}
|
||||
|
||||
private static uint Bits(float value) => BitConverter.SingleToUInt32Bits(value);
|
||||
}
|
||||
|
|
@ -203,8 +203,8 @@ public sealed class GlobalMemoryShaderTests(HeadlessVulkanFixture fixture, ITest
|
|||
private const uint Format16Float = 13;
|
||||
private const uint Format8x4Snorm = 57;
|
||||
private const uint Format8x4Sint = 61;
|
||||
private const uint Format10x3x2Uint = 48;
|
||||
private const uint Format11x2x10Float = 43;
|
||||
private const uint Format2x10x3Uint = 48;
|
||||
private const uint Format10x11x2Float = 43;
|
||||
private const uint BoundDescriptor = (Format32x4Float << 12) | IdentitySwizzle;
|
||||
private const float Half = 0.5f;
|
||||
|
||||
|
|
@ -250,8 +250,8 @@ public sealed class GlobalMemoryShaderTests(HeadlessVulkanFixture fixture, ITest
|
|||
yield return [Format8x4Unorm, BoundDescriptor, 7u, (uint)MemoryOffset, Floats(1f, Half, 0f, -1f), Bytes(0xFF, 0x80, 0x00, 0x00)];
|
||||
yield return [Format8x4Snorm, BoundDescriptor, 7u, (uint)MemoryOffset, Floats(1f, -1f, Half, -Half), Bytes(0x7F, 0x81, 0x40, 0xC0)];
|
||||
yield return [Format8x4Sint, BoundDescriptor, 7u, (uint)MemoryOffset, Words(unchecked((uint)-1), 127, unchecked((uint)-128), 200), Bytes(0xFF, 0x7F, 0x80, 0x7F)];
|
||||
yield return [Format10x3x2Uint, BoundDescriptor, 7u, (uint)MemoryOffset, Words(1, 2, 3, 1), Bytes(0x01, 0x08, 0x30, 0x40)];
|
||||
yield return [Format11x2x10Float, BoundDescriptor, 7u, (uint)MemoryOffset, Floats(1f, 2f, Half, 0), Bytes(0xC0, 0x03, 0x20, 0x70)];
|
||||
yield return [Format2x10x3Uint, BoundDescriptor, 7u, (uint)MemoryOffset, Words(1, 2, 3, 1), Bytes(0x09, 0x30, 0x40, 0x00)];
|
||||
yield return [Format10x11x2Float, BoundDescriptor, 7u, (uint)MemoryOffset, Floats(1f, 2f, Half, 0), Bytes(0xE0, 0x01, 0x10, 0x70)];
|
||||
yield return [Format32x4Float, (Format8x4Unorm << 12) | IdentitySwizzle, 7u, (uint)MemoryOffset, Words(0xABCD1200, 0xABCD1201, 0xABCD1202, 0xABCD1203), Bytes(0x00, 0x12, 0xCD, 0xAB, 0x01, 0x12, 0xCD, 0xAB, 0x02, 0x12, 0xCD, 0xAB, 0x03, 0x12, 0xCD, 0xAB)];
|
||||
yield return [Format32x4Float, 0u, 7u, (uint)MemoryOffset, Words(1, 2, 3, 4), null!];
|
||||
// An element that crosses the end of the binding is not written at all.
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ static inline void sharpemu_format_layout(uint dfmt, uint component, thread uint
|
|||
case 3u: byteOff = 0u; bitOff = 0u; bits = 8u; break;
|
||||
case 4u: byteOff = 0u; bitOff = 0u; bits = 32u; break;
|
||||
case 5u: byteOff = 0u; bitOff = 0u; bits = 16u; break;
|
||||
case 6u: byteOff = 0u; bitOff = 0u; bits = 10u; break;
|
||||
case 7u: byteOff = 0u; bitOff = 0u; bits = 11u; break;
|
||||
case 8u: byteOff = 0u; bitOff = 0u; bits = 10u; break;
|
||||
case 6u: byteOff = 0u; bitOff = 0u; bits = 11u; break;
|
||||
case 7u: byteOff = 0u; bitOff = 0u; bits = 10u; break;
|
||||
case 8u: byteOff = 0u; bitOff = 0u; bits = 2u; break;
|
||||
case 9u: byteOff = 0u; bitOff = 0u; bits = 10u; break;
|
||||
case 10u: byteOff = 0u; bitOff = 0u; bits = 8u; break;
|
||||
case 11u: byteOff = 0u; bitOff = 0u; bits = 32u; break;
|
||||
|
|
@ -35,24 +35,24 @@ static inline void sharpemu_format_layout(uint dfmt, uint component, thread uint
|
|||
case 14u: byteOff = 0u; bitOff = 0u; bits = 32u; break;
|
||||
case 19u: byteOff = 1u; bitOff = 0u; bits = 8u; break;
|
||||
case 21u: byteOff = 2u; bitOff = 0u; bits = 16u; break;
|
||||
case 22u: byteOff = 0u; bitOff = 10u; bits = 11u; break;
|
||||
case 23u: byteOff = 0u; bitOff = 11u; bits = 11u; break;
|
||||
case 24u: byteOff = 0u; bitOff = 10u; bits = 10u; break;
|
||||
case 22u: byteOff = 0u; bitOff = 11u; bits = 11u; break;
|
||||
case 23u: byteOff = 0u; bitOff = 10u; bits = 11u; break;
|
||||
case 24u: byteOff = 0u; bitOff = 2u; bits = 10u; break;
|
||||
case 25u: byteOff = 0u; bitOff = 10u; bits = 10u; break;
|
||||
case 26u: byteOff = 1u; bitOff = 0u; bits = 8u; break;
|
||||
case 27u: byteOff = 4u; bitOff = 0u; bits = 32u; break;
|
||||
case 28u: byteOff = 2u; bitOff = 0u; bits = 16u; break;
|
||||
case 29u: byteOff = 4u; bitOff = 0u; bits = 32u; break;
|
||||
case 30u: byteOff = 4u; bitOff = 0u; bits = 32u; break;
|
||||
case 38u: byteOff = 0u; bitOff = 21u; bits = 11u; break;
|
||||
case 39u: byteOff = 0u; bitOff = 22u; bits = 10u; break;
|
||||
case 40u: byteOff = 0u; bitOff = 20u; bits = 10u; break;
|
||||
case 38u: byteOff = 0u; bitOff = 22u; bits = 10u; break;
|
||||
case 39u: byteOff = 0u; bitOff = 21u; bits = 11u; break;
|
||||
case 40u: byteOff = 0u; bitOff = 12u; bits = 10u; break;
|
||||
case 41u: byteOff = 0u; bitOff = 20u; bits = 10u; break;
|
||||
case 42u: byteOff = 2u; bitOff = 0u; bits = 8u; break;
|
||||
case 44u: byteOff = 4u; bitOff = 0u; bits = 16u; break;
|
||||
case 45u: byteOff = 8u; bitOff = 0u; bits = 32u; break;
|
||||
case 46u: byteOff = 8u; bitOff = 0u; bits = 32u; break;
|
||||
case 56u: byteOff = 0u; bitOff = 30u; bits = 2u; break;
|
||||
case 56u: byteOff = 0u; bitOff = 22u; bits = 10u; break;
|
||||
case 57u: byteOff = 0u; bitOff = 30u; bits = 2u; break;
|
||||
case 58u: byteOff = 3u; bitOff = 0u; bits = 8u; break;
|
||||
case 60u: byteOff = 6u; bitOff = 0u; bits = 16u; break;
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ static inline void sharpemu_format_layout(uint dfmt, uint component, thread uint
|
|||
case 3u: byteOff = 0u; bitOff = 0u; bits = 8u; break;
|
||||
case 4u: byteOff = 0u; bitOff = 0u; bits = 32u; break;
|
||||
case 5u: byteOff = 0u; bitOff = 0u; bits = 16u; break;
|
||||
case 6u: byteOff = 0u; bitOff = 0u; bits = 10u; break;
|
||||
case 7u: byteOff = 0u; bitOff = 0u; bits = 11u; break;
|
||||
case 8u: byteOff = 0u; bitOff = 0u; bits = 10u; break;
|
||||
case 6u: byteOff = 0u; bitOff = 0u; bits = 11u; break;
|
||||
case 7u: byteOff = 0u; bitOff = 0u; bits = 10u; break;
|
||||
case 8u: byteOff = 0u; bitOff = 0u; bits = 2u; break;
|
||||
case 9u: byteOff = 0u; bitOff = 0u; bits = 10u; break;
|
||||
case 10u: byteOff = 0u; bitOff = 0u; bits = 8u; break;
|
||||
case 11u: byteOff = 0u; bitOff = 0u; bits = 32u; break;
|
||||
|
|
@ -35,24 +35,24 @@ static inline void sharpemu_format_layout(uint dfmt, uint component, thread uint
|
|||
case 14u: byteOff = 0u; bitOff = 0u; bits = 32u; break;
|
||||
case 19u: byteOff = 1u; bitOff = 0u; bits = 8u; break;
|
||||
case 21u: byteOff = 2u; bitOff = 0u; bits = 16u; break;
|
||||
case 22u: byteOff = 0u; bitOff = 10u; bits = 11u; break;
|
||||
case 23u: byteOff = 0u; bitOff = 11u; bits = 11u; break;
|
||||
case 24u: byteOff = 0u; bitOff = 10u; bits = 10u; break;
|
||||
case 22u: byteOff = 0u; bitOff = 11u; bits = 11u; break;
|
||||
case 23u: byteOff = 0u; bitOff = 10u; bits = 11u; break;
|
||||
case 24u: byteOff = 0u; bitOff = 2u; bits = 10u; break;
|
||||
case 25u: byteOff = 0u; bitOff = 10u; bits = 10u; break;
|
||||
case 26u: byteOff = 1u; bitOff = 0u; bits = 8u; break;
|
||||
case 27u: byteOff = 4u; bitOff = 0u; bits = 32u; break;
|
||||
case 28u: byteOff = 2u; bitOff = 0u; bits = 16u; break;
|
||||
case 29u: byteOff = 4u; bitOff = 0u; bits = 32u; break;
|
||||
case 30u: byteOff = 4u; bitOff = 0u; bits = 32u; break;
|
||||
case 38u: byteOff = 0u; bitOff = 21u; bits = 11u; break;
|
||||
case 39u: byteOff = 0u; bitOff = 22u; bits = 10u; break;
|
||||
case 40u: byteOff = 0u; bitOff = 20u; bits = 10u; break;
|
||||
case 38u: byteOff = 0u; bitOff = 22u; bits = 10u; break;
|
||||
case 39u: byteOff = 0u; bitOff = 21u; bits = 11u; break;
|
||||
case 40u: byteOff = 0u; bitOff = 12u; bits = 10u; break;
|
||||
case 41u: byteOff = 0u; bitOff = 20u; bits = 10u; break;
|
||||
case 42u: byteOff = 2u; bitOff = 0u; bits = 8u; break;
|
||||
case 44u: byteOff = 4u; bitOff = 0u; bits = 16u; break;
|
||||
case 45u: byteOff = 8u; bitOff = 0u; bits = 32u; break;
|
||||
case 46u: byteOff = 8u; bitOff = 0u; bits = 32u; break;
|
||||
case 56u: byteOff = 0u; bitOff = 30u; bits = 2u; break;
|
||||
case 56u: byteOff = 0u; bitOff = 22u; bits = 10u; break;
|
||||
case 57u: byteOff = 0u; bitOff = 30u; bits = 2u; break;
|
||||
case 58u: byteOff = 3u; bitOff = 0u; bits = 8u; break;
|
||||
case 60u: byteOff = 6u; bitOff = 0u; bits = 16u; break;
|
||||
|
|
|
|||
|
|
@ -193,6 +193,67 @@ public sealed class Gen5InterpolationParameterTests
|
|||
ValidateWhenAvailable(shader.Spirv);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x800u, 1)]
|
||||
[InlineData(0x400u, 0)]
|
||||
public void PositionW_IsTheReciprocalOfTheFragmentCoordinate(uint inputs, int reciprocals)
|
||||
{
|
||||
var program = ResourceTestProgram.Program(ResourceTestProgram.EndProgram(0));
|
||||
var (plan, resources, layout) = ResourceTestProgram.Prepare(program, ShaderStage.Pixel, userDataCount: 0);
|
||||
var request = new ShaderCompileRequest(plan, resources, layout) { PixelInputAddress = inputs, PixelInputEnable = inputs };
|
||||
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out var shader, out var error), error);
|
||||
var instructions = Instructions(shader.Spirv);
|
||||
var position = Assert.Single(instructions, instruction => instruction.Opcode == SpirvOp.Decorate &&
|
||||
instruction.Operands[1] == (uint)SpirvDecoration.BuiltIn && instruction.Operands[2] == (uint)SpirvBuiltIn.FragCoord).Operands[0];
|
||||
var loaded = instructions.Where(instruction => instruction.Opcode == SpirvOp.Load && instruction.Operands[2] == position)
|
||||
.Select(instruction => instruction.Operands[1]).ToHashSet();
|
||||
var w = instructions.Where(instruction => instruction.Opcode == SpirvOp.CompositeExtract &&
|
||||
loaded.Contains(instruction.Operands[2]) && instruction.Operands[3] == 3).Select(instruction => instruction.Operands[1]).ToHashSet();
|
||||
Assert.Equal(reciprocals, instructions.Count(instruction => instruction.Opcode == SpirvOp.FDiv && w.Contains(instruction.Operands[3])));
|
||||
ValidateWhenAvailable(shader.Spirv);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ShaderStage.Pixel, 32u)]
|
||||
[InlineData(ShaderStage.Pixel, 64u)]
|
||||
[InlineData(ShaderStage.Vertex, 32u)]
|
||||
[InlineData(ShaderStage.Vertex, 64u)]
|
||||
public void LaneSpills_AreReadBackWithoutTheHostSubgroup(ShaderStage stage, uint waveSize)
|
||||
{
|
||||
var program = ResourceTestProgram.Program(
|
||||
ResourceTestProgram.WriteLane(0, vectorRegister: 18, scalarRegister: 84, lane: 5),
|
||||
ResourceTestProgram.WriteLane(8, vectorRegister: 18, scalarRegister: 85, lane: 37),
|
||||
ResourceTestProgram.ReadLane(16, scalarRegister: 86, vectorRegister: 18, lane: 5),
|
||||
ResourceTestProgram.ReadLane(24, scalarRegister: 87, vectorRegister: 18, lane: 37),
|
||||
ResourceTestProgram.EndProgram(32));
|
||||
var (plan, resources, layout) = ResourceTestProgram.Prepare(program, stage, userDataCount: 0);
|
||||
var request = new ShaderCompileRequest(plan, resources, layout) { WaveSize = waveSize, EnableGraphicsSubgroupOperations = true };
|
||||
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out var shader, out var error), error);
|
||||
Assert.DoesNotContain(Instructions(shader.Spirv), instruction => instruction.Opcode == SpirvOp.GroupNonUniformBroadcast);
|
||||
var text = System.Text.Encoding.ASCII.GetString(shader.Spirv);
|
||||
Assert.Contains("v18_lane5", text, StringComparison.Ordinal);
|
||||
Assert.Contains("v18_lane37", text, StringComparison.Ordinal);
|
||||
ValidateWhenAvailable(shader.Spirv);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ShaderStage.Pixel, 0)]
|
||||
[InlineData(ShaderStage.Vertex, 0)]
|
||||
[InlineData(ShaderStage.Compute, 2)]
|
||||
public void ReadlaneOfAnUnspilledLane_UsesTheHostSubgroupOnlyInCompute(ShaderStage stage, int broadcasts)
|
||||
{
|
||||
var program = ResourceTestProgram.Program(
|
||||
ResourceTestProgram.WriteLane(0, vectorRegister: 18, scalarRegister: 84, lane: 5),
|
||||
ResourceTestProgram.ReadLane(8, scalarRegister: 86, vectorRegister: 18, lane: 6),
|
||||
ResourceTestProgram.ReadLane(16, scalarRegister: 87, vectorRegister: 19, lane: 5),
|
||||
ResourceTestProgram.EndProgram(24));
|
||||
var (plan, resources, layout) = ResourceTestProgram.Prepare(program, stage, userDataCount: 0);
|
||||
var request = new ShaderCompileRequest(plan, resources, layout) { WaveSize = 32, EnableGraphicsSubgroupOperations = true };
|
||||
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out var shader, out var error), error);
|
||||
Assert.Equal(broadcasts, Instructions(shader.Spirv).Count(instruction => instruction.Opcode == SpirvOp.GroupNonUniformBroadcast));
|
||||
ValidateWhenAvailable(shader.Spirv);
|
||||
}
|
||||
|
||||
private static ShaderCompileRequest Request(
|
||||
uint selector, bool custom, uint inputs = 2, string opcode = "VInterpMovF32",
|
||||
uint inputCntl = 0x401, bool supportsPerVertex = true)
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ public sealed class Gen5ScalarLaneTransferTests
|
|||
|
||||
[Theory]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(true, true)]
|
||||
public void OneLaneWaveKeepsSgprsSpilledToOtherLanes(bool subgroups, bool expectSlot)
|
||||
{
|
||||
// Astro Bot's skinning vertex shader saves EXEC this way and restores it before a
|
||||
|
|
|
|||
|
|
@ -148,6 +148,24 @@ public sealed class DirectImageTableTests
|
|||
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)]
|
||||
|
|
@ -365,6 +383,47 @@ public sealed class DirectImageTableTests
|
|||
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)]
|
||||
|
|
@ -453,6 +512,32 @@ public sealed class DirectImageTableTests
|
|||
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)]
|
||||
|
|
|
|||
Loading…
Reference in New Issue