Compare commits

...

3 Commits

Author SHA1 Message Date
ParantezTech 2e5787de06 [CPU] Fix Sema ORBIS_GEN2_ERROR_BUSY loop 2026-07-25 14:35:28 +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 355 additions and 192 deletions
@@ -1449,6 +1449,9 @@ public sealed partial class DirectExecutionBackend
var expectedSemaphoreTrywaitAgain =
string.Equals(nid, "H2a+IN9TP0E", StringComparison.Ordinal) &&
result == OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
var expectedPollSemaBusy =
string.Equals(nid, "12wOHk8ywb0", StringComparison.Ordinal) &&
result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
var expectedNetAcceptWouldBlock =
string.Equals(nid, "PIWqhn9oSxc", StringComparison.Ordinal) &&
resultValue == unchecked((int)0x80410123);
@@ -1463,6 +1466,7 @@ public sealed partial class DirectExecutionBackend
!expectedEqueueTimeout &&
!expectedMutexTrylockBusy &&
!expectedSemaphoreTrywaitAgain &&
!expectedPollSemaBusy &&
!expectedNetAcceptWouldBlock &&
!expectedUserServiceNoEvent &&
!expectedPrivacyInvalidParameter)
@@ -238,7 +238,15 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
var alignedSize = (size + 0xFFF) & ~0xFFFUL;
var protection = executable ? PAGE_EXECUTE_READWRITE : PAGE_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)
{
return false;
@@ -252,6 +260,8 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
return false;
}
var state = reservedOnly ? ReserveRegion(actualAddress, alignedSize) : "n/a";
_gate.EnterWriteLock();
try
{
@@ -260,7 +270,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
VirtualAddress = actualAddress,
Size = alignedSize,
IsExecutable = executable,
IsReservedOnly = false,
IsReservedOnly = reservedOnly,
Protection = protection
});
}
@@ -269,6 +279,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.ExitWriteLock();
}
var allocationKind = executable ? "executable memory" : "data memory";
TraceVmem($"Allocated exact {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes)");
return true;
@@ -361,44 +372,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
var actualAddress = result;
var lazyPrimeState = "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";
}
}
var lazyPrimeState = reservedOnly ? ReserveRegion(actualAddress, alignedSize) : "n/a";
_gate.EnterWriteLock();
try
@@ -425,6 +399,41 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
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)
{
if (size == 0)
+14 -1
View File
@@ -8263,6 +8263,9 @@ public static partial class AgcExports
private static bool IsGpuDetileBytesPerElement(int bytesPerElement) =>
bytesPerElement is 4 or 8 or 16;
private static bool IsGpuDetileTextureType(uint type) =>
type != Gen5TextureType3D;
private static bool TryGetTextureElementLayout(
TextureDescriptor descriptor,
uint sourceWidth,
@@ -8746,6 +8749,7 @@ public static partial class AgcExports
// take this path.
if (_gpuDetileEnabled && hasElementLayout && !baseMipInTail &&
IsGpuDetileBytesPerElement(bytesPerElement) &&
IsGpuDetileTextureType(descriptor.Type) &&
(long)physicalSourceByteCount * arrayLayers <= int.MaxValue)
{
var gpuArrayParams = GnmTiling.GetDetileParams(
@@ -8789,6 +8793,12 @@ public static partial class AgcExports
WriteGeneration: hasWriteGeneration ? writeGeneration : -1,
ArrayedView: true,
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,
Detile: gpuArrayParams);
return true;
@@ -8920,7 +8930,8 @@ public static partial class AgcExports
// Arrayed textures are handled by the arrayed branch above (they package
// every layer's tiled slice); this branch is the single-layer case.
if (_gpuDetileEnabled && hasElementLayout && !baseMipInTail &&
IsGpuDetileBytesPerElement(bytesPerElement) && !isArrayed)
IsGpuDetileBytesPerElement(bytesPerElement) && !isArrayed &&
IsGpuDetileTextureType(descriptor.Type))
{
var gpuDetileParams = GnmTiling.GetDetileParams(
descriptor.TileMode, bytesPerElement, elementsWide, elementsHigh);
@@ -8946,6 +8957,8 @@ public static partial class AgcExports
Sampler: ToGuestSampler(samplerDescriptor),
WriteGeneration: hasWriteGeneration ? writeGeneration : -1,
ArrayedView: isArrayed,
Type: descriptor.Type,
Depth: textureDepth,
TiledSource: source,
Detile: gpuDetileParams);
return true;
+270 -118
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Numerics;
using System.Runtime.CompilerServices;
using SharpEmu.Libs.Agc;
using SharpEmu.ShaderCompiler.Vulkan;
using Silk.NET.Vulkan;
@@ -45,6 +46,21 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
private bool _initialized;
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(
Vk vk,
Device device,
@@ -69,23 +85,32 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
parameters.Equation == DetileEquation.BlockTable) &&
parameters.BytesPerElement is 4 or 8 or 16;
/// <summary>Transient per-detile resources the caller must retire once the
/// command buffer they were recorded into has completed.</summary>
public readonly record struct Transients(
(VkBuffer Buffer, DeviceMemory Memory)[] Buffers,
DescriptorPool DescriptorPool);
/// <summary>Opaque handle to the pooled resources one recorded detile is using.
/// The caller hands it back to <see cref="Retire"/> once the command buffer they
/// were recorded into has completed; nothing is destroyed, the buffers and the
/// descriptor set return to this pass's free lists for the next texture.</summary>
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
{
public VkBuffer Tiled;
public DeviceMemory TiledMemory;
public VkBuffer XTerm;
public DeviceMemory XMemory;
public VkBuffer YTerm;
public DeviceMemory YMemory;
public VkBuffer Output;
public DeviceMemory OutputMemory;
public DescriptorPool Pool;
public Allocation Tiled;
public Allocation Output;
public DescriptorSet Set;
public ulong OutputBytes;
public uint SrcSliceElements;
@@ -93,6 +118,15 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
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>
/// Records the deswizzle of <paramref name="tiled"/> into <paramref name="image"/>
/// (<paramref name="texelWidth"/> x <paramref name="texelHeight"/> texels x
@@ -117,7 +151,7 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
in DetileParams parameters,
out Transients transients)
{
transients = new Transients([], default);
transients = Transients.Empty;
if (_disposed || !Supports(parameters) || texelWidth == 0 || texelHeight == 0 || layers == 0 ||
tiled.IsEmpty || tiled.Length % (int)(layers * (uint)parameters.BytesPerElement) != 0)
{
@@ -134,21 +168,33 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
}
catch
{
DestroyResources(in resources);
ReleaseResources(in resources);
throw;
}
transients = new Transients(
[
(resources.Tiled, resources.TiledMemory),
(resources.XTerm, resources.XMemory),
(resources.YTerm, resources.YMemory),
(resources.Output, resources.OutputMemory),
],
resources.Pool);
transients = new Transients
{
Tiled = resources.Tiled,
Output = resources.Output,
Set = resources.Set,
Rented = 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>
/// One-shot variant used by the isolation self-test: records the detile onto a
/// 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);
}
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.
// BlockTable: GetDetileParams' block table is already element offsets; it
// goes in binding 1 and binding 2 is an unused placeholder.
uint[] xTerm;
uint[] yTerm;
TermBuffer xTerm;
TermBuffer yTerm;
if (parameters.Equation == DetileEquation.BlockTable)
{
xTerm = new uint[parameters.BlockTable.Length];
for (var index = 0; index < xTerm.Length; index++)
{
xTerm[index] = (uint)parameters.BlockTable[index];
}
yTerm = [0];
xTerm = GetTermBuffer(_blockTermBuffers, parameters.BlockTable, shift: 0);
yTerm = GetPlaceholderTermBuffer();
resources.EquationValue = 1;
}
else
{
var shift = BitOperations.TrailingZeroCount((uint)parameters.BytesPerElement);
xTerm = ToElementTerms(parameters.XByteTerm, shift);
yTerm = ToElementTerms(parameters.YByteTerm, shift);
xTerm = GetTermBuffer(_xorTermBuffers, parameters.XByteTerm, shift);
yTerm = GetTermBuffer(_xorTermBuffers, parameters.YByteTerm, shift);
resources.EquationValue = 0;
}
@@ -249,25 +290,144 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
resources.OutputBytes =
(ulong)parameters.ElementsWide * (ulong)parameters.ElementsHigh * bytesPerElement * layers;
resources.Tiled = CreateHostBuffer((ulong)tiled.Length, BufferUsageFlags.StorageBufferBit, out resources.TiledMemory);
UploadBytes(resources.TiledMemory, tiled);
resources.XTerm = CreateHostBuffer((ulong)xTerm.Length * sizeof(uint), BufferUsageFlags.StorageBufferBit, out resources.XMemory);
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.Tiled = RentBuffer((ulong)tiled.Length, hostVisible: true);
UploadBytes(resources.Tiled.Memory, tiled);
resources.Output = RentBuffer(resources.OutputBytes, hostVisible: false);
resources.Pool = CreateDescriptorPool();
resources.Set = AllocateDescriptorSet(resources.Pool);
resources.Set = RentDescriptorSet();
WriteDescriptors(
resources.Set,
(resources.Tiled, (ulong)tiled.Length),
(resources.XTerm, (ulong)xTerm.Length * sizeof(uint)),
(resources.YTerm, (ulong)yTerm.Length * sizeof(uint)),
(resources.Output, resources.OutputBytes));
(resources.Tiled.Buffer, (ulong)tiled.Length),
(xTerm.Buffer, xTerm.ByteSize),
(yTerm.Buffer, yTerm.ByteSize),
(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(
@@ -326,7 +486,7 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
DstAccessMask = AccessFlags.TransferReadBit,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Buffer = resources.Output,
Buffer = resources.Output.Buffer,
Offset = 0,
Size = resources.OutputBytes,
};
@@ -367,7 +527,7 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
ImageExtent = new Extent3D(texelWidth, texelHeight, 1),
};
_vk.CmdCopyBufferToImage(
commandBuffer, resources.Output, image, ImageLayout.TransferDstOptimal, 1, &copyRegion);
commandBuffer, resources.Output.Buffer, image, ImageLayout.TransferDstOptimal, 1, &copyRegion);
TransitionImage(
commandBuffer,
@@ -381,19 +541,6 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
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()
{
if (_initialized)
@@ -500,45 +647,67 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
return terms;
}
private VkBuffer CreateHostBuffer(ulong size, BufferUsageFlags usage, out DeviceMemory memory)
private Allocation CreateBuffer(ulong size, bool hostVisible)
{
var bufferInfo = new BufferCreateInfo
{
SType = StructureType.BufferCreateInfo,
Size = size,
Usage = usage,
Usage = BufferUsageFlags.StorageBufferBit | BufferUsageFlags.TransferSrcBit,
SharingMode = SharingMode.Exclusive,
};
Check(_vk.CreateBuffer(_device, &bufferInfo, null, out var buffer), "vkCreateBuffer(detile)");
_vk.GetBufferMemoryRequirements(_device, buffer, out var requirements);
var required = hostVisible
? MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit
: MemoryPropertyFlags.DeviceLocalBit;
var allocateInfo = new MemoryAllocateInfo
{
SType = StructureType.MemoryAllocateInfo,
AllocationSize = requirements.Size,
MemoryTypeIndex = FindMemoryType(
requirements.MemoryTypeBits,
MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit),
MemoryTypeIndex = FindMemoryType(requirements.MemoryTypeBits, required, hostVisible),
};
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)");
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);
var memoryTypes = &properties.MemoryTypes.Element0;
for (uint index = 0; index < properties.MemoryTypeCount; index++)
if (!_memoryPropertiesLoaded)
{
if ((typeBits & (1u << (int)index)) != 0 &&
(memoryTypes[index].PropertyFlags & requiredFlags) == requiredFlags)
_vk.GetPhysicalDeviceMemoryProperties(_physicalDevice, out _memoryProperties);
_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)
@@ -558,42 +727,6 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
_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(
DescriptorSet descriptorSet,
(VkBuffer Buffer, ulong Size) binding0,
@@ -714,6 +847,25 @@ internal sealed unsafe class VulkanDetilePass : IDisposable
}
_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)
{
_vk.DestroyPipeline(_device, _pipeline, null);
@@ -261,23 +261,7 @@ internal static unsafe class VulkanDetileSelfTest
{
vk.DestroyFence(device, fence, null);
vk.FreeCommandBuffers(device, commandPool, 1, &commandBuffer);
foreach (var (buffer, memory) in transients.Buffers)
{
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);
}
pass.Retire(transients);
}
return true;
@@ -2914,9 +2914,7 @@ internal static unsafe class VulkanVideoPresenter
private readonly Stack<Fence> _recycledGuestFences = new();
private readonly Stack<CommandBuffer> _recycledGuestCommandBuffers = new();
private readonly List<(VkBuffer Buffer, DeviceMemory Memory)> _batchRetireBuffers = new();
// Descriptor pools from GPU-detile passes recorded into the batch; retired
// with the batch's fence, alongside _batchRetireBuffers.
private readonly List<DescriptorPool> _batchRetireDescriptorPools = new();
private readonly List<VulkanDetilePass.Transients> _batchRetireDetile = new();
private const int MaxRecycledGuestFences = 32;
private const int MaxRecycledGuestCommandBuffers = 32;
private VkBuffer _stagingBuffer;
@@ -3288,7 +3286,7 @@ internal static unsafe class VulkanVideoPresenter
IReadOnlyList<TranslatedDrawResources> Resources,
IReadOnlyList<GuestImageResource> TraceImages,
IReadOnlyList<(VkBuffer Buffer, DeviceMemory Memory)> RetireBuffers,
IReadOnlyList<DescriptorPool> RetirePools,
IReadOnlyList<VulkanDetilePass.Transients> RetireDetile,
ulong Timeline,
string DebugName,
VulkanGuestQueueIdentity Queue,
@@ -4963,8 +4961,8 @@ internal static unsafe class VulkanVideoPresenter
_batchResources.ToArray(),
_batchTraceImages.ToArray(),
_batchRetireBuffers.Count > 0 ? _batchRetireBuffers.ToArray() : [],
retirePools: _batchRetireDescriptorPools.Count > 0
? _batchRetireDescriptorPools.ToArray()
retireDetile: _batchRetireDetile.Count > 0
? _batchRetireDetile.ToArray()
: []);
}
catch
@@ -4983,9 +4981,9 @@ internal static unsafe class VulkanVideoPresenter
_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);
@@ -4996,7 +4994,7 @@ internal static unsafe class VulkanVideoPresenter
_batchResources.Clear();
_batchTraceImages.Clear();
_batchRetireBuffers.Clear();
_batchRetireDescriptorPools.Clear();
_batchRetireDetile.Clear();
_batchCommandBuffer = default;
}
}
@@ -5007,7 +5005,7 @@ internal static unsafe class VulkanVideoPresenter
IReadOnlyList<GuestImageResource> traceImages,
IReadOnlyList<(VkBuffer Buffer, DeviceMemory Memory)>? retireBuffers = null,
IReadOnlyList<TranslatedDrawResources>? referencedResources = null,
IReadOnlyList<DescriptorPool>? retirePools = null)
IReadOnlyList<VulkanDetilePass.Transients>? retireDetile = null)
{
var fence = AcquireGuestFence();
try
@@ -5065,7 +5063,7 @@ internal static unsafe class VulkanVideoPresenter
resources,
traceImages,
retireBuffers ?? [],
retirePools ?? [],
retireDetile ?? [],
_submitTimeline,
resources.Count > 0 ? resources[0].DebugName : "batch",
_activeGuestQueue,
@@ -5237,9 +5235,11 @@ internal static unsafe class VulkanVideoPresenter
_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);
@@ -7855,7 +7855,8 @@ internal static unsafe class VulkanVideoPresenter
// copy because this identity was marked cached; a miss here is
// an invalidation race (eviction, cache clear). Self-heal by
// 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);
if (refreshed is null)
@@ -8300,6 +8301,7 @@ internal static unsafe class VulkanVideoPresenter
texture.Detile is { } detileCandidate &&
texture.TiledSource is { Length: > 0 } tiledCandidate &&
VulkanDetilePass.Supports(detileCandidate) &&
!IsGuestTexture3D(texture.Type) &&
detileCandidate.ElementsWide > 0 &&
detileCandidate.ElementsHigh > 0 &&
(long)tiledCandidate.Length >=
@@ -8458,8 +8460,7 @@ internal static unsafe class VulkanVideoPresenter
detileParameters,
out var detileTransients))
{
_batchRetireBuffers.AddRange(detileTransients.Buffers);
_batchRetireDescriptorPools.Add(detileTransients.DescriptorPool);
_batchRetireDetile.Add(detileTransients);
gpuDetiled = true;
}
}