[VideoOut] Give every swapchain image its own sRGB-encode intermediate
A float guest flip is blitted through an sRGB image before it is copied into the UNORM swapchain image, and one such intermediate served every frame. Each frame discards its contents by transitioning it Undefined -> TransferDst before blitting, so with two frames in flight the next frame's blit raced the previous frame's copy-out and the presented image came out fully zeroed. The guest's own display buffers held the correct picture the whole time, so the window simply went black for long stretches. Each swapchain image now owns its own intermediate, created and destroyed with the swapchain, so a frame can only ever discard the intermediate belonging to the image it is about to present. On Superliminal the same run went from 60 of 84 presented-swapchain readbacks black to 10 of 187, all of them from before the menu appears, and the menu no longer turns black after a couple of minutes.
This commit is contained in:
parent
4d65ff6d51
commit
4512688301
|
|
@ -145,11 +145,16 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
// for any dim scene. Encode linear->sRGB by blitting through an sRGB
|
||||
// intermediate (sRGB stores encode), then raw-copying the encoded
|
||||
// bytes into the same-class UNORM swapchain image.
|
||||
private Image _presentEncodeImage;
|
||||
private DeviceMemory _presentEncodeMemory;
|
||||
// One intermediate per swapchain image: every frame discards the encode
|
||||
// image's contents (Undefined -> TransferDst) before blitting into it,
|
||||
// so a single shared image lets the next frame in flight overwrite the
|
||||
// one the previous frame has not copied out yet, which presents as a
|
||||
// fully zeroed (black) swapchain image.
|
||||
private Image[] _presentEncodeImages = [];
|
||||
private DeviceMemory[] _presentEncodeMemories = [];
|
||||
private Extent2D _presentEncodeExtent;
|
||||
|
||||
private bool TryGetPresentEncodeImage(out Image encodeImage)
|
||||
private bool TryGetPresentEncodeImage(uint imageIndex, out Image encodeImage)
|
||||
{
|
||||
encodeImage = default;
|
||||
var encodeFormat = GetSrgbCounterpart(_swapchainFormat);
|
||||
|
|
@ -158,14 +163,29 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
return false;
|
||||
}
|
||||
|
||||
if (_presentEncodeImage.Handle != 0 &&
|
||||
if (_presentEncodeImages.Length != 0 &&
|
||||
(_presentEncodeExtent.Width != _extent.Width ||
|
||||
_presentEncodeExtent.Height != _extent.Height))
|
||||
{
|
||||
DestroyPresentEncodeImage();
|
||||
}
|
||||
|
||||
if (_presentEncodeImage.Handle == 0)
|
||||
if (_presentEncodeImages.Length <= imageIndex)
|
||||
{
|
||||
var grown = new Image[_swapchainImages.Length];
|
||||
var grownMemory = new DeviceMemory[_swapchainImages.Length];
|
||||
_presentEncodeImages.CopyTo(grown, 0);
|
||||
_presentEncodeMemories.CopyTo(grownMemory, 0);
|
||||
_presentEncodeImages = grown;
|
||||
_presentEncodeMemories = grownMemory;
|
||||
}
|
||||
|
||||
if (imageIndex >= _presentEncodeImages.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_presentEncodeImages[imageIndex].Handle == 0)
|
||||
{
|
||||
var imageInfo = new ImageCreateInfo
|
||||
{
|
||||
|
|
@ -183,11 +203,11 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
InitialLayout = ImageLayout.Undefined,
|
||||
};
|
||||
Check(
|
||||
_vk.CreateImage(_device, &imageInfo, null, out _presentEncodeImage),
|
||||
_vk.CreateImage(_device, &imageInfo, null, out _presentEncodeImages[imageIndex]),
|
||||
"vkCreateImage(present encode)");
|
||||
_vk.GetImageMemoryRequirements(
|
||||
_device,
|
||||
_presentEncodeImage,
|
||||
_presentEncodeImages[imageIndex],
|
||||
out var requirements);
|
||||
var allocationInfo = new MemoryAllocateInfo
|
||||
{
|
||||
|
|
@ -198,35 +218,45 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
MemoryPropertyFlags.DeviceLocalBit),
|
||||
};
|
||||
Check(
|
||||
_deviceInfo.AllocateMemory(allocationInfo, out _presentEncodeMemory),
|
||||
_deviceInfo.AllocateMemory(allocationInfo, out _presentEncodeMemories[imageIndex]),
|
||||
"vkAllocateMemory(present encode)");
|
||||
Check(
|
||||
_vk.BindImageMemory(_device, _presentEncodeImage, _presentEncodeMemory, 0),
|
||||
_vk.BindImageMemory(
|
||||
_device,
|
||||
_presentEncodeImages[imageIndex],
|
||||
_presentEncodeMemories[imageIndex],
|
||||
0),
|
||||
"vkBindImageMemory(present encode)");
|
||||
_presentEncodeExtent = new Extent2D(_extent.Width, _extent.Height);
|
||||
SetDebugName(
|
||||
ObjectType.Image,
|
||||
_presentEncodeImage.Handle,
|
||||
"SharpEmu present sRGB-encode image");
|
||||
_presentEncodeImages[imageIndex].Handle,
|
||||
$"SharpEmu present sRGB-encode image {imageIndex}");
|
||||
}
|
||||
|
||||
encodeImage = _presentEncodeImage;
|
||||
encodeImage = _presentEncodeImages[imageIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
private void DestroyPresentEncodeImage()
|
||||
{
|
||||
if (_presentEncodeImage.Handle != 0)
|
||||
for (var index = 0; index < _presentEncodeImages.Length; index++)
|
||||
{
|
||||
_vk.DestroyImage(_device, _presentEncodeImage, null);
|
||||
_presentEncodeImage = default;
|
||||
if (_presentEncodeImages[index].Handle != 0)
|
||||
{
|
||||
_vk.DestroyImage(_device, _presentEncodeImages[index], null);
|
||||
_presentEncodeImages[index] = default;
|
||||
}
|
||||
|
||||
if (_presentEncodeMemories[index].Handle != 0)
|
||||
{
|
||||
_deviceInfo.FreeMemory(_presentEncodeMemories[index]);
|
||||
_presentEncodeMemories[index] = default;
|
||||
}
|
||||
}
|
||||
|
||||
if (_presentEncodeMemory.Handle != 0)
|
||||
{
|
||||
_deviceInfo.FreeMemory(_presentEncodeMemory);
|
||||
_presentEncodeMemory = default;
|
||||
}
|
||||
_presentEncodeImages = [];
|
||||
_presentEncodeMemories = [];
|
||||
|
||||
_presentEncodeExtent = default;
|
||||
}
|
||||
|
|
@ -291,7 +321,7 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
if (IsLinearFloatPresentSource(source.Format) &&
|
||||
GetSrgbCounterpart(PresentationTargetFormat) != Format.Undefined)
|
||||
{
|
||||
encodeForPresent = TryGetPresentEncodeImage(out encodeImage);
|
||||
encodeForPresent = TryGetPresentEncodeImage(imageIndex, out encodeImage);
|
||||
}
|
||||
|
||||
var encodeToTransferDst = new ImageMemoryBarrier2
|
||||
|
|
@ -303,7 +333,7 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
NewLayout = ImageLayout.TransferDstOptimal,
|
||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
Image = _presentEncodeImage,
|
||||
Image = encodeImage,
|
||||
SubresourceRange = ColorSubresourceRange(),
|
||||
};
|
||||
var barriers = stackalloc ImageMemoryBarrier2[3];
|
||||
|
|
|
|||
Loading…
Reference in New Issue