• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2024 Google LLC
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 "src/gpu/DataUtils.h"
9 
10 #include "include/core/SkTextureCompressionType.h"
11 #include "include/gpu/GpuTypes.h"
12 #include "include/private/base/SkAssert.h"
13 #include "include/private/base/SkMath.h"
14 #include "include/private/base/SkTPin.h"
15 #include "include/private/base/SkTemplates.h"
16 #include "src/base/SkMathPriv.h"
17 #include "src/core/SkCompressedDataUtils.h"
18 #include "src/core/SkMipmap.h"
19 #include "src/core/SkTraceEvent.h"
20 
21 #include <algorithm>
22 #include <cstdint>
23 #include <cstring>
24 
25 using namespace skia_private;
26 
27 namespace skgpu {
28 
29 struct ETC1Block {
30     uint32_t fHigh;
31     uint32_t fLow;
32 };
33 
34 constexpr uint32_t kDiffBit = 0x2; // set -> differential; not-set -> individual
35 
extend_5To8bits(int b)36 static inline int extend_5To8bits(int b) {
37     int c = b & 0x1f;
38     return (c << 3) | (c >> 2);
39 }
40 
41 static const int kNumETC1ModifierTables = 8;
42 static const int kNumETC1PixelIndices = 4;
43 
44 // The index of each row in this table is the ETC1 table codeword
45 // The index of each column in this table is the ETC1 pixel index value
46 static const int kETC1ModifierTables[kNumETC1ModifierTables][kNumETC1PixelIndices] = {
47     /* 0 */ { 2,    8,  -2,   -8 },
48     /* 1 */ { 5,   17,  -5,  -17 },
49     /* 2 */ { 9,   29,  -9,  -29 },
50     /* 3 */ { 13,  42, -13,  -42 },
51     /* 4 */ { 18,  60, -18,  -60 },
52     /* 5 */ { 24,  80, -24,  -80 },
53     /* 6 */ { 33, 106, -33, -106 },
54     /* 7 */ { 47, 183, -47, -183 }
55 };
56 
57 // Evaluate one of the entries in 'kModifierTables' to see how close it can get (r8,g8,b8) to
58 // the original color (rOrig, gOrib, bOrig).
test_table_entry(int rOrig,int gOrig,int bOrig,int r8,int g8,int b8,int table,int offset)59 static int test_table_entry(int rOrig, int gOrig, int bOrig,
60                             int r8, int g8, int b8,
61                             int table, int offset) {
62     SkASSERT(0 <= table && table < 8);
63     SkASSERT(0 <= offset && offset < 4);
64 
65     r8 = SkTPin<int>(r8 + kETC1ModifierTables[table][offset], 0, 255);
66     g8 = SkTPin<int>(g8 + kETC1ModifierTables[table][offset], 0, 255);
67     b8 = SkTPin<int>(b8 + kETC1ModifierTables[table][offset], 0, 255);
68 
69     return SkTAbs(rOrig - r8) + SkTAbs(gOrig - g8) + SkTAbs(bOrig - b8);
70 }
71 
72 // Create an ETC1 compressed block that is filled with 'col'
create_etc1_block(SkColor col,ETC1Block * block)73 static void create_etc1_block(SkColor col, ETC1Block* block) {
74     uint32_t high = 0;
75     uint32_t low = 0;
76 
77     int rOrig = SkColorGetR(col);
78     int gOrig = SkColorGetG(col);
79     int bOrig = SkColorGetB(col);
80 
81     int r5 = SkMulDiv255Round(31, rOrig);
82     int g5 = SkMulDiv255Round(31, gOrig);
83     int b5 = SkMulDiv255Round(31, bOrig);
84 
85     int r8 = extend_5To8bits(r5);
86     int g8 = extend_5To8bits(g5);
87     int b8 = extend_5To8bits(b5);
88 
89     // We always encode solid color textures in differential mode (i.e., with a 555 base color) but
90     // with zero diffs (i.e., bits 26-24, 18-16 and 10-8 are left 0).
91     high |= (r5 << 27) | (g5 << 19) | (b5 << 11) | kDiffBit;
92 
93     int bestTableIndex = 0, bestPixelIndex = 0;
94     int bestSoFar = 1024;
95     for (int tableIndex = 0; tableIndex < kNumETC1ModifierTables; ++tableIndex) {
96         for (int pixelIndex = 0; pixelIndex < kNumETC1PixelIndices; ++pixelIndex) {
97             int score = test_table_entry(rOrig, gOrig, bOrig, r8, g8, b8,
98                                          tableIndex, pixelIndex);
99 
100             if (bestSoFar > score) {
101                 bestSoFar = score;
102                 bestTableIndex = tableIndex;
103                 bestPixelIndex = pixelIndex;
104             }
105         }
106     }
107 
108     high |= (bestTableIndex << 5) | (bestTableIndex << 2);
109 
110     if (bestPixelIndex & 0x1) {
111         low |= 0xFFFF;
112     }
113     if (bestPixelIndex & 0x2) {
114         low |= 0xFFFF0000;
115     }
116 
117     block->fHigh = SkBSwap32(high);
118     block->fLow = SkBSwap32(low);
119 }
120 
num_4x4_blocks(int size)121 static int num_4x4_blocks(int size) {
122     return ((size + 3) & ~3) >> 2;
123 }
124 
num_6x6_blocks(int size)125 static int num_6x6_blocks(int size) {
126     // Divide the image size by 6, rounding down
127     return (size + 5) / 6;
128 }
129 
num_8x8_blocks(int size)130 static int num_8x8_blocks(int size) {
131     // Divide the image size by 8, rounding down
132     return ((size + 7) & ~7) >> 3;
133 }
134 
num_ETC1_blocks(int w,int h)135 static int num_ETC1_blocks(int w, int h) {
136     w = num_4x4_blocks(w);
137     h = num_4x4_blocks(h);
138 
139     return w * h;
140 }
141 
142 struct BC1Block {
143     uint16_t fColor0;
144     uint16_t fColor1;
145     uint32_t fIndices;
146 };
147 
to565(SkColor col)148 static uint16_t to565(SkColor col) {
149     int r5 = SkMulDiv255Round(31, SkColorGetR(col));
150     int g6 = SkMulDiv255Round(63, SkColorGetG(col));
151     int b5 = SkMulDiv255Round(31, SkColorGetB(col));
152 
153     return (r5 << 11) | (g6 << 5) | b5;
154 }
155 
156 // Create a BC1 compressed block that has two colors but is initialized to 'col0'
create_BC1_block(SkColor col0,SkColor col1,BC1Block * block)157 static void create_BC1_block(SkColor col0, SkColor col1, BC1Block* block) {
158     block->fColor0 = to565(col0);
159     block->fColor1 = to565(col1);
160     SkASSERT(block->fColor0 <= block->fColor1); // we always assume transparent blocks
161 
162     if (col0 == SK_ColorTRANSPARENT) {
163         // This sets all 16 pixels to just use color3 (under the assumption
164         // that this is a kBC1_RGBA8_UNORM texture. Note that in this case
165         // fColor0 will be opaque black.
166         block->fIndices = 0xFFFFFFFF;
167     } else {
168         // This sets all 16 pixels to just use 'fColor0'
169         block->fIndices = 0;
170     }
171 }
172 
NumCompressedBlocks(SkTextureCompressionType type,SkISize baseDimensions)173 size_t NumCompressedBlocks(SkTextureCompressionType type, SkISize baseDimensions) {
174     switch (type) {
175         case SkTextureCompressionType::kNone:
176             return baseDimensions.width() * baseDimensions.height();
177         case SkTextureCompressionType::kETC2_RGB8_UNORM:
178         case SkTextureCompressionType::kBC1_RGB8_UNORM:
179         case SkTextureCompressionType::kBC1_RGBA8_UNORM:
180         case SkTextureCompressionType::kASTC_RGBA8_4x4: {
181             int numBlocksWidth = num_4x4_blocks(baseDimensions.width());
182             int numBlocksHeight = num_4x4_blocks(baseDimensions.height());
183 
184             return numBlocksWidth * numBlocksHeight;
185         }
186         case SkTextureCompressionType::kASTC_RGBA8_6x6: {
187             int numBlocksWidth = num_6x6_blocks(baseDimensions.width());
188             int numBlocksHeight = num_6x6_blocks(baseDimensions.height());
189 
190             return numBlocksWidth * numBlocksHeight;
191         }
192         case SkTextureCompressionType::kASTC_RGBA8_8x8: {
193             int numBlocksWidth = num_8x8_blocks(baseDimensions.width());
194             int numBlocksHeight = num_8x8_blocks(baseDimensions.height());
195 
196             return numBlocksWidth * numBlocksHeight;
197         }
198     }
199     SkUNREACHABLE;
200 }
201 
CompressedRowBytes(SkTextureCompressionType type,int width)202 size_t CompressedRowBytes(SkTextureCompressionType type, int width) {
203     switch (type) {
204         case SkTextureCompressionType::kNone:
205             return 0;
206         case SkTextureCompressionType::kETC2_RGB8_UNORM:
207         case SkTextureCompressionType::kBC1_RGB8_UNORM:
208         case SkTextureCompressionType::kBC1_RGBA8_UNORM: {
209             int numBlocksWidth = num_4x4_blocks(width);
210 
211             static_assert(sizeof(ETC1Block) == sizeof(BC1Block));
212             return numBlocksWidth * sizeof(ETC1Block);
213         }
214         case SkTextureCompressionType::kASTC_RGBA8_4x4: {
215             int numBlocksWidth = num_4x4_blocks(width);
216 
217             // The evil number 16 here is the constant size of ASTC 4x4 block
218             return numBlocksWidth * 16;
219         }
220         case SkTextureCompressionType::kASTC_RGBA8_6x6: {
221             int numBlocksWidth = num_6x6_blocks(width);
222 
223             // The evil number 16 here is the constant size of ASTC 6x6 block
224             return numBlocksWidth * 16;
225         }
226         case SkTextureCompressionType::kASTC_RGBA8_8x8: {
227             int numBlocksWidth = num_8x8_blocks(width);
228 
229             // The evil number 16 here is the constant size of ASTC 8x8 block
230             return numBlocksWidth * 16;
231         }
232     }
233     SkUNREACHABLE;
234 }
235 
CompressedDimensions(SkTextureCompressionType type,SkISize baseDimensions)236 SkISize CompressedDimensions(SkTextureCompressionType type, SkISize baseDimensions) {
237     switch (type) {
238         case SkTextureCompressionType::kNone:
239             return baseDimensions;
240         case SkTextureCompressionType::kETC2_RGB8_UNORM:
241         case SkTextureCompressionType::kBC1_RGB8_UNORM:
242         case SkTextureCompressionType::kBC1_RGBA8_UNORM:
243         case SkTextureCompressionType::kASTC_RGBA8_4x4: {
244             SkISize blockDims = CompressedDimensionsInBlocks(type, baseDimensions);
245             // Each BC1_RGB8_UNORM and ETC1 block and ASTC 4x4 block has 16 pixels
246             return { 4 * blockDims.fWidth, 4 * blockDims.fHeight };
247         }
248         case SkTextureCompressionType::kASTC_RGBA8_6x6: {
249             int numBlocksWidth = num_6x6_blocks(baseDimensions.width());
250             int numBlocksHeight = num_6x6_blocks(baseDimensions.height());
251 
252             // Each ASTC 6x6 block has 36 pixels
253             return { 6 * numBlocksWidth, 6 * numBlocksHeight };
254         }
255         case SkTextureCompressionType::kASTC_RGBA8_8x8: {
256             int numBlocksWidth = num_8x8_blocks(baseDimensions.width());
257             int numBlocksHeight = num_8x8_blocks(baseDimensions.height());
258 
259             // Each ASTC 8x8 block has 64 pixels
260             return { 8 * numBlocksWidth, 8 * numBlocksHeight };
261         }
262     }
263     SkUNREACHABLE;
264 }
265 
CompressedDimensionsInBlocks(SkTextureCompressionType type,SkISize baseDimensions)266 SkISize CompressedDimensionsInBlocks(SkTextureCompressionType type, SkISize baseDimensions) {
267     switch (type) {
268         case SkTextureCompressionType::kNone:
269             return baseDimensions;
270         case SkTextureCompressionType::kETC2_RGB8_UNORM:
271         case SkTextureCompressionType::kBC1_RGB8_UNORM:
272         case SkTextureCompressionType::kBC1_RGBA8_UNORM:
273         case SkTextureCompressionType::kASTC_RGBA8_4x4: {
274             int numBlocksWidth = num_4x4_blocks(baseDimensions.width());
275             int numBlocksHeight = num_4x4_blocks(baseDimensions.height());
276 
277             // Each BC1_RGB8_UNORM and ETC1 block has 16 pixels
278             return { numBlocksWidth, numBlocksHeight };
279         }
280         case SkTextureCompressionType::kASTC_RGBA8_6x6: {
281             int numBlocksWidth = num_6x6_blocks(baseDimensions.width());
282             int numBlocksHeight = num_6x6_blocks(baseDimensions.height());
283 
284             return { numBlocksWidth, numBlocksHeight };
285         }
286         case SkTextureCompressionType::kASTC_RGBA8_8x8: {
287             int numBlocksWidth = num_8x8_blocks(baseDimensions.width());
288             int numBlocksHeight = num_8x8_blocks(baseDimensions.height());
289 
290             return { numBlocksWidth, numBlocksHeight };
291         }
292     }
293     SkUNREACHABLE;
294 }
295 
296 // Fill in 'dest' with ETC1 blocks derived from 'colorf'
fillin_ETC1_with_color(SkISize dimensions,const SkColor4f & colorf,char * dest)297 static void fillin_ETC1_with_color(SkISize dimensions, const SkColor4f& colorf, char* dest) {
298     SkColor color = colorf.toSkColor();
299 
300     ETC1Block block;
301     create_etc1_block(color, &block);
302 
303     int numBlocks = num_ETC1_blocks(dimensions.width(), dimensions.height());
304 
305     for (int i = 0; i < numBlocks; ++i) {
306         memcpy(dest, &block, sizeof(ETC1Block));
307         dest += sizeof(ETC1Block);
308     }
309 }
310 
311 // Fill in 'dest' with BC1 blocks derived from 'colorf'
fillin_BC1_with_color(SkISize dimensions,const SkColor4f & colorf,char * dest)312 static void fillin_BC1_with_color(SkISize dimensions, const SkColor4f& colorf, char* dest) {
313     SkColor color = colorf.toSkColor();
314 
315     BC1Block block;
316     create_BC1_block(color, color, &block);
317 
318     int numBlocks = num_ETC1_blocks(dimensions.width(), dimensions.height());
319 
320     for (int i = 0; i < numBlocks; ++i) {
321         memcpy(dest, &block, sizeof(BC1Block));
322         dest += sizeof(BC1Block);
323     }
324 }
325 
FillInCompressedData(SkTextureCompressionType type,SkISize dimensions,skgpu::Mipmapped mipmapped,char * dstPixels,const SkColor4f & colorf)326 void FillInCompressedData(SkTextureCompressionType type,
327                           SkISize dimensions,
328                           skgpu::Mipmapped mipmapped,
329                           char* dstPixels,
330                           const SkColor4f& colorf) {
331     TRACE_EVENT0("skia.gpu", TRACE_FUNC);
332 
333     int numMipLevels = 1;
334     if (mipmapped == skgpu::Mipmapped::kYes) {
335         numMipLevels = SkMipmap::ComputeLevelCount(dimensions.width(), dimensions.height()) + 1;
336     }
337 
338     size_t offset = 0;
339 
340     for (int i = 0; i < numMipLevels; ++i) {
341         size_t levelSize = SkCompressedDataSize(type, dimensions, nullptr, false);
342 
343         if (SkTextureCompressionType::kETC2_RGB8_UNORM == type) {
344             fillin_ETC1_with_color(dimensions, colorf, &dstPixels[offset]);
345         } else {
346             SkASSERT(type == SkTextureCompressionType::kBC1_RGB8_UNORM ||
347                      type == SkTextureCompressionType::kBC1_RGBA8_UNORM);
348             fillin_BC1_with_color(dimensions, colorf, &dstPixels[offset]);
349         }
350 
351         offset += levelSize;
352         dimensions = {std::max(1, dimensions.width()/2), std::max(1, dimensions.height()/2)};
353     }
354 }
355 
356 } // namespace skgpu
357