Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Implements Basis Universal / KTX2 driver.
5 : * Author: Even Rouault, <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2022, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_multiproc.h"
14 : #include "gdal_frmts.h"
15 : #include "common.h"
16 : #include "include_basisu_sdk.h"
17 :
18 : #include <algorithm>
19 : #include <mutex>
20 :
21 : /************************************************************************/
22 : /* GDALInitBasisUTranscoder() */
23 : /************************************************************************/
24 :
25 12 : void GDALInitBasisUTranscoder()
26 : {
27 : static std::once_flag flag;
28 12 : std::call_once(flag, basist::basisu_transcoder_init);
29 12 : }
30 :
31 : /************************************************************************/
32 : /* GDALInitBasisUEncoder() */
33 : /************************************************************************/
34 :
35 78 : void GDALInitBasisUEncoder()
36 : {
37 : static std::once_flag flag;
38 80 : std::call_once(flag, []() { basisu::basisu_encoder_init(); });
39 78 : }
40 :
41 : /************************************************************************/
42 : /* GDALRegister_BASISU_KTX2() */
43 : /* */
44 : /* This function exists so that when built as a plugin, there */
45 : /* is a function that will register both drivers. */
46 : /************************************************************************/
47 :
48 11 : void GDALRegister_BASISU_KTX2()
49 : {
50 11 : GDALRegister_BASISU();
51 11 : GDALRegister_KTX2();
52 11 : }
53 :
54 : /************************************************************************/
55 : /* GDAL_KTX2_BASISU_CreateCopy() */
56 : /************************************************************************/
57 :
58 110 : bool GDAL_KTX2_BASISU_CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
59 : bool bIsKTX2, CSLConstList papszOptions,
60 : GDALProgressFunc pfnProgress,
61 : void *pProgressData)
62 : {
63 110 : const int nBands = poSrcDS->GetRasterCount();
64 110 : if (nBands == 0 || nBands > 4)
65 : {
66 8 : CPLError(CE_Failure, CPLE_NotSupported,
67 : "Only band count >= 1 and <= 4 is supported");
68 8 : return false;
69 : }
70 102 : if (poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte)
71 : {
72 22 : CPLError(CE_Failure, CPLE_NotSupported,
73 : "Only Byte data type supported");
74 22 : return false;
75 : }
76 :
77 80 : const int nXSize = poSrcDS->GetRasterXSize();
78 80 : const int nYSize = poSrcDS->GetRasterYSize();
79 80 : void *pSrcData = VSI_MALLOC3_VERBOSE(nXSize, nYSize, nBands);
80 80 : if (pSrcData == nullptr)
81 0 : return false;
82 :
83 160 : if (poSrcDS->RasterIO(GF_Read, 0, 0, nXSize, nYSize, pSrcData, nXSize,
84 : nYSize, GDT_Byte, nBands, nullptr, nBands,
85 80 : static_cast<GSpacing>(nBands) * nXSize, 1,
86 80 : nullptr) != CE_None)
87 : {
88 2 : VSIFree(pSrcData);
89 2 : return false;
90 : }
91 :
92 156 : basisu::image img;
93 : try
94 : {
95 78 : img.init(static_cast<const uint8_t *>(pSrcData), nXSize, nYSize,
96 : nBands);
97 78 : VSIFree(pSrcData);
98 : }
99 0 : catch (const std::exception &e)
100 : {
101 0 : VSIFree(pSrcData);
102 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
103 0 : return false;
104 : }
105 :
106 78 : GDALInitBasisUEncoder();
107 :
108 78 : const bool bVerbose = CPLTestBool(CPLGetConfigOption("KTX2_VERBOSE", "NO"));
109 :
110 156 : basisu::basis_compressor_params params;
111 78 : params.m_create_ktx2_file = bIsKTX2;
112 :
113 78 : params.m_source_images.push_back(img);
114 :
115 78 : params.m_perceptual = EQUAL(
116 : CSLFetchNameValueDef(papszOptions, "COLORSPACE", "PERCEPTUAL_SRGB"),
117 : "PERCEPTUAL_SRGB");
118 :
119 78 : params.m_write_output_basis_files = true;
120 :
121 156 : std::string osTempFilename;
122 78 : bool bUseTempFilename = STARTS_WITH(pszFilename, "/vsi");
123 : #ifdef _WIN32
124 : if (!bUseTempFilename)
125 : {
126 : bUseTempFilename = !CPLIsASCII(pszFilename, static_cast<size_t>(-1));
127 : }
128 : #endif
129 78 : if (bUseTempFilename)
130 : {
131 64 : osTempFilename = CPLGenerateTempFilenameSafe(nullptr);
132 64 : CPLDebug("KTX2", "Using temporary file %s", osTempFilename.c_str());
133 64 : params.m_out_filename = osTempFilename;
134 : }
135 : else
136 : {
137 14 : params.m_out_filename = pszFilename;
138 : }
139 :
140 78 : params.m_uastc = EQUAL(
141 : CSLFetchNameValueDef(papszOptions, "COMPRESSION", "ETC1S"), "UASTC");
142 78 : if (params.m_uastc)
143 : {
144 12 : if (bIsKTX2)
145 : {
146 7 : const char *pszSuperCompression = CSLFetchNameValueDef(
147 : papszOptions, "UASTC_SUPER_COMPRESSION", "ZSTD");
148 7 : params.m_ktx2_uastc_supercompression =
149 7 : EQUAL(pszSuperCompression, "ZSTD") ? basist::KTX2_SS_ZSTANDARD
150 : : basist::KTX2_SS_NONE;
151 : }
152 :
153 : const int nLevel =
154 36 : std::min(std::max(0, atoi(CSLFetchNameValueDef(
155 : papszOptions, "UASTC_LEVEL", "2"))),
156 12 : static_cast<int>(basisu::TOTAL_PACK_UASTC_LEVELS - 1));
157 : static const uint32_t anLevelFlags[] = {
158 : basisu::cPackUASTCLevelFastest, basisu::cPackUASTCLevelFaster,
159 : basisu::cPackUASTCLevelDefault, basisu::cPackUASTCLevelSlower,
160 : basisu::cPackUASTCLevelVerySlow};
161 12 : CPL_STATIC_ASSERT(CPL_ARRAYSIZE(anLevelFlags) ==
162 : basisu::TOTAL_PACK_UASTC_LEVELS);
163 12 : params.m_pack_uastc_flags &= ~basisu::cPackUASTCLevelMask;
164 12 : params.m_pack_uastc_flags |= anLevelFlags[nLevel];
165 :
166 : const char *pszUASTC_RDO_LEVEL =
167 12 : CSLFetchNameValue(papszOptions, "UASTC_RDO_LEVEL");
168 12 : if (pszUASTC_RDO_LEVEL)
169 : {
170 4 : params.m_rdo_uastc_quality_scalar =
171 4 : static_cast<float>(CPLAtof(pszUASTC_RDO_LEVEL));
172 4 : params.m_rdo_uastc = true;
173 : }
174 :
175 48 : for (const char *pszOption :
176 : {"ETC1S_LEVEL", "ETC1S_QUALITY_LEVEL",
177 60 : "ETC1S_MAX_SELECTOR_CLUSTERS", "ETC1S_MAX_SELECTOR_CLUSTERS"})
178 : {
179 48 : if (CSLFetchNameValue(papszOptions, pszOption) != nullptr)
180 : {
181 0 : CPLError(CE_Warning, CPLE_AppDefined,
182 : "%s ignored for COMPRESSION=UASTC", pszOption);
183 : }
184 : }
185 : }
186 : else
187 : {
188 : // CPL_STATIC_ASSERT(basisu::BASISU_MIN_COMPRESSION_LEVEL == 0);
189 66 : CPL_STATIC_ASSERT(basisu::BASISU_MAX_COMPRESSION_LEVEL == 6);
190 66 : params.m_compression_level =
191 198 : std::min(std::max(0, atoi(CSLFetchNameValueDef(
192 : papszOptions, "ETC1S_LEVEL", "1"))),
193 66 : static_cast<int>(basisu::BASISU_MAX_COMPRESSION_LEVEL));
194 66 : CPL_STATIC_ASSERT(basisu::BASISU_QUALITY_MIN == 1);
195 66 : CPL_STATIC_ASSERT(basisu::BASISU_QUALITY_MAX == 255);
196 : const char *pszQualityLevel =
197 66 : CSLFetchNameValue(papszOptions, "ETC1S_QUALITY_LEVEL");
198 66 : params.m_quality_level =
199 132 : std::min(std::max(static_cast<int>(basisu::BASISU_QUALITY_MIN),
200 66 : atoi(pszQualityLevel ? pszQualityLevel : "128")),
201 132 : static_cast<int>(basisu::BASISU_QUALITY_MAX));
202 66 : params.m_max_endpoint_clusters = 0;
203 66 : params.m_max_selector_clusters = 0;
204 :
205 : const char *pszMaxEndpointClusters =
206 66 : CSLFetchNameValue(papszOptions, "ETC1S_MAX_ENDPOINTS_CLUSTERS");
207 : const char *pszMaxSelectorClusters =
208 66 : CSLFetchNameValue(papszOptions, "ETC1S_MAX_SELECTOR_CLUSTERS");
209 66 : if (pszQualityLevel == nullptr && (pszMaxEndpointClusters != nullptr ||
210 : pszMaxSelectorClusters != nullptr))
211 : {
212 8 : params.m_quality_level = -1;
213 8 : if (pszMaxEndpointClusters != nullptr)
214 : {
215 6 : params.m_max_endpoint_clusters = atoi(pszMaxEndpointClusters);
216 6 : if (pszMaxSelectorClusters == nullptr)
217 : {
218 2 : CPLError(CE_Failure, CPLE_AppDefined,
219 : "ETC1S_MAX_SELECTOR_CLUSTERS must be set when "
220 : "ETC1S_MAX_ENDPOINTS_CLUSTERS is set");
221 2 : return false;
222 : }
223 : }
224 :
225 6 : if (pszMaxSelectorClusters != nullptr)
226 : {
227 6 : params.m_max_selector_clusters = atoi(pszMaxSelectorClusters);
228 6 : if (pszMaxEndpointClusters == nullptr)
229 : {
230 2 : CPLError(CE_Failure, CPLE_AppDefined,
231 : "ETC1S_MAX_ENDPOINTS_CLUSTERS must be set when "
232 : "ETC1S_MAX_SELECTOR_CLUSTERS is set");
233 2 : return false;
234 : }
235 : }
236 : }
237 : else
238 : {
239 116 : for (const char *pszOption : {"ETC1S_MAX_ENDPOINTS_CLUSTERS",
240 174 : "ETC1S_MAX_SELECTOR_CLUSTERS"})
241 : {
242 116 : if (CSLFetchNameValue(papszOptions, pszOption) != nullptr)
243 : {
244 4 : CPLError(CE_Warning, CPLE_AppDefined,
245 : "%s ignored when ETC1S_QUALITY_LEVEL is specified",
246 : pszOption);
247 : }
248 : }
249 : }
250 :
251 186 : for (const char *pszOption : {"UASTC_LEVEL", "UASTC_RDO_LEVEL"})
252 : {
253 124 : if (CSLFetchNameValue(papszOptions, pszOption) != nullptr)
254 : {
255 0 : CPLError(CE_Warning, CPLE_AppDefined,
256 : "%s ignored for COMPRESSION=ETC1S", pszOption);
257 : }
258 : }
259 : }
260 :
261 74 : if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "MIPMAP", "NO")))
262 : {
263 2 : params.m_mip_gen = true;
264 2 : params.m_mip_srgb = params.m_perceptual;
265 : }
266 :
267 : const int nNumThreads = std::max(
268 74 : 1, atoi(CSLFetchNameValueDef(
269 : papszOptions, "NUM_THREADS",
270 : CPLGetConfigOption("GDAL_NUM_THREADS",
271 74 : CPLSPrintf("%d", CPLGetNumCPUs())))));
272 74 : CPLDebug("KTX2", "Using %d threads", nNumThreads);
273 74 : if (params.m_uastc)
274 : {
275 12 : params.m_rdo_uastc_multithreading = nNumThreads > 1;
276 : }
277 74 : params.m_multithreading = nNumThreads > 1;
278 74 : params.m_debug = bVerbose;
279 74 : params.m_status_output = bVerbose;
280 74 : params.m_compute_stats = bVerbose;
281 :
282 148 : basisu::job_pool jpool(nNumThreads);
283 74 : params.m_pJob_pool = &jpool;
284 :
285 148 : basisu::basis_compressor comp;
286 74 : basisu::enable_debug_printf(bVerbose);
287 :
288 74 : if (!comp.init(params))
289 : {
290 0 : CPLError(CE_Failure, CPLE_AppDefined,
291 : "basis_compressor::init() failed");
292 0 : return false;
293 : }
294 :
295 74 : const basisu::basis_compressor::error_code result = comp.process();
296 74 : if (result != basisu::basis_compressor::cECSuccess)
297 : {
298 6 : CPLError(CE_Failure, CPLE_AppDefined,
299 : "basis_compressor::process() failed");
300 6 : return false;
301 : }
302 :
303 68 : if (!osTempFilename.empty())
304 : {
305 58 : if (CPLCopyFile(pszFilename, osTempFilename.c_str()) != 0)
306 : {
307 20 : VSIUnlink(osTempFilename.c_str());
308 20 : return false;
309 : }
310 38 : VSIUnlink(osTempFilename.c_str());
311 : }
312 :
313 48 : if (pfnProgress)
314 48 : pfnProgress(1.0, "", pProgressData);
315 :
316 48 : return true;
317 : }
|