diff --git a/src/gpu/mtl/GrMtlCaps.mm b/src/gpu/mtl/GrMtlCaps.mm index 8b4e58d570..b1cd65988c 100644 --- a/src/gpu/mtl/GrMtlCaps.mm +++ b/src/gpu/mtl/GrMtlCaps.mm @@ -9,6 +9,7 @@ #include "include/core/SkRect.h" #include "include/gpu/GrBackendSurface.h" +#include "src/core/SkCompressedDataUtils.h" #include "src/gpu/GrProcessor.h" #include "src/gpu/GrProgramDesc.h" #include "src/gpu/GrProgramInfo.h" @@ -873,6 +874,13 @@ bool GrMtlCaps::onSurfaceSupportsWritePixels(const GrSurface* surface) const { bool GrMtlCaps::onAreColorTypeAndFormatCompatible(GrColorType ct, const GrBackendFormat& format) const { MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format); + + SkImage::CompressionType compression = GrMtlFormatToCompressionType(mtlFormat); + if (compression != SkImage::CompressionType::kNone) { + return ct == (SkCompressionTypeIsOpaque(compression) ? GrColorType::kRGB_888x + : GrColorType::kRGBA_8888); + } + const auto& info = this->getFormatInfo(mtlFormat); for (int i = 0; i < info.fColorTypeInfoCount; ++i) { if (info.fColorTypeInfos[i].fColorType == ct) { @@ -992,6 +1000,17 @@ GrCaps::SupportedRead GrMtlCaps::onSupportedReadPixelsColorType( GrColorType dstColorType) const { MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(srcBackendFormat); + SkImage::CompressionType compression = GrMtlFormatToCompressionType(mtlFormat); + if (compression != SkImage::CompressionType::kNone) { +#ifdef SK_BUILD_FOR_IOS + // Reading back to kRGB_888x doesn't work on Metal/iOS (skbug.com/9839) + return { GrColorType::kUnknown, 0 }; +#else + return { SkCompressionTypeIsOpaque(compression) ? GrColorType::kRGB_888x + : GrColorType::kRGBA_8888, 0 }; +#endif + } + // Metal requires the destination offset for copyFromTexture to be a multiple of the textures // pixels size. size_t offsetAlignment = GrColorTypeBytesPerPixel(srcColorType); diff --git a/src/gpu/mtl/GrMtlGpu.mm b/src/gpu/mtl/GrMtlGpu.mm index 31e9314c09..98e99c094d 100644 --- a/src/gpu/mtl/GrMtlGpu.mm +++ b/src/gpu/mtl/GrMtlGpu.mm @@ -7,6 +7,7 @@ #include "src/gpu/mtl/GrMtlGpu.h" +#include "include/private/GrTypesPriv.h" #include "src/core/SkCompressedDataUtils.h" #include "src/core/SkConvertPixels.h" #include "src/core/SkMipMap.h" @@ -594,7 +595,7 @@ sk_sp GrMtlGpu::onCreateCompressedTexture(SkISize dimensions, // TODO: can this all be done in one go? [blitCmdEncoder copyFromBuffer: transferBuffer - sourceOffset: individualMipOffsets[currentMipLevel] + sourceOffset: bufferOffset + individualMipOffsets[currentMipLevel] sourceBytesPerRow: levelRowBytes sourceBytesPerImage: levelSize sourceSize: MTLSizeMake(levelDimensions.width(), @@ -644,7 +645,8 @@ static inline void init_surface_desc(GrSurfaceDesc* surfaceDesc, id sk_sp GrMtlGpu::onWrapBackendTexture(const GrBackendTexture& backendTex, GrColorType grColorType, GrWrapOwnership, - GrWrapCacheable cacheable, GrIOType ioType) { + GrWrapCacheable cacheable, + GrIOType ioType) { id mtlTexture = get_texture_from_backend(backendTex); if (!mtlTexture) { return nullptr; @@ -657,9 +659,17 @@ sk_sp GrMtlGpu::onWrapBackendTexture(const GrBackendTexture& backendT } sk_sp GrMtlGpu::onWrapCompressedBackendTexture(const GrBackendTexture& backendTex, - GrWrapOwnership ownership, + GrWrapOwnership, GrWrapCacheable cacheable) { - return nullptr; + id mtlTexture = get_texture_from_backend(backendTex); + if (!mtlTexture) { + return nullptr; + } + + GrSurfaceDesc surfDesc; + init_surface_desc(&surfDesc, mtlTexture, GrRenderable::kNo); + + return GrMtlTexture::MakeWrappedTexture(this, surfDesc, mtlTexture, cacheable, kRead_GrIOType); } sk_sp GrMtlGpu::onWrapRenderableBackendTexture(const GrBackendTexture& backendTex, @@ -788,7 +798,7 @@ void copy_src_data(char* dst, size_t bytesPerPixel, const SkTArray& indi } } -bool GrMtlGpu::createMtlTextureForBackendSurface(MTLPixelFormat format, +bool GrMtlGpu::createMtlTextureForBackendSurface(MTLPixelFormat mtlFormat, SkISize dimensions, GrTexturable texturable, GrRenderable renderable, @@ -800,15 +810,10 @@ bool GrMtlGpu::createMtlTextureForBackendSurface(MTLPixelFormat format, SkASSERT(!data && mipMapped == GrMipMapped::kNo); } -#ifdef SK_BUILD_FOR_IOS - // Compressed formats go through onCreateCompressedBackendTexture - SkASSERT(!GrMtlFormatIsCompressed(format)); -#endif - - if (texturable == GrTexturable::kYes && !fMtlCaps->isFormatTexturable(format)) { + if (texturable == GrTexturable::kYes && !fMtlCaps->isFormatTexturable(mtlFormat)) { return false; } - if (renderable == GrRenderable::kYes && !fMtlCaps->isFormatRenderable(format, 1)) { + if (renderable == GrRenderable::kYes && !fMtlCaps->isFormatRenderable(mtlFormat, 1)) { return false; } @@ -817,7 +822,7 @@ bool GrMtlGpu::createMtlTextureForBackendSurface(MTLPixelFormat format, } MTLTextureDescriptor* desc = - [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: format + [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: mtlFormat width: dimensions.width() height: dimensions.height() mipmapped: mipMapped == GrMipMapped::kYes]; @@ -835,8 +840,6 @@ bool GrMtlGpu::createMtlTextureForBackendSurface(MTLPixelFormat format, } // Create the transfer buffer - size_t bytesPerPixel = fMtlCaps->bytesPerPixel(format); - NSUInteger options = 0; // TODO: consider other options here if (@available(macOS 10.11, iOS 9.0, *)) { #ifdef SK_BUILD_FOR_MAC @@ -851,69 +854,121 @@ bool GrMtlGpu::createMtlTextureForBackendSurface(MTLPixelFormat format, numMipLevels = SkMipMap::ComputeLevelCount(dimensions.width(), dimensions.height()) + 1; } + SkImage::CompressionType compression = GrMtlFormatToCompressionType(mtlFormat); + // Create a transfer buffer and fill with data. SkSTArray<16, size_t> individualMipOffsets; id transferBuffer; size_t transferBufferSize; - SkASSERT(data->type() != BackendTextureData::Type::kCompressed); - if (data->type() == BackendTextureData::Type::kPixmaps) { - transferBufferSize = GrComputeTightCombinedBufferSize(bytesPerPixel, dimensions, - &individualMipOffsets, numMipLevels); + if (data->type() == BackendTextureData::Type::kCompressed || + data->type() == BackendTextureData::Type::kPixmaps) { + + if (compression == SkImage::CompressionType::kNone) { + size_t bytesPerPixel = fMtlCaps->bytesPerPixel(mtlFormat); + + transferBufferSize = GrComputeTightCombinedBufferSize(bytesPerPixel, dimensions, + &individualMipOffsets, + numMipLevels); + + } else { + transferBufferSize = SkCompressedDataSize(compression, dimensions, + &individualMipOffsets, + mipMapped == GrMipMapped::kYes); + } + SkASSERT(individualMipOffsets.count() == numMipLevels); + transferBuffer = [fDevice newBufferWithLength: transferBufferSize options: options]; if (nil == transferBuffer) { return false; } char* buffer = (char*)transferBuffer.contents; - copy_src_data(buffer, bytesPerPixel, individualMipOffsets, data->pixmaps(), numMipLevels, - transferBufferSize); + + if (data->type() == BackendTextureData::Type::kPixmaps) { + size_t bytesPerPixel = fMtlCaps->bytesPerPixel(mtlFormat); + + copy_src_data(buffer, bytesPerPixel, individualMipOffsets, data->pixmaps(), + numMipLevels, transferBufferSize); + } else { + SkASSERT(data->type() == BackendTextureData::Type::kCompressed); + + memcpy(buffer, data->compressedData(), data->compressedSize()); + } } else { SkASSERT(data->type() == BackendTextureData::Type::kColor); - auto colorType = mtl_format_to_backend_tex_clear_colortype(format); - if (colorType == GrColorType::kUnknown) { - return false; + + if (compression == SkImage::CompressionType::kNone) { + auto colorType = mtl_format_to_backend_tex_clear_colortype(mtlFormat); + if (colorType == GrColorType::kUnknown) { + return false; + } + GrImageInfo ii(colorType, kUnpremul_SkAlphaType, nullptr, dimensions); + auto rb = ii.minRowBytes(); + transferBufferSize = rb*dimensions.height(); + transferBuffer = [fDevice newBufferWithLength: transferBufferSize + options: options]; + if (nil == transferBuffer) { + return false; + } + if (!GrClearImage(ii, transferBuffer.contents, rb, data->color())) { + return false; + } + // Reuse the same buffer for all levels. Should be ok since we made the row bytes tight. + individualMipOffsets.push_back_n(numMipLevels, (size_t)0); + } else { + transferBufferSize = SkCompressedDataSize(compression, dimensions, + &individualMipOffsets, + mipMapped == GrMipMapped::kYes); + SkASSERT(individualMipOffsets.count() == numMipLevels); + + transferBuffer = [fDevice newBufferWithLength: transferBufferSize + options: options]; + if (nil == transferBuffer) { + return false; + } + + char* buffer = (char*)transferBuffer.contents; + GrFillInCompressedData(compression, dimensions, mipMapped, buffer, data->color()); } - GrImageInfo ii(colorType, kUnpremul_SkAlphaType, nullptr, dimensions); - auto rb = ii.minRowBytes(); - transferBufferSize = rb*dimensions.height(); - transferBuffer = [fDevice newBufferWithLength: transferBufferSize - options: options]; - if (nil == transferBuffer) { - return false; - } - if (!GrClearImage(ii, transferBuffer.contents, rb, data->color())) { - return false; - } - // Reuse the same buffer for all levels. Should be ok since we made the row bytes tight. - individualMipOffsets.push_back_n(numMipLevels, (size_t)0); } // Transfer buffer contents to texture - int currentWidth = dimensions.width(); - int currentHeight = dimensions.height(); MTLOrigin origin = MTLOriginMake(0, 0, 0); id cmdBuffer = [fQueue commandBuffer]; id blitCmdEncoder = [cmdBuffer blitCommandEncoder]; + SkISize levelDimensions(dimensions); for (int currentMipLevel = 0; currentMipLevel < numMipLevels; currentMipLevel++) { - size_t trimRowBytes = currentWidth * bytesPerPixel; - size_t levelSize = trimRowBytes*currentHeight; + size_t levelRowBytes; + size_t levelSize; + + if (compression == SkImage::CompressionType::kNone) { + size_t bytesPerPixel = fMtlCaps->bytesPerPixel(mtlFormat); + + levelRowBytes = levelDimensions.width() * bytesPerPixel; + levelSize = levelRowBytes * levelDimensions.height(); + } else { + levelRowBytes = GrCompressedRowBytes(compression, levelDimensions.width()); + levelSize = SkCompressedDataSize(compression, levelDimensions, nullptr, + false); + } // TODO: can this all be done in one go? [blitCmdEncoder copyFromBuffer: transferBuffer sourceOffset: individualMipOffsets[currentMipLevel] - sourceBytesPerRow: trimRowBytes + sourceBytesPerRow: levelRowBytes sourceBytesPerImage: levelSize - sourceSize: MTLSizeMake(currentWidth, currentHeight, 1) + sourceSize: MTLSizeMake(levelDimensions.width(), + levelDimensions.height(), 1) toTexture: testTexture destinationSlice: 0 destinationLevel: currentMipLevel destinationOrigin: origin]; - currentWidth = SkTMax(1, currentWidth/2); - currentHeight = SkTMax(1, currentHeight/2); + levelDimensions = { SkTMax(1, levelDimensions.width() / 2), + SkTMax(1, levelDimensions.height() / 2) }; } #ifdef SK_BUILD_FOR_MAC [transferBuffer didModifyRange: NSMakeRange(0, transferBufferSize)]; @@ -925,7 +980,6 @@ bool GrMtlGpu::createMtlTextureForBackendSurface(MTLPixelFormat format, transferBuffer = nil; info->fTexture.reset(GrRetainPtrFromId(testTexture)); - return true; } @@ -935,8 +989,8 @@ GrBackendTexture GrMtlGpu::onCreateBackendTexture(SkISize dimensions, GrMipMapped mipMapped, GrProtected isProtected, const BackendTextureData* data) { - // GrGpu::createBackendTexture should've ensured these conditions const MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format); + GrMtlTextureInfo info; if (!this->createMtlTextureForBackendSurface(mtlFormat, dimensions, GrTexturable::kYes, renderable, mipMapped, &info, data)) { @@ -948,11 +1002,20 @@ GrBackendTexture GrMtlGpu::onCreateBackendTexture(SkISize dimensions, } GrBackendTexture GrMtlGpu::onCreateCompressedBackendTexture(SkISize dimensions, - const GrBackendFormat&, - GrMipMapped, - GrProtected, - const BackendTextureData*) { - return {}; + const GrBackendFormat& format, + GrMipMapped mipMapped, + GrProtected isProtected, + const BackendTextureData* data) { + const MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format); + + GrMtlTextureInfo info; + if (!this->createMtlTextureForBackendSurface(mtlFormat, dimensions, GrTexturable::kYes, + GrRenderable::kNo, mipMapped, &info, data)) { + return {}; + } + + GrBackendTexture backendTex(dimensions.width(), dimensions.height(), mipMapped, info); + return backendTex; } void GrMtlGpu::deleteBackendTexture(const GrBackendTexture& tex) { diff --git a/tests/CompressedBackendAllocationTest.cpp b/tests/CompressedBackendAllocationTest.cpp index 65a6f6a708..00d208c44f 100644 --- a/tests/CompressedBackendAllocationTest.cpp +++ b/tests/CompressedBackendAllocationTest.cpp @@ -110,6 +110,13 @@ static void check_readback(GrContext* context, sk_sp img, SkImage::CompressionType compressionType, const SkColor4f& expectedColor, skiatest::Reporter* reporter, const char* label) { +#ifdef SK_BUILD_FOR_IOS + // reading back ETC2 is broken on Metal/iOS (skbug.com/9839) + if (context->backend() == GrBackendApi::kMetal) { + return; + } +#endif + SkAutoPixmapStorage actual; SkImageInfo readBackII = SkImageInfo::Make(img->width(), img->height(), @@ -137,7 +144,6 @@ static void test_compressed_color_init(GrContext* context, GrMipMapped mipMapped) { GrBackendTexture backendTex = create(context, color, mipMapped); if (!backendTex.isValid()) { - // errors here should be reported by the test_wrapping test return; }