support for nonsquare scalers
[drnoksnes] / platform / sdlvscalers.cpp
index 2839a7e..ee29992 100644 (file)
@@ -1,13 +1,18 @@
 #include <stdio.h> 
+#include <math.h>
 
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
 #include <SDL.h>
-#include <SDL_syswm.h>
 
-#if CONF_XSP || CONF_HD
+#if CONF_XSP
 #      include <X11/extensions/Xsp.h>
 #endif
+#if CONF_HD
+#      include <X11/Xatom.h>
+#      include <sys/ipc.h>
+#      include <sys/shm.h>
+#endif
 
 #include "snes9x.h"
 #include "display.h"
@@ -96,9 +101,9 @@ public:
                x = m_area.x; y = m_area.y; w = m_area.w; h = m_area.h;
        };
 
-       virtual int getRatio() const
+       virtual void getRatio(float & x, float & y) const
        {
-               return 1;
+               x = 1.0f; y = 1.0f;
        };
 
        virtual void prepare() { };
@@ -131,7 +136,7 @@ protected:
                m_surface = reinterpret_cast<uint8*>(malloc(w * h * m_Bpp));
        }
 public:
-       ~SWScaler()
+       virtual ~SWScaler()
        {
                free(m_surface);
        };
@@ -156,7 +161,7 @@ public:
 
        static const Factory factory;
 
-       const char * getName() const
+       virtual const char * getName() const
        {
                return "software 2x scaling";
        }
@@ -177,9 +182,9 @@ public:
                x = m_area.x; y = m_area.y; w = m_area.w; h = m_area.h;
        };
 
-       int getRatio() const
+       void getRatio(float & x, float & y) const
        {
-               return 2;
+               x = 2.0f; y = 2.0f;
        };
 
        void prepare() { };
@@ -222,6 +227,7 @@ class ARMScaler : public Scaler
        uint8 * m_surface;
        const int m_w, m_h, m_Bpp;
 
+protected:
        ARMScaler(SDL_Surface* screen, int w, int h)
        : m_screen(screen), m_w(w), m_h(h),
         m_Bpp(m_screen->format->BitsPerPixel / 8)
@@ -230,7 +236,7 @@ class ARMScaler : public Scaler
                m_surface = reinterpret_cast<uint8*>(malloc(w * h * m_Bpp));
        }
 public:
-       ~ARMScaler()
+       virtual ~ARMScaler()
        {
                free(m_surface);
        };
@@ -256,7 +262,7 @@ public:
 
        static const Factory factory;
 
-       const char * getName() const
+       virtual const char * getName() const
        {
                return "software ARM 2x scaling";
        }
@@ -277,9 +283,9 @@ public:
                x = m_area.x; y = m_area.y; w = m_area.w; h = m_area.h;
        };
 
-       int getRatio() const
+       void getRatio(float & x, float & y) const
        {
-               return 2;
+               x = 2.0f; y = 2.0f;
        };
 
        void prepare() { };
@@ -357,124 +363,277 @@ const ARMScaler::Factory ARMScaler::factory;
 
 #if CONF_HD
 
