445 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			445 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
| /*
 | |
|  * 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/gpu/ProxyUtils.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 = sk_gpu_test::GetTextureImageProxy(image,
 | |
|                                                                       canvas->recordingContext());
 | |
|             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
 |