mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-18 08:16:39 +00:00
100 lines
2.4 KiB
Go
100 lines
2.4 KiB
Go
//go:build windows
|
|
|
|
package server
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
|
|
"github.com/kirides/go-d3d/d3d11"
|
|
"github.com/kirides/go-d3d/outputduplication"
|
|
)
|
|
|
|
// dxgiCapturer captures the desktop using DXGI Desktop Duplication.
|
|
// Provides GPU-accelerated capture with native dirty rect tracking.
|
|
// Only works from the interactive user session, not Session 0.
|
|
//
|
|
// Uses a double-buffer: DXGI writes into img, then we copy to the current
|
|
// output buffer and hand it out. Alternating between two output buffers
|
|
// avoids allocating a new image.RGBA per frame (~8MB at 1080p, 30fps).
|
|
type dxgiCapturer struct {
|
|
dup *outputduplication.OutputDuplicator
|
|
device *d3d11.ID3D11Device
|
|
ctx *d3d11.ID3D11DeviceContext
|
|
img *image.RGBA
|
|
out [2]*image.RGBA
|
|
outIdx int
|
|
width int
|
|
height int
|
|
}
|
|
|
|
func newDXGICapturer() (*dxgiCapturer, error) {
|
|
device, deviceCtx, err := d3d11.NewD3D11Device()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create D3D11 device: %w", err)
|
|
}
|
|
|
|
dup, err := outputduplication.NewIDXGIOutputDuplication(device, deviceCtx, 0)
|
|
if err != nil {
|
|
device.Release()
|
|
deviceCtx.Release()
|
|
return nil, fmt.Errorf("create output duplication: %w", err)
|
|
}
|
|
|
|
w, h := screenSize()
|
|
if w == 0 || h == 0 {
|
|
dup.Release()
|
|
device.Release()
|
|
deviceCtx.Release()
|
|
return nil, fmt.Errorf("screen dimensions are zero")
|
|
}
|
|
|
|
rect := image.Rect(0, 0, w, h)
|
|
c := &dxgiCapturer{
|
|
dup: dup,
|
|
device: device,
|
|
ctx: deviceCtx,
|
|
img: image.NewRGBA(rect),
|
|
out: [2]*image.RGBA{image.NewRGBA(rect), image.NewRGBA(rect)},
|
|
width: w,
|
|
height: h,
|
|
}
|
|
|
|
// Grab the initial frame with a longer timeout to ensure we have
|
|
// a valid image before returning.
|
|
_ = dup.GetImage(c.img, 2000)
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func (c *dxgiCapturer) capture() (*image.RGBA, error) {
|
|
err := c.dup.GetImage(c.img, 100)
|
|
if err != nil && !errors.Is(err, outputduplication.ErrNoImageYet) {
|
|
return nil, err
|
|
}
|
|
|
|
// Copy into the next output buffer. The DesktopCapturer hands out the
|
|
// returned pointer to VNC sessions that read pixels concurrently, so we
|
|
// alternate between two pre-allocated buffers instead of allocating per frame.
|
|
out := c.out[c.outIdx]
|
|
c.outIdx ^= 1
|
|
copy(out.Pix, c.img.Pix)
|
|
return out, nil
|
|
}
|
|
|
|
func (c *dxgiCapturer) close() {
|
|
if c.dup != nil {
|
|
c.dup.Release()
|
|
c.dup = nil
|
|
}
|
|
if c.ctx != nil {
|
|
c.ctx.Release()
|
|
c.ctx = nil
|
|
}
|
|
if c.device != nil {
|
|
c.device.Release()
|
|
c.device = nil
|
|
}
|
|
}
|