From 1ecd87495f858ffc010c34268c9bd17b4eb81b0d Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Fri, 18 Oct 2024 23:28:39 -0500 Subject: [PATCH] example: add maple_font --- .gitignore | 1 + example/maple_font.cpp | 243 +++++++++++++++++++++ font/portfolio_6x8/Bm437_Portfolio_6x8.otb | Bin 0 -> 7384 bytes font/portfolio_6x8/portfolio_6x8.data | Bin 0 -> 768 bytes tools/ttf_bitmap.cpp | 126 +++++++++++ 5 files changed, 370 insertions(+) create mode 100644 example/maple_font.cpp create mode 100644 font/portfolio_6x8/Bm437_Portfolio_6x8.otb create mode 100644 font/portfolio_6x8/portfolio_6x8.data create mode 100644 tools/ttf_bitmap.cpp diff --git a/.gitignore b/.gitignore index 2fe683d..e54c2d1 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ __pycache__ scramble cdi4dc tools/ttf_outline +tools/ttf_bitmap k_means_vq *.blend1 *.scramble diff --git a/example/maple_font.cpp b/example/maple_font.cpp new file mode 100644 index 0000000..f54df9b --- /dev/null +++ b/example/maple_font.cpp @@ -0,0 +1,243 @@ +#include +#include + +#include "maple/maple.hpp" +#include "maple/maple_bus_commands.hpp" +#include "maple/maple_bus_bits.hpp" +#include "maple/maple_host_command_writer.hpp" +#include "maple/maple_port.hpp" +#include "align.hpp" +#include "sh7091/serial.hpp" + +#include "holly/video_output.hpp" + +extern uint32_t _binary_font_portfolio_6x8 __asm("_binary_font_portfolio_6x8_portfolio_6x8_data_start"); + +namespace vmu_display { +constexpr int32_t width = 48; +constexpr int32_t height = 32; +constexpr int32_t pixels_per_byte = 8; +constexpr int32_t framebuffer_size = width * height / pixels_per_byte; +} + +uint32_t vmu_framebuffer[vmu_display::framebuffer_size / 4]; + +static inline void render_glyph(uint8_t * dst, const uint8_t * src, uint8_t c, int x, int y) +{ + int y_ix = 186 - (y * 6 * 8); + for (int i = 0; i < 8; i++) { + switch (x) { + case 0: + dst[y_ix - i * 6 + 5] = src[(c - ' ') * 8 + i]; + break; + case 1: + dst[y_ix - i * 6 + 5] |= (src[(c - ' ') * 8 + i] & 0b11) << 6; + dst[y_ix - i * 6 + 4] = src[(c - ' ') * 8 + i] >> 2; // 0b1111 + break; + case 2: + dst[y_ix - i * 6 + 4] |= (src[(c - ' ') * 8 + i] & 0b1111) << 4; + dst[y_ix - i * 6 + 3] = src[(c - ' ') * 8 + i] >> 4; // 0b11 + break; + case 3: + dst[y_ix - i * 6 + 3] |= src[(c - ' ') * 8 + i] << 2; + break; + case 4: + dst[y_ix - i * 6 + 2] = src[(c - ' ') * 8 + i]; + break; + case 5: + dst[y_ix - i * 6 + 2] |= (src[(c - ' ') * 8 + i] & 0b11) << 6; + dst[y_ix - i * 6 + 1] = src[(c - ' ') * 8 + i] >> 2; // 0b1111 + break; + case 6: + dst[y_ix - i * 6 + 1] |= (src[(c - ' ') * 8 + i] & 0b1111) << 4; + dst[y_ix - i * 6 + 0] = src[(c - ' ') * 8 + i] >> 4; // 0b11 + break; + case 7: + dst[y_ix - i * 6 + 0] |= src[(c - ' ') * 8 + i] << 2; + break; + } + } +} + +void make_vmu_framebuffer(uint32_t * buf) +{ + const uint8_t * src = reinterpret_cast(&_binary_font_portfolio_6x8); + uint8_t * dst = reinterpret_cast(buf); + + for (int i = 0; i < vmu_display::framebuffer_size; i++) { + dst[i] = 0; + } + const char * s = + " very " + " funneh " + " " + " :)) "; + for (int i = 0; i < 8 * 4; i++) { + int x = i % 8; + int y = i / 8; + render_glyph(dst, src, s[i], x, y); + } +} + +template +inline void copy(T * dst, const T * src, const int32_t n) noexcept +{ + int32_t n_t = n / (sizeof (T)); + while (n_t > 0) { + *dst++ = *src++; + n_t--; + } +} + +void send_vmu_framebuffer(uint8_t port, uint8_t lm) +{ + uint32_t send_buf[1024] __attribute__((aligned(32))); + uint32_t recv_buf[1024] __attribute__((aligned(32))); + + using command_type = maple::block_write; + using response_type = maple::device_reply; + + auto writer = maple::host_command_writer(send_buf, recv_buf); + + uint32_t host_port_select = host_instruction_port_select(port); + uint32_t destination_ap = ap_port_select(port) | ap::de::expansion_device | lm; + + auto [host_command, host_response] + = writer.append_command(host_port_select, + destination_ap, + true, // end_flag + vmu_display::framebuffer_size, // send_trailing + 0 // recv_trailing + ); + auto& data_fields = host_command->bus_data.data_fields; + data_fields.function_type = std::byteswap(function_type::bw_lcd); + data_fields.pt = 0; + data_fields.phase = 0; + data_fields.block_number = std::byteswap(0x0000); + + copy(data_fields.written_data, reinterpret_cast(vmu_framebuffer), vmu_display::framebuffer_size); + + maple::dma_start(send_buf, writer.send_offset, + recv_buf, writer.recv_offset); + + serial::integer(host_response->bus_data.command_code); + serial::integer(host_response->bus_data.destination_ap); + serial::integer(host_response->bus_data.source_ap); + serial::integer(host_response->bus_data.data_size); +} + +void do_lm_request(uint8_t port, uint8_t lm) +{ + uint32_t send_buf[1024] __attribute__((aligned(32))); + uint32_t recv_buf[1024] __attribute__((aligned(32))); + + auto writer = maple::host_command_writer(send_buf, recv_buf); + + uint32_t host_port_select = host_instruction_port_select(port); + uint32_t destination_ap = ap_port_select(port) | ap::de::expansion_device | lm; + + using command_type = maple::device_request; + using response_type = maple::device_status; + + auto [host_command, host_response] + = writer.append_command(host_port_select, + destination_ap, + true); // end_flag + + maple::dma_start(send_buf, writer.send_offset, + recv_buf, writer.recv_offset); + + auto& bus_data = host_response->bus_data; + auto& data_fields = bus_data.data_fields; + if (bus_data.command_code != maple::device_status::command_code) { + serial::string("lm did not reply: "); + serial::integer(port, ' '); + serial::integer(lm); + } else { + serial::string(" lm: "); + serial::integer(lm); + serial::string(" ft: "); + serial::integer(std::byteswap(data_fields.device_id.ft)); + serial::string(" fd[0]: "); + serial::integer(std::byteswap(data_fields.device_id.fd[0])); + serial::string(" fd[1]: "); + serial::integer(std::byteswap(data_fields.device_id.fd[1])); + serial::string(" fd[2]: "); + serial::integer(std::byteswap(data_fields.device_id.fd[2])); + serial::string(" source_ap.lm_bus: "); + serial::integer(bus_data.source_ap & ap::lm_bus::bit_mask); + + if (std::byteswap(data_fields.device_id.ft) & function_type::bw_lcd) { + serial::string("send vmu_framebuffer\n"); + send_vmu_framebuffer(port, lm); + } + } +} + +void do_lm_requests(uint8_t port, uint8_t lm) +{ + if (lm & ap::lm_bus::_0) + do_lm_request(port, lm & ap::lm_bus::_0); + if (lm & ap::lm_bus::_1) + do_lm_request(port, lm & ap::lm_bus::_1); + if (lm & ap::lm_bus::_2) + do_lm_request(port, lm & ap::lm_bus::_2); + if (lm & ap::lm_bus::_3) + do_lm_request(port, lm & ap::lm_bus::_3); + if (lm & ap::lm_bus::_4) + do_lm_request(port, lm & ap::lm_bus::_4); +} + +void do_device_request() +{ + uint32_t send_buf[1024] __attribute__((aligned(32))); + uint32_t recv_buf[1024] __attribute__((aligned(32))); + + auto writer = maple::host_command_writer(send_buf, recv_buf); + + using command_type = maple::device_request; + using response_type = maple::device_status; + + auto [host_command, host_response] + = writer.append_command_all_ports(); + + maple::dma_start(send_buf, writer.send_offset, + recv_buf, writer.recv_offset); + + for (uint8_t port = 0; port < 4; port++) { + auto& bus_data = host_response[port].bus_data; + auto& data_fields = bus_data.data_fields; + if (bus_data.command_code != response_type::command_code) { + serial::string("port: "); + serial::integer(port); + serial::string(" disconnected\n"); + } else { + serial::string("port: "); + serial::integer(port); + serial::string(" ft: "); + serial::integer(std::byteswap(data_fields.device_id.ft)); + serial::string(" fd[0]: "); + serial::integer(std::byteswap(data_fields.device_id.fd[0])); + serial::string(" fd[1]: "); + serial::integer(std::byteswap(data_fields.device_id.fd[1])); + serial::string(" fd[2]: "); + serial::integer(std::byteswap(data_fields.device_id.fd[2])); + serial::string(" source_ap.lm_bus: "); + serial::integer(bus_data.source_ap & ap::lm_bus::bit_mask); + + do_lm_requests(port, + bus_data.source_ap & ap::lm_bus::bit_mask); + } + } +} + +void main() +{ + serial::init(4); + + make_vmu_framebuffer(vmu_framebuffer); + + do_device_request(); + + while (1); +} diff --git a/font/portfolio_6x8/Bm437_Portfolio_6x8.otb b/font/portfolio_6x8/Bm437_Portfolio_6x8.otb new file mode 100644 index 0000000000000000000000000000000000000000..d78f723ed45de6fc80b204d63c1b69ea9193d409 GIT binary patch literal 7384 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 literal 0 HcmV?d00001 diff --git a/font/portfolio_6x8/portfolio_6x8.data b/font/portfolio_6x8/portfolio_6x8.data new file mode 100644 index 0000000000000000000000000000000000000000..25618f5a60aa92b0e9b9856790aa811d138d9f5b GIT binary patch literal 768 zcmX|90g}Qn2!x0=$<;L2iKF9P^OXDluWlEei&|Vt7FZB@AL9{|T5C9Jo%mF-LtEcW zPJ6cWe8^JHX_ULH*&+_!lq?H=8UICWw82+q6NGU-vGa}aTvw5fXccMx9J@EMg#|QV zzL+wz18PoL~4=6}*???&X5~x~YOAEbpuS_#t?UYc7q|p#E>jkjo-o&Zl#60SnF)e~rO7 z_kBDW55T&a3;}_uD>3hZzp-D4$iqAlAEpK#<3+w|HF%vRgf0pB8pe`RdpC^s8|%u@ zxjIz%Kfg=*3`tr)(2weMXaBen{r7%Hxd*XNJ!HM54aC5R?(GxrP?7`0yr4S!$K`VV z^6=6x@rOp@xp|fkj{SKU ZH|l{k#8%^XGF#0~a=#)Z4wS$``~k5Z8sGo` literal 0 HcmV?d00001 diff --git a/tools/ttf_bitmap.cpp b/tools/ttf_bitmap.cpp new file mode 100644 index 0000000..884ab8d --- /dev/null +++ b/tools/ttf_bitmap.cpp @@ -0,0 +1,126 @@ +#include +#include + +#include +#include + +#include +#include FT_FREETYPE_H + +int32_t +load_bitmap_char(FT_Face face, + FT_ULong char_code, + uint8_t * buf) +{ + 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; + } + + assert(face->glyph->format == FT_GLYPH_FORMAT_BITMAP); + //printf("num_grays %d\n", face->glyph->bitmap.num_grays); + //printf("pitch %d\n", face->glyph->bitmap.pitch); + //printf("width %d\n", face->glyph->bitmap.width); + //printf("char_code %lx rows %d\n", char_code, face->glyph->bitmap.rows); + //assert((face->glyph->bitmap.rows % 8) == 0); + //assert(face->glyph->bitmap.width / face->glyph->bitmap.pitch == 8); + + for (int y = 0; y < (int)face->glyph->bitmap.rows; y++) { + uint8_t * row = &face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch]; + uint8_t row_out = 0; + for (unsigned int x = 0; x < face->glyph->bitmap.width; x++) { + if (x % 8 == 0) row_out = 0; + const uint8_t bit = (row[x / 8] >> (7 - (x % 8))) & 1; + std::cerr << (bit ? "█" : " "); + row_out |= (bit << (x % 8)); + if (x % 8 == 7 || x == (face->glyph->bitmap.width - 1)) + buf[(y * face->glyph->bitmap.pitch) + (x / 8)] = row_out; + } + std::cerr << "|\n"; + } + + // 'pitch' is bytes; 'width' is pixels + return face->glyph->bitmap.rows * face->glyph->bitmap.pitch; +} + +template +constexpr inline T +parse_num(decltype(std::hex)& format, const char * s) +{ + T n; + std::stringstream ss; + ss << format << s; + ss >> n; + return n; +} + +int main(int argc, char *argv[]) +{ + FT_Library library; + FT_Face face; + FT_Error error; + + if (argc != 5) { + std::cerr << "usage: " << argv[0] << " [start-hex] [end-hex] [font-file-path] [output-file-path]\n\n"; + std::cerr << " ex: " << argv[0] << " 3000 30ff ipagp.ttf font.bin\n"; + return -1; + } + + auto start = parse_num(std::hex, argv[1]); + auto end = parse_num(std::hex, argv[2]); + auto font_file_path = argv[3]; + auto output_file_path = argv[4]; + + error = FT_Init_FreeType(&library); + if (error) { + std::cerr << "FT_Init_FreeType\n"; + return -1; + } + + error = FT_New_Face(library, font_file_path, 0, &face); + if (error) { + std::cerr << "FT_New_Face\n"; + return -1; + } + + error = FT_Select_Size(face, 0); + if (error) { + std::cerr << "FT_Select_Size: " << FT_Error_String(error) << ' ' << error << '\n'; + return -1; + } + + assert(end > start); + + uint32_t pixels_per_glyph = (face->size->metrics.height * face->size->metrics.max_advance); + assert(pixels_per_glyph % 8 == 0); + uint32_t bytes_per_glyph = pixels_per_glyph / 8; + uint32_t num_glyphs = (end - start) + 1; + uint8_t buf[bytes_per_glyph * num_glyphs]; + + uint32_t bitmap_offset = 0; + for (uint32_t char_code = start; char_code <= end; char_code++) { + int32_t bitmap_size = load_bitmap_char(face, + char_code, + &buf[bitmap_offset]); + if (bitmap_size < 0) { + std::cerr << "load_bitmap_char error\n"; + return -1; + } + + bitmap_offset += bitmap_size; + assert(bitmap_offset < (sizeof (buf))); + } + std::cerr << "bitmap_offset: 0x" << std::dec << bitmap_offset << '\n'; + + FILE * out = fopen(output_file_path, "w"); + if (out == NULL) { + perror("fopen(w)"); + return -1; + } + fwrite(reinterpret_cast(&buf), bitmap_offset, 1, out); + fclose(out); +}