Saving the texture with the original mips. [C++]

My friend gave me the code that I modified to work in the console, but ran into a problem when I needed to add a new flag. It was needed to save the original Mip-Maps. I mean, I have a dds texture with modified Mip-Maps and I need to save it with a script saving the original Mip-Maps, and not generate new ones. By creating different Mip-Maps, you can achieve a special visual effect. I did not see in the documentation that there was such a possibility at all, can you help in solving such a situation, if necessary, I can send all the files. The code is attached below.

#include "NVTT/nvtt.h"
#include <stdio.h>
#include <iostream>
#include <filesystem>
#include <cstring>
#include <regex>

using namespace std;
using namespace nvtt;

namespace fs = filesystem;

#pragma comment(lib,"NVTT/nvtt")

bool peresvet = false;
//bool saveMipmaps = false;

void nvttBC3(const char* input, const char* output, bool srgb = false)
{
    Surface image;
    OutputOptions outputOptions;
    Context context;
    CompressionOptions compressionOptions;

    bool s = true;
    image.load(input, &s);
    compressionOptions.setFormat(Format_BC3);
    outputOptions.setContainer(Container_DDS10);
    outputOptions.setFileName(output);
    const int numMipmaps = image.countMipmaps() - 1;

    if (srgb) {
        if (peresvet)
            image.toSrgb();
        outputOptions.setSrgbFlag(true);
    }

    if (!context.outputHeader(image, numMipmaps, compressionOptions, outputOptions)) {
        cerr << "[error] Writing the DDS header failed!\n";
        return;
    }

    for (int mip = 0; mip <= numMipmaps; mip++) {
        if (!context.compress(image, 0, mip, compressionOptions, outputOptions)) {
            cerr << "[error] Compressing and writing the DDS file failed!\n";
            return;
        }

        if (mip == numMipmaps) break;
        //if (mip == numMipmaps || saveMipmaps) break;
        image.buildNextMipmap(MipmapFilter_Box);
    }
}

int main(int argc, char** argv)
{
    vector<string> args(argv, argv + argc);
    string inputFilePath;
    string outputFilePath;
    string action;

    for (size_t i = 1; i < args.size(); ++i) {
        if (args[i] == "-i" && i + 1 < args.size()) {
            inputFilePath = args[i + 1];
            i++;
        }
        else if (args[i] == "-a" && i + 1 < args.size()) {
            action = args[i + 1];
            i++;
        }
        else if (args[i] == "-o" && i + 1 < args.size()) {
            outputFilePath = args[i + 1];
            i++;
        }
        //else if (args[i] == "-sm" && i + 1 < args.size()) {
        //    saveMipmaps = (args[i + 1] == "true");
        //    i++;
        //} 
    }

    if (inputFilePath.empty() || action.empty() || outputFilePath.empty()) {
        cerr << "Usage: NVTTConverter.exe -i <input_file> -a <action> -o <output_file>" << endl;
        cerr << "       -i  <input_file>   - Source file in TGA format." << endl;
        cerr << "       -a  <action>       - The action to use. (\"DT - Default texture\" or \"BC - Base Color\")" << endl;
        cerr << "       -o  <output_file>  - Where to save a file in DDS format." << endl;
        //cerr << "       -sm <save_mipmaps> - Whether to save the original mipmaps (\"true\" or \"false\")." << endl;
        return 1;
    }

    if (!fs::exists(inputFilePath)) {
        cerr << "[error] Input file does not exist: " << inputFilePath << endl;
        return 1;
    }

    fs::path outputPath(outputFilePath);
    fs::path outputDir = outputPath.parent_path();

    if (!fs::exists(outputDir)) {
        cerr << "[error] Output directory does not exist: " << outputDir.string() << endl;
        return 1;
    }

    bool isBC = false;
    bool needSwap = false;

    if (action == "DT") {
        needSwap = true;
    }
    else if (action == "BC") {
        isBC = true;
    }
    else {
        cerr << "[error] Unknown action: " << action << endl;
        return 1;
    }

    nvttBC3(inputFilePath.c_str(), outputFilePath.c_str(), isBC);
    cout << "[nvtt]  Conversion complete!" << endl;

    return 0;
}

Hi @sartakovanton163 ! This is absolutely possible with NVTT. The main idea is that instead of calling nvtt::Context::compress() and nvtt::Surface::buildNextMipmap() in a loop, like this:

for (int mip = 0; mip <= numMipmaps; mip++) {
    if (!context.compress(image, 0, mip, compressionOptions, outputOptions)) {
        cerr << "[error] Compressing and writing the DDS file failed!\n";
        return;
    }

    if (mip == numMipmaps) break;
    image.buildNextMipmap(MipmapFilter_Box);
}

you’d instead pass a different nvtt::Surface to the compress() function each time, like this:

std::vector<nvtt::Surface> images;
// Not shown: load images from multiple files...
// Not shown: check that images have the right sizes...
// Note: the number of mipmaps includes the base mip (mip 0), so the numMipmaps line should be
const int numMipmaps = image.countMipmaps(); 
// Not shown: set up compression options, output options, and output header
// (same as what you have above)

// Note that I changed <= to < here:
for (int mip = 0; mip < numMipmaps; mip++) {
    if (!context.compress(images[mip], 0, mip, compressionOptions, outputOptions)) {
        cerr << "[error] Compressing and writing the DDS file failed!\n";
        return;
    }
}

You’ll also need some logic to let the user input the file name for each mip - I’ll leave it up to you to decide how you want to do this and code this (e.g. should the user list each file name in order? should the user provide a directory, and the app will find the right file order for the mip chain?).

Once you have that though, the logic for the check that images have the right sizes... step looks like this:

#include <algorithm>
// ...
const int mip0Width  = images[0].width();
const int mip0Height = images[0].height();
const int mip0Depth  = images[0].depth();
for(int mip = 1; mip < numMipmaps; mip++) {
  const int expectedWidth  = std::max(1, mip0Width >> mip);
  const int expectedHeight = std::max(1, mip0Height >> mip);
  const int expectedDepth  = std::max(1, mip0Depth >> mip);
  if(images[mip].width() != expectedWidth
     || images[mip].height() != expectedHeight
     || images[mip].depth() != expectedDepth) {
    std::cerr << "[error] Mip " << mip << " should have had size ("
      << expectedWidth << ", " << expectedHeight << ", " << expectedDepth
      << "), but it actually had size ("
      << images[mip].width() << ", " << images[mip].height() << ", " << images[mip].depth() << ")!\n");
    return;
  }
}

Hope this helps! Let me know if you need any additional help.

Thanks! But there is such a question. And how realistic is it to download and save mipmaps from an existing dds texture or “texture scans” from TGA? I can attach a file, it is created by a new plugin. I don’t quite know how to implement this better. But it seems to be convenient to work with a file that already has all the mipmaps. I have a problem with C++ programming, so I turned to the forum.

images.zip (13.0 MB)

That is, I mean, is it possible to download all the mipmaps from the 2d scan of E-100-MIPS.tga? Thank you in advance for your help!

And I’ll probably attach the entire program…
NVTTConverter.zip (39.3 MB)