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