diff --git a/common.mk b/common.mk index 3145c60..8bbee2b 100644 --- a/common.mk +++ b/common.mk @@ -12,7 +12,7 @@ AFLAGS = --fatal-warnings CARCH = -m4-single-only -ml CFLAGS += -falign-functions=4 -ffunction-sections -fdata-sections -fshort-enums -ffreestanding -nostdlib CFLAGS += -Wall -Werror -Wfatal-errors -CFLAGS += -Wno-error=narrowing -Wno-error=unused-variable +CFLAGS += -Wno-error=narrowing -Wno-error=unused-variable -Wno-error=array-bounds= -Wno-array-bounds CFLAGS += -mfsca -funsafe-math-optimizations CFLAGS += -I$(dir $(MAKEFILE_PATH)) DEPFLAGS = -MMD -E diff --git a/dejavusansmono.data b/dejavusansmono.data new file mode 100644 index 0000000..fff4ab5 Binary files /dev/null and b/dejavusansmono.data differ diff --git a/dejavusansmono.hpp b/dejavusansmono.hpp new file mode 100644 index 0000000..9a2fb08 --- /dev/null +++ b/dejavusansmono.hpp @@ -0,0 +1,5 @@ +#include + +extern uint32_t _binary_dejavusansmono_data_start __asm("_binary_dejavusansmono_data_start"); +extern uint32_t _binary_dejavusansmono_data_end __asm("_binary_dejavusansmono_data_end"); +extern uint32_t _binary_dejavusansmono_data_size __asm("_binary_dejavusansmono_data_size"); diff --git a/example/example.mk b/example/example.mk index 014c764..c3c942c 100644 --- a/example/example.mk +++ b/example/example.mk @@ -46,6 +46,19 @@ FONT_BITMAP_OBJ = \ example/font_bitmap.elf: LDSCRIPT = $(LIB)/alt.lds example/font_bitmap.elf: $(START_OBJ) $(FONT_BITMAP_OBJ) +FONT_OUTLINE_OBJ = \ + example/font_outline.o \ + vga.o \ + holly/core.o \ + holly/region_array.o \ + holly/background.o \ + holly/ta_fifo_polygon_converter.o \ + serial.o \ + dejavusansmono.data.o + +example/font_outline.elf: LDSCRIPT = $(LIB)/alt.lds +example/font_outline.elf: $(START_OBJ) $(FONT_OUTLINE_OBJ) + MACAW_MULTIPASS_OBJ = \ example/macaw_multipass.o \ vga.o \ diff --git a/example/font_bitmap.cpp b/example/font_bitmap.cpp index b1feb1c..0065951 100644 --- a/example/font_bitmap.cpp +++ b/example/font_bitmap.cpp @@ -189,7 +189,7 @@ inline void inflate_character(const uint8_t * src, const uint8_t c) } */ - twiddle::texture2<4>(&texture[offset / 4], temp, 8, 8); + twiddle::texture2<4>(&texture[offset / 4], temp, 8, 8, 0, 0); } void inflate_font(const uint8_t * src) diff --git a/example/font_outline.cpp b/example/font_outline.cpp new file mode 100644 index 0000000..aa7ea42 --- /dev/null +++ b/example/font_outline.cpp @@ -0,0 +1,280 @@ +#include + +#include "align.hpp" + +#include "vga.hpp" +#include "holly.hpp" +#include "holly/core.hpp" +#include "holly/core_bits.hpp" +#include "holly/ta_parameter.hpp" +#include "holly/ta_fifo_polygon_converter.hpp" +#include "holly/texture_memory_alloc.hpp" +#include "memorymap.hpp" +#include "holly/background.hpp" +#include "holly/region_array.hpp" +#include "holly/ta_bits.hpp" +#include "twiddle.hpp" +#include "serial.hpp" + +#include "font/font.hpp" +#include "dejavusansmono.hpp" + +#include "sperrypc.hpp" + +struct vertex { + float x; + float y; + float z; + float u; + float v; +}; + +/* +// screen space coordinates +const struct vertex quad_verticies[4] = { + { 0.f, 64.f, 0.01f, 0.f, 1.f }, + { 0.f, 0.f, 0.01f, 0.f, 0.f }, + { 64.f, 0.f, 0.01f, 1.f, 0.f }, + { 64.f, 64.f, 0.01f, 1.f, 1.f, }, +}; + +uint32_t transform(uint32_t * ta_parameter_buf) +{ + auto parameter = ta_parameter_writer(ta_parameter_buf); + uint32_t texture_address = (offsetof (struct texture_memory_alloc, texture)); + constexpr uint32_t base_color = 0xffffffff; + auto sprite = global_sprite(base_color); + sprite.parameter_control_word = para_control::para_type::sprite + | para_control::list_type::opaque + | obj_control::col_type::packed_color + | obj_control::texture + | obj_control::_16bit_uv; + sprite.tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog + | tsp_instruction_word::texture_u_size::_8 // 8px + | tsp_instruction_word::texture_v_size::_8; // 8px + sprite.texture_control_word = texture_control_word::pixel_format::_565 + | texture_control_word::scan_order::twiddled + | texture_control_word::texture_address(texture_address / 8); + parameter.append() = sprite; + + parameter.append() = + vertex_sprite_type_1(quad_verticies[0].x, + quad_verticies[0].y, + quad_verticies[0].z, + quad_verticies[1].x, + quad_verticies[1].y, + quad_verticies[1].z, + quad_verticies[2].x, + quad_verticies[2].y, + quad_verticies[2].z, + quad_verticies[3].x, + quad_verticies[3].y, + uv_16bit(quad_verticies[0].u, quad_verticies[0].v), + uv_16bit(quad_verticies[1].u, quad_verticies[1].v), + uv_16bit(quad_verticies[2].u, quad_verticies[2].v)); + // curiously, there is no `dz` in vertex_sprite_type_1 + // curiously, there is no `du_dv` in vertex_sprite_type_1 + + parameter.append() = global_end_of_list(); + + return parameter.offset; +} +*/ + +const struct vertex strip_vertices[4] = { + // [ position ] [ uv coordinates ] + { 0.f, 1.f, 0.f, 0.f, 1.f, }, + { 0.f, 0.f, 0.f, 0.f, 0.f, }, + { 1.f, 1.f, 0.f, 1.f, 1.f, }, + { 1.f, 0.f, 0.f, 1.f, 0.f, }, +}; +constexpr uint32_t strip_length = (sizeof (strip_vertices)) / (sizeof (struct vertex)); + +uint32_t transform(ta_parameter_writer& parameter, + const uint32_t first_char_code, const glyph * glyphs, + const char * s, const uint32_t len, + const uint32_t y_offset) +{ + uint32_t texture_address = (offsetof (struct texture_memory_alloc, texture)); + + uint32_t advance = 0; // in 26.6 fixed-point + + for (uint32_t string_ix = 0; string_ix < len; string_ix++) { + auto polygon = global_polygon_type_0(texture_address); + polygon.parameter_control_word = para_control::para_type::polygon_or_modifier_volume + | para_control::list_type::opaque + | obj_control::col_type::packed_color + | obj_control::texture; + + polygon.tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog + | tsp_instruction_word::texture_u_size::_128 + | tsp_instruction_word::texture_v_size::_256; + + polygon.texture_control_word = texture_control_word::pixel_format::_8bpp_palette + | texture_control_word::scan_order::twiddled + | texture_control_word::texture_address(texture_address / 8); + parameter.append() = polygon; + + char c = s[string_ix]; + auto& glyph = glyphs[c - first_char_code]; + + for (uint32_t i = 0; i < strip_length; i++) { + bool end_of_strip = i == strip_length - 1; + + float x = strip_vertices[i].x; + float y = strip_vertices[i].y; + float z = strip_vertices[i].z; + + x *= glyph.bitmap.width; + y *= glyph.bitmap.height; + x += 100.f + ((advance + glyph.metrics.horiBearingX) >> 6); + y += 200.f - ((glyph.metrics.horiBearingY) >> 6); + y += y_offset >> 6; + z = 1.f / (z + 10.f); + + float u = strip_vertices[i].u; + float v = strip_vertices[i].v; + u *= glyph.bitmap.width; + v *= glyph.bitmap.height; + u += glyph.bitmap.x; + v += glyph.bitmap.y; + u = u / 128.f; + v = v / 256.f; + + parameter.append() = + vertex_polygon_type_3(x, y, z, + u, v, + 0x00000000, // base_color + end_of_strip); + } + + advance += glyph.metrics.horiAdvance; + } + + return parameter.offset; +} + + +void init_texture_memory(const struct opb_size& opb_size) +{ + auto mem = reinterpret_cast(texture_memory32); + + background_parameter(mem->background, 0xff0000ff); + + region_array2(mem->region_array, + (offsetof (struct texture_memory_alloc, object_list)), + 640 / 32, // width + 480 / 32, // height + opb_size + ); +} + +void inflate_font(const uint32_t * src, const uint32_t size) +{ + auto mem = reinterpret_cast(texture_memory64); + auto texture = reinterpret_cast(mem->texture); + + for (uint32_t i = 0; i < (size / 4); i++) { + texture[i] = src[i]; + } +} + +void palette_data() +{ + holly.PAL_RAM_CTRL = pal_ram_ctrl::pixel_format::rgb565; + + // palette of 256 greys + for (int i = 0; i < 256; i++) { + holly.PALETTE_RAM[i] = ((i >> 3) << 11) + | ((i >> 2) << 5) + | ((i >> 3) << 0); + } +} + +uint32_t _ta_parameter_buf[((32 * 10 * 17) + 32) / 4]; + +void main() +{ + vga(); + + auto font = reinterpret_cast(&_binary_dejavusansmono_data_start); + auto glyphs = reinterpret_cast(&font[1]); + auto texture = reinterpret_cast(&glyphs[font->glyph_count]); + + serial::integer(font->first_char_code); + serial::integer(font->glyph_count); + serial::integer(font->glyph_height); + serial::integer(font->texture_width); + serial::integer(font->texture_height); + serial::character('\n'); + serial::integer(((uint32_t)glyphs) - ((uint32_t)font)); + serial::integer(((uint32_t)texture) - ((uint32_t)font)); + + uint32_t texture_size = font->texture_width * font->texture_height; + inflate_font(texture, texture_size); + palette_data(); + + // The address of `ta_parameter_buf` must be a multiple of 32 bytes. + // This is mandatory for ch2-dma to the ta fifo polygon converter. + uint32_t * ta_parameter_buf = align_32byte(_ta_parameter_buf); + + constexpr uint32_t ta_alloc = ta_alloc_ctrl::pt_opb::no_list + | ta_alloc_ctrl::tm_opb::no_list + | ta_alloc_ctrl::t_opb::no_list + | ta_alloc_ctrl::om_opb::no_list + | ta_alloc_ctrl::o_opb::_16x4byte; + + constexpr struct opb_size opb_size = { .opaque = 16 * 4 + , .opaque_modifier = 0 + , .translucent = 0 + , .translucent_modifier = 0 + , .punch_through = 0 + }; + + constexpr uint32_t tiles = (640 / 32) * (320 / 32); + + holly.SOFTRESET = softreset::pipeline_soft_reset + | softreset::ta_soft_reset; + holly.SOFTRESET = 0; + + core_init(); + init_texture_memory(opb_size); + + uint32_t frame_ix = 0; + constexpr uint32_t num_frames = 1; + + const char ana[18] = "A from ana i know"; + const char cabal[27] = "where is this secret cabal"; + + while (true) { + ta_polygon_converter_init(opb_size.total() * tiles, ta_alloc, + 640, 480); + + auto parameter = ta_parameter_writer(ta_parameter_buf); + + transform(parameter, font->first_char_code, glyphs, + ana, 17, + 0); + + transform(parameter, font->first_char_code, glyphs, + cabal, 26, + font->glyph_height); + + parameter.append() = global_end_of_list(); + + ta_polygon_converter_transfer(ta_parameter_buf, parameter.offset); + ta_wait_opaque_list(); + + core_start_render(frame_ix, num_frames); + + v_sync_out(); + v_sync_in(); + core_wait_end_of_render_video(frame_ix, num_frames); + + frame_ix++; + } +} diff --git a/font/font.hpp b/font/font.hpp new file mode 100644 index 0000000..27fb418 --- /dev/null +++ b/font/font.hpp @@ -0,0 +1,39 @@ +// this file is designed to be platform-agnostic +#pragma once + +#include + +// metrics are 26.6 fixed point +struct glyph_metrics { + int32_t horiBearingX; + int32_t horiBearingY; + int32_t horiAdvance; +} __attribute__ ((packed)); + +static_assert((sizeof (glyph_metrics)) == ((sizeof (int32_t)) * 3)); + +struct glyph_bitmap { + uint16_t x; + uint16_t y; + uint16_t width; + uint16_t height; +} __attribute__ ((packed)); + +static_assert((sizeof (glyph_bitmap)) == ((sizeof (uint16_t)) * 4)); + +struct glyph { + glyph_bitmap bitmap; + glyph_metrics metrics; +} __attribute__ ((packed)); + +static_assert((sizeof (glyph)) == ((sizeof (glyph_bitmap)) + (sizeof (glyph_metrics)))); + +struct font { + uint32_t first_char_code; + uint16_t glyph_count; + uint16_t glyph_height; + uint16_t texture_width; + uint16_t texture_height; +} __attribute__ ((packed)); + +static_assert((sizeof (font)) == ((sizeof (uint32_t)) * 3)); diff --git a/font/font_draw.cpp b/font/font_draw.cpp new file mode 100644 index 0000000..a159dbc --- /dev/null +++ b/font/font_draw.cpp @@ -0,0 +1,31 @@ + + +uint32_t pixel_data(const uint32_t * dest, const uint8_t * glyph_bitmaps, const uint32_t size) +{ + const uint8_t temp[size]; + + const uint32_t * buf = reinterpret_cast(&glyph_bitmaps[0]); + + copy(table, buf, size); + + return table_address; +} + +uint32_t font_data(const uint32_t * buf, state& state) +{ + constexpr uint32_t font_offset = 0; + constexpr uint32_t glyphs_offset = (sizeof (struct font)); + const uint32_t glyph_bitmaps_offset = (sizeof (struct font)) + (sizeof (struct glyph)) * font->glyph_index; + + auto font = reinterpret_cast(&buf[font_offset / 4]); + auto glyphs = reinterpret_cast(&buf[glyphs_offset / 4]); + auto glyph_bitmaps = &(reinterpret_cast(buf))[glyph_bitmaps_offset]; + + for (uint32_t glyph_ix = 0; glyph_ix < font->glyph_index; glyph_ix++) { + auto& glyph_bitmap = glyphs[glyph_ix].bitmap; + + auto bitmap = &glyph_bitmaps[glyph_bitmap.offset]; + // bitmap.pitch may be zero; bitmap.pitch is a multiple of 8 pixels + SIZE__X(bitmap.pitch) | SIZE__Y(bitmap.rows); + } +} diff --git a/tools/2d-pack.cpp b/tools/2d-pack.cpp new file mode 100644 index 0000000..1b6edd8 --- /dev/null +++ b/tools/2d-pack.cpp @@ -0,0 +1,113 @@ +#include +#include +#include + +#include "insertion_sort.hpp" +#include "rect.hpp" +#include "../twiddle.hpp" +#include + +struct size { + uint32_t width; + uint32_t height; +}; + +constexpr struct size max_texture = {1024, 1024}; + +inline bool area_valid(const uint8_t texture[max_texture.height][max_texture.width], + const uint32_t x_offset, + const uint32_t y_offset, + const struct rect& rect, + const struct size& window) +{ + for (uint32_t yi = 0; yi < rect.height; yi++) { + for (uint32_t xi = 0; xi < rect.width; xi++) { + uint32_t x = x_offset + xi; + uint32_t y = y_offset + yi; + + if (texture[y][x] != 0) + return false; + + if (x >= window.width || y >= window.height) + return false; + } + } + + return true; +} + +bool pack_into(uint8_t texture[max_texture.height][max_texture.width], + struct size& window, + struct rect& rect) +{ + uint32_t z_curve_ix = 0; + + if (rect.width == 0 || rect.height == 0) { + rect.x = 0; + rect.y = 0; + return false; + } + + while (true) { + auto [x_offset, y_offset] = twiddle::from_ix(z_curve_ix); + + if (x_offset >= window.width and y_offset >= window.height) { + std::cerr << z_curve_ix << ' ' << window.width << ' ' << window.height << '\n'; + assert(window.width < max_texture.width || window.height < max_texture.height); + if (window.width == window.height) { window.height *= 2; } + else { window.width *= 2; } + + // when the window changes; start again from the beginning and + // re-check earlier locations that might have been skipped due + // to window size + z_curve_ix = 0; + } + + if (area_valid(texture, x_offset, y_offset, rect, window)) { + for (uint32_t yi = 0; yi < rect.height; yi++) { + for (uint32_t xi = 0; xi < rect.width; xi++) { + uint32_t x = x_offset + xi; + uint32_t y = y_offset + yi; + + texture[y][x] = 1; + } + } + + rect.x = x_offset; + rect.y = y_offset; + + return true; + } else { + z_curve_ix += 1; + continue; + } + } +} + +uint32_t pack_all(struct rect * rects, const uint32_t num_rects) +{ + uint8_t texture[max_texture.height][max_texture.width] = { 0 }; + size window = {1, 1}; + + // sort all rectangles by size + insertion_sort(rects, num_rects); + + uint32_t max_x = 0; + uint32_t max_y = 0; + + for (uint32_t i = 0; i < num_rects; i++) { + std::cerr << "pack " << i << '\n'; + bool packed = pack_into(texture, window, rects[i]); + if (packed) { + const uint32_t x = rects[i].x + rects[i].width - 1; + const uint32_t y = rects[i].y + rects[i].height - 1; + if (x > max_x) max_x = x; + if (y > max_y) max_y = y; + } + } + + const uint32_t curve_ix = twiddle::from_xy(max_x, max_y); + std::cerr << "max xy " << max_x << ' ' << max_y << '\n'; + std::cerr << "curve_ix " << curve_ix << '\n'; + return curve_ix; +} diff --git a/tools/2d-pack.hpp b/tools/2d-pack.hpp new file mode 100644 index 0000000..33f3ea4 --- /dev/null +++ b/tools/2d-pack.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +uint32_t pack_all(struct rect * rects, const uint32_t num_rects); diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 0000000..0f38c97 --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,23 @@ +CFLAGS = -Og -g -gdwarf-4 -Wall -Wextra -Werror -Wfatal-errors -ggdb -Wno-error=unused-parameter -Wno-error=unused-variable -fstack-protector-strong +CXXFLAGS = -std=c++23 + +CFLAGS += $(shell pkg-config --cflags freetype2) +LDFLAGS = $(shell pkg-config --libs freetype2) + +all: ttf-outline + +%.o: %.cpp + $(CXX) $(CFLAGS) $(CXXFLAGS) -c $< -o $@ + +%: %.o + $(CXX) $(LDFLAGS) $^ -o $@ + +ttf-outline: ttf-outline.o 2d-pack.o + +clean: + rm -f *.o ttf-convert ttf-bitmap + +.SUFFIXES: +.INTERMEDIATE: +.SECONDARY: +.PHONY: all clean diff --git a/tools/insertion_sort.hpp b/tools/insertion_sort.hpp new file mode 100644 index 0000000..26bef9d --- /dev/null +++ b/tools/insertion_sort.hpp @@ -0,0 +1,17 @@ +#include +#include +#include + +template +void insertion_sort(T * arr, int len) +{ + int i = 1; + while (i < len) { + int j = i; + while (j > 0 && arr[j - 1] < arr[j]) { + std::swap(arr[j - 1], arr[j]); + j -= 1; + } + i += 1; + } +} diff --git a/tools/rect.hpp b/tools/rect.hpp new file mode 100644 index 0000000..1df0be7 --- /dev/null +++ b/tools/rect.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +struct rect { + uint32_t char_code; + uint32_t width; + uint32_t height; + int32_t x; + int32_t y; + + std::strong_ordering operator<=>(const rect& b) const + { + return (width * height) <=> (b.width * b.height); + } +}; diff --git a/tools/sizes.py b/tools/sizes.py new file mode 100644 index 0000000..d56af84 --- /dev/null +++ b/tools/sizes.py @@ -0,0 +1,98 @@ +sizes = [ +(0, 0), +(23, 4), +(9, 9), +(23, 20), +(30, 16), +(23, 19), +(23, 20), +(9, 3), +(29, 8), +(29, 8), +(14, 15), +(17, 17), +(9, 6), +(3, 9), +(5, 5), +(26, 16), +(23, 16), +(23, 15), +(23, 15), +(23, 15), +(23, 17), +(23, 15), +(23, 16), +(23, 15), +(23, 16), +(23, 17), +(17, 5), +(21, 6), +(16, 17), +(10, 17), +(16, 17), +(23, 14), +(27, 19), +(23, 19), +(23, 16), +(23, 15), +(23, 16), +(23, 15), +(23, 15), +(23, 17), +(23, 16), +(23, 14), +(23, 14), +(23, 18), +(23, 15), +(23, 17), +(23, 16), +(23, 17), +(23, 15), +(27, 17), +(23, 18), +(23, 16), +(23, 19), +(23, 15), +(23, 19), +(23, 20), +(23, 19), +(23, 19), +(23, 17), +(29, 7), +(26, 16), +(29, 8), +(9, 18), +(3, 20), +(6, 8), +(18, 15), +(24, 15), +(18, 14), +(24, 16), +(18, 17), +(24, 14), +(25, 16), +(24, 14), +(24, 16), +(31, 11), +(24, 16), +(24, 15), +(18, 17), +(18, 14), +(18, 16), +(25, 16), +(25, 15), +(18, 14), +(18, 14), +(23, 15), +(18, 14), +(18, 17), +(18, 20), +(18, 18), +(25, 18), +(18, 14), +(30, 13), +(32, 3), +(30, 13), +(5, 17), +(28, 17), +] diff --git a/tools/ttf-outline.cpp b/tools/ttf-outline.cpp new file mode 100644 index 0000000..11740e0 --- /dev/null +++ b/tools/ttf-outline.cpp @@ -0,0 +1,254 @@ +#include +#include +#include + +#include +#include + +#include +#include FT_FREETYPE_H + +#include "../font/font.hpp" +#include "rect.hpp" +#include "2d-pack.hpp" +#include "../twiddle.hpp" + +std::endian _target_endian; + +uint32_t byteswap(const uint32_t n) +{ + if (std::endian::native != _target_endian) { + return std::byteswap(n); + } else { + return n; + } +} + +int32_t +load_outline_char_bitmap_rect(const FT_Face face, + const FT_ULong char_code, + struct rect& rect) +{ + FT_Error error; + FT_UInt glyph_index = FT_Get_Char_Index(face, char_code); + + error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); + if (error) { + std::cerr << "FT_Load_Glyph " << FT_Error_String(error) << '\n'; + return -1; + } + + rect.char_code = char_code; + rect.height = face->glyph->bitmap.rows; + rect.width = face->glyph->bitmap.width; + rect.x = -1; + rect.y = -1; + + return 0; +} + +int32_t +load_outline_char(const FT_Face face, + const FT_ULong char_code, + glyph * glyph, + uint8_t * texture, + struct rect& rect) +{ + FT_Error error; + FT_UInt glyph_index = FT_Get_Char_Index(face, char_code); + + error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); + if (error) { + std::cerr << "FT_Load_Glyph " << FT_Error_String(error) << '\n'; + return -1; + } + + std::cerr << "size " << face->glyph->bitmap.rows << ' ' << face->glyph->bitmap.width << '\n'; + + //assert(face->glyph->format == FT_GLYPH_FORMAT_OUTLINE); + + error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); + if (error) { + std::cerr << "FT_Render_Glyph " << FT_Error_String(error) << '\n'; + return -1; + } + + if (!(face->glyph->bitmap.pitch > 0)) { + assert(face->glyph->bitmap.width == 0); + assert(face->glyph->bitmap.rows == 0); + } + + assert(face->glyph->bitmap.width == rect.width); + assert(face->glyph->bitmap.rows == rect.height); + + std::cerr << "num_grays " << face->glyph->bitmap.num_grays << '\n'; + switch (face->glyph->bitmap.num_grays) { + case 2: + assert(false); + break; + case 256: + std::cerr << "rxy " << rect.x << ' ' << rect.y << '\n'; + std::cerr << "rwh " << rect.width << ' ' << rect.height << '\n'; + + for (uint32_t y = 0; y < rect.height; y++) { + for (uint32_t x = 0; x < rect.width; x++) { + uint32_t texture_ix = (rect.y + y) * 1024 + (rect.x + x); + assert(texture_ix < 1024 * 1024); + texture[texture_ix] = face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch + x]; + } + } + + break; + default: + assert(face->glyph->bitmap.num_grays == -1); + } + + glyph_bitmap& bitmap = glyph->bitmap; + bitmap.x = byteswap(rect.x); + bitmap.y = byteswap(rect.y); + bitmap.width = byteswap(rect.width); + bitmap.height = byteswap(rect.height); + + glyph_metrics& metrics = glyph->metrics; + metrics.horiBearingX = byteswap(face->glyph->metrics.horiBearingX); + metrics.horiBearingY = byteswap(face->glyph->metrics.horiBearingY); + metrics.horiAdvance = byteswap(face->glyph->metrics.horiAdvance); + + return 0; +} + +enum { + start_hex = 1, + end_hex = 2, + pixel_size = 3, + target_endian = 4, + font_file_path = 5, + output_file_path = 6, + argv_length = 7 +}; + +void load_all_positions(const FT_Face face, + const uint32_t start, + const uint32_t end, + glyph * glyphs, + uint32_t * texture + ) +{ + const uint32_t num_glyphs = (end - start) + 1; + struct rect rects[num_glyphs]; + + uint8_t temp[1024 * 1024]; + + // first, load all rectangles + for (uint32_t char_code = start; char_code <= end; char_code++) { + load_outline_char_bitmap_rect(face, char_code, rects[char_code - start]); + } + + // calculate a 2-dimensional packing for the rectangles + pack_all(rects, num_glyphs); + + // asdf + for (uint32_t i = 0; i < num_glyphs; i++) { + const uint32_t char_code = rects[i].char_code; + int32_t err = load_outline_char(face, + char_code, + &glyphs[char_code - start], + temp, + rects[i]); + if (err < 0) assert(false); + } + + twiddle::texture2<8>(texture, temp, + 128, 256, + 1024); +} + +int main(int argc, char *argv[]) +{ + FT_Library library; + FT_Face face; + FT_Error error; + + if (argc != argv_length) { + std::cerr << "usage: " << argv[0] << " [start-hex] [end-hex] [pixel-size] [target-endian] [font-file-path] [output-file-path]\n\n"; + std::cerr << "ex. 1: " << argv[0] << " 3000 30ff 30 little ipagp.ttf font.bin\n"; + std::cerr << "ex. 2: " << argv[0] << " 20 7f 30 big DejaVuSans.ttf font.bin\n"; + return -1; + } + + error = FT_Init_FreeType(&library); + if (error) { + std::cerr << "FT_Init_FreeType\n"; + return -1; + } + + error = FT_New_Face(library, argv[font_file_path], 0, &face); + if (error) { + std::cerr << "FT_New_Face\n"; + return -1; + } + + std::stringstream ss3; + int font_size; + ss3 << std::dec << argv[pixel_size]; + ss3 >> font_size; + std::cerr << "font_size: " << font_size << '\n'; + + error = FT_Set_Pixel_Sizes(face, 0, font_size); + if (error) { + std::cerr << "FT_Set_Pixel_Sizes: " << FT_Error_String(error) << error << '\n'; + return -1; + } + + if (std::string(argv[target_endian]).compare("little") == 0) { + _target_endian = std::endian::little; + } else if (std::string(argv[target_endian]).compare("big") == 0) { + _target_endian = std::endian::big; + } else { + std::cerr << "unknown endian: " << argv[target_endian] << '\n'; + std::cerr << "expected one of: big, little\n"; + return -1; + } + + std::cerr << "here\n"; + + uint32_t start; + uint32_t end; + + std::stringstream ss1; + ss1 << std::hex << argv[start_hex]; + ss1 >> start; + std::stringstream ss2; + ss2 << std::hex << argv[end_hex]; + ss2 >> end; + + uint32_t num_glyphs = (end - start) + 1; + glyph glyphs[num_glyphs]; + uint32_t texture[(1024 * 1024) / 4]; + memset(texture, 0x00, 1024 * 1024); + + font font; + font.first_char_code = byteswap(start); + font.glyph_count = byteswap(num_glyphs); + font.glyph_height = byteswap(face->size->metrics.height); + font.texture_width = 128; + font.texture_height = 256; + + load_all_positions(face, start, end, glyphs, texture); + + std::cerr << "start: 0x" << std::hex << start << '\n'; + std::cerr << "end: 0x" << std::hex << end << '\n'; + + FILE * out = fopen(argv[output_file_path], "w"); + if (out == NULL) { + perror("fopen(w)"); + return -1; + } + + uint32_t texture_size = 128 * 256; + fwrite(reinterpret_cast(&font), (sizeof (font)), 1, out); + fwrite(reinterpret_cast(&glyphs[0]), (sizeof (glyph)), num_glyphs, out); + fwrite(reinterpret_cast(&texture[0]), (sizeof (uint8_t)), texture_size, out); + + fclose(out); +} diff --git a/tools/z-curve-test.py b/tools/z-curve-test.py new file mode 100644 index 0000000..58b10a4 --- /dev/null +++ b/tools/z-curve-test.py @@ -0,0 +1,172 @@ +import sys + +def from_xy(x: int, y: int) -> int: + # maximum texture size : 1024x1024 + # maximum 1-dimensional index: 0xfffff + # bits : 19-0 + + twiddle_ix = 0 + for i in range(0, (19 // 2) + 1): + twiddle_ix |= ((y >> i) & 1) << (i * 2 + 0) + twiddle_ix |= ((x >> i) & 1) << (i * 2 + 1) + + return twiddle_ix + +assert from_xy(0b000, 0b000) == 0 +assert from_xy(0b001, 0b000) == 2 +assert from_xy(0b010, 0b000) == 8 +assert from_xy(0b011, 0b000) == 10 +assert from_xy(0b100, 0b000) == 32 +assert from_xy(0b101, 0b000) == 34 +assert from_xy(0b110, 0b000) == 40 +assert from_xy(0b111, 0b000) == 42 + +assert from_xy(0b000, 0b001) == 1 +assert from_xy(0b000, 0b010) == 4 +assert from_xy(0b000, 0b011) == 5 +assert from_xy(0b000, 0b100) == 16 +assert from_xy(0b000, 0b101) == 17 +assert from_xy(0b000, 0b110) == 20 +assert from_xy(0b000, 0b111) == 21 + +def from_ix(z_curve_ix: int) -> tuple[int, int]: + x_y = [0, 0] + xyi = 0 + while z_curve_ix != 0: + x_y[(xyi + 1) % 2] |= (z_curve_ix & 1) << (xyi // 2) + z_curve_ix >>= 1 + xyi += 1 + return tuple(x_y) + +assert from_ix(17) == (0b000, 0b101) +assert from_ix(21) == (0b000, 0b111) +assert from_ix(42) == (0b111, 0b000) + +""" +def texture(src: list[int], + width: int, height: int) -> list[int]: + dst = [0] * (width * height) + + for y in range(0, height): + for x in range(0, width): + twiddle_ix = from_xy(x, y) + value = src[y * width + x] + dst[twiddle_ix] = value + + return dst +""" + +import random +from sizes import sizes as _sizes +from colorsys import hsv_to_rgb + +def all_colors(num): + for i in range(num): + hue = i / (num - 1) + rgb = hsv_to_rgb(hue, 1.0, 1.0) + def color(): + for i in rgb: + yield int(i * 255) + yield tuple(color()) + +def random_colors(num): + l = list(all_colors(num)) + random.shuffle(l) + return l + +def area_pixels(x_off, y_off, width, height): + for x in range(height): + for y in range(width): + px_ix = (x_off + x, y_off + y) + yield px_ix + +max_size = (1, 1) + +def pack_into(texture: dict[tuple[int, int], int], + width_height: tuple[int, int], + z_curve_ix: int): + global max_size + # ignore passed z_curve_ix + z_curve_ix = 0 + + width, height = width_height + if width == 0 or height == 0: + return (0, 0), z_curve_ix + + while True: + x_off, y_off = from_ix(z_curve_ix) + if x_off >= max_size[0] and y_off >= max_size[0]: + if max_size[0] == max_size[1]: + max_size = (max_size[0], max_size[1] * 2) + else: + max_size = (max_size[0] * 2, max_size[1]) + z_curve_ix = 0 + + if all((pixel not in texture) and (pixel[0] < max_size[0] and pixel[1] < max_size[1]) + for pixel in area_pixels(x_off, y_off, width, height)): + #x, y = x_off + width - 1, y_off + height - 1 + return (x_off, y_off), z_curve_ix + else: + z_curve_ix += 1 + +def sort_by_area(width_height): + width, height = width_height + area = width * height + return area + +max_ix = 0 + +def insert_into_texture(texture, color_ix, x_off__y_off, width_height): + global max_ix + x_off, y_off = x_off__y_off + width, height = width_height + for px_ix in area_pixels(x_off, y_off, width, height): + assert px_ix not in texture, px_ix + ix = from_xy(*px_ix) + if ix > max_ix: + max_ix = ix + texture[px_ix] = color_ix + +def pack_all(sizes): + global max_size + max_size = (1, 1) + global max_ix + max_ix = 0 + + sorted_sizes = sorted(sizes, key=sort_by_area, reverse=True) + z_curve_ix = 0 + texture = dict() + for color_ix, width_height in enumerate(sorted_sizes): + x_off__y_off, z_curve_ix = pack_into(texture, width_height, z_curve_ix) + insert_into_texture(texture, color_ix, x_off__y_off, width_height) + #if color_ix == 2: + # break + return texture + +def ppm(texture: dict, num_colors): + colors = random_colors(num_colors) + max_x = max(px[0] for px in texture.keys()) + max_y = max(px[1] for px in texture.keys()) + width = max_x + 1 + height = max_y + 1 + print(" max xy:", max_x, max_y, file=sys.stderr) + print("max curve ix:", from_xy(max_x, max_y), file=sys.stderr) + + yield "P3" + yield f"{width} {height}" + yield "255" + for y in range(0, height): + for x in range(0, width): + if (x, y) in texture: + i = texture[(x, y)] + color = colors[i] + yield " ".join(map(str, color)) + else: + yield "0 0 0" + +if __name__ == '__main__': + texture = pack_all((x, y) for (y, x) in _sizes) + print("ideal size", sum(x * y for x, y in _sizes), file=sys.stderr) + image = list(ppm(texture, len(_sizes))) + print("\n".join(image)) + pass diff --git a/twiddle.hpp b/twiddle.hpp index 48eaaa7..9ccedd1 100644 --- a/twiddle.hpp +++ b/twiddle.hpp @@ -93,22 +93,23 @@ from_ix(uint32_t curve_ix) return {x, y}; } -static_assert(from_ix(0) == std::tuple{0b000, 0b000}); -static_assert(from_ix(2) == std::tuple{0b001, 0b000}); -static_assert(from_ix(8) == std::tuple{0b010, 0b000}); -static_assert(from_ix(10) == std::tuple{0b011, 0b000}); -static_assert(from_ix(32) == std::tuple{0b100, 0b000}); -static_assert(from_ix(34) == std::tuple{0b101, 0b000}); -static_assert(from_ix(40) == std::tuple{0b110, 0b000}); -static_assert(from_ix(42) == std::tuple{0b111, 0b000}); +using xy_type = std::tuple; +static_assert(from_ix(0) == xy_type{0b000, 0b000}); +static_assert(from_ix(2) == xy_type{0b001, 0b000}); +static_assert(from_ix(8) == xy_type{0b010, 0b000}); +static_assert(from_ix(10) == xy_type{0b011, 0b000}); +static_assert(from_ix(32) == xy_type{0b100, 0b000}); +static_assert(from_ix(34) == xy_type{0b101, 0b000}); +static_assert(from_ix(40) == xy_type{0b110, 0b000}); +static_assert(from_ix(42) == xy_type{0b111, 0b000}); -static_assert(from_ix(1) == std::tuple{0b000, 0b001}); -static_assert(from_ix(4) == std::tuple{0b000, 0b010}); -static_assert(from_ix(5) == std::tuple{0b000, 0b011}); -static_assert(from_ix(16) == std::tuple{0b000, 0b100}); -static_assert(from_ix(17) == std::tuple{0b000, 0b101}); -static_assert(from_ix(20) == std::tuple{0b000, 0b110}); -static_assert(from_ix(21) == std::tuple{0b000, 0b111}); +static_assert(from_ix(1) == xy_type{0b000, 0b001}); +static_assert(from_ix(4) == xy_type{0b000, 0b010}); +static_assert(from_ix(5) == xy_type{0b000, 0b011}); +static_assert(from_ix(16) == xy_type{0b000, 0b100}); +static_assert(from_ix(17) == xy_type{0b000, 0b101}); +static_assert(from_ix(20) == xy_type{0b000, 0b110}); +static_assert(from_ix(21) == xy_type{0b000, 0b111}); template void texture(volatile T * dst, const T * src, const uint32_t width, const uint32_t height) @@ -137,7 +138,9 @@ void texture_4bpp(volatile T * dst, const T * src, const uint32_t width, const u } template -void texture2(volatile T * dst, const U * src, const uint32_t width, const uint32_t height) +void texture2(volatile T * dst, const U * src, + const uint32_t width, const uint32_t height, + const uint32_t stride) { constexpr uint32_t t_bits = (sizeof (T)) * 8; constexpr uint32_t bits_per_pixel = B; @@ -147,9 +150,10 @@ void texture2(volatile T * dst, const U * src, const uint32_t width, const uint3 static_assert(pixels_per_t == 1 || pixels_per_t == 2 || pixels_per_t == 4 || pixels_per_t == 8); T dst_val = 0; - for (uint32_t curve_ix = 0; curve_ix < (width * height); curve_ix++) { + const uint32_t end_ix = from_xy(width - 1, height - 1); + for (uint32_t curve_ix = 0; curve_ix <= end_ix; curve_ix++) { auto [x, y] = from_ix(curve_ix); - const U src_val = src[y * width + x]; + const U src_val = src[y * stride + x]; if constexpr (pixels_per_t == 1) { dst[curve_ix] = src_val; } else {