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();
}
}