/src/libjxl/lib/jxl/dec_group_border.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) the JPEG XL Project Authors. All rights reserved. |
2 | | // |
3 | | // Use of this source code is governed by a BSD-style |
4 | | // license that can be found in the LICENSE file. |
5 | | |
6 | | #include "lib/jxl/dec_group_border.h" |
7 | | |
8 | | #include <algorithm> |
9 | | #include <atomic> |
10 | | #include <cstddef> |
11 | | #include <cstdint> |
12 | | #include <utility> |
13 | | #include <vector> |
14 | | |
15 | | #include "lib/jxl/base/rect.h" |
16 | | #include "lib/jxl/base/status.h" |
17 | | #include "lib/jxl/frame_dimensions.h" |
18 | | |
19 | | namespace jxl { |
20 | | |
21 | 22.3k | void GroupBorderAssigner::Init(const FrameDimensions& frame_dim) { |
22 | 22.3k | frame_dim_ = frame_dim; |
23 | 22.3k | size_t x_size = frame_dim_.xsize_groups; |
24 | 22.3k | size_t y_size = frame_dim_.ysize_groups; |
25 | 22.3k | counters_.resize(y_size + 1); |
26 | | // Initialize counters. |
27 | 45.8k | for (auto& current_row : counters_) { |
28 | 45.8k | std::vector<std::atomic<uint32_t>> row((x_size + 1 + 7) / 8); |
29 | 46.4k | for (auto& v : row) { |
30 | 46.4k | v = 0; |
31 | 46.4k | } |
32 | 45.8k | row.swap(current_row); |
33 | 45.8k | } |
34 | | // Counters at image borders don't have anything on the other side, we |
35 | | // pre-fill their value to have more uniform handling afterwards. |
36 | 187k | auto set = [this](size_t x, size_t y, uint32_t corners) { |
37 | 187k | counters_[y][x / 8] |= corners << (4 * (x & 7u)); |
38 | 187k | }; |
39 | 70.1k | for (size_t x = 0; x < x_size + 1; x++) { |
40 | 47.8k | set(x, 0, kTopLeft | kTopRight); |
41 | 47.8k | set(x, y_size, kBottomLeft | kBottomRight); |
42 | 47.8k | } |
43 | 68.1k | for (size_t y = 0; y < y_size + 1; y++) { |
44 | 45.8k | set(0, y, kTopLeft | kBottomLeft); |
45 | 45.8k | set(x_size, y, kTopRight | kBottomRight); |
46 | 45.8k | } |
47 | 22.3k | } |
48 | | |
49 | 43.7k | void GroupBorderAssigner::ClearDone(size_t group_id) { |
50 | 174k | auto clear = [this](size_t x, size_t y, uint32_t corners) { |
51 | 174k | counters_[y][x / 8].fetch_and(~(corners << (4 * (x & 7u)))); |
52 | 174k | }; |
53 | 43.7k | size_t x = group_id % frame_dim_.xsize_groups; |
54 | 43.7k | size_t y = group_id / frame_dim_.xsize_groups; |
55 | 43.7k | clear(x, y, kBottomRight); |
56 | 43.7k | clear(x + 1, y, kBottomLeft); |
57 | 43.7k | clear(x, y + 1, kTopRight); |
58 | 43.7k | clear(x + 1, y + 1, kTopLeft); |
59 | 43.7k | } |
60 | | |
61 | | // Looking at each corner between groups, we can guarantee that the four |
62 | | // involved groups will agree between each other regarding the order in which |
63 | | // each of the four groups terminated. Thus, the last of the four groups |
64 | | // gets the responsibility of handling the corner. For borders, every border |
65 | | // is assigned to its top corner (for vertical borders) or to its left corner |
66 | | // (for horizontal borders): the order as seen on those corners will decide who |
67 | | // handles that border. |
68 | | |
69 | | void GroupBorderAssigner::GroupDone(size_t group_id, size_t padx, size_t pady, |
70 | | Rect* rects_to_finalize, |
71 | 23.7k | size_t* num_to_finalize) { |
72 | 23.7k | size_t x = group_id % frame_dim_.xsize_groups; |
73 | 23.7k | size_t y = group_id / frame_dim_.xsize_groups; |
74 | 23.7k | Rect block_rect(x * frame_dim_.group_dim / kBlockDim, |
75 | 23.7k | y * frame_dim_.group_dim / kBlockDim, |
76 | 23.7k | frame_dim_.group_dim / kBlockDim, |
77 | 23.7k | frame_dim_.group_dim / kBlockDim, frame_dim_.xsize_blocks, |
78 | 23.7k | frame_dim_.ysize_blocks); |
79 | | |
80 | 95.0k | auto fetch_status = [this](size_t x, size_t y, uint32_t bit) { |
81 | | // Note that the acq-rel semantics of this fetch are actually needed to |
82 | | // ensure that the pixel data of the group is already written to memory. |
83 | 95.0k | size_t shift = 4 * (x & 7u); |
84 | 95.0k | size_t status = counters_[y][x / 8].fetch_or(bit << shift); |
85 | 95.0k | status >>= shift; |
86 | 95.0k | JXL_DASSERT((bit & status) == 0); |
87 | 95.0k | return (bit | status) & 0xF; |
88 | 95.0k | }; |
89 | | |
90 | 23.7k | size_t top_left_status = fetch_status(x, y, kBottomRight); |
91 | 23.7k | size_t top_right_status = fetch_status(x + 1, y, kBottomLeft); |
92 | 23.7k | size_t bottom_right_status = fetch_status(x + 1, y + 1, kTopLeft); |
93 | 23.7k | size_t bottom_left_status = fetch_status(x, y + 1, kTopRight); |
94 | | |
95 | 23.7k | size_t x1 = block_rect.x0() + block_rect.xsize(); |
96 | 23.7k | size_t y1 = block_rect.y0() + block_rect.ysize(); |
97 | | |
98 | 23.7k | bool is_last_group_x = frame_dim_.xsize_groups == x + 1; |
99 | 23.7k | bool is_last_group_y = frame_dim_.ysize_groups == y + 1; |
100 | | |
101 | | // Start of border of neighbouring group, end of border of this group, start |
102 | | // of border of this group (on the other side), end of border of next group. |
103 | 23.7k | size_t xpos[4] = { |
104 | 23.7k | block_rect.x0() == 0 ? 0 : block_rect.x0() * kBlockDim - padx, |
105 | 23.7k | block_rect.x0() == 0 |
106 | 23.7k | ? 0 |
107 | 23.7k | : std::min(frame_dim_.xsize, block_rect.x0() * kBlockDim + padx), |
108 | 23.7k | is_last_group_x ? frame_dim_.xsize : x1 * kBlockDim - padx, |
109 | 23.7k | std::min(frame_dim_.xsize, x1 * kBlockDim + padx)}; |
110 | 23.7k | size_t ypos[4] = { |
111 | 23.7k | block_rect.y0() == 0 ? 0 : block_rect.y0() * kBlockDim - pady, |
112 | 23.7k | block_rect.y0() == 0 |
113 | 23.7k | ? 0 |
114 | 23.7k | : std::min(frame_dim_.ysize, block_rect.y0() * kBlockDim + pady), |
115 | 23.7k | is_last_group_y ? frame_dim_.ysize : y1 * kBlockDim - pady, |
116 | 23.7k | std::min(frame_dim_.ysize, y1 * kBlockDim + pady)}; |
117 | | |
118 | 23.7k | *num_to_finalize = 0; |
119 | 24.7k | auto append_rect = [&](size_t x0, size_t x1, size_t y0, size_t y1) { |
120 | 24.7k | Rect rect(xpos[x0], ypos[y0], xpos[x1] - xpos[x0], ypos[y1] - ypos[y0]); |
121 | 24.7k | if (rect.xsize() == 0 || rect.ysize() == 0) return; |
122 | 23.7k | JXL_DASSERT(*num_to_finalize < kMaxToFinalize); |
123 | 23.7k | rects_to_finalize[(*num_to_finalize)++] = rect; |
124 | 23.7k | }; |
125 | | |
126 | | // Because of how group borders are assigned, it is impossible that we need to |
127 | | // process the left and right side of some area but not the center area. Thus, |
128 | | // we compute the first/last part to process in every horizontal strip and |
129 | | // merge them together. We first collect a mask of what parts should be |
130 | | // processed. |
131 | | // We do this horizontally rather than vertically because horizontal borders |
132 | | // are larger. |
133 | 23.7k | bool available_parts_mask[3][3] = {}; // [x][y] |
134 | | // Center |
135 | 23.7k | available_parts_mask[1][1] = true; |
136 | | // Corners |
137 | 23.7k | if (top_left_status == 0xF) available_parts_mask[0][0] = true; |
138 | 23.7k | if (top_right_status == 0xF) available_parts_mask[2][0] = true; |
139 | 23.7k | if (bottom_right_status == 0xF) available_parts_mask[2][2] = true; |
140 | 23.7k | if (bottom_left_status == 0xF) available_parts_mask[0][2] = true; |
141 | | // Other borders |
142 | 23.7k | if (top_left_status & kTopRight) available_parts_mask[1][0] = true; |
143 | 23.7k | if (top_left_status & kBottomLeft) available_parts_mask[0][1] = true; |
144 | 23.7k | if (top_right_status & kBottomRight) available_parts_mask[2][1] = true; |
145 | 23.7k | if (bottom_left_status & kBottomRight) available_parts_mask[1][2] = true; |
146 | | |
147 | | // Collect horizontal ranges. |
148 | 23.7k | constexpr size_t kNoSegment = 3; |
149 | 23.7k | std::pair<size_t, size_t> horizontal_segments[3] = {{kNoSegment, kNoSegment}, |
150 | 23.7k | {kNoSegment, kNoSegment}, |
151 | 23.7k | {kNoSegment, kNoSegment}}; |
152 | 95.0k | for (size_t py = 0; py < 3; py++) { |
153 | 285k | for (size_t px = 0; px < 3; px++) { |
154 | 213k | if (!available_parts_mask[px][py]) continue; |
155 | 205k | JXL_DASSERT(horizontal_segments[py].second == kNoSegment || |
156 | 205k | horizontal_segments[py].second == px); |
157 | 205k | JXL_DASSERT((horizontal_segments[py].first == kNoSegment) == |
158 | 205k | (horizontal_segments[py].second == kNoSegment)); |
159 | 205k | if (horizontal_segments[py].first == kNoSegment) { |
160 | 70.2k | horizontal_segments[py].first = px; |
161 | 70.2k | } |
162 | 205k | horizontal_segments[py].second = px + 1; |
163 | 205k | } |
164 | 71.2k | } |
165 | 23.7k | if (horizontal_segments[0] == horizontal_segments[1] && |
166 | 23.7k | horizontal_segments[0] == horizontal_segments[2]) { |
167 | 22.7k | append_rect(horizontal_segments[0].first, horizontal_segments[0].second, 0, |
168 | 22.7k | 3); |
169 | 22.7k | } else if (horizontal_segments[0] == horizontal_segments[1]) { |
170 | 997 | append_rect(horizontal_segments[0].first, horizontal_segments[0].second, 0, |
171 | 997 | 2); |
172 | 997 | append_rect(horizontal_segments[2].first, horizontal_segments[2].second, 2, |
173 | 997 | 3); |
174 | 997 | } else if (horizontal_segments[1] == horizontal_segments[2]) { |
175 | 0 | append_rect(horizontal_segments[0].first, horizontal_segments[0].second, 0, |
176 | 0 | 1); |
177 | 0 | append_rect(horizontal_segments[1].first, horizontal_segments[1].second, 1, |
178 | 0 | 3); |
179 | 0 | } else { |
180 | 0 | append_rect(horizontal_segments[0].first, horizontal_segments[0].second, 0, |
181 | 0 | 1); |
182 | 0 | append_rect(horizontal_segments[1].first, horizontal_segments[1].second, 1, |
183 | 0 | 2); |
184 | 0 | append_rect(horizontal_segments[2].first, horizontal_segments[2].second, 2, |
185 | 0 | 3); |
186 | 0 | } |
187 | 23.7k | } |
188 | | |
189 | | } // namespace jxl |