diff --git a/src/video/glhudmodel.c b/src/video/glhudmodel.c index 092de5e7..6998b7f4 100644 --- a/src/video/glhudmodel.c +++ b/src/video/glhudmodel.c @@ -12,6 +12,106 @@ #include "glhudmodel.h" #include "glvideo.h" +// . . . +// . x . +// . . . +typedef struct EightPatchArgs_s { + GLModel *parent; + unsigned int pixelSize; + unsigned int glyphScale; + unsigned int fb_h; + unsigned int fb_w; + unsigned int srcIdx; + unsigned int dstIdx; +} EightPatchArgs_s; + +// Generates a semi-opaque halo effect around each glyph +static void _eightpatch_opaquePixelHaloFilter(const EightPatchArgs_s args) { + +#if USE_RGBA4444 +# define SEMI_OPAQUE (0x0C << SHIFT_A) +#else +# define SEMI_OPAQUE (0xC0 << SHIFT_A) +#endif + + const unsigned int pixelSize = args.pixelSize; + const unsigned int glyphScale = args.glyphScale; + const unsigned int fb_w = args.fb_w; + const unsigned int fb_h = args.fb_h; + const unsigned int srcIdx = args.srcIdx; + const unsigned int srcCol = (srcIdx % fb_w); + const unsigned int lastCol = (fb_w-1); + const unsigned int dstPointStride = pixelSize * glyphScale; + const unsigned int dstRowStride = fb_w * dstPointStride; + const unsigned int texRowStride = ((fb_w * glyphScale) * (glyphScale/*1 row*/ * glyphScale) * pixelSize); + const unsigned int lastPoint = ((fb_w * glyphScale) * (fb_h * glyphScale) * pixelSize); + + uint8_t *texPixels = args.parent->texPixels; + const int dstIdx0 = (int)args.dstIdx; + const int dstPre0 = dstIdx0 - texRowStride; // negative is okay + const int dstPost0 = dstIdx0 + texRowStride; + + // scale glyph data 1x, 2x, ... + + // north pixels + if (dstPre0 >= 0) { + int dstPre = dstPre0; + for (unsigned int k=0; kglyphMultiplier = 1; hudElement->colorScheme = RED_ON_BLACK; } return hudElement; @@ -28,6 +129,8 @@ void *glhud_createCustom(unsigned int sizeofModel) { void glhud_setupDefault(GLModel *parent) { GLModelHUDElement *hudElement = (GLModelHUDElement *)parent->custom; + assert(hudElement->glyphMultiplier > 0); + char *submenu = (char *)(hudElement->tpl); const unsigned int cols = hudElement->tplWidth; const unsigned int rows = hudElement->tplHeight; @@ -36,71 +139,68 @@ void glhud_setupDefault(GLModel *parent) { // render template into indexed fb interface_plotMessage(fb, hudElement->colorScheme, submenu, cols, rows); - // Generate OpenGL color from indexed color + // generate OpenGL texture/color from indexed color const unsigned int fb_w = hudElement->pixWidth; const unsigned int fb_h = hudElement->pixHeight; - const unsigned int count = fb_w * fb_h; - const unsigned int countOut = count * sizeof(PIXEL_TYPE); - for (unsigned int srcIdx=0, dstIdx=0; srcIdxblackIsTransparent) { - // make black transparent - } else { - rgb |= ((PIXEL_TYPE)MAX_SATURATION << SHIFT_A); + const unsigned int pixelSize = sizeof(PIXEL_TYPE); + const unsigned int glyphScale = hudElement->glyphMultiplier; + const unsigned int dstPointStride = pixelSize * glyphScale; + const unsigned int dstRowStride = fb_w * dstPointStride; + const unsigned int texSubRowStride = dstRowStride * (glyphScale-1); + + LOG("fb_h:%u, fb_w:%u -- texH:%u texW:%u", fb_h, fb_w, parent->texHeight, parent->texWidth); + + do { + unsigned int srcIdx = 0; + unsigned int texIdx = 0; + for (unsigned int i=0; iblackIsTransparent) { + // black remains transparent + } else { + rgba |= ((PIXEL_TYPE)MAX_SATURATION << SHIFT_A); + } + + // scale glyph data 1x, 2x, ... + unsigned int dstIdx = texIdx; + for (unsigned int k=0; ktexPixels + dstIdx) ) = rgba; + } + dstIdx -= dstPointStride; + } + } } - *( (PIXEL_TYPE*)(parent->texPixels + dstIdx) ) = rgb; - } + } while (0); - // Second pass to generate a semi-opaque halo effect around each glyph if (hudElement->opaquePixelHalo) { - for (int // -negative index values allowed here ... - srcIdx=0, dstPre=-((fb_w+1)*sizeof(PIXEL_TYPE)), dstIdx=0, dstPost=((fb_w-1)*sizeof(PIXEL_TYPE)); - srcIdx= 0) { // north pixels - if (col != 0) { - *((PIXEL_TYPE*)(parent->texPixels + dstPre)) |= SEMI_OPAQUE; - } - *((PIXEL_TYPE*)(parent->texPixels + dstPre + sizeof(PIXEL_TYPE) )) |= SEMI_OPAQUE; - if (col < fb_w-1) { - *((PIXEL_TYPE*)(parent->texPixels + dstPre + (2*sizeof(PIXEL_TYPE)) )) |= SEMI_OPAQUE; - } - } - - if (col != 0) { // west pixel - *((PIXEL_TYPE*)(parent->texPixels + dstIdx - sizeof(PIXEL_TYPE) )) |= SEMI_OPAQUE; - } - - if (col < fb_w-1) { // east pixel - *((PIXEL_TYPE*)(parent->texPixels + dstIdx + sizeof(PIXEL_TYPE) )) |= SEMI_OPAQUE; - } - - if (dstPost < countOut) { // south pixels - if (col != 0) { - *((PIXEL_TYPE*)(parent->texPixels + dstPost)) |= SEMI_OPAQUE; - } - *((PIXEL_TYPE*)(parent->texPixels + dstPost + sizeof(PIXEL_TYPE) )) |= SEMI_OPAQUE; - if (col < fb_w-1) { - *((PIXEL_TYPE*)(parent->texPixels + dstPost + (2*sizeof(PIXEL_TYPE)) )) |= SEMI_OPAQUE; + // perform "eight patch" on adjacent pixels + if (rgb) { + EightPatchArgs_s args = { + .parent = parent, + .pixelSize = pixelSize, + .glyphScale = glyphScale, + .fb_h = fb_h, + .fb_w = fb_w, + .srcIdx = srcIdx, + .dstIdx = dstIdx, + }; + _eightpatch_opaquePixelHaloFilter(args); } } } diff --git a/src/video/glhudmodel.h b/src/video/glhudmodel.h index 9601f3a7..0e74faf6 100644 --- a/src/video/glhudmodel.h +++ b/src/video/glhudmodel.h @@ -24,8 +24,9 @@ unsigned int tplHeight; /* template height */ \ \ uint8_t *pixels; /* raw indexed data */ \ - unsigned int pixWidth; /* FB width -- FIXME TODO : this is really the same as GLModel.texWidth */ \ - unsigned int pixHeight; /* FB height -- FIXME TODO : this is really the same as GLModel.texHeight */ \ + unsigned int pixWidth; /* FB width -- this is the same as GLModel.texWidth if glyphMultiplier is 1 */ \ + unsigned int pixHeight; /* FB height -- this is the same as GLModel.texHeight if glyphMultiplier is 1 */ \ + unsigned int glyphMultiplier; \ \ interface_colorscheme_t colorScheme; \ bool blackIsTransparent; \ diff --git a/src/video/gltouchkbd.c b/src/video/gltouchkbd.c index b29b37bb..d5e513ed 100644 --- a/src/video/gltouchkbd.c +++ b/src/video/gltouchkbd.c @@ -37,8 +37,8 @@ #define MAINROW 4 // main keyboard row offset #define SWITCHCOL 0 -#define KBD_FB_WIDTH (KBD_TEMPLATE_COLS * FONT80_WIDTH_PIXELS) -#define KBD_FB_HEIGHT (KBD_TEMPLATE_ROWS * FONT_HEIGHT_PIXELS) +#define KBD_FB_WIDTH (KBD_TEMPLATE_COLS * FONT80_WIDTH_PIXELS) // 10 * 7 == 70 +#define KBD_FB_HEIGHT (KBD_TEMPLATE_ROWS * FONT_HEIGHT_PIXELS) // 8 * 16 == 128 #define KBD_OBJ_W 2.0 #define KBD_OBJ_H 2.0 @@ -129,27 +129,34 @@ static struct { bool ctrlPressed; + unsigned int glyphMultiplier; + struct timespec timingBegin; } kbd = { 0 }; // ---------------------------------------------------------------------------- // Misc internal methods +#warning FIXME TODO ... make this a generic GLModelHUDElement function static void _rerender_character(int col, int row) { GLModelHUDKeyboard *hudKeyboard = (GLModelHUDKeyboard *)(kbd.model->custom); - // re-generate texture from indexed color - const unsigned int colCount = 1; - const unsigned int pixCharsWidth = FONT80_WIDTH_PIXELS*colCount; - const unsigned int rowStride = hudKeyboard->pixWidth - pixCharsWidth; - unsigned int srcIdx = (row * hudKeyboard->pixWidth * FONT_HEIGHT_PIXELS) + (col * FONT80_WIDTH_PIXELS); - unsigned int dstIdx = srcIdx * sizeof(PIXEL_TYPE); + // In English, this changes one glyph within the keyboard texture data to be the (un)selected color. It handles + // scaling from indexed color to RGBA8888 (4x) and then possibly scaling to 2x or greater. - for (unsigned int i=0; ipixWidth; + const unsigned int pixelSize = sizeof(PIXEL_TYPE); + const unsigned int glyphScale = hudKeyboard->glyphMultiplier; + const unsigned int dstPointStride = pixelSize * glyphScale; + const unsigned int dstRowStride = fb_w * dstPointStride; + const unsigned int texSubRowStride = dstRowStride + (dstRowStride * (glyphScale-1)); + const unsigned int indexedIdx = (row * fb_w * FONT_HEIGHT_PIXELS) + (col * FONT80_WIDTH_PIXELS); + unsigned int texIdx = ((row * fb_w * FONT_HEIGHT_PIXELS * /*1 row:*/glyphScale) + (col * FONT80_WIDTH_PIXELS)) * dstPointStride; - // HACK : red <-> green swap - PIXEL_TYPE rgba = *((PIXEL_TYPE *)(kbd.model->texPixels + dstIdx)); + for (unsigned int i=0; i green swap of texture data + PIXEL_TYPE rgba = *((PIXEL_TYPE *)(kbd.model->texPixels + texIdx)); PIXEL_TYPE r = (rgba >> SHIFT_R) & MAX_SATURATION; PIXEL_TYPE g = (rgba >> SHIFT_G) & MAX_SATURATION; #if USE_RGBA4444 @@ -157,13 +164,16 @@ static void _rerender_character(int col, int row) { #else rgba = ( ((rgba>>SHIFT_B)<texPixels + dstIdx) ) = rgba; - - srcIdx += 1; - dstIdx += sizeof(PIXEL_TYPE); + // scale texture data 1x, 2x, ... + unsigned int dstIdx = texIdx; + for (unsigned int k=0; ktexPixels + dstIdx) ) = rgba; + } + dstIdx -= dstPointStride; + } } - srcIdx += rowStride; - dstIdx = srcIdx * sizeof(PIXEL_TYPE); + texIdx -= (FONT80_WIDTH_PIXELS * dstPointStride); } kbd.model->texDirty = true; @@ -464,6 +474,7 @@ static void *_create_touchkbd_hud(void) { if (hudKeyboard) { hudKeyboard->blackIsTransparent = true; hudKeyboard->opaquePixelHalo = true; + hudKeyboard->glyphMultiplier = kbd.glyphMultiplier; } return hudKeyboard; } @@ -498,7 +509,9 @@ static void gltouchkbd_setup(void) { gltouchkbd_shutdown(); - kbd.model = mdlCreateQuad(-1.0, -1.0, KBD_OBJ_W, KBD_OBJ_H, MODEL_DEPTH, KBD_FB_WIDTH, KBD_FB_HEIGHT, (GLCustom){ + GLsizei texW = KBD_FB_WIDTH * kbd.glyphMultiplier; + GLsizei texH = KBD_FB_HEIGHT * kbd.glyphMultiplier; + kbd.model = mdlCreateQuad(-1.0, -1.0, KBD_OBJ_W, KBD_OBJ_H, MODEL_DEPTH, texW, texH, (GLCustom){ .create = &_create_touchkbd_hud, .setup = &_setup_touchkbd_hud, .destroy = &_destroy_touchkbd_hud, @@ -974,6 +987,8 @@ static void _init_gltouchkbd(void) { kbd.ctrlCol = DEFAULT_CTRL_COL; kbd.ctrlRow = CTRLROW; + kbd.glyphMultiplier = 1; + glnode_registerNode(RENDER_LOW, (GLNode){ .setup = &gltouchkbd_setup, .shutdown = &gltouchkbd_shutdown, diff --git a/src/video/gltouchmenu.c b/src/video/gltouchmenu.c index 1870b935..adb6d197 100644 --- a/src/video/gltouchmenu.c +++ b/src/video/gltouchmenu.c @@ -71,6 +71,7 @@ static struct { static struct { GLModel *model; + unsigned int glyphMultiplier; bool topLeftShowing; bool topRightShowing; } menu = { 0 }; @@ -356,6 +357,7 @@ static void *_create_touchmenu(void) { if (hudMenu) { hudMenu->blackIsTransparent = true; hudMenu->opaquePixelHalo = true; + hudMenu->glyphMultiplier = menu.glyphMultiplier; } return hudMenu; } @@ -376,7 +378,10 @@ static void gltouchmenu_setup(void) { LOG("gltouchmenu_setup ..."); mdlDestroyModel(&menu.model); - menu.model = mdlCreateQuad(-1.0, 1.0-MENU_OBJ_H, MENU_OBJ_W, MENU_OBJ_H, MODEL_DEPTH, MENU_FB_WIDTH, MENU_FB_HEIGHT, (GLCustom){ + + GLsizei texW = MENU_FB_WIDTH * menu.glyphMultiplier; + GLsizei texH = MENU_FB_HEIGHT * menu.glyphMultiplier; + menu.model = mdlCreateQuad(-1.0, 1.0-MENU_OBJ_H, MENU_OBJ_W, MENU_OBJ_H, MODEL_DEPTH, texW, texH, (GLCustom){ .create = &_create_touchmenu, .setup = &_setup_touchmenu, .destroy = &_destroy_touchmenu, @@ -552,6 +557,8 @@ static void _init_gltouchmenu(void) { interface_setTouchMenuEnabled = &gltouchmenu_setTouchMenuEnabled; interface_setTouchMenuVisibility = &gltouchmenu_setTouchMenuVisibility; + menu.glyphMultiplier = 1; + glnode_registerNode(RENDER_TOP, (GLNode){ .setup = &gltouchmenu_setup, .shutdown = &gltouchmenu_shutdown,