#include "ui.h" #include "gpu.h" #include "cglm/affine.h" #include "cglm/mat4.h" #include "stdio.h" #include "stdlib.h" #include "string.h" #include "vk_mem_alloc.h" #include "vulkan/vulkan_core.h" #include "spng.h" #include VkShaderModule load_shader_file(const char* path, VkDevice device) { FILE* file; file = fopen(path, "rb"); if(file == 0) { return VK_NULL_HANDLE; } int result = fseek(file, 0, SEEK_END); if(result != 0) { return VK_NULL_HANDLE; } long buffer_size = ftell(file); result = fseek(file, 0, SEEK_SET); if(result != 0) { return VK_NULL_HANDLE; } char * buffer = malloc(buffer_size); if(buffer == 0) { return VK_NULL_HANDLE; } size_t read = fread(buffer, 1, buffer_size, file); VkShaderModuleCreateInfo shader_info = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = read, .pCode = (uint32_t*)buffer, }; VkShaderModule shader; result = vkCreateShaderModule(device, &shader_info, 0, &shader); free(buffer); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } return shader; } VkResult create_ui_pipeline( VkDevice device, VkRenderPass render_pass, VkDescriptorSetLayout samplers_layout, VkDescriptorSetLayout textures_layout, GraphicsPipeline* pipeline, ComputePipeline* compute) { VkResult result; VkShaderModule compute_shader = load_shader_file("shader_src/string.comp.spv", device); if(compute_shader == VK_NULL_HANDLE) { return VK_ERROR_UNKNOWN; } VkPipelineShaderStageCreateInfo compute_shader_stage = { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_COMPUTE_BIT, .pName = "main", .module = compute_shader, }; VkPushConstantRange push_constant = { .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, .size = 16, }; VkPipelineLayoutCreateInfo compute_layout_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .pushConstantRangeCount = 1, .pPushConstantRanges = &push_constant, }; result = vkCreatePipelineLayout(device, &compute_layout_info, NULL, &compute->layout); if(result != VK_SUCCESS) { return result; } VkComputePipelineCreateInfo compute_pipeline_info = { .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .layout = compute->layout, .stage = compute_shader_stage, }; result = vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, &compute_pipeline_info, NULL, &compute->pipeline); if(result != VK_SUCCESS) { return result; } VkShaderModule vert_shader = load_shader_file("shader_src/ui.vert.spv", device); if(vert_shader == VK_NULL_HANDLE) { return VK_ERROR_UNKNOWN; } VkShaderModule frag_shader = load_shader_file("shader_src/ui.frag.spv", device); if(frag_shader == VK_NULL_HANDLE) { return VK_ERROR_UNKNOWN; } VkPipelineShaderStageCreateInfo shader_stages[] = { { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = vert_shader, .pName = "main", }, { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = frag_shader, .pName = "main", }, }; VkVertexInputBindingDescription bindings[] = {}; VkVertexInputAttributeDescription attributes[] = {}; VkPipelineVertexInputStateCreateInfo input_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .pVertexBindingDescriptions = bindings, .vertexBindingDescriptionCount = sizeof(bindings)/sizeof(VkVertexInputBindingDescription), .pVertexAttributeDescriptions = attributes, .vertexAttributeDescriptionCount = sizeof(attributes)/sizeof(VkVertexInputAttributeDescription), }; VkPipelineInputAssemblyStateCreateInfo input_assembly_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, .primitiveRestartEnable = VK_FALSE, }; VkPushConstantRange push_constants[] = { { .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, .size = 16, .offset = 0, }, }; VkDescriptorSetLayout set_layouts[] = {samplers_layout, textures_layout, samplers_layout, textures_layout}; VkPipelineLayoutCreateInfo layout_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .pushConstantRangeCount = sizeof(push_constants)/sizeof(VkPushConstantRange), .pPushConstantRanges = push_constants, .setLayoutCount = sizeof(set_layouts)/sizeof(VkDescriptorSetLayout), .pSetLayouts = set_layouts, }; result = vkCreatePipelineLayout(device, &layout_info, 0, &pipeline->layout); if(result != VK_SUCCESS) { return result; } VkViewport viewport = { .x = 0.0f, .y = 0.0f, .width = (float)(100), .height = (float)(100), .minDepth = 0.0f, .maxDepth = 1.0f, }; VkRect2D scissor = { .offset = { .x = 0, .y = 0, }, .extent = { .width = 100, .height = 100, }, }; VkPipelineViewportStateCreateInfo viewport_state = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1, .pViewports = &viewport, .scissorCount = 1, .pScissors = &scissor, }; VkPipelineRasterizationStateCreateInfo raster_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .depthClampEnable = VK_FALSE, .rasterizerDiscardEnable = VK_FALSE, .polygonMode = VK_POLYGON_MODE_FILL, .lineWidth = 1.0f, .cullMode = VK_CULL_MODE_BACK_BIT, .frontFace = VK_FRONT_FACE_CLOCKWISE, .depthBiasEnable = VK_FALSE, .depthBiasConstantFactor = 0.0f, .depthBiasClamp = 0.0f, .depthBiasSlopeFactor = 0.0f, }; VkPipelineMultisampleStateCreateInfo multisample_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .sampleShadingEnable = VK_FALSE, .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, .minSampleShading = 1.0f, .pSampleMask = 0, .alphaToCoverageEnable = VK_FALSE, .alphaToOneEnable = VK_FALSE, }; // For blending, calculate the new color as new_color = (1-a)(old_color)+(a)(fragment_color) // Don't store an alpha value in the color attachment, since it's not used VkPipelineColorBlendAttachmentState color_blend_attachment = { .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, .blendEnable = VK_TRUE, .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA, .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, .colorBlendOp = VK_BLEND_OP_ADD, .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, .alphaBlendOp = VK_BLEND_OP_ADD, }; VkPipelineColorBlendStateCreateInfo color_blend_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .logicOpEnable = VK_FALSE, .logicOp = VK_LOGIC_OP_COPY, .attachmentCount = 1, .pAttachments = &color_blend_attachment, }; VkDynamicState dynamic_states[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, }; uint32_t dynamic_state_count = sizeof(dynamic_states)/sizeof(VkDynamicState); VkPipelineDynamicStateCreateInfo dynamic_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, .dynamicStateCount = dynamic_state_count, .pDynamicStates = dynamic_states, }; VkGraphicsPipelineCreateInfo draw_pipeline_info = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = sizeof(shader_stages)/sizeof(VkPipelineShaderStageCreateInfo), .pStages = shader_stages, .pVertexInputState = &input_info, .pInputAssemblyState = &input_assembly_info, .pViewportState = &viewport_state, .pRasterizationState = &raster_info, .pColorBlendState = &color_blend_info, .pDynamicState = &dynamic_info, .pMultisampleState = &multisample_info, .layout = pipeline->layout, .renderPass = render_pass, .subpass = 1, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = -1, }; result = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &draw_pipeline_info, 0, &pipeline->pipeline); if(result != VK_SUCCESS) { return result; } return VK_SUCCESS; } VkResult create_container( ContainerInput* container, RenderContext* gpu, UIContext* context) { uint32_t index = 0xFFFFFFFF; for(uint32_t i = 0; i < context->max_containers; i++) { if(context->containers[i].id == 0x00000000) { index = i; break; } } if(index == 0xFFFFFFFF) { return VK_ERROR_OUT_OF_HOST_MEMORY; } VkResult result; for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(GPUContainer), &context->containers[index].container[i], &context->containers[index].container_memory[i])); context->containers[index].address[i] = buffer_address(gpu->device, context->containers[index].container[i]); } fprintf(stderr, "Created container with storage buffers %p/%p\n", context->containers[index].container[0], context->containers[index].container[1]); context->containers[index].data.offset[0] = container->offset[0]; context->containers[index].data.offset[1] = container->offset[1]; context->containers[index].data.size[0] = container->size[0]; context->containers[index].data.size[1] = container->size[1]; context->containers[index].data.anchor = container->anchor; context->containers[index].data.context = context->address; add_transfers(&context->containers[index].data, context->containers[index].container, 0, sizeof(GPUContainer), gpu); context->containers[index].id = container->id; context->containers[index].layers = malloc(sizeof(Layer)*container->layer_count); for(uint32_t i = 0; i < container->layer_count; i++) { VK_RESULT(create_layer(i, &container->layers[i], gpu, &context->containers[index])); } context->containers[index].layer_count = container->layer_count; return VK_SUCCESS; } VkResult create_layer( uint32_t index, LayerInput* input, RenderContext* gpu, Container* container) { VkResult result; uint32_t max_strings = (input->num_strings > input->max_strings) ? input->num_strings : input->max_strings; uint32_t max_codes = (input->num_codes > input->max_codes) ? input->num_codes : input->max_codes; uint32_t max_drawables = (input->num_drawables > input->max_drawables) ? input->num_drawables : input->max_drawables; for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { VK_RESULT(create_storage_buffer(gpu->allocator, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, sizeof(GPULayer), &container->layers[index].layer[i], &container->layers[index].layer_memory[i])); if(max_strings > 0) { VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(GPUString)*max_strings, &container->layers[index].strings[i], &container->layers[index].strings_memory[i])); container->layers[index].strings_buffer = malloc(sizeof(GPUString)*max_strings); } if(max_codes + max_drawables > 0) { VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(GPUDrawable)*(max_drawables + max_codes), &container->layers[index].drawables[i], &container->layers[index].drawables_memory[i])); container->layers[index].drawables_buffer = malloc(sizeof(GPUDrawable)*max_drawables); } if(max_codes > 0) { VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(uint32_t)*max_codes, &container->layers[index].codes[i], &container->layers[index].codes_memory[i])); container->layers[index].codes_buffer = malloc(sizeof(uint32_t)*max_codes); } if(max_strings > 0) { container->layers[index].data.strings = buffer_address(gpu->device, container->layers[index].strings[i]); } else { container->layers[index].data.strings = 0x00000000; } if(max_codes > 0) { container->layers[index].data.codes = buffer_address(gpu->device, container->layers[index].codes[i]); } else { container->layers[index].data.codes = 0x00000000; } if(max_codes + max_drawables > 0) { container->layers[index].data.drawables = buffer_address(gpu->device, container->layers[index].drawables[i]); } else { container->layers[index].data.drawables = 0x00000000; } container->layers[index].address[i] = buffer_address(gpu->device, container->layers[index].layer[i]); } fprintf(stderr, "Created layer with storage buffers %p/%p\n", container->layers[index].layer[0], container->layers[index].layer[1]); fprintf(stderr, "String Buffers %p/%p\n", container->layers[index].strings[0], container->layers[index].strings[1]); fprintf(stderr, "Code Buffers %p/%p\n", container->layers[index].codes[0], container->layers[index].codes[1]); for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { add_transfer( &container->address[i], container->layers[index].layer[i], offsetof(GPULayer, container), sizeof(VkDeviceAddress), i, gpu); } container->layers[index].data.draw.first_vertex = 0; container->layers[index].data.draw.vertex_count = 6; container->layers[index].data.draw.first_instance = 0; container->layers[index].data.draw.instance_count = 0; container->layers[index].data.dispatch_strings.x = max_strings; container->layers[index].data.dispatch_strings.y = 1; container->layers[index].data.dispatch_strings.z = 1; container->layers[index].data.max_drawables = max_drawables + max_codes; container->layers[index].data.max_strings = max_strings; container->layers[index].data.max_codes = max_codes; container->layers[index].data.num_drawables = max_drawables; add_transfers(&container->layers[index].data, container->layers[index].layer, 0, sizeof(GPULayer)-sizeof(VkDeviceAddress), gpu); if(input->num_strings > 0) { memcpy(container->layers[index].strings_buffer, input->strings, sizeof(GPUString)*input->num_strings); add_transfers(container->layers[index].strings_buffer, container->layers[index].strings, 0, sizeof(GPUString)*input->num_strings, gpu); } if(input->num_drawables > 0) { memcpy(container->layers[index].drawables_buffer, input->drawables, sizeof(GPUDrawable)*input->num_drawables); add_transfers(container->layers[index].drawables_buffer, container->layers[index].drawables, 0, sizeof(GPUDrawable)*input->num_drawables, gpu); } if(input->num_codes > 0) { memcpy(container->layers[index].codes_buffer, input->codes, sizeof(uint32_t)*input->num_codes); add_transfers(container->layers[index].codes_buffer, container->layers[index].codes, 0, sizeof(uint32_t)*input->num_codes, gpu); } return VK_SUCCESS; } VkResult load_texture( const char* png_path, RenderContext* gpu, UIContext* context, uint32_t* index) { *index = 0xFFFFFFFF; for(uint32_t i = 0; i < context->max_textures; i++) { if(context->texture_slots[i].path == NULL) { context->texture_slots[i].path = malloc(strlen(png_path) + 1); memcpy(context->texture_slots[i].path, png_path, strlen(png_path) + 1); *index = i; break; } } if(*index == 0xFFFFFFFF) { return VK_ERROR_OUT_OF_DEVICE_MEMORY; } spng_ctx* spng = spng_ctx_new(0); if(spng == NULL) { return VK_ERROR_UNKNOWN; } FILE* png_file = fopen(png_path, "rb"); if(png_file == NULL) { return VK_ERROR_UNKNOWN; } fseek(png_file, 0, SEEK_END); int error = fseek(png_file, 0, SEEK_END); if(error != 0) { return VK_ERROR_UNKNOWN; } size_t png_size = ftell(png_file); error = fseek(png_file, 0, SEEK_SET); if(error != 0) { return VK_ERROR_UNKNOWN; } void* png = malloc(png_size); if(png == NULL) { return VK_ERROR_UNKNOWN; } size_t read = fread(png, 1, png_size, png_file); if(read != png_size) { return VK_ERROR_UNKNOWN; } fclose(png_file); spng_set_png_buffer(spng, png, png_size); size_t out_size; spng_decoded_image_size(spng, SPNG_FMT_RGBA8, &out_size); struct spng_ihdr ihdr; spng_get_ihdr(spng, &ihdr); void* image_buffer = malloc(out_size); if(image_buffer == NULL) { return VK_ERROR_UNKNOWN; } spng_decode_image(spng, image_buffer, out_size, SPNG_FMT_RGBA8, 0); VkResult result; VkImageCreateInfo image_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, .extent = { .width = ihdr.width, .height = ihdr.height, .depth = 1, }, .mipLevels = 1, .arrayLayers = 1, .format = VK_FORMAT_R8G8B8A8_SRGB, .tiling = VK_IMAGE_TILING_OPTIMAL, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .samples = VK_SAMPLE_COUNT_1_BIT, .imageType = VK_IMAGE_TYPE_2D, }; VmaAllocationCreateInfo memory_info = { .usage = VMA_MEMORY_USAGE_GPU_ONLY, }; VkBuffer transfer; VmaAllocation transfer_memory; void* mapped; VK_RESULT(vmaCreateImage(gpu->allocator, &image_info, &memory_info, &context->texture_slots[*index].image, &context->texture_slots[*index].image_memory, NULL)); VK_RESULT(create_transfer_buffer(gpu->allocator, sizeof(uint32_t)*ihdr.width*ihdr.height, &transfer, &transfer_memory, &mapped)); memcpy(mapped, image_buffer, sizeof(uint32_t)*ihdr.height*ihdr.width); VkCommandBuffer command_buffer = command_begin_single(gpu->device, gpu->transfer_pool); VkImageMemoryBarrier first_barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .image = context->texture_slots[*index].image, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .levelCount = 1, .layerCount = 1, }, .srcAccessMask = 0, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, }; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &first_barrier); VkBufferImageCopy image_copy = { .imageExtent = { .width = ihdr.width, .height = ihdr.height, .depth = 1, }, .imageSubresource = { .layerCount = 1, .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, }, }; vkCmdCopyBufferToImage(command_buffer, transfer, context->texture_slots[*index].image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy); VkImageMemoryBarrier second_barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .image = context->texture_slots[*index].image, .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .layerCount = 1, .levelCount = 1, }, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, }; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, &second_barrier); VK_RESULT(command_end_single(gpu->device, command_buffer, gpu->transfer_pool, gpu->transfer_queue)); destroy_transfer_buffer(gpu->allocator, transfer, transfer_memory); free(image_buffer); free(png); spng_ctx_free(spng); VkImageViewCreateInfo view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = context->texture_slots[*index].image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = VK_FORMAT_R8G8B8A8_SRGB, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .layerCount = 1, .levelCount = 1, }, }; VK_RESULT(vkCreateImageView(gpu->device, &view_info, NULL, &context->texture_slots[*index].view)); 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, }; VK_RESULT(vkCreateSampler(gpu->device, &sampler_info, NULL, &context->texture_slots[*index].sampler)); VkDescriptorImageInfo desc_sampler_info = { .sampler = context->texture_slots[*index].sampler, }; VkDescriptorImageInfo desc_image_info = { .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .imageView = context->texture_slots[*index].view, }; VkWriteDescriptorSet desc_writes[] = { { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = context->textures, .dstBinding = 0, .dstArrayElement = *index, .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .descriptorCount = 1, .pImageInfo = &desc_image_info, }, { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = context->samplers, .dstBinding = 0, .dstArrayElement = *index, .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER, .descriptorCount = 1, .pImageInfo = &desc_sampler_info, }, }; vkUpdateDescriptorSets(gpu->device, sizeof(desc_writes)/sizeof(VkWriteDescriptorSet), desc_writes, 0, NULL); return VK_SUCCESS; } VkResult load_font( uint32_t index, const char* ttf_file, uint32_t size, VkBool32 antialias, RenderContext* gpu, UIContext* context){ FT_Face face; int error; error = FT_New_Face(context->freetype, ttf_file, 0, &face); if(error != FT_Err_Ok) { return VK_ERROR_UNKNOWN; } context->fonts[index].family = malloc(strlen(face->family_name)+1); memcpy(&context->fonts[index].family, face->family_name, strlen(face->family_name)+1); context->fonts[index].style = malloc(strlen(face->style_name)+1); memcpy(&context->fonts[index].style, face->style_name, strlen(face->style_name)+1); error = FT_Set_Pixel_Sizes(face, 0, size); if(error != FT_Err_Ok) { return VK_ERROR_UNKNOWN; } uint32_t* tmp_charmap = malloc(sizeof(uint32_t)*face->num_glyphs); GPUSymbol* symbols = malloc(sizeof(GPUSymbol)*face->num_glyphs); GPUFont info; uint32_t glyph_index; uint32_t max_height = 0; uint32_t max_width = 0; uint32_t symbol_count = 0; uint32_t c; FT_Int32 load_flags = FT_LOAD_RENDER; if(antialias == VK_TRUE) { load_flags += FT_RENDER_MODE_NORMAL; } else { load_flags += FT_RENDER_MODE_MONO; } // TODO: worry about variants(that's why num_glyphs doesn't match symbol_count) c = FT_Get_First_Char(face, &glyph_index); for(uint32_t i = 0; i < face->num_glyphs; i++) { if(glyph_index == 0) { break; } else if(c > 255) { c = FT_Get_Next_Char(face, c, &glyph_index); continue; } FT_Load_Glyph(face, glyph_index, load_flags); uint32_t width = face->glyph->bitmap.width; uint32_t height = face->glyph->bitmap.rows; tmp_charmap[i] = c; max_width = width > max_width ? width : max_width; max_height = height > max_height ? height : max_height; symbol_count += 1; c = FT_Get_Next_Char(face, c, &glyph_index); } info.width = max_width; info.height = max_height; info.num_symbols = symbol_count; context->fonts[index].num_symbols = symbol_count; uint32_t image_size = max_width*max_height*sizeof(uint32_t); uint32_t* images = malloc(image_size*symbol_count); memset(images, 0x00, image_size*symbol_count); context->fonts[index].charmap = malloc(sizeof(uint32_t)*symbol_count); memcpy(context->fonts[index].charmap, tmp_charmap, sizeof(uint32_t)*symbol_count); free(tmp_charmap); for(uint32_t i = 0; i < symbol_count; i++) { glyph_index = FT_Get_Char_Index(face, context->fonts[index].charmap[i]); FT_Load_Glyph(face, glyph_index, load_flags); symbols[i].width = (float)face->glyph->bitmap.width/(float)max_width; symbols[i].height = (float)face->glyph->bitmap.rows/(float)max_height; if(face->units_per_EM == 2048) { symbols[i].advance[0] = (float)face->glyph->advance.x*30/(float)face->units_per_EM /(float)max_width; symbols[i].advance[1] = (float)face->glyph->advance.y*30/(float)face->units_per_EM /(float)max_height; } else { symbols[i].advance[0] = (float)face->glyph->advance.x*16/(float)face->units_per_EM /(float)max_width; symbols[i].advance[1] = (float)face->glyph->advance.y*16/(float)face->units_per_EM /(float)max_height; } symbols[i].left = (float)face->glyph->bitmap_left/(float)max_width; symbols[i].top = (float)face->glyph->bitmap_top/(float)max_height; for(uint32_t y = 0; y < face->glyph->bitmap.rows; y++) { for(uint32_t x = 0; x < face->glyph->bitmap.width; x++) { uint64_t level = face->glyph->bitmap.buffer[y*face->glyph->bitmap.width+x]; level = ((level * 0xFFFFFFFF) / 256); images[max_width*max_height*i + max_width*y + x] = level; } } } error = FT_Done_Face(face); if(error != FT_Err_Ok) { return VK_ERROR_UNKNOWN; } VkResult result; VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(GPUSymbol)*info.num_symbols, &context->fonts[index].symbols, &context->fonts[index].symbol_memory)); VkImageCreateInfo image_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, .extent.depth = 1, .extent.width = info.width, .extent.height = info.height, .mipLevels = 1, .arrayLayers = info.num_symbols, .format = VK_FORMAT_R8G8B8A8_SRGB, .tiling = VK_IMAGE_TILING_OPTIMAL, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .samples = VK_SAMPLE_COUNT_1_BIT, .imageType = VK_IMAGE_TYPE_2D, }; VmaAllocationCreateInfo image_memory_info = { .usage = VMA_MEMORY_USAGE_GPU_ONLY, }; VK_RESULT(vmaCreateImage(gpu->allocator, &image_info, &image_memory_info, &context->fonts[index].image, &context->fonts[index].image_memory, NULL)); VkBuffer transfer; VmaAllocation transfer_memory; void* mapped; VK_RESULT(create_transfer_buffer(gpu->allocator, sizeof(GPUFont) + image_size*info.num_symbols + sizeof(GPUSymbol)*info.num_symbols, &transfer, &transfer_memory, &mapped)); info.symbol_list = buffer_address(gpu->device, context->fonts[index].symbols); memcpy(mapped, images, image_size*info.num_symbols); memcpy(mapped + image_size*info.num_symbols, &info, sizeof(GPUFont)); memcpy(mapped + image_size*info.num_symbols + sizeof(GPUFont), symbols, sizeof(GPUSymbol)*info.num_symbols); free(images); free(symbols); VkCommandBuffer command_buffer = command_begin_single(gpu->device, gpu->transfer_pool); command_copy_buffer(command_buffer, transfer, context->font_infos, image_size*info.num_symbols, index*sizeof(GPUFont), sizeof(GPUFont)); command_copy_buffer(command_buffer, transfer, context->fonts[index].symbols, image_size*info.num_symbols + sizeof(GPUFont), 0, sizeof(GPUSymbol)*info.num_symbols); VkImageMemoryBarrier first_barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .image = context->fonts[index].image, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.levelCount = 1, .subresourceRange.layerCount = info.num_symbols, .srcAccessMask = 0, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, }; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &first_barrier); VkBufferImageCopy image_copy = { .imageSubresource.layerCount = info.num_symbols, .imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .imageExtent = image_info.extent, }; vkCmdCopyBufferToImage(command_buffer, transfer, context->fonts[index].image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy); VkImageMemoryBarrier second_barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .image = context->fonts[index].image, .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.levelCount = 1, .subresourceRange.layerCount = info.num_symbols, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, }; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, &second_barrier); VK_RESULT(command_end_single(gpu->device, command_buffer, gpu->transfer_pool, gpu->transfer_queue)); destroy_transfer_buffer(gpu->allocator, transfer, transfer_memory); VkImageViewCreateInfo view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = context->fonts[index].image, .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY, .format = VK_FORMAT_R8G8B8A8_SRGB, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .layerCount = info.num_symbols, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, }, }; VK_RESULT(vkCreateImageView(gpu->device, &view_info, NULL, &context->fonts[index].view)) VkSamplerCreateInfo sampler_info = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_NEAREST, .minFilter = VK_FILTER_NEAREST, .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, }; VK_RESULT(vkCreateSampler(gpu->device, &sampler_info, NULL, &context->fonts[index].sampler)); VkDescriptorImageInfo desc_sampler_info = { .sampler = context->fonts[index].sampler, }; VkDescriptorImageInfo desc_texture_info = { .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .imageView = context->fonts[index].view, }; VkWriteDescriptorSet descriptor_writes[] = { { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = context->font_textures, .dstBinding = 0, .dstArrayElement = index, .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .descriptorCount = 1, .pImageInfo = &desc_texture_info, }, { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = context->font_samplers, .dstBinding = 0, .dstArrayElement = index, .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER, .descriptorCount = 1, .pImageInfo = &desc_sampler_info, }, }; vkUpdateDescriptorSets(gpu->device, sizeof(descriptor_writes)/sizeof(VkWriteDescriptorSet), descriptor_writes, 0, NULL); return VK_SUCCESS; } VkResult create_ui_descriptor( VkDevice device, uint32_t max_fonts, uint32_t max_textures, VkDescriptorSetLayout* sampler_layout, VkDescriptorSetLayout* texture_layout, VkDescriptorSet* samplers, VkDescriptorSet* textures, VkDescriptorSet* font_samplers, VkDescriptorSet* font_textures, VkDescriptorPool* font_pool, VkDescriptorPool* texture_pool) { VkResult result; VkDescriptorSetLayoutBinding sampler_bindings[] = { { .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER, .descriptorCount = max_fonts, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, }, }; VkDescriptorBindingFlags bindless_flags[] = { VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT, }; VkDescriptorSetLayoutBindingFlagsCreateInfo sampler_binding_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, .bindingCount = sizeof(sampler_bindings)/sizeof(VkDescriptorSetLayoutBinding), .pBindingFlags = bindless_flags, }; VkDescriptorSetLayoutCreateInfo sampler_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .pBindings = sampler_bindings, .bindingCount = sizeof(sampler_bindings)/sizeof(VkDescriptorSetLayoutBinding), .flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, .pNext = &sampler_binding_info, }; VK_RESULT(vkCreateDescriptorSetLayout(device, &sampler_info, NULL, sampler_layout)); VkDescriptorSetLayoutBinding texture_bindings[] = { { .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .descriptorCount = max_fonts, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, }, }; VkDescriptorSetLayoutBindingFlagsCreateInfo texture_bindings_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, .bindingCount = sizeof(texture_bindings)/sizeof(VkDescriptorSetLayoutBinding), .pBindingFlags = bindless_flags, }; VkDescriptorSetLayoutCreateInfo texture_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .pBindings = texture_bindings, .bindingCount = sizeof(texture_bindings)/sizeof(VkDescriptorSetLayoutBinding), .flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, .pNext = &texture_bindings_info, }; VK_RESULT(vkCreateDescriptorSetLayout(device, &texture_info, NULL, texture_layout)); VkDescriptorPoolSize font_pool_sizes[] = { { .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .descriptorCount = max_fonts, }, { .type = VK_DESCRIPTOR_TYPE_SAMPLER, .descriptorCount = max_fonts, }, }; VkDescriptorPoolCreateInfo font_pool_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .pPoolSizes = font_pool_sizes, .poolSizeCount = 2, .maxSets = 2, .flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT, }; result = vkCreateDescriptorPool(device, &font_pool_info, NULL, font_pool); if(result != VK_SUCCESS) { return result; } uint32_t max_font_binding = max_fonts - 1; VkDescriptorSetVariableDescriptorCountAllocateInfo count_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO, .descriptorSetCount = 1, .pDescriptorCounts = &max_font_binding, }; VkDescriptorSetAllocateInfo allocate_font_samplers = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .pSetLayouts = sampler_layout, .descriptorSetCount = 1, .descriptorPool = *font_pool, .pNext = &count_info, }; VkDescriptorSetAllocateInfo allocate_font_textures = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .pSetLayouts = texture_layout, .descriptorSetCount = 1, .descriptorPool = *font_pool, .pNext = &count_info, }; result = vkAllocateDescriptorSets(device, &allocate_font_samplers, font_samplers); if(result != VK_SUCCESS) { return result; } result = vkAllocateDescriptorSets(device, &allocate_font_textures, font_textures); if(result != VK_SUCCESS) { return result; } VkDescriptorPoolSize texture_pool_sizes[] = { { .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .descriptorCount = max_textures, }, { .type = VK_DESCRIPTOR_TYPE_SAMPLER, .descriptorCount = max_textures, }, }; VkDescriptorPoolCreateInfo texture_pool_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .pPoolSizes = texture_pool_sizes, .poolSizeCount = 2, .maxSets = 2, .flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT, }; result = vkCreateDescriptorPool(device, &texture_pool_info, NULL, texture_pool); if(result != VK_SUCCESS) { return result; } uint32_t max_texture_binding = max_textures - 1; VkDescriptorSetVariableDescriptorCountAllocateInfo texture_count_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO, .descriptorSetCount = 1, .pDescriptorCounts = &max_texture_binding, }; VkDescriptorSetAllocateInfo allocate_samplers = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .pSetLayouts = sampler_layout, .descriptorSetCount = 1, .descriptorPool = *texture_pool, .pNext = &texture_count_info, }; VkDescriptorSetAllocateInfo allocate_textures = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .pSetLayouts = texture_layout, .descriptorSetCount = 1, .descriptorPool = *texture_pool, .pNext = &texture_count_info, }; result = vkAllocateDescriptorSets(device, &allocate_samplers, samplers); if(result != VK_SUCCESS) { return result; } result = vkAllocateDescriptorSets(device, &allocate_textures, textures); if(result != VK_SUCCESS) { return result; } return VK_SUCCESS; } VkResult create_ui_context( uint32_t max_fonts, uint32_t max_textures, uint32_t max_containers, RenderContext* gpu, UIContext* context) { VkResult result; if(FT_Init_FreeType(&context->freetype) != FT_Err_Ok) { return VK_ERROR_UNKNOWN; } VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(GPUUIContext), &context->context, &context->context_memory)); VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(GPUFont)*max_fonts, &context->font_infos, &context->font_infos_memory)); VkBuffer transfer; VmaAllocation transfer_context; UIContext* mapped; VK_RESULT(create_transfer_buffer(gpu->allocator, sizeof(UIContext), &transfer, &transfer_context, (void**)&mapped)); context->data.font_infos = buffer_address(gpu->device, context->font_infos); context->data.screen[0] = gpu->window_scale[0] / gpu->swapchain_extent.width; context->data.screen[1] = gpu->window_scale[1] / gpu->swapchain_extent.height; context->data.extent[0] = gpu->swapchain_extent.width / gpu->window_scale[0]; context->data.extent[1] = gpu->swapchain_extent.height / gpu->window_scale[1]; context->data.scale[0] = gpu->window_scale[0]; context->data.scale[1] = gpu->window_scale[1]; memcpy(mapped, &context->data, sizeof(GPUUIContext)); VkCommandBuffer command_buffer = command_begin_single(gpu->device, gpu->transfer_pool); command_copy_buffer(command_buffer, transfer, context->context, 0, 0, sizeof(GPUUIContext)); VK_RESULT(command_end_single(gpu->device, command_buffer, gpu->transfer_pool, gpu->transfer_queue)); destroy_transfer_buffer(gpu->allocator, transfer, transfer_context); context->address = buffer_address(gpu->device, context->context); VK_RESULT(create_ui_descriptor(gpu->device, max_fonts, max_textures, &context->samplers_layout, &context->textures_layout, &context->samplers, &context->textures, &context->font_samplers, &context->font_textures, &context->fonts_pool, &context->textures_pool)); VK_RESULT(create_ui_pipeline(gpu->device, gpu->render_pass, context->samplers_layout, context->textures_layout, &context->pipeline, &context->string_pipeline)); context->max_textures = max_textures; context->max_fonts = max_fonts; context->max_containers = max_containers; context->texture_slots = malloc(max_textures*sizeof(Texture)); memset(context->texture_slots, 0, max_textures*sizeof(Texture)); context->fonts = malloc(max_fonts*sizeof(Font)); memset(context->fonts, 0, max_fonts*sizeof(Font)); context->containers = malloc(max_containers*sizeof(Container)); memset(context->containers, 0, max_containers*sizeof(Container)); VK_RESULT(load_font(0, "fonts/eracake.ttf", 256, VK_TRUE, gpu, context)); VK_RESULT(load_font(1, "fonts/yumoda.ttf", 256, VK_TRUE, gpu, context)); VK_RESULT(load_font(2, "fonts/runescape.ttf", 16, VK_FALSE, gpu, context)); return VK_SUCCESS; } VkResult map_string( const char * text, uint32_t* buffer, uint32_t offset, uint32_t font, UIContext* context) { size_t i = 0; while(text[i] != '\0') { uint32_t mapped = 0xFFFFFFFF; for(uint32_t j = 0; j < context->fonts[font].num_symbols; j++) { if(context->fonts[font].charmap[j] == (uint32_t)text[i]) { mapped = j; break; } } if(mapped == 0xFFFFFFFF) { return VK_ERROR_UNKNOWN; } buffer[i + offset] = mapped; i += 1; } return VK_SUCCESS; }