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().
This commit is contained in:
lephe 2019-07-20 12:31:46 -04:00
parent 705854da39
commit ae4f7af172
14 changed files with 558 additions and 38 deletions

3
TODO
View File

@ -24,8 +24,10 @@ Complementary elements on existing code.
* core: use cmp/str for memchr()
* timer: try putting addresses in <gint/mpu/tmu.h>
* 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 <gint/mpu/pfc.h>
Future directions.

View File

@ -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 */

View File

@ -30,4 +30,8 @@
/* Weak symbols */
#define GWEAK __attribute__((weak))
/* Constructors */
#define GCONSTRUCTOR __attribute__((constructor))
#define GDESTRUCTOR __attribute__((destructor))
#endif /* GINT_DEFS_ATTRIBUTES */

View File

@ -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.

180
include/gint/gray.h Normal file
View File

@ -0,0 +1,180 @@
//---
// gint:gray - Gray engine and rendering functions
//---
#ifndef GINT_GRAY
#define GINT_GRAY
#include <gint/defs/types.h>
#include <gint/display.h>
//---
// 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 <gint/clock.h> 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 */

View File

@ -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))

142
src/gray/engine.c Normal file
View File

@ -0,0 +1,142 @@
//---
// gint:gray:engine - Core gray engine
//---
#define GINT_NEED_VRAM
#include <gint/defs/types.h>
#include <gint/std/stdlib.h>
#include <gint/drivers/t6k11.h>
#include <gint/gray.h>
#include <gint/display.h>
#include <gint/timer.h>
/* 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];
}

37
src/gray/gclear.c Normal file
View File

@ -0,0 +1,37 @@
#include <gint/gray.h>
/* 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;
}
}

124
src/gray/grect.c Normal file
View File

@ -0,0 +1,124 @@
#include <gint/defs/util.h>
#include <gint/gray.h>
#include <display/fx.h>
/* 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++;
}
}

15
src/gray/gtext.c Normal file
View File

@ -0,0 +1,15 @@
#include <gint/gray.h>
#include <display/common.h>
#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);
}

13
src/render-fx/dtext.c Normal file
View File

@ -0,0 +1,13 @@
#define GINT_NEED_VRAM
#include <gint/display.h>
#include <display/common.h>
#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);
}

View File

@ -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 */

View File

@ -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

View File

@ -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]);
}