blob: c25a8d02a56cf85c2029485da1f3febcd60e8708 [file]
// Copyright 2026 The Wuffs Authors.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// SPDX-License-Identifier: Apache-2.0 OR MIT
// --------
// Reconstructing YUV samples involves adding predictions (based on previous
// YUV samples, in macroblocks one column left or one column above) to
// residuals (decoded from the bitstream, dequantized and IDCT'ed).
//
// There are 16×16 = 256 luma and 2×8×8 = 128 chroma samples per macroblock.
// The decoder.yuv_cache field is a bit larger than that, being an array[26]
// array[32] base.u8. Here's the layout.
//
// 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F
// . . . . . . . a b b b b b b b b b b b b b b b b c c c c . . . . 00
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 01
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 02
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 03
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y c c c c . . . . 04
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 05
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 06
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 07
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y c c c c . . . . 08
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 09
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 0A
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 0B
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y c c c c . . . . 0C
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 0D
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 0E
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 0F
// . . . . . . . d Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . 10
// . . . . . . . e f f f f f f f f . . . . . . . g h h h h h h h h 11
// . . . . . . . i U U U U U U U U . . . . . . . j V V V V V V V V 12
// . . . . . . . i U U U U U U U U . . . . . . . j V V V V V V V V 13
// . . . . . . . i U U U U U U U U . . . . . . . j V V V V V V V V 14
// . . . . . . . i U U U U U U U U . . . . . . . j V V V V V V V V 15
// . . . . . . . i U U U U U U U U . . . . . . . j V V V V V V V V 16
// . . . . . . . i U U U U U U U U . . . . . . . j V V V V V V V V 17
// . . . . . . . i U U U U U U U U . . . . . . . j V V V V V V V V 18
// . . . . . . . i U U U U U U U U . . . . . . . j V V V V V V V V 19
//
// Y, U and V are this macroblock's luma and chroma values.
//
// The Y predictions (per 4×4 subblock) depend on the row above's Y values
// (either abc or dYc) and the column left's Y values (ad or bY).
//
// The U predictions (per 8×8 block) depend on the row above's U values (ef)
// and the column left's U values (i).
//
// The V predictions (per 8×8 block) depend on the row above's V values (gh)
// and the column left's V values (j).
//
// For top-row (mby == 0) macroblocks, abcefgh is initialized to 0x7F. For
// other macroblocks, these are copied from the bottom-most row of the
// macroblock above (before filtering, for I-Frames), from the workbuf.
//
// The c values are then duplicated from row 0x00 to rows 0x04, 0x08 and 0x0C.
//
// For left-column (mbx == 0) macroblocks, adeigj are initialized to 0x81 (but
// aeg is 0x7F if mby is also 0). For other macroblocks, these are copied from
// the right-most column of the macroblock to the left (before filtering, for
// I-Frames), from the yuv_cache to itself, shifted by 8 or 16 columns.
pri func decoder.reconstruct!(
workbuf: slice base.u8,
mbx: base.u32[..= 0x3FF],
mby: base.u32[..= 0x3FF],
luma_mode: base.u32[..= 4],
chroma_mode: base.u32[..= 3]) {
var mode : base.u32[..= 12]
var b : base.u32
var mask : base.u32
this.prepare_yuv_cache!(workbuf: args.workbuf, mbx: args.mbx, mby: args.mby)
if args.luma_mode < 4 {
mode = this.substitute_dc_top_left(mode: args.luma_mode, mbx: args.mbx, mby: args.mby)
this.predict_y16!(mode: mode)
}
b = 0
while b < 16 {
if args.luma_mode >= 4 {
this.predict_y4!(b: b)
}
mask = (1 as base.u32) << (b + 0x00)
if (this.mb_ac_nz & mask) <> 0 {
this.inverse_dct_full!(
cachex: ((b & 0x3) * 4) + 0x08,
cachey: ((b & 0xC) * 1) + 0x01,
b: (b + 0x00))
} else if (this.mb_dc_nz & mask) <> 0 {
this.inverse_dct_dc_only!(
cachex: ((b & 0x3) * 4) + 0x08,
cachey: ((b & 0xC) * 1) + 0x01,
b: (b + 0x00))
}
b += 1
}
mode = this.substitute_dc_top_left(mode: args.chroma_mode, mbx: args.mbx, mby: args.mby)
this.predict_uv8!(b: 0, mode: mode)
if (this.mb_ac_nz & 0x0F_0000) <> 0 {
b = 0
while b < 4 {
this.inverse_dct_full!(
cachex: ((b & 0x1) * 4) + 0x08,
cachey: ((b & 0x2) * 2) + 0x12,
b: (b + 0x10))
b += 1
}
} else if (this.mb_dc_nz & 0x0F_0000) <> 0 {
b = 0
while b < 4 {
this.inverse_dct_dc_only!(
cachex: ((b & 0x1) * 4) + 0x08,
cachey: ((b & 0x2) * 2) + 0x12,
b: (b + 0x10))
b += 1
}
}
this.predict_uv8!(b: 1, mode: mode)
if (this.mb_ac_nz & 0xF0_0000) <> 0 {
b = 0
while b < 4 {
this.inverse_dct_full!(
cachex: ((b & 0x1) * 4) + 0x18,
cachey: ((b & 0x2) * 2) + 0x12,
b: (b + 0x14))
b += 1
}
} else if (this.mb_dc_nz & 0xF0_0000) <> 0 {
b = 0
while b < 4 {
this.inverse_dct_dc_only!(
cachex: ((b & 0x1) * 4) + 0x18,
cachey: ((b & 0x2) * 2) + 0x12,
b: (b + 0x14))
b += 1
}
}
}
pri func decoder.substitute_dc_top_left(
mode: base.u32[..= 3],
mbx: base.u32[..= 0x3FF],
mby: base.u32[..= 0x3FF]) base.u32[..= 12] {
if args.mode == 0 { // DC.
if args.mbx == 0 {
if args.mby == 0 {
return 12 // DCTopLeft.
} else {
return 11 // DCLeft.
}
} else if args.mby == 0 {
return 10 // DCTop.
}
}
return args.mode
}
pri func decoder.prepare_yuv_cache!(
workbuf: roslice base.u8,
mbx: base.u32[..= 0x3FF],
mby: base.u32[..= 0x3FF]) {
var i : base.u32
var j : base.u32
var offset : base.u64[..= 0x17FE_FFF8]
var o : base.u64
// For abcdefghij comments, see the layout at the top of this file.
if args.mbx <= 0 {
// Initialize adeigj to 0x81.
j = 0x00
while j < 0x11 {
this.yuv_cache[j][0x07] = 0x81
j += 1
}
while j < 0x1A {
this.yuv_cache[j][0x07] = 0x81
this.yuv_cache[j][0x17] = 0x81
j += 1
}
} else {
// Copy from the previous (left) macroblock, in yuv_cache, to adeigj.
j = 0x00
while j < 0x11 {
this.yuv_cache[j][0x07] = this.yuv_cache[j][0x17]
j += 1
}
while j < 0x1A {
this.yuv_cache[j][0x07] = this.yuv_cache[j][0x0F]
this.yuv_cache[j][0x17] = this.yuv_cache[j][0x1F]
j += 1
}
}
if args.mby <= 0 {
// Initialize abcefgh to 0x7F.
i = 0x00
while i < 0x20 {
this.yuv_cache[0x00][i] = 0x7F
i += 1
}
i = 0x00
while i < 0x20 {
this.yuv_cache[0x11][i] = 0x7F
i += 1
}
} else {
// Copy from the previous (above) macroblock, in args.workbuf, to bcfh.
offset = 0 +
(((((args.mby * 16) - 1) * this.workbuf_yuv_y_stride) + (args.mbx * 16)) as base.u64)
i = 0x00
while i < 0x10,
inv args.mby > 0,
{
o = offset + (i as base.u64)
if o < args.workbuf.length() {
this.yuv_cache[0x00][0x08 + i] = args.workbuf[o]
}
i += 1
}
if (args.mbx + 1) < this.mbw {
while i < 0x14,
inv args.mby > 0,
{
o = offset + (i as base.u64)
if o < args.workbuf.length() {
this.yuv_cache[0x00][0x08 + i] = args.workbuf[o]
}
i += 1
}
} else {
while i < 0x14,
inv args.mby > 0,
{
o = offset + (0x0F as base.u64)
if o < args.workbuf.length() {
this.yuv_cache[0x00][0x08 + i] = args.workbuf[o]
}
i += 1
}
}
offset = this.workbuf_yuv_y_end +
(((((args.mby * 8) - 1) * this.workbuf_yuv_uv_stride) + (args.mbx * 8)) as base.u64)
i = 0x00
while i < 0x08,
inv args.mby > 0,
{
o = offset + (i as base.u64)
if o < args.workbuf.length() {
this.yuv_cache[0x11][0x08 + i] = args.workbuf[o]
}
i += 1
}
offset = this.workbuf_yuv_u_end +
(((((args.mby * 8) - 1) * this.workbuf_yuv_uv_stride) + (args.mbx * 8)) as base.u64)
i = 0x00
while i < 0x08,
inv args.mby > 0,
{
o = offset + (i as base.u64)
if o < args.workbuf.length() {
this.yuv_cache[0x11][0x18 + i] = args.workbuf[o]
}
i += 1
}
}
// Duplicate the c values.
j = 0x04
while j < 0x10 {
this.yuv_cache[j][0x18] = this.yuv_cache[0][0x18]
this.yuv_cache[j][0x19] = this.yuv_cache[0][0x19]
this.yuv_cache[j][0x1A] = this.yuv_cache[0][0x1A]
this.yuv_cache[j][0x1B] = this.yuv_cache[0][0x1B]
j += 4
}
}