#pragma once
#include <algorithm>
#include <cmath>
#include <cstring>
#include <type_traits>
#include <utility>
#include <whiteout/common_types.h>
#include <whiteout/textures/texture.h>
namespace whiteout::textures {
template <PixelFormat... Fmts>
struct FormatList {
static constexpr u32 size = sizeof...(Fmts);
};
template <PixelFormat>
struct FormatTraits;
#define WHITEOUT_FORMAT_TRAITS(FMT, TYPE, CH, BPP) \
template <> \
struct FormatTraits<PixelFormat::FMT> { \
using channel_type = TYPE; \
static constexpr u32 channels = CH; \
static constexpr u32 bytes_per_pixel = BPP; \
}
WHITEOUT_FORMAT_TRAITS(R8, u8, 1, 1);
WHITEOUT_FORMAT_TRAITS(R16, u16, 1, 2);
WHITEOUT_FORMAT_TRAITS(R32F, f32, 1, 4);
WHITEOUT_FORMAT_TRAITS(RG8, u8, 2, 2);
WHITEOUT_FORMAT_TRAITS(RG16, u16, 2, 4);
WHITEOUT_FORMAT_TRAITS(RG32F, f32, 2, 8);
WHITEOUT_FORMAT_TRAITS(RGBA8, u8, 4, 4);
WHITEOUT_FORMAT_TRAITS(RGBA16, u16, 4, 8);
WHITEOUT_FORMAT_TRAITS(RGBA32F, f32, 4, 16);
#undef WHITEOUT_FORMAT_TRAITS
using UncompressedFormats =
FormatList<PixelFormat::R8, PixelFormat::R16, PixelFormat::R32F, PixelFormat::RG8,
PixelFormat::RG16, PixelFormat::RG32F, PixelFormat::RGBA8, PixelFormat::RGBA16,
PixelFormat::RGBA32F>;
static constexpr u32 kUncompressedCount = UncompressedFormats::size;
namespace detail {
template <PixelFormat... Fmts, std::size_t... Is>
constexpr bool check_contiguity(FormatList<Fmts...>, std::index_sequence<Is...>) {
return ((static_cast<u32>(Fmts) == static_cast<u32>(Is)) && ...);
}
static_assert(
check_contiguity(UncompressedFormats{}, std::make_index_sequence<UncompressedFormats::size>{}),
"PixelFormat enum values must be contiguous starting at 0 and match UncompressedFormats order");
}
template <typename T>
constexpr T alpha_default() {
if constexpr (std::is_same_v<T, f32>)
return 1.0f;
else if constexpr (std::is_same_v<T, u16>)
return u16{65535};
else
return u8{255};
}
template <typename Dst, typename Src>
constexpr Dst convert_channel(Src val) {
if constexpr (std::is_same_v<Dst, Src>) {
return val;
}
else if constexpr (std::is_same_v<Src, u8> && std::is_same_v<Dst, u16>) {
return static_cast<u16>(static_cast<u32>(val) * 257u);
}
else if constexpr (std::is_same_v<Src, u16> && std::is_same_v<Dst, u8>) {
return static_cast<u8>((static_cast<u32>(val) + 128u) / 257u);
}
else if constexpr (std::is_same_v<Src, u8> && std::is_same_v<Dst, f32>) {
return static_cast<f32>(val) / 255.0f;
}
else if constexpr (std::is_same_v<Src, f32> && std::is_same_v<Dst, u8>) {
return static_cast<u8>(std::lround(std::clamp(val, 0.0f, 1.0f) * 255.0f));
}
else if constexpr (std::is_same_v<Src, u16> && std::is_same_v<Dst, f32>) {
return static_cast<f32>(val) / 65535.0f;
}
else if constexpr (std::is_same_v<Src, f32> && std::is_same_v<Dst, u16>) {
return static_cast<u16>(std::lround(std::clamp(val, 0.0f, 1.0f) * 65535.0f));
} else {
static_assert(!std::is_same_v<Src, Src>,
"unsupported channel type pair in convert_channel");
}
}
namespace detail {
template <typename T>
T read_channel(const u8* ptr) {
T val;
std::memcpy(&val, ptr, sizeof(T));
return val;
}
template <typename T>
void write_channel(u8* ptr, T val) {
std::memcpy(ptr, &val, sizeof(T));
}
}
template <PixelFormat Fmt>
void read_rgba32f(const u8* src, f32* dst) {
using Traits = FormatTraits<Fmt>;
using T = typename Traits::channel_type;
constexpr u32 ch = Traits::channels;
if constexpr (ch >= 1)
dst[0] = convert_channel<f32, T>(detail::read_channel<T>(src + 0 * sizeof(T)));
if constexpr (ch >= 2)
dst[1] = convert_channel<f32, T>(detail::read_channel<T>(src + 1 * sizeof(T)));
if constexpr (ch >= 4) {
dst[2] = convert_channel<f32, T>(detail::read_channel<T>(src + 2 * sizeof(T)));
dst[3] = convert_channel<f32, T>(detail::read_channel<T>(src + 3 * sizeof(T)));
}
if constexpr (ch < 2)
dst[1] = 0.0f; if constexpr (ch < 4)
dst[2] = 0.0f; if constexpr (ch < 4)
dst[3] = 1.0f; }
template <PixelFormat Fmt>
void write_rgba32f(const f32* src, u8* dst) {
using Traits = FormatTraits<Fmt>;
using T = typename Traits::channel_type;
constexpr u32 ch = Traits::channels;
if constexpr (ch >= 1)
detail::write_channel<T>(dst + 0 * sizeof(T), convert_channel<T, f32>(src[0]));
if constexpr (ch >= 2)
detail::write_channel<T>(dst + 1 * sizeof(T), convert_channel<T, f32>(src[1]));
if constexpr (ch >= 4) {
detail::write_channel<T>(dst + 2 * sizeof(T), convert_channel<T, f32>(src[2]));
detail::write_channel<T>(dst + 3 * sizeof(T), convert_channel<T, f32>(src[3]));
}
}
template <PixelFormat SrcFmt, PixelFormat DstFmt>
void convert_pixel(const u8* src, u8* dst) {
if constexpr (SrcFmt == DstFmt) {
std::memcpy(dst, src, FormatTraits<SrcFmt>::bytes_per_pixel);
return;
}
using SrcTraits = FormatTraits<SrcFmt>;
using DstTraits = FormatTraits<DstFmt>;
using SrcT = typename SrcTraits::channel_type;
using DstT = typename DstTraits::channel_type;
constexpr u32 src_ch = SrcTraits::channels;
constexpr u32 dst_ch = DstTraits::channels;
constexpr u32 common_ch = (src_ch < dst_ch) ? src_ch : dst_ch;
if constexpr (common_ch >= 1)
detail::write_channel<DstT>(
dst + 0 * sizeof(DstT),
convert_channel<DstT, SrcT>(detail::read_channel<SrcT>(src + 0 * sizeof(SrcT))));
if constexpr (common_ch >= 2)
detail::write_channel<DstT>(
dst + 1 * sizeof(DstT),
convert_channel<DstT, SrcT>(detail::read_channel<SrcT>(src + 1 * sizeof(SrcT))));
if constexpr (common_ch >= 4) {
detail::write_channel<DstT>(
dst + 2 * sizeof(DstT),
convert_channel<DstT, SrcT>(detail::read_channel<SrcT>(src + 2 * sizeof(SrcT))));
detail::write_channel<DstT>(
dst + 3 * sizeof(DstT),
convert_channel<DstT, SrcT>(detail::read_channel<SrcT>(src + 3 * sizeof(SrcT))));
}
if constexpr (dst_ch >= 2 && src_ch < 2) {
detail::write_channel<DstT>(dst + 1 * sizeof(DstT), DstT{0}); }
if constexpr (dst_ch >= 4 && src_ch < 4) {
if constexpr (src_ch < 3) {
detail::write_channel<DstT>(dst + 2 * sizeof(DstT), DstT{0}); }
detail::write_channel<DstT>(dst + 3 * sizeof(DstT), alpha_default<DstT>()); }
}
template <PixelFormat SrcFmt, PixelFormat DstFmt>
void convert_pixels(const u8* src, u8* dst, u32 count) {
constexpr u32 src_bpp = FormatTraits<SrcFmt>::bytes_per_pixel;
constexpr u32 dst_bpp = FormatTraits<DstFmt>::bytes_per_pixel;
for (u32 i = 0; i < count; ++i) {
convert_pixel<SrcFmt, DstFmt>(src + i * src_bpp, dst + i * dst_bpp);
}
}
namespace detail {
template <typename Fn, PixelFormat... Fmts, std::size_t... Is, typename... Args>
bool dispatch_impl(FormatList<Fmts...>, std::index_sequence<Is...>, PixelFormat fmt,
Args&... args) {
bool matched = false;
((static_cast<u32>(Fmts) == static_cast<u32>(fmt)
? (Fn::template apply<Fmts>(args...), matched = true, 0)
: 0),
...);
return matched;
}
}
template <typename Fn, typename... Args>
bool dispatch_uncompressed(PixelFormat fmt, Args&... args) {
return detail::dispatch_impl<Fn>(
UncompressedFormats{}, std::make_index_sequence<UncompressedFormats::size>{}, fmt, args...);
}
namespace detail {
template <typename Fn, PixelFormat... Fmts>
void for_each_impl(FormatList<Fmts...>, Fn&& fn) {
(fn.template operator()<Fmts>(), ...);
}
}
template <typename List, typename Fn>
void for_each_format(Fn&& fn) {
detail::for_each_impl(List{}, std::forward<Fn>(fn));
}
}