package rdp import "sync" // PixelBuffer holds a shared RGBA pixel buffer that is updated by the RDP // backend (via partial region updates) and read by the frame-serving path. type PixelBuffer struct { Width int Height int Data []byte // RGBA pixel data, len = Width * Height * 4 mu sync.RWMutex dirty bool } // NewPixelBuffer allocates a buffer for the given resolution. // The buffer is initialized to all zeros (transparent black). func NewPixelBuffer(width, height int) *PixelBuffer { return &PixelBuffer{ Width: width, Height: height, Data: make([]byte, width*height*4), } } // Update applies a partial region update to the pixel buffer. // The data slice must contain w*h*4 bytes of RGBA pixel data. // Pixels outside the buffer bounds are silently clipped. func (p *PixelBuffer) Update(x, y, w, h int, data []byte) { p.mu.Lock() defer p.mu.Unlock() for row := 0; row < h; row++ { destY := y + row if destY < 0 || destY >= p.Height { continue } srcOffset := row * w * 4 destOffset := (destY*p.Width + x) * 4 // Calculate how many pixels we can copy on this row copyWidth := w if x < 0 { srcOffset += (-x) * 4 copyWidth += x // reduce by the clipped amount destOffset = destY * p.Width * 4 } if x+copyWidth > p.Width { copyWidth = p.Width - x if x < 0 { copyWidth = p.Width } } if copyWidth <= 0 { continue } srcEnd := srcOffset + copyWidth*4 if srcEnd > len(data) { srcEnd = len(data) } if srcOffset >= len(data) { continue } destEnd := destOffset + copyWidth*4 if destEnd > len(p.Data) { destEnd = len(p.Data) } copy(p.Data[destOffset:destEnd], data[srcOffset:srcEnd]) } p.dirty = true } // GetFrame returns a copy of the full pixel buffer. // The caller receives an independent copy that will not be affected // by subsequent updates. func (p *PixelBuffer) GetFrame() []byte { p.mu.RLock() defer p.mu.RUnlock() frame := make([]byte, len(p.Data)) copy(frame, p.Data) return frame } // IsDirty reports whether the buffer has been updated since the last // ClearDirty call. func (p *PixelBuffer) IsDirty() bool { p.mu.RLock() defer p.mu.RUnlock() return p.dirty } // ClearDirty resets the dirty flag. Typically called after a frame has // been sent to the frontend. func (p *PixelBuffer) ClearDirty() { p.mu.Lock() defer p.mu.Unlock() p.dirty = false }