summaryrefslogtreecommitdiffstats
path: root/third_party/jpeg-xl/lib/jxl/dec_group_border.cc
blob: 4bee3ae6ef5263da4ef9dbd88815a63084a45c99 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include "lib/jxl/dec_group_border.h"

#include <atomic>

namespace jxl {

void GroupBorderAssigner::Init(const FrameDimensions& frame_dim) {
  frame_dim_ = frame_dim;
  size_t num_corners =
      (frame_dim_.xsize_groups + 1) * (frame_dim_.ysize_groups + 1);
  counters_.reset(new std::atomic<uint8_t>[num_corners]);
  // Initialize counters.
  for (size_t y = 0; y < frame_dim_.ysize_groups + 1; y++) {
    for (size_t x = 0; x < frame_dim_.xsize_groups + 1; x++) {
      // Counters at image borders don't have anything on the other side, we
      // pre-fill their value to have more uniform handling afterwards.
      uint8_t init_value = 0;
      if (x == 0) {
        init_value |= kTopLeft | kBottomLeft;
      }
      if (x == frame_dim_.xsize_groups) {
        init_value |= kTopRight | kBottomRight;
      }
      if (y == 0) {
        init_value |= kTopLeft | kTopRight;
      }
      if (y == frame_dim_.ysize_groups) {
        init_value |= kBottomLeft | kBottomRight;
      }
      counters_[y * (frame_dim_.xsize_groups + 1) + x] = init_value;
    }
  }
}

void GroupBorderAssigner::ClearDone(size_t group_id) {
  size_t x = group_id % frame_dim_.xsize_groups;
  size_t y = group_id / frame_dim_.xsize_groups;
  size_t top_left_idx = y * (frame_dim_.xsize_groups + 1) + x;
  size_t top_right_idx = y * (frame_dim_.xsize_groups + 1) + x + 1;
  size_t bottom_right_idx = (y + 1) * (frame_dim_.xsize_groups + 1) + x + 1;
  size_t bottom_left_idx = (y + 1) * (frame_dim_.xsize_groups + 1) + x;
  counters_[top_left_idx].fetch_and(~kBottomRight);
  counters_[top_right_idx].fetch_and(~kBottomLeft);
  counters_[bottom_left_idx].fetch_and(~kTopRight);
  counters_[bottom_right_idx].fetch_and(~kTopLeft);
}

// Looking at each corner between groups, we can guarantee that the four
// involved groups will agree between each other regarding the order in which
// each of the four groups terminated. Thus, the last of the four groups
// gets the responsibility of handling the corner. For borders, every border
// is assigned to its top corner (for vertical borders) or to its left corner
// (for horizontal borders): the order as seen on those corners will decide who
// handles that border.

void GroupBorderAssigner::GroupDone(size_t group_id, size_t padx, size_t pady,
                                    Rect* rects_to_finalize,
                                    size_t* num_to_finalize) {
  size_t x = group_id % frame_dim_.xsize_groups;
  size_t y = group_id / frame_dim_.xsize_groups;
  Rect block_rect(x * frame_dim_.group_dim / kBlockDim,
                  y * frame_dim_.group_dim / kBlockDim,
                  frame_dim_.group_dim / kBlockDim,
                  frame_dim_.group_dim / kBlockDim, frame_dim_.xsize_blocks,
                  frame_dim_.ysize_blocks);

  size_t top_left_idx = y * (frame_dim_.xsize_groups + 1) + x;
  size_t top_right_idx = y * (frame_dim_.xsize_groups + 1) + x + 1;
  size_t bottom_right_idx = (y + 1) * (frame_dim_.xsize_groups + 1) + x + 1;
  size_t bottom_left_idx = (y + 1) * (frame_dim_.xsize_groups + 1) + x;

  auto fetch_status = [this](size_t idx, uint8_t bit) {
    // Note that the acq-rel semantics of this fetch are actually needed to
    // ensure that the pixel data of the group is already written to memory.
    size_t status = counters_[idx].fetch_or(bit);
    JXL_DASSERT((bit & status) == 0);
    return bit | status;
  };

  size_t top_left_status = fetch_status(top_left_idx, kBottomRight);
  size_t top_right_status = fetch_status(top_right_idx, kBottomLeft);
  size_t bottom_right_status = fetch_status(bottom_right_idx, kTopLeft);
  size_t bottom_left_status = fetch_status(bottom_left_idx, kTopRight);

  size_t x1 = block_rect.x0() + block_rect.xsize();
  size_t y1 = block_rect.y0() + block_rect.ysize();

  bool is_last_group_x = frame_dim_.xsize_groups == x + 1;
  bool is_last_group_y = frame_dim_.ysize_groups == y + 1;

  // Start of border of neighbouring group, end of border of this group, start
  // of border of this group (on the other side), end of border of next group.
  size_t xpos[4] = {
      block_rect.x0() == 0 ? 0 : block_rect.x0() * kBlockDim - padx,
      block_rect.x0() == 0
          ? 0
          : std::min(frame_dim_.xsize, block_rect.x0() * kBlockDim + padx),
      is_last_group_x ? frame_dim_.xsize : x1 * kBlockDim - padx,
      std::min(frame_dim_.xsize, x1 * kBlockDim + padx)};
  size_t ypos[4] = {
      block_rect.y0() == 0 ? 0 : block_rect.y0() * kBlockDim - pady,
      block_rect.y0() == 0
          ? 0
          : std::min(frame_dim_.ysize, block_rect.y0() * kBlockDim + pady),
      is_last_group_y ? frame_dim_.ysize : y1 * kBlockDim - pady,
      std::min(frame_dim_.ysize, y1 * kBlockDim + pady)};

  *num_to_finalize = 0;
  auto append_rect = [&](size_t x0, size_t x1, size_t y0, size_t y1) {
    Rect rect(xpos[x0], ypos[y0], xpos[x1] - xpos[x0], ypos[y1] - ypos[y0]);
    if (rect.xsize() == 0 || rect.ysize() == 0) return;
    JXL_DASSERT(*num_to_finalize < kMaxToFinalize);
    rects_to_finalize[(*num_to_finalize)++] = rect;
  };

  // Because of how group borders are assigned, it is impossible that we need to
  // process the left and right side of some area but not the center area. Thus,
  // we compute the first/last part to process in every horizontal strip and
  // merge them together. We first collect a mask of what parts should be
  // processed.
  // We do this horizontally rather than vertically because horizontal borders
  // are larger.
  bool available_parts_mask[3][3] = {};  // [x][y]
  // Center
  available_parts_mask[1][1] = true;
  // Corners
  if (top_left_status == 0xF) available_parts_mask[0][0] = true;
  if (top_right_status == 0xF) available_parts_mask[2][0] = true;
  if (bottom_right_status == 0xF) available_parts_mask[2][2] = true;
  if (bottom_left_status == 0xF) available_parts_mask[0][2] = true;
  // Other borders
  if (top_left_status & kTopRight) available_parts_mask[1][0] = true;
  if (top_left_status & kBottomLeft) available_parts_mask[0][1] = true;
  if (top_right_status & kBottomRight) available_parts_mask[2][1] = true;
  if (bottom_left_status & kBottomRight) available_parts_mask[1][2] = true;

  // Collect horizontal ranges.
  constexpr size_t kNoSegment = 3;
  std::pair<size_t, size_t> horizontal_segments[3] = {{kNoSegment, kNoSegment},
                                                      {kNoSegment, kNoSegment},
                                                      {kNoSegment, kNoSegment}};
  for (size_t y = 0; y < 3; y++) {
    for (size_t x = 0; x < 3; x++) {
      if (!available_parts_mask[x][y]) continue;
      JXL_DASSERT(horizontal_segments[y].second == kNoSegment ||
                  horizontal_segments[y].second == x);
      JXL_DASSERT((horizontal_segments[y].first == kNoSegment) ==
                  (horizontal_segments[y].second == kNoSegment));
      if (horizontal_segments[y].first == kNoSegment) {
        horizontal_segments[y].first = x;
      }
      horizontal_segments[y].second = x + 1;
    }
  }
  if (horizontal_segments[0] == horizontal_segments[1] &&
      horizontal_segments[0] == horizontal_segments[2]) {
    append_rect(horizontal_segments[0].first, horizontal_segments[0].second, 0,
                3);
  } else if (horizontal_segments[0] == horizontal_segments[1]) {
    append_rect(horizontal_segments[0].first, horizontal_segments[0].second, 0,
                2);
    append_rect(horizontal_segments[2].first, horizontal_segments[2].second, 2,
                3);
  } else if (horizontal_segments[1] == horizontal_segments[2]) {
    append_rect(horizontal_segments[0].first, horizontal_segments[0].second, 0,
                1);
    append_rect(horizontal_segments[1].first, horizontal_segments[1].second, 1,
                3);
  } else {
    append_rect(horizontal_segments[0].first, horizontal_segments[0].second, 0,
                1);
    append_rect(horizontal_segments[1].first, horizontal_segments[1].second, 1,
                2);
    append_rect(horizontal_segments[2].first, horizontal_segments[2].second, 2,
                3);
  }
}

}  // namespace jxl