Bug 538323. Part 2: use ImageLayers to render video. r=kinetik
authorRobert O'Callahan <robert@ocallahan.org>
Tue, 02 Mar 2010 12:41:49 +1300
changeset 39499 1a4c3d1d0c57cd32f1f8f12b159c736fba980cdb
parent 39498 36e7bd37612710d6789796f9367309691c4f8460
child 39500 b712d912bf75e9d3ddcd31d041ef1c4003d2f89f
push id12231
push userrocallahan@mozilla.com
push dateWed, 17 Mar 2010 01:49:08 +0000
treeherdermozilla-central@1a4c3d1d0c57 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs538323
milestone1.9.3a4pre
Bug 538323. Part 2: use ImageLayers to render video. r=kinetik
content/html/content/public/nsHTMLMediaElement.h
content/html/content/src/nsHTMLMediaElement.cpp
content/media/nsMediaDecoder.cpp
content/media/nsMediaDecoder.h
content/media/ogg/nsOggDecoder.cpp
layout/base/nsLayoutUtils.cpp
layout/generic/nsVideoFrame.cpp
layout/generic/nsVideoFrame.h
layout/reftests/ogg-video/clipping-1-ref.html
layout/reftests/ogg-video/clipping-1a.html
layout/reftests/ogg-video/reftest.list
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -39,26 +39,29 @@
 #include "nsGenericHTMLElement.h"
 #include "nsMediaDecoder.h"
 #include "nsIChannel.h"
 #include "nsThreadUtils.h"
 #include "nsIDOMRange.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsILoadGroup.h"
 #include "nsIObserver.h"
+#include "ImageLayers.h"
 
 // Define to output information on decoding and painting framerate
 /* #define DEBUG_FRAME_RATE 1 */
 
 typedef PRUint16 nsMediaNetworkState;
 typedef PRUint16 nsMediaReadyState;
 
 class nsHTMLMediaElement : public nsGenericHTMLElement,
                            public nsIObserver
 {
+  typedef mozilla::layers::ImageContainer ImageContainer;
+
 public:
   nsHTMLMediaElement(nsINodeInfo *aNodeInfo, PRBool aFromParser = PR_FALSE);
   virtual ~nsHTMLMediaElement();
 
   /**
    * This is used when the browser is constructing a video element to play
    * a channel that we've already started loading. The src attribute and
    * <source> children are ignored.
@@ -154,21 +157,23 @@ public:
   // has been resumed by the cache or because the element itself
   // asked the decoder to resumed the download.
   void DownloadResumed();
 
   // Called by the media decoder to indicate that the download has stalled
   // (no data has arrived for a while).
   void DownloadStalled();
 
-  // Draw the latest video data. See nsMediaDecoder for
-  // details.
-  void Paint(gfxContext* aContext,
-             gfxPattern::GraphicsFilter aFilter,
-             const gfxRect& aRect);
+  // Called by the media decoder and the video frame to get the
+  // ImageContainer containing the video data.
+  ImageContainer* GetImageContainer();
+
+  // Called by the video frame to get the print surface, if this is
+  // a static document and we're not actually playing video
+  gfxASurface* GetPrintSurface() { return mPrintSurface; }
 
   // Dispatch events
   nsresult DispatchSimpleEvent(const nsAString& aName);
   nsresult DispatchProgressEvent(const nsAString& aName);
   nsresult DispatchAsyncSimpleEvent(const nsAString& aName);
   nsresult DispatchAsyncProgressEvent(const nsAString& aName);
 
   // Called by the decoder when some data has been downloaded or
@@ -388,16 +393,20 @@ protected:
 
   /**
    * Called asynchronously to release a self-reference to this element.
    */
   void DoRemoveSelfReference();
 
   nsRefPtr<nsMediaDecoder> mDecoder;
 
+  // A reference to the ImageContainer which contains the current frame
+  // of video to display.
+  nsRefPtr<ImageContainer> mImageContainer;
+
   // Holds a reference to the first channel we open to the media resource.
   // Once the decoder is created, control over the channel passes to the
   // decoder, and we null out this reference. We must store this in case
   // we need to cancel the channel before control of it passes to the decoder.
   nsCOMPtr<nsIChannel> mChannel;
 
   // Error attribute
   nsCOMPtr<nsIDOMHTMLMediaError> mError;
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -52,39 +52,42 @@
 #include "plbase64.h"
 #include "nsNetUtil.h"
 #include "prmem.h"
 #include "nsNetUtil.h"
 #include "nsXPCOMStrings.h"
 #include "prlock.h"
 #include "nsThreadUtils.h"
 #include "nsContentUtils.h"
+#include "nsFrameManager.h"
 
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 #include "jsapi.h"
 
 #include "nsIRenderingContext.h"
 #include "nsITimer.h"
 
 #include "nsEventDispatcher.h"
 #include "nsIDOMDocumentEvent.h"
 #include "nsIDOMProgressEvent.h"
 #include "nsHTMLMediaError.h"
 #include "nsICategoryManager.h"
 #include "nsCommaSeparatedTokenizer.h"
 #include "nsMediaStream.h"
 
+#include "nsIDOMHTMLVideoElement.h"
 #include "nsIContentPolicy.h"
 #include "nsContentPolicyUtils.h"
 #include "nsContentErrors.h"
 #include "nsCrossSiteListenerProxy.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsLayoutUtils.h"
 #include "nsVideoFrame.h"
+#include "BasicLayers.h"
 
 #ifdef MOZ_OGG
 #include "nsOggDecoder.h"
 #endif
 #ifdef MOZ_WAVE
 #include "nsWaveDecoder.h"
 #endif
 
@@ -93,16 +96,18 @@ static PRLogModuleInfo* gMediaElementLog
 static PRLogModuleInfo* gMediaElementEventsLog;
 #define LOG(type, msg) PR_LOG(gMediaElementLog, type, msg)
 #define LOG_EVENT(type, msg) PR_LOG(gMediaElementEventsLog, type, msg)
 #else
 #define LOG(type, msg)
 #define LOG_EVENT(type, msg)
 #endif
 
+using namespace mozilla::layers;
+
 // Under certain conditions there may be no-one holding references to
 // a media element from script, DOM parent, etc, but the element may still
 // fire meaningful events in the future so we can't destroy it yet:
 // 1) If the element is delaying the load event (or would be, if it were
 // in a document), then events up to loadeddata or error could be fired,
 // so we need to stay alive.
 // 2) If the element is not paused and playback has not ended, then
 // we will (or might) play, sending timeupdate and ended events and possibly
@@ -1732,36 +1737,72 @@ void nsHTMLMediaElement::NotifyAutoplayD
     if (mDecoder) {
       SetPlayedOrSeeked(PR_TRUE);
       mDecoder->Play();
     }
     DispatchAsyncSimpleEvent(NS_LITERAL_STRING("play"));
   }
 }
 
