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