Customized fork of github.com/rxi/lite
at main 7.2 kB view raw
1#include <stdio.h> 2#include "rencache.h" 3 4/* a cache over the software renderer -- all drawing operations are stored as 5** commands when issued. At the end of the frame we write the commands to a grid 6** of hash values, take the cells that have changed since the previous frame, 7** merge them into dirty rectangles and redraw only those regions */ 8 9#define CELLS_X 80 10#define CELLS_Y 50 11#define CELL_SIZE 96 12#define COMMAND_BUF_SIZE (1024 * 512) 13 14enum 15{ 16 FREE_FONT, 17 SET_CLIP, 18 DRAW_TEXT, 19 DRAW_RECT 20}; 21 22typedef struct 23{ 24 int type, size; 25 RenRect rect; 26 RenColor color; 27 RenFont *font; 28 int tab_width; 29 char text[0]; 30} Command; 31 32 33static unsigned cells_buf1[CELLS_X * CELLS_Y]; 34static unsigned cells_buf2[CELLS_X * CELLS_Y]; 35static unsigned *cells_prev = cells_buf1; 36static unsigned *cells = cells_buf2; 37static RenRect rect_buf[CELLS_X * CELLS_Y / 2]; 38static char command_buf[COMMAND_BUF_SIZE]; 39static int command_buf_idx; 40static RenRect screen_rect; 41static bool show_debug; 42 43 44static inline int min(int a, int b) { return a < b ? a : b; } 45static inline int max(int a, int b) { return a > b ? a : b; } 46 47/* 32bit fnv-1a hash */ 48#define HASH_INITIAL 2166136261 49 50static void hash(unsigned *h, const void *data, int size) 51{ 52 const unsigned char *p = data; 53 while (size--) 54 { 55 *h = (*h ^ *p++) * 16777619; 56 } 57} 58 59 60static inline int cell_idx(int x, int y) 61{ 62 return x + y * CELLS_X; 63} 64 65 66static inline bool rects_overlap(RenRect a, RenRect b) 67{ 68 return b.x + b.width >= a.x && 69 b.x <= a.x + a.width && 70 b.y + b.height >= a.y && 71 b.y <= a.y + a.height; 72} 73 74 75static RenRect intersect_rects(RenRect a, RenRect b) 76{ 77 int x1 = max(a.x, b.x); 78 int y1 = max(a.y, b.y); 79 int x2 = min(a.x + a.width, b.x + b.width); 80 int y2 = min(a.y + a.height, b.y + b.height); 81 return (RenRect){ x1, y1, max(0, x2 - x1), max(0, y2 - y1) }; 82} 83 84 85static RenRect merge_rects(RenRect a, RenRect b) 86{ 87 int x1 = min(a.x, b.x); 88 int y1 = min(a.y, b.y); 89 int x2 = max(a.x + a.width, b.x + b.width); 90 int y2 = max(a.y + a.height, b.y + b.height); 91 return (RenRect){ x1, y1, x2 - x1, y2 - y1 }; 92} 93 94 95static Command* push_command(int type, int size) 96{ 97 Command *cmd = (Command *)(command_buf + command_buf_idx); 98 int n = command_buf_idx + size; 99 if (n > COMMAND_BUF_SIZE) 100 { 101 fprintf(stderr, "Warning: (" __FILE__ "): exhausted command buffer\n"); 102 return NULL; 103 } 104 command_buf_idx = n; 105 memset(cmd, 0, sizeof(Command)); 106 cmd->type = type; 107 cmd->size = size; 108 return cmd; 109} 110 111 112static bool next_command(Command **prev) 113{ 114 if (*prev == NULL) 115 { 116 *prev = (Command *)command_buf; 117 } 118 else 119 { 120 *prev = (Command *)(((char *)*prev) + (*prev)->size); 121 } 122 123 return *prev != ((Command *)(command_buf + command_buf_idx)); 124} 125 126 127void rencache_show_debug(bool enable) 128{ 129 show_debug = enable; 130} 131 132 133void rencache_free_font(RenFont *font) 134{ 135 Command *cmd = push_command(FREE_FONT, sizeof(Command)); 136 if (cmd) 137 { 138 cmd->font = font; 139 } 140} 141 142 143void rencache_set_clip_rect(RenRect rect) 144{ 145 Command *cmd = push_command(SET_CLIP, sizeof(Command)); 146 if (cmd) 147 { 148 cmd->rect = intersect_rects(rect, screen_rect); 149 } 150} 151 152 153void rencache_draw_rect(RenRect rect, RenColor color) 154{ 155 if (!rects_overlap(screen_rect, rect)) 156 { 157 return; 158 } 159 Command *cmd = push_command(DRAW_RECT, sizeof(Command)); 160 if (cmd) 161 { 162 cmd->rect = rect; 163 cmd->color = color; 164 } 165} 166 167 168int rencache_draw_text(RenFont *font, const char *text, int x, int y, RenColor color) 169{ 170 RenRect rect; 171 rect.x = x; 172 rect.y = y; 173 rect.width = ren_get_font_width(font, text); 174 rect.height = ren_get_font_height(font); 175 176 if (rects_overlap(screen_rect, rect)) 177 { 178 int sz = strlen(text) + 1; 179 Command *cmd = push_command(DRAW_TEXT, sizeof(Command) + sz); 180 if (cmd) 181 { 182 memcpy(cmd->text, text, sz); 183 cmd->color = color; 184 cmd->font = font; 185 cmd->rect = rect; 186 cmd->tab_width = ren_get_font_tab_width(font); 187 } 188 } 189 190 return x + rect.width; 191} 192 193 194void rencache_invalidate(void) 195{ 196 memset(cells_prev, 0xff, sizeof(cells_buf1)); 197} 198 199 200void rencache_begin_frame(void) 201{ 202 /* reset all cells if the screen width/height has changed */ 203 int w, h; 204 ren_get_size(&w, &h); 205 if (screen_rect.width != w || h != screen_rect.height) 206 { 207 screen_rect.width = w; 208 screen_rect.height = h; 209 rencache_invalidate(); 210 } 211} 212 213 214static void update_overlapping_cells(RenRect r, unsigned h) 215{ 216 int x1 = r.x / CELL_SIZE; 217 int y1 = r.y / CELL_SIZE; 218 int x2 = (r.x + r.width) / CELL_SIZE; 219 int y2 = (r.y + r.height) / CELL_SIZE; 220 221 for (int y = y1 ; y <= y2 ; y++) 222 { 223 for (int x = x1 ; x <= x2 ; x++) 224 { 225 int idx = cell_idx(x, y); 226 hash(&cells[idx], &h, sizeof(h)); 227 } 228 } 229} 230 231 232static void push_rect(RenRect r, int *count) 233{ 234 /* try to merge with existing rectangle */ 235 for (int i = *count - 1 ; i >= 0 ; i--) 236 { 237 RenRect *rp = &rect_buf[i]; 238 if (rects_overlap(*rp, r)) 239 { 240 *rp = merge_rects(*rp, r); 241 return; 242 } 243 } 244 /* couldn't merge with previous rectangle: push */ 245 rect_buf[(*count)++] = r; 246} 247 248 249void rencache_end_frame(void) 250{ 251 /* update cells from commands */ 252 Command *cmd = NULL; 253 RenRect cr = screen_rect; 254 while (next_command(&cmd)) 255 { 256 if (cmd->type == SET_CLIP) 257 { 258 cr = cmd->rect; 259 } 260 RenRect r = intersect_rects(cmd->rect, cr); 261 if (r.width == 0 || r.height == 0) 262 { 263 continue; 264 } 265 unsigned h = HASH_INITIAL; 266 hash(&h, cmd, cmd->size); 267 update_overlapping_cells(r, h); 268 } 269 270 /* push rects for all cells changed from last frame, reset cells */ 271 int rect_count = 0; 272 int max_x = screen_rect.width / CELL_SIZE + 1; 273 int max_y = screen_rect.height / CELL_SIZE + 1; 274 for (int y = 0 ; y < max_y; y++) 275 { 276 for (int x = 0 ; x < max_x ; x++) 277 { 278 /* compare previous and current cell for change */ 279 int idx = cell_idx(x, y); 280 if (cells[idx] != cells_prev[idx]) 281 { 282 push_rect((RenRect) { x, y, 1, 1 }, &rect_count); 283 } 284 cells_prev[idx] = HASH_INITIAL; 285 } 286 } 287 288 /* expand rects from cells to pixels */ 289 for (int i = 0; i < rect_count; i++) 290 { 291 RenRect *r = &rect_buf[i]; 292 r->x *= CELL_SIZE; 293 r->y *= CELL_SIZE; 294 r->width *= CELL_SIZE; 295 r->height *= CELL_SIZE; 296 *r = intersect_rects(*r, screen_rect); 297 } 298 299 /* redraw updated regions */ 300 bool has_free_commands = false; 301 for (int i = 0; i < rect_count; i++) 302 { 303 /* draw */ 304 RenRect r = rect_buf[i]; 305 ren_set_clip_rect(r); 306 307 cmd = NULL; 308 while (next_command(&cmd)) 309 { 310 switch (cmd->type) 311 { 312 case FREE_FONT: 313 has_free_commands = true; 314 break; 315 case SET_CLIP: 316 ren_set_clip_rect(intersect_rects(cmd->rect, r)); 317 break; 318 case DRAW_RECT: 319 ren_draw_rect(cmd->rect, cmd->color); 320 break; 321 case DRAW_TEXT: 322 ren_set_font_tab_width(cmd->font, cmd->tab_width); 323 ren_draw_text(cmd->font, cmd->text, cmd->rect.x, cmd->rect.y, cmd->color); 324 break; 325 } 326 } 327 328 if (show_debug) 329 { 330 RenColor color = { rand(), rand(), rand(), 50 }; 331 ren_draw_rect(r, color); 332 } 333 } 334 335 /* update dirty rects */ 336 if (rect_count > 0) 337 { 338 ren_update_rects(rect_buf, rect_count); 339 } 340 341 /* free fonts */ 342 if (has_free_commands) 343 { 344 cmd = NULL; 345 while (next_command(&cmd)) 346 { 347 if (cmd->type == FREE_FONT) 348 { 349 ren_free_font(cmd->font); 350 } 351 } 352 } 353 354 /* swap cell buffer and reset */ 355 unsigned *tmp = cells; 356 cells = cells_prev; 357 cells_prev = tmp; 358 command_buf_idx = 0; 359}