summaryrefslogtreecommitdiff
path: root/pixpat-native/src/pixpat_convert.cpp
diff options
context:
space:
mode:
authorTomi Valkeinen <tomi.valkeinen@ideasonboard.com>2026-05-08 17:22:58 +0300
committerTomi Valkeinen <tomi.valkeinen@ideasonboard.com>2026-05-08 17:22:58 +0300
commite0b7d30fd437292c88141fb08d60681870b86c6e (patch)
tree7d7f4e94cbec0f4f494042f7cbf39c7c8e7234fe /pixpat-native/src/pixpat_convert.cpp
Squashed 'subprojects/pixpat/' content from commit d444626
git-subtree-dir: subprojects/pixpat git-subtree-split: d444626e6ba988ec6d487800721e447f94b1eaf5
Diffstat (limited to 'pixpat-native/src/pixpat_convert.cpp')
-rw-r--r--pixpat-native/src/pixpat_convert.cpp201
1 files changed, 201 insertions, 0 deletions
diff --git a/pixpat-native/src/pixpat_convert.cpp b/pixpat-native/src/pixpat_convert.cpp
new file mode 100644
index 0000000..63461d8
--- /dev/null
+++ b/pixpat-native/src/pixpat_convert.cpp
@@ -0,0 +1,201 @@
+// Convert-feature TU: built only when PIXPAT_FEATURE_CONVERT is on
+// (controlled by the meson source list). pixpat.cpp's pixpat_convert
+// entry calls into dispatch_convert() below via if-constexpr; when the
+// feature is off this file isn't compiled, the discarded if-constexpr
+// branch emits no symbol reference, and the .so simply lacks these
+// symbols.
+
+#include <cassert>
+#include <cstdint>
+#include <cstring>
+#include <vector>
+
+#include "color.h"
+#include "error.h"
+#include "format_catalog.h"
+#include "formats.h"
+#include "io.h"
+#include "layout.h"
+#include "pattern.h"
+#include "pipeline.h"
+#include "pixpat_internal.h"
+
+namespace pixpat
+{
+
+template <typename Src, typename Snk>
+static void run_convert_impl(const pixpat_buffer* src, const pixpat_buffer* dst,
+ size_t W, size_t H,
+ size_t by_start, size_t by_end,
+ ColorSpec spec)
+{
+ using SL = typename Src::Layout;
+ using DL = typename Snk::Layout;
+ // Entry point (pixpat_convert) validates W/H against each layout's
+ // h_sub / v_sub, plus the sink's block dims.
+ assert(W % SL::h_sub == 0 && W % DL::h_sub == 0);
+ assert(H % SL::v_sub == 0 && H % DL::v_sub == 0);
+
+ auto sb = make_buffer<SL>(src);
+ auto db = make_buffer<DL>(dst);
+ Converter<Src, Snk>::run(sb, db, W, H, by_start, by_end, spec);
+}
+
+static void run_norm(FormatId src_id, FormatId dst_id,
+ const pixpat_buffer* src, const pixpat_buffer* dst,
+ size_t W, size_t H,
+ size_t by_start, size_t by_end,
+ ColorSpec spec)
+{
+ const auto& si = s_format_info[size_t(src_id)];
+ const auto& di = s_format_info[size_t(dst_id)];
+
+ const size_t bh = di.snk_block_h;
+ // Entry point (pixpat_convert) guarantees W/H alignment to each
+ // of si.h_sub / si.v_sub and di.snk_block_w / di.snk_block_h.
+ assert(W % si.h_sub == 0 && W % di.snk_block_w == 0);
+ assert(H % si.v_sub == 0 && H % bh == 0);
+
+ // Per-thread normalized line buffer. RGB16 and YUV16 are both 8
+ // bytes, so one allocation works for both. thread_local gives each
+ // worker its own buffer when called from run_stripes.
+ thread_local std::vector<uint8_t> norm;
+ norm.resize(bh * W * sizeof(RGB16));
+
+ const ColorCoeffs c = coeffs_for(spec);
+ for (size_t by = by_start; by < by_end; by += bh) {
+ si.unpack(norm.data(), src, by, bh, W);
+ if (si.kind != di.kind) {
+ const size_t n = bh * W;
+ if (si.kind == ColorKind::RGB)
+ norm_rgb_to_yuv(norm.data(), n, c);
+ else
+ norm_yuv_to_rgb(norm.data(), n, c);
+ }
+ di.pack(dst, norm.data(), by, W);
+ }
+}
+
+// Generated: FormatCaps + s_format_caps[] (per-format readable/writable
+// + hot_src/hot_dst), plus s_pattern_* / DefaultPattern.
+#include "pixpat_caps.inc"
+
+// Per-Src dispatch: pick the right Sink for `dst_id` and call
+// run_convert_impl. The X-macro emits one case per catalog format;
+// `if constexpr (...writable)` discards the body for non-writable
+// formats — those cases fall to the trailing throw.
+template <typename Src>
+static void dispatch_dst_convert(FormatId dst_id,
+ const pixpat_buffer* src, const pixpat_buffer* dst,
+ size_t W, size_t H,
+ size_t by_start, size_t by_end,
+ ColorSpec spec)
+{
+ switch (dst_id) {
+#define CAPS(name) s_format_caps[size_t(FormatId::name)]
+#define X(name) \
+ case FormatId::name: \
+ if constexpr (CAPS(name).writable) { \
+ run_convert_impl<Src, formats::name::Sink>( \
+ src, dst, W, H, by_start, by_end, spec); \
+ return; \
+ } \
+ break;
+ PIXPAT_FORMAT_LIST(X)
+#undef X
+#undef CAPS
+ default:
+ break;
+ }
+ throw invalid_argument("destination format not enabled in this build");
+}
+
+// Per-Snk dispatch: mirror of dispatch_dst_convert.
+template <typename Snk>
+static void dispatch_src_convert(FormatId src_id,
+ const pixpat_buffer* src, const pixpat_buffer* dst,
+ size_t W, size_t H,
+ size_t by_start, size_t by_end,
+ ColorSpec spec)
+{
+ switch (src_id) {
+#define CAPS(name) s_format_caps[size_t(FormatId::name)]
+#define X(name) \
+ case FormatId::name: \
+ if constexpr (CAPS(name).readable) { \
+ run_convert_impl<formats::name::Source, Snk>( \
+ src, dst, W, H, by_start, by_end, spec); \
+ return; \
+ } \
+ break;
+ PIXPAT_FORMAT_LIST(X)
+#undef X
+#undef CAPS
+ default:
+ break;
+ }
+ throw invalid_argument("source format not enabled in this build");
+}
+
+// Hot-pivot probes. The wrapper has to be a template so that the
+// discarded `if constexpr` branch is not instantiated — otherwise
+// dispatch_dst_convert<formats::X::Source> would be instantiated for
+// every catalog format, not just hot pivots.
+template <bool HotSrc, FormatId Id, typename Source>
+static bool try_hot_src(FormatId src_id, FormatId dst_id,
+ const pixpat_buffer* src, const pixpat_buffer* dst,
+ size_t W, size_t H,
+ size_t by_start, size_t by_end,
+ ColorSpec spec)
+{
+ if constexpr (HotSrc) {
+ if (src_id == Id) {
+ dispatch_dst_convert<Source>(
+ dst_id, src, dst, W, H, by_start, by_end, spec);
+ return true;
+ }
+ }
+ return false;
+}
+
+template <bool HotDst, FormatId Id, typename Sink>
+static bool try_hot_dst(FormatId src_id, FormatId dst_id,
+ const pixpat_buffer* src, const pixpat_buffer* dst,
+ size_t W, size_t H,
+ size_t by_start, size_t by_end,
+ ColorSpec spec)
+{
+ if constexpr (HotDst) {
+ if (dst_id == Id) {
+ dispatch_src_convert<Sink>(
+ src_id, src, dst, W, H, by_start, by_end, spec);
+ return true;
+ }
+ }
+ return false;
+}
+
+void dispatch_convert(FormatId src_id, FormatId dst_id,
+ const pixpat_buffer* src, const pixpat_buffer* dst,
+ size_t W, size_t H,
+ size_t by_start, size_t by_end,
+ ColorSpec spec)
+{
+#define CAPS(name) s_format_caps[size_t(FormatId::name)]
+#define X(name) \
+ if (try_hot_src<CAPS(name).hot_src, FormatId::name, \
+ formats::name::Source>( \
+ src_id, dst_id, src, dst, W, H, by_start, by_end, spec)) \
+ return; \
+ if (try_hot_dst<CAPS(name).hot_dst, FormatId::name, \
+ formats::name::Sink>( \
+ src_id, dst_id, src, dst, W, H, by_start, by_end, spec)) \
+ return;
+ PIXPAT_FORMAT_LIST(X)
+#undef X
+#undef CAPS
+
+ run_norm(src_id, dst_id, src, dst, W, H, by_start, by_end, spec);
+}
+
+} // namespace pixpat