1/*****************************************************************************\
2 Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
3 This file is licensed under the Snes9x License.
4 For further information, consult the LICENSE file in the root directory.
5\*****************************************************************************/
6
7#include "snes9x.h"
8#include "ppu.h"
9#include "tile.h"
10#include "controls.h"
11#include "crosshairs.h"
12#include "cheats.h"
13#include "movie.h"
14#include "screenshot.h"
15#include "font.h"
16#include "display.h"
17
18extern struct SCheatData Cheat;
19extern struct SLineData LineData[240];
20extern struct SLineMatrixData LineMatrixData[240];
21
22void S9xComputeClipWindows (void);
23
24static int font_width = 8, font_height = 9;
25void (*S9xCustomDisplayString) (const char *, int, int, bool, int) = NULL;
26
27static void SetupOBJ (void);
28static void DrawOBJS (int);
29static void DisplayTime (void);
30static void DisplayFrameRate (void);
31static void DisplayPressedKeys (void);
32static void DisplayWatchedAddresses (void);
33static void DisplayStringFromBottom (const char *, int, int, bool);
34static void DrawBackground (int, uint8, uint8);
35static void DrawBackgroundMosaic (int, uint8, uint8);
36static void DrawBackgroundOffset (int, uint8, uint8, int);
37static void DrawBackgroundOffsetMosaic (int, uint8, uint8, int);
38static inline void DrawBackgroundMode7 (int, void (*DrawMath) (uint32, uint32, int), void (*DrawNomath) (uint32, uint32, int), int);
39static inline void DrawBackdrop (void);
40static inline void RenderScreen (bool8);
41static uint16 get_crosshair_color (uint8);
42static void S9xDisplayStringType (const char *, int, int, bool, int);
43
44#define TILE_PLUS(t, x) (((t) & 0xfc00) | ((t + x) & 0x3ff))
45
46
47bool8 S9xGraphicsInit (void)
48{
49 S9xInitTileRenderer();
50 memset(BlackColourMap, 0, 256 * sizeof(uint16));
51
52 GFX.RealPPL = GFX.Pitch >> 1;
53 IPPU.OBJChanged = TRUE;
54 Settings.BG_Forced = 0;
55 S9xFixColourBrightness();
56 S9xBuildDirectColourMaps();
57
58 GFX.ZERO = (uint16 *) malloc(sizeof(uint16) * 0x10000);
59
60 GFX.ScreenSize = GFX.Pitch / 2 * SNES_HEIGHT_EXTENDED * (Settings.SupportHiRes ? 2 : 1);
61 GFX.SubScreen = (uint16 *) malloc(GFX.ScreenSize * sizeof(uint16));
62 GFX.ZBuffer = (uint8 *) malloc(GFX.ScreenSize);
63 GFX.SubZBuffer = (uint8 *) malloc(GFX.ScreenSize);
64
65 if (!GFX.ZERO || !GFX.SubScreen || !GFX.ZBuffer || !GFX.SubZBuffer)
66 {
67 S9xGraphicsDeinit();
68 return (FALSE);
69 }
70
71 // Lookup table for 1/2 color subtraction
72 memset(GFX.ZERO, 0, 0x10000 * sizeof(uint16));
73 for (uint32 r = 0; r <= MAX_RED; r++)
74 {
75 uint32 r2 = r;
76 if (r2 & 0x10)
77 r2 &= ~0x10;
78 else
79 r2 = 0;
80
81 for (uint32 g = 0; g <= MAX_GREEN; g++)
82 {
83 uint32 g2 = g;
84 if (g2 & GREEN_HI_BIT)
85 g2 &= ~GREEN_HI_BIT;
86 else
87 g2 = 0;
88
89 for (uint32 b = 0; b <= MAX_BLUE; b++)
90 {
91 uint32 b2 = b;
92 if (b2 & 0x10)
93 b2 &= ~0x10;
94 else
95 b2 = 0;
96
97 GFX.ZERO[BUILD_PIXEL2(r, g, b)] = BUILD_PIXEL2(r2, g2, b2);
98 GFX.ZERO[BUILD_PIXEL2(r, g, b) & ~ALPHA_BITS_MASK] = BUILD_PIXEL2(r2, g2, b2);
99 }
100 }
101 }
102
103 return (TRUE);
104}
105
106void S9xGraphicsDeinit (void)
107{
108 if (GFX.ZERO) { free(GFX.ZERO); GFX.ZERO = NULL; }
109 if (GFX.SubScreen) { free(GFX.SubScreen); GFX.SubScreen = NULL; }
110 if (GFX.ZBuffer) { free(GFX.ZBuffer); GFX.ZBuffer = NULL; }
111 if (GFX.SubZBuffer) { free(GFX.SubZBuffer); GFX.SubZBuffer = NULL; }
112}
113
114void S9xGraphicsScreenResize (void)
115{
116 IPPU.MaxBrightness = PPU.Brightness;
117
118 IPPU.Interlace = Memory.FillRAM[0x2133] & 1;
119 IPPU.InterlaceOBJ = Memory.FillRAM[0x2133] & 2;
120 IPPU.PseudoHires = Memory.FillRAM[0x2133] & 8;
121
122 if (Settings.SupportHiRes && (PPU.BGMode == 5 || PPU.BGMode == 6 || IPPU.PseudoHires))
123 {
124 GFX.RealPPL = GFX.Pitch >> 1;
125 IPPU.DoubleWidthPixels = TRUE;
126 IPPU.RenderedScreenWidth = SNES_WIDTH << 1;
127 }
128 else
129 {
130 #ifdef USE_OPENGL
131 if (Settings.OpenGLEnable)
132 GFX.RealPPL = SNES_WIDTH;
133 else
134 #endif
135 GFX.RealPPL = GFX.Pitch >> 1;
136
137 IPPU.DoubleWidthPixels = FALSE;
138 IPPU.RenderedScreenWidth = SNES_WIDTH;
139 }
140
141 if (Settings.SupportHiRes && IPPU.Interlace)
142 {
143 GFX.PPL = GFX.RealPPL << 1;
144 IPPU.DoubleHeightPixels = TRUE;
145 IPPU.RenderedScreenHeight = PPU.ScreenHeight << 1;
146 GFX.DoInterlace++;
147 }
148 else
149 {
150 GFX.PPL = GFX.RealPPL;
151 IPPU.DoubleHeightPixels = FALSE;
152 IPPU.RenderedScreenHeight = PPU.ScreenHeight;
153 }
154}
155
156void S9xBuildDirectColourMaps (void)
157{
158 IPPU.XB = mul_brightness[PPU.Brightness];
159
160 for (uint32 p = 0; p < 8; p++)
161 for (uint32 c = 0; c < 256; c++)
162 DirectColourMaps[p][c] = BUILD_PIXEL(IPPU.XB[((c & 7) << 2) | ((p & 1) << 1)], IPPU.XB[((c & 0x38) >> 1) | (p & 2)], IPPU.XB[((c & 0xc0) >> 3) | (p & 4)]);
163}
164
165void S9xStartScreenRefresh (void)
166{
167 GFX.InterlaceFrame = !GFX.InterlaceFrame;
168 if (GFX.DoInterlace)
169 GFX.DoInterlace--;
170
171 if (IPPU.RenderThisFrame)
172 {
173 if (!GFX.DoInterlace || !GFX.InterlaceFrame)
174 {
175 if (!S9xInitUpdate())
176 {
177 IPPU.RenderThisFrame = FALSE;
178 return;
179 }
180
181 S9xGraphicsScreenResize();
182
183 IPPU.RenderedFramesCount++;
184 }
185
186 PPU.MosaicStart = 0;
187 PPU.RecomputeClipWindows = TRUE;
188 IPPU.PreviousLine = IPPU.CurrentLine = 0;
189
190 memset(GFX.ZBuffer, 0, GFX.ScreenSize);
191 memset(GFX.SubZBuffer, 0, GFX.ScreenSize);
192 }
193
194 if (++IPPU.FrameCount % Memory.ROMFramesPerSecond == 0)
195 {
196 IPPU.DisplayedRenderedFrameCount = IPPU.RenderedFramesCount;
197 IPPU.RenderedFramesCount = 0;
198 IPPU.FrameCount = 0;
199 }
200
201 if (GFX.InfoStringTimeout > 0 && --GFX.InfoStringTimeout == 0)
202 GFX.InfoString = NULL;
203
204 IPPU.TotalEmulatedFrames++;
205}
206
207void S9xEndScreenRefresh (void)
208{
209 if (IPPU.RenderThisFrame)
210 {
211 FLUSH_REDRAW();
212
213 if (GFX.DoInterlace && GFX.InterlaceFrame == 0)
214 {
215 S9xControlEOF();
216 S9xContinueUpdate(IPPU.RenderedScreenWidth, IPPU.RenderedScreenHeight);
217 }
218 else
219 {
220 if (IPPU.ColorsChanged)
221 {
222 uint32 saved = PPU.CGDATA[0];
223 IPPU.ColorsChanged = FALSE;
224 S9xSetPalette();
225 PPU.CGDATA[0] = saved;
226 }
227
228 S9xControlEOF();
229
230 if (Settings.TakeScreenshot)
231 S9xDoScreenshot(IPPU.RenderedScreenWidth, IPPU.RenderedScreenHeight);
232
233 if (Settings.AutoDisplayMessages)
234 S9xDisplayMessages(GFX.Screen, GFX.RealPPL, IPPU.RenderedScreenWidth, IPPU.RenderedScreenHeight, 1);
235
236 S9xDeinitUpdate(IPPU.RenderedScreenWidth, IPPU.RenderedScreenHeight);
237 }
238 }
239 else
240 S9xControlEOF();
241
242 S9xUpdateCheatsInMemory ();
243
244#ifdef DEBUGGER
245 if (CPU.Flags & FRAME_ADVANCE_FLAG)
246 {
247 if (ICPU.FrameAdvanceCount)
248 {
249 ICPU.FrameAdvanceCount--;
250 IPPU.RenderThisFrame = TRUE;
251 IPPU.FrameSkip = 0;
252 }
253 else
254 {
255 CPU.Flags &= ~FRAME_ADVANCE_FLAG;
256 CPU.Flags |= DEBUG_MODE_FLAG;
257 }
258 }
259#endif
260
261 if (CPU.SRAMModified)
262 {
263 if (!CPU.AutoSaveTimer)
264 {
265 if (!(CPU.AutoSaveTimer = Settings.AutoSaveDelay * Memory.ROMFramesPerSecond))
266 CPU.SRAMModified = FALSE;
267 }
268 else
269 {
270 if (!--CPU.AutoSaveTimer)
271 {
272 S9xAutoSaveSRAM();
273 CPU.SRAMModified = FALSE;
274 }
275 }
276 }
277}
278
279void RenderLine (uint8 C)
280{
281 if (IPPU.RenderThisFrame)
282 {
283 LineData[C].BG[0].VOffset = PPU.BG[0].VOffset + 1;
284 LineData[C].BG[0].HOffset = PPU.BG[0].HOffset;
285 LineData[C].BG[1].VOffset = PPU.BG[1].VOffset + 1;
286 LineData[C].BG[1].HOffset = PPU.BG[1].HOffset;
287
288 if (PPU.BGMode == 7)
289 {
290 struct SLineMatrixData *p = &LineMatrixData[C];
291 p->MatrixA = PPU.MatrixA;
292 p->MatrixB = PPU.MatrixB;
293 p->MatrixC = PPU.MatrixC;
294 p->MatrixD = PPU.MatrixD;
295 p->CentreX = PPU.CentreX;
296 p->CentreY = PPU.CentreY;
297 p->M7HOFS = PPU.M7HOFS;
298 p->M7VOFS = PPU.M7VOFS;
299 }
300 else
301 {
302 LineData[C].BG[2].VOffset = PPU.BG[2].VOffset + 1;
303 LineData[C].BG[2].HOffset = PPU.BG[2].HOffset;
304 LineData[C].BG[3].VOffset = PPU.BG[3].VOffset + 1;
305 LineData[C].BG[3].HOffset = PPU.BG[3].HOffset;
306 }
307
308 IPPU.CurrentLine = C + 1;
309 }
310 else
311 {
312 // if we're not rendering this frame, we still need to update this
313 // XXX: Check ForceBlank? Or anything else?
314 if (IPPU.OBJChanged)
315 SetupOBJ();
316 PPU.RangeTimeOver |= GFX.OBJLines[C].RTOFlags;
317 }
318}
319
320static inline void RenderScreen (bool8 sub)
321{
322 uint8 BGActive;
323 int D;
324
325 if (!sub)
326 {
327 GFX.S = GFX.Screen;
328 if (GFX.DoInterlace && GFX.InterlaceFrame)
329 GFX.S += GFX.RealPPL;
330 GFX.DB = GFX.ZBuffer;
331 GFX.Clip = IPPU.Clip[0];
332 BGActive = Memory.FillRAM[0x212c] & ~Settings.BG_Forced;
333 D = 32;
334 }
335 else
336 {
337 GFX.S = GFX.SubScreen;
338 GFX.DB = GFX.SubZBuffer;
339 GFX.Clip = IPPU.Clip[1];
340 BGActive = Memory.FillRAM[0x212d] & ~Settings.BG_Forced;
341 D = (Memory.FillRAM[0x2130] & 2) << 4; // 'do math' depth flag
342 }
343
344 if (BGActive & 0x10)
345 {
346 BG.TileAddress = PPU.OBJNameBase;
347 BG.NameSelect = PPU.OBJNameSelect;
348 BG.EnableMath = !sub && (Memory.FillRAM[0x2131] & 0x10);
349 BG.StartPalette = 128;
350 S9xSelectTileConverter(4, FALSE, sub, FALSE);
351 S9xSelectTileRenderers(PPU.BGMode, sub, TRUE);
352 DrawOBJS(D + 4);
353 }
354
355 BG.NameSelect = 0;
356 S9xSelectTileRenderers(PPU.BGMode, sub, FALSE);
357
358 #define DO_BG(n, pal, depth, hires, offset, Zh, Zl, voffoff) \
359 if (BGActive & (1 << n)) \
360 { \
361 BG.StartPalette = pal; \
362 BG.EnableMath = !sub && (Memory.FillRAM[0x2131] & (1 << n)); \
363 BG.TileSizeH = (!hires && PPU.BG[n].BGSize) ? 16 : 8; \
364 BG.TileSizeV = (PPU.BG[n].BGSize) ? 16 : 8; \
365 S9xSelectTileConverter(depth, hires, sub, PPU.BGMosaic[n]); \
366 \
367 if (offset) \
368 { \
369 BG.OffsetSizeH = (!hires && PPU.BG[2].BGSize) ? 16 : 8; \
370 BG.OffsetSizeV = (PPU.BG[2].BGSize) ? 16 : 8; \
371 \
372 if (PPU.BGMosaic[n] && (hires || PPU.Mosaic > 1)) \
373 DrawBackgroundOffsetMosaic(n, D + Zh, D + Zl, voffoff); \
374 else \
375 DrawBackgroundOffset(n, D + Zh, D + Zl, voffoff); \
376 } \
377 else \
378 { \
379 if (PPU.BGMosaic[n] && (hires || PPU.Mosaic > 1)) \
380 DrawBackgroundMosaic(n, D + Zh, D + Zl); \
381 else \
382 DrawBackground(n, D + Zh, D + Zl); \
383 } \
384 }
385
386 switch (PPU.BGMode)
387 {
388 case 0:
389 DO_BG(0, 0, 2, FALSE, FALSE, 15, 11, 0);
390 DO_BG(1, 32, 2, FALSE, FALSE, 14, 10, 0);
391 DO_BG(2, 64, 2, FALSE, FALSE, 7, 3, 0);
392 DO_BG(3, 96, 2, FALSE, FALSE, 6, 2, 0);
393 break;
394
395 case 1:
396 DO_BG(0, 0, 4, FALSE, FALSE, 15, 11, 0);
397 DO_BG(1, 0, 4, FALSE, FALSE, 14, 10, 0);
398 DO_BG(2, 0, 2, FALSE, FALSE, (PPU.BG3Priority ? 17 : 7), 3, 0);
399 break;
400
401 case 2:
402 DO_BG(0, 0, 4, FALSE, TRUE, 15, 7, 8);
403 DO_BG(1, 0, 4, FALSE, TRUE, 11, 3, 8);
404 break;
405
406 case 3:
407 DO_BG(0, 0, 8, FALSE, FALSE, 15, 7, 0);
408 DO_BG(1, 0, 4, FALSE, FALSE, 11, 3, 0);
409 break;
410
411 case 4:
412 DO_BG(0, 0, 8, FALSE, TRUE, 15, 7, 0);
413 DO_BG(1, 0, 2, FALSE, TRUE, 11, 3, 0);
414 break;
415
416 case 5:
417 DO_BG(0, 0, 4, TRUE, FALSE, 15, 7, 0);
418 DO_BG(1, 0, 2, TRUE, FALSE, 11, 3, 0);
419 break;
420
421 case 6:
422 DO_BG(0, 0, 4, TRUE, TRUE, 15, 7, 8);
423 break;
424
425 case 7:
426 if (BGActive & 0x01)
427 {
428 BG.EnableMath = !sub && (Memory.FillRAM[0x2131] & 1);
429 DrawBackgroundMode7(0, GFX.DrawMode7BG1Math, GFX.DrawMode7BG1Nomath, D);
430 }
431
432 if ((Memory.FillRAM[0x2133] & 0x40) && (BGActive & 0x02))
433 {
434 BG.EnableMath = !sub && (Memory.FillRAM[0x2131] & 2);
435 DrawBackgroundMode7(1, GFX.DrawMode7BG2Math, GFX.DrawMode7BG2Nomath, D);
436 }
437
438 break;
439 }
440
441 #undef DO_BG
442
443 BG.EnableMath = !sub && (Memory.FillRAM[0x2131] & 0x20);
444
445 DrawBackdrop();
446}
447
448void S9xUpdateScreen (void)
449{
450 if (IPPU.OBJChanged || IPPU.InterlaceOBJ)
451 SetupOBJ();
452
453 // XXX: Check ForceBlank? Or anything else?
454 PPU.RangeTimeOver |= GFX.OBJLines[GFX.EndY].RTOFlags;
455
456 GFX.StartY = IPPU.PreviousLine;
457 if ((GFX.EndY = IPPU.CurrentLine - 1) >= PPU.ScreenHeight)
458 GFX.EndY = PPU.ScreenHeight - 1;
459
460 if (!PPU.ForcedBlanking)
461 {
462 // If force blank, may as well completely skip all this. We only did
463 // the OBJ because (AFAWK) the RTO flags are updated even during force-blank.
464
465 if (PPU.RecomputeClipWindows)
466 {
467 S9xComputeClipWindows();
468 PPU.RecomputeClipWindows = FALSE;
469 }
470
471 if (Settings.SupportHiRes)
472 {
473 if (!IPPU.DoubleWidthPixels && (PPU.BGMode == 5 || PPU.BGMode == 6 || IPPU.PseudoHires))
474 {
475 #ifdef USE_OPENGL
476 if (Settings.OpenGLEnable && GFX.RealPPL == 256)
477 {
478 // Have to back out of the speed up hack where the low res.
479 // SNES image was rendered into a 256x239 sized buffer,
480 // ignoring the true, larger size of the buffer.
481 GFX.RealPPL = GFX.Pitch >> 1;
482
483 for (int32 y = (int32) GFX.StartY - 1; y >= 0; y--)
484 {
485 uint16 *p = GFX.Screen + y * GFX.PPL + 255;
486 uint16 *q = GFX.Screen + y * GFX.RealPPL + 510;
487
488 for (int x = 255; x >= 0; x--, p--, q -= 2)
489 *q = *(q + 1) = *p;
490 }
491
492 GFX.PPL = GFX.RealPPL; // = GFX.Pitch >> 1 above
493 }
494 else
495 #endif
496 // Have to back out of the regular speed hack
497 for (uint32 y = 0; y < GFX.StartY; y++)
498 {
499 uint16 *p = GFX.Screen + y * GFX.PPL + 255;
500 uint16 *q = GFX.Screen + y * GFX.PPL + 510;
501
502 for (int x = 255; x >= 0; x--, p--, q -= 2)
503 *q = *(q + 1) = *p;
504 }
505
506 IPPU.DoubleWidthPixels = TRUE;
507 IPPU.RenderedScreenWidth = 512;
508 }
509
510 if (!IPPU.DoubleHeightPixels && IPPU.Interlace && (PPU.BGMode == 5 || PPU.BGMode == 6))
511 {
512 IPPU.DoubleHeightPixels = TRUE;
513 IPPU.RenderedScreenHeight = PPU.ScreenHeight << 1;
514 GFX.PPL = GFX.RealPPL << 1;
515 GFX.DoInterlace = 2;
516
517 for (int32 y = (int32) GFX.StartY - 2; y >= 0; y--)
518 memmove(GFX.Screen + (y + 1) * GFX.PPL, GFX.Screen + y * GFX.RealPPL, GFX.PPL * sizeof(uint16));
519 }
520 }
521
522 if ((Memory.FillRAM[0x2130] & 0x30) != 0x30 && (Memory.FillRAM[0x2131] & 0x3f))
523 GFX.FixedColour = BUILD_PIXEL(IPPU.XB[PPU.FixedColourRed], IPPU.XB[PPU.FixedColourGreen], IPPU.XB[PPU.FixedColourBlue]);
524
525 if (PPU.BGMode == 5 || PPU.BGMode == 6 || IPPU.PseudoHires ||
526 ((Memory.FillRAM[0x2130] & 0x30) != 0x30 && (Memory.FillRAM[0x2130] & 2) && (Memory.FillRAM[0x2131] & 0x3f) && (Memory.FillRAM[0x212d] & 0x1f)))
527 // If hires (Mode 5/6 or pseudo-hires) or math is to be done
528 // involving the subscreen, then we need to render the subscreen...
529 RenderScreen(TRUE);
530
531 RenderScreen(FALSE);
532 }
533 else
534 {
535 const uint16 black = BUILD_PIXEL(0, 0, 0);
536
537 GFX.S = GFX.Screen + GFX.StartY * GFX.PPL;
538 if (GFX.DoInterlace && GFX.InterlaceFrame)
539 GFX.S += GFX.RealPPL;
540
541 for (uint32 l = GFX.StartY; l <= GFX.EndY; l++, GFX.S += GFX.PPL)
542 for (int x = 0; x < IPPU.RenderedScreenWidth; x++)
543 GFX.S[x] = black;
544 }
545
546 IPPU.PreviousLine = IPPU.CurrentLine;
547}
548
549static void SetupOBJ (void)
550{
551 int SmallWidth, SmallHeight, LargeWidth, LargeHeight;
552
553 switch (PPU.OBJSizeSelect)
554 {
555 case 0:
556 SmallWidth = SmallHeight = 8;
557 LargeWidth = LargeHeight = 16;
558 break;
559
560 case 1:
561 SmallWidth = SmallHeight = 8;
562 LargeWidth = LargeHeight = 32;
563 break;
564
565 case 2:
566 SmallWidth = SmallHeight = 8;
567 LargeWidth = LargeHeight = 64;
568 break;
569
570 case 3:
571 SmallWidth = SmallHeight = 16;
572 LargeWidth = LargeHeight = 32;
573 break;
574
575 case 4:
576 SmallWidth = SmallHeight = 16;
577 LargeWidth = LargeHeight = 64;
578 break;
579
580 case 5:
581 default:
582 SmallWidth = SmallHeight = 32;
583 LargeWidth = LargeHeight = 64;
584 break;
585
586 case 6:
587 SmallWidth = 16; SmallHeight = 32;
588 LargeWidth = 32; LargeHeight = 64;
589 break;
590
591 case 7:
592 SmallWidth = 16; SmallHeight = 32;
593 LargeWidth = LargeHeight = 32;
594 break;
595 }
596
597 int inc = IPPU.InterlaceOBJ ? 2 : 1;
598
599 int startline = (IPPU.InterlaceOBJ && GFX.InterlaceFrame) ? 1 : 0;
600
601 // OK, we have three cases here. Either there's no priority, priority is
602 // normal FirstSprite, or priority is FirstSprite+Y. The first two are
603 // easy, the last is somewhat more ... interesting. So we split them up.
604
605 int Height;
606 uint8 S;
607
608 if (!PPU.OAMPriorityRotation || !(PPU.OAMFlip & PPU.OAMAddr & 1)) // normal case
609 {
610 uint8 LineOBJ[SNES_HEIGHT_EXTENDED];
611 memset(LineOBJ, 0, sizeof(LineOBJ));
612
613 for (int i = 0; i < SNES_HEIGHT_EXTENDED; i++)
614 {
615 GFX.OBJLines[i].RTOFlags = 0;
616 GFX.OBJLines[i].Tiles = Settings.MaxSpriteTilesPerLine;
617 for (int j = 0; j < 32; j++)
618 GFX.OBJLines[i].OBJ[j].Sprite = -1;
619 }
620
621 uint8 FirstSprite = PPU.FirstSprite;
622 S = FirstSprite;
623
624 do
625 {
626 if (PPU.OBJ[S].Size)
627 {
628 GFX.OBJWidths[S] = LargeWidth;
629 Height = LargeHeight;
630 }
631 else
632 {
633 GFX.OBJWidths[S] = SmallWidth;
634 Height = SmallHeight;
635 }
636
637 int HPos = PPU.OBJ[S].HPos;
638 if (HPos == -256)
639 HPos = 0;
640
641 if (HPos > -GFX.OBJWidths[S] && HPos <= 256)
642 {
643 if (HPos < 0)
644 GFX.OBJVisibleTiles[S] = (GFX.OBJWidths[S] + HPos + 7) >> 3;
645 else
646 if (HPos + GFX.OBJWidths[S] > 255)
647 GFX.OBJVisibleTiles[S] = (256 - HPos + 7) >> 3;
648 else
649 GFX.OBJVisibleTiles[S] = GFX.OBJWidths[S] >> 3;
650
651 for (uint8 line = startline, Y = (uint8) (PPU.OBJ[S].VPos & 0xff); line < Height; Y++, line += inc)
652 {
653 if (Y >= SNES_HEIGHT_EXTENDED)
654 continue;
655
656 if (LineOBJ[Y] >= 32)
657 {
658 GFX.OBJLines[Y].RTOFlags |= 0x40;
659 continue;
660 }
661
662 GFX.OBJLines[Y].Tiles -= GFX.OBJVisibleTiles[S];
663 if (GFX.OBJLines[Y].Tiles < 0)
664 GFX.OBJLines[Y].RTOFlags |= 0x80;
665
666 GFX.OBJLines[Y].OBJ[LineOBJ[Y]].Sprite = S;
667 if (PPU.OBJ[S].VFlip)
668 // Yes, Width not Height. It so happens that the
669 // sprites with H=2*W flip as two WxW sprites.
670 GFX.OBJLines[Y].OBJ[LineOBJ[Y]].Line = line ^ (GFX.OBJWidths[S] - 1);
671 else
672 GFX.OBJLines[Y].OBJ[LineOBJ[Y]].Line = line;
673
674 LineOBJ[Y]++;
675 }
676 }
677
678 S = (S + 1) & 0x7f;
679 } while (S != FirstSprite);
680
681 for (int Y = 1; Y < SNES_HEIGHT_EXTENDED; Y++)
682 GFX.OBJLines[Y].RTOFlags |= GFX.OBJLines[Y - 1].RTOFlags;
683 }
684 else // evil FirstSprite+Y case
685 {
686 // First, find out which sprites are on which lines
687 uint8 OBJOnLine[SNES_HEIGHT_EXTENDED][128];
688 // memset(OBJOnLine, 0, sizeof(OBJOnLine));
689 /* Hold on here, that's a lot of bytes to initialise at once!
690 * So we only initialise them per line, as needed. [Neb]
691 * Bonus: We can quickly avoid looping if a line has no OBJs.
692 */
693 bool8 AnyOBJOnLine[SNES_HEIGHT_EXTENDED];
694 memset(AnyOBJOnLine, FALSE, sizeof(AnyOBJOnLine)); // better
695
696 for (S = 0; S < 128; S++)
697 {
698 if (PPU.OBJ[S].Size)
699 {
700 GFX.OBJWidths[S] = LargeWidth;
701 Height = LargeHeight;
702 }
703 else
704 {
705 GFX.OBJWidths[S] = SmallWidth;
706 Height = SmallHeight;
707 }
708
709 int HPos = PPU.OBJ[S].HPos;
710 if (HPos == -256)
711 HPos = 256;
712
713 if (HPos > -GFX.OBJWidths[S] && HPos <= 256)
714 {
715 if (HPos < 0)
716 GFX.OBJVisibleTiles[S] = (GFX.OBJWidths[S] + HPos + 7) >> 3;
717 else
718 if (HPos + GFX.OBJWidths[S] >= 257)
719 GFX.OBJVisibleTiles[S] = (257 - HPos + 7) >> 3;
720 else
721 GFX.OBJVisibleTiles[S] = GFX.OBJWidths[S] >> 3;
722
723 for (uint8 line = startline, Y = (uint8) (PPU.OBJ[S].VPos & 0xff); line < Height; Y++, line += inc)
724 {
725 if (Y >= SNES_HEIGHT_EXTENDED)
726 continue;
727
728 if (!AnyOBJOnLine[Y]) {
729 memset(OBJOnLine[Y], 0, sizeof(OBJOnLine[Y]));
730 AnyOBJOnLine[Y] = TRUE;
731 }
732
733 if (PPU.OBJ[S].VFlip)
734 // Yes, Width not Height. It so happens that the
735 // sprites with H=2*W flip as two WxW sprites.
736 OBJOnLine[Y][S] = (line ^ (GFX.OBJWidths[S] - 1)) | 0x80;
737 else
738 OBJOnLine[Y][S] = line | 0x80;
739 }
740 }
741 }
742
743 // Now go through and pull out those OBJ that are actually visible.
744 int j;
745 for (int Y = 0; Y < SNES_HEIGHT_EXTENDED; Y++)
746 {
747 GFX.OBJLines[Y].RTOFlags = Y ? GFX.OBJLines[Y - 1].RTOFlags : 0;
748 GFX.OBJLines[Y].Tiles = Settings.MaxSpriteTilesPerLine;
749
750 uint8 FirstSprite = (PPU.FirstSprite + Y) & 0x7f;
751 S = FirstSprite;
752 j = 0;
753
754 if (AnyOBJOnLine[Y])
755 {
756 do
757 {
758 if (OBJOnLine[Y][S])
759 {
760 if (j >= 32)
761 {
762 GFX.OBJLines[Y].RTOFlags |= 0x40;
763 break;
764 }
765
766 GFX.OBJLines[Y].Tiles -= GFX.OBJVisibleTiles[S];
767 if (GFX.OBJLines[Y].Tiles < 0)
768 GFX.OBJLines[Y].RTOFlags |= 0x80;
769 GFX.OBJLines[Y].OBJ[j].Sprite = S;
770 GFX.OBJLines[Y].OBJ[j++].Line = OBJOnLine[Y][S] & ~0x80;
771 }
772
773 S = (S + 1) & 0x7f;
774 } while (S != FirstSprite);
775 }
776
777 if (j < 32)
778 GFX.OBJLines[Y].OBJ[j].Sprite = -1;
779 }
780 }
781
782 IPPU.OBJChanged = FALSE;
783}
784
785#if defined(__GNUC__) && !defined(__clang__)
786#pragma GCC push_options
787#pragma GCC optimize ("no-tree-vrp")
788#endif
789static void DrawOBJS (int D)
790{
791 void (*DrawTile) (uint32, uint32, uint32, uint32) = NULL;
792 void (*DrawClippedTile) (uint32, uint32, uint32, uint32, uint32, uint32) = NULL;
793
794 int PixWidth = IPPU.DoubleWidthPixels ? 2 : 1;
795 BG.InterlaceLine = GFX.InterlaceFrame ? 8 : 0;
796 GFX.Z1 = 2;
797
798 for (uint32 Y = GFX.StartY, Offset = Y * GFX.PPL; Y <= GFX.EndY; Y++, Offset += GFX.PPL)
799 {
800 int I = 0;
801 int tiles = GFX.OBJLines[Y].Tiles;
802
803 for (int S = GFX.OBJLines[Y].OBJ[I].Sprite; S >= 0 && I < 32; S = GFX.OBJLines[Y].OBJ[++I].Sprite)
804 {
805 tiles += GFX.OBJVisibleTiles[S];
806 if (tiles <= 0)
807 continue;
808
809 int BaseTile = (((GFX.OBJLines[Y].OBJ[I].Line << 1) + (PPU.OBJ[S].Name & 0xf0)) & 0xf0) | (PPU.OBJ[S].Name & 0x100) | (PPU.OBJ[S].Palette << 10);
810 int TileX = PPU.OBJ[S].Name & 0x0f;
811 int TileLine = (GFX.OBJLines[Y].OBJ[I].Line & 7) * 8;
812 int TileInc = 1;
813
814 if (PPU.OBJ[S].HFlip)
815 {
816 TileX = (TileX + (GFX.OBJWidths[S] >> 3) - 1) & 0x0f;
817 BaseTile |= H_FLIP;
818 TileInc = -1;
819 }
820
821 GFX.Z2 = D + PPU.OBJ[S].Priority * 4;
822
823 int DrawMode = 3;
824 int clip = 0, next_clip = -1000;
825 int X = PPU.OBJ[S].HPos;
826 if (X == -256)
827 X = 256;
828
829 for (int t = tiles, O = Offset + X * PixWidth; X <= 256 && X < PPU.OBJ[S].HPos + GFX.OBJWidths[S]; TileX = (TileX + TileInc) & 0x0f, X += 8, O += 8 * PixWidth)
830 {
831 if (X < -7 || --t < 0 || X == 256)
832 continue;
833
834 for (int x = X; x < X + 8;)
835 {
836 if (x >= next_clip)
837 {
838 for (; clip < GFX.Clip[4].Count && GFX.Clip[4].Left[clip] <= x; clip++) ;
839 if (clip == 0 || x >= GFX.Clip[4].Right[clip - 1])
840 {
841 DrawMode = 0;
842 next_clip = ((clip < GFX.Clip[4].Count) ? GFX.Clip[4].Left[clip] : 1000);
843 }
844 else
845 {
846 DrawMode = GFX.Clip[4].DrawMode[clip - 1];
847 next_clip = GFX.Clip[4].Right[clip - 1];
848 GFX.ClipColors = !(DrawMode & 1);
849
850 if (BG.EnableMath && (PPU.OBJ[S].Palette & 4) && (DrawMode & 2))
851 {
852 DrawTile = GFX.DrawTileMath;
853 DrawClippedTile = GFX.DrawClippedTileMath;
854 }
855 else
856 {
857 DrawTile = GFX.DrawTileNomath;
858 DrawClippedTile = GFX.DrawClippedTileNomath;
859 }
860 }
861 }
862
863 if (x == X && x + 8 < next_clip)
864 {
865 if (DrawMode)
866 DrawTile(BaseTile | TileX, O, TileLine, 1);
867 x += 8;
868 }
869 else
870 {
871 int w = (next_clip <= X + 8) ? next_clip - x : X + 8 - x;
872 if (DrawMode)
873 DrawClippedTile(BaseTile | TileX, O, x - X, w, TileLine, 1);
874 x += w;
875 }
876 }
877 }
878 }
879 }
880}
881#if defined(__GNUC__) && !defined(__clang__)
882#pragma GCC pop_options
883#endif
884
885static void DrawBackground (int bg, uint8 Zh, uint8 Zl)
886{
887 BG.TileAddress = PPU.BG[bg].NameBase << 1;
888
889 uint32 Tile;
890 uint16 *SC0, *SC1, *SC2, *SC3;
891
892 SC0 = (uint16 *) &Memory.VRAM[PPU.BG[bg].SCBase << 1];
893 SC1 = (PPU.BG[bg].SCSize & 1) ? SC0 + 1024 : SC0;
894 if (SC1 >= (uint16 *) (Memory.VRAM + 0x10000))
895 SC1 -= 0x8000;
896 SC2 = (PPU.BG[bg].SCSize & 2) ? SC1 + 1024 : SC0;
897 if (SC2 >= (uint16 *) (Memory.VRAM + 0x10000))
898 SC2 -= 0x8000;
899 SC3 = (PPU.BG[bg].SCSize & 1) ? SC2 + 1024 : SC2;
900 if (SC3 >= (uint16 *) (Memory.VRAM + 0x10000))
901 SC3 -= 0x8000;
902
903 uint32 Lines;
904 int OffsetMask = (BG.TileSizeH == 16) ? 0x3ff : 0x1ff;
905 int OffsetShift = (BG.TileSizeV == 16) ? 4 : 3;
906 int PixWidth = IPPU.DoubleWidthPixels ? 2 : 1;
907 bool8 HiresInterlace = IPPU.Interlace && IPPU.DoubleWidthPixels;
908
909 void (*DrawTile) (uint32, uint32, uint32, uint32);
910 void (*DrawClippedTile) (uint32, uint32, uint32, uint32, uint32, uint32);
911
912 for (int clip = 0; clip < GFX.Clip[bg].Count; clip++)
913 {
914 GFX.ClipColors = !(GFX.Clip[bg].DrawMode[clip] & 1);
915
916 if (BG.EnableMath && (GFX.Clip[bg].DrawMode[clip] & 2))
917 {
918 DrawTile = GFX.DrawTileMath;
919 DrawClippedTile = GFX.DrawClippedTileMath;
920 }
921 else
922 {
923 DrawTile = GFX.DrawTileNomath;
924 DrawClippedTile = GFX.DrawClippedTileNomath;
925 }
926
927 for (uint32 Y = GFX.StartY; Y <= GFX.EndY; Y += Lines)
928 {
929 uint32 Y2 = HiresInterlace ? Y * 2 + GFX.InterlaceFrame : Y;
930 uint32 VOffset = LineData[Y].BG[bg].VOffset + (HiresInterlace ? 1 : 0);
931 uint32 HOffset = LineData[Y].BG[bg].HOffset;
932 int VirtAlign = ((Y2 + VOffset) & 7) >> (HiresInterlace ? 1 : 0);
933
934 for (Lines = 1; Lines < GFX.LinesPerTile - VirtAlign; Lines++)
935 {
936 if ((VOffset != LineData[Y + Lines].BG[bg].VOffset) || (HOffset != LineData[Y + Lines].BG[bg].HOffset))
937 break;
938 }
939
940 if (Y + Lines > GFX.EndY)
941 Lines = GFX.EndY - Y + 1;
942
943 VirtAlign <<= 3;
944
945 uint32 t1, t2;
946 uint32 TilemapRow = (VOffset + Y2) >> OffsetShift;
947 BG.InterlaceLine = ((VOffset + Y2) & 1) << 3;
948
949 if ((VOffset + Y2) & 8)
950 {
951 t1 = 16;
952 t2 = 0;
953 }
954 else
955 {
956 t1 = 0;
957 t2 = 16;
958 }
959
960 uint16 *b1, *b2;
961
962 if (TilemapRow & 0x20)
963 {
964 b1 = SC2;
965 b2 = SC3;
966 }
967 else
968 {
969 b1 = SC0;
970 b2 = SC1;
971 }
972
973 b1 += (TilemapRow & 0x1f) << 5;
974 b2 += (TilemapRow & 0x1f) << 5;
975
976 uint32 Left = GFX.Clip[bg].Left[clip];
977 uint32 Right = GFX.Clip[bg].Right[clip];
978 uint32 Offset = Left * PixWidth + Y * GFX.PPL;
979 uint32 HPos = (HOffset + Left) & OffsetMask;
980 uint32 HTile = HPos >> 3;
981 uint16 *t;
982
983 if (BG.TileSizeH == 8)
984 {
985 if (HTile > 31)
986 t = b2 + (HTile & 0x1f);
987 else
988 t = b1 + HTile;
989 }
990 else
991 {
992 if (HTile > 63)
993 t = b2 + ((HTile >> 1) & 0x1f);
994 else
995 t = b1 + (HTile >> 1);
996 }
997
998 uint32 Width = Right - Left;
999
1000 if (HPos & 7)
1001 {
1002 uint32 l = HPos & 7;
1003 uint32 w = 8 - l;
1004 if (w > Width)
1005 w = Width;
1006
1007 Offset -= l * PixWidth;
1008 Tile = READ_WORD(t);
1009 GFX.Z1 = GFX.Z2 = (Tile & 0x2000) ? Zh : Zl;
1010
1011 if (BG.TileSizeV == 16)
1012 Tile = TILE_PLUS(Tile, ((Tile & V_FLIP) ? t2 : t1));
1013
1014 if (BG.TileSizeH == 8)
1015 {
1016 DrawClippedTile(Tile, Offset, l, w, VirtAlign, Lines);
1017 t++;
1018 if (HTile == 31)
1019 t = b2;
1020 else
1021 if (HTile == 63)
1022 t = b1;
1023 }
1024 else
1025 {
1026 if (!(Tile & H_FLIP))
1027 DrawClippedTile(TILE_PLUS(Tile, (HTile & 1)), Offset, l, w, VirtAlign, Lines);
1028 else
1029 DrawClippedTile(TILE_PLUS(Tile, 1 - (HTile & 1)), Offset, l, w, VirtAlign, Lines);
1030 t += HTile & 1;
1031 if (HTile == 63)
1032 t = b2;
1033 else
1034 if (HTile == 127)
1035 t = b1;
1036 }
1037
1038 HTile++;
1039 Offset += 8 * PixWidth;
1040 Width -= w;
1041 }
1042
1043 while (Width >= 8)
1044 {
1045 Tile = READ_WORD(t);
1046 GFX.Z1 = GFX.Z2 = (Tile & 0x2000) ? Zh : Zl;
1047
1048 if (BG.TileSizeV == 16)
1049 Tile = TILE_PLUS(Tile, ((Tile & V_FLIP) ? t2 : t1));
1050
1051 if (BG.TileSizeH == 8)
1052 {
1053 DrawTile(Tile, Offset, VirtAlign, Lines);
1054 t++;
1055 if (HTile == 31)
1056 t = b2;
1057 else
1058 if (HTile == 63)
1059 t = b1;
1060 }
1061 else
1062 {
1063 if (!(Tile & H_FLIP))
1064 DrawTile(TILE_PLUS(Tile, (HTile & 1)), Offset, VirtAlign, Lines);
1065 else
1066 DrawTile(TILE_PLUS(Tile, 1 - (HTile & 1)), Offset, VirtAlign, Lines);
1067 t += HTile & 1;
1068 if (HTile == 63)
1069 t = b2;
1070 else
1071 if (HTile == 127)
1072 t = b1;
1073 }
1074
1075 HTile++;
1076 Offset += 8 * PixWidth;
1077 Width -= 8;
1078 }
1079
1080 if (Width)
1081 {
1082 Tile = READ_WORD(t);
1083 GFX.Z1 = GFX.Z2 = (Tile & 0x2000) ? Zh : Zl;
1084
1085 if (BG.TileSizeV == 16)
1086 Tile = TILE_PLUS(Tile, ((Tile & V_FLIP) ? t2 : t1));
1087
1088 if (BG.TileSizeH == 8)
1089 DrawClippedTile(Tile, Offset, 0, Width, VirtAlign, Lines);
1090 else
1091 {
1092 if (!(Tile & H_FLIP))
1093 DrawClippedTile(TILE_PLUS(Tile, (HTile & 1)), Offset, 0, Width, VirtAlign, Lines);
1094 else
1095 DrawClippedTile(TILE_PLUS(Tile, 1 - (HTile & 1)), Offset, 0, Width, VirtAlign, Lines);
1096 }
1097 }
1098 }
1099 }
1100}
1101
1102static void DrawBackgroundMosaic (int bg, uint8 Zh, uint8 Zl)
1103{
1104 BG.TileAddress = PPU.BG[bg].NameBase << 1;
1105
1106 uint32 Tile;
1107 uint16 *SC0, *SC1, *SC2, *SC3;
1108
1109 SC0 = (uint16 *) &Memory.VRAM[PPU.BG[bg].SCBase << 1];
1110 SC1 = (PPU.BG[bg].SCSize & 1) ? SC0 + 1024 : SC0;
1111 if (SC1 >= (uint16 *) (Memory.VRAM + 0x10000))
1112 SC1 -= 0x8000;
1113 SC2 = (PPU.BG[bg].SCSize & 2) ? SC1 + 1024 : SC0;
1114 if (SC2 >= (uint16 *) (Memory.VRAM + 0x10000))
1115 SC2 -= 0x8000;
1116 SC3 = (PPU.BG[bg].SCSize & 1) ? SC2 + 1024 : SC2;
1117 if (SC3 >= (uint16 *) (Memory.VRAM + 0x10000))
1118 SC3 -= 0x8000;
1119
1120 int Lines;
1121 int OffsetMask = (BG.TileSizeH == 16) ? 0x3ff : 0x1ff;
1122 int OffsetShift = (BG.TileSizeV == 16) ? 4 : 3;
1123 int PixWidth = IPPU.DoubleWidthPixels ? 2 : 1;
1124 bool8 HiresInterlace = IPPU.Interlace && IPPU.DoubleWidthPixels;
1125
1126 void (*DrawPix) (uint32, uint32, uint32, uint32, uint32, uint32);
1127
1128 int MosaicStart = ((uint32) GFX.StartY - PPU.MosaicStart) % PPU.Mosaic;
1129
1130 for (int clip = 0; clip < GFX.Clip[bg].Count; clip++)
1131 {
1132 GFX.ClipColors = !(GFX.Clip[bg].DrawMode[clip] & 1);
1133
1134 if (BG.EnableMath && (GFX.Clip[bg].DrawMode[clip] & 2))
1135 DrawPix = GFX.DrawMosaicPixelMath;
1136 else
1137 DrawPix = GFX.DrawMosaicPixelNomath;
1138
1139 for (uint32 Y = GFX.StartY - MosaicStart; Y <= GFX.EndY; Y += PPU.Mosaic)
1140 {
1141 uint32 Y2 = HiresInterlace ? Y * 2 : Y;
1142 uint32 VOffset = LineData[Y + MosaicStart].BG[bg].VOffset + (HiresInterlace ? 1 : 0);
1143 uint32 HOffset = LineData[Y + MosaicStart].BG[bg].HOffset;
1144
1145 Lines = PPU.Mosaic - MosaicStart;
1146 if (Y + MosaicStart + Lines > GFX.EndY)
1147 Lines = GFX.EndY - Y - MosaicStart + 1;
1148
1149 int VirtAlign = (((Y2 + VOffset) & 7) >> (HiresInterlace ? 1 : 0)) << 3;
1150
1151 uint32 t1, t2;
1152 uint32 TilemapRow = (VOffset + Y2) >> OffsetShift;
1153 BG.InterlaceLine = ((VOffset + Y2) & 1) << 3;
1154
1155 if ((VOffset + Y2) & 8)
1156 {
1157 t1 = 16;
1158 t2 = 0;
1159 }
1160 else
1161 {
1162 t1 = 0;
1163 t2 = 16;
1164 }
1165
1166 uint16 *b1, *b2;
1167
1168 if (TilemapRow & 0x20)
1169 {
1170 b1 = SC2;
1171 b2 = SC3;
1172 }
1173 else
1174 {
1175 b1 = SC0;
1176 b2 = SC1;
1177 }
1178
1179 b1 += (TilemapRow & 0x1f) << 5;
1180 b2 += (TilemapRow & 0x1f) << 5;
1181
1182 uint32 Left = GFX.Clip[bg].Left[clip];
1183 uint32 Right = GFX.Clip[bg].Right[clip];
1184 uint32 Offset = Left * PixWidth + (Y + MosaicStart) * GFX.PPL;
1185 uint32 HPos = (HOffset + Left - (Left % PPU.Mosaic)) & OffsetMask;
1186 uint32 HTile = HPos >> 3;
1187 uint16 *t;
1188
1189 if (BG.TileSizeH == 8)
1190 {
1191 if (HTile > 31)
1192 t = b2 + (HTile & 0x1f);
1193 else
1194 t = b1 + HTile;
1195 }
1196 else
1197 {
1198 if (HTile > 63)
1199 t = b2 + ((HTile >> 1) & 0x1f);
1200 else
1201 t = b1 + (HTile >> 1);
1202 }
1203
1204 uint32 Width = Right - Left;
1205
1206 HPos &= 7;
1207
1208 while (Left < Right)
1209 {
1210 uint32 w = PPU.Mosaic - (Left % PPU.Mosaic);
1211 if (w > Width)
1212 w = Width;
1213
1214 Tile = READ_WORD(t);
1215 GFX.Z1 = GFX.Z2 = (Tile & 0x2000) ? Zh : Zl;
1216
1217 if (BG.TileSizeV == 16)
1218 Tile = TILE_PLUS(Tile, ((Tile & V_FLIP) ? t2 : t1));
1219
1220 if (BG.TileSizeH == 8)
1221 DrawPix(Tile, Offset, VirtAlign, HPos & 7, w, Lines);
1222 else
1223 {
1224 if (!(Tile & H_FLIP))
1225 DrawPix(TILE_PLUS(Tile, (HTile & 1)), Offset, VirtAlign, HPos & 7, w, Lines);
1226 else
1227 DrawPix(TILE_PLUS(Tile, 1 - (HTile & 1)), Offset, VirtAlign, HPos & 7, w, Lines);
1228 }
1229
1230 HPos += PPU.Mosaic;
1231
1232 while (HPos >= 8)
1233 {
1234 HPos -= 8;
1235
1236 if (BG.TileSizeH == 8)
1237 {
1238 t++;
1239 if (HTile == 31)
1240 t = b2;
1241 else
1242 if (HTile == 63)
1243 t = b1;
1244 }
1245 else
1246 {
1247 t += HTile & 1;
1248 if (HTile == 63)
1249 t = b2;
1250 else
1251 if (HTile == 127)
1252 t = b1;
1253 }
1254
1255 HTile++;
1256 }
1257
1258 Offset += w * PixWidth;
1259 Width -= w;
1260 Left += w;
1261 }
1262
1263 MosaicStart = 0;
1264 }
1265 }
1266}
1267
1268static void DrawBackgroundOffset (int bg, uint8 Zh, uint8 Zl, int VOffOff)
1269{
1270 BG.TileAddress = PPU.BG[bg].NameBase << 1;
1271
1272 uint32 Tile;
1273 uint16 *SC0, *SC1, *SC2, *SC3;
1274 uint16 *BPS0, *BPS1, *BPS2, *BPS3;
1275
1276 BPS0 = (uint16 *) &Memory.VRAM[PPU.BG[2].SCBase << 1];
1277 BPS1 = (PPU.BG[2].SCSize & 1) ? BPS0 + 1024 : BPS0;
1278 if (BPS1 >= (uint16 *) (Memory.VRAM + 0x10000))
1279 BPS1 -= 0x8000;
1280 BPS2 = (PPU.BG[2].SCSize & 2) ? BPS1 + 1024 : BPS0;
1281 if (BPS2 >= (uint16 *) (Memory.VRAM + 0x10000))
1282 BPS2 -= 0x8000;
1283 BPS3 = (PPU.BG[2].SCSize & 1) ? BPS2 + 1024 : BPS2;
1284 if (BPS3 >= (uint16 *) (Memory.VRAM + 0x10000))
1285 BPS3 -= 0x8000;
1286
1287 SC0 = (uint16 *) &Memory.VRAM[PPU.BG[bg].SCBase << 1];
1288 SC1 = (PPU.BG[bg].SCSize & 1) ? SC0 + 1024 : SC0;
1289 if (SC1 >= (uint16 *) (Memory.VRAM + 0x10000))
1290 SC1 -= 0x8000;
1291 SC2 = (PPU.BG[bg].SCSize & 2) ? SC1 + 1024 : SC0;
1292 if (SC2 >= (uint16 *) (Memory.VRAM + 0x10000))
1293 SC2 -= 0x8000;
1294 SC3 = (PPU.BG[bg].SCSize & 1) ? SC2 + 1024 : SC2;
1295 if (SC3 >= (uint16 *) (Memory.VRAM + 0x10000))
1296 SC3 -= 0x8000;
1297
1298 int OffsetMask = (BG.TileSizeH == 16) ? 0x3ff : 0x1ff;
1299 int OffsetShift = (BG.TileSizeV == 16) ? 4 : 3;
1300 int Offset2Mask = (BG.OffsetSizeH == 16) ? 0x3ff : 0x1ff;
1301 int Offset2Shift = (BG.OffsetSizeV == 16) ? 4 : 3;
1302 int OffsetEnableMask = 0x2000 << bg;
1303 int PixWidth = IPPU.DoubleWidthPixels ? 2 : 1;
1304 bool8 HiresInterlace = IPPU.Interlace && IPPU.DoubleWidthPixels;
1305
1306 void (*DrawClippedTile) (uint32, uint32, uint32, uint32, uint32, uint32);
1307
1308 for (int clip = 0; clip < GFX.Clip[bg].Count; clip++)
1309 {
1310 GFX.ClipColors = !(GFX.Clip[bg].DrawMode[clip] & 1);
1311
1312 if (BG.EnableMath && (GFX.Clip[bg].DrawMode[clip] & 2))
1313 {
1314 DrawClippedTile = GFX.DrawClippedTileMath;
1315 }
1316 else
1317 {
1318 DrawClippedTile = GFX.DrawClippedTileNomath;
1319 }
1320
1321 for (uint32 Y = GFX.StartY; Y <= GFX.EndY; Y++)
1322 {
1323 uint32 Y2 = HiresInterlace ? Y * 2 + GFX.InterlaceFrame : Y;
1324 uint32 VOff = LineData[Y].BG[2].VOffset - 1;
1325 uint32 HOff = LineData[Y].BG[2].HOffset;
1326 uint32 HOffsetRow = VOff >> Offset2Shift;
1327 uint32 VOffsetRow = (VOff + VOffOff) >> Offset2Shift;
1328 uint16 *s, *s1, *s2;
1329
1330 if (HOffsetRow & 0x20)
1331 {
1332 s1 = BPS2;
1333 s2 = BPS3;
1334 }
1335 else
1336 {
1337 s1 = BPS0;
1338 s2 = BPS1;
1339 }
1340
1341 s1 += (HOffsetRow & 0x1f) << 5;
1342 s2 += (HOffsetRow & 0x1f) << 5;
1343 s = ((VOffsetRow & 0x20) ? BPS2 : BPS0) + ((VOffsetRow & 0x1f) << 5);
1344 int32 VOffsetOffset = s - s1;
1345
1346 uint32 Left = GFX.Clip[bg].Left[clip];
1347 uint32 Right = GFX.Clip[bg].Right[clip];
1348 uint32 Offset = Left * PixWidth + Y * GFX.PPL;
1349 uint32 HScroll = LineData[Y].BG[bg].HOffset;
1350 bool8 left_edge = (Left < (8 - (HScroll & 7)));
1351 uint32 Width = Right - Left;
1352
1353 while (Left < Right)
1354 {
1355 uint32 VOffset, HOffset;
1356
1357 if (left_edge)
1358 {
1359 // SNES cannot do OPT for leftmost tile column
1360 VOffset = LineData[Y].BG[bg].VOffset;
1361 HOffset = HScroll;
1362 left_edge = FALSE;
1363 }
1364 else
1365 {
1366 int HOffTile = ((HOff + Left - 1) & Offset2Mask) >> 3;
1367
1368 if (BG.OffsetSizeH == 8)
1369 {
1370 if (HOffTile > 31)
1371 s = s2 + (HOffTile & 0x1f);
1372 else
1373 s = s1 + HOffTile;
1374 }
1375 else
1376 {
1377 if (HOffTile > 63)
1378 s = s2 + ((HOffTile >> 1) & 0x1f);
1379 else
1380 s = s1 + (HOffTile >> 1);
1381 }
1382
1383 uint16 HCellOffset = READ_WORD(s);
1384 uint16 VCellOffset;
1385
1386 if (VOffOff)
1387 VCellOffset = READ_WORD(s + VOffsetOffset);
1388 else
1389 {
1390 if (HCellOffset & 0x8000)
1391 {
1392 VCellOffset = HCellOffset;
1393 HCellOffset = 0;
1394 }
1395 else
1396 VCellOffset = 0;
1397 }
1398
1399 if (VCellOffset & OffsetEnableMask)
1400 VOffset = VCellOffset + 1;
1401 else
1402 VOffset = LineData[Y].BG[bg].VOffset;
1403
1404 if (HCellOffset & OffsetEnableMask)
1405 HOffset = (HCellOffset & ~7) | (HScroll & 7);
1406 else
1407 HOffset = HScroll;
1408 }
1409
1410 if (HiresInterlace)
1411 VOffset++;
1412
1413 uint32 t1, t2;
1414 int VirtAlign = (((Y2 + VOffset) & 7) >> (HiresInterlace ? 1 : 0)) << 3;
1415 int TilemapRow = (VOffset + Y2) >> OffsetShift;
1416 BG.InterlaceLine = ((VOffset + Y2) & 1) << 3;
1417
1418 if ((VOffset + Y2) & 8)
1419 {
1420 t1 = 16;
1421 t2 = 0;
1422 }
1423 else
1424 {
1425 t1 = 0;
1426 t2 = 16;
1427 }
1428
1429 uint16 *b1, *b2;
1430
1431 if (TilemapRow & 0x20)
1432 {
1433 b1 = SC2;
1434 b2 = SC3;
1435 }
1436 else
1437 {
1438 b1 = SC0;
1439 b2 = SC1;
1440 }
1441
1442 b1 += (TilemapRow & 0x1f) << 5;
1443 b2 += (TilemapRow & 0x1f) << 5;
1444
1445 uint32 HPos = (HOffset + Left) & OffsetMask;
1446 uint32 HTile = HPos >> 3;
1447 uint16 *t;
1448
1449 if (BG.TileSizeH == 8)
1450 {
1451 if (HTile > 31)
1452 t = b2 + (HTile & 0x1f);
1453 else
1454 t = b1 + HTile;
1455 }
1456 else
1457 {
1458 if (HTile > 63)
1459 t = b2 + ((HTile >> 1) & 0x1f);
1460 else
1461 t = b1 + (HTile >> 1);
1462 }
1463
1464 uint32 l = HPos & 7;
1465 uint32 w = 8 - l;
1466 if (w > Width)
1467 w = Width;
1468
1469 Offset -= l * PixWidth;
1470 Tile = READ_WORD(t);
1471 GFX.Z1 = GFX.Z2 = (Tile & 0x2000) ? Zh : Zl;
1472
1473 if (BG.TileSizeV == 16)
1474 Tile = TILE_PLUS(Tile, ((Tile & V_FLIP) ? t2 : t1));
1475
1476 if (BG.TileSizeH == 8)
1477 {
1478 DrawClippedTile(Tile, Offset, l, w, VirtAlign, 1);
1479 }
1480 else
1481 {
1482 if (!(Tile & H_FLIP))
1483 DrawClippedTile(TILE_PLUS(Tile, (HTile & 1)), Offset, l, w, VirtAlign, 1);
1484 else
1485 DrawClippedTile(TILE_PLUS(Tile, 1 - (HTile & 1)), Offset, l, w, VirtAlign, 1);
1486 }
1487
1488 Left += w;
1489 Offset += 8 * PixWidth;
1490 Width -= w;
1491 }
1492 }
1493 }
1494}
1495
1496static void DrawBackgroundOffsetMosaic (int bg, uint8 Zh, uint8 Zl, int VOffOff)
1497{
1498 BG.TileAddress = PPU.BG[bg].NameBase << 1;
1499
1500 uint32 Tile;
1501 uint16 *SC0, *SC1, *SC2, *SC3;
1502 uint16 *BPS0, *BPS1, *BPS2, *BPS3;
1503
1504 BPS0 = (uint16 *) &Memory.VRAM[PPU.BG[2].SCBase << 1];
1505 BPS1 = (PPU.BG[2].SCSize & 1) ? BPS0 + 1024 : BPS0;
1506 if (BPS1 >= (uint16 *) (Memory.VRAM + 0x10000))
1507 BPS1 -= 0x8000;
1508 BPS2 = (PPU.BG[2].SCSize & 2) ? BPS1 + 1024 : BPS0;
1509 if (BPS2 >= (uint16 *) (Memory.VRAM + 0x10000))
1510 BPS2 -= 0x8000;
1511 BPS3 = (PPU.BG[2].SCSize & 1) ? BPS2 + 1024 : BPS2;
1512 if (BPS3 >= (uint16 *) (Memory.VRAM + 0x10000))
1513 BPS3 -= 0x8000;
1514
1515 SC0 = (uint16 *) &Memory.VRAM[PPU.BG[bg].SCBase << 1];
1516 SC1 = (PPU.BG[bg].SCSize & 1) ? SC0 + 1024 : SC0;
1517 if (SC1 >= (uint16 *) (Memory.VRAM + 0x10000))
1518 SC1 -= 0x8000;
1519 SC2 = (PPU.BG[bg].SCSize & 2) ? SC1 + 1024 : SC0;
1520 if (SC2 >= (uint16 *) (Memory.VRAM + 0x10000))
1521 SC2 -= 0x8000;
1522 SC3 = (PPU.BG[bg].SCSize & 1) ? SC2 + 1024 : SC2;
1523 if (SC3 >= (uint16 *) (Memory.VRAM + 0x10000))
1524 SC3 -= 0x8000;
1525
1526 int Lines;
1527 int OffsetMask = (BG.TileSizeH == 16) ? 0x3ff : 0x1ff;
1528 int OffsetShift = (BG.TileSizeV == 16) ? 4 : 3;
1529 int Offset2Shift = (BG.OffsetSizeV == 16) ? 4 : 3;
1530 int OffsetEnableMask = 0x2000 << bg;
1531 int PixWidth = IPPU.DoubleWidthPixels ? 2 : 1;
1532 bool8 HiresInterlace = IPPU.Interlace && IPPU.DoubleWidthPixels;
1533
1534 void (*DrawPix) (uint32, uint32, uint32, uint32, uint32, uint32);
1535
1536 int MosaicStart = ((uint32) GFX.StartY - PPU.MosaicStart) % PPU.Mosaic;
1537
1538 for (int clip = 0; clip < GFX.Clip[bg].Count; clip++)
1539 {
1540 GFX.ClipColors = !(GFX.Clip[bg].DrawMode[clip] & 1);
1541
1542 if (BG.EnableMath && (GFX.Clip[bg].DrawMode[clip] & 2))
1543 DrawPix = GFX.DrawMosaicPixelMath;
1544 else
1545 DrawPix = GFX.DrawMosaicPixelNomath;
1546
1547 for (uint32 Y = GFX.StartY - MosaicStart; Y <= GFX.EndY; Y += PPU.Mosaic)
1548 {
1549 uint32 Y2 = HiresInterlace ? Y * 2 : Y;
1550 uint32 VOff = LineData[Y + MosaicStart].BG[2].VOffset - 1;
1551 uint32 HOff = LineData[Y + MosaicStart].BG[2].HOffset;
1552
1553 Lines = PPU.Mosaic - MosaicStart;
1554 if (Y + MosaicStart + Lines > GFX.EndY)
1555 Lines = GFX.EndY - Y - MosaicStart + 1;
1556
1557 uint32 HOffsetRow = VOff >> Offset2Shift;
1558 uint32 VOffsetRow = (VOff + VOffOff) >> Offset2Shift;
1559 uint16 *s, *s1, *s2;
1560
1561 if (HOffsetRow & 0x20)
1562 {
1563 s1 = BPS2;
1564 s2 = BPS3;
1565 }
1566 else
1567 {
1568 s1 = BPS0;
1569 s2 = BPS1;
1570 }
1571
1572 s1 += (HOffsetRow & 0x1f) << 5;
1573 s2 += (HOffsetRow & 0x1f) << 5;
1574 s = ((VOffsetRow & 0x20) ? BPS2 : BPS0) + ((VOffsetRow & 0x1f) << 5);
1575 int32 VOffsetOffset = s - s1;
1576
1577 uint32 Left = GFX.Clip[bg].Left[clip];
1578 uint32 Right = GFX.Clip[bg].Right[clip];
1579 uint32 Offset = Left * PixWidth + (Y + MosaicStart) * GFX.PPL;
1580 uint32 HScroll = LineData[Y + MosaicStart].BG[bg].HOffset;
1581 uint32 Width = Right - Left;
1582
1583 while (Left < Right)
1584 {
1585 uint32 VOffset, HOffset;
1586
1587 if (Left < (8 - (HScroll & 7)))
1588 {
1589 // SNES cannot do OPT for leftmost tile column
1590 VOffset = LineData[Y + MosaicStart].BG[bg].VOffset;
1591 HOffset = HScroll;
1592 }
1593 else
1594 {
1595 int HOffTile = (((Left + (HScroll & 7)) - 8) + (HOff & ~7)) >> 3;
1596
1597 if (BG.OffsetSizeH == 8)
1598 {
1599 if (HOffTile > 31)
1600 s = s2 + (HOffTile & 0x1f);
1601 else
1602 s = s1 + HOffTile;
1603 }
1604 else
1605 {
1606 if (HOffTile > 63)
1607 s = s2 + ((HOffTile >> 1) & 0x1f);
1608 else
1609 s = s1 + (HOffTile >> 1);
1610 }
1611
1612 uint16 HCellOffset = READ_WORD(s);
1613 uint16 VCellOffset;
1614
1615 if (VOffOff)
1616 VCellOffset = READ_WORD(s + VOffsetOffset);
1617 else
1618 {
1619 if (HCellOffset & 0x8000)
1620 {
1621 VCellOffset = HCellOffset;
1622 HCellOffset = 0;
1623 }
1624 else
1625 VCellOffset = 0;
1626 }
1627
1628 if (VCellOffset & OffsetEnableMask)
1629 VOffset = VCellOffset + 1;
1630 else
1631 VOffset = LineData[Y + MosaicStart].BG[bg].VOffset;
1632
1633 if (HCellOffset & OffsetEnableMask)
1634 HOffset = (HCellOffset & ~7) | (HScroll & 7);
1635 else
1636 HOffset = HScroll;
1637 }
1638
1639 if (HiresInterlace)
1640 VOffset++;
1641
1642 uint32 t1, t2;
1643 int VirtAlign = (((Y2 + VOffset) & 7) >> (HiresInterlace ? 1 : 0)) << 3;
1644 int TilemapRow = (VOffset + Y2) >> OffsetShift;
1645 BG.InterlaceLine = ((VOffset + Y2) & 1) << 3;
1646
1647 if ((VOffset + Y2) & 8)
1648 {
1649 t1 = 16;
1650 t2 = 0;
1651 }
1652 else
1653 {
1654 t1 = 0;
1655 t2 = 16;
1656 }
1657
1658 uint16 *b1, *b2;
1659
1660 if (TilemapRow & 0x20)
1661 {
1662 b1 = SC2;
1663 b2 = SC3;
1664 }
1665 else
1666 {
1667 b1 = SC0;
1668 b2 = SC1;
1669 }
1670
1671 b1 += (TilemapRow & 0x1f) << 5;
1672 b2 += (TilemapRow & 0x1f) << 5;
1673
1674 uint32 HPos = (HOffset + Left - (Left % PPU.Mosaic)) & OffsetMask;
1675 uint32 HTile = HPos >> 3;
1676 uint16 *t;
1677
1678 if (BG.TileSizeH == 8)
1679 {
1680 if (HTile > 31)
1681 t = b2 + (HTile & 0x1f);
1682 else
1683 t = b1 + HTile;
1684 }
1685 else
1686 {
1687 if (HTile > 63)
1688 t = b2 + ((HTile >> 1) & 0x1f);
1689 else
1690 t = b1 + (HTile >> 1);
1691 }
1692
1693 uint32 w = PPU.Mosaic - (Left % PPU.Mosaic);
1694 if (w > Width)
1695 w = Width;
1696
1697 Tile = READ_WORD(t);
1698 GFX.Z1 = GFX.Z2 = (Tile & 0x2000) ? Zh : Zl;
1699
1700 if (BG.TileSizeV == 16)
1701 Tile = TILE_PLUS(Tile, ((Tile & V_FLIP) ? t2 : t1));
1702
1703 if (BG.TileSizeH == 8)
1704 DrawPix(Tile, Offset, VirtAlign, HPos & 7, w, Lines);
1705 else
1706 {
1707 if (!(Tile & H_FLIP))
1708 DrawPix(TILE_PLUS(Tile, (HTile & 1)), Offset, VirtAlign, HPos & 7, w, Lines);
1709 else
1710 if (!(Tile & V_FLIP))
1711 DrawPix(TILE_PLUS(Tile, 1 - (HTile & 1)), Offset, VirtAlign, HPos & 7, w, Lines);
1712 }
1713
1714 Left += w;
1715 Offset += w * PixWidth;
1716 Width -= w;
1717 }
1718
1719 MosaicStart = 0;
1720 }
1721 }
1722}
1723
1724static inline void DrawBackgroundMode7 (int bg, void (*DrawMath) (uint32, uint32, int), void (*DrawNomath) (uint32, uint32, int), int D)
1725{
1726 for (int clip = 0; clip < GFX.Clip[bg].Count; clip++)
1727 {
1728 GFX.ClipColors = !(GFX.Clip[bg].DrawMode[clip] & 1);
1729
1730 if (BG.EnableMath && (GFX.Clip[bg].DrawMode[clip] & 2))
1731 DrawMath(GFX.Clip[bg].Left[clip], GFX.Clip[bg].Right[clip], D);
1732 else
1733 DrawNomath(GFX.Clip[bg].Left[clip], GFX.Clip[bg].Right[clip], D);
1734 }
1735}
1736
1737static inline void DrawBackdrop (void)
1738{
1739 uint32 Offset = GFX.StartY * GFX.PPL;
1740
1741 for (int clip = 0; clip < GFX.Clip[5].Count; clip++)
1742 {
1743 GFX.ClipColors = !(GFX.Clip[5].DrawMode[clip] & 1);
1744
1745 if (BG.EnableMath && (GFX.Clip[5].DrawMode[clip] & 2))
1746 GFX.DrawBackdropMath(Offset, GFX.Clip[5].Left[clip], GFX.Clip[5].Right[clip]);
1747 else
1748 GFX.DrawBackdropNomath(Offset, GFX.Clip[5].Left[clip], GFX.Clip[5].Right[clip]);
1749 }
1750}
1751
1752void S9xReRefresh (void)
1753{
1754 // Be careful when calling this function from the thread other than the emulation one...
1755 // Here it's assumed no drawing occurs from the emulation thread when Settings.Paused is TRUE.
1756 if (Settings.Paused)
1757 S9xDeinitUpdate(IPPU.RenderedScreenWidth, IPPU.RenderedScreenHeight);
1758}
1759
1760void S9xSetInfoString (const char *string)
1761{
1762 if (Settings.InitialInfoStringTimeout > 0)
1763 {
1764 GFX.InfoString = string;
1765 GFX.InfoStringTimeout = Settings.InitialInfoStringTimeout;
1766 S9xReRefresh();
1767 }
1768}
1769
1770void S9xDisplayChar (uint16 *s, uint8 c)
1771{
1772 const uint16 black = BUILD_PIXEL(0, 0, 0);
1773
1774 int line = ((c - 32) >> 4) * font_height;
1775 int offset = ((c - 32) & 15) * font_width;
1776
1777 for (int h = 0; h < font_height; h++, line++, s += GFX.RealPPL - font_width)
1778 {
1779 for (int w = 0; w < font_width; w++, s++)
1780 {
1781 char p = font[line][offset + w];
1782
1783 if (p == '#')
1784 *s = Settings.DisplayColor;
1785 else
1786 if (p == '.')
1787 *s = black;
1788 }
1789 }
1790}
1791
1792static void DisplayStringFromBottom (const char *string, int linesFromBottom, int pixelsFromLeft, bool allowWrap)
1793{
1794 if (S9xCustomDisplayString)
1795 {
1796 S9xCustomDisplayString (string, linesFromBottom, pixelsFromLeft, allowWrap, S9X_NO_INFO);
1797 return;
1798 }
1799
1800 if (linesFromBottom <= 0)
1801 linesFromBottom = 1;
1802
1803 uint16 *dst = GFX.Screen + (IPPU.RenderedScreenHeight - font_height * linesFromBottom) * GFX.RealPPL + pixelsFromLeft;
1804
1805 int len = strlen(string);
1806 int max_chars = IPPU.RenderedScreenWidth / (font_width - 1);
1807 int char_count = 0;
1808
1809 for (int i = 0 ; i < len ; i++, char_count++)
1810 {
1811 if (char_count >= max_chars || (uint8) string[i] < 32)
1812 {
1813 if (!allowWrap)
1814 break;
1815
1816 dst += font_height * GFX.RealPPL - (font_width - 1) * max_chars;
1817 if (dst >= GFX.Screen + IPPU.RenderedScreenHeight * GFX.RealPPL)
1818 break;
1819
1820 char_count -= max_chars;
1821 }
1822
1823 if ((uint8) string[i] < 32)
1824 continue;
1825
1826 S9xDisplayChar(dst, string[i]);
1827 dst += font_width - 1;
1828 }
1829}
1830
1831static void S9xDisplayStringType (const char *string, int linesFromBottom, int pixelsFromLeft, bool allowWrap, int type)
1832{
1833 if (S9xCustomDisplayString)
1834 {
1835 S9xCustomDisplayString (string, linesFromBottom, pixelsFromLeft, allowWrap, type);
1836 return;
1837 }
1838
1839 S9xDisplayString (string, linesFromBottom, pixelsFromLeft, allowWrap);
1840}
1841
1842static void DisplayTime (void)
1843{
1844 char string[10];
1845
1846 time_t rawtime;
1847 struct tm *timeinfo;
1848
1849 time (&rawtime);
1850 timeinfo = localtime(&rawtime);
1851
1852 sprintf(string, "%02u:%02u", timeinfo->tm_hour, timeinfo->tm_min);
1853 S9xDisplayString(string, 0, 0, false);
1854}
1855
1856static void DisplayFrameRate (void)
1857{
1858 char string[10];
1859 static uint32 lastFrameCount = 0, calcFps = 0;
1860 static time_t lastTime = time(NULL);
1861
1862 time_t currTime = time(NULL);
1863 if (lastTime != currTime) {
1864 if (lastFrameCount < IPPU.TotalEmulatedFrames) {
1865 calcFps = (IPPU.TotalEmulatedFrames - lastFrameCount) / (uint32)(currTime - lastTime);
1866 }
1867 lastTime = currTime;
1868 lastFrameCount = IPPU.TotalEmulatedFrames;
1869 }
1870 sprintf(string, "%u fps", calcFps);
1871 S9xDisplayString(string, 2, IPPU.RenderedScreenWidth - (font_width - 1) * strlen(string) - 1, false);
1872
1873#ifdef DEBUGGER
1874 const int len = 8;
1875 sprintf(string, "%02d/%02d %02d", (int) IPPU.DisplayedRenderedFrameCount, (int) Memory.ROMFramesPerSecond, (int) IPPU.FrameCount);
1876#else
1877 const int len = 5;
1878 sprintf(string, "%02d/%02d", (int) IPPU.DisplayedRenderedFrameCount, (int) Memory.ROMFramesPerSecond);
1879#endif
1880
1881 S9xDisplayString(string, 1, IPPU.RenderedScreenWidth - (font_width - 1) * len - 1, false);
1882}
1883
1884static void DisplayPressedKeys (void)
1885{
1886 static unsigned char KeyMap[] = { '0', '1', '2', 'R', 'L', 'X', 'A', 225, 224, 227, 226, 'S', 's', 'Y', 'B' };
1887 static int KeyOrder[] = { 8, 10, 7, 9, 0, 6, 14, 13, 5, 1, 4, 3, 2, 11, 12 }; // < ^ > v A B Y X L R S s
1888
1889 enum controllers controller;
1890 int line = Settings.DisplayMovieFrame && S9xMovieActive() ? 2 : 1;
1891 int8 ids[4];
1892 char string[255];
1893
1894 for (int port = 0; port < 2; port++)
1895 {
1896 S9xGetController(port, &controller, &ids[0], &ids[1], &ids[2], &ids[3]);
1897
1898 switch (controller)
1899 {
1900 case CTL_MOUSE:
1901 {
1902 uint8 buf[5];
1903 if (!MovieGetMouse(port, buf))
1904 break;
1905 int16 x = READ_WORD(buf);
1906 int16 y = READ_WORD(buf + 2);
1907 uint8 buttons = buf[4];
1908 sprintf(string, "#%d %d: (%03d,%03d) %c%c", port + 1, ids[0] + 1, x, y,
1909 (buttons & 0x40) ? 'L' : ' ', (buttons & 0x80) ? 'R' : ' ');
1910 S9xDisplayStringType(string, line++, 1, false, S9X_PRESSED_KEYS_INFO);
1911 break;
1912 }
1913
1914 case CTL_SUPERSCOPE:
1915 {
1916 uint8 buf[6];
1917 if (!MovieGetScope(port, buf))
1918 break;
1919 int16 x = READ_WORD(buf);
1920 int16 y = READ_WORD(buf + 2);
1921 uint8 buttons = buf[4];
1922 sprintf(string, "#%d %d: (%03d,%03d) %c%c%c%c", port + 1, ids[0] + 1, x, y,
1923 (buttons & 0x80) ? 'F' : ' ', (buttons & 0x40) ? 'C' : ' ',
1924 (buttons & 0x20) ? 'T' : ' ', (buttons & 0x10) ? 'P' : ' ');
1925 S9xDisplayStringType(string, line++, 1, false, S9X_PRESSED_KEYS_INFO);
1926 break;
1927 }
1928
1929 case CTL_JUSTIFIER:
1930 {
1931 uint8 buf[11];
1932 if (!MovieGetJustifier(port, buf))
1933 break;
1934 int16 x1 = READ_WORD(buf);
1935 int16 x2 = READ_WORD(buf + 2);
1936 int16 y1 = READ_WORD(buf + 4);
1937 int16 y2 = READ_WORD(buf + 6);
1938 uint8 buttons = buf[8];
1939 bool8 offscreen1 = buf[9];
1940 bool8 offscreen2 = buf[10];
1941 sprintf(string, "#%d %d: (%03d,%03d) %c%c%c / (%03d,%03d) %c%c%c", port + 1, ids[0] + 1,
1942 x1, y1, (buttons & 0x80) ? 'T' : ' ', (buttons & 0x20) ? 'S' : ' ', offscreen1 ? 'O' : ' ',
1943 x2, y2, (buttons & 0x40) ? 'T' : ' ', (buttons & 0x10) ? 'S' : ' ', offscreen2 ? 'O' : ' ');
1944 S9xDisplayStringType(string, line++, 1, false, S9X_PRESSED_KEYS_INFO);
1945 break;
1946 }
1947
1948 case CTL_JOYPAD:
1949 {
1950 sprintf(string, "#%d %d: ", port + 1, ids[0] + 1);
1951 uint16 pad = MovieGetJoypad(ids[0]);
1952 for (int i = 0; i < 15; i++)
1953 {
1954 int j = KeyOrder[i];
1955 int mask = (1 << (j + 1));
1956 string[6 + i]= (pad & mask) ? KeyMap[j] : ' ';
1957 }
1958
1959 S9xDisplayStringType(string, line++, 1, false, S9X_PRESSED_KEYS_INFO);
1960 break;
1961 }
1962
1963 case CTL_MP5:
1964 {
1965 for (int n = 0; n < 4; n++)
1966 {
1967 if (ids[n] != -1)
1968 {
1969 sprintf(string, "#%d %d: ", port + 1, ids[n] + 1);
1970 uint16 pad = MovieGetJoypad(ids[n]);
1971 for (int i = 0; i < 15; i++)
1972 {
1973 int j = KeyOrder[i];
1974 int mask = (1 << (j + 1));
1975 string[6 + i]= (pad & mask) ? KeyMap[j] : ' ';
1976 }
1977
1978 S9xDisplayStringType(string, line++, 1, false, S9X_PRESSED_KEYS_INFO);
1979 }
1980 }
1981
1982 break;
1983 }
1984
1985 case CTL_MACSRIFLE:
1986 {
1987 /*
1988 uint8 buf[6], *p = buf;
1989 MovieGetScope(port, buf);
1990 int16 x = READ_WORD(p);
1991 int16 y = READ_WORD(p + 2);
1992 uint8 buttons = buf[4];
1993 sprintf(string, "#%d %d: (%03d,%03d) %c%c%c%c", port, ids[0], x, y,
1994 (buttons & 0x80) ? 'F' : ' ', (buttons & 0x40) ? 'C' : ' ',
1995 (buttons & 0x20) ? 'T' : ' ', (buttons & 0x10) ? 'P' : ' ');
1996 S9xDisplayString(string, line++, 1, false);
1997 */
1998 break;
1999 }
2000
2001 case CTL_NONE:
2002 {
2003 // Display Nothing
2004 break;
2005 }
2006 }
2007 }
2008}
2009
2010static void DisplayWatchedAddresses (void)
2011{
2012 for (unsigned int i = 0; i < sizeof(watches) / sizeof(watches[0]); i++)
2013 {
2014 if (!watches[i].on)
2015 break;
2016
2017 int32 displayNumber = 0;
2018 char buf[32];
2019
2020 for (int r = 0; r < watches[i].size; r++)
2021 displayNumber += (Cheat.CWatchRAM[(watches[i].address - 0x7E0000) + r]) << (8 * r);
2022
2023 if (watches[i].format == 1)
2024 sprintf(buf, "%s,%du = %u", watches[i].desc, watches[i].size, (unsigned int) displayNumber);
2025 else
2026 if (watches[i].format == 3)
2027 sprintf(buf, "%s,%dx = %X", watches[i].desc, watches[i].size, (unsigned int) displayNumber);
2028 else // signed
2029 {
2030 if (watches[i].size == 1)
2031 displayNumber = (int32) ((int8) displayNumber);
2032 else
2033 if (watches[i].size == 2)
2034 displayNumber = (int32) ((int16) displayNumber);
2035 else
2036 if (watches[i].size == 3)
2037 if (displayNumber >= 8388608)
2038 displayNumber -= 16777216;
2039
2040 sprintf(buf, "%s,%ds = %d", watches[i].desc, watches[i].size, (int) displayNumber);
2041 }
2042
2043 S9xDisplayString(buf, 6 + i, 1, false);
2044 }
2045}
2046
2047void S9xDisplayMessages (uint16 *screen, int ppl, int width, int height, int scale)
2048{
2049 if (Settings.DisplayTime)
2050 DisplayTime();
2051
2052 if (Settings.DisplayFrameRate)
2053 DisplayFrameRate();
2054
2055 if (Settings.DisplayWatchedAddresses)
2056 DisplayWatchedAddresses();
2057
2058 if (Settings.DisplayPressedKeys)
2059 DisplayPressedKeys();
2060
2061 if (Settings.DisplayMovieFrame && S9xMovieActive())
2062 S9xDisplayString(GFX.FrameDisplayString, 1, 1, false);
2063
2064 if (GFX.InfoString && *GFX.InfoString)
2065 S9xDisplayString(GFX.InfoString, 5, 1, true);
2066}
2067
2068static uint16 get_crosshair_color (uint8 color)
2069{
2070 switch (color & 15)
2071 {
2072 case 0: return (BUILD_PIXEL( 0, 0, 0)); // transparent, shouldn't be used
2073 case 1: return (BUILD_PIXEL( 0, 0, 0)); // Black
2074 case 2: return (BUILD_PIXEL( 8, 8, 8)); // 25Grey
2075 case 3: return (BUILD_PIXEL(16, 16, 16)); // 50Grey
2076 case 4: return (BUILD_PIXEL(23, 23, 23)); // 75Grey
2077 case 5: return (BUILD_PIXEL(31, 31, 31)); // White
2078 case 6: return (BUILD_PIXEL(31, 0, 0)); // Red
2079 case 7: return (BUILD_PIXEL(31, 16, 0)); // Orange
2080 case 8: return (BUILD_PIXEL(31, 31, 0)); // Yellow
2081 case 9: return (BUILD_PIXEL( 0, 31, 0)); // Green
2082 case 10: return (BUILD_PIXEL( 0, 31, 31)); // Cyan
2083 case 11: return (BUILD_PIXEL( 0, 23, 31)); // Sky
2084 case 12: return (BUILD_PIXEL( 0, 0, 31)); // Blue
2085 case 13: return (BUILD_PIXEL(23, 0, 31)); // Violet
2086 case 14: return (BUILD_PIXEL(31, 0, 31)); // Magenta
2087 case 15: return (BUILD_PIXEL(31, 0, 16)); // Purple
2088 }
2089
2090 return (0);
2091}
2092
2093void S9xDrawCrosshair (const char *crosshair, uint8 fgcolor, uint8 bgcolor, int16 x, int16 y)
2094{
2095 if (!crosshair)
2096 return;
2097
2098 int16 r, rx = 1, c, cx = 1, W = SNES_WIDTH, H = PPU.ScreenHeight;
2099 uint16 fg, bg;
2100
2101 x -= 7;
2102 y -= 7;
2103
2104 if (IPPU.DoubleWidthPixels) { cx = 2; x *= 2; W *= 2; }
2105 if (IPPU.DoubleHeightPixels) { rx = 2; y *= 2; H *= 2; }
2106
2107 fg = get_crosshair_color(fgcolor);
2108 bg = get_crosshair_color(bgcolor);
2109
2110 uint16 *s = GFX.Screen + y * (int32)GFX.RealPPL + x;
2111
2112 for (r = 0; r < 15 * rx; r++, s += GFX.RealPPL - 15 * cx)
2113 {
2114 if (y + r < 0)
2115 {
2116 s += 15 * cx;
2117 continue;
2118 }
2119
2120 if (y + r >= H)
2121 break;
2122
2123 for (c = 0; c < 15 * cx; c++, s++)
2124 {
2125 if (x + c < 0 || s < GFX.Screen)
2126 continue;
2127
2128 if (x + c >= W)
2129 {
2130 s += 15 * cx - c;
2131 break;
2132 }
2133
2134 uint8 p = crosshair[(r / rx) * 15 + (c / cx)];
2135
2136 if (p == '#' && fgcolor)
2137 *s = (fgcolor & 0x10) ? COLOR_ADD::fn1_2(fg, *s) : fg;
2138 else
2139 if (p == '.' && bgcolor)
2140 *s = (bgcolor & 0x10) ? COLOR_ADD::fn1_2(*s, bg) : bg;
2141 }
2142 }
2143}
2144
2145