Added textures

Textures are stored in a global hashtable. Each mesh pipeline has access
to a single texture. The texture may be used as an atlas by creating
meshes with different texture coordinates. The current system should be
fine for text glyphs. However, if you have an atlas where each
subdivision is equal in its dimensions, the use of layers and the
VK_IMAGE_VIEW_TYPE_2D_ARRAY view type would be best. Some preliminary
code for this was completed, but for now inserting textures into the
hashmap defaults to VK_IMAGE_VIEW_TYPE_2D.
This commit is contained in:
FroggyGreen 2024-05-22 09:57:13 -05:00
parent 6e32539435
commit b0dd2cdf3a
9 changed files with 661 additions and 45 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "external/glfw"]
path = external/glfw
url = https://github.com/glfw/glfw
[submodule "external/stb"]
path = external/stb
url = https://github.com/nothings/stb

1
external/stb vendored Submodule

@ -0,0 +1 @@
Subproject commit ae721c50eaf761660b4f90cc590453cdb0c2acd0

View File

@ -44,3 +44,6 @@ target_link_libraries(sofrenderer PRIVATE glfw)
# Vulkan
target_include_directories(sofrenderer PRIVATE ${Vulkan_INCLUDE_DIRS})
target_link_libraries(sofrenderer PRIVATE ${Vulkan_LIBRARIES})
# stb
target_include_directories(sofrenderer PRIVATE "${PROJECT_SOURCE_DIR}/../external/stb")

View File

@ -0,0 +1,13 @@
#version 460
layout(location = 0) in vec4 fragColor;
layout(location = 1) in vec2 fragTex;
layout(location = 0) out vec4 outColor;
layout(set = 1, binding = 0) uniform sampler2D texSampler;
void main()
{
outColor = texture(texSampler, fragTex);
}

View File

@ -0,0 +1,29 @@
#version 460
// vertex geometry data
layout(location = 0) in vec2 inPos;
layout(location = 1) in vec2 inTex;
// instance data
layout(location = 2) in vec3 inInstancePos;
layout(location = 3) in vec3 inInstanceScale;
layout(location = 4) in vec4 inInstanceColor;
layout(location = 0) out vec4 fragColor;
layout(location = 1) out vec2 fragTex;
layout(binding = 0) uniform Camera
{
mat4 view;
mat4 perspective;
mat4 orthographic;
} camera;
void main()
{
vec3 pos = vec3(inPos, 0.0) * inInstanceScale + inInstancePos;
gl_Position = camera.orthographic * vec4(pos, 1.0);
fragColor = inInstanceColor;
fragTex = inTex;
}

View File

