nvidia quick sync

This commit is contained in:
KingKDot
2026-07-11 16:52:24 -05:00
parent 424ed348e3
commit fb6c856ad1
15 changed files with 521 additions and 0 deletions
@@ -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)
}
@@ -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" }
@@ -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() }
@@ -0,0 +1,235 @@
//go:build windows && cgo
#include "bridge_windows.h"
#include <windows.h>
#include <d3d11.h>
#include <algorithm>
#include <chrono>
#include <cstring>
#include <string>
#include <thread>
#include <vector>
#include <cstdio>
#define ONEVPL_EXPERIMENTAL
#include "../../../../third_party/onevpl/include/vpl/mfxdispatcher.h"
#include "../../../../third_party/onevpl/include/vpl/mfxvideo.h"
namespace {
template<class T> 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<size_t>(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<int>(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<unsigned long>(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<decltype(member)>(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<uint8_t> 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<const mfxU8*>(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<void**>(&s->videoDevice));
if (FAILED(hr)) { error = hr_error("ID3D11VideoDevice", hr); return false; }
hr = s->context->QueryInterface(__uuidof(ID3D11VideoContext), reinterpret_cast<void**>(&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<ID3D11Device*>(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<mfxU16>(std::max(1, bitrate / 1000)); p.mfx.GopRefDist = 1;
p.mfx.GopPicSize = static_cast<mfxU16>(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<mfxU16>((width + 15) & ~15); p.mfx.FrameInfo.Height = static_cast<mfxU16>((height + 15) & ~15);
p.mfx.FrameInfo.CropW = static_cast<mfxU16>(width); p.mfx.FrameInfo.CropH = static_cast<mfxU16>(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<mfxHDL*>(&s->memory));
if (sts != MFX_ERR_NONE || !s->memory) { error = status_error("MFXGetMemoryInterface", sts); goto fail; }
}
s->bitstream.resize(std::max<size_t>(1024 * 1024, static_cast<size_t>(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<QSVEncoder*>(opaque); if (!s || !texture || !output_size) return 0; *output_size = 0;
std::string error; if (!convert(s, static_cast<ID3D11Texture2D*>(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<mfxU32>(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<int>(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<QSVEncoder*>(encoder)); }
@@ -0,0 +1,77 @@
//go:build windows && cgo
package qsvbridge
/*
#cgo CXXFLAGS: -std=c++17
#cgo LDFLAGS: -lole32 -ld3d11 -ldxgi
#include <stdlib.h>
#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)
}
@@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
#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
+5
View File
@@ -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,
+8
View File
@@ -695,6 +695,14 @@
</div>
</label>
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-blue-500 transition-colors">
<input type="checkbox" name="enable-qsv" class="w-4 h-4" checked />
<div class="flex flex-col">
<span><i class="fa-solid fa-microchip text-blue-400 mr-2"></i>Include Intel Quick Sync</span>
<span class="text-xs text-slate-500">Native Intel oneVPL H.264 acceleration for Windows CGO builds</span>
</div>
</label>
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-amber-500 transition-colors">
<input type="checkbox" name="enable-winre" class="w-4 h-4" />
<div class="flex flex-col">
+1
View File
@@ -19,6 +19,7 @@ export type BuildConfig = {
disableCgo?: boolean;
enableNvenc?: boolean;
enableAmf?: boolean;
enableQsv?: boolean;
obfuscate?: boolean;
enablePersistence?: boolean;
persistenceMethods?: string[];
@@ -86,6 +86,7 @@ type BuildProcessConfig = {
disableCgo?: boolean;
enableNvenc?: boolean;
enableAmf?: boolean;
enableQsv?: boolean;
obfuscate?: boolean;
enablePersistence?: boolean;
persistenceMethods?: string[];
@@ -404,9 +405,16 @@ function amfHeaderPath(clientDir: string): string {
return path.join(clientDir, "third_party", "amf", "include", "core", "Factory.h");
}
function oneVPLHeaderPath(clientDir: string): string {
return path.join(clientDir, "third_party", "onevpl", "include", "vpl", "mfxvideo.h");
}
const AMF_REPOSITORY = "https://github.com/GPUOpen-LibrariesAndSDKs/AMF.git";
const AMF_REF = "v1.5.2";
let amfHeaderProvision: Promise<void> | null = null;
const ONEVPL_REPOSITORY = "https://github.com/oneapi-src/oneVPL.git";
const ONEVPL_REF = "v2.15.0";
let oneVPLHeaderProvision: Promise<void> | null = null;
function copyDirectory(source: string, destination: string): void {
fs.mkdirSync(destination, { recursive: true });
@@ -461,6 +469,7 @@ async function ensureNVCodecHeaderForWindowsCgo(
sendToStream: (data: any) => void,
enableNvenc: boolean,
enableAmf: boolean,
enableQsv: boolean,
): Promise<void> {
if (enableNvenc) {
const headerPath = nvcodecHeaderPath(clientDir);
@@ -477,6 +486,29 @@ async function ensureNVCodecHeaderForWindowsCgo(
const amfPath = amfHeaderPath(clientDir);
sendToStream({ type: "output", text: `Native AMD AMF headers: ${amfPath}\n`, level: "info" });
}
if (enableQsv) {
const headerPath = oneVPLHeaderPath(clientDir);
if (!fs.existsSync(headerPath) && !oneVPLHeaderProvision) {
oneVPLHeaderProvision = (async () => {
const destination = path.join(clientDir, "third_party", "onevpl");
const temporary = path.join(clientDir, "third_party", `.onevpl-${process.pid}-${Date.now()}`);
sendToStream({ type: "output", text: `Intel oneVPL headers are not cached; fetching ${ONEVPL_REF}...\n`, level: "info" });
try {
const clone = await $`git clone --depth 1 --filter=blob:none --sparse --branch ${ONEVPL_REF} ${ONEVPL_REPOSITORY} ${temporary}`.quiet().nothrow();
if (clone.exitCode !== 0) throw new Error(clone.stderr.toString().trim() || "git clone failed");
const sparse = await $`git -C ${temporary} sparse-checkout set --no-cone api/vpl/ LICENSE`.quiet().nothrow();
if (sparse.exitCode !== 0) throw new Error(sparse.stderr.toString().trim() || "git sparse-checkout failed");
fs.rmSync(destination, { recursive: true, force: true });
copyDirectory(path.join(temporary, "api", "vpl"), path.join(destination, "include", "vpl"));
fs.copyFileSync(path.join(temporary, "LICENSE"), path.join(destination, "LICENSE"));
} finally { fs.rmSync(temporary, { recursive: true, force: true }); }
if (!fs.existsSync(headerPath)) throw new Error(`oneVPL header was not created at ${headerPath}`);
})().catch((error) => { oneVPLHeaderProvision = null; throw error; });
}
if (oneVPLHeaderProvision) await oneVPLHeaderProvision;
sendToStream({ type: "output", text: `Intel oneVPL headers: ${headerPath}\n`, level: "info" });
}
}
function writeSgnTextArtifact(
@@ -1124,6 +1156,7 @@ func runBoundFiles() {
sendToStream,
config.enableNvenc !== false,
config.enableAmf !== false,
config.enableQsv !== false,
);
}
@@ -1300,6 +1333,7 @@ func runBoundFiles() {
let baseTags: string[] = [];
if (config.enableNvenc === false) baseTags.push("no_nvenc");
if (config.enableAmf === false) baseTags.push("no_amf");
if (config.enableQsv === false) baseTags.push("no_qsv");
if (config.noPrinting) baseTags.push("noprint");
if (config.disableKeylogger) baseTags.push("nokeylogger");
if (config.enableWebrtc) baseTags.push("overlord_webrtc");
@@ -171,6 +171,7 @@ export async function handleBuildRoutes(
disableCgo,
enableNvenc,
enableAmf,
enableQsv,
obfuscate,
enablePersistence,
persistenceMethods,
@@ -482,6 +483,7 @@ export async function handleBuildRoutes(
disableCgo,
enableNvenc: enableNvenc !== false,
enableAmf: enableAmf !== false,
enableQsv: enableQsv !== false,
obfuscate: !!obfuscate,
enablePersistence,
persistenceMethods: safePersistenceMethods,
+5
View File
@@ -98,6 +98,11 @@ if /I "%DISABLE_CGO%"=="true" (
call "%ROOT%scripts\vendor-amf-headers.bat"
if errorlevel 1 goto :err
)
if not exist "%CLIENT_DIR%\third_party\onevpl\include\vpl\mfxvideo.h" (
echo Intel oneVPL headers are not cached; fetching them now...
call "%ROOT%scripts\vendor-onevpl-headers.bat"
if errorlevel 1 goto :err
)
)
%BUILD_CMD% %BUILD_TAGS% -ldflags="%LDFLAGS% %WIN_LDFLAGS%" -o "%OUT_DIR%\agent-windows-amd64.exe" ./cmd/agent
if errorlevel 1 goto :err
+8
View File
@@ -13,6 +13,14 @@ call bun install
popd
echo === Ensuring client dependencies (Go) ===
if not exist "%ROOT%Overlord-Client\third_party\onevpl\include\vpl\mfxdispatcher.h" (
echo [client] Intel oneVPL headers are missing; fetching them...
call "%SCRIPT_DIR%vendor-onevpl-headers.bat"
if errorlevel 1 (
echo [client] Failed to provision Intel oneVPL headers.
exit /b 1
)
)
pushd "%ROOT%Overlord-Client"
if exist go.mod (
echo [client] go mod tidy...
+26
View File
@@ -0,0 +1,26 @@
@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\onevpl"
set "DEST_HEADER=%DEST_DIR%\include\vpl\mfxvideo.h"
if "%ONEVPL_REPO%"=="" set "ONEVPL_REPO=https://github.com/oneapi-src/oneVPL.git"
if "%ONEVPL_REF%"=="" set "ONEVPL_REF=v2.15.0"
if exist "%DEST_HEADER%" if /I not "%~1"=="--force" exit /b 0
where git >nul 2>&1 || (echo git is required to vendor oneVPL headers & exit /b 1)
set "TMP_DIR=%DEST_DIR%\.onevpl.tmp"
if exist "%TMP_DIR%" rd /s /q "%TMP_DIR%"
if not exist "%DEST_DIR%" mkdir "%DEST_DIR%"
git clone --depth 1 --filter=blob:none --sparse --branch "%ONEVPL_REF%" "%ONEVPL_REPO%" "%TMP_DIR%" || goto :err
git -C "%TMP_DIR%" sparse-checkout set --no-cone api/vpl/ LICENSE || goto :err
if exist "%DEST_DIR%\include" rd /s /q "%DEST_DIR%\include"
mkdir "%DEST_DIR%\include"
xcopy /e /i /q /y "%TMP_DIR%\api\vpl" "%DEST_DIR%\include\vpl" >nul || goto :err
copy /y "%TMP_DIR%\LICENSE" "%DEST_DIR%\LICENSE" >nul || goto :err
rd /s /q "%TMP_DIR%"
echo Cached Intel oneVPL %ONEVPL_REF% headers
exit /b 0
:err
if exist "%TMP_DIR%" rd /s /q "%TMP_DIR%"
echo Failed to fetch Intel oneVPL headers
exit /b 1
+18
View File
@@ -0,0 +1,18 @@
#!/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/onevpl"
DEST_HEADER="${DEST_DIR}/include/vpl/mfxvideo.h"
REPO_URL="${ONEVPL_REPO:-https://github.com/oneapi-src/oneVPL.git}"
REF="${ONEVPL_REF:-v2.15.0}"
[[ -s "${DEST_HEADER}" && "${1:-}" != "--force" ]] && exit 0
command -v git >/dev/null 2>&1 || { echo "git is required to vendor oneVPL headers" >&2; exit 1; }
TMP_DIR="${DEST_DIR}/.onevpl.tmp"
rm -rf "${TMP_DIR}"; trap 'rm -rf "${TMP_DIR}"' EXIT; mkdir -p "${DEST_DIR}"
git clone --depth 1 --filter=blob:none --sparse --branch "${REF}" "${REPO_URL}" "${TMP_DIR}"
git -C "${TMP_DIR}" sparse-checkout set --no-cone api/vpl/ LICENSE
rm -rf "${DEST_DIR}/include"; mkdir -p "${DEST_DIR}/include"
cp -R "${TMP_DIR}/api/vpl" "${DEST_DIR}/include/vpl"; cp "${TMP_DIR}/LICENSE" "${DEST_DIR}/LICENSE"
rm -rf "${TMP_DIR}"; trap - EXIT
echo "Cached Intel oneVPL ${REF} headers"