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