From fb56c6af04b1f983673529928de015d1d5991215 Mon Sep 17 00:00:00 2001 From: drewcassidy Date: Sun, 28 Mar 2021 02:36:47 -0700 Subject: [PATCH] Refactor to add Texture classes --- quicktex/Block.h | 96 ++++++++++++++ quicktex/BlockDecoder.h | 90 ------------- quicktex/BlockEncoder.h | 92 ------------- quicktex/BlockView.h | 170 ------------------------ quicktex/Color.cpp | 4 - quicktex/Color.h | 4 +- quicktex/Decoder.h | 68 ++++++++++ quicktex/Encoder.h | 72 ++++++++++ quicktex/Texture.cpp | 61 +++++++++ quicktex/Texture.h | 219 +++++++++++++++++++++++++++++++ quicktex/_bindings.cpp | 91 ++++++++++--- quicktex/_bindings.h | 54 ++++++++ quicktex/s3tc/bc1/BC1Block.h | 9 +- quicktex/s3tc/bc1/BC1Decoder.cpp | 30 ++--- quicktex/s3tc/bc1/BC1Decoder.h | 11 +- quicktex/s3tc/bc1/BC1Encoder.cpp | 219 ++++++++++++++++--------------- quicktex/s3tc/bc1/BC1Encoder.h | 36 ++--- quicktex/s3tc/bc1/_bindings.cpp | 12 +- quicktex/s3tc/bc3/BC3Block.h | 5 +- quicktex/s3tc/bc3/BC3Decoder.cpp | 13 +- quicktex/s3tc/bc3/BC3Decoder.h | 8 +- quicktex/s3tc/bc3/BC3Encoder.cpp | 10 +- quicktex/s3tc/bc3/BC3Encoder.h | 8 +- quicktex/s3tc/bc3/_bindings.cpp | 10 +- quicktex/s3tc/bc4/BC4Block.h | 4 +- quicktex/s3tc/bc4/BC4Decoder.cpp | 24 ++-- quicktex/s3tc/bc4/BC4Decoder.h | 11 +- quicktex/s3tc/bc4/BC4Encoder.cpp | 40 +++--- quicktex/s3tc/bc4/BC4Encoder.h | 9 +- quicktex/s3tc/bc4/_bindings.cpp | 10 +- quicktex/s3tc/bc5/BC5Block.h | 5 +- quicktex/s3tc/bc5/BC5Decoder.cpp | 13 +- quicktex/s3tc/bc5/BC5Decoder.h | 9 +- quicktex/s3tc/bc5/BC5Encoder.cpp | 10 +- quicktex/s3tc/bc5/BC5Encoder.h | 8 +- quicktex/s3tc/bc5/_bindings.cpp | 10 +- 36 files changed, 918 insertions(+), 627 deletions(-) delete mode 100644 quicktex/BlockDecoder.h delete mode 100644 quicktex/BlockEncoder.h delete mode 100644 quicktex/BlockView.h create mode 100644 quicktex/Decoder.h create mode 100644 quicktex/Encoder.h create mode 100644 quicktex/Texture.cpp create mode 100644 quicktex/Texture.h create mode 100644 quicktex/_bindings.h diff --git a/quicktex/Block.h b/quicktex/Block.h index 8b9cc2c..473984b 100644 --- a/quicktex/Block.h +++ b/quicktex/Block.h @@ -19,6 +19,14 @@ #pragma once +#include +#include +#include +#include + +#include "Color.h" +#include "Vector4Int.h" + namespace quicktex { /** @@ -39,4 +47,92 @@ template class Block { static constexpr int Height = M; }; +template class ColorBlock : public Block { + public: + using Base = Block; + struct Metrics { + Color min; + Color max; + Color avg; + bool is_greyscale; + bool has_black; + Vector4Int sums; + }; + + constexpr Color Get(int x, int y) const { + if (x >= Base::Width || x < 0) throw std::invalid_argument("x value out of range"); + if (y >= Base::Height || y < 0) throw std::invalid_argument("y value out of range"); + + return _pixels[x + (N * y)]; + } + + constexpr Color Get(int i) const { + if (i >= N * M || i < 0) throw std::invalid_argument("i value out of range"); + return _pixels[i]; + } + + void Set(int x, int y, const Color &value) { + if (x >= Base::Width || x < 0) throw std::invalid_argument("x value out of range"); + if (y >= Base::Height || y < 0) throw std::invalid_argument("y value out of range"); + _pixels[x + (N * y)] = value; + } + + void Set(int i, const Color &value) { + if (i >= N * M || i < 0) throw std::invalid_argument("i value out of range"); + _pixels[i] = value; + } + + void GetRow(int y, Color *dst) const { + if (y >= Base::Height || y < 0) throw std::invalid_argument("y value out of range"); + std::memcpy(dst, &_pixels[N * y], N * sizeof(Color)); + } + + void SetRow(int y, const Color *src) { + if (y >= Base::Height || y < 0) throw std::invalid_argument("y value out of range"); + std::memcpy(&_pixels[N * y], src, N * sizeof(Color)); + } + + bool IsSingleColor() const { + auto first = Get(0, 0); + for (unsigned j = 1; j < M * N; j++) { + if (Get(j) != first) return false; + } + return true; + } + + Metrics GetMetrics(bool ignore_black = false) const { + Metrics metrics; + metrics.min = Color(UINT8_MAX, UINT8_MAX, UINT8_MAX); + metrics.max = Color(0, 0, 0); + metrics.has_black = false; + metrics.is_greyscale = true; + metrics.sums = {0, 0, 0}; + + unsigned total = 0; + + for (unsigned i = 0; i < M * N; i++) { + Color val = Get(i); + bool is_black = val.IsBlack(); + + metrics.has_black |= is_black; + + if (ignore_black && is_black) { continue; } + + metrics.is_greyscale &= val.IsGrayscale(); + for (unsigned c = 0; c < 3; c++) { + metrics.min[c] = std::min(metrics.min[c], val[c]); + metrics.max[c] = std::max(metrics.max[c], val[c]); + metrics.sums[c] += val[c]; + } + total++; + } + + if (total > 0) metrics.avg = (metrics.sums + Vector4Int(total / 2)) / (int)total; // half-total added for better rounding + return metrics; + } + + private: + std::array _pixels; +}; + } // namespace quicktex \ No newline at end of file diff --git a/quicktex/BlockDecoder.h b/quicktex/BlockDecoder.h deleted file mode 100644 index 5b779d6..0000000 --- a/quicktex/BlockDecoder.h +++ /dev/null @@ -1,90 +0,0 @@ -/* Python-rgbcx Texture Compression Library - Copyright (C) 2021 Andrew Cassidy - Partially derived from rgbcx.h written by Richard Geldreich 2020 - 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 . - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include "BlockView.h" -#include "ndebug.h" -#include "util.h" - -namespace quicktex { - -class BlockDecoder { - public: - using DecoderPtr = std::shared_ptr; - - virtual ~BlockDecoder() = default; - virtual void DecodeImage(uint8_t *encoded, Color *decoded, unsigned image_width, unsigned image_height) const = 0; - virtual size_t BlockSize() const = 0; - virtual size_t BlockWidth() const = 0; - virtual size_t BlockHeight() const = 0; -}; - -template -class BlockDecoderTemplate : public BlockDecoder { - public: - using DecodedBlock = ColorBlockView; - using EncodedBlock = B; - - BlockDecoderTemplate() noexcept = default; - virtual ~BlockDecoderTemplate() noexcept = default; - - virtual void DecodeBlock(DecodedBlock dest, EncodedBlock *const block) const noexcept(ndebug) = 0; - - virtual void DecodeImage(uint8_t *encoded, Color *decoded, unsigned image_width, unsigned image_height) const override { - assert(image_width % N == 0); - assert(image_width % M == 0); - - unsigned block_width = image_width / N; - unsigned block_height = image_height / M; - - auto blocks = reinterpret_cast(encoded); - - // from experimentation, multithreading this using OpenMP actually makes decoding slower - // due to thread creation/teardown taking longer than the decoding process itself. - // As a result, this is left as a serial operation despite being embarassingly parallelizable - for (unsigned y = 0; y < block_height; y++) { - for (unsigned x = 0; x < block_width; x++) { - unsigned pixel_x = x * N; - unsigned pixel_y = y * M; - - assert(pixel_x >= 0); - assert(pixel_y >= 0); - assert(pixel_y + M <= image_height); - assert(pixel_x + N <= image_width); - - unsigned top_left = pixel_x + (pixel_y * image_width); - auto dest = DecodedBlock(&decoded[top_left], (int)image_width); - - DecodeBlock(dest, &blocks[x + block_width * y]); - } - } - } - - virtual size_t BlockSize() const override { return sizeof(B); } - virtual size_t BlockWidth() const override { return N; } - virtual size_t BlockHeight() const override { return M; } -}; -} // namespace quicktex diff --git a/quicktex/BlockEncoder.h b/quicktex/BlockEncoder.h deleted file mode 100644 index 0a936b5..0000000 --- a/quicktex/BlockEncoder.h +++ /dev/null @@ -1,92 +0,0 @@ -/* Python-rgbcx Texture Compression Library - Copyright (C) 2021 Andrew Cassidy - Partially derived from rgbcx.h written by Richard Geldreich 2020 - 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 . - */ - -#pragma once - -#include -#include -#include -#include - -#include "BlockView.h" - -namespace quicktex { - -class BlockEncoder { - public: - using EncoderPtr = std::shared_ptr; - - virtual ~BlockEncoder() = default; - virtual void EncodeImage(uint8_t *encoded, Color *decoded, unsigned image_width, unsigned image_height) const = 0; - virtual size_t BlockSize() const = 0; - virtual size_t BlockWidth() const = 0; - virtual size_t BlockHeight() const = 0; -}; - -template class BlockEncoderTemplate : public BlockEncoder { - public: - using DecodedBlock = ColorBlockView; - using EncodedBlock = B; - - BlockEncoderTemplate() noexcept = default; - virtual ~BlockEncoderTemplate() noexcept = default; - - virtual void EncodeBlock(DecodedBlock pixels, EncodedBlock *dest) const = 0; - - virtual void EncodeImage(uint8_t *encoded, Color *decoded, unsigned image_width, unsigned image_height) const override { - assert(image_width % N == 0); - assert(image_width % M == 0); - - unsigned block_width = image_width / N; - unsigned block_height = image_height / M; - - auto blocks = reinterpret_cast(encoded); - - // from experimentation, multithreading this using OpenMP sometimes actually makes decoding slower - // due to thread creation/teardown taking longer than the decoding process itself. - // As a result, this is sometimes left as a serial operation despite being embarassingly parallelizable - // threshold for number of blocks before multithreading is set by overriding MTThreshold() - -#pragma omp parallel for if (block_width * block_height >= MTThreshold()) - for (int y = 0; y < (int)block_height; y++) { - for (int x = 0; x < (int)block_width; x++) { - unsigned pixel_x = (unsigned)x * N; - unsigned pixel_y = (unsigned)y * M; - - assert(pixel_x >= 0); - assert(pixel_y >= 0); - assert(pixel_y + M <= image_height); - assert(pixel_x + N <= image_width); - - unsigned top_left = pixel_x + (pixel_y * image_width); - unsigned block_index = (unsigned)x + (block_width * (unsigned)y); - auto src = DecodedBlock(&decoded[top_left], (int)image_width); - - EncodeBlock(src, &blocks[block_index]); - } - } - } - - virtual size_t BlockSize() const override { return sizeof(B); } - virtual size_t BlockWidth() const override { return N; } - virtual size_t BlockHeight() const override { return M; } - - virtual size_t MTThreshold() const { return SIZE_MAX; }; -}; -} // namespace quicktex diff --git a/quicktex/BlockView.h b/quicktex/BlockView.h deleted file mode 100644 index b11d94e..0000000 --- a/quicktex/BlockView.h +++ /dev/null @@ -1,170 +0,0 @@ -/* Python-rgbcx 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 . - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "Color.h" -#include "Vector4Int.h" -#include "ndebug.h" - -namespace quicktex { -template class RowView { - public: - RowView(S *start, int pixel_stride = 1) : start(start), pixel_stride(pixel_stride) {} - - constexpr S &operator[](size_t index) noexcept(ndebug) { - assert(index < N); - return start[index * pixel_stride]; - } - constexpr const S &operator[](size_t index) const noexcept(ndebug) { - assert(index < N); - return start[index * pixel_stride]; - } - - constexpr int Size() noexcept { return N; } - - S *const start; - const int pixel_stride; -}; - -template class BlockView { - public: - using Row = RowView; - - BlockView(S *start, int row_stride = N, int pixel_stride = 1) : start(start), row_stride(row_stride), pixel_stride(pixel_stride) {} - - constexpr Row operator[](unsigned index) noexcept(ndebug) { - assert(index < M); - return RowView(&start[row_stride * (int)index], pixel_stride); - } - - constexpr int Width() noexcept { return N; } - constexpr int Height() noexcept { return M; } - constexpr int Size() noexcept { return N * M; } - - constexpr S &Get(unsigned x, unsigned y) noexcept(ndebug) { - assert(x < N); - assert(y < M); - return start[(row_stride * (int)y) + (pixel_stride * (int)x)]; - } - - constexpr S Get(unsigned x, unsigned y) const noexcept(ndebug) { - assert(x < N); - assert(y < M); - return start[(row_stride * (int)y) + (pixel_stride * (int)x)]; - } - - constexpr void Set(unsigned x, unsigned y, S value) noexcept(ndebug) { - assert(x < N); - assert(y < M); - start[(row_stride * (int)y) + (pixel_stride * (int)x)] = value; - } - - constexpr S &Get(unsigned i) noexcept(ndebug) { return Get(i % N, i / N); } - constexpr S Get(unsigned i) const noexcept(ndebug) { return Get(i % N, i / N); } - - constexpr std::array Flatten() noexcept { - std::array result; - for (unsigned x = 0; x < N; x++) { - for (unsigned y = 0; y < M; y++) { result[x + (N * y)] = Get(x, y); } - } - return result; - } - - S *const start; - const int row_stride; - const int pixel_stride; -}; - -template class ColorBlockView : public BlockView { - public: - using Base = BlockView; - using ChannelView = BlockView; - - struct BlockMetrics { - Color min; - Color max; - Color avg; - bool is_greyscale; - bool has_black; - Vector4Int sums; - }; - - ColorBlockView(Color *start, int row_stride = N, int pixel_stride = 1) : Base(start, row_stride, pixel_stride) {} - - constexpr ChannelView GetChannel(uint8_t index) noexcept(ndebug) { - assert(index < 4U); - auto channelStart = reinterpret_cast(Base::start) + index; - return ChannelView(channelStart, Base::row_stride * 4, Base::pixel_stride * 4); - } - - void SetRGB(unsigned x, unsigned y, Color value) noexcept(ndebug) { Base::Get(x, y).SetRGB(value); } - - bool IsSingleColor() { - auto first = Base::Get(0, 0); - for (unsigned j = 1; j < M * N; j++) { - if (Base::Get(j) != first) return false; - } - return true; - } - - BlockMetrics GetMetrics(bool ignore_black = false) { - BlockMetrics metrics; - metrics.min = Color(UINT8_MAX, UINT8_MAX, UINT8_MAX); - metrics.max = Color(0, 0, 0); - metrics.has_black = false; - metrics.is_greyscale = true; - metrics.sums = {0, 0, 0}; - - unsigned total = 0; - - for (unsigned i = 0; i < M * N; i++) { - Color val = Base::Get(i); - bool is_black = val.IsBlack(); - - metrics.has_black |= is_black; - - if (ignore_black && is_black) { continue; } - - metrics.is_greyscale &= val.IsGrayscale(); - for (unsigned c = 0; c < 3; c++) { - metrics.min[c] = std::min(metrics.min[c], val[c]); - metrics.max[c] = std::max(metrics.max[c], val[c]); - metrics.sums[c] += val[c]; - } - total++; - } - - if (total > 0) metrics.avg = (metrics.sums + Vector4Int(total / 2)) / (int)total; // half-total added for better rounding - - return metrics; - } -}; - -using Color4x4 = ColorBlockView<4, 4>; -using Byte4x4 = BlockView; - -} // namespace quicktex \ No newline at end of file diff --git a/quicktex/Color.cpp b/quicktex/Color.cpp index 917e3da..84b0b4a 100644 --- a/quicktex/Color.cpp +++ b/quicktex/Color.cpp @@ -26,10 +26,6 @@ namespace quicktex { -constexpr Color::Color() { SetRGBA(0, 0, 0, 0xFF); } - -constexpr Color::Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { SetRGBA(r, g, b, a); } - Color::Color(Vector4Int v) { SetRGBA((uint8_t)v[0], (uint8_t)v[1], (uint8_t)v[2], (uint8_t)v[3]); } uint16_t Color::Pack565Unscaled(uint8_t r, uint8_t g, uint8_t b) { diff --git a/quicktex/Color.h b/quicktex/Color.h index 44dcda0..377ea71 100644 --- a/quicktex/Color.h +++ b/quicktex/Color.h @@ -34,9 +34,9 @@ class Color { uint8_t b; uint8_t a; - constexpr Color(); + constexpr Color() { SetRGBA(0, 0, 0, 0xFF); } - constexpr Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 0xFF); + constexpr Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 0xFF) { SetRGBA(r, g, b, a); } Color(Vector4Int v); diff --git a/quicktex/Decoder.h b/quicktex/Decoder.h new file mode 100644 index 0000000..49168a6 --- /dev/null +++ b/quicktex/Decoder.h @@ -0,0 +1,68 @@ +/* Python-rgbcx Texture Compression Library + Copyright (C) 2021 Andrew Cassidy + Partially derived from rgbcx.h written by Richard Geldreich 2020 + 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 . + */ + +#pragma once + +#include + +#include "Block.h" +#include "Texture.h" + +namespace quicktex { + +template class Decoder { + public: + using Texture = T; + + virtual ~Decoder() = default; + virtual RawTexture Decode(const T &encoded) const; +}; + +template class BlockDecoder : public Decoder { + public: + inline static constexpr int BlockWidth = T::BlockType::Width; + inline static constexpr int BlockHeight = T::BlockType::Height; + + using Texture = T; + using EncodedBlock = typename T::BlockType; + using DecodedBlock = ColorBlock; + + virtual DecodedBlock DecodeBlock(const EncodedBlock &block) const = 0; + + virtual RawTexture Decode(const T &encoded) const override { + auto decoded = RawTexture(encoded.Width(), encoded.Height()); + + int blocks_x = encoded.BlocksX(); + int blocks_y = encoded.BlocksY(); + + // from experimentation, multithreading this using OpenMP actually makes decoding slower + // due to thread creation/teardown taking longer than the decoding process itself. + // As a result, this is left as a serial operation despite being embarassingly parallelizable + for (int y = 0; y < blocks_y; y++) { + for (int x = 0; x < blocks_x; x++) { + auto block = encoded.GetBlock(x, y); + auto pixels = DecodeBlock(block); + decoded.SetBlock(x, y, pixels); + } + } + + return decoded; + } +}; +} // namespace quicktex diff --git a/quicktex/Encoder.h b/quicktex/Encoder.h new file mode 100644 index 0000000..a232900 --- /dev/null +++ b/quicktex/Encoder.h @@ -0,0 +1,72 @@ +/* Python-rgbcx Texture Compression Library + Copyright (C) 2021 Andrew Cassidy + Partially derived from rgbcx.h written by Richard Geldreich 2020 + 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 . + */ + +#pragma once + +#include + +#include "Block.h" +#include "Texture.h" + +namespace quicktex { + +template class Encoder { + public: + using Texture = T; + + virtual ~Encoder() = default; + virtual T Encode(const RawTexture &decoded) const; +}; + +template class BlockEncoder : public Encoder { + public: + inline static constexpr int BlockWidth = T::BlockType::Width; + inline static constexpr int BlockHeight = T::BlockType::Height; + + using Texture = T; + using EncodedBlock = typename T::BlockType; + using DecodedBlock = ColorBlock; + + virtual EncodedBlock EncodeBlock(const DecodedBlock &block) const = 0; + + virtual T Encode(const RawTexture &decoded) const override { + auto encoded = T(decoded.Width(), decoded.Height()); + + int blocks_x = encoded.BlocksX(); + int blocks_y = encoded.BlocksY(); + + // from experimentation, multithreading this using OpenMP sometimes actually makes encoding slower + // due to thread creation/teardown taking longer than the encoding process itself. + // As a result, this is sometimes left as a serial operation despite being embarassingly parallelizable + // threshold for number of blocks before multithreading is set by overriding MTThreshold() +#pragma omp parallel for if (blocks_x * blocks_y >= MTThreshold()) + for (int y = 0; y < blocks_y; y++) { + for (int x = 0; x < blocks_x; x++) { + auto pixels = decoded.GetBlock(x, y); + auto block = EncodeBlock(pixels); + encoded.SetBlock(x, y, block); + } + } + + return encoded; + } + + virtual size_t MTThreshold() const { return SIZE_MAX; }; +}; +} // namespace quicktex diff --git a/quicktex/Texture.cpp b/quicktex/Texture.cpp new file mode 100644 index 0000000..99b4415 --- /dev/null +++ b/quicktex/Texture.cpp @@ -0,0 +1,61 @@ +/* Python-rgbcx 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 "Texture.h" + +#include +#include + +#include "Color.h" + +namespace quicktex { +RawTexture::RawTexture(int width, int height) : Base(width, height) { _pixels = new Color[(size_t)(_width * _height)]; } + +RawTexture::RawTexture(RawTexture&& other) : Base(other._width, other._height) { + _pixels = other._pixels; + other._pixels = nullptr; +} + +RawTexture::RawTexture(const RawTexture& other) : RawTexture(other._width, other._height) { + std::memcpy(_pixels, other._pixels, (size_t)(_width * _height) * sizeof(Color)); +} + +RawTexture& RawTexture::operator=(RawTexture other) noexcept { + swap(*this, other); + return *this; +} + +void swap(RawTexture& first, RawTexture& second) noexcept { + using std::swap; // enable ADL + swap(first._pixels, second._pixels); + swap(first._width, second._width); + swap(first._height, second._height); +} + +Color RawTexture::GetPixel(int x, int y) const { + if (x < 0 || x >= _width) throw std::invalid_argument("x value out of range."); + if (y < 0 || y >= _height) throw std::invalid_argument("y value out of range."); + return _pixels[x + (y * _width)]; +} + +void RawTexture::SetPixel(int x, int y, Color val) { + if (x < 0 || x >= _width) throw std::invalid_argument("x value out of range."); + if (y < 0 || y >= _height) throw std::invalid_argument("y value out of range."); + _pixels[x + (y * _width)] = val; +} +} // namespace quicktex diff --git a/quicktex/Texture.h b/quicktex/Texture.h new file mode 100644 index 0000000..0b72aa2 --- /dev/null +++ b/quicktex/Texture.h @@ -0,0 +1,219 @@ +/* Python-rgbcx 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 . + */ + +#pragma once + +#include +#include +#include + +#include "Block.h" +#include "Color.h" +#include "ndebug.h" + +namespace quicktex { + +class Texture { + public: + virtual ~Texture() = default; + + virtual int Width() const { return _width; } + virtual int Height() const { return _height; } + virtual std::tuple Dimensions() const { return std::tuple(_width, _height); } + + /** + * The texture's total size + * @return The size of the texture in bytes. + */ + virtual size_t Size() const noexcept = 0; + + protected: + Texture(int width, int height) : _width(width), _height(height) { + if (width <= 0) throw std::invalid_argument("Texture width must be greater than 0"); + if (height <= 0) throw std::invalid_argument("Texture height must be greater than 0"); + } + virtual uint8_t *DataMutable() = 0; + + int _width; + int _height; +}; + +class RawTexture : public Texture { + using Base = Texture; + + public: + /** + * Create a new RawTexture + * @param width width of the texture in pixels + * @param height height of the texture in pixels + */ + RawTexture(int width, int height); + + /** + * Move constructor + * @param other object to move + */ + RawTexture(RawTexture &&other); + + /** + * Copy constructor + * @param other object to copy + */ + RawTexture(const RawTexture &other); + + /** + * assignment operator + * @param other object to copy (passed by value) + * @return this + */ + RawTexture &operator=(RawTexture other) noexcept; + + virtual ~RawTexture() override { delete[] _pixels; } + + friend void swap(RawTexture &first, RawTexture &second) noexcept; + + virtual Color GetPixel(int x, int y) const; + + virtual void SetPixel(int x, int y, Color val); + + virtual size_t Size() const noexcept override { return static_cast(Width() * Height()) * sizeof(Color); } + + template ColorBlock GetBlock(int block_x, int block_y) const { + if (block_x < 0 || (block_x + 1) * N > _width) throw std::invalid_argument("x value out of range."); + if (block_y < 0 || (block_y + 1) * M > _height) throw std::invalid_argument("y value out of range."); + + // coordinates in the image of the top-left pixel of the selected block + ColorBlock block; + int pixel_x = block_x * N; + int pixel_y = block_y * M; + + if (pixel_x + N < _width && pixel_y + M < _height) { + // fast memcpy if the block is entirely inside the bounds of the texture + for (int y = 0; y < M; y++) { + // copy each row into the ColorBlock + block.SetRow(y, &_pixels[pixel_x + (_width * (pixel_y + y))]); + } + } else { + // slower pixel-wise copy if the block goes over the edges + for (int x = 0; x < N; x++) { + for (int y = 0; y < M; y++) { block.Set(x, y, GetPixel((pixel_x + x) % _width, (pixel_y + y) % _height)); } + } + } + + return block; + }; + + template void SetBlock(int block_x, int block_y, const ColorBlock &block) { + if (block_x < 0) throw std::invalid_argument("x value out of range."); + if (block_y < 0) throw std::invalid_argument("y value out of range."); + + // coordinates in the image of the top-left pixel of the selected block + int pixel_x = block_x * N; + int pixel_y = block_y * M; + + if (pixel_x + N < _width && pixel_y + M < _height) { + // fast row-wise memcpy if the block is entirely inside the bounds of the texture + for (int y = 0; y < M; y++) { + // copy each row out of the ColorBlock + block.GetRow(y, &_pixels[pixel_x + (_width * (pixel_y + y))]); + } + } else { + // slower pixel-wise copy if the block goes over the edges + for (int x = 0; x < N; x++) { + for (int y = 0; y < M; y++) { SetPixel((pixel_x + x) % _width, (pixel_y + y) % _height, block.Get(x, y)); } + } + } + }; + + protected: + virtual uint8_t *DataMutable() override { return reinterpret_cast(_pixels); } + + Color *_pixels; +}; + +template class BlockTexture : public Texture { + public: + using BlockType = B; + using Base = Texture; + + /** + * Create a new BlockTexture + * @param width width of the texture in pixels. must be divisible by B::Width + * @param height height of the texture in pixels. must be divisible by B::Height + */ + BlockTexture(int width, int height) : Base(width, height) { _blocks = new B[(size_t)(BlocksX() * BlocksY())]; } + + /** + * Move constructor + * @param other object to move + */ + BlockTexture(BlockTexture &&other) : Base(other._width, other._height) { + _blocks = other._blocks; + other._blocks = nullptr; + } + + /** + * Copy constructor + * @param other object to copy + */ + BlockTexture(const BlockTexture &other) : BlockTexture(other.width, other.height) { std::memcpy(_blocks, other._blocks, Size()); } + + /** + * assignment operator + * @param other object to copy (passed by value) + * @return this + */ + BlockTexture &operator=(BlockTexture other) { + swap(*this, other); + return *this; + } + + friend void swap(BlockTexture &first, BlockTexture &second) noexcept { + using std::swap; // enable ADL + swap(first._blocks, second._blocks); + swap(first._width, second._width); + swap(first._height, second._height); + } + + ~BlockTexture() { delete[] _blocks; } + + constexpr int BlocksX() const { return _width / B::Width; } + constexpr int BlocksY() const { return _height / B::Height; } + + virtual B GetBlock(int x, int y) const { + if (x < 0 || x >= BlocksX()) throw std::invalid_argument("x value out of range."); + if (y < 0 || y >= BlocksY()) throw std::invalid_argument("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::invalid_argument("x value out of range."); + if (y < 0 || y >= BlocksY()) throw std::invalid_argument("y value out of range."); + _blocks[x + (y * _width)] = val; + } + + virtual size_t Size() const noexcept override { return (size_t)(BlocksX() * BlocksY()) * sizeof(B); } + + protected: + virtual uint8_t *DataMutable() override { return reinterpret_cast(_blocks); } + + B *_blocks; +}; + +} // namespace quicktex \ No newline at end of file diff --git a/quicktex/_bindings.cpp b/quicktex/_bindings.cpp index e3aefc2..1852e95 100644 --- a/quicktex/_bindings.cpp +++ b/quicktex/_bindings.cpp @@ -17,17 +17,73 @@ along with this program. If not, see . */ +#include "_bindings.h" + #include -#include "BlockDecoder.h" -#include "BlockEncoder.h" +#include "Color.h" +#include "Decoder.h" +#include "Encoder.h" +#include "Texture.h" +#include "_bindings.h" 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 policy, handle parent) { + 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); -py::bytes EncodeImage(const BlockEncoder &self, py::bytes decoded, unsigned image_width, unsigned image_height) { +/*py::bytes EncodeImage(const BlockEncoder &self, py::bytes decoded, unsigned image_width, unsigned image_height) { if (image_width % self.BlockWidth() != 0) throw std::invalid_argument("Width is not an even multiple of block_width"); if (image_height % self.BlockHeight() != 0) throw std::invalid_argument("Height is not an even multiple of block_height"); if (image_width == 0 || image_height == 0) throw std::invalid_argument("Image has zero size"); @@ -49,12 +105,12 @@ py::bytes EncodeImage(const BlockEncoder &self, py::bytes decoded, unsigned imag } py::bytes DecodeImage(const BlockDecoder &self, py::bytes encoded, unsigned image_width, unsigned image_height) { - if (image_width % self.BlockWidth() != 0) throw std::invalid_argument("Width is not an even multiple of block_width"); - if (image_height % self.BlockHeight() != 0) throw std::invalid_argument("Height is not an even multiple of block_height"); + if (image_width % self.WidthInBlocks() != 0) throw std::invalid_argument("Width is not an even multiple of block_width"); + if (image_height % self.HeightInBlocks() != 0) throw std::invalid_argument("Height is not an even multiple of block_height"); if (image_width == 0 || image_height == 0) throw std::invalid_argument("Image has zero size"); size_t size = image_width * image_height; - size_t block_size = (size / (self.BlockHeight() * self.BlockWidth())) * self.BlockSize(); + size_t block_size = (size / (self.HeightInBlocks() * self.WidthInBlocks())) * self.BlockSize(); size_t color_size = size * sizeof(Color); std::string encoded_str = (std::string)encoded; // encoded data is copied here, unfortunately @@ -67,28 +123,25 @@ py::bytes DecodeImage(const BlockDecoder &self, py::bytes encoded, unsigned imag auto bytes = py::bytes(decoded_str); // decoded data is copied here, unfortunately return bytes; -} +}*/ PYBIND11_MODULE(_quicktex, m) { m.doc() = "More Stuff"; py::options options; - // BlockDecoder - py::class_ block_decoder(m, "BlockDecoder"); + py::class_ texture(m, "Texture"); - block_decoder.def("decode_image", &DecodeImage); - block_decoder.def_property_readonly("block_size", &BlockDecoder::BlockSize); - block_decoder.def_property_readonly("block_width", &BlockDecoder::BlockWidth); - block_decoder.def_property_readonly("block_height", &BlockDecoder::BlockHeight); + texture.def_property_readonly("size", &Texture::Size); + texture.def_property_readonly("dimensions", &Texture::Dimensions); + texture.def_property_readonly("width", &Texture::Width); + texture.def_property_readonly("height", &Texture::Height); - // BlockEncoder - py::class_ block_encoder(m, "BlockEncoder"); + py::class_ raw_texture(m, "RawTexture", texture); - block_encoder.def("encode_image", &EncodeImage); - block_encoder.def_property_readonly("block_size", &BlockEncoder::BlockSize); - block_encoder.def_property_readonly("block_width", &BlockEncoder::BlockWidth); - block_encoder.def_property_readonly("block_height", &BlockEncoder::BlockHeight); + raw_texture.def(py::init(), "width"_a, "height"_a); + raw_texture.def("get_pixel", &RawTexture::GetPixel); + raw_texture.def("set_pixel", &RawTexture::SetPixel); InitS3TC(m); } diff --git a/quicktex/_bindings.h b/quicktex/_bindings.h new file mode 100644 index 0000000..7507e6a --- /dev/null +++ b/quicktex/_bindings.h @@ -0,0 +1,54 @@ +/* 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 . + */ + +#pragma once + +#include + +#include +#include + +#include "Block.h" +#include "Color.h" +#include "Texture.h" + +namespace pybind11::detail { +template <> struct type_caster; +} // namespace pybind11::detail + +namespace py = pybind11; +namespace quicktex::bindings { + +using namespace pybind11::literals; + +template py::class_> BindBlockTexture(py::module_& m, const char* name) { + using BTex = BlockTexture; + + py::type texture = py::type::of(); + + py::class_ block_texture(m, name, texture); + + 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("blocks_x", &BTex::BlocksX); + block_texture.def_property_readonly("blocks_y", &BTex::BlocksY); +} +} // namespace quicktex::bindings \ No newline at end of file diff --git a/quicktex/s3tc/bc1/BC1Block.h b/quicktex/s3tc/bc1/BC1Block.h index b618e1e..efefe5e 100644 --- a/quicktex/s3tc/bc1/BC1Block.h +++ b/quicktex/s3tc/bc1/BC1Block.h @@ -24,15 +24,18 @@ #include #include +#include "../../Block.h" #include "../../Color.h" #include "../../util.h" -namespace quicktex::s3tc { +namespace quicktex::s3tc { #pragma pack(push, 1) -class BC1Block { +class BC1Block : public Block<4, 4> { public: - using UnpackedSelectors = std::array, 4>; + using UnpackedSelectors = std::array, Height>; + + 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)); } diff --git a/quicktex/s3tc/bc1/BC1Decoder.cpp b/quicktex/s3tc/bc1/BC1Decoder.cpp index 6a39bea..f3b7ec4 100644 --- a/quicktex/s3tc/bc1/BC1Decoder.cpp +++ b/quicktex/s3tc/bc1/BC1Decoder.cpp @@ -23,34 +23,32 @@ #include #include -#include "../../BlockView.h" #include "../../Color.h" #include "../../ndebug.h" #include "BC1Block.h" -namespace quicktex::s3tc { -void BC1Decoder::DecodeBlock(Color4x4 dest, BC1Block *const block) const noexcept(ndebug) { - DecodeBlock(dest, block, true); -} +namespace quicktex::s3tc { -void BC1Decoder::DecodeBlock(Color4x4 dest, BC1Block *const block, bool allow_3color) const noexcept(ndebug) { - const auto l = block->GetLowColor(); - const auto h = block->GetHighColor(); - const auto selectors = block->UnpackSelectors(); - const auto colors = _interpolator->InterpolateBC1(l, h, allow_3color); +ColorBlock<4, 4> BC1Decoder::DecodeBlock(const BC1Block &block) const { return DecodeBlock(block, false); } + +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 colors = _interpolator->InterpolateBC1(l, h, use_3color); for (unsigned y = 0; y < 4; y++) { for (unsigned x = 0; x < 4; x++) { const auto selector = selectors[y][x]; - const auto color = colors[selector]; + auto color = colors[selector]; assert(selector < 4); assert((color.a == 0 && selector == 3 && l <= h) || color.a == UINT8_MAX); - if (write_alpha) { - dest.Get(x, y).SetRGBA(color); - } else { - dest.Get(x, y).SetRGB(color); - } + if (!write_alpha) { color.a = output.Get(x, y).a; } + output.Set(x, y, color); } } + + return output; } } // namespace quicktex::s3tc diff --git a/quicktex/s3tc/bc1/BC1Decoder.h b/quicktex/s3tc/bc1/BC1Decoder.h index c20b0a1..d207020 100644 --- a/quicktex/s3tc/bc1/BC1Decoder.h +++ b/quicktex/s3tc/bc1/BC1Decoder.h @@ -22,14 +22,14 @@ #include #include -#include "../../BlockDecoder.h" -#include "../../BlockView.h" +#include "../../Decoder.h" +#include "../../Texture.h" #include "../../ndebug.h" #include "../interpolator/Interpolator.h" #include "BC1Block.h" namespace quicktex::s3tc { -class BC1Decoder final : public BlockDecoderTemplate { +class BC1Decoder final : public BlockDecoder> { public: using InterpolatorPtr = std::shared_ptr; @@ -39,9 +39,8 @@ class BC1Decoder final : public BlockDecoderTemplate { BC1Decoder(InterpolatorPtr interpolator) : BC1Decoder(false, interpolator) {} - void DecodeBlock(Color4x4 dest, BC1Block *const block) const noexcept(ndebug) override; - - void DecodeBlock(Color4x4 dest, BC1Block *const block, bool allow_3color) const noexcept(ndebug); + ColorBlock<4, 4> DecodeBlock(const BC1Block& block) const override; + ColorBlock<4, 4> DecodeBlock(const BC1Block& block, bool use_3color) const; InterpolatorPtr GetInterpolator() const { return _interpolator; } diff --git a/quicktex/s3tc/bc1/BC1Encoder.cpp b/quicktex/s3tc/bc1/BC1Encoder.cpp index ede02a9..698de1b 100644 --- a/quicktex/s3tc/bc1/BC1Encoder.cpp +++ b/quicktex/s3tc/bc1/BC1Encoder.cpp @@ -28,7 +28,7 @@ #include #include -#include "../../BlockView.h" +#include "../../Block.h" #include "../../Color.h" #include "../../Matrix4x4.h" #include "../../Vector4.h" @@ -42,6 +42,9 @@ namespace quicktex::s3tc { +using CBlock = ColorBlock<4, 4>; +using BlockMetrics = CBlock::Metrics; + // constructors BC1Encoder::BC1Encoder(unsigned int level, ColorMode color_mode, InterpolatorPtr interpolator) : _interpolator(interpolator), _color_mode(color_mode) { @@ -258,11 +261,10 @@ void BC1Encoder::SetOrderings(OrderingPair orderings) { void BC1Encoder::SetPowerIterations(unsigned int power_iters) { _power_iterations = clamp(power_iters, min_power_iterations, max_power_iterations); } // Public methods -void BC1Encoder::EncodeBlock(Color4x4 pixels, BC1Block *dest) const { +BC1Block BC1Encoder::EncodeBlock(const ColorBlock<4, 4> &pixels) const { if (pixels.IsSingleColor()) { // single-color pixel block, do it the fast way - WriteBlockSolid(pixels.Get(0, 0), dest); - return; + return WriteBlockSolid(pixels.Get(0, 0)); } auto metrics = pixels.GetMetrics(); @@ -289,12 +291,12 @@ void BC1Encoder::EncodeBlock(Color4x4 pixels, BC1Block *dest) const { EndpointMode endpoint_mode = (round == 1) ? EndpointMode::BoundingBox : _endpoint_mode; EncodeResults trial_orig; - FindEndpoints(pixels, trial_orig, metrics, endpoint_mode); + FindEndpoints(trial_orig, pixels, metrics, endpoint_mode); EncodeResults trial_result = trial_orig; - FindSelectors(pixels, trial_result, error_mode); - RefineBlockLS(pixels, trial_result, metrics, error_mode, total_ls_passes); + FindSelectors(trial_result, pixels, error_mode); + RefineBlockLS(trial_result, pixels, metrics, error_mode, total_ls_passes); if (!needs_block_error || trial_result.error < result.error) { result = trial_result; @@ -304,20 +306,20 @@ void BC1Encoder::EncodeBlock(Color4x4 pixels, BC1Block *dest) const { // First refinement pass using ordered cluster fit if (result.error > 0 && use_likely_orderings) { - for (unsigned iter = 0; iter < total_cf_passes; iter++) { RefineBlockCF(pixels, result, metrics, _error_mode, _orderings4); } + for (unsigned iter = 0; iter < total_cf_passes; iter++) { RefineBlockCF(result, pixels, metrics, _error_mode, _orderings4); } } // try for 3-color block if (result.error > 0 && (bool)(_color_mode & ColorMode::ThreeColor)) { EncodeResults trial_result = orig; - FindSelectors(pixels, trial_result, ErrorMode::Full); - RefineBlockLS(pixels, trial_result, metrics, ErrorMode::Full, total_ls_passes); + FindSelectors(trial_result, pixels, ErrorMode::Full); + RefineBlockLS(trial_result, pixels, metrics, ErrorMode::Full, total_ls_passes); // First refinement pass using ordered cluster fit if (trial_result.error > 0 && use_likely_orderings) { for (unsigned iter = 0; iter < total_cf_passes; iter++) { - RefineBlockCF(pixels, trial_result, metrics, ErrorMode::Full, _orderings3); + RefineBlockCF(trial_result, pixels, metrics, ErrorMode::Full, _orderings3); } } @@ -329,21 +331,22 @@ void BC1Encoder::EncodeBlock(Color4x4 pixels, BC1Block *dest) const { EncodeResults trial_result; BlockMetrics metrics_no_black = pixels.GetMetrics(true); - FindEndpoints(pixels, trial_result, metrics_no_black, EndpointMode::PCA, true); - FindSelectors(pixels, trial_result, ErrorMode::Full); - RefineBlockLS(pixels, trial_result, metrics_no_black, ErrorMode::Full, total_ls_passes); + FindEndpoints(trial_result, pixels, metrics_no_black, EndpointMode::PCA, true); + FindSelectors(trial_result, pixels, ErrorMode::Full); + RefineBlockLS(trial_result, pixels, metrics_no_black, ErrorMode::Full, total_ls_passes); if (trial_result.error < result.error) { result = trial_result; } } // refine endpoints by searching for nearby colors - if (result.error > 0 && _search_rounds > 0) { EndpointSearch(pixels, result); } + if (result.error > 0 && _search_rounds > 0) { EndpointSearch(result, pixels); } - WriteBlock(result, dest); + WriteBlock(result); } // Private methods -void BC1Encoder::WriteBlockSolid(Color color, BC1Block *dest) const { +BC1Block BC1Encoder::WriteBlockSolid(Color color) const { + BC1Block block; uint8_t mask = 0xAA; // 2222 uint16_t min16, max16; @@ -390,23 +393,25 @@ void BC1Encoder::WriteBlockSolid(Color color, BC1Block *dest) const { } } - dest->SetLowColor(max16); - dest->SetHighColor(min16); - dest->selectors[0] = mask; - dest->selectors[1] = mask; - dest->selectors[2] = mask; - dest->selectors[3] = mask; + block.SetLowColor(max16); + block.SetHighColor(min16); + block.selectors[0] = mask; + block.selectors[1] = mask; + block.selectors[2] = mask; + block.selectors[3] = mask; + return block; } -void BC1Encoder::WriteBlock(EncodeResults &block, BC1Block *dest) const { +BC1Block BC1Encoder::WriteBlock(EncodeResults &result) const { + BC1Block block; BC1Block::UnpackedSelectors selectors; - uint16_t color1 = block.low.Pack565Unscaled(); - uint16_t color0 = block.high.Pack565Unscaled(); + uint16_t color1 = result.low.Pack565Unscaled(); + uint16_t color0 = result.high.Pack565Unscaled(); std::array lut; - assert(block.color_mode != ColorMode::Incomplete); + assert(result.color_mode != ColorMode::Incomplete); - if ((bool)(block.color_mode & ColorMode::FourColor)) { + if ((bool)(result.color_mode & ColorMode::FourColor)) { lut = {1, 3, 2, 0}; if (color1 > color0) { @@ -439,16 +444,16 @@ void BC1Encoder::WriteBlock(EncodeResults &block, BC1Block *dest) const { for (unsigned i = 0; i < 16; i++) { unsigned x = i % 4; unsigned y = i / 4; - selectors[y][x] = lut[block.selectors[i]]; - if (block.color_mode == ColorMode::ThreeColor) { assert(selectors[y][x] != 3); } + selectors[y][x] = lut[result.selectors[i]]; + if (result.color_mode == ColorMode::ThreeColor) { assert(selectors[y][x] != 3); } } - dest->SetLowColor(color0); - dest->SetHighColor(color1); - dest->PackSelectors(selectors); + block.SetLowColor(color0); + block.SetHighColor(color1); + block.PackSelectors(selectors); } -void BC1Encoder::FindEndpointsSingleColor(EncodeResults &block, Color color, bool is_3color) const { +void BC1Encoder::FindEndpointsSingleColor(EncodeResults &result, Color color, bool is_3color) const { auto &match5 = is_3color ? _single_match5_half : _single_match5; auto &match6 = is_3color ? _single_match6_half : _single_match6; @@ -456,40 +461,40 @@ void BC1Encoder::FindEndpointsSingleColor(EncodeResults &block, Color color, boo BC1MatchEntry match_g = match6->at(color.g); BC1MatchEntry match_b = match5->at(color.b); - block.color_mode = is_3color ? ColorMode::ThreeColor : ColorMode::FourColor; - block.error = match_r.error + match_g.error + match_b.error; - block.low = Color(match_r.low, match_g.low, match_b.low); - block.high = Color(match_r.high, match_g.high, match_b.high); + result.color_mode = is_3color ? ColorMode::ThreeColor : ColorMode::FourColor; + result.error = match_r.error + match_g.error + match_b.error; + result.low = Color(match_r.low, match_g.low, match_b.low); + result.high = Color(match_r.high, match_g.high, match_b.high); // selectors decided when writing, no point deciding them now } -void BC1Encoder::FindEndpointsSingleColor(EncodeResults &block, Color4x4 &pixels, Color color, bool is_3color) const { - std::array colors = _interpolator->InterpolateBC1(block.low, block.high, is_3color); +void BC1Encoder::FindEndpointsSingleColor(EncodeResults &result, const CBlock &pixels, Color color, bool is_3color) const { + std::array colors = _interpolator->InterpolateBC1(result.low, result.high, is_3color); Vector4Int result_vector = (Vector4Int)colors[2]; - FindEndpointsSingleColor(block, color, is_3color); + FindEndpointsSingleColor(result, color, is_3color); - block.error = 0; + result.error = 0; for (unsigned i = 0; i < 16; i++) { Vector4Int pixel_vector = (Vector4Int)pixels.Get(i); auto diff = pixel_vector - result_vector; - block.error += diff.SqrMag(); - block.selectors[i] = 1; + result.error += diff.SqrMag(); + result.selectors[i] = 1; } } -void BC1Encoder::FindEndpoints(Color4x4 pixels, EncodeResults &block, const BlockMetrics &metrics, EndpointMode endpoint_mode, bool ignore_black) const { +void BC1Encoder::FindEndpoints(EncodeResults &result, const CBlock &pixels, const BlockMetrics &metrics, EndpointMode endpoint_mode, bool ignore_black) const { if (metrics.is_greyscale) { // specialized greyscale case - const unsigned fr = pixels.Get(0).r; + const unsigned fr = pixels.Get(0, 0).r; if (metrics.max.r - metrics.min.r < 2) { // single color block uint8_t fr5 = (uint8_t)scale8To5(fr); uint8_t fr6 = (uint8_t)scale8To6(fr); - block.low = Color(fr5, fr6, fr5); - block.high = block.low; + result.low = Color(fr5, fr6, fr5); + result.high = result.low; } else { uint8_t lr5 = scale8To5(metrics.min.r); uint8_t lr6 = scale8To6(metrics.min.r); @@ -497,8 +502,8 @@ void BC1Encoder::FindEndpoints(Color4x4 pixels, EncodeResults &block, const Bloc uint8_t hr5 = scale8To5(metrics.max.r); uint8_t hr6 = scale8To6(metrics.max.r); - block.low = Color(lr5, lr6, lr5); - block.high = Color(hr5, hr6, hr5); + result.low = Color(lr5, lr6, lr5); + result.high = Color(hr5, hr6, hr5); } } else if (endpoint_mode == EndpointMode::LeastSquares) { // 2D Least Squares approach from Humus's example, with added inset and optimal rounding. @@ -556,8 +561,8 @@ void BC1Encoder::FindEndpoints(Color4x4 pixels, EncodeResults &block, const Bloc h[c] = ((h[c] - inset) / 255.0f); } - block.low = Color::PreciseRound565(l); - block.high = Color::PreciseRound565(h); + result.low = Color::PreciseRound565(l); + result.high = Color::PreciseRound565(h); } else if (endpoint_mode == EndpointMode::BoundingBox) { // Algorithm from icbc.h compress_dxt1_fast() Vector4 l, h; @@ -584,8 +589,8 @@ void BC1Encoder::FindEndpoints(Color4x4 pixels, EncodeResults &block, const Bloc if (icov_xz < 0) std::swap(l[0], h[0]); if (icov_yz < 0) std::swap(l[1], h[1]); - block.low = Color::PreciseRound565(l); - block.high = Color::PreciseRound565(h); + result.low = Color::PreciseRound565(l); + result.high = Color::PreciseRound565(h); } else if (endpoint_mode == EndpointMode::BoundingBoxInt) { // Algorithm from icbc.h compress_dxt1_fast(), but converted to integer. @@ -609,8 +614,8 @@ void BC1Encoder::FindEndpoints(Color4x4 pixels, EncodeResults &block, const Bloc if (icov_xz < 0) std::swap(min.r, max.r); if (icov_yz < 0) std::swap(min.g, max.g); - block.low = min.ScaleTo565(); - block.high = max.ScaleTo565(); + result.low = min.ScaleTo565(); + result.high = max.ScaleTo565(); } else if (endpoint_mode == EndpointMode::PCA) { // the slow way // Select 2 colors along the principle axis. (There must be a faster/simpler way.) @@ -677,19 +682,19 @@ void BC1Encoder::FindEndpoints(Color4x4 pixels, EncodeResults &block, const Bloc } } - block.low = pixels.Get(min_index).ScaleTo565(); - block.high = pixels.Get(max_index).ScaleTo565(); + result.low = pixels.Get(min_index).ScaleTo565(); + result.high = pixels.Get(max_index).ScaleTo565(); } - block.color_mode = ColorMode::Incomplete; + result.color_mode = ColorMode::Incomplete; } -template void BC1Encoder::FindSelectors(Color4x4 &pixels, EncodeResults &block, ErrorMode error_mode) const { +template void BC1Encoder::FindSelectors(EncodeResults &result, const CBlock &pixels, ErrorMode error_mode) const { assert(!((error_mode != ErrorMode::Full) && (bool)(M & ColorMode::ThreeColor))); const int color_count = (unsigned)M & 0x0F; - std::array colors = _interpolator->InterpolateBC1(block.low, block.high, color_count == 3); + std::array colors = _interpolator->InterpolateBC1(result.low, result.high, color_count == 3); std::array color_vectors; if (color_count == 4) { @@ -721,10 +726,10 @@ template void BC1Encoder::FindSelectors(Color4x4 &pixe // llvm is just going to unswitch this anyways so its not an issue auto diff = pixel_vector - color_vectors[selector]; total_error += diff.SqrMag(); - if (i % 4 != 0 && total_error >= block.error) break; // check only once per row if we're generating too much error + if (i % 4 != 0 && total_error >= result.error) break; // check only once per row if we're generating too much error } - block.selectors[i] = selector; + result.selectors[i] = selector; } } else if (error_mode == ErrorMode::Check2) { Vector4Int axis = color_vectors[3] - color_vectors[0]; @@ -751,9 +756,9 @@ template void BC1Encoder::FindSelectors(Color4x4 &pixe total_error += best_err; - if (total_error >= block.error) break; + if (total_error >= result.error) break; - block.selectors[i] = best_sel; + result.selectors[i] = best_sel; } } else if (error_mode == ErrorMode::Full) { unsigned max_sel = (bool)(M == ColorMode::ThreeColor) ? 3 : 4; @@ -774,22 +779,22 @@ template void BC1Encoder::FindSelectors(Color4x4 &pixe } total_error += best_error; - if (total_error >= block.error) { break; } + if (total_error >= result.error) { break; } assert(best_sel < max_sel); - block.selectors[i] = best_sel; + result.selectors[i] = best_sel; } } else { assert(false); } - block.error = total_error; - block.color_mode = M; + result.error = total_error; + result.color_mode = M; } -template bool BC1Encoder::RefineEndpointsLS(Color4x4 pixels, EncodeResults &block, BlockMetrics metrics) const { +template bool BC1Encoder::RefineEndpointsLS(EncodeResults &result, const CBlock &pixels, BlockMetrics metrics) const { const int color_count = (unsigned)M & 0x0F; static_assert(color_count == 3 || color_count == 4); - assert(block.color_mode != ColorMode::Incomplete); + assert(result.color_mode != ColorMode::Incomplete); int denominator = color_count - 1; @@ -798,7 +803,7 @@ template bool BC1Encoder::RefineEndpointsLS(Color4x4 p for (unsigned i = 0; i < 16; i++) { const Color color = pixels.Get(i); - const uint8_t sel = block.selectors[i]; + const uint8_t sel = result.selectors[i]; if ((bool)(M & ColorMode::ThreeColorBlack) && color.IsBlack()) continue; if ((bool)(M & ColorMode::ThreeColor) && sel == 3U) continue; // NOTE: selectors for 3-color are in linear order here, but not in original @@ -813,7 +818,7 @@ template bool BC1Encoder::RefineEndpointsLS(Color4x4 p // invert matrix float det = matrix.Determinant2x2(); // z00 * z11 - z01 * z10; if (fabs(det) < 1e-8f) { - block.color_mode = ColorMode::Incomplete; + result.color_mode = ColorMode::Incomplete; return false; } @@ -826,16 +831,16 @@ template bool BC1Encoder::RefineEndpointsLS(Color4x4 p Vector4 low = (matrix[0] * q00) + (matrix[1] * q10); Vector4 high = (matrix[2] * q00) + (matrix[3] * q10); - block.color_mode = M; - block.low = Color::PreciseRound565(low); - block.high = Color::PreciseRound565(high); + result.color_mode = M; + result.low = Color::PreciseRound565(low); + result.high = Color::PreciseRound565(high); return true; } -template void BC1Encoder::RefineEndpointsLS(std::array &sums, EncodeResults &block, Vector4 &matrix, Hash hash) const { +template void BC1Encoder::RefineEndpointsLS(EncodeResults &result, std::array &sums, Vector4 &matrix, Hash hash) const { const int color_count = (unsigned)M & 0x0F; static_assert(color_count == 3 || color_count == 4); - assert(block.color_mode != ColorMode::Incomplete); + assert(result.color_mode != ColorMode::Incomplete); int denominator = color_count - 1; @@ -852,30 +857,30 @@ template void BC1Encoder::RefineEndpointsLS(std::array Vector4 low = (matrix[0] * q00) + (matrix[1] * q10); Vector4 high = (matrix[2] * q00) + (matrix[3] * q10); - block.color_mode = M; - block.low = Color::PreciseRound565(low); - block.high = Color::PreciseRound565(high); + result.color_mode = M; + result.low = Color::PreciseRound565(low); + result.high = Color::PreciseRound565(high); } template -void BC1Encoder::RefineBlockLS(Color4x4 &pixels, EncodeResults &block, BlockMetrics &metrics, ErrorMode error_mode, unsigned passes) const { +void BC1Encoder::RefineBlockLS(EncodeResults &result, const CBlock &pixels, const BlockMetrics &metrics, ErrorMode error_mode, unsigned passes) const { assert(error_mode != ErrorMode::None || passes == 1); for (unsigned pass = 0; pass < passes; pass++) { - EncodeResults trial_result = block; + EncodeResults trial_result = result; Vector4 low, high; - bool multicolor = RefineEndpointsLS(pixels, trial_result, metrics); + bool multicolor = RefineEndpointsLS(trial_result, pixels, metrics); if (!multicolor) { FindEndpointsSingleColor(trial_result, pixels, metrics.avg, (M != ColorMode::FourColor)); } else { - FindSelectors(pixels, trial_result, error_mode); + FindSelectors(trial_result, pixels, error_mode); } - if (trial_result.low == block.low && trial_result.high == block.high) break; + if (trial_result.low == result.low && trial_result.high == result.high) break; - if (error_mode == ErrorMode::None || trial_result.error < block.error) { - block = trial_result; + if (error_mode == ErrorMode::None || trial_result.error < result.error) { + result = trial_result; } else { return; } @@ -883,15 +888,15 @@ void BC1Encoder::RefineBlockLS(Color4x4 &pixels, EncodeResults &block, BlockMetr } template -void BC1Encoder::RefineBlockCF(Color4x4 &pixels, EncodeResults &block, BlockMetrics &metrics, ErrorMode error_mode, unsigned orderings) const { +void BC1Encoder::RefineBlockCF(EncodeResults &result, const CBlock &pixels, const BlockMetrics &metrics, ErrorMode error_mode, unsigned orderings) const { const int color_count = (unsigned)M & 0x0F; static_assert(color_count == 3 || color_count == 4); - assert(block.color_mode != ColorMode::Incomplete); + assert(result.color_mode != ColorMode::Incomplete); using OrderTable = OrderTable; using Hist = Histogram; - EncodeResults orig = block; + EncodeResults orig = result; Hist h = Hist(orig.selectors); Hash start_hash = OrderTable::GetHash(h); @@ -900,11 +905,11 @@ void BC1Encoder::RefineBlockCF(Color4x4 &pixels, EncodeResults &block, BlockMetr std::array color_vectors; std::array dots; - for (unsigned i = 0; i < 16; i++) { - color_vectors[i] = Vector4::FromColorRGB(pixels.Get(i)); - int dot = 0x1000000 + (int)color_vectors[i].Dot(axis); + for (int i = 0; i < 16; i++) { + color_vectors[(unsigned)i] = Vector4::FromColorRGB(pixels.Get(i)); + int dot = 0x1000000 + (int)color_vectors[(unsigned)i].Dot(axis); assert(dot >= 0); - dots[i] = (uint32_t)(dot << 4) | i; + dots[(unsigned)i] = (uint32_t)(dot << 4) | i; } std::sort(dots.begin(), dots.end()); @@ -926,17 +931,17 @@ void BC1Encoder::RefineBlockCF(Color4x4 &pixels, EncodeResults &block, BlockMetr if (OrderTable::IsSingleColor(trial_hash)) { FindEndpointsSingleColor(trial_result, pixels, metrics.avg, (color_count == 3)); } else { - RefineEndpointsLS(sums, trial_result, trial_matrix, trial_hash); - FindSelectors(pixels, trial_result, error_mode); + RefineEndpointsLS(trial_result, sums, trial_matrix, trial_hash); + FindSelectors(trial_result, pixels, error_mode); } - if (trial_result.error < block.error) { block = trial_result; } + if (trial_result.error < result.error) { result = trial_result; } if (trial_result.error == 0) break; } } -void BC1Encoder::EndpointSearch(Color4x4 &pixels, EncodeResults &block) const { - if (block.solid) return; +void BC1Encoder::EndpointSearch(EncodeResults &result, const CBlock &pixels) const { + if (result.solid) return; static const std::array Voxels = {{ {1, 0, 0, 3}, // 0 @@ -967,7 +972,7 @@ void BC1Encoder::EndpointSearch(Color4x4 &pixels, EncodeResults &block) const { if ((int)(i & 31) == forbidden_direction) continue; Vector4Int delta = Voxels[voxel_index]; - EncodeResults trial_result = block; + EncodeResults trial_result = result; if (i & 16) { trial_result.low.r = (uint8_t)clamp(trial_result.low.r + delta[0], 0, 31); @@ -979,21 +984,21 @@ void BC1Encoder::EndpointSearch(Color4x4 &pixels, EncodeResults &block) const { trial_result.high.b = (uint8_t)clamp(trial_result.high.b + delta[2], 0, 31); } - switch (block.color_mode) { + switch (result.color_mode) { default: case ColorMode::FourColor: - FindSelectors(pixels, trial_result, _error_mode); + FindSelectors(trial_result, pixels, _error_mode); break; case ColorMode::ThreeColor: - FindSelectors(pixels, trial_result, ErrorMode::Full); + FindSelectors(trial_result, pixels, ErrorMode::Full); break; case ColorMode::ThreeColorBlack: - FindSelectors(pixels, trial_result, ErrorMode::Full); + FindSelectors(trial_result, pixels, ErrorMode::Full); break; } - if (trial_result.error < block.error) { - block = trial_result; + if (trial_result.error < result.error) { + result = trial_result; forbidden_direction = delta[3] | (int)(i & 16); prev_improvement_index = i; diff --git a/quicktex/s3tc/bc1/BC1Encoder.h b/quicktex/s3tc/bc1/BC1Encoder.h index b58b887..7fa6cec 100644 --- a/quicktex/s3tc/bc1/BC1Encoder.h +++ b/quicktex/s3tc/bc1/BC1Encoder.h @@ -27,9 +27,10 @@ #include #include -#include "../../BlockEncoder.h" -#include "../../BlockView.h" +#include "../../Block.h" #include "../../Color.h" +#include "../../Encoder.h" +#include "../../Texture.h" #include "../interpolator/Interpolator.h" #include "BC1Block.h" #include "SingleColorTable.h" @@ -40,10 +41,11 @@ class Vector4; namespace quicktex::s3tc { -class BC1Encoder final : public BlockEncoderTemplate { +class BC1Encoder final : public BlockEncoder> { public: using InterpolatorPtr = std::shared_ptr; using OrderingPair = std::tuple; + using CBlock = ColorBlock<4, 4>; inline static constexpr unsigned min_power_iterations = 4; inline static constexpr unsigned max_power_iterations = 10; @@ -130,13 +132,13 @@ class BC1Encoder final : public BlockEncoderTemplate { void SetPowerIterations(unsigned power_iters); // Public Methods - void EncodeBlock(Color4x4 pixels, BC1Block *dest) const override; + BC1Block EncodeBlock(const CBlock &pixels) const override; virtual size_t MTThreshold() const override { return 16; } private: using Hash = uint16_t; - using BlockMetrics = Color4x4::BlockMetrics; + using BlockMetrics = CBlock::Metrics; // Unpacked BC1 block with metadata struct EncodeResults { @@ -168,23 +170,25 @@ class BC1Encoder final : public BlockEncoderTemplate { unsigned _orderings4; unsigned _orderings3; - void WriteBlockSolid(Color color, BC1Block *dest) const; - void WriteBlock(EncodeResults &block, BC1Block *dest) const; + BC1Block WriteBlockSolid(Color color) const; + BC1Block WriteBlock(EncodeResults &result) const; - void FindEndpoints(Color4x4 pixels, EncodeResults &block, const BlockMetrics &metrics, EndpointMode endpoint_mode, bool ignore_black = false) const; - void FindEndpointsSingleColor(EncodeResults &block, Color color, bool is_3color = false) const; - void FindEndpointsSingleColor(EncodeResults &block, Color4x4 &pixels, Color color, bool is_3color) const; + void FindEndpoints(EncodeResults &result, const CBlock &pixels, const BlockMetrics &metrics, EndpointMode endpoint_mode, bool ignore_black = false) const; + void FindEndpointsSingleColor(EncodeResults &result, Color color, bool is_3color = false) const; + void FindEndpointsSingleColor(EncodeResults &result, const CBlock &pixels, Color color, bool is_3color) const; - template void FindSelectors(Color4x4 &pixels, EncodeResults &block, ErrorMode error_mode) const; + template void FindSelectors(EncodeResults &result, const CBlock &pixels, ErrorMode error_mode) const; - template bool RefineEndpointsLS(Color4x4 pixels, EncodeResults &block, BlockMetrics metrics) const; + template bool RefineEndpointsLS(EncodeResults &result, const CBlock &pixels, BlockMetrics metrics) const; - template void RefineEndpointsLS(std::array &sums, EncodeResults &block, Vector4 &matrix, Hash hash) const; + template void RefineEndpointsLS(EncodeResults &result, std::array &sums, Vector4 &matrix, Hash hash) const; - template void RefineBlockLS(Color4x4 &pixels, EncodeResults &block, BlockMetrics &metrics, ErrorMode error_mode, unsigned passes) const; + template + void RefineBlockLS(EncodeResults &result, const CBlock &pixels, const BlockMetrics &metrics, ErrorMode error_mode, unsigned passes) const; - template void RefineBlockCF(Color4x4 &pixels, EncodeResults &block, BlockMetrics &metrics, ErrorMode error_mode, unsigned orderings) const; + template + void RefineBlockCF(EncodeResults &result, const CBlock &pixels, const BlockMetrics &metrics, ErrorMode error_mode, unsigned orderings) const; - void EndpointSearch(Color4x4 &pixels, EncodeResults &block) const; + void EndpointSearch(EncodeResults &result, const CBlock &pixels) const; }; } // namespace quicktex::s3tc diff --git a/quicktex/s3tc/bc1/_bindings.cpp b/quicktex/s3tc/bc1/_bindings.cpp index 01a75de..2320d84 100644 --- a/quicktex/s3tc/bc1/_bindings.cpp +++ b/quicktex/s3tc/bc1/_bindings.cpp @@ -25,8 +25,8 @@ #include #include -#include "../../BlockDecoder.h" -#include "../../BlockEncoder.h" +#include "../../Decoder.h" +#include "../../Encoder.h" #include "../interpolator/Interpolator.h" #include "BC1Decoder.h" #include "BC1Encoder.h" @@ -41,16 +41,12 @@ using InterpolatorPtr = std::shared_ptr; void InitBC1(py::module_ &s3tc) { auto bc1 = s3tc.def_submodule("_bc1", "internal bc1 module"); - auto block_encoder = py::type::of(); - auto block_decoder = py::type::of(); py::options options; options.disable_function_signatures(); // BC1Encoder - py::class_ bc1_encoder(bc1, "BC1Encoder", block_encoder, R"doc( - Base: :py:class:`~quicktex.BlockEncoder` - + py::class_ bc1_encoder(bc1, "BC1Encoder" R"doc( Encodes RGB textures to BC1. )doc"); @@ -133,7 +129,7 @@ void InitBC1(py::module_ &s3tc) { "Automatically clamped to between :py:const:`BC1Encoder.min_power_iterations` and :py:const:`BC1Encoder.max_power_iterations`"); // BC1Decoder - py::class_ bc1_decoder(bc1, "BC1Decoder", block_decoder, R"doc( + py::class_ bc1_decoder(bc1, "BC1Decoder", R"doc( Base: :py:class:`~quicktex.BlockDecoder` Decodes BC1 textures to RGB diff --git a/quicktex/s3tc/bc3/BC3Block.h b/quicktex/s3tc/bc3/BC3Block.h index 6d76fb9..7f741e6 100644 --- a/quicktex/s3tc/bc3/BC3Block.h +++ b/quicktex/s3tc/bc3/BC3Block.h @@ -19,13 +19,14 @@ #pragma once +#include "../../Block.h" #include "../bc1/BC1Block.h" #include "../bc4/BC4Block.h" -namespace quicktex::s3tc { +namespace quicktex::s3tc { #pragma pack(push, 1) -class BC3Block { +class BC3Block : public Block<4, 4> { public: BC4Block alpha_block; BC1Block color_block; diff --git a/quicktex/s3tc/bc3/BC3Decoder.cpp b/quicktex/s3tc/bc3/BC3Decoder.cpp index df5b31c..d95ae2d 100644 --- a/quicktex/s3tc/bc3/BC3Decoder.cpp +++ b/quicktex/s3tc/bc3/BC3Decoder.cpp @@ -21,14 +21,17 @@ #include -#include "../../BlockView.h" +#include "../../Block.h" #include "../../ndebug.h" #include "BC3Block.h" -namespace quicktex::s3tc { +namespace quicktex::s3tc { -void BC3Decoder::DecodeBlock(Color4x4 dest, BC3Block *const block) const noexcept(ndebug) { - _bc1_decoder->DecodeBlock(dest, &(block->color_block), false); - _bc4_decoder->DecodeBlock(dest, &(block->alpha_block)); +ColorBlock<4, 4> BC3Decoder::DecodeBlock(const BC3Block &block) const { + auto output = _bc1_decoder->DecodeBlock(block.color_block, false); + + _bc4_decoder->DecodeInto(output, block.alpha_block); + + return output; } } // namespace quicktex::s3tc \ No newline at end of file diff --git a/quicktex/s3tc/bc3/BC3Decoder.h b/quicktex/s3tc/bc3/BC3Decoder.h index 1ab7b0f..1733ca3 100644 --- a/quicktex/s3tc/bc3/BC3Decoder.h +++ b/quicktex/s3tc/bc3/BC3Decoder.h @@ -21,8 +21,8 @@ #include -#include "../../BlockDecoder.h" -#include "../../BlockView.h" +#include "../../Block.h" +#include "../../Decoder.h" #include "../../ndebug.h" #include "../bc1/BC1Decoder.h" #include "../bc4/BC4Decoder.h" @@ -31,7 +31,7 @@ namespace quicktex::s3tc { -class BC3Decoder : public BlockDecoderTemplate { +class BC3Decoder : public BlockDecoder> { public: using BC1DecoderPtr = std::shared_ptr; using BC4DecoderPtr = std::shared_ptr; @@ -41,7 +41,7 @@ class BC3Decoder : public BlockDecoderTemplate { BC3Decoder() : BC3Decoder(std::make_shared()) {} - void DecodeBlock(Color4x4 dest, BC3Block *const block) const noexcept(ndebug) override; + ColorBlock<4, 4> DecodeBlock(const BC3Block &block) const override; BC1DecoderPtr GetBC1Decoder() const { return _bc1_decoder; } BC4DecoderPtr GetBC4Decoder() const { return _bc4_decoder; } diff --git a/quicktex/s3tc/bc3/BC3Encoder.cpp b/quicktex/s3tc/bc3/BC3Encoder.cpp index c36f8bd..750af2f 100644 --- a/quicktex/s3tc/bc3/BC3Encoder.cpp +++ b/quicktex/s3tc/bc3/BC3Encoder.cpp @@ -19,12 +19,14 @@ #include "BC3Encoder.h" -#include "../../BlockView.h" +#include "../../Block.h" #include "BC3Block.h" namespace quicktex::s3tc { -void BC3Encoder::EncodeBlock(Color4x4 pixels, BC3Block *dest) const { - _bc1_encoder->EncodeBlock(pixels, &(dest->color_block)); - _bc4_encoder->EncodeBlock(pixels, &(dest->alpha_block)); +BC3Block BC3Encoder::EncodeBlock(const ColorBlock<4, 4> &pixels) const { + auto output = BC3Block(); + output.color_block = _bc1_encoder->EncodeBlock(pixels); + output.alpha_block = _bc4_encoder->EncodeBlock(pixels); + return output; } } // namespace quicktex::s3tc \ No newline at end of file diff --git a/quicktex/s3tc/bc3/BC3Encoder.h b/quicktex/s3tc/bc3/BC3Encoder.h index 58caea2..562d66c 100644 --- a/quicktex/s3tc/bc3/BC3Encoder.h +++ b/quicktex/s3tc/bc3/BC3Encoder.h @@ -21,8 +21,8 @@ #include -#include "../../BlockEncoder.h" -#include "../../BlockView.h" +#include "../../Block.h" +#include "../../Encoder.h" #include "../bc1/BC1Encoder.h" #include "../bc4/BC4Encoder.h" #include "../interpolator/Interpolator.h" @@ -30,7 +30,7 @@ namespace quicktex::s3tc { -class BC3Encoder : public BlockEncoderTemplate { +class BC3Encoder : public BlockEncoder> { public: using BC1EncoderPtr = std::shared_ptr; using BC4EncoderPtr = std::shared_ptr; @@ -41,7 +41,7 @@ class BC3Encoder : public BlockEncoderTemplate { BC3Encoder(unsigned level = 5) : BC3Encoder(level, std::make_shared()) {} - void EncodeBlock(Color4x4 pixels, BC3Block *dest) const override; + BC3Block EncodeBlock(const ColorBlock<4, 4>& pixels) const override; BC1EncoderPtr GetBC1Encoder() const { return _bc1_encoder; } BC4EncoderPtr GetBC4Encoder() const { return _bc4_encoder; } diff --git a/quicktex/s3tc/bc3/_bindings.cpp b/quicktex/s3tc/bc3/_bindings.cpp index 36833df..6c45c75 100644 --- a/quicktex/s3tc/bc3/_bindings.cpp +++ b/quicktex/s3tc/bc3/_bindings.cpp @@ -25,8 +25,8 @@ #include #include -#include "../../BlockDecoder.h" -#include "../../BlockEncoder.h" +#include "../../Decoder.h" +#include "../../Encoder.h" #include "../interpolator/Interpolator.h" #include "BC3Decoder.h" #include "BC3Encoder.h" @@ -42,13 +42,11 @@ using BC1DecoderPtr = std::shared_ptr; void InitBC3(py::module_ &s3tc) { auto bc3 = s3tc.def_submodule("_bc3", "internal bc3 module"); - auto block_encoder = py::type::of(); - auto block_decoder = py::type::of(); py::options options; options.disable_function_signatures(); // BC3Encoder - py::class_ bc3_encoder(bc3, "BC3Encoder", block_encoder,R"doc( + py::class_ bc3_encoder(bc3, "BC3Encoder", R"doc( Base: :py:class:`~quicktex.BlockEncoder` Encodes RGBA textures to BC3 @@ -71,7 +69,7 @@ void InitBC3(py::module_ &s3tc) { "Internal :py:class:`~quicktex.s3tc.bc4.BC4Encoder` used for alpha data. Readonly."); // BC3Decoder - py::class_ bc3_decoder(bc3, "BC3Decoder", block_decoder, R"doc( + py::class_ bc3_decoder(bc3, "BC3Decoder", R"doc( Base: :py:class:`~quicktex.BlockDecoder` Decodes BC3 textures to RGBA diff --git a/quicktex/s3tc/bc4/BC4Block.h b/quicktex/s3tc/bc4/BC4Block.h index 5a8d84d..49defbf 100644 --- a/quicktex/s3tc/bc4/BC4Block.h +++ b/quicktex/s3tc/bc4/BC4Block.h @@ -28,10 +28,10 @@ #include "../../util.h" #include "../bc1/BC1Block.h" -namespace quicktex::s3tc { +namespace quicktex::s3tc { #pragma pack(push, 1) -class BC4Block { +class BC4Block : public Block<4, 4> { public: using UnpackedSelectors = std::array, 4>; diff --git a/quicktex/s3tc/bc4/BC4Decoder.cpp b/quicktex/s3tc/bc4/BC4Decoder.cpp index 538d20d..d007f77 100644 --- a/quicktex/s3tc/bc4/BC4Decoder.cpp +++ b/quicktex/s3tc/bc4/BC4Decoder.cpp @@ -22,24 +22,32 @@ #include // for array #include // for assert -#include "../../BlockView.h" // for ColorBlock -#include "../../ndebug.h" // for ndebug #include "BC4Block.h" -namespace quicktex::s3tc { -void BC4Decoder::DecodeBlock(Byte4x4 dest, BC4Block *const block) const noexcept(ndebug) { - auto l = block->GetLowAlpha(); - auto h = block->GetHighAlpha(); +namespace quicktex::s3tc { +void BC4Decoder::DecodeInto(ColorBlock<4, 4> &dest, const BC4Block &block) const { + auto l = block.GetLowAlpha(); + auto h = block.GetHighAlpha(); auto values = BC4Block::GetValues(l, h); - auto selectors = block->UnpackSelectors(); + auto selectors = block.UnpackSelectors(); for (unsigned y = 0; y < 4; y++) { for (unsigned x = 0; x < 4; x++) { const auto selector = selectors[y][x]; assert(selector < 8); - dest.Set(x, y, values[selector]); + + auto color = dest.Get(x, y); + color[_channel] = values[selector]; + dest.Set(x, y, color); } } } + +ColorBlock<4, 4> BC4Decoder::DecodeBlock(const BC4Block &block) const { + auto output = ColorBlock<4, 4>(); + DecodeInto(output, block); + + return output; +} } // namespace quicktex::s3tc diff --git a/quicktex/s3tc/bc4/BC4Decoder.h b/quicktex/s3tc/bc4/BC4Decoder.h index 130203a..3902296 100644 --- a/quicktex/s3tc/bc4/BC4Decoder.h +++ b/quicktex/s3tc/bc4/BC4Decoder.h @@ -23,22 +23,23 @@ #include #include -#include "../../BlockDecoder.h" -#include "../../BlockView.h" +#include "../../Block.h" +#include "../../Decoder.h" #include "../../ndebug.h" #include "BC4Block.h" namespace quicktex::s3tc { -class BC4Decoder : public BlockDecoderTemplate { +class BC4Decoder : public BlockDecoder> { public: BC4Decoder(uint8_t channel = 3) { if (channel >= 4U) throw std::invalid_argument("Channel out of range"); _channel = channel; } - void DecodeBlock(Color4x4 dest, BC4Block *const block) const noexcept(ndebug) override { DecodeBlock(dest.GetChannel(_channel), block); } - void DecodeBlock(Byte4x4 dest, BC4Block *const block) const noexcept(ndebug); + ColorBlock<4,4> DecodeBlock(const BC4Block &block) const override; + + void DecodeInto(ColorBlock<4,4> &dest, const BC4Block &block) const; uint8_t GetChannel() const { return _channel; } diff --git a/quicktex/s3tc/bc4/BC4Encoder.cpp b/quicktex/s3tc/bc4/BC4Encoder.cpp index 9c59480..3cd3fc4 100644 --- a/quicktex/s3tc/bc4/BC4Encoder.cpp +++ b/quicktex/s3tc/bc4/BC4Encoder.cpp @@ -22,26 +22,29 @@ #include #include #include -#include -#include "../../BlockView.h" -#include "../../ndebug.h" +#include "../../Block.h" #include "BC4Block.h" -namespace quicktex::s3tc { -void BC4Encoder::EncodeBlock(Byte4x4 pixels, BC4Block *const dest) const noexcept(ndebug) { - auto flattened = pixels.Flatten(); - auto minmax = std::minmax_element(flattened.begin(), flattened.end()); +namespace quicktex::s3tc { +BC4Block BC4Encoder::EncodeBlock(const ColorBlock<4, 4> &pixels) const { + auto output = BC4Block(); - uint8_t min = *minmax.first; - uint8_t max = *minmax.second; + uint8_t min = UINT8_MAX; + uint8_t max = 0; - dest->high_alpha = min; - dest->low_alpha = max; + for (int i = 0; i < 16; i++) { + auto value = pixels.Get(i)[_channel]; + min = std::min(min, value); + max = std::max(max, value); + } + + output.high_alpha = min; + output.low_alpha = max; if (max == min) { - dest->SetSelectorBits(0); - return; + output.SetSelectorBits(0); + return output; } std::array selectors = {}; @@ -58,16 +61,19 @@ void BC4Encoder::EncodeBlock(Byte4x4 pixels, BC4Block *const dest) const noexcep for (unsigned i = 0; i < 7; i++) thresholds[i] = delta * (1 + (2 * (int)i)) - bias; // iterate over all values and calculate selectors - for (unsigned i = 0; i < 16; i++) { - int value = flattened[i] * 14; // multiply by demonimator + for (int i = 0; i < 16; i++) { + int value = (int)pixels.Get(i)[_channel] * 14; // multiply by demonimator // level = number of thresholds this value is greater than unsigned level = 0; for (unsigned c = 0; c < 7; c++) level += value >= thresholds[c]; - selectors[i] = Levels[level]; + selectors[(unsigned)i] = Levels[level]; } - dest->PackSelectors(selectors); + output.PackSelectors(selectors); + + return output; } + } // namespace quicktex::s3tc \ No newline at end of file diff --git a/quicktex/s3tc/bc4/BC4Encoder.h b/quicktex/s3tc/bc4/BC4Encoder.h index 9d5e5a6..51cfcdf 100644 --- a/quicktex/s3tc/bc4/BC4Encoder.h +++ b/quicktex/s3tc/bc4/BC4Encoder.h @@ -23,22 +23,21 @@ #include #include -#include "../../BlockEncoder.h" -#include "../../BlockView.h" +#include "../../Block.h" +#include "../../Encoder.h" #include "../../ndebug.h" #include "BC4Block.h" namespace quicktex::s3tc { -class BC4Encoder : public BlockEncoderTemplate { +class BC4Encoder : public BlockEncoder> { public: BC4Encoder(const uint8_t channel) { if (channel >= 4) throw std::invalid_argument("Channel out of range"); _channel = channel; } - void EncodeBlock(Color4x4 pixels, BC4Block *const dest) const override { EncodeBlock(pixels.GetChannel(_channel), dest); } - void EncodeBlock(Byte4x4 pixels, BC4Block *const dest) const noexcept(ndebug); + BC4Block EncodeBlock(const ColorBlock<4,4> &pixels) const override; uint8_t GetChannel() const { return _channel; } diff --git a/quicktex/s3tc/bc4/_bindings.cpp b/quicktex/s3tc/bc4/_bindings.cpp index 997b194..5314111 100644 --- a/quicktex/s3tc/bc4/_bindings.cpp +++ b/quicktex/s3tc/bc4/_bindings.cpp @@ -25,8 +25,8 @@ #include #include -#include "../../BlockDecoder.h" -#include "../../BlockEncoder.h" +#include "../../Decoder.h" +#include "../../Encoder.h" #include "BC4Decoder.h" #include "BC4Encoder.h" @@ -38,13 +38,11 @@ using namespace quicktex::s3tc; void InitBC4(py::module_ &s3tc) { auto bc4 = s3tc.def_submodule("_bc4", "internal bc4 module"); - auto block_encoder = py::type::of(); - auto block_decoder = py::type::of(); py::options options; options.disable_function_signatures(); // BC4Encoder - py::class_ bc4_encoder(bc4, "BC4Encoder", block_encoder, R"doc( + py::class_ bc4_encoder(bc4, "BC4Encoder", R"doc( Base: :py:class:`~quicktex.BlockEncoder` Encodes single-channel textures to BC4. @@ -60,7 +58,7 @@ void InitBC4(py::module_ &s3tc) { bc4_encoder.def_property_readonly("channel", &BC4Encoder::GetChannel, "The channel that will be read from. 0 to 3 inclusive. Readonly."); // BC4Decoder - py::class_ bc4_decoder(bc4, "BC4Decoder", block_decoder, R"doc( + py::class_ bc4_decoder(bc4, "BC4Decoder", R"doc( Base: :py:class:`~quicktex.BlockDecoder` Decodes BC4 textures to a single-channel. diff --git a/quicktex/s3tc/bc5/BC5Block.h b/quicktex/s3tc/bc5/BC5Block.h index 58b7d35..6e51901 100644 --- a/quicktex/s3tc/bc5/BC5Block.h +++ b/quicktex/s3tc/bc5/BC5Block.h @@ -19,12 +19,13 @@ #pragma once +#include "../../Block.h" #include "../bc4/BC4Block.h" -namespace quicktex::s3tc { +namespace quicktex::s3tc { #pragma pack(push, 1) -class BC5Block { +class BC5Block : public Block<4, 4> { public: BC4Block chan0_block; BC4Block chan1_block; diff --git a/quicktex/s3tc/bc5/BC5Decoder.cpp b/quicktex/s3tc/bc5/BC5Decoder.cpp index 3425870..10538d1 100644 --- a/quicktex/s3tc/bc5/BC5Decoder.cpp +++ b/quicktex/s3tc/bc5/BC5Decoder.cpp @@ -19,14 +19,15 @@ #include "BC5Decoder.h" -#include "../../BlockView.h" +#include "../../Block.h" #include "../../ndebug.h" #include "BC5Block.h" -namespace quicktex::s3tc { - -void BC5Decoder::DecodeBlock(Color4x4 dest, BC5Block *const block) const noexcept(ndebug) { - _chan0_decoder->DecodeBlock(dest, &block->chan0_block); - _chan1_decoder->DecodeBlock(dest, &block->chan1_block); +namespace quicktex::s3tc { +ColorBlock<4, 4> BC5Decoder::DecodeBlock(const BC5Block &block) const { + auto output = ColorBlock<4, 4>(); + _chan0_decoder->DecodeInto(output, block.chan0_block); + _chan1_decoder->DecodeInto(output, block.chan1_block); + return output; } } // namespace quicktex::s3tc \ No newline at end of file diff --git a/quicktex/s3tc/bc5/BC5Decoder.h b/quicktex/s3tc/bc5/BC5Decoder.h index 02ac7e2..5f156a2 100644 --- a/quicktex/s3tc/bc5/BC5Decoder.h +++ b/quicktex/s3tc/bc5/BC5Decoder.h @@ -24,15 +24,16 @@ #include #include -#include "../../BlockDecoder.h" -#include "../../BlockView.h" +#include "../../Block.h" +#include "../../Texture.h" +#include "../../Decoder.h" #include "../../ndebug.h" #include "../bc4/BC4Decoder.h" #include "BC5Block.h" namespace quicktex::s3tc { -class BC5Decoder : public BlockDecoderTemplate { +class BC5Decoder : public BlockDecoder> { public: using ChannelPair = std::tuple; using BC4DecoderPtr = std::shared_ptr; @@ -41,7 +42,7 @@ class BC5Decoder : public BlockDecoderTemplate { BC5Decoder(uint8_t chan0 = 0, uint8_t chan1 = 1) : BC5Decoder(std::make_shared(chan0), std::make_shared(chan1)) {} BC5Decoder(BC4DecoderPtr chan0_decoder, BC4DecoderPtr chan1_decoder) : _chan0_decoder(chan0_decoder), _chan1_decoder(chan1_decoder) {} - void DecodeBlock(Color4x4 dest, BC5Block *const block) const noexcept(ndebug) override; + ColorBlock<4,4> DecodeBlock(const BC5Block &block) const override; ChannelPair GetChannels() const { return ChannelPair(_chan0_decoder->GetChannel(), _chan1_decoder->GetChannel()); } diff --git a/quicktex/s3tc/bc5/BC5Encoder.cpp b/quicktex/s3tc/bc5/BC5Encoder.cpp index 5b8bcdf..fe3726a 100644 --- a/quicktex/s3tc/bc5/BC5Encoder.cpp +++ b/quicktex/s3tc/bc5/BC5Encoder.cpp @@ -19,9 +19,11 @@ #include "BC5Encoder.h" -namespace quicktex::s3tc { -void BC5Encoder::EncodeBlock(Color4x4 pixels, BC5Block *dest) const { - _chan0_encoder->EncodeBlock(pixels, &(dest->chan0_block)); - _chan1_encoder->EncodeBlock(pixels, &(dest->chan1_block)); +namespace quicktex::s3tc { +BC5Block BC5Encoder::EncodeBlock(const ColorBlock<4, 4> &pixels) const { + auto output = BC5Block(); + output.chan0_block = _chan0_encoder->EncodeBlock(pixels); + output.chan1_block = _chan1_encoder->EncodeBlock(pixels); + return output; } } // namespace quicktex::s3tc \ No newline at end of file diff --git a/quicktex/s3tc/bc5/BC5Encoder.h b/quicktex/s3tc/bc5/BC5Encoder.h index b1109d8..3b0d0de 100644 --- a/quicktex/s3tc/bc5/BC5Encoder.h +++ b/quicktex/s3tc/bc5/BC5Encoder.h @@ -24,15 +24,15 @@ #include #include -#include "../../BlockEncoder.h" -#include "../../BlockView.h" +#include "../../Block.h" +#include "../../Encoder.h" #include "../../ndebug.h" #include "../bc4/BC4Encoder.h" #include "BC5Block.h" namespace quicktex::s3tc { -class BC5Encoder : public BlockEncoderTemplate { +class BC5Encoder : public BlockEncoder> { public: using ChannelPair = std::tuple; using BC4EncoderPtr = std::shared_ptr; @@ -41,7 +41,7 @@ class BC5Encoder : public BlockEncoderTemplate { BC5Encoder(uint8_t chan0 = 0, uint8_t chan1 = 1) : BC5Encoder(std::make_shared(chan0), std::make_shared(chan1)) {} BC5Encoder(BC4EncoderPtr chan0_encoder, BC4EncoderPtr chan1_encoder) : _chan0_encoder(chan0_encoder), _chan1_encoder(chan1_encoder) {} - void EncodeBlock(Color4x4 pixels, BC5Block *dest) const override; + BC5Block EncodeBlock(const ColorBlock<4, 4> &pixels) const override; ChannelPair GetChannels() const { return ChannelPair(_chan0_encoder->GetChannel(), _chan1_encoder->GetChannel()); } diff --git a/quicktex/s3tc/bc5/_bindings.cpp b/quicktex/s3tc/bc5/_bindings.cpp index 62ba017..6ac2b90 100644 --- a/quicktex/s3tc/bc5/_bindings.cpp +++ b/quicktex/s3tc/bc5/_bindings.cpp @@ -22,8 +22,8 @@ #include #include -#include "../../BlockDecoder.h" -#include "../../BlockEncoder.h" +#include "../../Decoder.h" +#include "../../Encoder.h" #include "BC5Decoder.h" #include "BC5Encoder.h" @@ -35,13 +35,11 @@ using namespace quicktex::s3tc; void InitBC5(py::module_ &s3tc) { auto bc5 = s3tc.def_submodule("_bc5", "internal bc5 module"); - auto block_encoder = py::type::of(); - auto block_decoder = py::type::of(); py::options options; options.disable_function_signatures(); // BC5Encoder - py::class_ bc5_encoder(bc5, "BC5Encoder", block_encoder, R"doc( + py::class_ bc5_encoder(bc5, "BC5Encoder", R"doc( Base: :py:class:`~quicktex.BlockEncoder` Encodes dual-channel textures to BC5. @@ -61,7 +59,7 @@ void InitBC5(py::module_ &s3tc) { "2-tuple of internal :py:class:`~quicktex.s3tc.bc4.BC4Encoder` s used for each channel. Readonly."); // BC5Decoder - py::class_ bc5_decoder(bc5, "BC5Decoder", block_decoder, R"doc( + py::class_ bc5_decoder(bc5, "BC5Decoder", R"doc( Base: :py:class:`~quicktex.BlockDecoder` Decodes BC4 textures to two channels.