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 | |
34 | enum CommandType { SET_CLIP, DRAW_TEXT, DRAW_RECT }; |
35 | |
36 | typedef 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 | |
44 | typedef struct { |
45 | RenRect rect; |
46 | } SetClipCommand; |
47 | |
48 | typedef 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 | |
58 | typedef struct { |
59 | RenRect rect; |
60 | RenColor color; |
61 | } DrawRectCommand; |
62 | |
63 | static unsigned cells_buf1[CELLS_X * CELLS_Y]; |
64 | static unsigned cells_buf2[CELLS_X * CELLS_Y]; |
65 | static unsigned *cells_prev = cells_buf1; |
66 | static unsigned *cells = cells_buf2; |
67 | static RenRect rect_buf[CELLS_X * CELLS_Y / 2]; |
68 | size_t command_buf_size = 0; |
69 | uint8_t *command_buf = NULL; |
70 | static bool resize_issue; |
71 | static int command_buf_idx; |
72 | static RenRect screen_rect; |
73 | static RenRect last_clip_rect; |
74 | static bool show_debug; |
75 | |
76 | static inline int rencache_min(int a, int b) { return a < b ? a : b; } |
77 | static 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 | |
83 | static 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 | |
91 | static inline int cell_idx(int x, int y) { |
92 | return x + y * CELLS_X; |
93 | } |
94 | |
95 | |
96 | static 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 | |
102 | static 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 | |
111 | static 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 | |
119 | static 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 | |
133 | static 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 | |
160 | static 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 | |
170 | void rencache_show_debug(bool enable) { |
171 | show_debug = enable; |
172 | } |
173 | |
174 | |
175 | void 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 | |
184 | void 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 | |
195 | double 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 | |
216 | void rencache_invalidate(void) { |
217 | memset(cells_prev, 0xff, sizeof(cells_buf1)); |
218 | } |
219 | |
220 | |
221 | void 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 | |
235 | static 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 | |
250 | static 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 | |
264 | void 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 | |