Reland "Add ETC2 support to Metal backend."

This is a reland of c25802db30fa371d3984eeef6b04a605e7b7f51f

Original change's description:
> Add ETC2 support to Metal backend.
> 
> Fills out onCreateCompressedTexture and sets iOS caps to support ETC2.
> Skia supports no compressed texture formats on MacOS as yet.
> 
> Bug: skia:8243
> Change-Id: I2ce20f601c035a8822e658c88b815fdd8587aa98
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/240692
> Commit-Queue: Jim Van Verth <jvanverth@google.com>
> Reviewed-by: Brian Salomon <bsalomon@google.com>

Bug: skia:8243
Change-Id: Idebc46f29f4b75d216cf877606af290b0b3b8318
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/242080
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
diff --git a/src/gpu/GrDataUtils.cpp b/src/gpu/GrDataUtils.cpp
index 748455f..51292ad 100644
--- a/src/gpu/GrDataUtils.cpp
+++ b/src/gpu/GrDataUtils.cpp
@@ -97,13 +97,18 @@
     }
 }
 
-static int num_ETC1_blocks(int w, int h) {
+static int num_ETC1_blocks_w(int w) {
     if (w < 4) {
         w = 1;
     } else {
-       SkASSERT((w & 3) == 0);
-       w >>= 2;
+        SkASSERT((w & 3) == 0);
+        w >>= 2;
     }
+    return w;
+}
+
+static int num_ETC1_blocks(int w, int h) {
+    w = num_ETC1_blocks_w(w);
 
     if (h < 4) {
         h = 1;
@@ -124,6 +129,15 @@
     SK_ABORT("Unexpected compression type");
 }
 
+size_t GrCompressedRowBytes(SkImage::CompressionType type, int width) {
+    switch (type) {
+        case SkImage::kETC1_CompressionType:
+            int numBlocksWidth = num_ETC1_blocks_w(width);
+            return numBlocksWidth * sizeof(ETC1Block);
+    }
+    SK_ABORT("Unexpected compression type");
+}
+
 // Fill in 'dest' with ETC1 blocks derived from 'colorf'
 static void fillin_ETC1_with_color(int width, int height, const SkColor4f& colorf, void* dest) {
     SkColor color = colorf.toSkColor();
diff --git a/src/gpu/GrDataUtils.h b/src/gpu/GrDataUtils.h
index c72feb7..819189f 100644
--- a/src/gpu/GrDataUtils.h
+++ b/src/gpu/GrDataUtils.h
@@ -15,6 +15,9 @@
 
 size_t GrCompressedDataSize(SkImage::CompressionType, int w, int h);
 
+// Returns a value that can be used to set rowBytes for a transfer function.
+size_t GrCompressedRowBytes(SkImage::CompressionType, int w);
+
 // Compute the size of the buffer required to hold all the mipLevels of the specified type
 // of data when all rowBytes are tight.
 // Note there may still be padding between the mipLevels to meet alignment requirements.
diff --git a/src/gpu/gl/GrGLTexture.cpp b/src/gpu/gl/GrGLTexture.cpp
index 5473980..a5a1a37 100644
--- a/src/gpu/gl/GrGLTexture.cpp
+++ b/src/gpu/gl/GrGLTexture.cpp
@@ -50,7 +50,7 @@
         , fParameters(sk_make_sp<GrGLTextureParameters>()) {
     this->init(desc);
     this->registerWithCache(budgeted);
-    if (GrPixelConfigIsCompressed(desc.fConfig)) {
+    if (GrGLFormatIsCompressed(desc.fFormat)) {
         this->setReadOnly();
     }
 }
diff --git a/src/gpu/mtl/GrMtlCaps.mm b/src/gpu/mtl/GrMtlCaps.mm
index 14cb2db..fbadae3 100644
--- a/src/gpu/mtl/GrMtlCaps.mm
+++ b/src/gpu/mtl/GrMtlCaps.mm
@@ -745,8 +745,7 @@
 #ifdef SK_BUILD_FOR_IOS
     // ETC2_RGB8
     info = &fFormatTable[GetFormatIndex(MTLPixelFormatETC2_RGB8)];
-    // GrMtlGpu::onCreateCompressedTexture() not implemented.
-    info->fFlags = 0;
+    info->fFlags = FormatInfo::kTexturable_Flag;
     // NO supported colorTypes
 #endif
 
diff --git a/src/gpu/mtl/GrMtlGpu.h b/src/gpu/mtl/GrMtlGpu.h
index bc5996c..c574d4d 100644
--- a/src/gpu/mtl/GrMtlGpu.h
+++ b/src/gpu/mtl/GrMtlGpu.h
@@ -143,9 +143,7 @@
                                      uint32_t levelClearMask) override;
     sk_sp<GrTexture> onCreateCompressedTexture(int width, int height, const GrBackendFormat&,
                                                SkImage::CompressionType, SkBudgeted,
-                                               const void* data) override {
-        return nullptr;
-    }
+                                               const void* data) override;
 
     sk_sp<GrTexture> onWrapBackendTexture(const GrBackendTexture&, GrColorType,
                                           GrWrapOwnership, GrWrapCacheable, GrIOType) override;
@@ -224,11 +222,11 @@
     GrStencilAttachment* createStencilAttachmentForRenderTarget(
             const GrRenderTarget*, int width, int height, int numStencilSamples) override;
 
-    bool createTestingOnlyMtlTextureInfo(MTLPixelFormat,
-                                         int w, int h, bool texturable,
-                                         bool renderable, GrMipMapped mipMapped,
-                                         const void* srcData, size_t srcRowBytes,
-                                         const SkColor4f* color, GrMtlTextureInfo* info);
+    bool createMtlTextureForBackendSurface(MTLPixelFormat,
+                                           int w, int h, bool texturable,
+                                           bool renderable, GrMipMapped mipMapped,
+                                           const void* srcData, size_t srcRowBytes,
+                                           const SkColor4f* color, GrMtlTextureInfo* info);
 
     sk_sp<GrMtlCaps> fMtlCaps;
 
diff --git a/src/gpu/mtl/GrMtlGpu.mm b/src/gpu/mtl/GrMtlGpu.mm
index a0a4c21..553f2f0 100644
--- a/src/gpu/mtl/GrMtlGpu.mm
+++ b/src/gpu/mtl/GrMtlGpu.mm
@@ -455,6 +455,91 @@
     return tex;
 }
 
+sk_sp<GrTexture> GrMtlGpu::onCreateCompressedTexture(int width, int height,
+                                                     const GrBackendFormat& format,
+                                                     SkImage::CompressionType compressionType,
+                                                     SkBudgeted budgeted, const void* data) {
+    SkASSERT(this->caps()->isFormatTexturable(format));
+    SkASSERT(data);
+
+    if (!check_max_blit_width(width)) {
+        return nullptr;
+    }
+
+    MTLPixelFormat mtlPixelFormat = GrBackendFormatAsMTLPixelFormat(format);
+
+    // This TexDesc refers to the texture that will be read by the client. Thus even if msaa is
+    // requested, this TexDesc describes the resolved texture. Therefore we always have samples
+    // set to 1.
+    // Compressed textures with MIP levels or multiple samples are not supported as of now.
+    MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init];
+    texDesc.textureType = MTLTextureType2D;
+    texDesc.pixelFormat = mtlPixelFormat;
+    texDesc.width = width;
+    texDesc.height = height;
+    texDesc.depth = 1;
+    texDesc.mipmapLevelCount = 1;
+    texDesc.sampleCount = 1;
+    texDesc.arrayLength = 1;
+    // Make all textures have private gpu only access. We can use transfer buffers or textures
+    // to copy to them.
+    texDesc.storageMode = MTLStorageModePrivate;
+    texDesc.usage = MTLTextureUsageShaderRead;
+
+    GrSurfaceDesc desc;
+    desc.fConfig = GrCompressionTypePixelConfig(compressionType);
+    desc.fWidth = width;
+    desc.fHeight = height;
+    auto tex = GrMtlTexture::MakeNewTexture(this, budgeted, desc, texDesc,
+                                            GrMipMapsStatus::kNotAllocated);
+    if (!tex) {
+        return nullptr;
+    }
+
+    // Upload to texture
+    id<MTLTexture> mtlTexture = tex->mtlTexture();
+    SkASSERT(mtlTexture);
+
+    SkImage::CompressionType textureCompressionType;
+    if (!GrMtlFormatToCompressionType(mtlTexture.pixelFormat, &textureCompressionType) ||
+        textureCompressionType != compressionType) {
+        return nullptr;
+    }
+
+    size_t dataSize = GrCompressedDataSize(compressionType, width, height);
+    SkASSERT(dataSize);
+
+    size_t bufferOffset;
+    id<MTLBuffer> transferBuffer = this->resourceProvider().getDynamicBuffer(dataSize,
+                                                                             &bufferOffset);
+    if (!transferBuffer) {
+        return nullptr;
+    }
+    char* buffer = (char*) transferBuffer.contents + bufferOffset;
+
+    MTLOrigin origin = MTLOriginMake(0, 0, 0);
+
+    id<MTLBlitCommandEncoder> blitCmdEncoder = this->commandBuffer()->getBlitCommandEncoder();
+    const size_t rowBytes = GrCompressedRowBytes(compressionType, width);
+
+    // copy data into the buffer, skipping any trailing bytes
+    memcpy(buffer, data, dataSize);
+    [blitCmdEncoder copyFromBuffer: transferBuffer
+                      sourceOffset: bufferOffset
+                 sourceBytesPerRow: rowBytes
+               sourceBytesPerImage: dataSize
+                        sourceSize: MTLSizeMake(width, height, 1)
+                         toTexture: mtlTexture
+                  destinationSlice: 0
+                  destinationLevel: 0
+                 destinationOrigin: origin];
+#ifdef SK_BUILD_FOR_MAC
+    [transferBuffer didModifyRange: NSMakeRange(bufferOffset, dataSize)];
+#endif
+
+    return tex;
+}
+
 static id<MTLTexture> get_texture_from_backend(const GrBackendTexture& backendTex) {
     GrMtlTextureInfo textureInfo;
     if (!backendTex.getMtlTextureInfo(&textureInfo)) {
@@ -627,11 +712,11 @@
     SkUNREACHABLE;
 }
 
-bool GrMtlGpu::createTestingOnlyMtlTextureInfo(MTLPixelFormat format,
-                                               int w, int h, bool texturable,
-                                               bool renderable, GrMipMapped mipMapped,
-                                               const void* srcData, size_t srcRowBytes,
-                                               const SkColor4f* color, GrMtlTextureInfo* info) {
+bool GrMtlGpu::createMtlTextureForBackendSurface(MTLPixelFormat format,
+                                                 int w, int h, bool texturable,
+                                                 bool renderable, GrMipMapped mipMapped,
+                                                 const void* srcData, size_t srcRowBytes,
+                                                 const SkColor4f* color, GrMtlTextureInfo* info) {
     SkASSERT(texturable || renderable);
     if (!texturable) {
         SkASSERT(GrMipMapped::kNo == mipMapped);
@@ -652,11 +737,6 @@
         return false;
     }
 
-    // TODO: allow uninitialized textures to be truly uninitialized
-    if (!color) {
-        color = &SkColors::kTransparent;
-    }
-
     int mipLevelCount = 1;
     if (GrMipMapped::kYes == mipMapped) {
         mipLevelCount = SkMipMap::ComputeLevelCount(w, h) + 1;
@@ -674,26 +754,27 @@
     desc.usage |= renderable ? MTLTextureUsageRenderTarget : 0;
     id<MTLTexture> testTexture = [fDevice newTextureWithDescriptor: desc];
 
-    size_t bytesPerPixel = GrMtlBytesPerFormat(format);
+    if (!srcData && !color) {
+        info->fTexture.reset(GrRetainPtrFromId(testTexture));
 
-    if (!srcRowBytes) {
-        srcRowBytes = w * bytesPerPixel;
-#ifdef SK_BUILD_FOR_MAC
-        if (!srcData) {
-            // On MacOS, the fillBuffer command needs a range with a multiple of 4 bytes
-            srcRowBytes = ((srcRowBytes + 3) & (~3));
-        }
-#endif
+        return true;
     }
 
+    // Create the transfer buffer
+    SkImage::CompressionType compressionType;
+    bool isCompressed = GrMtlFormatToCompressionType(format, &compressionType);
+    size_t bytesPerPixel = GrMtlBytesPerFormat(format);
+
     size_t combinedBufferSize = 0;
     SkTArray<size_t> individualMipOffsets(mipLevelCount);
-    if (srcData) {
-        SkASSERT(1 == mipLevelCount);
+    if (isCompressed) {
+        // Compressed textures currently must be non-MIP mapped.
+        if (mipLevelCount != 1) {
+            return false;
+        }
+        combinedBufferSize = GrCompressedDataSize(compressionType, w, h);
         individualMipOffsets.push_back(0);
-
-        combinedBufferSize = srcRowBytes * h;
-    } else if (color) {
+    } else {
         combinedBufferSize = GrComputeTightCombinedBufferSize(bytesPerPixel, w, h,
                                                               &individualMipOffsets,
                                                               mipLevelCount);
@@ -706,9 +787,6 @@
     options |= MTLResourceStorageModeShared;
 #endif
 
-    GrPixelConfig config = mtl_format_to_pixelconfig(format);
-    SkASSERT(kUnknown_GrPixelConfig != config);
-
     id<MTLBuffer> transferBuffer = [fDevice newBufferWithLength: combinedBufferSize
                                                         options: options];
     if (nil == transferBuffer) {
@@ -717,21 +795,34 @@
 
     char* buffer = (char*) transferBuffer.contents;
 
+    // Fill buffer with data
     if (srcData) {
-        const size_t trimRowBytes = w * bytesPerPixel;
+        if (isCompressed) {
+            memcpy(buffer, srcData, combinedBufferSize);
+        } else {
+            const size_t trimRowBytes = w * bytesPerPixel;
 
-        SkASSERT(1 == mipLevelCount);
-        if (!srcRowBytes) {
-            srcRowBytes = trimRowBytes;
+            // TODO: support mipmapping
+            SkASSERT(1 == mipLevelCount);
+            if (!srcRowBytes) {
+                srcRowBytes = trimRowBytes;
+            }
+
+            // copy data into the buffer, skipping the trailing bytes
+            const char* src = (const char*) srcData;
+            SkRectMemcpy(buffer, trimRowBytes, src, srcRowBytes, trimRowBytes, h);
         }
-
-        // copy data into the buffer, skipping the trailing bytes
-        const char* src = (const char*) srcData;
-        SkRectMemcpy(buffer, trimRowBytes, src, srcRowBytes, trimRowBytes, h);
     } else if (color) {
-        GrFillInData(config, w, h, individualMipOffsets, buffer, *color);
+        if (isCompressed) {
+            GrFillInCompressedData(compressionType, w, h, buffer, *color);
+        } else {
+            GrPixelConfig config = mtl_format_to_pixelconfig(format);
+            SkASSERT(kUnknown_GrPixelConfig != config);
+            GrFillInData(config, w, h, individualMipOffsets, buffer, *color);
+        }
     }
 
+    // Transfer buffer contents to texture
     int currentWidth = w;
     int currentHeight = h;
     MTLOrigin origin = MTLOriginMake(0, 0, 0);
@@ -740,13 +831,21 @@
     id<MTLBlitCommandEncoder> blitCmdEncoder = [cmdBuffer blitCommandEncoder];
 
     for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) {
-        const size_t trimRowBytes = currentWidth * bytesPerPixel;
+        size_t trimRowBytes;
+        size_t levelSize;
+        if (isCompressed) {
+            trimRowBytes = GrCompressedRowBytes(compressionType, currentWidth);
+            levelSize = GrCompressedDataSize(compressionType, currentWidth, currentHeight);
+        } else {
+            trimRowBytes = currentWidth * bytesPerPixel;
+            levelSize = trimRowBytes*currentHeight;
+        }
 
         // TODO: can this all be done in one go?
         [blitCmdEncoder copyFromBuffer: transferBuffer
                           sourceOffset: individualMipOffsets[currentMipLevel]
                      sourceBytesPerRow: trimRowBytes
-                   sourceBytesPerImage: trimRowBytes*currentHeight
+                   sourceBytesPerImage: levelSize
                             sourceSize: MTLSizeMake(currentWidth, currentHeight, 1)
                              toTexture: testTexture
                       destinationSlice: 0
@@ -782,10 +881,10 @@
 
     const MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format);
     GrMtlTextureInfo info;
-    if (!this->createTestingOnlyMtlTextureInfo(mtlFormat,
-                                               w, h, true,
-                                               GrRenderable::kYes == renderable, mipMapped,
-                                               pixels, rowBytes, color, &info)) {
+    if (!this->createMtlTextureForBackendSurface(mtlFormat,
+                                                 w, h, true,
+                                                 GrRenderable::kYes == renderable, mipMapped,
+                                                 pixels, rowBytes, color, &info)) {
         return {};
     }
 
@@ -826,8 +925,8 @@
     }
 
     GrMtlTextureInfo info;
-    if (!this->createTestingOnlyMtlTextureInfo(format, w, h, false, true,
-                                               GrMipMapped::kNo, nullptr, 0, nullptr, &info)) {
+    if (!this->createMtlTextureForBackendSurface(format, w, h, false, true,
+                                                 GrMipMapped::kNo, nullptr, 0, nullptr, &info)) {
         return {};
     }
 
diff --git a/src/gpu/mtl/GrMtlTexture.mm b/src/gpu/mtl/GrMtlTexture.mm
index a55b066..1e94396 100644
--- a/src/gpu/mtl/GrMtlTexture.mm
+++ b/src/gpu/mtl/GrMtlTexture.mm
@@ -26,6 +26,9 @@
         , fTexture(texture) {
     SkASSERT((GrMipMapsStatus::kNotAllocated == mipMapsStatus) == (1 == texture.mipmapLevelCount));
     this->registerWithCache(budgeted);
+    if (GrMtlFormatIsCompressed(texture.pixelFormat)) {
+        this->setReadOnly();
+    }
 }
 
 GrMtlTexture::GrMtlTexture(GrMtlGpu* gpu,
diff --git a/src/gpu/mtl/GrMtlUtil.h b/src/gpu/mtl/GrMtlUtil.h
index 35325f0..e09bd5e 100644
--- a/src/gpu/mtl/GrMtlUtil.h
+++ b/src/gpu/mtl/GrMtlUtil.h
@@ -116,4 +116,14 @@
     return static_cast<MTLPixelFormat>(format.asMtlFormat());
 }
 
+/**
+ * Returns true if the format is compressed.
+ */
+bool GrMtlFormatIsCompressed(MTLPixelFormat mtlFormat);
+
+/**
+ * Maps a MTLPixelFormat into the CompressionType enum if applicable.
+ */
+bool GrMtlFormatToCompressionType(MTLPixelFormat mtlFormat,
+                                  SkImage::CompressionType* compressionType);
 #endif
diff --git a/src/gpu/mtl/GrMtlUtil.mm b/src/gpu/mtl/GrMtlUtil.mm
index 62d6290..fbe12ae 100644
--- a/src/gpu/mtl/GrMtlUtil.mm
+++ b/src/gpu/mtl/GrMtlUtil.mm
@@ -330,6 +330,30 @@
     SK_ABORT("Invalid Mtl format");
 }
 
+bool GrMtlFormatIsCompressed(MTLPixelFormat mtlFormat) {
+    switch (mtlFormat) {
+#ifdef SK_BUILD_FOR_IOS
+        case MTLPixelFormatETC2_RGB8:
+            return true;
+#endif
+        default:
+            return false;
+    }
+}
+
+bool GrMtlFormatToCompressionType(MTLPixelFormat mtlFormat,
+                                  SkImage::CompressionType* compressionType) {
+    switch (mtlFormat) {
+#ifdef SK_BUILD_FOR_IOS
+        case MTLPixelFormatETC2_RGB8:
+            *compressionType = SkImage::kETC1_CompressionType;
+            return true;
+#endif
+        default:
+            return false;
+    }
+}
+
 #if GR_TEST_UTILS
 const char* GrMtlFormatToStr(GrMTLPixelFormat mtlFormat) {
     switch (mtlFormat) {
diff --git a/src/gpu/vk/GrVkTexture.cpp b/src/gpu/vk/GrVkTexture.cpp
index 967ecc6..5d09f66 100644
--- a/src/gpu/vk/GrVkTexture.cpp
+++ b/src/gpu/vk/GrVkTexture.cpp
@@ -32,7 +32,7 @@
         , fTextureView(view) {
     SkASSERT((GrMipMapsStatus::kNotAllocated == mipMapsStatus) == (1 == info.fLevelCount));
     this->registerWithCache(budgeted);
-    if (GrPixelConfigIsCompressed(desc.fConfig)) {
+    if (GrVkFormatIsCompressed(info.fFormat)) {
         this->setReadOnly();
     }
 }