-void nsHTMLMediaElement::Paint(gfxContext* aContext,
-                               gfxPattern::GraphicsFilter aFilter,
-                               const gfxRect& aRect)
+/**
+ * Returns a layer manager to use for the given document. Basically we
+ * look up the document hierarchy for the first document which has
+ * a presentation with an associated widget, and use that widget's
+ * layer manager.
+ */
+static already_AddRefed<LayerManager> GetLayerManagerForDoc(nsIDocument* aDoc)
 {
-  if (mPrintSurface) {
-    nsRefPtr<gfxPattern> pat = new gfxPattern(mPrintSurface);
-    if (!pat)
-      return;
-    // Make the source image fill the rectangle completely
-    pat->SetMatrix(gfxMatrix().Scale(mMediaSize.width/aRect.Width(),
-                                     mMediaSize.height/aRect.Height()));
+  while (aDoc) {
+    nsIDocument* displayDoc = aDoc->GetDisplayDocument();
+    if (displayDoc) {
+      aDoc = displayDoc;
+      continue;
+    }
+
+    nsIPresShell* shell = aDoc->GetPrimaryShell();
+    if (shell) {
+      nsIFrame* rootFrame = shell->FrameManager()->GetRootFrame();
+      if (rootFrame) {
+        nsIWidget* widget =
+          nsLayoutUtils::GetDisplayRootFrame(rootFrame)->GetWindow();
+        if (widget) {
+          nsRefPtr<LayerManager> manager = widget->GetLayerManager();
+          return manager.forget();
+        }
+      }
+    }
+    aDoc = aDoc->GetParentDocument();
+  }
 
-    pat->SetFilter(aFilter);
+  nsRefPtr<LayerManager> manager = new BasicLayerManager(nsnull);
+  return manager.forget();
+}
+
+ImageContainer* nsHTMLMediaElement::GetImageContainer()
+{
+  if (mImageContainer)
+    return mImageContainer;
 
-    aContext->NewPath();
-    aContext->PixelSnappedRectangleAndSetPattern(aRect, pat);
-    aContext->Fill();
-  } else if (mDecoder) {
-    mDecoder->Paint(aContext, aFilter, aRect);
-  }
+  // If we have a print surface, this is just a static image so
+  // no image container is required
+  if (mPrintSurface)
+    return nsnull;
+
+  // Only video frames need an image container.
+  nsCOMPtr<nsIDOMHTMLVideoElement> video =
+    do_QueryInterface(static_cast<nsIContent*>(this));
+  if (!video)
+    return nsnull;
+
+  nsRefPtr<LayerManager> manager = GetLayerManagerForDoc(GetOwnerDoc());
+  if (!manager)
+    return nsnull;
+
+  mImageContainer = manager->CreateImageContainer();
+  return mImageContainer;
 }
 
 nsresult nsHTMLMediaElement::DispatchSimpleEvent(const nsAString& aName)
 {
   LOG_EVENT(PR_LOG_DEBUG, ("%p Dispatching simple event %s", this,
                           NS_ConvertUTF16toUTF8(aName).get()));
 
   return nsContentUtils::DispatchTrustedEvent(GetOwnerDoc(),
--- a/content/media/nsMediaDecoder.cpp
+++ b/content/media/nsMediaDecoder.cpp
@@ -65,17 +65,16 @@
 
 nsMediaDecoder::nsMediaDecoder() :
   mElement(0),
   mRGBWidth(-1),
   mRGBHeight(-1),
   mProgressTime(),
   mDataTime(),
   mVideoUpdateLock(nsnull),
-  mFramerate(0.0),
   mAspectRatio(1.0),
   mSizeChanged(PR_FALSE),
   mShuttingDown(PR_FALSE)
 {
   MOZ_COUNT_CTOR(nsMediaDecoder);
 }
 
 nsMediaDecoder::~nsMediaDecoder()
@@ -208,83 +207,25 @@ nsresult nsMediaDecoder::StopProgress()
     return NS_OK;
 
   nsresult rv = mProgressTimer->Cancel();
   mProgressTimer = nsnull;
 
   return rv;
 }
 
-void nsMediaDecoder::SetRGBData(PRInt32 aWidth, PRInt32 aHeight, float aFramerate,
-                                float aAspectRatio, unsigned char* aRGBBuffer)
-{
-  nsAutoLock lock(mVideoUpdateLock);
-
-  if (mRGBWidth != aWidth || mRGBHeight != aHeight ||
-      mAspectRatio != aAspectRatio) {
-    mRGBWidth = aWidth;
-    mRGBHeight = aHeight;
-    mAspectRatio = aAspectRatio;
-    mSizeChanged = PR_TRUE;
-  }
-  mFramerate = aFramerate;
-  mRGB = aRGBBuffer;
-}
-
-void nsMediaDecoder::Paint(gfxContext* aContext,
-                           gfxPattern::GraphicsFilter aFilter,
-                           const gfxRect& aRect)
+void nsMediaDecoder::SetVideoData(const gfxIntSize& aSize,
+                                  float aAspectRatio,
+                                  Image* aImage)
 {
   nsAutoLock lock(mVideoUpdateLock);
 
-  if (!mRGB)
-    return;
-
-  nsRefPtr<gfxImageSurface> imgSurface =
-      new gfxImageSurface(mRGB,
-                          gfxIntSize(mRGBWidth, mRGBHeight),
-                          mRGBWidth * 4,
-                          gfxASurface::ImageFormatRGB24);
-  if (!imgSurface)
-    return;
-
-  nsRefPtr<gfxASurface> surface(imgSurface);
-
-#if defined(XP_MACOSX)
-  nsRefPtr<gfxQuartzImageSurface> quartzSurface =
-    new gfxQuartzImageSurface(imgSurface);
-  if (!quartzSurface)
-    return;
-
-  surface = quartzSurface;
-#endif
-
-  nsRefPtr<gfxPattern> pat = new gfxPattern(surface);
-  if (!pat)
-    return;
-
-  // Make the source image fill the rectangle completely
-  pat->SetMatrix(gfxMatrix().Scale(mRGBWidth/aRect.Width(), mRGBHeight/aRect.Height()));
-
-  pat->SetFilter(aFilter);
-
-  // 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;
+  if (mRGBWidth != aSize.width || mRGBHeight != aSize.height ||
+      mAspectRatio != aAspectRatio) {
+    mRGBWidth = aSize.width;
+    mRGBHeight = aSize.height;
+    mAspectRatio = aAspectRatio;
+    mSizeChanged = PR_TRUE;
   }
-
-  pat->SetExtend(extend);
-
-  /* Draw RGB surface onto frame */
-  aContext->NewPath();
-  aContext->PixelSnappedRectangleAndSetPattern(aRect, pat);
-  aContext->Fill();
+  if (mImageContainer && aImage) {
+    mImageContainer->SetCurrentImage(aImage);
+  }
 }
-
--- a/content/media/nsMediaDecoder.h
+++ b/content/media/nsMediaDecoder.h
@@ -41,29 +41,32 @@
 #include "mozilla/XPCOM.h"
 
 #include "nsIPrincipal.h"
 #include "nsSize.h"
 #include "prlog.h"
 #include "gfxContext.h"
 #include "gfxRect.h"
 #include "nsITimer.h"
+#include "ImageLayers.h"
 
 class nsHTMLMediaElement;
 class nsMediaStream;
 class nsIStreamListener;
 
 // All methods of nsMediaDecoder must be called from the main thread only
-// with the exception of SetRGBData and GetStatistics, which can be
-// called from any thread.
+// with the exception of GetImageContainer, SetVideoData and GetStatistics,
+// which can be called from any thread.
 class nsMediaDecoder : public nsIObserver
 {
 public:
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::TimeDuration TimeDuration;
+  typedef mozilla::layers::ImageContainer ImageContainer;
+  typedef mozilla::layers::Image Image;
 
   nsMediaDecoder();
   virtual ~nsMediaDecoder();
 
   // Create a new decoder of the same type as this one.
   virtual nsMediaDecoder* Clone() = 0;
 
   // Perform any initialization required for the decoder.
@@ -106,25 +109,16 @@ public:
   // Start downloading the media. Decode the downloaded data up to the
   // point of the first frame of data.
   // aStream is the media stream to use. Ownership of aStream passes to
   // the decoder, even if Load returns an error.
   // This is called at most once per decoder, after Init().
   virtual nsresult Load(nsMediaStream* aStream,
                         nsIStreamListener **aListener) = 0;
 
-  // Draw the latest video data. This is done
-  // here instead of in nsVideoFrame so that the lock around the
-  // RGB buffer doesn't have to be exposed publically.
-  // The current video frame is drawn to fill aRect.
-  // Called in the main thread only.
-  virtual void Paint(gfxContext* aContext,
-                     gfxPattern::GraphicsFilter aFilter,
-                     const gfxRect& aRect);
-
   // Called when the video file has completed downloading.
   virtual void ResourceLoaded() = 0;
 
   // Called if the media file encounters a network error.
   virtual void NetworkError() = 0;
 
   // Call from any thread safely. Return PR_TRUE if we are currently
   // seeking in the media resource.
@@ -224,51 +218,49 @@ public:
 
   // Moves any existing channel loads into the background, so that they don't
   // block the load event. This is called when we stop delaying the load
   // event. Any new loads initiated (for example to seek) will also be in the
   // background. Implementations of this must call MoveLoadsToBackground() on
   // their nsMediaStream.
   virtual void MoveLoadsToBackground()=0;
 
+  // Gets the image container for the media element. Will return null if
+  // the element is not a video element. This can be called from any
+  // thread; ImageContainers can be used from any thread.
+  ImageContainer* GetImageContainer() { return mImageContainer; }
+
 protected:
 
   // Start timer to update download progress information.
   nsresult StartProgress();
 
   // Stop progress information timer.
   nsresult StopProgress();
 
-  // Set the RGB width, height, pixel aspect ratio, and framerate.
-  // Ownership of the passed RGB buffer is transferred to the decoder.
-  // This is the only nsMediaDecoder method that may be called from
-  // threads other than the main thread.
-  void SetRGBData(PRInt32 aWidth,
-                  PRInt32 aHeight,
-                  float aFramerate,
-                  float aAspectRatio,
-                  unsigned char* aRGBBuffer);
+  // Set the video width, height, pixel aspect ratio, and current image.
+  // Ownership of the image is transferred to the decoder.
+  void SetVideoData(const gfxIntSize& aSize,
+                    float aAspectRatio,
+                    Image* aImage);
 
 protected:
   // Timer used for updating progress events
   nsCOMPtr<nsITimer> mProgressTimer;
 
   // This should only ever be accessed from the main thread.
   // It is set in Init and cleared in Shutdown when the element goes away.
   // The decoder does not add a reference the element.
   nsHTMLMediaElement* mElement;
 
-  // RGB data for last decoded frame of video data.
-  // The size of the buffer is mRGBWidth*mRGBHeight*4 bytes and
-  // contains bytes in RGBA format.
-  nsAutoArrayPtr<unsigned char> mRGB;
-
   PRInt32 mRGBWidth;
   PRInt32 mRGBHeight;
 
+  nsRefPtr<ImageContainer> mImageContainer;
+
   // Time that the last progress event was fired. Read/Write from the
   // main thread only.
   TimeStamp mProgressTime;
 
   // Time that data was last read from the media resource. Used for
   // computing if the download has stalled and to rate limit progress events
   // when data is arriving slower than PROGRESS_MS. A value of null indicates
   // that a stall event has already fired and not to fire another one until
--- a/content/media/ogg/nsOggDecoder.cpp
+++ b/content/media/ogg/nsOggDecoder.cpp
@@ -51,16 +51,17 @@
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsAutoLock.h"
 #include "nsTArray.h"
 #include "nsNetUtil.h"
 
 using mozilla::TimeDuration;
 using mozilla::TimeStamp;
+using namespace mozilla::layers;
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gOggDecoderLog;
 #define LOG(type, msg) PR_LOG(gOggDecoderLog, type, msg)
 #else
 #define LOG(type, msg)
 #endif
 
@@ -1010,45 +1011,73 @@ void nsOggDecodeStateMachine::PlayFrame(
       mon.Wait();
       if (mState == DECODER_STATE_SHUTDOWN) {
         return;
       }
     }
   }
 }
 
