Fix/agc zero dim and storage (#828)

* fix(videoout): promote guest images to storage usage

* fix(agc): treat zero-dimension indirect compute dispatches as valid no-ops
This commit is contained in:
Mathias
2026-08-18 21:20:54 +02:00
committed by GitHub
parent fe6521f617
commit a2241d0e83
2 changed files with 53 additions and 59 deletions
+11 -8
View File
@@ -12174,15 +12174,18 @@ public static partial class AgcExports
if (dispatchEndX == 0 || dispatchEndY == 0 || dispatchEndZ == 0)
{
// Indirect dispatches read their dimensions from a guest buffer a
// prior GPU dispatch fills. Zero here means that producer has not run
// yet — signal the caller to suspend on the dims buffer and retry,
// rather than dropping the work (which black-screens GPU-driven games
// like Astro Bot). Direct dispatches carry dims inline, so a zero is
// genuinely malformed and still rejected.
if (opcode == ItDispatchIndirect)
// For indirect dispatches (both absolute and base), zero dimensions are a valid outcome
// of GPU culling passes (0 workgroups). VulkanVideoPresenter handles groupCount = 0 as a clean no-op.
if (opcode == ItDispatchIndirect || dispatchSource is "absolute-indirect" or "base-indirect")
{
indirectDimsRetryAddress = dimensionsAddress;
var waveCount = (initiator & (1u << 15)) != 0 ? 32u : 64u;
dispatch = new ComputeDispatch(
0, 0, 0,
0, 0, 0,
waveCount,
IsIndirect: true,
0, 0, 0);
return true;
}
return RejectComputeDispatch(
@@ -13828,16 +13828,10 @@ internal static unsafe class VulkanVideoPresenter
existing.LogicalDepth == depth &&
existing.Type == type &&
existing.MipLevels == mipLevels &&
(!requiresStorage || existing.SupportsStorageUsage) &&
(exactFormatMatch ||
(IsAliasableGuestImageFormat(existing.Format, format) &&
(!requiresStorage || existing.SupportsStorageUsage))))
IsAliasableGuestImageFormat(existing.Format, format)))
{
if (requiresStorage && !existing.SupportsStorageUsage)
{
throw new InvalidOperationException(
$"Guest image 0x{target.Address:X16} was created without storage usage.");
}
existing.IsCpuBacked = false;
existing.CpuContentFingerprint = 0;
if (existing.RenderPass.Handle == 0 &&
@@ -13870,14 +13864,9 @@ internal static unsafe class VulkanVideoPresenter
if (existing.Width == target.Width &&
existing.Height == target.Height &&
existing.MipLevels == mipLevels &&
(!requiresStorage || existing.SupportsStorageUsage) &&
IsCompatibleViewFormat(existing.Format, format))
{
if (requiresStorage && !existing.SupportsStorageUsage)
{
throw new InvalidOperationException(
$"Guest image 0x{target.Address:X16} was created without storage usage.");
}
if (_traceGuestImageEvents)
{
Console.Error.WriteLine(
@@ -13952,50 +13941,52 @@ internal static unsafe class VulkanVideoPresenter
{
if (requiresStorage && !retained.SupportsStorageUsage)
{
throw new InvalidOperationException(
$"Retained guest image 0x{target.Address:X16} was created without storage usage.");
// Do not reuse retained image if it lacks required storage usage
DestroyGuestImage(retained);
}
retained.IsCpuBacked = false;
retained.CpuContentFingerprint = 0;
_guestImages.Add(target.Address, retained);
var retainedByteCount = GetTextureByteCount(
target.Format,
target.Width,
target.Height,
depth);
lock (_gate)
else
{
_cpuBackedUploadGenerations.Remove(target.Address);
_guestImageExtents[target.Address] = (
retained.IsCpuBacked = false;
retained.CpuContentFingerprint = 0;
_guestImages.Add(target.Address, retained);
var retainedByteCount = GetTextureByteCount(
target.Format,
target.Width,
target.Height,
retainedByteCount);
}
depth);
lock (_gate)
{
_cpuBackedUploadGenerations.Remove(target.Address);
_guestImageExtents[target.Address] = (
target.Width,
target.Height,
retainedByteCount);
}
// Arm the exact extent the flip/acquire sync path would read
// back, budgeted by bytes rather than by resolution: the old
// 1920x1080 cap left every 4K surface permanently
// un-invalidated, so a guest CPU rewrite of one was never
// reflected and the sample served stale bytes.
if (ShouldTrackGuestImageWrites(retainedByteCount))
{
SharpEmu.HLE.GuestImageWriteTracker.Track(
target.Address,
retainedByteCount,
CurrentGuestWorkSequenceForDiagnostics,
"vulkan.render-target");
}
// Arm the exact extent the flip/acquire sync path would read
// back, budgeted by bytes rather than by resolution: the old
// 1920x1080 cap left every 4K surface permanently
// un-invalidated, so a guest CPU rewrite of one was never
// reflected and the sample served stale bytes.
if (ShouldTrackGuestImageWrites(retainedByteCount))
{
SharpEmu.HLE.GuestImageWriteTracker.Track(
target.Address,
retainedByteCount,
CurrentGuestWorkSequenceForDiagnostics,
"vulkan.render-target");
}
if (_traceGuestImageEvents)
{
Console.Error.WriteLine(
$"[GIMG] retained addr=0x{target.Address:X} " +
$"{target.Width}x{target.Height} fmt={format} " +
$"initialized={retained.Initialized}");
}
if (_traceGuestImageEvents)
{
Console.Error.WriteLine(
$"[GIMG] retained addr=0x{target.Address:X} " +
$"{target.Width}x{target.Height} fmt={format} " +
$"initialized={retained.Initialized}");
}
return retained;
return retained;
}
}
var imageInfo = new ImageCreateInfo