From 7d08ca3a0efa24c07b8d0b4cf2f3e24fa1de1151 Mon Sep 17 00:00:00 2001 From: Dario Date: Sat, 3 Jan 2026 20:15:39 -0300 Subject: [PATCH 1/4] Refactor to use SwapChainDesc. Introduce tweaks to present() for Vulkan and D3D12. --- plume_d3d12.cpp | 50 +++++++++--------- plume_d3d12.h | 9 ++-- plume_metal.cpp | 25 ++++----- plume_metal.h | 6 +-- plume_render_interface.h | 2 +- plume_render_interface_types.h | 12 +++++ plume_vulkan.cpp | 97 ++++++++++++++++++---------------- plume_vulkan.h | 10 ++-- 8 files changed, 112 insertions(+), 99 deletions(-) diff --git a/plume_d3d12.cpp b/plume_d3d12.cpp index e6fc0a19..8c239f4d 100644 --- a/plume_d3d12.cpp +++ b/plume_d3d12.cpp @@ -1315,18 +1315,15 @@ namespace plume { // D3D12SwapChain - D3D12SwapChain::D3D12SwapChain(D3D12CommandQueue *commandQueue, RenderWindow renderWindow, uint32_t textureCount, RenderFormat format, uint32_t maxFrameLatency) { + D3D12SwapChain::D3D12SwapChain(D3D12CommandQueue *commandQueue, const RenderSwapChainDesc &desc) { assert(commandQueue != nullptr); - assert(renderWindow != 0); + assert(desc.renderWindow != 0); this->commandQueue = commandQueue; - this->renderWindow = renderWindow; - this->textureCount = textureCount; - this->format = format; - this->maxFrameLatency = maxFrameLatency; + this->desc = desc; // Store the native format representation. - nativeFormat = toDXGI(format); + nativeFormat = toDXGI(desc.format); getWindowSize(width, height); @@ -1336,7 +1333,7 @@ namespace plume { } DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; - swapChainDesc.BufferCount = textureCount; + swapChainDesc.BufferCount = desc.textureCount; swapChainDesc.Width = width; swapChainDesc.Height = height; swapChainDesc.Format = nativeFormat; @@ -1347,28 +1344,28 @@ namespace plume { IDXGISwapChain1 *swapChain1; IDXGIFactory4 *dxgiFactory = commandQueue->device->renderInterface->dxgiFactory; - HRESULT res = dxgiFactory->CreateSwapChainForHwnd(commandQueue->d3d, renderWindow, &swapChainDesc, nullptr, nullptr, &swapChain1); + HRESULT res = dxgiFactory->CreateSwapChainForHwnd(commandQueue->d3d, desc.renderWindow, &swapChainDesc, nullptr, nullptr, &swapChain1); if (FAILED(res)) { fprintf(stderr, "CreateSwapChainForHwnd failed with error code 0x%lX.\n", res); return; } - res = dxgiFactory->MakeWindowAssociation(renderWindow, DXGI_MWA_NO_ALT_ENTER); + res = dxgiFactory->MakeWindowAssociation(desc.renderWindow, DXGI_MWA_NO_ALT_ENTER); if (FAILED(res)) { fprintf(stderr, "MakeWindowAssociation failed with error code 0x%lX.\n", res); return; } d3d = static_cast(swapChain1); - d3d->SetMaximumFrameLatency(maxFrameLatency); + d3d->SetMaximumFrameLatency(desc.maxFrameLatency); waitableObject = d3d->GetFrameLatencyWaitableObject(); - textures.resize(textureCount); + textures.resize(desc.textureCount); - for (uint32_t i = 0; i < textureCount; i++) { + for (uint32_t i = 0; i < desc.textureCount; i++) { textures[i].device = commandQueue->device; textures[i].desc.dimension = RenderTextureDimension::TEXTURE_2D; - textures[i].desc.format = format; + textures[i].desc.format = desc.format; textures[i].desc.depth = 1; textures[i].desc.mipLevels = 1; textures[i].desc.arraySize = 1; @@ -1379,7 +1376,7 @@ namespace plume { } D3D12SwapChain::~D3D12SwapChain() { - for (uint32_t i = 0; i < textureCount; i++) { + for (uint32_t i = 0; i < desc.textureCount; i++) { if (textures[i].d3d != nullptr) { textures[i].d3d->Release(); textures[i].d3d = nullptr; @@ -1392,14 +1389,17 @@ namespace plume { } bool D3D12SwapChain::present(uint32_t textureIndex, RenderCommandSemaphore **waitSemaphores, uint32_t waitSemaphoreCount) { + // If using present wait, we prefer using a SyncInterval of 0 even when Vsync is enabled. Tearing won't happen if DXGI_PRESENT_ALLOW_TEARING is not specified. const bool tearingAllowed = (swapChainFlags & DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING) != 0U; - UINT syncInterval = vsyncEnabled ? 1 : 0; - UINT flags = (tearingAllowed && !vsyncEnabled) ? DXGI_PRESENT_ALLOW_TEARING : 0; + UINT syncInterval = (vsyncEnabled && !desc.enablePresentWait) ? 1 : 0; + UINT flags = (!vsyncEnabled && tearingAllowed) ? DXGI_PRESENT_ALLOW_TEARING : 0; HRESULT res = d3d->Present(syncInterval, flags); return SUCCEEDED(res); } void D3D12SwapChain::wait() { + assert(desc.enablePresentWait && "Present wait should've been explicitly enabled during swap chain creation before using the wait function."); + if (waitableObject != NULL) { WaitForSingleObject(waitableObject, INFINITE); } @@ -1413,7 +1413,7 @@ namespace plume { return false; } - for (uint32_t i = 0; i < textureCount; i++) { + for (uint32_t i = 0; i < desc.textureCount; i++) { textures[i].d3d->Release(); textures[i].d3d = nullptr; } @@ -1452,15 +1452,15 @@ namespace plume { void D3D12SwapChain::getWindowSize(uint32_t &dstWidth, uint32_t &dstHeight) const { RECT rect; - GetClientRect(renderWindow, &rect); + GetClientRect(desc.renderWindow, &rect); dstWidth = rect.right - rect.left; dstHeight = rect.bottom - rect.top; } void D3D12SwapChain::setTextures() { - assert(textureCount == textures.size()); + assert(desc.textureCount == textures.size()); - for (uint32_t i = 0; i < textureCount; i++) { + for (uint32_t i = 0; i < desc.textureCount; i++) { d3d->GetBuffer(i, IID_PPV_ARGS(&textures[i].d3d)); textures[i].desc.width = width; @@ -1475,7 +1475,7 @@ namespace plume { } uint32_t D3D12SwapChain::getTextureCount() const { - return textureCount; + return desc.textureCount; } bool D3D12SwapChain::acquireTexture(RenderCommandSemaphore *signalSemaphore, uint32_t *textureIndex) { @@ -1485,7 +1485,7 @@ namespace plume { } RenderWindow D3D12SwapChain::getWindow() const { - return renderWindow; + return desc.renderWindow; } bool D3D12SwapChain::isEmpty() const { @@ -2672,8 +2672,8 @@ namespace plume { return std::make_unique(this); } - std::unique_ptr D3D12CommandQueue::createSwapChain(RenderWindow renderWindow, uint32_t bufferCount, RenderFormat format, uint32_t maxFrameLatency) { - return std::make_unique(this, renderWindow, bufferCount, format, maxFrameLatency); + std::unique_ptr D3D12CommandQueue::createSwapChain(const RenderSwapChainDesc &desc) { + return std::make_unique(this, desc); } void D3D12CommandQueue::executeCommandLists(const RenderCommandList **commandLists, uint32_t commandListCount, RenderCommandSemaphore **waitSemaphores, uint32_t waitSemaphoreCount, RenderCommandSemaphore **signalSemaphores, uint32_t signalSemaphoreCount, RenderCommandFence *signalFence) { diff --git a/plume_d3d12.h b/plume_d3d12.h index 205ddeeb..48274528 100644 --- a/plume_d3d12.h +++ b/plume_d3d12.h @@ -100,22 +100,19 @@ namespace plume { }; struct D3D12SwapChain : RenderSwapChain { + RenderSwapChainDesc desc; IDXGISwapChain3 *d3d = nullptr; HANDLE waitableObject = 0; D3D12CommandQueue *commandQueue = nullptr; - RenderWindow renderWindow = {}; std::vector textures; - uint32_t textureCount = 0; - RenderFormat format = RenderFormat::UNKNOWN; DXGI_FORMAT nativeFormat = DXGI_FORMAT_UNKNOWN; uint32_t width = 0; uint32_t height = 0; uint32_t refreshRate = 0; bool vsyncEnabled = true; - uint32_t maxFrameLatency = 0; UINT swapChainFlags = 0; - D3D12SwapChain(D3D12CommandQueue *commandQueue, RenderWindow renderWindow, uint32_t textureCount, RenderFormat format, uint32_t maxFrameLatency); + D3D12SwapChain(D3D12CommandQueue *commandQueue, const RenderSwapChainDesc &desc); ~D3D12SwapChain() override; bool present(uint32_t textureIndex, RenderCommandSemaphore **waitSemaphores, uint32_t waitSemaphoreCount) override; void wait() override; @@ -266,7 +263,7 @@ namespace plume { D3D12CommandQueue(D3D12Device *device, RenderCommandListType type); ~D3D12CommandQueue() override; std::unique_ptr createCommandList() override; - std::unique_ptr createSwapChain(RenderWindow renderWindow, uint32_t textureCount, RenderFormat format, uint32_t newFrameLatency) override; + std::unique_ptr createSwapChain(const RenderSwapChainDesc &desc) override; void executeCommandLists(const RenderCommandList **commandLists, uint32_t commandListCount, RenderCommandSemaphore **waitSemaphores, uint32_t waitSemaphoreCount, RenderCommandSemaphore **signalSemaphores, uint32_t signalSemaphoreCount, RenderCommandFence *signalFence) override; void waitForCommandFence(RenderCommandFence *fence) override; }; diff --git a/plume_metal.cpp b/plume_metal.cpp index bb94a4eb..fd27894e 100644 --- a/plume_metal.cpp +++ b/plume_metal.cpp @@ -1900,19 +1900,18 @@ namespace plume { // MetalSwapChain - MetalSwapChain::MetalSwapChain(MetalCommandQueue *commandQueue, const RenderWindow renderWindow, uint32_t textureCount, const RenderFormat format, uint32_t maxFrameLatency) { - this->layer = static_cast(renderWindow.view); + MetalSwapChain::MetalSwapChain(MetalCommandQueue *commandQueue, const RenderSwapChainDesc &desc) { + this->layer = static_cast(desc.renderWindow.view); layer->setDevice(commandQueue->device->mtl); - layer->setPixelFormat(mapPixelFormat(format)); + layer->setPixelFormat(mapPixelFormat(desc.format)); this->commandQueue = commandQueue; - this->maxFrameLatency = maxFrameLatency; + this->desc = desc; // Metal supports a maximum of 3 drawables. this->drawables.resize(MAX_DRAWABLES); - this->renderWindow = renderWindow; - this->windowWrapper = std::make_unique(renderWindow.window); + this->windowWrapper = std::make_unique(desc.renderWindow.window); getWindowSize(width, height); // Set the layer's drawable size to match the window size @@ -1925,7 +1924,7 @@ namespace plume { MetalDrawable &drawable = this->drawables[i]; drawable.desc.width = width; drawable.desc.height = height; - drawable.desc.format = format; + drawable.desc.format = desc.format; drawable.desc.flags = RenderTextureFlag::RENDER_TARGET; } } @@ -1978,10 +1977,12 @@ namespace plume { } void MetalSwapChain::wait() { - if (currentPresentId >= maxFrameLatency) { + assert(desc.enablePresentWait && "Present wait should've been explicitly enabled during swap chain creation before using the wait function."); + + if (currentPresentId >= desc.maxFrameLatency) { std::unique_lock lock(lastPresentedIdMutex); lastPresentedIdCondVar.wait_for(lock, std::chrono::seconds(1), [this] { - return lastPresentedId >= currentPresentId - (maxFrameLatency - 1); + return lastPresentedId >= currentPresentId - (desc.maxFrameLatency - 1); }); } } @@ -2079,7 +2080,7 @@ namespace plume { } RenderWindow MetalSwapChain::getWindow() const { - return renderWindow; + return desc.renderWindow; } bool MetalSwapChain::isEmpty() const { @@ -3676,8 +3677,8 @@ namespace plume { return std::make_unique(this); } - std::unique_ptr MetalCommandQueue::createSwapChain(RenderWindow renderWindow, uint32_t textureCount, RenderFormat format, uint32_t maxFrameLatency) { - return std::make_unique(this, renderWindow, textureCount, format, maxFrameLatency); + std::unique_ptr MetalCommandQueue::createSwapChain(const RenderSwapChainDesc &desc) { + return std::make_unique(this, desc); } void MetalCommandQueue::executeCommandLists(const RenderCommandList **commandLists, const uint32_t commandListCount, RenderCommandSemaphore **waitSemaphores, const uint32_t waitSemaphoreCount, RenderCommandSemaphore **signalSemaphores, const uint32_t signalSemaphoreCount, RenderCommandFence *signalFence) { diff --git a/plume_metal.h b/plume_metal.h index cf55279d..57483b65 100644 --- a/plume_metal.h +++ b/plume_metal.h @@ -234,25 +234,23 @@ namespace plume { }; struct MetalSwapChain : RenderSwapChain { + RenderSwapChainDesc desc; CA::MetalLayer *layer = nullptr; MetalCommandQueue *commandQueue = nullptr; - RenderFormat format = RenderFormat::UNKNOWN; uint32_t width = 0; uint32_t height = 0; uint32_t refreshRate = 0; std::vector drawables; uint32_t currentAvailableDrawableIndex = 0; - RenderWindow renderWindow = {}; std::unique_ptr windowWrapper; // Present wait - uint32_t maxFrameLatency = 0; uint64_t currentPresentId = 0; uint64_t lastPresentedId = 0; std::mutex lastPresentedIdMutex; std::condition_variable lastPresentedIdCondVar; - MetalSwapChain(MetalCommandQueue *commandQueue, RenderWindow renderWindow, uint32_t textureCount, RenderFormat format, uint32_t maxFrameLatency); + MetalSwapChain(MetalCommandQueue *commandQueue, const RenderSwapChainDesc &desc); ~MetalSwapChain() override; bool present(uint32_t textureIndex, RenderCommandSemaphore **waitSemaphores, uint32_t waitSemaphoreCount) override; void wait() override; diff --git a/plume_render_interface.h b/plume_render_interface.h index 1c69c35f..20485570 100644 --- a/plume_render_interface.h +++ b/plume_render_interface.h @@ -200,7 +200,7 @@ namespace plume { struct RenderCommandQueue { virtual ~RenderCommandQueue() { } virtual std::unique_ptr createCommandList() = 0; - virtual std::unique_ptr createSwapChain(RenderWindow renderWindow, uint32_t textureCount, RenderFormat format, uint32_t maxFrameLatency) = 0; + virtual std::unique_ptr createSwapChain(const RenderSwapChainDesc &desc) = 0; virtual void executeCommandLists(const RenderCommandList **commandLists, uint32_t commandListCount, RenderCommandSemaphore **waitSemaphores = nullptr, uint32_t waitSemaphoreCount = 0, RenderCommandSemaphore **signalSemaphores = nullptr, uint32_t signalSemaphoreCount = 0, RenderCommandFence *signalFence = nullptr) = 0; virtual void waitForCommandFence(RenderCommandFence *fence) = 0; diff --git a/plume_render_interface_types.h b/plume_render_interface_types.h index 8be5b056..f62bfb22 100644 --- a/plume_render_interface_types.h +++ b/plume_render_interface_types.h @@ -1139,6 +1139,18 @@ namespace plume { bool allowOnlyBuffers = false; }; + struct RenderSwapChainDesc { + RenderWindow renderWindow = {}; + RenderFormat format = RenderFormat::UNKNOWN; + uint32_t textureCount = 0; + + // The capability for presentWait must be supported by the RenderDevice. + bool enablePresentWait = false; + uint32_t maxFrameLatency = 0; + + RenderSwapChainDesc() = default; + }; + struct RenderInputSlot { uint32_t index = 0; uint32_t stride = 0; diff --git a/plume_vulkan.cpp b/plume_vulkan.cpp index a8004f87..cad3ba25 100644 --- a/plume_vulkan.cpp +++ b/plume_vulkan.cpp @@ -2070,22 +2070,20 @@ namespace plume { // VulkanSwapChain - VulkanSwapChain::VulkanSwapChain(VulkanCommandQueue *commandQueue, RenderWindow renderWindow, uint32_t textureCount, RenderFormat format, uint32_t maxFrameLatency) { + VulkanSwapChain::VulkanSwapChain(VulkanCommandQueue *commandQueue, const RenderSwapChainDesc &desc) { assert(commandQueue != nullptr); - assert(textureCount > 0); + assert(desc.textureCount > 0); this->commandQueue = commandQueue; - this->renderWindow = renderWindow; - this->format = format; - this->maxFrameLatency = maxFrameLatency; + this->desc = desc; VkResult res; # ifdef _WIN64 - assert(renderWindow != 0); + assert(desc.renderWindow != 0); VkWin32SurfaceCreateInfoKHR surfaceCreateInfo = {}; surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; - surfaceCreateInfo.hwnd = HWND(renderWindow); + surfaceCreateInfo.hwnd = HWND(desc.renderWindow); surfaceCreateInfo.hinstance = GetModuleHandle(nullptr); VulkanInterface *renderInterface = commandQueue->device->renderInterface; @@ -2096,16 +2094,16 @@ namespace plume { } # elif defined(PLUME_SDL_VULKAN_ENABLED) VulkanInterface *renderInterface = commandQueue->device->renderInterface; - SDL_bool sdlRes = SDL_Vulkan_CreateSurface(renderWindow, renderInterface->instance, &surface); + SDL_bool sdlRes = SDL_Vulkan_CreateSurface(desc.renderWindow, renderInterface->instance, &surface); if (sdlRes == SDL_FALSE) { fprintf(stderr, "SDL_Vulkan_CreateSurface failed with error %s.\n", SDL_GetError()); return; } # elif defined(__ANDROID__) - assert(renderWindow != nullptr); + assert(desc.renderWindow != nullptr); VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo = {}; surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; - surfaceCreateInfo.window = renderWindow; + surfaceCreateInfo.window = desc.renderWindow; VulkanInterface *renderInterface = commandQueue->device->renderInterface; res = vkCreateAndroidSurfaceKHR(renderInterface->instance, &surfaceCreateInfo, nullptr, &surface); @@ -2114,12 +2112,12 @@ namespace plume { return; } # elif defined(__linux__) - assert(renderWindow.display != 0); - assert(renderWindow.window != 0); + assert(desc.renderWindow.display != 0); + assert(desc.renderWindow.window != 0); VkXlibSurfaceCreateInfoKHR surfaceCreateInfo = {}; surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; - surfaceCreateInfo.dpy = renderWindow.display; - surfaceCreateInfo.window = renderWindow.window; + surfaceCreateInfo.dpy = desc.renderWindow.display; + surfaceCreateInfo.window = desc.renderWindow.window; VulkanInterface *renderInterface = commandQueue->device->renderInterface; res = vkCreateXlibSurfaceKHR(renderInterface->instance, &surfaceCreateInfo, nullptr, &surface); @@ -2128,14 +2126,14 @@ namespace plume { return; } # elif defined(__APPLE__) - assert(renderWindow.window != 0); - assert(renderWindow.view != 0); + assert(desc.renderWindow.window != 0); + assert(desc.renderWindow.view != 0); // Creates a wrapper around the window for storing and fetching sizes. - this->windowWrapper = std::make_unique(renderWindow.window); + this->windowWrapper = std::make_unique(desc.renderWindow.window); VkMetalSurfaceCreateInfoEXT surfaceCreateInfo = {}; surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; - surfaceCreateInfo.pLayer = renderWindow.view; + surfaceCreateInfo.pLayer = desc.renderWindow.view; VulkanInterface *renderInterface = commandQueue->device->renderInterface; res = vkCreateMetalSurfaceEXT(renderInterface->instance, &surfaceCreateInfo, nullptr, &surface); @@ -2177,7 +2175,7 @@ namespace plume { surfaceCapabilities.maxImageCount = std::max(surfaceCapabilities.minImageCount, surfaceCapabilities.maxImageCount); // Clamp the requested buffer count between the bounds of the surface capabilities. - this->textureCount = std::clamp(textureCount, surfaceCapabilities.minImageCount, surfaceCapabilities.maxImageCount); + this->desc.textureCount = std::clamp(desc.textureCount, surfaceCapabilities.minImageCount, surfaceCapabilities.maxImageCount); uint32_t surfaceFormatCount = 0; vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &surfaceFormatCount, nullptr); @@ -2191,10 +2189,11 @@ namespace plume { std::vector presentModes(presentModeCount); vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, presentModes.data()); immediatePresentModeSupported = std::find(presentModes.begin(), presentModes.end(), VK_PRESENT_MODE_IMMEDIATE_KHR) != presentModes.end(); + mailboxPresentModeSupported = std::find(presentModes.begin(), presentModes.end(), VK_PRESENT_MODE_MAILBOX_KHR) != presentModes.end(); // Check if the format we requested is part of the supported surface formats. std::vector compatibleSurfaceFormats; - VkFormat requestedFormat = toVk(format); + VkFormat requestedFormat = toVk(desc.format); for (uint32_t i = 0; i < surfaceFormatCount; i++) { if (surfaceFormats[i].format == requestedFormat) { compatibleSurfaceFormats.emplace_back(surfaceFormats[i]); @@ -2219,8 +2218,8 @@ namespace plume { pickedSurfaceFormat = compatibleSurfaceFormats[0]; } - // FIFO is guaranteed to be supported. - requiredPresentMode = VK_PRESENT_MODE_FIFO_KHR; + // Sets the required presentation mode. + setVsyncEnabled(true); // Pick an alpha compositing mode, prefer opaque over inherit. if (surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) { @@ -2268,7 +2267,9 @@ namespace plume { presentInfo.waitSemaphoreCount = uint32_t(waitSemaphoresVector.size()); VkPresentIdKHR presentId = {}; - if (commandQueue->device->capabilities.presentWait) { + if (desc.enablePresentWait) { + assert(commandQueue->device->capabilities.presentWait && "Present wait must be supported by the RenderDevice."); + currentPresentId++; presentId.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR; presentId.pPresentIds = ¤tPresentId; @@ -2297,9 +2298,11 @@ namespace plume { } void VulkanSwapChain::wait() { - if (commandQueue->device->capabilities.presentWait && (currentPresentId >= maxFrameLatency)) { + assert(desc.enablePresentWait && "Present wait should've been explicitly enabled during swap chain creation before using the wait function."); + + if (currentPresentId >= desc.maxFrameLatency) { constexpr uint64_t waitTimeout = 100000000; - vkWaitForPresentKHR(commandQueue->device->vk, vk, currentPresentId - (maxFrameLatency - 1), waitTimeout); + vkWaitForPresentKHR(commandQueue->device->vk, vk, currentPresentId - (desc.maxFrameLatency - 1), waitTimeout); } } @@ -2320,7 +2323,7 @@ namespace plume { createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; - createInfo.minImageCount = textureCount; + createInfo.minImageCount = desc.textureCount; createInfo.imageFormat = pickedSurfaceFormat.format; createInfo.imageColorSpace = pickedSurfaceFormat.colorSpace; createInfo.imageExtent.width = width; @@ -2352,16 +2355,16 @@ namespace plume { uint32_t retrievedImageCount = 0; vkGetSwapchainImagesKHR(commandQueue->device->vk, vk, &retrievedImageCount, nullptr); - if (retrievedImageCount < textureCount) { + if (retrievedImageCount < desc.textureCount) { releaseSwapChain(); fprintf(stderr, "Image count differs from the texture count.\n"); return false; } - textureCount = retrievedImageCount; + desc.textureCount = retrievedImageCount; - std::vector images(textureCount); - res = vkGetSwapchainImagesKHR(commandQueue->device->vk, vk, &textureCount, images.data()); + std::vector images(desc.textureCount); + res = vkGetSwapchainImagesKHR(commandQueue->device->vk, vk, &desc.textureCount, images.data()); if (res != VK_SUCCESS) { releaseSwapChain(); fprintf(stderr, "vkGetSwapchainImagesKHR failed with error code 0x%X.\n", res); @@ -2369,12 +2372,12 @@ namespace plume { } // Assign the swap chain images to the buffer resources. - textures.resize(textureCount); + textures.resize(desc.textureCount); - for (uint32_t i = 0; i < textureCount; i++) { + for (uint32_t i = 0; i < desc.textureCount; i++) { textures[i] = VulkanTexture(commandQueue->device, images[i]); textures[i].desc.dimension = RenderTextureDimension::TEXTURE_2D; - textures[i].desc.format = format; + textures[i].desc.format = desc.format; textures[i].desc.width = width; textures[i].desc.height = height; textures[i].desc.depth = 1; @@ -2397,13 +2400,17 @@ namespace plume { void VulkanSwapChain::setVsyncEnabled(bool vsyncEnabled) { // Immediate mode must be supported and the presentation mode will only be used on the next resize. // needsResize() will return as true as long as the created and required present mode do not match. - if (immediatePresentModeSupported) { - requiredPresentMode = vsyncEnabled ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR; + if (immediatePresentModeSupported && !vsyncEnabled) { + requiredPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; + } + // Prefer mailbox if available and using present wait. FIFO is guaranteed to be supported. + else { + requiredPresentMode = (desc.enablePresentWait && mailboxPresentModeSupported) ? VK_PRESENT_MODE_MAILBOX_KHR : VK_PRESENT_MODE_FIFO_KHR; } } bool VulkanSwapChain::isVsyncEnabled() const { - return createdPresentMode == VK_PRESENT_MODE_FIFO_KHR; + return (createdPresentMode == VK_PRESENT_MODE_FIFO_KHR) || (createdPresentMode == VK_PRESENT_MODE_MAILBOX_KHR); } uint32_t VulkanSwapChain::getWidth() const { @@ -2419,11 +2426,11 @@ namespace plume { } uint32_t VulkanSwapChain::getTextureCount() const { - return textureCount; + return desc.textureCount; } RenderWindow VulkanSwapChain::getWindow() const { - return renderWindow; + return desc.renderWindow; } bool VulkanSwapChain::isEmpty() const { @@ -2444,17 +2451,17 @@ namespace plume { void VulkanSwapChain::getWindowSize(uint32_t &dstWidth, uint32_t &dstHeight) const { # if defined(_WIN64) RECT rect; - GetClientRect(renderWindow, &rect); + GetClientRect(desc.renderWindow, &rect); dstWidth = rect.right - rect.left; dstHeight = rect.bottom - rect.top; # elif defined(PLUME_SDL_VULKAN_ENABLED) - SDL_GetWindowSizeInPixels(renderWindow, (int *)(&dstWidth), (int *)(&dstHeight)); + SDL_GetWindowSizeInPixels(desc.renderWindow, (int *)(&dstWidth), (int *)(&dstHeight)); # elif defined(__ANDROID__) - dstWidth = ANativeWindow_getWidth(renderWindow); - dstHeight = ANativeWindow_getHeight(renderWindow); + dstWidth = ANativeWindow_getWidth(desc.renderWindow); + dstHeight = ANativeWindow_getHeight(desc.renderWindow); # elif defined(__linux__) XWindowAttributes attributes; - XGetWindowAttributes(renderWindow.display, renderWindow.window, &attributes); + XGetWindowAttributes(desc.renderWindow.display, desc.renderWindow.window, &attributes); // The attributes width and height members do not include the border. dstWidth = attributes.width; dstHeight = attributes.height; @@ -3548,8 +3555,8 @@ namespace plume { return std::make_unique(this); } - std::unique_ptr VulkanCommandQueue::createSwapChain(RenderWindow renderWindow, uint32_t bufferCount, RenderFormat format, uint32_t maxFrameLatency) { - return std::make_unique(this, renderWindow, bufferCount, format, maxFrameLatency); + std::unique_ptr VulkanCommandQueue::createSwapChain(const RenderSwapChainDesc &desc) { + return std::make_unique(this, desc); } void VulkanCommandQueue::executeCommandLists(const RenderCommandList **commandLists, uint32_t commandListCount, RenderCommandSemaphore **waitSemaphores, uint32_t waitSemaphoreCount, RenderCommandSemaphore **signalSemaphores, uint32_t signalSemaphoreCount, RenderCommandFence *signalFence) { diff --git a/plume_vulkan.h b/plume_vulkan.h index d3bdbd8d..0dfd23c8 100644 --- a/plume_vulkan.h +++ b/plume_vulkan.h @@ -222,16 +222,14 @@ namespace plume { }; struct VulkanSwapChain : RenderSwapChain { + RenderSwapChainDesc desc; VkSwapchainKHR vk = VK_NULL_HANDLE; VulkanCommandQueue *commandQueue = nullptr; VkSurfaceKHR surface = VK_NULL_HANDLE; - RenderWindow renderWindow = {}; #if defined(__APPLE__) std::unique_ptr windowWrapper; #endif - uint32_t textureCount = 0; uint64_t presentCount = 0; - RenderFormat format = RenderFormat::UNKNOWN; uint32_t width = 0; uint32_t height = 0; VkSwapchainCreateInfoKHR createInfo = {}; @@ -242,9 +240,9 @@ namespace plume { std::vector textures; uint64_t currentPresentId = 0; bool immediatePresentModeSupported = false; - uint32_t maxFrameLatency = 0; + bool mailboxPresentModeSupported = false; - VulkanSwapChain(VulkanCommandQueue *commandQueue, RenderWindow renderWindow, uint32_t textureCount, RenderFormat format, uint32_t maxFrameLatency); + VulkanSwapChain(VulkanCommandQueue *commandQueue, const RenderSwapChainDesc &desc); ~VulkanSwapChain() override; bool present(uint32_t textureIndex, RenderCommandSemaphore **waitSemaphores, uint32_t waitSemaphoreCount) override; void wait() override; @@ -376,7 +374,7 @@ namespace plume { VulkanCommandQueue(VulkanDevice *device, RenderCommandListType type); ~VulkanCommandQueue() override; std::unique_ptr createCommandList() override; - std::unique_ptr createSwapChain(RenderWindow renderWindow, uint32_t bufferCount, RenderFormat format, uint32_t maxFrameLatency) override; + std::unique_ptr createSwapChain(const RenderSwapChainDesc &desc) override; void executeCommandLists(const RenderCommandList **commandLists, uint32_t commandListCount, RenderCommandSemaphore **waitSemaphores, uint32_t waitSemaphoreCount, RenderCommandSemaphore **signalSemaphores, uint32_t signalSemaphoreCount, RenderCommandFence *signalFence) override; void waitForCommandFence(RenderCommandFence *fence) override; }; From 95e1b2973580cde44b122d33a11af41f4e9b1f91 Mon Sep 17 00:00:00 2001 From: Dario Date: Sat, 3 Jan 2026 20:19:58 -0300 Subject: [PATCH 2/4] Add new constructor for SwapChainDesc. --- plume_render_interface_types.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plume_render_interface_types.h b/plume_render_interface_types.h index f62bfb22..ba04a216 100644 --- a/plume_render_interface_types.h +++ b/plume_render_interface_types.h @@ -1149,6 +1149,14 @@ namespace plume { uint32_t maxFrameLatency = 0; RenderSwapChainDesc() = default; + + RenderSwapChainDesc(RenderWindow renderWindow, RenderFormat format, uint32_t textureCount, bool enablePresentWait = false, uint32_t maxFrameLatency = 0) { + this->renderWindow = renderWindow; + this->format = format; + this->textureCount = textureCount; + this->enablePresentWait = enablePresentWait; + this->maxFrameLatency = maxFrameLatency; + } }; struct RenderInputSlot { From db2577770864eb95960c9d0f4739a3ac872330b2 Mon Sep 17 00:00:00 2001 From: Dario Date: Sat, 3 Jan 2026 20:27:50 -0300 Subject: [PATCH 3/4] Fix metal header. --- plume_metal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plume_metal.h b/plume_metal.h index 57483b65..8de188da 100644 --- a/plume_metal.h +++ b/plume_metal.h @@ -548,7 +548,7 @@ namespace plume { MetalCommandQueue(MetalDevice *device, RenderCommandListType type); ~MetalCommandQueue() override; std::unique_ptr createCommandList() override; - std::unique_ptr createSwapChain(RenderWindow renderWindow, uint32_t bufferCount, RenderFormat format, uint32_t maxFrameLatency) override; + std::unique_ptr createSwapChain(const RenderSwapChainDesc &desc) override; void executeCommandLists(const RenderCommandList **commandLists, uint32_t commandListCount, RenderCommandSemaphore **waitSemaphores, uint32_t waitSemaphoreCount, RenderCommandSemaphore **signalSemaphores, uint32_t signalSemaphoreCount, RenderCommandFence *signalFence) override; void waitForCommandFence(RenderCommandFence *fence) override; }; From bbc65960a61f7aa125b212a052776a36dda7d873 Mon Sep 17 00:00:00 2001 From: Dario Date: Sat, 3 Jan 2026 20:33:28 -0300 Subject: [PATCH 4/4] Update examples. --- examples/cube/main.cpp | 2 +- examples/triangle/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/cube/main.cpp b/examples/cube/main.cpp index a2aeae81..36703589 100644 --- a/examples/cube/main.cpp +++ b/examples/cube/main.cpp @@ -401,7 +401,7 @@ namespace plume { ctx.m_device = renderInterface->createDevice(); ctx.m_commandQueue = ctx.m_device->createCommandQueue(RenderCommandListType::DIRECT); ctx.m_fence = ctx.m_device->createCommandFence(); - ctx.m_swapChain = ctx.m_commandQueue->createSwapChain(ctx.m_renderWindow, BufferCount, SwapchainFormat, 2); + ctx.m_swapChain = ctx.m_commandQueue->createSwapChain(RenderSwapChainDesc(ctx.m_renderWindow, SwapchainFormat, BufferCount)); ctx.m_swapChain->resize(); ctx.m_commandList = ctx.m_commandQueue->createCommandList(); ctx.m_acquireSemaphore = ctx.m_device->createCommandSemaphore(); diff --git a/examples/triangle/main.cpp b/examples/triangle/main.cpp index 503a18af..090c4ba8 100644 --- a/examples/triangle/main.cpp +++ b/examples/triangle/main.cpp @@ -173,7 +173,7 @@ namespace plume { ctx.m_fence = ctx.m_device->createCommandFence(); // Create a swap chain for the window using the render window from init - ctx.m_swapChain = ctx.m_commandQueue->createSwapChain(ctx.m_renderWindow, BufferCount, SwapchainFormat, 2); + ctx.m_swapChain = ctx.m_commandQueue->createSwapChain(RenderSwapChainDesc(ctx.m_renderWindow, SwapchainFormat, BufferCount)); // Explicitly resize the swapchain to create the textures ctx.m_swapChain->resize();