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