From 961426d5a702a9fa513ed35069b892c77ef47377 Mon Sep 17 00:00:00 2001 From: Argent77 Date: Sun, 31 Aug 2014 15:55:16 +0200 Subject: [PATCH 1/6] Fixed "halt on error" option and minor updates --- tileconv/README | 5 +++-- tileconv/convert.cpp | 4 ++++ tileconv/options.cpp | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tileconv/README b/tileconv/README index 6c90e7d..4101740 100644 --- a/tileconv/README +++ b/tileconv/README @@ -71,11 +71,12 @@ CONTACT ------- If you have questions or comments please post them on Spellhold Studios -at http://www.shsforums.net/ or contact me by private message on the same forum. +at http://www.shsforums.net/topic/57588-tileconv-a-mostis-compressor/ +or contact me by private message on the same forum. Version history --------------- -v0.1 (2014-08-27) +v0.1 (2014-08-30) * Initial release diff --git a/tileconv/convert.cpp b/tileconv/convert.cpp index 02b5dfa..25b596f 100644 --- a/tileconv/convert.cpp +++ b/tileconv/convert.cpp @@ -87,6 +87,7 @@ bool Convert::execute() noexcept if (!getOptions().isSilent()) { std::printf("Converting TIS -> TBC\n"); std::printf("Input: \"%s\", output: \"%s\"\n", inputFile.c_str(), outputFile.c_str()); + std::printf("Converting...\n"); } if (!gfx.tisToTBC(inputFile, outputFile)) { retVal = false; @@ -116,6 +117,7 @@ bool Convert::execute() noexcept if (!getOptions().isSilent()) { std::printf("Converting MOS -> MBC\n"); std::printf("Input: \"%s\", output: \"%s\"\n", inputFile.c_str(), outputFile.c_str()); + std::printf("Converting...\n"); } if (!gfx.mosToMBC(inputFile, outputFile)) { retVal = false; @@ -145,6 +147,7 @@ bool Convert::execute() noexcept if (!getOptions().isSilent()) { std::printf("Converting TBC -> TIS\n"); std::printf("Input: \"%s\", output: \"%s\"\n", inputFile.c_str(), outputFile.c_str()); + std::printf("Converting...\n"); } if (!gfx.tbcToTIS(inputFile, outputFile)) { retVal = false; @@ -174,6 +177,7 @@ bool Convert::execute() noexcept if (!getOptions().isSilent()) { std::printf("Converting MBC -> MOS\n"); std::printf("Input: \"%s\", output: \"%s\"\n", inputFile.c_str(), outputFile.c_str()); + std::printf("Converting...\n"); } if (!gfx.mbcToMOS(inputFile, outputFile)) { retVal = false; diff --git a/tileconv/options.cpp b/tileconv/options.cpp index 8f36f5f..2033bfc 100644 --- a/tileconv/options.cpp +++ b/tileconv/options.cpp @@ -76,7 +76,7 @@ bool Options::init(int argc, char *argv[]) noexcept switch (argv[i][1]) { case 'e': - setHaltOnError(true); + setHaltOnError(false); break; case 's': setSilence(2); From a449e0e3cbedf5c06b66b90f4261d696aad7ab16 Mon Sep 17 00:00:00 2001 From: Argent77 Date: Sun, 31 Aug 2014 22:30:31 +0200 Subject: [PATCH 2/6] Bugfix in setSpeed() and some clean-ups --- tileconv/colorquant.cpp | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/tileconv/colorquant.cpp b/tileconv/colorquant.cpp index 2d572e5..4dac781 100644 --- a/tileconv/colorquant.cpp +++ b/tileconv/colorquant.cpp @@ -124,7 +124,6 @@ bool ColorQuant::quantize() noexcept if ((m_liqResult = liq_quantize_image(m_liqAttr, m_liqImage)) == nullptr) { return false; } - liq_set_dithering_level(m_liqResult, m_dithering ? 1.0f : 0.0f); if (LIQ_OK != liq_write_remapped_image(m_liqResult, m_liqImage, m_target, m_targetSize)) { return false; @@ -153,39 +152,31 @@ bool ColorQuant::quantize() noexcept void ColorQuant::setMaxColors(int colors) noexcept { - if (colors < 2) colors = 2; - if (colors > 256) colors = 256; - m_maxColors = colors; + m_maxColors = std::max(2, std::min(256, colors)); } void ColorQuant::setQuality(int min, int max) noexcept { - if (min > max) { int tmp = min; min = max; max = tmp; } - if (min < 0) min = 0; if (min > max) min = max; - if (max > 100) max = 100; - if (max < min) max = min; + min = std::max(0, std::min(100, min)); + max = std::max(0, std::min(100, max)); + if (min > max) std::swap(min, max); m_qualityMin = min; m_qualityMax = max; } void ColorQuant::setSpeed(int speed) noexcept { - if (speed < 0) speed = 0; - if (speed > 10) speed = 10; + m_speed = std::max(1, std::min(10, speed)); } void ColorQuant::setMinOpacity(int min) noexcept { - if (min < 0) min = 0; - if (min > 255) min = 255; - m_minOpacity = min; + m_minOpacity = std::max(0, std::min(255, min)); } void ColorQuant::setPosterization(int bits) noexcept { - if (bits < 0) bits = 0; - if (bits > 4) bits = 4; - m_posterization = bits; + m_posterization = std::max(0, std::min(4, bits)); } double ColorQuant::getQuantizationError() noexcept From f851a255878580cf69444b46b6d72500ce1b9441 Mon Sep 17 00:00:00 2001 From: Argent77 Date: Sun, 31 Aug 2014 22:58:36 +0200 Subject: [PATCH 3/6] Added multithreading capabilities and minor improvements --- tileconv/Makefile | 5 +- tileconv/README | 12 +- tileconv/colors.cpp | 3 +- tileconv/graphics.cpp | 291 ++++++++++++++++++++++++++++-------- tileconv/graphics.h | 7 +- tileconv/options.cpp | 108 +++++++++---- tileconv/options.h | 27 ++-- tileconv/tiledata.cpp | 7 +- tileconv/tiledata.h | 4 +- tileconv/tilethreadpool.cpp | 164 ++++++++++++++++++++ tileconv/tilethreadpool.h | 94 ++++++++++++ 11 files changed, 606 insertions(+), 116 deletions(-) create mode 100644 tileconv/tilethreadpool.cpp create mode 100644 tileconv/tilethreadpool.h diff --git a/tileconv/Makefile b/tileconv/Makefile index 353fd72..033fdf6 100644 --- a/tileconv/Makefile +++ b/tileconv/Makefile @@ -7,8 +7,9 @@ include config.mk CXX ?= g++ CXXFLAGS = -c -Wall -O2 -std=c++11 -I$(ZLIB_INCLUDE) -I$(SQUISH_INCLUDE) -I$(PNGQUANT_INCLUDE) LDFLAGS = -L$(ZLIB_LIB) -L$(SQUISH_LIB) -L$(PNGQUANT_LIB) -SOURCES = tileconv.cpp convert.cpp version.cpp graphics.cpp tiledata.cpp compress.cpp colors.cpp fileio.cpp colorquant.cpp options.cpp -LIBS =-lz -lsquish -limagequant +SOURCES = tileconv.cpp convert.cpp version.cpp graphics.cpp tilethreadpool.cpp tiledata.cpp compress.cpp colors.cpp fileio.cpp colorquant.cpp options.cpp +SYSLIBS = -lpthread +LIBS =-lz -lsquish -limagequant $(SYSLIBS) OBJECTS = $(SOURCES:.cpp=.o) EXECUTABLE = tileconv ifeq ($(OS),Windows_NT) diff --git a/tileconv/README b/tileconv/README index 4101740..e9b65fb 100644 --- a/tileconv/README +++ b/tileconv/README @@ -1,6 +1,7 @@ TILECONV ~~~~~~~~ +Version: 0.1 Author: Argent77 This tool allows you to compress or decompress TIS and MOS files provided by @@ -34,8 +35,17 @@ Options: 3: BC3/DXT5 -u Do not apply tile compression. -o outfile Select output file. (Works with single input file only!) - -d Use color dithering when converting to TIS or MOS. -z MOS only: Decompress MBC into compressed MOS (MOSC). + -d Enable color dithering. (deprecated, use -q instead!) + -q level Specify quality vs. speed ratio when converting MBC->MOS + or TBC->TIS. Supported levels: 0..9 (Default: 4) + (0=fast and lower quality, 9=slow and higher quality) + Applied level-dependent features: + Dithering: levels 5 to 9 + Posterization: levels 0 to 2 + Additional techniques: levels 4 to 9 + -j num Number of parallel jobs to speed up the conversion process. + Valid numbers: 0 (autodetect), 1..256 (Default: 0) -V Print version number and exit. Supported input file types: TIS, MOS, TBC, MBC diff --git a/tileconv/colors.cpp b/tileconv/colors.cpp index e3525df..f8d605e 100644 --- a/tileconv/colors.cpp +++ b/tileconv/colors.cpp @@ -67,8 +67,7 @@ uint32_t Colors::ARGBToPal(uint8_t *src, uint8_t *dst, uint8_t *palette, if (!quant.setSource(src, width, height)) return 0; if (!quant.setTarget(dst, size)) return 0; if (!quant.setPalette(palette, 1024)) return 0; -// quant.setPosterization(2); // optimizing for RGB565 colors - quant.setDithering(getOptions().isDithering()); + quant.setSpeed(10 - getOptions().getQuality()); // speed is defined as "10 - quality" if (!quant.quantize()) return 0; if (getOptions().isVerbose()) { diff --git a/tileconv/graphics.cpp b/tileconv/graphics.cpp index c82fd1f..6fd1f24 100644 --- a/tileconv/graphics.cpp +++ b/tileconv/graphics.cpp @@ -28,6 +28,7 @@ THE SOFTWARE. #include "graphics.h" #include "colors.h" #include "compress.h" +#include "tilethreadpool.h" const char Graphics::HEADER_TIS_SIGNATURE[4] = {'T', 'I', 'S', ' '}; @@ -126,15 +127,35 @@ bool Graphics::tisToTBC(const std::string &inFile, const std::string &outFile) n if (!getOptions().isSilent()) std::printf("Tile count: %d\n", tileCount); // converting tiles - BytePtr ptrIndexed(new uint8_t[MAX_TILE_SIZE_8], std::default_delete()); - BytePtr ptrPalette(new uint8_t[PALETTE_SIZE], std::default_delete()); - BytePtr ptrDeflated(new uint8_t[MAX_TILE_SIZE_32*2], std::default_delete()); + TileThreadPool pool(*this, getOptions().getThreads(), 64); + unsigned nextTileIdx = 0; double ratioCount = 0.0; // counts the compression ratios of all tiles for (unsigned tileIdx = 0; tileIdx < tileCount; tileIdx++) { if (getOptions().isVerbose()) std::printf("Converting tile #%d\n", tileIdx); + // writing converted tiles to disk + while (pool.hasResult() && pool.peekResult() != nullptr && + (unsigned)pool.peekResult()->index == nextTileIdx) { + TileDataPtr retVal = pool.getResult(); + if (retVal == nullptr || retVal->error) { + if (retVal != nullptr && !retVal->errorMsg.empty()) { + std::printf("%s", retVal->errorMsg.c_str()); + } + return false; + } + double ratio = 0.0; + if (!writeEncodedTile(retVal, fout, ratio)) { + return false; + } + ratioCount += ratio; + nextTileIdx++; + } + // creating new tile data object - TileDataPtr tileData(new TileData(tileIdx, ptrIndexed, ptrPalette, ptrDeflated, + BytePtr ptrIndexed(new uint8_t[MAX_TILE_SIZE_8], std::default_delete()); + BytePtr ptrPalette(new uint8_t[PALETTE_SIZE], std::default_delete()); + BytePtr ptrDeflated(new uint8_t[MAX_TILE_SIZE_32*2], std::default_delete()); + TileDataPtr tileData(new TileData(true, tileIdx, ptrIndexed, ptrPalette, ptrDeflated, tileDim, tileDim, 0, 0)); // reading paletted tile if (fin.read(tileData->ptrPalette.get(), 1, PALETTE_SIZE) != PALETTE_SIZE) { @@ -144,20 +165,39 @@ bool Graphics::tisToTBC(const std::string &inFile, const std::string &outFile) n return false; } - // encoding tile - if (!encodeTile(tileData)) { - if (!tileData->errorMsg.empty()) { - std::printf("%s", tileData->errorMsg.c_str()); - } - return false; + // Wait until another tile data block can be added to the thread pool + while (!pool.canAddTileData()) { +// std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); } + pool.addTileData(tileData); + } - // writing result to output file - double ratio = 0.0; - if (!writeEncodedTile(tileData, fout, ratio)) { - return false; + // retrieving the remaining tile data blocks in queue + while (!pool.finished()) { + while (pool.hasResult() && pool.peekResult() != nullptr && + (unsigned)pool.peekResult()->index == nextTileIdx) { + TileDataPtr retVal = pool.getResult(); + if (retVal == nullptr || retVal->error) { + if (retVal != nullptr && !retVal->errorMsg.empty()) { + std::printf("%s", retVal->errorMsg.c_str()); + } + return false; + } + double ratio = 0.0; + if (!writeEncodedTile(retVal, fout, ratio)) { + return false; + } + ratioCount += ratio; + nextTileIdx++; } - ratioCount += ratio; +// std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + if (nextTileIdx < tileCount) { + std::printf("Missing tiles. Only %d of %d tiles converted.\n", nextTileIdx, tileCount); + return false; } // displaying summary @@ -228,10 +268,25 @@ bool Graphics::tbcToTIS(const std::string &inFile, const std::string &outFile) n tileCount, compType, Options::GetEncodingName(compType).c_str()); } - BytePtr ptrIndexed(new uint8_t[MAX_TILE_SIZE_8], std::default_delete()); - BytePtr ptrPalette(new uint8_t[PALETTE_SIZE], std::default_delete()); - BytePtr ptrDeflated(new uint8_t[MAX_TILE_SIZE_32*2], std::default_delete()); + TileThreadPool pool(*this, getOptions().getThreads(), 64); + uint32_t nextTileIdx = 0; for (uint32_t tileIdx = 0; tileIdx < tileCount; tileIdx++) { + // writing converted tiles to disk + while (pool.hasResult() && pool.peekResult() != nullptr && + (unsigned)pool.peekResult()->index == nextTileIdx) { + TileDataPtr retVal = pool.getResult(); + if (retVal == nullptr || retVal->error) { + if (retVal != nullptr && !retVal->errorMsg.empty()) { + std::printf("%s", retVal->errorMsg.c_str()); + } + return false; + } + if (!writeDecodedTisTile(retVal, fout)) { + return false; + } + nextTileIdx++; + } + // creating new tile data object uint32_t chunkSize; if (fin.read(&chunkSize, 4, 1) != 1) return false; @@ -240,22 +295,46 @@ bool Graphics::tbcToTIS(const std::string &inFile, const std::string &outFile) n std::printf("Invalid block size found for tile #%d\n", tileIdx); return false; } - TileDataPtr tileData(new TileData(tileIdx, ptrIndexed, ptrPalette, ptrDeflated, + BytePtr ptrIndexed(new uint8_t[MAX_TILE_SIZE_8], std::default_delete()); + BytePtr ptrPalette(new uint8_t[PALETTE_SIZE], std::default_delete()); + BytePtr ptrDeflated(new uint8_t[MAX_TILE_SIZE_32*2], std::default_delete()); + TileDataPtr tileData(new TileData(false, tileIdx, ptrIndexed, ptrPalette, ptrDeflated, 0, 0, compType, chunkSize)); if (fin.read(tileData->ptrDeflated.get(), 1, chunkSize) != chunkSize) { return false; } - if (!decodeTile(tileData)) { - if (!tileData->errorMsg.empty()) { - std::printf("%s", tileData->errorMsg.c_str()); - } - return false; + // Wait until another tile data block can be added to the thread pool + while (!pool.canAddTileData()) { +// std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); } + pool.addTileData(tileData); + } - if (!writeDecodedTisTile(tileData, fout)) { - return false; + // retrieving the remaining tile data blocks in queue + while (!pool.finished()) { + while (pool.hasResult() && pool.peekResult() != nullptr && + (unsigned)pool.peekResult()->index == nextTileIdx) { + TileDataPtr retVal = pool.getResult(); + if (retVal == nullptr || retVal->error) { + if (retVal != nullptr && !retVal->errorMsg.empty()) { + std::printf("%s", retVal->errorMsg.c_str()); + } + return false; + } + if (!writeDecodedTisTile(retVal, fout)) { + return false; + } + nextTileIdx++; } +// std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + if (nextTileIdx < tileCount) { + std::printf("Missing tiles. Only %d of %d tiles converted.\n", nextTileIdx, tileCount); + return false; } // displaying summary @@ -424,9 +503,8 @@ bool Graphics::mosToMBC(const std::string &inFile, const std::string &outFile) n if (getOptions().isVerbose()) std::printf("Tile count: %d\n", tileCount); // processing tiles - BytePtr ptrIndexed(new uint8_t[MAX_TILE_SIZE_8], std::default_delete()); - BytePtr ptrPalette(new uint8_t[PALETTE_SIZE], std::default_delete()); - BytePtr ptrDeflated(new uint8_t[MAX_TILE_SIZE_32*2], std::default_delete()); + TileThreadPool pool(*this, getOptions().getThreads(), 64); + unsigned nextTileIdx = 0; double ratioCount = 0.0; // counts the compression ratios of all tiles int curIndex = 0; uint32_t remTileHeight = mosHeight; // remaining tile height to cover @@ -439,8 +517,29 @@ bool Graphics::mosToMBC(const std::string &inFile, const std::string &outFile) n if (getOptions().isVerbose()) std::printf("Converting tile #%d\n", curIndex); + // writing converted tiles to disk + while (pool.hasResult() && pool.peekResult() != nullptr && + (unsigned)pool.peekResult()->index == nextTileIdx) { + TileDataPtr retVal = pool.getResult(); + if (retVal == nullptr || retVal->error) { + if (retVal != nullptr && !retVal->errorMsg.empty()) { + std::printf("%s", retVal->errorMsg.c_str()); + } + return false; + } + double ratio = 0.0; + if (!writeEncodedTile(retVal, fout, ratio)) { + return false; + } + ratioCount += ratio; + nextTileIdx++; + } + // creating new tile data object - TileDataPtr tileData(new TileData(curIndex, ptrIndexed, ptrPalette, ptrDeflated, + BytePtr ptrIndexed(new uint8_t[MAX_TILE_SIZE_8], std::default_delete()); + BytePtr ptrPalette(new uint8_t[PALETTE_SIZE], std::default_delete()); + BytePtr ptrDeflated(new uint8_t[MAX_TILE_SIZE_32*2], std::default_delete()); + TileDataPtr tileData(new TileData(true, curIndex, ptrIndexed, ptrPalette, ptrDeflated, tileWidth, tileHeight, 0, 0)); // reading paletted tile std::memcpy(tileData->ptrPalette.get(), mosData.get()+palOfs, PALETTE_SIZE); @@ -451,19 +550,40 @@ bool Graphics::mosToMBC(const std::string &inFile, const std::string &outFile) n tileOfs += 4; std::memcpy(tileData->ptrIndexed.get(), mosData.get()+dataOfs+v32, tileSizeIndexed); - if (!encodeTile(tileData)) { - if (!tileData->errorMsg.empty()) { - std::printf("%s", tileData->errorMsg.c_str()); + // Wait until another tile data block can be added to the thread pool + while (!pool.canAddTileData()) { +// std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + pool.addTileData(tileData); + } + } + + // retrieving the remaining tile data blocks in queue + while (!pool.finished()) { + while (pool.hasResult() && pool.peekResult() != nullptr && + (unsigned)pool.peekResult()->index == nextTileIdx) { + TileDataPtr retVal = pool.getResult(); + if (retVal == nullptr || retVal->error) { + if (retVal != nullptr && !retVal->errorMsg.empty()) { + std::printf("%s", retVal->errorMsg.c_str()); } return false; } - double ratio = 0.0; - if (!writeEncodedTile(tileData, fout, ratio)) { + if (!writeEncodedTile(retVal, fout, ratio)) { return false; } ratioCount += ratio; + nextTileIdx++; } +// std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + if (nextTileIdx < tileCount) { + std::printf("Missing tiles. Only %d of %d tiles converted.\n", nextTileIdx, tileCount); + return false; } // displaying summary @@ -564,14 +684,30 @@ bool Graphics::mbcToMOS(const std::string &inFile, const std::string &outFile) n mosWidth, mosHeight, mosCols, mosRows, compType, Options::GetEncodingName(compType).c_str()); } - BytePtr ptrIndexed(new uint8_t[MAX_TILE_SIZE_8], std::default_delete()); - BytePtr ptrPalette(new uint8_t[PALETTE_SIZE], std::default_delete()); - BytePtr ptrDeflated(new uint8_t[MAX_TILE_SIZE_32*2], std::default_delete()); + TileThreadPool pool(*this, getOptions().getThreads(), 64); + uint32_t tileCount = mosCols * mosRows; + uint32_t nextTileIdx = 0; int curIndex = 0; // the current tile index for (uint32_t row = 0; row < mosRows; row++) { for (uint32_t col = 0; col < mosCols; col++, curIndex++) { if (getOptions().isVerbose()) std::printf("Decoding tile #%d\n", curIndex); + // writing converted tiles to disk + while (pool.hasResult() && pool.peekResult() != nullptr && + (unsigned)pool.peekResult()->index == nextTileIdx) { + TileDataPtr retVal = pool.getResult(); + if (retVal == nullptr || retVal->error) { + if (retVal != nullptr && !retVal->errorMsg.empty()) { + std::printf("%s", retVal->errorMsg.c_str()); + } + return false; + } + if (!writeDecodedMosTile(retVal, mosData, palOfs, tileOfs, dataOfsRel, dataOfsBase)) { + return false; + } + nextTileIdx++; + } + // creating new tile data object uint32_t chunkSize; if (fin.read(&chunkSize, 4, 1) != 1) return false; @@ -580,23 +716,47 @@ bool Graphics::mbcToMOS(const std::string &inFile, const std::string &outFile) n std::printf("Invalid block size found for tile #%d\n", curIndex); return false; } - TileDataPtr tileData(new TileData(curIndex, ptrIndexed, ptrPalette, ptrDeflated, + BytePtr ptrIndexed(new uint8_t[MAX_TILE_SIZE_8], std::default_delete()); + BytePtr ptrPalette(new uint8_t[PALETTE_SIZE], std::default_delete()); + BytePtr ptrDeflated(new uint8_t[MAX_TILE_SIZE_32*2], std::default_delete()); + TileDataPtr tileData(new TileData(false, curIndex, ptrIndexed, ptrPalette, ptrDeflated, 0, 0, compType, chunkSize)); if (fin.read(tileData->ptrDeflated.get(), 1, chunkSize) != chunkSize) { return false; } - if (!decodeTile(tileData)) { - if (!tileData->errorMsg.empty()) { - std::printf("%s", tileData->errorMsg.c_str()); + // Wait until another tile data block can be added to the thread pool + while (!pool.canAddTileData()) { +// std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + pool.addTileData(tileData); + } + } + + // retrieving the remaining tile data blocks in queue + while (!pool.finished()) { + while (pool.hasResult() && pool.peekResult() != nullptr && + (unsigned)pool.peekResult()->index == nextTileIdx) { + TileDataPtr retVal = pool.getResult(); + if (retVal == nullptr || retVal->error) { + if (retVal != nullptr && !retVal->errorMsg.empty()) { + std::printf("%s", retVal->errorMsg.c_str()); } return false; } - - if (!writeDecodedMosTile(tileData, mosData, palOfs, tileOfs, dataOfsRel, dataOfsBase)) { + if (!writeDecodedMosTile(retVal, mosData, palOfs, tileOfs, dataOfsRel, dataOfsBase)) { return false; } + nextTileIdx++; } +// std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + if (nextTileIdx < tileCount) { + std::printf("Missing tiles. Only %d of %d tiles converted.\n", nextTileIdx, tileCount); + return false; } if (getOptions().isMosc()) { @@ -724,9 +884,10 @@ bool Graphics::writeDecodedMosTile(TileDataPtr tileData, BytePtr mosData, uint32 } -bool Graphics::encodeTile(TileDataPtr tileData) noexcept +TileDataPtr Graphics::encodeTile(TileDataPtr tileData) noexcept { - if (tileData->ptrIndexed != nullptr && tileData->ptrPalette != nullptr && + if (tileData != nullptr && + tileData->ptrIndexed != nullptr && tileData->ptrPalette != nullptr && tileData->ptrDeflated != nullptr && tileData->index >= 0 && tileData->tileWidth > 0 && tileData->tileHeight > 0) { BytePtr ptrEncoded(new uint8_t[MAX_TILE_SIZE_32], std::default_delete()); @@ -736,7 +897,6 @@ bool Graphics::encodeTile(TileDataPtr tileData) noexcept uint32_t tileSizeEncoded; // pixel encoded size of the tile int squishFlags = squish::kColourIterativeClusterFit; Colors colors(getOptions()); - Compression compression; tileData->size = 0; @@ -795,20 +955,23 @@ bool Graphics::encodeTile(TileDataPtr tileData) noexcept // converting tile to ARGB if (colors.palToARGB(tileData->ptrIndexed.get(), tileData->ptrPalette.get(), ptrARGB.get(), tileSizePixels) != tileSizePixels) { + tileData->error = true; tileData->errorMsg.assign("Error during color space conversion\n"); - return false; + return tileData; } // padding tile if needed if (colors.padBlock(ptrARGB.get(), ptrARGB2.get(), tileData->tileWidth, tileData->tileHeight, tileWidthPadded, tileHeightPadded) != tileSizePixelsPadded) { + tileData->error = true; tileData->errorMsg.assign("Error while encoding pixels\n"); - return false; + return tileData; } // preparing correct color components order if (colors.reorderColors(ptrARGB2.get(), tileSizePixelsPadded, Colors::FMT_ARGB, Colors::FMT_ABGR) < tileSizePixelsPadded) { + tileData->error = true; tileData->errorMsg.assign("Error while encoding pixels\n"); - return false; + return tileData; } uint8_t *argbPtr = ptrARGB2.get(); uint8_t *encodedPtr = ptrEncoded.get(); @@ -825,11 +988,13 @@ bool Graphics::encodeTile(TileDataPtr tileData) noexcept if (getOptions().isDeflate()) { // applying zlib compression + Compression compression; tileData->size = compression.deflate(ptrEncoded.get(), tileSizeEncoded+HEADER_TILE_ENCODED_SIZE, tileData->ptrDeflated.get(), MAX_TILE_SIZE_32*2); if (tileData->size == 0) { + tileData->error = true; tileData->errorMsg.assign("Error while compressing tile data\n"); - return false; + return tileData; } } else { // using pixel encoding only @@ -837,15 +1002,16 @@ bool Graphics::encodeTile(TileDataPtr tileData) noexcept std::memcpy(tileData->ptrDeflated.get(), ptrEncoded.get(), tileData->size); } - return true; + return tileData; } - return false; + return TileDataPtr(nullptr); } -bool Graphics::decodeTile(TileDataPtr tileData) noexcept +TileDataPtr Graphics::decodeTile(TileDataPtr tileData) noexcept { - if (tileData->ptrIndexed != nullptr && tileData->ptrPalette != nullptr && + if (tileData != nullptr && + tileData->ptrIndexed != nullptr && tileData->ptrPalette != nullptr && tileData->ptrDeflated != nullptr && tileData->index >= 0 && tileData->size > 0) { BytePtr ptrARGB(new uint8_t[MAX_TILE_SIZE_32], std::default_delete()); BytePtr ptrARGB2(new uint8_t[MAX_TILE_SIZE_32], std::default_delete()); @@ -854,10 +1020,10 @@ bool Graphics::decodeTile(TileDataPtr tileData) noexcept uint32_t tileWidth, tileHeight; uint32_t tileSizeIndexed; Colors colors(getOptions()); - Compression compression; if (Options::IsTileDeflated(tileData->encodingType)) { // inflating zlib compressed data + Compression compression; compression.inflate(tileData->ptrDeflated.get(), tileData->size, ptrEncoded.get(), MAX_TILE_SIZE_32); } else { // copy pixel encoded tile data @@ -901,27 +1067,30 @@ bool Graphics::decodeTile(TileDataPtr tileData) noexcept uint32_t tileSizeARGB = tileWidthPadded*tileHeightPadded; if (colors.reorderColors(ptrARGB2.get(), tileSizeARGB, Colors::FMT_ABGR, Colors::FMT_ARGB) < tileSizeARGB) { + tileData->error = true; tileData->errorMsg.assign("Error while decoding pixels\n"); - return false; + return tileData; } // unpadding tile block if (colors.unpadBlock(ptrARGB2.get(), ptrARGB.get(), tileWidthPadded, tileHeightPadded, tileWidth, tileHeight) != tileSizeIndexed) { + tileData->error = true; tileData->errorMsg.assign("Error while decoding pixels\n"); - return false; + return tileData; } // applying color reduction if (colors.ARGBToPal(ptrARGB.get(), tileData->ptrIndexed.get(), tileData->ptrPalette.get(), tileWidth, tileHeight) != tileSizeIndexed) { + tileData->error = true; tileData->errorMsg.assign("Error while decoding pixels\n"); - return false; + return tileData; } } tileData->size = tileSizeIndexed + PALETTE_SIZE; - return true; + return tileData; } - return false; + return TileDataPtr(nullptr); } diff --git a/tileconv/graphics.h b/tileconv/graphics.h index e9db462..3bbcc48 100644 --- a/tileconv/graphics.h +++ b/tileconv/graphics.h @@ -21,7 +21,6 @@ THE SOFTWARE. */ #ifndef GRAPHICS_H #define GRAPHICS_H - #include #include "types.h" #include "options.h" @@ -60,10 +59,10 @@ class Graphics uint32_t &tileOfs, uint32_t &dataOfsRel, uint32_t dataOfsBase) noexcept; /** Encodes a single tile. Returns success state. */ - bool encodeTile(TileDataPtr tileData) noexcept; + TileDataPtr encodeTile(TileDataPtr tileData) noexcept; /** Decodes a single tile. Returns success state. */ - bool decodeTile(TileDataPtr tileData) noexcept; + TileDataPtr decodeTile(TileDataPtr tileData) noexcept; private: static const char HEADER_TIS_SIGNATURE[4]; // TIS signature @@ -84,6 +83,8 @@ class Graphics static const unsigned MAX_TILE_SIZE_32; // max. size (in bytes) of a 32-bit pixels tile const Options& m_options; + + friend class TileThreadPool; }; diff --git a/tileconv/options.cpp b/tileconv/options.cpp index 2033bfc..1a1e150 100644 --- a/tileconv/options.cpp +++ b/tileconv/options.cpp @@ -22,6 +22,7 @@ THE SOFTWARE. #include #include #include "fileio.h" +#include "tilethreadpool.h" #include "version.h" #include "options.h" @@ -31,17 +32,19 @@ const int Options::DEFLATE = 256; const bool Options::DEF_HALT_ON_ERROR = true; const bool Options::DEF_MOSC = false; -const bool Options::DEF_DITHERING = false; const bool Options::DEF_DEFLATE = true; const int Options::DEF_SILENT = 1; +const int Options::DEF_QUALITY = 4; +const int Options::DEF_THREADS = 0; // autodetect const Encoding Options::DEF_ENCODING = Encoding::BC1; Options::Options() noexcept : m_haltOnError(DEF_HALT_ON_ERROR) , m_mosc(DEF_MOSC) -, m_dithering(DEF_DITHERING) , m_deflate(DEF_DEFLATE) , m_silent(DEF_SILENT) +, m_quality(DEF_QUALITY) +, m_threads(DEF_THREADS) , m_encoding(DEF_ENCODING) , m_inFiles() , m_outFile() @@ -108,22 +111,31 @@ bool Options::init(int argc, char *argv[]) noexcept setDeflate(false); break; case 'd': - setDithering(true); + std::printf("Parameter -d is deprecated. Use -q instead!"); + break; + case 'q': + if (i < argc - 1) { + int level = std::atoi(argv[i+1]); + setQuality(level); + i++; // skip level argument + } else { + showHelp(); + return false; + } break; case 'z': setMosc(true); break; -// case 'j': -// if (i < argc - 1) { -// int jobs = std::atoi(argv[i+1]); -// jobs = std::max(0, std::min(MAX_THREADS, jobs)); // limiting jobs to a sane number -// setThreads(jobs); -// i++; // skip jobs argument -// } else { -// showHelp(); -// return false; -// } -// break; + case 'j': + if (i < argc - 1) { + int jobs = std::atoi(argv[i+1]); + setThreads(jobs); + i++; // skip jobs argument + } else { + showHelp(); + return false; + } + break; case 'V': std::printf("%s %d.%d by %s\n", prog_name, vers_major, vers_minor, author); return false; @@ -187,9 +199,17 @@ void Options::showHelp() noexcept std::printf(" 3: BC3/DXT5\n"); std::printf(" -u Do not apply tile compression.\n"); std::printf(" -o outfile Select output file. (Works with single input file only!)\n"); - std::printf(" -d Use color dithering when converting to TIS or MOS.\n"); std::printf(" -z MOS only: Decompress MBC into compressed MOS (MOSC).\n"); -// std::printf(" -j num Number of threads to use for the conversion process (0=auto)"); + std::printf(" -d Enable color dithering. (deprecated, use -q instead!)\n"); + std::printf(" -q level Specify quality vs. speed ratio when converting MBC->MOS\n"); + std::printf(" or TBC->TIS. Supported levels: 0..9 (Default: 4)\n"); + std::printf(" (0=fast and lower quality, 9=slow and higher quality)\n"); + std::printf(" Applied level-dependent features:\n"); + std::printf(" Dithering: levels 5 to 9\n"); + std::printf(" Posterization: levels 0 to 2\n"); + std::printf(" Additional techniques: levels 4 to 9\n"); + std::printf(" -j num Number of parallel jobs to speed up the conversion process.\n"); + std::printf(" Valid numbers: 0 (autodetect), 1..%d (Default: 0)\n", TileThreadPool::MAX_THREADS); std::printf(" -V Print version number and exit.\n\n"); std::printf("Supported input file types: TIS, MOS, TBC, MBC\n"); std::printf("Note: You can mix and match input files of each supported type.\n\n"); @@ -255,10 +275,23 @@ void Options::setSilence(int level) noexcept } -//void Options::setThreads(int v) noexcept -//{ -// m_threads = std::max(0, std::min(MAX_THREADS, v)); -//} +void Options::setQuality(int v) noexcept +{ + m_quality = std::max(0, std::min(9, v)); +} + + +void Options::setThreads(int v) noexcept +{ + // limiting threads to a sane number + m_threads = std::max(0, std::min((int)TileThreadPool::MAX_THREADS, v)); +} + + +int Options::getThreads() const noexcept +{ + return m_threads ? m_threads : TileThreadPool::AUTO_THREADS; +} // ----------------------- STATIC METHODS ----------------------- @@ -392,34 +425,47 @@ std::string Options::getOptionsSummary(bool complete) const noexcept if (complete || m_encoding != DEF_ENCODING || m_deflate != DEF_DEFLATE) { if (!sum.empty()) sum += ", "; - sum += "Pixel encoding = "; + sum += "pixel encoding = "; sum += GetEncodingName(GetEncodingCode(m_encoding, m_deflate)); } if (complete || m_haltOnError != DEF_HALT_ON_ERROR) { if (!sum.empty()) sum += ", "; - sum += "Halt on errors = "; + sum += "halt on errors = "; sum += (m_haltOnError) ? "enabled" : "disabled"; } if (complete || m_silent != DEF_SILENT) { if (!sum.empty()) sum += ", "; - sum += "Verbosity level = "; - if (m_silent == 0) sum += "silent"; - else if (m_silent == 2) sum += "verbose"; - else sum += "default"; + sum += "verbosity level = "; + if (m_silent == 0) { + sum += "silent"; + }else if (m_silent == 2) { + sum += "verbose"; + } else { + sum += "default"; + } } - if (complete || m_dithering != DEF_DITHERING) { + if (complete || m_quality != DEF_QUALITY) { if (!sum.empty()) sum += ", "; - sum += "Color dithering = "; - sum += (m_dithering) ? "enabled" : "disabled"; + sum += "quality = " + std::to_string(m_quality); } if (complete || m_mosc != DEF_MOSC) { if (!sum.empty()) sum += ", "; - if (m_mosc) sum += "Convert MBC to MOSC"; - else sum += "Convert MBC to MOS"; + if (m_mosc) sum += "convert MBC to MOSC"; + else sum += "convert MBC to MOS"; + } + + if (complete || m_threads != DEF_THREADS) { + if (!sum.empty()) sum += ", "; + sum += "jobs = "; + if (m_threads == 0) { + sum += "autodetected (" + std::to_string(TileThreadPool::AUTO_THREADS) + ")"; + } else { + sum += std::to_string(m_threads); + } } return sum; diff --git a/tileconv/options.h b/tileconv/options.h index 2d4d36e..67c24f3 100644 --- a/tileconv/options.h +++ b/tileconv/options.h @@ -66,39 +66,39 @@ class Options int getInputCount() const noexcept { return m_inFiles.size(); } const std::string& getInput(int idx) const noexcept; - /** Define output file name (for single file conversion only). Default: auto-generate */ + /** Define output file name (for single file conversion only). */ bool setOutput(const std::string &outFile) noexcept; /** Call to activate auto-generation of output filename. */ void resetOutput() noexcept; bool isOutput() const noexcept { return !m_outFile.empty(); } const std::string& getOutput() const noexcept { return m_outFile; } - /** Cancel operation on error? Only effective when processing multiple files. Default: true */ + /** Cancel operation on error? Only effective when processing multiple files. */ void setHaltOnError(bool b) noexcept { m_haltOnError = b; } bool isHaltOnError() const noexcept { return m_haltOnError; } - /** Level of text output. 0=verbose, 1=summary only, 2=no output. Default: 1 */ + /** Level of text output. 0=verbose, 1=summary only, 2=no output. */ void setSilence(int level) noexcept; int getSilence() const noexcept { return m_silent; } /** Check for specific verbosity levels. */ bool isSilent() const noexcept { return m_silent > 1; } bool isVerbose() const noexcept { return m_silent < 1; } - /** Create MOSC files (MBC->MOS conversion only)? Default: false */ + /** Create MOSC files (MBC->MOS conversion only)? */ void setMosc(bool b) noexcept { m_mosc = b; } bool isMosc() const noexcept { return m_mosc; } - /** Apply color dithering when decoding tiles? Default: true */ - void setDithering(bool b) noexcept { m_dithering = b; } - bool isDithering() const noexcept { return m_dithering; } + /** Color reduction quality in range 0..9. Calculate "r = 10 - quality" to get real value. */ + void setQuality(int v) noexcept; + int getQuality() const noexcept { return m_quality; } /** Apply zlib compression to tiles? */ void setDeflate(bool b) noexcept { m_deflate = b; } bool isDeflate() const noexcept { return m_deflate; } /** Number of threads to use for encoding/decoding. (0=autodetect) */ -// void setThreads(int v) noexcept; -// int getThreads() const noexcept { return m_threads; } + void setThreads(int v) noexcept; + int getThreads() const noexcept; /** Specify encoding type. Default: BC1 */ void setEncoding(Encoding type) noexcept { m_encoding = type; } @@ -120,17 +120,18 @@ class Options // default values for options static const bool DEF_HALT_ON_ERROR; static const bool DEF_MOSC; - static const bool DEF_DITHERING; static const bool DEF_DEFLATE; static const int DEF_SILENT; + static const int DEF_QUALITY; + static const int DEF_THREADS; static const Encoding DEF_ENCODING; bool m_haltOnError; // cancel operation on error (when processing multiple files) bool m_mosc; // create MOSC output - bool m_dithering; // apply color dithering to TIS/MOS output bool m_deflate; // apply zlib compression to TBC/MBC - int m_silent; // silence level [0:verbose, 1:summary only, 2:no output] -// int m_threads; // how many threads to use for encoding/decoding (0=auto) + int m_silent; // silence level (0:verbose, 1:summary only, 2:no output) + int m_quality; // color reduction quality (0:fast, 9:slow) + int m_threads; // how many threads to use for encoding/decoding (0=auto) Encoding m_encoding; // encoding type std::vector m_inFiles; std::string m_outFile; diff --git a/tileconv/tiledata.cpp b/tileconv/tiledata.cpp index 65cb384..9300602 100644 --- a/tileconv/tiledata.cpp +++ b/tileconv/tiledata.cpp @@ -22,9 +22,11 @@ THE SOFTWARE. #include "graphics.h" -TileData::TileData(int idx, BytePtr indexed, BytePtr palette, BytePtr deflated, +TileData::TileData(bool encode, int idx, BytePtr indexed, BytePtr palette, BytePtr deflated, int width, int height, unsigned type, unsigned deflatedSize) noexcept -: ptrIndexed(indexed) + +: isEncoding(encode) +, ptrIndexed(indexed) , ptrPalette(palette) , ptrDeflated(deflated) , index(idx) @@ -32,6 +34,7 @@ TileData::TileData(int idx, BytePtr indexed, BytePtr palette, BytePtr deflated, , tileHeight(height) , size(deflatedSize) , encodingType(type) +, error(false) , errorMsg() { } diff --git a/tileconv/tiledata.h b/tileconv/tiledata.h index 0040db7..b476d66 100644 --- a/tileconv/tiledata.h +++ b/tileconv/tiledata.h @@ -28,9 +28,10 @@ THE SOFTWARE. /** Data structure needed to process individual tiles independently */ struct TileData { - TileData(int idx, BytePtr indexed, BytePtr palette, BytePtr deflated, + TileData(bool encode, int idx, BytePtr indexed, BytePtr palette, BytePtr deflated, int width, int height, unsigned type, unsigned deflatedSize) noexcept; + bool isEncoding; // indicates whether this data is used for encoding (true) or decoding (false) BytePtr ptrIndexed; // storage for indexed tile (encoding: in, decoding out) BytePtr ptrPalette; // storage for palette (encoding: in, decoding: out) BytePtr ptrDeflated; // storage for compressed tile (encoding: out, decoding: in) @@ -39,6 +40,7 @@ struct TileData { int tileHeight; // height of the tile (encoding: in, decoding: out) uint32_t size; // data size (encoding: deflated size, decoding input: deflated size, decoding output: size of palette+indexed tile, error: 0) uint32_t encodingType; // encoding type (needed for decoding) + bool error; // true if an error occurred std::string errorMsg; // Contains a descriptive message if an error occurred }; diff --git a/tileconv/tilethreadpool.cpp b/tileconv/tilethreadpool.cpp new file mode 100644 index 0000000..f649b18 --- /dev/null +++ b/tileconv/tilethreadpool.cpp @@ -0,0 +1,164 @@ +/* +Copyright (c) 2014 Argent77 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include +#include "graphics.h" +#include "tilethreadpool.h" + + +const unsigned TileThreadPool::MAX_THREADS = 256u; +const unsigned TileThreadPool::AUTO_THREADS = std::max(1u, std::min(MAX_THREADS, std::thread::hardware_concurrency())); +const unsigned TileThreadPool::MAX_TILES = std::numeric_limits::max(); + + +TileThreadPool::TileThreadPool(Graphics &gfx, unsigned threadNum, unsigned tileNum) noexcept +: m_gfx(gfx) +, m_terminate(false) +, m_maxTiles(MAX_TILES) +, m_activeThreads(0) +, m_activeMutex() +, m_tilesMutex() +, m_resultsMutex() +, m_tiles() +, m_threads() +, m_results() +{ + setMaxTiles(tileNum); + threadNum = std::max(1u, std::min(MAX_THREADS, threadNum)); + for (unsigned i = 0; i < threadNum; i++) { + m_threads.emplace_back(std::thread(&TileThreadPool::threadMain, this)); + } +} + + +TileThreadPool::~TileThreadPool() noexcept +{ + setTerminate(true); + for (auto iter = m_threads.begin(); iter != m_threads.end(); ++iter) { + iter->join(); + } +} + + + + +bool TileThreadPool::addTileData(TileDataPtr tileData) noexcept +{ + std::lock_guard lock(m_tilesMutex); + if (m_tiles.size() < getMaxTiles()) { + m_tiles.push(tileData); + return true; + } + return false; +} + + +TileDataPtr TileThreadPool::getResult() noexcept +{ + std::lock_guard lock(m_resultsMutex); + if (!m_results.empty()) { + TileDataPtr retVal = m_results.top(); + m_results.pop(); + return retVal; + } else { + return TileDataPtr(nullptr); + } +} + + +const TileDataPtr TileThreadPool::peekResult() noexcept +{ + std::lock_guard lock(m_resultsMutex); + if (!m_results.empty()) { + return m_results.top(); + } else { + return TileDataPtr(nullptr); + } +} + + +bool TileThreadPool::finished() noexcept +{ + std::unique_lock lock1(m_tilesMutex, std::defer_lock); + std::unique_lock lock2(m_activeMutex, std::defer_lock); + std::unique_lock lock3(m_resultsMutex, std::defer_lock); + std::lock(lock1, lock2, lock3); + bool retVal = (m_tiles.empty() && m_activeThreads == 0 && m_results.empty()); + lock1.unlock(); lock2.unlock(); lock3.unlock(); + return retVal; +} + + +void TileThreadPool::threadMain() noexcept +{ + std::unique_lock lockTiles(m_tilesMutex, std::defer_lock); + std::unique_lock lockResults(m_resultsMutex, std::defer_lock); + while (!terminate()) { + lockTiles.lock(); + if (!m_tiles.empty()) { + threadActivated(); + + TileDataPtr tileData = m_tiles.front(); + m_tiles.pop(); + lockTiles.unlock(); + if (tileData != nullptr) { + // executing encoding/decoding methods + if (tileData->isEncoding) { + try { + tileData = m_gfx.encodeTile(tileData); + } catch (...) { + tileData = TileDataPtr(nullptr); + } + } else { + try { + tileData = m_gfx.decodeTile(tileData); + } catch (...) { + tileData = TileDataPtr(nullptr); + } + } + } + + // storing results + lockResults.lock(); + m_results.push(tileData); + lockResults.unlock(); + + threadDeactivated(); + } else { + lockTiles.unlock(); + std::this_thread::yield(); + } + } +} + + +void TileThreadPool::threadActivated() noexcept +{ + std::lock_guard lock(m_activeMutex); + m_activeThreads++; +} + + +void TileThreadPool::threadDeactivated() noexcept +{ + std::lock_guard lock(m_activeMutex); + m_activeThreads--; +} diff --git a/tileconv/tilethreadpool.h b/tileconv/tilethreadpool.h new file mode 100644 index 0000000..1110d17 --- /dev/null +++ b/tileconv/tilethreadpool.h @@ -0,0 +1,94 @@ +/* +Copyright (c) 2014 Argent77 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#ifndef _TILETHREADPOOL_H_ +#define _TILETHREADPOOL_H_ +#include +#include +#include +#include +#include "tiledata.h" + +class Graphics; + +/** Provides threading capabilities specialized for encoding or decoding tile data. */ +class TileThreadPool +{ +public: + /** Max. number of supported threads. */ + static const unsigned MAX_THREADS; + /** Detected number of separate threads on the current CPU. */ + static const unsigned AUTO_THREADS; + /** Max. number of allowed tile data blocks to add for processing. */ + static const unsigned MAX_TILES; + +public: + TileThreadPool(Graphics &gfx, unsigned threadNum = AUTO_THREADS, unsigned tileNum = MAX_TILES) noexcept; + ~TileThreadPool() noexcept; + + /** Get/set max. number of tile data blocks to add. */ + unsigned getMaxTiles() const noexcept { return m_maxTiles; } + void setMaxTiles(unsigned maxTiles) noexcept { m_maxTiles = std::max(1u, std::min(MAX_TILES, maxTiles)); } + + /** Add tile data to queue. Returns true if successfully added, false otherwise. */ + bool addTileData(TileDataPtr tileData) noexcept; + /** Returns whether you can still add new tile data blocks to the waiting queue. */ + bool canAddTileData() const noexcept { return (m_tiles.size() < m_maxTiles); } + + /** Returns whether results are waiting to be returned. */ + bool hasResult() const noexcept { return !m_results.empty(); } + /** Returns the next result if available or null pointer otherwise. */ + TileDataPtr getResult() noexcept; + /** Provides read-only access to the next result or a null pointer otherwise. */ + const TileDataPtr peekResult() noexcept; + + /** Returns if all data blocks in the queue have been processed and pushed onto the result queue. */ + bool finished() noexcept; + +private: + // Executed by each thread. + void threadMain() noexcept; + + // Called whenever a thread is about to execute another encoding/decoding function + void threadActivated() noexcept; + // Called whenever a thread has finished the execution of an encoding/decoding function + void threadDeactivated() noexcept; + + // Queried by each thread function + bool terminate() const noexcept { return m_terminate; } + // Called by the destructor to signal all threads to finish + void setTerminate(bool b) noexcept { m_terminate = b; } + +private: + Graphics &m_gfx; + bool m_terminate; + unsigned m_maxTiles; + int m_activeThreads; + std::mutex m_activeMutex; + std::mutex m_tilesMutex; + std::mutex m_resultsMutex; + std::queue m_tiles; + std::vector m_threads; + std::priority_queue, std::greater> m_results; +}; + + +#endif // _TILETHREADPOOL_H_ From e98d066a588e7294dc2e3c1a8df5a4f838826a8c Mon Sep 17 00:00:00 2001 From: Argent77 Date: Sun, 31 Aug 2014 23:02:44 +0200 Subject: [PATCH 4/6] Update README.md --- README.md | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index bf035ed..4affef1 100644 --- a/README.md +++ b/README.md @@ -17,20 +17,29 @@ A detailed description of the TBC and MBC formats can be found in FORMATS. Usage: tileconv [options] infile [infile2 [...]] Options: - -e Do not halt on errors. - -s Be silent. - -v Be verbose. - -t type Select pixel encoding type. - Supported types: - 0: No pixel encoding - 1: BC1/DXT1 (Default) - 2: BC2/DXT3 - 3: BC3/DXT5 - -u Do not apply tile compression. - -o outfile Select output file. (Works with single input file only!) - -d Use color dithering when converting to TIS or MOS. - -z MOS only: Decompress MBC into compressed MOS (MOSC). - -V Print version number and exit. + -e Do not halt on errors. + -s Be silent. + -v Be verbose. + -t type Select pixel encoding type. + Supported types: + 0: No pixel encoding + 1: BC1/DXT1 (Default) + 2: BC2/DXT3 + 3: BC3/DXT5 + -u Do not apply tile compression. + -o outfile Select output file. (Works with single input file only!) + -z MOS only: Decompress MBC into compressed MOS (MOSC). + -d Enable color dithering. (deprecated, use -q instead!) + -q level Specify quality vs. speed ratio when converting MBC->MOS + or TBC->TIS. Supported levels: 0..9 (Default: 4) + (0=fast and lower quality, 9=slow and higher quality) + Applied level-dependent features: + Dithering: levels 5 to 9 + Posterization: levels 0 to 2 + Additional techniques: levels 4 to 9 + -j num Number of parallel jobs to speed up the conversion process. + Valid numbers: 0 (autodetect), 1..256 (Default: 0) + -V Print version number and exit. Supported input file types: TIS, MOS, TBC, MBC Note: You can mix and match input files of each supported type. @@ -59,4 +68,4 @@ you can do so by modifying the file "config.mk" by hand. ### CONTACT -If you have questions or comments please post them on [Spellhold Studios](http://www.shsforums.net/) or contact me (Argent77) by private message on the same forum. +If you have questions or comments please post them on [Spellhold Studios](http://www.shsforums.net/topic/57588-tileconv-a-mostis-compressor/) or contact me (Argent77) by private message on the same forum. From 43cbad5317652e28dcaf2c2609cb852d01a8a630 Mon Sep 17 00:00:00 2001 From: Argent77 Date: Mon, 1 Sep 2014 13:47:24 +0200 Subject: [PATCH 5/6] Made thread specific changes --- tileconv/Makefile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tileconv/Makefile b/tileconv/Makefile index 033fdf6..0c5f075 100644 --- a/tileconv/Makefile +++ b/tileconv/Makefile @@ -8,7 +8,7 @@ CXX ?= g++ CXXFLAGS = -c -Wall -O2 -std=c++11 -I$(ZLIB_INCLUDE) -I$(SQUISH_INCLUDE) -I$(PNGQUANT_INCLUDE) LDFLAGS = -L$(ZLIB_LIB) -L$(SQUISH_LIB) -L$(PNGQUANT_LIB) SOURCES = tileconv.cpp convert.cpp version.cpp graphics.cpp tilethreadpool.cpp tiledata.cpp compress.cpp colors.cpp fileio.cpp colorquant.cpp options.cpp -SYSLIBS = -lpthread +SYSLIBS = -pthread LIBS =-lz -lsquish -limagequant $(SYSLIBS) OBJECTS = $(SOURCES:.cpp=.o) EXECUTABLE = tileconv @@ -17,9 +17,11 @@ ifeq ($(OS),Windows_NT) LDFLAGS += -static else RM = rm - # Mac OS X does not support static linking - ifneq ($(shell uname -s),Darwin) - LDFLAGS += -static + ifeq ($(shell uname -s),Darwin) + # A few hacks to enable proper threading support in Mac OS X + CXXFLAGS += -D_GLIBCXX_USE_NANOSLEEP -D_GLIBCXX_USE_SCHED_YIELD + else + # Nothing to do for Linux (yet) endif endif From 41557b24dc54d5e66d0fb8e982b96bf7351f8cb6 Mon Sep 17 00:00:00 2001 From: Argent77 Date: Mon, 1 Sep 2014 14:22:10 +0200 Subject: [PATCH 6/6] Version 0.2 --- tileconv/README | 7 ++++++- tileconv/version.cpp | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tileconv/README b/tileconv/README index e9b65fb..8716f22 100644 --- a/tileconv/README +++ b/tileconv/README @@ -1,7 +1,7 @@ TILECONV ~~~~~~~~ -Version: 0.1 +Version: 0.2 Author: Argent77 This tool allows you to compress or decompress TIS and MOS files provided by @@ -90,3 +90,8 @@ Version history v0.1 (2014-08-30) * Initial release + +v0.2 (2014-09-01) + * Greatly increased conversion speed + * Added new options "-q level" and "-j num" + * Deprecated option "-d" diff --git a/tileconv/version.cpp b/tileconv/version.cpp index 0f670b5..57804ed 100644 --- a/tileconv/version.cpp +++ b/tileconv/version.cpp @@ -20,6 +20,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ int vers_major = 0; -int vers_minor = 1; +int vers_minor = 2; char prog_name[] = "tileconv"; char author[] = "Argent77";