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