/ src / Ryujinx.Graphics.Nvdec.Vp9 / DecodeFrame.cs
DecodeFrame.cs
   1  using Ryujinx.Common.Memory;
   2  using Ryujinx.Graphics.Nvdec.Vp9.Common;
   3  using Ryujinx.Graphics.Nvdec.Vp9.Dsp;
   4  using Ryujinx.Graphics.Nvdec.Vp9.Types;
   5  using Ryujinx.Graphics.Video;
   6  using System;
   7  using System.Buffers.Binary;
   8  using System.Diagnostics;
   9  using System.Runtime.CompilerServices;
  10  using System.Runtime.InteropServices;
  11  using System.Threading.Tasks;
  12  
  13  namespace Ryujinx.Graphics.Nvdec.Vp9
  14  {
  15      static class DecodeFrame
  16      {
  17          private static bool ReadIsValid(ArrayPtr<byte> start, int len)
  18          {
  19              return len != 0 && len <= start.Length;
  20          }
  21  
  22          private static void InverseTransformBlockInter(ref MacroBlockD xd, int plane, TxSize txSize, Span<byte> dst, int stride, int eob)
  23          {
  24              ref MacroBlockDPlane pd = ref xd.Plane[plane];
  25              ArrayPtr<int> dqcoeff = pd.DqCoeff;
  26              Debug.Assert(eob > 0);
  27              if (xd.CurBuf.HighBd)
  28              {
  29                  Span<ushort> dst16 = MemoryMarshal.Cast<byte, ushort>(dst);
  30                  if (xd.Lossless)
  31                  {
  32                      Idct.HighbdIwht4x4Add(dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd);
  33                  }
  34                  else
  35                  {
  36                      switch (txSize)
  37                      {
  38                          case TxSize.Tx4x4:
  39                              Idct.HighbdIdct4x4Add(dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd);
  40                              break;
  41                          case TxSize.Tx8x8:
  42                              Idct.HighbdIdct8x8Add(dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd);
  43                              break;
  44                          case TxSize.Tx16x16:
  45                              Idct.HighbdIdct16x16Add(dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd);
  46                              break;
  47                          case TxSize.Tx32x32:
  48                              Idct.HighbdIdct32x32Add(dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd);
  49                              break;
  50                          default:
  51                              Debug.Assert(false, "Invalid transform size");
  52                              break;
  53                      }
  54                  }
  55              }
  56              else
  57              {
  58                  if (xd.Lossless)
  59                  {
  60                      Idct.Iwht4x4Add(dqcoeff.AsSpan(), dst, stride, eob);
  61                  }
  62                  else
  63                  {
  64                      switch (txSize)
  65                      {
  66                          case TxSize.Tx4x4:
  67                              Idct.Idct4x4Add(dqcoeff.AsSpan(), dst, stride, eob);
  68                              break;
  69                          case TxSize.Tx8x8:
  70                              Idct.Idct8x8Add(dqcoeff.AsSpan(), dst, stride, eob);
  71                              break;
  72                          case TxSize.Tx16x16:
  73                              Idct.Idct16x16Add(dqcoeff.AsSpan(), dst, stride, eob);
  74                              break;
  75                          case TxSize.Tx32x32:
  76                              Idct.Idct32x32Add(dqcoeff.AsSpan(), dst, stride, eob);
  77                              break;
  78                          default:
  79                              Debug.Assert(false, "Invalid transform size");
  80                              return;
  81                      }
  82                  }
  83              }
  84  
  85              if (eob == 1)
  86              {
  87                  dqcoeff.AsSpan()[0] = 0;
  88              }
  89              else
  90              {
  91                  if (txSize <= TxSize.Tx16x16 && eob <= 10)
  92                  {
  93                      dqcoeff.AsSpan()[..(4 * (4 << (int)txSize))].Clear();
  94                  }
  95                  else if (txSize == TxSize.Tx32x32 && eob <= 34)
  96                  {
  97                      dqcoeff.AsSpan()[..256].Clear();
  98                  }
  99                  else
 100                  {
 101                      dqcoeff.AsSpan()[..(16 << ((int)txSize << 1))].Clear();
 102                  }
 103              }
 104          }
 105  
 106          private static void InverseTransformBlockIntra(
 107              ref MacroBlockD xd,
 108              int plane,
 109              TxType txType,
 110              TxSize txSize,
 111              Span<byte> dst,
 112              int stride,
 113              int eob)
 114          {
 115              ref MacroBlockDPlane pd = ref xd.Plane[plane];
 116              ArrayPtr<int> dqcoeff = pd.DqCoeff;
 117              Debug.Assert(eob > 0);
 118              if (xd.CurBuf.HighBd)
 119              {
 120                  Span<ushort> dst16 = MemoryMarshal.Cast<byte, ushort>(dst);
 121                  if (xd.Lossless)
 122                  {
 123                      Idct.HighbdIwht4x4Add(dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd);
 124                  }
 125                  else
 126                  {
 127                      switch (txSize)
 128                      {
 129                          case TxSize.Tx4x4:
 130                              Idct.HighbdIht4x4Add(txType, dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd);
 131                              break;
 132                          case TxSize.Tx8x8:
 133                              Idct.HighbdIht8x8Add(txType, dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd);
 134                              break;
 135                          case TxSize.Tx16x16:
 136                              Idct.HighbdIht16x16Add(txType, dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd);
 137                              break;
 138                          case TxSize.Tx32x32:
 139                              Idct.HighbdIdct32x32Add(dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd);
 140                              break;
 141                          default:
 142                              Debug.Assert(false, "Invalid transform size");
 143                              break;
 144                      }
 145                  }
 146              }
 147              else
 148              {
 149                  if (xd.Lossless)
 150                  {
 151                      Idct.Iwht4x4Add(dqcoeff.AsSpan(), dst, stride, eob);
 152                  }
 153                  else
 154                  {
 155                      switch (txSize)
 156                      {
 157                          case TxSize.Tx4x4:
 158                              Idct.Iht4x4Add(txType, dqcoeff.AsSpan(), dst, stride, eob);
 159                              break;
 160                          case TxSize.Tx8x8:
 161                              Idct.Iht8x8Add(txType, dqcoeff.AsSpan(), dst, stride, eob);
 162                              break;
 163                          case TxSize.Tx16x16:
 164                              Idct.Iht16x16Add(txType, dqcoeff.AsSpan(), dst, stride, eob);
 165                              break;
 166                          case TxSize.Tx32x32:
 167                              Idct.Idct32x32Add(dqcoeff.AsSpan(), dst, stride, eob);
 168                              break;
 169                          default:
 170                              Debug.Assert(false, "Invalid transform size");
 171                              return;
 172                      }
 173                  }
 174              }
 175  
 176              if (eob == 1)
 177              {
 178                  dqcoeff.AsSpan()[0] = 0;
 179              }
 180              else
 181              {
 182                  if (txType == TxType.DctDct && txSize <= TxSize.Tx16x16 && eob <= 10)
 183                  {
 184                      dqcoeff.AsSpan()[..(4 * (4 << (int)txSize))].Clear();
 185                  }
 186                  else if (txSize == TxSize.Tx32x32 && eob <= 34)
 187                  {
 188                      dqcoeff.AsSpan()[..256].Clear();
 189                  }
 190                  else
 191                  {
 192                      dqcoeff.AsSpan()[..(16 << ((int)txSize << 1))].Clear();
 193                  }
 194              }
 195          }
 196  
 197          private static unsafe void PredictAndReconstructIntraBlock(
 198              ref TileWorkerData twd,
 199              ref ModeInfo mi,
 200              int plane,
 201              int row,
 202              int col,
 203              TxSize txSize)
 204          {
 205              ref MacroBlockD xd = ref twd.Xd;
 206              ref MacroBlockDPlane pd = ref xd.Plane[plane];
 207              PredictionMode mode = (plane == 0) ? mi.Mode : mi.UvMode;
 208              int dstOffset = 4 * row * pd.Dst.Stride + 4 * col;
 209              byte* dst = &pd.Dst.Buf.ToPointer()[dstOffset];
 210              Span<byte> dstSpan = pd.Dst.Buf.AsSpan()[dstOffset..];
 211  
 212              if (mi.SbType < BlockSize.Block8x8)
 213              {
 214                  if (plane == 0)
 215                  {
 216                      mode = xd.Mi[0].Value.Bmi[(row << 1) + col].Mode;
 217                  }
 218              }
 219  
 220              ReconIntra.PredictIntraBlock(ref xd, pd.N4Wl, txSize, mode, dst, pd.Dst.Stride, dst, pd.Dst.Stride, col, row, plane);
 221  
 222              if (mi.Skip == 0)
 223              {
 224                  TxType txType =
 225                      (plane != 0 || xd.Lossless) ? TxType.DctDct : ReconIntra.IntraModeToTxTypeLookup[(int)mode];
 226                  var sc = (plane != 0 || xd.Lossless)
 227                      ? Luts.Vp9DefaultScanOrders[(int)txSize]
 228                      : Luts.Vp9ScanOrders[(int)txSize][(int)txType];
 229                  int eob = Detokenize.DecodeBlockTokens(ref twd, plane, sc, col, row, txSize, mi.SegmentId);
 230                  if (eob > 0)
 231                  {
 232                      InverseTransformBlockIntra(ref xd, plane, txType, txSize, dstSpan, pd.Dst.Stride, eob);
 233                  }
 234              }
 235          }
 236  
 237          private static int ReconstructInterBlock(
 238              ref TileWorkerData twd,
 239              ref ModeInfo mi,
 240              int plane,
 241              int row,
 242              int col,
 243              TxSize txSize)
 244          {
 245              ref MacroBlockD xd = ref twd.Xd;
 246              ref MacroBlockDPlane pd = ref xd.Plane[plane];
 247              var sc = Luts.Vp9DefaultScanOrders[(int)txSize];
 248              int eob = Detokenize.DecodeBlockTokens(ref twd, plane, sc, col, row, txSize, mi.SegmentId);
 249              Span<byte> dst = pd.Dst.Buf.AsSpan()[(4 * row * pd.Dst.Stride + 4 * col)..];
 250  
 251              if (eob > 0)
 252              {
 253                  InverseTransformBlockInter(ref xd, plane, txSize, dst, pd.Dst.Stride, eob);
 254              }
 255              return eob;
 256          }
 257  
 258          private static unsafe void BuildMcBorder(
 259              byte* src,
 260              int srcStride,
 261              byte* dst,
 262              int dstStride,
 263              int x,
 264              int y,
 265              int bW,
 266              int bH,
 267              int w,
 268              int h)
 269          {
 270              // Get a pointer to the start of the real data for this row.
 271              byte* refRow = src - x - y * srcStride;
 272  
 273              if (y >= h)
 274              {
 275                  refRow += (h - 1) * srcStride;
 276              }
 277              else if (y > 0)
 278              {
 279                  refRow += y * srcStride;
 280              }
 281  
 282              do
 283              {
 284                  int right = 0, copy;
 285                  int left = x < 0 ? -x : 0;
 286  
 287                  if (left > bW)
 288                  {
 289                      left = bW;
 290                  }
 291  
 292                  if (x + bW > w)
 293                  {
 294                      right = x + bW - w;
 295                  }
 296  
 297                  if (right > bW)
 298                  {
 299                      right = bW;
 300                  }
 301  
 302                  copy = bW - left - right;
 303  
 304                  if (left != 0)
 305                  {
 306                      MemoryUtil.Fill(dst, refRow[0], left);
 307                  }
 308  
 309                  if (copy != 0)
 310                  {
 311                      MemoryUtil.Copy(dst + left, refRow + x + left, copy);
 312                  }
 313  
 314                  if (right != 0)
 315                  {
 316                      MemoryUtil.Fill(dst + left + copy, refRow[w - 1], right);
 317                  }
 318  
 319                  dst += dstStride;
 320                  ++y;
 321  
 322                  if (y > 0 && y < h)
 323                  {
 324                      refRow += srcStride;
 325                  }
 326              } while (--bH != 0);
 327          }
 328  
 329          private static unsafe void HighBuildMcBorder(
 330              byte* src8,
 331              int srcStride,
 332              ushort* dst,
 333              int dstStride,
 334              int x,
 335              int y,
 336              int bW,
 337              int bH,
 338              int w,
 339              int h)
 340          {
 341              // Get a pointer to the start of the real data for this row.
 342              ushort* src = (ushort*)src8;
 343              ushort* refRow = src - x - y * srcStride;
 344  
 345              if (y >= h)
 346              {
 347                  refRow += (h - 1) * srcStride;
 348              }
 349              else if (y > 0)
 350              {
 351                  refRow += y * srcStride;
 352              }
 353  
 354              do
 355              {
 356                  int right = 0, copy;
 357                  int left = x < 0 ? -x : 0;
 358  
 359                  if (left > bW)
 360                  {
 361                      left = bW;
 362                  }
 363  
 364                  if (x + bW > w)
 365                  {
 366                      right = x + bW - w;
 367                  }
 368  
 369                  if (right > bW)
 370                  {
 371                      right = bW;
 372                  }
 373  
 374                  copy = bW - left - right;
 375  
 376                  if (left != 0)
 377                  {
 378                      MemoryUtil.Fill(dst, refRow[0], left);
 379                  }
 380  
 381                  if (copy != 0)
 382                  {
 383                      MemoryUtil.Copy(dst + left, refRow + x + left, copy);
 384                  }
 385  
 386                  if (right != 0)
 387                  {
 388                      MemoryUtil.Fill(dst + left + copy, refRow[w - 1], right);
 389                  }
 390  
 391                  dst += dstStride;
 392                  ++y;
 393  
 394                  if (y > 0 && y < h)
 395                  {
 396                      refRow += srcStride;
 397                  }
 398              } while (--bH != 0);
 399          }
 400  
 401          [SkipLocalsInit]
 402          private static unsafe void ExtendAndPredict(
 403              byte* bufPtr1,
 404              int preBufStride,
 405              int x0,
 406              int y0,
 407              int bW,
 408              int bH,
 409              int frameWidth,
 410              int frameHeight,
 411              int borderOffset,
 412              byte* dst,
 413              int dstBufStride,
 414              int subpelX,
 415              int subpelY,
 416              Array8<short>[] kernel,
 417              ref ScaleFactors sf,
 418              ref MacroBlockD xd,
 419              int w,
 420              int h,
 421              int refr,
 422              int xs,
 423              int ys)
 424          {
 425              ushort* mcBufHigh = stackalloc ushort[80 * 2 * 80 * 2];
 426              if (xd.CurBuf.HighBd)
 427              {
 428                  HighBuildMcBorder(bufPtr1, preBufStride, mcBufHigh, bW, x0, y0, bW, bH, frameWidth, frameHeight);
 429                  ReconInter.HighbdInterPredictor(
 430                      mcBufHigh + borderOffset,
 431                      bW,
 432                      (ushort*)dst,
 433                      dstBufStride,
 434                      subpelX,
 435                      subpelY,
 436                      ref sf,
 437                      w,
 438                      h,
 439                      refr,
 440                      kernel,
 441                      xs,
 442                      ys,
 443                      xd.Bd);
 444              }
 445              else
 446              {
 447                  BuildMcBorder(bufPtr1, preBufStride, (byte*)mcBufHigh, bW, x0, y0, bW, bH, frameWidth, frameHeight);
 448                  ReconInter.InterPredictor(
 449                      (byte*)mcBufHigh + borderOffset,
 450                      bW,
 451                      dst,
 452                      dstBufStride,
 453                      subpelX,
 454                      subpelY,
 455                      ref sf,
 456                      w,
 457                      h,
 458                      refr,
 459                      kernel,
 460                      xs,
 461                      ys);
 462              }
 463          }
 464  
 465          private static unsafe void DecBuildInterPredictors(
 466              ref MacroBlockD xd,
 467              int plane,
 468              int bw,
 469              int bh,
 470              int x,
 471              int y,
 472              int w,
 473              int h,
 474              int miX,
 475              int miY,
 476              Array8<short>[] kernel,
 477              ref ScaleFactors sf,
 478              ref Buf2D preBuf,
 479              ref Buf2D dstBuf,
 480              ref Mv mv,
 481              ref Surface refFrameBuf,
 482              bool isScaled,
 483              int refr)
 484          {
 485              ref MacroBlockDPlane pd = ref xd.Plane[plane];
 486              byte* dst = dstBuf.Buf.ToPointer() + dstBuf.Stride * y + x;
 487              Mv32 scaledMv;
 488              int xs, ys, x0, y0, x0_16, y0_16, frameWidth, frameHeight, bufStride, subpelX, subpelY;
 489              byte* refFrame;
 490              byte* bufPtr;
 491  
 492              // Get reference frame pointer, width and height.
 493              if (plane == 0)
 494              {
 495                  frameWidth = refFrameBuf.Width;
 496                  frameHeight = refFrameBuf.Height;
 497                  refFrame = refFrameBuf.YBuffer.ToPointer();
 498              }
 499              else
 500              {
 501                  frameWidth = refFrameBuf.UvWidth;
 502                  frameHeight = refFrameBuf.UvHeight;
 503                  refFrame = plane == 1 ? refFrameBuf.UBuffer.ToPointer() : refFrameBuf.VBuffer.ToPointer();
 504              }
 505  
 506              if (isScaled)
 507              {
 508                  Mv mvQ4 = ReconInter.ClampMvToUmvBorderSb(ref xd, ref mv, bw, bh, pd.SubsamplingX, pd.SubsamplingY);
 509                  // Co-ordinate of containing block to pixel precision.
 510                  int xStart = (-xd.MbToLeftEdge >> (3 + pd.SubsamplingX));
 511                  int yStart = (-xd.MbToTopEdge >> (3 + pd.SubsamplingY));
 512                  // Co-ordinate of the block to 1/16th pixel precision.
 513                  x0_16 = (xStart + x) << Filter.SubpelBits;
 514                  y0_16 = (yStart + y) << Filter.SubpelBits;
 515  
 516                  // Co-ordinate of current block in reference frame
 517                  // to 1/16th pixel precision.
 518                  x0_16 = sf.ScaleValueX(x0_16);
 519                  y0_16 = sf.ScaleValueY(y0_16);
 520  
 521                  // Map the top left corner of the block into the reference frame.
 522                  x0 = sf.ScaleValueX(xStart + x);
 523                  y0 = sf.ScaleValueY(yStart + y);
 524  
 525                  // Scale the MV and incorporate the sub-pixel offset of the block
 526                  // in the reference frame.
 527                  scaledMv = sf.ScaleMv(ref mvQ4, miX + x, miY + y);
 528                  xs = sf.XStepQ4;
 529                  ys = sf.YStepQ4;
 530              }
 531              else
 532              {
 533                  // Co-ordinate of containing block to pixel precision.
 534                  x0 = (-xd.MbToLeftEdge >> (3 + pd.SubsamplingX)) + x;
 535                  y0 = (-xd.MbToTopEdge >> (3 + pd.SubsamplingY)) + y;
 536  
 537                  // Co-ordinate of the block to 1/16th pixel precision.
 538                  x0_16 = x0 << Filter.SubpelBits;
 539                  y0_16 = y0 << Filter.SubpelBits;
 540  
 541                  scaledMv.Row = mv.Row * (1 << (1 - pd.SubsamplingY));
 542                  scaledMv.Col = mv.Col * (1 << (1 - pd.SubsamplingX));
 543                  xs = ys = 16;
 544              }
 545              subpelX = scaledMv.Col & Filter.SubpelMask;
 546              subpelY = scaledMv.Row & Filter.SubpelMask;
 547  
 548              // Calculate the top left corner of the best matching block in the
 549              // reference frame.
 550              x0 += scaledMv.Col >> Filter.SubpelBits;
 551              y0 += scaledMv.Row >> Filter.SubpelBits;
 552              x0_16 += scaledMv.Col;
 553              y0_16 += scaledMv.Row;
 554  
 555              // Get reference block pointer.
 556              bufPtr = refFrame + y0 * preBuf.Stride + x0;
 557              bufStride = preBuf.Stride;
 558  
 559              // Do border extension if there is motion or the
 560              // width/height is not a multiple of 8 pixels.
 561              if (isScaled || scaledMv.Col != 0 || scaledMv.Row != 0 || (frameWidth & 0x7) != 0 || (frameHeight & 0x7) != 0)
 562              {
 563                  int y1 = ((y0_16 + (h - 1) * ys) >> Filter.SubpelBits) + 1;
 564  
 565                  // Get reference block bottom right horizontal coordinate.
 566                  int x1 = ((x0_16 + (w - 1) * xs) >> Filter.SubpelBits) + 1;
 567                  int xPad = 0, yPad = 0;
 568  
 569                  if (subpelX != 0 || (sf.XStepQ4 != Filter.SubpelShifts))
 570                  {
 571                      x0 -= Constants.Vp9InterpExtend - 1;
 572                      x1 += Constants.Vp9InterpExtend;
 573                      xPad = 1;
 574                  }
 575  
 576                  if (subpelY != 0 || (sf.YStepQ4 != Filter.SubpelShifts))
 577                  {
 578                      y0 -= Constants.Vp9InterpExtend - 1;
 579                      y1 += Constants.Vp9InterpExtend;
 580                      yPad = 1;
 581                  }
 582  
 583                  // Skip border extension if block is inside the frame.
 584                  if (x0 < 0 || x0 > frameWidth - 1 || x1 < 0 || x1 > frameWidth - 1 ||
 585                      y0 < 0 || y0 > frameHeight - 1 || y1 < 0 || y1 > frameHeight - 1)
 586                  {
 587                      // Extend the border.
 588                      byte* bufPtr1 = refFrame + y0 * bufStride + x0;
 589                      int bW = x1 - x0 + 1;
 590                      int bH = y1 - y0 + 1;
 591                      int borderOffset = yPad * 3 * bW + xPad * 3;
 592  
 593                      ExtendAndPredict(
 594                          bufPtr1,
 595                          bufStride,
 596                          x0,
 597                          y0,
 598                          bW,
 599                          bH,
 600                          frameWidth,
 601                          frameHeight,
 602                          borderOffset,
 603                          dst,
 604                          dstBuf.Stride,
 605                          subpelX,
 606                          subpelY,
 607                          kernel,
 608                          ref sf,
 609                          ref xd,
 610                          w,
 611                          h,
 612                          refr,
 613                          xs,
 614                          ys);
 615  
 616                      return;
 617                  }
 618              }
 619  
 620              if (xd.CurBuf.HighBd)
 621              {
 622                  ReconInter.HighbdInterPredictor(
 623                      (ushort*)bufPtr,
 624                      bufStride,
 625                      (ushort*)dst,
 626                      dstBuf.Stride,
 627                      subpelX,
 628                      subpelY,
 629                      ref sf,
 630                      w,
 631                      h,
 632                      refr,
 633                      kernel,
 634                      xs,
 635                      ys,
 636                      xd.Bd);
 637              }
 638              else
 639              {
 640                  ReconInter.InterPredictor(
 641                      bufPtr,
 642                      bufStride,
 643                      dst,
 644                      dstBuf.Stride,
 645                      subpelX,
 646                      subpelY,
 647                      ref sf,
 648                      w,
 649                      h,
 650                      refr,
 651                      kernel,
 652                      xs,
 653                      ys);
 654              }
 655          }
 656  
 657          private static void DecBuildInterPredictorsSb(ref Vp9Common cm, ref MacroBlockD xd, int miRow, int miCol)
 658          {
 659              int plane;
 660              int miX = miCol * Constants.MiSize;
 661              int miY = miRow * Constants.MiSize;
 662              ref ModeInfo mi = ref xd.Mi[0].Value;
 663              Array8<short>[] kernel = Luts.Vp9FilterKernels[mi.InterpFilter];
 664              BlockSize sbType = mi.SbType;
 665              int isCompound = mi.HasSecondRef() ? 1 : 0;
 666              int refr;
 667              bool isScaled;
 668  
 669              for (refr = 0; refr < 1 + isCompound; ++refr)
 670              {
 671                  int frame = mi.RefFrame[refr];
 672                  ref RefBuffer refBuf = ref cm.FrameRefs[frame - Constants.LastFrame];
 673                  ref ScaleFactors sf = ref refBuf.Sf;
 674                  ref Surface refFrameBuf = ref refBuf.Buf;
 675  
 676                  if (!sf.IsValidScale())
 677                  {
 678                      xd.ErrorInfo.Value.InternalError(CodecErr.CodecUnsupBitstream, "Reference frame has invalid dimensions");
 679                  }
 680  
 681                  isScaled = sf.IsScaled();
 682                  ReconInter.SetupPrePlanes(ref xd, refr, ref refFrameBuf, miRow, miCol, isScaled ? new Ptr<ScaleFactors>(ref sf) : Ptr<ScaleFactors>.Null);
 683                  xd.BlockRefs[refr] = new Ptr<RefBuffer>(ref refBuf);
 684  
 685                  if (sbType < BlockSize.Block8x8)
 686                  {
 687                      for (plane = 0; plane < Constants.MaxMbPlane; ++plane)
 688                      {
 689                          ref MacroBlockDPlane pd = ref xd.Plane[plane];
 690                          ref Buf2D dstBuf = ref pd.Dst;
 691                          int num4x4W = pd.N4W;
 692                          int num4x4H = pd.N4H;
 693                          int n4Wx4 = 4 * num4x4W;
 694                          int n4Hx4 = 4 * num4x4H;
 695                          ref Buf2D preBuf = ref pd.Pre[refr];
 696                          int i = 0, x, y;
 697                          for (y = 0; y < num4x4H; ++y)
 698                          {
 699                              for (x = 0; x < num4x4W; ++x)
 700                              {
 701                                  Mv mv = ReconInter.AverageSplitMvs(ref pd, ref mi, refr, i++);
 702                                  DecBuildInterPredictors(
 703                                      ref xd,
 704                                      plane,
 705                                      n4Wx4,
 706                                      n4Hx4,
 707                                      4 * x,
 708                                      4 * y,
 709                                      4,
 710                                      4,
 711                                      miX,
 712                                      miY,
 713                                      kernel,
 714                                      ref sf,
 715                                      ref preBuf,
 716                                      ref dstBuf,
 717                                      ref mv,
 718                                      ref refFrameBuf,
 719                                      isScaled,
 720                                      refr);
 721                              }
 722                          }
 723                      }
 724                  }
 725                  else
 726                  {
 727                      Mv mv = mi.Mv[refr];
 728                      for (plane = 0; plane < Constants.MaxMbPlane; ++plane)
 729                      {
 730                          ref MacroBlockDPlane pd = ref xd.Plane[plane];
 731                          ref Buf2D dstBuf = ref pd.Dst;
 732                          int num4x4W = pd.N4W;
 733                          int num4x4H = pd.N4H;
 734                          int n4Wx4 = 4 * num4x4W;
 735                          int n4Hx4 = 4 * num4x4H;
 736                          ref Buf2D preBuf = ref pd.Pre[refr];
 737                          DecBuildInterPredictors(
 738                              ref xd,
 739                              plane,
 740                              n4Wx4,
 741                              n4Hx4,
 742                              0,
 743                              0,
 744                              n4Wx4,
 745                              n4Hx4,
 746                              miX,
 747                              miY,
 748                              kernel,
 749                              ref sf,
 750                              ref preBuf,
 751                              ref dstBuf,
 752                              ref mv,
 753                              ref refFrameBuf,
 754                              isScaled,
 755                              refr);
 756                      }
 757                  }
 758              }
 759          }
 760  
 761          private static unsafe void DecResetSkipContext(ref MacroBlockD xd)
 762          {
 763              int i;
 764              for (i = 0; i < Constants.MaxMbPlane; i++)
 765              {
 766                  ref MacroBlockDPlane pd = ref xd.Plane[i];
 767                  MemoryUtil.Fill(pd.AboveContext.ToPointer(), (sbyte)0, pd.N4W);
 768                  MemoryUtil.Fill(pd.LeftContext.ToPointer(), (sbyte)0, pd.N4H);
 769              }
 770          }
 771  
 772          private static void SetPlaneN4(ref MacroBlockD xd, int bw, int bh, int bwl, int bhl)
 773          {
 774              int i;
 775              for (i = 0; i < Constants.MaxMbPlane; i++)
 776              {
 777                  xd.Plane[i].N4W = (ushort)((bw << 1) >> xd.Plane[i].SubsamplingX);
 778                  xd.Plane[i].N4H = (ushort)((bh << 1) >> xd.Plane[i].SubsamplingY);
 779                  xd.Plane[i].N4Wl = (byte)(bwl - xd.Plane[i].SubsamplingX);
 780                  xd.Plane[i].N4Hl = (byte)(bhl - xd.Plane[i].SubsamplingY);
 781              }
 782          }
 783  
 784          private static ref ModeInfo SetOffsets(
 785              ref Vp9Common cm,
 786              ref MacroBlockD xd,
 787              BlockSize bsize,
 788              int miRow,
 789              int miCol,
 790              int bw,
 791              int bh,
 792              int xMis,
 793              int yMis,
 794              int bwl,
 795              int bhl)
 796          {
 797              int offset = miRow * cm.MiStride + miCol;
 798              int x, y;
 799              ref TileInfo tile = ref xd.Tile;
 800  
 801              xd.Mi = cm.MiGridVisible.Slice(offset);
 802              xd.Mi[0] = new Ptr<ModeInfo>(ref cm.Mi[offset]);
 803              xd.Mi[0].Value.SbType = bsize;
 804              for (y = 0; y < yMis; ++y)
 805              {
 806                  for (x = y == 0 ? 1 : 0; x < xMis; ++x)
 807                  {
 808                      xd.Mi[y * cm.MiStride + x] = xd.Mi[0];
 809                  }
 810              }
 811  
 812              SetPlaneN4(ref xd, bw, bh, bwl, bhl);
 813  
 814              xd.SetSkipContext(miRow, miCol);
 815  
 816              // Distance of Mb to the various image edges. These are specified to 8th pel
 817              // as they are always compared to values that are in 1/8th pel units
 818              xd.SetMiRowCol(ref tile, miRow, bh, miCol, bw, cm.MiRows, cm.MiCols);
 819  
 820              ReconInter.SetupDstPlanes(ref xd.Plane, ref xd.CurBuf, miRow, miCol);
 821  
 822              return ref xd.Mi[0].Value;
 823          }
 824  
 825          private static void DecodeBlock(
 826              ref TileWorkerData twd,
 827              ref Vp9Common cm,
 828              int miRow,
 829              int miCol,
 830              BlockSize bsize,
 831              int bwl,
 832              int bhl)
 833          {
 834              bool less8x8 = bsize < BlockSize.Block8x8;
 835              int bw = 1 << (bwl - 1);
 836              int bh = 1 << (bhl - 1);
 837              int xMis = Math.Min(bw, cm.MiCols - miCol);
 838              int yMis = Math.Min(bh, cm.MiRows - miRow);
 839              ref Reader r = ref twd.BitReader;
 840              ref MacroBlockD xd = ref twd.Xd;
 841  
 842              ref ModeInfo mi = ref SetOffsets(ref cm, ref xd, bsize, miRow, miCol, bw, bh, xMis, yMis, bwl, bhl);
 843  
 844              if (bsize >= BlockSize.Block8x8 && (cm.SubsamplingX != 0 || cm.SubsamplingY != 0))
 845              {
 846                  BlockSize uvSubsize = Luts.SsSizeLookup[(int)bsize][cm.SubsamplingX][cm.SubsamplingY];
 847                  if (uvSubsize == BlockSize.BlockInvalid)
 848                  {
 849                      xd.ErrorInfo.Value.InternalError(CodecErr.CodecCorruptFrame, "Invalid block size.");
 850                  }
 851              }
 852  
 853              DecodeMv.ReadModeInfo(ref twd, ref cm, miRow, miCol, xMis, yMis);
 854  
 855              if (mi.Skip != 0)
 856              {
 857                  DecResetSkipContext(ref xd);
 858              }
 859  
 860              if (!mi.IsInterBlock())
 861              {
 862                  int plane;
 863                  for (plane = 0; plane < Constants.MaxMbPlane; ++plane)
 864                  {
 865                      ref MacroBlockDPlane pd = ref xd.Plane[plane];
 866                      TxSize txSize = plane != 0 ? mi.GetUvTxSize(ref pd) : mi.TxSize;
 867                      int num4x4W = pd.N4W;
 868                      int num4x4H = pd.N4H;
 869                      int step = 1 << (int)txSize;
 870                      int row, col;
 871                      int maxBlocksWide = num4x4W + (xd.MbToRightEdge >= 0 ? 0 : xd.MbToRightEdge >> (5 + pd.SubsamplingX));
 872                      int maxBlocksHigh = num4x4H + (xd.MbToBottomEdge >= 0 ? 0 : xd.MbToBottomEdge >> (5 + pd.SubsamplingY));
 873  
 874                      xd.MaxBlocksWide = (uint)(xd.MbToRightEdge >= 0 ? 0 : maxBlocksWide);
 875                      xd.MaxBlocksHigh = (uint)(xd.MbToBottomEdge >= 0 ? 0 : maxBlocksHigh);
 876  
 877                      for (row = 0; row < maxBlocksHigh; row += step)
 878                      {
 879                          for (col = 0; col < maxBlocksWide; col += step)
 880                          {
 881                              PredictAndReconstructIntraBlock(ref twd, ref mi, plane, row, col, txSize);
 882                          }
 883                      }
 884                  }
 885              }
 886              else
 887              {
 888                  // Prediction
 889                  DecBuildInterPredictorsSb(ref cm, ref xd, miRow, miCol);
 890  
 891                  // Reconstruction
 892                  if (mi.Skip == 0)
 893                  {
 894                      int eobtotal = 0;
 895                      int plane;
 896  
 897                      for (plane = 0; plane < Constants.MaxMbPlane; ++plane)
 898                      {
 899                          ref MacroBlockDPlane pd = ref xd.Plane[plane];
 900                          TxSize txSize = plane != 0 ? mi.GetUvTxSize(ref pd) : mi.TxSize;
 901                          int num4x4W = pd.N4W;
 902                          int num4x4H = pd.N4H;
 903                          int step = 1 << (int)txSize;
 904                          int row, col;
 905                          int maxBlocksWide = num4x4W + (xd.MbToRightEdge >= 0 ? 0 : xd.MbToRightEdge >> (5 + pd.SubsamplingX));
 906                          int maxBlocksHigh = num4x4H + (xd.MbToBottomEdge >= 0 ? 0 : xd.MbToBottomEdge >> (5 + pd.SubsamplingY));
 907  
 908                          xd.MaxBlocksWide = (uint)(xd.MbToRightEdge >= 0 ? 0 : maxBlocksWide);
 909                          xd.MaxBlocksHigh = (uint)(xd.MbToBottomEdge >= 0 ? 0 : maxBlocksHigh);
 910  
 911                          for (row = 0; row < maxBlocksHigh; row += step)
 912                          {
 913                              for (col = 0; col < maxBlocksWide; col += step)
 914                              {
 915                                  eobtotal += ReconstructInterBlock(ref twd, ref mi, plane, row, col, txSize);
 916                              }
 917                          }
 918                      }
 919  
 920                      if (!less8x8 && eobtotal == 0)
 921                      {
 922                          mi.Skip = 1; // Skip loopfilter
 923                      }
 924                  }
 925              }
 926  
 927              xd.Corrupted |= r.HasError();
 928  
 929              if (cm.Lf.FilterLevel != 0)
 930              {
 931                  LoopFilter.BuildMask(ref cm, ref mi, miRow, miCol, bw, bh);
 932              }
 933          }
 934  
 935          private static int DecPartitionPlaneContext(ref TileWorkerData twd, int miRow, int miCol, int bsl)
 936          {
 937              ref sbyte aboveCtx = ref twd.Xd.AboveSegContext[miCol];
 938              ref sbyte leftCtx = ref twd.Xd.LeftSegContext[miRow & Constants.MiMask];
 939              int above = (aboveCtx >> bsl) & 1, left = (leftCtx >> bsl) & 1;
 940  
 941              return (left * 2 + above) + bsl * Constants.PartitionPloffset;
 942          }
 943  
 944          private static void DecUpdatePartitionContext(
 945              ref TileWorkerData twd,
 946              int miRow,
 947              int miCol,
 948              BlockSize subsize,
 949              int bw)
 950          {
 951              Span<sbyte> aboveCtx = twd.Xd.AboveSegContext.Slice(miCol).AsSpan();
 952              Span<sbyte> leftCtx = MemoryMarshal.CreateSpan(ref twd.Xd.LeftSegContext[miRow & Constants.MiMask], 8 - (miRow & Constants.MiMask));
 953  
 954              // Update the partition context at the end notes. Set partition bits
 955              // of block sizes larger than the current one to be one, and partition
 956              // bits of smaller block sizes to be zero.
 957              aboveCtx[..bw].Fill(Luts.PartitionContextLookup[(int)subsize].Above);
 958              leftCtx[..bw].Fill(Luts.PartitionContextLookup[(int)subsize].Left);
 959          }
 960  
 961          private static PartitionType ReadPartition(
 962              ref TileWorkerData twd,
 963              int miRow,
 964              int miCol,
 965              int hasRows,
 966              int hasCols,
 967              int bsl)
 968          {
 969              int ctx = DecPartitionPlaneContext(ref twd, miRow, miCol, bsl);
 970              ReadOnlySpan<byte> probs = MemoryMarshal.CreateReadOnlySpan(ref twd.Xd.PartitionProbs[ctx][0], 3);
 971              PartitionType p;
 972              ref Reader r = ref twd.BitReader;
 973  
 974              if (hasRows != 0 && hasCols != 0)
 975              {
 976                  p = (PartitionType)r.ReadTree(Luts.Vp9PartitionTree, probs);
 977              }
 978              else if (hasRows == 0 && hasCols != 0)
 979              {
 980                  p = r.Read(probs[1]) != 0 ? PartitionType.PartitionSplit : PartitionType.PartitionHorz;
 981              }
 982              else if (hasRows != 0 && hasCols == 0)
 983              {
 984                  p = r.Read(probs[2]) != 0 ? PartitionType.PartitionSplit : PartitionType.PartitionVert;
 985              }
 986              else
 987              {
 988                  p = PartitionType.PartitionSplit;
 989              }
 990  
 991              if (!twd.Xd.Counts.IsNull)
 992              {
 993                  ++twd.Xd.Counts.Value.Partition[ctx][(int)p];
 994              }
 995  
 996              return p;
 997          }
 998  
 999          private static void DecodePartition(
1000              ref TileWorkerData twd,
1001              ref Vp9Common cm,
1002              int miRow,
1003              int miCol,
1004              BlockSize bsize,
1005              int n4x4L2)
1006          {
1007              int n8x8L2 = n4x4L2 - 1;
1008              int num8x8Wh = 1 << n8x8L2;
1009              int hbs = num8x8Wh >> 1;
1010              PartitionType partition;
1011              BlockSize subsize;
1012              bool hasRows = (miRow + hbs) < cm.MiRows;
1013              bool hasCols = (miCol + hbs) < cm.MiCols;
1014              ref MacroBlockD xd = ref twd.Xd;
1015  
1016              if (miRow >= cm.MiRows || miCol >= cm.MiCols)
1017              {
1018                  return;
1019              }
1020  
1021              partition = ReadPartition(ref twd, miRow, miCol, hasRows ? 1 : 0, hasCols ? 1 : 0, n8x8L2);
1022              subsize = Luts.SubsizeLookup[(int)partition][(int)bsize];
1023              if (hbs == 0)
1024              {
1025                  // Calculate bmode block dimensions (log 2)
1026                  xd.BmodeBlocksWl = (byte)(1 >> ((partition & PartitionType.PartitionVert) != 0 ? 1 : 0));
1027                  xd.BmodeBlocksHl = (byte)(1 >> ((partition & PartitionType.PartitionHorz) != 0 ? 1 : 0));
1028                  DecodeBlock(ref twd, ref cm, miRow, miCol, subsize, 1, 1);
1029              }
1030              else
1031              {
1032                  switch (partition)
1033                  {
1034                      case PartitionType.PartitionNone:
1035                          DecodeBlock(ref twd, ref cm, miRow, miCol, subsize, n4x4L2, n4x4L2);
1036                          break;
1037                      case PartitionType.PartitionHorz:
1038                          DecodeBlock(ref twd, ref cm, miRow, miCol, subsize, n4x4L2, n8x8L2);
1039                          if (hasRows)
1040                          {
1041                              DecodeBlock(ref twd, ref cm, miRow + hbs, miCol, subsize, n4x4L2, n8x8L2);
1042                          }
1043  
1044                          break;
1045                      case PartitionType.PartitionVert:
1046                          DecodeBlock(ref twd, ref cm, miRow, miCol, subsize, n8x8L2, n4x4L2);
1047                          if (hasCols)
1048                          {
1049                              DecodeBlock(ref twd, ref cm, miRow, miCol + hbs, subsize, n8x8L2, n4x4L2);
1050                          }
1051  
1052                          break;
1053                      case PartitionType.PartitionSplit:
1054                          DecodePartition(ref twd, ref cm, miRow, miCol, subsize, n8x8L2);
1055                          DecodePartition(ref twd, ref cm, miRow, miCol + hbs, subsize, n8x8L2);
1056                          DecodePartition(ref twd, ref cm, miRow + hbs, miCol, subsize, n8x8L2);
1057                          DecodePartition(ref twd, ref cm, miRow + hbs, miCol + hbs, subsize, n8x8L2);
1058                          break;
1059                      default:
1060                          Debug.Assert(false, "Invalid partition type");
1061                          break;
1062                  }
1063              }
1064  
1065              // Update partition context
1066              if (bsize >= BlockSize.Block8x8 && (bsize == BlockSize.Block8x8 || partition != PartitionType.PartitionSplit))
1067              {
1068                  DecUpdatePartitionContext(ref twd, miRow, miCol, subsize, num8x8Wh);
1069              }
1070          }
1071  
1072          private static void SetupTokenDecoder(
1073              ArrayPtr<byte> data,
1074              int readSize,
1075              ref InternalErrorInfo errorInfo,
1076              ref Reader r)
1077          {
1078              // Validate the calculated partition length. If the buffer described by the
1079              // partition can't be fully read then throw an error.
1080              if (!ReadIsValid(data, readSize))
1081              {
1082                  errorInfo.InternalError(CodecErr.CodecCorruptFrame, "Truncated packet or corrupt tile length");
1083              }
1084  
1085              if (r.Init(data, readSize))
1086              {
1087                  errorInfo.InternalError(CodecErr.CodecMemError, "Failed to allocate bool decoder 1");
1088              }
1089          }
1090  
1091          // Reads the next tile returning its size and adjusting '*data' accordingly
1092          // based on 'isLast'.
1093          private static void GetTileBuffer(
1094              bool isLast,
1095              ref InternalErrorInfo errorInfo,
1096              ref ArrayPtr<byte> data,
1097              ref TileBuffer buf)
1098          {
1099              int size;
1100  
1101              if (!isLast)
1102              {
1103                  if (!ReadIsValid(data, 4))
1104                  {
1105                      errorInfo.InternalError(CodecErr.CodecCorruptFrame, "Truncated packet or corrupt tile length");
1106                  }
1107  
1108                  size = BinaryPrimitives.ReadInt32BigEndian(data.AsSpan());
1109                  data = data.Slice(4);
1110  
1111                  if (size > data.Length)
1112                  {
1113                      errorInfo.InternalError(CodecErr.CodecCorruptFrame, "Truncated packet or corrupt tile size");
1114                  }
1115              }
1116              else
1117              {
1118                  size = data.Length;
1119              }
1120  
1121              buf.Data = data;
1122              buf.Size = size;
1123  
1124              data = data.Slice(size);
1125          }
1126  
1127          private static void GetTileBuffers(ref Vp9Common cm, ArrayPtr<byte> data, int tileCols, ref Array64<TileBuffer> tileBuffers)
1128          {
1129              int c;
1130  
1131              for (c = 0; c < tileCols; ++c)
1132              {
1133                  bool isLast = c == tileCols - 1;
1134                  ref TileBuffer buf = ref tileBuffers[c];
1135                  buf.Col = c;
1136                  GetTileBuffer(isLast, ref cm.Error, ref data, ref buf);
1137              }
1138          }
1139  
1140          private static void GetTileBuffers(
1141              ref Vp9Common cm,
1142              ArrayPtr<byte> data,
1143              int tileCols,
1144              int tileRows,
1145              ref Array4<Array64<TileBuffer>> tileBuffers)
1146          {
1147              int r, c;
1148  
1149              for (r = 0; r < tileRows; ++r)
1150              {
1151                  for (c = 0; c < tileCols; ++c)
1152                  {
1153                      bool isLast = (r == tileRows - 1) && (c == tileCols - 1);
1154                      ref TileBuffer buf = ref tileBuffers[r][c];
1155                      GetTileBuffer(isLast, ref cm.Error, ref data, ref buf);
1156                  }
1157              }
1158          }
1159  
1160          public static unsafe ArrayPtr<byte> DecodeTiles(ref Vp9Common cm, ArrayPtr<byte> data)
1161          {
1162              int alignedCols = TileInfo.MiColsAlignedToSb(cm.MiCols);
1163              int tileCols = 1 << cm.Log2TileCols;
1164              int tileRows = 1 << cm.Log2TileRows;
1165              Array4<Array64<TileBuffer>> tileBuffers = new();
1166              int tileRow, tileCol;
1167              int miRow, miCol;
1168  
1169              Debug.Assert(tileRows <= 4);
1170              Debug.Assert(tileCols <= (1 << 6));
1171  
1172              // Note: this memset assumes above_context[0], [1] and [2]
1173              // are allocated as part of the same buffer.
1174              MemoryUtil.Fill(cm.AboveContext.ToPointer(), (sbyte)0, Constants.MaxMbPlane * 2 * alignedCols);
1175              MemoryUtil.Fill(cm.AboveSegContext.ToPointer(), (sbyte)0, alignedCols);
1176  
1177              LoopFilter.ResetLfm(ref cm);
1178  
1179              GetTileBuffers(ref cm, data, tileCols, tileRows, ref tileBuffers);
1180              // Load all tile information into tile_data.
1181              for (tileRow = 0; tileRow < tileRows; ++tileRow)
1182              {
1183                  for (tileCol = 0; tileCol < tileCols; ++tileCol)
1184                  {
1185                      ref TileBuffer buf = ref tileBuffers[tileRow][tileCol];
1186                      ref TileWorkerData tileData = ref cm.TileWorkerData[tileCols * tileRow + tileCol];
1187                      tileData.Xd = cm.Mb;
1188                      tileData.Xd.Corrupted = false;
1189                      tileData.Xd.Counts = cm.Counts;
1190                      tileData.Dqcoeff = new Array32<Array32<int>>();
1191                      tileData.Xd.Tile.Init(ref cm, tileRow, tileCol);
1192                      SetupTokenDecoder(buf.Data, buf.Size, ref cm.Error, ref tileData.BitReader);
1193                      cm.InitMacroBlockD(ref tileData.Xd, new ArrayPtr<int>(ref tileData.Dqcoeff[0][0], 32 * 32));
1194                  }
1195              }
1196  
1197              for (tileRow = 0; tileRow < tileRows; ++tileRow)
1198              {
1199                  TileInfo tile = new();
1200                  tile.SetRow(ref cm, tileRow);
1201                  for (miRow = tile.MiRowStart; miRow < tile.MiRowEnd; miRow += Constants.MiBlockSize)
1202                  {
1203                      for (tileCol = 0; tileCol < tileCols; ++tileCol)
1204                      {
1205                          int col = tileCol;
1206                          ref TileWorkerData tileData = ref cm.TileWorkerData[tileCols * tileRow + col];
1207                          tile.SetCol(ref cm, col);
1208                          tileData.Xd.LeftContext = new Array3<Array16<sbyte>>();
1209                          tileData.Xd.LeftSegContext = new Array8<sbyte>();
1210                          for (miCol = tile.MiColStart; miCol < tile.MiColEnd; miCol += Constants.MiBlockSize)
1211                          {
1212                              DecodePartition(ref tileData, ref cm, miRow, miCol, BlockSize.Block64x64, 4);
1213                          }
1214                          cm.Mb.Corrupted |= tileData.Xd.Corrupted;
1215                          if (cm.Mb.Corrupted)
1216                          {
1217                              cm.Error.InternalError(CodecErr.CodecCorruptFrame, "Failed to decode tile data");
1218                          }
1219                      }
1220                  }
1221              }
1222  
1223              // Get last tile data.
1224              return cm.TileWorkerData[tileCols * tileRows - 1].BitReader.FindEnd();
1225          }
1226  
1227          private static bool DecodeTileCol(ref TileWorkerData tileData, ref Vp9Common cm, ref Array64<TileBuffer> tileBuffers)
1228          {
1229              ref TileInfo tile = ref tileData.Xd.Tile;
1230              int finalCol = (1 << cm.Log2TileCols) - 1;
1231              ArrayPtr<byte> bitReaderEnd = ArrayPtr<byte>.Null;
1232  
1233              int n = tileData.BufStart;
1234  
1235              tileData.Xd.Corrupted = false;
1236  
1237              do
1238              {
1239                  ref TileBuffer buf = ref tileBuffers[n];
1240  
1241                  Debug.Assert(cm.Log2TileRows == 0);
1242                  tileData.Dqcoeff = new Array32<Array32<int>>();
1243                  tile.Init(ref cm, 0, buf.Col);
1244                  SetupTokenDecoder(buf.Data, buf.Size, ref tileData.ErrorInfo, ref tileData.BitReader);
1245                  cm.InitMacroBlockD(ref tileData.Xd, new ArrayPtr<int>(ref tileData.Dqcoeff[0][0], 32 * 32));
1246                  tileData.Xd.ErrorInfo = new Ptr<InternalErrorInfo>(ref tileData.ErrorInfo);
1247  
1248                  for (int miRow = tile.MiRowStart; miRow < tile.MiRowEnd; miRow += Constants.MiBlockSize)
1249                  {
1250                      tileData.Xd.LeftContext = new Array3<Array16<sbyte>>();
1251                      tileData.Xd.LeftSegContext = new Array8<sbyte>();
1252                      for (int miCol = tile.MiColStart; miCol < tile.MiColEnd; miCol += Constants.MiBlockSize)
1253                      {
1254                          DecodePartition(ref tileData, ref cm, miRow, miCol, BlockSize.Block64x64, 4);
1255                      }
1256                  }
1257  
1258                  if (buf.Col == finalCol)
1259                  {
1260                      bitReaderEnd = tileData.BitReader.FindEnd();
1261                  }
1262              } while (!tileData.Xd.Corrupted && ++n <= tileData.BufEnd);
1263  
1264              tileData.DataEnd = bitReaderEnd;
1265  
1266              return !tileData.Xd.Corrupted;
1267          }
1268  
1269          public static ArrayPtr<byte> DecodeTilesMt(ref Vp9Common cm, ArrayPtr<byte> data, int maxThreads)
1270          {
1271              ArrayPtr<byte> bitReaderEnd = ArrayPtr<byte>.Null;
1272  
1273              int tileCols = 1 << cm.Log2TileCols;
1274              int tileRows = 1 << cm.Log2TileRows;
1275              int totalTiles = tileCols * tileRows;
1276              int numWorkers = Math.Min(maxThreads, tileCols);
1277              int n;
1278  
1279              Debug.Assert(tileCols <= (1 << 6));
1280              Debug.Assert(tileRows == 1);
1281  
1282              cm.AboveContext.AsSpan().Clear();
1283              cm.AboveSegContext.AsSpan().Clear();
1284  
1285              for (n = 0; n < numWorkers; ++n)
1286              {
1287                  ref TileWorkerData tileData = ref cm.TileWorkerData[n + totalTiles];
1288  
1289                  tileData.Xd = cm.Mb;
1290                  tileData.Xd.Counts = new Ptr<Vp9BackwardUpdates>(ref tileData.Counts);
1291                  tileData.Counts = new Vp9BackwardUpdates();
1292              }
1293  
1294              Array64<TileBuffer> tileBuffers = new();
1295  
1296              GetTileBuffers(ref cm, data, tileCols, ref tileBuffers);
1297  
1298              tileBuffers.AsSpan()[..tileCols].Sort(CompareTileBuffers);
1299  
1300              if (numWorkers == tileCols)
1301              {
1302                  TileBuffer largest = tileBuffers[0];
1303                  Span<TileBuffer> buffers = tileBuffers.AsSpan();
1304                  buffers[1..].CopyTo(buffers[..(tileBuffers.Length - 1)]);
1305                  tileBuffers[tileCols - 1] = largest;
1306              }
1307              else
1308              {
1309                  int start = 0, end = tileCols - 2;
1310                  TileBuffer tmp;
1311  
1312                  // Interleave the tiles to distribute the load between threads, assuming a
1313                  // larger tile implies it is more difficult to decode.
1314                  while (start < end)
1315                  {
1316                      tmp = tileBuffers[start];
1317                      tileBuffers[start] = tileBuffers[end];
1318                      tileBuffers[end] = tmp;
1319                      start += 2;
1320                      end -= 2;
1321                  }
1322              }
1323  
1324              int baseVal = tileCols / numWorkers;
1325              int remain = tileCols % numWorkers;
1326              int bufStart = 0;
1327  
1328              for (n = 0; n < numWorkers; ++n)
1329              {
1330                  int count = baseVal + (remain + n) / numWorkers;
1331                  ref TileWorkerData tileData = ref cm.TileWorkerData[n + totalTiles];
1332  
1333                  tileData.BufStart = bufStart;
1334                  tileData.BufEnd = bufStart + count - 1;
1335                  tileData.DataEnd = data.Slice(data.Length);
1336                  bufStart += count;
1337              }
1338  
1339              Ptr<Vp9Common> cmPtr = new(ref cm);
1340  
1341              Parallel.For(0, numWorkers, n =>
1342              {
1343                  ref TileWorkerData tileData = ref cmPtr.Value.TileWorkerData[n + totalTiles];
1344  
1345                  if (!DecodeTileCol(ref tileData, ref cmPtr.Value, ref tileBuffers))
1346                  {
1347                      cmPtr.Value.Mb.Corrupted = true;
1348                  }
1349              });
1350  
1351              for (; n > 0; --n)
1352              {
1353                  if (bitReaderEnd.IsNull)
1354                  {
1355                      ref TileWorkerData tileData = ref cm.TileWorkerData[n - 1 + totalTiles];
1356                      bitReaderEnd = tileData.DataEnd;
1357                  }
1358              }
1359  
1360              for (n = 0; n < numWorkers; ++n)
1361              {
1362                  ref TileWorkerData tileData = ref cm.TileWorkerData[n + totalTiles];
1363                  AccumulateFrameCounts(ref cm.Counts.Value, ref tileData.Counts);
1364              }
1365  
1366              Debug.Assert(!bitReaderEnd.IsNull || cm.Mb.Corrupted);
1367  
1368              return bitReaderEnd;
1369          }
1370  
1371          private static int CompareTileBuffers(TileBuffer bufA, TileBuffer bufB)
1372          {
1373              return (bufA.Size < bufB.Size ? 1 : 0) - (bufA.Size > bufB.Size ? 1 : 0);
1374          }
1375  
1376          private static void AccumulateFrameCounts(ref Vp9BackwardUpdates accum, ref Vp9BackwardUpdates counts)
1377          {
1378              Span<uint> a = MemoryMarshal.Cast<Vp9BackwardUpdates, uint>(MemoryMarshal.CreateSpan(ref accum, 1));
1379              Span<uint> c = MemoryMarshal.Cast<Vp9BackwardUpdates, uint>(MemoryMarshal.CreateSpan(ref counts, 1));
1380  
1381              for (int i = 0; i < a.Length; i++)
1382              {
1383                  a[i] += c[i];
1384              }
1385          }
1386      }
1387  }