diff --git a/quicktex/s3tc/bc1/BC1Block.cpp b/quicktex/s3tc/bc1/BC1Block.cpp new file mode 100644 index 0000000..42ce407 --- /dev/null +++ b/quicktex/s3tc/bc1/BC1Block.cpp @@ -0,0 +1,44 @@ +/* Quicktex Texture Compression Library + Copyright (C) 2021 Andrew Cassidy + Partially derived from rgbcx.h written by Richard Geldreich + and licenced under the public domain + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . + */ + +#include "BC1Block.h" + +#include +#include + +#include "../../util.h" + +namespace quicktex::s3tc { +uint16_t BC1Block::GetColor0Raw() const { return Pack(_color0); } +uint16_t BC1Block::GetColor1Raw() const { return Pack(_color1); } + +void BC1Block::SetColor0Raw(uint16_t c) { _color0 = Unpack(c); } +void BC1Block::SetColor1Raw(uint16_t c) { _color1 = Unpack(c); } + +BC1Block::SelectorArray BC1Block::GetSelectors() const { return MapArray(_selectors, Unpack); } + +void BC1Block::SetSelectors(const BC1Block::SelectorArray& unpacked) { + for (unsigned y = 0; y < (unsigned)Height; y++) { + if (std::any_of(unpacked[y].begin(), unpacked[y].end(), [](uint8_t i) { return i > SelectorMax; })) + throw std::invalid_argument("Selector value out of bounds."); + } + _selectors = MapArray(unpacked, Pack); +} + +} // namespace quicktex::s3tc diff --git a/quicktex/s3tc/bc1/BC1Block.h b/quicktex/s3tc/bc1/BC1Block.h index e58af8f..02f02fc 100644 --- a/quicktex/s3tc/bc1/BC1Block.h +++ b/quicktex/s3tc/bc1/BC1Block.h @@ -20,23 +20,34 @@ #pragma once #include -#include #include #include +#include #include "../../Color.h" -#include "../../util.h" namespace quicktex::s3tc { class alignas(8) BC1Block { public: - static constexpr int Width = 4; - static constexpr int Height = 4; + static constexpr size_t Width = 4; + static constexpr size_t Height = 4; + + static constexpr size_t EndpointSize = 2; // size of a 5:6:5 endpoint in bytes + static constexpr size_t SelectorSize = 4; // size of selector array in bytes + static constexpr size_t SelectorBits = 2; // size of a selector in bits + static constexpr uint8_t SelectorMax = (1 << SelectorBits) - 1; // maximum value of a selector using SelectorArray = std::array, Height>; using ColorPair = std::pair; + private: + std::array _color0; + std::array _color1; + std::array _selectors; + + public: + /// Create a new BC1Block constexpr BC1Block() { static_assert(sizeof(BC1Block) == 8); static_assert(sizeof(std::array) == 8 * 10); @@ -45,29 +56,47 @@ class alignas(8) BC1Block { _selectors = {0, 0, 0, 0}; } + /** + * Create a new BC1Block + * @param color0 first endpoint color + * @param color1 second endpoint color + * @param selectors the selectors as a 4x4 list of integers, between 0 and 3 inclusive. + */ BC1Block(Color color0, Color color1, const SelectorArray& selectors) { SetColor0(color0); SetColor1(color1); SetSelectors(selectors); } + /** + * Create a new BC1Block + * @param ep0 first endpoint + * @param ep1 second endpoint + * @param selectors the selectors as a 4x4 list of integers, between 0 and 3 inclusive. + */ BC1Block(uint16_t ep0, uint16_t ep1, const SelectorArray& selectors) { SetColor0Raw(ep0); SetColor1Raw(ep1); SetSelectors(selectors); } + /** + * Create a new BC1Block + * @param ep0 first endpoint + * @param ep1 second endpoint + * @param solid_mask single byte mask to use for each row + */ BC1Block(uint16_t ep0, uint16_t ep1, uint8_t solid_mask) { SetColor0Raw(ep0); SetColor1Raw(ep1); _selectors.fill(solid_mask); } - constexpr uint16_t GetColor0Raw() const { return Pack(_color0); } - constexpr uint16_t GetColor1Raw() const { return Pack(_color1); } + uint16_t GetColor0Raw() const; + uint16_t GetColor1Raw() const; - void SetColor0Raw(uint16_t c) { _color0 = Unpack(c); } - void SetColor1Raw(uint16_t c) { _color1 = Unpack(c); } + void SetColor0Raw(uint16_t c); + void SetColor1Raw(uint16_t c); Color GetColor0() const { return Color::Unpack565(GetColor0Raw()); } Color GetColor1() const { return Color::Unpack565(GetColor1Raw()); } @@ -80,19 +109,18 @@ class alignas(8) BC1Block { SetColor1(cs.second); } - constexpr SelectorArray GetSelectors() const { return MapArray(_selectors, Unpack); } + /** + * Get this block's selectors + * @return a 4x4 array of integers between 0 and 3 inclusive + */ + SelectorArray GetSelectors() const; - void SetSelectors(const SelectorArray& unpacked) { _selectors = MapArray(unpacked, Pack); } + /** + * Set this block's selectors + * @param unpacked a 4x4 array of integers between 0 and 3 inclusive + */ + void SetSelectors(const SelectorArray& unpacked); constexpr bool Is3Color() const { return GetColor0Raw() <= GetColor1Raw(); } - - constexpr static inline size_t EndpointSize = 2; // in bytes - constexpr static inline size_t SelectorSize = 4; // in bytes - constexpr static inline uint8_t SelectorBits = 2; // in bits - - private: - std::array _color0; - std::array _color1; - std::array _selectors; }; } // namespace quicktex::s3tc \ No newline at end of file diff --git a/quicktex/s3tc/bc4/BC4Block.cpp b/quicktex/s3tc/bc4/BC4Block.cpp new file mode 100644 index 0000000..ff33bf9 --- /dev/null +++ b/quicktex/s3tc/bc4/BC4Block.cpp @@ -0,0 +1,66 @@ +/* Quicktex Texture Compression Library + Copyright (C) 2021 Andrew Cassidy + Partially derived from rgbcx.h written by Richard Geldreich + and licenced under the public domain + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . + */ + +#include "BC4Block.h" + +#include +#include + +#include "../../util.h" + +namespace quicktex::s3tc { + +BC4Block::SelectorArray BC4Block::GetSelectors() const { + auto packed = Pack(_selectors); + auto rows = Unpack(packed); + return MapArray(rows, Unpack); +} + +void BC4Block::SetSelectors(const BC4Block::SelectorArray& unpacked) { + for (unsigned y = 0; y < (unsigned)Height; y++) { + if (std::any_of(unpacked[y].begin(), unpacked[y].end(), [](uint8_t i) { return i > SelectorMax; })) + throw std::invalid_argument("Selector value out of bounds."); + } + auto rows = MapArray(unpacked, Pack); + auto packed = Pack(rows); + _selectors = Unpack(packed); +} + +std::array BC4Block::GetValues6() const { + return {alpha0, + alpha1, + static_cast((alpha0 * 4 + alpha1) / 5), + static_cast((alpha0 * 3 + alpha1 * 2) / 5), + static_cast((alpha0 * 2 + alpha1 * 3) / 5), + static_cast((alpha0 + alpha1 * 4) / 5), + 0, + 0xFF}; +} + +std::array BC4Block::GetValues8() const { + return {alpha0, + alpha1, + static_cast((alpha0 * 6 + alpha1) / 7), + static_cast((alpha0 * 5 + alpha1 * 2) / 7), + static_cast((alpha0 * 4 + alpha1 * 3) / 7), + static_cast((alpha0 * 3 + alpha1 * 4) / 7), + static_cast((alpha0 * 2 + alpha1 * 5) / 7), + static_cast((alpha0 + alpha1 * 6) / 7)}; +} +} // namespace quicktex::s3tc diff --git a/quicktex/s3tc/bc4/BC4Block.h b/quicktex/s3tc/bc4/BC4Block.h index caeec74..e041027 100644 --- a/quicktex/s3tc/bc4/BC4Block.h +++ b/quicktex/s3tc/bc4/BC4Block.h @@ -20,22 +20,34 @@ #pragma once #include -#include #include #include - -#include "../../util.h" +#include namespace quicktex::s3tc { class alignas(8) BC4Block { public: - static constexpr int Width = 4; - static constexpr int Height = 4; + static constexpr size_t Width = 4; + static constexpr size_t Height = 4; + + static constexpr size_t SelectorSize = 6; // size of selector array in bytes + static constexpr size_t SelectorBits = 3; // size of a selector in bits + static constexpr size_t SelectorMax = (1 << SelectorBits) - 1; // maximum value of a selector using SelectorArray = std::array, Height>; using AlphaPair = std::pair; + uint8_t alpha0; // first endpoint + uint8_t alpha1; // second endpoint + + private: + std::array _selectors; // internal array of selector bytes + + public: + // Constructors + + /// Create a new BC4Block constexpr BC4Block() { static_assert(sizeof(BC4Block) == 8); static_assert(sizeof(std::array) == 8 * 10); @@ -44,76 +56,51 @@ class alignas(8) BC4Block { _selectors = {0, 0, 0, 0, 0, 0}; } + /** + * Create a new BC4Block + * @param valpha0 first endpoint value + * @param valpha1 second endpoint value + * @param selectors the selectors as a 4x4 array of integers, between 0 and 7 inclusive. + */ BC4Block(uint8_t valpha0, uint8_t valpha1, const SelectorArray& selectors) { alpha0 = valpha0; alpha1 = valpha1; SetSelectors(selectors); } - constexpr AlphaPair GetAlphas() const { return AlphaPair(alpha0, alpha1); } + /** + * Create a new solid BC4Block + * @param alpha first endpoint value + */ + BC4Block(uint8_t alpha) { + alpha0 = alpha; + alpha1 = alpha; + _selectors.fill(0); + } + /// Get a alpha0 and alpha1 as a pair + AlphaPair GetAlphas() const { return AlphaPair(alpha0, alpha1); } + + /// Set alpha0 and alpha1 as a pair void SetAlphas(AlphaPair as) { alpha0 = as.first; alpha1 = as.second; } - constexpr uint64_t GetSelectorBits() const { - auto packed = Pack(_selectors); - assert(packed <= SelectorsPackedMax); - return packed; - } + /// Get the block's selectors as a 4x4 array of integers between 0 and 7 inclusive. + SelectorArray GetSelectors() const; - void SetSelectorBits(uint64_t packed) { - assert(packed <= SelectorsPackedMax); - _selectors = Unpack(packed); - } + /// Get the block's selectors as a 4x4 array of integers between 0 and 7 inclusive. + void SetSelectors(const SelectorArray& unpacked); - constexpr SelectorArray GetSelectors() const { - auto rows = Unpack(GetSelectorBits()); - auto unpacked = MapArray(rows, Unpack); - return unpacked; - } + /// True if the block uses 6-value interpolation, i.e. alpha0 <= alpha1. + bool Is6Value() const { return alpha0 <= alpha1; } - void SetSelectors(const SelectorArray& unpacked) { - auto rows = MapArray(unpacked, Pack); - auto packed = Pack(rows); - SetSelectorBits(packed); - } - - constexpr bool Is6Value() const { return alpha0 <= alpha1; } - - static constexpr std::array GetValues6(unsigned l, unsigned h) { - return {static_cast(l), - static_cast(h), - static_cast((l * 4 + h) / 5), - static_cast((l * 3 + h * 2) / 5), - static_cast((l * 2 + h * 3) / 5), - static_cast((l + h * 4) / 5), - 0, - 255}; - } - - static constexpr std::array GetValues8(unsigned l, unsigned h) { - return {static_cast(l), - static_cast(h), - static_cast((l * 6 + h) / 7), - static_cast((l * 5 + h * 2) / 7), - static_cast((l * 4 + h * 3) / 7), - static_cast((l * 3 + h * 4) / 7), - static_cast((l * 2 + h * 5) / 7), - static_cast((l + h * 6) / 7)}; - } - - static constexpr std::array GetValues(unsigned l, unsigned h) { return l > h ? GetValues8(l, h) : GetValues6(l, h); } - - static constexpr size_t SelectorSize = 6; - static constexpr uint8_t SelectorBits = 3; - static constexpr uint64_t SelectorsPackedMax = (1ULL << (8U * SelectorSize)) - 1U; - - uint8_t alpha0; - uint8_t alpha1; + /// The interpolated values of this block as an array of 8 integers. + std::array GetValues() const { return Is6Value() ? GetValues6() : GetValues8(); } private: - std::array _selectors; + std::array GetValues6() const; + std::array GetValues8() const; }; } // namespace quicktex::s3tc diff --git a/quicktex/s3tc/bc4/BC4Decoder.cpp b/quicktex/s3tc/bc4/BC4Decoder.cpp index 67e8312..c4e4294 100644 --- a/quicktex/s3tc/bc4/BC4Decoder.cpp +++ b/quicktex/s3tc/bc4/BC4Decoder.cpp @@ -28,10 +28,7 @@ namespace quicktex::s3tc { void BC4Decoder::DecodeInto(ColorBlock<4, 4> &dest, const BC4Block &block) const { - auto l = block.alpha0; - auto h = block.alpha1; - - auto values = BC4Block::GetValues(l, h); + auto values = block.GetValues(); auto selectors = block.GetSelectors(); for (unsigned y = 0; y < 4; y++) { diff --git a/quicktex/s3tc/bc4/BC4Encoder.cpp b/quicktex/s3tc/bc4/BC4Encoder.cpp index af45921..07eb2a1 100644 --- a/quicktex/s3tc/bc4/BC4Encoder.cpp +++ b/quicktex/s3tc/bc4/BC4Encoder.cpp @@ -25,12 +25,11 @@ #include "../../Color.h" #include "../../ColorBlock.h" +#include "../../util.h" #include "BC4Block.h" namespace quicktex::s3tc { BC4Block BC4Encoder::EncodeBlock(const ColorBlock<4, 4> &pixels) const { - auto output = BC4Block(); - uint8_t min = UINT8_MAX; uint8_t max = 0; @@ -40,12 +39,8 @@ BC4Block BC4Encoder::EncodeBlock(const ColorBlock<4, 4> &pixels) const { max = std::max(max, value); } - output.alpha1 = min; - output.alpha0 = max; - if (max == min) { - output.SetSelectorBits(0); - return output; + return BC4Block(min); // solid block } auto selectors = BC4Block::SelectorArray(); @@ -74,9 +69,7 @@ BC4Block BC4Encoder::EncodeBlock(const ColorBlock<4, 4> &pixels) const { } } - output.SetSelectors(selectors); - - return output; + return BC4Block(max, min, selectors); } } // namespace quicktex::s3tc \ No newline at end of file diff --git a/quicktex/util.h b/quicktex/util.h index 92b2059..2537aa8 100644 --- a/quicktex/util.h +++ b/quicktex/util.h @@ -84,10 +84,9 @@ template constexpr O Pack(const std static_assert(std::numeric_limits::digits >= (C * S), "Packed output type must be big enough to represent the number of bits multiplied by count"); O packed = 0; // output value of type O - const I max = (1U << S) - 1U; for (unsigned i = 0; i < C; i++) { - if (vals[i] > max) throw std::invalid_argument("Input value at index " + std::to_string(i) + " is larger than " + std::to_string(max)); + assert(vals[i] <= (1U << S) - 1U); packed |= static_cast(vals[i]) << (i * S); }