1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
|
#include "screen.h"
/*
Each screen layer is 24KB and contains 768 tiles (32*24). Each tile is 32
bytes, and contains 64 pixels (8*8), holding 2 pixels per byte. The upper
four bits of a tile byte represent the colour of the right pixel, and the
lower four bits represent the colour of the left pixel. The colour is a
4-bit index into a 16-colour RGB15 palette.
Four screen layers are used by each physical screen: a foreground layer, a
background layer, and then back buffers for each. Screen layers are stored
as u16* pointers to raw video memory.
Each 'tile slot' of video memory is 16KB, so we need two slots per layer. We
can't use memory more efficiently by packing the layers more tightly into the
available slots, because we can only draw the contents of a layer by passing
a tile slot base address to the video system. We can, however, use the gaps to
store our tile map, because each map slot takes only 2KB with a corresponding
increase in address granularity.
The tile map is shared by all foreground and background layers on a physical
screen, and is stored just past the contents of the first screen layer (at the
24KB offset in video memory). Each entry in the map is a 16-bit tile index.
The low 10 bits of the value represent the tile index, then h-flip, then
v-flip, then a 4-bit palette identifier. We don't flip any tiles, and we
always use palette zero. The map is initialised with an incrementing sequence,
and then never touched.
The memory bank system of the NDS is byzantine and most vexing, with banks being
identified by letters A through I, and in sizes of 128/128/128/128/64/16/16/32/16
KB, respectively. The main 2D engine can use banks A/B/C/D/E for BG data (tiles),
and the sub 2D engine can use bank C for the same.
*/
// ------ NDS SCREENS ----------------------------------------------------------
// VRAM addresses for the main 2D engine.
NDSScreen main_screen = {
.bgv = BG_TILE_RAM(BG_SLOT_FRONT),
.fgv = BG_TILE_RAM(FG_SLOT_FRONT),
.bg = BG_TILE_RAM(BG_SLOT_BACK),
.fg = BG_TILE_RAM(FG_SLOT_BACK),
.map = BG_MAP_RAM(MAP_SLOT),
.palv = BG_PALETTE,
};
// VRAM addresses for the sub 2D engine.
NDSScreen sub_screen = {
.bgv = BG_TILE_RAM_SUB(BG_SLOT_FRONT),
.fgv = BG_TILE_RAM_SUB(FG_SLOT_FRONT),
.bg = BG_TILE_RAM_SUB(BG_SLOT_BACK),
.fg = BG_TILE_RAM_SUB(FG_SLOT_BACK),
.map = BG_MAP_RAM_SUB(MAP_SLOT),
.palv = BG_PALETTE_SUB,
};
// Initialise the VRAM mappings and tile map for both NDS screens.
void init_nds_screens(void) {
// Allocate VRAM for screens.
videoSetMode(DISPLAY_BG0_ACTIVE | DISPLAY_BG1_ACTIVE | MODE_0_2D);
vramSetBankA(VRAM_A_MAIN_BG);
videoSetModeSub(DISPLAY_BG0_ACTIVE | DISPLAY_BG1_ACTIVE | MODE_0_2D);
vramSetBankC(VRAM_C_SUB_BG);
// Configure screen layers to use tile graphics.
REG_BG0CNT = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_3 | BG_TILE_BASE(BG_SLOT_FRONT) | BG_MAP_BASE(MAP_SLOT);
REG_BG1CNT = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_2 | BG_TILE_BASE(FG_SLOT_FRONT) | BG_MAP_BASE(MAP_SLOT);
REG_BG0CNT_SUB = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_3 | BG_TILE_BASE(BG_SLOT_FRONT) | BG_MAP_BASE(MAP_SLOT);
REG_BG1CNT_SUB = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_2 | BG_TILE_BASE(FG_SLOT_FRONT) | BG_MAP_BASE(MAP_SLOT);
// Populate tile maps.
u16 *main_map = BG_MAP_RAM(12);
u16 *sub_map = BG_MAP_RAM_SUB(12);
for (int i=0; i<TILES_COUNT; i++) {
*(main_map++) = i;
*(sub_map++) = i;
}
}
// Copy the contents of the back buffer to the front buffer.
void ndsscreen_flip_buffers(NDSScreen *nds) {
if (nds) {
dmaCopyWords(0, nds->bg, nds->bgv, LAYER_MEM);
dmaCopyWords(0, nds->fg, nds->fgv, LAYER_MEM);
for (int i=0; i<16; i++) nds->palv[i] = nds->pal[i];
}
}
// Erase both buffers and set every colour to black.
void ndsscreen_clear(NDSScreen *nds) {
if (nds) {
dmaFillWords(0, nds->bgv, LAYER_MEM);
dmaFillWords(0, nds->fgv, LAYER_MEM);
dmaFillWords(0, nds->bg, LAYER_MEM);
dmaFillWords(0, nds->fg, LAYER_MEM);
for (int i=0; i<16; i++) nds->palv[i] = RGB15(0,0,0);
}
}
// ------ SPRITE BUFFER --------------------------------------------------------
// Reset a sprite buffer.
void spritebuf_reset(SpriteBuffer *b) {
for (int i=0; i<16; i++) b->mem[i] = 0;
b->cached = false;
}
// Push a byte to a sprite buffer.
void spritebuf_push(SpriteBuffer *b, u8 row) {
b->mem[b->p++] = row;
b->p %= 16;
b->cached = FALSE;
}
// Parse, transform, and cache a 1-bit sprite.
void spritebuf_prepare_1bit(SpriteBuffer *b, u8 draw) {
u8 l,p,x,y;
if (b->cached && draw == b->draw) return;
// Parse and transform the sprite if not already cached.
switch (draw & 0x07) {
case 0x0: p=b->p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; for (x=0;x<8;x++) { b->sprite[y][x] = l>>(7-x) & 1; } }; break;
case 0x1: p=b->p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; for (x=0;x<8;x++) { b->sprite[y][x] = l>>( x) & 1; } }; break;
case 0x2: p=b->p; for (y=0;y<8;y++) { l=b->mem[--p % 16]; for (x=0;x<8;x++) { b->sprite[y][x] = l>>(7-x) & 1; } }; break;
case 0x3: p=b->p; for (y=0;y<8;y++) { l=b->mem[--p % 16]; for (x=0;x<8;x++) { b->sprite[y][x] = l>>( x) & 1; } }; break;
case 0x4: p=b->p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; for (x=0;x<8;x++) { b->sprite[x][y] = l>>(7-x) & 1; } }; break;
case 0x5: p=b->p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; for (x=0;x<8;x++) { b->sprite[x][y] = l>>( x) & 1; } }; break;
case 0x6: p=b->p; for (y=0;y<8;y++) { l=b->mem[--p % 16]; for (x=0;x<8;x++) { b->sprite[x][y] = l>>(7-x) & 1; } }; break;
case 0x7: p=b->p; for (y=0;y<8;y++) { l=b->mem[--p % 16]; for (x=0;x<8;x++) { b->sprite[x][y] = l>>( x) & 1; } }; break;
}
b->draw = draw;
b->cached = true;
}
// Parse, transform, and cache a 2-bit sprite.
void spritebuf_prepare_2bit(SpriteBuffer *b, u8 draw) {
u8 l,h,i,p,s,x,y;
if (b->cached && draw == b->draw) return;
// Parse and transform the sprite if not already cached.
switch (draw & 0x07) {
case 0x0: p=b->p+8; s=p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; h=b->mem[s++ % 16]; for (x=0;x<8;x++) { i=(7-x); b->sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }; break;
case 0x1: p=b->p+8; s=p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; h=b->mem[s++ % 16]; for (x=0;x<8;x++) { i=( x); b->sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }; break;
case 0x2: p=b->p; s=p+8; for (y=0;y<8;y++) { l=b->mem[--p % 16]; h=b->mem[--s % 16]; for (x=0;x<8;x++) { i=(7-x); b->sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }; break;
case 0x3: p=b->p; s=p+8; for (y=0;y<8;y++) { l=b->mem[--p % 16]; h=b->mem[--s % 16]; for (x=0;x<8;x++) { i=( x); b->sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }; break;
case 0x4: p=b->p+8; s=p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; h=b->mem[s++ % 16]; for (x=0;x<8;x++) { i=(7-x); b->sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }; break;
case 0x5: p=b->p+8; s=p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; h=b->mem[s++ % 16]; for (x=0;x<8;x++) { i=( x); b->sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }; break;
case 0x6: p=b->p; s=p+8; for (y=0;y<8;y++) { l=b->mem[--p % 16]; h=b->mem[--s % 16]; for (x=0;x<8;x++) { i=(7-x); b->sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }; break;
case 0x7: p=b->p; s=p+8; for (y=0;y<8;y++) { l=b->mem[--p % 16]; h=b->mem[--s % 16]; for (x=0;x<8;x++) { i=( x); b->sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }; break;
}
b->draw = draw;
b->cached = true;
}
// ------ BEDROCK SCREEN -------------------------------------------------------
// Reset a screen device.
void screen_reset(ScreenDevice *screen) {
screen->x = 0;
screen->y = 0;
screen->px = 0;
screen->py = 0;
screen->selected = 0;
spritebuf_reset(&screen->sprite);
screen->colour = 0;
for (int i=0; i<16; i++) screen->pal[i] = 0;
ndsscreen_clear(screen->nds);
screen->wake = false;
screen->dirty = false;
}
// Unmap a screen device from a screen.
void screen_map_to_none(ScreenDevice *screen) {
if (screen->nds) {
ndsscreen_clear(screen->nds);
screen->nds = NULL;
}
}
// Map a screen device to the main screen.
void screen_map_to_main(ScreenDevice *screen) {
screen->nds = &main_screen;
memcpy(&screen->nds->pal, &screen->pal, 32);
screen->wake = true;
}
// Map a screen device to the sub screen.
void screen_map_to_sub(ScreenDevice *screen) {
screen->nds = &sub_screen;
memcpy(&screen->nds->pal, &screen->pal, 32);
screen->wake = true;
}
// Commit a new colour to the colour palette.
void screen_commit_colour(ScreenDevice *screen) {
u8 i = screen->colour >> 12 & 0x0f;
// Extract 5-bit channel values.
u8 r = screen->colour >> 7 & 0x1e;
u8 g = screen->colour >> 3 & 0x1e;
u8 b = screen->colour << 1 & 0x1e;
screen->pal[i] = RGB15(r,g,b);
if (screen->nds) screen->nds->pal[i] = RGB15(r,g,b);
screen->dirty = true;
}
// Move the screen cursor.
void screen_move_cursor(ScreenDevice *screen, u8 move) {
switch (move >> 6) {
case 0b00: screen->x += move & 0x3f; return;
case 0b01: screen->y += move & 0x3f; return;
case 0b10: screen->x -= move & 0x3f; return;
case 0b11: screen->y -= move & 0x3f; return;
}
}
// ------ RAW DRAW OPERATIONS --------------------------------------------------
// Draw a pixel to a screen layer.
void draw_pixel(u16 *layer, u16 x, u16 y, u8 colour) {
if (x < PIXELS_WIDTH && y < PIXELS_HEIGHT) {
u32 addr = \
(x >> 2 & 0x0001) + (x << 1 & 0xfff0) + \
(y << 1 & 0x000f) + (y << 6 & 0xfe00);
u16 shift = (x & 0x3) << 2;
layer[addr] = (layer[addr] & ~(0xf << shift)) | (colour << shift);
}
}
// Fill a screen layer with a solid colour.
void fill_layer(u16 *layer, u8 colour) {
u8 byte = colour << 4 | colour;
u32 word = byte << 24 | byte << 16 | byte << 8 | byte;
dmaFillWords(word, layer, LAYER_MEM);
}
// ------ DRAW OPERATIONS ------------------------------------------------------
void op_draw_pixel(ScreenDevice *screen, u16 *layer, u8 draw) {
draw_pixel(layer, screen->x, screen->y, draw&0xf);
}
void op_fill_layer(ScreenDevice *screen, u16 *layer, u8 draw) {
fill_layer(layer, draw&0xf);
}
void op_draw_sprite(ScreenDevice *screen, u16 *layer, u8 draw) {
if (draw & 0x20) { spritebuf_prepare_2bit(&screen->sprite, draw); }
else { spritebuf_prepare_1bit(&screen->sprite, draw); }
// Parse sprite colours.
u8 selected[4] = {
screen->selected >> 12 & 0x000f,
screen->selected >> 8 & 0x000f,
screen->selected >> 4 & 0x000f,
screen->selected & 0x000f,
};
if (draw & 0x08) {
// Draw sprite with transparent background.
for (u8 y=0;y<8;y++) {
for (u8 x=0;x<8;x++) {
u8 i = screen->sprite.sprite[y][x];
if (i) draw_pixel(layer, screen->x+x, screen->y+y, selected[i]);
}
}
} else {
// Draw sprite with opaque background.
for (u8 y=0;y<8;y++) {
for (u8 x=0;x<8;x++) {
u8 i = screen->sprite.sprite[y][x];
draw_pixel(layer, screen->x+x, screen->y+y, selected[i]);
}
}
}
}
void op_draw_line(ScreenDevice *screen, u16 *layer, u8 draw) {
s16 x = (s16) screen->x;
s16 y = (s16) screen->y;
s16 x_end = (s16) screen->px;
s16 y_end = (s16) screen->py;
s32 dx = abs(x_end - x);
s32 dy = -abs(y_end - y);
s16 sx = x < x_end ? 1 : -1;
s16 sy = y < y_end ? 1 : -1;
s32 e1 = dx + dy;
if (draw & 0x10) {
// Draw 1-bit textured line.
spritebuf_prepare_1bit(&screen->sprite, draw);
u8 c1 = screen->selected >> 8 & 0xf;
u8 c0 = screen->selected >> 12 & 0xf;
bool opaque = !(draw & 0x08);
while (1) {
if (screen->sprite.sprite[y%8][x%8]) { draw_pixel(layer, x, y, c1); }
else if (opaque) { draw_pixel(layer, x, y, c0); }
if (x == x_end && y == y_end) return;
s32 e2 = e1 << 1;
if (e2 >= dy) { e1 += dy; x += sx; }
if (e2 <= dx) { e1 += dx; y += sy; }
}
} else {
// Draw solid line.
u8 c0 = draw & 0xf;
while (1) {
draw_pixel(layer, x, y, c0);
if (x == x_end && y == y_end) return;
s32 e2 = e1 << 1;
if (e2 >= dy) { e1 += dy; x += sx; }
if (e2 <= dx) { e1 += dx; y += sy; }
}
}
}
void op_draw_rect(ScreenDevice *screen, u16 *layer, u8 draw) {
#define SWAP(x,y) {u8 temp=x; x=y; y=temp;}
#define CLAMP(v,m) {v>0x7fff ? 0 : v>m ? m : v}
// Get bounding box.
u16 l = CLAMP(screen->px, PIXELS_WIDTH -1);
u16 r = CLAMP(screen->x , PIXELS_WIDTH -1);
u16 t = CLAMP(screen->py, PIXELS_HEIGHT-1);
u16 b = CLAMP(screen->y , PIXELS_HEIGHT-1);
if (l>r) SWAP(l,r);
if (t>b) SWAP(t,b);
if (draw & 0x10) {
// Draw 1-bit textured rectangle.
spritebuf_prepare_1bit(&screen->sprite, draw);
u8 c1 = screen->selected >> 8 & 0xf;
u8 c0 = screen->selected >> 12 & 0xf;
bool opaque = !(draw & 0x08);
for (u16 x=l; x<r+1; x++) {
for (u16 y=t; y<b+1; y++) {
if (screen->sprite.sprite[y%8][x%8]) { draw_pixel(layer, x, y, c1); }
else if (opaque) { draw_pixel(layer, x, y, c0); }
}
}
} else {
// Draw solid rectangle.
u8 c0 = draw & 0xf;
for (u16 x=l; x<r+1; x++) {
for (u16 y=t; y<b+1; y++) {
draw_pixel(layer, x, y, c0);
}
}
}
}
// Dispatch to the correct drawing operation.
void screen_draw(ScreenDevice *screen, u8 draw) {
if (screen->nds) {
switch (draw >> 4) {
case 0x0: op_draw_pixel(screen, screen->nds->bg, draw); break;
case 0x1: op_draw_sprite(screen, screen->nds->bg, draw); break;
case 0x2: op_fill_layer(screen, screen->nds->bg, draw); break;
case 0x3: op_draw_sprite(screen, screen->nds->bg, draw); break;
case 0x4: op_draw_line(screen, screen->nds->bg, draw); break;
case 0x5: op_draw_line(screen, screen->nds->bg, draw); break;
case 0x6: op_draw_rect(screen, screen->nds->bg, draw); break;
case 0x7: op_draw_rect(screen, screen->nds->bg, draw); break;
case 0x8: op_draw_pixel(screen, screen->nds->fg, draw); break;
case 0x9: op_draw_sprite(screen, screen->nds->fg, draw); break;
case 0xA: op_fill_layer(screen, screen->nds->fg, draw); break;
case 0xB: op_draw_sprite(screen, screen->nds->fg, draw); break;
case 0xC: op_draw_line(screen, screen->nds->fg, draw); break;
case 0xD: op_draw_line(screen, screen->nds->fg, draw); break;
case 0xE: op_draw_rect(screen, screen->nds->fg, draw); break;
case 0xF: op_draw_rect(screen, screen->nds->fg, draw); break;
}
screen->dirty = true;
}
screen->px = screen->x;
screen->py = screen->y;
}
|