mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-31 23:19:44 +08:00
Refresh CPU-rewritten guest textures by write generation (#447)
* Track guest CPU write generations * Refresh CPU-rewritten guest textures by write generation
This commit is contained in:
@@ -7893,9 +7893,13 @@ public static partial class AgcExports
|
||||
return true;
|
||||
}
|
||||
|
||||
// Upload-known (not plain availability): the presenter's answer goes
|
||||
// generation-stale when the guest CPU rewrites a CPU-backed image
|
||||
// (video planes, streamed font atlases), which routes this draw back
|
||||
// through the texel copy below so the refresh path re-uploads.
|
||||
if (!isStorage &&
|
||||
descriptor.Address != 0 &&
|
||||
GuestGpu.Current.IsGpuGuestImageAvailable(
|
||||
GuestGpu.Current.IsGuestImageUploadKnown(
|
||||
descriptor.Address,
|
||||
descriptor.Format,
|
||||
descriptor.NumberType))
|
||||
@@ -8002,6 +8006,19 @@ public static partial class AgcExports
|
||||
// (skipping would leave the draw with no pixels and a fallback
|
||||
// texture for the frame — visible flicker on animated textures).
|
||||
var sampler = ToGuestSampler(samplerDescriptor);
|
||||
// Track the guest allocation before reading its texels so a CPU
|
||||
// rewrite landing after the copy still bumps the write generation.
|
||||
// The generation rides on the texture and is recorded by the
|
||||
// presenter after upload, where the upload-known skip compares it
|
||||
// against the tracker to force fresh texels for rewritten memory.
|
||||
SharpEmu.HLE.GuestImageWriteTracker.Track(
|
||||
descriptor.Address,
|
||||
physicalSourceByteCount,
|
||||
source: "agc.decoded-texture");
|
||||
var hasWriteGeneration =
|
||||
SharpEmu.HLE.GuestImageWriteTracker.TryGetWriteGeneration(
|
||||
descriptor.Address,
|
||||
out var writeGeneration);
|
||||
if (!_textureCopySkipDisabled &&
|
||||
descriptor.Address != 0 &&
|
||||
!SharpEmu.HLE.GuestImageWriteTracker.PeekDirty(descriptor.Address) &&
|
||||
@@ -8093,7 +8110,8 @@ public static partial class AgcExports
|
||||
Pitch: sourceWidth,
|
||||
TileMode: descriptor.TileMode,
|
||||
DstSelect: descriptor.DstSelect,
|
||||
Sampler: ToGuestSampler(samplerDescriptor));
|
||||
Sampler: ToGuestSampler(samplerDescriptor),
|
||||
WriteGeneration: hasWriteGeneration ? writeGeneration : -1);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,10 @@ internal sealed record GuestDrawTexture(
|
||||
uint Pitch = 0,
|
||||
uint TileMode = 0,
|
||||
uint DstSelect = 0xFAC,
|
||||
GuestSampler Sampler = default);
|
||||
GuestSampler Sampler = default,
|
||||
// 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.
|
||||
long WriteGeneration = -1);
|
||||
|
||||
/// <summary>Raw guest sampler descriptor dwords, copied verbatim from guest memory.</summary>
|
||||
internal readonly record struct GuestSampler(
|
||||
|
||||
@@ -472,6 +472,11 @@ internal static unsafe class VulkanVideoPresenter
|
||||
private static readonly Queue<Presentation> _pendingGuestImagePresentations = new();
|
||||
private static readonly Dictionary<ulong, long> _guestImageWorkSequences = new();
|
||||
private static readonly Dictionary<ulong, uint> _availableGuestImages = new();
|
||||
// Write-tracker generation last uploaded for a CPU-backed guest image.
|
||||
// A newer generation in the tracker means the guest CPU rewrote the
|
||||
// memory (video frames, streamed atlases) and the upload-known skip in
|
||||
// draw translation must ship fresh texels instead of reusing the image.
|
||||
private static readonly Dictionary<ulong, long> _cpuBackedUploadGenerations = new();
|
||||
private static readonly Dictionary<(int Handle, int BufferIndex), long>
|
||||
_lastOrderedGuestFlipVersions = new();
|
||||
private static long _orderedGuestFlipVersionSequence;
|
||||
@@ -811,6 +816,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_pendingGuestImagePresentations.Clear();
|
||||
_guestImageWorkSequences.Clear();
|
||||
_availableGuestImages.Clear();
|
||||
_cpuBackedUploadGenerations.Clear();
|
||||
_lastOrderedGuestFlipVersions.Clear();
|
||||
_orderedGuestFlipVersionSequence = 0;
|
||||
_pendingGuestImageUploads.Clear();
|
||||
@@ -1790,9 +1796,30 @@ internal static unsafe class VulkanVideoPresenter
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
return _availableGuestImages.TryGetValue(address, out var availableFormat) &&
|
||||
var known =
|
||||
_availableGuestImages.TryGetValue(address, out var availableFormat) &&
|
||||
availableFormat == guestFormat ||
|
||||
_pendingGuestImageUploads.ContainsKey((address, guestFormat));
|
||||
if (!known)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// CPU-backed images (video frames, streamed atlases) go stale when
|
||||
// the guest CPU rewrites the memory after the recorded upload; a
|
||||
// changed write-tracker generation forces a fresh texel copy so
|
||||
// the refresh path can re-upload. GPU-rendered images have no
|
||||
// generation entry and keep the plain availability answer.
|
||||
if (_cpuBackedUploadGenerations.TryGetValue(address, out var uploadedGeneration) &&
|
||||
SharpEmu.HLE.GuestImageWriteTracker.TryGetWriteGeneration(
|
||||
address,
|
||||
out var currentGeneration) &&
|
||||
currentGeneration != uploadedGeneration)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2990,6 +3017,10 @@ internal static unsafe class VulkanVideoPresenter
|
||||
public Sampler Sampler;
|
||||
public GuestImageResource? GuestImage;
|
||||
public GuestDepthResource? GuestDepth;
|
||||
// Write-tracker generation of the guest memory the staged pixels
|
||||
// were read from; -1 when unknown. Recorded on the guest image
|
||||
// after upload so stale-content skips can be detected.
|
||||
public long WriteGeneration = -1;
|
||||
// A sampled render-target alias cannot remain bound to the same
|
||||
// image while that image is a color attachment. The per-draw
|
||||
// snapshot uses this source to copy the target's pre-draw contents
|
||||
@@ -7004,6 +7035,18 @@ internal static unsafe class VulkanVideoPresenter
|
||||
if ((guestImage.Initialized || guestImage.InitialUploadPending) &&
|
||||
guestImage.CpuContentFingerprint == fingerprint)
|
||||
{
|
||||
// Content unchanged despite a newer write generation: advance
|
||||
// the recorded generation so later draws can skip the copy
|
||||
// again instead of restaging identical texels every draw.
|
||||
if (texture.WriteGeneration >= 0)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_cpuBackedUploadGenerations[texture.Address] =
|
||||
texture.WriteGeneration;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -7033,6 +7076,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
GuestImage = guestImage,
|
||||
CpuContentFingerprint = fingerprint,
|
||||
UpdatesCpuContent = true,
|
||||
WriteGeneration = texture.WriteGeneration,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
@@ -7718,6 +7762,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
SamplerState = texture.Sampler,
|
||||
CpuContentFingerprint = contentFingerprint,
|
||||
UpdatesCpuContent = texture.Address != 0,
|
||||
WriteGeneration = texture.WriteGeneration,
|
||||
};
|
||||
|
||||
if (texture.Address != 0 &&
|
||||
@@ -7750,6 +7795,12 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_availableGuestImages[texture.Address] = guestFormat;
|
||||
}
|
||||
|
||||
if (texture.WriteGeneration >= 0)
|
||||
{
|
||||
_cpuBackedUploadGenerations[texture.Address] =
|
||||
texture.WriteGeneration;
|
||||
}
|
||||
|
||||
_guestImageExtents[texture.Address] =
|
||||
(width, height, expectedSize);
|
||||
}
|
||||
@@ -11222,6 +11273,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
lock (_gate)
|
||||
{
|
||||
_availableGuestImages.Remove(target.Address);
|
||||
_cpuBackedUploadGenerations.Remove(target.Address);
|
||||
_guestImageExtents.Remove(target.Address);
|
||||
}
|
||||
|
||||
@@ -11246,6 +11298,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_guestImages.Add(target.Address, retained);
|
||||
lock (_gate)
|
||||
{
|
||||
_cpuBackedUploadGenerations.Remove(target.Address);
|
||||
_guestImageExtents[target.Address] = (
|
||||
target.Width,
|
||||
target.Height,
|
||||
@@ -13873,6 +13926,11 @@ internal static unsafe class VulkanVideoPresenter
|
||||
if (texture.UpdatesCpuContent)
|
||||
{
|
||||
guestImage.CpuContentFingerprint = texture.CpuContentFingerprint;
|
||||
if (texture.WriteGeneration >= 0)
|
||||
{
|
||||
_cpuBackedUploadGenerations[guestImage.Address] =
|
||||
texture.WriteGeneration;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15190,6 +15248,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
lock (_gate)
|
||||
{
|
||||
_availableGuestImages.Clear();
|
||||
_cpuBackedUploadGenerations.Clear();
|
||||
_lastOrderedGuestFlipVersions.Clear();
|
||||
}
|
||||
DestroySwapchainResources();
|
||||
|
||||
Reference in New Issue
Block a user