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