-enum hdAtoms {
-       ATOM_HILDON_NON_COMPOSITED_WINDOW = 0,
-       ATOM_NET_WM_STATE,
-       ATOM_NET_WM_STATE_FULLSCREEN,
-       ATOM_NET_WM_WINDOW_TYPE,
-       ATOM_NET_WM_WINDOW_TYPE_NORMAL,
-       ATOM_NET_WM_WINDOW_TYPE_DIALOG,
-       ATOM_HILDON_WM_WINDOW_TYPE_ANIMATION_ACTOR,
-       ATOM_HILDON_ANIMATION_CLIENT_READY,
-       ATOM_HILDON_ANIMATION_CLIENT_MESSAGE_SHOW,
-       ATOM_HILDON_ANIMATION_CLIENT_MESSAGE_POSITION,
-       ATOM_HILDON_ANIMATION_CLIENT_MESSAGE_ROTATION,
-       ATOM_HILDON_ANIMATION_CLIENT_MESSAGE_SCALE,
-       ATOM_HILDON_ANIMATION_CLIENT_MESSAGE_ANCHOR,
-       ATOM_HILDON_ANIMATION_CLIENT_MESSAGE_PARENT,
-       ATOM_COUNT
-};
+class HDScalerBase : public Scaler
+{
+       SDL_Surface * m_screen;
+       SDL_Rect m_area;
+       const int m_w, m_h, m_Bpp;
+       const float ratio_x, ratio_y;
+
+       // SDL/X11 stuff we save for faster access.
+       Display* display;
+       Window window;
+
+       // Shared memory segment info.
+       key_t shmkey;
+       int shmid;
+       void *shmaddr;
+
+private:
+       /** Sends a message to hildon-desktop.
+         * This function comes mostly straight from libhildon.
+         */
+       void sendMessage(Atom message_type,
+               uint32 l0, uint32 l1, uint32 l2, uint32 l3, uint32 l4)
+       {
+               XEvent event = { 0 };
+
+               event.xclient.type = ClientMessage;
+               event.xclient.window = window;
+               event.xclient.message_type = message_type;
+               event.xclient.format = 32;
+               event.xclient.data.l[0] = l0;
+               event.xclient.data.l[1] = l1;
+               event.xclient.data.l[2] = l2;
+               event.xclient.data.l[3] = l3;
+               event.xclient.data.l[4] = l4;
+
+               XSendEvent (display, window, True,
+                           StructureNotifyMask,
+                           (XEvent *)&event);
+       }
 
-static const char * hdAtomNames[] = {
-       "_HILDON_NON_COMPOSITED_WINDOW",
-       "_NET_WM_STATE",
-       "_NET_WM_STATE_FULLSCREEN",
-       "_NET_WM_WINDOW_TYPE",
-       "_NET_WM_WINDOW_TYPE_NORMAL",
-       "_NET_WM_WINDOW_TYPE_DIALOG",
-       "_HILDON_WM_WINDOW_TYPE_ANIMATION_ACTOR",
-       "_HILDON_ANIMATION_CLIENT_READY",
-       "_HILDON_ANIMATION_CLIENT_MESSAGE_SHOW",        
-       "_HILDON_ANIMATION_CLIENT_MESSAGE_POSITION",
-       "_HILDON_ANIMATION_CLIENT_MESSAGE_ROTATION",
-       "_HILDON_ANIMATION_CLIENT_MESSAGE_SCALE",
-       "_HILDON_ANIMATION_CLIENT_MESSAGE_ANCHOR",
-       "_HILDON_ANIMATION_CLIENT_MESSAGE_PARENT",
-       ""
-};
+       /** Sends all configuration parameters for the remote texture. */
+       void reconfigure()
+       {
+               Window parent;
+               int yoffset = 0;
+               if (Config.fullscreen) {
+                       parent = WMinfo.info.x11.fswindow;
+               } else {
+                       parent = WMinfo.info.x11.wmwindow;
+                       yoffset = 60; // Hardcode the title bar size for now.
+               }
 
-static Atom hdAtomsValues[ATOM_COUNT];
-static bool hdAtomsLoaded = false;
+               sendMessage(HDATOM(_HILDON_TEXTURE_CLIENT_MESSAGE_SHM),
+                       (uint32) shmkey, m_w, m_h, m_Bpp, 0);
+               sendMessage(HDATOM(_HILDON_TEXTURE_CLIENT_MESSAGE_PARENT),
+                       (uint32) parent, 0, 0, 0, 0);
+               sendMessage(HDATOM(_HILDON_TEXTURE_CLIENT_MESSAGE_POSITION),
+                       m_area.x, yoffset + m_area.y, m_area.w, m_area.h, 0);
+               sendMessage(HDATOM(_HILDON_TEXTURE_CLIENT_MESSAGE_SCALE),
+                       ratio_x * (1 << 16), ratio_y * (1 << 16), 0, 0, 0);
+               sendMessage(HDATOM(_HILDON_TEXTURE_CLIENT_MESSAGE_SHOW),
+                       1, 255, 0, 0, 0);
+       }
 
-#define HDATOM(X) hdAtomsValues[ ATOM ## X ]
+protected:
+       HDScalerBase(SDL_Surface* screen, int w, int h, float r_x, float r_y)
+       : m_screen(screen), m_w(w), m_h(h),
+        m_Bpp(m_screen->format->BitsPerPixel / 8),
+        ratio_x(r_x), ratio_y(r_y)
+       {
+               centerRectangle(m_area, GUI.Width, GUI.Height, w * r_x, h * r_y);
 
-static void hildon_load_atoms(Display* display)
-{
-       if (hdAtomsLoaded) return;
-       
-       XInternAtoms(display, (char**)hdAtomNames, ATOM_COUNT, True, hdAtomsValues);
-       hdAtomsLoaded = true;
-       
-       if (HDATOM(_HILDON_NON_COMPOSITED_WINDOW) == None) {
-               DIE("Hildon Desktop seems not be loaded, since %s is not defined",
-                       "_HILDON_NON_COMPOSITED_WINDOW");
-               return;
-       }
-}
+               // What we're going to do:
+               //  - Create a new window that we're going to manage
+               //  - Set up that window as a Hildon Remote Texture
+               //  - Render to that new window, instead of the SDL window ("screen").
+               // Yet another load of uglyness, but hey.
 
-/** Enables or disables the Hildon NonCompositedWindow property */
-static void hildon_set_non_compositing(bool enable)
-{
-       SDL_SysWMinfo wminfo;
-       Display *display;
-       Window xwindow;
-       XSetWindowAttributes xattr;
-       Atom atom;
-       int one = 1;
-       
-       SDL_VERSION(&wminfo.version);
-       if (!SDL_GetWMInfo(&wminfo)) return;
-       
-       wminfo.info.x11.lock_func();
-       display = wminfo.info.x11.display;
-       xwindow = wminfo.info.x11.fswindow;
-       hildon_load_atoms(display);
-
-       if (enable) {
-               /* 
-                * The basic idea behind this is to disable the override_redirect
-                * window attribute, which SDL sets, and instead use _NET_WM_STATE
-                * to tell hildon-desktop to fullscreen the app.
-                * I am not really happy with this, which should ideally be fixed
-                * at the libsdl level, but seems to work.
-                * As soon as the window is managed by Hildon-Desktop again, set for it
-                * not to be composited.
-                */
-               XUnmapWindow(display, xwindow);
-               xattr.override_redirect = False;
-               XChangeWindowAttributes(display, xwindow, CWOverrideRedirect, &xattr);
-
-               atom = HDATOM(_NET_WM_STATE_FULLSCREEN);
-               XChangeProperty(display, xwindow, HDATOM(_NET_WM_STATE),
+               // Clear the SDL screen with black, just in case it gets drawn.
+               SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 0, 0, 0));
+
+               display = WMinfo.info.x11.display;
+
+               // The parent window needs to be mapped, so we sync it.
+               XSync(display, True);
+
+               // Create our alternative window.
+               const int blackColor = BlackPixel(display, DefaultScreen(display));
+               window = XCreateSimpleWindow(display, DefaultRootWindow(display),
+                       0, 0, m_w, m_h, 0, blackColor, blackColor);
+               XStoreName(display, window, "DrNokSnes Video output window");
+               Atom atom = HDATOM(_HILDON_WM_WINDOW_TYPE_REMOTE_TEXTURE);
+               XChangeProperty(display, window, HDATOM(_NET_WM_WINDOW_TYPE),
                        XA_ATOM, 32, PropModeReplace,
                        (unsigned char *) &atom, 1);
+               XSelectInput(display, window, PropertyChangeMask | StructureNotifyMask);
+               XMapWindow(display, window);
+
+               // Wait for "ready" property, set up by hildon-desktop after a while
+               // For now, loop here. In the future, merge with main event loop.
+               bool ready = false;
+               while (!ready) {
+                       XEvent e;
+                       XNextEvent(display, &e);
+                       switch(e.type) {
+                               case PropertyNotify:
+                                       if (e.xproperty.atom ==
+                                         HDATOM(_HILDON_TEXTURE_CLIENT_READY)) {
+                                               ready = true;
+                                       }
+                                       break;
+                               default:
+                                       break;
+                       }
+               }
 
-               XChangeProperty(display, xwindow, HDATOM(_HILDON_NON_COMPOSITED_WINDOW),
-                       XA_INTEGER, 32, PropModeReplace,
-                       (unsigned char *) &one, 1);
-               XMapWindow(display, xwindow);
-       } else {
-               xattr.override_redirect = True;
-               XDeleteProperty(display, xwindow,
-                       HDATOM(_HILDON_NON_COMPOSITED_WINDOW));
-               XChangeWindowAttributes(display, xwindow, CWOverrideRedirect, &xattr);
+               // Create a shared memory segment with hildon-desktop
+               shmkey = ftok(S9xGetFilename(FILE_ROM), 'v');
+               shmid = shmget(shmkey, m_w * m_h * m_Bpp, IPC_CREAT | 0777);
+               if (shmid < 0) {
+                       DIE("Failed to create shared memory");
+               }
+               shmaddr = shmat(shmid, 0, 0);
+               if (shmaddr == (void*)-1) {
+                       DIE("Failed to attach shared memory");
+               }
+
+               // Send all configuration events to hildon-desktop
+               reconfigure();
        }
 
-       wminfo.info.x11.unlock_func();
-}
+public:
+       virtual ~HDScalerBase()
+       {
+               // Hide, unparent and deattach the remote texture
+               sendMessage(HDATOM(_HILDON_TEXTURE_CLIENT_MESSAGE_SHOW),
+                       0, 255, 0, 0, 0);
+               sendMessage(HDATOM(_HILDON_TEXTURE_CLIENT_MESSAGE_PARENT),
+                       0, 0, 0, 0, 0);
+               sendMessage(HDATOM(_HILDON_TEXTURE_CLIENT_MESSAGE_SHM),
+                       0, 0, 0, 0, 0);
+               XFlush(display);
+               // Destroy our managed window and shared memory segment
+               XDestroyWindow(display, window);
+               XSync(display, True);
+               shmdt(shmaddr);
+               shmctl(shmid, IPC_RMID, 0);
+       };
+
+       virtual uint8* getDrawBuffer() const
+       {
+               return reinterpret_cast<uint8*>(shmaddr);
+       };
+
+       virtual unsigned int getDrawBufferPitch() const
+       {
+               return m_w * m_Bpp;
+       };
+
+       virtual void getRenderedGUIArea(unsigned short & x, unsigned short & y,
+                                                       unsigned short & w, unsigned short & h) const
+       {
+               x = m_area.x; y = m_area.y; w = m_area.w; h = m_area.h;
+       };
+
+       virtual void getRatio(float & x, float & y) const
+       {
+               x = ratio_x; y = ratio_y;
+       };
+
+       virtual void prepare()
+       {
+
+       };
+
+       virtual void finish()
+       {
+               // Send a damage event to hildon-desktop.
+               sendMessage(HDATOM(_HILDON_TEXTURE_CLIENT_MESSAGE_DAMAGE),
+                       0, 0, m_w, m_h, 0);
+               XSync(display, False);
+       };
+
+       virtual void pause() { };
+       virtual void resume() { };
+};
+
+class HDFillScaler : public HDScalerBase
+{
+       HDFillScaler(SDL_Surface* screen, int w, int h)
+       : HDScalerBase(screen, w, h,
+               GUI.Width / (float)w, GUI.Height / (float)h)
+       {
+       }
+
+public:
+       class Factory : public ScalerFactory
+       {
+               const char * getName() const
+               {
+                       return "hdfill";
+               }
+
+               bool canEnable(int bpp, int w, int h) const
+               {
+                       return true;
+               }
+
+               Scaler* instantiate(SDL_Surface* screen, int w, int h) const
+               {
+                       return new HDFillScaler(screen, w, h-20);
+               }
+       };
+
+       static const Factory factory;
+
+       virtual const char * getName() const
+       {
+               return "hildon-desktop fill screen scaling";
+       }
+};
+const HDFillScaler::Factory HDFillScaler::factory;
+
+class HDSquareScaler : public HDScalerBase
+{
+       HDSquareScaler(SDL_Surface* screen, int w, int h, float ratio)
+       : HDScalerBase(screen, w, h, ratio, ratio)
+       {
+       }
+
+public:
+       class Factory : public ScalerFactory
+       {
+               const char * getName() const
+               {
+                       return "hdsq";
+               }
+
+               bool canEnable(int bpp, int w, int h) const
+               {
+                       return true;
+               }
+
+               Scaler* instantiate(SDL_Surface* screen, int w, int h) const
+               {
+                       return new HDSquareScaler(screen, w, h,
+                               fminf(GUI.Width / (float)w, GUI.Height / (float)h));
+               }
+       };
+
+       static const Factory factory;
+
+       virtual const char * getName() const
+       {
+               return "hildon-desktop square screen scaling";
+       }
+};
+const HDSquareScaler::Factory HDSquareScaler::factory;
 
 class HDDummy : public DummyScaler
 {
        HDDummy(SDL_Surface* screen, int w, int h)
        : DummyScaler(screen, w, h)
        {
-               hildon_set_non_compositing(true);
+               hd_set_non_compositing(true);
        }
        
 public:
        ~HDDummy()
        {
-               hildon_set_non_compositing(false);
+               hd_set_non_compositing(false);
        };
 
        class Factory : public ScalerFactory
@@ -486,7 +645,7 @@ public:
 
                bool canEnable(int bpp, int w, int h) const
                {
-                       return true;
+                       return Config.fullscreen; // This makes sense only in fullscreen
                }
 
                Scaler* instantiate(SDL_Surface* screen, int w, int h) const
@@ -509,13 +668,13 @@ class HDSW : public SWScaler
        HDSW(SDL_Surface* screen, int w, int h)
        : SWScaler(screen, w, h)
        {
-               hildon_set_non_compositing(true);
+               hd_set_non_compositing(true);
        }
        
 public:
        ~HDSW()
        {
-               hildon_set_non_compositing(false);
+               hd_set_non_compositing(false);
        };
 
        class Factory : public ScalerFactory
@@ -527,7 +686,7 @@ public:
 
                bool canEnable(int bpp, int w, int h) const
                {
-                       return true;
+                       return Config.fullscreen; // This makes sense only in fullscreen
                }
 
                Scaler* instantiate(SDL_Surface* screen, int w, int h) const
@@ -544,7 +703,50 @@ public:
        }
 };
 const HDSW::Factory HDSW::factory;
