skia2/gm/exoticformats.cpp
Adlai Holler 302e8fb771 Downgrade SkImage to GrImageContext
We still occasionally downcast, so this is not airtight,
but it (1) allows us to know where we are downcasting and
(2) lets us move away from GrContext (and hopefully remove
it sooner than later.)

All three canaries are currently broken =( so here we go!

Bug: skia:104662
Change-Id: I84efe132574690b62ea512e194e4f9e318e9c050
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/316218
Commit-Queue: Adlai Holler <adlai@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
2020-09-14 17:01:07 +00:00

444 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/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