Bug 708154: Add a fallback non-GL rendering patch to the gonk widget backend and add support for the qemu touch device. r=kmachulis,gal pending-r=mwu
authorChris Jones <jones.chris.g@gmail.com>
Fri, 23 Dec 2011 23:13:33 -0800
changeset 84565 94b5440efa60f2b3782898b3947c8afde2ca9ae9
parent 84564 25224a78f895a98acca5cc9db2d47239fb266d84
child 84566 91909393c5a0c1621fc8e327ceecfb3df0da24d5
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmachulis, gal
bugs708154
milestone12.0a1
Bug 708154: Add a fallback non-GL rendering patch to the gonk widget backend and add support for the qemu touch device. r=kmachulis,gal pending-r=mwu
widget/src/gonk/Framebuffer.cpp
widget/src/gonk/Framebuffer.h
widget/src/gonk/Makefile.in
widget/src/gonk/nsAppShell.cpp
widget/src/gonk/nsWindow.cpp
new file mode 100644
--- /dev/null
+++ b/widget/src/gonk/Framebuffer.cpp
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: sw=2 ts=8 et ft=cpp : */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at:
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Code.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Chris Jones <jones.chris.g@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include <fcntl.h>
+#include <linux/fb.h>
+#include <linux/kd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "android/log.h"
+
+#include "Framebuffer.h"
+#include "gfxImageSurface.h"
+#include "mozilla/FileUtils.h"
+#include "nsTArray.h"
+
+#define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
+
+using namespace std;
+
+namespace mozilla {
+
+namespace Framebuffer {
+
+static int sFd = -1;
+static size_t sMappedSize;
+static struct fb_var_screeninfo sVi;
+static size_t sActiveBuffer;
+typedef vector<nsRefPtr<gfxImageSurface> > BufferVector;
+BufferVector* sBuffers;
+
+BufferVector& Buffers() { return *sBuffers; }
+
+bool
+SetGraphicsMode()
+{
+    ScopedClose fd(open("/dev/tty0", O_RDWR | O_SYNC));
+    if (0 > fd.mFd) {
+        // This is non-fatal; post-Cupcake kernels don't have tty0.
+        LOG("No /dev/tty0?");
+    } else if (ioctl(fd.mFd, KDSETMODE, (void*) KD_GRAPHICS)) {
+        LOG("Error setting graphics mode on /dev/tty0");
+        return false;
+    }
+    return true;
+}
+
+bool
+Open(nsIntSize* aScreenSize)
+{
+    if (0 <= sFd)
+        return true;
+
+    if (!SetGraphicsMode())
+        return false;
+
+    ScopedClose fd(open("/dev/graphics/fb0", O_RDWR));
+    if (0 > fd.mFd) {
+        LOG("Error opening framebuffer device");
+        return false;
+    }
+
+    struct fb_fix_screeninfo fi;
+    if (0 > ioctl(fd.mFd, FBIOGET_FSCREENINFO, &fi)) {
+        LOG("Error getting fixed screeninfo");
+        return false;
+    }
+
+    if (0 > ioctl(fd.mFd, FBIOGET_VSCREENINFO, &sVi)) {
+        LOG("Error getting variable screeninfo");
+        return false;
+    }
+
+    sMappedSize = fi.smem_len;
+    void* mem = mmap(0, sMappedSize, PROT_READ | PROT_WRITE, MAP_SHARED,
+                     fd.mFd, 0);
+    if (MAP_FAILED == mem) {
+        LOG("Error mmap'ing framebuffer");
+        return false;
+    }
+
+    sFd = fd.mFd;
+    fd.mFd = -1;
+
+    // The android porting doc requires a /dev/graphics/fb0 device
+    // that's double buffered with r5g6b5 format.  Hence the
+    // hard-coded numbers here.
+    gfxASurface::gfxImageFormat format = gfxASurface::ImageFormatRGB16_565;
+    int bytesPerPixel = gfxASurface::BytePerPixelFromFormat(format);
+    gfxIntSize size(sVi.xres, sVi.yres);
+    long stride = size.width * bytesPerPixel;
+    size_t numFrameBytes = stride * size.height;
+
+    sBuffers = new BufferVector(2);
+    unsigned char* data = static_cast<unsigned char*>(mem);
+    for (size_t i = 0; i < 2; ++i, data += numFrameBytes) {
+      memset(data, 0, numFrameBytes);
+      Buffers()[i] = new gfxImageSurface(data, size, stride, format);
+    }
+
+    // Clear the framebuffer to a known state.
+    Present();
+
+    *aScreenSize = size;
+    return true;
+}
+
+void
+Close()
+{
+    if (0 > sFd)
+        return;
+
+    munmap(Buffers()[0]->Data(), sMappedSize);
+    delete sBuffers;
+    sBuffers = NULL;
+
+    close(sFd);
+    sFd = -1;
+}
+
+gfxASurface*
+BackBuffer()
+{
+    return Buffers()[!sActiveBuffer];
+}
+
+void
+Present()
+{
+    sActiveBuffer = !sActiveBuffer;
+
+    sVi.yres_virtual = sVi.yres * 2;
+    sVi.yoffset = sActiveBuffer * sVi.yres;
+    sVi.bits_per_pixel = 16;
+    if (ioctl(sFd, FBIOPUT_VSCREENINFO, &sVi) < 0) {
+        LOG("Error presenting front buffer");
+    }
+}
+
+} // namespace Framebuffer
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/widget/src/gonk/Framebuffer.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: sw=2 ts=8 et ft=cpp : */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at:
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Code.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Chris Jones <jones.chris.g@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+class gfxASurface;
+class nsIntSize;
+
+namespace mozilla {
+
+namespace Framebuffer {
+
+//
+// The general usage of Framebuffer is
+//
+// -- in initialization code --
+//  Open();
+//
+// -- ready to paint next frame --
+//  nsRefPtr<gfxASurface> backBuffer = BackBuffer();
+//  // ...
+//  Paint(backBuffer);
+//  // ...
+//  Present();
+//
+
+// Return true if the fbdev was successfully opened, along with the
+// dimensions of the screen.  If this fails, the result of all further
+// calls is undefined.  Open() is idempotent.
+bool Open(nsIntSize* aScreenSize);
+
+// After Close(), the result of all further calls is undefined.
+// Close() is idempotent, and Open() can be called again after
+// Close().
+void Close();
+
+// Return the buffer to be drawn into, that will be the next frame.
+gfxASurface* BackBuffer();
+
+// Swap the front buffer for the back buffer.
+void Present();
+
+} // namespace Framebuffer
+
+} // namespace mozilla
--- a/widget/src/gonk/Makefile.in
+++ b/widget/src/gonk/Makefile.in
@@ -47,16 +47,17 @@ LIBRARY_NAME    = widget_gonk
 EXPORT_LIBRARY  = 1
 IS_COMPONENT    = 1
 MODULE_NAME     = nsWidgetGonkModule
 GRE_MODULE      = 1
 LIBXUL_LIBRARY  = 1
 
 
 CPPSRCS	= \
+	Framebuffer.cpp \
 	nsAppShell.cpp \
 	nsWidgetFactory.cpp \
 	nsWindow.cpp \
 	nsLookAndFeel.cpp \
 	nsScreenManagerGonk.cpp \
 	$(NULL)
 
 SHARED_LIBRARY_LIBS = ../xpwidgets/libxpwidgets_s.a
--- a/widget/src/gonk/nsAppShell.cpp
+++ b/widget/src/gonk/nsAppShell.cpp
@@ -57,16 +57,19 @@
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 
 #include "android/log.h"
 
 #ifndef ABS_MT_TOUCH_MAJOR
 // Taken from include/linux/input.h
 // XXX update the bionic input.h so we don't have to do this!
+#define ABS_X			0x00
+#define ABS_Y			0x01
+// ...
 #define ABS_MT_TOUCH_MAJOR      0x30    /* Major axis of touching ellipse */
 #define ABS_MT_TOUCH_MINOR      0x31    /* Minor axis (omit if circular) */
 #define ABS_MT_WIDTH_MAJOR      0x32    /* Major axis of approaching ellipse */
 #define ABS_MT_WIDTH_MINOR      0x33    /* Minor axis (omit if circular) */
 #define ABS_MT_ORIENTATION      0x34    /* Ellipse orientation */
 #define ABS_MT_POSITION_X       0x35    /* Center X ellipse position */
 #define ABS_MT_POSITION_Y       0x36    /* Center Y ellipse position */
 #define ABS_MT_TOOL_TYPE        0x37    /* Type of touching device */
@@ -131,16 +134,97 @@ sendMouseEvent(PRUint32 msg, struct time
     event.button = nsMouseEvent::eLeftButton;
     if (msg != NS_MOUSE_MOVE)
         event.clickCount = 1;
 
     nsWindow::DispatchInputEvent(event);
     //LOG("Dispatched type %d at %dx%d", msg, x, y);
 }
 
+static nsEventStatus
+sendKeyEventWithMsg(PRUint32 keyCode,
+                    PRUint32 msg,
+                    const timeval &time,
+                    PRUint32 flags)
+{
+    nsKeyEvent event(true, msg, NULL);
+    event.keyCode = keyCode;
+    event.time = timevalToMS(time);
+    event.flags |= flags;
+    return nsWindow::DispatchInputEvent(event);
+}
+
+static void
+sendKeyEvent(PRUint32 keyCode, bool down, const timeval &time)
+{
+    nsEventStatus status =
+        sendKeyEventWithMsg(keyCode, down ? NS_KEY_DOWN : NS_KEY_UP, time, 0);
+    if (down) {
+        sendKeyEventWithMsg(keyCode, NS_KEY_PRESS, time,
+                            status == nsEventStatus_eConsumeNoDefault ?
+                            NS_EVENT_FLAG_NO_DEFAULT : 0);
+    }
+}
+
+static void
+sendSpecialKeyEvent(nsIAtom *command, const timeval &time)
+{
+    nsCommandEvent event(true, nsGkAtoms::onAppCommand, command, NULL);
+    event.time = timevalToMS(time);
+    nsWindow::DispatchInputEvent(event);
+}
+
+static void
+maybeSendKeyEvent(const input_event& e)
+{
+    if (e.type != EV_KEY) {
+        LOG("Got unknown key event type. type 0x%04x code 0x%04x value %d",
+            e.type, e.code, e.value);
+        return;
+    }
+
+    if (e.value != 0 && e.value != 1) {
+        LOG("Got unknown key event value. type 0x%04x code 0x%04x value %d",
+            e.type, e.code, e.value);
+        return;
+    }
+
+    bool pressed = e.value == 1;
+    switch (e.code) {
+    case KEY_BACK:
+        sendKeyEvent(NS_VK_ESCAPE, pressed, e.time);
+        break;
+    case KEY_MENU:
+        if (!pressed)
+            sendSpecialKeyEvent(nsGkAtoms::Menu, e.time);
+        break;
+    case KEY_SEARCH:
+        if (pressed)
+            sendSpecialKeyEvent(nsGkAtoms::Search, e.time);
+        break;
+    case KEY_HOME:
+        sendKeyEvent(NS_VK_HOME, pressed, e.time);
+        break;
+    case KEY_POWER:
+        sendKeyEvent(NS_VK_SLEEP, pressed, e.time);
+        break;
+    case KEY_VOLUMEUP:
+        if (pressed)
+            sendSpecialKeyEvent(nsGkAtoms::VolumeUp, e.time);
+        break;
+    case KEY_VOLUMEDOWN:
+        if (pressed)
+            sendSpecialKeyEvent(nsGkAtoms::VolumeDown, e.time);
+        break;
+    default:
+        LOG("Got unknown key event code. type 0x%04x code 0x%04x value %d",
+            e.type, e.code, e.value);
+    }
+}
+
 static void
 multitouchHandler(int fd, FdHandler *data)
 {
     // The Linux's input documentation (Documentation/input/input.txt)
     // says that we'll always read a multiple of sizeof(input_event) bytes here.
     input_event events[16];
     int event_count = read(fd, events, sizeof(events));
     if (event_count < 0) {
@@ -212,47 +296,74 @@ multitouchHandler(int fd, FdHandler *dat
                     event->type, event->code, event->value);
             }
         } else
             LOG("Got unknown event type 0x%04x with code 0x%04x and value %d",
                 event->type, event->code, event->value);
     }
 }
 
