quakeforge/libs/video/renderer/vulkan/vulkan_draw.c
Bill Currie a7ac188d1d [vulkan] Use a scrap texture for draw
The scrap texture did very good things for the glsl renderer and the
better control over data copying might help it do even better things for
vulkan, especially with lots of little icons.
2021-01-13 10:43:23 +09:00

546 lines
13 KiB
C

/*
vulkan_draw.c
2D drawing support for Vulkan
Copyright (C) 2021 Bill Currie <bill@taniwha.org>
Author: Bill Currie <bill@taniwha.org>
Date: 2021/1/10
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#define NH_DEFINE
#include "namehack.h"
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include "QF/cvar.h"
#include "QF/draw.h"
#include "QF/dstring.h"
#include "QF/hash.h"
#include "QF/quakefs.h"
#include "QF/render.h"
#include "QF/sys.h"
#include "QF/vid.h"
#include "compat.h"
#include "QF/Vulkan/qf_draw.h"
#include "QF/Vulkan/qf_vid.h"
#include "QF/Vulkan/barrier.h"
#include "QF/Vulkan/buffer.h"
#include "QF/Vulkan/command.h"
#include "QF/Vulkan/descriptor.h"
#include "QF/Vulkan/device.h"
#include "QF/Vulkan/image.h"
#include "QF/Vulkan/staging.h"
#include "QF/Vulkan/texture.h"
#include "r_internal.h"
#include "vid_vulkan.h"
#include "vkparse.h"
typedef struct {
float xy[2];
float st[2];
float color[4];
} drawvert_t;
#define MAX_QUADS (65536)
#define VERTS_PER_QUAD (4)
#define INDS_PER_QUAD (5) // one per vert plus primitive reset
//FIXME move into a context struct
VkSampler conchars_sampler;
scrap_t *draw_scrap;
subpic_t *conchars;
VkBuffer quad_vert_buffer;
VkBuffer quad_ind_buffer;
VkDeviceMemory quad_memory;
drawvert_t *quad_verts;
uint32_t *quad_inds;
uint32_t num_quads;
VkPipeline twod_pipeline;
VkPipelineLayout twod_layout;
size_t draw_cmdBuffer;
static void
create_quad_buffers (vulkan_ctx_t *ctx)
{
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
//FIXME quad_inds can be a completely separate buffer that is
//pre-initialized to draw 2-tri triangle strips as the actual indices will
//never change
size_t vert_size = MAX_QUADS * VERTS_PER_QUAD * sizeof (drawvert_t);
size_t ind_size = MAX_QUADS * INDS_PER_QUAD * sizeof (uint32_t);
quad_vert_buffer = QFV_CreateBuffer (device, vert_size,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
quad_ind_buffer = QFV_CreateBuffer (device, ind_size,
VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
quad_memory = QFV_AllocBufferMemory (device, quad_vert_buffer,
VK_MEMORY_PROPERTY_HOST_CACHED_BIT,
vert_size + ind_size, 0);
QFV_BindBufferMemory (device, quad_vert_buffer, quad_memory, 0);
QFV_BindBufferMemory (device, quad_ind_buffer, quad_memory, vert_size);
void *data;
dfunc->vkMapMemory (device->dev, quad_memory, 0, vert_size + ind_size,
0, &data);
quad_verts = data;
quad_inds = (uint32_t *) (quad_verts + MAX_QUADS * VERTS_PER_QUAD);
// pre-initialize quad_inds as the indices will never change
uint32_t *ind = quad_inds;
for (int i = 0; i < MAX_QUADS; i++) {
for (int j = 0; j < VERTS_PER_QUAD; j++) {
*ind++ = i * VERTS_PER_QUAD + j;
}
// mark end of primitive
*ind++ = -1;
}
VkMappedMemoryRange range = {
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, 0,
quad_memory, vert_size, ind_size,
};
dfunc->vkFlushMappedMemoryRanges (device->dev, 1, &range);
}
static void
destroy_quad_buffers (vulkan_ctx_t *ctx)
{
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
dfunc->vkUnmapMemory (device->dev, quad_memory);
dfunc->vkFreeMemory (device->dev, quad_memory, 0);
dfunc->vkDestroyBuffer (device->dev, quad_vert_buffer, 0);
dfunc->vkDestroyBuffer (device->dev, quad_ind_buffer, 0);
}
static qpic_t *
pic_data (const char *name, int w, int h, const byte *data)
{
qpic_t *pic;
pic = malloc (field_offset (qpic_t, data[w * h]));
pic->width = w;
pic->height = h;
memcpy (pic->data, data, pic->width * pic->height);
return pic;
}
qpic_t *
Vulkan_Draw_MakePic (int width, int height, const byte *data,
vulkan_ctx_t *ctx)
{
return pic_data (0, width, height, data);
}
void
Vulkan_Draw_DestroyPic (qpic_t *pic, vulkan_ctx_t *ctx)
{
}
qpic_t *
Vulkan_Draw_PicFromWad (const char *name, vulkan_ctx_t *ctx)
{
return pic_data (0, 1, 1, (const byte *)"");
}
qpic_t *
Vulkan_Draw_CachePic (const char *path, qboolean alpha, vulkan_ctx_t *ctx)
{
return pic_data (0, 1, 1, (const byte *)"");
}
void
Vulkan_Draw_UncachePic (const char *path, vulkan_ctx_t *ctx)
{
}
void
Vulkan_Draw_TextBox (int x, int y, int width, int lines, byte alpha,
vulkan_ctx_t *ctx)
{
}
void
Vulkan_Draw_Shutdown (vulkan_ctx_t *ctx)
{
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
destroy_quad_buffers (ctx);
dfunc->vkDestroyPipeline (device->dev, twod_pipeline, 0);
QFV_DestroyScrap (draw_scrap);
}
void
Vulkan_Draw_Init (vulkan_ctx_t *ctx)
{
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
VkCommandBuffer cmd = ctx->cmdbuffer;
create_quad_buffers (ctx);
draw_scrap = QFV_CreateScrap (device, 2048, QFV_RGBA);
conchars_sampler = QFV_GetSampler (ctx, "quakepic");
qpic_t *charspic = Draw_Font8x8Pic ();
conchars = QFV_ScrapSubpic (draw_scrap, charspic->width, charspic->height);
byte *chars_data = QFV_SubpicBatch (conchars, ctx->staging[0]);
size_t size = charspic->width * charspic->height;
for (size_t i = 0; i < size; i++) {
byte pix = charspic->data[i];
byte *col = vid.palette + pix * 3;
*chars_data++ = *col++;
*chars_data++ = *col++;
*chars_data++ = *col++;
*chars_data++ = (pix == 255) - 1;
}
dfunc->vkWaitForFences (device->dev, 1, &ctx->fence, VK_TRUE, ~0ull);
dfunc->vkResetCommandBuffer (cmd, 0);
VkCommandBufferBeginInfo beginInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 0,
};
dfunc->vkBeginCommandBuffer (cmd, &beginInfo);
QFV_ScrapFlush (draw_scrap, ctx->staging[0], cmd);
dfunc->vkEndCommandBuffer (cmd);
VkSubmitInfo submitInfo = {
VK_STRUCTURE_TYPE_SUBMIT_INFO, 0,
0, 0, 0,
1, &cmd,
0, 0,
};
dfunc->vkResetFences (device->dev, 1, &ctx->fence);
dfunc->vkQueueSubmit (device->queue.queue, 1, &submitInfo, ctx->fence);
twod_pipeline = Vulkan_CreatePipeline (ctx, "twod");
twod_layout = QFV_GetPipelineLayout (ctx, "twod");
__auto_type layouts = QFV_AllocDescriptorSetLayoutSet (ctx->framebuffers.size, alloca);
for (size_t i = 0; i < layouts->size; i++) {
layouts->a[i] = QFV_GetDescriptorSetLayout (ctx, "twod");
}
__auto_type pool = QFV_GetDescriptorPool (ctx, "twod");
VkDescriptorBufferInfo bufferInfo = {
ctx->matrices.buffer_2d, 0, VK_WHOLE_SIZE
};
VkDescriptorImageInfo imageInfo = {
conchars_sampler,
QFV_ScrapImageView (draw_scrap),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
};
__auto_type cmdBuffers
= QFV_AllocateCommandBuffers (device, ctx->cmdpool, 1,
ctx->framebuffers.size);
__auto_type sets = QFV_AllocateDescriptorSet (device, pool, layouts);
for (size_t i = 0; i < ctx->framebuffers.size; i++) {
__auto_type frame = &ctx->framebuffers.a[i];
frame->twodDescriptors = sets->a[i];
VkWriteDescriptorSet write[] = {
{ VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0,
frame->twodDescriptors, 0, 0, 1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
0, &bufferInfo, 0 },
{ VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0,
frame->twodDescriptors, 1, 0, 1,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
&imageInfo, 0, 0 },
};
dfunc->vkUpdateDescriptorSets (device->dev, 2, write, 0, 0);
draw_cmdBuffer = frame->subCommand->size;
DARRAY_APPEND (frame->subCommand, cmdBuffers->a[i]);
}
free (sets);
free (cmdBuffers);
}
static inline void
draw_pic (float x, float y, int w, int h, qpic_t *pic,
int srcx, int srcy, int srcw, int srch,
float *color)
{
if (num_quads + VERTS_PER_QUAD > MAX_QUADS) {
return;
}
drawvert_t *verts = quad_verts + num_quads * VERTS_PER_QUAD;
num_quads += VERTS_PER_QUAD;
//FIXME extract subpic from pic
float size = conchars->size;
float sl = (srcx + 0.03125) * size;
float sr = (srcx + srcw - 0.03125) * size;
float st = (srcy + 0.03125) * size;
float sb = (srcy + srch - 0.03125) * size;
verts[0].xy[0] = x;
verts[0].xy[1] = y;
verts[0].st[0] = sl;
verts[0].st[1] = st;
QuatCopy (color, verts[0].color);
verts[1].xy[0] = x;
verts[1].xy[1] = y + h;
verts[1].st[0] = sl;
verts[1].st[1] = sb;
QuatCopy (color, verts[1].color);
verts[2].xy[0] = x + w;
verts[2].xy[1] = y;
verts[2].st[0] = sr;
verts[2].st[1] = st;
QuatCopy (color, verts[2].color);
verts[3].xy[0] = x + w;
verts[3].xy[1] = y + h;
verts[3].st[0] = sr;
verts[3].st[1] = sb;
QuatCopy (color, verts[3].color);
}
static inline void
queue_character (int x, int y, byte chr, vulkan_ctx_t *ctx)
{
quat_t color = {1, 1, 1, 1};
int cx, cy;
cx = chr % 16;
cy = chr / 16;
draw_pic (x, y, 8, 8, 0/*FIXME*/, cx * 8, cy * 8, 8, 8, color);
}
void
Vulkan_Draw_Character (int x, int y, unsigned int chr, vulkan_ctx_t *ctx)
{
if (chr == ' ') {
return;
}
if (y <= -8 || y >= vid.conheight) {
return;
}
if (x <= -8 || x >= vid.conwidth) {
return;
}
queue_character (x, y, chr, ctx);
}
void
Vulkan_Draw_String (int x, int y, const char *str, vulkan_ctx_t *ctx)
{
byte chr;
if (!str || !str[0]) {
return;
}
if (y <= -8 || y >= vid.conheight) {
return;
}
while (*str) {
if ((chr = *str++) != ' ' && x >= -8 && x < vid.conwidth) {
queue_character (x, y, chr, ctx);
}
x += 8;
}
}
void
Vulkan_Draw_nString (int x, int y, const char *str, int count,
vulkan_ctx_t *ctx)
{
byte chr;
if (!str || !str[0]) {
return;
}
if (y <= -8 || y >= vid.conheight) {
return;
}
while (count-- > 0 && *str) {
if ((chr = *str++) != ' ' && x >= -8 && x < vid.conwidth) {
queue_character (x, y, chr, ctx);
}
x += 8;
}
}
void
Vulkan_Draw_AltString (int x, int y, const char *str, vulkan_ctx_t *ctx)
{
byte chr;
if (!str || !str[0]) {
return;
}
if (y <= -8 || y >= vid.conheight) {
return;
}
while (*str) {
if ((chr = *str++ | 0x80) != (' ' | 0x80)
&& x >= -8 && x < vid.conwidth) {
queue_character (x, y, chr, ctx);
}
x += 8;
}
}
void
Vulkan_Draw_CrosshairAt (int ch, int x, int y, vulkan_ctx_t *ctx)
{
}
void
Vulkan_Draw_Crosshair (vulkan_ctx_t *ctx)
{
}
void
Vulkan_Draw_Pic (int x, int y, qpic_t *pic, vulkan_ctx_t *ctx)
{
}
void
Vulkan_Draw_Picf (float x, float y, qpic_t *pic, vulkan_ctx_t *ctx)
{
}
void
Vulkan_Draw_SubPic (int x, int y, qpic_t *pic,
int srcx, int srcy, int width, int height,
vulkan_ctx_t *ctx)
{
}
void
Vulkan_Draw_ConsoleBackground (int lines, byte alpha, vulkan_ctx_t *ctx)
{
}
void
Vulkan_Draw_TileClear (int x, int y, int w, int h, vulkan_ctx_t *ctx)
{
}
void
Vulkan_Draw_Fill (int x, int y, int w, int h, int c, vulkan_ctx_t *ctx)
{
}
void
Vulkan_Draw_FadeScreen (vulkan_ctx_t *ctx)
{
}
void
Vulkan_Set2D (vulkan_ctx_t *ctx)
{
}
void
Vulkan_Set2DScaled (vulkan_ctx_t *ctx)
{
}
void
Vulkan_End2D (vulkan_ctx_t *ctx)
{
}
void
Vulkan_DrawReset (vulkan_ctx_t *ctx)
{
}
void
Vulkan_FlushText (vulkan_ctx_t *ctx)
{
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
__auto_type frame = &ctx->framebuffers.a[ctx->curFrame];
VkCommandBuffer cmd = frame->subCommand->a[draw_cmdBuffer];
VkMappedMemoryRange range = {
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, 0,
quad_memory, 0, num_quads * VERTS_PER_QUAD * sizeof (drawvert_t),
};
dfunc->vkFlushMappedMemoryRanges (device->dev, 1, &range);
dfunc->vkResetCommandBuffer (cmd, 0);
VkCommandBufferInheritanceInfo inherit = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO, 0,
ctx->renderpass.renderpass, 0,
frame->framebuffer,
0, 0, 0
};
VkCommandBufferBeginInfo beginInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
| VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, &inherit,
};
dfunc->vkBeginCommandBuffer (cmd, &beginInfo);
dfunc->vkCmdBindPipeline (cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
twod_pipeline);
VkViewport viewport = {0, 0, vid.width, vid.height, 0, 1};
VkRect2D scissor = { {0, 0}, {vid.width, vid.height} };
dfunc->vkCmdSetViewport (cmd, 0, 1, &viewport);
dfunc->vkCmdSetScissor (cmd, 0, 1, &scissor);
VkDeviceSize offsets[] = {0};
dfunc->vkCmdBindVertexBuffers (cmd, 0, 1, &quad_vert_buffer, offsets);
dfunc->vkCmdBindIndexBuffer (cmd, quad_ind_buffer, 0,
VK_INDEX_TYPE_UINT32);
VkDescriptorSet set = frame->twodDescriptors;
VkPipelineLayout layout = twod_layout;
dfunc->vkCmdBindDescriptorSets (cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
layout, 0, 1, &set, 0, 0);
dfunc->vkCmdDrawIndexed (cmd, num_quads * INDS_PER_QUAD, 1, 0, 0, 0);
dfunc->vkEndCommandBuffer (cmd);
num_quads = 0;
}
void
Vulkan_Draw_BlendScreen (quat_t color, vulkan_ctx_t *ctx)
{
}