[shader-decoder] Fix address calculation for SW linear textures (#45)

This commit is contained in:
Berk
2026-07-11 05:12:41 +03:00
committed by GitHub
parent 29021b5a71
commit c618c116ba
2 changed files with 200 additions and 43 deletions
+49 -16
View File
@@ -142,6 +142,7 @@ public static class AgcExports
private const ulong ShaderSpecialGeUserVgprEnOffset = 0x28; private const ulong ShaderSpecialGeUserVgprEnOffset = 0x28;
private const uint CbSetShRegisterRangeMarker = 0x6875000D; private const uint CbSetShRegisterRangeMarker = 0x6875000D;
private static readonly object _submitTraceGate = new(); private static readonly object _submitTraceGate = new();
private static readonly object _textureHashTraceGate = new();
private static readonly HashSet<uint> _tracedDcbSizes = new(); private static readonly HashSet<uint> _tracedDcbSizes = new();
private static readonly HashSet<(ulong Es, ulong Ps, GuestDrawKind Kind)> _tracedShaderTranslations = new(); private static readonly HashSet<(ulong Es, ulong Ps, GuestDrawKind Kind)> _tracedShaderTranslations = new();
private static readonly HashSet<(ulong Es, ulong Ps)> _tracedShaderDecodePairs = new(); private static readonly HashSet<(ulong Es, ulong Ps)> _tracedShaderDecodePairs = new();
@@ -149,6 +150,7 @@ public static class AgcExports
private static readonly HashSet<(ulong Ps, string Error)> _tracedShaderFailures = new(); private static readonly HashSet<(ulong Ps, string Error)> _tracedShaderFailures = new();
private static readonly HashSet<(int Handle, int Index, ulong Address, string Path)> _tracedDisplayBuffers = new(); private static readonly HashSet<(int Handle, int Index, ulong Address, string Path)> _tracedDisplayBuffers = new();
private static readonly HashSet<ulong> _tracedComputeShaders = new(); private static readonly HashSet<ulong> _tracedComputeShaders = new();
private static readonly Dictionary<(ulong Address, uint Width, uint Height), ulong> _tracedTextureHashes = [];
private static readonly HashSet<uint> _tracedSubmittedDrawOpcodes = new(); private static readonly HashSet<uint> _tracedSubmittedDrawOpcodes = new();
private static readonly Dictionary<(ulong Ps, ulong State, Gen5PixelOutputKind Output), byte[]> _pixelSpirvCache = new(); private static readonly Dictionary<(ulong Ps, ulong State, Gen5PixelOutputKind Output), byte[]> _pixelSpirvCache = new();
private static readonly Dictionary< private static readonly Dictionary<
@@ -168,6 +170,10 @@ public static class AgcExports
Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC_SHADER"), Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC_SHADER"),
"1", "1",
StringComparison.Ordinal); StringComparison.Ordinal);
private static readonly bool _traceTextureHashes = string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_TRACE_TEXTURE_HASHES"),
"1",
StringComparison.Ordinal);
private static long _dcbWriteDataTraceCount; private static long _dcbWriteDataTraceCount;
private static long _dcbWaitRegMemTraceCount; private static long _dcbWaitRegMemTraceCount;
private static long _createShaderTraceCount; private static long _createShaderTraceCount;
@@ -3747,7 +3753,6 @@ public static class AgcExports
var sourceWidth = descriptor.TileMode == 0 var sourceWidth = descriptor.TileMode == 0
? GetLinearTexturePitch( ? GetLinearTexturePitch(
Math.Max(descriptor.Width, descriptor.Pitch), Math.Max(descriptor.Width, descriptor.Pitch),
descriptor.Height,
descriptor.Format) descriptor.Format)
: descriptor.Width; : descriptor.Width;
var sourceByteCount = GetTextureByteCount( var sourceByteCount = GetTextureByteCount(
@@ -3764,7 +3769,7 @@ public static class AgcExports
if (!isStorage && if (!isStorage &&
descriptor.Address != 0 && descriptor.Address != 0 &&
VulkanVideoPresenter.IsGuestImageAvailable( VulkanVideoPresenter.IsGpuGuestImageAvailable(
descriptor.Address, descriptor.Address,
descriptor.Format, descriptor.Format,
descriptor.NumberType)) descriptor.NumberType))
@@ -3825,6 +3830,8 @@ public static class AgcExports
return true; return true;
} }
TraceTextureHash(descriptor, source);
var nonZero = 0; var nonZero = 0;
for (var i = 0; i < source.Length; i++) for (var i = 0; i < source.Length; i++)
{ {
@@ -3884,6 +3891,34 @@ public static class AgcExports
MipLevel: 0); MipLevel: 0);
} }
private static void TraceTextureHash(TextureDescriptor descriptor, ReadOnlySpan<byte> source)
{
if (!_traceTextureHashes ||
descriptor.Address == 0 ||
descriptor.Width > 256 ||
descriptor.Height > 256)
{
return;
}
var hash = ComputeFingerprint(source);
var key = (descriptor.Address, descriptor.Width, descriptor.Height);
lock (_textureHashTraceGate)
{
if (_tracedTextureHashes.TryGetValue(key, out var previousHash) &&
previousHash == hash)
{
return;
}
_tracedTextureHashes[key] = hash;
}
Console.Error.WriteLine(
$"[LOADER][TRACE] agc.texture_hash addr=0x{descriptor.Address:X16} " +
$"size={descriptor.Width}x{descriptor.Height} bytes={source.Length} hash=0x{hash:X16}");
}
private static VulkanGuestSampler ToVulkanSampler(IReadOnlyList<uint> descriptor) => private static VulkanGuestSampler ToVulkanSampler(IReadOnlyList<uint> descriptor) =>
descriptor.Count >= 4 descriptor.Count >= 4
? new VulkanGuestSampler( ? new VulkanGuestSampler(
@@ -4527,23 +4562,17 @@ public static class AgcExports
: checked(((ulong)width + 3) / 4 * (((ulong)height + 3) / 4) * blockBytes); : checked(((ulong)width + 3) / 4 * (((ulong)height + 3) / 4) * blockBytes);
} }
private static uint GetLinearTexturePitch(uint pitch, uint height, uint format) private static uint GetLinearTexturePitch(uint pitch, uint format)
{ {
var bytesPerTexel = GetTextureBytesPerTexel(format); var bytesPerTexel = GetTextureBytesPerTexel(format);
if (bytesPerTexel == 0 || height == 0) if (bytesPerTexel == 0)
{ {
return pitch; return pitch;
} }
var pitchAlignment = Math.Max(8UL, 64UL / bytesPerTexel); // GFX10 ADDR_SW_LINEAR aligns each row to 256 bytes.
var alignedPitch = AlignUp(pitch, pitchAlignment); var pitchAlignment = Math.Max(1UL, 256UL / bytesPerTexel);
var sliceAlignment = Math.Max(64UL, 256UL / bytesPerTexel); return checked((uint)AlignUp(pitch, pitchAlignment));
while ((alignedPitch * height) % sliceAlignment != 0)
{
alignedPitch += pitchAlignment;
}
return checked((uint)alignedPitch);
} }
private static ulong AlignUp(ulong value, ulong alignment) => private static ulong AlignUp(ulong value, ulong alignment) =>
@@ -4833,9 +4862,13 @@ public static class AgcExports
var type = (fields[3] >> 28) & 0xFu; var type = (fields[3] >> 28) & 0xFu;
var baseLevel = (fields[3] >> 12) & 0xFu; var baseLevel = (fields[3] >> 12) & 0xFu;
var lastLevel = (fields[3] >> 16) & 0xFu; var lastLevel = (fields[3] >> 16) & 0xFu;
var pitch = fields.Count >= 5 // The 128-bit RDNA2 2D resource derives pitch[12:0] from width;
? ((fields[4] >> 13) & 0x3FFFu) + 1 // the optional extension word only supplies pitch[13].
: width; var pitch = width;
if (fields.Count >= 5)
{
pitch = ((((width - 1) & 0x1FFFu) | (((fields[4] >> 13) & 1u) << 13)) + 1);
}
var dstSelect = fields[3] & 0xFFFu; var dstSelect = fields[3] & 0xFFFu;
if (address == 0 || width == 0 || height == 0) if (address == 0 || width == 0 || height == 0)
{ {
@@ -168,6 +168,7 @@ internal static unsafe class VulkanVideoPresenter
private static readonly object _gate = new(); private static readonly object _gate = new();
private static readonly Queue<object> _pendingGuestWork = new(); private static readonly Queue<object> _pendingGuestWork = new();
private static readonly Dictionary<ulong, uint> _availableGuestImages = new(); private static readonly Dictionary<ulong, uint> _availableGuestImages = new();
private static readonly Dictionary<ulong, uint> _gpuGuestImages = new();
private static readonly HashSet<(ulong Address, uint Width, uint Height)> private static readonly HashSet<(ulong Address, uint Width, uint Height)>
_tracedGuestImageSubmissions = []; _tracedGuestImageSubmissions = [];
private static Thread? _thread; private static Thread? _thread;
@@ -600,7 +601,7 @@ internal static unsafe class VulkanVideoPresenter
return true; return true;
} }
internal static bool IsGuestImageAvailable( internal static bool IsGpuGuestImageAvailable(
ulong address, ulong address,
uint format, uint format,
uint numberType) uint numberType)
@@ -613,7 +614,7 @@ internal static unsafe class VulkanVideoPresenter
lock (_gate) lock (_gate)
{ {
return _availableGuestImages.TryGetValue(address, out var availableFormat) && return _gpuGuestImages.TryGetValue(address, out var availableFormat) &&
availableFormat == guestFormat; availableFormat == guestFormat;
} }
} }
@@ -971,6 +972,8 @@ internal static unsafe class VulkanVideoPresenter
public VulkanGuestSampler SamplerState; public VulkanGuestSampler SamplerState;
public Sampler Sampler; public Sampler Sampler;
public GuestImageResource? GuestImage; public GuestImageResource? GuestImage;
public ulong CpuContentFingerprint;
public bool UpdatesCpuContent;
} }
private sealed class GlobalBufferResource private sealed class GlobalBufferResource
@@ -1009,6 +1012,8 @@ internal static unsafe class VulkanVideoPresenter
public Framebuffer Framebuffer; public Framebuffer Framebuffer;
public bool Initialized; public bool Initialized;
public bool InitialUploadPending; public bool InitialUploadPending;
public bool IsCpuBacked;
public ulong CpuContentFingerprint;
} }
private sealed record PendingGuestSubmission( private sealed record PendingGuestSubmission(
@@ -2838,6 +2843,11 @@ internal static unsafe class VulkanVideoPresenter
$"tile={texture.TileMode} format={vkFormat}"); $"tile={texture.TileMode} format={vkFormat}");
} }
if (TryCreateCpuTextureRefreshResource(texture, guestImage, view, out var refresh))
{
return refresh;
}
return new TextureResource return new TextureResource
{ {
Address = texture.Address, Address = texture.Address,
@@ -2855,6 +2865,71 @@ internal static unsafe class VulkanVideoPresenter
return CreateTextureResource(texture); return CreateTextureResource(texture);
} }
private bool TryCreateCpuTextureRefreshResource(
VulkanGuestDrawTexture texture,
GuestImageResource guestImage,
ImageView view,
out TextureResource resource)
{
resource = default!;
if (!guestImage.IsCpuBacked ||
guestImage.Width != texture.Width ||
guestImage.Height != texture.Height ||
guestImage.MipLevels != 1 ||
texture.RgbaPixels.Length == 0)
{
return false;
}
var rowLength = texture.TileMode == 0
? Math.Max(texture.Pitch, texture.Width)
: texture.Width;
var expectedSize = GetTextureByteCount(texture.Format, rowLength, texture.Height);
if (expectedSize == 0 || expectedSize > int.MaxValue)
{
return false;
}
var pixels = texture.RgbaPixels.Length == (int)expectedSize
? texture.RgbaPixels
: CreateFallbackTexturePixels(texture.Format, rowLength, texture.Height, expectedSize);
var fingerprint = ComputeTextureContentFingerprint(pixels);
if ((guestImage.Initialized || guestImage.InitialUploadPending) &&
guestImage.CpuContentFingerprint == fingerprint)
{
return false;
}
var uploadPixels = texture.Format == 13
? ExpandRgb32Pixels(pixels)
: pixels;
var debugName = TextureDebugName(texture, guestImage.Format);
var (stagingBuffer, stagingMemory) = CreateTextureStagingBuffer(
uploadPixels,
$"{debugName} refresh staging");
TraceVulkanShader(
$"vk.texture_refresh addr=0x{texture.Address:X16} " +
$"size={texture.Width}x{texture.Height} bytes={uploadPixels.Length}");
resource = new TextureResource
{
Address = texture.Address,
StagingBuffer = stagingBuffer,
StagingMemory = stagingMemory,
Image = guestImage.Image,
View = view,
Width = guestImage.Width,
Height = guestImage.Height,
RowLength = rowLength,
DstSelect = texture.DstSelect,
NeedsUpload = true,
SamplerState = texture.Sampler,
GuestImage = guestImage,
CpuContentFingerprint = fingerprint,
UpdatesCpuContent = true,
};
return true;
}
private static bool IsCompatibleGuestImageAlias( private static bool IsCompatibleGuestImageAlias(
VulkanGuestDrawTexture texture, VulkanGuestDrawTexture texture,
GuestImageResource guestImage) GuestImageResource guestImage)
@@ -3096,24 +3171,11 @@ internal static unsafe class VulkanVideoPresenter
var uploadPixels = texture.Format == 13 var uploadPixels = texture.Format == 13
? ExpandRgb32Pixels(pixels) ? ExpandRgb32Pixels(pixels)
: pixels; : pixels;
var uploadSize = (ulong)uploadPixels.Length; var contentFingerprint = ComputeTextureContentFingerprint(pixels);
var stagingBuffer = CreateBuffer( var (stagingBuffer, stagingMemory) = CreateTextureStagingBuffer(
uploadSize, uploadPixels,
BufferUsageFlags.TransferSrcBit, $"{TextureDebugName(texture, vkFormat)} staging");
MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit,
out var stagingMemory);
void* mapped;
Check(_vk.MapMemory(_device, stagingMemory, 0, uploadSize, 0, &mapped), "vkMapMemory(texture)");
fixed (byte* source = uploadPixels)
{
System.Buffer.MemoryCopy(
source,
mapped,
uploadPixels.Length,
uploadPixels.Length);
}
_vk.UnmapMemory(_device, stagingMemory);
var supportsAttachmentUsage = !IsBlockCompressedFormat(vkFormat); var supportsAttachmentUsage = !IsBlockCompressedFormat(vkFormat);
var imageInfo = new ImageCreateInfo var imageInfo = new ImageCreateInfo
@@ -3163,7 +3225,6 @@ internal static unsafe class VulkanVideoPresenter
}; };
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);
SetDebugName(ObjectType.Buffer, stagingBuffer.Handle, $"{debugName} staging");
SetDebugName(ObjectType.Image, image.Handle, $"{debugName} image"); SetDebugName(ObjectType.Image, image.Handle, $"{debugName} image");
SetDebugName(ObjectType.ImageView, view.Handle, $"{debugName} view"); SetDebugName(ObjectType.ImageView, view.Handle, $"{debugName} view");
var resource = new TextureResource var resource = new TextureResource
@@ -3181,6 +3242,8 @@ internal static unsafe class VulkanVideoPresenter
NeedsUpload = true, NeedsUpload = true,
OwnsStorage = true, OwnsStorage = true,
SamplerState = texture.Sampler, SamplerState = texture.Sampler,
CpuContentFingerprint = contentFingerprint,
UpdatesCpuContent = texture.Address != 0,
}; };
if (texture.Address != 0 && if (texture.Address != 0 &&
@@ -3197,6 +3260,8 @@ internal static unsafe class VulkanVideoPresenter
Memory = imageMemory, Memory = imageMemory,
View = view, View = view,
InitialUploadPending = true, InitialUploadPending = true,
IsCpuBacked = true,
CpuContentFingerprint = contentFingerprint,
}; };
_guestImages.Add(texture.Address, guestImage); _guestImages.Add(texture.Address, guestImage);
resource.OwnsStorage = false; resource.OwnsStorage = false;
@@ -3209,6 +3274,7 @@ internal static unsafe class VulkanVideoPresenter
if (guestFormat != 0) if (guestFormat != 0)
{ {
_availableGuestImages[texture.Address] = guestFormat; _availableGuestImages[texture.Address] = guestFormat;
_gpuGuestImages.Remove(texture.Address);
} }
} }
} }
@@ -3216,6 +3282,41 @@ internal static unsafe class VulkanVideoPresenter
return resource; return resource;
} }
private (VkBuffer Buffer, DeviceMemory Memory) CreateTextureStagingBuffer(
byte[] pixels,
string debugName)
{
var size = (ulong)pixels.Length;
var buffer = CreateBuffer(
size,
BufferUsageFlags.TransferSrcBit,
MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit,
out var memory);
void* mapped;
Check(_vk.MapMemory(_device, memory, 0, size, 0, &mapped), "vkMapMemory(texture)");
fixed (byte* source = pixels)
{
System.Buffer.MemoryCopy(source, mapped, pixels.Length, pixels.Length);
}
_vk.UnmapMemory(_device, memory);
SetDebugName(ObjectType.Buffer, buffer.Handle, debugName);
return (buffer, memory);
}
private static ulong ComputeTextureContentFingerprint(ReadOnlySpan<byte> pixels)
{
const ulong offsetBasis = 14695981039346656037;
const ulong prime = 1099511628211;
var hash = offsetBasis;
foreach (var value in pixels)
{
hash ^= value;
hash *= prime;
}
return hash;
}
private void DumpTextureUpload( private void DumpTextureUpload(
VulkanGuestDrawTexture texture, VulkanGuestDrawTexture texture,
byte[] pixels, byte[] pixels,
@@ -4267,12 +4368,13 @@ internal static unsafe class VulkanVideoPresenter
RecordTextureUploads(resources, PipelineStageFlags.FragmentShaderBit); RecordTextureUploads(resources, PipelineStageFlags.FragmentShaderBit);
RecordStorageImagesForWrite(resources, PipelineStageFlags.FragmentShaderBit); RecordStorageImagesForWrite(resources, PipelineStageFlags.FragmentShaderBit);
var targetHasPriorContents = target.Initialized || target.InitialUploadPending;
var toColorAttachment = new ImageMemoryBarrier var toColorAttachment = new ImageMemoryBarrier
{ {
SType = StructureType.ImageMemoryBarrier, SType = StructureType.ImageMemoryBarrier,
SrcAccessMask = target.Initialized ? AccessFlags.ShaderReadBit : 0, SrcAccessMask = targetHasPriorContents ? AccessFlags.ShaderReadBit : 0,
DstAccessMask = AccessFlags.ColorAttachmentWriteBit, DstAccessMask = AccessFlags.ColorAttachmentWriteBit,
OldLayout = target.Initialized OldLayout = targetHasPriorContents
? ImageLayout.ShaderReadOnlyOptimal ? ImageLayout.ShaderReadOnlyOptimal
: ImageLayout.Undefined, : ImageLayout.Undefined,
NewLayout = ImageLayout.ColorAttachmentOptimal, NewLayout = ImageLayout.ColorAttachmentOptimal,
@@ -4283,8 +4385,8 @@ internal static unsafe class VulkanVideoPresenter
}; };
_vk.CmdPipelineBarrier( _vk.CmdPipelineBarrier(
_commandBuffer, _commandBuffer,
target.Initialized targetHasPriorContents
? PipelineStageFlags.FragmentShaderBit ? PipelineStageFlags.AllCommandsBit
: PipelineStageFlags.TopOfPipeBit, : PipelineStageFlags.TopOfPipeBit,
PipelineStageFlags.ColorAttachmentOutputBit, PipelineStageFlags.ColorAttachmentOutputBit,
0, 0,
@@ -4337,12 +4439,15 @@ internal static unsafe class VulkanVideoPresenter
MarkSampledImagesInitialized(resources); MarkSampledImagesInitialized(resources);
MarkStorageImagesInitialized(resources, traceContents: false); MarkStorageImagesInitialized(resources, traceContents: false);
var guestTextureFormat = GetGuestTextureFormat(target.Format); var guestTextureFormat = VulkanVideoPresenter.GetGuestTextureFormat(
work.Target.Format,
work.Target.NumberType);
if (work.PublishTarget && guestTextureFormat != 0) if (work.PublishTarget && guestTextureFormat != 0)
{ {
lock (_gate) lock (_gate)
{ {
_availableGuestImages[target.Address] = guestTextureFormat; _availableGuestImages[target.Address] = guestTextureFormat;
_gpuGuestImages[target.Address] = guestTextureFormat;
} }
} }
if (ShouldTraceGuestImageWriteForDiagnostics(target.Address)) if (ShouldTraceGuestImageWriteForDiagnostics(target.Address))
@@ -4384,6 +4489,7 @@ internal static unsafe class VulkanVideoPresenter
lock (_gate) lock (_gate)
{ {
_availableGuestImages.Remove(work.Target.Address); _availableGuestImages.Remove(work.Target.Address);
_gpuGuestImages.Remove(work.Target.Address);
} }
} }
@@ -4423,6 +4529,8 @@ internal static unsafe class VulkanVideoPresenter
existing.MipLevels == mipLevels && existing.MipLevels == mipLevels &&
existing.Format == format) existing.Format == format)
{ {
existing.IsCpuBacked = false;
existing.CpuContentFingerprint = 0;
if (existing.RenderPass.Handle == 0) if (existing.RenderPass.Handle == 0)
{ {
var attachmentView = existing.MipViews.Length > 0 var attachmentView = existing.MipViews.Length > 0
@@ -4448,6 +4556,7 @@ internal static unsafe class VulkanVideoPresenter
lock (_gate) lock (_gate)
{ {
_availableGuestImages.Remove(target.Address); _availableGuestImages.Remove(target.Address);
_gpuGuestImages.Remove(target.Address);
} }
} }
@@ -5386,11 +5495,18 @@ internal static unsafe class VulkanVideoPresenter
continue; continue;
} }
var hasPriorContents = texture.GuestImage is { } guestImage &&
(guestImage.Initialized || guestImage.InitialUploadPending);
var toTransfer = new ImageMemoryBarrier var toTransfer = new ImageMemoryBarrier
{ {
SType = StructureType.ImageMemoryBarrier, SType = StructureType.ImageMemoryBarrier,
SrcAccessMask = hasPriorContents
? AccessFlags.ShaderReadBit
: 0,
DstAccessMask = AccessFlags.TransferWriteBit, DstAccessMask = AccessFlags.TransferWriteBit,
OldLayout = ImageLayout.Undefined, OldLayout = hasPriorContents
? ImageLayout.ShaderReadOnlyOptimal
: ImageLayout.Undefined,
NewLayout = ImageLayout.TransferDstOptimal, NewLayout = ImageLayout.TransferDstOptimal,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored, SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored, DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
@@ -5399,7 +5515,9 @@ internal static unsafe class VulkanVideoPresenter
}; };
_vk.CmdPipelineBarrier( _vk.CmdPipelineBarrier(
_commandBuffer, _commandBuffer,
PipelineStageFlags.TopOfPipeBit, hasPriorContents
? PipelineStageFlags.AllCommandsBit
: PipelineStageFlags.TopOfPipeBit,
PipelineStageFlags.TransferBit, PipelineStageFlags.TransferBit,
0, 0,
0, 0,
@@ -5569,6 +5687,7 @@ internal static unsafe class VulkanVideoPresenter
if (format != 0) if (format != 0)
{ {
_availableGuestImages[texture.Address] = format; _availableGuestImages[texture.Address] = format;
_gpuGuestImages[texture.Address] = format;
} }
if (traceContents && if (traceContents &&
@@ -5608,6 +5727,10 @@ internal static unsafe class VulkanVideoPresenter
guestImage.Initialized = true; guestImage.Initialized = true;
guestImage.InitialUploadPending = false; guestImage.InitialUploadPending = false;
if (texture.UpdatesCpuContent)
{
guestImage.CpuContentFingerprint = texture.CpuContentFingerprint;
}
} }
} }
} }
@@ -6379,6 +6502,7 @@ internal static unsafe class VulkanVideoPresenter
lock (_gate) lock (_gate)
{ {
_availableGuestImages.Clear(); _availableGuestImages.Clear();
_gpuGuestImages.Clear();
} }
DestroySwapchainResources(); DestroySwapchainResources();
if (_device.Handle != 0) if (_device.Handle != 0)