mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-30 22:49:53 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1494384208 |
@@ -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;
|
||||||
|
|||||||
@@ -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, ©Region);
|
commandBuffer, resources.Output.Buffer, image, ImageLayout.TransferDstOptimal, 1, ©Region);
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user