-#endif
+
+#ifdef __arm__
+class HDARM : public ARMScaler
+{
+       HDARM(SDL_Surface* screen, int w, int h)
+       : ARMScaler(screen, w, h)
+       {
+               hildon_set_non_compositing(true);
+       }
+       
+public:
+       ~HDARM()
+       {
+               hildon_set_non_compositing(false);
+       };
+
+       class Factory : public ScalerFactory
+       {
+               const char * getName() const
+               {
+                       return "hdarm2x";
+               }
+
+               bool canEnable(int bpp, int w, int h) const
+               {
+                       return Config.fullscreen; // This makes sense only in fullscreen
+               }
+
+               Scaler* instantiate(SDL_Surface* screen, int w, int h) const
+               {
+                       return new HDARM(screen, w, h);
+               }
+       };
+
+       static const Factory factory;
+
+       const char * getName() const
+       {
+               return "compositor disabled and software ARM 2x scaling";
+       }
+};
+const HDARM::Factory HDARM::factory;
+#endif /* __arm__ */
+#endif /* CONF_HD */
 
 #if CONF_XSP
 class XSPScaler : public Scaler
@@ -627,9 +829,9 @@ public:
                x = m_area.x; y = m_area.y; w = m_area.w; h = m_area.h;
        };
 
