]> Shamusworld >> Repos - thunder/blob - src/screen.cpp
d3f5672c214db805ef7779b2e120f6a7c34e933e
[thunder] / src / screen.cpp
1 //
2 // Screen Handler
3 //
4 // This emulates the NAMCO tile/sprite hardware
5 //
6 // by James Hammons
7 // (C) 2003 Underground Software
8 //
9 // JLH = James Hammons <jlhamm@acm.org>
10 //
11 // Who  When        What
12 // ---  ----------  -----------------------------------------------------------
13 // JLH  03/12/2003  Ported this steaming pile of crap from VESA to SDL
14 // JLH  04/04/2014  Ported to SDL 2. Much less crappy.
15 //
16
17 #include "screen.h"
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string>                     // For memset()
22 #include "gui.h"
23 #include "video.h"
24
25 // Private function prototypes
26
27 void DrawSprites(uint8_t priority);
28 int FindPCXName(void);
29 static inline void DrawScreen(uint16_t ramBlock, uint16_t xAddress, uint16_t yAddress, uint32_t baseAddr, bool transparent = true);
30 static void DrawChar(uint8_t sx, uint8_t sy, uint16_t scp, uint32_t baseAddr, uint32_t xScroll, uint32_t yScroll, bool transparent = true);
31 void Sprite(uint32_t sprnum, uint16_t x, uint16_t y, uint8_t flip, uint8_t horiz_bl, uint8_t vert_bl);
32
33 // Private global variables
34
35 uint8_t my_scr[0x14000];                // Screen buffer...
36 uint32_t palette[256];                  // Screen palette
37 uint8_t ccolor[256][8];                 // Character colors
38 uint8_t scolor[128][16];                // Sprite colors
39 bool charBankSwitch;                    // Character bank switch
40 uint8_t spr_color_index;                // Sprite color index
41
42 extern bool show_text;                  // Whether or not to show text
43
44
45 //
46 // Render the NAMCO screen
47 //
48 void BlitChar(uint8_t * chr, uint8_t * ram)
49 {
50         // Screen structure:
51         //
52         // Each screen RAM is 4K in size, from lowest to highest priority:
53         // $0000-$0FFF, $1000-$1FFF, $2000-$2FFF, $3000-$3FFF
54         //
55         // Screen is 288 x 224 pixels, with character and independent sprites.
56         // Tiles are 36 x 28. There are four tile planes, all of them affected by
57         // their own h/vscroll values. Screens are 128 bytes wide by 32 bytes high.
58         //
59         // Also note that tiles are 16-bits wide, meaning the screen is really
60         // only 64 characters wide!
61
62         // Base address is $9000-1 (top 13 bits) + $9002 >> 3 << 7 (top 5 bits)
63 /*
64 $9000-2: $02 $0C $FF  0000 0010  0000 1100  1111 1111
65 $9004-6: $06 $0E $FF  0000 0110  0000 1110  1111 1111
66 $9400-2: $0A $0B $FF  0000 1010  0000 1011  1111 1111
67 $9404-6: $0E $0D $07  0000 1110  0000 1101  0000 0111
68                       ---- PP?H  HHHH Hhhh  VVVV Vvvv
69 ? = refresh layer bit?
70 PP = which 4K block to write to?
71 */
72         uint16_t screenX0 = (((ram[0x9000] << 8) | ram[0x9001]) + 20) & 0x1FF;
73         uint16_t screenX1 = (((ram[0x9004] << 8) | ram[0x9005]) + 18) & 0x1FF;
74         uint16_t screenX2 = (((ram[0x9400] << 8) | ram[0x9401]) + 21) & 0x1FF;
75         uint16_t screenX3 = (((ram[0x9404] << 8) | ram[0x9405]) + 19) & 0x1FF;
76
77         uint16_t screenY0 = ram[0x9002] + 25;
78         uint16_t screenY1 = ram[0x9006] + 25;
79         uint16_t screenY2 = ram[0x9402] + 25;
80         uint16_t screenY3 = ram[0x9406] + 25;
81
82         uint16_t ramBlock0 = (ram[0x9000] >> 2) & 0x03;
83         uint16_t ramBlock1 = (ram[0x9004] >> 2) & 0x03;
84         uint16_t ramBlock2 = (ram[0x9400] >> 2) & 0x03;
85         uint16_t ramBlock3 = (ram[0x9404] >> 2) & 0x03;
86
87         DrawScreen(ramBlock0, screenX0, screenY0, (charBankSwitch ? 2 : 0), false);
88         DrawSprites(0x40);
89         DrawScreen(ramBlock1, screenX1, screenY1, (charBankSwitch ? 3 : 1));
90         DrawSprites(0x80);
91         DrawScreen(ramBlock2, screenX2, screenY2, 4);
92         DrawSprites(0xC0);
93         DrawScreen(ramBlock3, screenX3, screenY3, 5);
94
95         // Draw a msg if needed...
96         if (show_text)
97                 DrawText();
98
99         // Show GUI if active...
100         if (ShowGUI())
101                 DrawGUI();
102
103         // Rolling Thunder screen size is 288 x 224. Virtual is this, real may not
104         // be... (and we don't have to care about that, the OpenGL backend takes
105         // care of it.)
106         for(uint32_t i=0; i<VIRTUAL_SCREEN_WIDTH*VIRTUAL_SCREEN_HEIGHT; i++)
107                 scrBuffer[i] = palette[my_scr[i]];
108
109         RenderScreenBuffer();
110 }
111
112
113 //
114 // Render tilemap
115 //
116 static inline void DrawScreen(uint16_t ramBlock, uint16_t xAddress, uint16_t yAddress, uint32_t tileBase, bool transparent/*= true*/)
117 {
118         uint16_t ramBase = (ramBlock << 12) | ((yAddress << 4) & 0xF80)
119                 | ((xAddress >> 2) & 0x7E);
120
121         for(uint8_t sy=0; sy<29; sy++)
122         {
123                 for(uint8_t sx=0; sx<37; sx++)
124                 {
125
126                         DrawChar(sx, sy, ramBase, tileBase << 16, xAddress & 0x07, yAddress & 0x07, transparent);
127                 }
128         }
129 }
130
131
132 //
133 // Draw character on screen
134 //
135 static inline void DrawChar(uint8_t sx, uint8_t sy, uint16_t ramBase, uint32_t tileBase, uint32_t xScroll, uint32_t yScroll, bool transparent/*= true*/)
136 {
137         extern uint8_t charROM[];
138         extern uint8_t gram1[];
139
140         // Calculate address in RAM of tile to draw
141         uint16_t addr = (ramBase & 0xF000) | ((ramBase + (sy << 7)) & 0x0F80)
142                 | ((ramBase + (sx << 1)) & 0x7F);
143         uint16_t tile  = ((gram1[addr + 1] << 8) | gram1[addr]) & 0x03FF;
144         // Yes, it really requires all 8 bits... even though the bottom 3 are used
145         // as a tile address!
146         uint8_t color = gram1[addr + 1];
147         uint32_t chind = tileBase + (tile << 6);
148
149         int xStart = (int)(sx * 8) - xScroll;
150         int yStart = (int)(sy * 8) - yScroll;
151         int32_t sc_addr = xStart + (yStart * 288);
152
153         for(int y=0; y<8; y++)
154         {
155                 for(int x=0; x<8; x++)
156                 {
157                         // Clipping...
158                         if (((xStart + x) < 0) || ((xStart + x) >= 288)
159                                 || ((yStart + y) < 0) || ((yStart + y) >= 224))
160                         {
161                                 sc_addr++;
162                                 chind++;
163                                 continue;
164                         }
165
166                         if (!(transparent && (charROM[chind] == 7)))
167                                 my_scr[sc_addr] = ccolor[color][charROM[chind]];
168
169                         sc_addr++;
170                         chind++;
171                 }
172
173                 sc_addr += (288 - 8);           // Do next line of char...
174         }
175 }
176
177
178 //
179 // Copy sprites in sprite RAM from positions 4-9 to 10-15
180 // (N.B.: This still doesn't solve the shifting signs on the walls problem...)
181 //
182 void CopySprites(void)
183 {
184         extern uint8_t gram1[];
185
186         for(uint16_t i=0x5800; i<0x6000; i+=0x10)
187         {
188                 for(uint16_t j=4; j<=9; j++)
189                 {
190                         gram1[i + j + 6] = gram1[i + j];
191                 }
192         }
193 }
194
195
196 //
197 // Draw sprites at priority level (new code)
198 //
199 void DrawSprites(uint8_t priority)
200 {
201 // Sprite blocks:
202 //
203 // Offset  Note
204 // ------  --------------------------------------------------------------------
205 // 4       h.fb .nnn (f = horz. flip, h = horz. expand, b = sprite offset lo
206 //         bit, nnn = upper bits of sprite #)
207 // 5       Lower 7 bits of sprite #
208 // 6       Sprite color index (top 7 bits only), bottom bit is bit 8 of X
209 //         position
210 // 7       Sprite X position (bits 0-7)
211 // 8       Top two bits are sprite priority, bits 4 & 2 are sprite offset hi
212 //         bit, vert. expand
213 // 9       Sprite Y position (192 - value)
214
215         extern uint8_t gram1[];                                                 // Game RAM space
216
217         for(uint16_t i=0x5800; i<0x6000; i+=0x10)
218         {
219                 if ((gram1[i + 8 + 6] & 0xC0) == priority)              // Check for correct layer...
220                 {
221                         spr_color_index = gram1[i + 6 + 6] >> 1;        // Set color...
222                         uint16_t x = ((gram1[i + 6 + 6] & 0x01) << 8) | gram1[i + 7 + 6];
223
224                         if (x > 512 - 32)
225                                 x -= 512;                                                       // Handle neg x values
226
227                         uint16_t y = 192 - gram1[i + 9 + 6];
228                         uint8_t flip = gram1[i + 4 + 6] & 0x20;         // Horizontal flip
229                         uint32_t spr_num = ((gram1[i + 4 + 6] & 0x07) << 9)
230                                 | ((gram1[i + 5 + 6] & 0x7F) << 2)
231                                 | ((gram1[i + 4 + 6] & 0x10) >> 4)
232                                 | ((gram1[i + 8 + 6] & 0x10) >> 3);
233
234                         // Draw sprite...
235                         Sprite(spr_num, x, y, flip, gram1[i + 4 + 6] & 0x80, gram1[i + 8 + 6] & 0x04);
236                 }
237         }
238 }
239
240
241 static inline void DrawSpriteBlock(uint32_t & sprnum, uint16_t x, uint16_t y, uint16_t xStart, uint16_t xEnd, int16_t xInc)
242 {
243         extern uint8_t spr_rom[];
244
245         for(uint16_t sy=0; sy<16; sy++)
246         {
247                 for(uint16_t sx=xStart; sx<xEnd; sx+=xInc)
248                 {
249                         uint8_t b1 = spr_rom[sprnum] >> 4, b2 = spr_rom[sprnum++] & 0x0F;
250                         uint16_t spy = y + sy, spx = x + sx;    // Need to optimize this clipping! [eh?]
251
252                         // This handles negative values, by casting as unsigned
253                         uint32_t sc_addr = ((spy >= 224) || (spx >= 288) ? 0x13FFE : spx + (spy * 288));
254
255                         if (b1 != 15)
256                                 my_scr[sc_addr] = scolor[spr_color_index][b1];  // Store it
257
258                         sc_addr++;
259
260                         if (b2 != 15)
261                                 my_scr[sc_addr] = scolor[spr_color_index][b2];  // Store it
262                 }
263         }
264 }
265
266
267 static inline void DrawSpriteBlock2(uint32_t & sprnum, uint16_t x, uint16_t y, uint16_t xStart, uint16_t xEnd, int16_t xInc)
268 {
269         extern uint8_t spr_rom[];
270
271         for(uint16_t sy=0; sy<16; sy++)
272         {
273                 for(uint16_t sx=xStart; sx!=xEnd; sx+=xInc)
274                 {
275                         uint8_t b1 = spr_rom[sprnum] >> 4, b2 = spr_rom[sprnum++] & 0x0F;
276                         uint16_t spy = y + sy, spx = x + sx;    // Need to optimize this clipping! [eh?]
277
278                         // This handles negative values, by casting as unsigned
279                         uint32_t sc_addr = ((spy >= 224) || (spx >= 288) ? 0x13FFE : spx + (spy * 288));
280
281                         if (b2 != 15)
282                                 my_scr[sc_addr] = scolor[spr_color_index][b2];  // Store it
283
284                         sc_addr++;
285
286                         if (b1 != 15)
287                                 my_scr[sc_addr] = scolor[spr_color_index][b1];  // Store it
288                 }
289         }
290 }
291
292
293 //
294 // Sprite handler
295 //
296 void Sprite(uint32_t sprnum, uint16_t x, uint16_t y, uint8_t flip,
297         uint8_t horiz_bl, uint8_t vert_bl)
298 {
299         // 128 bytes per sprite (16 x 16 chunks, 4 bits per pixel)
300         sprnum <<= 7;
301
302         if (!vert_bl)
303                 y += 16;
304
305         if (!flip)
306         {
307                 DrawSpriteBlock(sprnum, x, y, 0, 16, 2);
308
309                 if (horiz_bl)
310                         DrawSpriteBlock(sprnum, x, y, 16, 32, 2);
311                 else
312                         sprnum += 128;  // Advance to next...
313
314                 if (vert_bl)
315                 {
316                         y += 16;                // Do next row...
317
318                         DrawSpriteBlock(sprnum, x, y, 0, 16, 2);
319
320                         if (horiz_bl)
321                                 DrawSpriteBlock(sprnum, x, y, 16, 32, 2);
322                 }
323         }
324         else    // Flip
325         {
326                 if (horiz_bl)
327                         DrawSpriteBlock2(sprnum, x, y, 30, 14, -2);
328
329                 DrawSpriteBlock2(sprnum, x, y, 14, 0xFFFE, -2);
330
331                 if (!horiz_bl)
332                         sprnum += 128;  // If single, skip sprite...
333
334                 if (vert_bl)
335                 {
336                         y += 16;                // Adjust Y coord...
337
338                         if (horiz_bl)
339                                 DrawSpriteBlock2(sprnum, x, y, 30, 14, -2);
340
341                         DrawSpriteBlock2(sprnum, x, y, 14, 0xFFFE, -2);
342                 }
343         }
344 }
345
346
347 int FindPCXName(void)
348 {
349         static int pcxNum = -1; // This needs to go elsewhere... (or does it?)
350         char filename[30];
351         FILE * fr;
352
353         pcxNum++;
354
355         while (pcxNum < 10000)
356         {
357                 sprintf(filename, "thnd%04i.pcx", pcxNum);
358
359                 // file does not exist - we can create it
360                 if ((fr = fopen(filename, "r")) == NULL)
361                         return pcxNum;
362
363                 pcxNum++;
364         }
365
366         return -1;
367 }
368
369
370 void SavePCXSnapshot(void)
371 {
372         char filename[30];
373         int xMax = 287;
374         int yMax = 223;
375         int bytesPerLine = 288;
376         int i = FindPCXName();
377
378         if (i < 0)
379                 return;
380
381         sprintf(filename, "thnd%04i.pcx", i);
382         FILE * fw = fopen(filename, "wb");
383
384         if (fw == NULL)
385                 return;
386
387         // Write the header
388
389         fputc(0x0A, fw);        // PCX signature
390         fputc(0x05, fw);        // Version 5
391         fputc(0x01, fw);        // RLE encoding
392         fputc(0x08, fw);        // Bits per pixel
393         fputc(0, fw);
394         fputc(0, fw);
395         fputc(0, fw);
396         fputc(0, fw);           // XMin=0,YMin=0
397         fputc(xMax & 0xFF, fw);
398         fputc(xMax >> 8, fw);
399         fputc(yMax & 0xFF, fw);
400         fputc(yMax >> 8, fw);
401         fputc(0, fw);
402         fputc(0, fw);
403         fputc(0, fw);
404         fputc(0, fw);           // Unknown DPI
405
406         // EGA color palette
407         for(i=0; i<48; i++)
408                 fputc(0, fw);
409
410         fputc(0, fw);           // Reserved
411         fputc(1, fw);           // Number of bit planes
412         fputc(bytesPerLine & 0xFF, fw);
413         fputc(bytesPerLine >> 8, fw);
414         fputc(1, fw);
415         fputc(0, fw);           // Palette info - unused
416         fputc((xMax + 1) & 0xFF, fw);
417         fputc((xMax + 1) >> 8, fw);
418         fputc((yMax + 1) & 0xFF, fw);
419         fputc((yMax + 1) >> 8, fw);     // Screen resolution
420
421         // Unused
422         for (i=0; i<54; i++)
423                 fputc(0, fw);
424
425         uint8_t * mem = my_scr;
426
427         for(int line=0; line<=yMax; line++)
428         {
429                 int xpos = 0;
430
431                 while (xpos < bytesPerLine)
432                 {
433                         uint8_t count = 1;
434                         uint8_t last = *mem;
435                         mem++;
436                         xpos++;
437
438                         while ((*mem == last) && (xpos < bytesPerLine) && (count < 63))
439                         {
440                                 mem++;
441                                 count++;
442                                 xpos++;
443                         }
444
445                         if ((count > 1) || ((last & 0xC0) == 0xC0))
446                         {
447                                 fputc(0xC0 | (count & 0x3F), fw);
448                                 fputc(last & 0xFF, fw);
449                         }
450                         else
451                                 fputc(last & 0xFF, fw);
452                 }
453         }
454
455         // Write out the palette
456         fputc(0x0C, fw);
457
458         for(int i=0; i<256; i++)
459         {
460                 fputc(palette[i] & 0xFF, fw);
461                 fputc((palette[i] >> 8) & 0xFF, fw);
462                 fputc((palette[i] >> 16) & 0xFF, fw);
463         }
464
465         // Success!
466         fclose(fw);
467 }
468