Bug 649924. Use image surfaces to implement canvases beyond the texture size limit. r=bas
authorRobert O'Callahan <robert@ocallahan.org>
Fri, 08 Jul 2011 22:42:21 +1200
changeset 73612 364d2debc2a7c95797d1e03db5076ad0b8684183
parent 73570 5479a346b95b82162c72419a95cbb4022cbbfe4d
child 73613 0bd82b867cf012b05c1c283341f1ceea2dbe0ebc
push id67
push userclegnitto@mozilla.com
push dateFri, 04 Nov 2011 22:39:41 +0000
treeherdermozilla-release@04778346a3b0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbas
bugs649924
milestone8.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 649924. Use image surfaces to implement canvases beyond the texture size limit. r=bas
content/canvas/public/nsICanvasRenderingContextInternal.h
content/canvas/src/nsCanvasRenderingContext2D.cpp
content/html/content/public/nsHTMLCanvasElement.h
content/html/content/src/nsHTMLCanvasElement.cpp
gfx/layers/Layers.h
gfx/layers/d3d10/LayerManagerD3D10.h
gfx/layers/d3d9/DeviceManagerD3D9.cpp
gfx/layers/d3d9/DeviceManagerD3D9.h
gfx/layers/d3d9/LayerManagerD3D9.h
gfx/layers/opengl/LayerManagerOGL.h
layout/generic/nsHTMLCanvasFrame.cpp
layout/reftests/canvas/reftest.list
layout/reftests/canvas/size-1-ref.html
layout/reftests/canvas/size-1.html
--- a/content/canvas/public/nsICanvasRenderingContextInternal.h
+++ b/content/canvas/public/nsICanvasRenderingContextInternal.h
@@ -117,16 +117,21 @@ public:
   NS_IMETHOD Reset() = 0;
 
   // Return the CanvasLayer for this context, creating
   // one for the given layer manager if not available.
   virtual already_AddRefed<CanvasLayer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                                        CanvasLayer *aOldLayer,
                                                        LayerManager *aManager) = 0;
 
+  // Return true if the canvas should be forced to be "inactive" to ensure
+  // it can be drawn to the screen even if it's too large to be blitted by
+  // an accelerated CanvasLayer.
+  virtual PRBool ShouldForceInactiveLayer(LayerManager *aManager) { return PR_FALSE; }
+
   virtual void MarkContextClean() = 0;
 
   // Redraw the dirty rectangle of this canvas.
   NS_IMETHOD Redraw(const gfxRect &dirty) = 0;
 
   // Passes a generic nsIPropertyBag options argument, along with the
   // previous one, if any.  Optional.
   NS_IMETHOD SetContextOptions(nsIPropertyBag *aNewOptions) { return NS_OK; }
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp
@@ -344,20 +344,21 @@ public:
                               const PRUnichar* aEncoderOptions,
                               nsIInputStream **aStream);
     NS_IMETHOD GetThebesSurface(gfxASurface **surface);
     mozilla::TemporaryRef<mozilla::gfx::SourceSurface> GetSurfaceSnapshot()
         { return nsnull; }
 
     NS_IMETHOD SetIsOpaque(PRBool isOpaque);
     NS_IMETHOD Reset();
-    already_AddRefed<CanvasLayer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
-                                                 CanvasLayer *aOldLayer,
-                                                 LayerManager *aManager);
-    void MarkContextClean();
+    virtual already_AddRefed<CanvasLayer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
+                                                         CanvasLayer *aOldLayer,
+                                                         LayerManager *aManager);
+    virtual PRBool ShouldForceInactiveLayer(LayerManager *aManager);
+    virtual void MarkContextClean();
     NS_IMETHOD SetIsIPC(PRBool isIPC);
     // this rect is in canvas device space
     NS_IMETHOD Redraw(const gfxRect &r);
     // this rect is in mThebes's current user space
     NS_IMETHOD RedrawUser(const gfxRect &r);
 
     // nsISupports interface + CC
     NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@@ -1066,19 +1067,17 @@ nsCanvasRenderingContext2D::SetDimension
         if (height == 0 || width == 0) {
             mZero = PR_TRUE;
             height = 1;
             width = 1;
         }
 
         gfxASurface::gfxImageFormat format = GetImageFormat();
 
