diff --git a/azur/CMakeLists.txt b/azur/CMakeLists.txt index edd64df..d8d0ecb 100644 --- a/azur/CMakeLists.txt +++ b/azur/CMakeLists.txt @@ -75,7 +75,10 @@ if(AZUR_GRAPHICS_GINT_CG) src/gint/shaders/triangle.S # Rectangle shader src/gint/shaders/rect.c - src/gint/shaders/rect.S) + src/gint/shaders/rect.S + # Text shader + src/gint/shaders/text.c + src/gint/shaders/text.S) endif() add_library(azur STATIC ${SOURCES}) diff --git a/azur/include/azur/gint/render.h b/azur/include/azur/gint/render.h index 34b7ec9..8ae827b 100644 --- a/azur/include/azur/gint/render.h +++ b/azur/include/azur/gint/render.h @@ -239,6 +239,14 @@ enum { AZRP_RECT_WHITEN = -3, }; +/* azrp_text(): Render a string of text, like dtext(). */ +void azrp_text(int x, int y, font_t const *f, char const *str, int fg, + int size); + +/* azrp_text_opt(): Render text with options similar to dtext_opt(). */ +void azrp_text_opt(int x, int y, font_t const *font, int fg, int halign, + int valign, char const *str, int size); + //--- // Performance indicators // diff --git a/azur/src/gint/shaders/rect.c b/azur/src/gint/shaders/rect.c index f710a64..e248b51 100644 --- a/azur/src/gint/shaders/rect.c +++ b/azur/src/gint/shaders/rect.c @@ -1,6 +1,6 @@ #include -uint AZRP_SHADER_RECT = -1; +uint8_t AZRP_SHADER_RECT = -1; static void configure(void) { diff --git a/azur/src/gint/shaders/text.S b/azur/src/gint/shaders/text.S new file mode 100644 index 0000000..59ddd01 --- /dev/null +++ b/azur/src/gint/shaders/text.S @@ -0,0 +1,83 @@ +.global _azrp_text_glyph + +/* Naive glyph rendering. Possible optimizations: + - Use a 1-bit bitmask instead of a longword index? */ + +/* Parameters: + r4: fragment + r5: data + r6: color + r7: height + @(4,r15): width + @(8,r15): dataw - width (stride) + @(12,r15): starting index in data + Stack: + @(0,r15): r8 save + + Register allocation: + r0: (temporary) + r1: (temporary) + r2: x counter + r3: glyph data index + r4: fragment pointer + r5: glyph pointer + r6: color + r7: y counter + Callee-saved registers: + r8: fragment stride */ + +_azrp_text_glyph: + + /* Compute fragment stride: 2 * (azrp_width-width) */ + mov.l r8, @-r15 + mov.l 1f, r8 + mov.l @r8, r8 + mov.l @(4, r15), r3 + sub r3, r8 + shll r8 + + /* Load the starting index */ + mov.l @(12, r15), r3 + +.fg_y: + /* Initialize width counter */ + mov.l @(4, r15), r2 + +.fg_x: + /* Load one bit of data in T */ + mov r3, r0 + mov #-5, r1 + shld r1, r0 + shll2 r0 + mov.l @(r0, r5), r1 + + mov r3, r0 + and #31, r0 + + shld r0, r1 + shll r1 + + /* Write color to fragment only if it's a 1 bit */ + bf .fg_next + mov.w r6, @r4 + +.fg_next: + /* Leave the x-loop if x counter reaches 0 */ + add #2, r4 + dt r2 + bf/s .fg_x + add #1, r3 + + /* Move to next row, leave the y-loop if height reaches 0 */ + dt r7 + mov.l @(8, r15), r0 + add r0, r3 + bf/s .fg_y + add r8, r4 + + rts + mov.l @r15+, r8 + +.align 4 + +1: .long _azrp_width diff --git a/azur/src/gint/shaders/text.c b/azur/src/gint/shaders/text.c new file mode 100644 index 0000000..624d8ee --- /dev/null +++ b/azur/src/gint/shaders/text.c @@ -0,0 +1,165 @@ +#include +#include +#include +#include + +uint8_t AZRP_SHADER_TEXT = -1; + +__attribute__((constructor)) +static void register_shader(void) +{ + extern azrp_shader_t azrp_shader_text; + AZRP_SHADER_TEXT = azrp_register_shader(azrp_shader_text, NULL); +} + +//--- + +/* This version of the text shader is a very rough adaptation of dtext(). It + should be optimized in several important ways in the future: + 1. Use left-shifting in azrp_text_glyph(), which is probably faster both for + partial and full glyphs. + 2. Optimize the heck out of the full-width case, which is almost every + single call. + 3. Precompute the set of glyphs so the list can be reused when crossing + fragment boundaries, the shader can be written entirely in assembler, and + the command can possibly be reused? + 4. Provide noclip toplevel functions, which I believe should provide a + nontrivial speed boost. */ + +void azrp_text_glyph(uint16_t *fragment, uint32_t const *data, int color, + int height, int width, int stride, int index); + +struct command { + uint8_t shader_id; + uint8_t _; + int16_t x, y; + int16_t height, top; + font_t const *font; + char const *str; + int fg; + int size; +}; + +void azrp_shader_text(void *uniforms0, void *cmd0, void *frag0) +{ + struct command *cmd = cmd0; + int x = cmd->x; + int y = cmd->y; + font_t const *f = cmd->font; + int fg = cmd->fg; + int size = cmd->size; + /* Storage height, top position within glyph */ + int height = min(cmd->height, azrp_frag_height - y); + int top = cmd->top; + + uint8_t const *str = (void *)cmd->str; + uint8_t const *str0 = str; + + /* Raw glyph data */ + uint32_t const *data = f->data; + + /* Update for next fragment */ + cmd->height -= height; + cmd->top += height; + cmd->y = 0; + + /* Move to top row */ + uint16_t *frag = (uint16_t *)frag0 + azrp_width * y; + + /* Read each character from the input string */ + while(x < azrp_window.right) + { + uint32_t code_point = dtext_utf8_next(&str); + if(!code_point || (size >= 0 && str - str0 > size)) break; + + int glyph = dfont_glyph_index(f, code_point); + if(glyph < 0) continue; + + int dataw = f->prop ? f->glyph_width[glyph] : f->width; + + int index = dfont_glyph_offset(f, glyph); + + /* Compute horizontal intersection between glyph and screen */ + + int width = dataw, left = 0; + + if(x + dataw <= azrp_window.left) + { + x += dataw + f->char_spacing; + continue; + } + if(x < azrp_window.left) { + left = azrp_window.left - x; + width -= left; + } + width = min(width, azrp_window.right - x); + + /* Render glyph */ + azrp_text_glyph(frag + x + left, data + index, fg, height, width, + dataw - width, top * dataw + left); + + x += dataw + f->char_spacing; + } +} + +void azrp_text(int x, int y, font_t const *f, char const *str, + int fg, int size) +{ + prof_enter(azrp_perf_cmdgen); + + /* Clipping */ + if(x >= azrp_window.right || y >= azrp_window.bottom || + y + f->data_height <= azrp_window.top) { + prof_leave(azrp_perf_cmdgen); + return; + } + + int top_overflow = y - azrp_window.top; + int top = 0; + int height = f->data_height; + + if(top_overflow < 0) { + top = -top_overflow; + height += top_overflow; + y -= top_overflow; + } + height = min(height, azrp_window.bottom - y); + if(height <= 0) { + prof_leave(azrp_perf_cmdgen); + return; + } + + int frag_first, first_offset, frag_count; + azrp_config_get_lines(y, f->data_height, + &frag_first, &first_offset, &frag_count); + + struct command cmd; + cmd.shader_id = AZRP_SHADER_TEXT; + cmd.x = x; + cmd.y = first_offset; + cmd.height = height; + cmd.top = top; + cmd.font = f; + cmd.str = str; + cmd.fg = fg; + cmd.size = size; + + azrp_queue_command(&cmd, sizeof cmd, frag_first, frag_count); + prof_leave(azrp_perf_cmdgen); +} + +void azrp_text_opt(int x, int y, font_t const *font, int fg, int halign, + int valign, char const *str, int size) +{ + if(halign != DTEXT_LEFT || valign != DTEXT_TOP) { + int w, h; + dnsize(str, size, font, &w, &h); + + if(halign == DTEXT_RIGHT) x -= w - 1; + if(halign == DTEXT_CENTER) x -= (w >> 1); + if(valign == DTEXT_BOTTOM) y -= h - 1; + if(valign == DTEXT_MIDDLE) y -= (h >> 1); + } + + azrp_text(x, y, font, str, fg, size); +}