sharpemu/src/SharpEmu.Libs/Gpu/Images/GuestImageCache.Metadata.cs

171 lines
5.7 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.Libs.Gpu.Scheduling;
namespace SharpEmu.Libs.Gpu.Images;
// Surface metadata (HTile, DCC, CMask, FMask) keyed by its guest address.
public sealed partial class GuestImageCache
{
public bool IsMetadata(ulong address)
{
using var held = _lock.Hold();
return _surfaceMetadata.TryGetValue(address, out var found) && found.Kind != SurfaceMetadataKind.PendingDcc;
}
public bool IsMetadataCleared(ulong address, uint slice, out uint fillValue)
{
fillValue = 0;
using var held = _lock.Hold();
if (!_surfaceMetadata.TryGetValue(address, out var found) || found.Kind == SurfaceMetadataKind.PendingDcc || slice >= 32)
{
return false;
}
fillValue = found.FillValue;
return (found.ClearMask & (1u << (int)slice)) != 0;
}
public bool IsMetadataCleared(ulong address, uint slice) => IsMetadataCleared(address, slice, out _);
// A broad clear applies to CMask, FMask and HTile; DCC needs a validated fill value.
public bool ClearMetadata(ulong address)
{
using var held = _lock.Hold();
if (!_surfaceMetadata.TryGetValue(address, out var found) || found.Kind is SurfaceMetadataKind.PendingDcc or SurfaceMetadataKind.Dcc)
{
return false;
}
found.ClearMask = uint.MaxValue;
return true;
}
// True when registered DCC absorbed the fill and the guest dispatch can be skipped.
public bool TryAbsorbDccFill(ulong address, ulong size, uint fillValue)
{
if (!IsValidRange(address, size))
{
throw SubmissionScheduler.Fatal($"The DCC fill range is invalid: address=0x{address:X16} size=0x{size:X16}.");
}
// A DCC fill repeats one byte code; only the known deferred-clear codes count as clear.
var code = (byte)fillValue;
var dccClearMask = fillValue != code * 0x01010101u ? 0u : code switch
{
0x00 or 0x20 or 0x40 or 0x80 or 0xc0 => uint.MaxValue,
_ => 0u,
};
using var held = _lock.Hold();
if (!_surfaceMetadata.TryGetValue(address, out var found))
{
// The fill may precede color-target discovery; a pending entry stays invisible until then.
_surfaceMetadata.Add(address, new SurfaceMetadata { Kind = SurfaceMetadataKind.PendingDcc, ClearMask = dccClearMask, FillValue = fillValue, FillSize = size });
return false;
}
if (found.Kind == SurfaceMetadataKind.PendingDcc)
{
found.ClearMask = dccClearMask;
found.FillValue = fillValue;
found.FillSize = size;
return false;
}
if (found.Kind == SurfaceMetadataKind.Dcc)
{
found.ClearMask = dccClearMask;
found.FillValue = fillValue;
found.FillSize = size;
return true;
}
return false;
}
public static bool IsDccClearCode(byte code) => code is 0x00 or 0x20 or 0x40 or 0x80 or 0xc0;
public bool TryReadGuestDccClear(ulong metadataAddress, ulong sliceSize, uint slice, out ulong sliceAddress, out byte code)
{
sliceAddress = 0;
code = 0;
if (metadataAddress == 0 || sliceSize == 0 || sliceSize > int.MaxValue ||
(ulong)slice > (ulong.MaxValue - metadataAddress) / sliceSize)
{
return false;
}
var address = metadataAddress + (ulong)slice * sliceSize;
if (!IsValidRange(address, sliceSize) || _bufferCache.HasGpuDirtyBytes(address, sliceSize))
{
return false;
}
Span<byte> first = stackalloc byte[1];
if (!_backing.TryReadBacking(address, first) || !IsDccClearCode(first[0]))
{
return false;
}
var buffer = System.Buffers.ArrayPool<byte>.Shared.Rent((int)Math.Min(sliceSize, 64 * 1024));
try
{
for (ulong position = 0; position < sliceSize;)
{
var span = buffer.AsSpan(0, (int)Math.Min(sliceSize - position, (ulong)buffer.Length));
if (!_backing.TryReadBacking(address + position, span) || span.IndexOfAnyExcept(first[0]) >= 0)
{
return false;
}
position += (ulong)span.Length;
}
}
finally
{
System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
}
sliceAddress = address;
code = first[0];
return true;
}
public void SynchronizeGuestDccMetadata(ulong metadataAddress, ulong sliceSize, uint baseLayer, uint layerCount)
{
if (metadataAddress == 0 || sliceSize == 0 || layerCount == 0 ||
(ulong)baseLayer + layerCount > (ulong.MaxValue - metadataAddress) / sliceSize)
{
return;
}
var address = metadataAddress + (ulong)baseLayer * sliceSize;
var size = (ulong)layerCount * sliceSize;
if (IsValidRange(address, size) && _bufferCache.HasGpuDirtyBytes(address, size))
{
_ = _bufferCache.TrySynchronizeCpuRead(address, size);
}
}
public bool SetMetadataSlice(ulong address, uint slice, bool isClear)
{
using var held = _lock.Hold();
if (!_surfaceMetadata.TryGetValue(address, out var found) || found.Kind == SurfaceMetadataKind.PendingDcc || slice >= 32)
{
return false;
}
if (isClear)
{
found.ClearMask |= 1u << (int)slice;
}
else
{
found.ClearMask &= ~(1u << (int)slice);
}
return true;
}
}