fix(gpu): support Gen5 flat memory and 3D images (#587)

Vector-mesh UI text samples type-10 volume LUTs; treat MIMG DIM=2 as
Dim3D and transport depth through AGC and Vulkan so Z slices no longer
collapse into a single 2D plane.
This commit is contained in:
MarcelMediaDev
2026-07-24 13:44:58 +01:00
committed by GitHub
parent 21f964a0dc
commit 5228335f15
14 changed files with 1311 additions and 299 deletions
+119 -35
View File
@@ -8328,16 +8328,33 @@ public static partial class AgcExports
return tailLinear;
}
var volumeDepth = checked((int)GetTextureVolumeDepth(
descriptor.Type,
descriptor.Depth));
if (logicalByteCount % volumeDepth != 0 ||
source.Length % volumeDepth != 0)
{
return null;
}
var logicalSliceByteCount = logicalByteCount / volumeDepth;
var physicalSliceByteCount = source.Length / volumeDepth;
var linear = new byte[logicalByteCount];
return GnmTiling.TryDetile(
source,
linear,
descriptor.TileMode,
elementsWide,
elementsHigh,
bytesPerElement)
? linear
: null;
for (var slice = 0; slice < volumeDepth; slice++)
{
if (!GnmTiling.TryDetile(
source.AsSpan(slice * physicalSliceByteCount, physicalSliceByteCount),
linear.AsSpan(slice * logicalSliceByteCount, logicalSliceByteCount),
descriptor.TileMode,
elementsWide,
elementsHigh,
bytesPerElement))
{
return null;
}
}
return linear;
}
private static void TraceTextureFallback(TextureDescriptor descriptor, string reason)
@@ -8369,6 +8386,9 @@ public static partial class AgcExports
out GuestDrawTexture texture)
{
texture = default!;
var textureDepth = GetTextureVolumeDepth(
descriptor.Type,
descriptor.Depth);
if ((descriptor.Type != Gen5TextureType1D &&
descriptor.Type != Gen5TextureType2D &&
descriptor.Type != Gen5TextureType3D &&
@@ -8381,7 +8401,13 @@ public static partial class AgcExports
descriptor.Height > 8192)
{
TraceTextureFallback(descriptor, "invalid-descriptor");
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType, isArrayed);
texture = CreateFallbackGuestDrawTexture(
isStorage,
descriptor.Format,
descriptor.NumberType,
isArrayed,
descriptor.Type,
textureDepth);
return true;
}
@@ -8391,10 +8417,15 @@ public static partial class AgcExports
descriptor.Height,
descriptor.Format)
: descriptor.Width;
var sourceByteCount = GetTextureByteCount(
var sourceSliceByteCount = GetTextureByteCount(
descriptor.Format,
sourceWidth,
descriptor.Height);
var sourceByteCount = GetTextureByteCount(
descriptor.Format,
sourceWidth,
descriptor.Height,
textureDepth);
if (sourceByteCount == 0 ||
sourceByteCount > MaxPresentedTextureBytes ||
sourceByteCount > int.MaxValue)
@@ -8402,11 +8433,17 @@ public static partial class AgcExports
TraceTextureFallback(
descriptor,
$"invalid-byte-count:{sourceByteCount}");
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType, isArrayed);
texture = CreateFallbackGuestDrawTexture(
isStorage,
descriptor.Format,
descriptor.NumberType,
isArrayed,
descriptor.Type,
textureDepth);
return true;
}
var physicalSourceByteCount = sourceByteCount;
var physicalSourceByteCount = sourceSliceByteCount;
var elementsWide = 0;
var elementsHigh = 0;
var bytesPerElement = 0;
@@ -8428,13 +8465,6 @@ public static partial class AgcExports
physicalSourceByteCount = tiledByteCount;
}
if (physicalSourceByteCount > MaxPresentedTextureBytes ||
physicalSourceByteCount > int.MaxValue)
{
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType, isArrayed);
return true;
}
var resourceMipLevels = descriptor.HasExtendedDescriptor
? descriptor.ResourceMipLevels
: 1u;
@@ -8459,6 +8489,20 @@ public static partial class AgcExports
chainSliceBytes = placedChainSliceBytes;
}
physicalSourceByteCount = checked(physicalSourceByteCount * textureDepth);
if (physicalSourceByteCount > MaxPresentedTextureBytes ||
physicalSourceByteCount > int.MaxValue)
{
texture = CreateFallbackGuestDrawTexture(
isStorage,
descriptor.Format,
descriptor.NumberType,
isArrayed,
descriptor.Type,
textureDepth);
return true;
}
var wantsArrayUpload = isArrayed &&
!isStorage &&
descriptor.Address != 0 &&
@@ -8497,7 +8541,9 @@ public static partial class AgcExports
TileMode: descriptor.TileMode,
DstSelect: descriptor.DstSelect,
Sampler: ToGuestSampler(samplerDescriptor),
ArrayedView: isArrayed);
ArrayedView: isArrayed,
Type: descriptor.Type,
Depth: textureDepth);
return true;
}
@@ -8573,7 +8619,9 @@ public static partial class AgcExports
Pitch: sourceWidth,
TileMode: descriptor.TileMode,
DstSelect: descriptor.DstSelect,
Sampler: ToGuestSampler(samplerDescriptor));
Sampler: ToGuestSampler(samplerDescriptor),
Type: descriptor.Type,
Depth: textureDepth);
return true;
}
@@ -8614,7 +8662,9 @@ public static partial class AgcExports
sourceWidth,
sampler,
isArrayed,
arrayUploadLayers)))
arrayUploadLayers,
descriptor.Type,
textureDepth)))
{
texture = new GuestDrawTexture(
descriptor.Address,
@@ -8634,14 +8684,16 @@ public static partial class AgcExports
DstSelect: descriptor.DstSelect,
Sampler: sampler,
ArrayedView: isArrayed,
ArrayLayers: arrayUploadLayers);
ArrayLayers: arrayUploadLayers,
Type: descriptor.Type,
Depth: textureDepth);
return true;
}
if (wantsArrayUpload)
{
var arrayLayers = arrayUploadLayers;
var layerBytes = checked((int)sourceByteCount);
var layerBytes = checked((int)sourceSliceByteCount);
var totalBytes = (long)layerBytes * arrayLayers;
if (totalBytes <= int.MaxValue)
{
@@ -8649,7 +8701,7 @@ public static partial class AgcExports
var uploadedLayers = 0u;
for (var layer = 0u; layer < arrayLayers; layer++)
{
var sliceSource = new byte[(int)physicalSourceByteCount];
var sliceSource = new byte[(int)chainSliceBytes];
if (!ctx.Memory.TryRead(
descriptor.Address + layer * chainSliceBytes + baseMipByteOffset,
sliceSource))
@@ -8690,7 +8742,9 @@ public static partial class AgcExports
DstSelect: descriptor.DstSelect,
Sampler: sampler,
ArrayedView: true,
ArrayLayers: arrayLayers);
ArrayLayers: arrayLayers,
Type: descriptor.Type,
Depth: textureDepth);
return true;
}
}
@@ -8704,7 +8758,13 @@ public static partial class AgcExports
TraceTextureFallback(
descriptor,
$"guest-read-failed:{sourceByteCount}");
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType, isArrayed);
texture = CreateFallbackGuestDrawTexture(
isStorage,
descriptor.Format,
descriptor.NumberType,
isArrayed,
descriptor.Type,
textureDepth);
return true;
}
@@ -8759,7 +8819,9 @@ public static partial class AgcExports
DstSelect: descriptor.DstSelect,
Sampler: ToGuestSampler(samplerDescriptor),
WriteGeneration: hasWriteGeneration ? writeGeneration : -1,
ArrayedView: isArrayed);
ArrayedView: isArrayed,
Type: descriptor.Type,
Depth: textureDepth);
return true;
}
@@ -9039,7 +9101,9 @@ public static partial class AgcExports
bool isStorage,
uint format,
uint numberType,
bool isArrayed = false)
bool isArrayed = false,
uint type = Gen5TextureType2D,
uint depth = 1)
{
var fallbackFormat = format == 0 ? 10u : format;
var fallbackNumberType = numberType;
@@ -9054,7 +9118,9 @@ public static partial class AgcExports
IsStorage: isStorage,
MipLevels: 1,
MipLevel: 0,
ArrayedView: isArrayed);
ArrayedView: isArrayed,
Type: type,
Depth: GetTextureVolumeDepth(type, depth));
}
private static GuestSampler ToGuestSampler(IReadOnlyList<uint> descriptor) =>
@@ -10240,7 +10306,8 @@ public static partial class AgcExports
var totalBytes = GetTextureByteCount(
texture.Format,
texture.Width,
texture.Height);
texture.Height,
GetTextureVolumeDepth(texture.Type, texture.Depth));
if (totalBytes == 0)
{
return "probe=unsupported";
@@ -10323,20 +10390,37 @@ public static partial class AgcExports
_ => 0UL,
};
private static ulong GetTextureByteCount(uint format, uint width, uint height)
internal static ulong GetTextureByteCount(
uint format,
uint width,
uint height,
uint depth = 1)
{
var bytesPerTexel = GetTextureBytesPerTexel(format);
if (bytesPerTexel != 0)
{
return checked((ulong)width * height * bytesPerTexel);
return checked(
(ulong)width *
height *
Math.Max(depth, 1u) *
bytesPerTexel);
}
var blockBytes = (ulong)GetBlockCompressedBlockBytes(format);
return blockBytes == 0
? 0
: checked(((ulong)width + 3) / 4 * (((ulong)height + 3) / 4) * blockBytes);
: checked(
((ulong)width + 3) / 4 *
(((ulong)height + 3) / 4) *
Math.Max(depth, 1u) *
blockBytes);
}
internal static uint GetTextureVolumeDepth(uint type, uint depth) =>
type == Gen5TextureType3D
? Math.Max(depth, 1u)
: 1u;
private static uint GetLinearTexturePitch(uint pitch, uint height, uint format)
{
var bytesPerTexel = GetTextureBytesPerTexel(format);
+8 -3
View File
@@ -10,7 +10,8 @@ namespace SharpEmu.Libs.Gpu;
// translation for its API.
/// <summary>A guest texture referenced by a draw or dispatch. Format/NumberType/
/// TileMode/DstSelect are raw guest descriptor codes.</summary>
/// TileMode/DstSelect/Type are raw guest descriptor codes. Depth is the
/// normalized volume depth (one for non-3D resources).</summary>
internal sealed record GuestDrawTexture(
ulong Address,
uint Width,
@@ -32,7 +33,9 @@ internal sealed record GuestDrawTexture(
// from; -1 when the range is untracked or the pixels were not read here.
long WriteGeneration = -1,
bool ArrayedView = false,
uint ArrayLayers = 1);
uint ArrayLayers = 1,
uint Type = 9,
uint Depth = 1);
/// <summary>Raw guest sampler descriptor dwords, copied verbatim from guest memory.</summary>
internal readonly record struct GuestSampler(
@@ -55,7 +58,9 @@ internal readonly record struct TextureContentIdentity(
uint Pitch,
GuestSampler Sampler,
bool Arrayed = false,
uint ArrayLayers = 1);
uint ArrayLayers = 1,
uint Type = 9,
uint Depth = 1);
internal sealed record GuestMemoryBuffer(
ulong BaseAddress,
@@ -113,6 +113,26 @@ internal static unsafe class VulkanVideoPresenter
// always takes its dimensions from the native child control instead.
private const uint DefaultWindowWidth = 1920;
private const uint DefaultWindowHeight = 1080;
internal const uint Gen5TextureType2D = 9;
internal const uint Gen5TextureType3D = 10;
internal static bool IsGuestTexture3D(uint type) =>
type == Gen5TextureType3D;
internal static uint GetGuestTextureDepth(uint type, uint depth) =>
IsGuestTexture3D(type) ? Math.Max(depth, 1u) : 1u;
internal static ImageType GetGuestTextureImageType(uint type) =>
IsGuestTexture3D(type) ? ImageType.Type3D : ImageType.Type2D;
internal static ImageViewType GetGuestTextureViewType(
uint type,
bool arrayedView = false) =>
IsGuestTexture3D(type)
? ImageViewType.Type3D
: arrayedView
? ImageViewType.Type2DArray
: ImageViewType.Type2D;
internal enum StorageImageComponentKind
{
@@ -1759,6 +1779,13 @@ internal static unsafe class VulkanVideoPresenter
return checked((ulong)width * height * bytesPerPixel);
}
internal static ulong GetGuestImageByteCount(
uint format,
uint width,
uint height,
uint depth) =>
checked(GetGuestImageByteCount(format, width, height) * Math.Max(depth, 1u));
// Maps a UNORM swapchain format to the sRGB view of the same bit layout,
// or Undefined when no counterpart exists. Used to encode linear-float
// guest flips on their way into a UNORM swapchain.
@@ -2957,6 +2984,8 @@ internal static unsafe class VulkanVideoPresenter
ulong Address,
uint Width,
uint Height,
uint Depth,
uint Type,
uint MipLevels,
uint GuestFormat,
Format Format);
@@ -3110,9 +3139,12 @@ internal static unsafe class VulkanVideoPresenter
public ImageView View;
public uint Width;
public uint Height;
public uint Depth = 1;
public uint Type = Gen5TextureType2D;
public uint RowLength;
public uint DstSelect;
public uint Layers = 1;
public uint MipLevel;
public bool NeedsUpload;
public bool OwnsStorage;
public bool IsStorage;
@@ -3214,9 +3246,12 @@ internal static unsafe class VulkanVideoPresenter
public long FlipVersion;
public uint Width;
public uint Height;
public uint Depth = 1;
public uint Type = Gen5TextureType2D;
// Unscaled guest-requested size; Width/Height are the physical (scaled) backing size.
public uint LogicalWidth;
public uint LogicalHeight;
public uint LogicalDepth = 1;
public uint MipLevels;
public uint GuestFormat;
public Format Format;
@@ -5599,7 +5634,8 @@ internal static unsafe class VulkanVideoPresenter
var rowLength = texture.TileMode == 0
? Math.Max(texture.Pitch, width)
: width;
var byteCount = GetTextureByteCount(texture.Format, rowLength, height);
var depth = GetGuestTextureDepth(texture.Type, texture.Depth);
var byteCount = GetTextureByteCount(texture.Format, rowLength, height, depth);
if (byteCount == 0 || byteCount > int.MaxValue)
{
return null;
@@ -7461,6 +7497,8 @@ internal static unsafe class VulkanVideoPresenter
View = view,
Width = guestImage.Width,
Height = guestImage.Height,
Depth = guestImage.Depth,
Type = guestImage.Type,
RowLength = guestImage.Width,
DstSelect = texture.DstSelect,
SamplerState = texture.Sampler,
@@ -7500,6 +7538,8 @@ internal static unsafe class VulkanVideoPresenter
if (!guestImage.IsCpuBacked ||
guestImage.Width != texture.Width ||
guestImage.Height != texture.Height ||
guestImage.Depth != GetGuestTextureDepth(texture.Type, texture.Depth) ||
IsGuestTexture3D(guestImage.Type) != IsGuestTexture3D(texture.Type) ||
guestImage.MipLevels != 1 ||
texture.RgbaPixels.Length == 0)
{
@@ -7509,7 +7549,12 @@ internal static unsafe class VulkanVideoPresenter
var rowLength = texture.TileMode == 0
? Math.Max(texture.Pitch, texture.Width)
: texture.Width;
var expectedSize = GetTextureByteCount(texture.Format, rowLength, texture.Height);
var depth = GetGuestTextureDepth(texture.Type, texture.Depth);
var expectedSize = GetTextureByteCount(
texture.Format,
rowLength,
texture.Height,
depth);
if (expectedSize == 0 || expectedSize > int.MaxValue)
{
return false;
@@ -7556,6 +7601,8 @@ internal static unsafe class VulkanVideoPresenter
View = view,
Width = guestImage.Width,
Height = guestImage.Height,
Depth = guestImage.Depth,
Type = guestImage.Type,
RowLength = rowLength,
DstSelect = texture.DstSelect,
NeedsUpload = true,
@@ -7665,6 +7712,12 @@ internal static unsafe class VulkanVideoPresenter
GuestDrawTexture texture,
out TextureResource resource)
{
if (IsGuestTexture3D(texture.Type))
{
resource = null!;
return false;
}
foreach (var depth in _guestDepthImages.Values)
{
if (texture.Address != depth.Address &&
@@ -7761,7 +7814,9 @@ internal static unsafe class VulkanVideoPresenter
texture.Pitch,
texture.Sampler,
texture.ArrayedView,
Math.Max(texture.ArrayLayers, 1));
Math.Max(texture.ArrayLayers, 1),
Type: texture.Type,
Depth: GetGuestTextureDepth(texture.Type, texture.Depth));
if (_textureCache.TryGetValue(key, out var cached))
{
return cached;
@@ -7886,6 +7941,20 @@ internal static unsafe class VulkanVideoPresenter
GuestDrawTexture texture,
GuestImageResource guestImage)
{
var textureIs3D = IsGuestTexture3D(texture.Type);
if (textureIs3D != IsGuestTexture3D(guestImage.Type))
{
return false;
}
if (textureIs3D)
{
return texture.Width == guestImage.LogicalWidth &&
texture.Height == guestImage.LogicalHeight &&
GetGuestTextureDepth(texture.Type, texture.Depth) ==
guestImage.LogicalDepth;
}
if (guestImage.LogicalWidth == texture.Width &&
guestImage.LogicalHeight == texture.Height)
{
@@ -7921,6 +7990,9 @@ internal static unsafe class VulkanVideoPresenter
$"format={texture.Format}/num={texture.NumberType}.");
}
var selectedMipLevel = GetStorageMipLevel(texture);
var mipWidth = GetMipDimension(guestImage.Width, selectedMipLevel);
var mipHeight = GetMipDimension(guestImage.Height, selectedMipLevel);
var mipDepth = GetMipDimension(guestImage.Depth, selectedMipLevel);
var view = GetOrCreateGuestImageIdentityView(
guestImage,
vkFormat,
@@ -7931,10 +8003,13 @@ internal static unsafe class VulkanVideoPresenter
Address = texture.Address,
Image = guestImage.Image,
View = view,
Width = guestImage.Width,
Height = guestImage.Height,
RowLength = guestImage.Width,
Width = mipWidth,
Height = mipHeight,
Depth = mipDepth,
Type = guestImage.Type,
RowLength = mipWidth,
DstSelect = texture.DstSelect,
MipLevel = selectedMipLevel,
IsStorage = true,
SamplerState = texture.Sampler,
GuestImage = guestImage,
@@ -7947,7 +8022,8 @@ internal static unsafe class VulkanVideoPresenter
var expectedSize = GetTextureByteCount(
texture.Format,
texture.Width,
texture.Height);
texture.Height,
GetGuestTextureDepth(texture.Type, texture.Depth));
if ((ulong)texture.RgbaPixels.Length == expectedSize &&
texture.RgbaPixels.AsSpan().IndexOfAnyExcept((byte)0) >= 0)
{
@@ -7998,6 +8074,7 @@ internal static unsafe class VulkanVideoPresenter
{
var width = Math.Max(texture.Width, 1);
var height = Math.Max(texture.Height, 1);
var depth = GetGuestTextureDepth(texture.Type, texture.Depth);
var vkFormat = GetStorageImageFormat(
GetTextureFormat(texture.Format, texture.NumberType));
if (!SupportsStorageImage(vkFormat))
@@ -8009,9 +8086,9 @@ internal static unsafe class VulkanVideoPresenter
var imageInfo = new ImageCreateInfo
{
SType = StructureType.ImageCreateInfo,
ImageType = ImageType.Type2D,
ImageType = GetGuestTextureImageType(texture.Type),
Format = vkFormat,
Extent = new Extent3D(width, height, 1),
Extent = new Extent3D(width, height, depth),
MipLevels = 1,
ArrayLayers = 1,
Samples = SampleCountFlags.Count1Bit,
@@ -8047,7 +8124,7 @@ internal static unsafe class VulkanVideoPresenter
{
SType = StructureType.ImageViewCreateInfo,
Image = image,
ViewType = ImageViewType.Type2D,
ViewType = GetGuestTextureViewType(texture.Type),
Format = vkFormat,
Components = new ComponentMapping(
ComponentSwizzle.Identity,
@@ -8067,8 +8144,11 @@ internal static unsafe class VulkanVideoPresenter
Address = 0,
Width = width,
Height = height,
Depth = depth,
Type = texture.Type,
LogicalWidth = width,
LogicalHeight = height,
LogicalDepth = depth,
MipLevels = 1,
GuestFormat = GetGuestTextureFormat(texture.Format, texture.NumberType),
Format = vkFormat,
@@ -8086,6 +8166,8 @@ internal static unsafe class VulkanVideoPresenter
View = view,
Width = width,
Height = height,
Depth = depth,
Type = texture.Type,
RowLength = width,
DstSelect = texture.DstSelect,
OwnsStorage = true,
@@ -8113,7 +8195,9 @@ internal static unsafe class VulkanVideoPresenter
texture.NumberType,
texture.ResourceMipLevels),
format,
requiresStorage: true);
requiresStorage: true,
texture.Type,
GetGuestTextureDepth(texture.Type, texture.Depth));
var selectedMipLevel = GetStorageMipLevel(texture);
if (selectedMipLevel >= guestImage.MipLevels)
{
@@ -8146,21 +8230,29 @@ internal static unsafe class VulkanVideoPresenter
{
var width = Math.Max(texture.Width, 1);
var height = Math.Max(texture.Height, 1);
var depth = GetGuestTextureDepth(texture.Type, texture.Depth);
var rowLength = texture.TileMode == 0
? Math.Max(texture.Pitch, width)
: width;
var vkFormat = GetTextureFormat(texture.Format, texture.NumberType);
var layers = Math.Max(texture.ArrayLayers, 1);
var expectedSize = GetTextureByteCount(texture.Format, rowLength, height);
var layers = IsGuestTexture3D(texture.Type)
? 1u
: Math.Max(texture.ArrayLayers, 1);
var expectedSize = GetTextureByteCount(
texture.Format,
rowLength,
height,
depth);
if (ShouldTraceVulkanResources() &&
_tracedTextureUploads.Add((texture.Address, width, height, vkFormat)))
{
Console.Error.WriteLine(
$"[LOADER][TRACE] vk.texture addr=0x{texture.Address:X16} " +
$"fmt={texture.Format} num={texture.NumberType} vk={vkFormat} " +
$"size={width}x{height} row={rowLength} tile={texture.TileMode} " +
$"layers={layers} dst=0x{texture.DstSelect:X3} " +
$"size={width}x{height}x{depth} type={texture.Type} " +
$"row={rowLength} tile={texture.TileMode} layers={layers} " +
$"dst=0x{texture.DstSelect:X3} " +
$"bytes={texture.RgbaPixels.Length} expected={expectedSize}");
}
var pixels = texture.RgbaPixels.Length == (int)(expectedSize * layers)
@@ -8189,26 +8281,32 @@ internal static unsafe class VulkanVideoPresenter
uploadPixels,
$"{TextureDebugName(texture, vkFormat)} staging");
var supportsAttachmentUsage = !IsBlockCompressedFormat(vkFormat);
var supportsStorageUsage = supportsAttachmentUsage &&
var supportsMutableUsage = !IsBlockCompressedFormat(vkFormat);
var supportsAttachmentUsage =
supportsMutableUsage &&
!IsGuestTexture3D(texture.Type);
var supportsStorageUsage =
supportsMutableUsage &&
SupportsStorageImage(vkFormat);
var imageInfo = new ImageCreateInfo
{
SType = StructureType.ImageCreateInfo,
Flags = supportsAttachmentUsage
Flags = supportsMutableUsage
? ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit
: 0,
ImageType = ImageType.Type2D,
ImageType = GetGuestTextureImageType(texture.Type),
Format = vkFormat,
Extent = new Extent3D(width, height, 1),
Extent = new Extent3D(width, height, depth),
MipLevels = 1,
ArrayLayers = layers,
Samples = SampleCountFlags.Count1Bit,
Tiling = ImageTiling.Optimal,
Usage = supportsAttachmentUsage
Usage = supportsMutableUsage
? ImageUsageFlags.TransferDstBit |
ImageUsageFlags.SampledBit |
ImageUsageFlags.ColorAttachmentBit |
(supportsAttachmentUsage
? ImageUsageFlags.ColorAttachmentBit
: (ImageUsageFlags)0) |
(supportsStorageUsage ? ImageUsageFlags.StorageBit : (ImageUsageFlags)0) |
ImageUsageFlags.TransferSrcBit
: ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
@@ -8232,9 +8330,9 @@ internal static unsafe class VulkanVideoPresenter
{
SType = StructureType.ImageViewCreateInfo,
Image = image,
ViewType = texture.ArrayedView
? ImageViewType.Type2DArray
: ImageViewType.Type2D,
ViewType = GetGuestTextureViewType(
texture.Type,
texture.ArrayedView),
Format = vkFormat,
Components = ToVkComponentMapping(texture.DstSelect),
SubresourceRange = ColorSubresourceRange(layerCount: layers),
@@ -8253,6 +8351,8 @@ internal static unsafe class VulkanVideoPresenter
View = view,
Width = width,
Height = height,
Depth = depth,
Type = texture.Type,
RowLength = rowLength,
DstSelect = texture.DstSelect,
Layers = layers,
@@ -8275,8 +8375,11 @@ internal static unsafe class VulkanVideoPresenter
Address = texture.Address,
Width = width,
Height = height,
Depth = depth,
Type = texture.Type,
LogicalWidth = width,
LogicalHeight = height,
LogicalDepth = depth,
MipLevels = 1,
GuestFormat = guestFormat,
Format = vkFormat,
@@ -9731,7 +9834,13 @@ internal static unsafe class VulkanVideoPresenter
{
if (format is 9 or 10)
{
return CreateBlackFrame(width, height);
var pixels = new byte[checked((int)expectedSize)];
for (var offset = 3; offset < pixels.Length; offset += 4)
{
pixels[offset] = 0xFF;
}
return pixels;
}
return new byte[checked((int)expectedSize)];
@@ -9762,6 +9871,13 @@ internal static unsafe class VulkanVideoPresenter
private static ulong GetTextureByteCount(uint format, uint width, uint height)
=> GetGuestImageByteCount(format, width, height);
private static ulong GetTextureByteCount(
uint format,
uint width,
uint height,
uint depth) =>
GetGuestImageByteCount(format, width, height, depth);
private static ulong GetVulkanImageByteCount(Format format, uint width, uint height)
{
var blockBytes = format switch
@@ -9808,6 +9924,13 @@ internal static unsafe class VulkanVideoPresenter
: checked((ulong)width * height * bitsPerTexel / 8);
}
private static ulong GetVulkanImageByteCount(
Format format,
uint width,
uint height,
uint depth) =>
checked(GetVulkanImageByteCount(format, width, height) * Math.Max(depth, 1u));
private bool SupportsColorAttachment(Format format)
{
_vk.GetPhysicalDeviceFormatProperties(_physicalDevice, format, out var properties);
@@ -11786,7 +11909,8 @@ internal static unsafe class VulkanVideoPresenter
return 0;
}
var texelCount = (ulong)target.Width * target.Height;
var rowsPerVolume = checked((ulong)target.Height * target.Depth);
var texelCount = checked((ulong)target.Width * rowsPerVolume);
if (texelCount == 0 || expectedByteCount % texelCount != 0)
{
// Block-compressed or otherwise non-linear: no per-texel pitch.
@@ -11794,12 +11918,12 @@ internal static unsafe class VulkanVideoPresenter
}
var bytesPerTexel = expectedByteCount / texelCount;
if (bytesPerTexel == 0 || uploadByteCount % target.Height != 0)
if (bytesPerTexel == 0 || uploadByteCount % rowsPerVolume != 0)
{
return 0;
}
var rowBytes = uploadByteCount / target.Height;
var rowBytes = uploadByteCount / rowsPerVolume;
if (rowBytes % bytesPerTexel != 0)
{
return 0;
@@ -11836,7 +11960,8 @@ internal static unsafe class VulkanVideoPresenter
var expectedByteCount = GetVulkanImageByteCount(
target.Format,
target.Width,
target.Height);
target.Height,
target.Depth);
// The guest can hand us linear pixel data whose rows are padded out
// to a hardware pitch wider than the image, so the byte count runs
@@ -11931,7 +12056,10 @@ internal static unsafe class VulkanVideoPresenter
0,
1),
ImageOffset = default,
ImageExtent = new Extent3D(target.Width, target.Height, 1),
ImageExtent = new Extent3D(
target.Width,
target.Height,
target.Depth),
};
_vk.CmdCopyBufferToImage(
commandBuffer,
@@ -11993,8 +12121,11 @@ internal static unsafe class VulkanVideoPresenter
private GuestImageResource GetOrCreateGuestImage(
GuestRenderTarget target,
Format format,
bool requiresStorage = false)
bool requiresStorage = false,
uint type = Gen5TextureType2D,
uint depth = 1)
{
depth = GetGuestTextureDepth(type, depth);
var supportsStorageUsage = SupportsStorageImage(format);
if (requiresStorage && !supportsStorageUsage)
{
@@ -12010,12 +12141,19 @@ internal static unsafe class VulkanVideoPresenter
var physicalHeight = requiresStorage
? target.Height
: ScaleGuestDimension(target.Height);
var mipLevels = ClampMipLevels(physicalWidth, physicalHeight, target.MipLevels);
var physicalDepth = depth;
var mipLevels = ClampMipLevels(
physicalWidth,
physicalHeight,
physicalDepth,
target.MipLevels);
var guestFormat = GetGuestTextureFormat(target.Format, target.NumberType);
var requestedKey = new GuestImageVariantKey(
target.Address,
target.Width,
target.Height,
depth,
type,
mipLevels,
guestFormat,
format);
@@ -12023,6 +12161,8 @@ internal static unsafe class VulkanVideoPresenter
{
if (existing.LogicalWidth == target.Width &&
existing.LogicalHeight == target.Height &&
existing.LogicalDepth == depth &&
existing.Type == type &&
existing.MipLevels == mipLevels &&
existing.GuestFormat == guestFormat &&
existing.Format == format)
@@ -12035,7 +12175,9 @@ internal static unsafe class VulkanVideoPresenter
existing.IsCpuBacked = false;
existing.CpuContentFingerprint = 0;
if (existing.RenderPass.Handle == 0 && !requiresStorage)
if (existing.RenderPass.Handle == 0 &&
!requiresStorage &&
!IsGuestTexture3D(type))
{
var attachmentView = existing.MipViews.Length > 0
? existing.MipViews[0]
@@ -12074,9 +12216,11 @@ internal static unsafe class VulkanVideoPresenter
existing.Address,
existing.LogicalWidth,
existing.LogicalHeight,
existing.LogicalDepth,
existing.Type,
existing.MipLevels,
existing.GuestFormat,
existing.Format),
existing.GuestFormat,
existing.Format),
existing);
_guestImages.Remove(target.Address);
lock (_gate)
@@ -12111,14 +12255,19 @@ internal static unsafe class VulkanVideoPresenter
_guestImageExtents[target.Address] = (
target.Width,
target.Height,
GetTextureByteCount(target.Format, target.Width, target.Height));
GetTextureByteCount(
target.Format,
target.Width,
target.Height,
depth));
}
if (target.Width <= 1920 && target.Height <= 1080)
{
SharpEmu.HLE.GuestImageWriteTracker.Track(
target.Address,
(ulong)target.Width * target.Height * GetTextureBytesPerPixel(target.Format),
(ulong)target.Width * target.Height * depth *
GetTextureBytesPerPixel(target.Format),
CurrentGuestWorkSequenceForDiagnostics,
"vulkan.render-target");
}
@@ -12140,15 +12289,17 @@ internal static unsafe class VulkanVideoPresenter
Flags =
ImageCreateFlags.CreateMutableFormatBit |
ImageCreateFlags.CreateExtendedUsageBit,
ImageType = ImageType.Type2D,
ImageType = GetGuestTextureImageType(type),
Format = format,
Extent = new Extent3D(physicalWidth, physicalHeight, 1),
Extent = new Extent3D(physicalWidth, physicalHeight, physicalDepth),
MipLevels = mipLevels,
ArrayLayers = 1,
Samples = SampleCountFlags.Count1Bit,
Tiling = ImageTiling.Optimal,
Usage =
ImageUsageFlags.ColorAttachmentBit |
(IsGuestTexture3D(type)
? (ImageUsageFlags)0
: ImageUsageFlags.ColorAttachmentBit) |
ImageUsageFlags.SampledBit |
(supportsStorageUsage ? ImageUsageFlags.StorageBit : (ImageUsageFlags)0) |
ImageUsageFlags.TransferSrcBit |
@@ -12178,7 +12329,7 @@ internal static unsafe class VulkanVideoPresenter
{
SType = StructureType.ImageViewCreateInfo,
Image = image,
ViewType = ImageViewType.Type2D,
ViewType = GetGuestTextureViewType(type),
Format = format,
Components = new ComponentMapping(
ComponentSwizzle.Identity,
@@ -12209,7 +12360,7 @@ internal static unsafe class VulkanVideoPresenter
RenderPass renderPass = default;
RenderPass initialRenderPass = default;
Framebuffer framebuffer = default;
if (!requiresStorage)
if (!requiresStorage && !IsGuestTexture3D(type))
{
(renderPass, initialRenderPass, framebuffer) =
CreateRenderPassAndFramebuffer(
@@ -12224,8 +12375,11 @@ internal static unsafe class VulkanVideoPresenter
Address = target.Address,
Width = physicalWidth,
Height = physicalHeight,
Depth = physicalDepth,
Type = type,
LogicalWidth = target.Width,
LogicalHeight = target.Height,
LogicalDepth = depth,
MipLevels = mipLevels,
GuestFormat = guestFormat,
Format = format,
@@ -12260,14 +12414,19 @@ internal static unsafe class VulkanVideoPresenter
_guestImageExtents[target.Address] = (
target.Width,
target.Height,
GetTextureByteCount(target.Format, target.Width, target.Height));
GetTextureByteCount(
target.Format,
target.Width,
target.Height,
depth));
}
if (target.Width <= 1920 && target.Height <= 1080)
{
SharpEmu.HLE.GuestImageWriteTracker.Track(
target.Address,
(ulong)target.Width * target.Height * GetTextureBytesPerPixel(target.Format),
(ulong)target.Width * target.Height * depth *
GetTextureBytesPerPixel(target.Format),
CurrentGuestWorkSequenceForDiagnostics,
"vulkan.render-target");
}
@@ -12740,9 +12899,13 @@ internal static unsafe class VulkanVideoPresenter
return renderPass;
}
private static uint ClampMipLevels(uint width, uint height, uint requestedMipLevels)
private static uint ClampMipLevels(
uint width,
uint height,
uint depth,
uint requestedMipLevels)
{
var largestDimension = Math.Max(width, height);
var largestDimension = Math.Max(Math.Max(width, height), depth);
uint maximumMipLevels = 1;
while (largestDimension > 1)
{
@@ -12753,6 +12916,11 @@ internal static unsafe class VulkanVideoPresenter
return Math.Min(Math.Max(requestedMipLevels, 1u), maximumMipLevels);
}
private static uint GetMipDimension(uint dimension, uint mipLevel) =>
mipLevel >= 32
? 1
: Math.Max(dimension >> (int)mipLevel, 1u);
private void DestroyGuestImage(GuestImageResource resource)
{
foreach (var depthFramebuffer in resource.DepthFramebuffers.Values)
@@ -12929,7 +13097,7 @@ internal static unsafe class VulkanVideoPresenter
{
SType = StructureType.ImageViewCreateInfo,
Image = resource.Image,
ViewType = arrayedView ? ImageViewType.Type2DArray : ImageViewType.Type2D,
ViewType = GetGuestTextureViewType(resource.Type, arrayedView),
Format = format,
Components = ToVkComponentMapping(dstSelect),
SubresourceRange = ColorSubresourceRange(mipLevel, levelCount),
@@ -14060,7 +14228,10 @@ internal static unsafe class VulkanVideoPresenter
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = texture.Image,
SubresourceRange = ColorSubresourceRange(layerCount: texture.Layers),
SubresourceRange = ColorSubresourceRange(
texture.MipLevel,
1,
texture.Layers),
};
_vk.CmdPipelineBarrier(
_commandBuffer,
@@ -14084,9 +14255,13 @@ internal static unsafe class VulkanVideoPresenter
ImageSubresource = new ImageSubresourceLayers
{
AspectMask = ImageAspectFlags.ColorBit,
MipLevel = texture.MipLevel,
LayerCount = texture.Layers,
},
ImageExtent = new Extent3D(texture.Width, texture.Height, 1),
ImageExtent = new Extent3D(
texture.Width,
texture.Height,
texture.Depth),
};
_vk.CmdCopyBufferToImage(
_commandBuffer,
@@ -14106,7 +14281,10 @@ internal static unsafe class VulkanVideoPresenter
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = texture.Image,
SubresourceRange = ColorSubresourceRange(layerCount: texture.Layers),
SubresourceRange = ColorSubresourceRange(
texture.MipLevel,
1,
texture.Layers),
};
_vk.CmdPipelineBarrier(
_commandBuffer,