+static void ToARGBHook(const PlanarYCbCrImage::Data& aData, PRUint8* aOutput)
+{
+  OggPlayYUVChannels yuv;
+  NS_ASSERTION(aData.mYStride == aData.mYSize.width,
+               "Stride not supported");
+  NS_ASSERTION(aData.mCbCrStride == aData.mCbCrSize.width,
+               "Stride not supported");
+  yuv.ptry = aData.mYChannel;
+  yuv.ptru = aData.mCbChannel;
+  yuv.ptrv = aData.mCrChannel;
+  yuv.uv_width = aData.mCbCrSize.width;
+  yuv.uv_height = aData.mCbCrSize.height;
+  yuv.y_width = aData.mYSize.width;
+  yuv.y_height = aData.mYSize.height;
+
+  OggPlayRGBChannels rgb;
+  rgb.ptro = aOutput;
+  rgb.rgb_width = aData.mYSize.width;
+  rgb.rgb_height = aData.mYSize.height;
+
+  oggplay_yuv2bgra(&yuv, &rgb);  
+}
+
 void nsOggDecodeStateMachine::PlayVideo(FrameData* aFrame)
 {
   PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mDecoder->GetMonitor());
   if (aFrame && aFrame->mVideoHeader) {
-    OggPlayVideoData* videoData = oggplay_callback_info_get_video_data(aFrame->mVideoHeader);
-
-    OggPlayYUVChannels yuv;
-    yuv.ptry = videoData->y;
-    yuv.ptru = videoData->u;
-    yuv.ptrv = videoData->v;
-    yuv.uv_width = aFrame->mUVWidth;
-    yuv.uv_height = aFrame->mUVHeight;
-    yuv.y_width = aFrame->mVideoWidth;
-    yuv.y_height = aFrame->mVideoHeight;
-
-    size_t size = aFrame->mVideoWidth * aFrame->mVideoHeight * 4;
-    nsAutoArrayPtr<unsigned char> buffer(new unsigned char[size]);
-    if (!buffer)
-      return;
-
-    OggPlayRGBChannels rgb;
-    rgb.ptro = buffer;
-    rgb.rgb_width = aFrame->mVideoWidth;
-    rgb.rgb_height = aFrame->mVideoHeight;
-
-    oggplay_yuv2bgra(&yuv, &rgb);
-
-    mDecoder->SetRGBData(aFrame->mVideoWidth, aFrame->mVideoHeight,
-                         mFramerate, mAspectRatio, buffer.forget());
+    ImageContainer* container = mDecoder->GetImageContainer();
+    // Currently our Ogg decoder only knows how to output to PLANAR_YCBCR
+    // format.
+    Image::Format format = Image::PLANAR_YCBCR;
+    nsRefPtr<Image> image;
+    if (container) {
+      image = container->CreateImage(&format, 1);
+    }
+    if (image) {
+      NS_ASSERTION(image->GetFormat() == Image::PLANAR_YCBCR,
+                   "Wrong format?");
+      PlanarYCbCrImage* videoImage = static_cast<PlanarYCbCrImage*>(image.get());
+
+      // XXX this is only temporary until we get YUV code in the layer
+      // system.
+      videoImage->SetRGBConverter(ToARGBHook);
+
+      OggPlayVideoData* videoData = oggplay_callback_info_get_video_data(aFrame->mVideoHeader);
+      PlanarYCbCrImage::Data data;
+      data.mYChannel = videoData->y;
+      data.mYSize = gfxIntSize(aFrame->mVideoWidth, aFrame->mVideoHeight);
+      data.mYStride = data.mYSize.width;
+      data.mCbChannel = videoData->u;
+      data.mCrChannel = videoData->v;
+      data.mCbCrSize = gfxIntSize(aFrame->mUVWidth, aFrame->mUVHeight);
+      data.mCbCrStride = data.mCbCrSize.width;
+      videoImage->SetData(data);
+
+      mDecoder->SetVideoData(data.mYSize, mAspectRatio, image);
+    }
 
     // Don't play the frame's video data more than once.
     aFrame->ClearVideoHeader();
   }
 }
 
 void nsOggDecodeStateMachine::PlayAudio(FrameData* aFrame)
 {
@@ -1866,17 +1895,18 @@ void nsOggDecodeStateMachine::LoadOggHea
       OggPlayErrorCode r =
         oggplay_get_video_aspect_ratio(mPlayer, i, &aspectd, &aspectn);
       mAspectRatio = r == E_OGGPLAY_OK && aspectd > 0 ?
           float(aspectn)/float(aspectd) : 1.0;
 
       int y_width;
       int y_height;
       oggplay_get_video_y_size(mPlayer, i, &y_width, &y_height);
-      mDecoder->SetRGBData(y_width, y_height, mFramerate, mAspectRatio, nsnull);
+      mDecoder->SetVideoData(gfxIntSize(y_width, y_height), mAspectRatio,
+                             nsnull);
     }
     else if (mAudioTrack == -1 && oggplay_get_track_type(mPlayer, i) == OGGZ_CONTENT_VORBIS) {
       mAudioTrack = i;
       oggplay_set_offset(mPlayer, i, OGGPLAY_AUDIO_OFFSET);
       oggplay_get_audio_samplerate(mPlayer, i, &mAudioRate);
       oggplay_get_audio_channels(mPlayer, i, &mAudioChannels);
       LOG(PR_LOG_DEBUG, ("%p samplerate: %d, channels: %d", mDecoder, mAudioRate, mAudioChannels));
     }
