1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file.
6 #include "lib/jxl/render_pipeline/simple_render_pipeline.h"
10 #include "lib/jxl/image_ops.h"
11 #include "lib/jxl/render_pipeline/render_pipeline_stage.h"
12 #include "lib/jxl/sanitizers.h"
16 void SimpleRenderPipeline::PrepareForThreadsInternal(size_t num,
18 if (!channel_data_.empty()) {
21 auto ch_size = [](size_t frame_size, size_t shift) {
22 return DivCeil(frame_size, 1 << shift) + kRenderPipelineXOffset * 2;
24 for (size_t c = 0; c < channel_shifts_[0].size(); c++) {
25 channel_data_.push_back(ImageF(
26 ch_size(frame_dimensions_.xsize_upsampled, channel_shifts_[0][c].first),
27 ch_size(frame_dimensions_.ysize_upsampled,
28 channel_shifts_[0][c].second)));
29 msan::PoisonImage(channel_data_.back());
33 Rect SimpleRenderPipeline::MakeChannelRect(size_t group_id, size_t channel) {
34 size_t base_color_shift =
35 CeilLog2Nonzero(frame_dimensions_.xsize_upsampled_padded /
36 frame_dimensions_.xsize_padded);
38 const size_t gx = group_id % frame_dimensions_.xsize_groups;
39 const size_t gy = group_id / frame_dimensions_.xsize_groups;
40 size_t xgroupdim = (frame_dimensions_.group_dim << base_color_shift) >>
41 channel_shifts_[0][channel].first;
42 size_t ygroupdim = (frame_dimensions_.group_dim << base_color_shift) >>
43 channel_shifts_[0][channel].second;
45 kRenderPipelineXOffset + gx * xgroupdim,
46 kRenderPipelineXOffset + gy * ygroupdim, xgroupdim, ygroupdim,
47 kRenderPipelineXOffset + DivCeil(frame_dimensions_.xsize_upsampled,
48 1 << channel_shifts_[0][channel].first),
49 kRenderPipelineXOffset +
50 DivCeil(frame_dimensions_.ysize_upsampled,
51 1 << channel_shifts_[0][channel].second));
54 std::vector<std::pair<ImageF*, Rect>> SimpleRenderPipeline::PrepareBuffers(
55 size_t group_id, size_t thread_id) {
56 std::vector<std::pair<ImageF*, Rect>> ret;
57 for (size_t c = 0; c < channel_data_.size(); c++) {
58 ret.emplace_back(&channel_data_[c], MakeChannelRect(group_id, c));
63 void SimpleRenderPipeline::ProcessBuffers(size_t group_id, size_t thread_id) {
64 for (size_t c = 0; c < channel_data_.size(); c++) {
65 Rect r = MakeChannelRect(group_id, c);
67 JXL_CHECK_PLANE_INITIALIZED(channel_data_[c], r, c);
70 if (PassesWithAllInput() <= processed_passes_) return;
73 for (size_t stage_id = 0; stage_id < stages_.size(); stage_id++) {
74 const auto& stage = stages_[stage_id];
75 // Prepare buffers for kInOut channels.
76 std::vector<ImageF> new_channels(channel_data_.size());
77 std::vector<ImageF*> output_channels(channel_data_.size());
79 std::vector<std::pair<size_t, size_t>> input_sizes(channel_data_.size());
80 for (size_t c = 0; c < channel_data_.size(); c++) {
82 std::make_pair(channel_data_[c].xsize() - kRenderPipelineXOffset * 2,
83 channel_data_[c].ysize() - kRenderPipelineXOffset * 2);
86 for (size_t c = 0; c < channel_data_.size(); c++) {
87 if (stage->GetChannelMode(c) != RenderPipelineChannelMode::kInOut) {
90 // Ensure that the newly allocated channels are large enough to avoid
91 // problems with padding.
93 ImageF(frame_dimensions_.xsize_upsampled_padded +
94 kRenderPipelineXOffset * 2 + hwy::kMaxVectorSize * 8,
95 frame_dimensions_.ysize_upsampled_padded +
96 kRenderPipelineXOffset * 2);
97 new_channels[c].ShrinkTo(
98 (input_sizes[c].first << stage->settings_.shift_x) +
99 kRenderPipelineXOffset * 2,
100 (input_sizes[c].second << stage->settings_.shift_y) +
101 kRenderPipelineXOffset * 2);
102 output_channels[c] = &new_channels[c];
105 auto get_row = [&](size_t c, int64_t y) {
106 return channel_data_[c].Row(kRenderPipelineXOffset + y) +
107 kRenderPipelineXOffset;
110 // Add mirrored pixes to all kInOut channels.
111 for (size_t c = 0; c < channel_data_.size(); c++) {
112 if (stage->GetChannelMode(c) != RenderPipelineChannelMode::kInOut) {
115 // Horizontal mirroring.
116 for (size_t y = 0; y < input_sizes[c].second; y++) {
117 float* row = get_row(c, y);
118 for (size_t ix = 0; ix < stage->settings_.border_x; ix++) {
119 *(row - ix - 1) = row[Mirror(-ssize_t(ix) - 1, input_sizes[c].first)];
121 for (size_t ix = 0; ix < stage->settings_.border_x; ix++) {
122 *(row + ix + input_sizes[c].first) =
123 row[Mirror(ix + input_sizes[c].first, input_sizes[c].first)];
126 // Vertical mirroring.
127 for (int y = 0; y < static_cast<int>(stage->settings_.border_y); y++) {
128 memcpy(get_row(c, -y - 1) - stage->settings_.border_x,
129 get_row(c, Mirror(-ssize_t(y) - 1, input_sizes[c].second)) -
130 stage->settings_.border_x,
132 (input_sizes[c].first + 2 * stage->settings_.border_x));
134 for (int y = 0; y < static_cast<int>(stage->settings_.border_y); y++) {
136 get_row(c, input_sizes[c].second + y) - stage->settings_.border_x,
138 Mirror(input_sizes[c].second + y, input_sizes[c].second)) -
139 stage->settings_.border_x,
141 (input_sizes[c].first + 2 * stage->settings_.border_x));
147 for (size_t c = 0; c < channel_data_.size(); c++) {
148 if (stage->GetChannelMode(c) == RenderPipelineChannelMode::kIgnored) {
151 ysize = std::max(input_sizes[c].second, ysize);
152 xsize = std::max(input_sizes[c].first, xsize);
155 JXL_ASSERT(ysize != 0);
156 JXL_ASSERT(xsize != 0);
158 RenderPipelineStage::RowInfo input_rows(channel_data_.size());
159 RenderPipelineStage::RowInfo output_rows(channel_data_.size());
163 stage->SetInputSizes(input_sizes);
164 int border_y = stage->settings_.border_y;
165 for (size_t y = 0; y < ysize; y++) {
166 // Prepare input rows.
167 for (size_t c = 0; c < channel_data_.size(); c++) {
168 if (stage->GetChannelMode(c) == RenderPipelineChannelMode::kIgnored) {
171 input_rows[c].resize(2 * border_y + 1);
172 for (int iy = -border_y; iy <= border_y; iy++) {
173 input_rows[c][iy + border_y] =
174 channel_data_[c].Row(y + kRenderPipelineXOffset + iy);
177 // Prepare output rows.
178 for (size_t c = 0; c < channel_data_.size(); c++) {
179 if (!output_channels[c]) continue;
180 output_rows[c].resize(1 << stage->settings_.shift_y);
181 for (size_t iy = 0; iy < output_rows[c].size(); iy++) {
182 output_rows[c][iy] = output_channels[c]->Row(
183 (y << stage->settings_.shift_y) + iy + kRenderPipelineXOffset);
186 stage->ProcessRow(input_rows, output_rows, /*xextra=*/0, xsize,
187 /*xpos=*/0, y, thread_id);
191 // Move new channels to current channels.
192 for (size_t c = 0; c < channel_data_.size(); c++) {
193 if (stage->GetChannelMode(c) != RenderPipelineChannelMode::kInOut) {
196 channel_data_[c] = std::move(new_channels[c]);
198 for (size_t c = 0; c < channel_data_.size(); c++) {
199 size_t next_stage = std::min(stage_id + 1, channel_shifts_.size() - 1);
200 size_t xsize = DivCeil(frame_dimensions_.xsize_upsampled,
201 1 << channel_shifts_[next_stage][c].first);
202 size_t ysize = DivCeil(frame_dimensions_.ysize_upsampled,
203 1 << channel_shifts_[next_stage][c].second);
204 channel_data_[c].ShrinkTo(xsize + 2 * kRenderPipelineXOffset,
205 ysize + 2 * kRenderPipelineXOffset);
206 JXL_CHECK_PLANE_INITIALIZED(
208 Rect(kRenderPipelineXOffset, kRenderPipelineXOffset, xsize, ysize),
212 if (stage->SwitchToImageDimensions()) {
213 size_t image_xsize, image_ysize;
214 FrameOrigin frame_origin;
215 stage->GetImageDimensions(&image_xsize, &image_ysize, &frame_origin);
216 frame_dimensions_.Set(image_xsize, image_ysize, 0, 0, 0, false, 1);
217 std::vector<ImageF> old_channels = std::move(channel_data_);
218 channel_data_.clear();
219 channel_data_.reserve(old_channels.size());
220 for (size_t c = 0; c < old_channels.size(); c++) {
221 channel_data_.emplace_back(2 * kRenderPipelineXOffset + image_xsize,
222 2 * kRenderPipelineXOffset + image_ysize);
224 for (size_t y = 0; y < image_ysize; ++y) {
225 for (size_t c = 0; c < channel_data_.size(); c++) {
226 output_rows[c].resize(1);
227 output_rows[c][0] = channel_data_[c].Row(kRenderPipelineXOffset + y);
229 // TODO(sboukortt): consider doing this only on the parts of the
230 // background that won't be occluded.
231 stage->ProcessPaddingRow(output_rows, image_xsize, 0, y);
233 ssize_t x0 = frame_origin.x0;
234 ssize_t y0 = frame_origin.y0;
242 if (x0 + xsize > image_xsize) {
243 xsize = image_xsize - x0;
250 if (y0 + ysize > image_ysize) {
251 ysize = image_ysize - y0;
253 const Rect rect_fg_relative_to_image =
254 Rect(x0, y0, xsize, ysize)
255 .Translate(kRenderPipelineXOffset, kRenderPipelineXOffset);
257 Rect(x0_fg, y0_fg, xsize, ysize)
258 .Translate(kRenderPipelineXOffset, kRenderPipelineXOffset);
259 for (size_t c = 0; c < channel_data_.size(); c++) {
260 CopyImageTo(rect_fg, old_channels[c], rect_fg_relative_to_image,