diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp index a31408d..90f2dc1 100644 --- a/src/rendervulkan.cpp +++ b/src/rendervulkan.cpp @@ -3475,7 +3475,29 @@ void bind_all_layers(CVulkanCmdBuffer* cmdBuffer, const struct FrameInfo_t *fram } } -bool vulkan_composite( const struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture ) +bool vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture ) +{ + auto cmdBuffer = g_device.commandBuffer(); + + for (uint32_t i = 0; i < EOTF_Count; i++) + cmdBuffer->bindColorMgmtLuts(i, frameInfo->shaperLut[i], frameInfo->lut3D[i]); + + cmdBuffer->bindPipeline( g_device.pipeline(SHADER_TYPE_BLIT, frameInfo->layerCount, frameInfo->ycbcrMask(), 0u, frameInfo->colorspaceMask(), EOTF_Gamma22 )); + bind_all_layers(cmdBuffer.get(), frameInfo); + cmdBuffer->bindTarget(pScreenshotTexture); + cmdBuffer->pushConstants(frameInfo); + + const int pixelsPerGroup = 8; + + cmdBuffer->dispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup)); + + uint64_t sequence = g_device.submit(std::move(cmdBuffer)); + g_device.wait(sequence); + + return true; +} + +bool vulkan_composite( const struct FrameInfo_t *frameInfo, std::shared_ptr pPipewireTexture ) { auto compositeImage = g_output.outputImages[ g_output.nOutImage ]; @@ -3613,21 +3635,21 @@ bool vulkan_composite( const struct FrameInfo_t *frameInfo, std::shared_ptrdispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup)); } - if ( pScreenshotTexture != nullptr ) + if ( pPipewireTexture != nullptr ) { - if (compositeImage->format() == pScreenshotTexture->format() && - compositeImage->width() == pScreenshotTexture->width() && - compositeImage->height() == pScreenshotTexture->height()) { - cmdBuffer->copyImage(compositeImage, pScreenshotTexture); + if (compositeImage->format() == pPipewireTexture->format() && + compositeImage->width() == pPipewireTexture->width() && + compositeImage->height() == pPipewireTexture->height()) { + cmdBuffer->copyImage(compositeImage, pPipewireTexture); } else { - const bool ycbcr = pScreenshotTexture->isYcbcr(); + const bool ycbcr = pPipewireTexture->isYcbcr(); - float scale = (float)compositeImage->width() / pScreenshotTexture->width(); + float scale = (float)compositeImage->width() / pPipewireTexture->width(); if ( ycbcr ) { CaptureConvertBlitData_t constants( scale, colorspace_to_conversion_from_srgb_matrix( compositeImage->streamColorspace() ) ); - constants.halfExtent[0] = pScreenshotTexture->width() / 2.0f; - constants.halfExtent[1] = pScreenshotTexture->height() / 2.0f; + constants.halfExtent[0] = pPipewireTexture->width() / 2.0f; + constants.halfExtent[1] = pPipewireTexture->height() / 2.0f; cmdBuffer->pushConstants(constants); } else @@ -3648,14 +3670,14 @@ bool vulkan_composite( const struct FrameInfo_t *frameInfo, std::shared_ptrbindTexture(i, nullptr); } - cmdBuffer->bindTarget(pScreenshotTexture); + cmdBuffer->bindTarget(pPipewireTexture); const int pixelsPerGroup = 8; // For ycbcr, we operate on 2 pixels at a time, so use the half-extent. const int dispatchSize = ycbcr ? pixelsPerGroup * 2 : pixelsPerGroup; - cmdBuffer->dispatch(div_roundup(pScreenshotTexture->width(), dispatchSize), div_roundup(pScreenshotTexture->height(), dispatchSize)); + cmdBuffer->dispatch(div_roundup(pPipewireTexture->width(), dispatchSize), div_roundup(pPipewireTexture->height(), dispatchSize)); } } diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp index e66ae22..8b39637 100644 --- a/src/rendervulkan.hpp +++ b/src/rendervulkan.hpp @@ -348,6 +348,8 @@ std::shared_ptr vulkan_create_1d_lut(uint32_t size); std::shared_ptr vulkan_create_3d_lut(uint32_t width, uint32_t height, uint32_t depth); void vulkan_update_luts(const std::shared_ptr& lut1d, const std::shared_ptr& lut3d, void* lut1d_data, void* lut3d_data); +bool vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture ); + struct wlr_renderer *vulkan_renderer_create( void ); using mat3x4 = std::array, 3>; diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 8eaf358..433a72f 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -110,8 +110,12 @@ gamescope_color_mgmt_tracker_t g_ColorMgmt{}; static gamescope_color_mgmt_luts g_ColorMgmtLutsOverride[ EOTF_Count ]; static lut3d_t g_ColorMgmtLooks[ EOTF_Count ]; + + gamescope_color_mgmt_luts g_ColorMgmtLuts[ EOTF_Count ]; +gamescope_color_mgmt_luts g_ScreenshotColorMgmtLuts[ EOTF_Count ]; + static lut1d_t g_tmpLut1d; static lut3d_t g_tmpLut3d; @@ -127,9 +131,145 @@ uint64_t timespec_to_nanos(struct timespec& spec) return spec.tv_sec * 1'000'000'000ul + spec.tv_nsec; } +static const gamescope_color_mgmt_t k_ScreenshotColorMgmt = +{ + .enabled = true, + .displayColorimetry = displaycolorimetry_709, + .displayEOTF = EOTF_Gamma22, + .outputEncodingColorimetry = displaycolorimetry_709, + .outputEncodingEOTF = EOTF_Gamma22, +}; + //#define COLOR_MGMT_MICROBENCH // sudo cpupower frequency-set --governor performance +static void +create_color_mgmt_luts(const gamescope_color_mgmt_t& newColorMgmt, gamescope_color_mgmt_luts outColorMgmtLuts[ EOTF_Count ]) +{ + const displaycolorimetry_t& displayColorimetry = newColorMgmt.displayColorimetry; + const displaycolorimetry_t& outputEncodingColorimetry = newColorMgmt.outputEncodingColorimetry; + + for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ ) + { + if (!outColorMgmtLuts[nInputEOTF].vk_lut1d) + outColorMgmtLuts[nInputEOTF].vk_lut1d = vulkan_create_1d_lut(s_nLutSize1d); + + if (!outColorMgmtLuts[nInputEOTF].vk_lut3d) + outColorMgmtLuts[nInputEOTF].vk_lut3d = vulkan_create_3d_lut(s_nLutEdgeSize3d, s_nLutEdgeSize3d, s_nLutEdgeSize3d); + + if ( g_ColorMgmtLutsOverride[nInputEOTF].HasLuts() ) + { + memcpy(g_ColorMgmtLuts[nInputEOTF].lut1d, g_ColorMgmtLutsOverride[nInputEOTF].lut1d, sizeof(g_ColorMgmtLutsOverride[nInputEOTF].lut1d)); + memcpy(g_ColorMgmtLuts[nInputEOTF].lut3d, g_ColorMgmtLutsOverride[nInputEOTF].lut3d, sizeof(g_ColorMgmtLutsOverride[nInputEOTF].lut3d)); + } + else + { + displaycolorimetry_t inputColorimetry{}; + colormapping_t colorMapping{}; + + tonemapping_t tonemapping{}; + tonemapping.bUseShaper = true; + + EOTF inputEOTF = static_cast( nInputEOTF ); + float flGain = 1.f; + lut3d_t * pLook = g_ColorMgmtLooks[nInputEOTF].lutEdgeSize > 0 ? &g_ColorMgmtLooks[nInputEOTF] : nullptr; + + if ( inputEOTF == EOTF_Gamma22 ) + { + flGain = newColorMgmt.flSDRInputGain; + if ( newColorMgmt.outputEncodingEOTF == EOTF_Gamma22 ) + { + // G22 -> G22. Does not matter what the g22 mult is + tonemapping.g22_luminance = 1.f; + // xwm_log.infof("G22 -> G22"); + } + else if ( newColorMgmt.outputEncodingEOTF == EOTF_PQ ) + { + // G22 -> PQ. SDR content going on an HDR output + tonemapping.g22_luminance = newColorMgmt.flSDROnHDRBrightness; + // xwm_log.infof("G22 -> PQ"); + } + + // The final display colorimetry is used to build the output mapping, as we want a gamut-aware handling + // for sdrGamutWideness indepdendent of the output encoding (for SDR data), and when mapping SDR -> PQ output + // we only want to utilize a portion of the gamut the actual display can reproduce + buildSDRColorimetry( &inputColorimetry, &colorMapping, newColorMgmt.sdrGamutWideness, displayColorimetry ); + } + else if ( inputEOTF == EOTF_PQ ) + { + flGain = newColorMgmt.flHDRInputGain; + if ( newColorMgmt.outputEncodingEOTF == EOTF_Gamma22 ) + { + // PQ -> G22 Leverage the display's native brightness + tonemapping.g22_luminance = newColorMgmt.flInternalDisplayBrightness; + + // Determine the tonemapping parameters + // Use the external atoms if provided + tonemap_info_t source = newColorMgmt.hdrTonemapSourceMetadata; + tonemap_info_t dest = newColorMgmt.hdrTonemapDisplayMetadata; + // Otherwise, rely on the Vulkan source info and the EDID + // TODO: If source is invalid, use the provided metadata. + // TODO: If hdrTonemapDisplayMetadata is invalid, use the one provided by the display + + // Adjust the source brightness range by the requested HDR input gain + dest.flBlackPointNits /= flGain; + dest.flWhitePointNits /= flGain; + + if ( source.BIsValid() && dest.BIsValid() ) + { + tonemapping.eOperator = newColorMgmt.hdrTonemapOperator; + tonemapping.eetf2390.init( source, newColorMgmt.hdrTonemapDisplayMetadata ); + } + else + { + tonemapping.eOperator = ETonemapOperator_None; + } + /* + xwm_log.infof("PQ -> 2.2 - g22_luminance %f gain %f", tonemapping.g22_luminance, flGain ); + xwm_log.infof("source %f %f", source.flBlackPointNits, source.flWhitePointNits ); + xwm_log.infof("dest %f %f", dest.flBlackPointNits, dest.flWhitePointNits ); + xwm_log.infof("operator %d", (int) tonemapping.eOperator );*/ + } + else if ( newColorMgmt.outputEncodingEOTF == EOTF_PQ ) + { + // PQ -> PQ passthrough (though this does apply gain) + // TODO: should we manipulate the output static metadata to reflect the gain factor? + tonemapping.g22_luminance = 1.f; + // xwm_log.infof("PQ -> PQ"); + } + + buildPQColorimetry( &inputColorimetry, &colorMapping, displayColorimetry ); + } + + calcColorTransform( &g_tmpLut1d, s_nLutSize1d, &g_tmpLut3d, s_nLutEdgeSize3d, inputColorimetry, inputEOTF, + outputEncodingColorimetry, newColorMgmt.outputEncodingEOTF, + colorMapping, newColorMgmt.nightmode, tonemapping, pLook, flGain ); + + // Create quantized output luts + for ( size_t i=0, end = g_tmpLut1d.dataR.size(); i( nInputEOTF ); - float flGain = 1.f; - lut3d_t * pLook = g_ColorMgmtLooks[nInputEOTF].lutEdgeSize > 0 ? &g_ColorMgmtLooks[nInputEOTF] : nullptr; - - if ( inputEOTF == EOTF_Gamma22 ) - { - flGain = g_ColorMgmt.pending.flSDRInputGain; - if ( g_ColorMgmt.pending.outputEncodingEOTF == EOTF_Gamma22 ) - { - // G22 -> G22. Does not matter what the g22 mult is - tonemapping.g22_luminance = 1.f; - // xwm_log.infof("G22 -> G22"); - } - else if ( g_ColorMgmt.pending.outputEncodingEOTF == EOTF_PQ ) - { - // G22 -> PQ. SDR content going on an HDR output - tonemapping.g22_luminance = g_ColorMgmt.pending.flSDROnHDRBrightness; - // xwm_log.infof("G22 -> PQ"); - } - - // The final display colorimetry is used to build the output mapping, as we want a gamut-aware handling - // for sdrGamutWideness indepdendent of the output encoding (for SDR data), and when mapping SDR -> PQ output - // we only want to utilize a portion of the gamut the actual display can reproduce - buildSDRColorimetry( &inputColorimetry, &colorMapping, g_ColorMgmt.pending.sdrGamutWideness, displayColorimetry ); - } - else if ( inputEOTF == EOTF_PQ ) - { - flGain = g_ColorMgmt.pending.flHDRInputGain; - if ( g_ColorMgmt.pending.outputEncodingEOTF == EOTF_Gamma22 ) - { - // PQ -> G22 Leverage the display's native brightness - tonemapping.g22_luminance = g_ColorMgmt.pending.flInternalDisplayBrightness; - - // Determine the tonemapping parameters - // Use the external atoms if provided - tonemap_info_t source = g_ColorMgmt.pending.hdrTonemapSourceMetadata; - tonemap_info_t dest = g_ColorMgmt.pending.hdrTonemapDisplayMetadata; - // Otherwise, rely on the Vulkan source info and the EDID - // TODO: If source is invalid, use the provided metadata. - // TODO: If hdrTonemapDisplayMetadata is invalid, use the one provided by the display - - // Adjust the source brightness range by the requested HDR input gain - dest.flBlackPointNits /= flGain; - dest.flWhitePointNits /= flGain; - - if ( source.BIsValid() && dest.BIsValid() ) - { - tonemapping.eOperator = g_ColorMgmt.pending.hdrTonemapOperator; - tonemapping.eetf2390.init( source, g_ColorMgmt.pending.hdrTonemapDisplayMetadata ); - } - else - { - tonemapping.eOperator = ETonemapOperator_None; - } - /* - xwm_log.infof("PQ -> 2.2 - g22_luminance %f gain %f", tonemapping.g22_luminance, flGain ); - xwm_log.infof("source %f %f", source.flBlackPointNits, source.flWhitePointNits ); - xwm_log.infof("dest %f %f", dest.flBlackPointNits, dest.flWhitePointNits ); - xwm_log.infof("operator %d", (int) tonemapping.eOperator );*/ - } - else if ( g_ColorMgmt.pending.outputEncodingEOTF == EOTF_PQ ) - { - // PQ -> PQ passthrough (though this does apply gain) - // TODO: should we manipulate the output static metadata to reflect the gain factor? - tonemapping.g22_luminance = 1.f; - // xwm_log.infof("PQ -> PQ"); - } - - buildPQColorimetry( &inputColorimetry, &colorMapping, displayColorimetry ); - } - - calcColorTransform( &g_tmpLut1d, s_nLutSize1d, &g_tmpLut3d, s_nLutEdgeSize3d, inputColorimetry, inputEOTF, - outputEncodingColorimetry, g_ColorMgmt.pending.outputEncodingEOTF, - colorMapping, g_ColorMgmt.pending.nightmode, tonemapping, pLook, flGain ); - - // Create quantized output luts - for ( size_t i=0, end = g_tmpLut1d.dataR.size(); i pCaptureTexture = nullptr; + std::shared_ptr pPipewireTexture = nullptr; #if HAVE_PIPEWIRE if ( pw_buffer != nullptr ) { - pCaptureTexture = pw_buffer->texture; + pPipewireTexture = pw_buffer->texture; } #endif - constexpr bool bHackForceNV12DumpScreenshot = false; - - uint32_t drmCaptureFormat = bHackForceNV12DumpScreenshot - ? DRM_FORMAT_NV12 - : DRM_FORMAT_XRGB8888; - - if ( bCapture && pCaptureTexture == nullptr ) - { - pCaptureTexture = vulkan_acquire_screenshot_texture(g_nOutputWidth, g_nOutputHeight, false, drmCaptureFormat); - } - - bool bResult = vulkan_composite( &frameInfo, pCaptureTexture ); + bool bResult = vulkan_composite( &frameInfo, pPipewireTexture ); if ( bResult != true ) { @@ -2465,10 +2477,11 @@ paint_all(bool async) } else { - frameInfo = {}; - frameInfo.applyOutputColorMgmt = false; - frameInfo.layerCount = 1; - FrameInfo_t::Layer_t *layer = &frameInfo.layers[ 0 ]; + struct FrameInfo_t compositeFrameInfo = {}; + compositeFrameInfo = {}; + compositeFrameInfo.applyOutputColorMgmt = false; + compositeFrameInfo.layerCount = 1; + FrameInfo_t::Layer_t *layer = &compositeFrameInfo.layers[ 0 ]; layer->scale.x = 1.0; layer->scale.y = 1.0; layer->opacity = 1.0; @@ -2480,7 +2493,7 @@ paint_all(bool async) layer->linearFilter = false; layer->colorspace = g_bOutputHDREnabled ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; - int ret = drm_prepare( &g_DRM, async, &frameInfo ); + int ret = drm_prepare( &g_DRM, async, &compositeFrameInfo ); // Happens when we're VT-switched away if ( ret == -EACCES ) @@ -2499,7 +2512,7 @@ paint_all(bool async) drm_rollback( &g_DRM ); // Try once again to in case we need to fall back to another mode. - ret = drm_prepare( &g_DRM, async, &frameInfo ); + ret = drm_prepare( &g_DRM, async, &compositeFrameInfo ); // Happens when we're VT-switched away if ( ret == -EACCES ) @@ -2521,96 +2534,7 @@ paint_all(bool async) } } - drm_commit( &g_DRM, &frameInfo ); - } - - if ( takeScreenshot ) - { - assert( pCaptureTexture != nullptr ); - - std::thread screenshotThread = std::thread([=] { - pthread_setname_np( pthread_self(), "gamescope-scrsh" ); - - const uint8_t *mappedData = pCaptureTexture->mappedData(); - - if (pCaptureTexture->format() == VK_FORMAT_B8G8R8A8_UNORM) - { - // Make our own copy of the image to remove the alpha channel. - auto imageData = std::vector(currentOutputWidth * currentOutputHeight * 4); - const uint32_t comp = 4; - const uint32_t pitch = currentOutputWidth * comp; - for (uint32_t y = 0; y < currentOutputHeight; y++) - { - for (uint32_t x = 0; x < currentOutputWidth; x++) - { - // BGR... - imageData[y * pitch + x * comp + 0] = mappedData[y * pCaptureTexture->rowPitch() + x * comp + 2]; - imageData[y * pitch + x * comp + 1] = mappedData[y * pCaptureTexture->rowPitch() + x * comp + 1]; - imageData[y * pitch + x * comp + 2] = mappedData[y * pCaptureTexture->rowPitch() + x * comp + 0]; - imageData[y * pitch + x * comp + 3] = 255; - } - } - - char pTimeBuffer[1024] = "/tmp/gamescope.png"; - - if ( !propertyRequestedScreenshot ) - { - time_t currentTime = time(0); - struct tm *localTime = localtime( ¤tTime ); - strftime( pTimeBuffer, sizeof( pTimeBuffer ), "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.png", localTime ); - } - - if ( stbi_write_png(pTimeBuffer, currentOutputWidth, currentOutputHeight, 4, imageData.data(), pitch) ) - { - xwm_log.infof("Screenshot saved to %s", pTimeBuffer); - } - else - { - xwm_log.errorf( "Failed to save screenshot to %s", pTimeBuffer ); - } - } - else if (pCaptureTexture->format() == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM) - { - char pTimeBuffer[1024] = "/tmp/gamescope.raw"; - - if ( !propertyRequestedScreenshot ) - { - time_t currentTime = time(0); - struct tm *localTime = localtime( ¤tTime ); - strftime( pTimeBuffer, sizeof( pTimeBuffer ), "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.raw", localTime ); - } - - FILE *file = fopen(pTimeBuffer, "wb"); - if (file) - { - fwrite(mappedData, 1, pCaptureTexture->totalSize(), file ); - fclose(file); - - char cmd[4096]; - sprintf(cmd, "ffmpeg -f rawvideo -pixel_format nv12 -video_size %dx%d -i %s %s_encoded.png", pCaptureTexture->width(), pCaptureTexture->height(), pTimeBuffer, pTimeBuffer); - - int ret = system(cmd); - - /* Above call may fail, ffmpeg returns 0 on success */ - if (ret) { - xwm_log.infof("Ffmpeg call return status %i", ret); - xwm_log.errorf( "Failed to save screenshot to %s", pTimeBuffer ); - } else { - xwm_log.infof("Screenshot saved to %s", pTimeBuffer); - } - } - else - { - xwm_log.errorf( "Failed to save screenshot to %s", pTimeBuffer ); - } - } - - XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeScreenShotAtom ); - }); - - screenshotThread.detach(); - - takeScreenshot = false; + drm_commit( &g_DRM, &compositeFrameInfo ); } #if HAVE_PIPEWIRE @@ -2629,6 +2553,126 @@ paint_all(bool async) drm_commit( &g_DRM, &frameInfo ); } + if ( takeScreenshot ) + { + constexpr bool bHackForceNV12DumpScreenshot = false; + + uint32_t drmCaptureFormat = bHackForceNV12DumpScreenshot + ? DRM_FORMAT_NV12 + : DRM_FORMAT_XRGB8888; + + std::shared_ptr pScreenshotTexture = vulkan_acquire_screenshot_texture(g_nOutputWidth, g_nOutputHeight, false, drmCaptureFormat); + + assert( pScreenshotTexture != nullptr ); + + // Basically no color mgmt applied for screenshots. (aside from being able to handle HDR content with LUTs) + for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ ) + { + frameInfo.lut3D[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut3d; + frameInfo.shaperLut[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut1d; + } + // Remove everything but base planes from the screenshot. + for (int i = 0; i < frameInfo.layerCount; i++) + { + if (frameInfo.layers[i].zpos >= (int)g_zposExternalOverlay) + { + frameInfo.layerCount = i; + break; + } + } + + bool bResult = vulkan_screenshot( &frameInfo, pScreenshotTexture ); + if ( bResult != true ) + { + xwm_log.errorf("vulkan_screenshot failed"); + return; + } + + std::thread screenshotThread = std::thread([=] { + pthread_setname_np( pthread_self(), "gamescope-scrsh" ); + + const uint8_t *mappedData = pScreenshotTexture->mappedData(); + + if (pScreenshotTexture->format() == VK_FORMAT_B8G8R8A8_UNORM) + { + // Make our own copy of the image to remove the alpha channel. + auto imageData = std::vector(currentOutputWidth * currentOutputHeight * 4); + const uint32_t comp = 4; + const uint32_t pitch = currentOutputWidth * comp; + for (uint32_t y = 0; y < currentOutputHeight; y++) + { + for (uint32_t x = 0; x < currentOutputWidth; x++) + { + // BGR... + imageData[y * pitch + x * comp + 0] = mappedData[y * pScreenshotTexture->rowPitch() + x * comp + 2]; + imageData[y * pitch + x * comp + 1] = mappedData[y * pScreenshotTexture->rowPitch() + x * comp + 1]; + imageData[y * pitch + x * comp + 2] = mappedData[y * pScreenshotTexture->rowPitch() + x * comp + 0]; + imageData[y * pitch + x * comp + 3] = 255; + } + } + + char pTimeBuffer[1024] = "/tmp/gamescope.png"; + + if ( !propertyRequestedScreenshot ) + { + time_t currentTime = time(0); + struct tm *localTime = localtime( ¤tTime ); + strftime( pTimeBuffer, sizeof( pTimeBuffer ), "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.png", localTime ); + } + + if ( stbi_write_png(pTimeBuffer, currentOutputWidth, currentOutputHeight, 4, imageData.data(), pitch) ) + { + xwm_log.infof("Screenshot saved to %s", pTimeBuffer); + } + else + { + xwm_log.errorf( "Failed to save screenshot to %s", pTimeBuffer ); + } + } + else if (pScreenshotTexture->format() == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM) + { + char pTimeBuffer[1024] = "/tmp/gamescope.raw"; + + if ( !propertyRequestedScreenshot ) + { + time_t currentTime = time(0); + struct tm *localTime = localtime( ¤tTime ); + strftime( pTimeBuffer, sizeof( pTimeBuffer ), "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.raw", localTime ); + } + + FILE *file = fopen(pTimeBuffer, "wb"); + if (file) + { + fwrite(mappedData, 1, pScreenshotTexture->totalSize(), file ); + fclose(file); + + char cmd[4096]; + sprintf(cmd, "ffmpeg -f rawvideo -pixel_format nv12 -video_size %dx%d -i %s %s_encoded.png", pScreenshotTexture->width(), pScreenshotTexture->height(), pTimeBuffer, pTimeBuffer); + + int ret = system(cmd); + + /* Above call may fail, ffmpeg returns 0 on success */ + if (ret) { + xwm_log.infof("Ffmpeg call return status %i", ret); + xwm_log.errorf( "Failed to save screenshot to %s", pTimeBuffer ); + } else { + xwm_log.infof("Screenshot saved to %s", pTimeBuffer); + } + } + else + { + xwm_log.errorf( "Failed to save screenshot to %s", pTimeBuffer ); + } + } + + XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeScreenShotAtom ); + }); + + screenshotThread.detach(); + + takeScreenshot = false; + } + gpuvis_trace_end_ctx_printf( paintID, "paint_all" ); gpuvis_trace_printf( "paint_all %i layers, composite %i", (int)frameInfo.layerCount, bDoComposite ); } @@ -6825,6 +6869,8 @@ steamcompmgr_main(int argc, char **argv) update_edid_prop(); } + update_screenshot_color_mgmt(); + for (;;) { bool vblank = false;