From 98e0414f911c8a00db73526a9dfdbabecde73c7b Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Mon, 11 May 2026 14:14:19 +0200 Subject: [PATCH 1/2] rendervulkan: evict stale screenshot images, for reuse Fixes a crash that happened when cycling through more than 2 screen recording resolutions in the Steam client: pScreenshotImages would fill up and as a consequence a call to vulkan_acquire_screenshot_texture with a new resolution would return a null pointer. --- src/rendervulkan.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp index 0073ce7a70..de043634c8 100644 --- a/src/rendervulkan.cpp +++ b/src/rendervulkan.cpp @@ -3597,6 +3597,15 @@ gamescope::Rc vulkan_acquire_screenshot_texture(uint32_t width, { for (auto& pScreenshotImage : g_output.pScreenshotImages) { + // Evict a stale screenshot image, and reuse it + if (pScreenshotImage && pScreenshotImage->GetRefCount() == 0 && + (width != pScreenshotImage->width() || + height != pScreenshotImage->height() || + drmFormat != pScreenshotImage->drmFormat())) + { + pScreenshotImage = nullptr; + } + if (pScreenshotImage == nullptr) { pScreenshotImage = new CVulkanTexture(); From beccb1fdb4a4b81d3468826a19eaf49d038b68ae Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Fri, 15 May 2026 21:33:47 +0200 Subject: [PATCH 2/2] rendervulkan: define two separate texture pools for screenshots and video capture Avoids running out of textures when requesting a screenshot while a screen capture is ongoing. Both pools can hold two textures to account for concurrent requests in different formats or at different resolutions. --- src/rendervulkan.cpp | 79 +++++++++++++++++++++++++++----------------- src/rendervulkan.hpp | 4 ++- src/steamcompmgr.cpp | 2 +- 3 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp index de043634c8..7b1702281e 100644 --- a/src/rendervulkan.cpp +++ b/src/rendervulkan.cpp @@ -3247,9 +3247,11 @@ bool vulkan_remake_swapchain( void ) g_device.vk.DestroySwapchainKHR( g_device.device(), pOutput->swapChain, nullptr ); - // Delete screenshot image to be remade if needed - for (auto& pScreenshotImage : pOutput->pScreenshotImages) - pScreenshotImage = nullptr; + // Delete screenshot/capture textures to be remade if needed + for (auto& pScreenshotTexture : pOutput->pScreenshotTextures) + pScreenshotTexture = nullptr; + for (auto& pCaptureTexture : pOutput->pCaptureTextures) + pCaptureTexture = nullptr; bool bRet = vulkan_make_swapchain( pOutput ); assert( bRet ); // Something has gone horribly wrong! @@ -3343,9 +3345,11 @@ bool vulkan_remake_output_images() pOutput->nOutImage = 0; - // Delete screenshot image to be remade if needed - for (auto& pScreenshotImage : pOutput->pScreenshotImages) - pScreenshotImage = nullptr; + // Delete screenshot/capture textures to be remade if needed + for (auto& pScreenshotTexture : pOutput->pScreenshotTextures) + pScreenshotTexture = nullptr; + for (auto& pCaptureTexture : pOutput->pCaptureTextures) + pCaptureTexture = nullptr; bool bRet = vulkan_make_output_images( pOutput ); assert( bRet ); @@ -3593,51 +3597,66 @@ void vulkan_garbage_collect( void ) g_device.garbageCollect(); } -gamescope::Rc vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace) +static gamescope::Rc acquire_pooled_texture( auto& pool, uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace ) { - for (auto& pScreenshotImage : g_output.pScreenshotImages) + for (auto& pTexture : pool) { - // Evict a stale screenshot image, and reuse it - if (pScreenshotImage && pScreenshotImage->GetRefCount() == 0 && - (width != pScreenshotImage->width() || - height != pScreenshotImage->height() || - drmFormat != pScreenshotImage->drmFormat())) + // Evict a stale texture and reuse the slot + if (pTexture && pTexture->GetRefCount() == 0 && + (width != pTexture->width() || + height != pTexture->height() || + drmFormat != pTexture->drmFormat())) { - pScreenshotImage = nullptr; + pTexture = nullptr; } - if (pScreenshotImage == nullptr) + if (pTexture == nullptr) { - pScreenshotImage = new CVulkanTexture(); + pTexture = new CVulkanTexture(); - CVulkanTexture::createFlags screenshotImageFlags; - screenshotImageFlags.bMappable = true; - screenshotImageFlags.bTransferDst = true; - screenshotImageFlags.bStorage = true; + CVulkanTexture::createFlags textureFlags; + textureFlags.bMappable = true; + textureFlags.bTransferDst = true; + textureFlags.bStorage = true; if (exportable || drmFormat == DRM_FORMAT_NV12) { - screenshotImageFlags.bExportable = true; - screenshotImageFlags.bLinear = true; // TODO: support multi-planar DMA-BUF export via PipeWire + textureFlags.bExportable = true; + textureFlags.bLinear = true; // TODO: support multi-planar DMA-BUF export via PipeWire } - bool bSuccess = pScreenshotImage->BInit( width, height, 1u, drmFormat, screenshotImageFlags ); - pScreenshotImage->setStreamColorspace(colorspace); + bool bSuccess = pTexture->BInit( width, height, 1u, drmFormat, textureFlags ); + pTexture->setStreamColorspace(colorspace); assert( bSuccess ); } - if (pScreenshotImage->GetRefCount() != 0 || - width != pScreenshotImage->width() || - height != pScreenshotImage->height() || - drmFormat != pScreenshotImage->drmFormat()) + if (pTexture->GetRefCount() != 0 || + width != pTexture->width() || + height != pTexture->height() || + drmFormat != pTexture->drmFormat()) continue; - return pScreenshotImage.get(); + return pTexture.get(); } - vk_log.errorf("Unable to acquire screenshot texture. Out of textures."); return nullptr; } +gamescope::Rc vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace) +{ + auto texture = acquire_pooled_texture(g_output.pScreenshotTextures, width, height, exportable, drmFormat, colorspace); + if (!texture) + vk_log.errorf("Unable to acquire screenshot texture. Out of textures."); + return texture; +} + +gamescope::Rc vulkan_acquire_capture_texture(uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace) +{ + auto texture = acquire_pooled_texture(g_output.pCaptureTextures, width, height, exportable, drmFormat, colorspace); + if (!texture) + vk_log.errorf("Unable to acquire capture texture. Out of textures."); + return texture; +} + // Internal display's native brightness. float g_flInternalDisplayBrightnessNits = 500.0f; diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp index 4b65f9317f..b0ee2080bb 100644 --- a/src/rendervulkan.hpp +++ b/src/rendervulkan.hpp @@ -412,6 +412,7 @@ std::optional vulkan_composite( struct FrameInfo_t *frameInfo, gamesco void vulkan_wait( uint64_t ulSeqNo, bool bReset ); gamescope::Rc vulkan_get_last_output_image( bool partial, bool defer ); gamescope::Rc vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace = k_EStreamColorspace_Unknown); +gamescope::Rc vulkan_acquire_capture_texture(uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace = k_EStreamColorspace_Unknown); void vulkan_present_to_window( void ); @@ -537,7 +538,8 @@ struct VulkanOutput_t uint32_t uOutputFormat = DRM_FORMAT_INVALID; uint32_t uOutputFormatOverlay = DRM_FORMAT_INVALID; - std::array, 2> pScreenshotImages; + std::array, 2> pScreenshotTextures; + std::array, 2> pCaptureTextures; // NIS and FSR gamescope::OwningRc tmpOutput; diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 83c2a811ff..b64d4597dd 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -2405,7 +2405,7 @@ static void paint_pipewire() } gamescope::Rc pRGBTexture = s_pPipewireBuffer->texture->isYcbcr() - ? vulkan_acquire_screenshot_texture( uWidth, uHeight, false, DRM_FORMAT_XRGB2101010 ) + ? vulkan_acquire_capture_texture( uWidth, uHeight, false, DRM_FORMAT_XRGB2101010 ) : gamescope::Rc{ s_pPipewireBuffer->texture }; gamescope::Rc pYUVTexture = s_pPipewireBuffer->texture->isYcbcr() ? s_pPipewireBuffer->texture : nullptr;