From fb6c856ad1c633f293ec74a5361434850cbd99f4 Mon Sep 17 00:00:00 2001 From: KingKDot Date: Sat, 11 Jul 2026 16:52:24 -0500 Subject: [PATCH] nvidia quick sync --- .../capture/h264_texture_encoder_windows.go | 3 + .../agent/capture/qsv_d3d11_stub_windows.go | 6 + .../cmd/agent/capture/qsv_d3d11_windows.go | 78 ++++++ .../agent/capture/qsvbridge/bridge_windows.cc | 235 ++++++++++++++++++ .../agent/capture/qsvbridge/bridge_windows.go | 77 ++++++ .../agent/capture/qsvbridge/bridge_windows.h | 15 ++ Overlord-Server/public/assets/build.js | 5 + Overlord-Server/public/build.html | 8 + Overlord-Server/src/build/types.ts | 1 + Overlord-Server/src/server/build-process.ts | 34 +++ .../src/server/routes/build-routes.ts | 2 + scripts/build-clients.bat | 5 + scripts/start-dev.bat | 8 + scripts/vendor-onevpl-headers.bat | 26 ++ scripts/vendor-onevpl-headers.sh | 18 ++ 15 files changed, 521 insertions(+) create mode 100644 Overlord-Client/cmd/agent/capture/qsv_d3d11_stub_windows.go create mode 100644 Overlord-Client/cmd/agent/capture/qsv_d3d11_windows.go create mode 100644 Overlord-Client/cmd/agent/capture/qsvbridge/bridge_windows.cc create mode 100644 Overlord-Client/cmd/agent/capture/qsvbridge/bridge_windows.go create mode 100644 Overlord-Client/cmd/agent/capture/qsvbridge/bridge_windows.h create mode 100644 scripts/vendor-onevpl-headers.bat create mode 100644 scripts/vendor-onevpl-headers.sh diff --git a/Overlord-Client/cmd/agent/capture/h264_texture_encoder_windows.go b/Overlord-Client/cmd/agent/capture/h264_texture_encoder_windows.go index 0b14e1d..5a37fe5 100644 --- a/Overlord-Client/cmd/agent/capture/h264_texture_encoder_windows.go +++ b/Overlord-Client/cmd/agent/capture/h264_texture_encoder_windows.go @@ -54,6 +54,9 @@ var d3d11H264TextureRegistry = struct { } func init() { + if backend := newQSVD3D11TextureBackend(); backend != nil { + d3d11H264TextureRegistry.backends = append(d3d11H264TextureRegistry.backends, backend) + } if backend := newAMFD3D11TextureBackend(); backend != nil { d3d11H264TextureRegistry.backends = append(d3d11H264TextureRegistry.backends, backend) } diff --git a/Overlord-Client/cmd/agent/capture/qsv_d3d11_stub_windows.go b/Overlord-Client/cmd/agent/capture/qsv_d3d11_stub_windows.go new file mode 100644 index 0000000..943c751 --- /dev/null +++ b/Overlord-Client/cmd/agent/capture/qsv_d3d11_stub_windows.go @@ -0,0 +1,6 @@ +//go:build windows && (!cgo || no_qsv) + +package capture + +func newQSVD3D11TextureBackend() h264D3D11TextureBackend { return nil } +func probeQSVD3D11() (bool, string) { return false, "Intel Quick Sync requires a Windows CGO build" } diff --git a/Overlord-Client/cmd/agent/capture/qsv_d3d11_windows.go b/Overlord-Client/cmd/agent/capture/qsv_d3d11_windows.go new file mode 100644 index 0000000..5f376b9 --- /dev/null +++ b/Overlord-Client/cmd/agent/capture/qsv_d3d11_windows.go @@ -0,0 +1,78 @@ +//go:build windows && cgo && !no_qsv + +package capture + +import ( + "fmt" + "overlord-client/cmd/agent/capture/qsvbridge" + "sync/atomic" + "unsafe" +) + +type qsvD3D11TextureBackend struct { + encoder *qsvbridge.Encoder + device unsafe.Pointer + inputWidth, inputHeight, encodeWidth, encodeHeight, fps int + dxgiFormat uint32 + output []byte + forceIDR atomic.Bool +} + +func newQSVD3D11TextureBackend() h264D3D11TextureBackend { + return &qsvD3D11TextureBackend{output: make([]byte, 1024*1024)} +} + +func (b *qsvD3D11TextureBackend) Name() string { return "Intel Quick Sync (oneVPL)" } + +func (b *qsvD3D11TextureBackend) matches(req h264D3D11TextureRequest) bool { + return b.encoder != nil && b.device == req.Device && b.inputWidth == req.InputWidth && + b.inputHeight == req.InputHeight && b.encodeWidth == req.EncodeWidth && + b.encodeHeight == req.EncodeHeight && b.fps == req.FPS && b.dxgiFormat == req.DXGIFormat +} + +func (b *qsvD3D11TextureBackend) Encode(req h264D3D11TextureRequest) ([]byte, error) { + if !b.matches(req) { + b.Reset() + encoder, err := qsvbridge.Create(req.Device, req.InputWidth, req.InputHeight, req.EncodeWidth, + req.EncodeHeight, req.FPS, req.DXGIFormat, targetH264Bitrate(req.EncodeWidth, req.EncodeHeight, req.FPS)) + if err != nil { + return nil, err + } + b.encoder = encoder + b.device, b.inputWidth, b.inputHeight = req.Device, req.InputWidth, req.InputHeight + b.encodeWidth, b.encodeHeight, b.fps, b.dxgiFormat = req.EncodeWidth, req.EncodeHeight, req.FPS, req.DXGIFormat + } + forceIDR := req.ForceIDR || b.forceIDR.Swap(false) + for attempts := 0; attempts < 2; attempts++ { + result, size, err := b.encoder.Encode(req.Texture, forceIDR, b.output) + if err != nil { + return nil, err + } + if result == 2 { + if size <= 0 || size > 64*1024*1024 { + return nil, fmt.Errorf("oneVPL returned invalid output size %d", size) + } + b.output = make([]byte, size) + forceIDR = false + continue + } + if size == 0 { + return nil, nil + } + out := make([]byte, size) + copy(out, b.output[:size]) + return out, nil + } + return nil, fmt.Errorf("oneVPL output buffer resize retry failed") +} + +func (b *qsvD3D11TextureBackend) RequestKeyframe() { b.forceIDR.Store(true) } +func (b *qsvD3D11TextureBackend) Reset() { + if b.encoder != nil { + b.encoder.Close() + } + b.encoder, b.device = nil, nil + b.forceIDR.Store(false) +} + +func probeQSVD3D11() (bool, string) { return qsvbridge.Probe() } diff --git a/Overlord-Client/cmd/agent/capture/qsvbridge/bridge_windows.cc b/Overlord-Client/cmd/agent/capture/qsvbridge/bridge_windows.cc new file mode 100644 index 0000000..3584c4d --- /dev/null +++ b/Overlord-Client/cmd/agent/capture/qsvbridge/bridge_windows.cc @@ -0,0 +1,235 @@ +//go:build windows && cgo + +#include "bridge_windows.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define ONEVPL_EXPERIMENTAL +#include "../../../../third_party/onevpl/include/vpl/mfxdispatcher.h" +#include "../../../../third_party/onevpl/include/vpl/mfxvideo.h" + +namespace { + +template void release(T*& value) { if (value) { value->Release(); value = nullptr; } } +void set_error(char* dst, int cap, const std::string& text) { + if (!dst || cap <= 0) return; + size_t n = std::min(text.size(), static_cast(cap - 1)); + std::memcpy(dst, text.data(), n); dst[n] = 0; +} +std::string status_error(const char* stage, mfxStatus sts) { + return std::string(stage) + " failed (mfxStatus=" + std::to_string(static_cast(sts)) + ")"; +} +std::string hr_error(const char* stage, HRESULT hr) { + return std::string(stage) + " failed (HRESULT=0x" + [] (HRESULT v) { + char b[16]; std::snprintf(b, sizeof(b), "%08lx", static_cast(v)); return std::string(b); + }(hr) + ")"; +} + +struct VPLFunctions { + HMODULE dll = nullptr; + decltype(&MFXLoad) Load = nullptr; + decltype(&MFXUnload) Unload = nullptr; + decltype(&MFXCreateConfig) CreateConfig = nullptr; + decltype(&MFXSetConfigFilterProperty) SetConfig = nullptr; + decltype(&MFXCreateSession) CreateSession = nullptr; + decltype(&MFXClose) Close = nullptr; + decltype(&MFXVideoCORE_SetHandle) SetHandle = nullptr; + decltype(&MFXVideoCORE_GetHandle) GetHandle = nullptr; + decltype(&MFXVideoENCODE_Init) EncodeInit = nullptr; + decltype(&MFXVideoENCODE_Close) EncodeClose = nullptr; + decltype(&MFXVideoENCODE_EncodeFrameAsync) EncodeFrame = nullptr; + decltype(&MFXVideoCORE_SyncOperation) Sync = nullptr; + + bool open(std::string& error) { + dll = LoadLibraryW(L"libvpl.dll"); + if (!dll) dll = LoadLibraryW(L"vpl.dll"); + if (!dll) { error = "Intel oneVPL dispatcher (libvpl.dll) is not installed"; return false; } +#define LOAD_VPL(member, symbol) member = reinterpret_cast(GetProcAddress(dll, symbol)); if (!member) { error = std::string("oneVPL dispatcher is missing ") + symbol; close(); return false; } + LOAD_VPL(Load, "MFXLoad"); LOAD_VPL(Unload, "MFXUnload"); + LOAD_VPL(CreateConfig, "MFXCreateConfig"); LOAD_VPL(SetConfig, "MFXSetConfigFilterProperty"); + LOAD_VPL(CreateSession, "MFXCreateSession"); LOAD_VPL(Close, "MFXClose"); + LOAD_VPL(SetHandle, "MFXVideoCORE_SetHandle"); LOAD_VPL(GetHandle, "MFXVideoCORE_GetHandle"); + LOAD_VPL(EncodeInit, "MFXVideoENCODE_Init"); LOAD_VPL(EncodeClose, "MFXVideoENCODE_Close"); + LOAD_VPL(EncodeFrame, "MFXVideoENCODE_EncodeFrameAsync"); LOAD_VPL(Sync, "MFXVideoCORE_SyncOperation"); +#undef LOAD_VPL + return true; + } + void close() { if (dll) FreeLibrary(dll); dll = nullptr; } +}; + +struct QSVEncoder { + VPLFunctions api; + mfxLoader loader = nullptr; + mfxSession session = nullptr; + mfxMemoryInterface* memory = nullptr; + ID3D11Device* device = nullptr; + ID3D11DeviceContext* context = nullptr; + ID3D11VideoDevice* videoDevice = nullptr; + ID3D11VideoContext* videoContext = nullptr; + ID3D11VideoProcessorEnumerator* processorEnum = nullptr; + ID3D11VideoProcessor* processor = nullptr; + ID3D11Texture2D* nv12 = nullptr; + ID3D11VideoProcessorOutputView* outputView = nullptr; + int inputWidth = 0, inputHeight = 0, width = 0, height = 0; + std::vector bitstream; +}; + +void destroy(QSVEncoder* s) { + if (!s) return; + if (s->session) { s->api.EncodeClose(s->session); s->api.Close(s->session); } + if (s->loader) s->api.Unload(s->loader); + release(s->outputView); release(s->nv12); release(s->processor); release(s->processorEnum); + release(s->videoContext); release(s->videoDevice); release(s->context); release(s->device); + s->api.close(); delete s; +} + +bool config_u32(QSVEncoder* s, const char* name, mfxU32 value, std::string& error) { + mfxConfig cfg = s->api.CreateConfig(s->loader); + if (!cfg) { error = "MFXCreateConfig failed"; return false; } + mfxVariant v = {}; v.Type = MFX_VARIANT_TYPE_U32; v.Data.U32 = value; + mfxStatus sts = s->api.SetConfig(cfg, reinterpret_cast(name), v); + if (sts != MFX_ERR_NONE) { error = status_error(name, sts); return false; } + return true; +} + +bool init_session(QSVEncoder* s, std::string& error) { + s->loader = s->api.Load(); + if (!s->loader) { error = "MFXLoad found no oneVPL dispatcher"; return false; } + if (!config_u32(s, "mfxImplDescription.Impl", MFX_IMPL_TYPE_HARDWARE, error) || + !config_u32(s, "mfxImplDescription.VendorID", 0x8086, error) || + !config_u32(s, "mfxImplDescription.mfxEncoderDescription.encoder.CodecID", MFX_CODEC_AVC, error) || + !config_u32(s, "mfxImplDescription.AccelerationMode", MFX_ACCEL_MODE_VIA_D3D11, error) || + !config_u32(s, "mfxImplDescription.ApiVersion.Version", (2u << 16) | 10u, error)) return false; + mfxStatus sts = s->api.CreateSession(s->loader, 0, &s->session); + if (sts != MFX_ERR_NONE || !s->session) { error = status_error("MFXCreateSession (Intel D3D11 H.264)", sts); return false; } + return true; +} + +bool init_video_processor(QSVEncoder* s, std::string& error) { + HRESULT hr = s->device->QueryInterface(__uuidof(ID3D11VideoDevice), reinterpret_cast(&s->videoDevice)); + if (FAILED(hr)) { error = hr_error("ID3D11VideoDevice", hr); return false; } + hr = s->context->QueryInterface(__uuidof(ID3D11VideoContext), reinterpret_cast(&s->videoContext)); + if (FAILED(hr)) { error = hr_error("ID3D11VideoContext", hr); return false; } + D3D11_VIDEO_PROCESSOR_CONTENT_DESC desc = {}; + desc.InputFrameFormat = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE; + desc.InputFrameRate = {60, 1}; desc.OutputFrameRate = {60, 1}; + desc.InputWidth = s->inputWidth; desc.InputHeight = s->inputHeight; + desc.OutputWidth = s->width; desc.OutputHeight = s->height; + desc.Usage = D3D11_VIDEO_USAGE_PLAYBACK_NORMAL; + hr = s->videoDevice->CreateVideoProcessorEnumerator(&desc, &s->processorEnum); + if (FAILED(hr)) { error = hr_error("CreateVideoProcessorEnumerator", hr); return false; } + hr = s->videoDevice->CreateVideoProcessor(s->processorEnum, 0, &s->processor); + if (FAILED(hr)) { error = hr_error("CreateVideoProcessor", hr); return false; } + D3D11_TEXTURE2D_DESC td = {}; + td.Width = (s->width + 15) & ~15; td.Height = (s->height + 15) & ~15; + td.MipLevels = 1; td.ArraySize = 1; td.Format = DXGI_FORMAT_NV12; td.SampleDesc.Count = 1; + td.Usage = D3D11_USAGE_DEFAULT; td.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + td.MiscFlags = D3D11_RESOURCE_MISC_SHARED; + hr = s->device->CreateTexture2D(&td, nullptr, &s->nv12); + if (FAILED(hr)) { error = hr_error("CreateTexture2D NV12", hr); return false; } + D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC ov = {}; ov.ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2D; + hr = s->videoDevice->CreateVideoProcessorOutputView(s->nv12, s->processorEnum, &ov, &s->outputView); + if (FAILED(hr)) { error = hr_error("CreateVideoProcessorOutputView", hr); return false; } + RECT src = {0, 0, s->inputWidth, s->inputHeight}, dst = {0, 0, s->width, s->height}; + s->videoContext->VideoProcessorSetStreamSourceRect(s->processor, 0, TRUE, &src); + s->videoContext->VideoProcessorSetStreamDestRect(s->processor, 0, TRUE, &dst); + s->videoContext->VideoProcessorSetOutputTargetRect(s->processor, TRUE, &dst); + return true; +} + +bool convert(QSVEncoder* s, ID3D11Texture2D* texture, std::string& error) { + D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC iv = {}; + iv.FourCC = 0; iv.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D; iv.Texture2D.MipSlice = 0; + ID3D11VideoProcessorInputView* input = nullptr; + HRESULT hr = s->videoDevice->CreateVideoProcessorInputView(texture, s->processorEnum, &iv, &input); + if (FAILED(hr)) { error = hr_error("CreateVideoProcessorInputView", hr); return false; } + D3D11_VIDEO_PROCESSOR_STREAM stream = {}; stream.Enable = TRUE; stream.pInputSurface = input; + hr = s->videoContext->VideoProcessorBlt(s->processor, s->outputView, 0, 1, &stream); + input->Release(); + if (FAILED(hr)) { error = hr_error("VideoProcessorBlt", hr); return false; } + return true; +} + +} // namespace + +extern "C" int overlord_qsv_probe(char* error_text, int error_capacity) { + auto* s = new QSVEncoder(); std::string error; + bool ok = s->api.open(error) && init_session(s, error); + if (!ok) set_error(error_text, error_capacity, error); + destroy(s); return ok ? 1 : 0; +} + +extern "C" int overlord_qsv_create(void* device, int input_width, int input_height, + int width, int height, int fps, uint32_t, int bitrate, overlord_qsv_encoder* output, + char* error_text, int error_capacity) { + if (!device || !output || input_width <= 0 || input_height <= 0 || width <= 0 || height <= 0 || fps <= 0) { + set_error(error_text, error_capacity, "invalid oneVPL create arguments"); return 0; + } + *output = nullptr; auto* s = new QSVEncoder(); std::string error; + s->device = static_cast(device); s->device->AddRef(); s->device->GetImmediateContext(&s->context); + s->inputWidth = input_width; s->inputHeight = input_height; s->width = width; s->height = height; + if (!s->api.open(error) || !init_session(s, error)) goto fail; + { + mfxStatus sts = s->api.SetHandle(s->session, MFX_HANDLE_D3D11_DEVICE, device); + if (sts != MFX_ERR_NONE) { error = status_error("MFXVideoCORE_SetHandle", sts); goto fail; } + if (!init_video_processor(s, error)) goto fail; + mfxVideoParam p = {}; p.IOPattern = MFX_IOPATTERN_IN_VIDEO_MEMORY; p.AsyncDepth = 1; + p.mfx.CodecId = MFX_CODEC_AVC; p.mfx.CodecProfile = MFX_PROFILE_AVC_MAIN; + p.mfx.TargetUsage = MFX_TARGETUSAGE_BEST_SPEED; p.mfx.RateControlMethod = MFX_RATECONTROL_VBR; + p.mfx.TargetKbps = static_cast(std::max(1, bitrate / 1000)); p.mfx.GopRefDist = 1; + p.mfx.GopPicSize = static_cast(std::max(fps * 2, 1)); p.mfx.IdrInterval = 0; + p.mfx.FrameInfo.FourCC = MFX_FOURCC_NV12; p.mfx.FrameInfo.ChromaFormat = MFX_CHROMAFORMAT_YUV420; + p.mfx.FrameInfo.Width = static_cast((width + 15) & ~15); p.mfx.FrameInfo.Height = static_cast((height + 15) & ~15); + p.mfx.FrameInfo.CropW = static_cast(width); p.mfx.FrameInfo.CropH = static_cast(height); + p.mfx.FrameInfo.FrameRateExtN = fps; p.mfx.FrameInfo.FrameRateExtD = 1; p.mfx.FrameInfo.PicStruct = MFX_PICSTRUCT_PROGRESSIVE; + sts = s->api.EncodeInit(s->session, &p); + if (sts < MFX_ERR_NONE) { error = status_error("MFXVideoENCODE_Init", sts); goto fail; } + sts = s->api.GetHandle(s->session, MFX_HANDLE_MEMORY_INTERFACE, reinterpret_cast(&s->memory)); + if (sts != MFX_ERR_NONE || !s->memory) { error = status_error("MFXGetMemoryInterface", sts); goto fail; } + } + s->bitstream.resize(std::max(1024 * 1024, static_cast(bitrate) / 2)); + *output = s; return 1; +fail: set_error(error_text, error_capacity, error); destroy(s); return 0; +} + +extern "C" int overlord_qsv_encode(overlord_qsv_encoder opaque, void* texture, int force_idr, + uint8_t* output, int capacity, int* output_size, char* error_text, int error_capacity) { + auto* s = static_cast(opaque); if (!s || !texture || !output_size) return 0; *output_size = 0; + std::string error; if (!convert(s, static_cast(texture), error)) goto fail; + { + mfxSurfaceD3D11Tex2D ext = {}; + ext.SurfaceInterface.Header.SurfaceType = MFX_SURFACE_TYPE_D3D11_TEX2D; + ext.SurfaceInterface.Header.SurfaceFlags = MFX_SURFACE_FLAG_IMPORT_SHARED; + ext.SurfaceInterface.Header.StructSize = sizeof(ext); ext.texture2D = s->nv12; + mfxFrameSurface1* surface = nullptr; + mfxStatus sts = s->memory->ImportFrameSurface(s->memory, MFX_SURFACE_COMPONENT_ENCODE, + &ext.SurfaceInterface.Header, &surface); + if (sts != MFX_ERR_NONE || !surface) { error = status_error("ImportFrameSurface", sts); goto fail; } + mfxBitstream bs = {}; bs.Data = s->bitstream.data(); bs.MaxLength = static_cast(s->bitstream.size()); + mfxEncodeCtrl ctrl = {}; if (force_idr) ctrl.FrameType = MFX_FRAMETYPE_IDR | MFX_FRAMETYPE_I | MFX_FRAMETYPE_REF; + mfxSyncPoint sync = nullptr; + for (int tries = 0;; ++tries) { + sts = s->api.EncodeFrame(s->session, force_idr ? &ctrl : nullptr, surface, &bs, &sync); + if (sts != MFX_WRN_DEVICE_BUSY || tries >= 20) break; + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + surface->FrameInterface->Release(surface); + if (sts == MFX_ERR_MORE_DATA) return 1; + if (sts != MFX_ERR_NONE || !sync) { error = status_error("MFXVideoENCODE_EncodeFrameAsync", sts); goto fail; } + sts = s->api.Sync(s->session, sync, 100); + if (sts != MFX_ERR_NONE) { error = status_error("MFXVideoCORE_SyncOperation", sts); goto fail; } + *output_size = static_cast(bs.DataLength); + if (capacity < *output_size || !output) return 2; + std::memcpy(output, bs.Data + bs.DataOffset, bs.DataLength); + } + return 1; +fail: set_error(error_text, error_capacity, error); return 0; +} + +extern "C" void overlord_qsv_destroy(overlord_qsv_encoder encoder) { destroy(static_cast(encoder)); } diff --git a/Overlord-Client/cmd/agent/capture/qsvbridge/bridge_windows.go b/Overlord-Client/cmd/agent/capture/qsvbridge/bridge_windows.go new file mode 100644 index 0000000..a2b0a0b --- /dev/null +++ b/Overlord-Client/cmd/agent/capture/qsvbridge/bridge_windows.go @@ -0,0 +1,77 @@ +//go:build windows && cgo + +package qsvbridge + +/* +#cgo CXXFLAGS: -std=c++17 +#cgo LDFLAGS: -lole32 -ld3d11 -ldxgi +#include +#include "bridge_windows.h" +*/ +import "C" + +import "unsafe" + +type Encoder struct{ handle C.overlord_qsv_encoder } +type Error struct{ Text string } + +func (e *Error) Error() string { return e.Text } + +func Probe() (bool, string) { + b := make([]byte, 512) + if C.overlord_qsv_probe((*C.char)(unsafe.Pointer(&b[0])), C.int(len(b))) == 0 { + return false, message(b, "oneVPL unavailable") + } + return true, "Intel oneVPL H.264 hardware encoder available" +} + +func Create(device unsafe.Pointer, inputWidth, inputHeight, encodeWidth, encodeHeight, fps int, format uint32, bitrate int) (*Encoder, error) { + var h C.overlord_qsv_encoder + b := make([]byte, 512) + ok := C.overlord_qsv_create(device, C.int(inputWidth), C.int(inputHeight), C.int(encodeWidth), C.int(encodeHeight), + C.int(fps), C.uint32_t(format), C.int(bitrate), &h, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b))) + if ok == 0 || h == nil { + return nil, &Error{Text: message(b, "oneVPL encoder creation failed")} + } + return &Encoder{handle: h}, nil +} + +func (e *Encoder) Encode(texture unsafe.Pointer, forceIDR bool, output []byte) (int, int, error) { + if e == nil || e.handle == nil { + return 0, 0, &Error{Text: "oneVPL encoder is not initialized"} + } + b := make([]byte, 512) + var size C.int + var out *C.uint8_t + if len(output) != 0 { + out = (*C.uint8_t)(unsafe.Pointer(&output[0])) + } + force := C.int(0) + if forceIDR { + force = 1 + } + r := C.overlord_qsv_encode(e.handle, texture, force, out, C.int(len(output)), &size, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b))) + if r == 0 { + return 0, int(size), &Error{Text: message(b, "oneVPL encode failed")} + } + return int(r), int(size), nil +} + +func (e *Encoder) Close() { + if e != nil && e.handle != nil { + C.overlord_qsv_destroy(e.handle) + e.handle = nil + } +} +func message(b []byte, fallback string) string { + for i, v := range b { + if v == 0 { + b = b[:i] + break + } + } + if len(b) == 0 { + return fallback + } + return string(b) +} diff --git a/Overlord-Client/cmd/agent/capture/qsvbridge/bridge_windows.h b/Overlord-Client/cmd/agent/capture/qsvbridge/bridge_windows.h new file mode 100644 index 0000000..57628b7 --- /dev/null +++ b/Overlord-Client/cmd/agent/capture/qsvbridge/bridge_windows.h @@ -0,0 +1,15 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif +typedef void* overlord_qsv_encoder; +int overlord_qsv_probe(char*, int); +int overlord_qsv_create(void*, int, int, int, int, int, uint32_t, int, + overlord_qsv_encoder*, char*, int); +int overlord_qsv_encode(overlord_qsv_encoder, void*, int, uint8_t*, int, int*, char*, int); +void overlord_qsv_destroy(overlord_qsv_encoder); +#ifdef __cplusplus +} +#endif diff --git a/Overlord-Server/public/assets/build.js b/Overlord-Server/public/assets/build.js index 83a6f2f..ddaaa10 100644 --- a/Overlord-Server/public/assets/build.js +++ b/Overlord-Server/public/assets/build.js @@ -190,6 +190,7 @@ function setBuildField(field, value) { disableCgo: 'input[name="disable-cgo"]', enableNvenc: 'input[name="enable-nvenc"]', enableAmf: 'input[name="enable-amf"]', + enableQsv: 'input[name="enable-qsv"]', stripDebug: 'input[name="strip-debug"]', noPrinting: 'input[name="no-printing"]', enableWebrtc: 'input[name="enable-webrtc"]', @@ -459,6 +460,7 @@ function collectFormSettings() { disableCgo: document.querySelector('input[name="disable-cgo"]')?.checked ?? false, enableNvenc: document.querySelector('input[name="enable-nvenc"]')?.checked ?? true, enableAmf: document.querySelector('input[name="enable-amf"]')?.checked ?? true, + enableQsv: document.querySelector('input[name="enable-qsv"]')?.checked ?? true, noPrinting: document.querySelector('input[name="no-printing"]')?.checked ?? false, enableKeylogger: document.querySelector('input[name="enable-keylogger"]')?.checked ?? true, enableWebrtc: document.querySelector('input[name="enable-webrtc"]')?.checked ?? false, @@ -528,6 +530,7 @@ function applyFormSettings(settings) { if (settings.disableCgo !== undefined) setCb('input[name="disable-cgo"]', settings.disableCgo); if (settings.enableNvenc !== undefined) setCb('input[name="enable-nvenc"]', settings.enableNvenc); if (settings.enableAmf !== undefined) setCb('input[name="enable-amf"]', settings.enableAmf); + if (settings.enableQsv !== undefined) setCb('input[name="enable-qsv"]', settings.enableQsv); if (settings.noPrinting !== undefined) setCb('input[name="no-printing"]', settings.noPrinting); if (settings.enableKeylogger !== undefined) setCb('input[name="enable-keylogger"]', settings.enableKeylogger); if (settings.enableWebrtc !== undefined) setCb('input[name="enable-webrtc"]', settings.enableWebrtc); @@ -1700,6 +1703,7 @@ form?.addEventListener("submit", async (e) => { const disableCgo = form.querySelector('input[name="disable-cgo"]').checked; const enableNvenc = form.querySelector('input[name="enable-nvenc"]')?.checked ?? true; const enableAmf = form.querySelector('input[name="enable-amf"]')?.checked ?? true; + const enableQsv = form.querySelector('input[name="enable-qsv"]')?.checked ?? true; const obfuscate = form.querySelector('input[name="obfuscate"]').checked; const enablePersistence = form.querySelector( 'input[name="enable-persistence"]', @@ -1749,6 +1753,7 @@ form?.addEventListener("submit", async (e) => { disableCgo, enableNvenc, enableAmf, + enableQsv, obfuscate, enablePersistence, persistenceMethods: enablePersistence && hasWindowsTarget ? (persistenceMethods && persistenceMethods.length > 0 ? persistenceMethods : ['startup']) : undefined, diff --git a/Overlord-Server/public/build.html b/Overlord-Server/public/build.html index de978ef..401e390 100644 --- a/Overlord-Server/public/build.html +++ b/Overlord-Server/public/build.html @@ -695,6 +695,14 @@ + +