From 0055199359fee547ecee740e66cb19de263e0688 Mon Sep 17 00:00:00 2001 From: lephe Date: Sat, 15 Jun 2019 01:04:38 -0400 Subject: [PATCH] render: refactor to share functions, and basic text on fxcg50 --- include/display/common.h | 60 +++++++++++ include/display/fx.h | 3 - include/gint/display-cg.h | 77 ++------------- include/gint/display-fx.h | 150 +--------------------------- include/gint/display.h | 194 ++++++++++++++++++++++++++++++++---- include/gint/syscalls.h | 8 +- make/Makefile | 13 ++- src/font10x12.png | Bin 0 -> 17657 bytes src/font5x7.png | Bin 24851 -> 24858 bytes src/render-cg/dline.c | 70 +------------ src/render-cg/drect.c | 34 ++----- src/render-cg/topti-asm.h | 29 ++++++ src/render-cg/topti-asm.s | 202 ++++++++++++++++++++++++++++++++++++++ src/render-cg/topti.c | 96 ++++++++++++++++++ src/render-fx/dline.c | 94 +++--------------- src/render-fx/dpixel.c | 16 ++- src/render-fx/drect.c | 2 +- src/render-fx/topti-asm.s | 4 +- src/render-fx/topti.c | 96 +----------------- src/render/dline.c | 60 +++++++++++ src/render/topti.c | 73 ++++++++++++++ 21 files changed, 759 insertions(+), 522 deletions(-) create mode 100644 include/display/common.h create mode 100644 src/font10x12.png create mode 100644 src/render-cg/topti-asm.h create mode 100644 src/render-cg/topti-asm.s create mode 100644 src/render-cg/topti.c create mode 100644 src/render/dline.c create mode 100644 src/render/topti.c diff --git a/include/display/common.h b/include/display/common.h new file mode 100644 index 0000000..a1ec4b2 --- /dev/null +++ b/include/display/common.h @@ -0,0 +1,60 @@ +//--- +// display:common - Internal definitions for common display functions +//--- + +#ifndef DISPLAY_COMMON +#define DISPLAY_COMMON + +#include + +/* dhline() - optimized drawing of a horizontal line + @x1 @x2 @y Coordinates of endpoints of line (both included) + @color Any color suitable for dline() */ +void dhline(int x1, int x2, int y, color_t color); + +/* dvline() - optimized drawing of a vertical line + @y1 @y2 @x Coordinates of endpoints of line (both included) + @color Any color suitable for dline() */ +void dvline(int y1, int y2, int x, color_t color); + +//--- +// Font rendering (topti) +//--- + +/* Current font */ +extern font_t const * topti_font; +/* Default font */ +extern font_t const * gint_default_font; + +/* enum topti_charset: Available character set decoders + Each charset is associated with a reduced character table. */ +enum topti_charset +{ + charset_numeric = 0, /* 10 elements: 0..9 */ + charset_upper = 1, /* 26 elements: A..Z */ + charset_alpha = 2, /* 52 elements: A..Z, a..z */ + charset_alnum = 3, /* 62 elements: A..Z, a..z, 0..9 */ + charset_print = 4, /* 95 elements: 0x20..0x7e */ + charset_ascii = 5, /* 128 elements: 0x00..0x7f */ +}; + +/* charset_size(): Number of elements in each character set + @set Character set ID + Returns the expected number of glyphs, -1 if charset ID is invalid. */ +int charset_size(enum topti_charset set); + +/* charset_decode(): Translate ASCII into reduced character sets + Returns the position of [c] in the character table of the given charset, or + -1 if [c] is not part of that set. + @set Any character set + @c Character to decode */ +int charset_decode(enum topti_charset set, uint c); + +/* topti_offset(): Use a font index to find the location of a glyph + @f Font object + @glyph Glyph number obtained by charset_decode(), must be nonnegative. + Returns the offset the this glyph's data in the font's data array. When + using a proportional font, the size array is not heeded for. */ +int topti_offset(font_t const *f, uint glyph); + +#endif /* DISPLAY_COMMON */ diff --git a/include/display/fx.h b/include/display/fx.h index 289b964..761a3ef 100644 --- a/include/display/fx.h +++ b/include/display/fx.h @@ -24,9 +24,6 @@ @masks Stores the result of the function (four uint32_t values) */ void masks(int x1, int x2, uint32_t *masks); -/* Font currently configured for text rendering */ -extern font_t const * topti_font; - /* bopti_render_clip() - render a bopti image with clipping @x @y Location of the top-left corner @img Image encoded by [fxconv] diff --git a/include/gint/display-cg.h b/include/gint/display-cg.h index 01292c2..de0919e 100644 --- a/include/gint/display-cg.h +++ b/include/gint/display-cg.h @@ -22,11 +22,19 @@ In this module, colors are in the 16-bit R5G6B5 format, as it is the format used by the display controller. */ - #ifdef GINT_NEED_VRAM extern uint16_t *vram; #endif +/* Provide a platform-agnostic definition of color_t. + Some functions also support transparency, in which case they take an [int] + as argument and recognize negative values as transparent. */ +typedef uint16_t color_t; + +enum { + color_none = -1, +}; + //--- // Video RAM management //--- @@ -62,73 +70,6 @@ extern uint16_t *vram; @secondary Additional VRAM area, enables triple buffering if non-NULL */ void dvram(uint16_t *main, uint16_t *secondary); -/* dupdate() - push the video RAM to the display driver - This function makes the contents of the VRAM visible on the screen. It is - the direct equivalent of Bdisp_PutDisp_DD(). - - If triple buffering is enabled (this is the default, and disabled only if - dvram() is used to setup double buffering instead), it also swaps buffers. - Also waits for the previous dupdate() call to finish before executing. */ -void dupdate(void); - -//--- -// Area rendering functions -//--- - -/* dclear() - fill the screen with a single color - This function clears the screen by painting all the pixels in a single, - opaque color. - - @color Any R5G6B5 color */ -void dclear(uint16_t color); - -/* drect() - fill a rectangle of the screen - This functions paints a rectangle in an opaque color. The endpoints (x1 y1) - and (x2 y2) are included in the rectangle. - - @x1 @y1 @x2 @y2 Bounding rectangle (drawn area). - @color Any R5G6B5 color */ -void drect(int x1, int y1, int x2, int y2, uint16_t color); - -//--- -// Point drawing functions -//--- - -/* dpixel() - change a pixel's color - Paints the selected pixel with an opaque color. Setting pixels individually - is a slow method for rendering. Other functions that draw lines, rectangles, - images or text will take advantage of possible optimizations to make the - rendering faster: check them out first. - - @x @y Coordinates of the pixel to repaint - @color Any R5G6B5 color */ -void dpixel(int x, int y, uint16_t color); - -/* dline() - render a straight line - This function draws a line using a Bresenham-style algorithm. Please note - that the affected pixels may not be exactly the same when using dline() and - Bdisp algorithms. - - dline() has optimization facilities for horizontal and vertical lines. The - first kind is about twice as fast, while the second avoids some computation - (the optimization gain is not as significant as on fx9860g). dline() is not - able to clip the line without calculating all the pixels, so drawing a line - from (-1e6,0) to (1e6,395) will work, but will be veeery slow. - - @x1 @y1 @x2 @y2 End points of the line (both included). - @color Any R5G6B5 color */ -void dline(int x1, int y1, int x2, int y2, uint16_t color); - -//--- -// Image rendering (bopti) -//--- - -//--- -// Text rendering (topti) -//--- - -typedef void font_t; - #endif /* FXCG50 */ #endif /* GINT_DISPLAY_CG */ diff --git a/include/gint/display-fx.h b/include/gint/display-fx.h index e7e0426..8e01377 100644 --- a/include/gint/display-fx.h +++ b/include/gint/display-fx.h @@ -31,7 +31,7 @@ extern uint32_t *vram; OPERATORS (combine with existing pixels) none - leaves unchanged - reverse - inverts white <-> black, light <-> dark + invert - inverts white <-> black, light <-> dark lighten - shifts black -> dark -> light -> white -> white darken - shifts white -> light -> dark -> black -> black @@ -47,7 +47,7 @@ typedef enum /* Monochrome operators */ color_none = 4, - color_reverse = 5, + color_invert = 5, /* Gray operators */ color_lighten = 6, @@ -55,59 +55,6 @@ typedef enum } color_t; -//--- -// Area drawing functions -//--- - -/* dupdate() - push the video RAM to the display driver - This function makes the contents of the VRAM visible on the screen. It is - the direct equivalent of Bdisp_PutDisp_DD(). */ -void dupdate(void); - -/* dclear() - fill the screen with a single color - This function clears the screen by replacing all the pixels with a single - color. This function is optimized for opaque drawing. If you wish to apply - operators, use drect(). - - @color Allowed colors: white, black */ -void dclear(color_t color); - -/* drect() - fill a rectangle of the screen - This functions applies a color or an operator to a rectangle defined by two - points (x1 y1) and (x2 y2). Both are included in the rectangle. - - @x1 @y1 @x2 @y2 Bounding rectangle (drawn area). - @color Allowed colors: white, black, none, reverse */ -void drect(int x1, int y1, int x2, int y2, color_t color); - -//--- -// Point drawing functions -//--- - -/* dpixel() - change a pixel's color - If the requested color is an operator, the result will depend on the current - color of the pixel. Setting single pixels is the slowest method to produce a - graphical result: all other functions for rendering lines, rectangles, - images or text use highly-optimized methods, so check them out first if you - care about performance. - - @x @y Coordinates of the pixel to repaint - @color Allowed colors: white, black, none, reverse */ -void dpixel(int x, int y, color_t color); - -/* dline() - render a straight line - This function draws a line using a Bresenham-style algorithm. Please note - that the affected pixels may not be exactly the same when using dline() and - Bdisp_DrawLineVRAM(). - - dline() has optimization facilities for horizontal and vertical lines, but - it does not detect if your line doesn't fit in the screen. So drawing from - (-1e6,0) to (1e6,63) will work, but will be veeery slow. - - @x1 @y1 @x2 @y2 End points of the line (both included). - @color Allowed colors: white, black, none, reverse */ -void dline(int x1, int y1, int x2, int y2, color_t color); - //--- // Image rendering (bopti) //--- @@ -161,99 +108,6 @@ enum { void dimage_opt(int x, int y, image_t const *image, int left, int top, int width, int height, int flags); -//--- -// Text rendering (topti) -//--- - -/* font_t - font data encoded for topti */ -typedef struct -{ - /* Length of font name (not NUL-terminated) */ - uint title :5; - /* Font shape flags */ - uint bold :1; - uint italic :1; - uint serif :1; - uint mono :1; - /* Whether data is variable-length (proportional font) */ - uint prop :1; - /* Reserved for future use */ - uint :2; - /* Implemented charcter set */ - uint charset :4; - /* Line height */ - uint8_t line_height; - /* Storage height */ - uint8_t data_height; - - /* The rest of the data depends on whether the font is proportional */ - union { - /* For monospaced fonts */ - struct { - /* Width of glyphs */ - uint16_t width; - /* Storage size, in longwords, of each glyph */ - uint16_t storage_size; - /* Raw glyph data */ - uint32_t data[]; - }; - /* For proportional fonts */ - struct { - /* Storage index to find glyphs quickly */ - uint16_t index[16]; - /* Size array (padded to 4 bytes), 1 byte per entry, - followed by glyph data */ - uint8_t sized_data[]; - }; - }; - - /* The font name is stored after the data. The size is the length set - in the [title] field, padded to 4 bytes with NULs. There might not - be a NUL at the end. */ - -} GPACKED(4) font_t; - -/* dfont() - set the default font for text rendering - This font will be used by dtext() and sister functions. If font=NULL, gint's - default 5*6 font is used. - - @font Font to use for subsequent text rendering calls */ -void dfont(font_t const * font); - -/* dsize() - get the width and height of rendered text - This function computes the size that the given string would take up if - rendered with a certain font. If you specify a NULL font, the currently - configured font will be used; this differs from dfont() which uses gint's - default font when NULL is passed. - - Note that the height of each glyph is not stored in the font, only the - maximum. Usually this is what you want because vertically-centered strings - must have the same baseline regardless of their contents. So the height - returned by dsize() is the same for all strings, only depends on the font. - - The height is computed in constant time, and the width in linear time. - - @str String whose size must be evaluated - @font Font to use; if NULL, defaults to the current font - @w @h Set to the width and height of the rendered text, may be NULL */ -void dsize(const char *str, font_t const * font, int *w, int *h); - -/* dtext() - display a string of text - - Draws some text in the video RAM using the font set with dfont() (or gint's - default if no such font was set). - - Due to the particular design of topti, this function takes advantage of the - line structure of the VRAM to rendeer several characters at once. This is - not a printf()-family function so [str] cannot contain formats like "%d" - (they will be rendered directly) and cannot receive additional arguments. - - @x @y Coordinates of top-left corner of the rendered string - @str String to display - @fg Text color - @bg Background color, pass [color_none] to disable */ -void dtext(int x, int y, const char *str, color_t fg, color_t bg); - #endif /* FX9860G */ #endif /* GINT_DISPLAY_FX */ diff --git a/include/gint/display.h b/include/gint/display.h index 3e832e5..76d29f1 100644 --- a/include/gint/display.h +++ b/include/gint/display.h @@ -1,5 +1,9 @@ //--- // gint:display - Drawing functions +// +// This module covers the drawing functions that are common to fx9860g and +// fxcg50. Platform-specific definitions are found in +// and . //--- #ifndef GINT_DISPLAY @@ -7,8 +11,8 @@ #include -/* As you would expect, display on fx9860g and display on fxcg50 are completely - different worlds. As a consequence, so are the rendering functions ^^ */ +/* Platform-specific functions include VRAM management and the definition of + the color_t type. */ #ifdef FX9860G #include @@ -18,26 +22,180 @@ #include #endif -#if 0 -/* dinfo_t - summary of information provided by this module */ +/* TODO: dinfo() or similar */ + +//--- +// Video RAM management +//--- + +/* dupdate() - push the video RAM to the display driver + This function makes the contents of the VRAM visible on the screen. It is + the direct equivalent of Bdisp_PutDisp_DD(). + + On fxcg50, if triple buffering is enabled (which is the default and disabled + only by calling dvram()), it also swaps buffers. Due to the DMA being used, + always waits for the previous call to dupdate() call() to finish. */ +void dupdate(void); + +//--- +// Area rendering functions +//--- + +/* dclear() - fill the screen with a single color + This function clears the screen by painting all the pixels in a single + color. It is optimized for opaque colors. + + On fx9860g, use drect() if you need complex operators such as invert. + + @color fx9860g: white, black + fxcg50: Any R5G6B5 color */ +void dclear(color_t color); + +/* drect() - fill a rectangle of the screen + This functions applies a color or an operator to a rectangle defined by two + points (x1 y1) and (x2 y2). Both are included in the rectangle. + + @x1 @y1 @x2 @y2 Bounding rectangle (drawn area). + @color fx9860g: white, black, none, invert + fxcg50: Any R5G6B5 color */ +void drect(int x1, int y1, int x2, int y2, color_t color); + +//--- +// Point drawing functions +//--- + +/* dpixel() - change a pixel's color + Paints the selected pixel with an opaque color. Setting pixels individually + is a slow method for rendering. Other functions that draw lines, rectangles, + images or text will take advantage of possible optimizations to make the + rendering faster: check them out first. + + On fx9860g, if an operator such as invert is used, the result will depend + on the current color of the pixel. + + @x @y Coordinates of the pixel to repaint + @color fx9860g: white, black, none, invert + fxcg50: Any R5G6B5 color */ +void dpixel(int x, int y, color_t color); + +/* dline() - render a straight line + This function draws a line using a Bresenham-style algorithm. Please note + that dline() may not render lines exactly like Bdisp_DrawLineVRAM(). + + dline() has optimization facilities for horizontal and vertical lines. The + speedup for horizontal lines is about x2 on fxcg50 and probably about x30 + on fx9860g. Vertical lines have a smaller speedup. + + dline() is currently not able to clip arbitrary lines without calculating + all the pixels, so drawing a line from (-1e6,0) to (1e6,395) will work but + will be veeery slow. + + @x1 @y1 @x2 @y2 End points of the line (both included). + @color fx9860g: white, black, none, invert + fxcg50: Any R5G6B5 color */ +void dline(int x1, int y1, int x2, int y2, color_t color); + +//--- +// Text rendering (topti) +//--- + +/* font_t - font data encoded for topti */ typedef struct { - /* Screen width, in pixels */ - int width; - /* Screen height, in pixels */ - int height; - /* Color depth (is 1 on fx9860g regardless of the gray engine) */ - int bpp; - /* Current rendering font */ - font_t const * font; + /* Length of font name (not necessarily NUL-terminated!) */ + uint title :5; + /* Font shape flags */ + uint bold :1; + uint italic :1; + uint serif :1; + uint mono :1; + /* Whether data is variable-length (proportional font) */ + uint prop :1; + /* Reserved for future use */ + uint :2; + /* Implemented charcter set */ + uint charset :4; + /* Line height */ + uint8_t line_height; + /* Storage height */ + uint8_t data_height; -} dinfo_t; + /* The rest of the data depends on whether the font is proportional */ + union { + /* For monospaced fonts */ + struct { + /* Width of glyphs */ + uint16_t width; + /* Storage size, in longwords, of each glyph */ + uint16_t storage_size; + /* Raw glyph data */ + uint32_t data[]; + }; + /* For proportional fonts */ + struct { + /* Storage index to find glyphs quickly */ + uint16_t index[16]; + /* Size array (padded to 4 bytes), 1 byte per entry, + followed by glyph data */ + uint8_t sized_data[]; + }; + }; -/* dinfo() - retrieve information from the display module - This function returns the value of most parameters of the display module. + /* The font name is stored after the data. The size is the length set + in the [title] field, padded to 4 bytes with NULs. There might not + be a NUL at the end. */ - @info Pointer to allocated dinfo_t structure, will be filled. */ -void dinfo(dinfo_t *info); -#endif +} GPACKED(4) font_t; + +/* dfont() - set the default font for text rendering + This font will be used by dtext() and sister functions. If [font = NULL], + gint's default font is used. + + On fx9860g, the default font is a 5x7 font very close to the system's. + On fxcg50, the default font is an original 10x12 font. + + @font Font to use for subsequent text rendering calls */ +void dfont(font_t const * font); + +/* dsize() - get the width and height of rendered text + This function computes the size that the given string would take up if + rendered with a certain font. If you specify a NULL font, the currently + configured font will be used; this is different from dfont(), which uses + gint's default font when NULL is passed. + + Note that the height of each glyph is not stored in the font, only the + maximum. Usually this is what you want because vertically-centered strings + must have the same baseline regardless of their contents. So the height + returned by dsize() is the same for all strings, only depends on the font. + + The height is computed in constant time, and the width in linear time. If + [w = NULL], this function returns in constant time. + + @str String whose size must be evaluated + @font Font to use; if NULL, defaults to the current font + @w @h Set to the width and height of the rendered text, may be NULL */ +void dsize(const char *str, font_t const * font, int *w, int *h); + +/* dtext() - display a string of text + + Draws some text in the video RAM using the font set with dfont() (or gint's + default if no such font was set). + + On fx9860g, due to the particular design of topti, this function performs + drastic rendering optimizations using the line structure of the VRAM and is + able to render several characters at once. + + This is not a printf()-family function so [str] cannot contain formats like + "%d" and you cannot pass aditional arguments. + + @x @y Coordinates of top-left corner of the rendered string + @str String to display + @fg Text color + fx9860g: white, black, none, invert + fxcg50: Any R5G6B6 color, or [color_none] + @bg Background color + fx9860g: white, black, none, invert + fxcg50: Any R5G6B5 color, or [color_none] */ +void dtext(int x, int y, const char *str, int fg, int bg); #endif /* GINT_DISPLAY */ diff --git a/include/gint/syscalls.h b/include/gint/syscalls.h index 993e433..247297b 100644 --- a/include/gint/syscalls.h +++ b/include/gint/syscalls.h @@ -1,11 +1,11 @@ //--- -// gint:core:syscalls - calls to CASIOWIN +// gint:syscalls - calls to CASIOWIN //--- -#ifndef GINT_CORE_SYSCALLS -#define GINT_CORE_SYSCALLS +#ifndef GINT_SYSCALLS +#define GINT_SYSCALLS /* __os_version(): Get OS version on the form MM.mm.iiii (10 bytes) */ void __os_version(char *version); -#endif /* GINT_CORE_SYSCALLS */ +#endif /* GINT_SYSCALLS */ diff --git a/make/Makefile b/make/Makefile index 22aec68..0e1affb 100755 --- a/make/Makefile +++ b/make/Makefile @@ -53,8 +53,9 @@ src := $(shell find ../src \ src_obj := $(foreach s,$(src),$(call src2obj,$s)) # Files with special handling -spe := ../src/font5x7.png -spe_obj := version.o $(foreach s,$(spe),$(call src2obj,$s)) +spe-fx := ../src/font5x7.png +spe-cg := ../src/font10x12.png +spe_obj := version.o $(foreach s,$(spe-$(CONFIG.TARGET)),$(call src2obj,$s)) # All object files obj := $(src_obj) $(spe_obj) @@ -107,14 +108,18 @@ $(call src2obj,../src/font5x7.png): ../src/font5x7.png @ mkdir -p $(dir $@) $(call cmd_m,fxconv,font5x7.png)$(conv) -f $< -o $@ name:gint_font5x7 \ charset:ascii grid.size:5x7 grid.padding:1 grid.border:0 +$(call src2obj,../src/font10x12.png): ../src/font10x12.png + @ mkdir -p $(dir $@) + $(call cmd_m,fxconv,font10x12.png)$(conv) -f $< -o $@ name:gint_font10x12 \ + charset:print grid.size:10x12 grid.padding:0 grid.border:3 # Version symbol. ld generates a .stack section for unknown reasons; I remove # it in the linker script. -version.o: +version.o: ../.git/HEAD @ mkdir -p $(dir $@) @ echo "_GINT_VERSION = $(version_hash);" > $@.txt $(call cmd_b,ld,$@) $(ld) -r -R $@.txt -o $@ -.PHONY: version.o + # # Cleaning # diff --git a/src/font10x12.png b/src/font10x12.png new file mode 100644 index 0000000000000000000000000000000000000000..cb99fb03b43658c1f3272fb490af7c58f3dc5145 GIT binary patch literal 17657 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 literal 0 HcmV?d00001 diff --git a/src/font5x7.png b/src/font5x7.png index 4400501c38cb32f52e1fb782f80e79c8d77dac96..69d20594c8b6d46fa509b5f7ed36417f46a7749a 100644 GIT binary patch literal 24858 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
$bkuZ`#)eBGB0qlD@5HYMT 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%0AR``r; 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 #include +#include -/* dhline() - optimized drawing of a horizontal line - @x1 @x2 @y Coordinates of endpoints of line (both included) - @color Any R5G6B5 color */ -static void dhline(int x1, int x2, int y, uint16_t color) +/* dhline() - optimized drawing of a horizontal line */ +void dhline(int x1, int x2, int y, uint16_t color) { /* Order and bounds */ if((uint)y >= 224) return; @@ -32,10 +30,8 @@ static void dhline(int x1, int x2, int y, uint16_t color) while(end > start) *--end = op; } -/* dvline() - optimized drawing of a vertical line - @y1 @y2 @x Coordinates of endpoints of line (both included) - @color Any R5G6B5 color */ -static void dvline(int y1, int y2, int x, uint16_t color) +/* dvline() - optimized drawing of a vertical line */ +void dvline(int y1, int y2, int x, uint16_t color) { /* Order and bounds */ if((uint)x >= 395) return; @@ -49,59 +45,3 @@ static void dvline(int y1, int y2, int x, uint16_t color) while(height--) *v = color, v += 396; } -/* dline() - Bresenham line drawing algorithm - Remotely adapted from MonochromeLib code by Pierre "PerriotLL" Le Gall. - Relies on dhline() and dvline() for optimized situations. - @x1 @y1 @x2 @y2 Coordinates of endpoints of line (included) - @color Any R5G6B5 color */ -void dline(int x1, int y1, int x2, int y2, uint16_t color) -{ - /* Possible optimizations */ - if(y1 == y2) - { - dhline(x1, x2, y1, color); - return; - } - if(x1 == x2) - { - dvline(y1, y2, x1, color); - return; - } - - /* Brensenham line drawing algorithm */ - - int i, x = x1, y = y1, cumul; - int dx = x2 - x1, dy = y2 - y1; - int sx = sgn(dx), sy = sgn(dy); - - dx = abs(dx), dy = abs(dy); - - dpixel(x1, y1, color); - - if(dx >= dy) - { - /* Start with a non-zero cumul to even the overdue between the - two ends of the line (for more regularity) */ - cumul = dx >> 1; - for(i = 1; i < dx; i++) - { - x += sx; - cumul += dy; - if(cumul > dx) cumul -= dx, y += sy; - dpixel(x, y, color); - } - } - else - { - cumul = dy >> 1; - for(i = 1; i < dy; i++) - { - y += sy; - cumul += dx; - if(cumul > dy) cumul -= dy, x += sx; - dpixel(x, y, color); - } - } - - dpixel(x2, y2, color); -} diff --git a/src/render-cg/drect.c b/src/render-cg/drect.c index 510d0ee..6384e97 100644 --- a/src/render-cg/drect.c +++ b/src/render-cg/drect.c @@ -21,39 +21,21 @@ void drect(int x1, int y1, int x2, int y2, uint16_t color) uint16_t *base = vram + 396 * y1; int height = y2 - y1 + 1; - /* Do borders first if there are at an odd position */ - - if(x1 & 1) - { - uint16_t *v = base; - for(int h = height; h; h--) - { - v[x1] = color; - v += 396; - } - x1++; - } - - if(x2 & 1) x2++; - else - { - uint16_t *v = base; - for(int h = height; h; h--) - { - v[x2] = color; - v += 396; - } - } - /* Now copy everything that's left as longwords */ - uint32_t *v = (void *)(base + x1); + int ax1 = x1 + (x1 & 1); + int ax2 = (x2 + 1) & ~1; + + uint32_t *v = (void *)(base + ax1); uint32_t op = (color << 16) | color; - int width = (x2 - x1) >> 1; + int width = (ax2 - ax1) >> 1; for(int h = height; h; h--) { + base[x1] = color; + base[x2] = color; for(int w = 0; w < width; w++) v[w] = op; v += 198; + base += 396; } } diff --git a/src/render-cg/topti-asm.h b/src/render-cg/topti-asm.h new file mode 100644 index 0000000..d5d4e1a --- /dev/null +++ b/src/render-cg/topti-asm.h @@ -0,0 +1,29 @@ +//--- +// gint:render-cg:topti-asm - Assembler drawing routines for topti +//--- + +#ifndef GINT_RENDERCG_TOPTIASM +#define GINT_RENDERCG_TOPTIASM + +/* Text rendering functions + + @vram Pointer to VRAM, offset for subglyph position + @data Glyph data, offset for subglyph position + @color topti_glyph_fg: Foreground color + topti_glyph_bg: Background color + topti_glyph_fg_bg: (fg << 16) | bg + @height Subglyph height + @width Sublgyph width + @stride Storage width of subglyph - width + @index Starting index in data, ie. top * storage width + left */ +typedef void asm_text_t(uint16_t *vram, uint32_t const * data, uint32_t color, + int height, int width, int stride, int index); + +/* Opaque foreground, transparent background */ +extern asm_text_t topti_glyph_fg; +/* Transparent foreground, opaque background */ +extern asm_text_t topti_glyph_bg; +/* Opaque foreground, opaque background */ +extern asm_text_t topti_glyph_fg_bg; + +#endif /* GINT_RENDERFX_TOPTIASM */ diff --git a/src/render-cg/topti-asm.s b/src/render-cg/topti-asm.s new file mode 100644 index 0000000..ac3247e --- /dev/null +++ b/src/render-cg/topti-asm.s @@ -0,0 +1,202 @@ +.global _topti_glyph_fg_bg +.global _topti_glyph_fg +.global _topti_glyph_bg +.section .pretext + +# Glyph rendering functions. +# These are pretty naive, using only word accesses to index the VRAM and +# absolute positions to index the glyph data, instead of shiting a single +# longword to real all bits in order. This is because we only render a subglyph +# (for clipping) so there'a non-zero stride in glyph data. + +# Parameters: +# r4: vram +# r5: data +# r6: color (either fg, bg, or (fg << 16) | bg) +# 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: vram pointer +# r5: glyph pointer +# r6: color +# r7: y counter +# Callee-saved registers: +# r8: vram stride + +# Opaque foreground, opaque background + +_topti_glyph_fg_bg: + + # Compute VRAM stride 2 * (396-width) + mov.l r8, @-r15 + mov.l 1f, r8 + mov.l @(4, r15), r3 + shll r3 + sub r3, r8 + + # Load the starting index + mov.l @(12, r15), r3 + +.fg_bg_y: + # Initialize width counter + mov.l @(4, r15), r2 + +.fg_bg_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 + + # Select the correct 16 bits or r6 + bf/s .fg_bg_zero + mov r6, r1 + swap.w r6, r1 + +.fg_bg_zero: + # Write color to VRAM + mov.w r1, @r4 + add #2, r4 + + # Leave the x-loop if x counter reaches 0 + dt r2 + bf/s .fg_bg_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_bg_y + add r8, r4 + + rts + mov.l @r15+, r8 + +# Opaque foreground, transparent background + +_topti_glyph_fg: + + # Compute VRAM stride 2 * (396-width) + mov.l r8, @-r15 + mov.l 1f, r8 + mov.l @(4, r15), r3 + shll r3 + sub r3, 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 VRAM 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 + +# Transparent foreground, opaque background + +_topti_glyph_bg: + + # Compute VRAM stride 2 * (396-width) + mov.l r8, @-r15 + mov.l 1f, r8 + mov.l @(4, r15), r3 + shll r3 + sub r3, r8 + + # Load the starting index + mov.l @(12, r15), r3 + +.bg_y: + # Initialize width counter + mov.l @(4, r15), r2 + +.bg_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 VRAM only if it's a 0 bit + bt .bg_next + mov.w r6, @r4 + +.bg_next: + # Leave the x-loop if x counter reaches 0 + add #2, r4 + dt r2 + bf/s .bg_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 .bg_y + add r8, r4 + + rts + mov.l @r15+, r8 + +# Data + +.align 4 + +1: .long 396*2 diff --git a/src/render-cg/topti.c b/src/render-cg/topti.c new file mode 100644 index 0000000..538b16c --- /dev/null +++ b/src/render-cg/topti.c @@ -0,0 +1,96 @@ +#define GINT_NEED_VRAM +#include +#include +#include +#include +#include "topti-asm.h" + +/* Default font */ +extern font_t gint_font10x12; +font_t const * gint_default_font = &gint_font10x12; +font_t const * topti_font = &gint_font10x12; + +/* topti_glyph(): Render a glyph on the VRAM + Prints a glyph naively using word accesses, because for most fonts with a + small size (including gint's 10x12 font) this will be more efficient than + the complex logic for longword accesses. + + This function assumes that at least one of [fg] and [bg] is not transparent. + + @vram Target position on VRAM, adjusted to [top], not adjusted to [left] + @data Glyph data + @left Left-position of subglyph + @top Top-Position of subglyph + @width Subglyph width + @height Subglyph height + @dataw Glyph width + @fg @bg Foreground and background colors */ +GSECTION(".pretext") +void topti_glyph(uint16_t *vram, uint32_t const * data, int left, int top, + int width, int height, int dataw, int fg, int bg) +{ + int index = top * dataw + left; + + /* Most common situation: opaque text on transparent background */ + if(bg < 0) topti_glyph_fg(vram + left, data, fg, height, width, + dataw - width, index); + /* Full text on opaque background */ + else if(fg >= 0) topti_glyph_fg_bg(vram + left, data, (fg << 16) | bg, + height, width, dataw - width, index); + /* Draw background but not text */ + else topti_glyph_bg(vram + left, data, bg, height, width, + dataw - width, index); +} + +GSECTION(".pretext") +void topti_render(int x, int y, const char *str, size_t size, font_t const *f, + int fg, int bg) +{ + /* Raw glyph data */ + uint32_t const * data = f->prop + ? (void *)(f->sized_data + charset_size(f->charset)) + : f->data; + + /* Storage height, top position within glyph */ + int height = f->data_height, top = 0; + + /* Vertical clipping */ + if(x > 395 || y > 223 || y + height <= 0) return; + if(y + height > 224) height = 224 - y; + if(y < 0) top = -y, height += y, y = 0; + + /* Move to top row */ + uint16_t *target = vram + 396 * y; + + /* Character spacing */ + int space = 2; + + /* Read each character from the input string */ + while(size--) + { + int glyph = charset_decode(f->charset, *str++); + if(glyph < 0) continue; + + int index = topti_offset(f, glyph); + + /* Compute horizontal intersection between glyph and screen */ + + int dataw = f->prop ? f->sized_data[glyph] : f->width; + int width = dataw, left = 0; + + if(x + dataw <= 0) + { + x += dataw + space; + continue; + } + if(x < 0) left = -x, width += x; + if(x + width > 396) width = 396 - x; + + /* Render glyph */ + + topti_glyph(target + x, data + index, left, top, width, height, + dataw, fg, bg); + + x += dataw + space; + } +} diff --git a/src/render-fx/dline.c b/src/render-fx/dline.c index 93d23a9..8eff80b 100644 --- a/src/render-fx/dline.c +++ b/src/render-fx/dline.c @@ -1,11 +1,9 @@ #define GINT_NEED_VRAM -#include #include +#include #include -/* dhline() - optimized drawing of a horizontal line using a rectangle mask - @x1 @x2 @y Coordinates of endpoints of line (both included) - @color Allowed colors: white, black, none, reverse */ +/* dhline() - optimized drawing of a horizontal line using a rectangle mask */ void dhline(int x1, int x2, int y, color_t color) { if((uint)y >= 64) return; @@ -31,7 +29,7 @@ void dhline(int x1, int x2, int y, color_t color) data[2] |= m[2]; data[3] |= m[3]; } - else if(color == color_reverse) + else if(color == color_invert) { data[0] ^= m[0]; data[1] ^= m[1]; @@ -40,92 +38,26 @@ void dhline(int x1, int x2, int y, color_t color) } } -/* dvline() - optimized drawing of a vertical line - This variant is less powerful than dhline() because the line-based structure - of the vram cannot be used here. - @y1 @y2 @x Coordinates of endpoints of line (both included) - @color Allowed colors: black, white, none, reverse */ -void dvline(int y1, int y2, int x, color_t operator) +/* dvline() - optimized drawing of a vertical line */ +void dvline(int y1, int y2, int x, color_t color) { if((uint)x >= 128) return; if(y1 > y2) swap(y1, y2); uint32_t *base = vram + (y1 << 2) + (x >> 5); - uint32_t *lword = base + ((y2 - y1 + 1) << 4); + uint32_t *lword = base + ((y2 - y1 + 1) << 2); uint32_t mask = 1 << (~x & 31); - switch(operator) + if(color == color_white) { - case color_white: while(lword > base) lword -= 4, *lword &= ~mask; - break; - - case color_black: + } + else if(color == color_black) + { while(lword > base) lword -= 4, *lword |= mask; - break; - - case color_reverse: + } + else if(color == color_invert) + { while(lword > base) lword -= 4, *lword ^= mask; - break; - - default: - break; } } - -/* dline() - Bresenham line drawing algorithm - Remotely adapted from MonochromeLib code by Pierre "PerriotLL" Le Gall. - Relies on dhline() and dvline() for optimized situations. - @x1 @y1 @x2 @y2 Coordinates of endpoints of line (included) - @color Allowed colors: black, white, none, reverse */ -void dline(int x1, int y1, int x2, int y2, color_t color) -{ - /* Possible optimizations */ - if(y1 == y2) - { - dhline(x1, x2, y1, color); - return; - } - if(x1 == x2) - { - dvline(y1, y2, x1, color); - return; - } - - /* Brensenham line drawing algorithm */ - - int i, x = x1, y = y1, cumul; - int dx = x2 - x1, dy = y2 - y1; - int sx = sgn(dx), sy = sgn(dy); - - dx = abs(dx), dy = abs(dy); - - dpixel(x1, y1, color); - - if(dx >= dy) - { - /* Start with a non-zero cumul to even the overdue between the - two ends of the line (for more regularity) */ - cumul = dx >> 1; - for(i = 1; i < dx; i++) - { - x += sx; - cumul += dy; - if(cumul > dx) cumul -= dx, y += sy; - dpixel(x, y, color); - } - } - else - { - cumul = dy >> 1; - for(i = 1; i < dy; i++) - { - y += sy; - cumul += dx; - if(cumul > dy) cumul -= dy, x += sx; - dpixel(x, y, color); - } - } - - dpixel(x2, y2, color); -} diff --git a/src/render-fx/dpixel.c b/src/render-fx/dpixel.c index 51d3c96..abfa8df 100644 --- a/src/render-fx/dpixel.c +++ b/src/render-fx/dpixel.c @@ -11,18 +11,16 @@ void dpixel(int x, int y, color_t color) uint32_t *lword = vram + (y << 2) + (x >> 5); uint32_t mask = 1 << (~x & 31); - switch(color) + if(color == color_white) { - case color_white: *lword &= ~mask; - break; - case color_black: + } + else if(color == color_black) + { *lword |= mask; - break; - case color_reverse: + } + else if(color == color_invert) + { *lword ^= mask; - break; - default: - return; } } diff --git a/src/render-fx/drect.c b/src/render-fx/drect.c index 6d4ce82..e33eac5 100644 --- a/src/render-fx/drect.c +++ b/src/render-fx/drect.c @@ -37,7 +37,7 @@ void drect(int x1, int y1, int x2, int y2, color_t color) *--lword |= m[1]; *--lword |= m[0]; } - else if(color == color_reverse) while(lword > base) + else if(color == color_invert) while(lword > base) { *--lword ^= m[3]; *--lword ^= m[2]; diff --git a/src/render-fx/topti-asm.s b/src/render-fx/topti-asm.s index a2fae15..11bac42 100644 --- a/src/render-fx/topti-asm.s +++ b/src/render-fx/topti-asm.s @@ -94,7 +94,7 @@ _topti_asm_none: nop .align 4 -_topti_asm_reverse: +_topti_asm_invert: 1: mov.l @r6+, r0 dt r7 mov.l @r4, r1 @@ -165,7 +165,7 @@ _topti_asm_text: .long _topti_asm_dark .long _topti_asm_black .long _topti_asm_none - .long _topti_asm_reverse + .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 16b73b3..cea4803 100644 --- a/src/render-fx/topti.c +++ b/src/render-fx/topti.c @@ -2,104 +2,14 @@ #include #include #include +#include #include "topti-asm.h" /* Default font */ extern font_t gint_font5x7; +font_t const * gint_default_font = &gint_font5x7; font_t const * topti_font = &gint_font5x7; -/* dfont() - set the default font for text rendering */ -GSECTION(".pretext") -void dfont(font_t const * font) -{ - topti_font = font ? font : &gint_font5x7; -} - -/* enum charset: Available character set decoders - Each charset is associated with a reduced character table. */ -enum charset -{ - charset_numeric = 0, /* 10 elements: 0..9 */ - charset_upper = 1, /* 26 elements: A..Z */ - charset_alpha = 2, /* 52 elements: A..Z, a..z */ - charset_alnum = 3, /* 62 elements: A..Z, a..z, 0..9 */ - charset_print = 4, /* 95 elements: 0x20..0x7e */ - charset_ascii = 5, /* 128 elements: 0x00..0x7f */ -}; - -/* charset_size(): Number of elements in each character set - @set Character set ID - Returns the expected number of glyphs, -1 if charset ID is invalid. */ -GSECTION(".pretext") -int charset_size(enum charset set) -{ - int size[] = { 10, 26, 52, 62, 95, 128 }; - return (uint)set < 6 ? size[set] : -1; -} - -/* charset_decode(): Translate ASCII into reduced character sets - Returns the position of [c] in the character table of the given charset, or - -1 if [c] is not part of that set. - @set Any character set - @c Character to decode */ -GSECTION(".pretext") -int charset_decode(enum charset set, uint c) -{ - int x, y; - - switch(set) - { - case charset_numeric: - x = c - '0'; - return (x < 10) ? x : -1; - case charset_upper: - x = (c - 'A') & ~0x20; - return (x < 26) ? x : -1; - case charset_alnum: - x = c - '0'; - if(x < 10) return x; - /* Intentional fallthrough */ - case charset_alpha: - y = c & 0x20; - x = (c ^ y) - 'A'; - /* Turn 32 into 26 and leave 0 as 0 */ - y = y - (y >> 3) - (y >> 4); - return (x < 26) ? (x + y) : -1; - case charset_print: - x = c - 0x20; - return (x < 0x5f) ? x : -1; - case charset_ascii: - return c; - } - - return -1; -} - -/* topti_offset(): Use a font index to find the location of a glyph - @f Font object - @glyph Glyph number obtained by charset_decode(), must be nonnegative. - Returns the offset the this glyph's data in the font's data array. When - using a proportional font, the size array is not heeded for. */ -GSECTION(".pretext") -int topti_offset(font_t const *f, uint glyph) -{ - /* Non-proportional fonts don't need an index */ - if(!f->prop) return glyph * f->storage_size; - - uint8_t const *width = f->sized_data; - - /* The index gives us the position of all glyphs whose IDs are mutiples - of 8. Start with a close one and iterate from there. */ - uint g = glyph & ~0x7; - int offset = f->index[g >> 3]; - - /* Traverse the width array (which is in bits) while converting to - longword size */ - while(g < glyph) offset += (width[g++] * f->data_height + 31) >> 5; - - return offset; -} - /* topti_split(): Split glyph data into lines This function splits the data from [glyph] inyo lines and writes a bit of each line in [operators]. This operation is meant to be used multiple times @@ -299,7 +209,7 @@ void dsize(const char *str, font_t const * f, int *w, int *h) /* dtext() - display a string of text */ GSECTION(".pretext") -void dtext(int x, int y, const char *str, color_t fg, color_t bg) +void dtext(int x, int y, const char *str, int fg, int bg) { if((uint)fg >= 8 || (uint)bg >= 8) return; topti_render(x, y, str, topti_font, topti_asm_text[fg], diff --git a/src/render/dline.c b/src/render/dline.c new file mode 100644 index 0000000..2a2c016 --- /dev/null +++ b/src/render/dline.c @@ -0,0 +1,60 @@ +#include +#include +#include + +/* dline() - Bresenham line drawing algorithm + Remotely adapted from MonochromeLib code by Pierre "PerriotLL" Le Gall. + Relies on platform-dependent dhline() and dvline() for optimized situations. + @x1 @y1 @x2 @y2 Coordinates of endpoints of line (included) + @color Any R5G6B5 color */ +void dline(int x1, int y1, int x2, int y2, color_t color) +{ + /* Possible optimizations */ + if(y1 == y2) + { + dhline(x1, x2, y1, color); + return; + } + if(x1 == x2) + { + dvline(y1, y2, x1, color); + return; + } + + /* Brensenham line drawing algorithm */ + + int i, x = x1, y = y1, cumul; + int dx = x2 - x1, dy = y2 - y1; + int sx = sgn(dx), sy = sgn(dy); + + dx = abs(dx), dy = abs(dy); + + dpixel(x1, y1, color); + + if(dx >= dy) + { + /* Start with a non-zero cumul to even the overdue between the + two ends of the line (for more regularity) */ + cumul = dx >> 1; + for(i = 1; i < dx; i++) + { + x += sx; + cumul += dy; + if(cumul > dx) cumul -= dx, y += sy; + dpixel(x, y, color); + } + } + else + { + cumul = dy >> 1; + for(i = 1; i < dy; i++) + { + y += sy; + cumul += dx; + if(cumul > dy) cumul -= dy, x += sx; + dpixel(x, y, color); + } + } + + dpixel(x2, y2, color); +} diff --git a/src/render/topti.c b/src/render/topti.c new file mode 100644 index 0000000..037097c --- /dev/null +++ b/src/render/topti.c @@ -0,0 +1,73 @@ +#include +#include +#include + +/* dfont(): Set the default font for text rendering */ +GSECTION(".pretext") +void dfont(font_t const * font) +{ + topti_font = font ? font : gint_default_font; +} + +/* charset_size(): Number of elements in each character set */ +GSECTION(".pretext") +int charset_size(enum topti_charset set) +{ + int size[] = { 10, 26, 52, 62, 95, 128 }; + return (uint)set < 6 ? size[set] : -1; +} + +/* charset_decode(): Translate ASCII into reduced character sets */ +GSECTION(".pretext") +int charset_decode(enum topti_charset set, uint c) +{ + int x, y; + + switch(set) + { + case charset_numeric: + x = c - '0'; + return (x < 10) ? x : -1; + case charset_upper: + x = (c - 'A') & ~0x20; + return (x < 26) ? x : -1; + case charset_alnum: + x = c - '0'; + if(x < 10) return x; + /* Intentional fallthrough */ + case charset_alpha: + y = c & 0x20; + x = (c ^ y) - 'A'; + /* Turn 32 into 26 and leave 0 as 0 */ + y = y - (y >> 3) - (y >> 4); + return (x < 26) ? (x + y) : -1; + case charset_print: + x = c - 0x20; + return (x < 0x5f) ? x : -1; + case charset_ascii: + return c; + } + + return -1; +} + +/* topti_offset(): Use a font index to find the location of a glyph */ +GSECTION(".pretext") +int topti_offset(font_t const *f, uint glyph) +{ + /* Non-proportional fonts don't need an index */ + if(!f->prop) return glyph * f->storage_size; + + uint8_t const *width = f->sized_data; + + /* The index gives us the position of all glyphs whose IDs are mutiples + of 8. Start with a close one and iterate from there. */ + uint g = glyph & ~0x7; + int offset = f->index[g >> 3]; + + /* Traverse the width array (which is in bits) while converting to + longword size */ + while(g < glyph) offset += (width[g++] * f->data_height + 31) >> 5; + + return offset; +}