LCOV - code coverage report
Current view: top level - frmts/basisu_ktx2 - common.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 125 134 93.3 %
Date: 2025-10-19 15:46:27 Functions: 5 5 100.0 %

          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             : }

Generated by: LCOV version 1.14