-static nsEventStatus
-sendKeyEventWithMsg(PRUint32 keyCode,
-                    PRUint32 msg,
-                    const timeval &time,
-                    PRUint32 flags)
-{
-    nsKeyEvent event(true, msg, NULL);
-    event.keyCode = keyCode;
-    event.time = timevalToMS(time);
-    event.flags |= flags;
-    return nsWindow::DispatchInputEvent(event);
-}
-
 static void
-sendKeyEvent(PRUint32 keyCode, bool down, const timeval &time)
+singleTouchHandler(int fd, FdHandler *data)
 {
-    nsEventStatus status =
-        sendKeyEventWithMsg(keyCode, down ? NS_KEY_DOWN : NS_KEY_UP, time, 0);
-    if (down) {
-        sendKeyEventWithMsg(keyCode, NS_KEY_PRESS, time,
-                            status == nsEventStatus_eConsumeNoDefault ?
-                            NS_EVENT_FLAG_NO_DEFAULT : 0);
+    // The Linux's input documentation (Documentation/input/input.txt)
+    // says that we'll always read a multiple of sizeof(input_event) bytes here.
+    input_event events[16];
+    int event_count = read(fd, events, sizeof(events));
+    if (event_count < 0) {
+        LOG("Error reading in singleTouchHandler");
+        return;
     }
-}
+    MOZ_ASSERT(event_count % sizeof(input_event) == 0);
+
+    event_count /= sizeof(struct input_event);
+
+    for (int i = 0; i < event_count; i++) {
+        input_event *event = &events[i];
 
-static void
-sendSpecialKeyEvent(nsIAtom *command, const timeval &time)
-{
-    nsCommandEvent event(true, nsGkAtoms::onAppCommand, command, NULL);
-    event.time = timevalToMS(time);
-    nsWindow::DispatchInputEvent(event);
+        if (event->type == EV_KEY) {
+            switch (event->code) {
+            case BTN_TOUCH:
+                data->mtDown = event->value;
+                break;
+            default:
+                maybeSendKeyEvent(*event);
+            }
+        }
+        else if (event->type == EV_ABS) {
+            switch (event->code) {
+            case ABS_X:
+                data->mtX = event->value;
+                break;
+            case ABS_Y:
+                data->mtY = event->value;
+                break;
+            default:
+                LOG("Got unknown st abs event type 0x%04x with code 0x%04x and value %d",
+                    event->type, event->code, event->value);
+            }
+        } else if (event->type == EV_SYN) {
+            if (data->mtState == FdHandler::MT_START) {
+                MOZ_ASSERT(data->mtDown);
+                sendMouseEvent(NS_MOUSE_BUTTON_DOWN, &event->time,
+                               data->mtX, data->mtY);
+                data->mtState = FdHandler::MT_COLLECT;
+            } else if (data->mtDown) {
+                MOZ_ASSERT(data->mtDown);
+                sendMouseEvent(NS_MOUSE_MOVE, &event->time,
+                               data->mtX, data->mtY);
+            } else {
+                MOZ_ASSERT(!data->mtDown);
+                sendMouseEvent(NS_MOUSE_BUTTON_UP, &event->time,
+                                   data->mtX, data->mtY);
+                data->mtDown = false;
+                data->mtState = FdHandler::MT_START;
+            }
+        }
+    }
 }
 
 static void
 keyHandler(int fd, FdHandler *data)
 {
     input_event events[16];
     ssize_t bytesRead = read(fd, events, sizeof(events));
     if (bytesRead < 0) {
@@ -264,60 +375,17 @@ keyHandler(int fd, FdHandler *data)
     for (unsigned int i = 0; i < bytesRead / sizeof(struct input_event); i++) {
         const input_event &e = events[i];
 
         if (e.type == EV_SYN) {
             // Ignore this event; it just signifies that a key was pressed.
             continue;
         }
 
-        if (e.type != EV_KEY) {
-            LOG("Got unknown key event type. type 0x%04x code 0x%04x value %d",
-                e.type, e.code, e.value);
-            continue;
-        }
-
-        if (e.value != 0 && e.value != 1) {
-            LOG("Got unknown key event value. type 0x%04x code 0x%04x value %d",
-                e.type, e.code, e.value);
-            continue;
-        }
-
-        bool pressed = e.value == 1;
-        const char* upOrDown = pressed ? "pressed" : "released";
-        switch (e.code) {
-        case KEY_BACK:
-            sendKeyEvent(NS_VK_ESCAPE, pressed, e.time);
-            break;
-        case KEY_MENU:
-            if (!pressed)
-                sendSpecialKeyEvent(nsGkAtoms::Menu, e.time);
-            break;
-        case KEY_SEARCH:
-            if (pressed)
-                sendSpecialKeyEvent(nsGkAtoms::Search, e.time);
-            break;
-        case KEY_HOME:
-            sendKeyEvent(NS_VK_HOME, pressed, e.time);
-            break;
-        case KEY_POWER:
-            sendKeyEvent(NS_VK_SLEEP, pressed, e.time);
-            break;
-        case KEY_VOLUMEUP:
-            if (pressed)
-                sendSpecialKeyEvent(nsGkAtoms::VolumeUp, e.time);
-            break;
-        case KEY_VOLUMEDOWN:
-            if (pressed)
-                sendSpecialKeyEvent(nsGkAtoms::VolumeDown, e.time);
-            break;
-        default:
-            LOG("Got unknown key event code. type 0x%04x code 0x%04x value %d",
-                e.type, e.code, e.value);
-        }
+        maybeSendKeyEvent(e);
     }
 }
 
 nsAppShell::nsAppShell()
     : mNativeCallbackRequest(false)
     , mHandlers()
 {
     gAppShell = this;
@@ -373,18 +441,22 @@ nsAppShell::Init()
             continue;
 
         FdHandlerCallback handlerFunc = NULL;
 
         char flags[(NS_MAX(ABS_MAX, KEY_MAX) + 1) / 8];
         if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(flags)), flags) >= 0 &&
             BITSET(ABS_MT_POSITION_X, flags)) {
 
-            LOG("Found absolute input device");
+            LOG("Found multitouch input device");
             handlerFunc = multitouchHandler;
+        } else if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(flags)), flags) >= 0 &&
+                   BITSET(ABS_X, flags)) {
+            LOG("Found single touch input device");
+            handlerFunc = singleTouchHandler;
         } else if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(flags)), flags) >= 0) {
             LOG("Found key input device");
             handlerFunc = keyHandler;
         }
 
         // Register the handler, if we have one.
         if (!handlerFunc)
             continue;
