Bug 902651 - Restore the DrawTarget transform and clip when demoting r=mattwoodrow,bz
authorJames Willcox <snorp@snorp.net>
Fri, 23 Aug 2013 09:52:32 -0400
changeset 144393 d76df67ac3b1
parent 144392 6ef19b1dedf4
child 144394 114ac26938f3
push id25160
push userryanvm@gmail.com
push dateTue, 27 Aug 2013 00:19:34 +0000
treeherdermozilla-central@c0d1baa50a64 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow, bz
bugs902651
milestone26.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 902651 - Restore the DrawTarget transform and clip when demoting r=mattwoodrow,bz
browser/config/mozconfigs/win64/nightly
content/canvas/src/CanvasRenderingContext2D.cpp
content/canvas/src/CanvasRenderingContext2D.h
content/canvas/test/Makefile.in
content/canvas/test/test_bug902651.html
dom/webidl/CanvasRenderingContext2D.webidl
--- a/content/canvas/src/CanvasRenderingContext2D.cpp
+++ b/content/canvas/src/CanvasRenderingContext2D.cpp
@@ -542,37 +542,35 @@ CanvasRenderingContext2D::CanvasRenderin
   , mIPC(false)
   , mIsEntireFrameInvalid(false)
   , mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false)
   , mInvalidateCount(0)
 {
   sNumLivingContexts++;
   SetIsDOMBinding();
 
-#if USE_SKIA_GPU
+#ifdef USE_SKIA_GPU
   mForceSoftware = false;
 #endif
 }
 
 CanvasRenderingContext2D::~CanvasRenderingContext2D()
 {
   Reset();
   // Drop references from all CanvasRenderingContext2DUserData to this context
   for (uint32_t i = 0; i < mUserDatas.Length(); ++i) {
     mUserDatas[i]->Forget();
   }
   sNumLivingContexts--;
   if (!sNumLivingContexts) {
     NS_IF_RELEASE(sErrorTarget);
   }
 
-#if USE_SKIA_GPU
-  std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), this);
-  if (iter != DemotableContexts().end())
-    DemotableContexts().erase(iter);
+#ifdef USE_SKIA_GPU
+  RemoveDemotableContext(this);
 #endif
 }
 
 JSObject*
 CanvasRenderingContext2D::WrapObject(JSContext *cx, JS::Handle<JSObject*> scope)
 {
   return CanvasRenderingContext2DBinding::Wrap(cx, scope, this);
 }
@@ -741,39 +739,50 @@ CanvasRenderingContext2D::RedrawUser(con
     return;
   }
 
   mgfx::Rect newr =
     mTarget->GetTransform().TransformBounds(ToRect(r));
   Redraw(newr);
 }
 
-#if USE_SKIA_GPU
-
 void CanvasRenderingContext2D::Demote()
 {
+#ifdef  USE_SKIA_GPU
   if (!IsTargetValid() || mForceSoftware)
     return;
 
+  RemoveDemotableContext(this);
+
   RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
   RefPtr<DrawTarget> oldTarget = mTarget;
   mTarget = nullptr;
   mResetLayer = true;
   mForceSoftware = true;
 
   // Recreate target, now demoted to software only
   EnsureTarget();
   if (!IsTargetValid())
     return;
 
-  // Put back the content from the old DrawTarget
+  // Restore the content from the old DrawTarget
   mgfx::Rect r(0, 0, mWidth, mHeight);
   mTarget->DrawSurface(snapshot, r, r);
+
+  // Restore the clips and transform
+  for (uint32_t i = 0; i < CurrentState().clipsPushed.size(); i++) {
+    mTarget->PushClip(CurrentState().clipsPushed[i]);
+  }
+
+  mTarget->SetTransform(oldTarget->GetTransform());
+#endif
 }
 
+#ifdef USE_SKIA_GPU
+
 std::vector<CanvasRenderingContext2D*>&
 CanvasRenderingContext2D::DemotableContexts()
 {
   static std::vector<CanvasRenderingContext2D*> contexts;
   return contexts;
 }
 
 void
@@ -785,31 +794,37 @@ CanvasRenderingContext2D::DemoteOldestCo
   const size_t kMaxContexts = 16;
 #endif
 
   std::vector<CanvasRenderingContext2D*>& contexts = DemotableContexts();
   if (contexts.size() < kMaxContexts)
     return;
 
   CanvasRenderingContext2D* oldest = contexts.front();
-  contexts.erase(contexts.begin());
-
   oldest->Demote();
 }
 
 void
 CanvasRenderingContext2D::AddDemotableContext(CanvasRenderingContext2D* context)
 {
   std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), context);
   if (iter != DemotableContexts().end())
     return;
 
   DemotableContexts().push_back(context);
 }
 
+void
+CanvasRenderingContext2D::RemoveDemotableContext(CanvasRenderingContext2D* context)
+{
+  std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), context);
+  if (iter != DemotableContexts().end())
+    DemotableContexts().erase(iter);
+}
+
 #define MIN_SKIA_GL_DIMENSION 16
 
 bool
 CheckSizeForSkiaGL(IntSize size) {
   return size.width > MIN_SKIA_GL_DIMENSION && size.height > MIN_SKIA_GL_DIMENSION;
 }
 
 #endif