-        if (PR_GetEnv("MOZ_CANVAS_IMAGE_SURFACE")) {
-            surface = new gfxImageSurface(gfxIntSize(width, height), format);
-        } else {
+        if (!PR_GetEnv("MOZ_CANVAS_IMAGE_SURFACE")) {
             nsCOMPtr<nsIContent> content =
                 do_QueryInterface(static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement));
             nsIDocument* ownerDoc = nsnull;
             if (content)
                 ownerDoc = content->GetOwnerDoc();
             nsRefPtr<LayerManager> layerManager = nsnull;
 
             if (ownerDoc)
@@ -1088,18 +1087,25 @@ nsCanvasRenderingContext2D::SetDimension
             if (layerManager) {
               surface = layerManager->CreateOptimalSurface(gfxIntSize(width, height), format);
             } else {
               surface = gfxPlatform::GetPlatform()->
                 CreateOffscreenSurface(gfxIntSize(width, height), gfxASurface::ContentFromFormat(format));
             }
         }
 
-        if (surface && surface->CairoStatus() != 0)
-            surface = NULL;
+        if (!surface || surface->CairoStatus()) {
+            // If we couldn't create a surface of the type we want, fall back
+            // to an image surface. This lets us handle surface sizes that
+            // the underlying cairo backend might not handle.
+            surface = new gfxImageSurface(gfxIntSize(width, height), format);
+            if (!surface || surface->CairoStatus()) {
+                surface = nsnull;
+            }
+        }
     }
     if (surface) {
         if (gCanvasMemoryReporter == nsnull) {
             gCanvasMemoryReporter = new NS_MEMORY_REPORTER_NAME(CanvasMemory);
             NS_RegisterMemoryReporter(gCanvasMemoryReporter);
         }
 
         gCanvasMemoryUsed += width * height * 4;
@@ -4012,16 +4018,22 @@ nsCanvasRenderingContext2D::GetCanvasLay
     canvasLayer->SetContentFlags(flags);
     canvasLayer->Updated();
 
     mResetLayer = PR_FALSE;
 
     return canvasLayer.forget();
 }
 
+PRBool
+nsCanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager *aManager)
+{
+    return !aManager->CanUseCanvasLayerForSize(gfxIntSize(mWidth, mHeight));
+}
+
 void
 nsCanvasRenderingContext2D::MarkContextClean()
 {
     if (mInvalidateCount > 0) {
         mPredictManyRedrawCalls = mInvalidateCount > kCanvasMaxInvalidateCount;
     }
     mIsEntireFrameInvalid = PR_FALSE;
     mInvalidateCount = 0;
--- a/content/html/content/public/nsHTMLCanvasElement.h
+++ b/content/html/content/public/nsHTMLCanvasElement.h
@@ -165,16 +165,20 @@ public:
 
   /*
    * Helpers called by various users of Canvas
    */
 
   already_AddRefed<CanvasLayer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                                CanvasLayer *aOldLayer,
                                                LayerManager *aManager);
+  // Should return true if the canvas layer should always be marked inactive.
+  // We should return true here if we can't do accelerated compositing with
+  // a non-BasicCanvasLayer.
+  PRBool ShouldForceInactiveLayer(LayerManager *aManager);
 
   // Call this whenever we need future changes to the canvas
   // to trigger fresh invalidation requests. This needs to be called
   // whenever we render the canvas contents to the screen, or whenever we
   // take a snapshot of the canvas that needs to be "live" (e.g. -moz-element).
   void MarkContextClean();
 
   virtual nsXPCClassInfo* GetClassInfo();
@@ -188,16 +192,17 @@ protected:
                        bool& aFellBackToPNG);
   nsresult ToDataURLImpl(const nsAString& aMimeType,
                          nsIVariant* aEncoderOptions,
                          nsAString& aDataURL);
   nsresult MozGetAsFileImpl(const nsAString& aName,
                             const nsAString& aType,
                             nsIDOMFile** aResult);
   nsresult GetContextHelper(const nsAString& aContextId,
+                            PRBool aForceThebes,
                             nsICanvasRenderingContextInternal **aContext);
 
   nsString mCurrentContextId;
   nsCOMPtr<nsICanvasRenderingContextInternal> mCurrentContext;
   
 public:
   // Record whether this canvas should be write-only or not.
   // We set this when script paints an image from a different origin.
