#pragma once #include #include #include #include "conv-common.h" namespace kms { /* YUV Planar */ template class YUV_Planar_Layout : public FormatLayout< PlaneLayout>, PlaneLayout>, PlaneLayout> > { public: static constexpr std::array uv_order = { P1, P2 }; static constexpr size_t h_sub = HSubUV; static constexpr size_t v_sub = VSubUV; template 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(); static constexpr size_t cr_plane = find_plane(); }; using YUV444_Layout = YUV_Planar_Layout; using YVU444_Layout = YUV_Planar_Layout; using YUV422_Layout = YUV_Planar_Layout; using YVU422_Layout = YUV_Planar_Layout; using YUV420_Layout = YUV_Planar_Layout; using YVU420_Layout = YUV_Planar_Layout; template class YUVPlanarWriter { using YLayout = typename Format::template plane; using CbLayout = typename Format::template plane; using CrLayout = typename Format::template 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 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(fb.map(Format::y_plane), fb.height(), fb.width(), fb.stride(Format::y_plane)); auto cb_buf = make_strided_fb_view(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(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 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 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(fb.map(Format::y_plane), fb.height(), fb.width(), fb.stride(Format::y_plane)); auto cb_buf = make_strided_fb_view(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(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 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 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 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(fb.map(Format::y_plane), fb.height(), fb.width(), fb.stride(Format::y_plane)); auto cb_buf = make_strided_fb_view(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(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(y_val << 8), static_cast(u_val << 8), static_cast(v_val << 8) }; } } } }; } // namespace kms