From 8ce3f8bdc9114910c4b90aa02224cd84626e5670 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Wed, 3 Sep 2025 12:48:31 -0500 Subject: [PATCH] dreamcast2: add font example --- dreamcast2/base.mk | 4 +- .../example/cube_ta_fullscreen_textured.cpp | 7 +- dreamcast2/example/example.mk | 7 + dreamcast2/example/font.cpp | 439 ++++++++++++++++++ .../ter_u12n.128x64.palette_4bpp.twiddled | Bin 0 -> 4096 bytes 5 files changed, 451 insertions(+), 6 deletions(-) create mode 100644 dreamcast2/example/font.cpp create mode 100644 dreamcast2/example/font/ter_u12n.128x64.palette_4bpp.twiddled diff --git a/dreamcast2/base.mk b/dreamcast2/base.mk index 6af92e5..7216373 100644 --- a/dreamcast2/base.mk +++ b/dreamcast2/base.mk @@ -38,6 +38,8 @@ CXXFLAGS += -fno-exceptions -fno-non-call-exceptions -fno-rtti -fno-threadsafe-s LDFLAGS += --gc-sections --no-warn-rwx-segment --print-memory-usage --entry=_start --orphan-handling=error +LD_LIBGCC += -L $(dir $(shell $(CC) -print-file-name=libgcc.a)) -lgcc + DEPFLAGS = -MMD -MP ################################################################################ @@ -65,7 +67,7 @@ OBJDUMP = $(TARGET)objdump $(CXX) $(CARCH) $(CFLAGS) $(CXXSTD) $(CXXFLAGS) $(OPT) $(DEBUG) $(DEPFLAGS) -MF ${<}.d -c $< -o $@ %.elf: - $(LD) $(LDFLAGS) -L $(LIB) -T $(LDSCRIPT) $^ -o $@ + $(LD) $(LDFLAGS) -L $(LIB) -T $(LDSCRIPT) $^ $(LD_LIBGCC) -o $@ %.bin: %.elf $(OBJCOPY) -O binary $< $@ diff --git a/dreamcast2/example/cube_ta_fullscreen_textured.cpp b/dreamcast2/example/cube_ta_fullscreen_textured.cpp index d25fe8b..f780793 100644 --- a/dreamcast2/example/cube_ta_fullscreen_textured.cpp +++ b/dreamcast2/example/cube_ta_fullscreen_textured.cpp @@ -1,3 +1,4 @@ + #include "memorymap.hpp" #include "holly/core/object_list_bits.hpp" @@ -83,8 +84,7 @@ static inline uint32_t transfer_ta_global_polygon(uint32_t store_queue_ix, uint3 polygon->parameter_control_word = parameter_control_word::para_type::polygon_or_modifier_volume | parameter_control_word::list_type::opaque | parameter_control_word::col_type::packed_color - | parameter_control_word::texture - | parameter_control_word::gouraud; + | parameter_control_word::texture; polygon->isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::greater | isp_tsp_instruction_word::culling_mode::no_culling; @@ -105,9 +105,6 @@ static inline uint32_t transfer_ta_global_polygon(uint32_t store_queue_ix, uint3 | texture_control_word::scan_order::non_twiddled | texture_control_word::texture_address(texture_address / 8); - polygon->data_size_for_sort_dma = 0; - polygon->next_address_for_sort_dma = 0; - // start store queue transfer of `polygon` to the TA pref(polygon); diff --git a/dreamcast2/example/example.mk b/dreamcast2/example/example.mk index a16fdc2..ed331b8 100644 --- a/dreamcast2/example/example.mk +++ b/dreamcast2/example/example.mk @@ -79,3 +79,10 @@ SIERPINSKI_TETRAHEDRON_FSAA_YSCALER_OBJ = \ example/sierpinski_tetrahedron_fsaa_yscaler.elf: LDSCRIPT = $(LIB)/main.lds example/sierpinski_tetrahedron_fsaa_yscaler.elf: $(START_OBJ) $(SIERPINSKI_TETRAHEDRON_FSAA_YSCALER_OBJ) + +FONT_OBJ = \ + holly/core/region_array.o \ + example/font.o + +example/font.elf: LDSCRIPT = $(LIB)/main.lds +example/font.elf: $(START_OBJ) $(FONT_OBJ) diff --git a/dreamcast2/example/font.cpp b/dreamcast2/example/font.cpp new file mode 100644 index 0000000..ae93d45 --- /dev/null +++ b/dreamcast2/example/font.cpp @@ -0,0 +1,439 @@ +#include "memorymap.hpp" + +#include "holly/core/object_list_bits.hpp" +#include "holly/core/region_array.hpp" +#include "holly/core/region_array_bits.hpp" +#include "holly/core/parameter_bits.hpp" +#include "holly/core/parameter.hpp" +#include "holly/ta/global_parameter.hpp" +#include "holly/ta/vertex_parameter.hpp" +#include "holly/ta/parameter_bits.hpp" +#include "holly/holly.hpp" +#include "holly/holly_bits.hpp" + +#include "sh7091/sh7091.hpp" +#include "sh7091/pref.hpp" +#include "sh7091/store_queue_transfer.hpp" + +void transfer_background_polygon(uint32_t isp_tsp_parameter_start) +{ + using namespace holly::core::parameter; + + using parameter = isp_tsp_parameter<3>; + + volatile parameter * polygon = (volatile parameter *)&texture_memory32[isp_tsp_parameter_start]; + + polygon->isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::always + | isp_tsp_instruction_word::culling_mode::no_culling; + + 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; + + polygon->texture_control_word = 0; + + polygon->vertex[0].x = 0.0f; + polygon->vertex[0].y = 0.0f; + polygon->vertex[0].z = 0.00001f; + polygon->vertex[0].base_color = 0xff00ff; + + polygon->vertex[1].x = 32.0f; + polygon->vertex[1].y = 0.0f; + polygon->vertex[1].z = 0.00001f; + polygon->vertex[1].base_color = 0xff00ff; + + polygon->vertex[2].x = 32.0f; + polygon->vertex[2].y = 32.0f; + polygon->vertex[2].z = 0.00001f; + polygon->vertex[2].base_color = 0xff00ff; +} + +struct vec2 { + float x; + float y; +}; + +struct face { + float inverse_texture_width; + float inverse_texture_height; + float glyph_width; + float glyph_height; + int hori_advance; + int row_stride; +}; + +static const face ter_u12n = { + .inverse_texture_width = 1.0f / 128.0f, + .inverse_texture_height = 1.0f / 64.0f, + .glyph_width = 6.0f, + .glyph_height = 12.0f, + .hori_advance = 6, + .row_stride = 21, +}; + +static inline vec2 glyph_texture(const face& face, const vec2& v, int char_code) +{ + int row = char_code / face.row_stride; + int col = char_code % face.row_stride; + + return { + (((float)col) * face.glyph_width + v.x * face.glyph_width) * face.inverse_texture_width, + (((float)row) * face.glyph_height + v.y * face.glyph_height) * face.inverse_texture_height, + }; +} + +static inline vec2 glyph_position(const face& face, const vec2& v, const vec2& p) +{ + return { + v.x * face.glyph_width + p.x, + v.y * face.glyph_height + p.y, + }; +} + +static inline uint32_t transfer_ta_global_end_of_list(uint32_t store_queue_ix) +{ + using namespace holly::ta; + using namespace holly::ta::parameter; + + // + // TA "end of list" global transfer + // + volatile global_parameter::end_of_list * end_of_list = (volatile global_parameter::end_of_list *)&store_queue[store_queue_ix]; + store_queue_ix += (sizeof (global_parameter::end_of_list)); + + end_of_list->parameter_control_word = parameter_control_word::para_type::end_of_list; + + // start store queue transfer of `end_of_list` to the TA + pref(end_of_list); + + return store_queue_ix; +} + +static inline uint32_t transfer_ta_global_polygon(uint32_t store_queue_ix, uint32_t texture_address) +{ + using namespace holly::core::parameter; + using namespace holly::ta; + using namespace holly::ta::parameter; + + volatile global_parameter::polygon_type_0 * polygon = (volatile global_parameter::polygon_type_0 *)&store_queue[store_queue_ix]; + store_queue_ix += (sizeof (global_parameter::polygon_type_0)); + + polygon->parameter_control_word = parameter_control_word::para_type::polygon_or_modifier_volume + | parameter_control_word::list_type::opaque + | parameter_control_word::col_type::packed_color + | parameter_control_word::texture; + + polygon->isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::greater + | isp_tsp_instruction_word::culling_mode::no_culling; + + 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::filter_mode::point_sampled + | tsp_instruction_word::texture_shading_instruction::decal + | tsp_instruction_word::texture_u_size::_128 + | tsp_instruction_word::texture_v_size::_64; + + polygon->texture_control_word = texture_control_word::pixel_format::palette_4bpp + | texture_control_word::scan_order::twiddled + | texture_control_word::texture_address(texture_address / 8); + + + pref(polygon); + + return store_queue_ix; +} + +static inline uint32_t transfer_ta_vertex_quad(uint32_t store_queue_ix, + float ax, float ay, float az, float au, float av, uint32_t ac, + float bx, float by, float bz, float bu, float bv, uint32_t bc, + float cx, float cy, float cz, float cu, float cv, uint32_t cc, + float dx, float dy, float dz, float du, float dv, uint32_t dc) +{ + using namespace holly::ta; + using namespace holly::ta::parameter; + + // + // TA polygon vertex transfer + // + + volatile vertex_parameter::polygon_type_3 * vertex = (volatile vertex_parameter::polygon_type_3 *)&store_queue[store_queue_ix]; + store_queue_ix += (sizeof (vertex_parameter::polygon_type_3)) * 4; + + vertex[0].parameter_control_word = parameter_control_word::para_type::vertex_parameter; + vertex[0].x = ax; + vertex[0].y = ay; + vertex[0].z = az; + vertex[0].u = au; + vertex[0].v = av; + vertex[0].base_color = ac; + vertex[0].offset_color = 0; + + // start store queue transfer of `vertex[0]` to the TA + pref(&vertex[0]); + + vertex[1].parameter_control_word = parameter_control_word::para_type::vertex_parameter; + vertex[1].x = bx; + vertex[1].y = by; + vertex[1].z = bz; + vertex[1].u = bu; + vertex[1].v = bv; + vertex[1].base_color = bc; + vertex[1].offset_color = 0; + + // start store queue transfer of `vertex[1]` to the TA + pref(&vertex[1]); + + vertex[2].parameter_control_word = parameter_control_word::para_type::vertex_parameter; + vertex[2].x = dx; + vertex[2].y = dy; + vertex[2].z = dz; + vertex[2].u = du; + vertex[2].v = dv; + vertex[2].base_color = dc; + vertex[2].offset_color = 0; + + // start store queue transfer of `params[2]` to the TA + pref(&vertex[2]); + + vertex[3].parameter_control_word = parameter_control_word::para_type::vertex_parameter + | parameter_control_word::end_of_strip; + vertex[3].x = cx; + vertex[3].y = cy; + vertex[3].z = cz; + vertex[3].u = cu; + vertex[3].v = cv; + vertex[3].base_color = cc; + vertex[3].offset_color = 0; + + // start store queue transfer of `params[3]` to the TA + pref(&vertex[3]); + + return store_queue_ix; +} + +uint32_t transfer_glyph(uint32_t store_queue_ix, + const face& face, + const vec2& t, + int char_code) +{ + static const vec2 vtx[] = { + { 0, 0 }, + { 1, 0 }, + { 1, 1 }, + { 0, 1 }, + }; + + if (char_code <= 0x20 || char_code >= 0x7f) { + return store_queue_ix; + } + + char_code -= 0x20; + + vec2 ap = glyph_position(face, vtx[0], t); + vec2 bp = glyph_position(face, vtx[1], t); + vec2 cp = glyph_position(face, vtx[2], t); + vec2 dp = glyph_position(face, vtx[3], t); + + vec2 at = glyph_texture(face, vtx[0], char_code); + vec2 bt = glyph_texture(face, vtx[1], char_code); + vec2 ct = glyph_texture(face, vtx[2], char_code); + vec2 dt = glyph_texture(face, vtx[3], char_code); + + store_queue_ix = transfer_ta_vertex_quad(store_queue_ix, + ap.x, ap.y, 0.1f, at.x, at.y, 0, + bp.x, bp.y, 0.1f, bt.x, bt.y, 0, + cp.x, cp.y, 0.1f, ct.x, ct.y, 0, + dp.x, dp.y, 0.1f, dt.x, dt.y, 0); + + return store_queue_ix; +} + +void transfer_scene(uint32_t texture_address) +{ + { + using namespace sh7091; + using sh7091::sh7091; + + // set the store queue destination address to the TA Polygon Converter FIFO + sh7091.CCN.QACR0 = sh7091::ccn::qacr0::address(ta_fifo_polygon_converter); + sh7091.CCN.QACR1 = sh7091::ccn::qacr1::address(ta_fifo_polygon_converter); + } + + uint32_t store_queue_ix = 0; + + store_queue_ix = transfer_ta_global_polygon(store_queue_ix, texture_address); + + store_queue_ix = transfer_glyph(store_queue_ix, + ter_u12n, + vec2(10, 10), + 'a'); + + store_queue_ix = transfer_ta_global_end_of_list(store_queue_ix); +} + +const uint8_t texture[] __attribute__((aligned(4))) = { + #embed "font/ter_u12n.128x64.palette_4bpp.twiddled" +}; + +void transfer_texture(uint32_t texture_start) +{ + // use 4-byte transfers to texture memory, for slightly increased transfer + // speed + // + // It would be even faster to use the SH4 store queue for this operation, or + // SH4 DMA. + + sh7091::store_queue_transfer::copy((void *)&texture_memory64[texture_start], texture, (sizeof (texture))); +} + +void main() +{ + using namespace holly; + using namespace holly::core; + using holly::holly; + + /* palette */ + holly.PAL_RAM_CTRL = holly::pal_ram_ctrl::pixel_format::argb4444; + + holly.PALETTE_RAM[0] = 0x0000; + holly.PALETTE_RAM[1] = 0xffff; + + holly.PT_ALPHA_REF = 0xff; + + /* + a very simple memory map: + + the ordering within texture memory is not significant, and could be + anything + */ + uint32_t framebuffer_start = 0x200000; // intentionally the same address that the boot rom used to draw the SEGA logo + uint32_t isp_tsp_parameter_start = 0x400000; + uint32_t region_array_start = 0x500000; + uint32_t object_list_start = 0x100000; + + // these addresses are in "64-bit" texture memory address space: + uint32_t texture_start = 0x700000; + + const int tile_y_num = 480 / 32; + const int tile_x_num = 640 / 32; + + region_array::list_block_size list_block_size = { + .opaque = 8 * 4, + }; + + region_array::transfer(tile_x_num, + tile_y_num, + list_block_size, + region_array_start, + object_list_start); + + transfer_background_polygon(isp_tsp_parameter_start); + + ////////////////////////////////////////////////////////////////////////////// + // transfer the texture image to texture ram + ////////////////////////////////////////////////////////////////////////////// + + transfer_texture(texture_start); + + ////////////////////////////////////////////////////////////////////////////// + // configure the TA + ////////////////////////////////////////////////////////////////////////////// + + // TA_GLOB_TILE_CLIP restricts which "object pointer blocks" are written + // to. + // + // This can also be used to implement "windowing", as long as the desired + // window size happens to be a multiple of 32 pixels. The "User Tile Clip" TA + // control parameter can also ~equivalently be used as many times as desired + // within a single TA initialization to produce an identical effect. + // + // See DCDBSysArc990907E.pdf page 183. + holly.TA_GLOB_TILE_CLIP = ta_glob_tile_clip::tile_y_num(tile_y_num - 1) + | ta_glob_tile_clip::tile_x_num(tile_x_num - 1); + + // While CORE supports arbitrary-length object lists, the TA uses "object + // pointer blocks" as a memory allocation strategy. These fixed-length blocks + // can still have infinite length via "object pointer block links". This + // mechanism is illustrated in DCDBSysArc990907E.pdf page 188. + holly.TA_ALLOC_CTRL = ta_alloc_ctrl::opb_mode::increasing_addresses + | ta_alloc_ctrl::o_opb::_8x4byte; + + // While building object lists, the TA contains an internal index (exposed as + // the read-only TA_ITP_CURRENT) for the next address that new ISP/TSP will be + // stored at. The initial value of this index is TA_ISP_BASE. + + // reserve space in ISP/TSP parameters for the background parameter + using polygon = holly::core::parameter::isp_tsp_parameter<3>; + uint32_t ta_isp_base_offset = (sizeof (polygon)) * 1; + + holly.TA_ISP_BASE = isp_tsp_parameter_start + ta_isp_base_offset; + holly.TA_ISP_LIMIT = isp_tsp_parameter_start + 0x100000; + + // Similarly, the TA also contains, for up to 600 tiles, an internal index for + // the next address that an object list entry will be stored for each + // tile. These internal indicies are partially exposed via the read-only + // TA_OL_POINTERS. + holly.TA_OL_BASE = object_list_start; + + // TA_OL_LIMIT, DCDBSysArc990907E.pdf page 385: + // + // > Because the TA may automatically store data in the address that is + // > specified by this register, it must not be used for other data. For + // > example, the address specified here must not be the same as the address + // > in the TA_ISP_BASE register. + holly.TA_OL_LIMIT = object_list_start + 0x100000 - 32; + + ////////////////////////////////////////////////////////////////////////////// + // configure CORE + ////////////////////////////////////////////////////////////////////////////// + + // REGION_BASE is the (texture memory-relative) address of the region array. + holly.REGION_BASE = region_array_start; + + // PARAM_BASE is the (texture memory-relative) address of ISP/TSP parameters. + // Anything that references an ISP/TSP parameter does so relative to this + // address (and not relative to the beginning of texture memory). + holly.PARAM_BASE = isp_tsp_parameter_start; + + // Set the offset of the background ISP/TSP parameter, relative to PARAM_BASE + // SKIP is related to the size of each vertex + uint32_t background_offset = 0; + + holly.ISP_BACKGND_T = isp_backgnd_t::tag_address(background_offset / 4) + | isp_backgnd_t::tag_offset(0) + | isp_backgnd_t::skip(1); + + // FB_W_SOF1 is the (texture memory-relative) address of the framebuffer that + // will be written to when a tile is rendered/flushed. + holly.FB_W_SOF1 = framebuffer_start; + + // without waiting for rendering to actually complete, immediately display the + // framebuffer. + holly.FB_R_SOF1 = framebuffer_start; + + // TA_LIST_INIT needs to be written (every frame) prior to the first FIFO + // write. + holly.TA_LIST_INIT = ta_list_init::list_init; + + // dummy TA_LIST_INIT read; DCDBSysArc990907E.pdf in multiple places says this + // step is required. + (void)holly.TA_LIST_INIT; + + transfer_scene(texture_start); + + ////////////////////////////////////////////////////////////////////////////// + // wait for vertical synchronization (and the TA) + ////////////////////////////////////////////////////////////////////////////// + + while (!(spg_status::vsync(holly.SPG_STATUS))); + while (spg_status::vsync(holly.SPG_STATUS)); + + ////////////////////////////////////////////////////////////////////////////// + // start the actual rasterization + ////////////////////////////////////////////////////////////////////////////// + + // start the actual render--the rendering process begins by interpreting the + // region array + holly.STARTRENDER = 1; +} diff --git a/dreamcast2/example/font/ter_u12n.128x64.palette_4bpp.twiddled b/dreamcast2/example/font/ter_u12n.128x64.palette_4bpp.twiddled new file mode 100644 index 0000000000000000000000000000000000000000..1d01af88d40ae8cb9d24618c92d08200838cd781 GIT binary patch literal 4096 zcmc(hfpXj+3`Ea=aQ^?F?Y>en zrMDD3)s}GhvIy37-EX1jxoQOOeg_R*+9eoGv?NQZf-1WUU*^V$F@Pphv1M`TuffW< zFv`VBkzri&^09_axR&vPzcJO>Q6#Am?X5+JbzZmW&|N8@F~SFj%V8OzuJM_V@IiI@ z4jPGGP{t)3zGZ{5S_t1_XJ|acFTScXe%shS@sN1vvtI?e7iA*|KdkkBB7fN~)rLk~ z9Nm%y9A{g(EW5@vM8;;n(>}tXa?}o;k$e=xtNyhy}I|Jn!w;N8-ztp&Fv%+{Ch4oh-CZ+9{3j zZQJBF=VQgoD|cIJzWGWozu4}BmfwB*z= z`6dDy=HR3r=siL4C;}Q=OOGAi&gpS~XMJ)$L3Dm1Ct3e;ck4SIZl&zCV$4)=38lF2 z`Zb8rckdVH;2??HFY1n4A-tDUXC8Kr1#3GW?$5fvP!ShS9(^L5^(&4QjNDWGWZSWP zR1}C@rib-W3`VJYTY6l6%!;k(9BRJ{bg&Mj1LZNn5VmP#sd-N|g3j++w`A6{uSc8L zJ%*PY$g zx3u$(*E65CV{Yi!+x4s`Jf#UVA-qZK={8zJ_V_4?QWM?|>eGYTayni-9 zLf3WcY=UPy-kxiZbH8{kamLikgzx{Hee9waB0A79bH!Y>;%9A^M6!KssxG|e_a(kZ zi?3}7K_uSt-LgZu2jP+X5k}Fs?efyEFkktsM%VRgOO4cO#OW21==V`-(7p@ZzHMI+ zZ6vitB17?``HN`O;53?b$JMJN@c8|6c72`!?7jO4ITWd8_86Zh-Z1?yqEj6u;e5_U fZOJo#bsDF^53&vgUrYYN%h^e=u^~So{N3eWUQ;QM literal 0 HcmV?d00001