vulkan: Implement buffer chain

This mimics the interface from shm.h to be as close to a drop-in
replacement as possible.
This commit is contained in:
Kenny Levinsen 2024-08-04 23:38:40 +02:00
parent 42caf8b387
commit e3df9f6e1e
2 changed files with 469 additions and 2 deletions

420
vulkan.c
View file

@ -13,7 +13,7 @@
#include "vulkan.h" #include "vulkan.h"
#define LOG_MODULE "vulkan" #define LOG_MODULE "vulkan"
#define LOG_ENABLE_DBG 1 #define LOG_ENABLE_DBG 0
#include "log.h" #include "log.h"
#include "debug.h" #include "debug.h"
#include "macros.h" #include "macros.h"
@ -231,4 +231,422 @@ vulkan_create(dev_t preferred_device)
error: error:
vulkan_destroy(vk); vulkan_destroy(vk);
return NULL; return NULL;
}
static int
vulkan_find_mem_type(struct vulkan *vk, VkMemoryPropertyFlags flags, uint32_t req_bits)
{
VkPhysicalDeviceMemoryProperties props;
vkGetPhysicalDeviceMemoryProperties(vk->physical_device, &props);
for (unsigned i = 0u; i < props.memoryTypeCount; ++i)
if (req_bits & (1 << i))
if ((props.memoryTypes[i].propertyFlags & flags) == flags)
return i;
return -1;
}
static tll(struct vk_buffer_private *) vk_deferred;
struct vk_buffer_chain;
struct vk_buffer_private {
struct vk_buffer public;
struct vk_buffer_chain *chain;
size_t ref_count;
bool busy;
bool with_alpha;
struct vulkan *vk;
VkBuffer buffer;
VkDeviceMemory memory;
};
static void
vk_buffer_destroy_dont_close(struct vk_buffer *buf)
{
if (buf->pix != NULL)
for (size_t i = 0; i < buf->pix_instances; i++)
if (buf->pix[i] != NULL)
pixman_image_unref(buf->pix[i]);
free(buf->pix);
buf->pix = NULL;
}
static void
vk_buffer_destroy(struct vk_buffer_private *buf)
{
vk_buffer_destroy_dont_close(&buf->public);
for (size_t i = 0; i < buf->public.pix_instances; i++)
pixman_region32_fini(&buf->public.dirty[i]);
free(buf->public.dirty);
free(buf);
}
static bool
vk_buffer_unref_no_remove_from_chain(struct vk_buffer_private *buf)
{
xassert(buf->ref_count > 0);
buf->ref_count--;
if (buf->ref_count > 0)
return false;
if (buf->busy)
tll_push_back(vk_deferred, buf);
else
vk_buffer_destroy(buf);
return true;
}
struct vk_buffer_chain {
tll(struct vk_buffer_private *) bufs;
struct vulkan *vk;
struct zwp_linux_dmabuf_v1 *linux_dmabuf_v1;
size_t pix_instances;
};
void
vk_purge(struct vk_buffer_chain *chain)
{
LOG_DBG("chain: %p: purging all buffers", (void *)chain);
/* Purge old buffers associated with this cookie */
tll_foreach(chain->bufs, it) {
if (vk_buffer_unref_no_remove_from_chain(it->item))
tll_remove(chain->bufs, it);
}
}
bool
vk_can_scroll(const struct vk_buffer *_buf)
{
return false;
}
bool
vk_scroll(struct vk_buffer *_buf, int rows, int top_margin, int top_keep_rows, int bottom_margin, int bottom_keep_rows)
{
return false;
}
void vk_addref(struct vk_buffer *_buf)
{
struct vk_buffer_private *buf = (struct vk_buffer_private *)_buf;
buf->ref_count++;
}
void
vk_unref(struct vk_buffer *_buf)
{
if (_buf == NULL)
return;
struct vk_buffer_private *buf = (struct vk_buffer_private *)_buf;
struct vk_buffer_chain *chain = buf->chain;
tll_foreach(chain->bufs, it) {
if (it->item != buf)
continue;
if (vk_buffer_unref_no_remove_from_chain(buf))
tll_remove(chain->bufs, it);
break;
}
}
struct vk_buffer_chain *
vk_chain_new(struct vulkan *vk, struct zwp_linux_dmabuf_v1 *linux_dmabuf_v1, bool _scrollable, size_t pix_instances)
{
struct vk_buffer_chain *chain = xmalloc(sizeof(*chain));
*chain = (struct vk_buffer_chain){
.bufs = tll_init(),
.vk = vk,
.linux_dmabuf_v1 = linux_dmabuf_v1,
.pix_instances = pix_instances,
};
return chain;
}
void
vk_chain_free(struct vk_buffer_chain *chain)
{
if (chain == NULL)
return;
vk_purge(chain);
if (tll_length(chain->bufs) > 0)
BUG("chain=%p: there are buffers remaining; "
"is there a missing call to vk_unref()?", (void *)chain);
free(chain);
}
static void
vulkan_image_destroy(struct vk_buffer_private *img)
{
if (img->public.pix) {
for (size_t i = 0; i < img->public.pix_instances; i++)
if (img->public.pix[i] != NULL)
pixman_image_unref(img->public.pix[i]);
free(img->public.pix);
}
if (img->public.data)
vkUnmapMemory(img->vk->device, img->memory);
if (img->public.fd != -1)
close(img->public.fd);
if (img->memory)
vkFreeMemory(img->vk->device, img->memory, NULL);
if (img->buffer)
vkDestroyBuffer(img->vk->device, img->buffer, NULL);
free(img);
}
static void
buffer_release(void *data, struct wl_buffer *wl_buffer)
{
struct vk_buffer_private *buffer = data;
xassert(buffer->public.wl_buf == wl_buffer);
xassert(buffer->busy);
buffer->busy = false;
if (buffer->ref_count == 0) {
bool found = false;
tll_foreach(vk_deferred, it) {
if (it->item == buffer) {
found = true;
tll_remove(vk_deferred, it);
break;
}
}
vk_buffer_destroy(buffer);
xassert(found);
if (!found)
LOG_WARN("deferred delete: buffer not on the 'deferred' list");
}
}
static const struct wl_buffer_listener buffer_listener = {
.release = &buffer_release,
};
static struct vk_buffer *
vk_buffer_create(struct vk_buffer_chain *chain, int width, int height, bool with_alpha, bool immediate_purge)
{
struct vulkan *vk = chain->vk;
struct zwp_linux_dmabuf_v1 *linux_dmabuf_v1 = chain->linux_dmabuf_v1;
struct vk_buffer_private *img = xmalloc(sizeof(*img));
*img = (struct vk_buffer_private){
.vk = vk,
.public = (struct vk_buffer) {
.fd = -1,
.width = width,
.height = height,
.stride = width * 4,
},
};
uint64_t mod = DRM_FORMAT_MOD_LINEAR;
uint64_t mods[1] = { mod };
VkImageDrmFormatModifierListCreateInfoEXT drm_format_mod = {
.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT,
.drmFormatModifierCount = 1,
.pDrmFormatModifiers = mods,
};
VkExternalMemoryBufferCreateInfo ext_mem = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
.pNext = &drm_format_mod,
};
VkBufferCreateInfo buf_create = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = &ext_mem,
.size = width * height * 4,
.usage = VK_IMAGE_USAGE_SAMPLED_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
};
if (vkCreateBuffer(vk->device, &buf_create, NULL, &img->buffer) != VK_SUCCESS) {
LOG_ERR("Could not allocate image");
goto error;
}
VkMemoryRequirements mem_reqs = {0};
vkGetBufferMemoryRequirements(vk->device, img->buffer, &mem_reqs);
VkExportMemoryAllocateInfo export_mem = {
.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
};
// The flags we need:
// - HOST_VISIBLE in order to CPU map the buffer
// - HOST_COHERENT as we otherwise need to call vkFlushMappedMemoryRanges after a write
// - HOST_CACHED in order to have decent CPU access performancea
int mem_type_index = vulkan_find_mem_type(vk,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT,
mem_reqs.memoryTypeBits);
if (mem_type_index == -1) {
LOG_ERR("Could not find suitable memory type");
goto error;
}
VkMemoryAllocateInfo mem_alloc = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &export_mem,
.allocationSize = mem_reqs.size,
.memoryTypeIndex = mem_type_index,
};
if (vkAllocateMemory(vk->device, &mem_alloc, NULL, &img->memory) != VK_SUCCESS) {
LOG_ERR("Could not allocate memory");
goto error;
}
if (vkBindBufferMemory(vk->device, img->buffer, img->memory, 0) != VK_SUCCESS) {
LOG_ERR("Could not bind memory");
goto error;
}
VkMemoryGetFdInfoKHR mem_get_fd = {
.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
.memory = img->memory,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
};
if (vk->api.vkGetMemoryFdKHR(vk->device, &mem_get_fd, &img->public.fd) != VK_SUCCESS) {
LOG_ERR("Could not get dmabuf");
goto error;
}
if (vkMapMemory(vk->device, img->memory, 0, VK_WHOLE_SIZE, 0, &img->public.data) != VK_SUCCESS) {
LOG_ERR("Could not map memory");
goto error;
}
struct zwp_linux_buffer_params_v1 *params = zwp_linux_dmabuf_v1_create_params(linux_dmabuf_v1);
zwp_linux_buffer_params_v1_add(params, img->public.fd, 0, 0, width * 4, mod >> 32, mod & 0xFF);
img->public.wl_buf = zwp_linux_buffer_params_v1_create_immed(params, width, height,
with_alpha ? DRM_FORMAT_ARGB8888 : DRM_FORMAT_XRGB8888, 0);
wl_buffer_add_listener(img->public.wl_buf, &buffer_listener, img);
img->public.pix_instances = chain->pix_instances;
img->public.age = 1234;
img->chain = chain;
img->ref_count = immediate_purge ? 0 : 1;
img->busy = true;
img->with_alpha = with_alpha;
img->public.pix = xcalloc(img->public.pix_instances, sizeof(*img->public.pix));
/* One pixman image for each worker thread (do we really need multiple?) */
for (size_t i = 0; i < img->public.pix_instances; i++) {
img->public.pix[i] = pixman_image_create_bits_no_clear(
img->with_alpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
width, height, img->public.data, img->public.stride);
if (img->public.pix[i] == NULL) {
LOG_ERR("failed to create pixman image");
goto error;
}
}
if (immediate_purge)
tll_push_front(vk_deferred, img);
else
tll_push_front(chain->bufs, img);
img->public.dirty = xmalloc(chain->pix_instances * sizeof(img->public.dirty[0]));
for (size_t j = 0; j < chain->pix_instances; j++)
pixman_region32_init(&img->public.dirty[j]);
return &img->public;
error:
vulkan_image_destroy(img);
return NULL;
}
struct vk_buffer *
vk_get_buffer(struct vk_buffer_chain *chain, int width, int height, bool with_alpha)
{
LOG_DBG(
"chain=%p: looking for a reusable %dx%d buffer "
"among %zu potential buffers",
(void *)chain, width, height, tll_length(chain->bufs));
struct vk_buffer_private *cached = NULL;
tll_foreach(chain->bufs, it) {
struct vk_buffer_private *buf = it->item;
if (buf->public.width != width || buf->public.height != height ||
with_alpha != buf->with_alpha) {
LOG_DBG("purging mismatching buffer %p", (void *)buf);
if (vk_buffer_unref_no_remove_from_chain(buf))
tll_remove(chain->bufs, it);
continue;
}
if (buf->busy) {
buf->public.age++;
continue;
}
if (cached == NULL) {
cached = buf;
} else {
/* We have multiple buffers eligible for
* reuse. Pick the "youngest" one, and mark the
* other one for purging */
if (buf->public.age < cached->public.age) {
vk_unref(&cached->public);
cached = buf;
} else {
if (vk_buffer_unref_no_remove_from_chain(buf))
tll_remove(chain->bufs, it);
}
}
}
if (cached != NULL) {
LOG_DBG("re-using buffer %p from cache", (void *)cached);
cached->busy = true;
for (size_t i = 0; i < cached->public.pix_instances; i++)
pixman_region32_clear(&cached->public.dirty[i]);
xassert(cached->public.pix_instances == chain->pix_instances);
return &cached->public;
}
return vk_buffer_create(chain, width, height, with_alpha, false);
}
void
vk_did_not_use_buf(struct vk_buffer *_buf)
{
struct vk_buffer_private *buf = (struct vk_buffer_private *)_buf;
buf->busy = false;
}
void
vk_get_many(
struct vk_buffer_chain *chain, size_t count,
int widths[static count], int heights[static count],
struct vk_buffer *bufs[static count], bool with_alpha)
{
for (size_t idx = 0; idx < count; idx++)
bufs[count] = vk_buffer_create(chain, widths[idx], heights[idx], with_alpha, true);
} }

