• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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