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 0000000..d78f723 Binary files /dev/null and b/font/portfolio_6x8/Bm437_Portfolio_6x8.otb differ diff --git a/font/portfolio_6x8/portfolio_6x8.data b/font/portfolio_6x8/portfolio_6x8.data new file mode 100644 index 0000000..25618f5 Binary files /dev/null and b/font/portfolio_6x8/portfolio_6x8.data differ 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); +}