mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-30 13:24:19 +08:00
fix(agc): refine vertex layouts without rebasing captured inputs (#841)
This commit is contained in:
@@ -238,10 +238,12 @@ internal static class AgcVertexMetadata
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Patch IR-discovered fetches from the attrib table onto the V# format/offset.
|
/// Patch IR-discovered fetches from the attrib table onto the V# layout.
|
||||||
/// Prefer 1:1 Location pairing when counts match on one interleaved stream
|
/// Prefer 1:1 Location pairing when counts match on one interleaved stream
|
||||||
/// (GTA UI glyphs). Otherwise match by stride + byte offset. Never rebases
|
/// (GTA UI glyphs). Otherwise match by the effective captured byte offset.
|
||||||
/// BaseAddress/Data/Location/Pc/PerInstance.
|
/// Never rebases BaseAddress/Data/Location/Pc or overwrites a discovered
|
||||||
|
/// offset: metadata may refine the format, stride and instance rate only
|
||||||
|
/// after both address keys independently resolve to the same attribute.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static IReadOnlyList<Gen5VertexInputBinding> MergeVertexInputsFromMetadata(
|
internal static IReadOnlyList<Gen5VertexInputBinding> MergeVertexInputsFromMetadata(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
@@ -269,13 +271,13 @@ internal static class AgcVertexMetadata
|
|||||||
var changed = false;
|
var changed = false;
|
||||||
foreach (var input in discovered)
|
foreach (var input in discovered)
|
||||||
{
|
{
|
||||||
if (!TryMatchMetadataResource(input, resources, usedResources, out var resource, out var fillOffset))
|
if (!TryMatchMetadataResource(input, resources, usedResources, out var resource))
|
||||||
{
|
{
|
||||||
merged.Add(input);
|
merged.Add(input);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var refined = ApplyMetadataFormat(input, resource, fillOffset);
|
var refined = ApplyMetadataFormat(input, resource);
|
||||||
changed |= refined != input;
|
changed |= refined != input;
|
||||||
merged.Add(refined);
|
merged.Add(refined);
|
||||||
}
|
}
|
||||||
@@ -286,7 +288,7 @@ internal static class AgcVertexMetadata
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// When discovery and metadata describe the same interleaved stream with
|
/// When discovery and metadata describe the same interleaved stream with
|
||||||
/// equal attribute counts, pair by sorted Location (semantic order).
|
/// equal attribute counts, pair by sorted Location (semantic order).
|
||||||
/// Keeps each binding's Pc/Location for SPIR-V; overlays format + offset.
|
/// Keeps each binding's Pc/Location/address for SPIR-V and overlays layout.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static bool TryMergeByLocationPairing(
|
private static bool TryMergeByLocationPairing(
|
||||||
IReadOnlyList<Gen5VertexInputBinding> discovered,
|
IReadOnlyList<Gen5VertexInputBinding> discovered,
|
||||||
@@ -299,34 +301,36 @@ internal static class AgcVertexMetadata
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var orderedInputs = discovered.OrderBy(static input => input.Location).ToArray();
|
var orderedInputs = discovered
|
||||||
|
.Select(static (input, originalIndex) => (Input: input, OriginalIndex: originalIndex))
|
||||||
|
.OrderBy(static entry => entry.Input.Location)
|
||||||
|
.ThenBy(static entry => entry.OriginalIndex)
|
||||||
|
.ToArray();
|
||||||
var orderedResources = resources.OrderBy(static resource => resource.Location).ToArray();
|
var orderedResources = resources.OrderBy(static resource => resource.Location).ToArray();
|
||||||
var streamBase = orderedResources[0].SharpBase;
|
var streamBase = orderedResources[0].SharpBase;
|
||||||
var streamStride = orderedResources[0].Stride;
|
var streamStride = orderedResources[0].Stride;
|
||||||
for (var index = 0; index < orderedResources.Length; index++)
|
for (var index = 0; index < orderedResources.Length; index++)
|
||||||
{
|
{
|
||||||
var resource = orderedResources[index];
|
var resource = orderedResources[index];
|
||||||
var input = orderedInputs[index];
|
var input = orderedInputs[index].Input;
|
||||||
if (resource.SharpBase != streamBase ||
|
if (resource.SharpBase != streamBase ||
|
||||||
resource.Stride != streamStride ||
|
resource.Stride != streamStride ||
|
||||||
(input.Stride != 0 && input.Stride != streamStride) ||
|
!TryGetMetadataOffset(input, resource, out var resolvedOffset) ||
|
||||||
!IsSameVertexStream(input, resource))
|
resolvedOffset != input.OffsetBytes)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var byPc = new Dictionary<uint, Gen5VertexInputBinding>(discovered.Count);
|
var result = discovered.ToArray();
|
||||||
var changed = false;
|
var changed = false;
|
||||||
for (var index = 0; index < orderedInputs.Length; index++)
|
for (var index = 0; index < orderedInputs.Length; index++)
|
||||||
{
|
{
|
||||||
var input = orderedInputs[index];
|
var input = orderedInputs[index].Input;
|
||||||
var resource = orderedResources[index];
|
var resource = orderedResources[index];
|
||||||
var fillOffset = input.BaseAddress == resource.SharpBase ||
|
var refined = ApplyMetadataFormat(input, resource);
|
||||||
IsAddressInsideCapturedSpan(input, resource.SharpBase);
|
|
||||||
var refined = ApplyMetadataFormat(input, resource, fillOffset);
|
|
||||||
changed |= refined != input;
|
changed |= refined != input;
|
||||||
byPc[input.Pc] = refined;
|
result[orderedInputs[index].OriginalIndex] = refined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!changed)
|
if (!changed)
|
||||||
@@ -334,20 +338,13 @@ internal static class AgcVertexMetadata
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new Gen5VertexInputBinding[discovered.Count];
|
|
||||||
for (var index = 0; index < discovered.Count; index++)
|
|
||||||
{
|
|
||||||
result[index] = byPc[discovered[index].Pc];
|
|
||||||
}
|
|
||||||
|
|
||||||
merged = result;
|
merged = result;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Gen5VertexInputBinding ApplyMetadataFormat(
|
private static Gen5VertexInputBinding ApplyMetadataFormat(
|
||||||
Gen5VertexInputBinding input,
|
Gen5VertexInputBinding input,
|
||||||
MetadataVertexResource resource,
|
MetadataVertexResource resource)
|
||||||
bool fillOffsetBytes)
|
|
||||||
{
|
{
|
||||||
var components = input.ComponentCount != 0 &&
|
var components = input.ComponentCount != 0 &&
|
||||||
input.ComponentCount < resource.ComponentCount
|
input.ComponentCount < resource.ComponentCount
|
||||||
@@ -359,7 +356,8 @@ internal static class AgcVertexMetadata
|
|||||||
DataFormat = resource.DataFormat,
|
DataFormat = resource.DataFormat,
|
||||||
NumberFormat = resource.NumberFormat,
|
NumberFormat = resource.NumberFormat,
|
||||||
ComponentCount = components,
|
ComponentCount = components,
|
||||||
OffsetBytes = fillOffsetBytes ? resource.OffsetBytes : input.OffsetBytes,
|
Stride = resource.Stride,
|
||||||
|
PerInstance = resource.PerInstance,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,14 +432,11 @@ internal static class AgcVertexMetadata
|
|||||||
Gen5VertexInputBinding input,
|
Gen5VertexInputBinding input,
|
||||||
IReadOnlyList<MetadataVertexResource> resources,
|
IReadOnlyList<MetadataVertexResource> resources,
|
||||||
bool[] usedResources,
|
bool[] usedResources,
|
||||||
out MetadataVertexResource resource,
|
out MetadataVertexResource resource)
|
||||||
out bool fillOffsetBytes)
|
|
||||||
{
|
{
|
||||||
resource = default;
|
resource = default;
|
||||||
fillOffsetBytes = false;
|
|
||||||
var bestScore = int.MinValue;
|
var bestScore = int.MinValue;
|
||||||
var bestIndex = -1;
|
var bestIndex = -1;
|
||||||
var bestFillOffset = false;
|
|
||||||
for (var index = 0; index < resources.Count; index++)
|
for (var index = 0; index < resources.Count; index++)
|
||||||
{
|
{
|
||||||
if (usedResources[index])
|
if (usedResources[index])
|
||||||
@@ -450,100 +445,64 @@ internal static class AgcVertexMetadata
|
|||||||
}
|
}
|
||||||
|
|
||||||
var candidate = resources[index];
|
var candidate = resources[index];
|
||||||
if (candidate.Stride != 0 &&
|
if (!TryGetMetadataOffset(input, candidate, out var resolvedOffset) ||
|
||||||
input.Stride != 0 &&
|
resolvedOffset != input.OffsetBytes)
|
||||||
candidate.Stride != input.Stride)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsSameVertexStream(input, candidate))
|
// The effective captured offset (including any base rebasing done
|
||||||
{
|
// while coalescing adjacent vertex streams) is the primary key.
|
||||||
continue;
|
var score = 400;
|
||||||
}
|
|
||||||
|
|
||||||
var attrAddress = candidate.SharpBase + candidate.OffsetBytes;
|
// Discovery can carry a stale inferred stride (notably 32 for a
|
||||||
var score = int.MinValue;
|
// real stride-40 interleaved layout). Prefer a matching stride
|
||||||
var fillOffset = false;
|
// when candidates are otherwise equivalent, but do not reject an
|
||||||
|
// unambiguous metadata match: the V# descriptor is authoritative.
|
||||||
// Post-capture interleaved: shared BaseAddress, distinct OffsetBytes.
|
if (input.Stride == candidate.Stride)
|
||||||
if (input.OffsetBytes == candidate.OffsetBytes &&
|
|
||||||
(input.BaseAddress == candidate.SharpBase ||
|
|
||||||
IsAddressInsideCapturedSpan(input, candidate.SharpBase)))
|
|
||||||
{
|
{
|
||||||
score = 400;
|
score += 25;
|
||||||
}
|
|
||||||
// IR prolog baked attrib offset into the V# base.
|
|
||||||
else if (input.BaseAddress == attrAddress)
|
|
||||||
{
|
|
||||||
score = 350;
|
|
||||||
}
|
|
||||||
// Discovery never saw the attrib offset — only safe when this
|
|
||||||
// resource's offset uniquely identifies it among unused entries.
|
|
||||||
else if (input.BaseAddress == candidate.SharpBase &&
|
|
||||||
input.OffsetBytes == 0 &&
|
|
||||||
candidate.OffsetBytes != 0 &&
|
|
||||||
IsUniqueUnusedOffset(resources, usedResources, candidate.OffsetBytes, index))
|
|
||||||
{
|
|
||||||
score = 300;
|
|
||||||
fillOffset = true;
|
|
||||||
}
|
|
||||||
else if (input.BaseAddress == candidate.SharpBase &&
|
|
||||||
input.OffsetBytes == 0 &&
|
|
||||||
candidate.OffsetBytes == 0)
|
|
||||||
{
|
|
||||||
score = 250;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (score > bestScore)
|
if (score > bestScore)
|
||||||
{
|
{
|
||||||
bestScore = score;
|
bestScore = score;
|
||||||
bestIndex = index;
|
bestIndex = index;
|
||||||
bestFillOffset = fillOffset;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Require an offset-aware match. Bare SharpBase ties (score 250) are
|
if (bestIndex < 0)
|
||||||
// only accepted when a single unused resource remains for that stream.
|
|
||||||
if (bestIndex < 0 || bestScore < 300)
|
|
||||||
{
|
{
|
||||||
if (bestIndex < 0 || bestScore < 250)
|
return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var unusedSameStream = 0;
|
|
||||||
for (var index = 0; index < resources.Count; index++)
|
|
||||||
{
|
|
||||||
if (!usedResources[index] && IsSameVertexStream(input, resources[index]))
|
|
||||||
{
|
|
||||||
unusedSameStream++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unusedSameStream != 1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
usedResources[bestIndex] = true;
|
usedResources[bestIndex] = true;
|
||||||
resource = resources[bestIndex];
|
resource = resources[bestIndex];
|
||||||
fillOffsetBytes = bestFillOffset;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsSameVertexStream(
|
private static bool TryGetMetadataOffset(
|
||||||
Gen5VertexInputBinding input,
|
Gen5VertexInputBinding input,
|
||||||
MetadataVertexResource resource)
|
MetadataVertexResource resource,
|
||||||
|
out uint offsetBytes)
|
||||||
{
|
{
|
||||||
if (input.BaseAddress == resource.SharpBase ||
|
offsetBytes = input.OffsetBytes;
|
||||||
input.BaseAddress == resource.SharpBase + resource.OffsetBytes)
|
if (resource.SharpBase < input.BaseAddress ||
|
||||||
|
(!IsAddressInsideCapturedSpan(input, resource.SharpBase) &&
|
||||||
|
resource.SharpBase != input.BaseAddress))
|
||||||
{
|
{
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return IsAddressInsideCapturedSpan(input, resource.SharpBase);
|
var relativeBase = resource.SharpBase - input.BaseAddress;
|
||||||
|
var resolvedOffset = relativeBase + resource.OffsetBytes;
|
||||||
|
if (resolvedOffset > uint.MaxValue)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
offsetBytes = (uint)resolvedOffset;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsAddressInsideCapturedSpan(
|
private static bool IsAddressInsideCapturedSpan(
|
||||||
@@ -553,28 +512,6 @@ internal static class AgcVertexMetadata
|
|||||||
address >= input.BaseAddress &&
|
address >= input.BaseAddress &&
|
||||||
address < input.BaseAddress + (ulong)input.DataLength;
|
address < input.BaseAddress + (ulong)input.DataLength;
|
||||||
|
|
||||||
private static bool IsUniqueUnusedOffset(
|
|
||||||
IReadOnlyList<MetadataVertexResource> resources,
|
|
||||||
bool[] usedResources,
|
|
||||||
uint offsetBytes,
|
|
||||||
int candidateIndex)
|
|
||||||
{
|
|
||||||
for (var index = 0; index < resources.Count; index++)
|
|
||||||
{
|
|
||||||
if (index == candidateIndex || usedResources[index])
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resources[index].OffsetBytes == offsetBytes)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attrib-table format
|
/// Attrib-table format
|
||||||
/// fields are VertexAttribFormat; V# / Vulkan paths need BufferFormat.
|
/// fields are VertexAttribFormat; V# / Vulkan paths need BufferFormat.
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public sealed class AgcVertexMetadataTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void MergeVertexInputs_OverlaysFormatWithoutRebasingCapture()
|
public void MergeVertexInputs_OverlaysLayoutWithoutRebasingCapture()
|
||||||
{
|
{
|
||||||
const ulong memoryBase = 0x1_0000_0000;
|
const ulong memoryBase = 0x1_0000_0000;
|
||||||
var memory = new FakeCpuMemory(memoryBase, 0x2000);
|
var memory = new FakeCpuMemory(memoryBase, 0x2000);
|
||||||
@@ -114,7 +114,7 @@ public sealed class AgcVertexMetadataTests
|
|||||||
NumberFormat: 7,
|
NumberFormat: 7,
|
||||||
BaseAddress: sharpBase,
|
BaseAddress: sharpBase,
|
||||||
Stride: 16,
|
Stride: 16,
|
||||||
OffsetBytes: 0,
|
OffsetBytes: 12,
|
||||||
Data: data,
|
Data: data,
|
||||||
DataLength: data.Length,
|
DataLength: data.Length,
|
||||||
DataPooled: false),
|
DataPooled: false),
|
||||||
@@ -135,6 +135,137 @@ public sealed class AgcVertexMetadataTests
|
|||||||
Assert.Equal(0x40u, merged[0].Pc);
|
Assert.Equal(0x40u, merged[0].Pc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MergeVertexInputs_MetadataCorrectsStaleStride40()
|
||||||
|
{
|
||||||
|
const ulong memoryBase = 0x1_0000_0000;
|
||||||
|
var memory = new FakeCpuMemory(memoryBase, 0x2000);
|
||||||
|
var ctx = new CpuContext(memory, Generation.Gen5);
|
||||||
|
|
||||||
|
const ulong semanticsAddress = memoryBase + 0x100;
|
||||||
|
const ulong attribTable = memoryBase + 0x200;
|
||||||
|
const ulong bufferTable = memoryBase + 0x300;
|
||||||
|
const ulong sharpBase = memoryBase + 0x800;
|
||||||
|
|
||||||
|
WriteUInt32(memory, semanticsAddress, 0u | (0u << 8) | (4u << 16));
|
||||||
|
WriteUInt32(memory, attribTable, 0u | (56u << 5) | (12u << 14));
|
||||||
|
WriteUInt32(memory, bufferTable, (uint)(sharpBase & 0xFFFF_FFFFUL));
|
||||||
|
WriteUInt32(memory, bufferTable + 4, (uint)(sharpBase >> 32) | (40u << 16));
|
||||||
|
|
||||||
|
var scalars = new uint[32];
|
||||||
|
scalars[4] = (uint)(attribTable & 0xFFFF_FFFFUL);
|
||||||
|
scalars[5] = (uint)(attribTable >> 32);
|
||||||
|
scalars[6] = (uint)(bufferTable & 0xFFFF_FFFFUL);
|
||||||
|
scalars[7] = (uint)(bufferTable >> 32);
|
||||||
|
var tables = new AgcVertexMetadata.VertexTableRegisters(
|
||||||
|
VertexBufferReg: 6,
|
||||||
|
VertexAttribReg: 4,
|
||||||
|
InputSemanticsCount: 1,
|
||||||
|
InputSemanticsAddress: semanticsAddress);
|
||||||
|
|
||||||
|
var data = new byte[160];
|
||||||
|
var discovered = new[]
|
||||||
|
{
|
||||||
|
new Gen5VertexInputBinding(
|
||||||
|
0x40, 0, 4, 14, 7, sharpBase, 32, 12, data, data.Length, false),
|
||||||
|
};
|
||||||
|
|
||||||
|
var merged = AgcVertexMetadata.MergeVertexInputsFromMetadata(
|
||||||
|
ctx,
|
||||||
|
scalars,
|
||||||
|
tables,
|
||||||
|
discovered);
|
||||||
|
|
||||||
|
Assert.Single(merged);
|
||||||
|
Assert.Equal(40u, merged[0].Stride);
|
||||||
|
Assert.Equal(12u, merged[0].OffsetBytes);
|
||||||
|
Assert.Equal(sharpBase, merged[0].BaseAddress);
|
||||||
|
Assert.Same(data, merged[0].Data);
|
||||||
|
Assert.Equal(0x40u, merged[0].Pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MergeVertexInputs_ConflictingMetadataOffsetDoesNotMoveBinding()
|
||||||
|
{
|
||||||
|
const ulong memoryBase = 0x1_0000_0000;
|
||||||
|
var memory = new FakeCpuMemory(memoryBase, 0x2000);
|
||||||
|
var ctx = new CpuContext(memory, Generation.Gen5);
|
||||||
|
|
||||||
|
const ulong semanticsAddress = memoryBase + 0x100;
|
||||||
|
const ulong attribTable = memoryBase + 0x200;
|
||||||
|
const ulong bufferTable = memoryBase + 0x300;
|
||||||
|
const ulong sharpBase = memoryBase + 0x800;
|
||||||
|
|
||||||
|
WriteUInt32(memory, semanticsAddress, 0u | (0u << 8) | (4u << 16));
|
||||||
|
WriteUInt32(memory, attribTable, 0u | (56u << 5) | (12u << 14));
|
||||||
|
WriteUInt32(memory, bufferTable, (uint)(sharpBase & 0xFFFF_FFFFUL));
|
||||||
|
WriteUInt32(memory, bufferTable + 4, (uint)(sharpBase >> 32) | (40u << 16));
|
||||||
|
|
||||||
|
var scalars = new uint[32];
|
||||||
|
scalars[4] = (uint)(attribTable & 0xFFFF_FFFFUL);
|
||||||
|
scalars[5] = (uint)(attribTable >> 32);
|
||||||
|
scalars[6] = (uint)(bufferTable & 0xFFFF_FFFFUL);
|
||||||
|
scalars[7] = (uint)(bufferTable >> 32);
|
||||||
|
var tables = new AgcVertexMetadata.VertexTableRegisters(
|
||||||
|
VertexBufferReg: 6,
|
||||||
|
VertexAttribReg: 4,
|
||||||
|
InputSemanticsCount: 1,
|
||||||
|
InputSemanticsAddress: semanticsAddress);
|
||||||
|
|
||||||
|
var original = new Gen5VertexInputBinding(
|
||||||
|
0x40, 0, 4, 14, 7, sharpBase, 32, 0, new byte[160], 160, false);
|
||||||
|
var merged = AgcVertexMetadata.MergeVertexInputsFromMetadata(
|
||||||
|
ctx,
|
||||||
|
scalars,
|
||||||
|
tables,
|
||||||
|
[original]);
|
||||||
|
|
||||||
|
Assert.Same(original, Assert.Single(merged));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MergeVertexInputs_UsesOffsetRelativeToCapturedBase()
|
||||||
|
{
|
||||||
|
const ulong memoryBase = 0x1_0000_0000;
|
||||||
|
var memory = new FakeCpuMemory(memoryBase, 0x2000);
|
||||||
|
var ctx = new CpuContext(memory, Generation.Gen5);
|
||||||
|
|
||||||
|
const ulong semanticsAddress = memoryBase + 0x100;
|
||||||
|
const ulong attribTable = memoryBase + 0x200;
|
||||||
|
const ulong bufferTable = memoryBase + 0x300;
|
||||||
|
const ulong capturedBase = memoryBase + 0x7F8;
|
||||||
|
const ulong sharpBase = memoryBase + 0x800;
|
||||||
|
|
||||||
|
WriteUInt32(memory, semanticsAddress, 0u | (0u << 8) | (4u << 16));
|
||||||
|
WriteUInt32(memory, attribTable, 0u | (56u << 5) | (12u << 14));
|
||||||
|
WriteUInt32(memory, bufferTable, (uint)(sharpBase & 0xFFFF_FFFFUL));
|
||||||
|
WriteUInt32(memory, bufferTable + 4, (uint)(sharpBase >> 32) | (40u << 16));
|
||||||
|
|
||||||
|
var scalars = new uint[32];
|
||||||
|
scalars[4] = (uint)(attribTable & 0xFFFF_FFFFUL);
|
||||||
|
scalars[5] = (uint)(attribTable >> 32);
|
||||||
|
scalars[6] = (uint)(bufferTable & 0xFFFF_FFFFUL);
|
||||||
|
scalars[7] = (uint)(bufferTable >> 32);
|
||||||
|
var tables = new AgcVertexMetadata.VertexTableRegisters(
|
||||||
|
VertexBufferReg: 6,
|
||||||
|
VertexAttribReg: 4,
|
||||||
|
InputSemanticsCount: 1,
|
||||||
|
InputSemanticsAddress: semanticsAddress);
|
||||||
|
|
||||||
|
var data = new byte[160];
|
||||||
|
var merged = AgcVertexMetadata.MergeVertexInputsFromMetadata(
|
||||||
|
ctx,
|
||||||
|
scalars,
|
||||||
|
tables,
|
||||||
|
[new Gen5VertexInputBinding(
|
||||||
|
0x40, 0, 4, 14, 7, capturedBase, 32, 20, data, data.Length, false)]);
|
||||||
|
|
||||||
|
Assert.Equal(40u, Assert.Single(merged).Stride);
|
||||||
|
Assert.Equal(20u, merged[0].OffsetBytes);
|
||||||
|
Assert.Equal(capturedBase, merged[0].BaseAddress);
|
||||||
|
Assert.Same(data, merged[0].Data);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void MergeVertexInputs_AcceptsVertexAttribFormatEnums()
|
public void MergeVertexInputs_AcceptsVertexAttribFormatEnums()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user