1 // Copyright 2020 The libgav1 Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <algorithm>
16 #include <atomic>
17
18 #include "src/post_filter.h"
19
20 namespace libgav1 {
21 namespace {
22
HevThresh(int level)23 constexpr uint8_t HevThresh(int level) { return DivideBy16(level); }
24
25 // GetLoopFilterSize* functions depend on this exact ordering of the
26 // LoopFilterSize enums.
27 static_assert(dsp::kLoopFilterSize4 == 0, "");
28 static_assert(dsp::kLoopFilterSize6 == 1, "");
29 static_assert(dsp::kLoopFilterSize8 == 2, "");
30 static_assert(dsp::kLoopFilterSize14 == 3, "");
31
GetLoopFilterSizeY(int filter_length)32 dsp::LoopFilterSize GetLoopFilterSizeY(int filter_length) {
33 // |filter_length| must be a power of 2.
34 assert((filter_length & (filter_length - 1)) == 0);
35 // This code is the branch free equivalent of:
36 // if (filter_length == 4) return kLoopFilterSize4;
37 // if (filter_length == 8) return kLoopFilterSize8;
38 // return kLoopFilterSize14;
39 return static_cast<dsp::LoopFilterSize>(
40 MultiplyBy2(static_cast<int>(filter_length > 4)) +
41 static_cast<int>(filter_length > 8));
42 }
43
GetLoopFilterSizeUV(int filter_length)44 constexpr dsp::LoopFilterSize GetLoopFilterSizeUV(int filter_length) {
45 // For U & V planes, size is kLoopFilterSize4 if |filter_length| is 4,
46 // otherwise size is kLoopFilterSize6.
47 return static_cast<dsp::LoopFilterSize>(filter_length != 4);
48 }
49
NonBlockBorderNeedsFilter(const BlockParameters & bp,int filter_id,uint8_t * const level)50 bool NonBlockBorderNeedsFilter(const BlockParameters& bp, int filter_id,
51 uint8_t* const level) {
52 if (bp.deblock_filter_level[filter_id] == 0 || (bp.skip && bp.is_inter)) {
53 return false;
54 }
55 *level = bp.deblock_filter_level[filter_id];
56 return true;
57 }
58
59 // 7.14.5.
ComputeDeblockFilterLevelsHelper(const ObuFrameHeader & frame_header,int segment_id,int level_index,const int8_t delta_lf[kFrameLfCount],uint8_t deblock_filter_levels[kNumReferenceFrameTypes][2])60 void ComputeDeblockFilterLevelsHelper(
61 const ObuFrameHeader& frame_header, int segment_id, int level_index,
62 const int8_t delta_lf[kFrameLfCount],
63 uint8_t deblock_filter_levels[kNumReferenceFrameTypes][2]) {
64 const int delta = delta_lf[frame_header.delta_lf.multi ? level_index : 0];
65 uint8_t level = Clip3(frame_header.loop_filter.level[level_index] + delta, 0,
66 kMaxLoopFilterValue);
67 const auto feature = static_cast<SegmentFeature>(
68 kSegmentFeatureLoopFilterYVertical + level_index);
69 level =
70 Clip3(level + frame_header.segmentation.feature_data[segment_id][feature],
71 0, kMaxLoopFilterValue);
72 if (!frame_header.loop_filter.delta_enabled) {
73 static_assert(sizeof(deblock_filter_levels[0][0]) == 1, "");
74 memset(deblock_filter_levels, level, kNumReferenceFrameTypes * 2);
75 return;
76 }
77 assert(frame_header.loop_filter.delta_enabled);
78 const int shift = level >> 5;
79 deblock_filter_levels[kReferenceFrameIntra][0] = Clip3(
80 level +
81 LeftShift(frame_header.loop_filter.ref_deltas[kReferenceFrameIntra],
82 shift),
83 0, kMaxLoopFilterValue);
84 // deblock_filter_levels[kReferenceFrameIntra][1] is never used. So it does
85 // not have to be populated.
86 for (int reference_frame = kReferenceFrameIntra + 1;
87 reference_frame < kNumReferenceFrameTypes; ++reference_frame) {
88 for (int mode_id = 0; mode_id < 2; ++mode_id) {
89 deblock_filter_levels[reference_frame][mode_id] = Clip3(
90 level +
91 LeftShift(frame_header.loop_filter.ref_deltas[reference_frame] +
92 frame_header.loop_filter.mode_deltas[mode_id],
93 shift),
94 0, kMaxLoopFilterValue);
95 }
96 }
97 }
98
99 } // namespace
100
ComputeDeblockFilterLevels(const int8_t delta_lf[kFrameLfCount],uint8_t deblock_filter_levels[kMaxSegments][kFrameLfCount][kNumReferenceFrameTypes][2]) const101 void PostFilter::ComputeDeblockFilterLevels(
102 const int8_t delta_lf[kFrameLfCount],
103 uint8_t deblock_filter_levels[kMaxSegments][kFrameLfCount]
104 [kNumReferenceFrameTypes][2]) const {
105 if (!DoDeblock()) return;
106 const int num_segments =
107 frame_header_.segmentation.enabled ? kMaxSegments : 1;
108 for (int segment_id = 0; segment_id < num_segments; ++segment_id) {
109 int level_index = 0;
110 for (; level_index < 2; ++level_index) {
111 ComputeDeblockFilterLevelsHelper(
112 frame_header_, segment_id, level_index, delta_lf,
113 deblock_filter_levels[segment_id][level_index]);
114 }
115 for (; level_index < kFrameLfCount; ++level_index) {
116 if (frame_header_.loop_filter.level[level_index] != 0) {
117 ComputeDeblockFilterLevelsHelper(
118 frame_header_, segment_id, level_index, delta_lf,
119 deblock_filter_levels[segment_id][level_index]);
120 }
121 }
122 }
123 }
124
GetHorizontalDeblockFilterEdgeInfo(int row4x4,int column4x4,uint8_t * level,int * step,int * filter_length) const125 bool PostFilter::GetHorizontalDeblockFilterEdgeInfo(int row4x4, int column4x4,
126 uint8_t* level, int* step,
127 int* filter_length) const {
128 *step = kTransformHeight[inter_transform_sizes_[row4x4][column4x4]];
129 if (row4x4 == 0) return false;
130
131 const BlockParameters* bp = block_parameters_.Find(row4x4, column4x4);
132 const int row4x4_prev = row4x4 - 1;
133 assert(row4x4_prev >= 0);
134 const BlockParameters* bp_prev =
135 block_parameters_.Find(row4x4_prev, column4x4);
136
137 if (bp == bp_prev) {
138 // Not a border.
139 if (!NonBlockBorderNeedsFilter(*bp, 1, level)) return false;
140 } else {
141 const uint8_t level_this = bp->deblock_filter_level[1];
142 *level = level_this;
143 if (level_this == 0) {
144 const uint8_t level_prev = bp_prev->deblock_filter_level[1];
145 if (level_prev == 0) return false;
146 *level = level_prev;
147 }
148 }
149 const int step_prev =
150 kTransformHeight[inter_transform_sizes_[row4x4_prev][column4x4]];
151 *filter_length = std::min(*step, step_prev);
152 return true;
153 }
154
GetHorizontalDeblockFilterEdgeInfoUV(int row4x4,int column4x4,uint8_t * level_u,uint8_t * level_v,int * step,int * filter_length) const155 void PostFilter::GetHorizontalDeblockFilterEdgeInfoUV(
156 int row4x4, int column4x4, uint8_t* level_u, uint8_t* level_v, int* step,
157 int* filter_length) const {
158 const int subsampling_x = subsampling_x_[kPlaneU];
159 const int subsampling_y = subsampling_y_[kPlaneU];
160 row4x4 = GetDeblockPosition(row4x4, subsampling_y);
161 column4x4 = GetDeblockPosition(column4x4, subsampling_x);
162 const BlockParameters* bp = block_parameters_.Find(row4x4, column4x4);
163 *level_u = 0;
164 *level_v = 0;
165 *step = kTransformHeight[bp->uv_transform_size];
166 if (row4x4 == subsampling_y) {
167 return;
168 }
169
170 bool need_filter_u = frame_header_.loop_filter.level[kPlaneU + 1] != 0;
171 bool need_filter_v = frame_header_.loop_filter.level[kPlaneV + 1] != 0;
172 assert(need_filter_u || need_filter_v);
173 const int filter_id_u =
174 kDeblockFilterLevelIndex[kPlaneU][kLoopFilterTypeHorizontal];
175 const int filter_id_v =
176 kDeblockFilterLevelIndex[kPlaneV][kLoopFilterTypeHorizontal];
177 const int row4x4_prev = row4x4 - (1 << subsampling_y);
178 assert(row4x4_prev >= 0);
179 const BlockParameters* bp_prev =
180 block_parameters_.Find(row4x4_prev, column4x4);
181
182 if (bp == bp_prev) {
183 // Not a border.
184 const bool skip = bp->skip && bp->is_inter;
185 need_filter_u =
186 need_filter_u && bp->deblock_filter_level[filter_id_u] != 0 && !skip;
187 need_filter_v =
188 need_filter_v && bp->deblock_filter_level[filter_id_v] != 0 && !skip;
189 if (!need_filter_u && !need_filter_v) return;
190 if (need_filter_u) *level_u = bp->deblock_filter_level[filter_id_u];
191 if (need_filter_v) *level_v = bp->deblock_filter_level[filter_id_v];
192 *filter_length = *step;
193 return;
194 }
195
196 // It is a border.
197 if (need_filter_u) {
198 const uint8_t level_u_this = bp->deblock_filter_level[filter_id_u];
199 *level_u = level_u_this;
200 if (level_u_this == 0) {
201 *level_u = bp_prev->deblock_filter_level[filter_id_u];
202 }
203 }
204 if (need_filter_v) {
205 const uint8_t level_v_this = bp->deblock_filter_level[filter_id_v];
206 *level_v = level_v_this;
207 if (level_v_this == 0) {
208 *level_v = bp_prev->deblock_filter_level[filter_id_v];
209 }
210 }
211 const int step_prev = kTransformHeight[bp_prev->uv_transform_size];
212 *filter_length = std::min(*step, step_prev);
213 }
214
GetVerticalDeblockFilterEdgeInfo(int row4x4,int column4x4,BlockParameters * const * bp_ptr,uint8_t * level,int * step,int * filter_length) const215 bool PostFilter::GetVerticalDeblockFilterEdgeInfo(
216 int row4x4, int column4x4, BlockParameters* const* bp_ptr, uint8_t* level,
217 int* step, int* filter_length) const {
218 const BlockParameters* bp = *bp_ptr;
219 *step = kTransformWidth[inter_transform_sizes_[row4x4][column4x4]];
220 if (column4x4 == 0) return false;
221
222 const int filter_id = 0;
223 const int column4x4_prev = column4x4 - 1;
224 assert(column4x4_prev >= 0);
225 const BlockParameters* bp_prev = *(bp_ptr - 1);
226 if (bp == bp_prev) {
227 // Not a border.
228 if (!NonBlockBorderNeedsFilter(*bp, filter_id, level)) return false;
229 } else {
230 // It is a border.
231 const uint8_t level_this = bp->deblock_filter_level[filter_id];
232 *level = level_this;
233 if (level_this == 0) {
234 const uint8_t level_prev = bp_prev->deblock_filter_level[filter_id];
235 if (level_prev == 0) return false;
236 *level = level_prev;
237 }
238 }
239 const int step_prev =
240 kTransformWidth[inter_transform_sizes_[row4x4][column4x4_prev]];
241 *filter_length = std::min(*step, step_prev);
242 return true;
243 }
244
GetVerticalDeblockFilterEdgeInfoUV(int column4x4,BlockParameters * const * bp_ptr,uint8_t * level_u,uint8_t * level_v,int * step,int * filter_length) const245 void PostFilter::GetVerticalDeblockFilterEdgeInfoUV(
246 int column4x4, BlockParameters* const* bp_ptr, uint8_t* level_u,
247 uint8_t* level_v, int* step, int* filter_length) const {
248 const int subsampling_x = subsampling_x_[kPlaneU];
249 column4x4 = GetDeblockPosition(column4x4, subsampling_x);
250 const BlockParameters* bp = *bp_ptr;
251 *level_u = 0;
252 *level_v = 0;
253 *step = kTransformWidth[bp->uv_transform_size];
254 if (column4x4 == subsampling_x) {
255 return;
256 }
257
258 bool need_filter_u = frame_header_.loop_filter.level[kPlaneU + 1] != 0;
259 bool need_filter_v = frame_header_.loop_filter.level[kPlaneV + 1] != 0;
260 assert(need_filter_u || need_filter_v);
261 const int filter_id_u =
262 kDeblockFilterLevelIndex[kPlaneU][kLoopFilterTypeVertical];
263 const int filter_id_v =
264 kDeblockFilterLevelIndex[kPlaneV][kLoopFilterTypeVertical];
265 const BlockParameters* bp_prev = *(bp_ptr - (ptrdiff_t{1} << subsampling_x));
266
267 if (bp == bp_prev) {
268 // Not a border.
269 const bool skip = bp->skip && bp->is_inter;
270 need_filter_u =
271 need_filter_u && bp->deblock_filter_level[filter_id_u] != 0 && !skip;
272 need_filter_v =
273 need_filter_v && bp->deblock_filter_level[filter_id_v] != 0 && !skip;
274 if (!need_filter_u && !need_filter_v) return;
275 if (need_filter_u) *level_u = bp->deblock_filter_level[filter_id_u];
276 if (need_filter_v) *level_v = bp->deblock_filter_level[filter_id_v];
277 *filter_length = *step;
278 return;
279 }
280
281 // It is a border.
282 if (need_filter_u) {
283 const uint8_t level_u_this = bp->deblock_filter_level[filter_id_u];
284 *level_u = level_u_this;
285 if (level_u_this == 0) {
286 *level_u = bp_prev->deblock_filter_level[filter_id_u];
287 }
288 }
289 if (need_filter_v) {
290 const uint8_t level_v_this = bp->deblock_filter_level[filter_id_v];
291 *level_v = level_v_this;
292 if (level_v_this == 0) {
293 *level_v = bp_prev->deblock_filter_level[filter_id_v];
294 }
295 }
296 const int step_prev = kTransformWidth[bp_prev->uv_transform_size];
297 *filter_length = std::min(*step, step_prev);
298 }
299
HorizontalDeblockFilter(int row4x4_start,int row4x4_end,int column4x4_start,int column4x4_end)300 void PostFilter::HorizontalDeblockFilter(int row4x4_start, int row4x4_end,
301 int column4x4_start,
302 int column4x4_end) {
303 const int height4x4 = row4x4_end - row4x4_start;
304 const int width4x4 = column4x4_end - column4x4_start;
305 if (height4x4 <= 0 || width4x4 <= 0) return;
306
307 const int column_step = 1;
308 const int src_step = 4 << pixel_size_log2_;
309 const ptrdiff_t src_stride = frame_buffer_.stride(kPlaneY);
310 uint8_t* src = GetSourceBuffer(kPlaneY, row4x4_start, column4x4_start);
311 int row_step;
312 uint8_t level;
313 int filter_length;
314
315 const int width = frame_header_.width;
316 const int height = frame_header_.height;
317 for (int column4x4 = 0;
318 column4x4 < width4x4 && MultiplyBy4(column4x4_start + column4x4) < width;
319 column4x4 += column_step, src += src_step) {
320 uint8_t* src_row = src;
321 for (int row4x4 = 0;
322 row4x4 < height4x4 && MultiplyBy4(row4x4_start + row4x4) < height;
323 row4x4 += row_step) {
324 const bool need_filter = GetHorizontalDeblockFilterEdgeInfo(
325 row4x4_start + row4x4, column4x4_start + column4x4, &level, &row_step,
326 &filter_length);
327 if (need_filter) {
328 assert(level > 0 && level <= kMaxLoopFilterValue);
329 const dsp::LoopFilterSize size = GetLoopFilterSizeY(filter_length);
330 dsp_.loop_filters[size][kLoopFilterTypeHorizontal](
331 src_row, src_stride, outer_thresh_[level], inner_thresh_[level],
332 HevThresh(level));
333 }
334 src_row += row_step * src_stride;
335 row_step = DivideBy4(row_step);
336 }
337 }
338
339 if (needs_chroma_deblock_) {
340 const int8_t subsampling_x = subsampling_x_[kPlaneU];
341 const int8_t subsampling_y = subsampling_y_[kPlaneU];
342 const int column_step = 1 << subsampling_x;
343 const ptrdiff_t src_stride_u = frame_buffer_.stride(kPlaneU);
344 const ptrdiff_t src_stride_v = frame_buffer_.stride(kPlaneV);
345 uint8_t* src_u = GetSourceBuffer(kPlaneU, row4x4_start, column4x4_start);
346 uint8_t* src_v = GetSourceBuffer(kPlaneV, row4x4_start, column4x4_start);
347 int row_step;
348 uint8_t level_u;
349 uint8_t level_v;
350 int filter_length;
351
352 for (int column4x4 = 0; column4x4 < width4x4 &&
353 MultiplyBy4(column4x4_start + column4x4) < width;
354 column4x4 += column_step, src_u += src_step, src_v += src_step) {
355 uint8_t* src_row_u = src_u;
356 uint8_t* src_row_v = src_v;
357 for (int row4x4 = 0;
358 row4x4 < height4x4 && MultiplyBy4(row4x4_start + row4x4) < height;
359 row4x4 += row_step) {
360 GetHorizontalDeblockFilterEdgeInfoUV(
361 row4x4_start + row4x4, column4x4_start + column4x4, &level_u,
362 &level_v, &row_step, &filter_length);
363 if (level_u != 0) {
364 const dsp::LoopFilterSize size = GetLoopFilterSizeUV(filter_length);
365 dsp_.loop_filters[size][kLoopFilterTypeHorizontal](
366 src_row_u, src_stride_u, outer_thresh_[level_u],
367 inner_thresh_[level_u], HevThresh(level_u));
368 }
369 if (level_v != 0) {
370 const dsp::LoopFilterSize size = GetLoopFilterSizeUV(filter_length);
371 dsp_.loop_filters[size][kLoopFilterTypeHorizontal](
372 src_row_v, src_stride_v, outer_thresh_[level_v],
373 inner_thresh_[level_v], HevThresh(level_v));
374 }
375 src_row_u += row_step * src_stride_u;
376 src_row_v += row_step * src_stride_v;
377 row_step = DivideBy4(row_step << subsampling_y);
378 }
379 }
380 }
381 }
382
VerticalDeblockFilter(int row4x4_start,int row4x4_end,int column4x4_start,int column4x4_end)383 void PostFilter::VerticalDeblockFilter(int row4x4_start, int row4x4_end,
384 int column4x4_start, int column4x4_end) {
385 const int height4x4 = row4x4_end - row4x4_start;
386 const int width4x4 = column4x4_end - column4x4_start;
387 if (height4x4 <= 0 || width4x4 <= 0) return;
388
389 const ptrdiff_t row_stride = MultiplyBy4(frame_buffer_.stride(kPlaneY));
390 const ptrdiff_t src_stride = frame_buffer_.stride(kPlaneY);
391 uint8_t* src = GetSourceBuffer(kPlaneY, row4x4_start, column4x4_start);
392 int column_step;
393 uint8_t level;
394 int filter_length;
395
396 BlockParameters* const* bp_row_base =
397 block_parameters_.Address(row4x4_start, column4x4_start);
398 const int bp_stride = block_parameters_.columns4x4();
399 const int column_step_shift = pixel_size_log2_;
400 const int width = frame_header_.width;
401 const int height = frame_header_.height;
402 for (int row4x4 = 0;
403 row4x4 < height4x4 && MultiplyBy4(row4x4_start + row4x4) < height;
404 ++row4x4, src += row_stride, bp_row_base += bp_stride) {
405 uint8_t* src_row = src;
406 BlockParameters* const* bp = bp_row_base;
407 for (int column4x4 = 0; column4x4 < width4x4 &&
408 MultiplyBy4(column4x4_start + column4x4) < width;
409 column4x4 += column_step, bp += column_step) {
410 const bool need_filter = GetVerticalDeblockFilterEdgeInfo(
411 row4x4_start + row4x4, column4x4_start + column4x4, bp, &level,
412 &column_step, &filter_length);
413 if (need_filter) {
414 assert(level > 0 && level <= kMaxLoopFilterValue);
415 const dsp::LoopFilterSize size = GetLoopFilterSizeY(filter_length);
416 dsp_.loop_filters[size][kLoopFilterTypeVertical](
417 src_row, src_stride, outer_thresh_[level], inner_thresh_[level],
418 HevThresh(level));
419 }
420 src_row += column_step << column_step_shift;
421 column_step = DivideBy4(column_step);
422 }
423 }
424
425 if (needs_chroma_deblock_) {
426 const int8_t subsampling_x = subsampling_x_[kPlaneU];
427 const int8_t subsampling_y = subsampling_y_[kPlaneU];
428 const int row_step = 1 << subsampling_y;
429 uint8_t* src_u = GetSourceBuffer(kPlaneU, row4x4_start, column4x4_start);
430 uint8_t* src_v = GetSourceBuffer(kPlaneV, row4x4_start, column4x4_start);
431 const ptrdiff_t src_stride_u = frame_buffer_.stride(kPlaneU);
432 const ptrdiff_t src_stride_v = frame_buffer_.stride(kPlaneV);
433 const ptrdiff_t row_stride_u = MultiplyBy4(frame_buffer_.stride(kPlaneU));
434 const ptrdiff_t row_stride_v = MultiplyBy4(frame_buffer_.stride(kPlaneV));
435 const LoopFilterType type = kLoopFilterTypeVertical;
436 int column_step;
437 uint8_t level_u;
438 uint8_t level_v;
439 int filter_length;
440
441 BlockParameters* const* bp_row_base = block_parameters_.Address(
442 GetDeblockPosition(row4x4_start, subsampling_y),
443 GetDeblockPosition(column4x4_start, subsampling_x));
444 const int bp_stride = block_parameters_.columns4x4() << subsampling_y;
445 for (int row4x4 = 0;
446 row4x4 < height4x4 && MultiplyBy4(row4x4_start + row4x4) < height;
447 row4x4 += row_step, src_u += row_stride_u, src_v += row_stride_v,
448 bp_row_base += bp_stride) {
449 uint8_t* src_row_u = src_u;
450 uint8_t* src_row_v = src_v;
451 BlockParameters* const* bp = bp_row_base;
452 for (int column4x4 = 0; column4x4 < width4x4 &&
453 MultiplyBy4(column4x4_start + column4x4) < width;
454 column4x4 += column_step, bp += column_step) {
455 GetVerticalDeblockFilterEdgeInfoUV(column4x4_start + column4x4, bp,
456 &level_u, &level_v, &column_step,
457 &filter_length);
458 if (level_u != 0) {
459 const dsp::LoopFilterSize size = GetLoopFilterSizeUV(filter_length);
460 dsp_.loop_filters[size][type](
461 src_row_u, src_stride_u, outer_thresh_[level_u],
462 inner_thresh_[level_u], HevThresh(level_u));
463 }
464 if (level_v != 0) {
465 const dsp::LoopFilterSize size = GetLoopFilterSizeUV(filter_length);
466 dsp_.loop_filters[size][type](
467 src_row_v, src_stride_v, outer_thresh_[level_v],
468 inner_thresh_[level_v], HevThresh(level_v));
469 }
470 src_row_u += column_step << column_step_shift;
471 src_row_v += column_step << column_step_shift;
472 column_step = DivideBy4(column_step << subsampling_x);
473 }
474 }
475 }
476 }
477
478 template <LoopFilterType loop_filter_type>
DeblockFilterWorker(std::atomic<int> * row4x4_atomic)479 void PostFilter::DeblockFilterWorker(std::atomic<int>* row4x4_atomic) {
480 const int rows4x4 = frame_header_.rows4x4;
481 const int columns4x4 = frame_header_.columns4x4;
482 int row4x4;
483 while ((row4x4 = row4x4_atomic->fetch_add(
484 kNum4x4InLoopFilterUnit, std::memory_order_relaxed)) < rows4x4) {
485 (this->*deblock_filter_func_[loop_filter_type])(
486 row4x4, row4x4 + kNum4x4InLoopFilterUnit, 0, columns4x4);
487 }
488 }
489
490 template void PostFilter::DeblockFilterWorker<kLoopFilterTypeVertical>(
491 std::atomic<int>* row4x4_atomic);
492 template void PostFilter::DeblockFilterWorker<kLoopFilterTypeHorizontal>(
493 std::atomic<int>* row4x4_atomic);
494
ApplyDeblockFilter(LoopFilterType loop_filter_type,int row4x4_start,int column4x4_start,int column4x4_end,int sb4x4)495 void PostFilter::ApplyDeblockFilter(LoopFilterType loop_filter_type,
496 int row4x4_start, int column4x4_start,
497 int column4x4_end, int sb4x4) {
498 assert(row4x4_start >= 0);
499 assert(DoDeblock());
500 column4x4_end =
501 std::min(Align(column4x4_end, static_cast<int>(kNum4x4InLoopFilterUnit)),
502 frame_header_.columns4x4);
503 if (column4x4_start >= column4x4_end) return;
504 (this->*deblock_filter_func_[loop_filter_type])(
505 row4x4_start, row4x4_start + sb4x4, column4x4_start, column4x4_end);
506 }
507
508 } // namespace libgav1
509