Add texture handler

Added functionality to load images as textures and bind them to meshes.
This commit is contained in:
Piotr Krygier committed 2023-10-02 13:21:17 +02:00
1 parent 0e040dd0d5
commit 4fcda01ba2
24 files changed
+897 -200

No files matched your search

+332
View File
@@ -0,0 +1,332 @@
#include "rse_vulkan_image.h"
#include "utilities/file_utils.h"
#include "utilities/logger.h"
#include "locale_vulkan.h"
#include "vulkan_errors.h"
#include "rse_vulkan_commands.h"
#include "vulkan_buffers.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include "vma/vk_mem_alloc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define IMAGE_TAKEN 1U
#define IMAGE_FREE 0U
/**
* @brief Wrapper for Vulkan buffer. Holds allocation data;
*
*/
struct rse_vulkan_image_t
{
uint8_t id_taken;
size_t allocated_size;
VkImage image;
VkImageView image_view;
VmaAllocation allocation;
VmaAllocationInfo allocation_info;
};
/* For now we will only have one image view per image */
extern struct rse_vulkan_state_t g_vulkan_state;
struct rse_vulkan_image_t g_texture_images[RSE_MAX_IMAGE_COUNT];
VkSampler g_sampler;
static uint8_t create_sampler()
{
VkSamplerCreateInfo create_info = {0};
create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
create_info.pNext = NULL;
create_info.flags = 0U;
create_info.magFilter = VK_FILTER_LINEAR;
create_info.minFilter = VK_FILTER_LINEAR;
create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
create_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
create_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
create_info.mipLodBias = 0.0f;
create_info.anisotropyEnable = VK_FALSE; // TODO: Enable this
create_info.maxAnisotropy = 1.0f;
create_info.compareEnable = VK_FALSE;
create_info.compareOp = VK_COMPARE_OP_ALWAYS;
create_info.minLod = 0.0f;
create_info.maxLod = 0.0f;
create_info.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
create_info.unnormalizedCoordinates = VK_FALSE;
if (VK_SUCCESS != vkCreateSampler(g_vulkan_state.device,
&create_info,
NULL,
&g_sampler)) {
LOGF("Failed to create image view");
return 1;
}
return VULKAN_ERROR_NO_ERROR;
}
static VkDeviceSize format_to_pixel_size(VkFormat format)
{
switch (format)
{
case VK_FORMAT_R8G8B8A8_SRGB:
return 4;
break;
default:
LOGE("Failed translate vulkan format to pixel width");
break;
}
return 0;
}
static uint8_t create_vulkan_image(uint32_t width, uint32_t height, VkFormat format, unsigned char* pixels)
{
struct rse_vulkan_buffer_t staging_buffer = {0};
VkDeviceSize staging_buffer_size = 0U;
VkImageCreateInfo image_info = {0};
VmaAllocationCreateInfo alloc_create_info = {0};
VkImageSubresourceRange range = {0};
VkImageMemoryBarrier image_barrier = {0};
VkCommandBuffer command_buffer = {0};
VkBufferImageCopy copy_region = {0};
VkExtent3D image_extent = {0};
VkImageViewCreateInfo image_view_create_info = {0};
struct rse_vulkan_image_t* free_texture_image = NULL;
size_t i = 0U;
while(free_texture_image == NULL || i > RSE_MAX_IMAGE_COUNT) {
if(g_texture_images[i].id_taken == IMAGE_FREE) {
free_texture_image = &g_texture_images[i];
} else {
++i;
}
}
/* Check if we found free image handle */
if (free_texture_image == NULL) {
LOGE("Failed to find free texture image handle");
return 1;
}
staging_buffer_size = width * height * format_to_pixel_size(format);
image_extent.width = width;
image_extent.height = height;
image_extent.depth = 1U;
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.pNext = NULL;
image_info.flags = 0U;
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.format = format;
image_info.extent = image_extent;
image_info.mipLevels = 1U;
image_info.arrayLayers = 1U;
image_info.samples = VK_SAMPLE_COUNT_1_BIT; //TODO: Enable multisampling
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; //TODO: Change if used by more than one queue family
image_info.queueFamilyIndexCount = 0U;
image_info.pQueueFamilyIndices = NULL;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
alloc_create_info.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
alloc_create_info.usage = VMA_MEMORY_USAGE_AUTO;
alloc_create_info.requiredFlags = 0U;
alloc_create_info.preferredFlags = 0U;
alloc_create_info.memoryTypeBits = 0U;
alloc_create_info.pool = NULL;
alloc_create_info.pUserData = NULL;
alloc_create_info.priority = 1.0f;
if (VK_SUCCESS != vmaCreateImage(g_vulkan_state.allocator,
&image_info,
&alloc_create_info,
&free_texture_image->image,
&free_texture_image->allocation,
&free_texture_image->allocation_info)) {
LOGF("Failed to create image");
return 1;
}
/* Put image into correct layout to copy pixels from buffer to image memory */
range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
range.baseMipLevel = 0;
range.levelCount = 1;
range.baseArrayLayer = 0;
range.layerCount = 1;
image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_barrier.pNext = NULL;
image_barrier.srcAccessMask = 0U;
image_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
image_barrier.srcQueueFamilyIndex = 0U;
image_barrier.dstQueueFamilyIndex = 0U;
image_barrier.image = free_texture_image->image;
image_barrier.subresourceRange = range;
command_buffer = rse_begin_single_time_command();
vkCmdPipelineBarrier(command_buffer,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0,
0,
NULL,
0,
NULL,
1,
&image_barrier);
rse_end_single_time_comands(command_buffer);
/* Copy pixel data to GPU memory */
if (VK_SUCCESS != create_buffer(staging_buffer_size,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_HOST,
VMA_ALLOCATION_CREATE_MAPPED_BIT
| VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
&staging_buffer)) {
LOGF(vulkan_error_messages[VULKAN_BUFFER_CREATION_FAILED]);
return VULKAN_ERROR_BUFFER_CREATION_FAILED;
}
memcpy(staging_buffer.allocation_info.pMappedData, pixels, staging_buffer_size);
copy_region.bufferOffset = 0U;
copy_region.bufferRowLength = 0U;
copy_region.bufferImageHeight = 0U;
copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy_region.imageSubresource.mipLevel = 0U;
copy_region.imageSubresource.baseArrayLayer = 0U;
copy_region.imageSubresource.layerCount = 1U;
copy_region.imageOffset.x = 0;
copy_region.imageOffset.y = 0;
copy_region.imageOffset.z = 0;
copy_region.imageExtent = image_extent;
command_buffer = rse_begin_single_time_command();
vkCmdCopyBufferToImage(command_buffer,
staging_buffer.buffer,
free_texture_image->image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1,
&copy_region);
rse_end_single_time_comands(command_buffer);
destroy_buffer(&staging_buffer);
/* Change image layout for shaders to be able to read it */
image_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
image_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
image_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
image_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
command_buffer = rse_begin_single_time_command();
vkCmdPipelineBarrier(command_buffer,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0,
0,
NULL,
0,
NULL,
1,
&image_barrier);
rse_end_single_time_comands(command_buffer);
/* Fill image view */
image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image_view_create_info.pNext = NULL;
image_view_create_info.flags = 0U;
image_view_create_info.image = free_texture_image->image;
image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
image_view_create_info.format = format;
// image_view_create_info.components = 0;
image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_view_create_info.subresourceRange.baseMipLevel = 0;
image_view_create_info.subresourceRange.levelCount = 1;
image_view_create_info.subresourceRange.baseArrayLayer = 0;
image_view_create_info.subresourceRange.layerCount = 1;
if (VK_SUCCESS != vkCreateImageView(g_vulkan_state.device,
&image_view_create_info,
NULL,
&free_texture_image->image_view)) {
LOGF("Failed to create image view");
return 1;
}
free_texture_image->id_taken = IMAGE_TAKEN;
return VULKAN_ERROR_NO_ERROR;
}
uint8_t init_vulkan_images()
{
uint8_t status = VULKAN_ERROR_NO_ERROR;
// memset(g_texture_images, 0, sizeof(struct rse_vulkan_image_t) * RSE_MAX_IMAGE_COUNT);
STATUS_CHECK(create_sampler());
return status;
}
void rse_load_texture(const char* file_path)
{
int width = 0;
int height = 0;
int channels = 0;
stbi_uc* pixel_buffer = NULL;
pixel_buffer = stbi_load(file_path, &width, &height, &channels, STBI_rgb_alpha);
if (pixel_buffer == NULL) {
LOGE("Failed to load texture from file: %s", file_path);
return;
}
/* Create actual Vulkan Image */
create_vulkan_image(width,
height,
VK_FORMAT_R8G8B8A8_SRGB,
pixel_buffer);
stbi_image_free(pixel_buffer);
}
void rse_destroy_textures()
{
size_t i = 0U;
vkDestroySampler(g_vulkan_state.device, g_sampler, NULL);
for (i = 0U; i < RSE_MAX_IMAGE_COUNT; ++i) {
if (g_texture_images[i].id_taken == IMAGE_TAKEN) {
vkDestroyImageView(g_vulkan_state.device, g_texture_images[i].image_view, NULL);
vmaDestroyImage(g_vulkan_state.allocator, g_texture_images[i].image, g_texture_images[i].allocation);
}
}
}
VkImageView rse_get_image_view(size_t image_id)
{
return g_texture_images[image_id].image_view;
}
VkSampler rse_get_texture_sampler()
{
return g_sampler;
}