--- a/widget/src/gonk/nsWindow.cpp
+++ b/widget/src/gonk/nsWindow.cpp
@@ -30,70 +30,115 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include "android/log.h"
+#include "ui/FramebufferNativeWindow.h"
+
+#include "Framebuffer.h"
+#include "gfxContext.h"
+#include "gfxUtils.h"
+#include "GLContextProvider.h"
+#include "LayerManagerOGL.h"
 #include "nsAutoPtr.h"
 #include "nsAppShell.h"
 #include "nsTArray.h"
 #include "nsWindow.h"
 
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include "ui/FramebufferNativeWindow.h"
-
-#include "LayerManagerOGL.h"
-#include "GLContextProvider.h"
-
-#include "android/log.h"
-
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
 
 #define IS_TOPLEVEL() (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog)
 
+using namespace mozilla;
 using namespace mozilla::gl;
 using namespace mozilla::layers;
 using namespace mozilla::widget;
 
 nsIntRect gScreenBounds;
 
 static nsRefPtr<GLContext> sGLContext;
 static nsTArray<nsWindow *> sTopWindows;
 static nsWindow *gWindowToRedraw = nsnull;
 static nsWindow *gFocusedWindow = nsnull;
 static android::FramebufferNativeWindow *gNativeWindow = nsnull;
