#include "config.h" #include "gskvulkanimageprivate.h" #include "gskvulkanbufferprivate.h" #include "gskvulkanmemoryprivate.h" #include "gskvulkanpipelineprivate.h" #include struct _GskVulkanUploader { GdkVulkanContext *vulkan; GskVulkanCommandPool *command_pool; GArray *before_buffer_barriers; GArray *before_image_barriers; VkCommandBuffer copy_buffer; GArray *after_buffer_barriers; GArray *after_image_barriers; GSList *staging_image_free_list; GSList *staging_buffer_free_list; }; struct _GskVulkanImage { GObject parent_instance; GdkVulkanContext *vulkan; gsize width; gsize height; VkImageUsageFlags vk_usage; VkImage vk_image; VkImageView vk_image_view; VkImageLayout vk_image_layout; VkAccessFlags vk_access; GskVulkanMemory *memory; }; G_DEFINE_TYPE (GskVulkanImage, gsk_vulkan_image, G_TYPE_OBJECT) GskVulkanUploader * gsk_vulkan_uploader_new (GdkVulkanContext *context, GskVulkanCommandPool *command_pool) { GskVulkanUploader *self; self = g_slice_new0 (GskVulkanUploader); self->vulkan = g_object_ref (context); self->command_pool = command_pool; self->before_buffer_barriers = g_array_new (FALSE, FALSE, sizeof (VkBufferMemoryBarrier)); self->after_buffer_barriers = g_array_new (FALSE, FALSE, sizeof (VkBufferMemoryBarrier)); self->before_image_barriers = g_array_new (FALSE, FALSE, sizeof (VkImageMemoryBarrier)); self->after_image_barriers = g_array_new (FALSE, FALSE, sizeof (VkImageMemoryBarrier)); return self; } void gsk_vulkan_uploader_free (GskVulkanUploader *self) { gsk_vulkan_uploader_reset (self); g_array_unref (self->after_buffer_barriers); g_array_unref (self->before_buffer_barriers); g_array_unref (self->after_image_barriers); g_array_unref (self->before_image_barriers); g_object_unref (self->vulkan); g_slice_free (GskVulkanUploader, self); } static void gsk_vulkan_uploader_add_image_barrier (GskVulkanUploader *self, gboolean after, GskVulkanImage *image, VkImageLayout new_layout, VkAccessFlags new_access) { GArray *array; VkImageMemoryBarrier barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcAccessMask = image->vk_access, .dstAccessMask = new_access, .oldLayout = image->vk_image_layout, .newLayout = new_layout, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = image->vk_image, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1 } }; if (after) array = self->after_image_barriers; else array = self->before_image_barriers; g_array_append_val (array, barrier); image->vk_image_layout = new_layout; image->vk_access = new_access; } static void gsk_vulkan_uploader_add_buffer_barrier (GskVulkanUploader *self, gboolean after, const VkBufferMemoryBarrier *barrier) { GArray *array; if (after) array = self->after_buffer_barriers; else array = self->before_buffer_barriers; g_array_append_val (array, *barrier); } static VkCommandBuffer gsk_vulkan_uploader_get_copy_buffer (GskVulkanUploader *self) { if (self->copy_buffer == VK_NULL_HANDLE) self->copy_buffer = gsk_vulkan_command_pool_get_buffer (self->command_pool); return self->copy_buffer; } void gsk_vulkan_uploader_upload (GskVulkanUploader *self) { if (self->before_buffer_barriers->len > 0 || self->before_image_barriers->len > 0) { VkCommandBuffer command_buffer; command_buffer = gsk_vulkan_command_pool_get_buffer (self->command_pool); vkCmdPipelineBarrier (command_buffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, self->before_buffer_barriers->len, (VkBufferMemoryBarrier *) self->before_buffer_barriers->data, self->before_image_barriers->len, (VkImageMemoryBarrier *) self->before_image_barriers->data); gsk_vulkan_command_pool_submit_buffer (self->command_pool, command_buffer, 0, NULL, 0, NULL, VK_NULL_HANDLE); g_array_set_size (self->before_buffer_barriers, 0); g_array_set_size (self->before_image_barriers, 0); } /* append these to existing buffer */ if (self->after_buffer_barriers->len > 0 || self->after_image_barriers->len > 0) { VkCommandBuffer command_buffer = gsk_vulkan_uploader_get_copy_buffer (self); vkCmdPipelineBarrier (command_buffer, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, self->after_buffer_barriers->len, (VkBufferMemoryBarrier *) self->after_buffer_barriers->data, self->after_image_barriers->len, (VkImageMemoryBarrier *) self->after_image_barriers->data); g_array_set_size (self->after_buffer_barriers, 0); g_array_set_size (self->after_image_barriers, 0); } if (self->copy_buffer != VK_NULL_HANDLE) { gsk_vulkan_command_pool_submit_buffer (self->command_pool, self->copy_buffer, 0, NULL, 0, NULL, VK_NULL_HANDLE); self->copy_buffer = VK_NULL_HANDLE; } } void gsk_vulkan_uploader_reset (GskVulkanUploader *self) { g_array_set_size (self->before_image_barriers, 0); self->copy_buffer = VK_NULL_HANDLE; g_array_set_size (self->after_image_barriers, 0); g_slist_free_full (self->staging_image_free_list, g_object_unref); self->staging_image_free_list = NULL; g_slist_free_full (self->staging_buffer_free_list, (GDestroyNotify) gsk_vulkan_buffer_free); self->staging_buffer_free_list = NULL; } static GskVulkanImage * gsk_vulkan_image_new (GdkVulkanContext *context, gsize width, gsize height, VkImageTiling tiling, VkImageUsageFlags usage, VkImageLayout layout, VkAccessFlags access, VkMemoryPropertyFlags memory) { VkMemoryRequirements requirements; GskVulkanImage *self; self = g_object_new (GSK_TYPE_VULKAN_IMAGE, NULL); self->vulkan = g_object_ref (context); self->width = width; self->height = height; self->vk_usage = usage; self->vk_image_layout = layout; self->vk_access = access; GSK_VK_CHECK (vkCreateImage, gdk_vulkan_context_get_device (context), &(VkImageCreateInfo) { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .flags = 0, .imageType = VK_IMAGE_TYPE_2D, .format = VK_FORMAT_B8G8R8A8_UNORM, .extent = { width, height, 1 }, .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = tiling, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = self->vk_image_layout, }, NULL, &self->vk_image); vkGetImageMemoryRequirements (gdk_vulkan_context_get_device (context), self->vk_image, &requirements); self->memory = gsk_vulkan_memory_new (context, requirements.memoryTypeBits, memory, requirements.size); GSK_VK_CHECK (vkBindImageMemory, gdk_vulkan_context_get_device (context), self->vk_image, gsk_vulkan_memory_get_device_memory (self->memory), 0); return self; } static void gsk_vulkan_image_upload_data (GskVulkanImage *self, guchar *data, gsize width, gsize height, gsize data_stride) { VkImageSubresource image_res; VkSubresourceLayout image_layout; gsize mem_stride; guchar *mem; image_res.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; image_res.mipLevel = 0; image_res.arrayLayer = 0; mem_stride = width * 4; vkGetImageSubresourceLayout (gdk_vulkan_context_get_device (self->vulkan), self->vk_image, &image_res, &image_layout); mem = gsk_vulkan_memory_map (self->memory) + image_layout.offset; if (image_layout.rowPitch == width * 4 && data_stride == mem_stride) { memcpy (mem, data, data_stride * height); } else { for (gsize i = 0; i < height; i++) { memcpy (mem + i * image_layout.rowPitch, data + i * data_stride, width * 4); } } gsk_vulkan_memory_unmap (self->memory); } static void gsk_vulkan_image_ensure_view (GskVulkanImage *self, VkFormat format) { if (self->vk_image_view == VK_NULL_HANDLE) GSK_VK_CHECK (vkCreateImageView, gdk_vulkan_context_get_device (self->vulkan), &(VkImageViewCreateInfo) { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = self->vk_image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = format, .components = { .r = VK_COMPONENT_SWIZZLE_R, .g = VK_COMPONENT_SWIZZLE_G, .b = VK_COMPONENT_SWIZZLE_B, .a = VK_COMPONENT_SWIZZLE_A, }, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }, NULL, &self->vk_image_view); } static GskVulkanImage * gsk_vulkan_image_new_from_data_via_staging_buffer (GskVulkanUploader *uploader, guchar *data, gsize width, gsize height, gsize stride) { GskVulkanImage *self; GskVulkanBuffer *staging; gsize buffer_size = width * height * 4; guchar *mem; staging = gsk_vulkan_buffer_new_staging (uploader->vulkan, buffer_size); mem = gsk_vulkan_buffer_map (staging); if (stride == width * 4) { memcpy (mem, data, stride * height); } else { for (gsize i = 0; i < height; i++) { memcpy (mem + i * width * 4, data + i * stride, width * 4); } } gsk_vulkan_buffer_unmap (staging); gsk_vulkan_uploader_add_buffer_barrier (uploader, FALSE, &(VkBufferMemoryBarrier) { .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_HOST_WRITE_BIT, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .buffer = gsk_vulkan_buffer_get_buffer(staging), .offset = 0, .size = buffer_size, }); self = gsk_vulkan_image_new (uploader->vulkan, width, height, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_ACCESS_TRANSFER_WRITE_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); gsk_vulkan_uploader_add_image_barrier (uploader, FALSE, self, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT); vkCmdCopyBufferToImage (gsk_vulkan_uploader_get_copy_buffer (uploader), gsk_vulkan_buffer_get_buffer (staging), self->vk_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, (VkBufferImageCopy[1]) { { .bufferOffset = 0, .imageSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1 }, .imageOffset = { 0, 0, 0 }, .imageExtent = { .width = width, .height = height, .depth = 1 } } }); gsk_vulkan_uploader_add_image_barrier (uploader, TRUE, self, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_SHADER_READ_BIT); uploader->staging_buffer_free_list = g_slist_prepend (uploader->staging_buffer_free_list, staging); gsk_vulkan_image_ensure_view (self, VK_FORMAT_B8G8R8A8_UNORM); return self; } static GskVulkanImage * gsk_vulkan_image_new_from_data_via_staging_image (GskVulkanUploader *uploader, guchar *data, gsize width, gsize height, gsize stride) { GskVulkanImage *self, *staging; staging = gsk_vulkan_image_new (uploader->vulkan, width, height, VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_ACCESS_TRANSFER_WRITE_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); gsk_vulkan_image_upload_data (staging, data, width, height, stride); self = gsk_vulkan_image_new (uploader->vulkan, width, height, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_ACCESS_TRANSFER_WRITE_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); gsk_vulkan_uploader_add_image_barrier (uploader, FALSE, staging, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT); gsk_vulkan_uploader_add_image_barrier (uploader, FALSE, self, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT); vkCmdCopyImage (gsk_vulkan_uploader_get_copy_buffer (uploader), staging->vk_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, self->vk_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &(VkImageCopy) { .srcSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1 }, .srcOffset = { 0, 0, 0 }, .dstSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1 }, .dstOffset = { 0, 0, 0 }, .extent = { .width = width, .height = height, .depth = 1 } }); gsk_vulkan_uploader_add_image_barrier (uploader, TRUE, self, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_SHADER_READ_BIT); uploader->staging_image_free_list = g_slist_prepend (uploader->staging_image_free_list, staging); gsk_vulkan_image_ensure_view (self, VK_FORMAT_B8G8R8A8_UNORM); return self; } static GskVulkanImage * gsk_vulkan_image_new_from_data_directly (GskVulkanUploader *uploader, guchar *data, gsize width, gsize height, gsize stride) { GskVulkanImage *self; self = gsk_vulkan_image_new (uploader->vulkan, width, height, VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_ACCESS_HOST_WRITE_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); gsk_vulkan_image_upload_data (self, data, width, height, stride); gsk_vulkan_uploader_add_image_barrier (uploader, TRUE, self, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_SHADER_READ_BIT); gsk_vulkan_image_ensure_view (self, VK_FORMAT_B8G8R8A8_UNORM); return self; } GskVulkanImage * gsk_vulkan_image_new_from_data (GskVulkanUploader *uploader, guchar *data, gsize width, gsize height, gsize stride) { if (GSK_RENDER_MODE_CHECK (STAGING_BUFFER)) return gsk_vulkan_image_new_from_data_via_staging_buffer (uploader, data, width, height, stride); if (GSK_RENDER_MODE_CHECK (STAGING_IMAGE)) return gsk_vulkan_image_new_from_data_via_staging_image (uploader, data, width, height, stride); else return gsk_vulkan_image_new_from_data_directly (uploader, data, width, height, stride); } GskVulkanImage * gsk_vulkan_image_new_for_swapchain (GdkVulkanContext *context, VkImage image, VkFormat format, gsize width, gsize height) { GskVulkanImage *self; self = g_object_new (GSK_TYPE_VULKAN_IMAGE, NULL); self->vulkan = g_object_ref (context); self->width = width; self->height = height; self->vk_image = image; gsk_vulkan_image_ensure_view (self, VK_FORMAT_B8G8R8A8_UNORM); return self; } GskVulkanImage * gsk_vulkan_image_new_for_framebuffer (GdkVulkanContext *context, gsize width, gsize height) { GskVulkanImage *self; self = gsk_vulkan_image_new (context, width, height, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); gsk_vulkan_image_ensure_view (self, VK_FORMAT_B8G8R8A8_UNORM); return self; } GskVulkanImage * gsk_vulkan_image_new_for_atlas (GdkVulkanContext *context, gsize width, gsize height) { GskVulkanImage *self; self = gsk_vulkan_image_new (context, width, height, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_LAYOUT_UNDEFINED, 0, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); gsk_vulkan_image_ensure_view (self, VK_FORMAT_B8G8R8A8_UNORM); return self; } GskVulkanImage * gsk_vulkan_image_new_for_texture (GdkVulkanContext *context, gsize width, gsize height) { GskVulkanImage *self; self = gsk_vulkan_image_new (context, width, height, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_IMAGE_LAYOUT_UNDEFINED, 0, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); gsk_vulkan_image_ensure_view (self, VK_FORMAT_B8G8R8A8_UNORM); return self; } GdkTexture * gsk_vulkan_image_download (GskVulkanImage *self, GskVulkanUploader *uploader) { GskVulkanBuffer *buffer; GdkTexture *texture; guchar *mem; gsk_vulkan_uploader_add_image_barrier (uploader, FALSE, self, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT); buffer = gsk_vulkan_buffer_new_download (self->vulkan, self->width * self->height * 4); vkCmdCopyImageToBuffer (gsk_vulkan_uploader_get_copy_buffer (uploader), self->vk_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, gsk_vulkan_buffer_get_buffer (buffer), 1, (VkBufferImageCopy[1]) { { .bufferOffset = 0, .imageSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1 }, .imageOffset = { 0, 0, 0 }, .imageExtent = { .width = self->width, .height = self->height, .depth = 1 } } }); gsk_vulkan_uploader_upload (uploader); GSK_VK_CHECK (vkQueueWaitIdle, gdk_vulkan_context_get_queue (self->vulkan)); mem = gsk_vulkan_buffer_map (buffer); texture = gdk_texture_new_for_data (mem, self->width, self->height, self->width * 4); gsk_vulkan_buffer_unmap (buffer); gsk_vulkan_buffer_free (buffer); return texture; } void gsk_vulkan_image_upload_regions (GskVulkanImage *self, GskVulkanUploader *uploader, guint num_regions, GskImageRegion *regions) { GskVulkanBuffer *staging; guchar *mem; guchar *m; gsize size; gsize offset; VkBufferImageCopy *bufferImageCopy; size = 0; for (int i = 0; i < num_regions; i++) size += regions[i].width * regions[i].height * 4; staging = gsk_vulkan_buffer_new_staging (uploader->vulkan, size); mem = gsk_vulkan_buffer_map (staging); bufferImageCopy = alloca (sizeof (VkBufferImageCopy) * num_regions); memset (bufferImageCopy, 0, sizeof (VkBufferImageCopy) * num_regions); offset = 0; for (int i = 0; i < num_regions; i++) { m = mem + offset; if (regions[i].stride == regions[i].width * 4) { memcpy (m, regions[i].data, regions[i].stride * regions[i].height); } else { for (gsize r = 0; r < regions[i].height; i++) memcpy (m + r * regions[i].width * 4, regions[i].data + r * regions[i].stride, regions[i].width * 4); } bufferImageCopy[i].bufferOffset = offset; bufferImageCopy[i].imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; bufferImageCopy[i].imageSubresource.mipLevel = 0; bufferImageCopy[i].imageSubresource.baseArrayLayer = 0; bufferImageCopy[i].imageSubresource.layerCount = 1; bufferImageCopy[i].imageOffset.x = regions[i].x; bufferImageCopy[i].imageOffset.y = regions[i].y; bufferImageCopy[i].imageOffset.z = 0; bufferImageCopy[i].imageExtent.width = regions[i].width; bufferImageCopy[i].imageExtent.height = regions[i].height; bufferImageCopy[i].imageExtent.depth = 1; offset += regions[i].width * regions[i].height * 4; } gsk_vulkan_buffer_unmap (staging); gsk_vulkan_uploader_add_image_barrier (uploader, FALSE, self, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT); vkCmdCopyBufferToImage (gsk_vulkan_uploader_get_copy_buffer (uploader), gsk_vulkan_buffer_get_buffer (staging), self->vk_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, num_regions, bufferImageCopy); gsk_vulkan_uploader_add_image_barrier (uploader, TRUE, self, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_SHADER_READ_BIT); uploader->staging_buffer_free_list = g_slist_prepend (uploader->staging_buffer_free_list, staging); gsk_vulkan_image_ensure_view (self, VK_FORMAT_B8G8R8A8_UNORM); } static void gsk_vulkan_image_finalize (GObject *object) { GskVulkanImage *self = GSK_VULKAN_IMAGE (object); if (self->vk_image_view != VK_NULL_HANDLE) { vkDestroyImageView (gdk_vulkan_context_get_device (self->vulkan), self->vk_image_view, NULL); } /* memory is NULL for for_swapchain() images, where we don't own * the VkImage */ if (self->memory) { vkDestroyImage (gdk_vulkan_context_get_device (self->vulkan), self->vk_image, NULL); gsk_vulkan_memory_free (self->memory); } g_object_unref (self->vulkan); G_OBJECT_CLASS (gsk_vulkan_image_parent_class)->finalize (object); } static void gsk_vulkan_image_class_init (GskVulkanImageClass *klass) { G_OBJECT_CLASS (klass)->finalize = gsk_vulkan_image_finalize; } static void gsk_vulkan_image_init (GskVulkanImage *self) { } gsize gsk_vulkan_image_get_width (GskVulkanImage *self) { return self->width; } gsize gsk_vulkan_image_get_height (GskVulkanImage *self) { return self->height; } VkImage gsk_vulkan_image_get_image (GskVulkanImage *self) { return self->vk_image; } VkImageView gsk_vulkan_image_get_image_view (GskVulkanImage *self) { return self->vk_image_view; }