Customized fork of github.com/rxi/lite
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}