/* * 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/gpu/GrDirectContext.h" #include "include/gpu/GrRecordingContext.h" #include "src/core/SkCompressedDataUtils.h" #include "src/gpu/GrCaps.h" #include "src/gpu/GrRecordingContextPriv.h" #include "src/image/SkImage_Base.h" constexpr int kImgWidth = 16; constexpr int kImgHeight = 8; constexpr int kPad = 4; struct BC1Block { uint16_t fColor0; uint16_t fColor1; uint32_t fIndices; }; static int num_4x4_blocks(int size) { return ((size + 3) & ~3) >> 2; } static uint16_t to565(SkColor col) { int r5 = SkMulDiv255Round(31, SkColorGetR(col)); int g6 = SkMulDiv255Round(63, SkColorGetG(col)); int b5 = SkMulDiv255Round(31, SkColorGetB(col)); return (r5 << 11) | (g6 << 5) | b5; } // BC1 has per-block transparency. If, taken as ints, // fColor0 < fColor1 -> the block has transparency (& it is in color3) // fColor1 > fColor0 -> the block is opaque // // This method can create two blocks to test out BC1's behavior. If BC1 // behaves as expected (i.e., w/ per-block transparency) then, for RGBA textures, // the transparent block(s) should appear as: // opaque black, medium grey, transparent black, white. // and the opaque block(s) should appear as: // opaque black, dark grey, light grey, white // // For RGB textures, however, the transparent block(s) should appear as: // opaque black, medium grey, _opaque_ black, white // and the opaque block(s) should appear as: // opaque black, dark grey, light grey, white. static void create_BC1_block(BC1Block* block, bool transparent) { unsigned int byte; if (transparent) { block->fColor0 = to565(SK_ColorBLACK); block->fColor1 = to565(SK_ColorWHITE); SkASSERT(block->fColor0 <= block->fColor1); // this signals a transparent block // opaque black (col0), medium grey (col2), transparent black (col3), white (col1). byte = (0x0 << 0) | (0x2 << 2) | (0x3 << 4) | (0x1 << 6); } else { block->fColor0 = to565(SK_ColorWHITE); block->fColor1 = to565(SK_ColorBLACK); SkASSERT(block->fColor0 > block->fColor1); // this signals an opaque block // opaque black (col1), dark grey (col3), light grey (col2), white (col0) byte = (0x1 << 0) | (0x3 << 2) | (0x2 << 4) | (0x0 << 6); } block->fIndices = (byte << 24) | (byte << 16) | (byte << 8) | byte; } // This makes a 16x8 BC1 texture which has the top 4 rows be officially transparent // and the bottom 4 rows be officially opaque. static sk_sp make_compressed_data() { SkISize dim{ kImgWidth, kImgHeight }; size_t totalSize = SkCompressedDataSize(SkImage::CompressionType::kBC1_RGB8_UNORM, dim, nullptr, false); sk_sp tmp = SkData::MakeUninitialized(totalSize); BC1Block* dstBlocks = reinterpret_cast(tmp->writable_data()); BC1Block transBlock, opaqueBlock; create_BC1_block(&transBlock, true); create_BC1_block(&opaqueBlock, false); int numXBlocks = num_4x4_blocks(kImgWidth); int numYBlocks = num_4x4_blocks(kImgHeight); for (int y = 0; y < numYBlocks; ++y) { for (int x = 0; x < numXBlocks; ++x) { dstBlocks[y*numXBlocks + x] = (y < numYBlocks/2) ? transBlock : opaqueBlock; } } return tmp; } static sk_sp data_to_img(GrDirectContext *direct, sk_sp data, SkImage::CompressionType compression) { if (direct) { return SkImage::MakeTextureFromCompressed(direct, std::move(data), kImgWidth, kImgHeight, compression, GrMipmapped::kNo); } else { return SkImage::MakeRasterFromCompressed(std::move(data), kImgWidth, kImgHeight, compression); } } static void draw_image(GrRecordingContext* context, SkCanvas* canvas, sk_sp image, int x, int y) { bool isCompressed = false; if (image && image->isTextureBacked()) { const GrCaps* caps = context->priv().caps(); GrTextureProxy* proxy = as_IB(image)->peekProxy(); isCompressed = caps->isFormatCompressed(proxy->backendFormat()); } canvas->drawImage(image, x, y); if (!isCompressed) { SkRect r = SkRect::MakeXYWH(x, y, kImgWidth, kImgHeight); r.outset(1.0f, 1.0f); SkPaint redStroke; redStroke.setColor(SK_ColorRED); redStroke.setStyle(SkPaint::kStroke_Style); redStroke.setStrokeWidth(2.0f); canvas->drawRect(r, redStroke); } } namespace skiagm { // This GM draws the BC1 compressed texture filled with "make_compressed_data"s data twice. // // It is drawn once (on the top) as a kBC1_RGB8_UNORM texture and then again (on the bottom) // as a kBC1_RGBA8_UNORM texture. // // If BC1 behaves as expected we should see: // // RGB8 Black MidGrey Black* White ... // Black DrkGrey LtGrey White ... // // RGBA8 Black MidGrey Green+ White ... // Black DrkGrey LtGrey White ... // // * We expect this to be black bc the transparent black will be forced to opaque. If BC1 were // treating it as an opaque block then it would be LtGrey - not black. // + This is just the background showing through the transparent black class BC1TransparencyGM : public GM { public: BC1TransparencyGM() { this->setBGColor(SK_ColorGREEN); } protected: SkString onShortName() override { return SkString("bc1_transparency"); } SkISize onISize() override { return SkISize::Make(kImgWidth + 2 * kPad, 2 * kImgHeight + 3 * kPad); } void onOnceBeforeDraw() override { fBC1Data = make_compressed_data(); } void onDraw(SkCanvas* canvas) override { auto direct = GrAsDirectContext(canvas->recordingContext()); sk_sp rgbImg = data_to_img(direct, fBC1Data, SkImage::CompressionType::kBC1_RGB8_UNORM); sk_sp rgbaImg = data_to_img(direct, fBC1Data, SkImage::CompressionType::kBC1_RGBA8_UNORM); draw_image(direct, canvas, rgbImg, kPad, kPad); draw_image(direct, canvas, rgbaImg, kPad, 2 * kPad + kImgHeight); } private: sk_sp fBC1Data; typedef GM INHERITED; }; ////////////////////////////////////////////////////////////////////////////// DEF_GM(return new BC1TransparencyGM;) }