-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathvideo.c
235 lines (193 loc) · 6.66 KB
/
video.c
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
#include <stdint.h>
#include <assert.h>
#include <stdlib.h>
#include <memory.h>
#include "video.h"
#include "roms.h"
#include "hw.h"
#include "platform.h"
// Magic table to calculate pixel-perfect vertical shrinking.
// This table can be thought of a condensed version of the original
// NeoGeo L0 ROM, but we just need 16 bytes to achieve the same results.
// The table is then duplicated (and mirrored) to 32 bytes to simplify
// lookup for tiles 16-31 (where the L0 ROM is read backwards).
// To see how it's calculated, see l0.py.
static const uint8_t VSHRINK_MAGIC[32] = {
1, 9, 5, 13, 3, 11, 7, 15, 0, 8, 4, 12, 2, 10, 6, 14,
14, 6, 10, 2, 12, 4, 8, 0, 15, 7, 11, 3, 13, 5, 9, 1
};
// Given a vshrink code and the tile number, compute the tile
// height in pixels. For instance:
// vshrink_tile_height(0xBC, 4) == 12 means that when using
// vshrink code 0xBC, the fifth tile of a sprite will be exactly
// 12 pixel tall (shrunk from 16).
static inline int vshrink_tile_height(int vshrink, int num_tile) {
vshrink += 1;
return vshrink/16 + (vshrink%16 > VSHRINK_MAGIC[num_tile]);
}
// Given a tile of the specified height and y in [0,15],
// return True if the given line is drawn, or False if
// it should be skipped.
static inline bool vshrink_line_drawn(int height, int y) {
return height > VSHRINK_MAGIC[y];
}
static uint16_t color_convert(uint16_t val) {
uint16_t c16 = 0;
c16 |= ((val & 0x0F00) << 4) | ((val & 0x4000) >> 3);
c16 |= ((val & 0x00F0) << 3) | ((val & 0x2000) >> 7);
c16 |= ((val & 0x000F) << 2) | ((val & 0x1000) >> 11);
return c16;
}
static uint16_t PALETTE_RAM_EMU[4*1024];
#ifdef N64
#if 1
#include "video_n64.c"
#else
#include "video_cpu.c"
#endif
#else
#include "video_cpu.c"
#endif
static void render_fix(void) {
uint16_t *fix = VIDEO_RAM + 0x7000;
render_begin_fix();
for (int i=0;i<40;i++) {
fix += 2; // skip two lines
for (int j=0;j<28;j++) {
uint16_t v = *fix++;
if (v)
draw_sprite_fix(v & 0xFFF, (v >> 12) & 0xF, i*8, j*8);
}
fix += 2;
}
render_end_fix();
}
static void render_sprites(void) {
int sx = 0, sy = 0, sh = 0, sw = 0, vshrink = 0;
bool repeat_tiles = false;
uint8_t aa;
bool aa_enabled = lspc_get_auto_animation(&aa);
render_begin_sprites();
for (int snum=0;snum<381;snum++) {
uint16_t zc = VIDEO_RAM[0x8000 + snum];
uint16_t yc = VIDEO_RAM[0x8200 + snum];
uint16_t xc = VIDEO_RAM[0x8400 + snum];
uint16_t *tmap = VIDEO_RAM + snum*64;
if (!(yc & 0x40)) {
sx = xc >> 7;
sy = 496 - (yc >> 7);
sh = (yc & 0x3F) * 16;
repeat_tiles = false;
if (sh > 32*16) { sh = 32*16; repeat_tiles = true; };
vshrink = zc & 0xFF;
} else {
sx += sw;
}
sw = ((zc>>8)&0xF) + 1;
if (sh == 0) continue;
if (sx >= 320 && sx+sw <= 512) continue;
// debugf("[VIDEO] sprite snum:%d xc:%04x yc:%04x zc:%04x pos:%d,%d sh:%d chain:%d repeat:%d tmap:%04x:%04x\n", snum, xc, yc, zc, sx, sy, sh, (yc & 0x40), repeat_tiles, tmap[0], tmap[1]);
int nt, y, maxy;
int halfy = sh < 256 ? sh : 256;
// Iterate on the two halves of the vertical sprite. This for loop
// is mainly useful to reuse the core drawing loop. The setup
// of the two halves is different (see below).
for (int half = 0; half < 2; half++) {
if (half == 0) {
// Top half of the sprite (first 256 pixels). This part shrinks
// to the top of the sprite position. In case of overfill, this
// is exactly 256 pixels, repeating all tiles as required.
maxy = halfy;
nt = y = 0;
} else {
if (sh <= 256) break;
// Bottom half of the sprite (pixels after 256). This part shrinks
// to the bottom of the sprite (because it accesses the line ROM
// backward, reversing also its contents).
maxy = sh;
if (repeat_tiles) {
// In repeat mode, we need to find a starting Y where the next
// tile begins, which is basically symmetric across the 256 pixel
// line compared to where we ended up with the top half.
// FIXME: overdraw here, we should instead clip.
y -= (y-256)*2;
nt = (32-nt)&31;
} else {
// In non-repeat mode, skip overfill area. We basically want
// to reach the symmetric y coordinate in the bottom area.
// FIXME: the top half overfill area should be filled with
// the last line of tile #15, while the bottom half overfill
// should be filled with the first line of tile #16. This
// is currently not implemented.
y = 32*16 - y;
}
}
// Loop through the vertical sprite, tile by tile
while (y < maxy) {
// Calculate the vertical size of this tile. This is
// a pixel-perfect formula using the magic table derived
// from the original NeoGeo L0 ROM.
// int ssh = vshrink/16 + (vshrink%16 > VSHRINK_MAGIC[nt]);
int ssh = vshrink_tile_height(vshrink, nt);
if (ssh > 0) {
// vertical clip of the tile to the total sprite height
// FIXME: this is wrong because ssh also affects the
// shrinking size of the sprite. We should separate the
// two matters.
if (y + ssh > sh)
ssh = sh - y;
// See if this tile is visible, given its Y coordinate and size
int ssy = sy + y;
if (ssy < 224 || (ssy+ssh) > 512) {
uint32_t tnum = tmap[nt*2+0];
uint32_t tc = tmap[nt*2+1];
tnum |= (tc << 12) & 0xF0000;
int palnum = ((tc >> 8) & 0xFF);
// debugf("[VIDEO] %s: nt:%d y:%d ssy:%d ssh:%d tnum:%x\n", half?"bot":"top", nt, y, ssy, ssh, tnum);
// Auto animation
if (aa_enabled) {
if (tc & 8) { tnum &= ~7; tnum |= aa & 7; }
else if (tc & 4) { tnum &= ~3; tnum |= aa & 3; }
}
// Draw the tile
draw_sprite(tnum, palnum, sx, ssy, sw, ssh, tc&1, tc&2);
}
}
y += ssh;
nt++; nt &= 31;
// In non-repeat mode (standard), the top half
// finishes when/if we reach tile #16 (or before, if
// the vertical sprite size is reached).
if (!repeat_tiles && nt == 16) break; // FIXME: draw overfill when not repeating
}
}
}
render_end_sprites();
}
void video_render(void) {
render_begin();
render_sprites();
render_fix();
render_end();
}
void video_palette_w(uint32_t address, uint32_t val, int sz) {
if (sz == 4) {
video_palette_w(address+0, val >> 16, 2);
video_palette_w(address+2, val & 0xFFFF, 2);
return;
}
if (sz == 1) val |= val << 8; // FIXME: this is used by unibios in-game menu, verify
address &= 0x1FFF;
address /= 2;
address += PALETTE_RAM_BANK;
PALETTE_RAM[address] = val;
}
uint32_t video_palette_r(uint32_t address, int sz) {
if (sz==4)
return (video_palette_r(address+0, 2) << 16) | video_palette_r(address+2, 2);
assertf(sz == 2, "video_palette_r access size %d", sz);
address &= 0x1FFF;
address /= 2;
address += PALETTE_RAM_BANK;
return PALETTE_RAM[address];
}