From ae4f7af172e359565487f65559eb6e87b47c3bd5 Mon Sep 17 00:00:00 2001 From: lephe Date: Sat, 20 Jul 2019 12:31:46 -0400 Subject: [PATCH] gray: add engine, basic drawing and text This revision includes the base gray engine with sensible starting defaults, gclear() and grect(), as well as gtext(). --- TODO | 3 + include/display/fx.h | 1 - include/gint/defs/attributes.h | 4 + include/gint/display-cg.h | 9 +- include/gint/gray.h | 180 +++++++++++++++++++++++++++++++++ make/Makefile | 6 +- src/gray/engine.c | 142 ++++++++++++++++++++++++++ src/gray/gclear.c | 37 +++++++ src/gray/grect.c | 124 +++++++++++++++++++++++ src/gray/gtext.c | 15 +++ src/render-fx/dtext.c | 13 +++ src/render-fx/topti-asm.h | 14 +++ src/render-fx/topti-asm.s | 21 ++-- src/render-fx/topti.c | 27 +---- 14 files changed, 558 insertions(+), 38 deletions(-) create mode 100644 include/gint/gray.h create mode 100644 src/gray/engine.c create mode 100644 src/gray/gclear.c create mode 100644 src/gray/grect.c create mode 100644 src/gray/gtext.c create mode 100644 src/render-fx/dtext.c diff --git a/TODO b/TODO index e6c375f..a56ffad 100644 --- a/TODO +++ b/TODO @@ -24,8 +24,10 @@ Complementary elements on existing code. * core: use cmp/str for memchr() * timer: try putting addresses in * r61524: brightness control and clean the file +* t6k11: check if dupdate() can be done by the DMA Keep in mind. +* core: run destructors when a task-switch results in leaving the app * prizm: don't hardcode stack address in fxcg50.ld * prizm: detect P1 static RAM (stack) in TLB * core: prove and use qdiv10() instead of __sdivsi3 @@ -33,6 +35,7 @@ Keep in mind. * core: free heap when a task-switch results in leaving the app * core: save and restore interrupt masks * timer: make sure ETMU interrupts are disabled in ctx_restore() +* timer: look for ways to improve the code again * core: document the SH7305 PFC in Future directions. diff --git a/include/display/fx.h b/include/display/fx.h index 761a3ef..eb9e4cc 100644 --- a/include/display/fx.h +++ b/include/display/fx.h @@ -43,5 +43,4 @@ void bopti_render_clip(int x, int y, image_t const *img, int left, int top, void bopti_render_noclip(int x, int y, image_t const *img, int left, int top, int w, int h); - #endif /* DISPLAY_FX */ diff --git a/include/gint/defs/attributes.h b/include/gint/defs/attributes.h index c66e033..791cfac 100644 --- a/include/gint/defs/attributes.h +++ b/include/gint/defs/attributes.h @@ -30,4 +30,8 @@ /* Weak symbols */ #define GWEAK __attribute__((weak)) +/* Constructors */ +#define GCONSTRUCTOR __attribute__((constructor)) +#define GDESTRUCTOR __attribute__((destructor)) + #endif /* GINT_DEFS_ATTRIBUTES */ diff --git a/include/gint/display-cg.h b/include/gint/display-cg.h index c5ca351..d95a410 100644 --- a/include/gint/display-cg.h +++ b/include/gint/display-cg.h @@ -59,16 +59,15 @@ enum { Since gint transfers data to the screen using the DMA, it is possible to run the application while the finished frame is being transferred. However, - writing to the VRAM during this period it is still begin read by the DMA. - Changing the contents of the VRAM too soon would alter the frame being sent. + writing to the VRAM during this period will cause display artifacts since + the VRAM it is still being read by the DMA. The solution to this is to use triple-buffering with the display and two - VRAMs that are alternately begin written to while the other is being + VRAMs that are alternately being written to while the other is being transferred. The VRAM switching is handled by dupdate() and is activated whenever two VRAMs are configured. - By default gint uses triple buffering with one VRAM in the user stack and - a second one in the system stack. + By default gint uses triple buffering with two VRAMs in the system stack. VRAMs must be contiguous, 32-aligned, (2*396*224)-byte buffers. diff --git a/include/gint/gray.h b/include/gint/gray.h new file mode 100644 index 0000000..5e0278b --- /dev/null +++ b/include/gint/gray.h @@ -0,0 +1,180 @@ +//--- +// gint:gray - Gray engine and rendering functions +//--- + +#ifndef GINT_GRAY +#define GINT_GRAY + +#include +#include + +//--- +// Engine control +//--- + +/* gray_start(): Start the gray engine + The control of the screen is transferred to the engine; you should not use + dupdate() after this function, only gupdate(). */ +void gray_start(void); + +/* gray_stop(): Stop the gray engine + + Safe to call if the engine was not running. The gray engine returns the + control of the screen and dupdate() is safe to use again. + + This function will leave the screen in whatever state it is, which is most + probably one of the gray buffers being displayed. You should dupdate() + quickly after this call to avoid visual artifacts. If the next monochrome + frame is slow to render, consider rendering it before stopping the gray + engine, and calling dupdate() immediately after. */ +void gray_stop(void); + +/* gray_delays(): Set the gray engine delays + + The gray engine works by swapping two images at a fast pace. Pixels that are + white on both or black or both will appear as such, but pixels that are + black on only one of the images will look gray. + + If both images stay on-screen for the same amount on time, there will be + only one shade of gray. But if one stays longer, then pixels that are only + black here will look darker than their counterparts, making two shades of + gray. This is the default. + + Since the gray engine has its default settings, you don't need to set the + delays before using gray drawing functions. But you can do it to customize + the appearance of the gray to your application. Be aware that most values + will just produce visual artifacts and will not look good. There are three + characteristics that you'll want to control: + + * The stability of the shades; depending on the frequency gray areas can + appear to blink. + * Stripes. If the refresh timing coincides with the screen's display + duration, stripes will appear. They can be of various sizes, visibility + and speed; but overall they're the *one* thing you want to avoid. + * And the color of the shades themselves. + + Here are values from an older version of gint, which may not look good now + but provide bases to search for good settings: + + LIGHT DARK BLINKING STRIPES COLORS + -------------------------------------------------- + 860 1298 none terrible decent + 912 1343 heavy none good + 993 1609 medium light decent + 1325 1607 heavy light excellent + -------------------------------------------------- + + Here are values for this version of gint; + + LIGHT DARK BLINKING STRIPES COLORS + -------------------------------------------------- + 869 1311 medium none good [default] + 937 1425 medium none good + -------------------------------------------------- + + @light New light delay + @dark New dark delay */ +void gray_delays(uint32_t light, uint32_t dark); + +/* gray_config(): Get the current configuration of the engine + + Provides the value of the current light and dark delays, measured in timer + ticks of prescaler P_phi/64. See on how to obtain this value. + Both pointers may be NULL. + + @light Set to the current light delay setting + @dark Set to the current dark delay setting + Returns non-zero if the engine is currently running, 0 otherwise. */ +int gray_config(uint32_t *light, uint32_t *dark); + +//--- +// Area rendering functions +//--- + +/* gclear(): Fill the screen with a single color + Clears the VRAM and paints all the pixels in the same color. Optimized for + opaque colors; use grect() for other colors. + + @color white, light, dark, black */ +void gclear(color_t color); + +/* grect(): Fill a rectangle on the screen + Applies a color or an operator to the rectangle enclosed by (x1 y1) and + (x2 y2), both included. + + @x1 @x2 @y1 @y2 Bounding rectangle + @color white, light, dark, black, none, invert, lighten, darken */ +void grect(int x1, int y1, int x2, int y2, color_t color); + +//--- +// Point drawing functions +//--- + +/* gpixel(): Change a pixel's color + Paints the specified pixel. Use lines or area rendering when possible + because painting pixels individually is slow. + + @x @y Coordinates of the pixel to paint + @color white, light, dark, black, none, invert, lighten, darken */ +void gpixel(int x, int y, color_t color); + +/* gline(): Render a straight line + Draws a line without anti-aliasing, using a Bresenham-style algorithm. Much + like dline(), has optimizations for vertical and horizontal lines but is not + able to handle clipping. + + @x1 @y1 @x2 @y2 End points of the line (both included) + @color white, light, dark, black, none, invert, lighten, darken */ +void gline(int x1, int y1, int x2, int y2, color_t color); + +//--- +// Text rendering +//--- + +/* dfont() and dsize() still work with the gray engine. */ + +/* gtext(): Display a string of text + Exactly like dtext(), except that the rendering is done on the two gray + VRAMs and all colors are supported. + + @x @y Coordinates of top-left corner of the rendered string + @str String to display + @fg Foreground: white, light, dark, black, none, invert, lighten, darken + @bg Background, same colors as fg */ +void gtext(int x, int y, const char *str, int fg, int bg); + +//--- +// VRAM management +//--- + +/* gupdate(): Push the current VRAMs to the screen + + This function swaps pairs of VRAM buffers in the gray engine so that the + gray and light VRAMs that were being drawn to are now available to be + displayed on the screen at the next refresh, while the VRAMs that were + previously being displayed are now available for drawing. + + Unlike dupdate(), gupdate() does not interact with the screen as this + interaction is done very frequently by the engine itself. It performs an + atomic operation to swap buffers, but nothing on the screen will change + until the engine's timer actually fires. */ +void gupdate(void); + + +/* gvram(): Get the current VRAM pointers + + This function stores the current VRAM pointers to the specified locations. + This operation needs to be done atomically because the engine might kick in + and sway buffers at any time; calling gvraml() then gvramd() is unsafe. + + @light Set to the current light VRAM pointer + @dark Set to the current dark VRAM pointer */ +void gvram(uint32_t **light, uint32_t **dark); + +/* gvraml(): Shorthand to retrieve the current light VRAM pointer */ +uint32_t *gvraml(void); + +/* gvramd(): Shorthand to retrieve the current dark VRAM pointer */ +uint32_t *gvramd(void); + +#endif /* GINT_GRAY */ diff --git a/make/Makefile b/make/Makefile index 1e74629..3a413da 100755 --- a/make/Makefile +++ b/make/Makefile @@ -45,10 +45,10 @@ src2obj = $(1:../src/%=src/%).o src2dep = $(1:../src/%=src/%).d # Source files -prune-fx := "render-cg" -prune-cg := "render-fx" +prune-fx := -name render-cg -prune +prune-cg := -name render-fx -prune -o -name gray -prune src := $(shell find ../src \ - -name $(prune-$(CONFIG.TARGET)) -prune \ + $(prune-$(CONFIG.TARGET)) \ -o -name '*.[csS]' -print) src_obj := $(foreach s,$(src),$(call src2obj,$s)) diff --git a/src/gray/engine.c b/src/gray/engine.c new file mode 100644 index 0000000..9723282 --- /dev/null +++ b/src/gray/engine.c @@ -0,0 +1,142 @@ +//--- +// gint:gray:engine - Core gray engine +//--- + +#define GINT_NEED_VRAM + +#include +#include + +#include +#include +#include +#include + +/* Three additional video RAMS, allocated statically if --static-gray was set + at configure time, or with malloc() otherwise. */ +#ifdef GINT_STATIC_GRAY +GBSS static uint32_t gvrams[3][256]; +#endif + +/* Four VRAMs: two to draw and two to display */ +GBSS static uint32_t *vrams[4]; + +/* Current VRAM pair used for drawing; the value can either be 0 (draws to + VRAMs 0 and 1) or 2 (draws to VRAMs 2 and 3). */ +GDATA static volatile int st = 0; + +/* Whether the engine is running. Delays of light and dark frames. */ +GDATA static int runs = 0; +GDATA static int delays[2] = { 869, 1311 }; + +/* Underlying timer */ +#define GRAY_TIMER 0 + +//--- +// Engine control +//--- + +/* gray_init(): Engine setup */ +GCONSTRUCTOR static void gray_init(void) +{ + /* Here [vram] refers to the standard, monochrome VRAM */ + vrams[0] = vram; + + #ifdef GINT_STATIC_GRAY + vrams[1] = gvrams[0]; + vrams[2] = gvrams[1]; + vrams[3] = gvrams[2]; + #else + vrams[1] = malloc(1024); + vrams[2] = malloc(1024); + vrams[3] = malloc(1024); + #endif /* GINT_STATIC_GRAY */ +} + +/* gray_quit(): Engine deinitialization() */ +GDESTRUCTOR static void gray_quit(void) +{ + #ifndef GINT_STATIC_GRAY + free(vrams[1]); + free(vrams[2]); + free(vrams[3]); + #endif /* GINT_STATIC_GRAY */ +} + +/* gray_int(): Interrupt handler */ +int gray_int(GUNUSED volatile void *arg) +{ + t6k11_display(vrams[st ^ 2], 0, 64, 16); + timer_reload(GRAY_TIMER, delays[(st ^ 3) & 1]); + st ^= 1; + + return 0; +} + +/* gray_start(): Start the gray engine */ +void gray_start(void) +{ + #ifndef GINT_STATIC_GRAY + if(!vrams[1] || !vrams[2] || !vrams[3]) return; + #endif + + if(runs) return; + + int free = timer_setup(GRAY_TIMER, delays[0], timer_Po_64, gray_int, + NULL); + if(free != GRAY_TIMER) return; + + timer_start(GRAY_TIMER); + st = 0; + runs = 1; +} + +/* gray_stop(): Stop the gray engine */ +void gray_stop(void) +{ + timer_stop(GRAY_TIMER); + runs = 0; +} + +/* gray_delays(): Set the gray engine delays */ +void gray_delays(uint32_t light, uint32_t dark) +{ + delays[0] = light; + delays[1] = dark; +} + +/* gray_config(): Get the current configuration of the engine */ +int gray_config(uint32_t *light, uint32_t *dark) +{ + if(light) *light = delays[0]; + if(dark) *dark = delays[1]; + + return runs; +} + +/* gupdate(): Push the current VRAMs to the screen */ +void gupdate(void) +{ + st ^= 2; +} + +/* gvram(): Get the current VRAM pointers */ +void gvram(uint32_t **light, uint32_t **dark) +{ + int base = st; + + if(light) *light = vrams[base & 2]; + if(dark) *dark = vrams[base | 1]; +} + +/* gvraml(): Shorthand to retrieve the current light VRAM pointer */ +uint32_t *gvraml(void) +{ + return vrams[st & 2]; +} + +/* gvramd(): Shorthand to retrieve the current dark VRAM pointer */ +uint32_t *gvramd(void) +{ + return vrams[st | 1]; +} diff --git a/src/gray/gclear.c b/src/gray/gclear.c new file mode 100644 index 0000000..3559ff0 --- /dev/null +++ b/src/gray/gclear.c @@ -0,0 +1,37 @@ +#include + +/* gclear(): Fill the screen with a single color */ +void gclear(color_t color) +{ + uint32_t *light, *dark; + gvram(&light, &dark); + + uint32_t l = -(color == C_LIGHT || color == C_BLACK); + uint32_t d = -(color == C_DARK || color == C_BLACK); + + for(int i = 0; i < 32; i++) + { + light[0] = l; + light[1] = l; + light[2] = l; + light[3] = l; + + light[4] = l; + light[5] = l; + light[6] = l; + light[7] = l; + + dark[0] = d; + dark[1] = d; + dark[2] = d; + dark[3] = d; + + dark[4] = d; + dark[5] = d; + dark[6] = d; + dark[7] = d; + + light += 8; + dark += 8; + } +} diff --git a/src/gray/grect.c b/src/gray/grect.c new file mode 100644 index 0000000..5eb9cd3 --- /dev/null +++ b/src/gray/grect.c @@ -0,0 +1,124 @@ +#include +#include +#include + +/* grect(): Fill a rectangle on the screen */ +void grect(int x1, int y1, int x2, int y2, color_t color) +{ + if(x1 > x2) swap(x1, x2); + if(y1 > y2) swap(y1, y2); + + /* Argument checking */ + if(x1 >= 128 || x2 < 0 || y1 >= 64 || y2 < 0) return; + if(x1 < 0) x1 = 0; + if(x2 >= 128) x2 = 127; + if(y1 < 0) y1 = 0; + if(y2 >= 64) y2 = 63; + + /* Use masks to get the work done fast! */ + uint32_t m[4]; + masks(x1, x2, m); + + uint32_t *light, *dark; + gvram(&light, &dark); + + light += (y1 << 2); + dark += (y1 << 2); + int h = y2 - y1 + 1; + + if(color == C_WHITE) while(h--) + { + light[0] &= ~m[0]; + light[1] &= ~m[1]; + light[2] &= ~m[2]; + light[3] &= ~m[3]; + + dark[0] &= ~m[0]; + dark[1] &= ~m[1]; + dark[2] &= ~m[2]; + dark[3] &= ~m[3]; + + light += 4; + dark += 4; + } + else if(color == C_LIGHT) while(h--) + { + light[0] |= m[0]; + light[1] |= m[1]; + light[2] |= m[2]; + light[3] |= m[3]; + + dark[0] &= ~m[0]; + dark[1] &= ~m[1]; + dark[2] &= ~m[2]; + dark[3] &= ~m[3]; + + light += 4; + dark += 4; + } + else if(color == C_DARK) while(h--) + { + light[0] &= ~m[0]; + light[1] &= ~m[1]; + light[2] &= ~m[2]; + light[3] &= ~m[3]; + + dark[0] |= m[0]; + dark[1] |= m[1]; + dark[2] |= m[2]; + dark[3] |= m[3]; + + light += 4; + dark += 4; + } + else if(color == C_BLACK) while(h--) + { + light[0] |= m[0]; + light[1] |= m[1]; + light[2] |= m[2]; + light[3] |= m[3]; + + dark[0] |= m[0]; + dark[1] |= m[1]; + dark[2] |= m[2]; + dark[3] |= m[3]; + + light += 4; + dark += 4; + } + else if(color == C_INVERT) while(h--) + { + light[0] ^= m[0]; + light[1] ^= m[1]; + light[2] ^= m[2]; + light[3] ^= m[3]; + + dark[0] ^= m[0]; + dark[1] ^= m[1]; + dark[2] ^= m[2]; + dark[3] ^= m[3]; + + light += 4; + dark += 4; + } + else if(color == C_LIGHTEN) for(int i = 0; i < (h << 2); i++) + { + int j = i & 3; + uint32_t tmp = *dark, x = m[j]; + + *dark &= *light | ~x; + *light = (*light ^ x) & (tmp | ~x); + + light++, dark++; + } + else if(color == C_DARKEN) for(int i = 0; i < (h << 2); i++) + { + int j = i & 3; + uint32_t tmp = *dark, x = m[j]; + + *dark |= *light & x; + *light = (*light ^ x) | (tmp & x); + + light++, dark++; + } +} diff --git a/src/gray/gtext.c b/src/gray/gtext.c new file mode 100644 index 0000000..1741600 --- /dev/null +++ b/src/gray/gtext.c @@ -0,0 +1,15 @@ +#include +#include +#include "../render-fx/topti-asm.h" + +/* gtext(): Display a string of text */ +void gtext(int x, int y, const char *str, int fg, int bg) +{ + if((uint)fg >= 8 || (uint)bg >= 8) return; + + uint32_t *light, *dark; + gvram(&light, &dark); + + topti_render(x, y, str, topti_font, topti_asm_text[fg], + topti_asm_text[bg], light, dark); +} diff --git a/src/render-fx/dtext.c b/src/render-fx/dtext.c new file mode 100644 index 0000000..b6c688e --- /dev/null +++ b/src/render-fx/dtext.c @@ -0,0 +1,13 @@ +#define GINT_NEED_VRAM +#include +#include +#include "topti-asm.h" + +/* dtext() - display a string of text */ +GSECTION(".pretext") +void dtext(int x, int y, char const *str, int fg, int bg) +{ + if((uint)fg >= 8 || (uint)bg >= 8) return; + topti_render(x, y, str, topti_font, topti_asm_text[fg], + topti_asm_text[bg], vram, vram); +} diff --git a/src/render-fx/topti-asm.h b/src/render-fx/topti-asm.h index 6211ccd..29a33ec 100644 --- a/src/render-fx/topti-asm.h +++ b/src/render-fx/topti-asm.h @@ -12,4 +12,18 @@ typedef void asm_text_t(uint32_t *v1, uint32_t *v2, uint32_t *op, int height); /* One rendering function per color */ extern asm_text_t *topti_asm_text[8]; +/* topti_render(): Render a string on the VRAM + Combines glyph data onto VRAM operands and blits them efficiently onto the + VRAM. To write a single character, use a 2-byte string with a NUL. + + @x @y Target position on VRAM + @str Text source + @f Font + @asm_fg Assembler function for text rendering + @asm_bg Assembler function for background rendering + @v1 Monochrome VRAM or light gray VRAM + @v2 Monochrome or dark gray VRAM */ +void topti_render(int x, int y, char const *str, font_t const *f, + asm_text_t *asm_fg, asm_text_t *asm_bg, uint32_t *v1, uint32_t *v2); + #endif /* GINT_RENDERFX_TOPTIASM */ diff --git a/src/render-fx/topti-asm.s b/src/render-fx/topti-asm.s index d590175..3e49c0b 100644 --- a/src/render-fx/topti-asm.s +++ b/src/render-fx/topti-asm.s @@ -8,7 +8,7 @@ # r2: dark (except lighten/darken: swapped at some point with light) # r3: (tmp) # r4: vram (mono or light) -# r5: vram (nothing or dark) +# r5: vram (mono or dark) # r6: operators # r7: number of rows (r7>0, otherwise the font is clearly ill-formed) @@ -24,16 +24,20 @@ .align 4 _topti_asm_white: add #-16, r4 - nop + add #-16, r5 1: mov.l @r6+, r0 add #16, r4 mov.l @r4, r1 dt r7 + mov.l @r5, r2 not r0, r0 and r0, r1 - bf.s 1b + and r0, r2 mov.l r1, @r4 + add #16, r5 + bf.s 1b + mov.l r2, @r5 rts nop @@ -81,10 +85,14 @@ _topti_asm_black: dt r7 mov.l @r4, r1 /* (bubble) */ - or r1, r0 - mov.l r0, @r4 - bf.s 1b + or r0, r1 + mov.l @r5, r2 + or r0, r2 + mov.l r1, @r4 add #16, r4 + mov.l r2, @r5 + bf.s 1b + add #16, r5 rts nop @@ -169,4 +177,3 @@ _topti_asm_text: .long _topti_asm_invert .long _topti_asm_lighten .long _topti_asm_darken - diff --git a/src/render-fx/topti.c b/src/render-fx/topti.c index 3b8b46c..34a4cbf 100644 --- a/src/render-fx/topti.c +++ b/src/render-fx/topti.c @@ -81,18 +81,10 @@ static int topti_split(uint32_t const * glyph, int width, int height, int free, return free - width; } -/* topti_render(): Render a string on the VRAM - Combines glyph data onto VRAM operands and blits them efficiently onto the - VRAM. To write a single character, use a 2-byte string with a NUL. - - @x @y Target position on VRAM - @str Text source - @f Font - @asm_fg Assembler function for text rendering - @asm_bg Assembler function for background rendering */ +/* topti_render(): Render a string on the VRAM */ GSECTION(".pretext") void topti_render(int x, int y, char const *str, font_t const *f, - asm_text_t *asm_fg, asm_text_t *asm_bg) + asm_text_t *asm_fg, asm_text_t *asm_bg, uint32_t *v1, uint32_t *v2) { /* Storage height and number of free bits in operators[] */ int height = f->data_height, free; @@ -127,10 +119,10 @@ void topti_render(int x, int y, char const *str, font_t const *f, /* Put an initial offset to the operators to honor the x coordinate */ free = 32 - (x & 31); x >>= 5; + /* VRAM pointers */ - /* TODO: topti: Support gray vrams */ - uint32_t *v1 = vram + (y << 2) + x; - uint32_t *v2 = vram + (y << 2) + x; + v1 += (y << 2) + x; + v2 += (y << 2) + x; /* Pull each character into the operator buffer */ while(*str) @@ -184,12 +176,3 @@ void topti_render(int x, int y, char const *str, font_t const *f, asm_fg(v1, v2, operators + vdisp, height - vdisp); } } - -/* dtext() - display a string of text */ -GSECTION(".pretext") -void dtext(int x, int y, char const *str, int fg, int bg) -{ - if((uint)fg >= 8 || (uint)bg >= 8) return; - topti_render(x, y, str, topti_font, topti_asm_text[fg], - topti_asm_text[bg]); -}