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 177545 2bd9f799fd5ab750e87ab13ae2076db2b4d3d045
parent 177544 a09193c70bc8e2c69edb27a94f7d683b4409fa78
child 177546 ea0518320f9fc973468619617e7aa720ac0d9cd3
push id26556
push userryanvm@gmail.com
push dateTue, 08 Apr 2014 22:16:57 +0000
treeherdermozilla-central@5811efc11011 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersBas
bugs985545
milestone31.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 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);