@ -27,6 +27,12 @@ You should have received a copy of the GNU General Public License along with Sha
#include <vulkan/vulkan.h>
#include <GLFW/glfw3.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#define MAX_FRAMES_IN_FLIGHT 2 // must be a power of two!
#define MAX_TEXTURES 256
typedef struct vulkan_frame_data
{
VkCommandBuffer command_buffer;
@ -46,6 +52,9 @@ typedef struct vulkan_buffer
typedef struct vulkan_image
{
u32 width;
u32 height;
u32 layer_count;
VkImage handle;
VkDeviceMemory memory;
VkImageView view_handle;
@ -60,6 +69,15 @@ typedef struct renderer_camera
} renderer_camera;
typedef struct texture
{
vulkan_image image;
VkSampler sampler;
VkFormat format;
VkDescriptorSet descriptor_sets[MAX_FRAMES_IN_FLIGHT];
} texture;
typedef struct mesh
{
vulkan_buffer vertex_buffer;
@ -80,11 +98,11 @@ typedef struct mesh_pipeline
vertex_input_type geometry_type;
vertex_input_type instance_type;
hash_table meshes;
char texture_name[HASH_TABLE_KEY_LEN];
} mesh_pipeline;
#define MAX_FRAMES_IN_FLIGHT 2 // must be a power of two!
typedef struct vulkan_context
{
db_list deletion_stack;
@ -95,7 +113,7 @@ typedef struct vulkan_context
VkDebugUtilsMessengerEXT debug_messenger;
#endif
VkSurfaceKHR surface;
struct
{
VkPhysicalDevice physical;
@ -139,6 +157,11 @@ typedef struct vulkan_context
VkDescriptorSetLayout camera_descriptor_set_layout;
VkDescriptorPool camera_descriptor_pool;
VkDescriptorSet camera_descriptor_sets[MAX_FRAMES_IN_FLIGHT];
VkSampler sampler;
VkDescriptorSetLayout texture_descriptor_set_layout;
VkDescriptorPool texture_descriptor_pool;
hash_table textures;
hash_table mesh_pipelines;
@ -172,7 +195,10 @@ static vulkan_context context;
#ifdef RENDERER_DEBUG
static const char* validation_layers[] = { "VK_LAYER_KHRONOS_validation" };
#endif
static const char* device_extensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
static const char* device_extensions[] =
{
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
//
// Window
@ -247,6 +273,11 @@ void poll_window_input()
// Vulkan
//
static VkDeviceSize aligned_size(VkDeviceSize old_size, VkDeviceSize alignment)
{
return (old_size + alignment - 1) & ~(alignment - 1);
}
static void signal_for_shutdown(void (*fn)())
{
db_list_pushback(&context.deletion_stack, fn);
@ -389,6 +420,13 @@ static u8 init_vulkan_instance()
#ifdef RENDERER_DEBUG
if (!check_validation_layer_support()) return 0;
#endif
u32 api_version = VK_API_VERSION_1_3;
if (vkEnumerateInstanceVersion(&api_version) != VK_SUCCESS)
{
trace_log(LOG_FATAL, "Failed to create Vulkan instance; VK_API_VERSION_1_3 is not supported.");
return 0;
}
const VkApplicationInfo app_info =
{
@ -398,7 +436,7 @@ static u8 init_vulkan_instance()
.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
.pEngineName = "No Engine",
.engineVersion = VK_MAKE_VERSION(1, 0, 0),
.apiVersion = VK_API_VERSION_1_0
.apiVersion = api_version
};
VkInstanceCreateInfo create_info =
@ -655,6 +693,20 @@ static u8 pick_physical_device()
// discrete GPUs are more performant than integrated GPUs
if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) score += 1000;
// check anisotropic filtering
if (features.samplerAnisotropy != VK_TRUE)
{
score = 0;
trace_log(LOG_WARN, "The physical device '%s' does not support anisotropic filtering.", properties.deviceName);
}
// check Vulkan 1.3 support
if (properties.apiVersion < VK_API_VERSION_MINOR(VK_API_VERSION_1_3))
{
score = 0;
trace_log(LOG_WARN, "The physical device '%s' does not support Vulkan 1.3.", properties.deviceName);
}
// check queue requirements
const queue_family_indices indices = find_queue_families(physical_devices[i]);
if (!indices.is_graphics || !indices.is_present)
@ -663,10 +715,11 @@ static u8 pick_physical_device()
trace_log(LOG_WARN, "The physical device '%s' does not meet queue requirements.", properties.deviceName);
}
// check swap chain requirements
u8 extensions_supported = check_physical_device_extension_support(physical_devices[i]);
const u8 extensions_supported = check_physical_device_extension_support(physical_devices[i]);
if (extensions_supported)
{
// check swap chain requirements
swap_chain_support_details support_details;
if (!init_swap_chain_support_details(&support_details, physical_devices[i]) || !support_details.format_size || !support_details.present_mode_size)
{
@ -732,29 +785,35 @@ static u8 init_logical_device()
.queueCount = 1,
.pQueuePriorities = &priority
};
VkPhysicalDeviceFeatures2 physical_device_features =
{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.features = {},
.pNext = NULL
};
VkPhysicalDeviceFeatures available_features;
vkGetPhysicalDeviceFeatures(context.device.physical, &available_features);
physical_device_features.features.fillModeNonSolid = available_features.fillModeNonSolid;
physical_device_features.features.samplerAnisotropy = VK_TRUE;
VkPhysicalDeviceFeatures device_features = {};
{
VkPhysicalDeviceFeatures available_features;
vkGetPhysicalDeviceFeatures(context.device.physical, &available_features);
device_features.fillModeNonSolid = available_features.fillModeNonSolid;
}
VkDeviceCreateInfo device_info =
const VkDeviceCreateInfo device_info =
{
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pQueueCreateInfos = queue_infos,
.queueCreateInfoCount = unique_queue_family_size,
.pEnabledFeatures = &device_features,
.pEnabledFeatures = NULL,
.enabledExtensionCount = sizeof(device_extensions) / sizeof(const char*),
.ppEnabledExtensionNames = device_extensions,
#ifdef RENDERER_DEBUG
.enabledLayerCount = sizeof(validation_layers) / sizeof(const char*),
.ppEnabledLayerNames = validation_layers
.ppEnabledLayerNames = validation_layers,
#else
.enabledLayerCount = 0,
.ppEnabledLayerNames = NULL
.ppEnabledLayerNames = NULL,
#endif
.flags = 0,
.pNext = &physical_device_features
};
if (vkCreateDevice(context.device.physical, &device_info, context.allocator, &context.device.logical) != VK_SUCCESS)
@ -770,7 +829,6 @@ static u8 init_logical_device()
return 1;
}
static u8 allocate_command_buffers(VkCommandPool command_pool, VkCommandBuffer* command_buffers, u32 command_buffer_count)
{
const VkCommandBufferAllocateInfo alloc_info =
@ -791,7 +849,6 @@ static u8 allocate_command_buffers(VkCommandPool command_pool, VkCommandBuffer*
return 1;
}
static u8 begin_command_recording(VkCommandBuffer command_buffer, VkCommandBufferUsageFlags flags)
{
const VkCommandBufferBeginInfo info =
@ -1027,8 +1084,8 @@ static u8 copy_vulkan_buffer(vulkan_buffer* dst, vulkan_buffer* src, VkDeviceSiz
vkCmdCopyBuffer(command_buffer, src->handle, dst->handle, 1, &copy_region);
if (!end_single_submit_command_recording(command_buffer)) return 0;
vkFreeCommandBuffers(context.device.logical, context.single_submit_context.command_pool, 1, &command_buffer);
return 1;
}
@ -1047,7 +1104,6 @@ static u8 find_vulkan_memory_type(u32* type_index, u32 type_filter, VkMemoryProp
return 0;
}
static u8 init_image_view(VkImageView* view, VkImage image, VkImageViewType type, VkFormat format, VkImageAspectFlags flags, u32 layer_count)
{
const VkImageSubresourceRange subresource_range =
@ -1087,7 +1143,10 @@ static u8 init_vulkan_image(vulkan_image* image, u32 width, u32 height, u32 laye
VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties,
VkImageViewType view_type, VkImageAspectFlags aspect_flags)
{
VkImageCreateInfo image_info =
image->width = width;
image->height = height;
image->layer_count = layer_count;
const VkImageCreateInfo image_info =
{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
@ -1153,6 +1212,155 @@ static void shutdown_vulkan_image(vulkan_image* image)
vkDestroyImage(context.device.logical, image->handle, context.allocator);
}
static u8 transition_image_layout(vulkan_image* image, VkImageLayout old_layout, VkImageLayout new_layout)
{
VkCommandBuffer command_buffer;
if (!allocate_command_buffers(context.single_submit_context.command_pool, &command_buffer, 1)) return 0;
if (!begin_single_submit_command_recording(command_buffer)) return 0;
VkImageMemoryBarrier barrier =
{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.oldLayout = old_layout,
.newLayout = new_layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image->handle,
.subresourceRange = (VkImageSubresourceRange)
{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = image->layer_count
},
.pNext = NULL
};
VkPipelineStageFlags src_stage = VK_PIPELINE_STAGE_NONE;
VkPipelineStageFlags dst_stage = VK_PIPELINE_STAGE_NONE;
if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED &&
new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
{
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
src_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
dst_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
}
else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL &&
new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
{
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
src_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
dst_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
}
else
{
trace_log(LOG_ERROR, "Unsupported image layout transition.");
return 0;
}
vkCmdPipelineBarrier(command_buffer, src_stage, dst_stage, 0, 0, NULL, 0, NULL, 1, &barrier);
if (!end_single_submit_command_recording(command_buffer)) return 0;
vkFreeCommandBuffers(context.device.logical, context.single_submit_context.command_pool, 1, &command_buffer);
return 1;
}
static u8 load_image_2D(vulkan_image* image, u32 offset_x, u32 offset_y, u32 size_x, u32 size_y, const void* pixels, u64 byte_size)
{
vulkan_buffer staging_buffer;
if (!init_vulkan_staging_buffer(&staging_buffer, byte_size)) return 0;
if (!load_vulkan_buffer(&staging_buffer, 0, byte_size, 0, pixels)) return 0;
VkCommandBuffer command_buffer;
if (!allocate_command_buffers(context.single_submit_context.command_pool, &command_buffer, 1)) return 0;
if (!begin_single_submit_command_recording(command_buffer)) return 0;
if (!transition_image_layout(image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL))
return 0;
const VkBufferImageCopy copy_region =
{
.bufferOffset = 0,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource = (VkImageSubresourceLayers)
{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = image->layer_count
},
.imageOffset = (VkOffset3D){ offset_x, offset_y, 0 },
.imageExtent = (VkExtent3D){ size_x, size_y, 1 }
};
vkCmdCopyBufferToImage(command_buffer, staging_buffer.handle, image->handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy_region);
if (!end_single_submit_command_recording(command_buffer)) return 0;
if (!transition_image_layout(image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL))
return 0;
vkFreeCommandBuffers(context.device.logical, context.single_submit_context.command_pool, 1, &command_buffer);
shutdown_vulkan_buffer(&staging_buffer);
return 1;
}
static u8 load_image_2D_array(vulkan_image* image, u32 size_x, u32 size_y, const void* pixels, u64 byte_size)
{
vulkan_buffer staging_buffer;
if (!init_vulkan_staging_buffer(&staging_buffer, byte_size)) return 0;
if (!load_vulkan_buffer(&staging_buffer, 0, byte_size, 0, pixels)) return 0;
VkCommandBuffer command_buffer;
if (!allocate_command_buffers(context.single_submit_context.command_pool, &command_buffer, 1)) return 0;
if (!begin_single_submit_command_recording(command_buffer)) return 0;
if (!transition_image_layout(image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL))
return 0;
VkBufferImageCopy* copy_regions = malloc(image->layer_count * sizeof(VkBufferImageCopy));
for (u32 i = 0; i < image->layer_count; i++)
{
copy_regions[i] = (VkBufferImageCopy)
{
.bufferOffset = i * byte_size / image->layer_count,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource = (VkImageSubresourceLayers)
{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = 0,
.baseArrayLayer = i,
.layerCount = 1
},
.imageOffset = (VkOffset3D){ 0, 0, 0 },
.imageExtent = (VkExtent3D){ size_x, size_y, 1 }
};
}
vkCmdCopyBufferToImage(command_buffer, staging_buffer.handle, image->handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, image->layer_count, copy_regions);
if (!transition_image_layout(image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL))
return 0;
if (!end_single_submit_command_recording(command_buffer)) return 0;
free(copy_regions);
vkFreeCommandBuffers(context.device.logical, context.single_submit_context.command_pool, 1, &command_buffer);
shutdown_vulkan_buffer(&staging_buffer);
return 1;
}
static VkSurfaceFormatKHR choose_surface_format(const VkSurfaceFormatKHR* available_formats, u32 available_format_size)
{
for (u32 i = 0; i < available_format_size; i++)
@ -1593,7 +1801,7 @@ static u8 init_camera()
}
// descriptor set layout
const VkDescriptorSetLayoutBinding camera_layout_binding =
const VkDescriptorSetLayoutBinding descriptor_layout_binding =
{
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
@ -1605,7 +1813,7 @@ static u8 init_camera()
{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = 1,
.pBindings = &camera_layout_binding,
.pBindings = &descriptor_layout_binding,
.flags = 0,
.pNext = NULL
};
@ -1650,7 +1858,7 @@ static u8 init_camera()
};
if (vkAllocateDescriptorSets(context.device.logical, &alloc_info, context.camera_descriptor_sets) != VK_SUCCESS)
{
trace_log(LOG_FATAL, "Failed to create Vulkan camera; failed to allocated descriptor sets");
trace_log(LOG_FATAL, "Failed to create Vulkan camera; failed to allocate descriptor sets.");
return 0;
}
for (u8 i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
@ -1702,6 +1910,211 @@ void set_camera_orthographic_projection(const f32* orthographic)
context.update_camera_uniform_counter = MAX_FRAMES_IN_FLIGHT;
}
static u8 init_texture(texture* t, u32 width, u32 height, VkImageViewType view_type, VkFormat format, u32 layer_count)
{
if (!init_vulkan_image(&t->image, width, height, layer_count, format, VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, view_type, VK_IMAGE_ASPECT_COLOR_BIT))
return 0;
if (!transition_image_layout(&t->image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL))
return 0;
if (!transition_image_layout(&t->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL))
return 0;
t->sampler = context.sampler;
t->format = format;
// descriptor sets
VkDescriptorSetLayout* descriptor_set_layouts = malloc(MAX_FRAMES_IN_FLIGHT * sizeof(VkDescriptorSetLayout));
for (u8 i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
descriptor_set_layouts[i] = context.texture_descriptor_set_layout;
const VkDescriptorSetAllocateInfo alloc_info =
{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.descriptorPool = context.texture_descriptor_pool,
.descriptorSetCount = MAX_FRAMES_IN_FLIGHT,
.pSetLayouts = descriptor_set_layouts,
.pNext = NULL
};
if (vkAllocateDescriptorSets(context.device.logical, &alloc_info, t->descriptor_sets) != VK_SUCCESS)
{
trace_log(LOG_FATAL, "Failed to create texture; failed to allocate descriptor sets.");
return 0;
}
for (u8 i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
{
const VkDescriptorImageInfo image_info =
{
.sampler = t->sampler,
.imageView = t->image.view_handle,
.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
};
const VkWriteDescriptorSet write =
{
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = t->descriptor_sets[i],
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = 1,
.pImageInfo = &image_info,
.pBufferInfo = NULL,
.pTexelBufferView = NULL,
.pNext = NULL
};
vkUpdateDescriptorSets(context.device.logical, 1, &write, 0, NULL);
}
free(descriptor_set_layouts);
return 1;
}
static void shutdown_texture(texture* t)
{
vkFreeDescriptorSets(context.device.logical, context.texture_descriptor_pool, MAX_FRAMES_IN_FLIGHT, t->descriptor_sets);
shutdown_vulkan_image(&t->image);
}
static void shutdown_textures()
{
for(u32 i = 0; i < context.textures.slot_count; i++)
for (hash_table_entry* entry = context.textures.entries[i]; entry != NULL; entry = entry->next)
{
texture* t = entry->data;
shutdown_texture(t);
}
shutdown_hash_table(&context.textures);
vkDestroyDescriptorPool(context.device.logical, context.texture_descriptor_pool, context.allocator);
vkDestroyDescriptorSetLayout(context.device.logical, context.texture_descriptor_set_layout, context.allocator);
vkDestroySampler(context.device.logical, context.sampler, context.allocator);
trace_log(LOG_DEBUG, "Succesfully destroyed textures.");
}
static u8 init_textures()
{
VkPhysicalDeviceProperties properties;
vkGetPhysicalDeviceProperties(context.device.physical, &properties);
VkSamplerCreateInfo sampler_info =
{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.magFilter = VK_FILTER_LINEAR,
.minFilter = VK_FILTER_LINEAR,
.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT,
.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT,
.anisotropyEnable = VK_TRUE,
.maxAnisotropy = properties.limits.maxSamplerAnisotropy,
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
.unnormalizedCoordinates = VK_FALSE,
.compareOp = VK_COMPARE_OP_ALWAYS,
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
.mipLodBias = 0.0f,
.minLod = 0.0f,
.maxLod = 0.0f,
.pNext = NULL
};
if (vkCreateSampler(context.device.logical, &sampler_info, context.allocator, &context.sampler) != VK_SUCCESS)
{
trace_log(LOG_FATAL, "Failed texture initialization; failed to create sampler.");
return 0;
}
// descriptor set layout
const VkDescriptorSetLayoutBinding descriptor_layout_binding =
{
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
.pImmutableSamplers = NULL
};
const VkDescriptorSetLayoutCreateInfo descriptor_layout_info =
{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = 1,
.pBindings = &descriptor_layout_binding,
.flags = 0,
.pNext = NULL
};
if (vkCreateDescriptorSetLayout(context.device.logical, &descriptor_layout_info, context.allocator, &context.texture_descriptor_set_layout) != VK_SUCCESS)
{
trace_log(LOG_FATAL, "Failed texture initialization; failed to create descriptor set layout.");
return 0;
}
// descriptor pool
const VkDescriptorPoolSize pool_size =
{
.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = MAX_FRAMES_IN_FLIGHT
};
const VkDescriptorPoolCreateInfo descriptor_pool_info =
{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.poolSizeCount = 1,
.pPoolSizes = &pool_size,
.maxSets = MAX_FRAMES_IN_FLIGHT * MAX_TEXTURES,
.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
.pNext = NULL
};
if (vkCreateDescriptorPool(context.device.logical, &descriptor_pool_info, context.allocator, &context.texture_descriptor_pool) != VK_SUCCESS)
{
trace_log(LOG_FATAL, "Failed texture initialization; failed to create descriptor pool.");
return 0;
}
init_hash_table(&context.textures, 32, sizeof(texture), basic_hash);
trace_log(LOG_DEBUG, "Textures have successfully initialized.");
signal_for_shutdown(shutdown_textures);
return 1;
}
u8 insert_image_2d_texture_from_file(const char* name)
{
char path[512];
snprintf(path, 512, "resources/textures/%s.png", name);
i32 width, height, channels;
stbi_uc* pixels = stbi_load(path, &width, &height, &channels, STBI_rgb_alpha);
if (!pixels)
{
trace_log(LOG_ERROR, "Failed to insert image2D texture at path '%s'; stb failed to load the image data.", path);
return 0;
}
texture t;
if (!init_texture(&t, width, height, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R8G8B8A8_SRGB, 1) ||
!load_image_2D(&t.image, 0, 0, width, height, pixels, width * height * 4))
{
trace_log(LOG_ERROR, "Failed to insert image2D texture '%s'; failed to setup vulkan texture.", name);
return 0;
}
return (hash_table_insert(&context.textures, name, &t) != NULL);
}
u8 remove_texture(const char* name)
{
texture t;
void* ptexture = &t;
if(!hash_table_remove(&context.textures, name, &ptexture))
{
trace_log(LOG_ERROR, "Failed to remove texture '%s'; lookup failed.", name);
return 0;
}
shutdown_texture(&t);
return 1;
}
static u8 init_mesh(mesh* msh, const mesh_geometry* geometry, vertex_input_type geometry_type, const mesh_instance_data* instance_data, vertex_input_type instance_type)
{
VkDeviceSize geometry_vertices_byte_size = 0, instance_data_byte_size = 0;
@ -1728,6 +2141,9 @@ static u8 init_mesh(mesh* msh, const mesh_geometry* geometry, vertex_input_type
case VERTEX_INPUT_TYPE_POS2:
*ds = count * sizeof(pos2);
break;
case VERTEX_INPUT_TYPE_POS2_TEX2:
*ds = count * sizeof(pos2_tex2);
break;
case VERTEX_INPUT_TYPE_POS3_POS3_COLOR4:
*ds = count * sizeof(pos3_pos3_color4);
break;
@ -1877,6 +2293,23 @@ static u8 init_mesh_pipeline(mesh_pipeline* pipeline, const mesh_pipeline_templa
attribute_descriptions = darray_pushback(attribute_descriptions, attributes, 1);
}
break;
case VERTEX_INPUT_TYPE_POS2_TEX2:
{
const VkVertexInputBindingDescription binding =
{
.binding = i, .stride = sizeof(pos2_tex2), .inputRate = input_rate
};
const VkVertexInputAttributeDescription attributes[] =
{
(VkVertexInputAttributeDescription){ .location = idx, .binding = i, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(pos2_tex2, pos) },
(VkVertexInputAttributeDescription){ .location = idx + 1, .binding = i, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(pos2_tex2, tex) }
};
binding_descriptions = darray_pushback(binding_descriptions, &binding, 1);
attribute_descriptions = darray_pushback(attribute_descriptions, attributes, 2);
}
break;
case VERTEX_INPUT_TYPE_POS3_POS3_COLOR4:
{
const VkVertexInputBindingDescription binding =
@ -2009,12 +2442,17 @@ static u8 init_mesh_pipeline(mesh_pipeline* pipeline, const mesh_pipeline_templa
.pNext = NULL
};
// layout; TO DO probably need something in the pipeline template to make this better
// layout
const VkDescriptorSetLayout descriptor_set_layouts[2] =
{
context.camera_descriptor_set_layout,
context.texture_descriptor_set_layout
};
const VkPipelineLayoutCreateInfo layout_info =
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = 1,
.pSetLayouts = &context.camera_descriptor_set_layout,
.setLayoutCount = 2,
.pSetLayouts = descriptor_set_layouts,
.pushConstantRangeCount = 0,
.pPushConstantRanges = NULL,
.flags = 0,
@ -2084,6 +2522,12 @@ static u8 init_mesh_pipeline(mesh_pipeline* pipeline, const mesh_pipeline_templa
pipeline->instance_type = template->instance_type;
init_hash_table(&pipeline->meshes, 8, sizeof(mesh), basic_hash);
// texture
if (template->texture_name != NULL)
strncpy(pipeline->texture_name, template->texture_name, HASH_TABLE_KEY_LEN);
else
pipeline->texture_name[0] = '\0';
return 1;
}
@ -2110,15 +2554,27 @@ u8 draw_mesh_pipeline(const char* name)
mesh_pipeline* pipeline = hash_table_lookup(&context.mesh_pipelines, name);
if (!pipeline)
{
trace_log(LOG_ERROR, "Failed to draw mesh pipeline '%s'; lookup failed." , name);
trace_log(LOG_ERROR, "Failed to draw mesh pipeline '%s'; pipeline lookup failed." , name);
return 0;
}
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->handle);
// bind camera descriptor set (set 0)
vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->layout, 0, 1, context.camera_descriptor_sets + context.current_frame, 0, NULL);
// bind texture descriptor set (set 1)
if (pipeline->texture_name[0] != '\0')
{
texture* t = hash_table_lookup(&context.textures, pipeline->texture_name);
if (!t)
{
trace_log(LOG_ERROR, "Failed to draw mesh pipeline '%s'; texture '%s' lookup failed.", name, pipeline->texture_name);
return 0;
}
vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->layout, 1, 1, t->descriptor_sets + context.current_frame, 0, NULL);
}
for (u32 i = 0; i < pipeline->meshes.slot_count; i++)
for (u32 i = 0; i < pipeline->meshes.slot_count; i++)
for (hash_table_entry* entry = pipeline->meshes.entries[i]; entry != NULL; entry = entry->next)
{
mesh* msh = entry->data;
@ -2250,6 +2706,7 @@ u8 init_vulkan()
if (!init_command_pool()) return 0;
if (!init_frame_data()) return 0;
if (!init_camera()) return 0;
if (!init_textures()) return 0;
init_hash_table(&context.mesh_pipelines, 8, sizeof(mesh_pipeline), basic_hash);

View File

@ -3,6 +3,7 @@
//
// vulkan-tutorial.com
// vkguide.dev
// saschawillems.de
#pragma once
@ -43,6 +44,7 @@ typedef struct window_event
typedef enum
{
VERTEX_INPUT_TYPE_POS2,
VERTEX_INPUT_TYPE_POS2_TEX2,
VERTEX_INPUT_TYPE_POS3_POS3_COLOR4
} vertex_input_type;
@ -53,6 +55,13 @@ typedef struct pos2
} pos2;
typedef struct pos2_tex2
{
f32 pos[2];
f32 tex[2];
} pos2_tex2;
typedef struct pos3_pos3_color4
{
f32 posa[3];
@ -80,9 +89,12 @@ typedef struct mesh_instance_data
typedef struct mesh_pipeline_template
{
const char* name;
vertex_input_type geometry_type;
vertex_input_type instance_type;
const char* texture_name;
} mesh_pipeline_template;
typedef enum
@ -109,6 +121,9 @@ void set_camera_view(const f32* view);
void set_camera_perspective_projection(const f32* perspective);
void set_camera_orthographic_projection(const f32* orthographic);
u8 insert_image_2d_texture_from_file(const char* name);
u8 remove_texture(const char* name);
u8 draw_mesh_pipeline(const char* name);
u8 insert_mesh_pipeline(const mesh_pipeline_template* template);
u8 remove_mesh_pipeline(const char* name);

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

View File

@ -41,6 +41,7 @@ You should have received a copy of the GNU General Public License along with Sha
(cffi:defcenum vertex-input-type
:vertex-input-type-pos2
:vertex-input-type-pos2-tex2
:vertex-input-type-pos3-pos3-color4)
(cffi:defcstruct (%pos2 :class c-pos2)
@ -73,6 +74,46 @@ You should have received a copy of the GNU General Public License along with Sha
(cffi:mem-aref pos :float 0) (aref (pos2-pos ,value) 0)
(cffi:mem-aref pos :float 1) (aref (pos2-pos ,value) 1))))
(cffi:defcstruct (%pos2-tex2 :class c-pos2-tex2)
(pos :float :count 2)
(tex :float :count 2))
(defstruct pos2-tex2
(pos (vector 0.0 0.0))
(tex (vector 0.0 0.0)))
(defmethod cffi:translate-from-foreign (ptr (type c-pos2-tex2))
(cffi:with-foreign-slots ((pos tex) ptr (:struct %pos2-tex2))
(make-pos2-tex2
:pos (vector (cffi:mem-aref pos :float 0)
(cffi:mem-aref pos :float 1))
:tex (vector (cffi:mem-aref tex :float 0)
(cffi:mem-aref tex :float 1)))))
(defmethod cffi:expand-from-foreign (ptr (type c-pos2-tex2))
`(cffi:with-foreign-slots ((pos tex) ,ptr (:struct %pos2-tex2))
(make-pos2-tex2
:pos (vector (cffi:mem-aref pos :float 0)
(cffi:mem-aref pos :float 1))
:tex (vector (cffi:mem-aref tex :float 0)
(cffi:mem-aref tex :float 1)))))
(defmethod cffi:translate-into-foreign-memory (value (type c-pos2-tex2) ptr)
(cffi:with-foreign-slots ((pos tex) ptr (:struct %pos2-tex2))
(setf
(cffi:mem-aref pos :float 0) (aref (pos2-tex2-pos value) 0)
(cffi:mem-aref pos :float 1) (aref (pos2-tex2-pos value) 1)
(cffi:mem-aref tex :float 0) (aref (pos2-tex2-tex value) 0)
(cffi:mem-aref tex :float 1) (aref (pos2-tex2-tex value) 1))))
(defmethod cffi:expand-into-foreign-memory (value (type c-pos2-tex2) ptr)
`(cffi:with-foreign-slots ((pos tex) ,ptr (:struct %pos2-tex2))
(setf
(cffi:mem-aref pos :float 0) (aref (pos2-tex2-pos ,value) 0)
(cffi:mem-aref pos :float 1) (aref (pos2-tex2-pos ,value) 1)
(cffi:mem-aref tex :float 0) (aref (pos2-tex2-tex ,value) 0)
(cffi:mem-aref tex :float 1) (aref (pos2-tex2-tex ,value) 1))))
(cffi:defcstruct (%pos3-pos3-color4 :class c-pos3-pos3-color4)
(posa :float :count 3)
(posb :float :count 3)
@ -153,40 +194,46 @@ You should have received a copy of the GNU General Public License along with Sha
(cffi:defcstruct (%mesh-pipeline-template :class c-mesh-pipeline-template)
(name :string)
(geometry-type vertex-input-type)
(instance-type vertex-input-type))
(instance-type vertex-input-type)
(texture-name :string))
(defstruct mesh-pipeline-template
(name "" :type string)
(geometry-type 0 :type integer)
(instance-type 0 :type integer))
(instance-type 0 :type integer)
(texture-name "" :type string))
(defmethod cffi:translate-from-foreign (ptr (type c-mesh-pipeline-template))
(cffi:with-foreign-slots ((name geometry-type instance-type) ptr (:struct %mesh-pipeline-template))
(cffi:with-foreign-slots ((name geometry-type instance-type texture-name) ptr (:struct %mesh-pipeline-template))
(make-mesh-pipeline-template
:name name
:geometry-type geometry-type
:instance-type instance-type)))
:instance-type instance-type
:texture-name texture-name)))
(defmethod cffi:expand-from-foreign (ptr (type c-mesh-pipeline-template))
`(cffi:with-foreign-slots ((name geometry-type instance-type) ,ptr (:struct %mesh-pipeline-template))
`(cffi:with-foreign-slots ((name geometry-type instance-type texture-name) ,ptr (:struct %mesh-pipeline-template))
(make-mesh-pipeline-template
:name name
:geometry-type geometry-type
:instance-type instance-type)))
:instance-type instance-type
:texture-name texture-name)))
(defmethod cffi:translate-into-foreign-memory (value (type c-mesh-pipeline-template) ptr)
(cffi:with-foreign-slots ((name geometry-type instance-type) ptr (:struct %mesh-pipeline-template))
(cffi:with-foreign-slots ((name geometry-type instance-type texture-name) ptr (:struct %mesh-pipeline-template))
(setf
name (mesh-pipeline-template-name value)
geometry-type (mesh-pipeline-template-geometry-type value)
instance-type (mesh-pipeline-template-instance-type value))))
instance-type (mesh-pipeline-template-instance-type value)
texture-name (mesh-pipeline-template-texture-name value))))
(defmethod cffi:expand-into-foreign-memory (value (type c-mesh-pipeline-template) ptr)
`(cffi:with-foreign-slots ((name geometry-type instance-type) ,ptr (:struct %mesh-pipeline-template))
(setf
name (mesh-pipeline-template-name ,value)
geometry-type (mesh-pipeline-template-geometry-type ,value)
instance-type (mesh-pipeline-template-instance-type ,value))))
instance-type (mesh-pipeline-template-instance-type ,value)
texture-name (mesh-pipeline-template-texture-name ,value))))
(cffi:defcenum mesh-pipeline-mode
:mesh-pipeline-mode-main
@ -234,14 +281,20 @@ You should have received a copy of the GNU General Public License along with Sha
(defun set-camera-orthographic-projection (orthographic)
(set-camera-helper orthographic #'%set-camera-orthographic-projection))
(cffi:defcfun ("insert_image_2d_texture_from_file" insert-image-2d-texture-from-file) :boolean
(name :string))
(cffi:defcfun ("remove_texture" remove-texture) :boolean
(name :string))
(cffi:defcfun ("draw_mesh_pipeline" draw-mesh-pipeline) :boolean
(name :string))
(cffi:defcfun ("insert_mesh_pipeline" %insert-mesh-pipeline) :boolean
(template (:pointer (:struct %mesh-pipeline-template))))
(defun insert-mesh-pipeline (name geometry-type instance-type)
(let ((template (make-mesh-pipeline-template :name name :geometry-type geometry-type :instance-type instance-type)))
(defun insert-mesh-pipeline (name geometry-type instance-type texture-name)
(let ((template (make-mesh-pipeline-template :name name :geometry-type geometry-type :instance-type instance-type :texture-name texture-name)))
(cffi:with-foreign-object (ptr '(:struct %mesh-pipeline-template))
(setf (cffi:mem-aref ptr '(:struct %mesh-pipeline-template)) template)
(%insert-mesh-pipeline ptr))))
@ -301,8 +354,17 @@ You should have received a copy of the GNU General Public License along with Sha
(insert-mesh-pipeline
"ui-pane"
(cffi:foreign-enum-value 'vertex-input-type :vertex-input-type-pos2)
(cffi:foreign-enum-value 'vertex-input-type :vertex-input-type-pos3-pos3-color4))
(cffi:foreign-enum-value 'vertex-input-type :vertex-input-type-pos3-pos3-color4)
"")
(insert-image-2d-texture-from-file "arrow")
(insert-mesh-pipeline
"ui-pane-tex"
(cffi:foreign-enum-value 'vertex-input-type :vertex-input-type-pos2-tex2)
(cffi:foreign-enum-value 'vertex-input-type :vertex-input-type-pos3-pos3-color4)
"arrow")
(cffi:with-foreign-object (quad-vertices '(:struct %pos2) 4)
(setf
(cffi:mem-aref quad-vertices '(:struct %pos2) 0) (make-pos2 :pos (vector 0.0 0.0))
@ -335,7 +397,39 @@ You should have received a copy of the GNU General Public License along with Sha
(setf
instances quad-instances
instance-count 2)
(insert-mesh "ui-pane" "quad" geometry instance-data)))))))))
(insert-mesh "ui-pane" "quad" geometry instance-data))))))))
(cffi:with-foreign-object (quad-vertices '(:struct %pos2-tex2) 4)
(setf
(cffi:mem-aref quad-vertices '(:struct %pos2-tex2) 0) (make-pos2-tex2 :pos (vector 0.0 0.0) :tex (vector (/ 0.5 16) (/ 15.5 16)))
(cffi:mem-aref quad-vertices '(:struct %pos2-tex2) 1) (make-pos2-tex2 :pos (vector 1.0 1.0) :tex (vector (/ 15.5 16) (/ 0.5 16)))
(cffi:mem-aref quad-vertices '(:struct %pos2-tex2) 2) (make-pos2-tex2 :pos (vector 1.0 0.0) :tex (vector (/ 15.5 16) (/ 15.5 16)))
(cffi:mem-aref quad-vertices '(:struct %pos2-tex2) 3) (make-pos2-tex2 :pos (vector 0.0 1.0) :tex (vector (/ 0.5 16) (/ 0.5 16))))
(cffi:with-foreign-object (quad-indices :uint32 6)
(setf
(cffi:mem-aref quad-indices :uint32 0) 0
(cffi:mem-aref quad-indices :uint32 1) 1
(cffi:mem-aref quad-indices :uint32 2) 2
(cffi:mem-aref quad-indices :uint32 3) 0
(cffi:mem-aref quad-indices :uint32 4) 3
(cffi:mem-aref quad-indices :uint32 5) 1)
(cffi:with-foreign-object (geometry '(:struct %mesh-geometry))
(cffi:with-foreign-slots ((vertices vertex-count indices index-count) geometry (:struct %mesh-geometry))
(setf
vertices quad-vertices
vertex-count 4
indices quad-indices
index-count 6)
(cffi:with-foreign-object (quad-instances '(:struct %pos3-pos3-color4) 1)
(setf
(cffi:mem-aref quad-instances '(:struct %pos3-pos3-color4) 0)
(make-pos3-pos3-color4 :posa (vector 300.0 300.0 3.0) :posb (vector 300.0 300.0 1.0) :color (vector 0.7 0.0 0.0 1.0)))
(cffi:with-foreign-object (instance-data '(:struct %mesh-instance-data))
(cffi:with-foreign-slots ((instances instance-count) instance-data (:struct %mesh-instance-data))
(setf
instances quad-instances
instance-count 1)
(insert-mesh "ui-pane-tex" "quad" geometry instance-data)))))))))
(defun shutdown-game ()
(shutdown-vulkan)
@ -364,7 +458,8 @@ You should have received a copy of the GNU General Public License along with Sha
(declare (ignore l))
(when (begin-render-frame)
(when (draw-mesh-pipeline "ui-pane")
(when (end-render-frame) t))))
(when (draw-mesh-pipeline "ui-pane-tex")
(when (end-render-frame) t)))))
(defun run-game ()