1 /*
2 * Copyright (C) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "astc_codec.h"
17 #ifdef ENABLE_ASTC_ENCODE_BASED_GPU
18 #include "image_compressor.h"
19 #endif
20 #include "image_log.h"
21 #include "image_system_properties.h"
22 #include "securec.h"
23 #include "media_errors.h"
24
25 #undef LOG_DOMAIN
26 #define LOG_DOMAIN LOG_TAG_DOMAIN_ID_PLUGIN
27
28 #undef LOG_TAG
29 #define LOG_TAG "AstcCodec"
30
31 namespace OHOS {
32 namespace ImagePlugin {
33 using namespace Media;
34 #ifdef ENABLE_ASTC_ENCODE_BASED_GPU
35 using namespace AstcEncBasedCl;
36 #endif
37 constexpr uint8_t TEXTURE_HEAD_BYTES = 16;
38 constexpr uint8_t ASTC_MASK = 0xFF;
39 constexpr uint8_t ASTC_NUM_8 = 8;
40 constexpr uint8_t ASTC_HEADER_SIZE = 16;
41 constexpr uint8_t ASTC_NUM_24 = 24;
42 static const uint32_t ASTC_MAGIC_ID = 0x5CA1AB13;
43 constexpr uint8_t DEFAULT_DIM = 4;
44 constexpr uint8_t HIGH_SPEED_PROFILE_MAP_QUALITY = 20; // quality level is 20 for thumbnail
45
SetAstcEncode(OutputDataStream * outputStream,PlEncodeOptions & option,Media::PixelMap * pixelMap)46 uint32_t AstcCodec::SetAstcEncode(OutputDataStream* outputStream, PlEncodeOptions &option, Media::PixelMap* pixelMap)
47 {
48 if (outputStream == nullptr || pixelMap == nullptr) {
49 IMAGE_LOGE("input data is nullptr.");
50 return ERROR;
51 }
52 astcOutput_ = outputStream;
53 astcOpts_ = option;
54 astcPixelMap_ = pixelMap;
55 return SUCCESS;
56 }
57
58 // test ASTCEncoder
GenAstcHeader(uint8_t * header,astcenc_image img,TextureEncodeOptions * encodeParams,size_t size)59 uint32_t GenAstcHeader(uint8_t *header, astcenc_image img, TextureEncodeOptions *encodeParams, size_t size)
60 {
61 if ((encodeParams == nullptr) || (header == nullptr) || size < ASTC_HEADER_SIZE) {
62 IMAGE_LOGE("header is nullptr or encodeParams is nullptr or header_size is error");
63 return ERROR;
64 }
65 uint8_t *tmp = header;
66 *tmp++ = ASTC_MAGIC_ID & ASTC_MASK;
67 *tmp++ = (ASTC_MAGIC_ID >> ASTC_NUM_8) & ASTC_MASK;
68 *tmp++ = (ASTC_MAGIC_ID >> ASTC_HEADER_SIZE) & ASTC_MASK;
69 *tmp++ = (ASTC_MAGIC_ID >> ASTC_NUM_24) & ASTC_MASK;
70 *tmp++ = static_cast<uint8_t>(encodeParams->blockX_);
71 *tmp++ = static_cast<uint8_t>(encodeParams->blockY_);
72 *tmp++ = 1;
73 *tmp++ = img.dim_x & ASTC_MASK;
74 *tmp++ = (img.dim_x >> ASTC_NUM_8) & ASTC_MASK;
75 *tmp++ = (img.dim_x >> ASTC_HEADER_SIZE) & ASTC_MASK;
76 *tmp++ = img.dim_y & ASTC_MASK;
77 *tmp++ = (img.dim_y >> ASTC_NUM_8) & ASTC_MASK;
78 *tmp++ = (img.dim_y >> ASTC_HEADER_SIZE) & ASTC_MASK;
79 *tmp++ = img.dim_z & ASTC_MASK;
80 *tmp++ = (img.dim_z >> ASTC_NUM_8) & ASTC_MASK;
81 *tmp++ = (img.dim_z >> ASTC_HEADER_SIZE) & ASTC_MASK;
82 return SUCCESS;
83 }
84
InitAstcencConfig(AstcEncoder * work,TextureEncodeOptions * option)85 uint32_t InitAstcencConfig(AstcEncoder* work, TextureEncodeOptions* option)
86 {
87 if ((work == nullptr) || (option == nullptr)) {
88 IMAGE_LOGE("astc input work or option is nullptr.");
89 return ERROR;
90 }
91 unsigned int blockX = option->blockX_;
92 unsigned int blockY = option->blockY_;
93 unsigned int blockZ = 1;
94
95 float quality = ASTCENC_PRE_FAST;
96 unsigned int flags = ASTCENC_FLG_SELF_DECOMPRESS_ONLY;
97 astcenc_error status = astcenc_config_init(work->profile, blockX, blockY,
98 blockZ, quality, flags, &work->config);
99 if (status == ASTCENC_ERR_BAD_BLOCK_SIZE) {
100 IMAGE_LOGE("ERROR: block size is invalid");
101 return ERROR;
102 } else if (status == ASTCENC_ERR_BAD_CPU_FLOAT) {
103 IMAGE_LOGE("ERROR: astcenc must not be compiled with fast-math");
104 return ERROR;
105 } else if (status != ASTCENC_SUCCESS) {
106 IMAGE_LOGE("ERROR: config failed");
107 return ERROR;
108 }
109 work->config.privateProfile = option->privateProfile_;
110 if (work->config.privateProfile == HIGH_SPEED_PROFILE) {
111 work->config.tune_refinement_limit = 1;
112 work->config.tune_candidate_limit = 1;
113 work->config.tune_partition_count_limit = 1;
114 }
115 if (astcenc_context_alloc(&work->config, 1, &work->codec_context) != ASTCENC_SUCCESS) {
116 return ERROR;
117 }
118 return SUCCESS;
119 }
120
extractDimensions(std::string & format,TextureEncodeOptions & param)121 void extractDimensions(std::string &format, TextureEncodeOptions ¶m)
122 {
123 param.blockX_ = DEFAULT_DIM;
124 param.blockY_ = DEFAULT_DIM;
125 std::size_t slashPos = format.rfind('/');
126 if (slashPos != std::string::npos) {
127 std::string dimensions = format.substr(slashPos + 1);
128 std::size_t starPos = dimensions.find('*');
129 if (starPos != std::string::npos) {
130 std::string widthStr = dimensions.substr(0, starPos);
131 std::string heightStr = dimensions.substr(starPos + 1);
132
133 param.blockX_ = static_cast<uint8_t>(std::stoi(widthStr));
134 param.blockY_ = static_cast<uint8_t>(std::stoi(heightStr));
135 }
136 }
137 }
138
139 #if defined(QUALITY_CONTROL) && (QUALITY_CONTROL == 1)
140 constexpr double MAX_PSNR = 99.9;
141 constexpr double MAX_VALUE = 255;
142 constexpr double THRESHOLD_R = 30.0;
143 constexpr double THRESHOLD_G = 30.0;
144 constexpr double THRESHOLD_B = 30.0;
145 constexpr double THRESHOLD_A = 30.0;
146 constexpr double THRESHOLD_RGB = 30.0;
147 constexpr double LOG_BASE = 10.0;
CheckQuality(int32_t * mseIn[RGBA_COM],int blockNum,int blockXYZ)148 bool CheckQuality(int32_t *mseIn[RGBA_COM], int blockNum, int blockXYZ)
149 {
150 double psnr[RGBA_COM + 1];
151 const double threshold[RGBA_COM + 1] = {THRESHOLD_R, THRESHOLD_G, THRESHOLD_B, THRESHOLD_A, THRESHOLD_RGB};
152 uint64_t mseTotal[RGBA_COM + 1] = {0, 0, 0, 0, 0};
153 for (int i = R_COM; i < RGBA_COM; i++) {
154 int32_t *mse = mseIn[i];
155 if (!mse) {
156 return false;
157 }
158 for (int j = 0; j < blockNum; j++) {
159 mseTotal[i] += *mse;
160 if (i != A_COM) mseTotal[RGBA_COM] += *mse;
161 mse++;
162 }
163 }
164 for (int i = R_COM; i < RGBA_COM; i++) {
165 if (mseTotal[i] == 0) {
166 psnr[i] = MAX_PSNR;
167 continue;
168 }
169 double mseRgb = static_cast<double>(mseTotal[i] / (blockNum * blockXYZ));
170 psnr[i] = LOG_BASE * log(static_cast<double>(MAX_VALUE * MAX_VALUE) / mseRgb) / log(LOG_BASE);
171 }
172 if (mseTotal[RGBA_COM] == 0) {
173 psnr[RGBA_COM] = MAX_PSNR;
174 } else {
175 double mseRgb = static_cast<double>(mseTotal[RGBA_COM] / (blockNum * blockXYZ * (RGBA_COM - 1)));
176 psnr[RGBA_COM] = LOG_BASE * log(static_cast<double>(MAX_VALUE * MAX_VALUE) / mseRgb) / log(LOG_BASE);
177 }
178 IMAGE_LOGD("astc psnr r%{public}f g%{public}f b%{public}f a%{public}f rgb%{public}f",
179 psnr[R_COM], psnr[G_COM], psnr[B_COM], psnr[A_COM],
180 psnr[RGBA_COM]);
181 return (psnr[R_COM] > threshold[R_COM]) && (psnr[G_COM] > threshold[G_COM])
182 && (psnr[B_COM] > threshold[B_COM]) && (psnr[A_COM] > threshold[A_COM])
183 && (psnr[RGBA_COM] > threshold[RGBA_COM]);
184 }
185 #endif
186
FreeMem(AstcEncoder * work)187 static void FreeMem(AstcEncoder *work)
188 {
189 if (!work) {
190 return;
191 }
192 #if defined(QUALITY_CONTROL) && (QUALITY_CONTROL == 1)
193 if (work->calQualityEnable) {
194 for (int i = R_COM; i < RGBA_COM; i++) {
195 if (work->mse[i]) {
196 free(work->mse[i]);
197 work->mse[i] = nullptr;
198 }
199 }
200 }
201 #endif
202 if (work->image_.data) {
203 free(work->image_.data);
204 work->image_.data = nullptr;
205 }
206 if (work->codec_context != nullptr) {
207 astcenc_context_free(work->codec_context);
208 work->codec_context = nullptr;
209 }
210 }
211
InitMem(AstcEncoder * work,TextureEncodeOptions param,bool enableQualityCheck,int blockNum)212 static bool InitMem(AstcEncoder *work, TextureEncodeOptions param, bool enableQualityCheck, int blockNum)
213 {
214 if (!work) {
215 return false;
216 }
217 work->swizzle_ = {ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A};
218 work->image_.dim_x = param.width_;
219 work->image_.dim_y = param.height_;
220 work->image_.dim_z = 1;
221 work->image_.data_type = ASTCENC_TYPE_U8;
222 work->image_.dim_stride = param.stride_;
223 work->codec_context = nullptr;
224 work->image_.data = nullptr;
225 work->profile = ASTCENC_PRF_LDR_SRGB;
226 #if defined(QUALITY_CONTROL) && (QUALITY_CONTROL == 1)
227 work->mse[R_COM] = work->mse[G_COM] = work->mse[B_COM] = work->mse[RGBA_COM] = nullptr;
228 work->calQualityEnable = enableQualityCheck;
229 if (work->calQualityEnable) {
230 for (int i = R_COM; i < RGBA_COM; i++) {
231 work->mse[i] = static_cast<int32_t *>(calloc(blockNum, sizeof(int32_t)));
232 if (!work->mse[i]) {
233 IMAGE_LOGE("quality control calloc failed");
234 return false;
235 }
236 }
237 }
238 #endif
239 work->image_.data = static_cast<void **>(malloc(sizeof(void*) * work->image_.dim_z));
240 if (!work->image_.data) {
241 return false;
242 }
243 return true;
244 }
245
246 constexpr uint8_t RGBA_BYTES_PIXEL_LOG2 = 2;
247
AstcSoftwareEncode(TextureEncodeOptions & param,bool enableQualityCheck,int32_t blocksNum,int32_t outSize)248 uint32_t AstcCodec::AstcSoftwareEncode(TextureEncodeOptions ¶m, bool enableQualityCheck,
249 int32_t blocksNum, int32_t outSize)
250 {
251 AstcEncoder work;
252 if (!InitMem(&work, param, enableQualityCheck, blocksNum)) {
253 FreeMem(&work);
254 return ERROR;
255 }
256 if (InitAstcencConfig(&work, ¶m) != SUCCESS) {
257 IMAGE_LOGE("astc InitAstcencConfig failed");
258 FreeMem(&work);
259 return ERROR;
260 }
261 work.image_.data[0] = static_cast<uint8_t *>(astcPixelMap_->GetWritablePixels());
262 work.data_out_ = astcOutput_->GetAddr();
263 size_t size;
264 astcOutput_->GetCapicity(size);
265 if (GenAstcHeader(work.data_out_, work.image_, ¶m, size) != SUCCESS) {
266 IMAGE_LOGE("astc GenAstcHeader failed");
267 FreeMem(&work);
268 return ERROR;
269 }
270 work.error_ = astcenc_compress_image(work.codec_context, &work.image_, &work.swizzle_,
271 work.data_out_ + TEXTURE_HEAD_BYTES, outSize - TEXTURE_HEAD_BYTES,
272 #if defined(QUALITY_CONTROL) && (QUALITY_CONTROL == 1)
273 work.calQualityEnable, work.mse,
274 #endif
275 0);
276 #if defined(QUALITY_CONTROL) && (QUALITY_CONTROL == 1)
277 if ((ASTCENC_SUCCESS != work.error_) ||
278 (work.calQualityEnable && !CheckQuality(work.mse, blocksNum, param.blockX_ * param.blockY_))) {
279 #else
280 if (ASTCENC_SUCCESS != work.error_) {
281 #endif
282 IMAGE_LOGE("astc compress failed");
283 FreeMem(&work);
284 return ERROR;
285 }
286 FreeMem(&work);
287 return SUCCESS;
288 }
289
290 static QualityProfile GetAstcQuality(int32_t quality)
291 {
292 QualityProfile privateProfile;
293 switch (quality) {
294 case HIGH_SPEED_PROFILE_MAP_QUALITY:
295 privateProfile = HIGH_SPEED_PROFILE;
296 break;
297 default:
298 privateProfile = HIGH_QUALITY_PROFILE;
299 break;
300 }
301 return privateProfile;
302 }
303
304 #ifdef ENABLE_ASTC_ENCODE_BASED_GPU
305 static bool TryAstcEncBasedOnCl(uint8_t *inData, int32_t stride, TextureEncodeOptions *param,
306 uint8_t *buffer, const std::string &clBinPath)
307 {
308 ClAstcHandle *astcClEncoder = nullptr;
309 if ((inData == nullptr) || (param == nullptr) || (buffer == nullptr)) {
310 IMAGE_LOGE("astc Please check TryAstcEncBasedOnCl input!");
311 return false;
312 }
313 if (AstcClCreate(&astcClEncoder, clBinPath) != CL_ASTC_ENC_SUCCESS) {
314 IMAGE_LOGE("astc AstcClCreate failed!");
315 return false;
316 }
317 ClAstcImageOption imageIn;
318 if (AstcClFillImage(&imageIn, inData, stride, param->width_, param->height_) != CL_ASTC_ENC_SUCCESS) {
319 IMAGE_LOGE("astc AstcClFillImage failed!");
320 AstcClClose(astcClEncoder);
321 return false;
322 }
323 if (AstcClEncImage(astcClEncoder, &imageIn, buffer) != CL_ASTC_ENC_SUCCESS) {
324 IMAGE_LOGE("astc AstcClEncImage failed!");
325 AstcClClose(astcClEncoder);
326 return false;
327 }
328 if (AstcClClose(astcClEncoder) != CL_ASTC_ENC_SUCCESS) {
329 IMAGE_LOGE("astc AstcClClose failed!");
330 return false;
331 }
332 return true;
333 }
334 #endif
335
336 uint32_t AstcCodec::ASTCEncode()
337 {
338 ImageInfo imageInfo;
339 astcPixelMap_->GetImageInfo(imageInfo);
340 TextureEncodeOptions param;
341 param.width_ = imageInfo.size.width;
342 param.height_ = imageInfo.size.height;
343 param.stride_ = astcPixelMap_->GetRowStride() >> RGBA_BYTES_PIXEL_LOG2;
344 param.privateProfile_ = GetAstcQuality(astcOpts_.quality);
345 bool enableQualityCheck = false; // astcOpts_.enableQualityCheck
346 bool hardwareFlag = false;
347 extractDimensions(astcOpts_.format, param);
348 int32_t blocksNum = ((param.width_ + param.blockX_ - 1) / param.blockX_) *
349 ((param.height_ + param.blockY_ - 1) / param.blockY_);
350 int32_t outSize = blocksNum * TEXTURE_HEAD_BYTES + TEXTURE_HEAD_BYTES;
351
352 #ifdef ENABLE_ASTC_ENCODE_BASED_GPU
353 if (ImageSystemProperties::GetAstcHardWareEncodeEnabled() &&
354 (param.blockX_ == DEFAULT_DIM) && (param.blockY_ == DEFAULT_DIM)) { // HardWare only support 4x4 now
355 IMAGE_LOGI("astc hardware encode begin");
356 std::string clBinPath = "/data/local/tmp/astcKernelBin.bin";
357 if (TryAstcEncBasedOnCl(static_cast<uint8_t *>(astcPixelMap_->GetWritablePixels()),
358 astcPixelMap_->GetRowStride(), ¶m, astcOutput_->GetAddr(), clBinPath)) {
359 hardwareFlag = true;
360 IMAGE_LOGI("astc hardware encode success!");
361 } else {
362 IMAGE_LOGI("astc hardware encode failed!");
363 }
364 }
365 #endif
366 if (!hardwareFlag) {
367 uint32_t res = AstcSoftwareEncode(param, enableQualityCheck, blocksNum, outSize);
368 if (res != SUCCESS) {
369 IMAGE_LOGE("AstcSoftwareEncode failed");
370 return ERROR;
371 }
372 IMAGE_LOGD("astc software encode success!");
373 }
374 IMAGE_LOGD("astc hardwareFlag %{public}d, enableQualityCheck %{public}d, privateProfile %{public}d",
375 hardwareFlag, enableQualityCheck, param.privateProfile_);
376 astcOutput_->SetOffset(outSize);
377 return SUCCESS;
378 }
379 } // namespace ImagePlugin
380 } // namespace OHOS