mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-25 20:28:48 +08:00
[Gpu] Sample 2D array textures with real layers (#471)
Texture arrays were uploaded and viewed as plain 2D images, so every layer index in a shader resolved to slice 0. The guest-texture gate also rejected array, 3D and cube descriptor types outright, sending those resources to the 1x1 black fallback. UI atlases hit this constantly, since they pack several sheets as array slices and pick one per vertex. In Demon's Souls the settings menu bottom bar stretched a mid-atlas crop across itself, and slider thumbs and button prompts drew the wrong sheet. The MIMG decoder already had Dimension and IsArray, so IsArrayedImageBinding makes one rule out of them for the SPIR-V translator and the Vulkan backend to share. Both have to agree or the declared image type and the bound view type mismatch. Sample and gather bindings with an array address now declare an arrayed image and pass (u, v, slice). AgcExports reads every slice at the per-slice mip-chain stride and passes the layers packed in one buffer, which uploads as a 2D array image in a single copy region. Load and store bindings are unchanged. Arrayed bindings that resolve to a fallback or to a single-layer guest image get a one-layer 2D array view so the descriptor still matches the shader. Tested on Demon's Souls (PPSA01342): the bottom bar, slider thumbs and button prompts draw their correct sheets. 470 tests pass.
This commit is contained in:
@@ -147,6 +147,10 @@ public static partial class AgcExports
|
|||||||
private const uint Gen5TextureFormatR16G16B16A16Float = 12;
|
private const uint Gen5TextureFormatR16G16B16A16Float = 12;
|
||||||
private const uint Gen5TextureType1D = 8;
|
private const uint Gen5TextureType1D = 8;
|
||||||
private const uint Gen5TextureType2D = 9;
|
private const uint Gen5TextureType2D = 9;
|
||||||
|
private const uint Gen5TextureType3D = 10;
|
||||||
|
private const uint Gen5TextureTypeCube = 11;
|
||||||
|
private const uint Gen5TextureType1DArray = 12;
|
||||||
|
private const uint Gen5TextureType2DArray = 13;
|
||||||
private const ulong MaxPresentedTextureBytes = 128UL * 1024UL * 1024UL;
|
private const ulong MaxPresentedTextureBytes = 128UL * 1024UL * 1024UL;
|
||||||
private const ulong VideoOutPixelFormatA8R8G8B8Srgb = 0x80000000;
|
private const ulong VideoOutPixelFormatA8R8G8B8Srgb = 0x80000000;
|
||||||
private const ulong VideoOutPixelFormatA8B8G8R8Srgb = 0x80002200;
|
private const ulong VideoOutPixelFormatA8B8G8R8Srgb = 0x80002200;
|
||||||
@@ -438,7 +442,8 @@ public static partial class AgcExports
|
|||||||
TextureDescriptor Descriptor,
|
TextureDescriptor Descriptor,
|
||||||
bool IsStorage,
|
bool IsStorage,
|
||||||
uint MipLevel,
|
uint MipLevel,
|
||||||
IReadOnlyList<uint> SamplerDescriptor);
|
IReadOnlyList<uint> SamplerDescriptor,
|
||||||
|
bool IsArrayed = false);
|
||||||
|
|
||||||
private readonly record struct RenderTargetWriter(
|
private readonly record struct RenderTargetWriter(
|
||||||
ulong Sequence,
|
ulong Sequence,
|
||||||
@@ -5959,7 +5964,8 @@ public static partial class AgcExports
|
|||||||
binding,
|
binding,
|
||||||
exportEvaluation.ImageBindings),
|
exportEvaluation.ImageBindings),
|
||||||
binding.MipLevel ?? 0,
|
binding.MipLevel ?? 0,
|
||||||
binding.SamplerDescriptor));
|
binding.SamplerDescriptor,
|
||||||
|
Gen5ShaderTranslator.IsArrayedImageBinding(binding)));
|
||||||
}
|
}
|
||||||
|
|
||||||
IReadOnlyList<Gen5VertexInputBinding> vertexInputs =
|
IReadOnlyList<Gen5VertexInputBinding> vertexInputs =
|
||||||
@@ -6416,7 +6422,8 @@ public static partial class AgcExports
|
|||||||
texture,
|
texture,
|
||||||
isStorage,
|
isStorage,
|
||||||
binding.MipLevel ?? 0,
|
binding.MipLevel ?? 0,
|
||||||
binding.SamplerDescriptor));
|
binding.SamplerDescriptor,
|
||||||
|
Gen5ShaderTranslator.IsArrayedImageBinding(binding)));
|
||||||
}
|
}
|
||||||
|
|
||||||
error = string.Empty;
|
error = string.Empty;
|
||||||
@@ -7387,6 +7394,7 @@ public static partial class AgcExports
|
|||||||
binding.IsStorage,
|
binding.IsStorage,
|
||||||
binding.MipLevel,
|
binding.MipLevel,
|
||||||
binding.SamplerDescriptor,
|
binding.SamplerDescriptor,
|
||||||
|
binding.IsArrayed,
|
||||||
out var texture))
|
out var texture))
|
||||||
{
|
{
|
||||||
textures.Add(texture);
|
textures.Add(texture);
|
||||||
@@ -7877,18 +7885,23 @@ public static partial class AgcExports
|
|||||||
bool isStorage,
|
bool isStorage,
|
||||||
uint mipLevel,
|
uint mipLevel,
|
||||||
IReadOnlyList<uint> samplerDescriptor,
|
IReadOnlyList<uint> samplerDescriptor,
|
||||||
|
bool isArrayed,
|
||||||
out GuestDrawTexture texture)
|
out GuestDrawTexture texture)
|
||||||
{
|
{
|
||||||
texture = default!;
|
texture = default!;
|
||||||
if ((descriptor.Type != Gen5TextureType1D &&
|
if ((descriptor.Type != Gen5TextureType1D &&
|
||||||
descriptor.Type != Gen5TextureType2D) ||
|
descriptor.Type != Gen5TextureType2D &&
|
||||||
|
descriptor.Type != Gen5TextureType3D &&
|
||||||
|
descriptor.Type != Gen5TextureTypeCube &&
|
||||||
|
descriptor.Type != Gen5TextureType1DArray &&
|
||||||
|
descriptor.Type != Gen5TextureType2DArray) ||
|
||||||
descriptor.Width == 0 ||
|
descriptor.Width == 0 ||
|
||||||
descriptor.Height == 0 ||
|
descriptor.Height == 0 ||
|
||||||
descriptor.Width > 8192 ||
|
descriptor.Width > 8192 ||
|
||||||
descriptor.Height > 8192)
|
descriptor.Height > 8192)
|
||||||
{
|
{
|
||||||
TraceTextureFallback(descriptor, "invalid-descriptor");
|
TraceTextureFallback(descriptor, "invalid-descriptor");
|
||||||
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
|
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType, isArrayed);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7909,7 +7922,7 @@ public static partial class AgcExports
|
|||||||
TraceTextureFallback(
|
TraceTextureFallback(
|
||||||
descriptor,
|
descriptor,
|
||||||
$"invalid-byte-count:{sourceByteCount}");
|
$"invalid-byte-count:{sourceByteCount}");
|
||||||
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
|
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType, isArrayed);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7938,7 +7951,7 @@ public static partial class AgcExports
|
|||||||
if (physicalSourceByteCount > MaxPresentedTextureBytes ||
|
if (physicalSourceByteCount > MaxPresentedTextureBytes ||
|
||||||
physicalSourceByteCount > int.MaxValue)
|
physicalSourceByteCount > int.MaxValue)
|
||||||
{
|
{
|
||||||
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
|
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType, isArrayed);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7949,8 +7962,8 @@ public static partial class AgcExports
|
|||||||
var baseMipInTail = false;
|
var baseMipInTail = false;
|
||||||
var mipTailElementX = 0;
|
var mipTailElementX = 0;
|
||||||
var mipTailElementY = 0;
|
var mipTailElementY = 0;
|
||||||
if (hasElementLayout && resourceMipLevels > 1)
|
var chainSliceBytes = physicalSourceByteCount;
|
||||||
{
|
if (hasElementLayout && resourceMipLevels > 1 &&
|
||||||
GnmTiling.TryGetBaseMipPlacement(
|
GnmTiling.TryGetBaseMipPlacement(
|
||||||
descriptor.TileMode,
|
descriptor.TileMode,
|
||||||
elementsWide,
|
elementsWide,
|
||||||
@@ -7960,14 +7973,26 @@ public static partial class AgcExports
|
|||||||
out baseMipByteOffset,
|
out baseMipByteOffset,
|
||||||
out baseMipInTail,
|
out baseMipInTail,
|
||||||
out mipTailElementX,
|
out mipTailElementX,
|
||||||
out mipTailElementY);
|
out mipTailElementY,
|
||||||
|
out var placedChainSliceBytes))
|
||||||
|
{
|
||||||
|
chainSliceBytes = placedChainSliceBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var wantsArrayUpload = isArrayed &&
|
||||||
|
!isStorage &&
|
||||||
|
descriptor.Address != 0 &&
|
||||||
|
(descriptor.Type == Gen5TextureType2DArray ||
|
||||||
|
descriptor.Type == Gen5TextureType1DArray) &&
|
||||||
|
descriptor.Depth > 1;
|
||||||
|
var arrayUploadLayers = wantsArrayUpload ? descriptor.Depth : 1u;
|
||||||
|
|
||||||
// Upload-known (not plain availability): the presenter's answer goes
|
// Upload-known (not plain availability): the presenter's answer goes
|
||||||
// generation-stale when the guest CPU rewrites a CPU-backed image
|
// generation-stale when the guest CPU rewrites a CPU-backed image
|
||||||
// (video planes, streamed font atlases), which routes this draw back
|
// (video planes, streamed font atlases), which routes this draw back
|
||||||
// through the texel copy below so the refresh path re-uploads.
|
// through the texel copy below so the refresh path re-uploads.
|
||||||
if (!isStorage &&
|
if (!isStorage &&
|
||||||
|
!wantsArrayUpload &&
|
||||||
descriptor.Address != 0 &&
|
descriptor.Address != 0 &&
|
||||||
GuestGpu.Current.IsGuestImageUploadKnown(
|
GuestGpu.Current.IsGuestImageUploadKnown(
|
||||||
descriptor.Address,
|
descriptor.Address,
|
||||||
@@ -7990,7 +8015,8 @@ public static partial class AgcExports
|
|||||||
Pitch: sourceWidth,
|
Pitch: sourceWidth,
|
||||||
TileMode: descriptor.TileMode,
|
TileMode: descriptor.TileMode,
|
||||||
DstSelect: descriptor.DstSelect,
|
DstSelect: descriptor.DstSelect,
|
||||||
Sampler: ToGuestSampler(samplerDescriptor));
|
Sampler: ToGuestSampler(samplerDescriptor),
|
||||||
|
ArrayedView: isArrayed);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8105,7 +8131,9 @@ public static partial class AgcExports
|
|||||||
descriptor.DstSelect,
|
descriptor.DstSelect,
|
||||||
descriptor.TileMode,
|
descriptor.TileMode,
|
||||||
sourceWidth,
|
sourceWidth,
|
||||||
sampler)))
|
sampler,
|
||||||
|
isArrayed,
|
||||||
|
arrayUploadLayers)))
|
||||||
{
|
{
|
||||||
texture = new GuestDrawTexture(
|
texture = new GuestDrawTexture(
|
||||||
descriptor.Address,
|
descriptor.Address,
|
||||||
@@ -8123,17 +8151,77 @@ public static partial class AgcExports
|
|||||||
Pitch: sourceWidth,
|
Pitch: sourceWidth,
|
||||||
TileMode: descriptor.TileMode,
|
TileMode: descriptor.TileMode,
|
||||||
DstSelect: descriptor.DstSelect,
|
DstSelect: descriptor.DstSelect,
|
||||||
Sampler: sampler);
|
Sampler: sampler,
|
||||||
|
ArrayedView: isArrayed,
|
||||||
|
ArrayLayers: arrayUploadLayers);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wantsArrayUpload)
|
||||||
|
{
|
||||||
|
var arrayLayers = arrayUploadLayers;
|
||||||
|
var layerBytes = checked((int)sourceByteCount);
|
||||||
|
var totalBytes = (long)layerBytes * arrayLayers;
|
||||||
|
if (totalBytes <= int.MaxValue)
|
||||||
|
{
|
||||||
|
var layered = new byte[totalBytes];
|
||||||
|
var uploadedLayers = 0u;
|
||||||
|
for (var layer = 0u; layer < arrayLayers; layer++)
|
||||||
|
{
|
||||||
|
var sliceSource = new byte[(int)physicalSourceByteCount];
|
||||||
|
if (!ctx.Memory.TryRead(
|
||||||
|
descriptor.Address + layer * chainSliceBytes + baseMipByteOffset,
|
||||||
|
sliceSource))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sliceLinear = TryDetileTextureSource(
|
||||||
|
descriptor,
|
||||||
|
sourceWidth,
|
||||||
|
layerBytes,
|
||||||
|
sliceSource,
|
||||||
|
baseMipInTail,
|
||||||
|
mipTailElementX,
|
||||||
|
mipTailElementY) ?? sliceSource.AsSpan(0, layerBytes).ToArray();
|
||||||
|
sliceLinear.AsSpan(0, layerBytes)
|
||||||
|
.CopyTo(layered.AsSpan(checked((int)(layer * layerBytes))));
|
||||||
|
uploadedLayers++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uploadedLayers == arrayLayers)
|
||||||
|
{
|
||||||
|
texture = new GuestDrawTexture(
|
||||||
|
descriptor.Address,
|
||||||
|
descriptor.Width,
|
||||||
|
descriptor.Height,
|
||||||
|
descriptor.Format,
|
||||||
|
descriptor.NumberType,
|
||||||
|
layered,
|
||||||
|
IsFallback: false,
|
||||||
|
IsStorage: false,
|
||||||
|
MipLevels: descriptor.MipLevels,
|
||||||
|
MipLevel: mipLevel,
|
||||||
|
BaseMipLevel: descriptor.ViewBaseLevel,
|
||||||
|
ResourceMipLevels: descriptor.ResourceMipLevels,
|
||||||
|
Pitch: sourceWidth,
|
||||||
|
TileMode: descriptor.TileMode,
|
||||||
|
DstSelect: descriptor.DstSelect,
|
||||||
|
Sampler: sampler,
|
||||||
|
ArrayedView: true,
|
||||||
|
ArrayLayers: arrayLayers);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var source = new byte[(int)physicalSourceByteCount];
|
var source = new byte[(int)physicalSourceByteCount];
|
||||||
if (!ctx.Memory.TryRead(descriptor.Address + baseMipByteOffset, source))
|
if (!ctx.Memory.TryRead(descriptor.Address + baseMipByteOffset, source))
|
||||||
{
|
{
|
||||||
TraceTextureFallback(
|
TraceTextureFallback(
|
||||||
descriptor,
|
descriptor,
|
||||||
$"guest-read-failed:{sourceByteCount}");
|
$"guest-read-failed:{sourceByteCount}");
|
||||||
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
|
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType, isArrayed);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8187,7 +8275,8 @@ public static partial class AgcExports
|
|||||||
TileMode: descriptor.TileMode,
|
TileMode: descriptor.TileMode,
|
||||||
DstSelect: descriptor.DstSelect,
|
DstSelect: descriptor.DstSelect,
|
||||||
Sampler: ToGuestSampler(samplerDescriptor),
|
Sampler: ToGuestSampler(samplerDescriptor),
|
||||||
WriteGeneration: hasWriteGeneration ? writeGeneration : -1);
|
WriteGeneration: hasWriteGeneration ? writeGeneration : -1,
|
||||||
|
ArrayedView: isArrayed);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8466,7 +8555,8 @@ public static partial class AgcExports
|
|||||||
private static GuestDrawTexture CreateFallbackGuestDrawTexture(
|
private static GuestDrawTexture CreateFallbackGuestDrawTexture(
|
||||||
bool isStorage,
|
bool isStorage,
|
||||||
uint format,
|
uint format,
|
||||||
uint numberType)
|
uint numberType,
|
||||||
|
bool isArrayed = false)
|
||||||
{
|
{
|
||||||
var fallbackFormat = format == 0 ? 10u : format;
|
var fallbackFormat = format == 0 ? 10u : format;
|
||||||
var fallbackNumberType = numberType;
|
var fallbackNumberType = numberType;
|
||||||
@@ -8480,7 +8570,8 @@ public static partial class AgcExports
|
|||||||
IsFallback: true,
|
IsFallback: true,
|
||||||
IsStorage: isStorage,
|
IsStorage: isStorage,
|
||||||
MipLevels: 1,
|
MipLevels: 1,
|
||||||
MipLevel: 0);
|
MipLevel: 0,
|
||||||
|
ArrayedView: isArrayed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GuestSampler ToGuestSampler(IReadOnlyList<uint> descriptor) =>
|
private static GuestSampler ToGuestSampler(IReadOnlyList<uint> descriptor) =>
|
||||||
@@ -8856,7 +8947,8 @@ public static partial class AgcExports
|
|||||||
texture,
|
texture,
|
||||||
isStorage,
|
isStorage,
|
||||||
binding.MipLevel ?? 0,
|
binding.MipLevel ?? 0,
|
||||||
binding.SamplerDescriptor));
|
binding.SamplerDescriptor,
|
||||||
|
Gen5ShaderTranslator.IsArrayedImageBinding(binding)));
|
||||||
hasStorageBinding |= isStorage;
|
hasStorageBinding |= isStorage;
|
||||||
|
|
||||||
var descriptorState = descriptorValid ? string.Empty : "/invalid-desc";
|
var descriptorState = descriptorValid ? string.Empty : "/invalid-desc";
|
||||||
|
|||||||
@@ -231,12 +231,14 @@ internal static class GnmTiling
|
|||||||
out ulong byteOffset,
|
out ulong byteOffset,
|
||||||
out bool inMipTail,
|
out bool inMipTail,
|
||||||
out int tailElementX,
|
out int tailElementX,
|
||||||
out int tailElementY)
|
out int tailElementY,
|
||||||
|
out ulong chainSliceBytes)
|
||||||
{
|
{
|
||||||
byteOffset = 0;
|
byteOffset = 0;
|
||||||
inMipTail = false;
|
inMipTail = false;
|
||||||
tailElementX = 0;
|
tailElementX = 0;
|
||||||
tailElementY = 0;
|
tailElementY = 0;
|
||||||
|
chainSliceBytes = 0;
|
||||||
if (resourceMipLevels <= 1 ||
|
if (resourceMipLevels <= 1 ||
|
||||||
!ShouldDetile(swizzleMode) ||
|
!ShouldDetile(swizzleMode) ||
|
||||||
elementsWide <= 0 ||
|
elementsWide <= 0 ||
|
||||||
@@ -331,15 +333,22 @@ internal static class GnmTiling
|
|||||||
}
|
}
|
||||||
|
|
||||||
inMipTail = true;
|
inMipTail = true;
|
||||||
|
chainSliceBytes = (ulong)blockBytes;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
byteOffset = firstMipInTail < mipLevels ? (ulong)blockBytes : 0;
|
byteOffset = firstMipInTail < mipLevels ? (ulong)blockBytes : 0;
|
||||||
|
chainSliceBytes = byteOffset;
|
||||||
for (var i = firstMipInTail - 1; i >= 1; i--)
|
for (var i = firstMipInTail - 1; i >= 1; i--)
|
||||||
{
|
{
|
||||||
byteOffset += mipSizes[i];
|
byteOffset += mipSizes[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < firstMipInTail; i++)
|
||||||
|
{
|
||||||
|
chainSliceBytes += mipSizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ internal sealed record GuestDrawTexture(
|
|||||||
GuestSampler Sampler = default,
|
GuestSampler Sampler = default,
|
||||||
// Guest CPU write-tracker generation of the memory RgbaPixels was read
|
// Guest CPU write-tracker generation of the memory RgbaPixels was read
|
||||||
// from; -1 when the range is untracked or the pixels were not read here.
|
// from; -1 when the range is untracked or the pixels were not read here.
|
||||||
long WriteGeneration = -1);
|
long WriteGeneration = -1,
|
||||||
|
bool ArrayedView = false,
|
||||||
|
uint ArrayLayers = 1);
|
||||||
|
|
||||||
/// <summary>Raw guest sampler descriptor dwords, copied verbatim from guest memory.</summary>
|
/// <summary>Raw guest sampler descriptor dwords, copied verbatim from guest memory.</summary>
|
||||||
internal readonly record struct GuestSampler(
|
internal readonly record struct GuestSampler(
|
||||||
@@ -51,7 +53,9 @@ internal readonly record struct TextureContentIdentity(
|
|||||||
uint DstSelect,
|
uint DstSelect,
|
||||||
uint TileMode,
|
uint TileMode,
|
||||||
uint Pitch,
|
uint Pitch,
|
||||||
GuestSampler Sampler);
|
GuestSampler Sampler,
|
||||||
|
bool Arrayed = false,
|
||||||
|
uint ArrayLayers = 1);
|
||||||
|
|
||||||
internal sealed record GuestMemoryBuffer(
|
internal sealed record GuestMemoryBuffer(
|
||||||
ulong BaseAddress,
|
ulong BaseAddress,
|
||||||
|
|||||||
@@ -3022,6 +3022,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
public uint Height;
|
public uint Height;
|
||||||
public uint RowLength;
|
public uint RowLength;
|
||||||
public uint DstSelect;
|
public uint DstSelect;
|
||||||
|
public uint Layers = 1;
|
||||||
public bool NeedsUpload;
|
public bool NeedsUpload;
|
||||||
public bool OwnsStorage;
|
public bool OwnsStorage;
|
||||||
public bool IsStorage;
|
public bool IsStorage;
|
||||||
@@ -6939,6 +6940,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
var vkFormat = GetTextureFormat(texture.Format, texture.NumberType);
|
var vkFormat = GetTextureFormat(texture.Format, texture.NumberType);
|
||||||
if (texture.Address != 0 &&
|
if (texture.Address != 0 &&
|
||||||
|
!(texture.ArrayedView && texture.ArrayLayers > 1) &&
|
||||||
TryResolveGuestImageAlias(texture, vkFormat, out var guestImage) &&
|
TryResolveGuestImageAlias(texture, vkFormat, out var guestImage) &&
|
||||||
TryGetOrCreateGuestImageView(
|
TryGetOrCreateGuestImageView(
|
||||||
guestImage,
|
guestImage,
|
||||||
@@ -6946,7 +6948,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
mipLevel: texture.BaseMipLevel,
|
mipLevel: texture.BaseMipLevel,
|
||||||
levelCount: texture.MipLevels,
|
levelCount: texture.MipLevels,
|
||||||
dstSelect: texture.DstSelect,
|
dstSelect: texture.DstSelect,
|
||||||
out var view))
|
out var view,
|
||||||
|
arrayedView: texture.ArrayedView))
|
||||||
{
|
{
|
||||||
if (ShouldTraceVulkanResources() &&
|
if (ShouldTraceVulkanResources() &&
|
||||||
_tracedTextureCacheHits.Add(
|
_tracedTextureCacheHits.Add(
|
||||||
@@ -7293,7 +7296,9 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
texture.DstSelect,
|
texture.DstSelect,
|
||||||
texture.TileMode,
|
texture.TileMode,
|
||||||
texture.Pitch,
|
texture.Pitch,
|
||||||
texture.Sampler);
|
texture.Sampler,
|
||||||
|
texture.ArrayedView,
|
||||||
|
Math.Max(texture.ArrayLayers, 1));
|
||||||
if (_textureCache.TryGetValue(key, out var cached))
|
if (_textureCache.TryGetValue(key, out var cached))
|
||||||
{
|
{
|
||||||
return cached;
|
return cached;
|
||||||
@@ -7681,6 +7686,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
: width;
|
: width;
|
||||||
var vkFormat = GetTextureFormat(texture.Format, texture.NumberType);
|
var vkFormat = GetTextureFormat(texture.Format, texture.NumberType);
|
||||||
|
|
||||||
|
var layers = Math.Max(texture.ArrayLayers, 1);
|
||||||
var expectedSize = GetTextureByteCount(texture.Format, rowLength, height);
|
var expectedSize = GetTextureByteCount(texture.Format, rowLength, height);
|
||||||
if (ShouldTraceVulkanResources() &&
|
if (ShouldTraceVulkanResources() &&
|
||||||
_tracedTextureUploads.Add((texture.Address, width, height, vkFormat)))
|
_tracedTextureUploads.Add((texture.Address, width, height, vkFormat)))
|
||||||
@@ -7689,12 +7695,16 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
$"[LOADER][TRACE] vk.texture addr=0x{texture.Address:X16} " +
|
$"[LOADER][TRACE] vk.texture addr=0x{texture.Address:X16} " +
|
||||||
$"fmt={texture.Format} num={texture.NumberType} vk={vkFormat} " +
|
$"fmt={texture.Format} num={texture.NumberType} vk={vkFormat} " +
|
||||||
$"size={width}x{height} row={rowLength} tile={texture.TileMode} " +
|
$"size={width}x{height} row={rowLength} tile={texture.TileMode} " +
|
||||||
$"dst=0x{texture.DstSelect:X3} " +
|
$"layers={layers} dst=0x{texture.DstSelect:X3} " +
|
||||||
$"bytes={texture.RgbaPixels.Length} expected={expectedSize}");
|
$"bytes={texture.RgbaPixels.Length} expected={expectedSize}");
|
||||||
}
|
}
|
||||||
var pixels = texture.RgbaPixels.Length == (int)expectedSize
|
var pixels = texture.RgbaPixels.Length == (int)(expectedSize * layers)
|
||||||
? texture.RgbaPixels
|
? texture.RgbaPixels
|
||||||
: CreateFallbackTexturePixels(texture.Format, rowLength, height, expectedSize);
|
: CreateFallbackTexturePixels(texture.Format, rowLength, height, expectedSize);
|
||||||
|
if (!ReferenceEquals(pixels, texture.RgbaPixels))
|
||||||
|
{
|
||||||
|
layers = 1;
|
||||||
|
}
|
||||||
if (AddressListContains("SHARPEMU_FORCE_WHITE_TEXTURE_TARGETS", texture.Address))
|
if (AddressListContains("SHARPEMU_FORCE_WHITE_TEXTURE_TARGETS", texture.Address))
|
||||||
{
|
{
|
||||||
pixels = pixels.ToArray();
|
pixels = pixels.ToArray();
|
||||||
@@ -7727,7 +7737,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
Format = vkFormat,
|
Format = vkFormat,
|
||||||
Extent = new Extent3D(width, height, 1),
|
Extent = new Extent3D(width, height, 1),
|
||||||
MipLevels = 1,
|
MipLevels = 1,
|
||||||
ArrayLayers = 1,
|
ArrayLayers = layers,
|
||||||
Samples = SampleCountFlags.Count1Bit,
|
Samples = SampleCountFlags.Count1Bit,
|
||||||
Tiling = ImageTiling.Optimal,
|
Tiling = ImageTiling.Optimal,
|
||||||
Usage = supportsAttachmentUsage
|
Usage = supportsAttachmentUsage
|
||||||
@@ -7757,10 +7767,12 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
{
|
{
|
||||||
SType = StructureType.ImageViewCreateInfo,
|
SType = StructureType.ImageViewCreateInfo,
|
||||||
Image = image,
|
Image = image,
|
||||||
ViewType = ImageViewType.Type2D,
|
ViewType = texture.ArrayedView
|
||||||
|
? ImageViewType.Type2DArray
|
||||||
|
: ImageViewType.Type2D,
|
||||||
Format = vkFormat,
|
Format = vkFormat,
|
||||||
Components = ToVkComponentMapping(texture.DstSelect),
|
Components = ToVkComponentMapping(texture.DstSelect),
|
||||||
SubresourceRange = ColorSubresourceRange(),
|
SubresourceRange = ColorSubresourceRange(layerCount: layers),
|
||||||
};
|
};
|
||||||
Check(_vk.CreateImageView(_device, &viewInfo, null, out var view), "vkCreateImageView(texture)");
|
Check(_vk.CreateImageView(_device, &viewInfo, null, out var view), "vkCreateImageView(texture)");
|
||||||
var debugName = TextureDebugName(texture, vkFormat);
|
var debugName = TextureDebugName(texture, vkFormat);
|
||||||
@@ -7778,6 +7790,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
Height = height,
|
Height = height,
|
||||||
RowLength = rowLength,
|
RowLength = rowLength,
|
||||||
DstSelect = texture.DstSelect,
|
DstSelect = texture.DstSelect,
|
||||||
|
Layers = layers,
|
||||||
NeedsUpload = true,
|
NeedsUpload = true,
|
||||||
OwnsStorage = true,
|
OwnsStorage = true,
|
||||||
SamplerState = texture.Sampler,
|
SamplerState = texture.Sampler,
|
||||||
@@ -7787,6 +7800,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (texture.Address != 0 &&
|
if (texture.Address != 0 &&
|
||||||
|
!texture.ArrayedView &&
|
||||||
|
layers == 1 &&
|
||||||
!_guestImages.ContainsKey(texture.Address))
|
!_guestImages.ContainsKey(texture.Address))
|
||||||
{
|
{
|
||||||
var guestFormat = GetGuestTextureFormat(texture.Format, texture.NumberType);
|
var guestFormat = GetGuestTextureFormat(texture.Format, texture.NumberType);
|
||||||
@@ -12105,11 +12120,12 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
uint mipLevel,
|
uint mipLevel,
|
||||||
uint levelCount,
|
uint levelCount,
|
||||||
uint dstSelect,
|
uint dstSelect,
|
||||||
out ImageView view)
|
out ImageView view,
|
||||||
|
bool arrayedView = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
view = GetOrCreateGuestImageView(resource, format, mipLevel, levelCount, dstSelect);
|
view = GetOrCreateGuestImageView(resource, format, mipLevel, levelCount, dstSelect, arrayedView);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
@@ -12127,7 +12143,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
Format format,
|
Format format,
|
||||||
uint mipLevel,
|
uint mipLevel,
|
||||||
uint levelCount,
|
uint levelCount,
|
||||||
uint dstSelect = 0xFAC)
|
uint dstSelect = 0xFAC,
|
||||||
|
bool arrayedView = false)
|
||||||
{
|
{
|
||||||
if (mipLevel >= resource.MipLevels)
|
if (mipLevel >= resource.MipLevels)
|
||||||
{
|
{
|
||||||
@@ -12137,7 +12154,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
levelCount = Math.Max(levelCount, 1);
|
levelCount = Math.Max(levelCount, 1);
|
||||||
levelCount = Math.Min(levelCount, resource.MipLevels - mipLevel);
|
levelCount = Math.Min(levelCount, resource.MipLevels - mipLevel);
|
||||||
if (format == resource.Format && dstSelect == 0xFAC)
|
if (format == resource.Format && dstSelect == 0xFAC && !arrayedView)
|
||||||
{
|
{
|
||||||
if (mipLevel == 0 && levelCount == resource.MipLevels)
|
if (mipLevel == 0 && levelCount == resource.MipLevels)
|
||||||
{
|
{
|
||||||
@@ -12156,7 +12173,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
$"Incompatible image view format {format} for image {resource.Format}.");
|
$"Incompatible image view format {format} for image {resource.Format}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var key = (format, mipLevel, levelCount, dstSelect);
|
var key = (format, mipLevel + (arrayedView ? 0x100u : 0), levelCount, dstSelect);
|
||||||
if (resource.FormatViews.TryGetValue(key, out var existing))
|
if (resource.FormatViews.TryGetValue(key, out var existing))
|
||||||
{
|
{
|
||||||
return existing;
|
return existing;
|
||||||
@@ -12166,7 +12183,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
{
|
{
|
||||||
SType = StructureType.ImageViewCreateInfo,
|
SType = StructureType.ImageViewCreateInfo,
|
||||||
Image = resource.Image,
|
Image = resource.Image,
|
||||||
ViewType = ImageViewType.Type2D,
|
ViewType = arrayedView ? ImageViewType.Type2DArray : ImageViewType.Type2D,
|
||||||
Format = format,
|
Format = format,
|
||||||
Components = ToVkComponentMapping(dstSelect),
|
Components = ToVkComponentMapping(dstSelect),
|
||||||
SubresourceRange = ColorSubresourceRange(mipLevel, levelCount),
|
SubresourceRange = ColorSubresourceRange(mipLevel, levelCount),
|
||||||
@@ -13272,7 +13289,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
Image = texture.Image,
|
Image = texture.Image,
|
||||||
SubresourceRange = ColorSubresourceRange(),
|
SubresourceRange = ColorSubresourceRange(layerCount: texture.Layers),
|
||||||
};
|
};
|
||||||
_vk.CmdPipelineBarrier(
|
_vk.CmdPipelineBarrier(
|
||||||
_commandBuffer,
|
_commandBuffer,
|
||||||
@@ -13294,7 +13311,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
ImageSubresource = new ImageSubresourceLayers
|
ImageSubresource = new ImageSubresourceLayers
|
||||||
{
|
{
|
||||||
AspectMask = ImageAspectFlags.ColorBit,
|
AspectMask = ImageAspectFlags.ColorBit,
|
||||||
LayerCount = 1,
|
LayerCount = texture.Layers,
|
||||||
},
|
},
|
||||||
ImageExtent = new Extent3D(texture.Width, texture.Height, 1),
|
ImageExtent = new Extent3D(texture.Width, texture.Height, 1),
|
||||||
};
|
};
|
||||||
@@ -13316,7 +13333,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
Image = texture.Image,
|
Image = texture.Image,
|
||||||
SubresourceRange = ColorSubresourceRange(),
|
SubresourceRange = ColorSubresourceRange(layerCount: texture.Layers),
|
||||||
};
|
};
|
||||||
_vk.CmdPipelineBarrier(
|
_vk.CmdPipelineBarrier(
|
||||||
_commandBuffer,
|
_commandBuffer,
|
||||||
@@ -15287,13 +15304,14 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
private static ImageSubresourceRange ColorSubresourceRange(
|
private static ImageSubresourceRange ColorSubresourceRange(
|
||||||
uint baseMipLevel = 0,
|
uint baseMipLevel = 0,
|
||||||
uint levelCount = 1) =>
|
uint levelCount = 1,
|
||||||
|
uint layerCount = 1) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
AspectMask = ImageAspectFlags.ColorBit,
|
AspectMask = ImageAspectFlags.ColorBit,
|
||||||
BaseMipLevel = baseMipLevel,
|
BaseMipLevel = baseMipLevel,
|
||||||
LevelCount = levelCount,
|
LevelCount = levelCount,
|
||||||
LayerCount = 1,
|
LayerCount = layerCount,
|
||||||
};
|
};
|
||||||
|
|
||||||
private static byte[] ScaleBgra(byte[] source, uint sourceWidth, uint sourceHeight, uint width, uint height)
|
private static byte[] ScaleBgra(byte[] source, uint sourceWidth, uint sourceHeight, uint width, uint height)
|
||||||
|
|||||||
@@ -313,7 +313,8 @@ public static partial class Gen5SpirvTranslator
|
|||||||
uint ComponentType,
|
uint ComponentType,
|
||||||
uint VectorType,
|
uint VectorType,
|
||||||
ImageComponentKind ComponentKind,
|
ImageComponentKind ComponentKind,
|
||||||
bool IsStorage);
|
bool IsStorage,
|
||||||
|
bool Arrayed);
|
||||||
|
|
||||||
private readonly record struct SpirvVertexInput(
|
private readonly record struct SpirvVertexInput(
|
||||||
uint Variable,
|
uint Variable,
|
||||||
@@ -1000,11 +1001,13 @@ public static partial class Gen5SpirvTranslator
|
|||||||
SpirvCapability.StorageImageExtendedFormats);
|
SpirvCapability.StorageImageExtendedFormats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isArrayed = !isStorage &&
|
||||||
|
Gen5ShaderTranslator.IsArrayedImageBinding(binding);
|
||||||
var imageType = _module.TypeImage(
|
var imageType = _module.TypeImage(
|
||||||
componentType,
|
componentType,
|
||||||
SpirvImageDim.Dim2D,
|
SpirvImageDim.Dim2D,
|
||||||
depth: false,
|
depth: false,
|
||||||
arrayed: false,
|
arrayed: isArrayed,
|
||||||
multisampled: false,
|
multisampled: false,
|
||||||
sampled: isStorage ? 2u : 1u,
|
sampled: isStorage ? 2u : 1u,
|
||||||
isStorage ? format : SpirvImageFormat.Unknown);
|
isStorage ? format : SpirvImageFormat.Unknown);
|
||||||
@@ -1031,7 +1034,8 @@ public static partial class Gen5SpirvTranslator
|
|||||||
componentType,
|
componentType,
|
||||||
_module.TypeVector(componentType, 4),
|
_module.TypeVector(componentType, 4),
|
||||||
componentKind,
|
componentKind,
|
||||||
isStorage));
|
isStorage,
|
||||||
|
isArrayed));
|
||||||
_interfaces.Add(variable);
|
_interfaces.Add(variable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3529,12 +3533,16 @@ public static partial class Gen5SpirvTranslator
|
|||||||
addressCursor += 4;
|
addressCursor += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
var coordinates = BuildFloatCoordinates(image, addressCursor);
|
var coordinates = resource.Arrayed
|
||||||
|
? BuildFloatArrayCoordinates(image, addressCursor)
|
||||||
|
: BuildFloatCoordinates(image, addressCursor);
|
||||||
var explicitLod = hasGradients || hasZeroLod || hasLod;
|
var explicitLod = hasGradients || hasZeroLod || hasLod;
|
||||||
var lod = hasZeroLod
|
var lod = hasZeroLod
|
||||||
? Float(0)
|
? Float(0)
|
||||||
: hasLod
|
: hasLod
|
||||||
? LoadImageFloatAddress(image, addressCursor + 2)
|
? LoadImageFloatAddress(
|
||||||
|
image,
|
||||||
|
addressCursor + (resource.Arrayed ? 3 : 2))
|
||||||
: lodOrBias;
|
: lodOrBias;
|
||||||
if (hasOffset)
|
if (hasOffset)
|
||||||
{
|
{
|
||||||
@@ -3619,7 +3627,9 @@ public static partial class Gen5SpirvTranslator
|
|||||||
addressCursor += ImageFullAddressSlots(image);
|
addressCursor += ImageFullAddressSlots(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
var coordinates = BuildFloatCoordinates(image, addressCursor);
|
var coordinates = resource.Arrayed
|
||||||
|
? BuildFloatArrayCoordinates(image, addressCursor)
|
||||||
|
: BuildFloatCoordinates(image, addressCursor);
|
||||||
var operands = new List<uint>
|
var operands = new List<uint>
|
||||||
{
|
{
|
||||||
imageObject,
|
imageObject,
|
||||||
@@ -3823,6 +3833,19 @@ public static partial class Gen5SpirvTranslator
|
|||||||
y);
|
y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private uint BuildFloatArrayCoordinates(Gen5ImageControl image, int start)
|
||||||
|
{
|
||||||
|
var x = LoadImageFloatAddress(image, start);
|
||||||
|
var y = LoadImageFloatAddress(image, start + 1);
|
||||||
|
var slice = LoadImageFloatAddress(image, start + 2);
|
||||||
|
return _module.AddInstruction(
|
||||||
|
SpirvOp.CompositeConstruct,
|
||||||
|
_vec3Type,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
slice);
|
||||||
|
}
|
||||||
|
|
||||||
private static int ImageAddressRegister(
|
private static int ImageAddressRegister(
|
||||||
Gen5ImageControl image,
|
Gen5ImageControl image,
|
||||||
int component) => image.A16 ? component / 2 : component;
|
int component) => image.A16 ? component / 2 : component;
|
||||||
@@ -4140,9 +4163,20 @@ public static partial class Gen5SpirvTranslator
|
|||||||
signedLod);
|
signedLod);
|
||||||
var size = _module.AddInstruction(
|
var size = _module.AddInstruction(
|
||||||
SpirvOp.ImageQuerySizeLod,
|
SpirvOp.ImageQuerySizeLod,
|
||||||
ivec2,
|
resource.Arrayed ? _module.TypeVector(_intType, 3) : ivec2,
|
||||||
image,
|
image,
|
||||||
clampedLod);
|
clampedLod);
|
||||||
|
if (resource.Arrayed)
|
||||||
|
{
|
||||||
|
size = _module.AddInstruction(
|
||||||
|
SpirvOp.VectorShuffle,
|
||||||
|
ivec2,
|
||||||
|
size,
|
||||||
|
size,
|
||||||
|
0u,
|
||||||
|
1u);
|
||||||
|
}
|
||||||
|
|
||||||
var sizeFloat = _module.AddInstruction(
|
var sizeFloat = _module.AddInstruction(
|
||||||
SpirvOp.ConvertSToF,
|
SpirvOp.ConvertSToF,
|
||||||
_vec2Type,
|
_vec2Type,
|
||||||
@@ -4156,11 +4190,34 @@ public static partial class Gen5SpirvTranslator
|
|||||||
_vec2Type,
|
_vec2Type,
|
||||||
offsetFloat,
|
offsetFloat,
|
||||||
sizeFloat);
|
sizeFloat);
|
||||||
|
if (!resource.Arrayed)
|
||||||
|
{
|
||||||
|
return _module.AddInstruction(
|
||||||
|
SpirvOp.FAdd,
|
||||||
|
_vec2Type,
|
||||||
|
coordinates,
|
||||||
|
normalizedOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
var offsetVec3 = _module.AddInstruction(
|
||||||
|
SpirvOp.CompositeConstruct,
|
||||||
|
_vec3Type,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.CompositeExtract,
|
||||||
|
_floatType,
|
||||||
|
normalizedOffset,
|
||||||
|
0u),
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.CompositeExtract,
|
||||||
|
_floatType,
|
||||||
|
normalizedOffset,
|
||||||
|
1u),
|
||||||
|
Float(0));
|
||||||
return _module.AddInstruction(
|
return _module.AddInstruction(
|
||||||
SpirvOp.FAdd,
|
SpirvOp.FAdd,
|
||||||
_vec2Type,
|
_vec3Type,
|
||||||
coordinates,
|
coordinates,
|
||||||
normalizedOffset);
|
offsetVec3);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryEmitExport(
|
private bool TryEmitExport(
|
||||||
|
|||||||
@@ -1612,6 +1612,11 @@ public static class Gen5ShaderTranslator
|
|||||||
binding.ResourceDescriptor.SequenceEqual(candidate.ResourceDescriptor));
|
binding.ResourceDescriptor.SequenceEqual(candidate.ResourceDescriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsArrayedImageBinding(Gen5ImageBinding binding) =>
|
||||||
|
binding.Control.IsArray &&
|
||||||
|
(binding.Opcode.StartsWith("ImageSample", StringComparison.Ordinal) ||
|
||||||
|
binding.Opcode.StartsWith("ImageGather4", StringComparison.Ordinal));
|
||||||
|
|
||||||
public static bool IsDataShareAtomic(string name) => name switch
|
public static bool IsDataShareAtomic(string name) => name switch
|
||||||
{
|
{
|
||||||
"DsAddU32" or "DsSubU32" or "DsIncU32" or "DsDecU32" or
|
"DsAddU32" or "DsSubU32" or "DsIncU32" or "DsDecU32" or
|
||||||
|
|||||||
Reference in New Issue
Block a user