Files
rdplib/plugin/rdpgfx/rfx_progressive.go
T

1322 lines
38 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package rdpgfx
// RFX Progressive Codec decoder (MS-RDPEGFX 2.2.4), algorithm aligned with
// FreeRDP libfreerdp/codec/progressive.c. Handles RDPGFX_CODECID_CAPROGRESSIVE
// (0x0009) in WIRE_TO_SURFACE_PDU_1/2.
//
// Key points that differ from the plain RemoteFX codec (MS-RDPRFX):
// - The 5-byte quant table uses the RDPEGFX band order (LL3,HL3,LH3,HH3,
// HL2,LH2,HH2,HL1,LH1,HH1), which swaps LH3/HL3, HL2/LH2 and HL1/LH1
// compared to the RDPRFX order parsed by parseRfxQuant.
// - The effective dequant shift per band is (plain + progressive quant) - 1.
// - TILE_FIRST/TILE_SIMPLE carry the first pass of a tile; TILE_UPGRADE
// carries incremental bit-plane refinements using an SRL+RAW dual bit
// stream applied to the cached coefficients in extrapolate layout.
// - Region flag RFX_DWT_REDUCE_EXTRAPOLATE switches the IDWT to the
// extrapolate variant with irregular band sizes.
import (
"encoding/binary"
"fmt"
"log/slog"
"runtime"
"sync"
)
// Progressive block types (different from non-progressive WBT_* at same values!)
const (
progWBTSync = 0xCCC0
progWBTFrameBegin = 0xCCC1
progWBTFrameEnd = 0xCCC2
progWBTContext = 0xCCC3
progWBTRegion = 0xCCC4
progWBTTileSimple = 0xCCC5
progWBTTileFirst = 0xCCC6
progWBTTileUpgrade = 0xCCC7
)
// Region/context flags (MS-RDPEGFX progressive.h)
const (
progFlagSubbandDiffing = 0x01 // PROGRESSIVE_BLOCK_CONTEXT::flags
progFlagDWTReduceExtrapolate = 0x01 // PROGRESSIVE_BLOCK_REGION::flags
progFlagTileDifference = 0x01 // tile flags
)
const rfxTileSize = 64
// rfxQuant holds the 10 quantization values in RDPRFX order (standard codec).
type rfxQuant struct {
LL3, LH3, HL3, HH3 uint8
LH2, HL2, HH2 uint8
LH1, HL1, HH1 uint8
}
// parseRfxQuant parses a standard (MS-RDPRFX) 5-byte quant table.
// Used by the plain RemoteFX tileset decoder in rfx.go.
func parseRfxQuant(data []byte) rfxQuant {
return rfxQuant{
LL3: data[0] & 0x0F,
LH3: data[0] >> 4,
HL3: data[1] & 0x0F,
HH3: data[1] >> 4,
LH2: data[2] & 0x0F,
HL2: data[2] >> 4,
HH2: data[3] & 0x0F,
LH1: data[3] >> 4,
HL1: data[4] & 0x0F,
HH1: data[4] >> 4,
}
}
// progBandQuant holds the 10 band quant values in RDPEGFX order
// (RFX_COMPONENT_CODEC_QUANT in FreeRDP progressive.h).
type progBandQuant struct {
LL3, HL3, LH3, HH3 uint8
HL2, LH2, HH2 uint8
HL1, LH1, HH1 uint8
}
// parseProgBandQuant reads one 5-byte progressive quant component.
func parseProgBandQuant(data []byte) progBandQuant {
return progBandQuant{
LL3: data[0] & 0x0F,
HL3: data[0] >> 4,
LH3: data[1] & 0x0F,
HH3: data[1] >> 4,
HL2: data[2] & 0x0F,
LH2: data[2] >> 4,
HH2: data[3] & 0x0F,
HL1: data[3] >> 4,
LH1: data[4] & 0x0F,
HH1: data[4] >> 4,
}
}
// progCodecQuant is one 16-byte RFX_PROGRESSIVE_CODEC_QUANT entry.
type progCodecQuant struct {
quality byte
y, cb, cr progBandQuant
}
// progAdd returns a+b band-wise.
func progAdd(a, b progBandQuant) progBandQuant {
return progBandQuant{
a.LL3 + b.LL3, a.HL3 + b.HL3, a.LH3 + b.LH3, a.HH3 + b.HH3,
a.HL2 + b.HL2, a.LH2 + b.LH2, a.HH2 + b.HH2,
a.HL1 + b.HL1, a.LH1 + b.LH1, a.HH1 + b.HH1,
}
}
// progSub returns a-b band-wise, ok=false when any band would underflow.
func progSub(a, b progBandQuant) (progBandQuant, bool) {
if a.LL3 < b.LL3 || a.HL3 < b.HL3 || a.LH3 < b.LH3 || a.HH3 < b.HH3 ||
a.HL2 < b.HL2 || a.LH2 < b.LH2 || a.HH2 < b.HH2 ||
a.HL1 < b.HL1 || a.LH1 < b.LH1 || a.HH1 < b.HH1 {
return progBandQuant{}, false
}
return progBandQuant{
a.LL3 - b.LL3, a.HL3 - b.HL3, a.LH3 - b.LH3, a.HH3 - b.HH3,
a.HL2 - b.HL2, a.LH2 - b.LH2, a.HH2 - b.HH2,
a.HL1 - b.HL1, a.LH1 - b.LH1, a.HH1 - b.HH1,
}, true
}
// progLSub subtracts v from every band, ok=false on underflow or out-of-range v.
func progLSub(a progBandQuant, v int) (progBandQuant, bool) {
if v < 0 || v > 255 {
return progBandQuant{}, false
}
return progSub(a, progBandQuant{
LL3: uint8(v), HL3: uint8(v), LH3: uint8(v), HH3: uint8(v),
HL2: uint8(v), LH2: uint8(v), HH2: uint8(v),
HL1: uint8(v), LH1: uint8(v), HH1: uint8(v),
})
}
// progTileState caches the progressive state of one tile (per component:
// current coefficients and first-pass sign array).
type progTileState struct {
pass int
// per component: 0=Y, 1=Cb, 2=Cr
current [3]*coeffArr // accumulated band coefficients (extrapolate layout)
sign [3]*coeffArr // raw first-pass RLGR output (signs for upgrades)
yBitPos progBandQuant
cbBitPos progBandQuant
crBitPos progBandQuant
}
type rfxTileCoeffs = progTileState
type rfxProgTileWork struct {
tileType uint16
data []byte
}
type progRegionCtx struct {
quantVals []progBandQuant // numQuant entries (plain quants)
quantProgVals []progCodecQuant // numProgQuant entries
numQuant int
numProgQuant int
flags byte // RFX_DWT_REDUCE_EXTRAPOLATE
extrapolate bool
rects []rfxRect // 脏矩形:瓦片渲染必须裁剪到其并集内
}
type rfxProgressiveDecoder struct {
mu sync.RWMutex
tileCache map[uint32]*progTileState // key: yIdx<<16 | xIdx
rectsBuf []rfxRect
quantsBuf []progBandQuant
progQuantsBuf []progCodecQuant
tilesBuf []rfxProgTileWork
contextFlags byte // PROGRESSIVE_BLOCK_CONTEXT flags
logged [32]bool
}
// logOnce 每类失败只打第一条日志(复用 clearCodecCtx 的槽位思路)
func (d *rfxProgressiveDecoder) logOnce(slot int, msg string, args ...any) {
d.mu.Lock()
defer d.mu.Unlock()
if d.logged[slot] {
return
}
d.logged[slot] = true
slog.Warn("progressive:"+msg, args...)
}
func newRfxProgressiveDecoder() *rfxProgressiveDecoder {
return &rfxProgressiveDecoder{
tileCache: make(map[uint32]*progTileState),
}
}
// Reset discards the tile coefficient cache. Call this whenever the server
// starts a new progressive sequence (e.g. on RESET_GRAPHICS).
func (d *rfxProgressiveDecoder) Reset() {
d.mu.Lock()
old := d.tileCache
d.tileCache = make(map[uint32]*progTileState)
d.mu.Unlock()
for _, ts := range old {
progFreeTileState(ts)
}
}
func progFreeTileState(ts *progTileState) {
if ts == nil {
return
}
for c := 0; c < 3; c++ {
if ts.current[c] != nil {
coeffPool.Put(ts.current[c])
}
if ts.sign[c] != nil {
coeffPool.Put(ts.sign[c])
}
}
}
// rfxRect represents a rectangle of decoded tiles.
type rfxRect struct {
x, y, w, h int
}
// Decode processes RFX Progressive codec data, rendering tiles onto the
// provided surface buffer. Returns the bounding rectangles of decoded regions.
func (d *rfxProgressiveDecoder) Decode(data []byte, surfData []byte, width, height int) []rfxRect {
var rects []rfxRect
offset := 0
for offset+6 <= len(data) {
blockType := binary.LittleEndian.Uint16(data[offset:])
blockLen := binary.LittleEndian.Uint32(data[offset+2:])
if blockLen < 6 || offset+int(blockLen) > len(data) {
break
}
blockData := data[offset+6 : offset+int(blockLen)]
switch blockType {
case progWBTSync:
// magic + version — nothing to do.
case progWBTFrameBegin, progWBTFrameEnd:
// frame bookkeeping — nothing to do.
case progWBTContext:
// ctxId(1) + tileSize(2) + flags(1)
if len(blockData) >= 4 {
d.contextFlags = blockData[3]
}
case progWBTRegion:
regionRects, _ := d.parseRegion(blockData, surfData, width, height)
rects = append(rects, regionRects...)
default:
slog.Debug("RFX: unknown progressive block type", "type", blockType)
}
offset += int(blockLen)
}
return rects
}
// parseRegion extracts rects and quant tables from a PROGRESSIVE_WBT_REGION block,
// and decodes the tile sub-blocks embedded within it onto the surface.
func (d *rfxProgressiveDecoder) parseRegion(data []byte, surfData []byte, outW, outH int) ([]rfxRect, []progBandQuant) {
if len(data) < 12 {
return nil, nil
}
// tileSize := data[0]
numRects := int(binary.LittleEndian.Uint16(data[1:]))
numQuant := int(data[3])
numProgQuant := int(data[4])
flags := data[5]
numTiles := int(binary.LittleEndian.Uint16(data[6:]))
// tileDataSize := binary.LittleEndian.Uint32(data[8:])
offset := 12
extrapolate := flags&progFlagDWTReduceExtrapolate != 0
region := progRegionCtx{
numQuant: numQuant,
numProgQuant: numProgQuant,
flags: flags,
extrapolate: extrapolate,
}
// Parse rects (8 bytes each: x, y, width, height as uint16)
if cap(d.rectsBuf) >= numRects {
d.rectsBuf = d.rectsBuf[:numRects]
} else {
d.rectsBuf = make([]rfxRect, numRects)
}
rects := d.rectsBuf
for i := range numRects {
if offset+8 > len(data) {
return nil, nil
}
rx := int(binary.LittleEndian.Uint16(data[offset:]))
ry := int(binary.LittleEndian.Uint16(data[offset+2:]))
rw := int(binary.LittleEndian.Uint16(data[offset+4:]))
rh := int(binary.LittleEndian.Uint16(data[offset+6:]))
rects[i] = rfxRect{x: rx, y: ry, w: rw, h: rh}
offset += 8
}
region.rects = rects
// Parse plain quant values (5 bytes each, RDPEGFX band order)
if cap(d.quantsBuf) >= numQuant {
d.quantsBuf = d.quantsBuf[:numQuant]
} else {
d.quantsBuf = make([]progBandQuant, numQuant)
}
quants := d.quantsBuf
for i := range numQuant {
if offset+5 > len(data) {
return nil, nil
}
quants[i] = parseProgBandQuant(data[offset:])
offset += 5
}
region.quantVals = quants
// Parse progressive quant values (16 bytes each: quality + 3 components)
if cap(d.progQuantsBuf) >= numProgQuant {
d.progQuantsBuf = d.progQuantsBuf[:numProgQuant]
} else {
d.progQuantsBuf = make([]progCodecQuant, numProgQuant)
}
progQuants := d.progQuantsBuf
for i := range numProgQuant {
if offset+16 > len(data) {
return nil, nil
}
progQuants[i].quality = data[offset]
progQuants[i].y = parseProgBandQuant(data[offset+1:])
progQuants[i].cb = parseProgBandQuant(data[offset+6:])
progQuants[i].cr = parseProgBandQuant(data[offset+11:])
offset += 16
}
region.quantProgVals = progQuants
// Collect all decodable tiles before dispatching, so we can parallelise
// when there are enough to amortise goroutine overhead (same threshold as
// non-progressive decodeTileset in rfx.go).
if cap(d.tilesBuf) >= numTiles {
d.tilesBuf = d.tilesBuf[:0]
} else {
d.tilesBuf = make([]rfxProgTileWork, 0, numTiles)
}
tiles := d.tilesBuf
for offset+6 <= len(data) {
tileType := binary.LittleEndian.Uint16(data[offset:])
tileLen := binary.LittleEndian.Uint32(data[offset+2:])
if tileLen < 6 || offset+int(tileLen) > len(data) {
break
}
switch tileType {
case progWBTTileSimple, progWBTTileFirst, progWBTTileUpgrade:
tiles = append(tiles, rfxProgTileWork{tileType: tileType, data: data[offset+6 : offset+int(tileLen)]})
default:
slog.Debug("RFX: unknown progressive tile type", "type", tileType)
}
offset += int(tileLen)
}
d.tilesBuf = tiles
const parallelTileThreshold = 12
decodeTile := func(tw rfxProgTileWork, parallel bool) {
switch tw.tileType {
case progWBTTileSimple:
d.decodeTileSimple(tw.data, &region, surfData, outW, outH, parallel)
case progWBTTileFirst:
d.decodeTileFirst(tw.data, &region, surfData, outW, outH, parallel)
case progWBTTileUpgrade:
d.decodeTileUpgrade(tw.data, &region, surfData, outW, outH, parallel)
}
}
if len(tiles) >= parallelTileThreshold {
workers := min(runtime.NumCPU(), len(tiles))
ch := make(chan rfxProgTileWork, len(tiles))
for _, tw := range tiles {
ch <- tw
}
close(ch)
var wg sync.WaitGroup
for range workers {
wg.Go(func() {
defer func() {
if r := recover(); r != nil {
slog.Error("RFX progressive: tile decode panic", "err", r)
}
}()
for tw := range ch {
decodeTile(tw, false)
}
})
}
wg.Wait()
} else {
for _, tw := range tiles {
decodeTile(tw, true)
}
}
return rects, quants
}
// safeSlice returns data[offset:offset+length] when fully in range, else nil.
// Shared with the standard RemoteFX tileset decoder in rfx.go.
func safeSlice(data []byte, offset, length int) []byte {
if length <= 0 || offset < 0 || offset+length > len(data) {
return nil
}
return data[offset : offset+length]
}
// progTileHeader is the common prefix of all tile block headers.
type progTileHeader struct {
quantIdxY byte
quantIdxCb byte
quantIdxCr byte
xIdx int
yIdx int
flags byte
quality byte // 0xFF = full quality (quantProgValFull)
// simple/first
yLen, cbLen, crLen, tailLen int
// upgrade
ySrlLen, yRawLen int
cbSrlLen, cbRawLen int
crSrlLen, crRawLen int
}
// getProgTileState returns the cache entry for the tile, allocating a fresh
// state (releasing the old one) for a new SIMPLE/FIRST pass.
func (d *rfxProgressiveDecoder) getProgTileState(key uint32, firstPass bool) *progTileState {
d.mu.Lock()
defer d.mu.Unlock()
// 注意:FIRST pass 到达时不得销毁已有 state——服务器可能对同一瓦片
// 连续发送多个 FIRST(如 RFX_TILE_DIFFERENCE),其差分系数基于客户端
// 应持有的参考状态(Ref);重置会导致差分失去基准而产生花屏块。
ts := d.tileCache[key]
_ = firstPass
if ts == nil {
ts = &progTileState{}
for c := 0; c < 3; c++ {
ts.current[c] = coeffPool.Get().(*coeffArr)
ts.sign[c] = coeffPool.Get().(*coeffArr)
clear(ts.current[c][:])
clear(ts.sign[c][:])
}
ts.pass = 0
ts.yBitPos, ts.cbBitPos, ts.crBitPos = progBandQuant{}, progBandQuant{}, progBandQuant{}
d.tileCache[key] = ts
}
return ts
}
// selectQuant resolves the plain and progressive quant sets for a
// SIMPLE/FIRST tile. FIRST passes never consult the previous tile state:
// original tiles replace the reference and difference tiles add to it, so
// the numBits bookkeeping (an UPGRADE-only concept, FreeRDP computes it in
// progressive_rfx_upgrade_component only) must not gate FIRST decoding.
func selectQuant(region *progRegionCtx, hdr *progTileHeader,
which int) (plain, prog, shift progBandQuant, ok bool) {
var idx byte
switch which {
case 0:
idx = hdr.quantIdxY
case 1:
idx = hdr.quantIdxCb
default:
idx = hdr.quantIdxCr
}
if int(idx) >= len(region.quantVals) {
return progBandQuant{}, progBandQuant{}, progBandQuant{}, false
}
plain = region.quantVals[idx]
if hdr.quality == 0xFF {
prog = progBandQuant{} // quantProgValFull is all-zero in FreeRDP
} else {
if int(hdr.quality) >= len(region.quantProgVals) {
return progBandQuant{}, progBandQuant{}, progBandQuant{}, false
}
switch which {
case 0:
prog = region.quantProgVals[hdr.quality].y
case 1:
prog = region.quantProgVals[hdr.quality].cb
default:
prog = region.quantProgVals[hdr.quality].cr
}
}
combined := progAdd(plain, prog)
// 量化值为 0 的波段按规范不编码(MS-RDPRFX),该带 shift 不会应用到任何
// 系数;钳到 0 兼容组合值为 0 的区域量化,而不是丢弃整个瓦片。
shift = progShiftClamped(combined, 1)
return plain, prog, shift, true
}
// progShiftClamped returns max(a-v, 0) band-wise.
func progShiftClamped(a progBandQuant, v int) progBandQuant {
sub := func(x uint8) uint8 {
s := int(x) - v
if s < 0 {
return 0
}
return uint8(s)
}
return progBandQuant{
LL3: sub(a.LL3), HL3: sub(a.HL3), LH3: sub(a.LH3), HH3: sub(a.HH3),
HL2: sub(a.HL2), LH2: sub(a.LH2), HH2: sub(a.HH2),
HL1: sub(a.HL1), LH1: sub(a.LH1), HH1: sub(a.HH1),
}
}
// decodeTileSimple handles PROGRESSIVE_WBT_TILE_SIMPLE (0xCCC5).
func (d *rfxProgressiveDecoder) decodeTileSimple(data []byte, region *progRegionCtx, output []byte, outW, outH int, parallelComponents bool) {
d.decodeTileFirstPass(data, region, output, outW, outH, parallelComponents, progWBTTileSimple)
}
// decodeTileFirst handles PROGRESSIVE_WBT_TILE_FIRST (0xCCC6).
func (d *rfxProgressiveDecoder) decodeTileFirst(data []byte, region *progRegionCtx, output []byte, outW, outH int, parallelComponents bool) {
d.decodeTileFirstPass(data, region, output, outW, outH, parallelComponents, progWBTTileFirst)
}
// decodeTileFirstPass implements the shared SIMPLE/FIRST logic (first pass of
// a tile progression).
func (d *rfxProgressiveDecoder) decodeTileFirstPass(data []byte, region *progRegionCtx, output []byte, outW, outH int, parallelComponents bool, tileType uint16) {
hdrLen := 16
if tileType == progWBTTileFirst {
hdrLen = 17
}
if len(data) < hdrLen+1 {
return
}
hdr := progTileHeader{
quantIdxY: data[0],
quantIdxCb: data[1],
quantIdxCr: data[2],
xIdx: int(binary.LittleEndian.Uint16(data[3:])),
yIdx: int(binary.LittleEndian.Uint16(data[5:])),
flags: data[7],
quality: 0xFF,
}
// Length fields precede the payload: the SIMPLE header is 16 bytes
// (yLen@8, cbLen@10, crLen@12, tailLen@14), FIRST inserts quality@8 and
// shifts the lengths to offsets 9/11/13/15 with a 17-byte header.
if tileType == progWBTTileFirst {
hdr.quality = data[8]
hdr.yLen = int(binary.LittleEndian.Uint16(data[9:]))
hdr.cbLen = int(binary.LittleEndian.Uint16(data[11:]))
hdr.crLen = int(binary.LittleEndian.Uint16(data[13:]))
hdr.tailLen = int(binary.LittleEndian.Uint16(data[15:]))
} else {
hdr.yLen = int(binary.LittleEndian.Uint16(data[8:]))
hdr.cbLen = int(binary.LittleEndian.Uint16(data[10:]))
hdr.crLen = int(binary.LittleEndian.Uint16(data[12:]))
hdr.tailLen = int(binary.LittleEndian.Uint16(data[14:]))
}
off := hdrLen
yData := safeSlice(data, off, hdr.yLen)
off += hdr.yLen
cbData := safeSlice(data, off, hdr.cbLen)
off += hdr.cbLen
crData := safeSlice(data, off, hdr.crLen)
key := uint32(hdr.yIdx)<<16 | uint32(hdr.xIdx)
ts := d.getProgTileState(key, true)
var shifts, combineds [3]progBandQuant
for c := 0; c < 3; c++ {
plain, prog, shift, ok := selectQuant(region, &hdr, c)
if !ok {
return
}
shifts[c] = shift
combineds[c] = progAdd(plain, prog)
}
_ = combineds
coeffDiff := hdr.flags&progFlagTileDifference != 0
work := coeffPool.Get().(*coeffArr)
defer coeffPool.Put(work)
// Decode each component; the DWT output lands in `work`, which we snapshot
// per component before the next component reuses the buffer.
var spatial [3]*coeffArr
spatial[0] = coeffPool.Get().(*coeffArr)
spatial[1] = coeffPool.Get().(*coeffArr)
spatial[2] = coeffPool.Get().(*coeffArr)
for c := 0; c < 3; c++ {
var compData []byte
switch c {
case 0:
compData = yData
case 1:
compData = cbData
default:
compData = crData
}
progDecodeComponent(compData, shifts[c], work, ts.sign[c], ts.current[c], coeffDiff, region.extrapolate)
copy(spatial[c][:], work[:])
}
rfxPlaceTile(spatial[0][:], spatial[1][:], spatial[2][:], hdr.xIdx, hdr.yIdx, output, outW, outH, region.rects)
coeffPool.Put(spatial[0])
coeffPool.Put(spatial[1])
coeffPool.Put(spatial[2])
// FreeRDP: 每个 FIRST pass(含 DIFFERENCE)都把 pass 重置为 1,并把
// bitPos 记为 quant+quantProg(组合位位置);UPGRADE 用 bitPos 差计算
// numBits。
ts.pass = 1
ts.yBitPos = combineds[0]
ts.cbBitPos = combineds[1]
ts.crBitPos = combineds[2]
}
func arrMin(a []int16) int16 {
m := a[0]
for _, v := range a {
if v < m {
m = v
}
}
return m
}
func arrMax(a []int16) int16 {
m := a[0]
for _, v := range a {
if v > m {
m = v
}
}
return m
}
// decodeTileUpgrade handles PROGRESSIVE_WBT_TILE_UPGRADE (0xCCC7): a 20-byte
// header followed by SRL/RAW stream pairs per component.
func (d *rfxProgressiveDecoder) decodeTileUpgrade(data []byte, region *progRegionCtx, output []byte, outW, outH int, parallelComponents bool) {
const hdrLen = 20
if len(data) < hdrLen+1 {
return
}
hdr := progTileHeader{
quantIdxY: data[0],
quantIdxCb: data[1],
quantIdxCr: data[2],
xIdx: int(binary.LittleEndian.Uint16(data[3:])),
yIdx: int(binary.LittleEndian.Uint16(data[5:])),
quality: data[7],
}
hdr.ySrlLen = int(binary.LittleEndian.Uint16(data[8:]))
hdr.yRawLen = int(binary.LittleEndian.Uint16(data[10:]))
hdr.cbSrlLen = int(binary.LittleEndian.Uint16(data[12:]))
hdr.cbRawLen = int(binary.LittleEndian.Uint16(data[14:]))
hdr.crSrlLen = int(binary.LittleEndian.Uint16(data[16:]))
hdr.crRawLen = int(binary.LittleEndian.Uint16(data[18:]))
off := hdrLen
ySrl := safeSlice(data, off, hdr.ySrlLen)
off += hdr.ySrlLen
yRaw := safeSlice(data, off, hdr.yRawLen)
off += hdr.yRawLen
cbSrl := safeSlice(data, off, hdr.cbSrlLen)
off += hdr.cbSrlLen
cbRaw := safeSlice(data, off, hdr.cbRawLen)
off += hdr.cbRawLen
crSrl := safeSlice(data, off, hdr.crSrlLen)
off += hdr.crSrlLen
crRaw := safeSlice(data, off, hdr.crRawLen)
key := uint32(hdr.yIdx)<<16 | uint32(hdr.xIdx)
ts := d.getProgTileState(key, false)
if ts.pass == 0 {
// Upgrade for a tile we never saw the first pass of: nothing to
// refine — skip rather than corrupt the cache.
d.logOnce(20, "upgrade skipped: no first pass", "x", hdr.xIdx, "y", hdr.yIdx)
return
}
var shifts, numBitss, combineds [3]progBandQuant
for c := 0; c < 3; c++ {
plain, prog, shift, numBits, ok := selectQuantUpgrade(region, &hdr, ts, c)
if !ok {
d.logOnce(21, "upgrade quant resolve failed", "c", c, "quality", hdr.quality,
"quantIdxY", hdr.quantIdxY, "quantIdxCb", hdr.quantIdxCb, "quantIdxCr", hdr.quantIdxCr,
"nQuantVals", len(region.quantVals), "nQuantProgVals", len(region.quantProgVals),
"x", hdr.xIdx, "y", hdr.yIdx)
// 状态与 upgrade 目标不一致(此前的 pass 被丢弃或解析失败)。
// 重置该瓦片,让下一个 FIRST 以全新基准重建,避免永久陈旧内容。
ts.pass = 0
ts.yBitPos, ts.cbBitPos, ts.crBitPos = progBandQuant{}, progBandQuant{}, progBandQuant{}
return
}
shifts[c] = shift
numBitss[c] = numBits
combineds[c] = progAdd(plain, prog)
}
work := coeffPool.Get().(*coeffArr)
defer coeffPool.Put(work)
var spatial [3]*coeffArr
spatial[0] = coeffPool.Get().(*coeffArr)
spatial[1] = coeffPool.Get().(*coeffArr)
spatial[2] = coeffPool.Get().(*coeffArr)
for c := 0; c < 3; c++ {
var srlData, rawData []byte
switch c {
case 0:
srlData, rawData = ySrl, yRaw
case 1:
srlData, rawData = cbSrl, cbRaw
default:
srlData, rawData = crSrl, crRaw
}
progUpgradeComponent(work, ts.current[c], ts.sign[c], shifts[c], numBitss[c], srlData, rawData, region.extrapolate)
copy(spatial[c][:], work[:])
}
rfxPlaceTile(spatial[0][:], spatial[1][:], spatial[2][:], hdr.xIdx, hdr.yIdx, output, outW, outH, region.rects)
coeffPool.Put(spatial[0])
coeffPool.Put(spatial[1])
coeffPool.Put(spatial[2])
// 与 FIRST pass 相同:bitPos 记录组合位位置,供后续 UPGRADE 差分。
ts.yBitPos = combineds[0]
ts.cbBitPos = combineds[1]
ts.crBitPos = combineds[2]
ts.pass++
}
// selectQuantUpgrade resolves shift/numBits for an upgrade pass. numBits =
// previous bit position - new combined bit position (the newly significant
// bits delivered by the upgrade stream).
func selectQuantUpgrade(region *progRegionCtx, hdr *progTileHeader, ts *progTileState,
which int) (progBandQuant, progBandQuant, progBandQuant, progBandQuant, bool) {
var idx byte
switch which {
case 0:
idx = hdr.quantIdxY
case 1:
idx = hdr.quantIdxCb
default:
idx = hdr.quantIdxCr
}
if int(idx) >= len(region.quantVals) {
return progBandQuant{}, progBandQuant{}, progBandQuant{}, progBandQuant{}, false
}
plain := region.quantVals[idx]
var prog progBandQuant
if hdr.quality == 0xFF {
prog = progBandQuant{}
} else {
if int(hdr.quality) >= len(region.quantProgVals) {
return progBandQuant{}, progBandQuant{}, progBandQuant{}, progBandQuant{}, false
}
switch which {
case 0:
prog = region.quantProgVals[hdr.quality].y
case 1:
prog = region.quantProgVals[hdr.quality].cb
default:
prog = region.quantProgVals[hdr.quality].cr
}
}
combined := progAdd(plain, prog)
var prev progBandQuant
switch which {
case 0:
prev = ts.yBitPos
case 1:
prev = ts.cbBitPos
default:
prev = ts.crBitPos
}
shift, ok := progLSub(combined, 1)
if !ok {
dumpUpgradeQuantOnce("lsub-underflow", which, plain, prog, prev, combined)
return progBandQuant{}, progBandQuant{}, progBandQuant{}, progBandQuant{}, false
}
numBits, ok := progSub(prev, combined)
if !ok {
dumpUpgradeQuantOnce("numbits-underflow", which, plain, prog, prev, combined)
return progBandQuant{}, progBandQuant{}, progBandQuant{}, progBandQuant{}, false
}
return plain, prog, shift, numBits, true
}
// dumpUpgradeQuantOnce 诊断:UPGRADE 量化解析失败时转储全部带量化值。
// 仅首条生效,避免刷屏。
var upgradeQuantDumpOnce sync.Once
func dumpUpgradeQuantOnce(kind string, which int, plain, prog, prev, combined progBandQuant) {
upgradeQuantDumpOnce.Do(func() {
slog.Warn("progressive: upgrade quant detail",
"kind", kind, "comp", which,
"plain", bandQuantStr(plain),
"prog", bandQuantStr(prog),
"prev", bandQuantStr(prev),
"combined", bandQuantStr(combined))
})
}
func bandQuantStr(q progBandQuant) string {
return fmt.Sprintf("LL3=%d HL3=%d LH3=%d HH3=%d HL2=%d LH2=%d HH2=%d HL1=%d LH1=%d HH1=%d",
q.LL3, q.HL3, q.LH3, q.HH3, q.HL2, q.LH2, q.HH2, q.HL1, q.LH1, q.HH1)
}
// progDecodeComponent implements progressive_rfx_decode_component for the
// SIMPLE/FIRST first pass: RLGR decode → sign snapshot → LL3 differential →
// per-band left-shift dequant → current update → IDWT.
//
// MS-RDPEGFX 3.3.8.2.1.1:LL3 差分累积与按带反量化(DecProgQ*PQF)对
// ORIGINAL 与 DIFFERENCE 瓦片一视同仁;coeffDiff 只改变与参考状态 current
// 的合并方式(original 覆盖,difference 叠加)。与 FreeRDP
// progressive_rfx_decode_component / progressive_rfx_dwt_2d_decode 一致。
func progDecodeComponent(data []byte, shift progBandQuant, buf, sign, current *coeffArr, coeffDiff, extrapolate bool) {
b := buf[:]
if data == nil || len(data) == 0 {
clear(b)
} else {
rlgr1Decode(data, 4096, b)
}
copy(sign[:], b)
if !extrapolate {
progDiffDecode(b[4032:4096])
progDecodeBlock(b[0:1024], shift.HL1)
progDecodeBlock(b[1024:2048], shift.LH1)
progDecodeBlock(b[2048:3072], shift.HH1)
progDecodeBlock(b[3072:3328], shift.HL2)
progDecodeBlock(b[3328:3584], shift.LH2)
progDecodeBlock(b[3584:3840], shift.HH2)
progDecodeBlock(b[3840:3904], shift.HL3)
progDecodeBlock(b[3904:3968], shift.LH3)
progDecodeBlock(b[3968:4032], shift.HH3)
progDecodeBlock(b[4032:4096], shift.LL3)
} else {
progDiffDecode(b[4015:4096])
progDecodeBlock(b[0:1023], shift.HL1)
progDecodeBlock(b[1023:2046], shift.LH1)
progDecodeBlock(b[2046:3007], shift.HH1)
progDecodeBlock(b[3007:3279], shift.HL2)
progDecodeBlock(b[3279:3551], shift.LH2)
progDecodeBlock(b[3551:3807], shift.HH2)
progDecodeBlock(b[3807:3879], shift.HL3)
progDecodeBlock(b[3879:3951], shift.LH3)
progDecodeBlock(b[3951:4015], shift.HH3)
progDecodeBlock(b[4015:4096], shift.LL3)
}
if coeffDiff {
for i := range b {
current[i] += b[i]
}
copy(b, current[:])
} else {
copy(current[:], b)
}
if !extrapolate {
rfxInverseDWT2D(b)
} else {
progDWTExtrapolate(b)
}
}
// progDiffDecode is rfx_differential_decode (in-place cumulative sum).
func progDiffDecode(data []int16) {
for i := 1; i < len(data); i++ {
data[i] += data[i-1]
}
}
// progDecodeBlock is progressive_rfx_decode_block (left-shift dequant).
func progDecodeBlock(data []int16, shift uint8) {
if shift == 0 {
return
}
s := int16(shift)
for i := range data {
data[i] <<= s
}
}
// ── Upgrade bit streams ────────────────────────────────────────────────────
// progBitStream is an MSB-first bit reader matching FreeRDP's wBitStream
// semantics (zero-padding past the end).
type progBitStream struct {
data []byte
bytePos int
acc uint32
bits int
posBits int
}
func (b *progBitStream) fill() {
for b.bits <= 24 && b.bytePos < len(b.data) {
b.acc |= uint32(b.data[b.bytePos]) << uint(24-b.bits)
b.bits += 8
b.bytePos++
}
}
func (b *progBitStream) readBit() uint32 {
b.fill()
v := (b.acc >> 31) & 1
b.acc <<= 1
if b.bits > 0 {
b.bits--
}
b.posBits++
return v
}
func (b *progBitStream) readBits(n uint) uint32 {
if n == 0 {
return 0
}
b.fill()
var v uint32
if b.bits >= int(n) {
v = (b.acc >> uint(32-int(n))) & uint32((1<<n)-1)
b.acc <<= n
b.bits -= int(n)
} else {
avail := b.bits
if avail > 0 {
v = (b.acc >> uint(32-avail)) & uint32((1<<avail)-1)
}
v <<= n - uint(avail)
b.acc = 0
b.bits = 0
}
b.posBits += int(n)
return v
}
func (b *progBitStream) skip(n uint) {
b.posBits += int(n)
}
func (b *progBitStream) remaining() int {
return len(b.data)*8 - b.posBits
}
type progUpgradeState struct {
srl *progBitStream
raw *progBitStream
kp int
nz int
mode int
nonLL bool
}
// srlRead ports progressive_rfx_srl_read.
func (st *progUpgradeState) srlRead(numBits uint32) int16 {
if st.nz != 0 {
st.nz--
return 0
}
k := uint(st.kp / 8)
if st.mode == 0 {
// zero encoding
bit := st.srl.readBit()
if bit == 0 {
st.nz = 1 << k
st.kp += 4
if st.kp > 80 {
st.kp = 80
}
st.nz--
return 0
}
// '1' bit: nz comes from the next k bits
st.nz = 0
st.mode = 1
if k > 0 {
st.nz = int(st.srl.readBits(k))
}
if st.nz != 0 {
st.nz--
return 0
}
}
st.mode = 0
// unary encoding; read sign bit
sign := st.srl.readBit()
if st.kp < 6 {
st.kp = 0
} else {
st.kp -= 6
}
if numBits == 1 {
if sign != 0 {
return -1
}
return 1
}
mag := uint32(1)
max := uint32(1<<numBits) - 1
for mag < max {
bit := st.srl.readBit()
if bit != 0 {
break
}
mag++
}
if mag > 32767 {
mag = 32767
}
if sign != 0 {
return -int16(mag)
}
return int16(mag)
}
func progRawShift(raw *progBitStream, numBits uint32) int16 {
return int16(raw.readBits(uint(numBits)))
}
// progUpgradeBlock ports progressive_rfx_upgrade_block.
func progUpgradeBlock(st *progUpgradeState, buf, sign []int16, length uint32, shift, numBits uint32) {
if numBits < 1 {
return
}
raw := st.raw
if !st.nonLL {
for i := uint32(0); i < length; i++ {
input := progRawShift(raw, numBits)
buf[i] = int16(int32(buf[i]) + (int32(input) << shift))
}
return
}
for i := uint32(0); i < length; i++ {
var input int32
switch {
case sign[i] > 0:
input = int32(progRawShift(raw, numBits))
case sign[i] < 0:
input = -int32(progRawShift(raw, numBits))
default:
input = int32(st.srlRead(numBits))
sign[i] = int16(input)
}
buf[i] = int16(int32(buf[i]) + (input << shift))
}
}
// progUpgradeStateFinish ports progressive_rfx_upgrade_state_finish: byte-
// align both streams and drop a trailing 8-bit srl remainder.
func progUpgradeStateFinish(st *progUpgradeState) {
raw, srl := st.raw, st.srl
if pad := (8 - raw.posBits%8) % 8; pad > 0 {
raw.skip(uint(pad))
}
if pad := (8 - srl.posBits%8) % 8; pad > 0 {
srl.skip(uint(pad))
}
if srl.remaining() == 8 {
srl.skip(8)
}
}
// progUpgradeComponent ports progressive_rfx_upgrade_component: refines the
// cached coefficients (current) in extrapolate layout using an SRL stream
// (for sign==0 coefficients) and a RAW stream (for the rest).
func progUpgradeComponent(buf, current, sign *coeffArr, shift, numBits progBandQuant, srlData, rawData []byte, extrapolate bool) {
st := progUpgradeState{
kp: 8,
mode: 0,
srl: &progBitStream{data: srlData},
raw: &progBitStream{data: rawData},
}
cur := current[:]
sgn := sign[:]
st.nonLL = true
progUpgradeBlock(&st, cur[0:1023], sgn[0:1023], 1023, uint32(shift.HL1), uint32(numBits.HL1))
progUpgradeBlock(&st, cur[1023:2046], sgn[1023:2046], 1023, uint32(shift.LH1), uint32(numBits.LH1))
progUpgradeBlock(&st, cur[2046:3007], sgn[2046:3007], 961, uint32(shift.HH1), uint32(numBits.HH1))
progUpgradeBlock(&st, cur[3007:3279], sgn[3007:3279], 272, uint32(shift.HL2), uint32(numBits.HL2))
progUpgradeBlock(&st, cur[3279:3551], sgn[3279:3551], 272, uint32(shift.LH2), uint32(numBits.LH2))
progUpgradeBlock(&st, cur[3551:3807], sgn[3551:3807], 256, uint32(shift.HH2), uint32(numBits.HH2))
progUpgradeBlock(&st, cur[3807:3879], sgn[3807:3879], 72, uint32(shift.HL3), uint32(numBits.HL3))
progUpgradeBlock(&st, cur[3879:3951], sgn[3879:3951], 72, uint32(shift.LH3), uint32(numBits.LH3))
progUpgradeBlock(&st, cur[3951:4015], sgn[3951:4015], 64, uint32(shift.HH3), uint32(numBits.HH3))
st.nonLL = false
progUpgradeBlock(&st, cur[4015:4096], sgn[4015:4096], 81, uint32(shift.LL3), uint32(numBits.LL3))
progUpgradeStateFinish(&st)
// dwt_2d_decode(..., reverse=TRUE): buffer = current, then IDWT.
copy(buf[:], cur)
if !extrapolate {
rfxInverseDWT2D(buf[:])
} else {
progDWTExtrapolate(buf[:])
}
}
// ── Extrapolate IDWT (progressive_rfx_dwt_2d_decode_block) ─────────────────
func progBandLCount(level int) int { return (64 >> level) + 1 }
func progBandHCount(level int) int {
if level == 1 {
return (64 >> 1) - 1
}
return (64 + (1 << uint(level-1))) >> level
}
func progClamp16(v int32) int16 {
if v < -32768 {
return -32768
}
if v > 32767 {
return 32767
}
return int16(v)
}
// progDWTExtrapolate ports rfx_dwt_2d_extrapolate_decode: three irregular
// blocks at fixed offsets covering the extrapolate band layout.
func progDWTExtrapolate(buffer []int16) {
bufs := idwtBufPool.Get().(*idwtBufs)
tmp := bufs.tmp[:]
progDWT2DBlock(buffer[3807:], tmp, 3)
progDWT2DBlock(buffer[3007:], tmp, 2)
progDWT2DBlock(buffer[0:], tmp, 1)
idwtBufPool.Put(bufs)
}
// progDWT2DBlock decodes one extrapolate block in place.
func progDWT2DBlock(buffer, temp []int16, level int) {
nBandL := progBandLCount(level)
nBandH := progBandHCount(level)
hlLen := nBandH * nBandL
lhLen := nBandL * nBandH
hhLen := nBandH * nBandH
llLen := nBandL * nBandL
hl := buffer[0:hlLen]
lh := buffer[hlLen : hlLen+lhLen]
hh := buffer[hlLen+lhLen : hlLen+lhLen+hhLen]
ll := buffer[hlLen+lhLen+hhLen : hlLen+lhLen+hhLen+llLen]
dstStep := nBandL + nBandH
lBuf := temp[0 : nBandL*dstStep]
hBuf := temp[nBandL*dstStep : nBandL*dstStep+nBandH*dstStep]
progIDWTX(ll, nBandL, hl, nBandH, lBuf, dstStep, nBandL, nBandH, nBandL)
progIDWTX(lh, nBandL, hh, nBandH, hBuf, dstStep, nBandL, nBandH, nBandH)
progIDWTY(lBuf, dstStep, hBuf, dstStep, buffer, dstStep, nBandL, nBandH, nBandL+nBandH)
}
// progIDWTX ports progressive_rfx_idwt_x (horizontal 1-D IDWT of every row).
// Index arithmetic instead of slice reslicing: the C original walks pointers
// one element past the final read, which Go bounds checks reject.
func progIDWTX(low []int16, lowStep int, high []int16, highStep int, dst []int16, dstStep int, lowCount, highCount, dstCount int) {
for i := 0; i < dstCount; i++ {
lRow := low[i*lowStep:]
hRow := high[i*highStep:]
xRow := dst[i*dstStep:]
H0 := hRow[0]
L0 := lRow[0]
li, hi := 1, 1
xi := 0
X0 := progClamp16(int32(L0) - int32(H0))
X2 := X0
for j := 0; j < highCount-1; j++ {
H1 := hRow[hi]
hi++
L0 = lRow[li]
li++
X2 = progClamp16(int32(L0) - (int32(H0)+int32(H1))/2)
X1 := progClamp16((int32(X0)+int32(X2))/2 + 2*int32(H0))
xRow[xi] = X0
xRow[xi+1] = X1
xi += 2
X0 = X2
H0 = H1
}
switch {
case lowCount <= highCount:
xRow[xi] = X2
xRow[xi+1] = progClamp16(int32(X2) + 2*int32(H0))
case lowCount == highCount+1:
L0 = lRow[li]
X0t := progClamp16(int32(L0) - int32(H0))
xRow[xi] = X2
xRow[xi+1] = progClamp16((int32(X0t)+int32(X2))/2 + 2*int32(H0))
xRow[xi+2] = X0t
default:
L0 = lRow[li]
li++
X0t := progClamp16(int32(L0) - int32(H0)/2)
xRow[xi] = X2
xRow[xi+1] = progClamp16((int32(X0t)+int32(X2))/2 + 2*int32(H0))
xRow[xi+2] = X0t
L0 = lRow[li]
xRow[xi+3] = progClamp16((int32(X0t) + int32(L0)) / 2)
}
}
}
// progIDWTY ports progressive_rfx_idwt_y (vertical 1-D IDWT of every column).
// Index arithmetic instead of slice reslicing: the C original walks pointers
// one element past the final read, which Go bounds checks reject.
func progIDWTY(low []int16, lowStep int, high []int16, highStep int, dst []int16, dstStep int, lowCount, highCount, dstCount int) {
for i := 0; i < dstCount; i++ {
H0 := high[i]
L0 := low[i]
li, hi := 1, 1
xi := 0
X0 := progClamp16(int32(L0) - int32(H0))
X2 := X0
for j := 0; j < highCount-1; j++ {
H1 := high[i+hi*highStep]
hi++
L0 = low[i+li*lowStep]
li++
X2 = progClamp16(int32(L0) - (int32(H0)+int32(H1))/2)
X1 := progClamp16((int32(X0)+int32(X2))/2 + 2*int32(H0))
dst[i+xi] = X0
xi += dstStep
dst[i+xi] = X1
xi += dstStep
X0 = X2
H0 = H1
}
switch {
case lowCount <= highCount:
dst[i+xi] = X2
dst[i+xi+dstStep] = progClamp16(int32(X2) + 2*int32(H0))
case lowCount == highCount+1:
L0 = low[i+li*lowStep]
X0t := progClamp16(int32(L0) - int32(H0))
dst[i+xi] = X2
dst[i+xi+dstStep] = progClamp16((int32(X0t)+int32(X2))/2 + 2*int32(H0))
dst[i+xi+2*dstStep] = X0t
default:
L0 = low[i+li*lowStep]
li++
X0t := progClamp16(int32(L0) - int32(H0)/2)
dst[i+xi] = X2
dst[i+xi+dstStep] = progClamp16((int32(X0t)+int32(X2))/2 + 2*int32(H0))
dst[i+xi+2*dstStep] = X0t
L0 = low[i+li*lowStep]
dst[i+xi+3*dstStep] = progClamp16((int32(X0t) + int32(L0)) / 2)
}
}
}
// rfxPlaceTile converts YCbCr tile to BGRA using tile-grid indices (xIdx, yIdx).
// rfxPlaceTile 把解码后的瓦片绘制到表面。region.rects 非空时只绘制与矩形
// 并集相交的瓦片,但相交的瓦片必须整块 64×64 落屏(FreeRDP update_tiles
// 语义:rects 仅用于筛掉不相交的瓦片,tile 边界按 64 对齐外延,矩形外的
// 瓦片像素同样是本帧的有效内容)。实测 Win10 最小化动画:region 矩形
// (34,254 662x374) 的瓦片从 yIdx=3(y=192)开始,让出的条带 y=198..254
// 只存在于瓦片内——若按矩形交集裁剪,该条带永远不被重绘,留下残影。
func rfxPlaceTile(yCoeffs, cbCoeffs, crCoeffs []int16, xIdx, yIdx int, output []byte, outW, outH int, rects []rfxRect) {
tileX := xIdx * rfxTileSize
tileY := yIdx * rfxTileSize
if len(rects) > 0 {
intersects := false
for _, rc := range rects {
if tileX < rc.x+rc.w && tileX+rfxTileSize > rc.x &&
tileY < rc.y+rc.h && tileY+rfxTileSize > rc.y {
intersects = true
break
}
}
if !intersects {
return
}
}
rfxPlaceTileAbs(yCoeffs, cbCoeffs, crCoeffs, tileX, tileY, output, outW, outH)
}
// rfxPlaceTileAbs converts YCbCr tile to BGRA and writes into the output buffer
// at absolute pixel coordinates (tileX, tileY).
// Uses ICT (Irreversible Color Transform) from MS-RDPRFX.
func rfxPlaceTileAbs(yCoeffs, cbCoeffs, crCoeffs []int16, tileX, tileY int, output []byte, outW, outH int) {
tileW := rfxTileSize
tileH := rfxTileSize
if tileX+tileW > outW {
tileW = outW - tileX
}
if tileY+tileH > outH {
tileH = outH - tileY
}
if tileW <= 0 || tileH <= 0 {
return
}
for row := 0; row < tileH; row++ {
dstStart := ((tileY+row)*outW + tileX) * 4
dstEnd := dstStart + tileW*4
if dstStart < 0 || dstEnd > len(output) {
continue
}
dstRow := output[dstStart:dstEnd:dstEnd]
srcOff := row * rfxTileSize
ictToBGRA(
yCoeffs[srcOff:srcOff+tileW:srcOff+tileW],
cbCoeffs[srcOff:srcOff+tileW:srcOff+tileW],
crCoeffs[srcOff:srcOff+tileW:srcOff+tileW],
dstRow, tileW,
)
}
}