-       int getRatio() const
+       void getRatio(float & x, float & y) const
        {
-               return 2;
+               x = 2.0f; y = 2.0f;
        };
 
        void prepare() 
@@ -664,20 +866,28 @@ const XSPScaler::Factory XSPScaler::factory;
 
 static const ScalerFactory* scalers[] = {
 /* More useful scalers come first */
+#if CONF_HD && defined(__arm__)
+       &HDARM::factory,                                /* non-composited arm 2x scaling */
+#endif
+#if CONF_HD
+       &HDSquareScaler::factory,               /* h-d assisted square scaling */
+       &HDSW::factory,                                 /* non-composited soft 2x scaling */
+#endif
 #if CONF_XSP
-       &XSPScaler::factory,
+       &XSPScaler::factory,                    /* n8x0 pixel doubling */
 #endif
 #ifdef __arm__
-       &ARMScaler::factory,
+       &ARMScaler::factory,                    /* arm 2x scaling */
 #endif
+       &SWScaler::factory,                             /* soft 2x scaling */
 #if CONF_HD
-       &HDSW::factory,
+       &HDDummy::factory,                              /* non composited */
 #endif
-       &SWScaler::factory,
+       &DummyScaler::factory,                  /* failsafe */
+/* The following scalers will not be automatically enabled, no matter what */
 #if CONF_HD
-       &HDDummy::factory,
+       &HDFillScaler::factory,
 #endif
-       &DummyScaler::factory,
 };
 
 /* Entry point functions */