1 /*
2 * Copyright 2020 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "gm/gm.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkImage.h"
11 #include "include/core/SkTextureCompressionType.h"
12 #include "include/gpu/ganesh/GrDirectContext.h"
13 #include "include/gpu/ganesh/GrRecordingContext.h"
14 #include "include/gpu/ganesh/SkImageGanesh.h"
15 #include "src/core/SkCompressedDataUtils.h"
16 #include "src/gpu/ganesh/GrCaps.h"
17 #include "src/gpu/ganesh/GrImageContextPriv.h"
18 #include "src/gpu/ganesh/image/SkImage_GaneshBase.h"
19 #include "src/image/SkImage_Base.h"
20 #include "tools/gpu/ProxyUtils.h"
21
22 constexpr int kImgWidth = 16;
23 constexpr int kImgHeight = 8;
24 constexpr int kPad = 4;
25
26 struct BC1Block {
27 uint16_t fColor0;
28 uint16_t fColor1;
29 uint32_t fIndices;
30 };
31
num_4x4_blocks(int size)32 static int num_4x4_blocks(int size) {
33 return ((size + 3) & ~3) >> 2;
34 }
35
to565(SkColor col)36 static uint16_t to565(SkColor col) {
37 int r5 = SkMulDiv255Round(31, SkColorGetR(col));
38 int g6 = SkMulDiv255Round(63, SkColorGetG(col));
39 int b5 = SkMulDiv255Round(31, SkColorGetB(col));
40
41 return (r5 << 11) | (g6 << 5) | b5;
42 }
43
44 // BC1 has per-block transparency. If, taken as ints,
45 // fColor0 < fColor1 -> the block has transparency (& it is in color3)
46 // fColor1 > fColor0 -> the block is opaque
47 //
48 // This method can create two blocks to test out BC1's behavior. If BC1
49 // behaves as expected (i.e., w/ per-block transparency) then, for RGBA textures,
50 // the transparent block(s) should appear as:
51 // opaque black, medium grey, transparent black, white.
52 // and the opaque block(s) should appear as:
53 // opaque black, dark grey, light grey, white
54 //
55 // For RGB textures, however, the transparent block(s) should appear as:
56 // opaque black, medium grey, _opaque_ black, white
57 // and the opaque block(s) should appear as:
58 // opaque black, dark grey, light grey, white.
create_BC1_block(BC1Block * block,bool transparent)59 static void create_BC1_block(BC1Block* block, bool transparent) {
60 unsigned int byte;
61
62 if (transparent) {
63 block->fColor0 = to565(SK_ColorBLACK);
64 block->fColor1 = to565(SK_ColorWHITE);
65 SkASSERT(block->fColor0 <= block->fColor1); // this signals a transparent block
66 // opaque black (col0), medium grey (col2), transparent black (col3), white (col1).
67 byte = (0x0 << 0) | (0x2 << 2) | (0x3 << 4) | (0x1 << 6);
68 } else {
69 block->fColor0 = to565(SK_ColorWHITE);
70 block->fColor1 = to565(SK_ColorBLACK);
71 SkASSERT(block->fColor0 > block->fColor1); // this signals an opaque block
72 // opaque black (col1), dark grey (col3), light grey (col2), white (col0)
73 byte = (0x1 << 0) | (0x3 << 2) | (0x2 << 4) | (0x0 << 6);
74 }
75
76 block->fIndices = (byte << 24) | (byte << 16) | (byte << 8) | byte;
77 }
78
79 // This makes a 16x8 BC1 texture which has the top 4 rows be officially transparent
80 // and the bottom 4 rows be officially opaque.
make_compressed_data()81 static sk_sp<SkData> make_compressed_data() {
82 SkISize dim{ kImgWidth, kImgHeight };
83
84 size_t totalSize = SkCompressedDataSize(SkTextureCompressionType::kBC1_RGB8_UNORM, dim,
85 nullptr, false);
86
87 sk_sp<SkData> tmp = SkData::MakeUninitialized(totalSize);
88 BC1Block* dstBlocks = reinterpret_cast<BC1Block*>(tmp->writable_data());
89
90 BC1Block transBlock, opaqueBlock;
91 create_BC1_block(&transBlock, true);
92 create_BC1_block(&opaqueBlock, false);
93
94 int numXBlocks = num_4x4_blocks(kImgWidth);
95 int numYBlocks = num_4x4_blocks(kImgHeight);
96
97 for (int y = 0; y < numYBlocks; ++y) {
98 for (int x = 0; x < numXBlocks; ++x) {
99 dstBlocks[y*numXBlocks + x] = (y < numYBlocks/2) ? transBlock : opaqueBlock;
100 }
101 }
102
103 return tmp;
104 }
105
data_to_img(GrDirectContext * direct,sk_sp<SkData> data,SkTextureCompressionType compression)106 static sk_sp<SkImage> data_to_img(GrDirectContext *direct, sk_sp<SkData> data,
107 SkTextureCompressionType compression) {
108 if (direct) {
109 return SkImages::TextureFromCompressedTextureData(
110 direct, std::move(data), kImgWidth, kImgHeight, compression, skgpu::Mipmapped::kNo);
111 } else {
112 return SkImages::RasterFromCompressedTextureData(
113 std::move(data), kImgWidth, kImgHeight, compression);
114 }
115 }
116
draw_image(SkCanvas * canvas,sk_sp<SkImage> image,int x,int y)117 static void draw_image(SkCanvas* canvas, sk_sp<SkImage> image, int x, int y) {
118
119 bool isCompressed = false;
120 if (image && image->isTextureBacked()) {
121 const GrCaps* caps = as_IB(image)->context()->priv().caps();
122 GrTextureProxy* proxy = sk_gpu_test::GetTextureImageProxy(image.get(),
123 canvas->recordingContext());
124 isCompressed = caps->isFormatCompressed(proxy->backendFormat());
125 }
126
127 canvas->drawImage(image, x, y);
128
129 if (!isCompressed) {
130 SkRect r = SkRect::MakeXYWH(x, y, kImgWidth, kImgHeight);
131 r.outset(1.0f, 1.0f);
132
133 SkPaint redStroke;
134 redStroke.setColor(SK_ColorRED);
135 redStroke.setStyle(SkPaint::kStroke_Style);
136 redStroke.setStrokeWidth(2.0f);
137
138 canvas->drawRect(r, redStroke);
139 }
140 }
141
142 namespace skiagm {
143
144 // This GM draws the BC1 compressed texture filled with "make_compressed_data"s data twice.
145 //
146 // It is drawn once (on the top) as a kBC1_RGB8_UNORM texture and then again (on the bottom)
147 // as a kBC1_RGBA8_UNORM texture.
148 //
149 // If BC1 behaves as expected we should see:
150 //
151 // RGB8 Black MidGrey Black* White ...
152 // Black DrkGrey LtGrey White ...
153 //
154 // RGBA8 Black MidGrey Green+ White ...
155 // Black DrkGrey LtGrey White ...
156 //
157 // * We expect this to be black bc the transparent black will be forced to opaque. If BC1 were
158 // treating it as an opaque block then it would be LtGrey - not black.
159 // + This is just the background showing through the transparent black
160 class BC1TransparencyGM : public GM {
161 public:
BC1TransparencyGM()162 BC1TransparencyGM() {
163 this->setBGColor(SK_ColorGREEN);
164 }
165
166 protected:
getName() const167 SkString getName() const override { return SkString("bc1_transparency"); }
168
getISize()169 SkISize getISize() override {
170 return SkISize::Make(kImgWidth + 2 * kPad, 2 * kImgHeight + 3 * kPad);
171 }
172
onGpuSetup(SkCanvas * canvas,SkString * errorMsg,GraphiteTestContext *)173 DrawResult onGpuSetup(SkCanvas* canvas, SkString* errorMsg, GraphiteTestContext*) override {
174 auto dContext = GrAsDirectContext(canvas->recordingContext());
175 if (dContext && dContext->abandoned()) {
176 // This isn't a GpuGM so a null 'context' is okay but an abandoned context
177 // if forbidden.
178 return DrawResult::kSkip;
179 }
180
181 sk_sp<SkData> bc1Data = make_compressed_data();
182
183 fRGBImage = data_to_img(dContext, bc1Data, SkTextureCompressionType::kBC1_RGB8_UNORM);
184 fRGBAImage = data_to_img(dContext, std::move(bc1Data),
185 SkTextureCompressionType::kBC1_RGBA8_UNORM);
186 if (!fRGBImage || !fRGBAImage) {
187 *errorMsg = "Failed to create BC1 images.";
188 return DrawResult::kFail;
189 }
190
191 return DrawResult::kOk;
192 }
193
onGpuTeardown()194 void onGpuTeardown() override {
195 fRGBImage = nullptr;
196 fRGBAImage = nullptr;
197 }
198
onDraw(SkCanvas * canvas)199 void onDraw(SkCanvas* canvas) override {
200 draw_image(canvas, fRGBImage, kPad, kPad);
201 draw_image(canvas, fRGBAImage, kPad, 2 * kPad + kImgHeight);
202 }
203
204 private:
205 sk_sp<SkImage> fRGBImage;
206 sk_sp<SkImage> fRGBAImage;
207
208 using INHERITED = GM;
209 };
210
211 //////////////////////////////////////////////////////////////////////////////
212
213 DEF_GM(return new BC1TransparencyGM;)
214 } // namespace skiagm
215