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 }