Bind BC1Block and BC1Texture

This commit is contained in:
Andrew Cassidy 2021-03-31 20:26:40 -07:00
parent 28b541d49a
commit 3852da6249
10 changed files with 286 additions and 134 deletions

View File

@ -29,6 +29,7 @@
#include "Vector4Int.h"
namespace quicktex {
using Coords = std::tuple<int, int>;
/**
* Base class for all compressed blocks
@ -46,6 +47,7 @@ template <int N, int M> class Block {
public:
static constexpr int Width = N;
static constexpr int Height = M;
static constexpr Coords Dimensions = Coords(Width, Height);
};
template <int N, int M> class ColorBlock : public Block<N, M> {

View File

@ -151,11 +151,10 @@ class RawTexture : public Texture {
virtual uint8_t *Data() noexcept override { return reinterpret_cast<uint8_t *>(_pixels); }
protected:
Color *_pixels;
};
template <typename B> class BlockTexture : public Texture {
template <typename B> class BlockTexture final: public Texture {
public:
using BlockType = B;
using Base = Texture;
@ -180,7 +179,7 @@ template <typename B> class BlockTexture : public Texture {
* Copy constructor
* @param other object to copy
*/
BlockTexture(const BlockTexture<B> &other) : BlockTexture(other.width, other.height) { std::memcpy(_blocks, other._blocks, Size()); }
BlockTexture(const BlockTexture<B> &other) : BlockTexture(other._width, other._height) { std::memcpy(_blocks, other._blocks, Size()); }
/**
* assignment operator
@ -201,29 +200,28 @@ template <typename B> 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<int, int> BlocksXY() const { return std::tuple<int, int>(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<const uint8_t *>(_blocks); }
virtual uint8_t *Data() noexcept override { return reinterpret_cast<uint8_t *>(_blocks); }
const uint8_t *Data() const noexcept override { return reinterpret_cast<const uint8_t *>(_blocks); }
uint8_t *Data() noexcept override { return reinterpret_cast<uint8_t *>(_blocks); }
protected:
B *_blocks;
};

View File

@ -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<Color> {
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<unsigned>(i)] = static_cast<uint8_t>(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<unsigned>(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<const char *>(t.Data()), t.Size()); });
//RawTexture
// RawTexture
py::class_<RawTexture> raw_texture(m, "RawTexture", texture);
raw_texture.def(py::init<int, int>(), "width"_a, "height"_a);
raw_texture.def_static("frombytes", &BufferToTexture<RawTexture>, "data"_a, "width"_a, "height"_a);
DefSubscript2D(raw_texture, &RawTexture::GetPixel, &RawTexture::SetPixel, &RawTexture::Dimensions);
raw_texture.def_static("frombytes", &BufferToTexture<RawTexture>, "data"_a, "width"_a, "height"_a);
// InitS3TC(m);
InitS3TC(m);
}
} // namespace quicktex::bindings

View File

@ -30,9 +30,60 @@
#include "Block.h"
#include "Color.h"
#include "Texture.h"
#include "util.h"
/*namespace pybind11::detail {
extern template struct type_caster<quicktex::Color>;
} // namespace pybind11::detail*/
namespace pybind11::detail {
template <> struct type_caster<quicktex::Color>;
using namespace quicktex;
/// Type caster for color class to allow it to be converted to and from a python tuple
template <> struct type_caster<Color> {
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<unsigned>(i)] = static_cast<uint8_t>(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<unsigned>(i)]);
PyTuple_SetItem(val, i, chan);
}
return val;
}
};
} // namespace pybind11::detail
namespace py = pybind11;
@ -44,7 +95,7 @@ template <typename T> T BufferToTexture(py::buffer buf, int width, int height) {
static_assert(std::is_constructible<T, int, int>::value);
auto info = buf.request(false);
auto output = T(width, height); // std::make_shared<T>(width, height);
auto output = T(width, height);
auto dst_size = output.Size();
if (info.format != py::format_descriptor<uint8_t>::format()) throw std::runtime_error("Incompatible format in python buffer: expected a byte array.");
@ -61,6 +112,24 @@ template <typename T> T BufferToTexture(py::buffer buf, int width, int height) {
return output;
}
template <typename T> T BufferToPOD(py::buffer buf) {
static_assert(std::is_trivially_copyable_v<T>);
auto info = buf.request(false);
if (info.format != py::format_descriptor<uint8_t>::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<const T*>(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 <typename T, typename Getter, typename Setter, typename Extent> void De
"key"_a, "value"_a);
}
template <typename B> py::class_<B> 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_<B> block(m, name, py::buffer_protocol());
block.def_static("frombytes", &BufferToPOD<B>, "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<uint8_t*>(&b), sizeof(B)); });
block.def(
"tobytes", [](const B& b) { return py::bytes(reinterpret_cast<const char*>(&b), sizeof(B)); },
Format(tobytes_doc, name, std::to_string(sizeof(B))).c_str());
return block;
}
template <typename B> py::class_<BlockTexture<B>> 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<B>;
py::type texture = py::type::of<Texture>();
py::class_<BTex> block_texture(m, name, py::type::of<Texture>(), py::is_final());
py::class_<BTex> block_texture(m, name, texture);
block_texture.def(py::init<int, int>(), "width"_a, "height"_a, Format(constructor_str, name).c_str());
block_texture.def_static("from_bytes", &BufferToTexture<BTex>, "data"_a, "width"_a, "height"_a, Format(from_bytes_str, name).c_str());
block_texture.def(py::init<int, int>(), "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<BTex>, "data"_a, "width"_a, "height"_a);
block_texture.def_property_readonly_static(
"block", [](py::object) { return py::type::of<B>(); }, "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

View File

@ -33,44 +33,69 @@ namespace quicktex::s3tc {
#pragma pack(push, 1)
class BC1Block : public Block<4, 4> {
public:
using UnpackedSelectors = std::array<std::array<uint8_t, Width>, Height>;
using SelectorArray = std::array<std::array<uint8_t, Width>, Height>;
using ColorPair = std::tuple<Color, Color>;
constexpr BC1Block() { static_assert(sizeof(BC1Block) == 8); }
uint16_t GetLowColor() const { return static_cast<uint16_t>(_low_color[0] | (_low_color[1] << 8U)); }
uint16_t GetHighColor() const { return static_cast<uint16_t>(_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<uint16_t>(_color_0[0] | (_color_0[1] << 8U)); }
uint16_t GetColor1Raw() const { return static_cast<uint16_t>(_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<uint8_t, uint8_t, 2, 4>(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<uint8_t, uint8_t, 2, 4>(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<uint8_t, EndpointSize> _low_color;
std::array<uint8_t, EndpointSize> _high_color;
public:
std::array<uint8_t, EndpointSize> _color_0;
std::array<uint8_t, EndpointSize> _color_1;
std::array<uint8_t, 4> selectors;
};
#pragma pack(pop)

View File

@ -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++) {

View File

@ -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<uint8_t, 4> 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;
}

View File

@ -17,7 +17,10 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../../_bindings.h"
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <array>
#include <cstddef>
@ -45,11 +48,43 @@ void InitBC1(py::module_ &s3tc) {
py::options options;
options.disable_function_signatures();
// BC1Encoder
py::class_<BC1Encoder> bc1_encoder(bc1, "BC1Encoder" R"doc(
Encodes RGB textures to BC1.
// region BC1Block
auto bc1_block = BindBlock<BC1Block>(bc1, "BC1Block");
bc1_block.doc() = "A single BC1 block.";
bc1_block.def(py::init<>());
bc1_block.def(py::init<Color, Color, BC1Block::SelectorArray>(), "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<BC1Block>(bc1, "BC1Texture");
bc1_texture.doc() = "A texture comprised of BC1 blocks.";
//endregion
//region BC1Encoder
py::class_<BC1Encoder> bc1_encoder(bc1, "BC1Encoder", "Encodes RGB textures to BC1.");
py::enum_<BC1Encoder::EndpointMode>(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_<BC1Decoder> 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

View File

@ -21,7 +21,9 @@
#include <cassert>
#include <cstdint>
#include <limits>
#include <string>
#include <type_traits>
#include <vector>
#include "ndebug.h"
@ -73,7 +75,7 @@ template <typename I, typename O, size_t S, size_t C> constexpr auto Unpack(I pa
* @param vals Unpacked std::array of type I and size C.
* @return Packed integer input of type O.
*/
template <typename I, typename O, size_t S, size_t C> constexpr auto Pack(const std::array<I, C> &vals) noexcept(ndebug) {
template <typename I, typename O, size_t S, size_t C> constexpr auto Pack(const std::array<I, C> &vals) {
// type checking
static_assert(std::is_unsigned<I>::value, "Unpacked input type must be unsigned");
static_assert(std::is_unsigned<O>::value, "Packed output type must be unsigned");
@ -81,9 +83,10 @@ template <typename I, typename O, size_t S, size_t C> constexpr auto Pack(const
static_assert(std::numeric_limits<O>::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<O>(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 <typename F> constexpr F lerp(F a, F b, F s) { return a + (b - a) * s; }
template <typename... Args> std::string Format(const char *str, const Args &...args) {
auto output = std::string(str);
std::vector<std::string> 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;
}

View File

@ -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");
}