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_ETC1_blocks(int w,int h)125 static int num_ETC1_blocks(int w, int h) {
126 w = num_4x4_blocks(w);
127 h = num_4x4_blocks(h);
128
129 return w * h;
130 }
131
132 struct BC1Block {
133 uint16_t fColor0;
134 uint16_t fColor1;
135 uint32_t fIndices;
136 };
137
to565(SkColor col)138 static uint16_t to565(SkColor col) {
139 int r5 = SkMulDiv255Round(31, SkColorGetR(col));
140 int g6 = SkMulDiv255Round(63, SkColorGetG(col));
141 int b5 = SkMulDiv255Round(31, SkColorGetB(col));
142
143 return (r5 << 11) | (g6 << 5) | b5;
144 }
145
146 // Create a BC1 compressed block that has two colors but is initialized to 'col0'
create_BC1_block(SkColor col0,SkColor col1,BC1Block * block)147 static void create_BC1_block(SkColor col0, SkColor col1, BC1Block* block) {
148 block->fColor0 = to565(col0);
149 block->fColor1 = to565(col1);
150 SkASSERT(block->fColor0 <= block->fColor1); // we always assume transparent blocks
151
152 if (col0 == SK_ColorTRANSPARENT) {
153 // This sets all 16 pixels to just use color3 (under the assumption
154 // that this is a kBC1_RGBA8_UNORM texture. Note that in this case
155 // fColor0 will be opaque black.
156 block->fIndices = 0xFFFFFFFF;
157 } else {
158 // This sets all 16 pixels to just use 'fColor0'
159 block->fIndices = 0;
160 }
161 }
162
NumCompressedBlocks(SkTextureCompressionType type,SkISize baseDimensions)163 size_t NumCompressedBlocks(SkTextureCompressionType type, SkISize baseDimensions) {
164 switch (type) {
165 case SkTextureCompressionType::kNone:
166 return baseDimensions.width() * baseDimensions.height();
167 case SkTextureCompressionType::kETC2_RGB8_UNORM:
168 case SkTextureCompressionType::kBC1_RGB8_UNORM:
169 case SkTextureCompressionType::kBC1_RGBA8_UNORM: {
170 int numBlocksWidth = num_4x4_blocks(baseDimensions.width());
171 int numBlocksHeight = num_4x4_blocks(baseDimensions.height());
172
173 return numBlocksWidth * numBlocksHeight;
174 }
175 }
176 SkUNREACHABLE;
177 }
178
CompressedRowBytes(SkTextureCompressionType type,int width)179 size_t CompressedRowBytes(SkTextureCompressionType type, int width) {
180 switch (type) {
181 case SkTextureCompressionType::kNone:
182 return 0;
183 case SkTextureCompressionType::kETC2_RGB8_UNORM:
184 case SkTextureCompressionType::kBC1_RGB8_UNORM:
185 case SkTextureCompressionType::kBC1_RGBA8_UNORM: {
186 int numBlocksWidth = num_4x4_blocks(width);
187
188 static_assert(sizeof(ETC1Block) == sizeof(BC1Block));
189 return numBlocksWidth * sizeof(ETC1Block);
190 }
191 }
192 SkUNREACHABLE;
193 }
194
CompressedDimensions(SkTextureCompressionType type,SkISize baseDimensions)195 SkISize CompressedDimensions(SkTextureCompressionType type, SkISize baseDimensions) {
196 switch (type) {
197 case SkTextureCompressionType::kNone:
198 return baseDimensions;
199 case SkTextureCompressionType::kETC2_RGB8_UNORM:
200 case SkTextureCompressionType::kBC1_RGB8_UNORM:
201 case SkTextureCompressionType::kBC1_RGBA8_UNORM: {
202 SkISize blockDims = CompressedDimensionsInBlocks(type, baseDimensions);
203 // Each BC1_RGB8_UNORM and ETC1 block has 16 pixels
204 return { 4 * blockDims.fWidth, 4 * blockDims.fHeight };
205 }
206 }
207 SkUNREACHABLE;
208 }
209
CompressedDimensionsInBlocks(SkTextureCompressionType type,SkISize baseDimensions)210 SkISize CompressedDimensionsInBlocks(SkTextureCompressionType type, SkISize baseDimensions) {
211 switch (type) {
212 case SkTextureCompressionType::kNone:
213 return baseDimensions;
214 case SkTextureCompressionType::kETC2_RGB8_UNORM:
215 case SkTextureCompressionType::kBC1_RGB8_UNORM:
216 case SkTextureCompressionType::kBC1_RGBA8_UNORM: {
217 int numBlocksWidth = num_4x4_blocks(baseDimensions.width());
218 int numBlocksHeight = num_4x4_blocks(baseDimensions.height());
219
220 // Each BC1_RGB8_UNORM and ETC1 block has 16 pixels
221 return { numBlocksWidth, numBlocksHeight };
222 }
223 }
224 SkUNREACHABLE;
225 }
226
227 // Fill in 'dest' with ETC1 blocks derived from 'colorf'
fillin_ETC1_with_color(SkISize dimensions,const SkColor4f & colorf,char * dest)228 static void fillin_ETC1_with_color(SkISize dimensions, const SkColor4f& colorf, char* dest) {
229 SkColor color = colorf.toSkColor();
230
231 ETC1Block block;
232 create_etc1_block(color, &block);
233
234 int numBlocks = num_ETC1_blocks(dimensions.width(), dimensions.height());
235
236 for (int i = 0; i < numBlocks; ++i) {
237 memcpy(dest, &block, sizeof(ETC1Block));
238 dest += sizeof(ETC1Block);
239 }
240 }
241
242 // Fill in 'dest' with BC1 blocks derived from 'colorf'
fillin_BC1_with_color(SkISize dimensions,const SkColor4f & colorf,char * dest)243 static void fillin_BC1_with_color(SkISize dimensions, const SkColor4f& colorf, char* dest) {
244 SkColor color = colorf.toSkColor();
245
246 BC1Block block;
247 create_BC1_block(color, color, &block);
248
249 int numBlocks = num_ETC1_blocks(dimensions.width(), dimensions.height());
250
251 for (int i = 0; i < numBlocks; ++i) {
252 memcpy(dest, &block, sizeof(BC1Block));
253 dest += sizeof(BC1Block);
254 }
255 }
256
FillInCompressedData(SkTextureCompressionType type,SkISize dimensions,skgpu::Mipmapped mipmapped,char * dstPixels,const SkColor4f & colorf)257 void FillInCompressedData(SkTextureCompressionType type,
258 SkISize dimensions,
259 skgpu::Mipmapped mipmapped,
260 char* dstPixels,
261 const SkColor4f& colorf) {
262 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
263
264 int numMipLevels = 1;
265 if (mipmapped == skgpu::Mipmapped::kYes) {
266 numMipLevels = SkMipmap::ComputeLevelCount(dimensions.width(), dimensions.height()) + 1;
267 }
268
269 size_t offset = 0;
270
271 for (int i = 0; i < numMipLevels; ++i) {
272 size_t levelSize = SkCompressedDataSize(type, dimensions, nullptr, false);
273
274 if (SkTextureCompressionType::kETC2_RGB8_UNORM == type) {
275 fillin_ETC1_with_color(dimensions, colorf, &dstPixels[offset]);
276 } else {
277 SkASSERT(type == SkTextureCompressionType::kBC1_RGB8_UNORM ||
278 type == SkTextureCompressionType::kBC1_RGBA8_UNORM);
279 fillin_BC1_with_color(dimensions, colorf, &dstPixels[offset]);
280 }
281
282 offset += levelSize;
283 dimensions = {std::max(1, dimensions.width()/2), std::max(1, dimensions.height()/2)};
284 }
285 }
286
287 } // namespace skgpu
288