diff options
author | Bad Diode <bd@badd10de.dev> | 2023-04-15 19:27:50 +0200 |
---|---|---|
committer | Bad Diode <bd@badd10de.dev> | 2023-04-15 19:27:50 +0200 |
commit | 87e5c991bf866db9253915f403bdb8e30f9d6449 (patch) | |
tree | 82dc49354723d133f242ac7a1c5406ba1d1a5a47 | |
parent | b22d1bb0081a93f43856077917b8b029a8e97ec9 (diff) | |
download | gba-renderers-87e5c991bf866db9253915f403bdb8e30f9d6449.tar.gz gba-renderers-87e5c991bf866db9253915f403bdb8e30f9d6449.zip |
Add initial double buffering mode0 renderer
-rw-r--r-- | src/main.c | 24 | ||||
-rw-r--r-- | src/renderer_m0.c | 687 |
2 files changed, 703 insertions, 8 deletions
@@ -11,7 +11,7 @@ WITH REGARD TO THIS SOFTWARE. | |||
11 | 11 | ||
12 | #include "gba/gba.h" | 12 | #include "gba/gba.h" |
13 | 13 | ||
14 | #include "renderer.c" | 14 | #include "renderer_m0.c" |
15 | 15 | ||
16 | // | 16 | // |
17 | // Config parameters. | 17 | // Config parameters. |
@@ -121,6 +121,14 @@ int main(void) { | |||
121 | irs_set(IRQ_VBLANK, irs_stub); | 121 | irs_set(IRQ_VBLANK, irs_stub); |
122 | 122 | ||
123 | // Initialize sequencer. | 123 | // Initialize sequencer. |
124 | // flip_buffer(); | ||
125 | // screen_fill(1); | ||
126 | // screen_fill(2); | ||
127 | // backbuf[0] = 0x11111111; | ||
128 | // screen_fill(1); | ||
129 | // flip_buffer(); | ||
130 | // screen_fill(2); | ||
131 | // flip_buffer(); | ||
124 | 132 | ||
125 | // Main loop. | 133 | // Main loop. |
126 | PROF_INIT(); | 134 | PROF_INIT(); |
@@ -128,13 +136,13 @@ int main(void) { | |||
128 | bios_vblank_wait(); | 136 | bios_vblank_wait(); |
129 | 137 | ||
130 | PROF(test_clear(), test_clear_cycles); | 138 | PROF(test_clear(), test_clear_cycles); |
131 | PROF(test_lines(), test_lines_cycles); | 139 | // PROF(test_lines(), test_lines_cycles); |
132 | PROF(test_rect(), test_rect_cycles); | 140 | // PROF(test_rect(), test_rect_cycles); |
133 | PROF(test_fill_rect(), test_fill_rect_cycles); | 141 | // PROF(test_fill_rect(), test_fill_rect_cycles); |
134 | PROF(test_chr(), test_chr_cycles); | 142 | // PROF(test_chr(), test_chr_cycles); |
135 | PROF(test_icn(), test_icn_cycles); | 143 | // PROF(test_icn(), test_icn_cycles); |
136 | draw_filled_rect(0, 0, 140, 60, 0); | 144 | // draw_filled_rect(0, 0, 140, 60, 0); |
137 | PROF_SHOW(); | 145 | // PROF_SHOW(); |
138 | PROF(flip_buffer(), flip_cycles); | 146 | PROF(flip_buffer(), flip_cycles); |
139 | } | 147 | } |
140 | 148 | ||
diff --git a/src/renderer_m0.c b/src/renderer_m0.c new file mode 100644 index 0000000..3034d40 --- /dev/null +++ b/src/renderer_m0.c | |||
@@ -0,0 +1,687 @@ | |||
1 | // | ||
2 | // This Mode 0 renderer provides a way of drawing directly to a framebuffer | ||
3 | // (similar to Mode 3 and 4) while retaining the flexibility of using other | ||
4 | // backgrounds if needed. It also performs double buffering to avoid tearing | ||
5 | // artifacts and tries to only draw tiles that changed on each frame. | ||
6 | // | ||
7 | // In addition to the frontbuffer (displayed on background 0), a tiled text | ||
8 | // layer is displayed on background 1, which can be used for application | ||
9 | // development or for debug information. | ||
10 | // | ||
11 | // These two layers occupy the first and second background charblocks, leaving | ||
12 | // the remaining two available for other background layers. There are 14KB of | ||
13 | // sprite memory available, since the backbuffer is located at the end of the | ||
14 | // VRAM, but if more space is needed it can be moved to the end of the BG | ||
15 | // charblocks instead. | ||
16 | // | ||
17 | |||
18 | #include "renderer.h" | ||
19 | #include "text.h" | ||
20 | |||
21 | // Front/back buffers for double buffering. | ||
22 | #define BUF_0 ((u32*)(MEM_VRAM)) | ||
23 | #define BUF_1 ((u32*)(MEM_VRAM + KB(20))) | ||
24 | // Position of the tilemap. | ||
25 | #define TILE_MAP ((u32*)(MEM_VRAM + KB(40))) | ||
26 | |||
27 | // Charblock and screenblock for both render buffers. | ||
28 | #define CB_0 0 | ||
29 | #define CB_1 1 | ||
30 | #define SB_0 20 | ||
31 | #define SB_1 21 | ||
32 | |||
33 | // Available storage for other memory. | ||
34 | // #define FG_PIXELS ((u32*)(MEM_VRAM + KB(44))) | ||
35 | // #define BG_PIXELS ((u32*)(MEM_VRAM + KB(64))) | ||
36 | |||
37 | // // The frontbuffs,eer is located at the beginning of the VRAM, and requires 20KB of | ||
38 | // // video memory for 32 * 20 tiles at 4bpp. | ||
39 | // #define FRONTBUF ((u32*)(MEM_VRAM)) | ||
40 | |||
41 | // // Adjust both of these if the location of the map changes. Each screnblock | ||
42 | // // requires less than 2KB. | ||
43 | // #define FRONTBUF_TILEMAP ((u16*)(MEM_VRAM + KB(20))) | ||
44 | // #define FRONTBUF_SB 10 | ||
45 | |||
46 | // The backbuffer is located at the end of the VRAM. This can allow us to use | ||
47 | // more backgrounds but eats into the available memory for sprites. This should | ||
48 | // be fine for non sprite intensive applications. If more sprite memory is | ||
49 | // needed, the backbuffer can be located at the end of the background memory | ||
50 | // instead (64KB - 20KB). | ||
51 | // #define BACKBUF ((u32*)(MEM_VRAM + KB(96) - KB(20))) | ||
52 | |||
53 | // Keep track of which tiles need to be copied to the frontbuffer. | ||
54 | static u32 dirty_tiles[21] = {0}; | ||
55 | static u32 *backbuf = BUF_1; | ||
56 | |||
57 | // Boundchecks can be disable at compile time but this will not always improve | ||
58 | // the performance and can in fact make it worse. It is possible that this is | ||
59 | // due to some aliasing optimiztions but not sure at this moment. | ||
60 | #ifdef DISABLE_BOUNDCHECK_SCREEN | ||
61 | #define BOUNDCHECK_SCREEN(X,Y) | ||
62 | #else | ||
63 | #define BOUNDCHECK_SCREEN(X,Y) if ((X) >= SCREEN_WIDTH || (Y) >= SCREEN_HEIGHT) return; | ||
64 | #endif | ||
65 | |||
66 | // Swap A and B values without a tmp variable. | ||
67 | #define SWAP(A, B) (((A) ^= (B)), ((B) ^= (A)), ((A) ^= (B))) | ||
68 | |||
69 | // Swap A and B values to make sure A <= B. | ||
70 | #define MAYBE_SWAP(A,B) if ((A) > (B)) { SWAP(A,B); } | ||
71 | |||
72 | IWRAM_CODE | ||
73 | void screen_fill(u8 clr) { | ||
74 | #if 0 | ||
75 | // u32 *dst = backbuf; | ||
76 | // for(int i = 0; i < KB(75) / 8; i++) { | ||
77 | // *dst++ = 0x01010101 * clr; | ||
78 | // } | ||
79 | #else | ||
80 | dma_fill(backbuf, 0x11111111 * clr, KB(20), 3); | ||
81 | #endif | ||
82 | } | ||
83 | |||
84 | IWRAM_CODE | ||
85 | void | ||
86 | draw_pixel(size_t x, size_t y, u8 clr) { | ||
87 | BOUNDCHECK_SCREEN(x, y); | ||
88 | |||
89 | // Find row position for the given x/y coordinates. | ||
90 | size_t tile_x = x / 8; | ||
91 | size_t tile_y = y / 8; | ||
92 | size_t start_col = x % 8; | ||
93 | size_t start_row = y % 8; | ||
94 | size_t pos = start_row + (tile_x + tile_y * 32) * 8; | ||
95 | |||
96 | // Update backbuffer. | ||
97 | size_t shift = start_col * sizeof(u32); | ||
98 | backbuf[pos] = (backbuf[pos] & ~(0xF << shift)) | clr << shift; | ||
99 | |||
100 | // Mark tile as dirty. | ||
101 | dirty_tiles[tile_y] |= 1 << tile_x; | ||
102 | } | ||
103 | |||
104 | IWRAM_CODE | ||
105 | static inline | ||
106 | void | ||
107 | draw_hline(size_t x0, size_t x1, size_t y0, u8 clr) { | ||
108 | // TODO | ||
109 | } | ||
110 | |||
111 | IWRAM_CODE | ||
112 | UNROLL_LOOPS | ||
113 | static inline | ||
114 | void | ||
115 | draw_vline(size_t x0, size_t y0, size_t y1, u8 clr) { | ||
116 | // TODO | ||
117 | } | ||
118 | |||
119 | IWRAM_CODE | ||
120 | void | ||
121 | draw_line(size_t x0, size_t y0, size_t x1, size_t y1, u8 clr) { | ||
122 | // BOUNDCHECK_SCREEN(x0, y0); | ||
123 | // BOUNDCHECK_SCREEN(x1, y1); | ||
124 | if (y0 == y1) { | ||
125 | MAYBE_SWAP(x0, x1); | ||
126 | draw_hline(x0, x1, y0, clr); | ||
127 | } else if (x0 == x1) { | ||
128 | MAYBE_SWAP(y0, y1); | ||
129 | draw_vline(x0, y0, y1, clr); | ||
130 | } else { | ||
131 | // // Diagonal line. | ||
132 | // int dx = x0 > x1 ? x0 - x1 : x1 - x0; | ||
133 | // int dy = y0 > y1 ? y0 - y1 : y1 - y0; | ||
134 | // int x_step = x0 > x1 ? -1 : 1; | ||
135 | // int y_step = y0 > y1 ? -SCREEN_WIDTH : SCREEN_WIDTH; | ||
136 | |||
137 | // u16 *dst = NULL; | ||
138 | // uintptr_t addr = ((uintptr_t)backbuf + y0 * SCREEN_WIDTH + x0); | ||
139 | // u32 mask = x0 & 1 ? ~0xFF : 0xFF; | ||
140 | // u32 color = (clr & 0xFF) | ((clr & 0xFF) << 8); | ||
141 | // if (dx >= dy) { | ||
142 | // int diff = 2 * dy - dx; | ||
143 | // for (int i = 0; i < dx + 1; i++) { | ||
144 | // dst = (u16 *)(addr - (mask >> 31)); | ||
145 | // *dst = (*dst & ~mask) | (color & mask); | ||
146 | // if (diff >= 0) { | ||
147 | // diff -= 2 * dx; | ||
148 | // addr += y_step; | ||
149 | // } | ||
150 | // diff += 2 * dy; | ||
151 | // addr += x_step; | ||
152 | // mask = ~mask; | ||
153 | // } | ||
154 | // } else { | ||
155 | // int diff = 2 * dx - dy; | ||
156 | // for (int i = 0; i < dy + 1; i++) { | ||
157 | // dst = (u16 *)(addr - (mask >> 31)); | ||
158 | // *dst = (*dst & ~mask) | (color & mask); | ||
159 | // if (diff >= 0) { | ||
160 | // diff -= 2 * dy; | ||
161 | // addr += x_step; | ||
162 | // mask = ~mask; | ||
163 | // } | ||
164 | // diff += 2 * dx; | ||
165 | // addr += y_step; | ||
166 | // } | ||
167 | // } | ||
168 | } | ||
169 | |||
170 | // // Find row positions for the given x/y coordinates. | ||
171 | // size_t tile_x0 = x0 / 8; | ||
172 | // size_t tile_y0 = y0 / 8; | ||
173 | // size_t tile_x1 = x1 / 8; | ||
174 | // size_t tile_y1 = y1 / 8; | ||
175 | // size_t start_col0 = x0 % 8; | ||
176 | // size_t start_col1 = x1 % 8; | ||
177 | // size_t start_row0 = y0 % 8; | ||
178 | // size_t start_row1 = y1 % 8; | ||
179 | |||
180 | // // Get a pointer to the backbuffer and the tile row. | ||
181 | // u32 *backbuffer = &BACKBUF[start_row0 + (tile_x0 + tile_y0 * 32) * 8]; | ||
182 | |||
183 | // if (y0 == y1) { | ||
184 | // // Horizontal line. There are 3 cases: | ||
185 | // // 1. Lines fit on a single tile. | ||
186 | // // 2. Lines go through 2 tiles, both require partial row updates. | ||
187 | // // 3. Lines go through 3 or more tiles, first and last tiles use | ||
188 | // // partial row updates, rows in the middle can write the. | ||
189 | // size_t dx = tile_x1 - tile_x0; | ||
190 | // if (dx < 1) { | ||
191 | // u32 row_mask = 0xFFFFFFFF; | ||
192 | // row_mask >>= (7 - start_col1 - dx) * 4; | ||
193 | // row_mask &= 0xFFFFFFFF << start_col0 * 4; | ||
194 | // u32 row = (0x11111111 * clr) & row_mask; | ||
195 | // backbuffer[0] = (backbuffer[0] & ~row_mask) | row; | ||
196 | // dirty_tiles[tile_y0] |= 1 << tile_x0; | ||
197 | // } else { | ||
198 | // size_t shift_left = start_col0 * 4; | ||
199 | // size_t shift_right = (7 - start_col1) * 4; | ||
200 | // u32 row_mask = 0xFFFFFFFF; | ||
201 | // u32 row = 0x11111111 * clr; | ||
202 | // backbuffer[0] = backbuffer[0] & ~(row_mask << shift_left); | ||
203 | // backbuffer[0] |= row << shift_left; | ||
204 | // dirty_tiles[tile_y0] |= 1 << tile_x0; | ||
205 | // for (size_t i = 1; i < dx; i++) { | ||
206 | // backbuffer[i * 8] = row; | ||
207 | // dirty_tiles[tile_y0] |= 1 << (tile_x0 + i); | ||
208 | // } | ||
209 | // backbuffer[dx * 8] = backbuffer[dx * 8] & ~(row_mask >> shift_right); | ||
210 | // backbuffer[dx * 8] |= row >> shift_right; | ||
211 | // dirty_tiles[tile_y0] |= 1 << (tile_x0 + dx); | ||
212 | // } | ||
213 | // } else if (x0 == x1) { | ||
214 | // // Vertical line. The cases are analogous to the horizontal ones. | ||
215 | // size_t dy = tile_y1 - tile_y0; | ||
216 | // u32 row_mask = 0xF << start_col0 * 4; | ||
217 | // u32 row_left = (0x11111111 * clr) & row_mask; | ||
218 | // if (dy < 1) { | ||
219 | // for (size_t i = 0; i <= y1 - y0; i++, backbuffer++) { | ||
220 | // backbuffer[0] = (backbuffer[0] & ~row_mask) | row_left; | ||
221 | // } | ||
222 | // } else { | ||
223 | // for (size_t i = 0; i < (8 - start_row0); i++, backbuffer++) { | ||
224 | // backbuffer[0] = (backbuffer[0] & ~row_mask) | row_left; | ||
225 | // } | ||
226 | // dirty_tiles[tile_y0] |= 1 << tile_x0; | ||
227 | // backbuffer += 8 * 31; | ||
228 | // for (size_t j = 1; j < dy; j++) { | ||
229 | // for (size_t i = 0; i < 8; i++, backbuffer++) { | ||
230 | // backbuffer[0] = (backbuffer[0] & ~row_mask) | row_left; | ||
231 | // } | ||
232 | // backbuffer += 8 * 31; | ||
233 | // dirty_tiles[tile_y0 + j] |= 1 << tile_x0; | ||
234 | // } | ||
235 | // for (size_t i = 0; i <= start_row1; i++, backbuffer++) { | ||
236 | // backbuffer[0] = (backbuffer[0] & ~row_mask) | row_left; | ||
237 | // } | ||
238 | // dirty_tiles[tile_y1] |= 1 << tile_x0; | ||
239 | // } | ||
240 | // } else { | ||
241 | // // Diagonal line. | ||
242 | // int dx = x0 > x1 ? x0 - x1 : x1 - x0; | ||
243 | // int dy = y0 > y1 ? y1 - y0 : y0 - y1; | ||
244 | // int x_step = x0 < x1 ? 1 : -1; | ||
245 | // int y_step = y0 < y1 ? 1 : -1; | ||
246 | // int err = dx + dy; | ||
247 | // while (!(x0 == x1 && y0 == y1)) { | ||
248 | // draw_pixel(x0, y0, clr); | ||
249 | // int diff = 2 * err; | ||
250 | // if (diff >= dy) { | ||
251 | // err += dy; | ||
252 | // x0 += x_step; | ||
253 | // } | ||
254 | // if (diff <= dx) { | ||
255 | // err += dx; | ||
256 | // y0 += y_step; | ||
257 | // } | ||
258 | // } | ||
259 | // } | ||
260 | } | ||
261 | |||
262 | IWRAM_CODE | ||
263 | void | ||
264 | draw_rect(size_t x0, size_t y0, size_t x1, size_t y1, u8 clr) { | ||
265 | BOUNDCHECK_SCREEN(x0, y0); | ||
266 | BOUNDCHECK_SCREEN(x1, y1); | ||
267 | MAYBE_SWAP(x0, x1); | ||
268 | MAYBE_SWAP(y0, y1); | ||
269 | |||
270 | draw_hline(x0, x1, y0, clr); | ||
271 | draw_hline(x0, x1, y1, clr); | ||
272 | draw_vline(x0, y0, y1, clr); | ||
273 | draw_vline(x1, y0, y1, clr); | ||
274 | |||
275 | // TODO: check if this is better. | ||
276 | // BOUNDCHECK_SCREEN(x0, y0); | ||
277 | // BOUNDCHECK_SCREEN(x1, y1); | ||
278 | |||
279 | // // Find row positions for the given x/y coordinates. | ||
280 | // size_t tile_x0 = x0 / 8; | ||
281 | // size_t tile_y0 = y0 / 8; | ||
282 | // size_t tile_x1 = x1 / 8; | ||
283 | // size_t tile_y1 = y1 / 8; | ||
284 | // size_t start_col0 = x0 % 8; | ||
285 | // size_t start_col1 = x1 % 8; | ||
286 | // size_t start_row0 = y0 % 8; | ||
287 | // size_t start_row1 = y1 % 8; | ||
288 | |||
289 | // // Get a pointer to the backbuffer and the tile row. | ||
290 | // u32 *buf_top = &BACKBUF[start_row0 + (tile_x0 + tile_y0 * 32) * 8]; | ||
291 | // u32 *buf_bot = &BACKBUF[start_row1 + (tile_x0 + tile_y1 * 32) * 8]; | ||
292 | |||
293 | // size_t dx = tile_x1 - tile_x0; | ||
294 | // size_t dy = tile_y1 - tile_y0; | ||
295 | |||
296 | // memset(buf_top, 3, x1 - x0); | ||
297 | // memset(buf_bot, 3, x1 - x0); | ||
298 | |||
299 | // if (dx < 1) { | ||
300 | // dirty_tiles[tile_y0] |= 1 << tile_x0; | ||
301 | // dirty_tiles[tile_y1] |= 1 << tile_x0; | ||
302 | // } else { | ||
303 | // dirty_tiles[tile_y0] |= 1 << tile_x0; | ||
304 | // dirty_tiles[tile_y1] |= 1 << tile_x0; | ||
305 | // for (size_t i = 1; i < dx; i++) { | ||
306 | // dirty_tiles[tile_y0] |= 1 << (tile_x0 + i); | ||
307 | // dirty_tiles[tile_y1] |= 1 << (tile_x0 + i); | ||
308 | // } | ||
309 | // dirty_tiles[tile_y0] |= 1 << (tile_x0 + dx); | ||
310 | // dirty_tiles[tile_y1] |= 1 << (tile_x0 + dx); | ||
311 | // } | ||
312 | // if (dy < 1) { | ||
313 | // } else { | ||
314 | // for (size_t j = 1; j < dy; j++) { | ||
315 | // dirty_tiles[tile_y0 + j] |= 1 << tile_x0; | ||
316 | // dirty_tiles[tile_y0 + j] |= 1 << (tile_x0 + dx); | ||
317 | // } | ||
318 | // } | ||
319 | } | ||
320 | |||
321 | IWRAM_CODE | ||
322 | void | ||
323 | draw_filled_rect(size_t x0, size_t y0, size_t x1, size_t y1, u8 clr) { | ||
324 | BOUNDCHECK_SCREEN(x0, y0); | ||
325 | BOUNDCHECK_SCREEN(x1, y1); | ||
326 | MAYBE_SWAP(x0, x1); | ||
327 | MAYBE_SWAP(y0, y1); | ||
328 | |||
329 | // Special condition. If the screen is to be completely filled, use the DMA | ||
330 | // instead. | ||
331 | if (x0 == 0 && x1 >= (SCREEN_WIDTH - 1) && y0 == 0 && y1 >= (SCREEN_HEIGHT - 1)) { | ||
332 | screen_fill(clr); | ||
333 | return; | ||
334 | } | ||
335 | |||
336 | // Drawline implementation. | ||
337 | for (size_t y = y0; y <= y1; y++) { | ||
338 | // NOTE: Unclear why here draw_hline is faster than draw_line. | ||
339 | draw_hline(x0, x1, y, clr); | ||
340 | } | ||
341 | |||
342 | // TODO: check if this is better. | ||
343 | // BOUNDCHECK_SCREEN(x0, y0); | ||
344 | // BOUNDCHECK_SCREEN(x1, y1); | ||
345 | |||
346 | // size_t dx = x1 - x0; | ||
347 | // size_t dy = y1 - y0; | ||
348 | // u8 *buf = &BACKBUF[0]; | ||
349 | // memset(buf, 0x11 * clr, 16); | ||
350 | //for (size_t j = 0; j < 1; j++) { | ||
351 | // // for (size_t i = 0; i < dx; i++) { | ||
352 | // // buf[i + j * 16] = clr; | ||
353 | // // } | ||
354 | // // | ||
355 | // // BACKBUF[j + 0] = 0x11111111 * clr; | ||
356 | // // BACKBUF[j + 1] = 0x11111111 * clr; | ||
357 | // // BACKBUF[j + 2] = 0x11111111 * clr; | ||
358 | // // BACKBUF[j + 3] = 0x11111111 * clr; | ||
359 | // // BACKBUF[j + 4] = 0x11111111 * clr; | ||
360 | // // BACKBUF[j + 5] = 0x11111111 * clr; | ||
361 | // // BACKBUF[j + 6] = 0x11111111 * clr; | ||
362 | // // BACKBUF[j + 7] = 0x11111111 * clr; | ||
363 | |||
364 | // buf[j + 0] = 0x1 * clr; | ||
365 | // buf[j + 1] = 0x1 * clr; | ||
366 | // buf[j + 2] = 0x1 * clr; | ||
367 | // buf[j + 3] = 0x1 * clr; | ||
368 | // // buf[j + 4] = 0x1111 * clr; | ||
369 | // // buf[j + 5] = 0x1111 * clr; | ||
370 | // // buf[j + 6] = 0x1111 * clr; | ||
371 | // // buf[j + 7] = 0x1111 * clr; | ||
372 | //} | ||
373 | // u8 *buf = &BACKBUF[0]; | ||
374 | // buf[8 * 16 + 0] = clr; | ||
375 | // buf[8 * 16 + 1] = clr; | ||
376 | // buf[8 * 16 + 2] = clr; | ||
377 | // buf[8 * 16 + 3] = clr; | ||
378 | // buf[8 * 16 + 4] = clr; | ||
379 | // buf[8 * 16 + 5] = clr; | ||
380 | // buf[8 * 16 + 6] = clr; | ||
381 | // buf[8 * 16 + 7] = clr; | ||
382 | // for (size_t j = 0; j < dy; j++) { | ||
383 | // for (size_t i = 0; i < dx; i++) { | ||
384 | // buf[i + j * 16] = clr; | ||
385 | // } | ||
386 | // } | ||
387 | // size_t n_rect = MIN(dx, dy); | ||
388 | // n_rect = n_rect / 2 + 1; | ||
389 | // for (size_t i = 0; i < n_rect; i++) { | ||
390 | // draw_rect(x0 + i, y0 + i, x1 - i, y1 - i, clr); | ||
391 | // } | ||
392 | } | ||
393 | |||
394 | // IWRAM_CODE | ||
395 | // void | ||
396 | // draw_tile(size_t x, size_t y, Tile *tile, u8 clr) { | ||
397 | // BOUNDCHECK_SCREEN(x, y); | ||
398 | |||
399 | // // Find row position for the given x/y coordinates. | ||
400 | // size_t tile_x = x / 8; | ||
401 | // size_t tile_y = y / 8; | ||
402 | // size_t start_col = x % 8; | ||
403 | // size_t start_row = y % 8; | ||
404 | |||
405 | // // Get a pointer to the backbuffer and the tile row. | ||
406 | // size_t pos = start_row + (tile_x + tile_y * 32) * 8; | ||
407 | // u32 *backbuffer = &BACKBUF[pos]; | ||
408 | // u32 *row = tile; | ||
409 | |||
410 | // // This will blend all colors weirdly if using tiles that contain colors | ||
411 | // // higher than 1. | ||
412 | // size_t shift_left = start_col * 4; | ||
413 | // size_t shift_right = (8 - start_col) * 4; | ||
414 | // // u32 row_mask_left = merge ? 0 : 0xFFFFFFFF << shift_left; | ||
415 | // // u32 row_mask_right = merge ? 0 : 0xFFFFFFFF >> shift_right; | ||
416 | |||
417 | // // Draw the tiles. There are 4 possible cases: | ||
418 | // // 1. The tile is exactly at the tile boundary. | ||
419 | // // 2. The tile spans 2 tiles horizontally. | ||
420 | // // 3. The tile spans 2 tiles vertically. | ||
421 | // // 4. The tile spans 4 tiles. | ||
422 | // if (start_col == 0 && start_row == 0) { | ||
423 | // for (size_t i = 0; i < (8 - start_row); i++, backbuffer++) { | ||
424 | // BOUNDCHECK_SCREEN(x, y + i); | ||
425 | // backbuffer[0] = (backbuffer[0] & ~row_mask_left) | row[i] * clr; | ||
426 | // } | ||
427 | // dirty_tiles[tile_y] |= 1 << tile_x; | ||
428 | // } else if (start_row == 0) { | ||
429 | // for (size_t i = 0; i < 8; i++, backbuffer++) { | ||
430 | // BOUNDCHECK_SCREEN(x, y + i); | ||
431 | // backbuffer[0] = (backbuffer[0] & ~row_mask_left) | (row[i] * clr << shift_left); | ||
432 | // backbuffer[8] = (backbuffer[8] & ~row_mask_right) | (row[i] * clr >> shift_right); | ||
433 | // } | ||
434 | // dirty_tiles[tile_y] |= 1 << tile_x; | ||
435 | // dirty_tiles[tile_y] |= 1 << (tile_x + 1); | ||
436 | // } else if (start_col == 0) { | ||
437 | // for (size_t i = 0; i < (8 - start_row); i++, backbuffer++) { | ||
438 | // BOUNDCHECK_SCREEN(x, y + i); | ||
439 | // backbuffer[0] = (backbuffer[0] & ~row_mask_left) | row[i] * clr; | ||
440 | // } | ||
441 | // backbuffer += 8 * 31; | ||
442 | // for (size_t i = (8 - start_row); i < 8; i++, backbuffer++) { | ||
443 | // BOUNDCHECK_SCREEN(x, y + i); | ||
444 | // backbuffer[0] = (backbuffer[0] & ~row_mask_left) | row[i] * clr; | ||
445 | // } | ||
446 | // dirty_tiles[tile_y] |= 1 << tile_x; | ||
447 | // dirty_tiles[tile_y + 1] |= 1 << tile_x; | ||
448 | // } else { | ||
449 | // for (size_t i = 0; i < (8 - start_row); i++, backbuffer++) { | ||
450 | // BOUNDCHECK_SCREEN(x, y + i); | ||
451 | // backbuffer[0] = (backbuffer[0] & ~row_mask_left) | (row[i] * clr << shift_left); | ||
452 | // backbuffer[8] = (backbuffer[8] & ~row_mask_right) | (row[i] * clr >> shift_right); | ||
453 | // } | ||
454 | // backbuffer += 8 * 31; | ||
455 | // for (size_t i = (8 - start_row); i < 8; i++, backbuffer++) { | ||
456 | // BOUNDCHECK_SCREEN(x, y + i); | ||
457 | // backbuffer[0] = (backbuffer[0] & ~row_mask_left) | (row[i] * clr << shift_left); | ||
458 | // backbuffer[8] = (backbuffer[8] & ~row_mask_right) | (row[i] * clr >> shift_right); | ||
459 | // } | ||
460 | // dirty_tiles[tile_y] |= 1 << tile_x; | ||
461 | // dirty_tiles[tile_y] |= 1 << (tile_x + 1); | ||
462 | // dirty_tiles[tile_y + 1] |= 1 << tile_x; | ||
463 | // dirty_tiles[tile_y + 1] |= 1 << (tile_x + 1); | ||
464 | // } | ||
465 | // } | ||
466 | |||
467 | // void | ||
468 | // clear_screen(void) { | ||
469 | // dma_fill(FRONTBUF, 0, KB(20), 3); | ||
470 | // } | ||
471 | |||
472 | IWRAM_CODE | ||
473 | void | ||
474 | flip_buffer(void) { | ||
475 | if (backbuf == BUF_0) { | ||
476 | backbuf = BUF_1; | ||
477 | DISP_CTRL = DISP_MODE_0 | DISP_BG_1 | DISP_OBJ; | ||
478 | BG_CTRL(0) = BG_CHARBLOCK(CB_0) | BG_SCREENBLOCK(SB_0) | BG_PRIORITY(2); | ||
479 | BG_CTRL(1) = BG_CHARBLOCK(CB_1) | BG_SCREENBLOCK(SB_1) | BG_PRIORITY(1); | ||
480 | } else { | ||
481 | backbuf = BUF_0; | ||
482 | DISP_CTRL = DISP_MODE_0 | DISP_BG_0 | DISP_OBJ; | ||
483 | BG_CTRL(0) = BG_CHARBLOCK(CB_0) | BG_SCREENBLOCK(SB_0) | BG_PRIORITY(1); | ||
484 | BG_CTRL(1) = BG_CHARBLOCK(CB_1) | BG_SCREENBLOCK(SB_1) | BG_PRIORITY(2); | ||
485 | } | ||
486 | // TODO: Copying all tiles for now. Study if it's better to use dirty_tiles | ||
487 | // or dirty_lines. | ||
488 | // Copy dirty tiles from the backbuffer to the frontbuffer. | ||
489 | // Tile *dst = FRONTBUF; | ||
490 | // Tile *src = BACKBUF; | ||
491 | // for (size_t j = 0; j < 20; ++j) { | ||
492 | // // if (dirty_tiles[j] == 0) { | ||
493 | // // continue; | ||
494 | // // } | ||
495 | // for (size_t i = 0, k = 1; i < 30; ++i, k <<= 1) { | ||
496 | // // if (dirty_tiles[j] & k) { | ||
497 | // dst[i + j * 32] = src[i + j * 32]; | ||
498 | // // } | ||
499 | // } | ||
500 | // // dirty_tiles[j] = 0; | ||
501 | // } | ||
502 | } | ||
503 | |||
504 | static u32 dec_nibble[] = { | ||
505 | 0x00000000, 0x01000000, 0x00010000, 0x01010000, | ||
506 | 0x00000100, 0x01000100, 0x00010100, 0x01010100, | ||
507 | 0x00000001, 0x01000001, 0x00010001, 0x01010001, | ||
508 | 0x00000101, 0x01000101, 0x00010101, 0x01010101, | ||
509 | }; | ||
510 | |||
511 | static u32 dec_nibble_flip_x[] = { | ||
512 | 0x00000000, 0x00000001, 0x00000100, 0x00000101, | ||
513 | 0x00010000, 0x00010001, 0x00010100, 0x00010101, | ||
514 | 0x01000000, 0x01000001, 0x01000100, 0x01000101, | ||
515 | 0x01010000, 0x01010001, 0x01010100, 0x01010101, | ||
516 | }; | ||
517 | |||
518 | IWRAM_CODE | ||
519 | static inline | ||
520 | u64 | ||
521 | decode_1bpp(u8 row, u8 flip_x) { | ||
522 | if (flip_x) { | ||
523 | u32 *lut = dec_nibble_flip_x; | ||
524 | return (u64)lut[(row >> 4) & 0xF] << 32 | (u64)lut[(row >> 0) & 0xF]; | ||
525 | } | ||
526 | u32 *lut = dec_nibble; | ||
527 | return (u64)lut[(row >> 0) & 0xF] << 32 | (u64)lut[(row >> 4) & 0xF]; | ||
528 | } | ||
529 | |||
530 | IWRAM_CODE | ||
531 | static inline | ||
532 | void | ||
533 | draw_2bpp_row(size_t x, size_t y, u8 a, u8 b, u8 flip_x) { | ||
534 | // BOUNDCHECK_SCREEN(x, y); | ||
535 | |||
536 | // size_t tile_x = x / 8; | ||
537 | // size_t start_col = x % 8; | ||
538 | // size_t shift_left = start_col * 8; | ||
539 | // size_t shift_right = (8 - start_col) * 8; | ||
540 | |||
541 | // u64 *dst = &backbuf[(y * 30 + tile_x) * 8 / 2]; | ||
542 | // if (start_col == 0) { | ||
543 | // u64 clr_a = decode_1bpp(a, flip_x); | ||
544 | // u64 clr_b = decode_1bpp(b, flip_x); | ||
545 | // u64 mask_a = (clr_a * 0xF); | ||
546 | // u64 mask_b = (clr_b * 0xF); | ||
547 | // u64 mask = (mask_a | mask_b); | ||
548 | // u64 color = clr_a + (clr_b << 1); | ||
549 | // dst[0] = (dst[0] & ~mask) | color; | ||
550 | // } else { | ||
551 | // u64 clr_a = decode_1bpp(a, flip_x); | ||
552 | // u64 clr_b = decode_1bpp(b, flip_x); | ||
553 | // u64 mask_a = (clr_a * 0xF); | ||
554 | // u64 mask_b = (clr_b * 0xF); | ||
555 | // u64 mask = (mask_a | mask_b); | ||
556 | // u64 color = clr_a + (clr_b << 1); | ||
557 | // dst[0] = (dst[0] & ~(mask << shift_left)) | (color << shift_left); | ||
558 | // if ((x + 7) > (SCREEN_WIDTH)) { | ||
559 | // return; | ||
560 | // } | ||
561 | // dst[1] = (dst[1] & ~(mask >> shift_right)) | (color >> shift_right); | ||
562 | // } | ||
563 | |||
564 | // TODO: different blend modes? | ||
565 | } | ||
566 | |||
567 | IWRAM_CODE | ||
568 | static inline | ||
569 | void | ||
570 | draw_1bpp_row(size_t x, size_t y, u8 a, u8 clr, u8 flip_x) { | ||
571 | // BOUNDCHECK_SCREEN(x, y); | ||
572 | |||
573 | // size_t tile_x = x / 8; | ||
574 | // size_t start_col = x % 8; | ||
575 | // size_t shift_left = start_col * 8; | ||
576 | // size_t shift_right = (8 - start_col) * 8; | ||
577 | |||
578 | // u64 *dst = &backbuf[(y * 30 + tile_x) * 8 / 2]; | ||
579 | // if (start_col == 0) { | ||
580 | // u64 color = decode_1bpp(a, flip_x); | ||
581 | // u64 mask = (color * 0xF); | ||
582 | // color *= clr; | ||
583 | // dst[0] = (dst[0] & ~mask) | color; | ||
584 | // } else { | ||
585 | // u64 color = decode_1bpp(a, flip_x); | ||
586 | // u64 mask = (color * 0xF); | ||
587 | // color *= clr; | ||
588 | // dst[0] = (dst[0] & ~(mask << shift_left)) | (color << shift_left); | ||
589 | // if ((x + 7) > (SCREEN_WIDTH)) { | ||
590 | // return; | ||
591 | // } | ||
592 | // dst[1] = (dst[1] & ~(mask >> shift_right)) | (color >> shift_right); | ||
593 | // } | ||
594 | |||
595 | // TODO: different blend modes? | ||
596 | } | ||
597 | |||
598 | IWRAM_CODE | ||
599 | void | ||
600 | draw_chr(size_t x, size_t y, u8 *sprite, u8 flip_x, u8 flip_y) { | ||
601 | // BOUNDCHECK_SCREEN(x, y); | ||
602 | // if (!flip_y) { | ||
603 | // for(size_t v = 0; v < 8; v++) { | ||
604 | // if ((y + v) >= SCREEN_HEIGHT) break; | ||
605 | // u8 ch1 = sprite[v + 0]; | ||
606 | // u8 ch2 = sprite[v + 8]; | ||
607 | // draw_2bpp_row(x, y + v, ch1, ch2, flip_x); | ||
608 | // } | ||
609 | // } else { | ||
610 | // for(size_t v = 0; v < 8; v++) { | ||
611 | // if ((y + v) >= SCREEN_HEIGHT) break; | ||
612 | // u8 ch1 = sprite[(7 - v) + 0]; | ||
613 | // u8 ch2 = sprite[(7 - v) + 8]; | ||
614 | // draw_2bpp_row(x, y + v, ch1, ch2, flip_x); | ||
615 | // } | ||
616 | // } | ||
617 | } | ||
618 | |||
619 | IWRAM_CODE | ||
620 | void | ||
621 | draw_icn(size_t x, size_t y, u8 *sprite, u8 clr, u8 flip_x, u8 flip_y) { | ||
622 | // BOUNDCHECK_SCREEN(x, y); | ||
623 | // if (!flip_y) { | ||
624 | // for(size_t v = 0; v < 8; v++) { | ||
625 | // if ((y + v) >= SCREEN_HEIGHT) break; | ||
626 | // u8 ch1 = sprite[v]; | ||
627 | // draw_1bpp_row(x, y + v, ch1, clr, flip_x); | ||
628 | // } | ||
629 | // } else { | ||
630 | // for(size_t v = 0; v < 8; v++) { | ||
631 | // if ((y + v) >= SCREEN_HEIGHT) break; | ||
632 | // u8 ch1 = sprite[(7 - v)]; | ||
633 | // draw_1bpp_row(x, y + v, ch1, clr, flip_x); | ||
634 | // } | ||
635 | // } | ||
636 | } | ||
637 | |||
638 | #include "font.h" | ||
639 | |||
640 | // Font rendering function for the text engine. | ||
641 | void | ||
642 | txt_drawc(char c, size_t x, size_t y, u8 clr) { | ||
643 | u8 *tile = font_icn; | ||
644 | draw_icn(x, y, tile + 8 * c, clr, 1, 0); | ||
645 | } | ||
646 | |||
647 | void | ||
648 | renderer_init(void) { | ||
649 | // Initialize display mode and bg palette. | ||
650 | DISP_CTRL = DISP_MODE_0 | DISP_BG_0 | DISP_BG_1 | DISP_OBJ; | ||
651 | |||
652 | // Clear VRAM. | ||
653 | dma_fill(MEM_VRAM, 0, KB(96), 3); | ||
654 | |||
655 | |||
656 | // Initialize backgrounds. | ||
657 | BG_CTRL(0) = BG_CHARBLOCK(CB_0) | BG_SCREENBLOCK(SB_0) | BG_PRIORITY(1); | ||
658 | BG_CTRL(1) = BG_CHARBLOCK(CB_1) | BG_SCREENBLOCK(SB_1) | BG_PRIORITY(2); | ||
659 | |||
660 | DISP_CTRL = DISP_MODE_0 | DISP_OBJ; | ||
661 | |||
662 | // Initialize background memory map for frontbuffer. | ||
663 | // for (size_t i = 0; i < 32 * 20; ++i) { | ||
664 | // TILE_MAP[i] = i; | ||
665 | // } | ||
666 | u16 *mem_map_fg = SCREENBLOCK_MEM[SB_0]; | ||
667 | u16 *mem_map_bg = SCREENBLOCK_MEM[SB_1]; | ||
668 | size_t k = 0; | ||
669 | for (size_t i = 0; i < 32 * 20; ++i, ++k) { | ||
670 | mem_map_fg[i] = k; | ||
671 | mem_map_bg[i] = k + 32 * 4; | ||
672 | } | ||
673 | |||
674 | // Initialize default palette. | ||
675 | PAL_BUFFER_BG[0] = COLOR_BLACK; | ||
676 | PAL_BUFFER_BG[1] = COLOR_WHITE; | ||
677 | PAL_BUFFER_BG[2] = COLOR_RED; | ||
678 | PAL_BUFFER_BG[3] = COLOR_BLUE; | ||
679 | PAL_BUFFER_BG[4] = COLOR_CYAN; | ||
680 | PAL_BUFFER_BG[5] = COLOR_PURPLE; | ||
681 | PAL_BUFFER_BG[6] = COLOR_YELLOW; | ||
682 | PAL_BUFFER_BG[7] = COLOR_GREEN; | ||
683 | PAL_BUFFER_BG[8] = COLOR_GREY; | ||
684 | |||
685 | // Initialize text engine. | ||
686 | txt_init(txt_drawc); | ||
687 | } | ||