@@ -2006,16 +2036,18 @@ PRBool nsOggDecoder::Init(nsHTMLMediaEle
   if (!mMonitor)
     return PR_FALSE;
 
   nsContentUtils::RegisterShutdownObserver(this);
 
   mReader = new nsChannelReader();
   NS_ENSURE_TRUE(mReader, PR_FALSE);
 
+  mImageContainer = aElement->GetImageContainer();
+
   return PR_TRUE;
 }
 
 void nsOggDecoder::Stop()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread");
 
   // The decode thread must die before the state machine can die.
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -84,24 +84,27 @@
 #ifdef MOZ_MEDIA
 #include "nsHTMLVideoElement.h"
 #endif
 #include "imgIRequest.h"
 #include "imgIContainer.h"
 #include "nsIImageLoadingContent.h"
 #include "nsCOMPtr.h"
 #include "nsListControlFrame.h"
+#include "ImageLayers.h"
 
 #ifdef MOZ_SVG
 #include "nsSVGUtils.h"
 #include "nsSVGIntegrationUtils.h"
 #include "nsSVGForeignObjectFrame.h"
 #include "nsSVGOuterSVGFrame.h"
 #endif
 
+using namespace mozilla::layers;
+
 /**
  * A namespace class for static layout utilities.
  */
 
 nsIFrame*
 nsLayoutUtils::GetLastContinuationWithChild(nsIFrame* aFrame)
 {
   NS_PRECONDITION(aFrame, "NULL frame pointer");
@@ -3431,36 +3434,41 @@ nsLayoutUtils::SurfaceFromElement(nsIDOM
   if (node && ve) {
     nsHTMLVideoElement *video = static_cast<nsHTMLVideoElement*>(ve.get());
 
     // If it doesn't have a principal, just bail
     nsCOMPtr<nsIPrincipal> principal = video->GetCurrentPrincipal();
     if (!principal)
       return result;
 
-    PRUint32 w, h;
-    rv = video->GetVideoWidth(&w);
-    rv |= video->GetVideoHeight(&h);
-    if (NS_FAILED(rv))
+    ImageContainer *container = video->GetImageContainer();
+    if (!container)
+      return result;
+
+    gfxIntSize size;
+    nsRefPtr<gfxASurface> surf = container->GetCurrentAsSurface(&size);
+    if (!surf)
       return result;
 
-    nsRefPtr<gfxASurface> surf;
-    if (wantImageSurface) {
-      surf = new gfxImageSurface(gfxIntSize(w, h), gfxASurface::ImageFormatARGB32);
-    } else {
-      surf = gfxPlatform::GetPlatform()->CreateOffscreenSurface(gfxIntSize(w, h), gfxASurface::ImageFormatARGB32);
+    if (wantImageSurface && surf->GetType() != gfxASurface::SurfaceTypeImage) {
+      nsRefPtr<gfxImageSurface> imgSurf =
+        new gfxImageSurface(size, gfxASurface::ImageFormatARGB32);
+      if (!imgSurf)
+        return result;
+
+      nsRefPtr<gfxContext> ctx = new gfxContext(imgSurf);
+      if (!ctx)
+        return result;
+      ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
+      ctx->DrawSurface(surf, size);
+      surf = imgSurf;
     }
 
-    nsRefPtr<gfxContext> ctx = new gfxContext(surf);
-
-    ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
-    video->Paint(ctx, gfxPattern::FILTER_NEAREST, gfxRect(0, 0, w, h));
-
     result.mSurface = surf;
-    result.mSize = gfxIntSize(w, h);
+    result.mSize = size;
     result.mPrincipal = principal;
     result.mIsWriteOnly = PR_FALSE;
 
     return result;
   }
 #endif
 
   // Finally, check if it's a normal image
--- a/layout/generic/nsVideoFrame.cpp
+++ b/layout/generic/nsVideoFrame.cpp
@@ -59,16 +59,18 @@
 #include "nsIImageLoadingContent.h"
 
 #ifdef ACCESSIBILITY
 #include "nsIServiceManager.h"
 #include "nsIAccessible.h"
 #include "nsIAccessibilityService.h"
 #endif
 
+using namespace mozilla::layers;
+
 nsIFrame*
 NS_NewHTMLVideoFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsVideoFrame(aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsVideoFrame)
 
@@ -167,35 +169,87 @@ CorrectForAspectRatio(const gfxRect& aRe
   gfxFloat scale =
     NS_MIN(aRect.Width()/aRatio.width, aRect.Height()/aRatio.height);
   gfxSize scaledRatio(scale*aRatio.width, scale*aRatio.height);
   gfxPoint topLeft((aRect.Width() - scaledRatio.width)/2,
                    (aRect.Height() - scaledRatio.height)/2);
   return gfxRect(aRect.TopLeft() + topLeft, scaledRatio);
 }
 
-void
-nsVideoFrame::PaintVideo(nsIRenderingContext& aRenderingContext,
-                         const nsRect& aDirtyRect, nsPoint aPt)
+already_AddRefed<Layer>
+nsVideoFrame::BuildLayer(nsDisplayListBuilder* aBuilder,
+                         LayerManager* aManager)
 {
-  nsRect area = GetContentRect() - GetPosition() + aPt;
+  nsRect area = GetContentRect() + aBuilder->ToReferenceFrame(GetParent());
   nsHTMLVideoElement* element = static_cast<nsHTMLVideoElement*>(GetContent());
   nsIntSize videoSize = element->GetVideoSize(nsIntSize(0, 0));
   if (videoSize.width <= 0 || videoSize.height <= 0 || area.IsEmpty())
-    return;
+    return nsnull;
 
-  gfxContext* ctx = aRenderingContext.ThebesContext();
+  nsRefPtr<ImageContainer> container = element->GetImageContainer();
+  // If we have a container with the right layer manager already, we don't
+  // need to do anything here. Otherwise we need to set up a temporary
+  // ImageContainer, capture the video data and store it in the temp
+  // container.
+  if (!container || container->Manager() != aManager) {
+    nsRefPtr<ImageContainer> tmpContainer = aManager->CreateImageContainer();
+    if (!tmpContainer)
+      return nsnull;
+    
+    // We get a reference to the video data as a cairo surface.
+    CairoImage::Data cairoData;
+    nsRefPtr<gfxASurface> imageSurface;
+    if (container) {
+      // Get video from the existing container. It was created for a
+      // different layer manager, so we do fallback through cairo.
+      imageSurface = container->GetCurrentAsSurface(&cairoData.mSize);
+      cairoData.mSurface = imageSurface;
+    } else {
+      // We're probably printing.
+      cairoData.mSurface = element->GetPrintSurface();
+      if (!cairoData.mSurface)
+        return nsnull;
+      cairoData.mSize = gfxIntSize(videoSize.width, videoSize.height);
+    }
+
+    // Now create a CairoImage to display the surface.
+    Image::Format cairoFormat = Image::CAIRO_SURFACE;
+    nsRefPtr<Image> image = tmpContainer->CreateImage(&cairoFormat, 1);
+    if (!image)
+      return nsnull;
+
+    NS_ASSERTION(image->GetFormat() == cairoFormat, "Wrong format");
+    static_cast<CairoImage*>(image.get())->SetData(cairoData);
+    tmpContainer->SetCurrentImage(image);
+    container = tmpContainer.forget();
+  }
+
+  // Compute the rectangle in which to paint the video. We need to use
+  // the largest rectangle that fills our content-box and has the
+  // correct aspect ratio.
   nsPresContext* presContext = PresContext();
   gfxRect r = gfxRect(presContext->AppUnitsToGfxUnits(area.x),
                       presContext->AppUnitsToGfxUnits(area.y),
                       presContext->AppUnitsToGfxUnits(area.width),
                       presContext->AppUnitsToGfxUnits(area.height));
+  r = CorrectForAspectRatio(r, videoSize);
 
-  r = CorrectForAspectRatio(r, videoSize);
-  element->Paint(ctx, nsLayoutUtils::GetGraphicsFilterForFrame(this), r);
+  nsRefPtr<ImageLayer> layer = aManager->CreateImageLayer();
+  if (!layer)
+    return nsnull;
+
+  layer->SetContainer(container);
+  layer->SetFilter(nsLayoutUtils::GetGraphicsFilterForFrame(this));
+  // Set a transform on the layer to draw the video in the right place
+  gfxMatrix transform;
+  transform.Translate(r.pos);
+  transform.Scale(r.Width()/videoSize.width, r.Height()/videoSize.height);
+  layer->SetTransform(gfx3DMatrix::From2D(transform));
+  nsRefPtr<Layer> result = layer.forget();
+  return result.forget();
 }
 
 NS_IMETHODIMP
 nsVideoFrame::Reflow(nsPresContext*           aPresContext,
                      nsHTMLReflowMetrics&     aMetrics,
                      const nsHTMLReflowState& aReflowState,
                      nsReflowStatus&          aStatus)
 {
@@ -269,47 +323,68 @@ nsVideoFrame::Reflow(nsPresContext*     
   NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
                   ("exit nsVideoFrame::Reflow: size=%d,%d",
                   aMetrics.width, aMetrics.height));
   NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics);
 
   return NS_OK;
 }
 
-static void PaintVideo(nsIFrame* aFrame, nsIRenderingContext* aCtx,
-                        const nsRect& aDirtyRect, nsPoint aPt)
-{
-#if 0
-  double start = double(PR_IntervalToMilliseconds(PR_IntervalNow()))/1000.0;
+class nsDisplayVideo : public nsDisplayItem {
+public:
+  nsDisplayVideo(nsVideoFrame* aFrame)
+    : nsDisplayItem(aFrame)
+  {
+    MOZ_COUNT_CTOR(nsDisplayVideo);
+  }
+#ifdef NS_BUILD_REFCNT_LOGGING
+  virtual ~nsDisplayVideo() {
+    MOZ_COUNT_DTOR(nsDisplayVideo);
+  }
 #endif
+  
+  NS_DISPLAY_DECL_NAME("Video")
 
-  static_cast<nsVideoFrame*>(aFrame)->PaintVideo(*aCtx, aDirtyRect, aPt);
-#if 0
-  double end = double(PR_IntervalToMilliseconds(PR_IntervalNow()))/1000.0;
-  printf("PaintVideo: %f\n", (float)end - (float)start);
+  // It would be great if we could override IsOpaque to return false here,
+  // but it's probably not safe to do so in general. Video frames are
+  // updated asynchronously from decoder threads, and it's possible that
+  // we might have an opaque video frame when IsOpaque is called, but
+  // when we come to paint, the video frame is transparent or has gone
+  // away completely (e.g. because of a decoder error). The problem would
+  // be especially acute if we have off-main-thread rendering.
 
-#endif
-}
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder)
+  {
+    nsIFrame* f = GetUnderlyingFrame();
+    return f->GetContentRect() + aBuilder->ToReferenceFrame(f->GetParent());
+  }
+
+  virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+                                             LayerManager* aManager)
+  {
+    return static_cast<nsVideoFrame*>(mFrame)->BuildLayer(aBuilder, aManager);
+  }
+};
 
 NS_IMETHODIMP
 nsVideoFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                const nsRect&           aDirtyRect,
                                const nsDisplayListSet& aLists)
 {
   if (!IsVisibleForPainting(aBuilder))
     return NS_OK;
 
   DO_GLOBAL_REFLOW_COUNT_DSP("nsVideoFrame");
 
   nsresult rv = DisplayBorderBackgroundOutline(aBuilder, aLists);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (!ShouldDisplayPoster() && HasVideoData()) {
+  if (HasVideoElement() && !ShouldDisplayPoster()) {
     rv = aLists.Content()->AppendNewToTop(
-        new (aBuilder) nsDisplayGeneric(this, ::PaintVideo, "Video"));
+      new (aBuilder) nsDisplayVideo(this));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Add child frames to display list. We expect up to two children, an image
   // frame for the poster, and the box frame for the video controls.
   for (nsIFrame *child = mFrames.FirstChild();
        child;
        child = child->GetNextSibling()) {
--- a/layout/generic/nsVideoFrame.h
+++ b/layout/generic/nsVideoFrame.h
@@ -44,22 +44,27 @@
 #include "nsContainerFrame.h"
 #include "nsString.h"
 #include "nsAString.h"
 #include "nsPresContext.h"
 #include "nsIIOService.h"
 #include "nsITimer.h"
 #include "nsTArray.h"
 #include "nsIAnonymousContentCreator.h"
+#include "Layers.h"
+#include "ImageLayers.h"
 
 nsIFrame* NS_NewVideoFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
 
 class nsVideoFrame : public nsContainerFrame, public nsIAnonymousContentCreator
 {
 public:
+  typedef mozilla::layers::Layer Layer;
+  typedef mozilla::layers::LayerManager LayerManager;
+
   nsVideoFrame(nsStyleContext* aContext);
 
   NS_DECL_QUERYFRAME
   NS_DECL_FRAMEARENA_HELPERS
 
   NS_IMETHOD BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                               const nsRect&           aDirtyRect,
                               const nsDisplayListSet& aLists);
@@ -107,16 +112,19 @@ public:
   // Returns PR_TRUE if we should display the poster. Note that once we show
   // a video frame, the poster will never be displayed again.
   PRBool ShouldDisplayPoster();
 
 #ifdef DEBUG
   NS_IMETHOD GetFrameName(nsAString& aResult) const;
 #endif
 
+  already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+                                     LayerManager* aManager);
+
 protected:
 
   // Returns PR_TRUE if we're rendering for a video element. We still create
   // nsVideoFrame to render controls for an audio element.
   PRBool HasVideoElement();
 
   // Returns PR_TRUE if there is video data to render. Can return false
   // when we're the frame for an audio element, or we've created a video
new file mode 100644
--- /dev/null
+++ b/layout/reftests/ogg-video/clipping-1-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<body style="background:white">
+<div style="position:absolute; left:0; top:0; width:200px; height:600px;">
+  <video src="black140x100.ogv" style="width:400px; margin-left:-100px;"></video>
+</div>
+<div style="position:absolute; left:200px; top:0; background:white; width:200px; height:600px;"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/ogg-video/clipping-1a.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<body style="background:white">
+<div style="overflow:hidden; position:absolute; left:0; top:0; width:200px; height:600px;">
+  <video src="black140x100.ogv" style="width:400px; margin-left:-100px;"></video>
+</div>
+</body>
+</html>
--- a/layout/reftests/ogg-video/reftest.list
+++ b/layout/reftests/ogg-video/reftest.list
@@ -2,16 +2,17 @@ HTTP(..) == aspect-ratio-1a.xhtml aspect
 HTTP(..) == aspect-ratio-1b.xhtml aspect-ratio-1-ref.html
 skip-if(MOZ_WIDGET_TOOLKIT=="gtk2") HTTP(..) == aspect-ratio-2a.xhtml aspect-ratio-2-ref.html
 skip-if(MOZ_WIDGET_TOOLKIT=="gtk2") HTTP(..) == aspect-ratio-2b.xhtml aspect-ratio-2-ref.html
 HTTP(..) == aspect-ratio-3a.xhtml aspect-ratio-3-ref.xhtml
 HTTP(..) == aspect-ratio-3b.xhtml aspect-ratio-3-ref.xhtml
 HTTP(..) == basic-1.xhtml basic-1-ref.html
 HTTP(..) == canvas-1a.xhtml basic-1-ref.html
 HTTP(..) == canvas-1b.xhtml basic-1-ref.html
+== clipping-1a.html clipping-1-ref.html
 == empty-1a.html empty-1-ref.html
 == empty-1b.html empty-1-ref.html
 random HTTP(..) == object-aspect-ratio-1a.xhtml aspect-ratio-1-ref.html
 random HTTP(..) == object-aspect-ratio-1b.xhtml aspect-ratio-1-ref.html
 HTTP(..) == offset-1.xhtml offset-1-ref.html
 random HTTP(..) == object-aspect-ratio-2a.xhtml aspect-ratio-2-ref.html
 random HTTP(..) == object-aspect-ratio-2b.xhtml aspect-ratio-2-ref.html
 skip-if(MOZ_WIDGET_TOOLKIT=="gtk2") HTTP(..) == zoomed-1.xhtml zoomed-1-ref.html