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