rendervulkan: Add NV12 capture blit infra

Performs blit + rgb -> nv12 in one.
This commit is contained in:
Joshua Ashton 2022-11-19 00:39:05 +00:00
parent dba8480d18
commit 30519fd9ec
8 changed files with 233 additions and 54 deletions

View file

@ -97,7 +97,8 @@ shader_src = [
'src/shaders/cs_easu_fp16.comp',
'src/shaders/cs_gaussian_blur_horizontal.comp',
'src/shaders/cs_nis.comp',
'src/shaders/cs_nis_fp16.comp'
'src/shaders/cs_nis_fp16.comp',
'src/shaders/cs_rgb_to_nv12.comp',
]
spirv_shaders = glsl_generator.process(shader_src)

View file

@ -355,7 +355,7 @@ static void stream_handle_add_buffer(void *user_data, struct pw_buffer *pw_buffe
bool is_dmabuf = (spa_data->type & (1 << SPA_DATA_DmaBuf)) != 0;
bool is_memfd = (spa_data->type & (1 << SPA_DATA_MemFd)) != 0;
buffer->texture = vulkan_acquire_screenshot_texture(s_nCaptureWidth, s_nCaptureHeight, is_dmabuf);
buffer->texture = vulkan_acquire_screenshot_texture(s_nCaptureWidth, s_nCaptureHeight, is_dmabuf, false);
assert(buffer->texture != nullptr);
if (is_dmabuf) {

View file

@ -31,7 +31,7 @@
#include "cs_gaussian_blur_horizontal.h"
#include "cs_nis.h"
#include "cs_nis_fp16.h"
#include "cs_rgb_to_nv12.h"
#define A_CPU
#include "shaders/ffx_a.h"
@ -88,6 +88,7 @@ enum ShaderType {
SHADER_TYPE_EASU,
SHADER_TYPE_RCAS,
SHADER_TYPE_NIS,
SHADER_TYPE_RGB_TO_NV12,
SHADER_TYPE_COUNT
};
@ -335,6 +336,7 @@ public:
void begin();
void end();
void bindTexture(uint32_t slot, std::shared_ptr<CVulkanTexture> texture);
void setTextureStorage(bool storage);
void setTextureSrgb(uint32_t slot, bool srgb);
void setSamplerNearest(uint32_t slot, bool nearest);
void setSamplerUnnormalized(uint32_t slot, bool unnormalized);
@ -942,7 +944,7 @@ bool CVulkanDevice::createLayouts()
for (auto& sampler : ycbcrSamplers)
sampler = m_ycbcrSampler;
std::array<VkDescriptorSetLayoutBinding, 3> layoutBindings = {
std::array<VkDescriptorSetLayoutBinding, 4> layoutBindings = {
VkDescriptorSetLayoutBinding {
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
@ -951,12 +953,18 @@ bool CVulkanDevice::createLayouts()
},
VkDescriptorSetLayoutBinding {
.binding = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
},
VkDescriptorSetLayoutBinding {
.binding = 2,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = VKR_SAMPLER_SLOTS,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
},
VkDescriptorSetLayoutBinding {
.binding = 2,
.binding = 3,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = VKR_SAMPLER_SLOTS,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
@ -1020,7 +1028,7 @@ bool CVulkanDevice::createPools()
VkDescriptorPoolSize poolSizes[2] {
{
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
uint32_t(m_descriptorSets.size()) * 1,
uint32_t(m_descriptorSets.size()) * 2,
},
{
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
@ -1070,6 +1078,7 @@ bool CVulkanDevice::createShaders()
SHADER(EASU, cs_easu);
SHADER(NIS, cs_nis);
}
SHADER(RGB_TO_NV12, cs_rgb_to_nv12);
#undef SHADER
for (uint32_t i = 0; i < shaderInfos.size(); i++)
@ -1276,6 +1285,7 @@ void CVulkanDevice::compileAllPipelines()
SHADER(RCAS, k_nMaxLayers, k_nMaxYcbcrMask_ToPreCompile, 1);
SHADER(EASU, 1, 1, 1);
SHADER(NIS, 1, 1, 1);
SHADER(RGB_TO_NV12, 1, 1, 1);
#undef SHADER
for (auto& info : pipelineInfos) {
@ -1560,13 +1570,10 @@ void CVulkanCmdBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z)
VkDescriptorSet descriptorSet = m_device->descriptorSet();
std::array<VkWriteDescriptorSet, 3> writeDescriptorSets;
std::array<VkWriteDescriptorSet, 4> writeDescriptorSets;
std::array<VkDescriptorImageInfo, VKR_SAMPLER_SLOTS> imageDescriptors = {};
std::array<VkDescriptorImageInfo, VKR_SAMPLER_SLOTS> ycbcrImageDescriptors = {};
VkDescriptorImageInfo targetDescriptor = {
.imageView = m_target->srgbView(),
.imageLayout = VK_IMAGE_LAYOUT_GENERAL,
};
std::array<VkDescriptorImageInfo, VKR_TARGET_SLOTS> targetDescriptors = {};
writeDescriptorSets[0] = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
@ -1575,7 +1582,7 @@ void CVulkanCmdBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z)
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.pImageInfo = &targetDescriptor,
.pImageInfo = &targetDescriptors[0],
};
writeDescriptorSets[1] = {
@ -1583,9 +1590,9 @@ void CVulkanCmdBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z)
.dstSet = descriptorSet,
.dstBinding = 1,
.dstArrayElement = 0,
.descriptorCount = imageDescriptors.size(),
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = imageDescriptors.data(),
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.pImageInfo = &targetDescriptors[1],
};
writeDescriptorSets[2] = {
@ -1593,6 +1600,16 @@ void CVulkanCmdBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z)
.dstSet = descriptorSet,
.dstBinding = 2,
.dstArrayElement = 0,
.descriptorCount = imageDescriptors.size(),
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = imageDescriptors.data(),
};
writeDescriptorSets[3] = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = descriptorSet,
.dstBinding = 3,
.dstArrayElement = 0,
.descriptorCount = ycbcrImageDescriptors.size(),
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = ycbcrImageDescriptors.data(),
@ -1614,6 +1631,20 @@ void CVulkanCmdBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z)
imageDescriptors[i].imageView = view;
}
if (!m_target->isYcbcr())
{
targetDescriptors[0].imageView = m_target->srgbView();
targetDescriptors[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
}
else
{
targetDescriptors[0].imageView = m_target->lumaView();
targetDescriptors[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
targetDescriptors[1].imageView = m_target->chromaView();
targetDescriptors[1].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
}
m_device->vk.UpdateDescriptorSets(m_device->device(), writeDescriptorSets.size(), writeDescriptorSets.data(), 0, nullptr);
m_device->vk.CmdBindDescriptorSets(m_cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, m_device->pipelineLayout(), 0, 1, &descriptorSet, 0, nullptr);
@ -2067,6 +2098,8 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t drmFormat,
.memoryTypeIndex = uint32_t(g_device.findMemoryType(properties, memRequirements.memoryTypeBits)),
};
m_size = allocInfo.allocationSize;
if ( flags.bExportable == true || pDMA != nullptr )
{
memory_dedicated_info = {
@ -2291,6 +2324,31 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t drmFormat,
return false;
}
}
if ( isYcbcr() )
{
createInfo.pNext = NULL;
createInfo.format = VK_FORMAT_R8_UNORM;
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT;
res = vkCreateImageView(g_device.device(), &createInfo, nullptr, &m_lumaView);
if ( res != VK_SUCCESS ) {
vk_errorf( res, "vkCreateImageView failed" );
return false;
}
createInfo.pNext = NULL;
createInfo.format = VK_FORMAT_R8G8_UNORM;
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT;
res = vkCreateImageView(g_device.device(), &createInfo, nullptr, &m_chromaView);
if ( res != VK_SUCCESS ) {
vk_errorf( res, "vkCreateImageView failed" );
return false;
}
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
}
}
if ( flags.bMappable )
@ -2904,7 +2962,7 @@ void vulkan_garbage_collect( void )
g_device.garbageCollect();
}
std::shared_ptr<CVulkanTexture> vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable)
std::shared_ptr<CVulkanTexture> vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable, bool nv12)
{
for (auto& pScreenshotImage : g_output.pScreenshotImages)
{
@ -2915,13 +2973,13 @@ std::shared_ptr<CVulkanTexture> vulkan_acquire_screenshot_texture(uint32_t width
CVulkanTexture::createFlags screenshotImageFlags;
screenshotImageFlags.bMappable = true;
screenshotImageFlags.bTransferDst = true;
if (exportable) {
if (exportable || nv12) {
screenshotImageFlags.bExportable = true;
screenshotImageFlags.bLinear = true; // TODO: support multi-planar DMA-BUF export via PipeWire
screenshotImageFlags.bStorage = true;
}
bool bSuccess = pScreenshotImage->BInit( width, height, VulkanFormatToDRM(g_output.outputFormat), screenshotImageFlags );
bool bSuccess = pScreenshotImage->BInit( width, height, nv12 ? DRM_FORMAT_NV12 : VulkanFormatToDRM(g_output.outputFormat), screenshotImageFlags );
assert( bSuccess );
}
@ -3196,14 +3254,17 @@ bool vulkan_composite( const struct FrameInfo_t *frameInfo, std::shared_ptr<CVul
if ( pScreenshotTexture != nullptr )
{
if (compositeImage->width() == pScreenshotTexture->width() &&
if (compositeImage->format() == pScreenshotTexture->format() &&
compositeImage->width() == pScreenshotTexture->width() &&
compositeImage->height() == pScreenshotTexture->height()) {
cmdBuffer->copyImage(compositeImage, pScreenshotTexture);
} else {
const bool ycbcr = pScreenshotTexture->isYcbcr();
float scale = (float)compositeImage->width() / pScreenshotTexture->width();
BlitPushData_t constants( scale );
cmdBuffer->bindPipeline(g_device.pipeline(SHADER_TYPE_BLIT));
cmdBuffer->bindPipeline(g_device.pipeline( ycbcr ? SHADER_TYPE_RGB_TO_NV12 : SHADER_TYPE_BLIT ));
cmdBuffer->bindTexture(0, compositeImage);
cmdBuffer->setTextureSrgb(0, false);
cmdBuffer->setSamplerNearest(0, false);
@ -3217,7 +3278,10 @@ bool vulkan_composite( const struct FrameInfo_t *frameInfo, std::shared_ptr<CVul
const int pixelsPerGroup = 8;
cmdBuffer->dispatch(div_roundup(pScreenshotTexture->width(), pixelsPerGroup), div_roundup(pScreenshotTexture->height(), pixelsPerGroup));
// 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(), 1));
}
}

View file

@ -107,6 +107,8 @@ public:
inline VkImageView view( bool linear ) { return linear ? m_linearView : m_srgbView; }
inline VkImageView linearView() { return m_linearView; }
inline VkImageView srgbView() { return m_srgbView; }
inline VkImageView lumaView() { return m_lumaView; }
inline VkImageView chromaView() { return m_chromaView; }
inline uint32_t width() { return m_width; }
inline uint32_t height() { return m_height; }
inline uint32_t contentWidth() {return m_contentWidth; }
@ -114,11 +116,17 @@ public:
inline uint32_t rowPitch() { return m_unRowPitch; }
inline uint32_t fbid() { return m_FBID; }
inline void *mappedData() { return m_pMappedData; }
inline VkFormat format() { return m_format; }
inline VkFormat format() const { return m_format; }
inline const struct wlr_dmabuf_attributes& dmabuf() { return m_dmabuf; }
inline VkImage vkImage() { return m_vkImage; }
inline bool swapchainImage() { return m_vkImageMemory == VK_NULL_HANDLE; }
inline bool externalImage() { return m_bExternal; }
inline VkDeviceSize totalSize() const { return m_size; }
inline bool isYcbcr() const
{
return format() == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM;
}
int memoryFence();
@ -135,6 +143,9 @@ private:
VkImageView m_srgbView = VK_NULL_HANDLE;
VkImageView m_linearView = VK_NULL_HANDLE;
VkImageView m_lumaView = VK_NULL_HANDLE;
VkImageView m_chromaView = VK_NULL_HANDLE;
uint32_t m_width = 0;
uint32_t m_height = 0;
@ -142,6 +153,7 @@ private:
uint32_t m_contentHeight = 0;
uint32_t m_unRowPitch = 0;
VkDeviceSize m_size = 0;
uint32_t m_FBID = 0;
@ -185,7 +197,7 @@ struct FrameInfo_t
if ( !tex )
return false;
return tex->format() == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM;
return tex->isYcbcr();
}
uint32_t integerWidth() const { return tex->width() / scale.x; }
@ -230,7 +242,7 @@ std::shared_ptr<CVulkanTexture> vulkan_create_texture_from_wlr_buffer( struct wl
bool vulkan_composite( const struct FrameInfo_t *frameInfo, std::shared_ptr<CVulkanTexture> pScreenshotTexture );
std::shared_ptr<CVulkanTexture> vulkan_get_last_output_image( void );
std::shared_ptr<CVulkanTexture> vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable);
std::shared_ptr<CVulkanTexture> vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable, bool nv12);
void vulkan_present_to_window( void );

View file

@ -0,0 +1,63 @@
#version 450
#extension GL_GOOGLE_include_directive : require
#include "descriptor_set.h"
layout(
local_size_x = 8,
local_size_y = 8,
local_size_z = 1) in;
// NV12 Format is:
// YYYYYYYYYYYYYYY...
// YYYYYYYYYYYYYYY...
// UVUVUVUVUVUVUVU...
layout(push_constant)
uniform layers_t {
vec2 u_scale[VKR_MAX_LAYERS];
vec2 u_offset[VKR_MAX_LAYERS];
float u_opacity[VKR_MAX_LAYERS];
uint u_borderMask;
uint u_frameId;
};
#include "composite.h"
vec4 sampleLayer(uint layerIdx, vec2 uv) {
return sampleLayer(s_samplers[layerIdx], layerIdx, uv, true);
}
void main() {
ivec3 thread_id = ivec3(gl_GlobalInvocationID);
// todo: fix
if (all(lessThan(thread_id.xy, ivec2(640, 400)))) {
ivec2 offset_table[4] = {
ivec2(0, 0), ivec2(1, 0), ivec2(0, 1), ivec2(1, 1),
};
ivec2 chroma_uv = thread_id.xy;
ivec2 luma_uv = thread_id.xy * 2;
vec3 color[4] = {
sampleLayer(0, vec2(luma_uv.x + offset_table[0].x, luma_uv.y + offset_table[0].y)).rgb,
sampleLayer(0, vec2(luma_uv.x + offset_table[1].x, luma_uv.y + offset_table[1].y)).rgb,
sampleLayer(0, vec2(luma_uv.x + offset_table[2].x, luma_uv.y + offset_table[2].y)).rgb,
sampleLayer(0, vec2(luma_uv.x + offset_table[3].x, luma_uv.y + offset_table[3].y)).rgb,
};
vec3 avg_color = (color[0] + color[1] + color[2] + color[3]) / 4.0f;
vec2 uv = vec2(
-0.148f * avg_color.r - 0.291f * avg_color.g + 0.439f * avg_color.b + (128.0f / 255.0f),
0.439f * avg_color.r - 0.368f * avg_color.g - 0.071f * avg_color.b + (128.0f / 255.0f)
);
imageStore(dst_chroma, chroma_uv, vec4(uv, 0.0f, 1.0f));
for (int i = 0; i < 4; i++) {
float y = 0.257f * color[i].r + 0.504f * color[i].g + 0.098f * color[i].b + (16.0f / 255.0f);
imageStore(dst_luma, luma_uv + offset_table[i], vec4(y, 0.0f, 0.0f, 1.0f));
}
}
}

View file

@ -6,5 +6,9 @@ layout(constant_id = 2) const bool c_compositing_debug = false;
layout(constant_id = 3) const int c_blur_layer_count = 0;
layout(binding = 0, rgba8) writeonly uniform image2D dst;
layout(binding = 1) uniform sampler2D s_samplers[VKR_SAMPLER_SLOTS];
layout(binding = 2) uniform sampler2D s_ycbcr_samplers[VKR_SAMPLER_SLOTS];
// alias
layout(binding = 0, rgba8) writeonly uniform image2D dst_luma;
layout(binding = 1, rgba8) writeonly uniform image2D dst_chroma;
layout(binding = 2) uniform sampler2D s_samplers[VKR_SAMPLER_SLOTS];
layout(binding = 3) uniform sampler2D s_ycbcr_samplers[VKR_SAMPLER_SLOTS];

View file

@ -1,6 +1,7 @@
#ifndef DESCRIPTOR_SET_CONSTANTS_H_
#define DESCRIPTOR_SET_CONSTANTS_H_
#define VKR_TARGET_SLOTS 2u
#define VKR_SAMPLER_SLOTS 16u
#define VKR_MAX_LAYERS 6u

View file

@ -1917,9 +1917,12 @@ paint_all(bool async)
pCaptureTexture = pw_buffer->texture;
}
#endif
// hack: forced to nv12 rn
bool bHackForceNV12DumpScreenshot = false;
if ( bCapture && pCaptureTexture == nullptr )
{
pCaptureTexture = vulkan_acquire_screenshot_texture(g_nOutputWidth, g_nOutputHeight, false);
pCaptureTexture = vulkan_acquire_screenshot_texture(g_nOutputWidth, g_nOutputHeight, false, bHackForceNV12DumpScreenshot);
}
bool bResult = vulkan_composite( &frameInfo, pCaptureTexture );
@ -1999,45 +2002,76 @@ paint_all(bool async)
if ( takeScreenshot )
{
assert( pCaptureTexture != nullptr );
assert( pCaptureTexture->format() == VK_FORMAT_B8G8R8A8_UNORM );
std::thread screenshotThread = std::thread([=] {
pthread_setname_np( pthread_self(), "gamescope-scrsh" );
const uint8_t *mappedData = reinterpret_cast<const uint8_t *>(pCaptureTexture->mappedData());
// Make our own copy of the image to remove the alpha channel.
auto imageData = std::vector<uint8_t>(currentOutputWidth * currentOutputHeight * 4);
const uint32_t comp = 4;
const uint32_t pitch = currentOutputWidth * comp;
for (uint32_t y = 0; y < currentOutputHeight; y++)
if (pCaptureTexture->format() == VK_FORMAT_B8G8R8A8_UNORM)
{
for (uint32_t x = 0; x < currentOutputWidth; x++)
// Make our own copy of the image to remove the alpha channel.
auto imageData = std::vector<uint8_t>(currentOutputWidth * currentOutputHeight * 4);
const uint32_t comp = 4;
const uint32_t pitch = currentOutputWidth * comp;
for (uint32_t y = 0; y < currentOutputHeight; y++)
{
// 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;
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( &currentTime );
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 );
}
}
char pTimeBuffer[1024] = "/tmp/gamescope.png";
if ( !propertyRequestedScreenshot )
else if (pCaptureTexture->format() == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM)
{
time_t currentTime = time(0);
struct tm *localTime = localtime( &currentTime );
strftime( pTimeBuffer, sizeof( pTimeBuffer ), "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.png", localTime );
}
char pTimeBuffer[1024] = "/tmp/gamescope.raw";
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 );
if ( !propertyRequestedScreenshot )
{
time_t currentTime = time(0);
struct tm *localTime = localtime( &currentTime );
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);
system(cmd);
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 );