fix: make Vulkan swapchain recreation resilient
This commit is contained in:
@@ -121,10 +121,13 @@ class Program
|
||||
{
|
||||
lastWidth = window.Width;
|
||||
lastHeight = window.Height;
|
||||
renderContext.Resize(lastWidth, lastHeight);
|
||||
ref var cam = ref cameraEntity.Ensure<Camera>();
|
||||
cam.AspectRatio = (float)lastWidth / lastHeight;
|
||||
cameraEntity.Set(cam);
|
||||
if (lastWidth > 0 && lastHeight > 0)
|
||||
{
|
||||
renderContext.Resize(lastWidth, lastHeight);
|
||||
ref var cam = ref cameraEntity.Ensure<Camera>();
|
||||
cam.AspectRatio = (float)lastWidth / lastHeight;
|
||||
cameraEntity.Set(cam);
|
||||
}
|
||||
}
|
||||
|
||||
cameraController.Update(input, (float)timing.DeltaTime);
|
||||
|
||||
@@ -27,7 +27,6 @@ internal sealed unsafe class VulkanContext : IDisposable
|
||||
public VkPhysicalDeviceMemoryProperties MemoryProperties;
|
||||
public VkFormat SurfaceFormat;
|
||||
public VkColorSpaceKHR SurfaceColorSpace;
|
||||
public VkExtent2D SurfaceExtent;
|
||||
public bool ValidationEnabled;
|
||||
|
||||
private VkDebugUtilsMessengerEXT _debugMessenger;
|
||||
|
||||
@@ -241,12 +241,35 @@ internal sealed unsafe class VulkanFrameResources : IDisposable
|
||||
Vk.vkUnmapMemory(_device, UboMemories[frameIndex]);
|
||||
}
|
||||
|
||||
public void WaitFrame(int frameIndex)
|
||||
public void WaitForFrame(int frameIndex)
|
||||
{
|
||||
fixed (VkFence* fencePtr = &FrameFences[frameIndex])
|
||||
{
|
||||
Vk.vkWaitForFences(_device, 1, fencePtr, VkBool32.True, ulong.MaxValue);
|
||||
}
|
||||
|
||||
public void ResetFrameFence(int frameIndex)
|
||||
{
|
||||
fixed (VkFence* fencePtr = &FrameFences[frameIndex])
|
||||
Vk.vkResetFences(_device, 1, fencePtr);
|
||||
}
|
||||
|
||||
public void RecreateSubmitSemaphores(uint swapchainImageCount)
|
||||
{
|
||||
foreach (var semaphore in SubmitSemaphores)
|
||||
{
|
||||
if (semaphore.Handle != 0)
|
||||
Vk.vkDestroySemaphore(_device, semaphore, 0);
|
||||
}
|
||||
|
||||
SubmitSemaphores = new VkSemaphore[swapchainImageCount];
|
||||
var semInfo = new VkSemaphoreCreateInfo { sType = VkStructureType.SemaphoreCreateInfo };
|
||||
for (int i = 0; i < SubmitSemaphores.Length; i++)
|
||||
{
|
||||
var semaphore = VkSemaphore.Null;
|
||||
var result = Vk.vkCreateSemaphore(_device, &semInfo, 0, &semaphore);
|
||||
if (result != VkResult.Success)
|
||||
throw new InvalidOperationException($"vkCreateSemaphore (submit) failed: {result}");
|
||||
SubmitSemaphores[i] = semaphore;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ internal sealed class VulkanRenderContext : IRenderContext{
|
||||
private readonly VulkanContext _ctx;
|
||||
private readonly VulkanSwapchain _swapchain;
|
||||
private readonly IWindow _window;
|
||||
private VulkanRenderer? _renderer;
|
||||
private bool _disposed;
|
||||
|
||||
public IWindow Window => _window;
|
||||
@@ -27,12 +28,18 @@ internal sealed class VulkanRenderContext : IRenderContext{
|
||||
|
||||
public IRenderer CreateRenderer()
|
||||
{
|
||||
return new VulkanRenderer(_ctx, _swapchain);
|
||||
return _renderer ??= new VulkanRenderer(_ctx, _swapchain, _window);
|
||||
}
|
||||
|
||||
public void Resize(int width, int height)
|
||||
{
|
||||
_swapchain.Recreate(width, height);
|
||||
if (width <= 0 || height <= 0)
|
||||
return;
|
||||
|
||||
if (_renderer is not null)
|
||||
_renderer.RecreateSwapchain(width, height);
|
||||
else
|
||||
_swapchain.Recreate(width, height);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -11,6 +11,7 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
||||
{
|
||||
private readonly VulkanContext _ctx;
|
||||
private readonly VulkanSwapchain _swapchain;
|
||||
private readonly IWindow _window;
|
||||
private readonly VulkanPipeline _pipeline;
|
||||
private readonly VulkanFrameResources _frameResources;
|
||||
private readonly VulkanShadowMap _shadowMap;
|
||||
@@ -40,10 +41,11 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
||||
public bool IsScreenshotRequested => _screenshotRequested;
|
||||
public IScreenshotProvider ScreenshotProvider => this;
|
||||
|
||||
internal VulkanRenderer(VulkanContext ctx, VulkanSwapchain swapchain)
|
||||
internal VulkanRenderer(VulkanContext ctx, VulkanSwapchain swapchain, IWindow window)
|
||||
{
|
||||
_ctx = ctx;
|
||||
_swapchain = swapchain;
|
||||
_window = window;
|
||||
|
||||
var vertSpv = LoadShader("Shaders/triangle.vert.spv");
|
||||
var fragSpv = LoadShader("Shaders/triangle.frag.spv");
|
||||
@@ -63,6 +65,16 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
||||
|
||||
internal VulkanImGui? ImGuiLayer => _imGui;
|
||||
|
||||
internal void RecreateSwapchain(int width, int height)
|
||||
{
|
||||
if (width <= 0 || height <= 0)
|
||||
return;
|
||||
|
||||
_swapchain.Recreate(width, height);
|
||||
_frameResources.RecreateSubmitSemaphores(_swapchain.ImageCount);
|
||||
DisposeScreenshotBuffer();
|
||||
}
|
||||
|
||||
private void InitScreenshotBuffer()
|
||||
{
|
||||
if (_screenshotInitialized) return;
|
||||
@@ -101,6 +113,20 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
||||
Vk.vkBindBufferMemory(_ctx.Device, _screenshotBuffer, _screenshotMemory, 0);
|
||||
}
|
||||
|
||||
private void DisposeScreenshotBuffer()
|
||||
{
|
||||
if (_screenshotBuffer.Handle != 0)
|
||||
Vk.vkDestroyBuffer(_ctx.Device, _screenshotBuffer, 0);
|
||||
if (_screenshotMemory.Handle != 0)
|
||||
Vk.vkFreeMemory(_ctx.Device, _screenshotMemory, 0);
|
||||
|
||||
_screenshotBuffer = VkBuffer.Null;
|
||||
_screenshotMemory = VkDeviceMemory.Null;
|
||||
_screenshotBufferSize = 0;
|
||||
_screenshotInitialized = false;
|
||||
CapturedFrame = null;
|
||||
}
|
||||
|
||||
public void CaptureFrame(VkCommandBuffer cmd, uint imageIndex)
|
||||
{
|
||||
InitScreenshotBuffer();
|
||||
@@ -266,7 +292,12 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
||||
|
||||
private void Render(Matrix4x4 vp, List<(VkBuffer vertexBuf, VkBuffer indexBuf, uint indexCount, Matrix4x4 model, bool castShadow)> drawCalls, byte* uboData, List<(Vector3 pos, float intensity, Vector3 color, float range)> lights, Matrix4x4[] lightViewProjs, int numShadowLights)
|
||||
{
|
||||
_frameResources.WaitFrame(_frameIndex);
|
||||
_frameResources.WaitForFrame(_frameIndex);
|
||||
|
||||
// A minimized window has no drawable surface. Keep the frame fence signaled and wait
|
||||
// for the window event loop to provide a non-zero extent before trying to acquire.
|
||||
if (_window.Width <= 0 || _window.Height <= 0)
|
||||
return;
|
||||
|
||||
// Read captured frame from previous render (GPU has finished by now)
|
||||
if (IsRecording)
|
||||
@@ -288,17 +319,23 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
||||
var acquireResult = Vk.vkAcquireNextImageKHR(_ctx.Device, _swapchain.Swapchain,
|
||||
ulong.MaxValue, _frameResources.AcquireSemaphores[_frameIndex], VkFence.Null, &imageIndex);
|
||||
|
||||
if (acquireResult == VkResult.ErrorOutOfDateKHR || acquireResult == VkResult.SuboptimalKHR)
|
||||
// An out-of-date acquire does not signal the semaphore. Do not reset the frame fence
|
||||
// until an image has been acquired and a submission is guaranteed; otherwise returning
|
||||
// from this frame would leave the next WaitForFrame blocked forever.
|
||||
if (acquireResult == VkResult.ErrorOutOfDateKHR)
|
||||
{
|
||||
_swapchain.Recreate(_ctx.SurfaceExtent.Width == 0 ? 1280 : (int)_ctx.SurfaceExtent.Width,
|
||||
_ctx.SurfaceExtent.Height == 0 ? 720 : (int)_ctx.SurfaceExtent.Height);
|
||||
Render(vp, drawCalls, uboData, lights, lightViewProjs, numShadowLights);
|
||||
RecreateSwapchain(_window.Width, _window.Height);
|
||||
return;
|
||||
}
|
||||
|
||||
if (acquireResult != VkResult.Success)
|
||||
// SuboptimalKHR is a success code: the semaphore WAS signaled and imageIndex is valid.
|
||||
// Render and present this frame normally; the post-present check below recreates the
|
||||
// swapchain at a safe point, after this frame's submit has re-armed the frame fence.
|
||||
if (acquireResult != VkResult.Success && acquireResult != VkResult.SuboptimalKHR)
|
||||
throw new InvalidOperationException($"vkAcquireNextImageKHR failed: {acquireResult}");
|
||||
|
||||
_frameResources.ResetFrameFence(_frameIndex);
|
||||
|
||||
_totalTime += 0.016f;
|
||||
|
||||
_frameResources.UpdateUbo(_frameIndex, uboData, VulkanFrameResources.UboSize);
|
||||
@@ -620,8 +657,7 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
||||
|
||||
if (presentResult == VkResult.ErrorOutOfDateKHR || presentResult == VkResult.SuboptimalKHR)
|
||||
{
|
||||
_swapchain.Recreate(_ctx.SurfaceExtent.Width == 0 ? 1280 : (int)_ctx.SurfaceExtent.Width,
|
||||
_ctx.SurfaceExtent.Height == 0 ? 720 : (int)_ctx.SurfaceExtent.Height);
|
||||
RecreateSwapchain(_window.Width, _window.Height);
|
||||
}
|
||||
|
||||
_frameIndex = (_frameIndex + 1) % VulkanFrameResources.MaxFramesInFlight;
|
||||
@@ -742,8 +778,7 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
||||
}
|
||||
_meshCache.Clear();
|
||||
|
||||
if (_screenshotBuffer.Handle != 0) Vk.vkDestroyBuffer(_ctx.Device, _screenshotBuffer, 0);
|
||||
if (_screenshotMemory.Handle != 0) Vk.vkFreeMemory(_ctx.Device, _screenshotMemory, 0);
|
||||
DisposeScreenshotBuffer();
|
||||
_shadowMap?.Dispose();
|
||||
_imGui?.Dispose();
|
||||
_frameResources?.Dispose();
|
||||
|
||||
Reference in New Issue
Block a user