[vulkan] Implement screenshot

Finally, I can brag about my progress on irc :)
This commit is contained in:
Bill Currie 2021-03-24 19:20:53 +09:00
parent 51d6ec7c8c
commit c5c44da727
11 changed files with 109 additions and 18 deletions

View file

@ -164,6 +164,7 @@ DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdPipelineBarrier)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdCopyBuffer) DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdCopyBuffer)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdCopyBufferToImage) DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdCopyBufferToImage)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdCopyImageToBuffer) DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdCopyImageToBuffer)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdCopyImage)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdBlitImage) DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdBlitImage)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdBeginRenderPass) DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdBeginRenderPass)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdNextSubpass) DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdNextSubpass)

View file

@ -66,6 +66,8 @@ VkImageView QFV_CreateImageView (struct qfv_device_s *device,
VkImage image, VkImageViewType type, VkImage image, VkImageViewType type,
VkFormat format, VkImageAspectFlags aspect); VkFormat format, VkImageAspectFlags aspect);
size_t QFV_GetImageSize (struct qfv_device_s *device, VkImage image);
/** Generate all mipmaps for a given texture down to a 1x1 pixel. /** Generate all mipmaps for a given texture down to a 1x1 pixel.
* *
* Uses the GPU blit command from one mip level to the next, thus the base mip * Uses the GPU blit command from one mip level to the next, thus the base mip

View file

@ -60,6 +60,7 @@ enum {
struct vulkan_ctx_s; struct vulkan_ctx_s;
void Vulkan_DestroyFrames (struct vulkan_ctx_s *ctx); void Vulkan_DestroyFrames (struct vulkan_ctx_s *ctx);
void Vulkan_CreateFrames (struct vulkan_ctx_s *ctx); void Vulkan_CreateFrames (struct vulkan_ctx_s *ctx);
void Vulkan_CreateCapture (struct vulkan_ctx_s *ctx);
void Vulkan_CreateFramebuffers (struct vulkan_ctx_s *ctx); void Vulkan_CreateFramebuffers (struct vulkan_ctx_s *ctx);
void Vulkan_DestroyFramebuffers (struct vulkan_ctx_s *ctx); void Vulkan_DestroyFramebuffers (struct vulkan_ctx_s *ctx);
void Vulkan_CreateRenderPass (struct vulkan_ctx_s *ctx); void Vulkan_CreateRenderPass (struct vulkan_ctx_s *ctx);

View file

@ -8,6 +8,7 @@ typedef struct qfv_swapchain_s {
VkFormat format; VkFormat format;
VkExtent2D extent; VkExtent2D extent;
int32_t numImages; int32_t numImages;
VkImageUsageFlags usage;
struct qfv_imageset_s *images; struct qfv_imageset_s *images;
struct qfv_imageviewset_s *imageViews; struct qfv_imageviewset_s *imageViews;
} qfv_swapchain_t; } qfv_swapchain_t;

View file

@ -92,6 +92,9 @@ typedef struct vulkan_ctx_s {
size_t curFrame; size_t curFrame;
vulkan_frameset_t frames; vulkan_frameset_t frames;
struct qfv_capture_s *capture;
void (*capture_callback) (const byte *data, int width, int height);
struct qfv_tex_s *default_black; struct qfv_tex_s *default_black;
struct qfv_tex_s *default_white; struct qfv_tex_s *default_white;
struct qfv_tex_s *default_magenta; struct qfv_tex_s *default_magenta;

View file

@ -87,21 +87,6 @@ static void vulkan_brush_clear (model_t *mod, void *data)
dfunc->vkFreeMemory (device->dev, mctx->texture_memory, 0); dfunc->vkFreeMemory (device->dev, mctx->texture_memory, 0);
} }
static size_t
get_image_size (VkImage image, qfv_device_t *device)
{
qfv_devfuncs_t *dfunc = device->funcs;
size_t size;
size_t align;
VkMemoryRequirements requirements;
dfunc->vkGetImageMemoryRequirements (device->dev, image, &requirements);
size = requirements.size;
align = requirements.alignment - 1;
size = (size + align) & ~(align);
return size;
}
static void static void
transfer_mips (byte *dst, const void *_src, const texture_t *tx, byte *palette) transfer_mips (byte *dst, const void *_src, const texture_t *tx, byte *palette)
{ {
@ -193,7 +178,7 @@ load_textures (model_t *mod, vulkan_ctx_t *ctx)
} }
vulktex_t *tex = tx->render; vulktex_t *tex = tx->render;
tex->tex->offset = memsize; tex->tex->offset = memsize;
memsize += get_image_size (tex->tex->image, device); memsize += QFV_GetImageSize (device, tex->tex->image);
image_count++; image_count++;
copy_count += MIPLEVELS; copy_count += MIPLEVELS;
if (strncmp (tx->name, "sky", 3) == 0) { if (strncmp (tx->name, "sky", 3) == 0) {
@ -204,7 +189,7 @@ load_textures (model_t *mod, vulkan_ctx_t *ctx)
if (tex->glow) { if (tex->glow) {
copy_count += MIPLEVELS; copy_count += MIPLEVELS;
tex->glow->offset = memsize; tex->glow->offset = memsize;
memsize += get_image_size (tex->glow->image, device); memsize += QFV_GetImageSize (device, tex->glow->image);
image_count++; image_count++;
} }
} }

View file

@ -250,6 +250,7 @@ libs_video_renderer_librender_vulkan_la_SOURCES = \
libs/video/renderer/vulkan/barrier.c \ libs/video/renderer/vulkan/barrier.c \
libs/video/renderer/vulkan/buffer.c \ libs/video/renderer/vulkan/buffer.c \
libs/video/renderer/vulkan/command.c \ libs/video/renderer/vulkan/command.c \
libs/video/renderer/vulkan/capture.c \
libs/video/renderer/vulkan/descriptor.c \ libs/video/renderer/vulkan/descriptor.c \
libs/video/renderer/vulkan/device.c \ libs/video/renderer/vulkan/device.c \
libs/video/renderer/vulkan/image.c \ libs/video/renderer/vulkan/image.c \

View file

@ -31,7 +31,10 @@
#include <stdlib.h> #include <stdlib.h>
#include "QF/darray.h" #include "QF/darray.h"
#include "QF/dstring.h"
#include "QF/quakefs.h"
#include "QF/sys.h" #include "QF/sys.h"
#include "QF/va.h"
#include "QF/plugin/general.h" #include "QF/plugin/general.h"
#include "QF/plugin/vid_render.h" #include "QF/plugin/vid_render.h"
@ -46,6 +49,7 @@
#include "QF/Vulkan/qf_particles.h" #include "QF/Vulkan/qf_particles.h"
#include "QF/Vulkan/qf_texture.h" #include "QF/Vulkan/qf_texture.h"
#include "QF/Vulkan/qf_vid.h" #include "QF/Vulkan/qf_vid.h"
#include "QF/Vulkan/capture.h"
#include "QF/Vulkan/command.h" #include "QF/Vulkan/command.h"
#include "QF/Vulkan/device.h" #include "QF/Vulkan/device.h"
#include "QF/Vulkan/image.h" #include "QF/Vulkan/image.h"
@ -90,6 +94,7 @@ vulkan_R_Init (void)
Vulkan_CreateMatrices (vulkan_ctx); Vulkan_CreateMatrices (vulkan_ctx);
Vulkan_CreateSwapchain (vulkan_ctx); Vulkan_CreateSwapchain (vulkan_ctx);
Vulkan_CreateFrames (vulkan_ctx); Vulkan_CreateFrames (vulkan_ctx);
Vulkan_CreateCapture (vulkan_ctx);
Vulkan_CreateRenderPass (vulkan_ctx); Vulkan_CreateRenderPass (vulkan_ctx);
Vulkan_CreateFramebuffers (vulkan_ctx); Vulkan_CreateFramebuffers (vulkan_ctx);
Vulkan_Texture_Init (vulkan_ctx); Vulkan_Texture_Init (vulkan_ctx);
@ -165,6 +170,12 @@ vulkan_R_RenderFrame (SCR_Func scr_3dfunc, SCR_Func *scr_funcs)
} }
dfunc->vkCmdEndRenderPass (frame->cmdBuffer); dfunc->vkCmdEndRenderPass (frame->cmdBuffer);
if (vulkan_ctx->capture_callback) {
VkImage srcImage = vulkan_ctx->swapchain->images->a[imageIndex];
VkCommandBuffer cmd = QFV_CaptureImage (vulkan_ctx->capture, srcImage,
vulkan_ctx->curFrame);
dfunc->vkCmdExecuteCommands (frame->cmdBuffer, 1, &cmd);
}
dfunc->vkEndCommandBuffer (frame->cmdBuffer); dfunc->vkEndCommandBuffer (frame->cmdBuffer);
VkPipelineStageFlags waitStage VkPipelineStageFlags waitStage
@ -178,6 +189,17 @@ vulkan_R_RenderFrame (SCR_Func scr_3dfunc, SCR_Func *scr_funcs)
dfunc->vkResetFences (dev, 1, &frame->fence); dfunc->vkResetFences (dev, 1, &frame->fence);
dfunc->vkQueueSubmit (queue->queue, 1, &submitInfo, frame->fence); dfunc->vkQueueSubmit (queue->queue, 1, &submitInfo, frame->fence);
if (vulkan_ctx->capture_callback) {
//FIXME look into "threading" this rather than waiting here
dfunc->vkWaitForFences (device->dev, 1, &frame->fence, VK_TRUE,
1000000000ull);
vulkan_ctx->capture_callback (QFV_CaptureData (vulkan_ctx->capture,
vulkan_ctx->curFrame),
vulkan_ctx->capture->extent.width,
vulkan_ctx->capture->extent.height);
vulkan_ctx->capture_callback = 0;
}
VkPresentInfoKHR presentInfo = { VkPresentInfoKHR presentInfo = {
VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, 0, VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, 0,
1, &frame->renderDoneSemaphore, 1, &frame->renderDoneSemaphore,
@ -358,9 +380,55 @@ vulkan_R_InitParticles (void)
Vulkan_InitParticles (vulkan_ctx); Vulkan_InitParticles (vulkan_ctx);
} }
static int
is_bgr (VkFormat format)
{
return (format >= VK_FORMAT_B8G8R8A8_UNORM
&& format <= VK_FORMAT_B8G8R8A8_SRGB);
}
static void
capture_screenshot (const byte *data, int width, int height)
{
dstring_t *name = dstring_new ();
// find a file name to save it to
if (!QFS_NextFilename (name, va (vulkan_ctx->va_ctx, "%s/qf",
qfs_gamedir->dir.shots),
".ppm")) {
Sys_Printf ("SCR_ScreenShot_f: Couldn't create a ppm file\n");
} else {
QFile *file = QFS_Open (name->str, "wb");
if (!file) {
Sys_Printf ("Couldn't open %s\n", name->str);
} else {
Qprintf (file, "P6\n%d\n%d\n255\n", width, height);
if (vulkan_ctx->capture->canBlit ||
!is_bgr (vulkan_ctx->swapchain->format)) {
for (int count = width * height; count-- > 0; ) {
Qwrite (file, data, 3);
data += 4;
}
} else {
for (int count = width * height; count-- > 0; ) {
byte rgb[] = { data[2], data[1], data[0] };
Qwrite (file, rgb, 3);
data += 4;
}
}
Qclose (file);
}
}
dstring_delete (name);
}
static void static void
vulkan_SCR_ScreenShot_f (void) vulkan_SCR_ScreenShot_f (void)
{ {
if (!vulkan_ctx->capture) {
Sys_Printf ("Screenshot not supported\n");
return;
}
vulkan_ctx->capture_callback = capture_screenshot;
} }
static void static void

View file

@ -194,6 +194,21 @@ QFV_CreateImageView (qfv_device_t *device, VkImage image,
return view; return view;
} }
size_t
QFV_GetImageSize (qfv_device_t *device, VkImage image)
{
qfv_devfuncs_t *dfunc = device->funcs;
size_t size;
size_t align;
VkMemoryRequirements requirements;
dfunc->vkGetImageMemoryRequirements (device->dev, image, &requirements);
size = requirements.size;
align = requirements.alignment - 1;
size = (size + align) & ~(align);
return size;
}
void void
QFV_GenerateMipMaps (qfv_device_t *device, VkCommandBuffer cmd, QFV_GenerateMipMaps (qfv_device_t *device, VkCommandBuffer cmd,
VkImage image, unsigned mips, VkImage image, unsigned mips,

View file

@ -83,8 +83,10 @@ QFV_CreateSwapchain (vulkan_ctx_t *ctx, VkSwapchainKHR old_swapchain)
imageSize.width, imageSize.height); imageSize.width, imageSize.height);
VkImageUsageFlags imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; VkImageUsageFlags imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
imageUsage &= surfCaps.supportedUsageFlags; imageUsage &= surfCaps.supportedUsageFlags;
Sys_MaskPrintf (SYS_VULKAN, "%x %x\n", imageUsage,
surfCaps.supportedUsageFlags);
VkSurfaceTransformFlagBitsKHR surfTransform VkSurfaceTransformFlagBitsKHR surfTransform
= VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
@ -147,6 +149,7 @@ QFV_CreateSwapchain (vulkan_ctx_t *ctx, VkSwapchainKHR old_swapchain)
sc->format = useFormat.format; sc->format = useFormat.format;
sc->extent = imageSize; sc->extent = imageSize;
sc->numImages = numImages; sc->numImages = numImages;
sc->usage = imageUsage;
sc->images = DARRAY_ALLOCFIXED (qfv_imageset_t, numImages, malloc); sc->images = DARRAY_ALLOCFIXED (qfv_imageset_t, numImages, malloc);
sc->imageViews = DARRAY_ALLOCFIXED (qfv_imageviewset_t, numImages, malloc); sc->imageViews = DARRAY_ALLOCFIXED (qfv_imageviewset_t, numImages, malloc);
dfuncs->vkGetSwapchainImagesKHR (dev, swapchain, &numImages, sc->images->a); dfuncs->vkGetSwapchainImagesKHR (dev, swapchain, &numImages, sc->images->a);

View file

@ -54,6 +54,7 @@
#include "QF/Vulkan/qf_vid.h" #include "QF/Vulkan/qf_vid.h"
#include "QF/Vulkan/barrier.h" #include "QF/Vulkan/barrier.h"
#include "QF/Vulkan/buffer.h" #include "QF/Vulkan/buffer.h"
#include "QF/Vulkan/capture.h"
#include "QF/Vulkan/debug.h" #include "QF/Vulkan/debug.h"
#include "QF/Vulkan/descriptor.h" #include "QF/Vulkan/descriptor.h"
#include "QF/Vulkan/device.h" #include "QF/Vulkan/device.h"
@ -189,6 +190,9 @@ Vulkan_Shutdown_Common (vulkan_ctx_t *ctx)
{ {
PL_Free (ctx->pipelineDef); PL_Free (ctx->pipelineDef);
PL_Free (ctx->renderpassDef); PL_Free (ctx->renderpassDef);
if (ctx->capture) {
QFV_DestroyCapture (ctx->capture);
}
if (ctx->frames.size) { if (ctx->frames.size) {
Vulkan_DestroyFrames (ctx); Vulkan_DestroyFrames (ctx);
} }
@ -535,6 +539,13 @@ Vulkan_CreateFrames (vulkan_ctx_t *ctx)
} }
} }
void
Vulkan_CreateCapture (vulkan_ctx_t *ctx)
{
ctx->capture = QFV_CreateCapture (ctx->device, ctx->frames.size,
ctx->swapchain, ctx->cmdpool);
}
void void
Vulkan_DestroyFrames (vulkan_ctx_t *ctx) Vulkan_DestroyFrames (vulkan_ctx_t *ctx)
{ {