Bug 1323617 - Fix canvas_sub_rectangle tests. - r=ethlin draft
authorJeff Gilbert <jdashg@gmail.com>
Tue, 13 Dec 2016 12:51:06 -1000
changeset 450532 5146d7a101a4cfb8857f29835c0595ce9d52d808
parent 447759 c2526f6786f074888d71c8e166a02aea3e19e75b
child 450533 b35154d153ad22a6f54459aa5d685c941a55fd05
push id38882
push userbmo:jgilbert@mozilla.com
push dateFri, 16 Dec 2016 21:59:10 +0000
reviewersethlin
bugs1323617
milestone53.0a1
Bug 1323617 - Fix canvas_sub_rectangle tests. - r=ethlin MozReview-Commit-ID: 8wWDiiyp7ZS
dom/canvas/TexUnpackBlob.cpp
dom/canvas/TexUnpackBlob.h
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -14,30 +14,16 @@
 #include "WebGLBuffer.h"
 #include "WebGLContext.h"
 #include "WebGLTexelConversions.h"
 #include "WebGLTexture.h"
 
 namespace mozilla {
 namespace webgl {
 
-static bool
-UnpackFormatHasColorAndAlpha(GLenum unpackFormat)
-{
-    switch (unpackFormat) {
-    case LOCAL_GL_LUMINANCE_ALPHA:
-    case LOCAL_GL_RGBA:
-    case LOCAL_GL_SRGB_ALPHA:
-        return true;
-
-    default:
-        return false;
-    }
-}
-
 static WebGLTexelFormat
 FormatForPackingInfo(const PackingInfo& pi)
 {
     switch (pi.type) {
     case LOCAL_GL_UNSIGNED_BYTE:
         switch (pi.format) {
         case LOCAL_GL_RED:
         case LOCAL_GL_LUMINANCE:
@@ -220,181 +206,104 @@ ZeroOn2D(TexImageTarget target, uint32_t
 static uint32_t
 FallbackOnZero(uint32_t val, uint32_t fallback)
 {
     return (val ? val : fallback);
 }
 
 TexUnpackBlob::TexUnpackBlob(const WebGLContext* webgl, TexImageTarget target,
                              uint32_t rowLength, uint32_t width, uint32_t height,
-                             uint32_t depth, bool isSrcPremult)
+                             uint32_t depth, bool srcIsPremult)
     : mAlignment(webgl->mPixelStore_UnpackAlignment)
     , mRowLength(rowLength)
-    , mImageHeight(FallbackOnZero(ZeroOn2D(target,
-                                           webgl->mPixelStore_UnpackImageHeight),
+    , mImageHeight(FallbackOnZero(ZeroOn2D(target, webgl->mPixelStore_UnpackImageHeight),
                                   height))
 
     , mSkipPixels(webgl->mPixelStore_UnpackSkipPixels)
     , mSkipRows(webgl->mPixelStore_UnpackSkipRows)
     , mSkipImages(ZeroOn2D(target, webgl->mPixelStore_UnpackSkipImages))
 
     , mWidth(width)
     , mHeight(height)
     , mDepth(depth)
 
-    , mIsSrcPremult(isSrcPremult)
+    , mSrcIsPremult(srcIsPremult)
 
     , mNeedsExactUpload(false)
 {
     MOZ_ASSERT_IF(!IsTarget3D(target), mDepth == 1);
 }
 
 bool
 TexUnpackBlob::ConvertIfNeeded(WebGLContext* webgl, const char* funcName,
-                               const uint8_t* srcBytes, uint32_t srcStride,
-                               uint8_t srcBPP, WebGLTexelFormat srcFormat,
-                               const webgl::DriverUnpackInfo* dstDUI,
-                               const uint8_t** const out_bytes,
+                               const uint32_t rowLength, const uint32_t rowCount,
+                               WebGLTexelFormat srcFormat,
+                               const uint8_t* const srcBegin, const ptrdiff_t srcStride,
+                               WebGLTexelFormat dstFormat, const ptrdiff_t dstStride,
+
+                               const uint8_t** const out_begin,
                                UniqueBuffer* const out_anchoredBuffer) const
 {
-    *out_bytes = srcBytes;
+    MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
+    MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
 
-    if (!HasData() || !mWidth || !mHeight || !mDepth)
+    *out_begin = srcBegin;
+
+    if (!rowLength || !rowCount)
         return true;
 
-    //////
-
-    const auto totalSkipRows = mSkipRows + CheckedUint32(mSkipImages) * mImageHeight;
-    const auto offset = mSkipPixels * CheckedUint32(srcBPP) + totalSkipRows * srcStride;
-    if (!offset.isValid()) {
-        webgl->ErrorOutOfMemory("%s: Invalid offset calculation during conversion.",
-                                funcName);
-        return false;
-    }
-    const uint32_t skipBytes = offset.value();
-
-    auto const srcBegin = srcBytes + skipBytes;
-
-    //////
-
+    const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
     const auto srcOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft
                                                      : gl::OriginPos::BottomLeft);
     const auto dstOrigin = gl::OriginPos::BottomLeft;
-    const bool isDstPremult = webgl->mPixelStore_PremultiplyAlpha;
-
-    const auto pi = dstDUI->ToPacking();
-
-    const auto dstBPP = webgl::BytesPerPixel(pi);
-    const auto dstWidthBytes = CheckedUint32(dstBPP) * mWidth;
-    const auto dstRowLengthBytes = CheckedUint32(dstBPP) * mRowLength;
 
-    const auto dstAlignment = mAlignment;
-    const auto dstStride = RoundUpToMultipleOf(dstRowLengthBytes, dstAlignment);
-
-    //////
+    if (srcFormat != dstFormat) {
+        webgl->GenerateWarning("%s: Conversion requires pixel reformatting.", funcName);
+    } else if (mSrcIsPremult != dstIsPremult) {
+        webgl->GenerateWarning("%s: Conversion requires change in"
+                               "alpha-premultiplication.",
+                               funcName);
+    } else if (srcOrigin != dstOrigin) {
+        webgl->GenerateWarning("%s: Conversion requires y-flip.", funcName);
+    } else if (srcStride != dstStride) {
+        webgl->GenerateWarning("%s: Conversion requires change in stride.", funcName);
+    } else {
+        return true;
+    }
 
-    const auto dstTotalRows = CheckedUint32(mDepth - 1) * mImageHeight + mHeight;
-    const auto dstUsedSizeExceptLastRow = (dstTotalRows - 1) * dstStride;
+    ////
 
-    const auto dstSize = skipBytes + dstUsedSizeExceptLastRow + dstWidthBytes;
-    if (!dstSize.isValid()) {
-        webgl->ErrorOutOfMemory("%s: Invalid dstSize calculation during conversion.",
-                                funcName);
+    const auto dstTotalBytes = CheckedUint32(rowCount) * dstStride;
+    if (!dstTotalBytes.isValid()) {
+        webgl->ErrorOutOfMemory("%s: Calculation failed.", funcName);
         return false;
     }
 
-    //////
-
-    const auto dstFormat = FormatForPackingInfo(pi);
-
-    bool premultMatches = (mIsSrcPremult == isDstPremult);
-    if (!UnpackFormatHasColorAndAlpha(dstDUI->unpackFormat)) {
-        premultMatches = true;
-    }
-
-    const bool needsPixelConversion = (srcFormat != dstFormat || !premultMatches);
-    const bool originsMatch = (srcOrigin == dstOrigin);
-
-    MOZ_ASSERT_IF(!needsPixelConversion, srcBPP == dstBPP);
-
-    if (!needsPixelConversion &&
-        originsMatch &&
-        srcStride == dstStride.value())
-    {
-        // No conversion needed!
-        return true;
-    }
-
-    //////
-    // We need some sort of conversion, so create the dest buffer.
-
-    *out_anchoredBuffer = calloc(1, dstSize.value());
-    const auto dstBytes = (uint8_t*)out_anchoredBuffer->get();
-
-    if (!dstBytes) {
-        webgl->ErrorOutOfMemory("%s: Unable to allocate buffer during conversion.",
-                                funcName);
+    UniqueBuffer dstBuffer = calloc(1, dstTotalBytes.value());
+    if (!dstBuffer.get()) {
+        webgl->ErrorOutOfMemory("%s: Failed to allocate dest buffer.", funcName);
         return false;
     }
-    *out_bytes = dstBytes;
-    const auto dstBegin = dstBytes + skipBytes;
-
-    //////
-    // Row conversion
-
-    if (!needsPixelConversion) {
-        webgl->GenerateWarning("%s: Incurred CPU row conversion, which is slow.",
-                               funcName);
-
-        const uint8_t* srcRow = srcBegin;
-        uint8_t* dstRow = dstBegin;
-        const auto widthBytes = dstWidthBytes.value();
-        ptrdiff_t dstCopyStride = dstStride.value();
+    const auto dstBegin = static_cast<uint8_t*>(dstBuffer.get());
 
-        if (!originsMatch) {
-            dstRow += dstUsedSizeExceptLastRow.value();
-            dstCopyStride = -dstCopyStride;
-        }
-
-        for (uint32_t i = 0; i < dstTotalRows.value(); i++) {
-            memcpy(dstRow, srcRow, widthBytes);
-            srcRow += srcStride;
-            dstRow += dstCopyStride;
-        }
-        return true;
-    }
-
-    ////////////
-    // Pixel conversion.
-
-    MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
-    MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
-
-    webgl->GenerateWarning("%s: Incurred CPU pixel conversion, which is very slow.",
-                           funcName);
-
-    //////
+    ////
 
     // And go!:
     bool wasTrivial;
-    if (!ConvertImage(mWidth, dstTotalRows.value(),
-                      srcBegin, srcStride, srcOrigin, srcFormat, mIsSrcPremult,
-                      dstBegin, dstStride.value(), dstOrigin, dstFormat, isDstPremult,
+    if (!ConvertImage(rowLength, rowCount,
+                      srcBegin, srcStride, srcOrigin, srcFormat, mSrcIsPremult,
+                      dstBegin, dstStride, dstOrigin, dstFormat, dstIsPremult,
                       &wasTrivial))
     {
         webgl->ErrorImplementationBug("%s: ConvertImage failed.", funcName);
         return false;
     }
 
-    if (!wasTrivial) {
-        webgl->GenerateWarning("%s: Chosen format/type incurred an expensive reformat:"
-                               " 0x%04x/0x%04x",
-                               funcName, dstDUI->unpackFormat, dstDUI->unpackType);
-    }
-
+    *out_begin = dstBegin;
+    *out_anchoredBuffer = Move(dstBuffer);
     return true;
 }
 
 static GLenum
 DoTexOrSubImage(bool isSubImage, gl::GLContext* gl, TexImageTarget target, GLint level,
                 const DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset,
                 GLsizei width, GLsizei height, GLsizei depth, const void* data)
 {
@@ -408,18 +317,18 @@ DoTexOrSubImage(bool isSubImage, gl::GLC
 
 //////////////////////////////////////////////////////////////////////////////////////////
 // TexUnpackBytes
 
 TexUnpackBytes::TexUnpackBytes(const WebGLContext* webgl, TexImageTarget target,
                                uint32_t width, uint32_t height, uint32_t depth,
                                bool isClientData, const uint8_t* ptr, size_t availBytes)
     : TexUnpackBlob(webgl, target,
-                    FallbackOnZero(webgl->mPixelStore_UnpackRowLength, width), width,
-                    height, depth, false)
+                    FallbackOnZero(webgl->mPixelStore_UnpackRowLength, width),
+                    width, height, depth, false)
     , mIsClientData(isClientData)
     , mPtr(ptr)
     , mAvailBytes(availBytes)
 { }
 
 bool
 TexUnpackBytes::Validate(WebGLContext* webgl, const char* funcName,
                          const webgl::PackingInfo& pi)
@@ -434,39 +343,62 @@ bool
 TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                               WebGLTexture* tex, TexImageTarget target, GLint level,
                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
                               GLint yOffset, GLint zOffset, GLenum* const out_error) const
 {
     WebGLContext* webgl = tex->mContext;
 
     const auto pi = dui->ToPacking();
-
+    const auto format = FormatForPackingInfo(pi);
     const auto bytesPerPixel = webgl::BytesPerPixel(pi);
-    const auto bytesPerRow = CheckedUint32(mRowLength) * bytesPerPixel;
-    const auto rowStride = RoundUpToMultipleOf(bytesPerRow, mAlignment);
-    if (!rowStride.isValid()) {
-        MOZ_CRASH("Should be checked earlier.");
-    }
+
+    const uint8_t* uploadPtr = mPtr;
+    UniqueBuffer tempBuffer;
+
+    do {
+        if (!mIsClientData || !mPtr)
+            break;
+
+        if (!webgl->mPixelStore_FlipY &&
+            !webgl->mPixelStore_PremultiplyAlpha)
+        {
+            break;
+        }
 
-    const auto format = FormatForPackingInfo(pi);
+        if (webgl->mPixelStore_UnpackImageHeight ||
+            webgl->mPixelStore_UnpackSkipImages ||
+            webgl->mPixelStore_UnpackRowLength ||
+            webgl->mPixelStore_UnpackSkipRows ||
+            webgl->mPixelStore_UnpackSkipPixels)
+        {
+            webgl->ErrorInvalidOperation("%s: Non-DOM-Element uploads with alpha-premult"
+                                         " or y-flip do not support subrect selection.",
+                                         funcName);
+            return false;
+        }
 
-    auto uploadPtr = mPtr;
-    UniqueBuffer tempBuffer;
-    if (mIsClientData &&
-        !ConvertIfNeeded(webgl, funcName, mPtr, rowStride.value(), bytesPerPixel, format,
-                         dui, &uploadPtr, &tempBuffer))
-    {
-        return false;
-    }
+        webgl->GenerateWarning("%s: Alpha-premult and y-flip are deprecated for"
+                               " non-DOM-Element uploads.",
+                               funcName);
+
+        const uint32_t rowLength = mWidth;
+        const uint32_t rowCount = mHeight * mDepth;
+        const auto stride = RoundUpToMultipleOf(rowLength * bytesPerPixel, mAlignment);
+        if (!ConvertIfNeeded(webgl, funcName, rowLength, rowCount, format, mPtr, stride,
+                             format, stride, &uploadPtr, &tempBuffer))
+        {
+            return false;
+        }
+    } while (false);
+
+    //////
 
     const auto& gl = webgl->gl;
 
-    //////
-
     bool useParanoidHandling = false;
     if (mNeedsExactUpload && webgl->mBoundPixelUnpackBuffer) {
         webgl->GenerateWarning("%s: Uploads from a buffer with a final row with a byte"
                                " count smaller than the row stride can incur extra"
                                " overhead.",
                                funcName);
 
         if (gl->WorkAroundDriverBugs()) {
@@ -515,16 +447,21 @@ TexUnpackBytes::TexOrSubImage(bool isSub
         *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
                                      zOffset+mDepth-1, mWidth, mHeight-1, 1, uploadPtr);
     }
 
     const auto totalSkipRows = CheckedUint32(mSkipImages) * mImageHeight + mSkipRows;
     const auto totalFullRows = CheckedUint32(mDepth - 1) * mImageHeight + mHeight - 1;
     const auto tailOffsetRows = totalSkipRows + totalFullRows;
 
+    const auto bytesPerRow = CheckedUint32(mRowLength) * bytesPerPixel;
+    const auto rowStride = RoundUpToMultipleOf(bytesPerRow, mAlignment);
+    if (!rowStride.isValid()) {
+        MOZ_CRASH("Should be checked earlier.");
+    }
     const auto tailOffsetBytes = tailOffsetRows * rowStride;
 
     uploadPtr += tailOffsetBytes.value();
 
     //////
 
     gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);   // No stride padding.
     gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0);  // No padding in general.
@@ -589,18 +526,18 @@ TexUnpackImage::TexOrSubImage(bool isSub
         if (*out_error)
             return true;
     }
 
     do {
         if (mDepth != 1)
             break;
 
-        const bool isDstPremult = webgl->mPixelStore_PremultiplyAlpha;
-        if (mIsSrcPremult != isDstPremult)
+        const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
+        if (mSrcIsPremult != dstIsPremult)
             break;
 
         if (dui->unpackFormat != LOCAL_GL_RGB && dui->unpackFormat != LOCAL_GL_RGBA)
             break;
 
         if (dui->unpackType != LOCAL_GL_UNSIGNED_BYTE)
             break;
 
@@ -649,17 +586,17 @@ TexUnpackImage::TexOrSubImage(bool isSub
     if (!dataSurf) {
         webgl->ErrorOutOfMemory("%s: GetAsSourceSurface or GetDataSurface failed after"
                                 " blit failed for TexUnpackImage.",
                                 funcName);
         return false;
     }
 
     const TexUnpackSurface surfBlob(webgl, target, mWidth, mHeight, mDepth, dataSurf,
-                                    mIsSrcPremult);
+                                    mSrcIsPremult);
 
     return surfBlob.TexOrSubImage(isSubImage, needsRespec, funcName, tex, target, level,
                                   dui, xOffset, yOffset, zOffset, out_error);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackSurface
@@ -734,67 +671,88 @@ TexUnpackSurface::Validate(WebGLContext*
 
 bool
 TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                                 WebGLTexture* tex, TexImageTarget target, GLint level,
                                 const webgl::DriverUnpackInfo* dstDUI, GLint xOffset,
                                 GLint yOffset, GLint zOffset,
                                 GLenum* const out_error) const
 {
-    WebGLContext* webgl = tex->mContext;
+    const auto& webgl = tex->mContext;
+
+    ////
+
+    const auto& rowLength = mSurf->GetSize().width;
+    const auto& rowCount = mSurf->GetSize().height;
+
+    const auto& dstPI = dstDUI->ToPacking();
+    const auto& dstBPP = webgl::BytesPerPixel(dstPI);
+    const auto dstFormat = FormatForPackingInfo(dstPI);
+
+    ////
 
     WebGLTexelFormat srcFormat;
     uint8_t srcBPP;
     if (!GetFormatForSurf(mSurf, &srcFormat, &srcBPP)) {
         webgl->ErrorImplementationBug("%s: GetFormatForSurf failed for"
                                       " WebGLTexelFormat::%u.",
                                       funcName, uint32_t(mSurf->GetFormat()));
         return false;
     }
 
     gfx::DataSourceSurface::ScopedMap map(mSurf, gfx::DataSourceSurface::MapType::READ);
     if (!map.IsMapped()) {
         webgl->ErrorOutOfMemory("%s: Failed to map source surface for upload.", funcName);
         return false;
     }
 
-    const auto srcBytes = map.GetData();
-    const auto srcStride = map.GetStride();
+    const auto& srcBegin = map.GetData();
+    const auto& srcStride = map.GetStride();
 
-    // CPU conversion. (++numCopies)
+    ////
+
+    const auto srcRowLengthBytes = rowLength * srcBPP;
 
-    webgl->GenerateWarning("%s: Incurred CPU-side conversion, which is very slow.",
-                           funcName);
+    const uint8_t maxGLAlignment = 8;
+    uint8_t srcAlignment = 1;
+    for (; srcAlignment <= maxGLAlignment; srcAlignment *= 2) {
+        const auto strideGuess = RoundUpToMultipleOf(srcRowLengthBytes, srcAlignment);
+        if (strideGuess == srcStride)
+            break;
+    }
+    const uint32_t dstAlignment = (srcAlignment > maxGLAlignment) ? 1 : srcAlignment;
 
-    const uint8_t* uploadBytes;
+    const auto dstRowLengthBytes = rowLength * dstBPP;
+    const auto dstStride = RoundUpToMultipleOf(dstRowLengthBytes, dstAlignment);
+
+    ////
+
+    const uint8_t* dstBegin = srcBegin;
     UniqueBuffer tempBuffer;
-    if (!ConvertIfNeeded(webgl, funcName, srcBytes, srcStride, srcBPP, srcFormat,
-                         dstDUI, &uploadBytes, &tempBuffer))
+    if (!ConvertIfNeeded(webgl, funcName, rowLength, rowCount, srcFormat, srcBegin,
+                         srcStride, dstFormat, dstStride, &dstBegin, &tempBuffer))
     {
         return false;
     }
 
-    //////
+    ////
 
-    gl::GLContext* const gl = webgl->gl;
+    const auto& gl = webgl->gl;
     MOZ_ALWAYS_TRUE( gl->MakeCurrent() );
 
-    const auto curEffectiveRowLength = FallbackOnZero(webgl->mPixelStore_UnpackRowLength,
-                                                      mWidth);
-
-    const bool changeRowLength = (mRowLength != curEffectiveRowLength);
-    if (changeRowLength) {
-        MOZ_ASSERT(webgl->IsWebGL2());
-        gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, mRowLength);
+    gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, dstAlignment);
+    if (webgl->IsWebGL2()) {
+        gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
     }
 
     *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dstDUI, xOffset,
-                                 yOffset, zOffset, mWidth, mHeight, mDepth, uploadBytes);
+                                 yOffset, zOffset, mWidth, mHeight, mDepth, dstBegin);
 
-    if (changeRowLength) {
+    gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, webgl->mPixelStore_UnpackAlignment);
+    if (webgl->IsWebGL2()) {
         gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, webgl->mPixelStore_UnpackRowLength);
     }
 
     return true;
 }
 
 } // namespace webgl
 } // namespace mozilla
--- a/dom/canvas/TexUnpackBlob.h
+++ b/dom/canvas/TexUnpackBlob.h
@@ -45,33 +45,36 @@ public:
     const uint32_t mRowLength;
     const uint32_t mImageHeight;
     const uint32_t mSkipPixels;
     const uint32_t mSkipRows;
     const uint32_t mSkipImages;
     const uint32_t mWidth;
     const uint32_t mHeight;
     const uint32_t mDepth;
-    const bool mIsSrcPremult;
+
+    const bool mSrcIsPremult;
 
     bool mNeedsExactUpload;
 
 protected:
     TexUnpackBlob(const WebGLContext* webgl, TexImageTarget target, uint32_t rowLength,
                   uint32_t width, uint32_t height, uint32_t depth, bool isSrcPremult);
 
 public:
     virtual ~TexUnpackBlob() { }
 
 protected:
     bool ConvertIfNeeded(WebGLContext* webgl, const char* funcName,
-                         const uint8_t* srcBytes, uint32_t srcStride, uint8_t srcBPP,
+                         const uint32_t rowLength, const uint32_t rowCount,
                          WebGLTexelFormat srcFormat,
-                         const webgl::DriverUnpackInfo* dstDUI,
-                         const uint8_t** const out_bytes,
+                         const uint8_t* const srcBegin, const ptrdiff_t srcStride,
+                         WebGLTexelFormat dstFormat, const ptrdiff_t dstStride,
+
+                         const uint8_t** const out_begin,
                          UniqueBuffer* const out_anchoredBuffer) const;
 
 public:
     virtual bool HasData() const { return true; }
 
     virtual bool Validate(WebGLContext* webgl, const char* funcName,
                           const webgl::PackingInfo& pi) = 0;