Bug 538323. Part 1: create ImageLayers and associated API for displaying pixel-buffers in various formats. r=jrmuizel,sr=dbaron
authorRobert O'Callahan <robert@ocallahan.org>
Tue, 02 Mar 2010 12:09:35 +1300
changeset 39498 36e7bd37612710d6789796f9367309691c4f8460
parent 39497 568f79f6d0c4f6d05a3e60695042b756032b155c
child 39499 1a4c3d1d0c57cd32f1f8f12b159c736fba980cdb
push idunknown
push userunknown
push dateunknown
reviewersjrmuizel, dbaron
bugs538323
milestone1.9.3a4pre
Bug 538323. Part 1: create ImageLayers and associated API for displaying pixel-buffers in various formats. r=jrmuizel,sr=dbaron
gfx/layers/ImageLayers.h
gfx/layers/Layers.h
gfx/layers/Makefile.in
gfx/layers/basic/BasicImages.cpp
gfx/layers/basic/BasicLayers.cpp
gfx/layers/basic/BasicLayers.h
gfx/thebes/public/gfxTypes.h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ImageLayers.h
@@ -0,0 +1,272 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * ***** 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 Corporation code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Robert O'Callahan <robert@ocallahan.org>
+ *
+ * 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 ***** */
+
+#ifndef GFX_IMAGELAYER_H
+#define GFX_IMAGELAYER_H
+
+#include "Layers.h"
+
+#include "gfxPattern.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * A class representing a buffer of pixel data. The data can be in one
+ * of various formats including YCbCr.
+ * 
+ * Create an image using an ImageContainer. Fill the image with data, and
+ * then call ImageContainer::SetImage to display it. An image must not be
+ * modified after calling SetImage. Image implementations do not need to
+ * perform locking; when filling an Image, the Image client is responsible
+ * for ensuring only one thread accesses the Image at a time, and after
+ * SetImage the image is immutable.
+ * 
+ * When resampling an Image, only pixels within the buffer should be
+ * sampled. For example, cairo images should be sampled in EXTEND_PAD mode.
+ */
+class THEBES_API Image {
+  THEBES_INLINE_DECL_THREADSAFE_REFCOUNTING(Image)
+
+public:
+  virtual ~Image() {}
+
+  enum Format {
+    /**
+     * The PLANAR_YCBCR format creates a PlanarYCbCrImage. All backends should
+     * support this format, because the Ogg video decoder depends on it.
+     * The maximum image width and height is 16384.
+     */
+    PLANAR_YCBCR,
+
+    /**
+     * The CAIRO_SURFACE format creates a CairoImage. All backends should
+     * support this format, because video rendering sometimes requires it.
+     * 
+     * This format is useful even though a ThebesLayer could be used.
+     * It makes it easy to render a cairo surface when another Image format
+     * could be used. It can also avoid copying the surface data in some
+     * cases.
+     * 
+     * Images in CAIRO_SURFACE format should only be created and
+     * manipulated on the main thread, since the underlying cairo surface
+     * is main-thread-only.
+     */
+    CAIRO_SURFACE
+  };
+
+  Format GetFormat() { return mFormat; }
+  void* GetImplData() { return mImplData; }
+
+protected:
+  Image(void* aImplData, Format aFormat) :
+    mImplData(aImplData),
+    mFormat(aFormat)
+  {}
+
+  void* mImplData;
+  Format mFormat;
+};
+
+/**
+ * A class that manages Images for an ImageLayer. The only reason
+ * we need a separate class here is that ImageLayers aren't threadsafe
+ * (because layers can only be used on the main thread) and we want to
+ * be able to set the current Image from any thread, to facilitate
+ * video playback without involving the main thread, for example.
+ */
+class THEBES_API ImageContainer {
+  THEBES_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageContainer)
+
+public:
+  virtual ~ImageContainer() {}
+
+  /**
+   * Create an Image in one of the given formats.
+   * Picks the "best" format from the list and creates an Image of that
+   * format.
+   * Returns null if this backend does not support any of the formats.
+   */
+  virtual already_AddRefed<Image> CreateImage(const Image::Format* aFormats,
+                                              PRUint32 aNumFormats) = 0;
+
+  /**
+   * Set an Image as the current image to display. The Image must have
+   * been created by this ImageContainer.
+   * 
+   * The Image data must not be modified after this method is called!
+   */
+  virtual void SetCurrentImage(Image* aImage) = 0;
+
+  /**
+   * Get the current Image.
+   * This has to add a reference since otherwise there are race conditions
+   * where the current image is destroyed before the caller can add
+   * a reference.
+   */
+  virtual already_AddRefed<Image> GetCurrentImage() = 0;
+
+  /**
+   * Get the current image as a gfxASurface. This is useful for fallback
+   * rendering.
+   * This can only be called from the main thread, since cairo objects
+   * can only be used from the main thread.
+   * This is defined here and not on Image because it's possible (likely)
+   * that some backends will make an Image "ready to draw" only when it
+   * becomes the current image for an image container.
+   * Returns null if there is no current image.
+   * Returns the size in aSize.
+   * The returned surface will never be modified. The caller must not
+   * modify it.
+   */
+  virtual already_AddRefed<gfxASurface> GetCurrentAsSurface(gfxIntSize* aSizeResult) = 0;
+
+  /**
+   * Returns the layer manager for this container. This can only
+   * be used on the main thread, since layer managers should only be
+   * accessed on the main thread.
+   */
+  LayerManager* Manager()
+  {
+    NS_PRECONDITION(NS_IsMainThread(), "Must be called on main thread");
+    return mManager;
+  }
+
+protected:
+  LayerManager* mManager;
+
+  ImageContainer(LayerManager* aManager) : mManager(aManager) {}
+};
+
+/**
+ * A Layer which renders an Image.
+ */
+class THEBES_API ImageLayer : public Layer {
+public:
+  /**
+   * CONSTRUCTION PHASE ONLY
+   * Set the ImageContainer. aContainer must have the same layer manager
+   * as this layer.
+   */
+  void SetContainer(ImageContainer* aContainer) { mContainer = aContainer; }
+  /**
+   * CONSTRUCTION PHASE ONLY
+   * Set the filter used to resample this image if necessary.
+   */
+  void SetFilter(gfxPattern::GraphicsFilter aFilter) { mFilter = aFilter; }
+
+  ImageContainer* GetContainer() { return mContainer; }
+  gfxPattern::GraphicsFilter GetFilter() { return mFilter; }
+
+protected:
+  ImageLayer(LayerManager* aManager, void* aImplData)
+    : Layer(aManager, aImplData), mFilter(gfxPattern::FILTER_GOOD) {}
+
+  nsRefPtr<ImageContainer> mContainer;
+  gfxPattern::GraphicsFilter mFilter;
+};
+
+/****** Image subtypes for the different formats ******/
+
+/**
+ * We assume that the image data is in the REC 470M color space (see
+ * Theora specification, section 4.3.1).
+ * XXX Eventually we should some color space parameter(s) here.
+ */
+class THEBES_API PlanarYCbCrImage : public Image {
+public:
+  struct Data {
+    // Luminance buffer
+    PRUint8* mYChannel;
+    PRInt32 mYStride;
+    gfxIntSize mYSize;
+    // Chroma buffers
+    PRUint8* mCbChannel;
+    PRUint8* mCrChannel;
+    PRInt32 mCbCrStride;
+    gfxIntSize mCbCrSize;
+  };
+
+  typedef void (* ToARGBHook)(const Data& aData, PRUint8* aOutput);
+  /**
+   * XXX this is just a hack until we can get YCbCr conversion code into
+   * gfx
+   * This must be called before SetData().
+   */
+  virtual void SetRGBConverter(ToARGBHook aHook) {}
+
+  /**
+   * This makes a copy of the data buffers.
+   * XXX Eventually we will change this to not make a copy of the data,
+   * but we can't do that until we have tighter control of nsOggDecoder's
+   * buffer management (i.e. not going through liboggplay). Right now
+   * it doesn't matter because the BasicLayer implementation does YCbCr
+   * conversion here anyway.
+   */
+  virtual void SetData(const Data& aData) = 0;
+
+protected:
+  PlanarYCbCrImage(void* aImplData) : Image(aImplData, PLANAR_YCBCR) {}
+};
+
+/**
+ * Currently, the data in a CairoImage surface is treated as being in the
+ * device output color space.
+ */
+class THEBES_API CairoImage : public Image {
+public:
+  struct Data {
+    gfxASurface* mSurface;
+    gfxIntSize mSize;
+  };
+
+  /**
+   * This can only be called on the main thread. It may add a reference
+   * to the surface (which will eventually be released on the main thread).
+   * The surface must not be modified after this call!!!
+   */
+  virtual void SetData(const Data& aData) = 0;
+
+protected:
+  CairoImage(void* aImplData) : Image(aImplData, CAIRO_SURFACE) {}
+};
+
+}
+}
+
+#endif /* GFX_IMAGELAYER_H */
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -48,16 +48,18 @@ class gfxContext;
 class nsPaintEvent;
 
 namespace mozilla {
 namespace layers {
 
 class Layer;
 class ThebesLayer;
 class ContainerLayer;
+class ImageLayer;
+class ImageContainer;
 
 /*
  * Motivation: For truly smooth animation and video playback, we need to
  * be able to compose frames and render them on a dedicated thread (i.e.
  * off the main thread where DOM manipulation, script execution and layout
  * induce difficult-to-bound latency). This requires Gecko to construct
  * some kind of persistent scene structure (graph or tree) that can be
  * safely transmitted across threads. We have other scenarios (e.g. mobile 
@@ -145,16 +147,26 @@ public:
    * Create a ThebesLayer for this manager's layer tree.
    */
   virtual already_AddRefed<ThebesLayer> CreateThebesLayer() = 0;
   /**
    * CONSTRUCTION PHASE ONLY
    * Create a ContainerLayer for this manager's layer tree.
    */
   virtual already_AddRefed<ContainerLayer> CreateContainerLayer() = 0;
+  /**
+   * CONSTRUCTION PHASE ONLY
+   * Create an ImageLayer for this manager's layer tree.
+   */
+  virtual already_AddRefed<ImageLayer> CreateImageLayer() = 0;
+
+  /**
+   * Can be called anytime
+   */
+  virtual already_AddRefed<ImageContainer> CreateImageContainer() = 0;
 };
 
 /**
  * A Layer represents anything that can be rendered onto a destination
  * surface.
  */
 class THEBES_API Layer {
   THEBES_INLINE_DECL_REFCOUNTING(Layer)  
--- a/gfx/layers/Makefile.in
+++ b/gfx/layers/Makefile.in
@@ -48,21 +48,23 @@ include $(DEPTH)/config/autoconf.mk
 MODULE         = layers
 LIBRARY_NAME   = layers
 EXPORT_LIBRARY = 1
 LIBXUL_LIBRARY = 1
 
 DEFINES += -DIMPL_THEBES
 
 EXPORTS = \
+        BasicLayers.h \
+        ImageLayers.h \
         Layers.h \
-        BasicLayers.h \
         $(NULL)
 
 CPPSRCS = \
+        BasicImages.cpp \
         BasicLayers.cpp \
         $(NULL)
 
 EXTRA_DSO_LIBS = \
         gkgfx \
         thebes \
         $(NULL)
 
@@ -70,8 +72,10 @@ EXTRA_DSO_LDOPTS += \
         $(LIBS_DIR) \
         $(EXTRA_DSO_LIBS) \
         $(MOZ_CAIRO_LIBS) \
         $(XPCOM_LIBS) \
         $(NSPR_LIBS) \
         $(NULL)
 
 include $(topsrcdir)/config/rules.mk
+
+CXXFLAGS += $(MOZ_CAIRO_CFLAGS)
new file mode 100644
--- /dev/null
+++ b/gfx/layers/basic/BasicImages.cpp
@@ -0,0 +1,278 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * ***** 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 Corporation code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Robert O'Callahan <robert@ocallahan.org>
+ *
+ * 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 "mozilla/Monitor.h"
+
+#include "ImageLayers.h"
+#include "BasicLayers.h"
+#include "gfxImageSurface.h"
+
+#ifdef XP_MACOSX
+#include "gfxQuartzImageSurface.h"
+#endif
+
+#include "cairo.h"
+
+using mozilla::Monitor;
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * All our images can yield up a cairo surface and their size.
+ */
+class BasicImageImplData {
+public:
+  /**
+   * This must be called on the main thread.
+   */
+  virtual already_AddRefed<gfxASurface> GetAsSurface() = 0;
+
+  gfxIntSize GetSize() { return mSize; }
+
+protected:
+  gfxIntSize mSize;
+};
+
+/**
+ * Since BasicLayers only paint on the main thread, handling a CairoImage
+ * is extremely simple. We just hang on to a reference to the surface and
+ * return that surface when BasicImageLayer::Paint asks for it via
+ * BasicImageContainer::GetAsSurface.
+ */
+class BasicCairoImage : public CairoImage, public BasicImageImplData {
+public:
+  BasicCairoImage() : CairoImage(static_cast<BasicImageImplData*>(this)) {}
+
+  virtual void SetData(const Data& aData)
+  {
+    mSurface = aData.mSurface;
+    mSize = aData.mSize;
+  }
+
+  virtual already_AddRefed<gfxASurface> GetAsSurface()
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Must be main thread");
+    nsRefPtr<gfxASurface> surface = mSurface.get();
+    return surface.forget();
+  }
+
+protected:
+  nsCountedRef<nsMainThreadSurfaceRef> mSurface;
+};
+
+/**
+ * We handle YCbCr by converting to RGB when the image is initialized
+ * (which should be done off the main thread). The RGB results are stored
+ * in a memory buffer and converted to a cairo surface lazily.
+ */
+class BasicPlanarYCbCrImage : public PlanarYCbCrImage, public BasicImageImplData {
+public:
+  BasicPlanarYCbCrImage() :
+    PlanarYCbCrImage(static_cast<BasicImageImplData*>(this)),
+    mToARGB(nsnull)
+    {}
+
+  virtual void SetRGBConverter(ToARGBHook aHook) { mToARGB = aHook; }
+  virtual void SetData(const Data& aData);
+
+  virtual already_AddRefed<gfxASurface> GetAsSurface();
+
+protected:
+  ToARGBHook                           mToARGB;
+  nsAutoArrayPtr<PRUint8>              mBuffer;
+  nsCountedRef<nsMainThreadSurfaceRef> mSurface;
+};
+
+void
+BasicPlanarYCbCrImage::SetData(const Data& aData)
+{
+  // Do some sanity checks to prevent integer overflow
+  if (aData.mYSize.width > 16384 || aData.mYSize.height > 16384) {
+    NS_ERROR("Illegal width or height");
+    return;
+  }
+  size_t size = aData.mYSize.width*aData.mYSize.height*4;
+  mBuffer = new PRUint8[size];
+  if (!mBuffer) {
+    // out of memory
+    return;
+  }
+
+  // Convert from YCbCr to RGB now
+  mToARGB(aData, mBuffer);
+  mSize = aData.mYSize;
+}
+
+static cairo_user_data_key_t imageSurfaceDataKey;
+
+static void
+DestroyBuffer(void* aBuffer)
+{
+  delete[] static_cast<PRUint8*>(aBuffer);
+}
+
+already_AddRefed<gfxASurface>
+BasicPlanarYCbCrImage::GetAsSurface()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Must be main thread");
+
+  if (mSurface) {
+    nsRefPtr<gfxASurface> result = mSurface.get();
+    return result.forget();
+  }
+
+  if (!mBuffer) {
+    return nsnull;
+  }
+  nsRefPtr<gfxImageSurface> imgSurface =
+      new gfxImageSurface(mBuffer, mSize,
+                          mSize.width * 4,
+                          gfxASurface::ImageFormatRGB24);
+  if (!imgSurface) {
+    return nsnull;
+  }
+
+  // Pass ownership of the buffer to the surface
+  imgSurface->SetData(&imageSurfaceDataKey, mBuffer.forget(), DestroyBuffer);
+
+  nsRefPtr<gfxASurface> result = imgSurface.get();
+#if defined(XP_MACOSX)
+  nsRefPtr<gfxQuartzImageSurface> quartzSurface =
+    new gfxQuartzImageSurface(imgSurface);
+  if (quartzSurface) {
+    result = quartzSurface.forget();
+  }
+#endif
+  mSurface = result.get();
+
+  return result.forget();
+}
+
+/**
+ * Our image container is very simple. It's really just a factory
+ * for the image objects. We use a Monitor to synchronize access to
+ * mImage.
+ */
+class BasicImageContainer : public ImageContainer {
+public:
+  BasicImageContainer(BasicLayerManager* aManager) :
+    ImageContainer(aManager), mMonitor("BasicImageContainer")
+  {}
+  virtual already_AddRefed<Image> CreateImage(const Image::Format* aFormats,
+                                              PRUint32 aNumFormats);
+  virtual void SetCurrentImage(Image* aImage);
+  virtual already_AddRefed<Image> GetCurrentImage();
+  virtual already_AddRefed<gfxASurface> GetCurrentAsSurface(gfxIntSize* aSize);
+
+protected:
+  Monitor mMonitor;
+  nsRefPtr<Image> mImage;
+};
+
+/**
+ * Returns true if aFormat is in the given format array.
+ */
+static PRBool
+FormatInList(const Image::Format* aFormats, PRUint32 aNumFormats,
+             Image::Format aFormat)
+{
+  for (PRUint32 i = 0; i < aNumFormats; ++i) {
+    if (aFormats[i] == aFormat) {
+      return PR_TRUE;
+    }
+  }
+  return PR_FALSE;
+}
+
+already_AddRefed<Image>
+BasicImageContainer::CreateImage(const Image::Format* aFormats,
+                                 PRUint32 aNumFormats)
+{
+  nsRefPtr<Image> image;
+  // Prefer cairo surfaces because they're native for us
+  if (FormatInList(aFormats, aNumFormats, Image::CAIRO_SURFACE)) {
+    image = new BasicCairoImage();
+  } else if (FormatInList(aFormats, aNumFormats, Image::PLANAR_YCBCR)) {
+    image = new BasicPlanarYCbCrImage();
+  }
+  return image.forget();
+}
+
+void
+BasicImageContainer::SetCurrentImage(Image* aImage)
+{
+  MonitorAutoEnter mon(mMonitor);
+  mImage = aImage;
+}
+
+already_AddRefed<Image>
+BasicImageContainer::GetCurrentImage()
+{
+  MonitorAutoEnter mon(mMonitor);
+  nsRefPtr<Image> image = mImage;
+  return image.forget();
+}
+
+static BasicImageImplData*
+ToImageData(Image* aImage)
+{
+  return static_cast<BasicImageImplData*>(aImage->GetImplData());
+}
+
+already_AddRefed<gfxASurface>
+BasicImageContainer::GetCurrentAsSurface(gfxIntSize* aSizeResult)
+{
+  NS_PRECONDITION(NS_IsMainThread(), "Must be called on main thread");
+
+  MonitorAutoEnter mon(mMonitor);
+  if (!mImage) {
+    return nsnull;
+  }
+  *aSizeResult = ToImageData(mImage)->GetSize();
+  return ToImageData(mImage)->GetAsSurface();
+}
+
+already_AddRefed<ImageContainer>
+BasicLayerManager::CreateImageContainer()
+{
+  nsRefPtr<ImageContainer> container = new BasicImageContainer(this);
+  return container.forget();
+}
+
+}
+}
--- a/gfx/layers/basic/BasicLayers.cpp
+++ b/gfx/layers/basic/BasicLayers.cpp
@@ -31,42 +31,73 @@
  * 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 "BasicLayers.h"
