diff --git a/src/SharpEmu.Libs/Agc/AgcExports.cs b/src/SharpEmu.Libs/Agc/AgcExports.cs index 1faad7c4..53ed15ba 100644 --- a/src/SharpEmu.Libs/Agc/AgcExports.cs +++ b/src/SharpEmu.Libs/Agc/AgcExports.cs @@ -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( diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs index cfd482ec..4bc8be3e 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs @@ -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