171 lines
5.7 KiB
C#
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;
|
|
}
|
|
}
|