+#include "ImageLayers.h"
+
 #include "nsTArray.h"
 #include "nsGUIEvent.h"
 #include "nsIRenderingContext.h"
+#include "gfxContext.h"
+#include "gfxASurface.h"
+#include "gfxPattern.h"
 
 namespace mozilla {
 namespace layers {
 
 class BasicContainerLayer;
 
 /**
  * This is the ImplData for all Basic layers. It also exposes methods
- * private to the Basic implementation.
+ * private to the Basic implementation that are common to all Basic layer types.
+ * In particular, there is an internal Paint() method that we can use
+ * to paint the contents of non-Thebes layers.
+ *
+ * The class hierarchy for Basic layers is like this:
+ *                                 BasicImplData
+ * Layer                            |   |   |
+ *  |                               |   |   |
+ *  +-> ContainerLayer              |   |   |
+ *  |    |                          |   |   |
+ *  |    +-> BasicContainerLayer <--+   |   |
+ *  |                                   |   |
+ *  +-> ThebesLayer                     |   |
+ *  |    |                              |   |
+ *  |    +-> BasicThebesLayer <---------+   |
+ *  |                                       |
+ *  +-> ImageLayer                          |
+ *       |                                  |
+ *       +-> BasicImageLayer <--------------+
  */
 class BasicImplData {
 public:
   BasicImplData()
   {
     MOZ_COUNT_CTOR(BasicImplData);
   }
   ~BasicImplData()
   {
     MOZ_COUNT_DTOR(BasicImplData);
   }
 
   const nsIntRegion& GetVisibleRegion() { return mVisibleRegion; }
 
+  /**
+   * Layers that paint themselves, such as ImageLayers, should paint
+   * in response to this method call. aContext will already have been
+   * set up to account for all the properties of the layer (transform,
+   * opacity, etc).
+   */
+  virtual void Paint(gfxContext* aContext) {}
+
 protected:
   nsIntRegion mVisibleRegion;
 };
 
 static BasicImplData*
 ToData(Layer* aLayer)
 {
   return static_cast<BasicImplData*>(aLayer->ImplData());
@@ -256,16 +287,86 @@ BasicThebesLayer::CopyFrom(ThebesLayer* 
                "Cannot copy into a layer already painted");
   NS_ASSERTION(BasicManager()->InDrawing(),
                "Can only draw in drawing phase");
   // Nothing to do here since we have no retained buffers. Our
   // valid region is empty (since we haven't painted this layer yet),
   // so no need to mark anything invalid.
 }
 
+class BasicImageLayer : public ImageLayer, BasicImplData {
+public:
+  BasicImageLayer(BasicLayerManager* aLayerManager) :
+    ImageLayer(aLayerManager, static_cast<BasicImplData*>(this))
+  {
+    MOZ_COUNT_CTOR(BasicImageLayer);
+  }
+  virtual ~BasicImageLayer()
+  {
+    MOZ_COUNT_DTOR(BasicImageLayer);
+  }
+
+  virtual void SetVisibleRegion(const nsIntRegion& aRegion)
+  {
+    NS_ASSERTION(BasicManager()->InConstruction(),
+                 "Can only set properties in construction phase");
+    mVisibleRegion = aRegion;
+  }
+
+  virtual void Paint(gfxContext* aContext);
+
+protected:
+  BasicLayerManager* BasicManager()
+  {
+    return static_cast<BasicLayerManager*>(mManager);
+  }
+};
+
+void
+BasicImageLayer::Paint(gfxContext* aContext)
+{
+  if (!mContainer)
+    return;
+
+  gfxIntSize size;
+  nsRefPtr<gfxASurface> surface = mContainer->GetCurrentAsSurface(&size);
+  if (!surface) {
+    return;
+  }
+
+  nsRefPtr<gfxPattern> pat = new gfxPattern(surface);
+  if (!pat) {
+    return;
+  }
+
+  pat->SetFilter(mFilter);
+
+  // Set PAD mode so that when the video is being scaled, we do not sample
+  // outside the bounds of the video image.
+  gfxPattern::GraphicsExtend extend = gfxPattern::EXTEND_PAD;
+
+  // PAD is slow with X11 and Quartz surfaces, so prefer speed over correctness
+  // and use NONE.
+  nsRefPtr<gfxASurface> target = aContext->CurrentSurface();
+  gfxASurface::gfxSurfaceType type = target->GetType();
+  if (type == gfxASurface::SurfaceTypeXlib ||
+      type == gfxASurface::SurfaceTypeXcb ||
+      type == gfxASurface::SurfaceTypeQuartz) {
+    extend = gfxPattern::EXTEND_NONE;
+  }
+
+  pat->SetExtend(extend);
+
+  /* Draw RGB surface onto frame */
+  aContext->NewPath();
+  aContext->PixelSnappedRectangleAndSetPattern(
+      gfxRect(0, 0, size.width, size.height), pat);
+  aContext->Fill();
+}
+
 BasicLayerManager::BasicLayerManager(gfxContext* aContext) :
   mDefaultTarget(aContext), mLastPainted(nsnull)
 #ifdef DEBUG
   , mPhase(PHASE_NONE)
 #endif
 {
   MOZ_COUNT_CTOR(BasicLayerManager);
 }
@@ -414,16 +515,20 @@ BasicLayerManager::BeginPaintingLayer(La
 
       gfxASurface::gfxContentType type = UseOpaqueSurface(aLayer)
           ? gfxASurface::CONTENT_COLOR : gfxASurface::CONTENT_COLOR_ALPHA;
       mTarget->PushGroup(type);
     }
   }
 
   mLastPainted = aLayer;
+
+  // For layers that paint themselves (e.g., BasicImageLayer), paint
+  // them now.
+  ToData(aLayer)->Paint(mTarget);
 }
 
 void
 BasicLayerManager::EndPaintingLayer()
 {
   PRBool needsGroup = NeedsGroup(mLastPainted);
   if ((needsGroup || NeedsState(mLastPainted)) && mTarget) {
     if (needsGroup) {
@@ -485,16 +590,24 @@ BasicLayerManager::CreateThebesLayer()
 already_AddRefed<ContainerLayer>
 BasicLayerManager::CreateContainerLayer()
 {
   NS_ASSERTION(InConstruction(), "Only allowed in construction phase");
   nsRefPtr<ContainerLayer> layer = new BasicContainerLayer(this);
   return layer.forget();
 }
 
+already_AddRefed<ImageLayer>
+BasicLayerManager::CreateImageLayer()
+{
+  NS_ASSERTION(InConstruction(), "Only allowed in construction phase");
+  nsRefPtr<ImageLayer> layer = new BasicImageLayer(this);
+  return layer.forget();
+}
+
 #ifdef DEBUG
 static void
 AppendAncestors(Layer* aLayer, nsTArray<Layer*>* aAncestors)
 {
   while (aLayer) {
     aAncestors->AppendElement(aLayer);
     aLayer = aLayer->GetParent();
   }
--- a/gfx/layers/basic/BasicLayers.h
+++ b/gfx/layers/basic/BasicLayers.h
@@ -36,16 +36,18 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef GFX_BASICLAYERS_H
 #define GFX_BASICLAYERS_H
 
 #include "Layers.h"
 
 #include "gfxContext.h"
+#include "nsAutoRef.h"
+#include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace layers {
 
 class BasicThebesLayer;
 
 /**
  * This is a cairo/Thebes-only, main-thread-only implementation of layers.
@@ -78,16 +80,18 @@ public:
   virtual void BeginTransactionWithTarget(gfxContext* aTarget);
   virtual void EndConstruction();
   virtual void EndTransaction();
 
   virtual void SetRoot(Layer* aLayer);
 
   virtual already_AddRefed<ThebesLayer> CreateThebesLayer();
   virtual already_AddRefed<ContainerLayer> CreateContainerLayer();
+  virtual already_AddRefed<ImageLayer> CreateImageLayer();
+  virtual already_AddRefed<ImageContainer> CreateImageContainer();
 
 #ifdef DEBUG
   PRBool InConstruction() { return mPhase == PHASE_CONSTRUCTION; }
   PRBool InDrawing() { return mPhase == PHASE_DRAWING; }
   PRBool IsBeforeInTree(Layer* aBefore, Layer* aLayer);
 #endif
   // Prepares mTarget for painting into aLayer. Layers are painted
   // in tree order, so this method essentially traverses the layer tree
@@ -122,9 +126,59 @@ private:
   enum TransactionPhase { PHASE_NONE, PHASE_CONSTRUCTION, PHASE_DRAWING };
   TransactionPhase mPhase;
 #endif
 };
 
 }
 }
 
+/**
+ * We need to be able to hold a reference to a gfxASurface from Image
+ * subclasses. This is potentially a problem since Images can be addrefed
+ * or released off the main thread. We can ensure that we never AddRef
+ * a gfxASurface off the main thread, but we might want to Release due
+ * to an Image being destroyed off the main thread.
+ * 
+ * We use nsCountedRef<nsMainThreadSurfaceRef> to reference the
+ * gfxASurface. When AddRefing, we assert that we're on the main thread.
+ * When Releasing, if we're not on the main thread, we post an event to
+ * the main thread to do the actual release.
+ */
+class nsMainThreadSurfaceRef;
+
+NS_SPECIALIZE_TEMPLATE
+class nsAutoRefTraits<nsMainThreadSurfaceRef> {
+public:
+  typedef gfxASurface* RawRef;
+
+  /**
+   * The XPCOM event that will do the actual release on the main thread.
+   */
+  class SurfaceReleaser : public nsRunnable {
+  public:
+    SurfaceReleaser(RawRef aRef) : mRef(aRef) {}
+    NS_IMETHOD Run() {
+      mRef->Release();
+      return NS_OK;
+    }
+    RawRef mRef;
+  };
+
+  static RawRef Void() { return nsnull; }
+  static void Release(RawRef aRawRef)
+  {
+    if (NS_IsMainThread()) {
+      aRawRef->Release();
+      return;
+    }
+    nsCOMPtr<nsIRunnable> runnable = new SurfaceReleaser(aRawRef);
+    NS_DispatchToMainThread(runnable);
+  }
+  static void AddRef(RawRef aRawRef)
+  {
+    NS_ASSERTION(NS_IsMainThread(),
+                 "Can only add a reference on the main thread");
+    aRawRef->AddRef();
+  }
+};
+
 #endif /* GFX_BASICLAYERS_H */
--- a/gfx/thebes/public/gfxTypes.h
+++ b/gfx/thebes/public/gfxTypes.h
@@ -115,9 +115,32 @@ public:                                 
             return 0;                                                         \
         }                                                                     \
         return mRefCnt;                                                       \
     }                                                                         \
 protected:                                                                    \
     nsAutoRefCnt mRefCnt;                                                     \
 public:
 
+#define THEBES_INLINE_DECL_THREADSAFE_REFCOUNTING(_class)                     \
+public:                                                                       \
+    nsrefcnt AddRef(void) {                                                   \
+        NS_PRECONDITION(PRInt32(mRefCnt) >= 0, "illegal refcnt");             \
+        nsrefcnt count = PR_AtomicIncrement((PRInt32*)&mRefCnt);              \
+        NS_LOG_ADDREF(this, count, #_class, sizeof(*this));                   \
+        return count;                                                         \
+    }                                                                         \
+    nsrefcnt Release(void) {                                                  \
+        NS_PRECONDITION(0 != mRefCnt, "dup release");                         \
+        nsrefcnt count = PR_AtomicDecrement((PRInt32 *)&mRefCnt);             \
+        NS_LOG_RELEASE(this, count, #_class);                                 \
+        if (count == 0) {                                                     \
+            mRefCnt = 1; /* stabilize */                                      \
+            NS_DELETEXPCOM(this);                                             \
+            return 0;                                                         \
+        }                                                                     \
+        return count;                                                         \
+    }                                                                         \
+protected:                                                                    \
+    nsAutoRefCnt mRefCnt;                                                     \
+public:
+
 #endif /* GFX_TYPES_H */