Compare commits

...

4 Commits

Author SHA1 Message Date
Berk 99004a3ccd [GPU] Host cached guest buffer (#649) 2026-07-26 04:28:32 +03:00
Berk e1a3b92567 [CPU] Fix Sema ORBIS_GEN2_ERROR_BUSY loop (#621) 2026-07-25 14:42:35 +03:00
Andrew 8f9456229a fix(memory): reserve only large regions (#608)
* fix(memory): reserve only large regions

* Potential fix for pull request finding
2026-07-25 14:25:53 +03:00
Berk 5b602c0232 [GPU] Fix detiled cache key for VulkanDetilePass (#620) 2026-07-25 14:12:17 +03:00
6 changed files with 376 additions and 200 deletions
@@ -1449,6 +1449,9 @@ public sealed partial class DirectExecutionBackend
var expectedSemaphoreTrywaitAgain = var expectedSemaphoreTrywaitAgain =
string.Equals(nid, "H2a+IN9TP0E", StringComparison.Ordinal) && string.Equals(nid, "H2a+IN9TP0E", StringComparison.Ordinal) &&
result == OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN; result == OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
var expectedPollSemaBusy =
string.Equals(nid, "12wOHk8ywb0", StringComparison.Ordinal) &&
result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
var expectedNetAcceptWouldBlock = var expectedNetAcceptWouldBlock =
string.Equals(nid, "PIWqhn9oSxc", StringComparison.Ordinal) && string.Equals(nid, "PIWqhn9oSxc", StringComparison.Ordinal) &&
resultValue == unchecked((int)0x80410123); resultValue == unchecked((int)0x80410123);
@@ -1463,6 +1466,7 @@ public sealed partial class DirectExecutionBackend
!expectedEqueueTimeout && !expectedEqueueTimeout &&
!expectedMutexTrylockBusy && !expectedMutexTrylockBusy &&
!expectedSemaphoreTrywaitAgain && !expectedSemaphoreTrywaitAgain &&
!expectedPollSemaBusy &&
!expectedNetAcceptWouldBlock && !expectedNetAcceptWouldBlock &&
!expectedUserServiceNoEvent && !expectedUserServiceNoEvent &&
!expectedPrivacyInvalidParameter) !expectedPrivacyInvalidParameter)
@@ -238,7 +238,15 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
var alignedSize = (size + 0xFFF) & ~0xFFFUL; var alignedSize = (size + 0xFFF) & ~0xFFFUL;
var protection = executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; var protection = executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
var hostProtection = executable ? HostPageProtection.ReadWriteExecute : HostPageProtection.ReadWrite; var hostProtection = executable ? HostPageProtection.ReadWriteExecute : HostPageProtection.ReadWrite;
var result = _hostMemory.Allocate(desiredAddress, alignedSize, hostProtection);
// Reserve address space only for very large non-executable regions; commit is done lazily later.
var reservedOnly = !executable &&
alignedSize >= LargeDataReserveThreshold &&
alignedSize > FullCommitRegionLimit;
var result = reservedOnly
? _hostMemory.Reserve(desiredAddress, alignedSize, HostPageProtection.ReadWrite)
: _hostMemory.Allocate(desiredAddress, alignedSize, hostProtection);
if (result == 0) if (result == 0)
{ {
return false; return false;
@@ -252,6 +260,8 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
return false; return false;
} }
var state = reservedOnly ? ReserveRegion(actualAddress, alignedSize) : "n/a";
_gate.EnterWriteLock(); _gate.EnterWriteLock();
try try
{ {
@@ -260,7 +270,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
VirtualAddress = actualAddress, VirtualAddress = actualAddress,
Size = alignedSize, Size = alignedSize,
IsExecutable = executable, IsExecutable = executable,
IsReservedOnly = false, IsReservedOnly = reservedOnly,
Protection = protection Protection = protection
}); });
} }
@@ -269,6 +279,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.ExitWriteLock(); _gate.ExitWriteLock();
} }
var allocationKind = executable ? "executable memory" : "data memory"; var allocationKind = executable ? "executable memory" : "data memory";
TraceVmem($"Allocated exact {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes)"); TraceVmem($"Allocated exact {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes)");
return true; return true;
@@ -361,44 +372,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
var actualAddress = result; var actualAddress = result;
var lazyPrimeState = "n/a"; var lazyPrimeState = reservedOnly ? ReserveRegion(actualAddress, alignedSize) : "n/a";
if (reservedOnly)
{
var primeBytes = Math.Min(alignedSize, LazyReservePrimeBytes);
if (primeBytes != 0)
{
ulong committedBytes = 0;
while (committedBytes < primeBytes)
{
var remaining = primeBytes - committedBytes;
var chunkBytes = Math.Min(remaining, LazyReservePrimeChunkBytes);
var commitAddress = actualAddress + committedBytes;
if (!_hostMemory.Commit(commitAddress, chunkBytes, HostPageProtection.ReadWrite))
{
break;
}
committedBytes += chunkBytes;
}
if (committedBytes != 0)
{
lazyPrimeState = committedBytes == primeBytes
? $"ok:{committedBytes:X}"
: $"partial:{committedBytes:X}/{primeBytes:X}";
TraceVmem($"Primed lazy region: 0x{actualAddress:X16} - 0x{actualAddress + committedBytes:X16} ({committedBytes} bytes)");
}
else
{
lazyPrimeState = $"fail:{primeBytes:X}";
TraceVmem($"Failed to prime lazy region at 0x{actualAddress:X16} ({primeBytes} bytes), continuing with on-demand commit");
}
}
else
{
lazyPrimeState = "skip:0";
}
}
_gate.EnterWriteLock(); _gate.EnterWriteLock();
try try
@@ -425,6 +399,41 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
return actualAddress; return actualAddress;
} }
private string ReserveRegion(ulong actualAddress, ulong alignedSize)
{
var primeBytes = Math.Min(alignedSize, LazyReservePrimeBytes);
if (primeBytes == 0)
{
return "skip:0";
}
ulong committedBytes = 0;
while (committedBytes < primeBytes)
{
var remaining = primeBytes - committedBytes;
var chunkBytes = Math.Min(remaining, LazyReservePrimeChunkBytes);
var commitAddress = actualAddress + committedBytes;
if (!_hostMemory.Commit(commitAddress, chunkBytes, HostPageProtection.ReadWrite))
{
break;
}
committedBytes += chunkBytes;
}
if (committedBytes != 0)
{
var state = committedBytes == primeBytes
? $"ok:{committedBytes:X}"
: $"partial:{committedBytes:X}/{primeBytes:X}";
TraceVmem($"region: 0x{actualAddress:X16} - 0x{actualAddress + committedBytes:X16} ({committedBytes} bytes)");
return state;
}
TraceVmem($"Failed to reserve region at 0x{actualAddress:X16} ({primeBytes} bytes)!");
return $"fail:{primeBytes:X}";
}
public bool TryBackFixedRange(ulong address, ulong size, bool executable) public bool TryBackFixedRange(ulong address, ulong size, bool executable)
{ {
if (size == 0) if (size == 0)
+14 -1
View File
@@ -8263,6 +8263,9 @@ public static partial class AgcExports
private static bool IsGpuDetileBytesPerElement(int bytesPerElement) => private static bool IsGpuDetileBytesPerElement(int bytesPerElement) =>
bytesPerElement is 4 or 8 or 16; bytesPerElement is 4 or 8 or 16;
private static bool IsGpuDetileTextureType(uint type) =>
type != Gen5TextureType3D;
private static bool TryGetTextureElementLayout( private static bool TryGetTextureElementLayout(
TextureDescriptor descriptor, TextureDescriptor descriptor,
uint sourceWidth, uint sourceWidth,
@@ -8746,6 +8749,7 @@ public static partial class AgcExports
// take this path. // take this path.
if (_gpuDetileEnabled && hasElementLayout && !baseMipInTail && if (_gpuDetileEnabled && hasElementLayout && !baseMipInTail &&
IsGpuDetileBytesPerElement(bytesPerElement) && IsGpuDetileBytesPerElement(bytesPerElement) &&
IsGpuDetileTextureType(descriptor.Type) &&
(long)physicalSourceByteCount * arrayLayers <= int.MaxValue) (long)physicalSourceByteCount * arrayLayers <= int.MaxValue)
{ {
var gpuArrayParams = GnmTiling.GetDetileParams( var gpuArrayParams = GnmTiling.GetDetileParams(
@@ -8789,6 +8793,12 @@ public static partial class AgcExports
WriteGeneration: hasWriteGeneration ? writeGeneration : -1, WriteGeneration: hasWriteGeneration ? writeGeneration : -1,
ArrayedView: true, ArrayedView: true,
ArrayLayers: arrayLayers, ArrayLayers: arrayLayers,
// Must match the identity the CPU path below ships, or
// the presenter caches this texture under a different
// key than IsTextureContentCached queries above and the
// texel-copy skip never hits for non-2D descriptors.
Type: descriptor.Type,
Depth: textureDepth,
TiledSource: tiledLayers, TiledSource: tiledLayers,
Detile: gpuArrayParams); Detile: gpuArrayParams);
return true; return true;
@@ -8920,7 +8930,8 @@ public static partial class AgcExports
// Arrayed textures are handled by the arrayed branch above (they package // Arrayed textures are handled by the arrayed branch above (they package
// every layer's tiled slice); this branch is the single-layer case. // every layer's tiled slice); this branch is the single-layer case.
if (_gpuDetileEnabled && hasElementLayout && !baseMipInTail && if (_gpuDetileEnabled && hasElementLayout && !baseMipInTail &&
IsGpuDetileBytesPerElement(bytesPerElement) && !isArrayed) IsGpuDetileBytesPerElement(bytesPerElement) && !isArrayed &&
IsGpuDetileTextureType(descriptor.Type))
{ {
var gpuDetileParams = GnmTiling.GetDetileParams( var gpuDetileParams = GnmTiling.GetDetileParams(
descriptor.TileMode, bytesPerElement, elementsWide, elementsHigh); descriptor.TileMode, bytesPerElement, elementsWide, elementsHigh);
@@ -8946,6 +8957,8 @@ public static partial class AgcExports
Sampler: ToGuestSampler(samplerDescriptor), Sampler: ToGuestSampler(samplerDescriptor),
WriteGeneration: hasWriteGeneration ? writeGeneration : -1, WriteGeneration: hasWriteGeneration ? writeGeneration : -1,
ArrayedView: isArrayed, ArrayedView: isArrayed,
Type: descriptor.Type,
Depth: textureDepth,
TiledSource: source, TiledSource: source,
Detile: gpuDetileParams); Detile: gpuDetileParams);
return true; return true;
+270 -118
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using SharpEmu.Libs.Agc; using SharpEmu.Libs.Agc;
using SharpEmu.ShaderCompiler.Vulkan; using SharpEmu.ShaderCompiler.Vulkan;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
@@ -45,6 +46,21 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
private bool _initialized; private bool _initialized;
private bool _disposed; private bool _disposed;
private PhysicalDeviceMemoryProperties _memoryProperties;
private bool _memoryPropertiesLoaded;
private readonly Dictionary<int[], TermBuffer> _xorTermBuffers = new(ReferenceComparer.Instance);
private readonly Dictionary<int[], TermBuffer> _blockTermBuffers = new(ReferenceComparer.Instance);
private TermBuffer _placeholderTermBuffer;
private readonly Dictionary<(ulong Bucket, bool HostVisible), Stack<Allocation>> _bufferPool = new();
private readonly List<Allocation> _allAllocations = new();
private readonly Stack<DescriptorSet> _freeDescriptorSets = new();
private readonly List<DescriptorPool> _descriptorPools = new();
private const uint DescriptorSetsPerPool = 64;
private const ulong MinimumBufferBucket = 4096;
public VulkanDetilePass( public VulkanDetilePass(
Vk vk, Vk vk,
Device device, Device device,
@@ -69,23 +85,32 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
parameters.Equation == DetileEquation.BlockTable) && parameters.Equation == DetileEquation.BlockTable) &&
parameters.BytesPerElement is 4 or 8 or 16; parameters.BytesPerElement is 4 or 8 or 16;
/// <summary>Transient per-detile resources the caller must retire once the /// <summary>Opaque handle to the pooled resources one recorded detile is using.
/// command buffer they were recorded into has completed.</summary> /// The caller hands it back to <see cref="Retire"/> once the command buffer they
public readonly record struct Transients( /// were recorded into has completed; nothing is destroyed, the buffers and the
(VkBuffer Buffer, DeviceMemory Memory)[] Buffers, /// descriptor set return to this pass's free lists for the next texture.</summary>
DescriptorPool DescriptorPool); public sealed class Transients
{
internal static readonly Transients Empty = new();
internal Allocation Tiled;
internal Allocation Output;
internal DescriptorSet Set;
internal bool Rented;
}
internal readonly record struct Allocation(
VkBuffer Buffer,
DeviceMemory Memory,
ulong Capacity,
bool HostVisible);
private readonly record struct TermBuffer(VkBuffer Buffer, ulong ByteSize);
private struct DetileResources private struct DetileResources
{ {
public VkBuffer Tiled; public Allocation Tiled;
public DeviceMemory TiledMemory; public Allocation Output;
public VkBuffer XTerm;
public DeviceMemory XMemory;
public VkBuffer YTerm;
public DeviceMemory YMemory;
public VkBuffer Output;
public DeviceMemory OutputMemory;
public DescriptorPool Pool;
public DescriptorSet Set; public DescriptorSet Set;
public ulong OutputBytes; public ulong OutputBytes;
public uint SrcSliceElements; public uint SrcSliceElements;
@@ -93,6 +118,15 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
public uint UintsPerElement; public uint UintsPerElement;
} }
private sealed class ReferenceComparer : IEqualityComparer<int[]>
{
public static readonly ReferenceComparer Instance = new();
public bool Equals(int[]? x, int[]? y) => ReferenceEquals(x, y);
public int GetHashCode(int[] obj) => RuntimeHelpers.GetHashCode(obj);
}
/// <summary> /// <summary>
/// Records the deswizzle of <paramref name="tiled"/> into <paramref name="image"/> /// Records the deswizzle of <paramref name="tiled"/> into <paramref name="image"/>
/// (<paramref name="texelWidth"/> x <paramref name="texelHeight"/> texels x /// (<paramref name="texelWidth"/> x <paramref name="texelHeight"/> texels x
@@ -117,7 +151,7 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
in DetileParams parameters, in DetileParams parameters,
out Transients transients) out Transients transients)
{ {
transients = new Transients([], default); transients = Transients.Empty;
if (_disposed || !Supports(parameters) || texelWidth == 0 || texelHeight == 0 || layers == 0 || if (_disposed || !Supports(parameters) || texelWidth == 0 || texelHeight == 0 || layers == 0 ||
tiled.IsEmpty || tiled.Length % (int)(layers * (uint)parameters.BytesPerElement) != 0) tiled.IsEmpty || tiled.Length % (int)(layers * (uint)parameters.BytesPerElement) != 0)
{ {
@@ -134,21 +168,33 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
} }
catch catch
{ {
DestroyResources(in resources); ReleaseResources(in resources);
throw; throw;
} }
transients = new Transients( transients = new Transients
[ {
(resources.Tiled, resources.TiledMemory), Tiled = resources.Tiled,
(resources.XTerm, resources.XMemory), Output = resources.Output,
(resources.YTerm, resources.YMemory), Set = resources.Set,
(resources.Output, resources.OutputMemory), Rented = true,
], };
resources.Pool);
return true; return true;
} }
public void Retire(Transients transients)
{
if (_disposed || transients is null || !transients.Rented)
{
return;
}
transients.Rented = false;
ReturnBuffer(transients.Tiled);
ReturnBuffer(transients.Output);
_freeDescriptorSets.Push(transients.Set);
}
/// <summary> /// <summary>
/// One-shot variant used by the isolation self-test: records the detile onto a /// One-shot variant used by the isolation self-test: records the detile onto a
/// private command buffer, submits, waits, and frees every transient. Never /// private command buffer, submits, waits, and frees every transient. Never
@@ -207,7 +253,7 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
_vk.FreeCommandBuffers(_device, _commandPool, 1, &commandBuffer); _vk.FreeCommandBuffers(_device, _commandPool, 1, &commandBuffer);
} }
DestroyResources(in resources); ReleaseResources(in resources);
} }
} }
@@ -219,24 +265,19 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
// log2(bpp) bits of every term are 0, so the right shift is exact. // log2(bpp) bits of every term are 0, so the right shift is exact.
// BlockTable: GetDetileParams' block table is already element offsets; it // BlockTable: GetDetileParams' block table is already element offsets; it
// goes in binding 1 and binding 2 is an unused placeholder. // goes in binding 1 and binding 2 is an unused placeholder.
uint[] xTerm; TermBuffer xTerm;
uint[] yTerm; TermBuffer yTerm;
if (parameters.Equation == DetileEquation.BlockTable) if (parameters.Equation == DetileEquation.BlockTable)
{ {
xTerm = new uint[parameters.BlockTable.Length]; xTerm = GetTermBuffer(_blockTermBuffers, parameters.BlockTable, shift: 0);
for (var index = 0; index < xTerm.Length; index++) yTerm = GetPlaceholderTermBuffer();
{
xTerm[index] = (uint)parameters.BlockTable[index];
}
yTerm = [0];
resources.EquationValue = 1; resources.EquationValue = 1;
} }
else else
{ {
var shift = BitOperations.TrailingZeroCount((uint)parameters.BytesPerElement); var shift = BitOperations.TrailingZeroCount((uint)parameters.BytesPerElement);
xTerm = ToElementTerms(parameters.XByteTerm, shift); xTerm = GetTermBuffer(_xorTermBuffers, parameters.XByteTerm, shift);
yTerm = ToElementTerms(parameters.YByteTerm, shift); yTerm = GetTermBuffer(_xorTermBuffers, parameters.YByteTerm, shift);
resources.EquationValue = 0; resources.EquationValue = 0;
} }
@@ -249,25 +290,144 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
resources.OutputBytes = resources.OutputBytes =
(ulong)parameters.ElementsWide * (ulong)parameters.ElementsHigh * bytesPerElement * layers; (ulong)parameters.ElementsWide * (ulong)parameters.ElementsHigh * bytesPerElement * layers;
resources.Tiled = CreateHostBuffer((ulong)tiled.Length, BufferUsageFlags.StorageBufferBit, out resources.TiledMemory); resources.Tiled = RentBuffer((ulong)tiled.Length, hostVisible: true);
UploadBytes(resources.TiledMemory, tiled); UploadBytes(resources.Tiled.Memory, tiled);
resources.XTerm = CreateHostBuffer((ulong)xTerm.Length * sizeof(uint), BufferUsageFlags.StorageBufferBit, out resources.XMemory); resources.Output = RentBuffer(resources.OutputBytes, hostVisible: false);
UploadUInts(resources.XMemory, xTerm);
resources.YTerm = CreateHostBuffer((ulong)yTerm.Length * sizeof(uint), BufferUsageFlags.StorageBufferBit, out resources.YMemory);
UploadUInts(resources.YMemory, yTerm);
resources.Output = CreateHostBuffer(
resources.OutputBytes,
BufferUsageFlags.StorageBufferBit | BufferUsageFlags.TransferSrcBit,
out resources.OutputMemory);
resources.Pool = CreateDescriptorPool(); resources.Set = RentDescriptorSet();
resources.Set = AllocateDescriptorSet(resources.Pool);
WriteDescriptors( WriteDescriptors(
resources.Set, resources.Set,
(resources.Tiled, (ulong)tiled.Length), (resources.Tiled.Buffer, (ulong)tiled.Length),
(resources.XTerm, (ulong)xTerm.Length * sizeof(uint)), (xTerm.Buffer, xTerm.ByteSize),
(resources.YTerm, (ulong)yTerm.Length * sizeof(uint)), (yTerm.Buffer, yTerm.ByteSize),
(resources.Output, resources.OutputBytes)); (resources.Output.Buffer, resources.OutputBytes));
}
private TermBuffer GetTermBuffer(Dictionary<int[], TermBuffer> cache, int[] table, int shift)
{
if (cache.TryGetValue(table, out var cached))
{
return cached;
}
var terms = ToElementTerms(table, shift);
var byteSize = (ulong)terms.Length * sizeof(uint);
var allocation = CreateBuffer(byteSize, hostVisible: true);
UploadUInts(allocation.Memory, terms);
var termBuffer = new TermBuffer(allocation.Buffer, byteSize);
cache[table] = termBuffer;
return termBuffer;
}
private TermBuffer GetPlaceholderTermBuffer()
{
if (_placeholderTermBuffer.Buffer.Handle != 0)
{
return _placeholderTermBuffer;
}
var allocation = CreateBuffer(sizeof(uint), hostVisible: true);
UploadUInts(allocation.Memory, [0u]);
_placeholderTermBuffer = new TermBuffer(allocation.Buffer, sizeof(uint));
return _placeholderTermBuffer;
}
private static ulong BucketFor(ulong size)
{
var bucket = MinimumBufferBucket;
while (bucket < size)
{
bucket <<= 1;
}
return bucket;
}
private Allocation RentBuffer(ulong size, bool hostVisible)
{
var bucket = BucketFor(size);
if (_bufferPool.TryGetValue((bucket, hostVisible), out var free) && free.Count > 0)
{
return free.Pop();
}
return CreateBuffer(bucket, hostVisible);
}
private void ReturnBuffer(Allocation allocation)
{
if (allocation.Buffer.Handle == 0)
{
return;
}
var key = (allocation.Capacity, allocation.HostVisible);
if (!_bufferPool.TryGetValue(key, out var free))
{
free = new Stack<Allocation>();
_bufferPool[key] = free;
}
free.Push(allocation);
}
private DescriptorSet RentDescriptorSet()
{
if (_freeDescriptorSets.Count > 0)
{
return _freeDescriptorSets.Pop();
}
var poolSize = new DescriptorPoolSize
{
Type = DescriptorType.StorageBuffer,
DescriptorCount = 4 * DescriptorSetsPerPool,
};
var poolInfo = new DescriptorPoolCreateInfo
{
SType = StructureType.DescriptorPoolCreateInfo,
MaxSets = DescriptorSetsPerPool,
PoolSizeCount = 1,
PPoolSizes = &poolSize,
};
Check(
_vk.CreateDescriptorPool(_device, &poolInfo, null, out var pool),
"vkCreateDescriptorPool(detile)");
_descriptorPools.Add(pool);
var layouts = stackalloc DescriptorSetLayout[(int)DescriptorSetsPerPool];
for (var index = 0; index < DescriptorSetsPerPool; index++)
{
layouts[index] = _descriptorSetLayout;
}
var sets = stackalloc DescriptorSet[(int)DescriptorSetsPerPool];
var allocateInfo = new DescriptorSetAllocateInfo
{
SType = StructureType.DescriptorSetAllocateInfo,
DescriptorPool = pool,
DescriptorSetCount = DescriptorSetsPerPool,
PSetLayouts = layouts,
};
Check(
_vk.AllocateDescriptorSets(_device, &allocateInfo, sets),
"vkAllocateDescriptorSets(detile)");
for (var index = 0; index < DescriptorSetsPerPool; index++)
{
_freeDescriptorSets.Push(sets[index]);
}
return _freeDescriptorSets.Pop();
}
private void ReleaseResources(in DetileResources resources)
{
ReturnBuffer(resources.Tiled);
ReturnBuffer(resources.Output);
if (resources.Set.Handle != 0)
{
_freeDescriptorSets.Push(resources.Set);
}
} }
private void RecordCommands( private void RecordCommands(
@@ -326,7 +486,7 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
DstAccessMask = AccessFlags.TransferReadBit, DstAccessMask = AccessFlags.TransferReadBit,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored, SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored, DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Buffer = resources.Output, Buffer = resources.Output.Buffer,
Offset = 0, Offset = 0,
Size = resources.OutputBytes, Size = resources.OutputBytes,
}; };
@@ -367,7 +527,7 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
ImageExtent = new Extent3D(texelWidth, texelHeight, 1), ImageExtent = new Extent3D(texelWidth, texelHeight, 1),
}; };
_vk.CmdCopyBufferToImage( _vk.CmdCopyBufferToImage(
commandBuffer, resources.Output, image, ImageLayout.TransferDstOptimal, 1, &copyRegion); commandBuffer, resources.Output.Buffer, image, ImageLayout.TransferDstOptimal, 1, &copyRegion);
TransitionImage( TransitionImage(
commandBuffer, commandBuffer,
@@ -381,19 +541,6 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
layers); layers);
} }
private void DestroyResources(in DetileResources resources)
{
if (resources.Pool.Handle != 0)
{
_vk.DestroyDescriptorPool(_device, resources.Pool, null);
}
DestroyBuffer(resources.Output, resources.OutputMemory);
DestroyBuffer(resources.YTerm, resources.YMemory);
DestroyBuffer(resources.XTerm, resources.XMemory);
DestroyBuffer(resources.Tiled, resources.TiledMemory);
}
private void EnsurePipeline() private void EnsurePipeline()
{ {
if (_initialized) if (_initialized)
@@ -500,45 +647,67 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
return terms; return terms;
} }
private VkBuffer CreateHostBuffer(ulong size, BufferUsageFlags usage, out DeviceMemory memory) private Allocation CreateBuffer(ulong size, bool hostVisible)
{ {
var bufferInfo = new BufferCreateInfo var bufferInfo = new BufferCreateInfo
{ {
SType = StructureType.BufferCreateInfo, SType = StructureType.BufferCreateInfo,
Size = size, Size = size,
Usage = usage, Usage = BufferUsageFlags.StorageBufferBit | BufferUsageFlags.TransferSrcBit,
SharingMode = SharingMode.Exclusive, SharingMode = SharingMode.Exclusive,
}; };
Check(_vk.CreateBuffer(_device, &bufferInfo, null, out var buffer), "vkCreateBuffer(detile)"); Check(_vk.CreateBuffer(_device, &bufferInfo, null, out var buffer), "vkCreateBuffer(detile)");
_vk.GetBufferMemoryRequirements(_device, buffer, out var requirements); _vk.GetBufferMemoryRequirements(_device, buffer, out var requirements);
var required = hostVisible
? MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit
: MemoryPropertyFlags.DeviceLocalBit;
var allocateInfo = new MemoryAllocateInfo var allocateInfo = new MemoryAllocateInfo
{ {
SType = StructureType.MemoryAllocateInfo, SType = StructureType.MemoryAllocateInfo,
AllocationSize = requirements.Size, AllocationSize = requirements.Size,
MemoryTypeIndex = FindMemoryType( MemoryTypeIndex = FindMemoryType(requirements.MemoryTypeBits, required, hostVisible),
requirements.MemoryTypeBits,
MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit),
}; };
Check(_vk.AllocateMemory(_device, &allocateInfo, null, out memory), "vkAllocateMemory(detile)"); Check(_vk.AllocateMemory(_device, &allocateInfo, null, out var memory), "vkAllocateMemory(detile)");
Check(_vk.BindBufferMemory(_device, buffer, memory, 0), "vkBindBufferMemory(detile)"); Check(_vk.BindBufferMemory(_device, buffer, memory, 0), "vkBindBufferMemory(detile)");
return buffer; var allocation = new Allocation(buffer, memory, size, hostVisible);
_allAllocations.Add(allocation);
return allocation;
} }
private uint FindMemoryType(uint typeBits, MemoryPropertyFlags requiredFlags) private uint FindMemoryType(uint typeBits, MemoryPropertyFlags requiredFlags, bool hostVisible)
{ {
_vk.GetPhysicalDeviceMemoryProperties(_physicalDevice, out var properties); if (!_memoryPropertiesLoaded)
var memoryTypes = &properties.MemoryTypes.Element0;
for (uint index = 0; index < properties.MemoryTypeCount; index++)
{ {
if ((typeBits & (1u << (int)index)) != 0 && _vk.GetPhysicalDeviceMemoryProperties(_physicalDevice, out _memoryProperties);
(memoryTypes[index].PropertyFlags & requiredFlags) == requiredFlags) _memoryPropertiesLoaded = true;
}
fixed (PhysicalDeviceMemoryProperties* properties = &_memoryProperties)
{
var memoryTypes = &properties->MemoryTypes.Element0;
for (uint index = 0; index < properties->MemoryTypeCount; index++)
{ {
return index; if ((typeBits & (1u << (int)index)) != 0 &&
(memoryTypes[index].PropertyFlags & requiredFlags) == requiredFlags)
{
return index;
}
}
if (!hostVisible)
{
for (uint index = 0; index < properties->MemoryTypeCount; index++)
{
if ((typeBits & (1u << (int)index)) != 0)
{
return index;
}
}
} }
} }
throw new InvalidOperationException("No compatible Vulkan host-visible memory type for detile."); throw new InvalidOperationException("No compatible Vulkan memory type for detile.");
} }
private void UploadBytes(DeviceMemory memory, ReadOnlySpan<byte> data) private void UploadBytes(DeviceMemory memory, ReadOnlySpan<byte> data)
@@ -558,42 +727,6 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
_vk.UnmapMemory(_device, memory); _vk.UnmapMemory(_device, memory);
} }
private DescriptorPool CreateDescriptorPool()
{
var poolSize = new DescriptorPoolSize
{
Type = DescriptorType.StorageBuffer,
DescriptorCount = 4,
};
var poolInfo = new DescriptorPoolCreateInfo
{
SType = StructureType.DescriptorPoolCreateInfo,
MaxSets = 1,
PoolSizeCount = 1,
PPoolSizes = &poolSize,
};
Check(
_vk.CreateDescriptorPool(_device, &poolInfo, null, out var pool),
"vkCreateDescriptorPool(detile)");
return pool;
}
private DescriptorSet AllocateDescriptorSet(DescriptorPool pool)
{
var setLayout = _descriptorSetLayout;
var allocateInfo = new DescriptorSetAllocateInfo
{
SType = StructureType.DescriptorSetAllocateInfo,
DescriptorPool = pool,
DescriptorSetCount = 1,
PSetLayouts = &setLayout,
};
Check(
_vk.AllocateDescriptorSets(_device, &allocateInfo, out var descriptorSet),
"vkAllocateDescriptorSets(detile)");
return descriptorSet;
}
private void WriteDescriptors( private void WriteDescriptors(
DescriptorSet descriptorSet, DescriptorSet descriptorSet,
(VkBuffer Buffer, ulong Size) binding0, (VkBuffer Buffer, ulong Size) binding0,
@@ -714,6 +847,25 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
} }
_disposed = true; _disposed = true;
foreach (var allocation in _allAllocations)
{
DestroyBuffer(allocation.Buffer, allocation.Memory);
}
_allAllocations.Clear();
_bufferPool.Clear();
_xorTermBuffers.Clear();
_blockTermBuffers.Clear();
_placeholderTermBuffer = default;
_freeDescriptorSets.Clear();
foreach (var pool in _descriptorPools)
{
_vk.DestroyDescriptorPool(_device, pool, null);
}
_descriptorPools.Clear();
if (_pipeline.Handle != 0) if (_pipeline.Handle != 0)
{ {
_vk.DestroyPipeline(_device, _pipeline, null); _vk.DestroyPipeline(_device, _pipeline, null);
@@ -261,23 +261,7 @@ internal static unsafe class VulkanDetileSelfTest
{ {
vk.DestroyFence(device, fence, null); vk.DestroyFence(device, fence, null);
vk.FreeCommandBuffers(device, commandPool, 1, &commandBuffer); vk.FreeCommandBuffers(device, commandPool, 1, &commandBuffer);
foreach (var (buffer, memory) in transients.Buffers) pass.Retire(transients);
{
if (buffer.Handle != 0)
{
vk.DestroyBuffer(device, buffer, null);
}
if (memory.Handle != 0)
{
vk.FreeMemory(device, memory, null);
}
}
if (transients.DescriptorPool.Handle != 0)
{
vk.DestroyDescriptorPool(device, transients.DescriptorPool, null);
}
} }
return true; return true;
@@ -2914,9 +2914,7 @@ internal static unsafe class VulkanVideoPresenter
private readonly Stack<Fence> _recycledGuestFences = new(); private readonly Stack<Fence> _recycledGuestFences = new();
private readonly Stack<CommandBuffer> _recycledGuestCommandBuffers = new(); private readonly Stack<CommandBuffer> _recycledGuestCommandBuffers = new();
private readonly List<(VkBuffer Buffer, DeviceMemory Memory)> _batchRetireBuffers = new(); private readonly List<(VkBuffer Buffer, DeviceMemory Memory)> _batchRetireBuffers = new();
// Descriptor pools from GPU-detile passes recorded into the batch; retired private readonly List<VulkanDetilePass.Transients> _batchRetireDetile = new();
// with the batch's fence, alongside _batchRetireBuffers.
private readonly List<DescriptorPool> _batchRetireDescriptorPools = new();
private const int MaxRecycledGuestFences = 32; private const int MaxRecycledGuestFences = 32;
private const int MaxRecycledGuestCommandBuffers = 32; private const int MaxRecycledGuestCommandBuffers = 32;
private VkBuffer _stagingBuffer; private VkBuffer _stagingBuffer;
@@ -3288,7 +3286,7 @@ internal static unsafe class VulkanVideoPresenter
IReadOnlyList<TranslatedDrawResources> Resources, IReadOnlyList<TranslatedDrawResources> Resources,
IReadOnlyList<GuestImageResource> TraceImages, IReadOnlyList<GuestImageResource> TraceImages,
IReadOnlyList<(VkBuffer Buffer, DeviceMemory Memory)> RetireBuffers, IReadOnlyList<(VkBuffer Buffer, DeviceMemory Memory)> RetireBuffers,
IReadOnlyList<DescriptorPool> RetirePools, IReadOnlyList<VulkanDetilePass.Transients> RetireDetile,
ulong Timeline, ulong Timeline,
string DebugName, string DebugName,
VulkanGuestQueueIdentity Queue, VulkanGuestQueueIdentity Queue,
@@ -4963,8 +4961,8 @@ internal static unsafe class VulkanVideoPresenter
_batchResources.ToArray(), _batchResources.ToArray(),
_batchTraceImages.ToArray(), _batchTraceImages.ToArray(),
_batchRetireBuffers.Count > 0 ? _batchRetireBuffers.ToArray() : [], _batchRetireBuffers.Count > 0 ? _batchRetireBuffers.ToArray() : [],
retirePools: _batchRetireDescriptorPools.Count > 0 retireDetile: _batchRetireDetile.Count > 0
? _batchRetireDescriptorPools.ToArray() ? _batchRetireDetile.ToArray()
: []); : []);
} }
catch catch
@@ -4983,9 +4981,9 @@ internal static unsafe class VulkanVideoPresenter
_vk.FreeMemory(_device, memory, null); _vk.FreeMemory(_device, memory, null);
} }
foreach (var pool in _batchRetireDescriptorPools) foreach (var transients in _batchRetireDetile)
{ {
_vk.DestroyDescriptorPool(_device, pool, null); _detilePass?.Retire(transients);
} }
ReleaseGuestCommandBuffer(_batchCommandBuffer); ReleaseGuestCommandBuffer(_batchCommandBuffer);
@@ -4996,7 +4994,7 @@ internal static unsafe class VulkanVideoPresenter
_batchResources.Clear(); _batchResources.Clear();
_batchTraceImages.Clear(); _batchTraceImages.Clear();
_batchRetireBuffers.Clear(); _batchRetireBuffers.Clear();
_batchRetireDescriptorPools.Clear(); _batchRetireDetile.Clear();
_batchCommandBuffer = default; _batchCommandBuffer = default;
} }
} }
@@ -5007,7 +5005,7 @@ internal static unsafe class VulkanVideoPresenter
IReadOnlyList<GuestImageResource> traceImages, IReadOnlyList<GuestImageResource> traceImages,
IReadOnlyList<(VkBuffer Buffer, DeviceMemory Memory)>? retireBuffers = null, IReadOnlyList<(VkBuffer Buffer, DeviceMemory Memory)>? retireBuffers = null,
IReadOnlyList<TranslatedDrawResources>? referencedResources = null, IReadOnlyList<TranslatedDrawResources>? referencedResources = null,
IReadOnlyList<DescriptorPool>? retirePools = null) IReadOnlyList<VulkanDetilePass.Transients>? retireDetile = null)
{ {
var fence = AcquireGuestFence(); var fence = AcquireGuestFence();
try try
@@ -5065,7 +5063,7 @@ internal static unsafe class VulkanVideoPresenter
resources, resources,
traceImages, traceImages,
retireBuffers ?? [], retireBuffers ?? [],
retirePools ?? [], retireDetile ?? [],
_submitTimeline, _submitTimeline,
resources.Count > 0 ? resources[0].DebugName : "batch", resources.Count > 0 ? resources[0].DebugName : "batch",
_activeGuestQueue, _activeGuestQueue,
@@ -5237,9 +5235,11 @@ internal static unsafe class VulkanVideoPresenter
_vk.FreeMemory(_device, memory, null); _vk.FreeMemory(_device, memory, null);
} }
foreach (var pool in submission.RetirePools) // The fence has signalled, so the detile dispatch that used these
// is done reading them; hand them back for the next texture.
foreach (var transients in submission.RetireDetile)
{ {
_vk.DestroyDescriptorPool(_device, pool, null); _detilePass?.Retire(transients);
} }
ReleaseGuestCommandBuffer(submission.CommandBuffer); ReleaseGuestCommandBuffer(submission.CommandBuffer);
@@ -7855,7 +7855,8 @@ internal static unsafe class VulkanVideoPresenter
// copy because this identity was marked cached; a miss here is // copy because this identity was marked cached; a miss here is
// an invalidation race (eviction, cache clear). Self-heal by // an invalidation race (eviction, cache clear). Self-heal by
// reading the texels directly rather than rendering a fallback. // reading the texels directly rather than rendering a fallback.
if (texture.RgbaPixels.Length == 0) if (texture.RgbaPixels.Length == 0 &&
texture.TiledSource is not { Length: > 0 })
{ {
var refreshed = TryReadGuestTexturePixels(texture); var refreshed = TryReadGuestTexturePixels(texture);
if (refreshed is null) if (refreshed is null)
@@ -8300,6 +8301,7 @@ internal static unsafe class VulkanVideoPresenter
texture.Detile is { } detileCandidate && texture.Detile is { } detileCandidate &&
texture.TiledSource is { Length: > 0 } tiledCandidate && texture.TiledSource is { Length: > 0 } tiledCandidate &&
VulkanDetilePass.Supports(detileCandidate) && VulkanDetilePass.Supports(detileCandidate) &&
!IsGuestTexture3D(texture.Type) &&
detileCandidate.ElementsWide > 0 && detileCandidate.ElementsWide > 0 &&
detileCandidate.ElementsHigh > 0 && detileCandidate.ElementsHigh > 0 &&
(long)tiledCandidate.Length >= (long)tiledCandidate.Length >=
@@ -8458,8 +8460,7 @@ internal static unsafe class VulkanVideoPresenter
detileParameters, detileParameters,
out var detileTransients)) out var detileTransients))
{ {
_batchRetireBuffers.AddRange(detileTransients.Buffers); _batchRetireDetile.Add(detileTransients);
_batchRetireDescriptorPools.Add(detileTransients.DescriptorPool);
gpuDetiled = true; gpuDetiled = true;
} }
} }
@@ -9444,7 +9445,8 @@ internal static unsafe class VulkanVideoPresenter
size, size,
BufferUsageFlags.StorageBufferBit, BufferUsageFlags.StorageBufferBit,
MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit, MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit,
out var memory); out var memory,
preferredMemoryFlags: MemoryPropertyFlags.HostCachedBit);
void* mapped; void* mapped;
Check(_vk.MapMemory(_device, memory, 0, size, 0, &mapped), "vkMapMemory(guest buffer)"); Check(_vk.MapMemory(_device, memory, 0, size, 0, &mapped), "vkMapMemory(guest buffer)");
var shadow = new byte[checked((int)size)]; var shadow = new byte[checked((int)size)];
@@ -10253,7 +10255,8 @@ internal static unsafe class VulkanVideoPresenter
ulong size, ulong size,
BufferUsageFlags usage, BufferUsageFlags usage,
MemoryPropertyFlags memoryFlags, MemoryPropertyFlags memoryFlags,
out DeviceMemory memory) out DeviceMemory memory,
MemoryPropertyFlags preferredMemoryFlags = 0)
{ {
var bufferInfo = new BufferCreateInfo var bufferInfo = new BufferCreateInfo
{ {
@@ -10269,7 +10272,10 @@ internal static unsafe class VulkanVideoPresenter
{ {
SType = StructureType.MemoryAllocateInfo, SType = StructureType.MemoryAllocateInfo,
AllocationSize = requirements.Size, AllocationSize = requirements.Size,
MemoryTypeIndex = FindMemoryType(requirements.MemoryTypeBits, memoryFlags), MemoryTypeIndex = FindMemoryType(
requirements.MemoryTypeBits,
memoryFlags,
preferredMemoryFlags),
}; };
Check(_vk.AllocateMemory(_device, &memoryInfo, null, out memory), "vkAllocateMemory"); Check(_vk.AllocateMemory(_device, &memoryInfo, null, out memory), "vkAllocateMemory");
Check(_vk.BindBufferMemory(_device, buffer, memory, 0), "vkBindBufferMemory"); Check(_vk.BindBufferMemory(_device, buffer, memory, 0), "vkBindBufferMemory");
@@ -10306,16 +10312,24 @@ internal static unsafe class VulkanVideoPresenter
} }
} }
private uint FindMemoryType(uint typeBits, MemoryPropertyFlags requiredFlags) private uint FindMemoryType(
uint typeBits,
MemoryPropertyFlags requiredFlags,
MemoryPropertyFlags preferredFlags = 0)
{ {
_vk.GetPhysicalDeviceMemoryProperties(_physicalDevice, out var properties); _vk.GetPhysicalDeviceMemoryProperties(_physicalDevice, out var properties);
var memoryTypes = &properties.MemoryTypes.Element0; var memoryTypes = &properties.MemoryTypes.Element0;
for (uint index = 0; index < properties.MemoryTypeCount; index++)
for (var pass = preferredFlags != 0 ? 0 : 1; pass < 2; pass++)
{ {
if ((typeBits & (1u << (int)index)) != 0 && var wanted = pass == 0 ? requiredFlags | preferredFlags : requiredFlags;
(memoryTypes[index].PropertyFlags & requiredFlags) == requiredFlags) for (uint index = 0; index < properties.MemoryTypeCount; index++)
{ {
return index; if ((typeBits & (1u << (int)index)) != 0 &&
(memoryTypes[index].PropertyFlags & wanted) == wanted)
{
return index;
}
} }
} }