+static bool sFramebufferOpen;
 
 nsWindow::nsWindow()
 {
-    if (!sGLContext) {
+    if (!sGLContext && !sFramebufferOpen) {
+        // We (apparently) don't have a way to tell if allocating the
+        // fbs succeeded or failed.
         gNativeWindow = new android::FramebufferNativeWindow();
         sGLContext = GLContextProvider::CreateForWindow(this);
         // CreateForWindow sets up gScreenBounds
+        if (!sGLContext) {
+            LOG("Failed to create GL context for fb, trying /dev/fb0");
+
+            // We can't delete gNativeWindow.
+
+            nsIntSize screenSize;
+            sFramebufferOpen = Framebuffer::Open(&screenSize);
+            gScreenBounds = nsIntRect(nsIntPoint(0, 0), screenSize);
+            if (!sFramebufferOpen) {
+                LOG("Failed to mmap fb(?!?), aborting ...");
+                NS_RUNTIMEABORT("Can't open GL context and can't fall back on /dev/fb0 ...");
+            }
+        }
     }
 }
 
 nsWindow::~nsWindow()
 {
 }
 
 void
 nsWindow::DoDraw(void)
 {
-    if (!gWindowToRedraw)
+    if (!gWindowToRedraw) {
+        LOG("  no window to draw, bailing");
         return;
+    }
 
     nsPaintEvent event(true, NS_PAINT, gWindowToRedraw);
     event.region = gScreenBounds;
-    static_cast<LayerManagerOGL*>(gWindowToRedraw->GetLayerManager(nsnull))->
-                    SetClippingRegion(nsIntRegion(gScreenBounds));
-    gWindowToRedraw->mEventCallback(&event);
+
+    LayerManager* lm = gWindowToRedraw->GetLayerManager();
+    if (LayerManager::LAYERS_OPENGL == lm->GetBackendType()) {
+        static_cast<LayerManagerOGL*>(lm)->
+            SetClippingRegion(nsIntRegion(gScreenBounds));
+        gWindowToRedraw->mEventCallback(&event);
+    } else if (LayerManager::LAYERS_BASIC == lm->GetBackendType()) {
+        MOZ_ASSERT(sFramebufferOpen);
+
+        nsRefPtr<gfxASurface> backBuffer = Framebuffer::BackBuffer();
+        {
+            nsRefPtr<gfxContext> ctx = new gfxContext(backBuffer);
+            ctx->NewPath();
+            gfxUtils::PathFromRegion(ctx, event.region);
+            ctx->Clip();
+
+            // No double-buffering needed.
+            AutoLayerManagerSetup setupLayerManager(
+                gWindowToRedraw, ctx, BasicLayerManager::BUFFER_NONE);
+            gWindowToRedraw->mEventCallback(&event);
+        }
+        backBuffer->Flush();
+
+        Framebuffer::Present();
+    } else {
+        NS_RUNTIMEABORT("Unexpected layer manager type");
+    }
 }
 
 nsEventStatus
 nsWindow::DispatchInputEvent(nsGUIEvent &aEvent)
 {
     if (!gFocusedWindow)
         return nsEventStatus_eIgnore;
 
@@ -238,18 +283,20 @@ nsWindow::ConfigureChildren(const nsTArr
 
 NS_IMETHODIMP
 nsWindow::Invalidate(const nsIntRect &aRect,
                      bool aIsSynchronous)
 {
     nsWindow *parent = mParent;
     while (parent && parent != sTopWindows[0])
         parent = parent->mParent;
-    if (parent != sTopWindows[0])
+    if (parent != sTopWindows[0]) {
+        LOG("  parent isn't top window, bailing");
         return NS_OK;
+    }
 
     gWindowToRedraw = this;
     gDrawRequest = true;
     mozilla::NotifyEvent();
     return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -332,28 +379,27 @@ nsWindow::GetLayerManager(PLayersChild* 
 
     nsWindow *topWindow = sTopWindows[0];
 
     if (!topWindow) {
         LOG(" -- no topwindow\n");
         return nsnull;
     }
 
-    if (!sGLContext) {
-        LOG(" -- no GLContext\n");
-        return nsnull;
-    }
+    if (sGLContext) {
+        nsRefPtr<LayerManagerOGL> layerManager = new LayerManagerOGL(this);
 
-    nsRefPtr<LayerManagerOGL> layerManager =
-        new LayerManagerOGL(this);
-
-    if (layerManager->Initialize(sGLContext))
-        mLayerManager = layerManager;
-    else
-        LOG("Could not create LayerManager");
+        if (layerManager->Initialize(sGLContext))
+            mLayerManager = layerManager;
+        else
+            LOG("Could not create OGL LayerManager");
+    } else {
+        MOZ_ASSERT(sFramebufferOpen);
+        mLayerManager = new BasicShadowLayerManager(this);
+    }
 
     return mLayerManager;
 }
 
 gfxASurface *
 nsWindow::GetThebesSurface()
 {
     /* This is really a dummy surface; this is only used when doing reflow, because