Add texture handler
Added functionality to load images as textures and bind them to meshes.
This commit is contained in:
1 parent
0e040dd0d5
commit
4fcda01ba2
24 files changed
+897
-200
No files matched your search
@@ -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,
|
||||
©_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;
|
||||
}
|
||||
Reference in new issue
Block a user