--- a/content/canvas/src/CanvasRenderingContext2D.h
+++ b/content/canvas/src/CanvasRenderingContext2D.h
@@ -362,16 +362,18 @@ public:
 
   void DrawWindow(nsIDOMWindow* window, double x, double y, double w, double h,
                   const nsAString& bgColor, uint32_t flags,
                   mozilla::ErrorResult& error);
   void AsyncDrawXULElement(nsXULElement& elem, double x, double y, double w,
                            double h, const nsAString& bgColor, uint32_t flags,
                            mozilla::ErrorResult& error);
 
+  void Demote();
+
   nsresult Redraw();
 
   // nsICanvasRenderingContextInternal
   NS_IMETHOD SetDimensions(int32_t width, int32_t height) MOZ_OVERRIDE;
   NS_IMETHOD InitializeWithSurface(nsIDocShell *shell, gfxASurface *surface, int32_t width, int32_t height) MOZ_OVERRIDE;
 
   NS_IMETHOD Render(gfxContext *ctx,
                     gfxPattern::GraphicsFilter aFilter,
@@ -562,23 +564,21 @@ protected:
   {
     /* will initilize the value if not set, else does nothing */
     GetCurrentFontStyle();
 
     return CurrentState().font;
   }
 
 #if USE_SKIA_GPU
-
-  // Recreate the DrawTarget in software mode
-  void Demote();
-
   static std::vector<CanvasRenderingContext2D*>& DemotableContexts();
   static void DemoteOldestContextIfNecessary();
+
   static void AddDemotableContext(CanvasRenderingContext2D* context);
+  static void RemoveDemotableContext(CanvasRenderingContext2D* context);
 
   // Do not use GL
   bool mForceSoftware;
 #endif
 
   // Member vars
   int32_t mWidth, mHeight;
 
--- a/content/canvas/test/Makefile.in
+++ b/content/canvas/test/Makefile.in
@@ -83,16 +83,17 @@ MOCHITEST_FILES = \
 	test_bug856472.html \
 	test_bug866575.html \
 	test_drawImage_edge_cases.html \
 	test_drawImage_document_domain.html \
 	test_mozDashOffset.html \
 	file_drawImage_document_domain.html \
 	test_windingRuleUndefined.html \
 	test_strokeText_throw.html \
+	test_bug902651.html \
 	$(NULL)
 
 # SkiaGL on Android/Gonk does not implement these composite ops yet
 
 MOCHITEST_FILES += \
 	test_2d.composite.canvas.color-burn.html \
 	test_2d.composite.canvas.color-dodge.html \
 	test_2d.composite.canvas.darken.html \
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/test_bug902651.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<title>Canvas test: canvas demotion</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+<canvas id="c" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function () {
+
+var canvas = document.getElementById('c');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = 'rgb(50, 50, 50)';
+ctx.fillRect(0, 0, 100, 50);
+ctx.translate(25, 25);
+
+SpecialPowers.wrap(ctx).demote();
+
+setTimeout(function() {
+  ctx.fillStyle = 'rgb(127, 127, 127)';
+  ctx.fillRect(0, 0, 10, 10);
+
+  var pixels = ctx.getImageData(0, 0, 1, 1);
+
+  ok(pixels.data[0] === 50, "pixels.data[0] expected 50, got " + pixels.data[0]);
+  ok(pixels.data[1] === 50, "pixels.data[1] expected 50, got " + pixels.data[1]);
+  ok(pixels.data[2] === 50, "pixels.data[2] expected 50, got " + pixels.data[2]);
+
+  pixels = ctx.getImageData(25, 25, 1, 1);
+
+  ok(pixels.data[0] === 127, "pixels.data[0] expected 127, got " + pixels.data[0]);
+  ok(pixels.data[1] === 127, "pixels.data[1] expected 127, got " + pixels.data[1]);
+  ok(pixels.data[2] === 127, "pixels.data[2] expected 127, got " + pixels.data[2]);
+
+  SimpleTest.finish();
+}, 50);
+
+
+});
+</script>
+
--- a/dom/webidl/CanvasRenderingContext2D.webidl
+++ b/dom/webidl/CanvasRenderingContext2D.webidl
@@ -202,16 +202,22 @@ interface CanvasRenderingContext2D {
    */
   [Throws, ChromeOnly]
   void drawWindow(Window window, double x, double y, double w, double h,
                   DOMString bgColor, optional unsigned long flags = 0);
   [Throws, ChromeOnly]
   void asyncDrawXULElement(XULElement elem, double x, double y, double w,
                            double h, DOMString bgColor,
                            optional unsigned long flags = 0);
+  /**
+   * This causes a context that is currently using a hardware-accelerated
+   * backend to fallback to a software one. All state should be preserved.
+   */
+  [ChromeOnly]
+  void demote();
 };
 CanvasRenderingContext2D implements CanvasDrawingStyles;
 CanvasRenderingContext2D implements CanvasPathMethods;
 
 [NoInterfaceObject]
 interface CanvasDrawingStyles {
   // line caps/joins
            [LenientFloat]