| /* | 
 |  * Copyright 2020 Google Inc. | 
 |  * | 
 |  * Use of this source code is governed by a BSD-style license that can be | 
 |  * found in the LICENSE file. | 
 |  */ | 
 |  | 
 | #include "gm/gm.h" | 
 | #include "include/core/SkCanvas.h" | 
 | #include "include/core/SkImage.h" | 
 | #include "include/core/SkStream.h" | 
 | #include "include/gpu/GrDirectContext.h" | 
 | #include "include/gpu/GrRecordingContext.h" | 
 | #include "src/core/SkCompressedDataUtils.h" | 
 | #include "src/core/SkMipmap.h" | 
 | #include "src/gpu/GrImageContextPriv.h" | 
 | #include "src/gpu/GrRecordingContextPriv.h" | 
 | #include "src/gpu/gl/GrGLDefines.h" | 
 | #include "src/image/SkImage_Base.h" | 
 | #include "src/image/SkImage_GpuBase.h" | 
 |  | 
 | #include "tools/Resources.h" | 
 |  | 
 | //------------------------------------------------------------------------------------------------- | 
 | struct ImageInfo { | 
 |     SkISize                  fDim; | 
 |     GrMipmapped              fMipmapped; | 
 |     SkImage::CompressionType fCompressionType; | 
 | }; | 
 |  | 
 | /* | 
 |  * Get an int from a buffer | 
 |  * This method is unsafe, the caller is responsible for performing a check | 
 |  */ | 
 | static inline uint32_t get_uint(uint8_t* buffer, uint32_t i) { | 
 |     uint32_t result; | 
 |     memcpy(&result, &(buffer[i]), 4); | 
 |     return result; | 
 | } | 
 |  | 
 | // This KTX loader is barely sufficient to load the specific files this GM requires. Use | 
 | // at your own peril. | 
 | static sk_sp<SkData> load_ktx(const char* filename, ImageInfo* imageInfo) { | 
 |     SkFILEStream input(filename); | 
 |     if (!input.isValid()) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     constexpr int kKTXIdentifierSize = 12; | 
 |     constexpr int kKTXHeaderSize = kKTXIdentifierSize + 13 * sizeof(uint32_t); | 
 |     uint8_t header[kKTXHeaderSize]; | 
 |  | 
 |     if (input.read(header, kKTXHeaderSize) != kKTXHeaderSize) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     static const uint8_t kExpectedIdentifier[kKTXIdentifierSize] = { | 
 |         0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A | 
 |     }; | 
 |  | 
 |     if (0 != memcmp(header, kExpectedIdentifier, kKTXIdentifierSize)) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     uint32_t endianness = get_uint(header, 12); | 
 |     if (endianness != 0x04030201) { | 
 |         // TODO: need to swap rest of header and, if glTypeSize is > 1, all | 
 |         // the texture data. | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     uint32_t glType = get_uint(header, 16); | 
 |     SkDEBUGCODE(uint32_t glTypeSize = get_uint(header, 20);) | 
 |     uint32_t glFormat = get_uint(header, 24); | 
 |     uint32_t glInternalFormat = get_uint(header, 28); | 
 |     //uint32_t glBaseInternalFormat = get_uint(header, 32); | 
 |     uint32_t pixelWidth = get_uint(header, 36); | 
 |     uint32_t pixelHeight = get_uint(header, 40); | 
 |     uint32_t pixelDepth = get_uint(header, 44); | 
 |     //uint32_t numberOfArrayElements = get_uint(header, 48); | 
 |     uint32_t numberOfFaces = get_uint(header, 52); | 
 |     int numberOfMipmapLevels = get_uint(header, 56); | 
 |     uint32_t bytesOfKeyValueData = get_uint(header, 60); | 
 |  | 
 |     if (glType != 0 || glFormat != 0) {  // only care about compressed data for now | 
 |         return nullptr; | 
 |     } | 
 |     SkASSERT(glTypeSize == 1); // required for compressed data | 
 |  | 
 |     // We only handle these four formats right now | 
 |     switch (glInternalFormat) { | 
 |         case GR_GL_COMPRESSED_ETC1_RGB8: | 
 |         case GR_GL_COMPRESSED_RGB8_ETC2: | 
 |             imageInfo->fCompressionType = SkImage::CompressionType::kETC2_RGB8_UNORM; | 
 |             break; | 
 |         case GR_GL_COMPRESSED_RGB_S3TC_DXT1_EXT: | 
 |             imageInfo->fCompressionType = SkImage::CompressionType::kBC1_RGB8_UNORM; | 
 |             break; | 
 |         case GR_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: | 
 |             imageInfo->fCompressionType = SkImage::CompressionType::kBC1_RGBA8_UNORM; | 
 |             break; | 
 |         default: | 
 |             return nullptr; | 
 |     } | 
 |  | 
 |     imageInfo->fDim.fWidth = pixelWidth; | 
 |     imageInfo->fDim.fHeight = pixelHeight; | 
 |  | 
 |     if (pixelDepth != 0) { | 
 |         return nullptr; // pixel depth is always zero for 2D textures | 
 |     } | 
 |  | 
 |     if (numberOfFaces != 1) { | 
 |         return nullptr; // we don't support cube maps right now | 
 |     } | 
 |  | 
 |     if (numberOfMipmapLevels == 1) { | 
 |         imageInfo->fMipmapped = GrMipmapped::kNo; | 
 |     } else { | 
 |         int numRequiredMipLevels = SkMipmap::ComputeLevelCount(pixelWidth, pixelHeight)+1; | 
 |         if (numberOfMipmapLevels != numRequiredMipLevels) { | 
 |             return nullptr; | 
 |         } | 
 |         imageInfo->fMipmapped = GrMipmapped::kYes; | 
 |     } | 
 |  | 
 |     if (bytesOfKeyValueData != 0) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     SkTArray<size_t> individualMipOffsets(numberOfMipmapLevels); | 
 |  | 
 |     size_t dataSize = SkCompressedDataSize(imageInfo->fCompressionType, | 
 |                                            { (int) pixelWidth, (int) pixelHeight }, | 
 |                                            &individualMipOffsets, | 
 |                                            imageInfo->fMipmapped == GrMipmapped::kYes); | 
 |     SkASSERT(individualMipOffsets.size() == (size_t) numberOfMipmapLevels); | 
 |  | 
 |     sk_sp<SkData> data = SkData::MakeUninitialized(dataSize); | 
 |  | 
 |     uint8_t* dest = (uint8_t*) data->writable_data(); | 
 |  | 
 |     size_t offset = 0; | 
 |     for (int i = 0; i < numberOfMipmapLevels; ++i) { | 
 |         uint32_t imageSize; | 
 |  | 
 |         if (input.read(&imageSize, 4) != 4) { | 
 |             return nullptr; | 
 |         } | 
 |  | 
 |         SkASSERT(offset + imageSize <= dataSize); | 
 |         SkASSERT(offset == individualMipOffsets[i]); | 
 |  | 
 |         if (input.read(&dest[offset], imageSize) != imageSize) { | 
 |             return nullptr; | 
 |         } | 
 |  | 
 |         offset += imageSize; | 
 |     } | 
 |  | 
 |     return data; | 
 | } | 
 |  | 
 | //------------------------------------------------------------------------------------------------- | 
 | typedef uint32_t DWORD; | 
 |  | 
 | // Values for the DDS_PIXELFORMAT 'dwFlags' field | 
 | constexpr unsigned int kDDPF_FOURCC      = 0x4; | 
 |  | 
 | struct DDS_PIXELFORMAT { | 
 |     DWORD dwSize; | 
 |     DWORD dwFlags; | 
 |     DWORD dwFourCC; | 
 |     DWORD dwRGBBitCount; | 
 |     DWORD dwRBitMask; | 
 |     DWORD dwGBitMask; | 
 |     DWORD dwBBitMask; | 
 |     DWORD dwABitMask; | 
 | }; | 
 |  | 
 | // Values for the DDS_HEADER 'dwFlags' field | 
 | constexpr unsigned int kDDSD_CAPS        = 0x1;        // required | 
 | constexpr unsigned int kDDSD_HEIGHT      = 0x2;        // required | 
 | constexpr unsigned int kDDSD_WIDTH       = 0x4;        // required | 
 | constexpr unsigned int kDDSD_PITCH       = 0x8; | 
 | constexpr unsigned int kDDSD_PIXELFORMAT = 0x001000;   // required | 
 | constexpr unsigned int kDDSD_MIPMAPCOUNT = 0x020000; | 
 | constexpr unsigned int kDDSD_LINEARSIZE  = 0x080000; | 
 | constexpr unsigned int kDDSD_DEPTH       = 0x800000; | 
 |  | 
 | constexpr unsigned int kDDSD_REQUIRED = kDDSD_CAPS | kDDSD_HEIGHT | kDDSD_WIDTH | kDDSD_PIXELFORMAT; | 
 |  | 
 | typedef struct { | 
 |     DWORD           dwSize; | 
 |     DWORD           dwFlags; | 
 |     DWORD           dwHeight; | 
 |     DWORD           dwWidth; | 
 |     DWORD           dwPitchOrLinearSize; | 
 |     DWORD           dwDepth; | 
 |     DWORD           dwMipMapCount; | 
 |     DWORD           dwReserved1[11]; | 
 |     DDS_PIXELFORMAT ddspf; | 
 |     DWORD           dwCaps; | 
 |     DWORD           dwCaps2; | 
 |     DWORD           dwCaps3; | 
 |     DWORD           dwCaps4; | 
 |     DWORD           dwReserved2; | 
 | } DDS_HEADER; | 
 |  | 
 | // This DDS loader is barely sufficient to load the specific files this GM requires. Use | 
 | // at your own peril. | 
 | static sk_sp<SkData> load_dds(const char* filename, ImageInfo* imageInfo) { | 
 |     SkFILEStream input(filename); | 
 |     if (!input.isValid()) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     constexpr uint32_t kMagic = 0x20534444; | 
 |     uint32_t magic; | 
 |  | 
 |     if (input.read(&magic, 4) != 4) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     if (magic != kMagic) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     constexpr size_t kDDSHeaderSize = sizeof(DDS_HEADER); | 
 |     static_assert(kDDSHeaderSize == 124); | 
 |     constexpr size_t kDDSPixelFormatSize = sizeof(DDS_PIXELFORMAT); | 
 |     static_assert(kDDSPixelFormatSize == 32); | 
 |  | 
 |     DDS_HEADER header; | 
 |  | 
 |     if (input.read(&header, kDDSHeaderSize) != kDDSHeaderSize) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     if (header.dwSize != kDDSHeaderSize || | 
 |         header.ddspf.dwSize != kDDSPixelFormatSize) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     if ((header.dwFlags & kDDSD_REQUIRED) != kDDSD_REQUIRED) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     if (header.dwFlags & (kDDSD_PITCH | kDDSD_LINEARSIZE | kDDSD_DEPTH)) { | 
 |         // TODO: support these features | 
 |     } | 
 |  | 
 |     imageInfo->fDim.fWidth = header.dwWidth; | 
 |     imageInfo->fDim.fHeight = header.dwHeight; | 
 |  | 
 |     int numberOfMipmapLevels = 1; | 
 |     if (header.dwFlags & kDDSD_MIPMAPCOUNT) { | 
 |         if (header.dwMipMapCount == 1) { | 
 |             imageInfo->fMipmapped = GrMipmapped::kNo; | 
 |         } else { | 
 |             int numRequiredLevels = SkMipmap::ComputeLevelCount(header.dwWidth, header.dwHeight)+1; | 
 |             if (header.dwMipMapCount != (unsigned) numRequiredLevels) { | 
 |                 return nullptr; | 
 |             } | 
 |             imageInfo->fMipmapped = GrMipmapped::kYes; | 
 |             numberOfMipmapLevels = numRequiredLevels; | 
 |         } | 
 |     } else { | 
 |         imageInfo->fMipmapped = GrMipmapped::kNo; | 
 |     } | 
 |  | 
 |     if (!(header.ddspf.dwFlags & kDDPF_FOURCC)) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     // We only handle these one format right now | 
 |     switch (header.ddspf.dwFourCC) { | 
 |         case 0x31545844: // DXT1 | 
 |             imageInfo->fCompressionType = SkImage::CompressionType::kBC1_RGB8_UNORM; | 
 |             break; | 
 |         default: | 
 |             return nullptr; | 
 |     } | 
 |  | 
 |     SkTArray<size_t> individualMipOffsets(numberOfMipmapLevels); | 
 |  | 
 |     size_t dataSize = SkCompressedDataSize(imageInfo->fCompressionType, | 
 |                                            { (int) header.dwWidth, (int) header.dwHeight }, | 
 |                                            &individualMipOffsets, | 
 |                                            imageInfo->fMipmapped == GrMipmapped::kYes); | 
 |     SkASSERT(individualMipOffsets.size() == (size_t) numberOfMipmapLevels); | 
 |  | 
 |     sk_sp<SkData> data = SkData::MakeUninitialized(dataSize); | 
 |  | 
 |     uint8_t* dest = (uint8_t*) data->writable_data(); | 
 |  | 
 |     size_t amountRead = input.read(dest, dataSize); | 
 |     if (amountRead != dataSize) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     return data; | 
 | } | 
 |  | 
 | //------------------------------------------------------------------------------------------------- | 
 | static sk_sp<SkImage> data_to_img(GrDirectContext *direct, sk_sp<SkData> data, | 
 |                                   const ImageInfo& info) { | 
 |     if (direct) { | 
 |         return SkImage::MakeTextureFromCompressed(direct, std::move(data), | 
 |                                                   info.fDim.fWidth, | 
 |                                                   info.fDim.fHeight, | 
 |                                                   info.fCompressionType, | 
 |                                                   info.fMipmapped); | 
 |     } else { | 
 |         return SkImage::MakeRasterFromCompressed(std::move(data), | 
 |                                                  info.fDim.fWidth, | 
 |                                                  info.fDim.fHeight, | 
 |                                                  info.fCompressionType); | 
 |     } | 
 | } | 
 |  | 
 | namespace skiagm { | 
 |  | 
 | // This GM exercises our handling of some of the more exotic formats using externally | 
 | // generated content. Right now it only tests ETC1 and BC1. | 
 | class ExoticFormatsGM : public GM { | 
 | public: | 
 |     ExoticFormatsGM() { | 
 |         this->setBGColor(SK_ColorBLACK); | 
 |     } | 
 |  | 
 | protected: | 
 |     SkString onShortName() override { | 
 |         return SkString("exoticformats"); | 
 |     } | 
 |  | 
 |     SkISize onISize() override { | 
 |         return SkISize::Make(2*kImgWidthHeight + 3 * kPad, kImgWidthHeight + 2 * kPad); | 
 |     } | 
 |  | 
 |     bool loadImages(GrDirectContext *direct) { | 
 |         SkASSERT(!fETC1Image && !fBC1Image); | 
 |  | 
 |         { | 
 |             ImageInfo info; | 
 |             sk_sp<SkData> data = load_ktx(GetResourcePath("images/flower-etc1.ktx").c_str(), &info); | 
 |             if (data) { | 
 |                 SkASSERT(info.fDim.equals(kImgWidthHeight, kImgWidthHeight)); | 
 |                 SkASSERT(info.fMipmapped == GrMipmapped::kNo); | 
 |                 SkASSERT(info.fCompressionType == SkImage::CompressionType::kETC2_RGB8_UNORM); | 
 |  | 
 |                 fETC1Image = data_to_img(direct, std::move(data), info); | 
 |             } else { | 
 |                 SkDebugf("failed to load flower-etc1.ktx\n"); | 
 |                 return false; | 
 |             } | 
 |         } | 
 |  | 
 |         { | 
 |             ImageInfo info; | 
 |             sk_sp<SkData> data = load_dds(GetResourcePath("images/flower-bc1.dds").c_str(), &info); | 
 |             if (data) { | 
 |                 SkASSERT(info.fDim.equals(kImgWidthHeight, kImgWidthHeight)); | 
 |                 SkASSERT(info.fMipmapped == GrMipmapped::kNo); | 
 |                 SkASSERT(info.fCompressionType == SkImage::CompressionType::kBC1_RGB8_UNORM); | 
 |  | 
 |                 fBC1Image = data_to_img(direct, std::move(data), info); | 
 |             } else { | 
 |                 SkDebugf("failed to load flower-bc1.dds\n"); | 
 |                 return false; | 
 |             } | 
 |         } | 
 |  | 
 |         return true; | 
 |     } | 
 |  | 
 |     void drawImage(SkCanvas* canvas, SkImage* image, int x, int y) { | 
 |         if (!image) { | 
 |             return; | 
 |         } | 
 |  | 
 |         bool isCompressed = false; | 
 |         if (image->isTextureBacked()) { | 
 |             const GrCaps* caps = as_IB(image)->context()->priv().caps(); | 
 |  | 
 |             GrTextureProxy* proxy = as_IB(image)->peekProxy(); | 
 |             isCompressed = caps->isFormatCompressed(proxy->backendFormat()); | 
 |         } | 
 |  | 
 |         canvas->drawImage(image, x, y); | 
 |  | 
 |         if (!isCompressed) { | 
 |             // Make it obvious which drawImages used decompressed images | 
 |             SkRect r = SkRect::MakeXYWH(x, y, kImgWidthHeight, kImgWidthHeight); | 
 |             SkPaint paint; | 
 |             paint.setColor(SK_ColorRED); | 
 |             paint.setStyle(SkPaint::kStroke_Style); | 
 |             paint.setStrokeWidth(2.0f); | 
 |             canvas->drawRect(r, paint); | 
 |         } | 
 |     } | 
 |  | 
 |     DrawResult onGpuSetup(GrDirectContext* dContext, SkString* errorMsg) override { | 
 |         if (dContext && dContext->abandoned()) { | 
 |             // This isn't a GpuGM so a null 'context' is okay but an abandoned context | 
 |             // if forbidden. | 
 |             return DrawResult::kSkip; | 
 |         } | 
 |  | 
 |         if (!this->loadImages(dContext)) { | 
 |             *errorMsg = "Failed to create images."; | 
 |             return DrawResult::kFail; | 
 |         } | 
 |  | 
 |         return DrawResult::kOk; | 
 |     } | 
 |  | 
 |     void onGpuTeardown() override { | 
 |         fETC1Image = nullptr; | 
 |         fBC1Image = nullptr; | 
 |     } | 
 |  | 
 |     void onDraw(SkCanvas* canvas) override { | 
 |         SkASSERT(fETC1Image && fBC1Image); | 
 |  | 
 |         this->drawImage(canvas, fETC1Image.get(), kPad, kPad); | 
 |         this->drawImage(canvas, fBC1Image.get(), kImgWidthHeight + 2 * kPad, kPad); | 
 |     } | 
 |  | 
 | private: | 
 |     static const int kImgWidthHeight = 128; | 
 |     static const int kPad = 4; | 
 |  | 
 |     sk_sp<SkImage> fETC1Image; | 
 |     sk_sp<SkImage> fBC1Image; | 
 |  | 
 |     using INHERITED = GM; | 
 | }; | 
 |  | 
 | ////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | DEF_GM(return new ExoticFormatsGM;) | 
 | }  // namespace skiagm |