Moved font loadinf from script to C

main
noah metz 2024-10-16 20:29:19 -06:00
parent a6e44fcd0e
commit 2a99c7be99
8 changed files with 131 additions and 207 deletions

@ -1,6 +1,6 @@
ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
CFLAGS = -I $(ROOT_DIR)/include -I/usr/local/include -O0 -g -Wall -Wextra
LDFLAGS = -lz -lglfw -lvulkan -ldl -Xlinker -rpath -Xlinker /opt/homebrew/lib
LDFLAGS = -lfreetype -lz -lglfw -lvulkan -ldl -Xlinker -rpath -Xlinker /opt/homebrew/lib
SOURCES = src/main.c src/render.c src/vma.cpp src/pipeline.c src/command.c src/spng.c
OBJECTS = $(addsuffix .o, $(basename $(SOURCES)))

@ -6,6 +6,9 @@
#include "vk_mem_alloc.h"
#include "command.h"
#include "ft2build.h"
#include FT_FREETYPE_H
typedef struct ComputePipelineStruct {
VkPipelineLayout layout;
VkPipeline pipeline;
@ -60,21 +63,17 @@ typedef struct FontUniformStruct {
uint32_t num_symbols;
uint32_t width;
uint32_t height;
uint32_t space_width;
VkDeviceAddress symbol_list;
} FontUniform;
typedef struct SymbolInfoStruct {
uint32_t x;
uint32_t top;
int32_t top;
uint32_t left;
uint32_t width;
uint32_t height;
uint32_t advance;
} SymbolInfo;
typedef struct FontDataStruct {
FontUniform info;
uint16_t* codes;
} FontData;
typedef struct FontDescriptorStruct {
VmaAllocation symbol_memory;
VmaAllocation uniform_memory;
@ -130,6 +129,6 @@ typedef struct UIContextStruct {
VkResult init_pipelines(VkDevice device, VmaAllocator allocator, VkExtent2D swapchain_extent, vec2 window_scale, VkRenderPass render_pass, Queue transfer_queue, VkCommandPool transfer_pool, UIContext* context);
VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescriptorSetLayout layout, VkDescriptorPool pool, FontData* font, SymbolInfo* symbols, uint32_t* atlas, VkCommandPool transfer_pool, Queue transfer_queue, FontDescriptor* descriptor);
VkResult load_font(VkDevice device, VmaAllocator allocator, VkDescriptorSetLayout layout, VkDescriptorPool pool,VkCommandPool transfer_pool, Queue transfer_queue, FT_Library library, const char* ttf_file, uint32_t size, uint32_t** charmap, FontDescriptor* descriptor);
#endif

@ -2,9 +2,11 @@
#extension GL_EXT_buffer_reference : require
struct Symbol {
uint x;
uint top;
int top;
uint left;
uint width;
uint height;
uint advance;
};
struct Character {
@ -42,7 +44,6 @@ layout(set = 0, binding = 0) uniform Font {
uint num_symbols;
uint width;
uint height;
uint space_width;
SymbolList symbol_list;
} font;
@ -74,8 +75,8 @@ void main() {
float x = 0;
for(uint i = 0; i < string.len; i++) {
Symbol symbol = font.symbol_list.symbols[push.pointers.codes.codes[buffer_pos + i]];
push.pointers.characters.characters[buffer_pos + i].pos = string.pos + vec3(x, symbol.top/font.height*string.size, 0);
x += symbol.width * string.size / font.height;
push.pointers.characters.characters[buffer_pos + i].pos = string.pos + vec3(x, 0, 0);
x += string.size*symbol.advance/font.width;
push.pointers.characters.characters[buffer_pos + i].size = string.size;
push.pointers.characters.characters[buffer_pos + i].color = string.color;
push.pointers.characters.characters[buffer_pos + i].code = push.pointers.codes.codes[buffer_pos + i];

@ -1,13 +1,14 @@
#version 450
layout(set = 1, binding = 1) uniform sampler2D font;
layout(set = 1, binding = 1) uniform sampler2DArray font;
layout(location = 0) in vec4 fragColor;
layout(location = 1) in vec2 fragUV;
layout(location = 2) flat in uint code;
layout(location = 0) out vec4 outColor;
void main() {
outColor = fragColor * texture(font, fragUV);
outColor = fragColor * texture(font, vec3(fragUV, code));
}

@ -2,9 +2,11 @@
#extension GL_EXT_buffer_reference : require
struct Symbol {
uint x;
uint top;
int top;
uint left;
uint width;
uint height;
uint advance;
};
struct Character {
@ -30,7 +32,6 @@ layout(set = 1, binding = 0) uniform Font {
uint num_symbols;
uint width;
uint height;
uint space_width;
SymbolList symbol_list;
} font;
@ -47,19 +48,20 @@ layout(location = 0) in vec2 inVertexPosition;
layout(location = 0) out vec4 fragColor;
layout(location = 1) out vec2 fragUV;
layout(location = 2) out uint code;
void main() {
Character character = push.pointers.characters.characters[gl_InstanceIndex];
Symbol symbol = font.symbol_list.symbols[character.code];
float fragU = (inVertexPosition.x*symbol.width + symbol.x) / font.width;
float fragV = inVertexPosition.y + symbol.top / font.height;
float x = inVertexPosition.x * character.size * symbol.width / font.height;
float y = (inVertexPosition.y + float(symbol.top)/float(font.height)) * character.size;
float fragU = inVertexPosition.x * symbol.width/font.width;
float fragV = inVertexPosition.y * symbol.height/font.height;
float x = (inVertexPosition.x * symbol.width + symbol.left) * character.size / font.width;
float y = (inVertexPosition.y * symbol.height + symbol.top) * character.size / font.height;
fragUV = vec2(fragU, fragV);
fragColor = character.color;
gl_Position = ubo.screen * vec4(vec3(x, y, 0.0) + character.pos, 1.0);
code = character.code;
}

@ -4,7 +4,9 @@
#include "vk_mem_alloc.h"
#include "vulkan/vk_enum_string_helper.h"
#include "vulkan/vulkan_core.h"
#include "spng.h"
#include "ft2build.h"
#include FT_FREETYPE_H
ColoredRect colored_rect(float width, float height, float r, float g, float b, float a, float x, float y, float z) {
ColoredRect rect = {
@ -15,89 +17,6 @@ ColoredRect colored_rect(float width, float height, float r, float g, float b, f
return rect;
}
VkResult load_font(const char* atlas_file, const char* metadata_file, FontData* font, SymbolInfo** symbols, uint32_t** image) {
FILE* atlas = fopen(atlas_file, "rb");
if(atlas == NULL) {
return VK_ERROR_UNKNOWN;
}
fseek(atlas, 0L, SEEK_END);
size_t buf_size = ftell(atlas);
void* buf = malloc(buf_size);
fseek(atlas, 0L, SEEK_SET);
size_t read = fread(buf, buf_size, 1, atlas);
if(read != 1) {
return VK_ERROR_UNKNOWN;
}
fclose(atlas);
size_t out_size = 0;
spng_ctx *ctx = spng_ctx_new(0);
int result = 0;
result = spng_set_png_buffer(ctx, buf, buf_size);
if(result != SPNG_OK) {
return VK_ERROR_UNKNOWN;
}
result = spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &out_size);
if(result != SPNG_OK) {
return VK_ERROR_UNKNOWN;
}
*image = malloc(out_size);
result = spng_decode_image(ctx, *image, out_size, SPNG_FMT_RGBA8, 0);
if(result != SPNG_OK) {
return VK_ERROR_UNKNOWN;
}
struct spng_ihdr ihdr;
result = spng_get_ihdr(ctx, &ihdr);
if(result != SPNG_OK) {
return VK_ERROR_UNKNOWN;
}
font->info.height = ihdr.height;
font->info.width = ihdr.width;
spng_ctx_free(ctx);
free(buf);
FILE* metadata = fopen(metadata_file, "rb");
if(atlas == NULL) {
return VK_ERROR_UNKNOWN;
}
uint16_t symbol_count;
uint8_t ascent, descent;
fread(&symbol_count, 2, 1, metadata);
fread(&ascent, 1, 1, metadata);
fread(&descent, 1, 1, metadata);
font->info.num_symbols = ntohs(symbol_count);
*symbols = malloc(sizeof(SymbolInfo)*symbol_count);
font->codes = malloc(sizeof(uint16_t)*symbol_count);
uint8_t width;
uint8_t top;
uint16_t code;
uint32_t x;
for(uint16_t i = 0; i < symbol_count; i++) {
fread(&code, 2, 1, metadata);
font->codes[i] = ntohs(code);
fread(&x, 4, 1, metadata);
(*symbols)[i].x = ntohl(x);
fread(&width, 1, 1, metadata);
(*symbols)[i].width = width;
fread(&top, 1, 1, metadata);
(*symbols)[i].top = top;
}
fclose(metadata);
return VK_SUCCESS;
}
typedef struct TextPointersMemoryStruct {
VmaAllocation pointers_memory;
VmaAllocation draw_memory;
@ -321,16 +240,14 @@ VkResult render_thread(GLFWwindow* window, RenderContext* render_context) {
return result;
}
FontData test_font = {};
uint32_t* test_atlas;
SymbolInfo* test_symbols;
result = load_font("tools/test.png", "tools/test.meta", &test_font, &test_symbols, &test_atlas);
if(result != VK_SUCCESS) {
return result;
FT_Library library;
if(FT_Init_FreeType(&library) != FT_Err_Ok) {
return VK_ERROR_UNKNOWN;
}
FontDescriptor test_font_descriptor;
result = create_text_descriptor(render_context->device, render_context->allocator, ui_context.font_layout, ui_context.font_pool, &test_font, test_symbols, test_atlas, render_context->transfer_pool, render_context->transfer_queue, &test_font_descriptor);
FontDescriptor test_font;
uint32_t* charmap;
result = load_font(render_context->device, render_context->allocator, ui_context.font_layout, ui_context.font_pool, render_context->transfer_pool, render_context->transfer_queue, library, "test.ttf", 16, &charmap, &test_font);
if(result != VK_SUCCESS) {
return result;
}
@ -382,7 +299,7 @@ VkResult render_thread(GLFWwindow* window, RenderContext* render_context) {
String* mapped_string = (String*)mapped;
mapped_string->pos[0] = 200.0;
mapped_string->pos[1] = 200.0;
mapped_string->pos[1] = 0.5;
mapped_string->pos[2] = 0.5;
mapped_string->color[0] = 1.0;
mapped_string->color[1] = 1.0;
mapped_string->color[2] = 1.0;
@ -420,7 +337,7 @@ VkResult render_thread(GLFWwindow* window, RenderContext* render_context) {
.colored_rects = colored_rect_buffer,
.colored_rect_count = 3,
.font = test_font_descriptor,
.font = test_font,
.chars_size = 10*sizeof(Character),
.chars = text_pointers.characters_buffer,
.string_count = 1,
@ -428,7 +345,6 @@ VkResult render_thread(GLFWwindow* window, RenderContext* render_context) {
.string_draw = text_pointers.draw_buffer,
.string_draw_clear = text_pointers.draw_clear_buffer,
};
fprintf(stderr, "GPU Buffer: 0x%llX\n", text_pointers_address);
while(glfwWindowShouldClose(window) == 0) {
glfwPollEvents();

@ -459,7 +459,79 @@ VkResult create_font_descriptor_pools(VkDevice device, uint32_t max_sets, VkDesc
return VK_SUCCESS;
}
VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescriptorSetLayout layout, VkDescriptorPool pool, FontData* font, SymbolInfo* symbols, uint32_t* atlas, VkCommandPool transfer_pool, Queue transfer_queue, FontDescriptor* descriptor) {
VkResult load_font(VkDevice device, VmaAllocator allocator, VkDescriptorSetLayout layout, VkDescriptorPool pool,VkCommandPool transfer_pool, Queue transfer_queue, FT_Library library, const char* ttf_file, uint32_t size, uint32_t** charmap, FontDescriptor* descriptor) {
FT_Face face;
int error;
error = FT_New_Face(library, ttf_file, 0, &face);
if(error != FT_Err_Ok) {
return VK_ERROR_UNKNOWN;
}
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);
SymbolInfo* symbols = malloc(sizeof(SymbolInfo)*face->num_glyphs);
FontUniform uniform;
uint32_t glyph_index;
uint32_t max_height = 0;
uint32_t max_width = 0;
uint32_t symbol_count = 0;
uint32_t c;
// 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;
}
FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER | FT_RENDER_MODE_MONO);
uint32_t width = face->glyph->bitmap.width;
uint32_t height = face->glyph->bitmap.rows;
tmp_charmap[i] = c;
symbols[i].width = width;
symbols[i].height = height;
symbols[i].left = face->glyph->bitmap_left;
symbols[i].top = ((face->bbox.yMax*size)/face->units_per_EM) - face->glyph->bitmap_top;
symbols[i].advance = face->glyph->advance.x*16/face->units_per_EM;
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);
}
uniform.width = max_width;
uniform.height = max_height;
uniform.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);
*charmap = malloc(sizeof(uint32_t)*symbol_count);
memcpy(*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, (*charmap)[i]);
FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER | FT_RENDER_MODE_MONO);
for(uint32_t y = 0; y < face->glyph->bitmap.rows; y++) {
for(uint32_t x = 0; x < face->glyph->bitmap.width; x++) {
if(face->glyph->bitmap.buffer[y*face->glyph->bitmap.width+x] != 0) {
images[max_width*max_height*i + max_width*y + x] = 0xFFFFFFFF;
}
}
}
}
error = FT_Done_Face(face);
if(error != FT_Err_Ok) {
return VK_ERROR_UNKNOWN;
}
VkResult result;
VkDescriptorSetAllocateInfo set_allocate_info = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
@ -477,7 +549,7 @@ VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescr
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.size = sizeof(SymbolInfo)*font->info.num_symbols,
.size = sizeof(SymbolInfo)*uniform.num_symbols,
};
VmaAllocationCreateInfo symbol_memory_info = {
.usage = VMA_MEMORY_USAGE_GPU_ONLY,
@ -504,10 +576,10 @@ VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescr
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
.extent.depth = 1,
.extent.width = font->info.width,
.extent.height = font->info.height,
.extent.width = uniform.width,
.extent.height = uniform.height,
.mipLevels = 1,
.arrayLayers = 1,
.arrayLayers = uniform.num_symbols,
.format = VK_FORMAT_R8G8B8A8_SRGB,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
@ -524,12 +596,11 @@ VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescr
return result;
}
uint32_t image_size = sizeof(uint32_t)*font->info.width*font->info.height;
VkBufferCreateInfo staging_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.size = sizeof(FontUniform) + image_size + sizeof(SymbolInfo)*font->info.num_symbols,
.size = sizeof(FontUniform) + image_size*uniform.num_symbols + sizeof(SymbolInfo)*uniform.num_symbols,
};
VmaAllocationCreateInfo staging_memory_info = {
@ -548,28 +619,30 @@ VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescr
if(result != VK_SUCCESS) {
return result;
}
memcpy(mapped_staging + image_size + sizeof(FontUniform), symbols, sizeof(SymbolInfo)*font->info.num_symbols);
memcpy(mapped_staging + image_size*uniform.num_symbols + sizeof(FontUniform), symbols, sizeof(SymbolInfo)*uniform.num_symbols);
VkBufferDeviceAddressInfo address_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
.buffer = descriptor->symbols,
};
font->info.symbol_list = vkGetBufferDeviceAddress(device, &address_info);
memcpy(mapped_staging + image_size, &font->info, sizeof(FontUniform));
memcpy(mapped_staging, atlas, image_size);
uniform.symbol_list = vkGetBufferDeviceAddress(device, &address_info);
memcpy(mapped_staging + image_size*uniform.num_symbols, &uniform, sizeof(FontUniform));
memcpy(mapped_staging, images, image_size*uniform.num_symbols);
vmaUnmapMemory(allocator, staging_memory);
free(images);
free(symbols);
VkCommandBuffer command_buffer = command_begin_single(device, transfer_pool);
VkBufferCopy uniform_copy_info = {
.size = sizeof(FontUniform),
.srcOffset = image_size,
.srcOffset = image_size*uniform.num_symbols,
.dstOffset = 0,
};
vkCmdCopyBuffer(command_buffer, staging_buffer, descriptor->uniform, 1, &uniform_copy_info);
VkBufferCopy symbol_copy_info = {
.size = sizeof(SymbolInfo)*font->info.num_symbols,
.srcOffset = image_size + sizeof(FontUniform),
.size = sizeof(SymbolInfo)*uniform.num_symbols,
.srcOffset = image_size*uniform.num_symbols + sizeof(FontUniform),
.dstOffset = 0,
};
vkCmdCopyBuffer(command_buffer, staging_buffer, descriptor->symbols, 1, &symbol_copy_info);
@ -583,14 +656,14 @@ VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescr
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.subresourceRange.levelCount = 1,
.subresourceRange.layerCount = 1,
.subresourceRange.layerCount = uniform.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 = 1,
.imageSubresource.layerCount = uniform.num_symbols,
.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.imageExtent = image_info.extent,
};
@ -605,7 +678,7 @@ VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescr
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.subresourceRange.levelCount = 1,
.subresourceRange.layerCount = 1,
.subresourceRange.layerCount = uniform.num_symbols,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
};
@ -620,11 +693,11 @@ VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescr
VkImageViewCreateInfo view_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = descriptor->image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
.format = VK_FORMAT_R8G8B8A8_SRGB,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.layerCount = 1,
.layerCount = uniform.num_symbols,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,

@ -1,68 +0,0 @@
#!/usr/bin/env python3
from fontTools.ttLib import TTFont
from fontTools.unicode import Unicode
from PIL import ImageDraw, Image, ImageFont
from os import mkdir
from argparse import ArgumentParser
from struct import pack
parser = ArgumentParser()
parser.add_argument("ttf_file")
parser.add_argument("atlas")
parser.add_argument("metadata")
args = parser.parse_args()
# Add control characters, keep 0x00 for "unknown"
blacklist = set([
0x08,
0x09,
0x0D,
0x1D,
0x20,
])
# Add other control characters
blacklist.update(set(range(0x80, 0xA0)))
ttfont = TTFont(args.ttf_file)
keycodes = set()
for table in ttfont['cmap'].tables:
for keycode in table.cmap.keys():
keycodes.add(keycode)
keycodes = keycodes.difference(blacklist)
font = ImageFont.truetype(args.ttf_file, size=16)
(ascent, descent) = font.getmetrics()
widths = dict()
tops = dict()
for keycode in keycodes:
(left, top, right, bottom) = font.getbbox(chr(keycode))
widths[keycode] = right-left
tops[keycode] = top
height = ascent - descent
image = Image.new(mode="RGBA", size=(sum(widths.values()), height))
draw = ImageDraw.Draw(image)
x = 0
keycode_list = list(keycodes)
keycode_list.sort()
for keycode in keycode_list:
draw.text((x, -tops[keycode]), chr(keycode), font=font)
x += widths[keycode]
print(f"{chr(keycode)} - {tops[keycode]}")
image.save(args.atlas)
metadata = bytearray(pack("!HBB", len(keycode_list), ascent, descent))
idx = 0
x = 0
for keycode in keycode_list:
metadata += pack("!HIBB", keycode, x, widths[keycode], tops[keycode])
x += widths[keycode]
with open(args.metadata, "wb") as metadata_file:
metadata_file.write(metadata)