--- a/content/html/content/src/nsHTMLCanvasElement.cpp
+++ b/content/html/content/src/nsHTMLCanvasElement.cpp
@@ -375,21 +375,22 @@ nsHTMLCanvasElement::MozGetAsFileImpl(co
   nsRefPtr<nsDOMMemoryFile> file =
     new nsDOMMemoryFile(imgData, imgSize, aName, type);
 
   return CallQueryInterface(file, aResult);
 }
 
 nsresult
 nsHTMLCanvasElement::GetContextHelper(const nsAString& aContextId,
+                                      PRBool aForceThebes,
                                       nsICanvasRenderingContextInternal **aContext)
 {
   NS_ENSURE_ARG(aContext);
 
-  NS_LossyConvertUTF16toASCII ctxId(aContextId);
+  NS_ConvertUTF16toUTF8 ctxId(aContextId);
 
   // check that ctxId is clamped to A-Za-z0-9_-
   for (PRUint32 i = 0; i < ctxId.Length(); i++) {
     if ((ctxId[i] < 'A' || ctxId[i] > 'Z') &&
         (ctxId[i] < 'a' || ctxId[i] > 'z') &&
         (ctxId[i] < '0' || ctxId[i] > '9') &&
         (ctxId[i] != '-') &&
         (ctxId[i] != '_'))
@@ -397,16 +398,20 @@ nsHTMLCanvasElement::GetContextHelper(co
       // XXX ERRMSG we need to report an error to developers here! (bug 329026)
       return NS_OK;
     }
   }
 
   nsCString ctxString("@mozilla.org/content/canvas-rendering-context;1?id=");
   ctxString.Append(ctxId);
 
+  if (aForceThebes && ctxId.EqualsASCII("2d")) {
+    ctxString.AssignASCII("@mozilla.org/content/2dthebes-canvas-rendering-context;1");
+  }
+
   nsresult rv;
   nsCOMPtr<nsICanvasRenderingContextInternal> ctx =
     do_CreateInstance(ctxString.get(), &rv);
   if (rv == NS_ERROR_OUT_OF_MEMORY) {
     *aContext = nsnull;
     return NS_ERROR_OUT_OF_MEMORY;
   }
   if (NS_FAILED(rv)) {
@@ -428,18 +433,20 @@ nsHTMLCanvasElement::GetContextHelper(co
 
 NS_IMETHODIMP
 nsHTMLCanvasElement::GetContext(const nsAString& aContextId,
                                 const jsval& aContextOptions,
                                 nsISupports **aContext)
 {
   nsresult rv;
 
-  if (mCurrentContextId.IsEmpty()) {
-    rv = GetContextHelper(aContextId, getter_AddRefs(mCurrentContext));
+  PRBool forceThebes = PR_FALSE;
+
+  while (mCurrentContextId.IsEmpty()) {
+    rv = GetContextHelper(aContextId, forceThebes, getter_AddRefs(mCurrentContext));
     NS_ENSURE_SUCCESS(rv, rv);
     if (!mCurrentContext) {
       return NS_OK;
     }
 
     // Ensure that the context participates in CC.  Note that returning a
     // CC participant from QI doesn't addref.
     nsXPCOMCycleCollectionParticipant *cp = nsnull;
@@ -501,21 +508,28 @@ nsHTMLCanvasElement::GetContext(const ns
       }
 
       contextProps = newProps;
     }
 
     rv = UpdateContext(contextProps);
     if (NS_FAILED(rv)) {
       mCurrentContext = nsnull;
+      if (!forceThebes) {
+        // Try again with a Thebes context
+        forceThebes = PR_TRUE;
+        continue;
+      }
       return rv;
     }
 
     mCurrentContextId.Assign(aContextId);
-  } else if (!mCurrentContextId.Equals(aContextId)) {
+    break;
+  }
+  if (!mCurrentContextId.Equals(aContextId)) {
     //XXX eventually allow for more than one active context on a given canvas
     return NS_OK;
   }
 
   NS_ADDREF (*aContext = mCurrentContext);
   return NS_OK;
 }
 
@@ -530,17 +544,17 @@ nsHTMLCanvasElement::MozGetIPCContext(co
 
   // We only support 2d shmem contexts for now.
   if (!aContextId.Equals(NS_LITERAL_STRING("2d")))
     return NS_ERROR_INVALID_ARG;
 
   nsresult rv;
 
   if (mCurrentContextId.IsEmpty()) {
-    rv = GetContextHelper(aContextId, getter_AddRefs(mCurrentContext));
+    rv = GetContextHelper(aContextId, PR_FALSE, getter_AddRefs(mCurrentContext));
     NS_ENSURE_SUCCESS(rv, rv);
     if (!mCurrentContext) {
       return NS_OK;
     }
 
     mCurrentContext->SetIsIPC(PR_TRUE);
 
     rv = UpdateContext();
@@ -695,16 +709,22 @@ nsHTMLCanvasElement::GetCanvasLayer(nsDi
                                     LayerManager *aManager)
 {
   if (!mCurrentContext)
     return nsnull;
 
   return mCurrentContext->GetCanvasLayer(aBuilder, aOldLayer, aManager);
 }
 
+PRBool
+nsHTMLCanvasElement::ShouldForceInactiveLayer(LayerManager *aManager)
+{
+  return !mCurrentContext || mCurrentContext->ShouldForceInactiveLayer(aManager);
+}
+
 void
 nsHTMLCanvasElement::MarkContextClean()
 {
   if (!mCurrentContext)
     return;
 
   mCurrentContext->MarkContextClean();
 }
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -429,16 +429,17 @@ public:
   /**
    * Creates a DrawTarget which is optimized for inter-operating with this
    * layermanager.
    */
   virtual TemporaryRef<mozilla::gfx::DrawTarget>
     CreateDrawTarget(const mozilla::gfx::IntSize &aSize,
                      mozilla::gfx::SurfaceFormat aFormat);
 
+  virtual bool CanUseCanvasLayerForSize(const gfxIntSize &aSize) { return PR_TRUE; }
 
   /**
    * Return the name of the layer manager's backend.
    */
   virtual void GetBackendName(nsAString& aName) = 0;
 
   /**
    * This setter can be used anytime. The user data for all keys is
--- a/gfx/layers/d3d10/LayerManagerD3D10.h
+++ b/gfx/layers/d3d10/LayerManagerD3D10.h
@@ -110,16 +110,25 @@ public:
     void *CallbackData;
   };
 
   virtual void EndTransaction(DrawThebesLayerCallback aCallback,
                               void* aCallbackData);
 
   const CallbackInfo &GetCallbackInfo() { return mCurrentCallbackInfo; }
 
+  // D3D10 guarantees textures can be at least this size
+  enum {
+    MAX_TEXTURE_SIZE = 8192
+  };
+  virtual bool CanUseCanvasLayerForSize(const gfxIntSize &aSize)
+  {
+    return aSize <= gfxIntSize(MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE);
+  }
+
   virtual already_AddRefed<ThebesLayer> CreateThebesLayer();
 
   virtual already_AddRefed<ContainerLayer> CreateContainerLayer();
 
   virtual already_AddRefed<ImageLayer> CreateImageLayer();
 
   virtual already_AddRefed<ColorLayer> CreateColorLayer();
 
--- a/gfx/layers/d3d9/DeviceManagerD3D9.cpp
+++ b/gfx/layers/d3d9/DeviceManagerD3D9.cpp
@@ -176,16 +176,17 @@ SwapChainD3D9::Reset()
   mSwapChain = nsnull;
 }
 
 #define HAS_CAP(a, b) (((a) & (b)) == (b))
 #define LACKS_CAP(a, b) !(((a) & (b)) == (b))
 
 DeviceManagerD3D9::DeviceManagerD3D9()
   : mDeviceResetCount(0)
+  , mMaxTextureSize(0)
   , mHasDynamicTextures(false)
   , mDeviceWasRemoved(false)
 {
 }
 
 DeviceManagerD3D9::~DeviceManagerD3D9()
 {
   LayerManagerD3D9::OnDeviceManagerDestroy(this);
@@ -641,16 +642,17 @@ DeviceManagerD3D9::VerifyCaps()
   if (LACKS_CAP(caps.TextureAddressCaps, D3DPTADDRESSCAPS_CLAMP)) {
     return false;
   }
 
   if (caps.MaxTextureHeight < 4096 ||
       caps.MaxTextureWidth < 4096) {
     return false;
   }
+  mMaxTextureSize = NS_MIN(caps.MaxTextureHeight, caps.MaxTextureWidth);
 
   if ((caps.PixelShaderVersion & 0xffff) < 0x200 ||
       (caps.VertexShaderVersion & 0xffff) < 0x200) {
     return false;
   }
 
   if (HAS_CAP(caps.Caps2, D3DCAPS2_DYNAMICTEXTURES)) {
     mHasDynamicTextures = true;
--- a/gfx/layers/d3d9/DeviceManagerD3D9.h
+++ b/gfx/layers/d3d9/DeviceManagerD3D9.h
@@ -163,16 +163,18 @@ public:
   PRUint32 GetDeviceResetCount() { return mDeviceResetCount; }
 
   /**
    * We keep a list of all layers here that may have hardware resource allocated
    * so we can clean their resources on reset.
    */
   nsTArray<LayerD3D9*> mLayersWithResources;
 
+  PRInt32 GetMaxTextureSize() { return mMaxTextureSize; }
+
 private:
   friend class SwapChainD3D9;
 
   ~DeviceManagerD3D9();
 
   /**
    * This function verifies the device is ready for rendering, internally this
    * will test the cooperative level of the device and reset the device if
@@ -233,16 +235,18 @@ private:
    */
   HWND mFocusWnd;
 
   /* we use this to help track if our device temporarily or permanently lost */
   HMONITOR mDeviceMonitor;
 
   PRUint32 mDeviceResetCount;
 
+  PRUint32 mMaxTextureSize;
+
   /* If this device supports dynamic textures */
   bool mHasDynamicTextures;
 
   /* If this device was removed */
   bool mDeviceWasRemoved;
 
   /* Nv3DVUtils instance */ 
   nsAutoPtr<Nv3DVUtils> mNv3DVUtils; 
--- a/gfx/layers/d3d9/LayerManagerD3D9.h
+++ b/gfx/layers/d3d9/LayerManagerD3D9.h
@@ -136,16 +136,24 @@ public:
 
   virtual void EndTransaction(DrawThebesLayerCallback aCallback,
                               void* aCallbackData);
 
   const CallbackInfo &GetCallbackInfo() { return mCurrentCallbackInfo; }
 
   void SetRoot(Layer* aLayer);
 
+  virtual bool CanUseCanvasLayerForSize(const gfxIntSize &aSize)
+  {
+    if (!mDeviceManager)
+      return false;
+    PRInt32 maxSize = mDeviceManager->GetMaxTextureSize();
+    return aSize <= gfxIntSize(maxSize, maxSize);
+  }
+
   virtual already_AddRefed<ThebesLayer> CreateThebesLayer();
 
   virtual already_AddRefed<ContainerLayer> CreateContainerLayer();
 
   virtual already_AddRefed<ImageLayer> CreateImageLayer();
 
   virtual already_AddRefed<ColorLayer> CreateColorLayer();
 
--- a/gfx/layers/opengl/LayerManagerOGL.h
+++ b/gfx/layers/opengl/LayerManagerOGL.h
@@ -133,16 +133,24 @@ public:
   void EndConstruction();
 
   virtual bool EndEmptyTransaction();
   virtual void EndTransaction(DrawThebesLayerCallback aCallback,
                               void* aCallbackData);
 
   virtual void SetRoot(Layer* aLayer) { mRoot = aLayer; }
 
+  virtual bool CanUseCanvasLayerForSize(const gfxIntSize &aSize)
+  {
+      if (!mGLContext)
+          return false;
+      PRInt32 maxSize = mGLContext->GetMaxTextureSize();
+      return aSize <= gfxIntSize(maxSize, maxSize);
+  }
+
   virtual already_AddRefed<ThebesLayer> CreateThebesLayer();
 
   virtual already_AddRefed<ContainerLayer> CreateContainerLayer();
 
   virtual already_AddRefed<ImageLayer> CreateImageLayer();
 
   virtual already_AddRefed<ColorLayer> CreateColorLayer();
 
--- a/layout/generic/nsHTMLCanvasFrame.cpp
+++ b/layout/generic/nsHTMLCanvasFrame.cpp
@@ -101,16 +101,19 @@ public:
                                              const ContainerParameters& aContainerParameters)
   {
     return static_cast<nsHTMLCanvasFrame*>(mFrame)->
       BuildLayer(aBuilder, aManager, this);
   }
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager)
   {
+    if (CanvasElementFromContent(mFrame->GetContent())->ShouldForceInactiveLayer(aManager))
+      return LAYER_INACTIVE;
+
     // If compositing is cheap, just do that
     if (aManager->IsCompositingCheap())
       return mozilla::LAYER_ACTIVE;
 
     return mFrame->AreLayersMarkedActive() ? LAYER_ACTIVE : LAYER_INACTIVE;
   }
 };
 
--- a/layout/reftests/canvas/reftest.list
+++ b/layout/reftests/canvas/reftest.list
@@ -1,9 +1,10 @@
 == default-size.html default-size-ref.html
+== size-1.html size-1-ref.html
 
 == image-rendering-test.html image-rendering-ref.html
 == image-shadow.html image-shadow-ref.html
 
 asserts-if(cocoaWidget,0-2) == size-change-1.html size-change-1-ref.html
 
 != text-ltr-left.html text-blank.html
 != text-ltr-right.html text-blank.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/canvas/size-1-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div style="background:lime; width:100px; height:30000px;"></div>
+<script>
+window.scrollTo(0, 100000);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/canvas/size-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<canvas style="display:block" id="c" width="100" height="30000"></canvas>
+<script>
+var ctx = document.getElementById("c").getContext("2d");
+ctx.fillStyle = "lime";
+ctx.fillRect(0, 0, 100, 30000);
+window.scrollTo(0, 100000);
+</script>
+</body>
+</html>