View file

@ -17,4 +17,53 @@ struct vulkan {
}; };
void vulkan_destroy(struct vulkan *vk); void vulkan_destroy(struct vulkan *vk);
struct vulkan *vulkan_create(dev_t preferred_device); struct vulkan *vulkan_create(dev_t preferred_device);
struct vk_buffer {
int fd;
uint32_t width, height, stride;
void *data;
struct wl_buffer *wl_buf;
pixman_image_t **pix;
size_t pix_instances;
unsigned age;
/*
* First item in the array is used to track frame-to-frame
* damage. This is used when re-applying damage from the last
* frame, when the compositor doesn't release buffers immediately
* (forcing us to double buffer)
*
* The remaining items are used to track surface damage. Each
* worker thread adds its own cell damage to "its" region. When
* the frame is done, all damage is converted to a single region,
* which is then used in calls to wl_surface_damage_buffer().
*/
pixman_region32_t *dirty;
};
struct vk_buffer_chain;
struct vk_buffer_chain *vk_chain_new(struct vulkan *vk, struct zwp_linux_dmabuf_v1 *linux_dmabuf_v1, bool scrollable, size_t pix_instances);
void vk_chain_free(struct vk_buffer_chain *chain);
struct vk_buffer *vk_get_buffer(struct vk_buffer_chain *chain, int wdth, int height, bool with_alpha);
void vk_get_many(
struct vk_buffer_chain *chain, size_t count,
int widths[static count], int heights[static count],
struct vk_buffer *bufs[static count], bool with_alpha);
void vk_did_not_use_buf(struct vk_buffer *buf);
bool vk_can_scroll(const struct vk_buffer *buf);
bool vk_scroll(struct vk_buffer *buf, int rows,
int top_margin, int top_keep_rows,
int bottom_margin, int bottom_keep_rows);
void vk_addref(struct vk_buffer *buf);
void vk_unref(struct vk_buffer *buf);
void vk_purge(struct vk_buffer_chain *chain);