fix: make Vulkan swapchain recreation resilient
This commit is contained in:
@@ -121,10 +121,13 @@ class Program
|
|||||||
{
|
{
|
||||||
lastWidth = window.Width;
|
lastWidth = window.Width;
|
||||||
lastHeight = window.Height;
|
lastHeight = window.Height;
|
||||||
renderContext.Resize(lastWidth, lastHeight);
|
if (lastWidth > 0 && lastHeight > 0)
|
||||||
ref var cam = ref cameraEntity.Ensure<Camera>();
|
{
|
||||||
cam.AspectRatio = (float)lastWidth / lastHeight;
|
renderContext.Resize(lastWidth, lastHeight);
|
||||||
cameraEntity.Set(cam);
|
ref var cam = ref cameraEntity.Ensure<Camera>();
|
||||||
|
cam.AspectRatio = (float)lastWidth / lastHeight;
|
||||||
|
cameraEntity.Set(cam);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cameraController.Update(input, (float)timing.DeltaTime);
|
cameraController.Update(input, (float)timing.DeltaTime);
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ internal sealed unsafe class VulkanContext : IDisposable
|
|||||||
public VkPhysicalDeviceMemoryProperties MemoryProperties;
|
public VkPhysicalDeviceMemoryProperties MemoryProperties;
|
||||||
public VkFormat SurfaceFormat;
|
public VkFormat SurfaceFormat;
|
||||||
public VkColorSpaceKHR SurfaceColorSpace;
|
public VkColorSpaceKHR SurfaceColorSpace;
|
||||||
public VkExtent2D SurfaceExtent;
|
|
||||||
public bool ValidationEnabled;
|
public bool ValidationEnabled;
|
||||||
|
|
||||||
private VkDebugUtilsMessengerEXT _debugMessenger;
|
private VkDebugUtilsMessengerEXT _debugMessenger;
|
||||||
|
|||||||
@@ -241,12 +241,35 @@ internal sealed unsafe class VulkanFrameResources : IDisposable
|
|||||||
Vk.vkUnmapMemory(_device, UboMemories[frameIndex]);
|
Vk.vkUnmapMemory(_device, UboMemories[frameIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WaitFrame(int frameIndex)
|
public void WaitForFrame(int frameIndex)
|
||||||
{
|
{
|
||||||
fixed (VkFence* fencePtr = &FrameFences[frameIndex])
|
fixed (VkFence* fencePtr = &FrameFences[frameIndex])
|
||||||
{
|
|
||||||
Vk.vkWaitForFences(_device, 1, fencePtr, VkBool32.True, ulong.MaxValue);
|
Vk.vkWaitForFences(_device, 1, fencePtr, VkBool32.True, ulong.MaxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetFrameFence(int frameIndex)
|
||||||
|
{
|
||||||
|
fixed (VkFence* fencePtr = &FrameFences[frameIndex])
|
||||||
Vk.vkResetFences(_device, 1, fencePtr);
|
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 VulkanContext _ctx;
|
||||||
private readonly VulkanSwapchain _swapchain;
|
private readonly VulkanSwapchain _swapchain;
|
||||||
private readonly IWindow _window;
|
private readonly IWindow _window;
|
||||||
|
private VulkanRenderer? _renderer;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public IWindow Window => _window;
|
public IWindow Window => _window;
|
||||||
@@ -27,12 +28,18 @@ internal sealed class VulkanRenderContext : IRenderContext{
|
|||||||
|
|
||||||
public IRenderer CreateRenderer()
|
public IRenderer CreateRenderer()
|
||||||
{
|
{
|
||||||
return new VulkanRenderer(_ctx, _swapchain);
|
return _renderer ??= new VulkanRenderer(_ctx, _swapchain, _window);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Resize(int width, int height)
|
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()
|
public void Dispose()
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
{
|
{
|
||||||
private readonly VulkanContext _ctx;
|
private readonly VulkanContext _ctx;
|
||||||
private readonly VulkanSwapchain _swapchain;
|
private readonly VulkanSwapchain _swapchain;
|
||||||
|
private readonly IWindow _window;
|
||||||
private readonly VulkanPipeline _pipeline;
|
private readonly VulkanPipeline _pipeline;
|
||||||
private readonly VulkanFrameResources _frameResources;
|
private readonly VulkanFrameResources _frameResources;
|
||||||
private readonly VulkanShadowMap _shadowMap;
|
private readonly VulkanShadowMap _shadowMap;
|
||||||
@@ -40,10 +41,11 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
public bool IsScreenshotRequested => _screenshotRequested;
|
public bool IsScreenshotRequested => _screenshotRequested;
|
||||||
public IScreenshotProvider ScreenshotProvider => this;
|
public IScreenshotProvider ScreenshotProvider => this;
|
||||||
|
|
||||||
internal VulkanRenderer(VulkanContext ctx, VulkanSwapchain swapchain)
|
internal VulkanRenderer(VulkanContext ctx, VulkanSwapchain swapchain, IWindow window)
|
||||||
{
|
{
|
||||||
_ctx = ctx;
|
_ctx = ctx;
|
||||||
_swapchain = swapchain;
|
_swapchain = swapchain;
|
||||||
|
_window = window;
|
||||||
|
|
||||||
var vertSpv = LoadShader("Shaders/triangle.vert.spv");
|
var vertSpv = LoadShader("Shaders/triangle.vert.spv");
|
||||||
var fragSpv = LoadShader("Shaders/triangle.frag.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 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()
|
private void InitScreenshotBuffer()
|
||||||
{
|
{
|
||||||
if (_screenshotInitialized) return;
|
if (_screenshotInitialized) return;
|
||||||
@@ -101,6 +113,20 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
Vk.vkBindBufferMemory(_ctx.Device, _screenshotBuffer, _screenshotMemory, 0);
|
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)
|
public void CaptureFrame(VkCommandBuffer cmd, uint imageIndex)
|
||||||
{
|
{
|
||||||
InitScreenshotBuffer();
|
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)
|
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)
|
// Read captured frame from previous render (GPU has finished by now)
|
||||||
if (IsRecording)
|
if (IsRecording)
|
||||||
@@ -288,17 +319,23 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
var acquireResult = Vk.vkAcquireNextImageKHR(_ctx.Device, _swapchain.Swapchain,
|
var acquireResult = Vk.vkAcquireNextImageKHR(_ctx.Device, _swapchain.Swapchain,
|
||||||
ulong.MaxValue, _frameResources.AcquireSemaphores[_frameIndex], VkFence.Null, &imageIndex);
|
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,
|
RecreateSwapchain(_window.Width, _window.Height);
|
||||||
_ctx.SurfaceExtent.Height == 0 ? 720 : (int)_ctx.SurfaceExtent.Height);
|
|
||||||
Render(vp, drawCalls, uboData, lights, lightViewProjs, numShadowLights);
|
|
||||||
return;
|
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}");
|
throw new InvalidOperationException($"vkAcquireNextImageKHR failed: {acquireResult}");
|
||||||
|
|
||||||
|
_frameResources.ResetFrameFence(_frameIndex);
|
||||||
|
|
||||||
_totalTime += 0.016f;
|
_totalTime += 0.016f;
|
||||||
|
|
||||||
_frameResources.UpdateUbo(_frameIndex, uboData, VulkanFrameResources.UboSize);
|
_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)
|
if (presentResult == VkResult.ErrorOutOfDateKHR || presentResult == VkResult.SuboptimalKHR)
|
||||||
{
|
{
|
||||||
_swapchain.Recreate(_ctx.SurfaceExtent.Width == 0 ? 1280 : (int)_ctx.SurfaceExtent.Width,
|
RecreateSwapchain(_window.Width, _window.Height);
|
||||||
_ctx.SurfaceExtent.Height == 0 ? 720 : (int)_ctx.SurfaceExtent.Height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_frameIndex = (_frameIndex + 1) % VulkanFrameResources.MaxFramesInFlight;
|
_frameIndex = (_frameIndex + 1) % VulkanFrameResources.MaxFramesInFlight;
|
||||||
@@ -742,8 +778,7 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
}
|
}
|
||||||
_meshCache.Clear();
|
_meshCache.Clear();
|
||||||
|
|
||||||
if (_screenshotBuffer.Handle != 0) Vk.vkDestroyBuffer(_ctx.Device, _screenshotBuffer, 0);
|
DisposeScreenshotBuffer();
|
||||||
if (_screenshotMemory.Handle != 0) Vk.vkFreeMemory(_ctx.Device, _screenshotMemory, 0);
|
|
||||||
_shadowMap?.Dispose();
|
_shadowMap?.Dispose();
|
||||||
_imGui?.Dispose();
|
_imGui?.Dispose();
|
||||||
_frameResources?.Dispose();
|
_frameResources?.Dispose();
|
||||||
|
|||||||
Reference in New Issue
Block a user