From 427da6fcbbc6a161e0ca48c83f34295ec1147073 Mon Sep 17 00:00:00 2001 From: Leon Scroggins Date: Fri, 16 Dec 2016 13:51:59 +0000 Subject: [PATCH] Revert "Remove all KTX support" This reverts commit ada12ab0557a1f540e775288e2ce44f64fcd8c7e. Reason for revert: Google3 needs it: https://test.corp.google.com/ui#id=OCL:142184832:BASE:142184975:1481839118985:32fde8ef Original change's description: > Remove all KTX support > > It is untested and unused. > > Change-Id: I010ff4ad942738f362d42a99af4edbbb1cb0cd71 > Reviewed-on: https://skia-review.googlesource.com/6142 > Commit-Queue: Leon Scroggins > Reviewed-by: Mike Klein > Reviewed-by: Robert Phillips > TBR=mtklein@chromium.org,mtklein@google.com,robertphillips@google.com,scroggo@google.com NOPRESUBMIT=true NOTREECHECKS=true NOTRY=true Change-Id: I1ea2f9487eb2212efbfcc514122792b70c9e8737 Reviewed-on: https://skia-review.googlesource.com/6181 Commit-Queue: Leon Scroggins Reviewed-by: Leon Scroggins --- BUILD.gn | 2 + gyp/gpu.gyp | 1 + gyp/ktx.gyp | 34 +++ public.bzl | 3 + src/gpu/SkGr.cpp | 17 ++ third_party/ktx/ktx.cpp | 565 ++++++++++++++++++++++++++++++++++++++++++ third_party/ktx/ktx.h | 145 +++++++++++ tools/flags/SkCommonFlags.cpp | 4 +- 8 files changed, 769 insertions(+), 2 deletions(-) create mode 100644 gyp/ktx.gyp create mode 100644 third_party/ktx/ktx.cpp create mode 100644 third_party/ktx/ktx.h diff --git a/BUILD.gn b/BUILD.gn index 67fe9f5..a048092 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -110,6 +110,7 @@ config("skia_private") { "src/utils/win", "third_party/etc1", "third_party/gif", + "third_party/ktx", ] defines = [ @@ -589,6 +590,7 @@ component("skia") { "src/utils/mac/SkStream_mac.cpp", "third_party/etc1/etc1.cpp", "third_party/gif/SkGifImageReader.cpp", + "third_party/ktx/ktx.cpp", ] libs = [] diff --git a/gyp/gpu.gyp b/gyp/gpu.gyp index 634a458..8b50fda 100644 --- a/gyp/gpu.gyp +++ b/gyp/gpu.gyp @@ -80,6 +80,7 @@ 'core.gyp:*', 'utils.gyp:utils', 'etc1.gyp:libetc1', + 'ktx.gyp:libSkKTX', 'sksl.gyp:sksl', ], 'include_dirs': [ diff --git a/gyp/ktx.gyp b/gyp/ktx.gyp new file mode 100644 index 0000000..e01ef1f --- /dev/null +++ b/gyp/ktx.gyp @@ -0,0 +1,34 @@ +# Copyright 2015 Google Inc. +# +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'variables': { + 'skia_warnings_as_errors': 0, + }, + 'targets': [ + { + 'target_name': 'libSkKTX', + 'type': 'static_library', + 'include_dirs' : [ + '../third_party/ktx', + '../include/gpu', + '../include/private', + '../src/core', + '../src/gpu', + '../src/utils', + ], + 'sources': [ + '../third_party/ktx/ktx.cpp', + ], + 'dependencies': [ + 'core.gyp:*', + 'etc1.gyp:libetc1', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '../third_party/ktx', + ], + }, + }], +} diff --git a/public.bzl b/public.bzl index 33542ea..5c2e09f 100644 --- a/public.bzl +++ b/public.bzl @@ -86,6 +86,8 @@ BASE_SRCS_ALL = struct( "third_party/etc1/*.h", "third_party/gif/*.cpp", "third_party/gif/*.h", + "third_party/ktx/*.cpp", + "third_party/ktx/*.h", ], # Note: PRIVATE_HDRS_INCLUDE_LIST is excluded from BASE_SRCS_ALL here # because they are required to appear in srcs for some rules but hdrs for @@ -383,6 +385,7 @@ INCLUDES = [ "src/utils", "third_party/etc1", "third_party/gif", + "third_party/ktx", ] ################################################################################ diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp index d1cecde..4147855 100644 --- a/src/gpu/SkGr.cpp +++ b/src/gpu/SkGr.cpp @@ -40,6 +40,7 @@ #include "effects/GrYUVEffect.h" #ifndef SK_IGNORE_ETC1_SUPPORT +# include "ktx.h" # include "etc1.h" #endif @@ -87,6 +88,22 @@ GrPixelConfig GrIsCompressedTextureDataSupported(GrContext* ctx, SkData* data, *outStartOfDataToUpload = bytes + ETC_PKM_HEADER_SIZE; return kETC1_GrPixelConfig; + } else if (SkKTXFile::is_ktx(bytes, data->size())) { + SkKTXFile ktx(data); + + // Is it actually an ETC1 texture? + if (!ktx.isCompressedFormat(SkTextureCompressor::kETC1_Format)) { + return kUnknown_GrPixelConfig; + } + + // Does the data match the dimensions of the bitmap? If not, + // then we don't know how to scale the image to match it... + if (ktx.width() != expectedW || ktx.height() != expectedH) { + return kUnknown_GrPixelConfig; + } + + *outStartOfDataToUpload = ktx.pixelData(); + return kETC1_GrPixelConfig; } #endif return kUnknown_GrPixelConfig; diff --git a/third_party/ktx/ktx.cpp b/third_party/ktx/ktx.cpp new file mode 100644 index 0000000..ba3ba1f --- /dev/null +++ b/third_party/ktx/ktx.cpp @@ -0,0 +1,565 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "ktx.h" +#include "SkBitmap.h" +#include "SkStream.h" +#include "SkEndian.h" + +#include "gl/GrGLDefines.h" +#include "GrConfig.h" + +#include "etc1.h" + +static inline uint32_t compressed_fmt_to_gl_define(SkTextureCompressor::Format fmt) { + static const uint32_t kGLDefineMap[SkTextureCompressor::kFormatCnt] = { + GR_GL_COMPRESSED_LUMINANCE_LATC1, // kLATC_Format + GR_GL_COMPRESSED_R11_EAC, // kR11_EAC_Format + GR_GL_COMPRESSED_ETC1_RGB8, // kETC1_Format + GR_GL_COMPRESSED_RGBA_ASTC_4x4, // kASTC_4x4_Format + GR_GL_COMPRESSED_RGBA_ASTC_5x4, // kASTC_5x4_Format + GR_GL_COMPRESSED_RGBA_ASTC_5x5, // kASTC_5x5_Format + GR_GL_COMPRESSED_RGBA_ASTC_6x5, // kASTC_6x5_Format + GR_GL_COMPRESSED_RGBA_ASTC_6x6, // kASTC_6x6_Format + GR_GL_COMPRESSED_RGBA_ASTC_8x5, // kASTC_8x5_Format + GR_GL_COMPRESSED_RGBA_ASTC_8x6, // kASTC_8x6_Format + GR_GL_COMPRESSED_RGBA_ASTC_8x8, // kASTC_8x8_Format + GR_GL_COMPRESSED_RGBA_ASTC_10x5, // kASTC_10x5_Format + GR_GL_COMPRESSED_RGBA_ASTC_10x6, // kASTC_10x6_Format + GR_GL_COMPRESSED_RGBA_ASTC_10x8, // kASTC_10x8_Format + GR_GL_COMPRESSED_RGBA_ASTC_10x10, // kASTC_10x10_Format + GR_GL_COMPRESSED_RGBA_ASTC_12x10, // kASTC_12x10_Format + GR_GL_COMPRESSED_RGBA_ASTC_12x12, // kASTC_12x12_Format + }; + + GR_STATIC_ASSERT(0 == SkTextureCompressor::kLATC_Format); + GR_STATIC_ASSERT(1 == SkTextureCompressor::kR11_EAC_Format); + GR_STATIC_ASSERT(2 == SkTextureCompressor::kETC1_Format); + GR_STATIC_ASSERT(3 == SkTextureCompressor::kASTC_4x4_Format); + GR_STATIC_ASSERT(4 == SkTextureCompressor::kASTC_5x4_Format); + GR_STATIC_ASSERT(5 == SkTextureCompressor::kASTC_5x5_Format); + GR_STATIC_ASSERT(6 == SkTextureCompressor::kASTC_6x5_Format); + GR_STATIC_ASSERT(7 == SkTextureCompressor::kASTC_6x6_Format); + GR_STATIC_ASSERT(8 == SkTextureCompressor::kASTC_8x5_Format); + GR_STATIC_ASSERT(9 == SkTextureCompressor::kASTC_8x6_Format); + GR_STATIC_ASSERT(10 == SkTextureCompressor::kASTC_8x8_Format); + GR_STATIC_ASSERT(11 == SkTextureCompressor::kASTC_10x5_Format); + GR_STATIC_ASSERT(12 == SkTextureCompressor::kASTC_10x6_Format); + GR_STATIC_ASSERT(13 == SkTextureCompressor::kASTC_10x8_Format); + GR_STATIC_ASSERT(14 == SkTextureCompressor::kASTC_10x10_Format); + GR_STATIC_ASSERT(15 == SkTextureCompressor::kASTC_12x10_Format); + GR_STATIC_ASSERT(16 == SkTextureCompressor::kASTC_12x12_Format); + GR_STATIC_ASSERT(SK_ARRAY_COUNT(kGLDefineMap) == SkTextureCompressor::kFormatCnt); + + return kGLDefineMap[fmt]; +} + +#define KTX_FILE_IDENTIFIER_SIZE 12 +static const uint8_t KTX_FILE_IDENTIFIER[KTX_FILE_IDENTIFIER_SIZE] = { + 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A +}; + +static const uint32_t kKTX_ENDIANNESS_CODE = 0x04030201; + +bool SkKTXFile::KeyValue::readKeyAndValue(const uint8_t* data) { + const char *key = reinterpret_cast(data); + const char *value = key; + + size_t bytesRead = 0; + while (*value != '\0' && bytesRead < this->fDataSz) { + ++bytesRead; + ++value; + } + + // Error of some sort.. + if (bytesRead >= this->fDataSz) { + return false; + } + + // Read the zero terminator + ++bytesRead; + ++value; + + size_t bytesLeft = this->fDataSz - bytesRead; + + // We ignore the null terminator when setting the string value. + this->fKey.set(key, bytesRead - 1); + if (bytesLeft > 0) { + this->fValue.set(value, bytesLeft - 1); + } else { + return false; + } + + return true; +} + +bool SkKTXFile::KeyValue::writeKeyAndValueForKTX(SkWStream* strm) { + size_t bytesWritten = 0; + if (!strm->write(&(this->fDataSz), 4)) { + return false; + } + + bytesWritten += 4; + + // Here we know that C-strings must end with a null terminating + // character, so when we get a c_str(), it will have as many + // bytes of data as size() returns plus a zero, so we just + // write size() + 1 bytes into the stream. + + size_t keySize = this->fKey.size() + 1; + if (!strm->write(this->fKey.c_str(), keySize)) { + return false; + } + + bytesWritten += keySize; + + size_t valueSize = this->fValue.size() + 1; + if (!strm->write(this->fValue.c_str(), valueSize)) { + return false; + } + + bytesWritten += valueSize; + + size_t bytesWrittenPadFour = (bytesWritten + 3) & ~3; + uint8_t nullBuf[4] = { 0, 0, 0, 0 }; + + size_t padding = bytesWrittenPadFour - bytesWritten; + SkASSERT(padding < 4); + + return strm->write(nullBuf, padding); +} + +uint32_t SkKTXFile::readInt(const uint8_t** buf, size_t* bytesLeft) const { + SkASSERT(buf && bytesLeft); + + uint32_t result; + + if (*bytesLeft < 4) { + SkASSERT(false); + return 0; + } + + memcpy(&result, *buf, 4); + *buf += 4; + + if (fSwapBytes) { + SkEndianSwap32(result); + } + + *bytesLeft -= 4; + + return result; +} + +SkString SkKTXFile::getValueForKey(const SkString& key) const { + const KeyValue *begin = this->fKeyValuePairs.begin(); + const KeyValue *end = this->fKeyValuePairs.end(); + for (const KeyValue *kv = begin; kv != end; ++kv) { + if (kv->key() == key) { + return kv->value(); + } + } + return SkString(); +} + +bool SkKTXFile::isCompressedFormat(SkTextureCompressor::Format fmt) const { + if (!this->valid()) { + return false; + } + + // This has many aliases + bool isFmt = false; + if (fmt == SkTextureCompressor::kLATC_Format) { + isFmt = GR_GL_COMPRESSED_RED_RGTC1 == fHeader.fGLInternalFormat || + GR_GL_COMPRESSED_3DC_X == fHeader.fGLInternalFormat; + } + + return isFmt || compressed_fmt_to_gl_define(fmt) == fHeader.fGLInternalFormat; +} + +bool SkKTXFile::isRGBA8() const { + return this->valid() && GR_GL_RGBA8 == fHeader.fGLInternalFormat; +} + +bool SkKTXFile::isRGB8() const { + return this->valid() && GR_GL_RGB8 == fHeader.fGLInternalFormat; +} + +bool SkKTXFile::readKTXFile(const uint8_t* data, size_t dataLen) { + const uint8_t *buf = data; + size_t bytesLeft = dataLen; + + // Make sure original KTX header is there... this should have been checked + // already by a call to is_ktx() + SkASSERT(bytesLeft > KTX_FILE_IDENTIFIER_SIZE); + SkASSERT(0 == memcmp(KTX_FILE_IDENTIFIER, buf, KTX_FILE_IDENTIFIER_SIZE)); + buf += KTX_FILE_IDENTIFIER_SIZE; + bytesLeft -= KTX_FILE_IDENTIFIER_SIZE; + + // Read header, but first make sure that we have the proper space: we need + // two 32-bit ints: 1 for endianness, and another for the mandatory image + // size after the header. + if (bytesLeft < 8 + sizeof(Header)) { + return false; + } + + uint32_t endianness = this->readInt(&buf, &bytesLeft); + fSwapBytes = kKTX_ENDIANNESS_CODE != endianness; + + // Read header values + fHeader.fGLType = this->readInt(&buf, &bytesLeft); + fHeader.fGLTypeSize = this->readInt(&buf, &bytesLeft); + fHeader.fGLFormat = this->readInt(&buf, &bytesLeft); + fHeader.fGLInternalFormat = this->readInt(&buf, &bytesLeft); + fHeader.fGLBaseInternalFormat = this->readInt(&buf, &bytesLeft); + fHeader.fPixelWidth = this->readInt(&buf, &bytesLeft); + fHeader.fPixelHeight = this->readInt(&buf, &bytesLeft); + fHeader.fPixelDepth = this->readInt(&buf, &bytesLeft); + fHeader.fNumberOfArrayElements = this->readInt(&buf, &bytesLeft); + fHeader.fNumberOfFaces = this->readInt(&buf, &bytesLeft); + fHeader.fNumberOfMipmapLevels = this->readInt(&buf, &bytesLeft); + fHeader.fBytesOfKeyValueData = this->readInt(&buf, &bytesLeft); + + // Check for things that we understand... + { + // First, we only support compressed formats and single byte + // representations at the moment. If the internal format is + // compressed, the the GLType field in the header must be zero. + // In the future, we may support additional data types (such + // as GL_UNSIGNED_SHORT_5_6_5) + if (fHeader.fGLType != 0 && fHeader.fGLType != GR_GL_UNSIGNED_BYTE) { + return false; + } + + // This means that for well-formatted KTX files, the glTypeSize + // field must be one... + if (fHeader.fGLTypeSize != 1) { + return false; + } + + // We don't support 3D textures. + if (fHeader.fPixelDepth > 1) { + return false; + } + + // We don't support texture arrays + if (fHeader.fNumberOfArrayElements > 1) { + return false; + } + + // We don't support cube maps + if (fHeader.fNumberOfFaces > 1) { + return false; + } + + // We don't support width and/or height <= 0 + if (fHeader.fPixelWidth <= 0 || fHeader.fPixelHeight <= 0) { + return false; + } + } + + // Make sure that we have enough bytes left for the key/value + // data according to what was said in the header. + if (bytesLeft < fHeader.fBytesOfKeyValueData) { + return false; + } + + // Next read the key value pairs + size_t keyValueBytesRead = 0; + while (keyValueBytesRead < fHeader.fBytesOfKeyValueData) { + uint32_t keyValueBytes = this->readInt(&buf, &bytesLeft); + keyValueBytesRead += 4; + + if (keyValueBytes > bytesLeft) { + return false; + } + + KeyValue kv(keyValueBytes); + if (!kv.readKeyAndValue(buf)) { + return false; + } + + fKeyValuePairs.push_back(kv); + + uint32_t keyValueBytesPadded = (keyValueBytes + 3) & ~3; + buf += keyValueBytesPadded; + keyValueBytesRead += keyValueBytesPadded; + bytesLeft -= keyValueBytesPadded; + } + + // Read the pixel data... + int mipmaps = SkMax32(fHeader.fNumberOfMipmapLevels, 1); + SkASSERT(mipmaps == 1); + + int arrayElements = SkMax32(fHeader.fNumberOfArrayElements, 1); + SkASSERT(arrayElements == 1); + + int faces = SkMax32(fHeader.fNumberOfFaces, 1); + SkASSERT(faces == 1); + + int depth = SkMax32(fHeader.fPixelDepth, 1); + SkASSERT(depth == 1); + + for (int mipmap = 0; mipmap < mipmaps; ++mipmap) { + // Make sure that we have at least 4 more bytes for the first image size + if (bytesLeft < 4) { + return false; + } + + uint32_t imgSize = this->readInt(&buf, &bytesLeft); + + // Truncated file. + if (bytesLeft < imgSize) { + return false; + } + + // !FIXME! If support is ever added for cube maps then the padding + // needs to be taken into account here. + for (int arrayElement = 0; arrayElement < arrayElements; ++arrayElement) { + for (int face = 0; face < faces; ++face) { + for (int z = 0; z < depth; ++z) { + PixelData pd(buf, imgSize); + fPixelData.append(1, &pd); + } + } + } + + uint32_t imgSizePadded = (imgSize + 3) & ~3; + buf += imgSizePadded; + bytesLeft -= imgSizePadded; + } + + return bytesLeft == 0; +} + +bool SkKTXFile::is_ktx(const uint8_t data[], size_t size) { + return size >= KTX_FILE_IDENTIFIER_SIZE && + 0 == memcmp(KTX_FILE_IDENTIFIER, data, KTX_FILE_IDENTIFIER_SIZE); +} + +bool SkKTXFile::is_ktx(SkStreamRewindable* stream) { + // Read the KTX header and make sure it's valid. + unsigned char buf[KTX_FILE_IDENTIFIER_SIZE]; + bool largeEnough = + stream->read((void*)buf, KTX_FILE_IDENTIFIER_SIZE) == KTX_FILE_IDENTIFIER_SIZE; + stream->rewind(); + if (!largeEnough) { + return false; + } + return is_ktx(buf, KTX_FILE_IDENTIFIER_SIZE); +} + +SkKTXFile::KeyValue SkKTXFile::CreateKeyValue(const char *cstrKey, const char *cstrValue) { + SkString key(cstrKey); + SkString value(cstrValue); + + // Size of buffer is length of string plus the null terminators... + size_t size = key.size() + 1 + value.size() + 1; + + SkAutoSMalloc<256> buf(size); + uint8_t* kvBuf = reinterpret_cast(buf.get()); + memcpy(kvBuf, key.c_str(), key.size() + 1); + memcpy(kvBuf + key.size() + 1, value.c_str(), value.size() + 1); + + KeyValue kv(size); + SkAssertResult(kv.readKeyAndValue(kvBuf)); + return kv; +} + +bool SkKTXFile::WriteETC1ToKTX(SkWStream* stream, const uint8_t *etc1Data, + uint32_t width, uint32_t height) { + // First thing's first, write out the magic identifier and endianness... + if (!stream->write(KTX_FILE_IDENTIFIER, KTX_FILE_IDENTIFIER_SIZE)) { + return false; + } + + if (!stream->write(&kKTX_ENDIANNESS_CODE, 4)) { + return false; + } + + Header hdr; + hdr.fGLType = 0; + hdr.fGLTypeSize = 1; + hdr.fGLFormat = 0; + hdr.fGLInternalFormat = GR_GL_COMPRESSED_ETC1_RGB8; + hdr.fGLBaseInternalFormat = GR_GL_RGB; + hdr.fPixelWidth = width; + hdr.fPixelHeight = height; + hdr.fNumberOfArrayElements = 0; + hdr.fNumberOfFaces = 1; + hdr.fNumberOfMipmapLevels = 1; + + // !FIXME! The spec suggests that we put KTXOrientation as a + // key value pair in the header, but that means that we'd have to + // pipe through the pixmap's orientation to properly do that. + hdr.fBytesOfKeyValueData = 0; + + // Write the header + if (!stream->write(&hdr, sizeof(hdr))) { + return false; + } + + // Write the size of the image data + etc1_uint32 dataSize = etc1_get_encoded_data_size(width, height); + if (!stream->write(&dataSize, 4)) { + return false; + } + + // Write the actual image data + if (!stream->write(etc1Data, dataSize)) { + return false; + } + + return true; +} + +bool SkKTXFile::WritePixmapToKTX(SkWStream* stream, const SkPixmap& pixmap) { + const SkColorType ct = pixmap.colorType(); + + const int width = pixmap.width(); + const int height = pixmap.height(); + const uint8_t* src = reinterpret_cast(pixmap.addr()); + if (!src) { + return false; + } + const size_t rowBytes = pixmap.rowBytes(); + const int bytesPerPixel = pixmap.info().bytesPerPixel(); + + // First thing's first, write out the magic identifier and endianness... + if (!stream->write(KTX_FILE_IDENTIFIER, KTX_FILE_IDENTIFIER_SIZE) || + !stream->write(&kKTX_ENDIANNESS_CODE, 4)) { + return false; + } + + // Collect our key/value pairs... + SkTArray kvPairs; + + // Next, write the header based on the pixmap's config. + Header hdr; + switch (ct) { + case kIndex_8_SkColorType: + // There is a compressed format for this, but we don't support it yet. + SkDebugf("Writing indexed pixmap to KTX unsupported.\n"); + // VVV fall through VVV + default: + case kUnknown_SkColorType: + // Pixmap hasn't been configured. + return false; + + case kAlpha_8_SkColorType: + hdr.fGLType = GR_GL_UNSIGNED_BYTE; + hdr.fGLTypeSize = 1; + hdr.fGLFormat = GR_GL_RED; + hdr.fGLInternalFormat = GR_GL_R8; + hdr.fGLBaseInternalFormat = GR_GL_RED; + break; + + case kRGB_565_SkColorType: + hdr.fGLType = GR_GL_UNSIGNED_SHORT_5_6_5; + hdr.fGLTypeSize = 2; + hdr.fGLFormat = GR_GL_RGB; + hdr.fGLInternalFormat = GR_GL_RGB; + hdr.fGLBaseInternalFormat = GR_GL_RGB; + break; + + case kARGB_4444_SkColorType: + hdr.fGLType = GR_GL_UNSIGNED_SHORT_4_4_4_4; + hdr.fGLTypeSize = 2; + hdr.fGLFormat = GR_GL_RGBA; + hdr.fGLInternalFormat = GR_GL_RGBA4; + hdr.fGLBaseInternalFormat = GR_GL_RGBA; + kvPairs.push_back(CreateKeyValue("KTXPremultipliedAlpha", "True")); + break; + + case kN32_SkColorType: + hdr.fGLType = GR_GL_UNSIGNED_BYTE; + hdr.fGLTypeSize = 1; + hdr.fGLFormat = GR_GL_RGBA; + hdr.fGLInternalFormat = GR_GL_RGBA8; + hdr.fGLBaseInternalFormat = GR_GL_RGBA; + kvPairs.push_back(CreateKeyValue("KTXPremultipliedAlpha", "True")); + break; + } + + // Everything else in the header is shared. + hdr.fPixelWidth = width; + hdr.fPixelHeight = height; + hdr.fNumberOfArrayElements = 0; + hdr.fNumberOfFaces = 1; + hdr.fNumberOfMipmapLevels = 1; + + // Calculate the key value data size + hdr.fBytesOfKeyValueData = 0; + for (KeyValue *kv = kvPairs.begin(); kv != kvPairs.end(); ++kv) { + // Key value size is the size of the key value data, + // four bytes for saying how big the key value size is + // and then additional bytes for padding to four byte boundary + size_t kvsize = kv->size(); + kvsize += 4; + kvsize = (kvsize + 3) & ~3; + hdr.fBytesOfKeyValueData = SkToU32(hdr.fBytesOfKeyValueData + kvsize); + } + + // Write the header + if (!stream->write(&hdr, sizeof(hdr))) { + return false; + } + + // Write out each key value pair + for (KeyValue *kv = kvPairs.begin(); kv != kvPairs.end(); ++kv) { + if (!kv->writeKeyAndValueForKTX(stream)) { + return false; + } + } + + // Calculate the size of the data + uint32_t dataSz = bytesPerPixel * width * height; + + if (0 >= bytesPerPixel) { + return false; + } + + // Write it into the buffer + if (!stream->write(&dataSz, 4)) { + return false; + } + + // Write the pixel data... + const uint8_t* rowPtr = src; + if (kN32_SkColorType == ct) { + for (int j = 0; j < height; ++j) { + const uint32_t* pixelsPtr = reinterpret_cast(rowPtr); + for (int i = 0; i < width; ++i) { + uint32_t pixel = pixelsPtr[i]; + uint8_t dstPixel[4]; + dstPixel[0] = pixel >> SK_R32_SHIFT; + dstPixel[1] = pixel >> SK_G32_SHIFT; + dstPixel[2] = pixel >> SK_B32_SHIFT; + dstPixel[3] = pixel >> SK_A32_SHIFT; + if (!stream->write(dstPixel, 4)) { + return false; + } + } + rowPtr += rowBytes; + } + } else { + for (int i = 0; i < height; ++i) { + if (!stream->write(rowPtr, bytesPerPixel * width)) { + return false; + } + rowPtr += rowBytes; + } + } + + return true; +} + +bool SkKTXFile::WriteBitmapToKTX(SkWStream* stream, const SkBitmap& bitmap) { + SkAutoLockPixels autoLockPixels(bitmap); + SkPixmap pixmap; + return bitmap.peekPixels(&pixmap) && SkKTXFile::WritePixmapToKTX(stream, pixmap); +} diff --git a/third_party/ktx/ktx.h b/third_party/ktx/ktx.h new file mode 100644 index 0000000..3aaea15 --- /dev/null +++ b/third_party/ktx/ktx.h @@ -0,0 +1,145 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkKTXFile_DEFINED +#define SkKTXFile_DEFINED + +#include "SkData.h" +#include "SkTextureCompressor.h" +#include "SkTypes.h" +#include "SkTDArray.h" +#include "SkString.h" +#include "SkRefCnt.h" + +class SkBitmap; +class SkPixmap; +class SkStreamRewindable; +class SkWStream; + +// KTX Image File +// --- +// KTX is a general texture data storage file format ratified by the Khronos Group. As an +// overview, a KTX file contains all of the appropriate values needed to fully specify a +// texture in an OpenGL application, including the use of compressed data. +// +// A full format specification can be found here: +// http://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ + +class SkKTXFile { +public: + // The ownership of the data remains with the caller. This class is intended + // to be used as a logical wrapper around the data in order to properly + // access the pixels. + SkKTXFile(SkData* data) : fData(data), fSwapBytes(false) { + data->ref(); + fValid = this->readKTXFile(fData->bytes(), fData->size()); + } + + bool valid() const { return fValid; } + + int width() const { return static_cast(fHeader.fPixelWidth); } + int height() const { return static_cast(fHeader.fPixelHeight); } + + const uint8_t *pixelData(int mipmap = 0) const { + SkASSERT(!this->valid() || mipmap < fPixelData.count()); + return this->valid() ? fPixelData[mipmap].data() : NULL; + } + + // If the decoded KTX file has the following key, then it will + // return the associated value. If not found, the empty string + // is returned. + SkString getValueForKey(const SkString& key) const; + + int numMipmaps() const { return static_cast(fHeader.fNumberOfMipmapLevels); } + + bool isCompressedFormat(SkTextureCompressor::Format fmt) const; + bool isRGBA8() const; + bool isRGB8() const; + + static bool is_ktx(const uint8_t data[], size_t size); + static bool is_ktx(SkStreamRewindable* stream); + + static bool WriteETC1ToKTX(SkWStream* stream, const uint8_t *etc1Data, + uint32_t width, uint32_t height); + static bool WritePixmapToKTX(SkWStream* stream, const SkPixmap& pixmap); + static bool WriteBitmapToKTX(SkWStream* stream, const SkBitmap& bitmap); + +private: + + // The blob holding the file data. + sk_sp fData; + + // This header captures all of the data that describes the format + // of the image data in a KTX file. + struct Header { + uint32_t fGLType; + uint32_t fGLTypeSize; + uint32_t fGLFormat; + uint32_t fGLInternalFormat; + uint32_t fGLBaseInternalFormat; + uint32_t fPixelWidth; + uint32_t fPixelHeight; + uint32_t fPixelDepth; + uint32_t fNumberOfArrayElements; + uint32_t fNumberOfFaces; + uint32_t fNumberOfMipmapLevels; + uint32_t fBytesOfKeyValueData; + + Header() { memset(this, 0, sizeof(*this)); } + } fHeader; + + // A Key Value pair stored in the KTX file. There may be + // arbitrarily many of these. + class KeyValue { + public: + KeyValue(size_t size) : fDataSz(size) { } + bool readKeyAndValue(const uint8_t *data); + size_t size() const { return fDataSz; } + const SkString& key() const { return fKey; } + const SkString& value() const { return fValue; } + bool writeKeyAndValueForKTX(SkWStream* strm); + private: + const size_t fDataSz; + SkString fKey; + SkString fValue; + }; + + static KeyValue CreateKeyValue(const char *key, const char *value); + + // The pixel data for a single mipmap level in an image. Based on how + // the rest of the data is stored, this may be compressed, a cubemap, etc. + // The header will describe the format of this data. + class PixelData { + public: + PixelData(const uint8_t *ptr, size_t sz) : fDataSz(sz), fDataPtr(ptr) { } + const uint8_t *data() const { return fDataPtr; } + size_t dataSize() const { return fDataSz; } + private: + const size_t fDataSz; + const uint8_t *fDataPtr; + }; + + // This function is only called once from the constructor. It loads the data + // and populates the appropriate fields of this class + // (fKeyValuePairs, fPixelData, fSwapBytes) + bool readKTXFile(const uint8_t *data, size_t dataLen); + + SkTArray fKeyValuePairs; + SkTDArray fPixelData; + bool fValid; + + // If the endianness of the platform is different than the file, + // then we need to do proper byte swapping. + bool fSwapBytes; + + // Read an integer from a buffer, advance the buffer, and swap + // bytes if fSwapBytes is set + uint32_t readInt(const uint8_t** buf, size_t* bytesLeft) const; +}; + +#endif // SkKTXFile_DEFINED diff --git a/tools/flags/SkCommonFlags.cpp b/tools/flags/SkCommonFlags.cpp index 26b718f..52a5388 100644 --- a/tools/flags/SkCommonFlags.cpp +++ b/tools/flags/SkCommonFlags.cpp @@ -72,8 +72,8 @@ bool CollectImages(SkCommandLineFlags::StringArray images, SkTArray* o SkASSERT(output); static const char* const exts[] = { - "bmp", "gif", "jpg", "jpeg", "png", "webp", "astc", "wbmp", "ico", - "BMP", "GIF", "JPG", "JPEG", "PNG", "WEBP", "ASTC", "WBMP", "ICO", + "bmp", "gif", "jpg", "jpeg", "png", "webp", "ktx", "astc", "wbmp", "ico", + "BMP", "GIF", "JPG", "JPEG", "PNG", "WEBP", "KTX", "ASTC", "WBMP", "ICO", #ifdef SK_CODEC_DECODES_RAW "arw", "cr2", "dng", "nef", "nrw", "orf", "raf", "rw2", "pef", "srw", "ARW", "CR2", "DNG", "NEF", "NRW", "ORF", "RAF", "RW2", "PEF", "SRW", -- 2.7.4