1 #include "video.hpp"
2 #include "hwvideo/hwsprites.hpp"
3 #include "globals.hpp"
4 #include "frontend/config.hpp"
5
6 /***************************************************************************
7 Video Emulation: OutRun Sprite Rendering Hardware.
8 Based on MAME source code.
9
10 Copyright Aaron Giles.
11 All rights reserved.
12 ***************************************************************************/
13
14 /*******************************************************************************************
15 * Out Run/X-Board-style sprites
16 *
17 * Offs Bits Usage
18 * +0 e------- -------- Signify end of sprite list
19 * +0 -h-h---- -------- Hide this sprite if either bit is set
20 * +0 ----bbb- -------- Sprite bank
21 * +0 -------t tttttttt Top scanline of sprite + 256
22 * +2 oooooooo oooooooo Offset within selected sprite bank
23 * +4 ppppppp- -------- Signed 7-bit pitch value between scanlines
24 * +4 -------x xxxxxxxx X position of sprite (position $BE is screen position 0)
25 * +6 -s------ -------- Enable shadows
26 * +6 --pp---- -------- Sprite priority, relative to tilemaps
27 * +6 ------vv vvvvvvvv Vertical zoom factor (0x200 = full size, 0x100 = half size, 0x300 = 2x size)
28 * +8 y------- -------- Render from top-to-bottom (1) or bottom-to-top (0) on screen
29 * +8 -f------ -------- Horizontal flip: read the data backwards if set
30 * +8 --x----- -------- Render from left-to-right (1) or right-to-left (0) on screen
31 * +8 ------hh hhhhhhhh Horizontal zoom factor (0x200 = full size, 0x100 = half size, 0x300 = 2x size)
32 * +E dddddddd dddddddd Scratch space for current address
33 *
34 * Out Run only:
35 * +A hhhhhhhh -------- Height in scanlines - 1
36 * +A -------- -ccccccc Sprite color palette
37 *
38 * X-Board only:
39 * +A ----hhhh hhhhhhhh Height in scanlines - 1
40 * +C -------- cccccccc Sprite color palette
41 *
42 * Final bitmap format:
43 *
44 * -s------ -------- Shadow control
45 * --pp---- -------- Sprite priority
46 * ----cccc cccc---- Sprite color palette
47 * -------- ----llll 4-bit pixel data
48 *
49 *******************************************************************************************/
50
51 // Enable for hardware pixel accuracy, where sprite shadowing delayed by 1 clock cycle (slower)
52 #define PIXEL_ACCURACY 0
53
hwsprites()54 hwsprites::hwsprites()
55 {
56 }
57
~hwsprites()58 hwsprites::~hwsprites()
59 {
60 }
61
init(const uint8_t * src_sprites)62 void hwsprites::init(const uint8_t* src_sprites)
63 {
64 reset();
65
66 if (src_sprites)
67 {
68 // Convert S16 tiles to a more useable format
69 const uint8_t *spr = src_sprites;
70
71 for (uint32_t i = 0; i < SPRITES_LENGTH; i++)
72 {
73 uint8_t d3 = *spr++;
74 uint8_t d2 = *spr++;
75 uint8_t d1 = *spr++;
76 uint8_t d0 = *spr++;
77
78 sprites[i] = (d0 << 24) | (d1 << 16) | (d2 << 8) | d3;
79 }
80 }
81 }
82
reset()83 void hwsprites::reset()
84 {
85 // Clear Sprite RAM buffers
86 for (uint16_t i = 0; i < SPRITE_RAM_SIZE; i++)
87 {
88 ram[i] = 0;
89 ramBuff[i] = 0;
90 }
91 }
92
93 // Clip areas of the screen in wide-screen mode
set_x_clip(bool on)94 void hwsprites::set_x_clip(bool on)
95 {
96 // Clip to central 320 width window.
97 if (on)
98 {
99 x1 = config.s16_x_off;
100 x2 = x1 + S16_WIDTH;
101
102 if (config.video.hires)
103 {
104 x1 <<= 1;
105 x2 <<= 1;
106 }
107 }
108 // Allow full wide-screen.
109 else
110 {
111 x1 = 0;
112 x2 = config.s16_width;
113 }
114 }
115
read(const uint16_t adr)116 uint8_t hwsprites::read(const uint16_t adr)
117 {
118 uint16_t a = adr >> 1;
119 if ((adr & 1) == 1)
120 return ram[a] & 0xff;
121 else
122 return ram[a] >> 8;
123 }
124
write(const uint16_t adr,const uint16_t data)125 void hwsprites::write(const uint16_t adr, const uint16_t data)
126 {
127 ram[adr >> 1] = data;
128 }
129
130 // Copy back buffer to main ram, ready for blit
swap()131 void hwsprites::swap()
132 {
133 uint16_t *src = (uint16_t *)ram;
134 uint16_t *dst = (uint16_t *)ramBuff;
135
136 // swap the halves of the road RAM
137 for (uint16_t i = 0; i < SPRITE_RAM_SIZE; i++)
138 {
139 uint16_t temp = *src;
140 *src++ = *dst;
141 *dst++ = temp;
142 }
143 }
144
145 #if PIXEL_ACCURACY
146
147 // Reproduces glowy edge around sprites on top of shadows as seen on Hardware.
148 // Believed to be caused by shadowing being out by one clock cycle / pixel.
149 //
150 // 1/ Sprites Drawn on top of Shadow clears the shadow flags for its opaque pixels.
151 // 2/ Either the flag clear or the sprite itself is offset by one pixel horizontally.
152 //
153 // Thanks to Alex B. for this implementation.
154
155 #define draw_pixel() \
156 { \
157 if (x >= x1 && x < x2) \
158 { \
159 if (shadow && pix == 0xa) \
160 { \
161 pPixel[x] &= 0xfff; \
162 pPixel[x] += S16_PALETTE_ENTRIES; \
163 } \
164 else if (pix != 0 && pix != 15) \
165 { \
166 if (x > x1) pPixel[x-1] &= 0xfff; \
167 pPixel[x] = (pix | color); \
168 } \
169 } \
170 }
171
172 #else
173
174 #define draw_pixel() \
175 { \
176 if (x >= x1 && x < x2 && pix != 0 && pix != 15) \
177 { \
178 if (shadow && pix == 0xa) \
179 { \
180 pPixel[x] &= 0xfff; \
181 pPixel[x] += S16_PALETTE_ENTRIES; \
182 } \
183 else \
184 { \
185 pPixel[x] = (pix | color); \
186 } \
187 } \
188 }
189
190 #endif
191
render(const uint8_t priority)192 void hwsprites::render(const uint8_t priority)
193 {
194 const uint32_t numbanks = SPRITES_LENGTH / 0x10000;
195
196 for (uint16_t data = 0; data < SPRITE_RAM_SIZE; data += 8)
197 {
198 // stop when we hit the end of sprite list
199 if ((ramBuff[data+0] & 0x8000) != 0) break;
200
201 uint32_t sprpri = 1 << ((ramBuff[data+3] >> 12) & 3);
202 if (sprpri != priority) continue;
203
204 // if hidden, or top greater than/equal to bottom, or invalid bank, punt
205 int16_t hide = (ramBuff[data+0] & 0x5000);
206 int32_t height = (ramBuff[data+5] >> 8) + 1;
207 if (hide != 0 || height == 0) continue;
208
209 int16_t bank = (ramBuff[data+0] >> 9) & 7;
210 int32_t top = (ramBuff[data+0] & 0x1ff) - 0x100;
211 uint32_t addr = ramBuff[data+1];
212 int32_t pitch = ((ramBuff[data+2] >> 1) | ((ramBuff[data+4] & 0x1000) << 3)) >> 8;
213 int32_t xpos = ramBuff[data+6]; // moved from original structure to accomodate widescreen
214 uint8_t shadow = (ramBuff[data+3] >> 14) & 1;
215 int32_t vzoom = ramBuff[data+3] & 0x7ff;
216 int32_t ydelta = ((ramBuff[data+4] & 0x8000) != 0) ? 1 : -1;
217 int32_t flip = (~ramBuff[data+4] >> 14) & 1;
218 int32_t xdelta = ((ramBuff[data+4] & 0x2000) != 0) ? 1 : -1;
219 int32_t hzoom = ramBuff[data+4] & 0x7ff;
220 int32_t color = COLOR_BASE + ((ramBuff[data+5] & 0x7f) << 4);
221 int32_t x, y, ytarget, yacc = 0, pix;
222
223 // adjust X coordinate
224 // note: the threshhold below is a guess. If it is too high, rachero will draw garbage
225 // If it is too low, smgp won't draw the bottom part of the road
226 if (xpos < 0x80 && xdelta < 0)
227 xpos += 0x200;
228 xpos -= 0xbe;
229
230 // initialize the end address to the start address
231 ramBuff[data+7] = addr;
232
233 // clamp to within the memory region size
234 if (numbanks)
235 bank %= numbanks;
236
237 const uint32_t* spritedata = sprites + 0x10000 * bank;
238
239 // clamp to a maximum of 8x (not 100% confirmed)
240 if (vzoom < 0x40) vzoom = 0x40;
241 if (hzoom < 0x40) hzoom = 0x40;
242
243 // loop from top to bottom
244 ytarget = top + ydelta * height;
245
246 // Adjust for widescreen mode
247 xpos += config.s16_x_off;
248
249 // Adjust for hi-res mode
250 if (config.video.hires)
251 {
252 xpos <<= 1;
253 top <<= 1;
254 ytarget <<= 1;
255 hzoom >>= 1;
256 vzoom >>= 1;
257 }
258
259 for (y = top; y != ytarget; y += ydelta)
260 {
261 // skip drawing if not within the cliprect
262 if (y >= 0 && y < config.s16_height)
263 {
264 uint16_t* pPixel = &video.pixels[y * config.s16_width];
265 int32_t xacc = 0;
266
267 // non-flipped case
268 if (flip == 0)
269 {
270 // start at the word before because we preincrement below
271 ramBuff[data+7] = (addr - 1);
272
273 for (x = xpos; (xdelta > 0 && x < config.s16_width) || (xdelta < 0 && x >= 0); )
274 {
275 uint32_t pixels = spritedata[++ramBuff[data+7]]; // Add to base sprite data the vzoom value
276
277 // draw four pixels
278 pix = (pixels >> 28) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
279 pix = (pixels >> 24) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
280 pix = (pixels >> 20) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
281 pix = (pixels >> 16) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
282 pix = (pixels >> 12) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
283 pix = (pixels >> 8) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
284 pix = (pixels >> 4) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
285 pix = (pixels >> 0) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
286
287 // stop if the second-to-last pixel in the group was 0xf
288 if ((pixels & 0x000000f0) == 0x000000f0)
289 break;
290 }
291 }
292 // flipped case
293 else
294 {
295 // start at the word after because we predecrement below
296 ramBuff[data+7] = (addr + 1);
297
298 for (x = xpos; (xdelta > 0 && x < config.s16_width) || (xdelta < 0 && x >= 0); )
299 {
300 uint32_t pixels = spritedata[--ramBuff[data+7]];
301
302 // draw four pixels
303 pix = (pixels >> 0) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
304 pix = (pixels >> 4) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
305 pix = (pixels >> 8) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
306 pix = (pixels >> 12) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
307 pix = (pixels >> 16) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
308 pix = (pixels >> 20) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
309 pix = (pixels >> 24) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
310 pix = (pixels >> 28) & 0xf; while (xacc < 0x200) { draw_pixel(); x += xdelta; xacc += hzoom; } xacc -= 0x200;
311
312 // stop if the second-to-last pixel in the group was 0xf
313 if ((pixels & 0x0f000000) == 0x0f000000)
314 break;
315 }
316 }
317 }
318 // accumulate zoom factors; if we carry into the high bit, skip an extra row
319 yacc += vzoom;
320 addr += pitch * (yacc >> 9);
321 yacc &= 0x1ff;
322 }
323 }
324 }