#include #include #include #include #include "rencache.h" #define CELL_SIZE 96 #define COMMAND_BARE_SIZE offsetof(Command, text) enum { SET_CLIP, DRAW_TEXT, DRAW_RECT, DRAW_TEXT_SUBPIXEL, DRAW_IMAGE }; typedef struct { int8_t type; int8_t tab_size; int8_t subpixel_scale; int8_t x_subpixel_offset; int32_t size; RenRect rect; RenColor color; FontDesc *font_desc; CPReplaceTable *replacements; RenColor replace_color; uint32_t image_id; /* FIXME: do not add a new field but use unions or alias another field. */ char text[0]; } Command; #define FONT_REFS_MAX 12 struct FontRef { FontDesc *font_desc; int index; }; typedef struct FontRef FontRef; FontRef font_refs[FONT_REFS_MAX]; int font_refs_len = 0; static inline int min(int a, int b) { return a < b ? a : b; } static inline int max(int a, int b) { return a > b ? a : b; } static int font_refs_add(lua_State *L, FontDesc *font_desc, int index) { for (int i = 0; i < font_refs_len; i++) { if (font_refs[i].font_desc == font_desc) { return font_refs[i].index; } } if (font_refs_len >= FONT_REFS_MAX) { fprintf(stderr, "Warning: (" __FILE__ "): exhausted font reference buffer\n"); return LUA_NOREF; } lua_pushvalue(L, index); int ref = luaL_ref(L, LUA_REGISTRYINDEX); font_refs[font_refs_len++] = (FontRef) { font_desc, ref }; return ref; } static void font_refs_clear(lua_State *L) { for (int i = 0; i < font_refs_len; i++) { luaL_unref(L, LUA_REGISTRYINDEX, font_refs[i].index); } font_refs_len = 0; } /* 32bit fnv-1a hash */ #define HASH_INITIAL 2166136261 static void hash(unsigned *h, const void *data, int size) { const unsigned char *p = data; while (size--) { *h = (*h ^ *p++) * 16777619; } } static inline int cell_idx(int x, int y) { return x + y * CELLS_X; } static inline bool rects_overlap(RenRect a, RenRect b) { return b.x + b.width >= a.x && b.x <= a.x + a.width && b.y + b.height >= a.y && b.y <= a.y + a.height; } static RenRect intersect_rects(RenRect a, RenRect b) { int x1 = max(a.x, b.x); int y1 = max(a.y, b.y); int x2 = min(a.x + a.width, b.x + b.width); int y2 = min(a.y + a.height, b.y + b.height); return (RenRect) { x1, y1, max(0, x2 - x1), max(0, y2 - y1) }; } static RenRect merge_rects(RenRect a, RenRect b) { int x1 = min(a.x, b.x); int y1 = min(a.y, b.y); int x2 = max(a.x + a.width, b.x + b.width); int y2 = max(a.y + a.height, b.y + b.height); return (RenRect) { x1, y1, x2 - x1, y2 - y1 }; } static Command* push_command(RenCache *rc, int type, int size) { Command *cmd = (Command*) (rc->command_buf + rc->command_buf_idx); int n = rc->command_buf_idx + size; if (n > COMMAND_BUF_SIZE) { fprintf(stderr, "Warning: (" __FILE__ "): exhausted command buffer\n"); return NULL; } rc->command_buf_idx = n; memset(cmd, 0, COMMAND_BARE_SIZE); cmd->type = type; cmd->size = size; return cmd; } static bool next_command(RenCache *rc, Command **prev) { if (*prev == NULL) { *prev = (Command*) rc->command_buf; } else { *prev = (Command*) (((char*) *prev) + (*prev)->size); } return *prev != ((Command*) (rc->command_buf + rc->command_buf_idx)); } void rencache_init(RenCache *rc) { rc->cells_prev = rc->cells_buf1; rc->cells = rc->cells_buf2; } void rencache_show_debug(RenCache *rc, bool enable) { rc->show_debug = enable; } void rencache_set_clip_rect(RenCache *rc, RenRect rect) { Command *cmd = push_command(rc, SET_CLIP, COMMAND_BARE_SIZE); if (cmd) { cmd->rect = intersect_rects(rect, rc->screen_rect); } } void rencache_draw_rect(RenCache *rc, RenRect rect, RenColor color) { if (!rects_overlap(rc->screen_rect, rect)) { return; } Command *cmd = push_command(rc, DRAW_RECT, COMMAND_BARE_SIZE); if (cmd) { cmd->rect = rect; cmd->color = color; } } void rencache_draw_image(RenCache *rc, int image_id, int x, int y, RenRect image_rect) { RenRect rect = {x, y, image_rect.width, image_rect.height}; if (!rects_overlap(rc->screen_rect, rect)) { return; } Command *cmd = push_command(rc, DRAW_IMAGE, COMMAND_BARE_SIZE + 2 * sizeof(int)); int *img_coord = (int *) cmd->text; if (cmd) { cmd->rect = rect; cmd->image_id = image_id; img_coord[0] = image_rect.x; img_coord[1] = image_rect.y; } } int rencache_draw_text(RenCache *rc, lua_State *L, FontDesc *font_desc, int font_index, const char *text, int x, int y, RenColor color, bool draw_subpixel, CPReplaceTable *replacements, RenColor replace_color) { int subpixel_scale; int w_subpixel = ren_get_font_width(rc->ren_surface, font_desc, text, &subpixel_scale); RenRect rect; rect.x = (draw_subpixel ? ren_font_subpixel_round(x, subpixel_scale, -1) : x); rect.y = y; rect.width = ren_font_subpixel_round(w_subpixel, subpixel_scale, 0); rect.height = ren_get_font_height(rc->ren_surface, font_desc); if (rects_overlap(rc->screen_rect, rect) && font_refs_add(L, font_desc, font_index) >= 0) { int sz = strlen(text) + 1; Command *cmd = push_command(rc, draw_subpixel ? DRAW_TEXT_SUBPIXEL : DRAW_TEXT, COMMAND_BARE_SIZE + sz); if (cmd) { memcpy(cmd->text, text, sz); cmd->color = color; cmd->font_desc = font_desc; cmd->rect = rect; cmd->subpixel_scale = (draw_subpixel ? subpixel_scale : 1); cmd->x_subpixel_offset = x - subpixel_scale * rect.x; cmd->tab_size = font_desc_get_tab_size(font_desc); cmd->replacements = replacements; cmd->replace_color = replace_color; } } return x + (draw_subpixel ? w_subpixel : rect.width); } void rencache_invalidate(RenCache *rc) { memset(rc->cells_prev, 0xff, sizeof(rc->cells_buf1)); } void rencache_begin_frame(RenCache *rc, RenSurface *ren, lua_State *L) { /* reset all cells if the screen width/height has changed */ int w, h; ren_get_size(ren, &w, &h); if (rc->screen_rect.width != w || h != rc->screen_rect.height) { rc->screen_rect.width = w; rc->screen_rect.height = h; rencache_invalidate(rc); } rc->ren_surface = ren; font_refs_clear(L); } static void update_overlapping_cells(RenCache *rc, RenRect r, unsigned h) { int x1 = r.x / CELL_SIZE; int y1 = r.y / CELL_SIZE; int x2 = (r.x + r.width) / CELL_SIZE; int y2 = (r.y + r.height) / CELL_SIZE; for (int y = y1; y <= y2; y++) { for (int x = x1; x <= x2; x++) { int idx = cell_idx(x, y); hash(&rc->cells[idx], &h, sizeof(h)); } } } static void push_rect(RenCache *rc, RenRect r, int *count) { /* try to merge with existing rectangle */ for (int i = *count - 1; i >= 0; i--) { RenRect *rp = &rc->rect_buf[i]; if (rects_overlap(*rp, r)) { *rp = merge_rects(*rp, r); return; } } /* couldn't merge with previous rectangle: push */ rc->rect_buf[(*count)++] = r; } void rencache_end_frame(RenCache *rc, lua_State *L) { /* update cells from commands */ Command *cmd = NULL; RenRect cr = rc->screen_rect; while (next_command(rc, &cmd)) { if (cmd->type == SET_CLIP) { cr = cmd->rect; } RenRect r = intersect_rects(cmd->rect, cr); if (r.width == 0 || r.height == 0) { continue; } unsigned h = HASH_INITIAL; hash(&h, cmd, cmd->size); update_overlapping_cells(rc, r, h); } /* push rects for all cells changed from last frame, reset cells */ int rect_count = 0; int max_x = rc->screen_rect.width / CELL_SIZE + 1; int max_y = rc->screen_rect.height / CELL_SIZE + 1; for (int y = 0; y < max_y; y++) { for (int x = 0; x < max_x; x++) { /* compare previous and current cell for change */ int idx = cell_idx(x, y); if (rc->cells[idx] != rc->cells_prev[idx]) { push_rect(rc, (RenRect) { x, y, 1, 1 }, &rect_count); } rc->cells_prev[idx] = HASH_INITIAL; } } /* expand rects from cells to pixels */ for (int i = 0; i < rect_count; i++) { RenRect *r = &rc->rect_buf[i]; r->x *= CELL_SIZE; r->y *= CELL_SIZE; r->width *= CELL_SIZE; r->height *= CELL_SIZE; *r = intersect_rects(*r, rc->screen_rect); } /* redraw updated regions */ for (int i = 0; i < rect_count; i++) { const int *image_coord; /* draw */ RenRect r = rc->rect_buf[i]; ren_set_clip_rect(rc->ren_surface, r); cmd = NULL; while (next_command(rc, &cmd)) { switch (cmd->type) { case SET_CLIP: ren_set_clip_rect(rc->ren_surface, intersect_rects(cmd->rect, r)); break; case DRAW_RECT: ren_draw_rect(rc->ren_surface, cmd->rect, cmd->color); break; case DRAW_TEXT: font_desc_set_tab_size(cmd->font_desc, cmd->tab_size); ren_draw_text(rc->ren_surface, cmd->font_desc, cmd->text, cmd->rect.x, cmd->rect.y, cmd->color, cmd->replacements, cmd->replace_color); break; case DRAW_TEXT_SUBPIXEL: font_desc_set_tab_size(cmd->font_desc, cmd->tab_size); ren_draw_text_subpixel(rc->ren_surface, cmd->font_desc, cmd->text, cmd->subpixel_scale * cmd->rect.x + cmd->x_subpixel_offset, cmd->rect.y, cmd->color, cmd->replacements, cmd->replace_color); break; case DRAW_IMAGE: image_coord = (const int *) cmd->text; ren_draw_image(rc->ren_surface, cmd->image_id, image_coord[0], image_coord[1], cmd->rect); break; } } if (rc->show_debug) { RenColor color = { rand(), rand(), rand(), 50 }; ren_draw_rect(rc->ren_surface, r, color); } } /* update dirty rects */ if (rect_count > 0) { ren_update_rects(rc->ren_surface, rc->rect_buf, rect_count); } /* swap cell buffer and reset */ unsigned *tmp = rc->cells; rc->cells = rc->cells_prev; rc->cells_prev = tmp; rc->command_buf_idx = 0; rc->ren_surface = NULL; }