Bug 985545 - Add a helper method to convert SourceSurfaces to a given format. r=Bas
authorJonathan Watt <jwatt@jwatt.org>
Tue, 08 Apr 2014 10:05:39 +0100
changeset 189575 2bd9f799fd5ab750e87ab13ae2076db2b4d3d045
parent 189574 a09193c70bc8e2c69edb27a94f7d683b4409fa78
child 189576 ea0518320f9fc973468619617e7aa720ac0d9cd3
push idunknown
push userunknown
push dateunknown
reviewersBas
bugs985545
milestone31.0a1
Bug 985545 - Add a helper method to convert SourceSurfaces to a given format. r=Bas
gfx/thebes/gfxUtils.cpp
gfx/thebes/gfxUtils.h
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -2,16 +2,18 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gfxUtils.h"
 #include "gfxContext.h"
 #include "gfxPlatform.h"
 #include "gfxDrawable.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
 #include "nsRegion.h"
 #include "yuv_convert.h"
 #include "ycbcr_to_rgb565.h"
 #include "GeckoProfiler.h"
 #include "ImageContainer.h"
 #include "gfx2DGlue.h"
 
 #ifdef XP_WIN
@@ -843,16 +845,86 @@ gfxUtils::ConvertYCbCrToRGB(const Planar
                                aData.mPicSize.height,
                                aData.mYStride,
                                aData.mCbCrStride,
                                aStride,
                                yuvtype);
   }
 }
 
