added restriction in arm sw scaler
[drnoksnes] / platform / sdlv.cpp
1 #include <stdio.h>
2
3 #include <X11/Xlib.h>
4 #include <X11/Xutil.h>
5 #include <SDL.h>
6 #include <SDL_syswm.h>
7
8 #if CONF_XSP
9 #       include <X11/extensions/Xsp.h>
10 #endif
11
12 #include "snes9x.h"
13 #include "platform.h"
14 #include "display.h"
15 #include "gfx.h"
16 #include "ppu.h"
17
18 #define DIE(format, ...) do { \
19                 fprintf(stderr, "Died at %s:%d: ", __FILE__, __LINE__ ); \
20                 fprintf(stderr, format "\n", ## __VA_ARGS__); \
21                 abort(); \
22         } while (0);
23
24 struct gui GUI;
25
26 static SDL_Surface* screen;
27
28 static SDL_Rect windowSize, screenSize;
29 static bool gotWindowSize, gotScreenSize;
30
31 class Scaler;
32 /** The current scaler object */
33 static Scaler* scaler;
34
35 static void centerRectangle(SDL_Rect& result, int areaW, int areaH, int w, int h)
36 {
37         result.x = areaW / 2 - w / 2;
38         result.w = w;
39         result.y = areaH / 2 - h / 2;
40         result.h = h;
41 }
42
43 class Scaler
44 {
45 public:
46         Scaler() { };
47         virtual ~Scaler() { };
48
49         virtual const char * getName() const = 0;
50
51         virtual uint8* getDrawBuffer() const = 0;
52         virtual unsigned int getDrawBufferPitch() const = 0;
53         virtual void getRenderedGUIArea(unsigned short & x, unsigned short & y,
54                                                                         unsigned short & w, unsigned short & h)
55                                                                         const = 0;
56         virtual int getRatio() const = 0;
57         virtual void prepare() = 0;
58         virtual void finish() = 0;
59 };
60
61 class ScalerFactory
62 {
63 public:
64         ScalerFactory() { };
65         virtual ~ScalerFactory() { };
66         virtual const char * getName() const = 0;
67         virtual bool canEnable(int bpp, int w, int h) const = 0;
68         virtual Scaler* instantiate(SDL_Surface* screen, int w, int h) const = 0;
69 };
70
71 class DummyScaler : public Scaler
72 {
73         SDL_Surface * m_screen;
74         SDL_Rect m_area;
75
76         DummyScaler(SDL_Surface* screen, int w, int h)
77         : m_screen(screen)
78         {
79                 centerRectangle(m_area, GUI.Width, GUI.Height, w, h);
80         }
81 public:
82
83         ~DummyScaler()
84         {
85         };
86
87         class Factory : public ScalerFactory
88         {
89                 const char * getName() const
90                 {
91                         return "none";
92                 }
93
94                 bool canEnable(int bpp, int w, int h) const
95                 {
96                         return true;
97                 }
98
99                 Scaler* instantiate(SDL_Surface* screen, int w, int h) const
100                 {
101                         return new DummyScaler(screen, w, h);
102                 }
103         };
104
105         static const Factory factory;
106
107         const char * getName() const
108         {
109                 return "no scaling";
110         }
111
112         uint8* getDrawBuffer() const
113         {
114                 const int Bpp = screen->format->BitsPerPixel / 8;
115                 const int pitch = screen->pitch;
116                 return ((uint8*) screen->pixels)
117                         + (m_area.x * Bpp)
118                         + (m_area.y * pitch);
119         };
120
121         unsigned int getDrawBufferPitch() const
122         {
123                 return screen->pitch;
124         };
125
126         void getRenderedGUIArea(unsigned short & x, unsigned short & y,
127                                                         unsigned short & w, unsigned short & h) const
128         {
129                 x = m_area.x; y = m_area.y; w = m_area.w; h = m_area.h;
130         };
131
132         int getRatio() const
133         {
134                 return 1;
135         };
136
137         void prepare() { };
138
139         void finish()
140         {
141                 SDL_UpdateRects(m_screen, 1, &m_area);
142         };
143 };
144 const DummyScaler::Factory DummyScaler::factory;
145
146 #ifdef __arm__
147 class ARMScaler : public Scaler
148 {
149         SDL_Surface * m_screen;
150         SDL_Rect m_area;
151         uint8 * m_surface;
152         const int m_w, m_h, m_Bpp;
153
154         ARMScaler(SDL_Surface* screen, int w, int h)
155         : m_screen(screen), m_w(w), m_h(h),
156          m_Bpp(m_screen->format->BitsPerPixel / 8)
157         {
158                 centerRectangle(m_area, GUI.Width, GUI.Height, w * 2, h * 2);
159                 m_surface = reinterpret_cast<uint8*>(malloc(w * h * m_Bpp));
160         }
161 public:
162         ~ARMScaler()
163         {
164                 free(m_surface);
165         };
166
167         class Factory : public ScalerFactory
168         {
169                 const char * getName() const
170                 {
171                         return "2x";
172                 }
173
174                 bool canEnable(int bpp, int w, int h) const
175                 {
176                         return bpp == 16 && w * 2 < GUI.Width && h * 2 < GUI.Height &&
177                                 w % 16 == 0 /* asm assumes w div by 16 */;
178                 }
179
180                 Scaler* instantiate(SDL_Surface* screen, int w, int h) const
181                 {
182                         return new ARMScaler(screen, w, h);
183                 }
184         };
185
186         static const Factory factory;
187
188         const char * getName() const
189         {
190                 return "software ARM 2x scaling";
191         }
192
193         uint8* getDrawBuffer() const
194         {
195                 return m_surface;
196         };
197
198         unsigned int getDrawBufferPitch() const
199         {
200                 return m_w * m_Bpp;
201         };
202
203         void getRenderedGUIArea(unsigned short & x, unsigned short & y,
204                                                         unsigned short & w, unsigned short & h) const
205         {
206                 x = m_area.x; y = m_area.y; w = m_area.w; h = m_area.h;
207         };
208
209         int getRatio() const
210         {
211                 return 2;
212         };
213
214         void prepare() { };
215
216         void finish()
217         {
218                 uint16 * src = reinterpret_cast<uint16*>(m_surface);
219                 uint16 * dst = reinterpret_cast<uint16*>(
220                         ((uint8*) m_screen->pixels)
221                         + (m_area.x * m_Bpp)
222                         + (m_area.y * m_screen->pitch));
223                 const int src_pitch = m_w;
224                 const int dst_pitch = m_screen->pitch / m_Bpp;
225                 int y;
226
227                 for (y = 0; y < m_h*2; y++) {
228                         asm volatile
229                         (
230                                 "mov r0, %0; mov r1, %1; mov r2, %2;"
231                                 "stmdb r13!,{r4,r5,r6,r7,r8,r9,r10,r11,r12,r14};"
232                                 "1:     ldmia r1!,{r3,r4,r5,r6,r7,r8,r9,r10};"
233                                 "mov r14,r5,lsr #16;"
234                                 "mov r12,r5,lsl #16;"
235                                 "orr r14,r14,r14,lsl #16;"
236                                 "orr r12,r12,r12,lsr #16;"
237                                 "mov r11,r4,lsr #16;"
238                                 "mov r5,r4,lsl #16;"
239                                 "orr r11,r11,r11,lsl #16;"
240                                 "orr r5,r5,r5,lsr #16;"
241                                 "mov r4,r3,lsr #16;"
242                                 "mov r3,r3,lsl #16;"
243                                 "orr r4,r4,r4,lsl #16;"
244                                 "orr r3,r3,r3,lsr #16;"
245                                 "stmia r0!,{r3,r4,r5,r11,r12,r14};"
246                                 "mov r3,r6,lsl #16;"
247                                 "mov r4,r6,lsr #16;"
248                                 "orr r3,r3,r3,lsr #16;"
249                                 "orr r4,r4,r4,lsl #16;"
250                                 "mov r5,r7,lsl #16;"
251                                 "mov r6,r7,lsr #16;"
252                                 "orr r5,r5,r5,lsr #16;"
253                                 "orr r6,r6,r6,lsl #16;"
254                                 "mov r7,r8,lsl #16;"
255                                 "mov r8,r8,lsr #16;"
256                                 "orr r7,r7,r7,lsr #16;"
257                                 "orr r8,r8,r8,lsl #16;"
258                                 "mov r12,r10,lsr #16;"
259                                 "mov r11,r10,lsl #16;"
260                                 "orr r12,r12,r12,lsl #16;"
261                                 "orr r11,r11,r11,lsr #16;"
262                                 "mov r10,r9,lsr #16;"
263                                 "mov r9,r9,lsl #16;"
264                                 "orr r10,r10,r10,lsl #16;"
265                                 "orr r9,r9,r9,lsr #16;"
266                                 "stmia r0!,{r3,r4,r5,r6,r7,r8,r9,r10,r11,r12};"
267                                 "subs r2,r2,#16;"
268                                 "bhi 1b;"
269                                 "ldmia r13!,{r4,r5,r6,r7,r8,r9,r10,r11,r12,r14};"
270                         :
271                         : "r" (dst), "r" (src), "r" (m_w)
272                         : "r0", "r1", "r2", "r3"
273                         );
274                         dst += dst_pitch;
275                         if (y&1) src += src_pitch;
276                 }
277
278                 SDL_UpdateRects(m_screen, 1, &m_area);
279         };
280 };
281 const ARMScaler::Factory ARMScaler::factory;
282 #endif
283
284 class SWScaler : public Scaler
285 {
286         SDL_Surface * m_screen;
287         SDL_Rect m_area;
288         uint8 * m_surface;
289         const int m_w, m_h, m_Bpp;
290
291         SWScaler(SDL_Surface* screen, int w, int h)
292         : m_screen(screen), m_w(w), m_h(h),
293          m_Bpp(m_screen->format->BitsPerPixel / 8)
294         {
295                 centerRectangle(m_area, GUI.Width, GUI.Height, w * 2, h * 2);
296                 m_surface = reinterpret_cast<uint8*>(malloc(w * h * m_Bpp));
297         }
298 public:
299         ~SWScaler()
300         {
301                 free(m_surface);
302         };
303
304         class Factory : public ScalerFactory
305         {
306                 const char * getName() const
307                 {
308                         return "soft2x";
309                 }
310
311                 bool canEnable(int bpp, int w, int h) const
312                 {
313                         return w * 2 < GUI.Width && h * 2 < GUI.Height;
314                 }
315
316                 Scaler* instantiate(SDL_Surface* screen, int w, int h) const
317                 {
318                         return new SWScaler(screen, w, h);
319                 }
320         };
321
322         static const Factory factory;
323
324         const char * getName() const
325         {
326                 return "software 2x scaling";
327         }
328
329         uint8* getDrawBuffer() const
330         {
331                 return m_surface;
332         };
333
334         unsigned int getDrawBufferPitch() const
335         {
336                 return m_w * m_Bpp;
337         };
338
339         void getRenderedGUIArea(unsigned short & x, unsigned short & y,
340                                                         unsigned short & w, unsigned short & h) const
341         {
342                 x = m_area.x; y = m_area.y; w = m_area.w; h = m_area.h;
343         };
344
345         int getRatio() const
346         {
347                 return 2;
348         };
349
350         void prepare() { };
351
352         void finish()
353         {
354                 uint16 * src = reinterpret_cast<uint16*>(m_surface);
355                 uint16 * dst = reinterpret_cast<uint16*>(
356                         ((uint8*) m_screen->pixels)
357                         + (m_area.x * m_Bpp)
358                         + (m_area.y * m_screen->pitch));
359                 const int src_pitch = m_w;
360                 const int dst_pitch = m_screen->pitch / m_Bpp;
361                 int x, y;
362
363                 for (y = 0; y < m_h*2; y++) {
364                         for (x = 0; x < m_w*2; x+=2) {
365                                 dst[x] = src[x/2];
366                                 dst[x + 1] = src[x/2];
367                         }
368                         dst += dst_pitch;
369                         if (y&1) src += src_pitch;
370                 }
371
372                 SDL_UpdateRects(m_screen, 1, &m_area);
373         };
374 };
375 const SWScaler::Factory SWScaler::factory;
376
377 #if CONF_XSP
378 class XSPScaler : public Scaler
379 {
380         SDL_Surface* m_screen;
381         SDL_Rect m_area;
382         SDL_Rect m_real_area;
383
384         static void setDoubling(bool enable)
385         {
386                 SDL_SysWMinfo wminfo;
387                 SDL_VERSION(&wminfo.version);
388                 if ( SDL_GetWMInfo(&wminfo) ) {
389                         Display *dpy = wminfo.info.x11.display;
390                         XSPSetPixelDoubling(dpy, 0, enable ? 1 : 0);
391                         XFlush(dpy);
392                 }
393         }
394
395         XSPScaler(SDL_Surface* screen, int w, int h)
396         : m_screen(screen)
397         {
398                 centerRectangle(m_area, GUI.Width, GUI.Height,
399                         w * 2, h * 2);
400                 setDoubling(true);
401
402                 m_real_area.x = m_area.x;
403                 m_real_area.y = m_area.y;
404                 m_real_area.w = m_area.w / 2;
405                 m_real_area.h = m_area.h / 2;
406         };
407 public:
408         ~XSPScaler()
409         {
410                 setDoubling(false);
411         };
412
413         class Factory : public ScalerFactory
414         {
415                 const char * getName() const
416                 {
417                         return "xsp";
418                 }
419
420                 bool canEnable(int bpp, int w, int h) const
421                 {
422                         return w * 2 < GUI.Width && h * 2 < GUI.Height;
423                 };
424
425                 Scaler* instantiate(SDL_Surface* screen, int w, int h) const
426                 {
427                         return new XSPScaler(screen, w, h);
428                 }
429         };
430
431         static const Factory factory;
432
433         const char * getName() const
434         {
435                 return "XSP pixel doubling";
436         }
437
438         uint8* getDrawBuffer() const
439         {
440                 const int Bpp = screen->format->BitsPerPixel / 8;
441                 const int pitch = screen->pitch;
442                 return ((uint8*) screen->pixels)
443                         + (m_area.x * Bpp)
444                         + (m_area.y * pitch);
445         };
446
447         unsigned int getDrawBufferPitch() const
448         {
449                 return screen->pitch;
450         };
451
452         void getRenderedGUIArea(unsigned short & x, unsigned short & y,
453                                                         unsigned short & w, unsigned short & h) const
454         {
455                 x = m_area.x; y = m_area.y; w = m_area.w; h = m_area.h;
456         };
457
458         int getRatio() const
459         {
460                 return 2;
461         };
462
463         void prepare() { };
464
465         void finish()
466         {
467                 SDL_UpdateRects(m_screen, 1, &m_real_area);
468         };
469 };
470 const XSPScaler::Factory XSPScaler::factory;
471 #endif
472
473 static const ScalerFactory* scalers[] = {
474 #if CONF_XSP
475         &XSPScaler::factory,
476 #endif
477 #ifdef __arm__
478         &ARMScaler::factory,
479 #endif
480         &SWScaler::factory,
481         &DummyScaler::factory
482 };
483
484 static const ScalerFactory* searchForScaler(int bpp, int w, int h)
485 {
486         const int n = sizeof(scalers) / sizeof(ScalerFactory*);
487         int i;
488
489         if (Config.scaler && strcasecmp(Config.scaler, "help") == 0 ) {
490                 // List scalers
491                 printf("Scalers list:\n");
492                 for (i = 0; i < n; i++) {
493                         printf(" %s\n", scalers[i]->getName());
494                 }
495                 DIE("End of scalers list");
496         } else if (Config.scaler && strcasecmp(Config.scaler, "auto") != 0 ) {
497                 // We prefer a specific scaler
498                 for (i = 0; i < n; i++) {
499                         if (strcasecmp(scalers[i]->getName(), Config.scaler) == 0) {
500                                 if (!scalers[i]->canEnable(bpp, w, h)) {
501                                         DIE("Cannot use selected scaler");
502                                 }
503                                 return scalers[i];
504                         }
505                 }
506                 DIE("Selected scaler '%s' does not exist", Config.scaler);
507         } else {
508                 // Just try them all
509                 for (i = 0; i < n; i++) {
510                         if (scalers[i]->canEnable(bpp, w, h)) {
511                                 return scalers[i];
512                         }
513                 }
514                 DIE("Can't use any scaler");
515         }
516 }
517
518 static void calculateScreenSize()
519 {
520         SDL_SysWMinfo wminfo;
521         SDL_VERSION(&wminfo.version);
522
523         if ( SDL_GetWMInfo(&wminfo) ) {
524                 Display *dpy = wminfo.info.x11.display;
525                 Window w;
526                 SDL_Rect* size;
527                 XWindowAttributes xwa;
528
529                 if (Config.fullscreen) {
530                         w =  wminfo.info.x11.fswindow;
531                         size = &screenSize;
532                         gotScreenSize = true;
533                 } else {
534                         w =  wminfo.info.x11.wmwindow;
535                         size = &windowSize;
536                         gotWindowSize = true;
537                 }
538
539                 XGetWindowAttributes(dpy, w, &xwa);
540                 size->x = xwa.x;
541                 size->y = xwa.y;
542                 size->w = xwa.width;
543                 size->h = xwa.height;
544         }
545 }
546
547 void S9xSetTitle(const char *title)
548 {
549         SDL_SysWMinfo info;
550         SDL_VERSION(&info.version);
551         if ( SDL_GetWMInfo(&info) ) {
552                 Display *dpy = info.info.x11.display;
553                 Window win;
554                 if (dpy) {
555                         win = info.info.x11.fswindow;
556                         if (win) XStoreName(dpy, win, title);
557                         win = info.info.x11.wmwindow;
558                         if (win) XStoreName(dpy, win, title);
559                 }
560         }
561 }
562
563 static void freeVideoSurface()
564 {
565         screen = 0; // There's no need to free the screen surface.
566         GFX.Screen = 0;
567
568         free(GFX.SubScreen); GFX.SubScreen = 0;
569         free(GFX.ZBuffer); GFX.ZBuffer = 0;
570         free(GFX.SubZBuffer); GFX.SubZBuffer = 0;
571
572         free(scaler); scaler = 0;
573 }
574
575 static void setupVideoSurface()
576 {
577         // Real surface area.
578         const unsigned gameWidth = IMAGE_WIDTH;
579         const unsigned gameHeight = IMAGE_HEIGHT;
580
581         if (scaler) {
582                 delete scaler;
583                 scaler = 0;
584         }
585
586 #ifdef MAEMO
587         if ((Config.fullscreen && !gotScreenSize) ||
588                 (!Config.fullscreen && !gotWindowSize)) {
589                 // Do a first try, in order to get window/screen size
590                 screen = SDL_SetVideoMode(gameWidth, gameHeight, 16,
591                         SDL_SWSURFACE | SDL_RESIZABLE |
592                         (Config.fullscreen ? SDL_FULLSCREEN : 0));
593                 if (!screen) DIE("SDL_SetVideoMode: %s", SDL_GetError());
594                 calculateScreenSize();
595         }
596         if (Config.fullscreen) {
597                 GUI.Width = screenSize.w;
598                 GUI.Height = screenSize.h;
599         } else {
600                 GUI.Width = windowSize.w;
601                 GUI.Height = windowSize.h;
602         }
603 #else
604         GUI.Width = gameWidth;
605         GUI.Height = gameHeight;
606 #endif
607
608         // Safeguard
609         if (gameHeight > GUI.Height || gameWidth > GUI.Width)
610                 DIE("Video is larger than window size!");
611
612         const ScalerFactory* sFactory =
613                 searchForScaler(Settings.SixteenBit ? 16 : 8, gameWidth, gameHeight);
614
615         screen = SDL_SetVideoMode(GUI.Width, GUI.Height,
616                                                                 Settings.SixteenBit ? 16 : 8,
617                                                                 SDL_SWSURFACE |
618                                                                 (Config.fullscreen ? SDL_FULLSCREEN : 0));
619         if (!screen)
620                 DIE("SDL_SetVideoMode: %s", SDL_GetError());
621         
622         SDL_ShowCursor(SDL_DISABLE);
623
624         scaler = sFactory->instantiate(screen, gameWidth, gameHeight);
625
626         // We get pitch surface values from SDL
627         GFX.RealPitch = GFX.Pitch = scaler->getDrawBufferPitch();
628         GFX.ZPitch = GFX.Pitch / 2; // gfx & tile.cpp depend on this, unfortunately.
629         GFX.PixSize = screen->format->BitsPerPixel / 8;
630         
631         GFX.Screen = scaler->getDrawBuffer();
632         GFX.SubScreen = (uint8 *) malloc(GFX.Pitch * IMAGE_HEIGHT);
633         GFX.ZBuffer =  (uint8 *) malloc(GFX.ZPitch * IMAGE_HEIGHT);
634         GFX.SubZBuffer = (uint8 *) malloc(GFX.ZPitch * IMAGE_HEIGHT);
635
636         GFX.Delta = (GFX.SubScreen - GFX.Screen) >> 1;
637         GFX.PPL = GFX.Pitch >> 1;
638         GFX.PPLx2 = GFX.Pitch;
639
640         scaler->getRenderedGUIArea(GUI.RenderX, GUI.RenderY, GUI.RenderW, GUI.RenderH);
641         GUI.Scale = scaler->getRatio();
642
643         printf("Video: %dx%d (%dx%d output), %hu bits per pixel, %s, %s\n",
644                 gameWidth, gameHeight,
645                 screen->w, screen->h, screen->format->BitsPerPixel,
646                 Config.fullscreen ? "fullscreen" : "windowed",
647                 scaler->getName());
648 }
649
650 static void drawOnscreenControls()
651 {
652         if (Config.touchscreenInput) {
653                 S9xInputScreenChanged();
654                 if (Config.touchscreenShow) {
655                         S9xInputScreenDraw(Settings.SixteenBit ? 2 : 1,
656                                                                 screen->pixels, screen->pitch);
657                         SDL_Flip(screen);
658                 }
659         }
660 }
661
662 void S9xInitDisplay(int argc, const char ** argv)
663 {       
664         if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) 
665                 DIE("SDL_InitSubSystem(VIDEO): %s", SDL_GetError());
666
667         setupVideoSurface();
668         drawOnscreenControls();
669 }
670
671 void S9xDeinitDisplay()
672 {
673         freeVideoSurface();     
674         SDL_QuitSubSystem(SDL_INIT_VIDEO);
675 }
676
677 void S9xVideoToggleFullscreen()
678 {
679         Config.fullscreen = !Config.fullscreen;
680         freeVideoSurface();
681         setupVideoSurface();
682         drawOnscreenControls();
683 }
684
685 void S9xVideoOutputFocus(bool hasFocus)
686 {
687 #if 0 // TODO
688         if (Config.xsp) {
689                 setDoubling(hasFocus);
690         } 
691 #endif
692 }
693
694 // This is here for completeness, but palette mode is useless on N8x0
695 void S9xSetPalette ()
696 {
697         if (Settings.SixteenBit) return;
698         
699         SDL_Color colors[256];
700         int brightness = IPPU.MaxBrightness *138;
701         for (int i = 0; i < 256; i++)
702         {
703                 colors[i].r = ((PPU.CGDATA[i] >> 0) & 0x1F) * brightness;
704                 colors[i].g = ((PPU.CGDATA[i] >> 5) & 0x1F) * brightness;
705                 colors[i].b = ((PPU.CGDATA[i] >> 10) & 0x1F) * brightness;
706         }
707         
708         SDL_SetColors(screen, colors, 0, 256);
709 }
710
711 bool8_32 S9xInitUpdate ()
712 {
713         scaler->prepare();
714
715         return TRUE;
716 }
717
718 bool8_32 S9xDeinitUpdate (int width, int height, bool8_32 sixteenBit)
719 {
720         scaler->finish();
721
722         return TRUE;
723 }
724