diff --git a/quicktex/Block.h b/quicktex/Block.h index 996c4d9..4ff34b6 100644 --- a/quicktex/Block.h +++ b/quicktex/Block.h @@ -29,6 +29,7 @@ #include "Vector4Int.h" namespace quicktex { +using Coords = std::tuple; /** * Base class for all compressed blocks @@ -46,6 +47,7 @@ template class Block { public: static constexpr int Width = N; static constexpr int Height = M; + static constexpr Coords Dimensions = Coords(Width, Height); }; template class ColorBlock : public Block { diff --git a/quicktex/Texture.h b/quicktex/Texture.h index 83d55ce..efe406f 100644 --- a/quicktex/Texture.h +++ b/quicktex/Texture.h @@ -151,11 +151,10 @@ class RawTexture : public Texture { virtual uint8_t *Data() noexcept override { return reinterpret_cast(_pixels); } protected: - Color *_pixels; }; -template class BlockTexture : public Texture { +template class BlockTexture final: public Texture { public: using BlockType = B; using Base = Texture; @@ -180,7 +179,7 @@ template class BlockTexture : public Texture { * Copy constructor * @param other object to copy */ - BlockTexture(const BlockTexture &other) : BlockTexture(other.width, other.height) { std::memcpy(_blocks, other._blocks, Size()); } + BlockTexture(const BlockTexture &other) : BlockTexture(other._width, other._height) { std::memcpy(_blocks, other._blocks, Size()); } /** * assignment operator @@ -201,29 +200,28 @@ template class BlockTexture : public Texture { ~BlockTexture() { delete[] _blocks; } - constexpr int BlocksX() const { return _width / B::Width; } - constexpr int BlocksY() const { return _height / B::Height; } + constexpr int BlocksX() const { return (_width + B::Width - 1) / B::Width; } + constexpr int BlocksY() const { return (_height + B::Height - 1) / B::Height; } constexpr std::tuple BlocksXY() const { return std::tuple(BlocksX(), BlocksY()); } - virtual B GetBlock(int x, int y) const { - if (x < 0 || x >= BlocksX()) throw std::range_error("x value out of range."); - if (y < 0 || y >= BlocksY()) throw std::range_error("y value out of range."); + B GetBlock(int x, int y) const { + if (x < 0 || x >= BlocksX()) throw std::out_of_range("x value out of range."); + if (y < 0 || y >= BlocksY()) throw std::out_of_range("y value out of range."); return _blocks[x + (y * _width)]; } - virtual void SetBlock(int x, int y, const B &val) { - if (x < 0 || x >= BlocksX()) throw std::range_error("x value out of range."); - if (y < 0 || y >= BlocksY()) throw std::range_error("y value out of range."); + void SetBlock(int x, int y, const B &val) { + if (x < 0 || x >= BlocksX()) throw std::out_of_range("x value out of range."); + if (y < 0 || y >= BlocksY()) throw std::out_of_range("y value out of range."); _blocks[x + (y * _width)] = val; } - virtual size_t Size() const noexcept override { return (size_t)(BlocksX() * BlocksY()) * sizeof(B); } + size_t Size() const noexcept override { return (size_t)(BlocksX() * BlocksY()) * sizeof(B); } - virtual const uint8_t *Data() const noexcept override { return reinterpret_cast(_blocks); } - virtual uint8_t *Data() noexcept override { return reinterpret_cast(_blocks); } + const uint8_t *Data() const noexcept override { return reinterpret_cast(_blocks); } + uint8_t *Data() noexcept override { return reinterpret_cast(_blocks); } protected: - B *_blocks; }; diff --git a/quicktex/_bindings.cpp b/quicktex/_bindings.cpp index 9963bad..df0d8b4 100644 --- a/quicktex/_bindings.cpp +++ b/quicktex/_bindings.cpp @@ -29,56 +29,6 @@ namespace py = pybind11; -namespace pybind11::detail { -using namespace quicktex; -/// Type caster for color class to allow it to be converted to and from a python tuple -template <> struct type_caster { - public: - PYBIND11_TYPE_CASTER(Color, _("Color")); - - bool load(handle src, bool) { - PyObject *source = src.ptr(); - - PyObject *tmp = PySequence_Tuple(source); - - // if the object is not a tuple, return false - if (!tmp) { return false; } // incorrect type - - // check the size - Py_ssize_t size = PyTuple_Size(tmp); - if (size < 3 || size > 4) { return false; } // incorrect size - - value.a = 0xFF; - // now we get the contents - for (int i = 0; i < size; i++) { - PyObject *src_chan = PyTuple_GetItem(tmp, i); - PyObject *tmp_chan = PyNumber_Long(src_chan); - - if (!tmp_chan) return false; // incorrect channel type - - auto chan = PyLong_AsLong(tmp_chan); - if (chan > 0xFF || chan < 0) return false; // item out of range - value[static_cast(i)] = static_cast(chan); - Py_DECREF(tmp_chan); - } - Py_DECREF(tmp); - - return !PyErr_Occurred(); - } - - static handle cast(Color src, return_value_policy, handle) { - PyObject *val = PyTuple_New(4); - - for (int i = 0; i < 4; i++) { - PyObject *chan = PyLong_FromLong(src[static_cast(i)]); - PyTuple_SetItem(val, i, chan); - } - - return val; - } -}; -} // namespace pybind11::detail - namespace quicktex::bindings { void InitS3TC(py::module_ &m); @@ -100,17 +50,16 @@ PYBIND11_MODULE(_quicktex, m) { texture.def_buffer([](Texture &t) { return py::buffer_info(t.Data(), t.Size()); }); texture.def("tobytes", [](const Texture &t) { return py::bytes(reinterpret_cast(t.Data()), t.Size()); }); - //RawTexture + // RawTexture py::class_ raw_texture(m, "RawTexture", texture); raw_texture.def(py::init(), "width"_a, "height"_a); + raw_texture.def_static("frombytes", &BufferToTexture, "data"_a, "width"_a, "height"_a); DefSubscript2D(raw_texture, &RawTexture::GetPixel, &RawTexture::SetPixel, &RawTexture::Dimensions); - raw_texture.def_static("frombytes", &BufferToTexture, "data"_a, "width"_a, "height"_a); - - // InitS3TC(m); + InitS3TC(m); } } // namespace quicktex::bindings \ No newline at end of file diff --git a/quicktex/_bindings.h b/quicktex/_bindings.h index 0e524d0..bab7bea 100644 --- a/quicktex/_bindings.h +++ b/quicktex/_bindings.h @@ -30,9 +30,60 @@ #include "Block.h" #include "Color.h" #include "Texture.h" +#include "util.h" + +/*namespace pybind11::detail { +extern template struct type_caster; +} // namespace pybind11::detail*/ namespace pybind11::detail { -template <> struct type_caster; +using namespace quicktex; +/// Type caster for color class to allow it to be converted to and from a python tuple +template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(Color, _("Color")); + + bool load(handle src, bool) { + PyObject* source = src.ptr(); + + PyObject* tmp = PySequence_Tuple(source); + + // if the object is not a tuple, return false + if (!tmp) { return false; } // incorrect type + + // check the size + Py_ssize_t size = PyTuple_Size(tmp); + if (size < 3 || size > 4) { return false; } // incorrect size + + value.a = 0xFF; + // now we get the contents + for (int i = 0; i < size; i++) { + PyObject* src_chan = PyTuple_GetItem(tmp, i); + PyObject* tmp_chan = PyNumber_Long(src_chan); + + if (!tmp_chan) return false; // incorrect channel type + + auto chan = PyLong_AsLong(tmp_chan); + if (chan > 0xFF || chan < 0) return false; // item out of range + value[static_cast(i)] = static_cast(chan); + Py_DECREF(tmp_chan); + } + Py_DECREF(tmp); + + return !PyErr_Occurred(); + } + + static handle cast(Color src, return_value_policy, handle) { + PyObject* val = PyTuple_New(4); + + for (int i = 0; i < 4; i++) { + PyObject* chan = PyLong_FromLong(src[static_cast(i)]); + PyTuple_SetItem(val, i, chan); + } + + return val; + } +}; } // namespace pybind11::detail namespace py = pybind11; @@ -44,7 +95,7 @@ template T BufferToTexture(py::buffer buf, int width, int height) { static_assert(std::is_constructible::value); auto info = buf.request(false); - auto output = T(width, height); // std::make_shared(width, height); + auto output = T(width, height); auto dst_size = output.Size(); if (info.format != py::format_descriptor::format()) throw std::runtime_error("Incompatible format in python buffer: expected a byte array."); @@ -61,6 +112,24 @@ template T BufferToTexture(py::buffer buf, int width, int height) { return output; } +template T BufferToPOD(py::buffer buf) { + static_assert(std::is_trivially_copyable_v); + + auto info = buf.request(false); + + if (info.format != py::format_descriptor::format()) throw std::runtime_error("Incompatible format in python buffer: expected a byte array."); + if (info.size < (ssize_t)sizeof(T)) std::runtime_error("Incompatible format in python buffer: Input data is smaller than texture size."); + if (info.ndim == 1) { + if (info.shape[0] < (ssize_t)sizeof(T)) throw std::runtime_error("Incompatible format in python buffer: 1-D buffer has incorrect length."); + if (info.strides[0] != 1) throw std::runtime_error("Incompatible format in python buffer: 1-D buffer is not contiguous."); + } else { + throw std::runtime_error("Incompatible format in python buffer: Incorrect number of dimensions."); + } + + const T* ptr = reinterpret_cast(info.ptr); + return *ptr; +} + inline int PyIndex(int val, int size, std::string name = "index") { if (val < -size || val >= size) throw std::out_of_range(name + " value out of range"); if (val < 0) return size + val; @@ -98,23 +167,79 @@ template void De "key"_a, "value"_a); } +template py::class_ BindBlock(py::module_& m, const char* name) { + const char* frombytes_doc = R"doc( + from_bytes(b) -> {0} + + Create a new {0} by copying a bytes-like object. + + :param b: A bytes-like object at least the size of the block. + )doc"; + + const char* tobytes_doc = R"doc( + tobytes(self) -> bytes + + Pack the {0} into a bytestring. + + :returns: A bytes object of length {1}. + )doc"; + + py::class_ block(m, name, py::buffer_protocol()); + block.def_static("frombytes", &BufferToPOD, "data"_a, Format(frombytes_doc, name).c_str()); + + block.def_readonly_static("width", &B::Width, "The width of the block in pixels."); + block.def_readonly_static("height", &B::Height, "The height of the block in pixels."); + block.def_readonly_static("dimensions", &B::Dimensions, "The dimensions of the block in pixels."); + block.def_property_readonly_static("size", [](py::object) { return sizeof(B); }, "The size of the block in bytes."); + + block.def_buffer([](B& b) { return py::buffer_info(reinterpret_cast(&b), sizeof(B)); }); + block.def( + "tobytes", [](const B& b) { return py::bytes(reinterpret_cast(&b), sizeof(B)); }, + Format(tobytes_doc, name, std::to_string(sizeof(B))).c_str()); + + return block; +} + template py::class_> BindBlockTexture(py::module_& m, const char* name) { + const auto* const constructor_str = R"doc( + __init__(self, width: int, height: int) -> None + + Create a new blank {0} with the given dimensions. + If the dimenions are not multiples of the block dimensions, enough blocks will be allocated + to cover the entire texture, and it will be implicitly cropped during decoding. + + :param int width: The width of the texture in pixels. Must be > 0. + :param int height: The height of the texture in pixels. must be > 0 + )doc"; + + const auto* const from_bytes_str = R"doc( + from_bytes(b, width: int, height: int) -> {0} + + Create a new {0} with the given dimensions, and copy a bytes-like object into it. + If the dimenions are not multiples of the block dimensions, enough blocks will be allocated + to cover the entire texture, and it will be implicitly cropped during decoding. + + :param b: A bytes-like object at least the size of the resulting texture. + :param int width: The width of the texture in pixels. Must be > 0. + :param int height: The height of the texture in pixels. must be > 0 + )doc"; + using BTex = BlockTexture; - py::type texture = py::type::of(); + py::class_ block_texture(m, name, py::type::of(), py::is_final()); - py::class_ block_texture(m, name, texture); + block_texture.def(py::init(), "width"_a, "height"_a, Format(constructor_str, name).c_str()); + block_texture.def_static("from_bytes", &BufferToTexture, "data"_a, "width"_a, "height"_a, Format(from_bytes_str, name).c_str()); - block_texture.def(py::init(), "width"_a, "height"_a); - block_texture.def("get_block", &BTex::GetBlock, "x"_a, "y"_a); - block_texture.def("set_block", &BTex::SetBlock, "x"_a, "y"_a, "block"_a); + block_texture.def_property_readonly("width_blocks", &BTex::BlocksX, "The width of the texture in blocks."); + block_texture.def_property_readonly("height_blocks", &BTex::BlocksY, "The height of the texture in blocks."); + block_texture.def_property_readonly("dimensions_blocks", &BTex::BlocksXY, "The dimensions of the texture in blocks."); - block_texture.def_static("from_bytes", &BufferToTexture, "data"_a, "width"_a, "height"_a); + block_texture.def_property_readonly_static( + "block", [](py::object) { return py::type::of(); }, "The block type used by this texture."); - block_texture.def_property_readonly("blocks_x", &BTex::BlocksX); - block_texture.def_property_readonly("blocks_y", &BTex::BlocksY); - block_texture.def_property_readonly("blocks_xy", &BTex::BlocksXY); + DefSubscript2D(block_texture, &BTex::GetBlock, &BTex::SetBlock, &BTex::BlocksXY); - DefSubscript2D(block_texture, &BTex::GetPixel, &BTex::SetPixel, &BTex::BlocksXY); + return block_texture; } } // namespace quicktex::bindings \ No newline at end of file diff --git a/quicktex/s3tc/bc1/BC1Block.h b/quicktex/s3tc/bc1/BC1Block.h index efefe5e..228793b 100644 --- a/quicktex/s3tc/bc1/BC1Block.h +++ b/quicktex/s3tc/bc1/BC1Block.h @@ -33,44 +33,69 @@ namespace quicktex::s3tc { #pragma pack(push, 1) class BC1Block : public Block<4, 4> { public: - using UnpackedSelectors = std::array, Height>; + using SelectorArray = std::array, Height>; + using ColorPair = std::tuple; - constexpr BC1Block() { static_assert(sizeof(BC1Block) == 8); } - - uint16_t GetLowColor() const { return static_cast(_low_color[0] | (_low_color[1] << 8U)); } - uint16_t GetHighColor() const { return static_cast(_high_color[0] | (_high_color[1] << 8U)); } - Color GetLowColor32() const { return Color::Unpack565(GetLowColor()); } - Color GetHighColor32() const { return Color::Unpack565(GetHighColor()); } - - bool Is3Color() const { return GetLowColor() <= GetHighColor(); } - void SetLowColor(uint16_t c) { - _low_color[0] = c & 0xFF; - _low_color[1] = (c >> 8) & 0xFF; - } - void SetHighColor(uint16_t c) { - _high_color[0] = c & 0xFF; - _high_color[1] = (c >> 8) & 0xFF; - } - uint32_t GetSelector(uint32_t x, uint32_t y) const { - assert((x < 4U) && (y < 4U)); - return (selectors[y] >> (x * SelectorBits)) & SelectorMask; - } - void SetSelector(uint32_t x, uint32_t y, uint32_t val) { - assert((x < 4U) && (y < 4U) && (val < 4U)); - selectors[y] &= (~(SelectorMask << (x * SelectorBits))); - selectors[y] |= (val << (x * SelectorBits)); + constexpr BC1Block() { + SetColor0Raw(0); + SetColor1Raw(0); + SetSelectorsSolid(0); } - UnpackedSelectors UnpackSelectors() const { - UnpackedSelectors unpacked; + constexpr BC1Block(Color color0, Color color1, const SelectorArray& selectors) { + SetColor0(color0); + SetColor1(color1); + SetSelectors(selectors); + } + + constexpr BC1Block(Color color0, Color color1, uint8_t solid_mask) { + SetColor0(color0); + SetColor1(color1); + SetSelectorsSolid(solid_mask); + } + + uint16_t GetColor0Raw() const { return static_cast(_color_0[0] | (_color_0[1] << 8U)); } + uint16_t GetColor1Raw() const { return static_cast(_color_1[0] | (_color_1[1] << 8U)); } + void SetColor0Raw(uint16_t c) { + _color_0[0] = c & 0xFF; + _color_0[1] = (c >> 8) & 0xFF; + } + void SetColor1Raw(uint16_t c) { + _color_1[0] = c & 0xFF; + _color_1[1] = (c >> 8) & 0xFF; + } + + Color GetColor0() const { return Color::Unpack565(GetColor0Raw()); } + Color GetColor1() const { return Color::Unpack565(GetColor1Raw()); } + ColorPair GetColors() const { return {GetColor0(), GetColor1()}; } + + void SetColor0(Color c) { SetColor0Raw(c.Pack565()); } + void SetColor1(Color c) { SetColor1Raw(c.Pack565()); } + void SetColors(ColorPair cs) { + SetColor0(std::get<0>(cs)); + SetColor1(std::get<1>(cs)); + } + + bool Is3Color() const { return GetColor0Raw() <= GetColor1Raw(); } + + SelectorArray GetSelectors() const { + SelectorArray unpacked; for (unsigned i = 0; i < 4; i++) { unpacked[i] = Unpack(selectors[i]); } return unpacked; } - void PackSelectors(const UnpackedSelectors& unpacked, uint8_t mask = 0) { + void SetSelectors(const SelectorArray& unpacked, uint8_t mask = 0) { for (unsigned i = 0; i < 4; i++) { selectors[i] = mask ^ Pack(unpacked[i]); } } + /** + * Set every row of selectors to the same 8-bit mask. useful for solid-color blocks + * @param mask the 8-bit mask to use for each row + */ + void SetSelectorsSolid(uint8_t mask) { + for (unsigned i = 0; i < 4; i++) selectors[i] = mask; + } + constexpr static inline size_t EndpointSize = 2; constexpr static inline size_t SelectorSize = 4; constexpr static inline uint8_t SelectorBits = 2; @@ -78,10 +103,8 @@ class BC1Block : public Block<4, 4> { constexpr static inline uint8_t SelectorMask = SelectorValues - 1; private: - std::array _low_color; - std::array _high_color; - - public: + std::array _color_0; + std::array _color_1; std::array selectors; }; #pragma pack(pop) diff --git a/quicktex/s3tc/bc1/BC1Decoder.cpp b/quicktex/s3tc/bc1/BC1Decoder.cpp index a354f93..f84cacc 100644 --- a/quicktex/s3tc/bc1/BC1Decoder.cpp +++ b/quicktex/s3tc/bc1/BC1Decoder.cpp @@ -33,9 +33,9 @@ ColorBlock<4, 4> BC1Decoder::DecodeBlock(const BC1Block &block) const { return D ColorBlock<4, 4> BC1Decoder::DecodeBlock(const BC1Block &block, bool use_3color) const { auto output = ColorBlock<4, 4>(); - const auto l = block.GetLowColor(); - const auto h = block.GetHighColor(); - const auto selectors = block.UnpackSelectors(); + const auto l = block.GetColor0Raw(); + const auto h = block.GetColor1Raw(); + const auto selectors = block.GetSelectors(); const auto colors = _interpolator->InterpolateBC1(l, h, use_3color); for (unsigned y = 0; y < 4; y++) { diff --git a/quicktex/s3tc/bc1/BC1Encoder.cpp b/quicktex/s3tc/bc1/BC1Encoder.cpp index a8b465e..98c723e 100644 --- a/quicktex/s3tc/bc1/BC1Encoder.cpp +++ b/quicktex/s3tc/bc1/BC1Encoder.cpp @@ -394,18 +394,15 @@ BC1Block BC1Encoder::WriteBlockSolid(Color color) const { } } - block.SetLowColor(max16); - block.SetHighColor(min16); - block.selectors[0] = mask; - block.selectors[1] = mask; - block.selectors[2] = mask; - block.selectors[3] = mask; + block.SetColor0Raw(max16); + block.SetColor1Raw(min16); + block.SetSelectorsSolid(mask); return block; } BC1Block BC1Encoder::WriteBlock(EncodeResults &result) const { BC1Block block; - BC1Block::UnpackedSelectors selectors; + BC1Block::SelectorArray selectors; uint16_t color1 = result.low.Pack565Unscaled(); uint16_t color0 = result.high.Pack565Unscaled(); std::array lut; @@ -449,9 +446,9 @@ BC1Block BC1Encoder::WriteBlock(EncodeResults &result) const { if (result.color_mode == ColorMode::ThreeColor) { assert(selectors[y][x] != 3); } } - block.SetLowColor(color0); - block.SetHighColor(color1); - block.PackSelectors(selectors); + block.SetColor0Raw(color0); + block.SetColor1Raw(color1); + block.SetSelectors(selectors); return block; } diff --git a/quicktex/s3tc/bc1/_bindings.cpp b/quicktex/s3tc/bc1/_bindings.cpp index 2320d84..397cd6b 100644 --- a/quicktex/s3tc/bc1/_bindings.cpp +++ b/quicktex/s3tc/bc1/_bindings.cpp @@ -17,7 +17,10 @@ along with this program. If not, see . */ +#include "../../_bindings.h" + #include +#include #include #include @@ -45,11 +48,43 @@ void InitBC1(py::module_ &s3tc) { py::options options; options.disable_function_signatures(); - // BC1Encoder - py::class_ bc1_encoder(bc1, "BC1Encoder" R"doc( - Encodes RGB textures to BC1. + // region BC1Block + auto bc1_block = BindBlock(bc1, "BC1Block"); + bc1_block.doc() = "A single BC1 block."; + + bc1_block.def(py::init<>()); + bc1_block.def(py::init(), "color0"_a, "color1"_a, "selectors"_a, R"doc( + __init__(self, color0, color1) -> None + + Create a new BC1Block with the specified endpoints and selectors + + :param color0: The first endpoint + :param color1: The second endpoint + :param selectors: the selectors as a 4x4 list of integers, between 0 and 3 inclusive. )doc"); + bc1_block.def_property("colors", &BC1Block::GetColors, &BC1Block::SetColors, "The block's endpoint colors as a 2-tuple."); + bc1_block.def_property("selectors", &BC1Block::GetSelectors, &BC1Block::SetSelectors, R"doc( + The block's selectors as a 4x4 list of integers between 0 and 3 inclusive. + + .. note:: + This is a property, so directly modifying its value will not propogate back to the block. + Instead you must read, modify, then write the new value back to the property, like so:: + + selectors = block.selectors + selectors[0,0] = 0 + block.selectors = selectors + )doc"); + // endregion + + //region BC1Texture + auto bc1_texture = BindBlockTexture(bc1, "BC1Texture"); + bc1_texture.doc() = "A texture comprised of BC1 blocks."; + //endregion + + //region BC1Encoder + py::class_ bc1_encoder(bc1, "BC1Encoder", "Encodes RGB textures to BC1."); + py::enum_(bc1_encoder, "EndpointMode", "Enum representing various methods of finding endpoints in a block.") .value("LeastSquares", BC1Encoder::EndpointMode::LeastSquares, "Find endpoints using a 2D least squares approach.") .value("BoundingBox", BC1Encoder::EndpointMode::BoundingBox, "Find endpoints using a simple bounding box. Fast but inaccurate.") @@ -127,8 +162,9 @@ void InitBC1(py::module_ &s3tc) { bc1_encoder.def_property("power_iterations", &BC1Encoder::GetPowerIterations, &BC1Encoder::SetPowerIterations, "Number of power iterations used with the PCA endpoint mode. Value should be around 4 to 6. " "Automatically clamped to between :py:const:`BC1Encoder.min_power_iterations` and :py:const:`BC1Encoder.max_power_iterations`"); + //endregion - // BC1Decoder + //region BC1Decoder py::class_ bc1_decoder(bc1, "BC1Decoder", R"doc( Base: :py:class:`~quicktex.BlockDecoder` @@ -147,5 +183,6 @@ void InitBC1(py::module_ &s3tc) { bc1_decoder.def_property_readonly("interpolator", &BC1Decoder::GetInterpolator, "The interpolator used by this decoder. This is a readonly property."); bc1_decoder.def_readwrite("write_alpha", &BC1Decoder::write_alpha, "Determines if the alpha channel of the output is written to."); + //endregion } } // namespace quicktex::bindings \ No newline at end of file diff --git a/quicktex/util.h b/quicktex/util.h index b462749..2387d32 100644 --- a/quicktex/util.h +++ b/quicktex/util.h @@ -21,7 +21,9 @@ #include #include #include +#include #include +#include #include "ndebug.h" @@ -73,7 +75,7 @@ template constexpr auto Unpack(I pa * @param vals Unpacked std::array of type I and size C. * @return Packed integer input of type O. */ -template constexpr auto Pack(const std::array &vals) noexcept(ndebug) { +template constexpr auto Pack(const std::array &vals) { // type checking static_assert(std::is_unsigned::value, "Unpacked input type must be unsigned"); static_assert(std::is_unsigned::value, "Packed output type must be unsigned"); @@ -81,9 +83,10 @@ template constexpr auto Pack(const 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++) { - assert(vals[i] <= (1U << S) - 1U); + if (vals[i] > max) throw std::invalid_argument("Input value at index " + std::to_string(i) + " is larger than " + std::to_string(max)); packed |= static_cast(vals[i]) << (i * S); } @@ -147,3 +150,21 @@ constexpr int squarei(int a) { return a * a; } constexpr int absi(int a) { return (a < 0) ? -a : a; } template constexpr F lerp(F a, F b, F s) { return a + (b - a) * s; } + +template std::string Format(const char *str, const Args &...args) { + auto output = std::string(str); + + std::vector values = {{args...}}; + + for (unsigned i = 0; i < values.size(); i++) { + auto key = "{" + std::to_string(i) + "}"; + auto value = values[i]; + while(true) { + size_t where = output.find(key); + if (where == output.npos) break; + output.replace(where, key.length(), value); + } + } + + return output; +} \ No newline at end of file diff --git a/tests/run_tests.cpp b/tests/run_tests.cpp index 3d0aeb8..91b3e9f 100644 --- a/tests/run_tests.cpp +++ b/tests/run_tests.cpp @@ -37,6 +37,6 @@ int main() { site.attr("addsitedir")(CUSTOM_SYS_PATH); py::module_ nose = py::module_::import("nose"); - py::module_ tests = py::module_::import("test_BC1"); - nose.attr("main")("test_BC1"); + py::module_ tests = py::module_::import("test_texture"); + nose.attr("main")("test_texture"); } \ No newline at end of file