summaryrefslogtreecommitdiff
path: root/kms++util/src
diff options
context:
space:
mode:
authorTomi Valkeinen <tomi.valkeinen@ideasonboard.com>2025-02-02 11:26:23 +0200
committerTomi Valkeinen <tomi.valkeinen@ideasonboard.com>2025-03-26 15:44:00 +0200
commitf758e324e17b52116075bb9175a3dd03d223a424 (patch)
treeb18fba90e2b8498f8228f3eff9bc60adf3153cf2 /kms++util/src
parent6c49fe5b811464f59e3a31b869734071da0ec7c1 (diff)
kms++util: New template based conversion & testpat code
New templated conversion and test pattern code. Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Diffstat (limited to 'kms++util/src')
-rw-r--r--kms++util/src/conv-common.h177
-rw-r--r--kms++util/src/conv-rgb.h300
-rw-r--r--kms++util/src/conv-yuv-packed.h83
-rw-r--r--kms++util/src/conv-yuv-planar.h214
-rw-r--r--kms++util/src/conv-yuv-semiplanar.h214
-rw-r--r--kms++util/src/conv-yuv.h147
-rw-r--r--kms++util/src/conv.h7
-rw-r--r--kms++util/src/testpat.cpp301
8 files changed, 1339 insertions, 104 deletions
diff --git a/kms++util/src/conv-common.h b/kms++util/src/conv-common.h
new file mode 100644
index 0000000..837668f
--- /dev/null
+++ b/kms++util/src/conv-common.h
@@ -0,0 +1,177 @@
+#pragma once
+
+#include <algorithm>
+#include <array>
+
+#define MDSPAN_IMPL_STANDARD_NAMESPACE md
+#define MDSPAN_IMPL_PROPOSED_NAMESPACE exp
+
+#include <mdspan/mdspan.hpp>
+
+namespace kms
+{
+
+/*
+ * Helpers
+ */
+
+template<class T>
+constexpr auto make_strided_view(T* data, size_t rows, size_t cols, size_t row_stride_bytes)
+{
+ assert(row_stride_bytes % sizeof(T) == 0 && "Row stride must be aligned to element size");
+
+ size_t row_stride = row_stride_bytes / sizeof(T);
+ std::array<size_t, 2> strides = { row_stride, 1 };
+
+ auto layout = md::layout_stride::mapping(md::dextents<size_t, 2>(rows, cols), strides);
+
+ return md::mdspan<T, md::dextents<size_t, 2>, md::layout_stride>(data, layout);
+}
+
+template<class T>
+constexpr auto make_strided_fb_view(void* data, size_t rows, size_t cols, size_t row_stride_bytes)
+{
+ return make_strided_view(reinterpret_cast<T*>(data), rows, cols, row_stride_bytes);
+}
+
+// Helper for static loop unrolling
+template<size_t Begin, size_t End, typename F>
+constexpr void static_for(F&& f)
+{
+ [&]<size_t... I>(std::index_sequence<I...>) {
+ (f(std::integral_constant<size_t, Begin + I>{}), ...);
+ }(std::make_index_sequence<End - Begin>{});
+}
+
+template<typename T, typename TElement>
+concept HasIndexOperatorReturning = requires(T t) {
+ { t[0] } -> std::convertible_to<const TElement&>;
+};
+
+template<typename T, typename TElement>
+concept Is2Dspan = std::convertible_to<T, md::mdspan<TElement, md::dextents<size_t, 2>>>;
+
+/*
+ * Packing and Layouts
+ */
+
+/* This type must be big enough to hold any of the components' value */
+using component_storage_type = uint16_t;
+
+enum class ComponentType {
+ X, A,
+ R, G, B,
+ Y, Cb, Cr,
+ Y0, Y1, Y2,
+ Cb0, Cb1, Cb2,
+ Cr0, Cr1, Cr2,
+};
+
+// Describes a single component's bit layout
+template<ComponentType Type, size_t BitSize, size_t BitOffset>
+struct ComponentLayout {
+ static constexpr size_t size = BitSize;
+ static constexpr size_t offset = BitOffset;
+ static constexpr ComponentType type = Type;
+
+ static_assert(sizeof(component_storage_type) * 8 >= size);
+
+ static constexpr auto get_mask() { return ((1ull << BitSize) - 1) << BitOffset; }
+
+ template<typename TStorage>
+ static constexpr TStorage pack_value(TStorage value)
+ {
+ return (value & ((1ull << BitSize) - 1)) << BitOffset;
+ }
+
+ template<typename TStorage>
+ static constexpr component_storage_type unpack_value(TStorage value)
+ {
+ return (value >> BitOffset) & ((1ull << BitSize) - 1);
+ }
+};
+
+// Describes N components packed into a storage type
+template<typename TStorage, typename... Components>
+struct PlaneLayout {
+ using components_tuple = std::tuple<Components...>;
+
+ static constexpr size_t num_components = sizeof...(Components);
+
+ static constexpr size_t total_bits = (Components::size + ...);
+ static constexpr size_t storage_bits = sizeof(TStorage) * 8;
+ static_assert(total_bits <= storage_bits, "Components don't fit in storage type");
+
+ template<size_t N>
+ static constexpr size_t component_size = std::tuple_element_t<N, components_tuple>::size;
+
+ using storage_type = TStorage;
+
+ static constexpr std::array<ComponentType, sizeof...(Components)> order = {
+ Components::type...
+ };
+
+ template<ComponentType C>
+ static constexpr size_t component_count()
+ {
+ return std::ranges::count(order, C);
+ }
+
+ template<ComponentType C>
+ static constexpr size_t find_pos()
+ {
+ return std::ranges::find(order, C) - order.begin();
+ }
+
+ template<ComponentType C>
+ static constexpr size_t find_nth_pos(size_t n = 0)
+ {
+ auto it = std::ranges::find_if(
+ order, [&n](ComponentType type) mutable {
+ return type == C && n-- == 0;
+ });
+ return it - order.begin();
+ }
+
+ // Pack values into storage type (separate parameters version)
+ template<typename... Values>
+ static constexpr TStorage pack(Values... values)
+ {
+ static_assert(sizeof...(values) == num_components,
+ "Number of values must match number of components");
+ return (Components::template pack_value<TStorage>(values) | ...);
+ }
+
+ // Pack values into storage type (std:array version)
+ template<typename T, size_t N>
+ static constexpr TStorage pack(const std::array<T, N>& values)
+ {
+ static_assert(N == num_components,
+ "Number of values must match number of components");
+ return [&]<size_t... I>(std::index_sequence<I...>) {
+ return (Components::template pack_value<TStorage>(values[I]) | ...);
+ }(std::make_index_sequence<N>{});
+ }
+
+ static constexpr std::array<component_storage_type, num_components>
+ unpack(TStorage value)
+ {
+ return [&]<size_t... I>(std::index_sequence<I...>) {
+ return std::array{
+ std::tuple_element_t<I, components_tuple>::template unpack_value<
+ TStorage>(value)...
+ };
+ }(std::make_index_sequence<num_components>{});
+ }
+};
+
+template<typename... Planes>
+class FormatLayout
+{
+public:
+ static constexpr size_t num_planes = sizeof...(Planes);
+
+ template<size_t N> using plane = std::tuple_element_t<N, std::tuple<Planes...>>;
+};
+
+} // namespace kms
diff --git a/kms++util/src/conv-rgb.h b/kms++util/src/conv-rgb.h
new file mode 100644
index 0000000..e03ded1
--- /dev/null
+++ b/kms++util/src/conv-rgb.h
@@ -0,0 +1,300 @@
+#pragma once
+
+#include <vector>
+
+#include <kms++/framebuffer.h>
+#include <kms++util/color16.h>
+
+#include "conv-common.h"
+
+namespace kms
+{
+
+/*
+ * RGB
+ */
+
+template<ComponentType C3, size_t size3, size_t offset3,
+ ComponentType C2, size_t size2, size_t offset2,
+ ComponentType C1, size_t size1, size_t offset1,
+ ComponentType C0, size_t size0, size_t offset0>
+struct RGB_16_Layout
+ : public FormatLayout<PlaneLayout<uint16_t,
+ ComponentLayout<C0, size0, offset0>,
+ ComponentLayout<C1, size1, offset1>,
+ ComponentLayout<C2, size2, offset2>,
+ ComponentLayout<C3, size3, offset3>>>
+{
+};
+
+using XRGB4444_Layout = RGB_16_Layout<
+ ComponentType::X, 4, 12,
+ ComponentType::R, 4, 8,
+ ComponentType::G, 4, 4,
+ ComponentType::B, 4, 0
+>;
+
+using ARGB4444_Layout = RGB_16_Layout<
+ ComponentType::A, 4, 12,
+ ComponentType::R, 4, 8,
+ ComponentType::G, 4, 4,
+ ComponentType::B, 4, 0
+>;
+
+using XRGB1555_Layout = RGB_16_Layout<
+ ComponentType::X, 1, 15,
+ ComponentType::R, 5, 10,
+ ComponentType::G, 5, 5,
+ ComponentType::B, 5, 0
+>;
+
+using ARGB1555_Layout = RGB_16_Layout<
+ ComponentType::A, 1, 15,
+ ComponentType::R, 5, 10,
+ ComponentType::G, 5, 5,
+ ComponentType::B, 5, 0
+>;
+
+using RGB565_Layout = FormatLayout<PlaneLayout<uint16_t,
+ ComponentLayout<ComponentType::R, 5, 11>,
+ ComponentLayout<ComponentType::G, 6, 5>,
+ ComponentLayout<ComponentType::B, 5, 0>>
+>;
+
+using BGR565_Layout = FormatLayout<PlaneLayout<uint16_t,
+ ComponentLayout<ComponentType::B, 5, 11>,
+ ComponentLayout<ComponentType::G, 6, 5>,
+ ComponentLayout<ComponentType::R, 5, 0>>
+>;
+
+using RGB888_Layout = FormatLayout<PlaneLayout<uint32_t,
+ ComponentLayout<ComponentType::R, 8, 16>,
+ ComponentLayout<ComponentType::G, 8, 8>,
+ ComponentLayout<ComponentType::B, 8, 0>>
+>;
+
+using BGR888_Layout = FormatLayout<PlaneLayout<uint32_t,
+ ComponentLayout<ComponentType::B, 8, 16>,
+ ComponentLayout<ComponentType::G, 8, 8>,
+ ComponentLayout<ComponentType::R, 8, 0>>
+>;
+
+template<ComponentType C3, size_t size3, size_t offset3,
+ ComponentType C2, size_t size2, size_t offset2,
+ ComponentType C1, size_t size1, size_t offset1,
+ ComponentType C0, size_t size0, size_t offset0>
+struct RGB_32_Layout
+ : public FormatLayout<PlaneLayout<uint32_t,
+ ComponentLayout<C0, size0, offset0>,
+ ComponentLayout<C1, size1, offset1>,
+ ComponentLayout<C2, size2, offset2>,
+ ComponentLayout<C3, size3, offset3>>>
+{
+};
+
+template<ComponentType C3, ComponentType C2, ComponentType C1, ComponentType C0>
+struct RGB_8_32_Layout
+ : public RGB_32_Layout<
+ C0, 8, 0,
+ C1, 8, 8,
+ C2, 8, 16,
+ C3, 8, 24>
+{
+};
+
+using XRGB8888_Layout = RGB_8_32_Layout<ComponentType::X, ComponentType::R, ComponentType::G, ComponentType::B>;
+using ARGB8888_Layout = RGB_8_32_Layout<ComponentType::A, ComponentType::R, ComponentType::G, ComponentType::B>;
+
+using XBGR8888_Layout = RGB_8_32_Layout<ComponentType::X, ComponentType::B, ComponentType::G, ComponentType::R>;
+using ABGR8888_Layout = RGB_8_32_Layout<ComponentType::A, ComponentType::B, ComponentType::G, ComponentType::R>;
+
+using RGBX8888_Layout = RGB_8_32_Layout<ComponentType::R, ComponentType::G, ComponentType::B, ComponentType::X>;
+using RGBA8888_Layout = RGB_8_32_Layout<ComponentType::R, ComponentType::G, ComponentType::B, ComponentType::A>;
+
+using BGRX8888_Layout = RGB_8_32_Layout<ComponentType::B, ComponentType::G, ComponentType::R, ComponentType::X>;
+using BGRA8888_Layout = RGB_8_32_Layout<ComponentType::B, ComponentType::G, ComponentType::R, ComponentType::A>;
+
+using XRGB2101010_Layout = RGB_32_Layout<
+ ComponentType::X, 2, 30,
+ ComponentType::R, 10, 20,
+ ComponentType::G, 10, 10,
+ ComponentType::B, 10, 0
+>;
+
+using ARGB2101010_Layout = RGB_32_Layout<
+ ComponentType::A, 2, 30,
+ ComponentType::R, 10, 20,
+ ComponentType::G, 10, 10,
+ ComponentType::B, 10, 0
+>;
+
+using XBGR2101010_Layout = RGB_32_Layout<
+ ComponentType::X, 2, 30,
+ ComponentType::B, 10, 20,
+ ComponentType::G, 10, 10,
+ ComponentType::R, 10, 0
+>;
+
+using ABGR2101010_Layout = RGB_32_Layout<
+ ComponentType::A, 2, 30,
+ ComponentType::B, 10, 20,
+ ComponentType::G, 10, 10,
+ ComponentType::R, 10, 0
+>;
+
+using RGBX1010102_Layout = RGB_32_Layout<
+ ComponentType::R, 10, 22,
+ ComponentType::G, 10, 12,
+ ComponentType::B, 10, 2,
+ ComponentType::X, 2, 0
+>;
+
+using RGBA1010102_Layout = RGB_32_Layout<
+ ComponentType::R, 10, 22,
+ ComponentType::G, 10, 12,
+ ComponentType::B, 10, 2,
+ ComponentType::A, 2, 0
+>;
+
+using BGRX1010102_Layout = RGB_32_Layout<
+ ComponentType::B, 10, 22,
+ ComponentType::G, 10, 12,
+ ComponentType::R, 10, 2,
+ ComponentType::X, 2, 0
+>;
+
+using BGRA1010102_Layout = RGB_32_Layout<
+ ComponentType::B, 10, 22,
+ ComponentType::G, 10, 12,
+ ComponentType::R, 10, 2,
+ ComponentType::A, 2, 0
+>;
+
+template<typename Layout>
+class ARGB_Writer
+{
+ using Plane = Layout::template plane<0>;
+ using TStorage = Plane::storage_type;
+
+ static_assert(Layout::num_planes == 1);
+ static_assert(Plane::num_components == 3 || Plane::num_components == 4);
+
+ static_assert(Plane::template component_count<ComponentType::R>() == 1);
+ static_assert(Plane::template component_count<ComponentType::G>() == 1);
+ static_assert(Plane::template component_count<ComponentType::B>() == 1);
+
+ static constexpr bool has_alpha = Plane::template component_count<ComponentType::A>();
+ static constexpr bool has_padding = Plane::template component_count<ComponentType::X>();
+
+ static constexpr bool needs_packed_access = Plane::total_bits != Plane::storage_bits;
+
+ static constexpr size_t a_idx = Plane::template find_pos<ComponentType::A>();
+ static constexpr size_t x_idx = Plane::template find_pos<ComponentType::X>();
+ static constexpr size_t r_idx = Plane::template find_pos<ComponentType::R>();
+ static constexpr size_t g_idx = Plane::template find_pos<ComponentType::G>();
+ static constexpr size_t b_idx = Plane::template find_pos<ComponentType::B>();
+
+ static constexpr size_t a_shift = has_alpha ? 16 - Plane::template component_size<a_idx> : 0;
+ static constexpr size_t x_shift = has_padding ? 16 - Plane::template component_size<x_idx> : 0;
+ static constexpr size_t r_shift = 16 - Plane::template component_size<r_idx>;
+ static constexpr size_t g_shift = 16 - Plane::template component_size<g_idx>;
+ static constexpr size_t b_shift = 16 - Plane::template component_size<b_idx>;
+
+ static_assert(Plane::total_bits % 8 == 0);
+ static constexpr size_t bytes_per_pixel = Plane::total_bits / 8;
+
+public:
+ // Pack and write num_pixels pixels from src_line to dst_line
+ static void pack_line(HasIndexOperatorReturning<TStorage> auto&& dst_line,
+ HasIndexOperatorReturning<RGB16> auto&& src_line,
+ size_t num_pixels)
+ {
+ for (size_t x = 0; x < num_pixels; x++) {
+ const RGB16& pix = src_line[x];
+
+ std::array<component_storage_type, Plane::num_components>
+ components;
+
+ if constexpr (has_alpha)
+ components[a_idx] = pix.a >> a_shift;
+
+ if constexpr (has_padding)
+ components[x_idx] = 0;
+
+ components[r_idx] = pix.r >> r_shift;
+ components[g_idx] = pix.g >> g_shift;
+ components[b_idx] = pix.b >> b_shift;
+
+ if constexpr (!needs_packed_access) {
+ dst_line[x] = Plane::pack(components);
+ } else {
+ auto dst_bytes = reinterpret_cast<uint8_t*>(&dst_line[0]);
+
+ TStorage packed = Plane::pack(components);
+
+ memcpy(dst_bytes + x * bytes_per_pixel, &packed,
+ bytes_per_pixel);
+ }
+ }
+ }
+
+ // Read and unpack num_pixels pixels from src_line to dst_line
+ static void unpack_line(HasIndexOperatorReturning<RGB16> auto&& dst_line,
+ HasIndexOperatorReturning<TStorage> auto&& src_line,
+ size_t num_pixels)
+ {
+ for (size_t x = 0; x < num_pixels; x++) {
+ decltype(Plane::unpack(src_line[x])) components;
+
+ if constexpr (!needs_packed_access) {
+ components = Plane::unpack(src_line[x]);
+ } else {
+ auto src_bytes =
+ reinterpret_cast<const uint8_t*>(&src_line[0]);
+ TStorage packed;
+
+ memcpy(&packed, src_bytes + x * bytes_per_pixel,
+ bytes_per_pixel);
+
+ components = Plane::unpack(packed);
+ }
+
+ dst_line[x] = RGB16 {
+ static_cast<uint16_t>(components[r_idx] << r_shift),
+ static_cast<uint16_t>(components[g_idx] << g_shift),
+ static_cast<uint16_t>(components[b_idx] << b_shift),
+ static_cast<uint16_t>(has_alpha ? components[a_idx] << a_shift : 0),
+ };
+ }
+ }
+
+ static void write_pattern(IFramebuffer& fb, size_t start_y, size_t end_y,
+ auto&& generate_line)
+ {
+ std::vector<RGB16> linebuf(fb.width());
+
+ // View to the plane
+ auto view = make_strided_fb_view<TStorage>(fb.map(0), fb.height(), fb.width(),
+ fb.stride(0));
+
+ for (size_t y_src = start_y; y_src <= end_y; y_src++) {
+ generate_line(y_src, linebuf);
+
+ auto dst = md::submdspan(view, y_src, md::full_extent);
+
+ pack_line(dst, linebuf, fb.width());
+ }
+ }
+
+ static void get_line(IFramebuffer& fb, size_t w, size_t h, size_t row, std::span<RGB16> linebuf)
+ {
+ auto view = make_strided_fb_view<const TStorage>(fb.map(0), fb.height(), fb.width(),
+ fb.stride(0));
+
+ auto src = md::submdspan(view, row, md::full_extent);
+
+ unpack_line(linebuf, src);
+ }
+};
+
+} // namespace kms
diff --git a/kms++util/src/conv-yuv-packed.h b/kms++util/src/conv-yuv-packed.h
new file mode 100644
index 0000000..3aa01fa
--- /dev/null
+++ b/kms++util/src/conv-yuv-packed.h
@@ -0,0 +1,83 @@
+#pragma once
+
+#include <vector>
+
+#include <kms++/framebuffer.h>
+#include <kms++util/color16.h>
+
+#include "conv-common.h"
+
+namespace kms
+{
+
+/* YUV Packed */
+
+template<ComponentType C0, ComponentType C1, ComponentType C2, ComponentType C3>
+struct YUV_Packed_Format
+ : public FormatLayout<
+ PlaneLayout<uint32_t,
+ ComponentLayout<C0, 8, 0>,
+ ComponentLayout<C1, 8, 8>,
+ ComponentLayout<C2, 8, 16>,
+ ComponentLayout<C3, 8, 24>
+ >
+ >
+{
+};
+
+// Define common packed YUV formats
+using YUYV_Layout = YUV_Packed_Format<ComponentType::Y0, ComponentType::Cb,
+ ComponentType::Y1, ComponentType::Cr>;
+
+using YVYU_Layout = YUV_Packed_Format<ComponentType::Y0, ComponentType::Cr,
+ ComponentType::Y1, ComponentType::Cb>;
+
+using UYVY_Layout = YUV_Packed_Format<ComponentType::Cb, ComponentType::Y0,
+ ComponentType::Cr, ComponentType::Y1>;
+
+using VYUY_Layout = YUV_Packed_Format<ComponentType::Cr, ComponentType::Y0,
+ ComponentType::Cb, ComponentType::Y1>;
+
+template<typename Layout>
+class YUVPackedWriter
+{
+ using Plane = Layout::template plane<0>;
+ using TStorage = Plane::storage_type;
+
+ static constexpr size_t y0_pos = Plane::template find_pos<ComponentType::Y0>();
+ static constexpr size_t y1_pos = Plane::template find_pos<ComponentType::Y1>();
+ static constexpr size_t cb_pos = Plane::template find_pos<ComponentType::Cb>();
+ static constexpr size_t cr_pos = Plane::template find_pos<ComponentType::Cr>();
+
+public:
+ static void write_pattern(IFramebuffer& fb, size_t start_y, size_t end_y,
+ auto&& generate_line)
+ {
+ std::vector<YUV16> linebuf(fb.width());
+
+ auto view = make_strided_fb_view<TStorage>(fb.map(0), fb.height(),
+ fb.width() / 2, // Two pixels per storage unit
+ fb.stride(0));
+
+ for (size_t y = start_y; y <= end_y; y++) {
+ generate_line(y, linebuf);
+
+ for (size_t x = 0; x < fb.width(); x += 2) {
+ // Get two pixels
+ const YUV16& pix0 = linebuf[x];
+ const YUV16& pix1 = linebuf[x + 1];
+
+ std::array<uint8_t, 4> components;
+
+ components[y0_pos] = pix0.y >> 8;
+ components[y1_pos] = pix1.y >> 8;
+ components[cb_pos] = ((pix0.u + pix1.u) / 2) >> 8;
+ components[cr_pos] = ((pix0.v + pix1.v) / 2) >> 8;
+
+ view(y, x / 2) = Plane::pack(components);
+ }
+ }
+ }
+};
+
+} // namespace kms
diff --git a/kms++util/src/conv-yuv-planar.h b/kms++util/src/conv-yuv-planar.h
new file mode 100644
index 0000000..0ece6f7
--- /dev/null
+++ b/kms++util/src/conv-yuv-planar.h
@@ -0,0 +1,214 @@
+#pragma once
+
+#include <vector>
+
+#include <kms++/framebuffer.h>
+#include <kms++util/color16.h>
+
+#include "conv-common.h"
+
+namespace kms
+{
+/* YUV Planar */
+
+template<ComponentType P1, ComponentType P2, size_t HSubUV = 1, size_t VSubUV = 1>
+class YUV_Planar_Layout
+ : public FormatLayout<
+ PlaneLayout<uint8_t, ComponentLayout<ComponentType::Y, 8, 0>>,
+ PlaneLayout<uint8_t, ComponentLayout<P1, 8, 0>>,
+ PlaneLayout<uint8_t, ComponentLayout<P2, 8, 0>>
+ >
+{
+public:
+ static constexpr std::array<ComponentType, 2> uv_order = { P1, P2 };
+ static constexpr size_t h_sub = HSubUV;
+ static constexpr size_t v_sub = VSubUV;
+
+ template<ComponentType P>
+ static constexpr size_t find_plane()
+ {
+ return 1 + (std::ranges::find(uv_order, P) - uv_order.begin());
+ }
+
+ static constexpr size_t y_plane = 0; // Y is always plane 0
+ static constexpr size_t cb_plane = find_plane<ComponentType::Cb>();
+ static constexpr size_t cr_plane = find_plane<ComponentType::Cr>();
+};
+
+using YUV444_Layout = YUV_Planar_Layout<ComponentType::Cb, ComponentType::Cr>;
+using YVU444_Layout = YUV_Planar_Layout<ComponentType::Cr, ComponentType::Cb>;
+using YUV422_Layout = YUV_Planar_Layout<ComponentType::Cb, ComponentType::Cr, 2, 1>;
+using YVU422_Layout = YUV_Planar_Layout<ComponentType::Cr, ComponentType::Cb, 2, 1>;
+using YUV420_Layout = YUV_Planar_Layout<ComponentType::Cb, ComponentType::Cr, 2, 2>;
+using YVU420_Layout = YUV_Planar_Layout<ComponentType::Cr, ComponentType::Cb, 2, 2>;
+
+template<typename Format>
+class YUVPlanarWriter
+{
+ using YLayout = Format::template plane<Format::y_plane>;
+ using CbLayout = Format::template plane<Format::cb_plane>;
+ using CrLayout = Format::template plane<Format::cr_plane>;
+
+ using TY = typename YLayout::storage_type;
+ using TCb = typename CbLayout::storage_type;
+ using TCr = typename CrLayout::storage_type;
+public:
+ static void write_pattern(IFramebuffer& fb, size_t start_y, size_t end_y,
+ auto&& generate_line)
+ {
+ assert(start_y % Format::v_sub == 0);
+ assert((end_y + 1) % Format::v_sub == 0);
+
+ // Line buffers
+ std::vector<YUV16> linebuf_storage(fb.width() * Format::v_sub);
+ auto linebuf = md::mdspan(linebuf_storage.data(), Format::v_sub, fb.width());
+
+ // Views to all planes
+ auto y_buf = make_strided_fb_view<TY>(fb.map(Format::y_plane), fb.height(),
+ fb.width(), fb.stride(Format::y_plane));
+
+ auto cb_buf = make_strided_fb_view<TCb>(fb.map(Format::cb_plane),
+ fb.height() / Format::v_sub,
+ fb.width() / Format::h_sub,
+ fb.stride(Format::cb_plane));
+
+ auto cr_buf = make_strided_fb_view<TCr>(fb.map(Format::cr_plane),
+ fb.height() / Format::v_sub,
+ fb.width() / Format::h_sub,
+ fb.stride(Format::cr_plane));
+
+ for (size_t y_src = start_y; y_src <= end_y; y_src++) {
+ size_t y_offset = y_src % Format::v_sub;
+
+ if (y_offset == 0) {
+ // Fill line buffers
+ for (size_t buf_y = 0; buf_y < Format::v_sub; buf_y++) {
+ auto line = md::submdspan(linebuf, buf_y, md::full_extent);
+ std::span<YUV16> span(line.data_handle(), line.size());
+ generate_line(y_src + buf_y, span);
+ }
+ }
+
+ // Write Y plane
+ write_y_line(y_buf, y_src, linebuf, y_offset);
+
+ // Write Cb/Cr planes if we're at a subsampling boundary
+ if (y_offset == 0)
+ write_uv_line(cb_buf, cr_buf, linebuf, y_src);
+ }
+ }
+
+ static void write_lines(IFramebuffer& fb, size_t start_y, size_t end_y,
+ Is2Dspan<YUV16> auto&& lines)
+ {
+ const size_t height = end_y - start_y + 1;
+
+ if (lines.extent(0) < height || lines.extent(1) < fb.width())
+ throw std::invalid_argument("Source line buffer too small");
+
+ assert(start_y % Format::v_sub == 0);
+ assert((end_y + 1) % Format::v_sub == 0);
+
+ // Views to all planes
+ auto y_buf = make_strided_fb_view<TY>(fb.map(Format::y_plane), fb.height(),
+ fb.width(), fb.stride(Format::y_plane));
+
+ auto cb_buf = make_strided_fb_view<TCb>(fb.map(Format::cb_plane),
+ fb.height() / Format::v_sub,
+ fb.width() / Format::h_sub,
+ fb.stride(Format::cb_plane));
+
+ auto cr_buf = make_strided_fb_view<TCr>(fb.map(Format::cr_plane),
+ fb.height() / Format::v_sub,
+ fb.width() / Format::h_sub,
+ fb.stride(Format::cr_plane));
+
+ for (size_t y_src = start_y; y_src <= end_y; y_src++) {
+ size_t y_offset = y_src % Format::v_sub;
+
+ // Write Y plane
+ write_y_line(y_buf, y_src, lines, y_offset);
+
+ // Write Cb/Cr planes if we're at a subsampling boundary
+ if (y_offset == 0)
+ write_uv_line(cb_buf, cr_buf, lines, y_src);
+ }
+ }
+
+private:
+ template<typename YBuf>
+ static void write_y_line(YBuf& y_buf, size_t y_src, auto& linebuf, size_t y_offset)
+ {
+ for (size_t x = 0; x < linebuf.extent(1); x++)
+ y_buf(y_src, x) = YLayout::pack(linebuf(y_offset, x).y >> 8);
+ }
+
+ template<typename UVBuf>
+ static void write_uv_line(UVBuf& cb_buf, UVBuf& cr_buf, auto& linebuf, size_t y_src)
+ {
+ const size_t uv_y = y_src / Format::v_sub;
+
+ for (size_t x = 0; x < linebuf.extent(1); x += Format::h_sub) {
+ // Average subsampled region
+ uint32_t u_sum = 0, v_sum = 0;
+ for (size_t y = 0; y < Format::v_sub; y++) {
+ for (size_t x_off = 0; x_off < Format::h_sub; x_off++) {
+ u_sum += linebuf(y, x + x_off).u;
+ v_sum += linebuf(y, x + x_off).v;
+ }
+ }
+
+ const size_t total_samples = Format::h_sub * Format::v_sub;
+ const size_t uv_x = x / Format::h_sub;
+
+ cb_buf(uv_y, uv_x) = CbLayout::pack(u_sum / total_samples >> 8);
+ cr_buf(uv_y, uv_x) = CrLayout::pack(v_sum / total_samples >> 8);
+ }
+ }
+
+public:
+ static void read_lines(IFramebuffer& fb, size_t start_y, size_t end_y,
+ Is2Dspan<YUV16> auto&& dest)
+ {
+ const size_t height = end_y - start_y + 1;
+
+ if (dest.extent(0) < height || dest.extent(1) < fb.width())
+ throw std::invalid_argument("Destination line buffer too small");
+
+ assert(start_y % Format::v_sub == 0);
+ assert((end_y + 1) % Format::v_sub == 0);
+
+ auto y_buf = make_strided_fb_view<const TY>(fb.map(Format::y_plane), fb.height(),
+ fb.width(), fb.stride(Format::y_plane));
+
+ auto cb_buf = make_strided_fb_view<const TCb>(fb.map(Format::cb_plane),
+ fb.height() / Format::v_sub,
+ fb.width() / Format::h_sub,
+ fb.stride(Format::cb_plane));
+
+ auto cr_buf = make_strided_fb_view<const TCr>(fb.map(Format::cr_plane),
+ fb.height() / Format::v_sub,
+ fb.width() / Format::h_sub,
+ fb.stride(Format::cr_plane));
+
+ for (size_t y = start_y; y <= end_y; y++) {
+ for (size_t x = 0; x < fb.width(); x++) {
+ const auto y_val = YLayout::unpack(y_buf(y, x))[0];
+
+ const size_t uv_y = y / Format::v_sub;
+ const size_t uv_x = x / Format::h_sub;
+
+ const auto u_val = CbLayout::unpack(cb_buf(uv_y, uv_x))[0];
+ const auto v_val = CrLayout::unpack(cr_buf(uv_y, uv_x))[0];
+
+ dest(y - start_y, x) = YUV16 {
+ static_cast<uint16_t>(y_val << 8),
+ static_cast<uint16_t>(u_val << 8),
+ static_cast<uint16_t>(v_val << 8)
+ };
+ }
+ }
+ }
+};
+
+} // namespace kms
diff --git a/kms++util/src/conv-yuv-semiplanar.h b/kms++util/src/conv-yuv-semiplanar.h
new file mode 100644
index 0000000..fa7e0cb
--- /dev/null
+++ b/kms++util/src/conv-yuv-semiplanar.h
@@ -0,0 +1,214 @@
+#pragma once
+
+#include <vector>
+
+#include <kms++/framebuffer.h>
+#include <kms++util/color16.h>
+
+#include "conv-common.h"
+
+namespace kms
+{
+
+/* Semiplanar YUV */
+
+template<ComponentType C0, ComponentType C1, size_t HSub, size_t VSub>
+struct NV12_Family_Layout
+ : public FormatLayout<
+ PlaneLayout<uint8_t,
+ ComponentLayout<ComponentType::Y, 8, 0>
+ >,
+ PlaneLayout<uint16_t,
+ ComponentLayout<C0, 8, 0>,
+ ComponentLayout<C1, 8, 8>
+ >
+ >
+{
+ static constexpr size_t h_sub = HSub;
+ static constexpr size_t v_sub = VSub;
+};
+
+using NV12_Layout = NV12_Family_Layout<ComponentType::Cb, ComponentType::Cr, 2, 2>;
+using NV21_Layout = NV12_Family_Layout<ComponentType::Cr, ComponentType::Cb, 2, 2>;
+
+using NV16_Layout = NV12_Family_Layout<ComponentType::Cb, ComponentType::Cr, 2, 1>;
+using NV61_Layout = NV12_Family_Layout<ComponentType::Cr, ComponentType::Cb, 2, 1>;
+
+template<size_t HSub, size_t VSub>
+struct XV15_Family_Layout
+ : public FormatLayout<
+ PlaneLayout<uint32_t,
+ ComponentLayout<ComponentType::Y, 10, 0>,
+ ComponentLayout<ComponentType::Y, 10, 10>,
+ ComponentLayout<ComponentType::Y, 10, 20>
+ >,
+ PlaneLayout<uint64_t,
+ ComponentLayout<ComponentType::Cb, 10, 0>,
+ ComponentLayout<ComponentType::Cr, 10, 10>,
+ ComponentLayout<ComponentType::Cb, 10, 20>,
+ ComponentLayout<ComponentType::Cr, 10, 32>,
+ ComponentLayout<ComponentType::Cb, 10, 42>,
+ ComponentLayout<ComponentType::Cr, 10, 52>
+ >
+ >
+{
+ static constexpr size_t h_sub = HSub;
+ static constexpr size_t v_sub = VSub;
+};
+
+using XV15_Layout = XV15_Family_Layout<2, 2>;
+using XV20_Layout = XV15_Family_Layout<2, 1>;
+
+template<size_t HSub, size_t VSub>
+struct SubsampleHelper {
+ template<typename View> static constexpr auto subsample(const View& view, size_t group_idx)
+ {
+ uint32_t sum = 0;
+
+ static_for<0, HSub>([&](auto x) { sum += view(0, group_idx + x); });
+
+ if constexpr (VSub > 1) {
+ static_for<1, VSub>([&](auto y) {
+ static_for<0, HSub>([&](auto x) { sum += view(y, group_idx + x); });
+ });
+ }
+
+ return sum / (HSub * VSub);
+ }
+};
+
+template<typename Layout>
+class YUVSemiPlanarWriter
+{
+ static_assert(Layout::num_planes == 2);
+
+ static constexpr size_t h_sub = Layout::h_sub;
+ static constexpr size_t v_sub = Layout::v_sub;
+
+ using YLayout = Layout::template plane<0>;
+ using UVLayout = Layout::template plane<1>;
+
+ using TY = YLayout::storage_type;
+ using TCrCb = UVLayout::storage_type;
+
+ static constexpr size_t pixels_in_group = YLayout::template component_count<ComponentType::Y>();
+ static_assert(pixels_in_group == UVLayout::template component_count<ComponentType::Cb>());
+ static_assert(pixels_in_group == UVLayout::template component_count<ComponentType::Cr>());
+
+public:
+ static void write_pattern(IFramebuffer& fb, size_t start_y, size_t end_y,
+ auto&& generate_line)
+ {
+ assert(start_y % v_sub == 0);
+ assert((end_y + 1) % v_sub == 0);
+ if (fb.width() % pixels_in_group != 0)
+ throw std::invalid_argument("FB width doesn't align to pixel format");
+
+ // Line buffers
+ std::vector<YUV16> linebuf_storage(fb.width() * v_sub);
+ auto linebuf = md::mdspan(linebuf_storage.data(), v_sub, fb.width());
+
+ // Views to the planes
+ auto y_view = make_strided_fb_view<TY>(fb.map(0), fb.height(),
+ fb.width() / pixels_in_group, fb.stride(0));
+
+ auto uv_view = make_strided_fb_view<TCrCb>(fb.map(1), fb.height() / v_sub,
+ fb.width() / pixels_in_group / h_sub,
+ fb.stride(1));
+
+ for (size_t y_src = start_y; y_src <= end_y; y_src++) {
+ size_t y_offset = y_src % v_sub;
+
+ if (y_offset == 0) {
+ // Fill line buffers
+ for (size_t y_offset = 0; y_offset < v_sub; y_offset++) {
+ auto line = md::submdspan(linebuf, y_offset, md::full_extent);
+ std::span<YUV16> span(line.data_handle(), line.size());
+ generate_line(y_src + y_offset, span);
+ }
+ }
+
+ // Write Y values from the line buffer
+ write_y_samples(md::submdspan(y_view, y_src, md::full_extent),
+ md::submdspan(linebuf, y_offset, md::full_extent));
+
+ if (y_offset == 0) {
+ // Write UV values from the line buffers
+ write_uv_samples(uv_view, linebuf, y_src);
+ }
+ }
+ }
+
+private:
+
+ template<typename YBuf>
+ static void write_y_samples(YBuf&& y_view, auto&& linebuf)
+ {
+ for (size_t x_src = 0; x_src < linebuf.extent(1); x_src += pixels_in_group) {
+ auto x_dst = x_src / pixels_in_group;
+
+ write_y_group(y_view, linebuf, x_src, x_dst,
+ std::make_index_sequence<pixels_in_group>{});
+ }
+ }
+
+ template<typename YBuf, size_t... I>
+ static void write_y_group(YBuf&& y_view, auto&& linebuf, size_t x_src,
+ size_t x_dst, std::index_sequence<I...>)
+ {
+ std::array<component_storage_type, YLayout::num_components> y_values{
+ static_cast<component_storage_type>((linebuf(x_src + I).y >> (16 - YLayout::template component_size<I>)))...
+ };
+
+ y_view(x_dst) = YLayout::pack(y_values);
+ }
+
+ template<typename UVBuf>
+ static void write_uv_samples(UVBuf& uv_view, auto& linebuf, size_t y_src)
+ {
+ for (size_t x_src = 0; x_src < linebuf.extent(1); x_src += pixels_in_group * h_sub) {
+ const size_t y_offset = 0;
+ auto y_dst = (y_src + y_offset) / v_sub;
+ auto x_dst = x_src / (pixels_in_group * h_sub);
+
+ auto group_view = md::submdspan(linebuf, std::tuple(y_offset, y_offset + v_sub),
+ std::tuple(x_src, x_src + h_sub * pixels_in_group));
+
+ write_uv_group(uv_view, group_view, y_dst, x_dst,
+ std::make_index_sequence<pixels_in_group>{});
+ }
+ }
+
+ template<typename UVBuf, size_t... I>
+ static void write_uv_group(UVBuf& uv_view, auto& group_view, size_t y_dst, size_t x_dst,
+ std::index_sequence<I...>)
+ {
+ std::array<component_storage_type, UVLayout::num_components> uv_values;
+
+ (
+ [&]<size_t i>() {
+ constexpr size_t group_idx = i * h_sub;
+
+ constexpr size_t u_idx = UVLayout::template find_nth_pos<ComponentType::Cb>(i);
+ constexpr size_t v_idx = UVLayout::template find_nth_pos<ComponentType::Cr>(i);
+
+ auto u = SubsampleHelper<h_sub, v_sub>::subsample(
+ [&group_view](size_t y, size_t x) { return group_view(y, x).u; },
+ group_idx);
+
+ auto v = SubsampleHelper<h_sub, v_sub>::subsample(
+ [&group_view](size_t y, size_t x) { return group_view(y, x).v; },
+ group_idx);
+
+ uv_values[u_idx] =
+ u >> (16 - UVLayout::template component_size<u_idx>);
+ uv_values[v_idx] =
+ v >> (16 - UVLayout::template component_size<v_idx>);
+ }.template operator()<I>(),
+ ...);
+
+ uv_view(y_dst, x_dst) = UVLayout::pack(uv_values);
+ }
+};
+
+} // namespace kms
diff --git a/kms++util/src/conv-yuv.h b/kms++util/src/conv-yuv.h
new file mode 100644
index 0000000..4478226
--- /dev/null
+++ b/kms++util/src/conv-yuv.h
@@ -0,0 +1,147 @@
+#pragma once
+
+#include <vector>
+
+#include <kms++/framebuffer.h>
+#include <kms++util/color16.h>
+
+#include "conv-common.h"
+
+namespace kms
+{
+
+using XVUY2101010_Layout =
+ FormatLayout<PlaneLayout<uint32_t,
+ ComponentLayout<ComponentType::Y, 10, 0>,
+ ComponentLayout<ComponentType::Cb, 10, 10>,
+ ComponentLayout<ComponentType::Cr, 10, 20>,
+ ComponentLayout<ComponentType::X, 2, 30>>>;
+
+template<typename Layout>
+class YUV_Writer
+{
+ using Plane = Layout::template plane<0>;
+ using TStorage = Plane::storage_type;
+
+ static_assert(Layout::num_planes == 1);
+ static_assert(Plane::num_components == 3 || Plane::num_components == 4);
+
+ static_assert(Plane::template component_count<ComponentType::Y>() == 1);
+ static_assert(Plane::template component_count<ComponentType::Cb>() == 1);
+ static_assert(Plane::template component_count<ComponentType::Cr>() == 1);
+
+ static constexpr bool has_alpha = Plane::template component_count<ComponentType::A>();
+ static constexpr bool has_padding = Plane::template component_count<ComponentType::X>();
+
+ static constexpr bool needs_packed_access = Plane::total_bits != Plane::storage_bits;
+
+ static constexpr size_t a_idx = Plane::template find_pos<ComponentType::A>();
+ static constexpr size_t x_idx = Plane::template find_pos<ComponentType::X>();
+ static constexpr size_t y_idx = Plane::template find_pos<ComponentType::Y>();
+ static constexpr size_t cb_idx = Plane::template find_pos<ComponentType::Cb>();
+ static constexpr size_t cr_idx = Plane::template find_pos<ComponentType::Cr>();
+
+ static constexpr size_t a_shift = has_alpha ? 16 - Plane::template component_size<a_idx> : 0;
+ static constexpr size_t x_shift = has_padding ? 16 - Plane::template component_size<x_idx> : 0;
+ static constexpr size_t y_shift = 16 - Plane::template component_size<y_idx>;
+ static constexpr size_t cb_shift = 16 - Plane::template component_size<cb_idx>;
+ static constexpr size_t cr_shift = 16 - Plane::template component_size<cr_idx>;
+
+ static_assert(Plane::total_bits % 8 == 0);
+ static constexpr size_t bytes_per_pixel = Plane::total_bits / 8;
+
+public:
+ // Pack and write num_pixels pixels from src_line to dst_line
+ static void pack_line(HasIndexOperatorReturning<TStorage> auto&& dst_line,
+ HasIndexOperatorReturning<YUV16> auto&& src_line,
+ size_t num_pixels)
+ {
+ for (size_t x = 0; x < num_pixels; x++) {
+ const YUV16& pix = src_line[x];
+
+ std::array<component_storage_type, Plane::num_components>
+ components;
+
+ if constexpr (has_alpha)
+ components[a_idx] = pix.a >> a_shift;
+
+ if constexpr (has_padding)
+ components[x_idx] = 0;
+
+ components[y_idx] = pix.y >> y_shift;
+ components[cb_idx] = pix.u >> cb_shift;
+ components[cr_idx] = pix.v >> cr_shift;
+
+ if constexpr (!needs_packed_access) {
+ dst_line[x] = Plane::pack(components);
+ } else {
+ auto dst_bytes = reinterpret_cast<uint8_t*>(&dst_line[0]);
+
+ TStorage packed = Plane::pack(components);
+
+ memcpy(dst_bytes + x * bytes_per_pixel, &packed,
+ bytes_per_pixel);
+ }
+ }
+ }
+
+ // Read and unpack num_pixels pixels from src_line to dst_line
+ static void unpack_line(HasIndexOperatorReturning<YUV16> auto&& dst_line,
+ HasIndexOperatorReturning<TStorage> auto&& src_line,
+ size_t num_pixels)
+ {
+ for (size_t x = 0; x < num_pixels; x++) {
+ decltype(Plane::unpack(src_line[x])) components;
+
+ if constexpr (!needs_packed_access) {
+ components = Plane::unpack(src_line[x]);
+ } else {
+ auto src_bytes =
+ reinterpret_cast<const uint8_t*>(&src_line[0]);
+ TStorage packed;
+
+ memcpy(&packed, src_bytes + x * bytes_per_pixel,
+ bytes_per_pixel);
+
+ components = Plane::unpack(packed);
+ }
+
+ dst_line[x] = YUV16 {
+ static_cast<uint16_t>(components[y_idx] << y_shift),
+ static_cast<uint16_t>(components[cb_idx] << cb_shift),
+ static_cast<uint16_t>(components[cr_idx] << cr_shift),
+ static_cast<uint16_t>(has_alpha ? components[a_idx] << a_shift : 0),
+ };
+ }
+ }
+
+ static void write_pattern(IFramebuffer& fb, size_t start_y, size_t end_y,
+ auto&& generate_line)
+ {
+ std::vector<YUV16> linebuf(fb.width());
+
+ // View to the plane
+ auto view = make_strided_fb_view<TStorage>(fb.map(0), fb.height(), fb.width(),
+ fb.stride(0));
+
+ for (size_t y_src = start_y; y_src <= end_y; y_src++) {
+ generate_line(y_src, linebuf);
+
+ auto dst = md::submdspan(view, y_src, md::full_extent);
+
+ pack_line(dst, linebuf, fb.width());
+ }
+ }
+
+ static void get_line(IFramebuffer& fb, size_t w, size_t h, size_t row, std::span<YUV16> linebuf)
+ {
+ auto view = make_strided_fb_view<const TStorage>(fb.map(0), fb.height(), fb.width(),
+ fb.stride(0));
+
+ auto src = md::submdspan(view, row, md::full_extent);
+
+ unpack_line(linebuf, src);
+ }
+};
+
+} // namespace kms
diff --git a/kms++util/src/conv.h b/kms++util/src/conv.h
new file mode 100644
index 0000000..4b2baa5
--- /dev/null
+++ b/kms++util/src/conv.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "conv-rgb.h"
+#include "conv-yuv.h"
+#include "conv-yuv-packed.h"
+#include "conv-yuv-semiplanar.h"
+#include "conv-yuv-planar.h"
diff --git a/kms++util/src/testpat.cpp b/kms++util/src/testpat.cpp
index 029684a..05efeab 100644
--- a/kms++util/src/testpat.cpp
+++ b/kms++util/src/testpat.cpp
@@ -1,8 +1,11 @@
-//#define DRAW_PERF_PRINT
-
#include <cstring>
-#include <cassert>
+#include <fmt/format.h>
+#include <functional>
+#include <optional>
+#include <span>
+#include <stdexcept>
+#include <vector>
#ifdef HAS_PTHREAD
#include <thread>
@@ -11,15 +14,14 @@
#include <kms++/kms++.h>
#include <kms++util/kms++util.h>
+#include "conv.h"
+
using namespace std;
namespace kms
{
-static RGB get_test_pattern_pixel(IFramebuffer& fb, unsigned x, unsigned y)
+static RGB16 get_test_pattern_pixel_16(size_t w, size_t h, size_t x, size_t y)
{
- const unsigned w = fb.width();
- const unsigned h = fb.height();
-
const unsigned mw = 20;
const unsigned xm1 = mw;
@@ -29,36 +31,36 @@ static RGB get_test_pattern_pixel(IFramebuffer& fb, unsigned x, unsigned y)
// white margin lines
if (x == xm1 || x == xm2 || y == ym1 || y == ym2)
- return RGB(255, 255, 255);
+ return RGB16::from_8(255, 255, 255);
// white box in top left corner
else if (x < xm1 && y < ym1)
- return RGB(255, 255, 255);
+ return RGB16::from_8(255, 255, 255);
// white box outlines to corners
else if ((x == 0 || x == w - 1) && (y < ym1 || y > ym2))
- return RGB(255, 255, 255);
+ return RGB16::from_8(255, 255, 255);
// white box outlines to corners
else if ((y == 0 || y == h - 1) && (x < xm1 || x > xm2))
- return RGB(255, 255, 255);
+ return RGB16::from_8(255, 255, 255);
// blue bar on the left
else if (x < xm1 && (y > ym1 && y < ym2))
- return RGB(0, 0, 255);
+ return RGB16::from_8(0, 0, 255);
// blue bar on the top
else if (y < ym1 && (x > xm1 && x < xm2))
- return RGB(0, 0, 255);
+ return RGB16::from_8(0, 0, 255);
// red bar on the right
else if (x > xm2 && (y > ym1 && y < ym2))
- return RGB(255, 0, 0);
+ return RGB16::from_8(255, 0, 0);
// red bar on the bottom
else if (y > ym2 && (x > xm1 && x < xm2))
- return RGB(255, 0, 0);
+ return RGB16::from_8(255, 0, 0);
// inside the margins
else if (x > xm1 && x < xm2 && y > ym1 && y < ym2) {
// diagonal line
if (x == y || w - x == h - y)
- return RGB(255, 255, 255);
+ return RGB16::from_8(255, 255, 255);
// diagonal line
else if (w - x - 1 == y || x == h - y - 1)
- return RGB(255, 255, 255);
+ return RGB16::from_8(255, 255, 255);
else {
int t = (x - xm1 - 1) * 8 / (xm2 - xm1 - 1);
unsigned r = 0, g = 0, b = 0;
@@ -91,108 +93,203 @@ static RGB get_test_pattern_pixel(IFramebuffer& fb, unsigned x, unsigned y)
break;
}
- return RGB(r, g, b);
+ return RGB16::from_8(r, g, b);
}
} else {
// black corners
- return RGB(0, 0, 0);
+ return RGB16::from_8(0, 0, 0);
}
}
-static void draw_test_pattern_part(IFramebuffer& fb, unsigned start_y, unsigned end_y, YUVType yuvt)
+static void get_test_pattern_line(size_t w, size_t h, size_t row, std::span<RGB16> buf)
{
- unsigned x, y;
- unsigned w = fb.width();
-
- const PixelFormatInfo& format_info = get_pixel_format_info(fb.format());
- const PixelFormatPlaneInfo& plane_info = format_info.planes[format_info.num_planes - 1];
-
- switch (format_info.type) {
- case PixelColorType::RGB:
- for (y = start_y; y < end_y; y++) {
- for (x = 0; x < w; x++) {
- RGB pixel = get_test_pattern_pixel(fb, x, y);
- draw_rgb_pixel(fb, x, y, pixel);
- }
- }
+ for (size_t x = 0; x < w; ++x)
+ buf[x] = get_test_pattern_pixel_16(w, h, x, row);
+}
+
+static void get_test_pattern_line_yuv(size_t w, size_t h, size_t row,
+ std::span<YUV16> buf,
+ const TestPatternOptions& options)
+{
+ for (size_t x = 0; x < w; ++x)
+ buf[x] = get_test_pattern_pixel_16(w, h, x, row)
+ .to_yuv(options.rec, options.range);
+}
+
+static void get_plain_line_rgb(size_t w, const RGB16& color, std::span<RGB16> buf)
+{
+ for (size_t x = 0; x < w; ++x)
+ buf[x] = color;
+}
+
+static void get_plain_line_yuv(size_t w, const YUV16& color, std::span<YUV16> buf)
+{
+ for (size_t x = 0; x < w; ++x)
+ buf[x] = color;
+}
+
+static void draw_test_pattern_part(IFramebuffer& fb, size_t start_y, size_t end_y,
+ const TestPatternOptions& options)
+{
+ std::optional<RGB16> solid;
+
+ if (options.pattern == "red")
+ solid = RGB16(0xffff, 0, 0);
+ else if (options.pattern == "green")
+ solid = RGB16(0, 0xffff, 0);
+ else if (options.pattern == "blue")
+ solid = RGB16(0, 0, 0xffff);
+ else if (options.pattern == "white")
+ solid = RGB16(0xffff, 0xffff, 0xffff);
+ else if (options.pattern == "black")
+ solid = RGB16(0, 0, 0);
+
+ std::function<void(size_t y, std::span<RGB16> span)> generate_line_rgb;
+ std::function<void(size_t y, std::span<YUV16> span)> generate_line_yuv;
+
+ if (solid.has_value()) {
+ generate_line_rgb = [&fb, rgb = solid.value()](size_t y,
+ std::span<RGB16> span) {
+ get_plain_line_rgb(fb.width(), rgb, span);
+ };
+
+ generate_line_yuv = [&fb, rgb = solid.value(),
+ &options](size_t y, std::span<YUV16> span) {
+ get_plain_line_yuv(fb.width(),
+ rgb.to_yuv(options.rec, options.range), span);
+ };
+ } else {
+ generate_line_rgb = [&fb](size_t y, std::span<RGB16> span) {
+ get_test_pattern_line(fb.width(), fb.height(), y, span);
+ };
+
+ generate_line_yuv = [&fb, &options](size_t y, std::span<YUV16> span) {
+ get_test_pattern_line_yuv(fb.width(), fb.height(), y, span,
+ options);
+ };
+ }
+
+#define CASE_ARGB(x) \
+ case PixelFormat::x: \
+ ARGB_Writer<x##_Layout>::write_pattern(fb, start_y, end_y, \
+ generate_line_rgb); \
break;
- case PixelColorType::YUV:
- switch (plane_info.hsub + plane_info.vsub) {
- case 2:
- for (y = start_y; y < end_y; y++) {
- for (x = 0; x < w; x++) {
- RGB pixel = get_test_pattern_pixel(fb, x, y);
- draw_yuv444_pixel(fb, x, y, pixel.yuv(yuvt));
- }
- }
- break;
-
- case 3:
- for (y = start_y; y < end_y; y++) {
- for (x = 0; x < w; x += 2) {
- RGB pixel1 = get_test_pattern_pixel(fb, x, y);
- RGB pixel2 = get_test_pattern_pixel(fb, x + 1, y);
- draw_yuv422_macropixel(fb, x, y, pixel1.yuv(yuvt), pixel2.yuv(yuvt));
- }
- }
- break;
-
- case 4:
- for (y = start_y; y < end_y; y += 2) {
- for (x = 0; x < w; x += 2) {
- RGB pixel00 = get_test_pattern_pixel(fb, x, y);
- RGB pixel10 = get_test_pattern_pixel(fb, x + 1, y);
- RGB pixel01 = get_test_pattern_pixel(fb, x, y + 1);
- RGB pixel11 = get_test_pattern_pixel(fb, x + 1, y + 1);
- draw_yuv420_macropixel(fb, x, y,
- pixel00.yuv(yuvt), pixel10.yuv(yuvt),
- pixel01.yuv(yuvt), pixel11.yuv(yuvt));
- }
- }
- break;
+#define CASE_YUV(x) \
+ case PixelFormat::x: \
+ YUV_Writer<x##_Layout>::write_pattern(fb, start_y, end_y, \
+ generate_line_yuv); \
+ break;
- default:
- throw invalid_argument("unsupported number of pixel format planes");
- }
+#define CASE_YUV_PACKED(x) \
+ case PixelFormat::x: \
+ YUVPackedWriter<x##_Layout>::write_pattern(fb, start_y, end_y, \
+ generate_line_yuv); \
+ break;
+#define CASE_YUV_SEMI(x) \
+ case PixelFormat::x: \
+ YUVSemiPlanarWriter<x##_Layout>::write_pattern(fb, start_y, end_y, \
+ generate_line_yuv); \
break;
+#define CASE_YUV_PLANAR(x) \
+ case PixelFormat::x: \
+ YUVPlanarWriter<x##_Layout>::write_pattern(fb, start_y, end_y, \
+ generate_line_yuv); \
+ break;
+
+ switch (fb.format()) {
+ CASE_YUV_SEMI(XV20);
+ CASE_YUV_SEMI(XV15);
+ CASE_YUV_SEMI(NV12);
+ CASE_YUV_SEMI(NV21);
+ CASE_YUV_SEMI(NV16);
+ CASE_YUV_SEMI(NV61);
+
+ CASE_ARGB(RGB565);
+ CASE_ARGB(BGR565);
+
+ CASE_ARGB(XRGB1555);
+ CASE_ARGB(ARGB1555);
+ CASE_ARGB(XRGB4444);
+ CASE_ARGB(ARGB4444);
+
+ CASE_ARGB(RGB888);
+ CASE_ARGB(BGR888);
+
+ CASE_ARGB(XRGB8888);
+ CASE_ARGB(ARGB8888);
+ CASE_ARGB(XBGR8888);
+ CASE_ARGB(ABGR8888);
+ CASE_ARGB(RGBX8888);
+ CASE_ARGB(RGBA8888);
+ CASE_ARGB(BGRX8888);
+ CASE_ARGB(BGRA8888);
+ CASE_ARGB(XRGB2101010);
+ CASE_ARGB(ARGB2101010);
+ CASE_ARGB(XBGR2101010);
+ CASE_ARGB(ABGR2101010);
+ CASE_ARGB(RGBX1010102);
+ CASE_ARGB(RGBA1010102);
+ CASE_ARGB(BGRX1010102);
+ CASE_ARGB(BGRA1010102);
+
+ CASE_YUV_PACKED(YUYV);
+ CASE_YUV_PACKED(YVYU);
+ CASE_YUV_PACKED(UYVY);
+ CASE_YUV_PACKED(VYUY);
+
+ CASE_YUV(XVUY2101010);
+
+ CASE_YUV_PLANAR(YUV444);
+ CASE_YUV_PLANAR(YVU444);
+ CASE_YUV_PLANAR(YUV422);
+ CASE_YUV_PLANAR(YVU422);
+ CASE_YUV_PLANAR(YUV420);
+ CASE_YUV_PLANAR(YVU420);
+
default:
- throw invalid_argument("unsupported pixel format");
+ break;
}
}
-static void draw_test_pattern_impl(IFramebuffer& fb, YUVType yuvt)
+void draw_test_pattern_multi(IFramebuffer& fb, const TestPatternOptions& options)
{
-#ifdef HAS_PTHREAD
- if (fb.height() < 20) {
- draw_test_pattern_part(fb, 0, fb.height(), yuvt);
- return;
- }
+ auto& info = get_pixel_format_info(fb.format());
+ uint8_t v_sub = 0;
+ for (size_t p = 0; p < info.num_planes; ++p)
+ v_sub = max(v_sub, info.planes[p].vsub);
+
+ if (fb.height() % v_sub)
+ throw invalid_argument("FB height must be divisible with vsub");
// Create the mmaps before starting the threads
- for (unsigned i = 0; i < fb.num_planes(); ++i)
+ for (size_t i = 0; i < fb.num_planes(); ++i)
fb.map(i);
- unsigned num_threads = thread::hardware_concurrency();
- vector<thread> workers;
+ size_t num_threads = thread::hardware_concurrency();
+
+ size_t part_height = fb.height() / num_threads;
- unsigned part = (fb.height() / num_threads) & ~1;
+ // round up to v_sub
+ part_height = (part_height + v_sub - 1) / v_sub * v_sub;
+
+ vector<thread> workers;
std::vector<std::exception_ptr> errors(num_threads);
- for (unsigned n = 0; n < num_threads; ++n) {
- unsigned start = n * part;
- unsigned end = start + part;
+ for (size_t n = 0; n < num_threads; ++n) {
+ size_t start = n * part_height;
+ size_t end = start + part_height - 1;
if (n == num_threads - 1)
- end = fb.height();
+ end = fb.height() - 1;
- workers.push_back(thread([&fb, start, end, yuvt, &error = errors[n]]() {
+ workers.push_back(thread([&fb, start, end, &options, &error = errors[n]]() {
try {
- draw_test_pattern_part(fb, start, end, yuvt);
- } catch(...) {
+ draw_test_pattern_part(fb, start, end, options);
+ } catch (...) {
error = std::current_exception();
}
}));
@@ -201,27 +298,23 @@ static void draw_test_pattern_impl(IFramebuffer& fb, YUVType yuvt)
for (thread& t : workers)
t.join();
- auto i = std::find_if(errors.begin(), errors.end(), [](auto& e) { return e != nullptr; });
+ auto i = std::find_if(errors.begin(), errors.end(),
+ [](auto& e) { return e != nullptr; });
if (i != errors.end())
std::rethrow_exception(*i);
-
-#else
- draw_test_pattern_part(fb, 0, fb.height(), yuvt);
-#endif
}
-void draw_test_pattern(IFramebuffer& fb, YUVType yuvt)
+void draw_test_pattern_single(IFramebuffer& fb, const TestPatternOptions& options)
{
-#ifdef DRAW_PERF_PRINT
- Stopwatch sw;
- sw.start();
-#endif
-
- draw_test_pattern_impl(fb, yuvt);
+ draw_test_pattern_part(fb, 0, fb.height() - 1, options);
+}
-#ifdef DRAW_PERF_PRINT
- double us = sw.elapsed_us();
- printf("draw took %u us\n", (unsigned)us);
+void draw_test_pattern(IFramebuffer& fb, const TestPatternOptions& options)
+{
+#ifdef HAS_PTHREAD
+ draw_test_pattern_multi(fb, options);
+#else
+ draw_test_pattern_single(fb, options);
#endif
}