save current

This commit is contained in:
KingKDot
2026-07-06 20:19:38 -05:00
parent f7e9da26f8
commit 04b04831da
13 changed files with 426 additions and 145 deletions
@@ -493,6 +493,7 @@ var (
statKeepaliveFrames atomic.Int64
statBlockRegions atomic.Int64
statBlockFallbacks atomic.Int64
statFrameSlotSkips atomic.Int64
overrideQuality atomic.Int64
overrideCodec atomic.Value
@@ -700,6 +701,7 @@ func resetStats() {
statKeepaliveFrames.Store(0)
statBlockRegions.Store(0)
statBlockFallbacks.Store(0)
statFrameSlotSkips.Store(0)
}
func avgMs(ns int64, frames int64) float64 {
@@ -30,6 +30,7 @@ func AcquireFrameSlot() bool {
inFlightFrames.Store(0)
continue
}
statFrameSlotSkips.Add(1)
return false
}
if inFlightFrames.CompareAndSwap(cur, cur+1) {
@@ -81,3 +82,13 @@ func activeFrameSlotLimit() int64 {
}
return defaultMaxInFlightFrames
}
func frameFlowSnapshot() (inFlight, limit int64, ackSeen bool, ackAge time.Duration) {
inFlight = inFlightFrames.Load()
limit = activeFrameSlotLimit()
ackSeen = frameAckSeen.Load()
if lastAck := lastAckNano.Load(); lastAck > 0 {
ackAge = time.Since(time.Unix(0, lastAck))
}
return inFlight, limit, ackSeen, ackAge
}
@@ -530,6 +530,7 @@ func resetH264Encoder() {
}
func RequestDesktopH264Keyframe() {
requestNativeH264D3D11TextureKeyframe()
resetH264Encoder()
}
@@ -17,4 +17,6 @@ func encodeNativeH264D3D11Texture(device, texture unsafe.Pointer, inputWidth, in
return nil, fmt.Errorf("native NVENC D3D11 texture encode requires cgo for input=%dx%d output=%dx%d@%dfps", inputWidth, inputHeight, encodeWidth, encodeHeight, fps)
}
func requestNativeH264D3D11TextureKeyframe() {}
func resetNativeH264D3D11TextureEncoder() {}
@@ -414,70 +414,75 @@ static nvenc_d3d11_texture_create_result nvenc_create_d3d11_texture_encoder(ID3D
return out;
}
HRESULT hr = enc->device->lpVtbl->QueryInterface(enc->device, &nvenc_iid_id3d11_video_device, (void**)&enc->video_device);
if (FAILED(hr) || !enc->video_device) {
out.stage = 11;
out.hr = hr;
strncpy(out.message, "ID3D11VideoDevice unavailable for GPU BGRA->NV12 conversion", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
hr = enc->context->lpVtbl->QueryInterface(enc->context, &nvenc_iid_id3d11_video_context, (void**)&enc->video_context);
if (FAILED(hr) || !enc->video_context) {
out.stage = 12;
out.hr = hr;
strncpy(out.message, "ID3D11VideoContext unavailable for GPU BGRA->NV12 conversion", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
D3D11_VIDEO_PROCESSOR_CONTENT_DESC content_desc;
memset(&content_desc, 0, sizeof(content_desc));
content_desc.InputFrameFormat = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE;
content_desc.InputFrameRate.Numerator = (UINT)fps;
content_desc.InputFrameRate.Denominator = 1;
content_desc.InputWidth = (UINT)input_width;
content_desc.InputHeight = (UINT)input_height;
content_desc.OutputFrameRate.Numerator = (UINT)fps;
content_desc.OutputFrameRate.Denominator = 1;
content_desc.OutputWidth = (UINT)encode_width;
content_desc.OutputHeight = (UINT)encode_height;
content_desc.Usage = D3D11_VIDEO_USAGE_OPTIMAL_SPEED;
hr = enc->video_device->lpVtbl->CreateVideoProcessorEnumerator(enc->video_device, &content_desc, &enc->video_enum);
if (FAILED(hr) || !enc->video_enum) {
out.stage = 13;
out.hr = hr;
strncpy(out.message, "CreateVideoProcessorEnumerator failed for GPU BGRA->NV12 conversion", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
UINT format_flags = 0;
hr = enc->video_enum->lpVtbl->CheckVideoProcessorFormat(enc->video_enum, (DXGI_FORMAT)dxgi_format, &format_flags);
if (FAILED(hr) || !(format_flags & D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_INPUT)) {
out.stage = 14;
out.hr = FAILED(hr) ? hr : E_FAIL;
strncpy(out.message, "desktop DXGI format is not supported as D3D11 video processor input", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
DXGI_FORMAT output_dxgi_format = output_mode == 1 ? (DXGI_FORMAT)dxgi_format : DXGI_FORMAT_NV12;
NV_ENC_BUFFER_FORMAT output_buffer_format = output_mode == 1 ? buffer_format : NV_ENC_BUFFER_FORMAT_NV12;
format_flags = 0;
hr = enc->video_enum->lpVtbl->CheckVideoProcessorFormat(enc->video_enum, output_dxgi_format, &format_flags);
if (FAILED(hr) || !(format_flags & D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_OUTPUT)) {
out.stage = 15;
out.hr = FAILED(hr) ? hr : E_FAIL;
strncpy(out.message, "requested D3D11 video processor output format is not supported", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
hr = enc->video_device->lpVtbl->CreateVideoProcessor(enc->video_device, enc->video_enum, 0, &enc->video_processor);
if (FAILED(hr) || !enc->video_processor) {
out.stage = 16;
out.hr = hr;
strncpy(out.message, "CreateVideoProcessor failed for GPU BGRA->NV12 conversion", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
int direct_copy = output_mode == 1 && input_width == encode_width && input_height == encode_height;
HRESULT hr = S_OK;
if (!direct_copy) {
hr = enc->device->lpVtbl->QueryInterface(enc->device, &nvenc_iid_id3d11_video_device, (void**)&enc->video_device);
if (FAILED(hr) || !enc->video_device) {
out.stage = 11;
out.hr = hr;
strncpy(out.message, "ID3D11VideoDevice unavailable for GPU conversion/scaling", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
hr = enc->context->lpVtbl->QueryInterface(enc->context, &nvenc_iid_id3d11_video_context, (void**)&enc->video_context);
if (FAILED(hr) || !enc->video_context) {
out.stage = 12;
out.hr = hr;
strncpy(out.message, "ID3D11VideoContext unavailable for GPU conversion/scaling", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
D3D11_VIDEO_PROCESSOR_CONTENT_DESC content_desc;
memset(&content_desc, 0, sizeof(content_desc));
content_desc.InputFrameFormat = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE;
content_desc.InputFrameRate.Numerator = (UINT)fps;
content_desc.InputFrameRate.Denominator = 1;
content_desc.InputWidth = (UINT)input_width;
content_desc.InputHeight = (UINT)input_height;
content_desc.OutputFrameRate.Numerator = (UINT)fps;
content_desc.OutputFrameRate.Denominator = 1;
content_desc.OutputWidth = (UINT)encode_width;
content_desc.OutputHeight = (UINT)encode_height;
content_desc.Usage = D3D11_VIDEO_USAGE_OPTIMAL_SPEED;
hr = enc->video_device->lpVtbl->CreateVideoProcessorEnumerator(enc->video_device, &content_desc, &enc->video_enum);
if (FAILED(hr) || !enc->video_enum) {
out.stage = 13;
out.hr = hr;
strncpy(out.message, "CreateVideoProcessorEnumerator failed for GPU conversion/scaling", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
UINT format_flags = 0;
hr = enc->video_enum->lpVtbl->CheckVideoProcessorFormat(enc->video_enum, (DXGI_FORMAT)dxgi_format, &format_flags);
if (FAILED(hr) || !(format_flags & D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_INPUT)) {
out.stage = 14;
out.hr = FAILED(hr) ? hr : E_FAIL;
strncpy(out.message, "desktop DXGI format is not supported as D3D11 video processor input", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
format_flags = 0;
hr = enc->video_enum->lpVtbl->CheckVideoProcessorFormat(enc->video_enum, output_dxgi_format, &format_flags);
if (FAILED(hr) || !(format_flags & D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_OUTPUT)) {
out.stage = 15;
out.hr = FAILED(hr) ? hr : E_FAIL;
strncpy(out.message, "requested D3D11 video processor output format is not supported", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
hr = enc->video_device->lpVtbl->CreateVideoProcessor(enc->video_device, enc->video_enum, 0, &enc->video_processor);
if (FAILED(hr) || !enc->video_processor) {
out.stage = 16;
out.hr = hr;
strncpy(out.message, "CreateVideoProcessor failed for GPU conversion/scaling", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
}
D3D11_TEXTURE2D_DESC source_desc;
@@ -514,37 +519,41 @@ static nvenc_d3d11_texture_create_result nvenc_create_d3d11_texture_encoder(ID3D
output_view_desc.Texture2D.MipSlice = 0;
for (int i = 0; i < NVENC_D3D11_TEXTURE_PIPELINE_DEPTH; i++) {
nvenc_d3d11_texture_slot *slot = &enc->slots[i];
hr = enc->device->lpVtbl->CreateTexture2D(enc->device, &source_desc, NULL, &slot->source_texture);
if (FAILED(hr) || !slot->source_texture) {
out.stage = 17;
out.hr = hr;
strncpy(out.message, "CreateTexture2D desktop video-processor input failed", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
if (!direct_copy) {
hr = enc->device->lpVtbl->CreateTexture2D(enc->device, &source_desc, NULL, &slot->source_texture);
if (FAILED(hr) || !slot->source_texture) {
out.stage = 17;
out.hr = hr;
strncpy(out.message, "CreateTexture2D desktop video-processor input failed", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
}
hr = enc->device->lpVtbl->CreateTexture2D(enc->device, &tex_desc, NULL, &slot->input_texture);
if (FAILED(hr) || !slot->input_texture) {
out.stage = 18;
out.hr = hr;
strncpy(out.message, "CreateTexture2D video-processor output failed", sizeof(out.message)-1);
strncpy(out.message, direct_copy ? "CreateTexture2D direct NVENC input failed" : "CreateTexture2D video-processor output failed", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
hr = enc->video_device->lpVtbl->CreateVideoProcessorInputView(enc->video_device, (ID3D11Resource*)slot->source_texture, enc->video_enum, &input_view_desc, &slot->video_input_view);
if (FAILED(hr) || !slot->video_input_view) {
out.stage = 19;
out.hr = hr;
strncpy(out.message, "CreateVideoProcessorInputView failed", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
hr = enc->video_device->lpVtbl->CreateVideoProcessorOutputView(enc->video_device, (ID3D11Resource*)slot->input_texture, enc->video_enum, &output_view_desc, &slot->video_output_view);
if (FAILED(hr) || !slot->video_output_view) {
out.stage = 20;
out.hr = hr;
strncpy(out.message, "CreateVideoProcessorOutputView failed", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
if (!direct_copy) {
hr = enc->video_device->lpVtbl->CreateVideoProcessorInputView(enc->video_device, (ID3D11Resource*)slot->source_texture, enc->video_enum, &input_view_desc, &slot->video_input_view);
if (FAILED(hr) || !slot->video_input_view) {
out.stage = 19;
out.hr = hr;
strncpy(out.message, "CreateVideoProcessorInputView failed", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
hr = enc->video_device->lpVtbl->CreateVideoProcessorOutputView(enc->video_device, (ID3D11Resource*)slot->input_texture, enc->video_enum, &output_view_desc, &slot->video_output_view);
if (FAILED(hr) || !slot->video_output_view) {
out.stage = 20;
out.hr = hr;
strncpy(out.message, "CreateVideoProcessorOutputView failed", sizeof(out.message)-1);
nvenc_release_texture_encoder(enc);
return out;
}
}
NV_ENC_REGISTER_RESOURCE reg;
@@ -580,7 +589,7 @@ static nvenc_d3d11_texture_create_result nvenc_create_d3d11_texture_encoder(ID3D
}
slot->bitstream = bs.bitstreamBuffer;
}
enc->use_video_processor = 1;
enc->use_video_processor = direct_copy ? 0 : 1;
out.encoder = enc;
return out;
}
@@ -866,7 +875,7 @@ static nvenc_d3d11_encode_result nvenc_encode_d3d11_texture(nvenc_d3d11_texture_
strncpy(out.message, "nil encoder or texture", sizeof(out.message)-1);
return out;
}
if (!enc->use_video_processor || !enc->video_context || !enc->video_processor) {
if (enc->use_video_processor && (!enc->video_context || !enc->video_processor)) {
out.stage = 3;
out.nvstatus = NV_ENC_ERR_INVALID_PTR;
strncpy(out.message, "GPU BGRA->NV12 video processor is missing", sizeof(out.message)-1);
@@ -925,48 +934,56 @@ static nvenc_d3d11_encode_result nvenc_encode_d3d11_texture(nvenc_d3d11_texture_
return out;
}
}
if (slot->in_flight || !slot->source_texture || !slot->input_texture || !slot->video_input_view || !slot->video_output_view || !slot->registered_resource || !slot->bitstream) {
if (slot->in_flight || !slot->input_texture || !slot->registered_resource || !slot->bitstream) {
out.stage = 2;
out.nvstatus = NV_ENC_ERR_INVALID_PTR;
strncpy(out.message, "pipeline slot is not ready for encode", sizeof(out.message)-1);
return out;
}
if (enc->use_video_processor && (!slot->source_texture || !slot->video_input_view || !slot->video_output_view)) {
out.stage = 2;
out.nvstatus = NV_ENC_ERR_INVALID_PTR;
strncpy(out.message, "video-processor pipeline slot is not ready for encode", sizeof(out.message)-1);
return out;
}
QueryPerformanceCounter(&t0);
enc->context->lpVtbl->CopyResource(enc->context, (ID3D11Resource*)slot->source_texture, (ID3D11Resource*)texture);
enc->context->lpVtbl->CopyResource(enc->context, enc->use_video_processor ? (ID3D11Resource*)slot->source_texture : (ID3D11Resource*)slot->input_texture, (ID3D11Resource*)texture);
QueryPerformanceCounter(&t1);
out.copy_ms = nvenc_qpc_ms(t0, t1, qpc_freq);
RECT src_rect;
src_rect.left = 0;
src_rect.top = 0;
src_rect.right = enc->input_width;
src_rect.bottom = enc->input_height;
RECT dst_rect;
dst_rect.left = 0;
dst_rect.top = 0;
dst_rect.right = enc->encode_width;
dst_rect.bottom = enc->encode_height;
enc->video_context->lpVtbl->VideoProcessorSetStreamFrameFormat(enc->video_context, enc->video_processor, 0, D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE);
nvenc_set_video_processor_color_space(enc);
enc->video_context->lpVtbl->VideoProcessorSetStreamSourceRect(enc->video_context, enc->video_processor, 0, TRUE, &src_rect);
enc->video_context->lpVtbl->VideoProcessorSetStreamDestRect(enc->video_context, enc->video_processor, 0, TRUE, &dst_rect);
enc->video_context->lpVtbl->VideoProcessorSetOutputTargetRect(enc->video_context, enc->video_processor, TRUE, &dst_rect);
enc->video_context->lpVtbl->VideoProcessorSetStreamAutoProcessingMode(enc->video_context, enc->video_processor, 0, FALSE);
D3D11_VIDEO_PROCESSOR_STREAM stream;
memset(&stream, 0, sizeof(stream));
stream.Enable = TRUE;
stream.OutputIndex = 0;
stream.InputFrameOrField = 0;
stream.pInputSurface = slot->video_input_view;
QueryPerformanceCounter(&t0);
HRESULT hr = enc->video_context->lpVtbl->VideoProcessorBlt(enc->video_context, enc->video_processor, slot->video_output_view, enc->frame, 1, &stream);
QueryPerformanceCounter(&t1);
out.blt_ms = nvenc_qpc_ms(t0, t1, qpc_freq);
if (FAILED(hr)) {
out.stage = 4;
out.nvstatus = NV_ENC_ERR_GENERIC;
snprintf(out.message, sizeof(out.message), "VideoProcessorBlt BGRA->NV12 failed hr=0x%08lx", (unsigned long)hr);
return out;
if (enc->use_video_processor) {
RECT src_rect;
src_rect.left = 0;
src_rect.top = 0;
src_rect.right = enc->input_width;
src_rect.bottom = enc->input_height;
RECT dst_rect;
dst_rect.left = 0;
dst_rect.top = 0;
dst_rect.right = enc->encode_width;
dst_rect.bottom = enc->encode_height;
enc->video_context->lpVtbl->VideoProcessorSetStreamFrameFormat(enc->video_context, enc->video_processor, 0, D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE);
nvenc_set_video_processor_color_space(enc);
enc->video_context->lpVtbl->VideoProcessorSetStreamSourceRect(enc->video_context, enc->video_processor, 0, TRUE, &src_rect);
enc->video_context->lpVtbl->VideoProcessorSetStreamDestRect(enc->video_context, enc->video_processor, 0, TRUE, &dst_rect);
enc->video_context->lpVtbl->VideoProcessorSetOutputTargetRect(enc->video_context, enc->video_processor, TRUE, &dst_rect);
enc->video_context->lpVtbl->VideoProcessorSetStreamAutoProcessingMode(enc->video_context, enc->video_processor, 0, FALSE);
D3D11_VIDEO_PROCESSOR_STREAM stream;
memset(&stream, 0, sizeof(stream));
stream.Enable = TRUE;
stream.OutputIndex = 0;
stream.InputFrameOrField = 0;
stream.pInputSurface = slot->video_input_view;
QueryPerformanceCounter(&t0);
HRESULT hr = enc->video_context->lpVtbl->VideoProcessorBlt(enc->video_context, enc->video_processor, slot->video_output_view, enc->frame, 1, &stream);
QueryPerformanceCounter(&t1);
out.blt_ms = nvenc_qpc_ms(t0, t1, qpc_freq);
if (FAILED(hr)) {
out.stage = 4;
out.nvstatus = NV_ENC_ERR_GENERIC;
snprintf(out.message, sizeof(out.message), "VideoProcessorBlt failed hr=0x%08lx", (unsigned long)hr);
return out;
}
}
NV_ENC_MAP_INPUT_RESOURCE map;
@@ -1444,6 +1461,7 @@ import (
"os"
"strings"
"sync"
"sync/atomic"
"unsafe"
)
@@ -1668,6 +1686,10 @@ func newNativeD3D11TextureH264Encoder(device unsafe.Pointer, inputWidth, inputHe
}
return nil, fmt.Errorf("NVENC D3D11 texture encoder create failed")
}
pipeline := "d3d11_video_processor"
if outputMode == 1 && inputWidth == encodeWidth && inputHeight == encodeHeight {
pipeline = "d3d11_direct_copy"
}
enc := &nativeD3D11TextureH264Encoder{
enc: raw.encoder,
device: device,
@@ -1682,9 +1704,9 @@ func newNativeD3D11TextureH264Encoder(device unsafe.Pointer, inputWidth, inputHe
outputName: outputName,
}
if inputWidth != encodeWidth || inputHeight != encodeHeight {
log.Printf("capture: native NVENC D3D11 desktop texture encoder active provider=NVIDIA NVENC input=%dx%d output=%dx%d fps=%d source_format=%s(dxgi=%d) nvenc_input=%s gpu_scale=d3d11_video_processor bitrate=%d", inputWidth, inputHeight, encodeWidth, encodeHeight, fps, name, dxgiFormat, outputName, bitrate)
log.Printf("capture: native NVENC D3D11 desktop texture encoder active provider=NVIDIA NVENC input=%dx%d output=%dx%d fps=%d source_format=%s(dxgi=%d) nvenc_input=%s pipeline=%s bitrate=%d", inputWidth, inputHeight, encodeWidth, encodeHeight, fps, name, dxgiFormat, outputName, pipeline, bitrate)
} else {
log.Printf("capture: native NVENC D3D11 desktop texture encoder active provider=NVIDIA NVENC size=%dx%d fps=%d source_format=%s(dxgi=%d) nvenc_input=%s gpu_scale=d3d11_video_processor bitrate=%d", encodeWidth, encodeHeight, fps, name, dxgiFormat, outputName, bitrate)
log.Printf("capture: native NVENC D3D11 desktop texture encoder active provider=NVIDIA NVENC size=%dx%d fps=%d source_format=%s(dxgi=%d) nvenc_input=%s pipeline=%s bitrate=%d", encodeWidth, encodeHeight, fps, name, dxgiFormat, outputName, pipeline, bitrate)
}
return enc, nil
}
@@ -1782,7 +1804,7 @@ func preferredNVENCD3D11OutputModes() []int {
case "bgra", "argb":
return []int{1}
default:
return []int{0, 1}
return []int{1, 0}
}
}
@@ -1812,8 +1834,9 @@ func envBool(name string) bool {
}
var (
nativeTextureH264Mu sync.Mutex
nativeTextureH264Enc *nativeD3D11TextureH264Encoder
nativeTextureH264Mu sync.Mutex
nativeTextureH264Enc *nativeD3D11TextureH264Encoder
nativeTextureH264ForceIDR atomic.Bool
)
func encodeNativeH264D3D11Texture(device, texture unsafe.Pointer, inputWidth, inputHeight, encodeWidth, encodeHeight, fps int, dxgiFormat uint32, forceIDR bool) ([]byte, error) {
@@ -1834,7 +1857,11 @@ func encodeNativeH264D3D11Texture(device, texture unsafe.Pointer, inputWidth, in
}
nativeTextureH264Enc = enc
}
return nativeTextureH264Enc.EncodeTexture(texture, forceIDR)
return nativeTextureH264Enc.EncodeTexture(texture, forceIDR || nativeTextureH264ForceIDR.Swap(false))
}
func requestNativeH264D3D11TextureKeyframe() {
nativeTextureH264ForceIDR.Store(true)
}
func resetNativeH264D3D11TextureEncoder() {
@@ -1844,4 +1871,5 @@ func resetNativeH264D3D11TextureEncoder() {
nativeTextureH264Enc.Close()
nativeTextureH264Enc = nil
}
nativeTextureH264ForceIDR.Store(false)
}
@@ -617,6 +617,8 @@ type duplicationState struct {
context *d3d11DeviceContext
staging *d3d11Texture2D
stagingDesc d3d11Texture2DDesc
h264LastTex *d3d11Texture2D
h264LastDesc d3d11Texture2DDesc
desc dxgiOutDuplDesc
lastBase *image.RGBA
lastFrame *image.RGBA
@@ -658,6 +660,11 @@ func (s *duplicationState) closeLocked() {
s.staging.Release()
s.staging = nil
}
if s.h264LastTex != nil {
s.h264LastTex.Release()
s.h264LastTex = nil
s.h264LastDesc = d3d11Texture2DDesc{}
}
resetNativeH264D3D11TextureEncoder()
if s.context != nil {
s.context.Release()
@@ -888,6 +895,23 @@ func (s *duplicationState) captureH264(display int, forceKeyframe bool) ([]byte,
var resource *iunknown
hr := s.dup.AcquireNextFrame(5, &info, &resource)
if hr == dxgiErrorWaitTimeout {
if s.h264LastTex != nil {
captureDur := time.Since(capStart)
fps := activeH264FPS()
encStart := time.Now()
out, err := encodeNativeH264D3D11Texture(unsafe.Pointer(s.device), unsafe.Pointer(s.h264LastTex), width, height, encodeW, encodeH, fps, s.h264LastDesc.Format, forceKeyframe)
encodeDur := time.Since(encStart)
if err != nil {
directH264WarnOnce.Do(func() {
log.Printf("capture: direct DXGI->NVENC h264 cached-frame encode failed for %dx%d@%dfps format=%d: %v; using readback path", width, height, fps, s.h264LastDesc.Format, err)
})
return nil, 0, 0, 0, 0, false, err
}
if len(out) == 0 {
return nil, encodeW, encodeH, captureDur, encodeDur, true, nil
}
return out, encodeW, encodeH, captureDur, encodeDur, true, nil
}
return nil, encodeW, encodeH, time.Since(capStart), 0, true, nil
}
if hr == dxgiErrorAccessLost {
@@ -907,7 +931,12 @@ func (s *duplicationState) captureH264(display int, forceKeyframe bool) ([]byte,
if resource == nil {
return nil, 0, 0, 0, 0, false, errors.New("dxgi: acquired nil resource")
}
defer s.dup.ReleaseFrame()
frameReleased := false
defer func() {
if !frameReleased {
_ = s.dup.ReleaseFrame()
}
}()
var tex *d3d11Texture2D
hr = resource.QueryInterface(&IID_ID3D11Texture2D, unsafe.Pointer(&tex))
@@ -930,15 +959,24 @@ func (s *duplicationState) captureH264(display int, forceKeyframe bool) ([]byte,
})
return nil, 0, 0, 0, 0, false, fmt.Errorf("direct h264 unsupported DXGI texture format %d", srcDesc.Format)
}
if err := s.cacheH264TextureLocked(tex, srcDesc); err != nil {
directH264WarnOnce.Do(func() {
log.Printf("capture: direct DXGI->NVENC h264 cache setup failed for %dx%d format=%d: %v; using readback path", width, height, srcDesc.Format, err)
})
return nil, 0, 0, 0, 0, false, err
}
captureDur := time.Since(capStart)
_ = s.dup.ReleaseFrame()
frameReleased = true
fps := activeH264FPS()
encStart := time.Now()
out, err := encodeNativeH264D3D11Texture(unsafe.Pointer(s.device), unsafe.Pointer(tex), width, height, encodeW, encodeH, fps, srcDesc.Format, forceKeyframe)
out, err := encodeNativeH264D3D11Texture(unsafe.Pointer(s.device), unsafe.Pointer(s.h264LastTex), width, height, encodeW, encodeH, fps, s.h264LastDesc.Format, forceKeyframe)
encodeDur := time.Since(encStart)
if err != nil {
directH264WarnOnce.Do(func() {
log.Printf("capture: direct DXGI->NVENC h264 failed for %dx%d@%dfps format=%d: %v; using readback path", width, height, fps, srcDesc.Format, err)
log.Printf("capture: direct DXGI->NVENC h264 failed for %dx%d@%dfps format=%d: %v; using readback path", width, height, fps, s.h264LastDesc.Format, err)
})
return nil, 0, 0, 0, 0, false, err
}
@@ -951,6 +989,40 @@ func (s *duplicationState) captureH264(display int, forceKeyframe bool) ([]byte,
return out, encodeW, encodeH, captureDur, encodeDur, true, nil
}
func (s *duplicationState) cacheH264TextureLocked(src *d3d11Texture2D, srcDesc d3d11Texture2DDesc) error {
if src == nil || s.device == nil || s.context == nil {
return errors.New("nil D3D11 source/device/context")
}
recreate := s.h264LastTex == nil ||
s.h264LastDesc.Width != srcDesc.Width ||
s.h264LastDesc.Height != srcDesc.Height ||
s.h264LastDesc.Format != srcDesc.Format
if recreate {
if s.h264LastTex != nil {
s.h264LastTex.Release()
s.h264LastTex = nil
}
cacheDesc := srcDesc
cacheDesc.MipLevels = 1
cacheDesc.ArraySize = 1
cacheDesc.SampleDesc.Count = 1
cacheDesc.SampleDesc.Quality = 0
cacheDesc.Usage = 0 // D3D11_USAGE_DEFAULT
cacheDesc.BindFlags = 0
cacheDesc.CPUAccessFlags = 0
cacheDesc.MiscFlags = 0
var tex *d3d11Texture2D
hr := s.device.CreateTexture2D(&cacheDesc, nil, &tex)
if hr != S_OK || tex == nil {
return fmt.Errorf("CreateTexture2D h264 cache failed 0x%x", hr)
}
s.h264LastTex = tex
s.h264LastDesc = cacheDesc
}
s.context.CopyResource(s.h264LastTex, src)
return nil
}
func (s *duplicationState) composeFrame(base *image.RGBA, nativeW, nativeH int) *image.RGBA {
if base == nil {
return nil
@@ -46,7 +46,7 @@ func HandleScreenshot(ctx context.Context, env *rt.Env, cmdID string, allDisplay
}
}()
img, displayIndex, bounds, err := captureScreenshotImage(allDisplays)
img, _, bounds, err := captureScreenshotImage(allDisplays)
if err != nil {
log.Printf("screenshot: capture failed: %v", err)
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
@@ -86,20 +86,7 @@ func HandleScreenshot(ctx context.Context, env *rt.Env, cmdID string, allDisplay
return err
}
frame := wire.Frame{
Type: "frame",
Header: wire.FrameHeader{
Monitor: displayIndex,
FPS: 0,
Format: "jpeg",
},
Data: jpegData,
}
if err := wire.WriteMsg(ctx, env.Conn, frame); err != nil {
log.Printf("screenshot: failed to send frame: %v", err)
return err
}
capture.RequestDesktopH264Keyframe()
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
Type: "command_result",
@@ -3,9 +3,15 @@
package handlers
import (
"context"
"image"
"image/color"
"testing"
rt "overlord-client/cmd/agent/runtime"
"overlord-client/cmd/agent/wire"
"github.com/vmihailenco/msgpack/v5"
)
func TestCaptureScreenshotImageWindows_PrimaryOnly(t *testing.T) {
@@ -105,3 +111,44 @@ func TestCaptureScreenshotImageWindows_NoDisplays(t *testing.T) {
t.Fatal("expected nil image on error")
}
}
func TestHandleScreenshot_DoesNotEmitLiveFrame(t *testing.T) {
origMonitorCountFn := monitorCountFn
origCaptureFn := captureDisplayRGBABitBltFn
t.Cleanup(func() {
monitorCountFn = origMonitorCountFn
captureDisplayRGBABitBltFn = origCaptureFn
})
monitorCountFn = func() int { return 1 }
captureDisplayRGBABitBltFn = func(display int) (*image.RGBA, error) {
img := image.NewRGBA(image.Rect(0, 0, 2, 2))
img.SetRGBA(0, 0, color.RGBA{B: 255, A: 255})
return img, nil
}
ctx := context.Background()
writer := &testWriter{}
if err := HandleScreenshot(ctx, &rt.Env{Conn: writer}, "cmd-1", false); err != nil {
t.Fatalf("HandleScreenshot failed: %v", err)
}
if len(writer.msgs) != 2 {
t.Fatalf("expected 2 messages without live frame emission, got %d", len(writer.msgs))
}
var first wire.ScreenshotResult
if err := msgpack.Unmarshal(writer.msgs[0], &first); err != nil {
t.Fatalf("unmarshal screenshot_result: %v", err)
}
if first.Type != "screenshot_result" {
t.Fatalf("expected screenshot_result, got %q", first.Type)
}
var second wire.CommandResult
if err := msgpack.Unmarshal(writer.msgs[1], &second); err != nil {
t.Fatalf("unmarshal command_result: %v", err)
}
if second.Type != "command_result" || !second.OK {
t.Fatalf("expected successful command_result, got %+v", second)
}
}