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, ®ion, surfData, outW, outH, parallel) case progWBTTileFirst: d.decodeTileFirst(tw.data, ®ion, surfData, outW, outH, parallel) case progWBTTileUpgrade: d.decodeTileUpgrade(tw.data, ®ion, 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< 0 { v = (b.acc >> uint(32-avail)) & uint32((1< 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< 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, ) } }