example: implement font_outline

This still needs to be cleaned up, particularly to properly pass the
texture size around--there are a few unnecessary '128x256' magic
numbers scattered in the code.
This commit is contained in:
Zack Buhman 2023-12-22 00:03:52 +08:00
parent a8cceee46e
commit 3b7e1eaef8
17 changed files with 1090 additions and 20 deletions

View File

@ -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

BIN
dejavusansmono.data Normal file

Binary file not shown.

5
dejavusansmono.hpp Normal file
View File

@ -0,0 +1,5 @@
#include <cstdint>
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");

View File

@ -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 \

View File

@ -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)

280
example/font_outline.cpp Normal file
View File

@ -0,0 +1,280 @@
#include <cstdint>
#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<global_sprite>() = sprite;
parameter.append<vertex_sprite_type_1>() =
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>() = 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<global_polygon_type_0>() = 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>() =
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<volatile texture_memory_alloc *>(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<volatile texture_memory_alloc *>(texture_memory64);
auto texture = reinterpret_cast<volatile uint32_t *>(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<const struct font *>(&_binary_dejavusansmono_data_start);
auto glyphs = reinterpret_cast<const struct glyph *>(&font[1]);
auto texture = reinterpret_cast<const uint32_t *>(&glyphs[font->glyph_count]);
serial::integer<uint32_t>(font->first_char_code);
serial::integer<uint32_t>(font->glyph_count);
serial::integer<uint32_t>(font->glyph_height);
serial::integer<uint32_t>(font->texture_width);
serial::integer<uint32_t>(font->texture_height);
serial::character('\n');
serial::integer<uint32_t>(((uint32_t)glyphs) - ((uint32_t)font));
serial::integer<uint32_t>(((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>() = 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++;
}
}

39
font/font.hpp Normal file
View File

@ -0,0 +1,39 @@
// this file is designed to be platform-agnostic
#pragma once
#include <cstdint>
// 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));

31
font/font_draw.cpp Normal file
View File

@ -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<const uint32_t *>(&glyph_bitmaps[0]);
copy<uint32_t>(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<const font *>(&buf[font_offset / 4]);
auto glyphs = reinterpret_cast<const glyph *>(&buf[glyphs_offset / 4]);
auto glyph_bitmaps = &(reinterpret_cast<const uint8_t *>(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);
}
}

113
tools/2d-pack.cpp Normal file
View File

@ -0,0 +1,113 @@
#include <cassert>
#include <cstdint>
#include <compare>
#include "insertion_sort.hpp"
#include "rect.hpp"
#include "../twiddle.hpp"
#include <iostream>
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;
}

5
tools/2d-pack.hpp Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include <cstdint>
uint32_t pack_all(struct rect * rects, const uint32_t num_rects);

23
tools/Makefile Normal file
View File

@ -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

17
tools/insertion_sort.hpp Normal file
View File

@ -0,0 +1,17 @@
#include <cstdint>
#include <algorithm>
#include <iostream>
template <typename T>
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;
}
}

16
tools/rect.hpp Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
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);
}
};

98
tools/sizes.py Normal file
View File

@ -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),
]

254
tools/ttf-outline.cpp Normal file
View File

@ -0,0 +1,254 @@
#include <bit>
#include <sstream>
#include <iostream>
#include <cassert>
#include <cstdint>
#include <ft2build.h>
#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<void*>(&font), (sizeof (font)), 1, out);
fwrite(reinterpret_cast<void*>(&glyphs[0]), (sizeof (glyph)), num_glyphs, out);
fwrite(reinterpret_cast<void*>(&texture[0]), (sizeof (uint8_t)), texture_size, out);
fclose(out);
}

172
tools/z-curve-test.py Normal file
View File

@ -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

View File

@ -93,22 +93,23 @@ from_ix(uint32_t curve_ix)
return {x, y};
}
static_assert(from_ix(0) == std::tuple<uint32_t, uint32_t>{0b000, 0b000});
static_assert(from_ix(2) == std::tuple<uint32_t, uint32_t>{0b001, 0b000});
static_assert(from_ix(8) == std::tuple<uint32_t, uint32_t>{0b010, 0b000});
static_assert(from_ix(10) == std::tuple<uint32_t, uint32_t>{0b011, 0b000});
static_assert(from_ix(32) == std::tuple<uint32_t, uint32_t>{0b100, 0b000});
static_assert(from_ix(34) == std::tuple<uint32_t, uint32_t>{0b101, 0b000});
static_assert(from_ix(40) == std::tuple<uint32_t, uint32_t>{0b110, 0b000});
static_assert(from_ix(42) == std::tuple<uint32_t, uint32_t>{0b111, 0b000});
using xy_type = std::tuple<uint32_t, uint32_t>;
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<uint32_t, uint32_t>{0b000, 0b001});
static_assert(from_ix(4) == std::tuple<uint32_t, uint32_t>{0b000, 0b010});
static_assert(from_ix(5) == std::tuple<uint32_t, uint32_t>{0b000, 0b011});
static_assert(from_ix(16) == std::tuple<uint32_t, uint32_t>{0b000, 0b100});
static_assert(from_ix(17) == std::tuple<uint32_t, uint32_t>{0b000, 0b101});
static_assert(from_ix(20) == std::tuple<uint32_t, uint32_t>{0b000, 0b110});
static_assert(from_ix(21) == std::tuple<uint32_t, uint32_t>{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 <typename T>
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 <int B, typename T, typename U>
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 {