+/* static */ TemporaryRef<DataSourceSurface>
+gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface,
+                                                   SurfaceFormat aFormat)
+{
+  MOZ_ASSERT(aFormat != aSurface->GetFormat(),
+             "Unnecessary - and very expersive - surface format conversion");
+
+  Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height);
+
+  if (aSurface->GetType() != SurfaceType::DATA) {
+    // If the surface is NOT of type DATA then its data is not mapped into main
+    // memory. Format conversion is probably faster on the GPU, and by doing it
+    // there we can avoid any expensive uploads/readbacks except for (possibly)
+    // a single readback due to the unavoidable GetDataSurface() call. Using
+    // CreateOffscreenContentDrawTarget ensures the conversion happens on the
+    // GPU.
+    RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->
+      CreateOffscreenContentDrawTarget(aSurface->GetSize(), aFormat);
+    // Using DrawSurface() here rather than CopySurface() because CopySurface
+    // is optimized for memcpy and therefore isn't good for format conversion.
+    // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
+    // generally more optimized.
+    dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
+                    DrawOptions(1.0f, CompositionOp::OP_OVER));
+    RefPtr<SourceSurface> surface = dt->Snapshot();
+    return surface->GetDataSurface();
+  }
+
+  // If the surface IS of type DATA then it may or may not be in main memory
+  // depending on whether or not it has been mapped yet. We have no way of
+  // knowing, so we can't be sure if it's best to create a data wrapping
+  // DrawTarget for the conversion or an offscreen content DrawTarget. We could
+  // guess it's not mapped and create an offscreen content DrawTarget, but if
+  // it is then we'll end up uploading the surface data, and most likely the
+  // caller is going to be accessing the resulting surface data, resulting in a
+  // readback (both very expensive operations). Alternatively we could guess
+  // the data is mapped and create a data wrapping DrawTarget and, if the
+  // surface is not in main memory, then we will incure a readback. The latter
+  // of these two "wrong choices" is the least costly (a readback, vs an
+  // upload and a readback), and more than likely the DATA surface that we've
+  // been passed actually IS in main memory anyway. For these reasons it's most
+  // likely best to create a data wrapping DrawTarget here to do the format
+  // conversion.
+  RefPtr<DataSourceSurface> dataSurface =
+    Factory::CreateDataSourceSurface(aSurface->GetSize(), aFormat);
+  DataSourceSurface::MappedSurface map;
+  if (!dataSurface ||
+      !dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
+    return nullptr;
+  }
+  RefPtr<DrawTarget> dt =
+    Factory::CreateDrawTargetForData(BackendType::CAIRO,
+                                     map.mData,
+                                     dataSurface->GetSize(),
+                                     map.mStride,
+                                     aFormat);
+  if (!dt) {
+    dataSurface->Unmap();
+    return nullptr;
+  }
+  // Using DrawSurface() here rather than CopySurface() because CopySurface
+  // is optimized for memcpy and therefore isn't good for format conversion.
+  // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
+  // generally more optimized.
+  dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
+                  DrawOptions(1.0f, CompositionOp::OP_OVER));
+  dataSurface->Unmap();
+  return dataSurface.forget();
+}
+
 #ifdef MOZ_DUMP_PAINTING
 /* static */ void
 gfxUtils::WriteAsPNG(DrawTarget* aDT, const char* aFile)
 {
   aDT->Flush();
   nsRefPtr<gfxASurface> surf = gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(aDT);
   if (surf) {
     surf->WriteAsPNG(aFile);
--- a/gfx/thebes/gfxUtils.h
+++ b/gfx/thebes/gfxUtils.h
@@ -4,31 +4,37 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_UTILS_H
 #define GFX_UTILS_H
 
 #include "gfxTypes.h"
 #include "GraphicsFilter.h"
 #include "imgIContainer.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
 
 class gfxDrawable;
 class nsIntRegion;
 struct nsIntRect;
 
 namespace mozilla {
 namespace layers {
 class PlanarYCbCrData;
 }
 }
 
 class gfxUtils {
 public:
+    typedef mozilla::gfx::DataSourceSurface DataSourceSurface;
     typedef mozilla::gfx::IntPoint IntPoint;
     typedef mozilla::gfx::Matrix Matrix;
+    typedef mozilla::gfx::SourceSurface SourceSurface;
+    typedef mozilla::gfx::SurfaceFormat SurfaceFormat;
+
     /*
      * Premultiply or Unpremultiply aSourceSurface, writing the result
      * to aDestSurface or back into aSourceSurface if aDestSurface is null.
      *
      * If aDestSurface is given, it must have identical format, dimensions, and
      * stride as the source.
      *
      * If the source is not gfxImageFormat::ARGB32, no operation is performed.  If
@@ -150,16 +156,61 @@ public:
      */
     static void
     ConvertYCbCrToRGB(const mozilla::layers::PlanarYCbCrData& aData,
                       const gfxImageFormat& aDestFormat,
                       const gfxIntSize& aDestSize,
                       unsigned char* aDestBuffer,
                       int32_t aStride);
 
+    /**
+     * Creates a copy of aSurface, but having the SurfaceFormat aFormat.
+     *
+     * This function always creates a new surface. Do not call it if aSurface's
+     * format is the same as aFormat. Such a non-conversion would just be an
+     * unnecessary and wasteful copy (this function asserts to prevent that).
+     *
+     * This function is intended to be called by code that needs to access the
+     * pixel data of the surface, but doesn't want to have lots of branches
+     * to handle different pixel data formats (code which would become out of
+     * date if and when new formats are added). Callers can use this function
+     * to copy the surface to a specified format so that they only have to
+     * handle pixel data in that one format.
+     *
+     * WARNING: There are format conversions that will not be supported by this
+     * function. It very much depends on what the Moz2D backends support. If
+     * the temporary B8G8R8A8 DrawTarget that this function creates has a
+     * backend that supports DrawSurface() calls passing a surface with
+     * aSurface's format it will work. Otherwise it will not.
+     *
+     *                      *** IMPORTANT PERF NOTE ***
+     *
+     * This function exists partly because format conversion is fraught with
+     * non-obvious performance hazards, so we don't want Moz2D consumers to be
+     * doing their own format conversion. Do not try to do so, or at least read
+     * the comments in this functions implemtation. That said, the copy that
+     * this function carries out has a cost and, although this function tries
+     * to avoid perf hazards such as expensive uploads to/readbacks from the
+     * GPU, it can't guarantee that it always successfully does so. Perf
+     * critical code that can directly handle the common formats that it
+     * encounters in a way that is cheaper than a copy-with-format-conversion
+     * should consider doing so, and only use this function as a fallback to
+     * handle other formats.
+     *
+     * XXXjwatt it would be nice if SourceSurface::GetDataSurface took a
+     * SurfaceFormat argument (with a default argument meaning "use the
+     * existing surface's format") and returned a DataSourceSurface in that
+     * format. (There would then be an issue of callers maybe failing to
+     * realize format conversion may involve expensive copying/uploading/
+     * readback.)
+     */
+    static mozilla::TemporaryRef<DataSourceSurface>
+    CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface,
+                                             SurfaceFormat aFormat);
+
     static const uint8_t sUnpremultiplyTable[256*256];
     static const uint8_t sPremultiplyTable[256*256];
 #ifdef MOZ_DUMP_PAINTING
     /**
      * Writes a binary PNG file.
      */
     static void WriteAsPNG(mozilla::gfx::DrawTarget* aDT, const char* aFile);