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