- modules/Screen_Record/wgc_source.jai: the screen through Windows.Graphics.Capture, with hand-written bindings for its WinRT interfaces (capture item interop, item, free-threaded frame pool, frame, session 1-3, IDirect3DDxgiInterfaceAccess, IClosable). A small COM handler on FrameArrived sets an event per frame; when Windows refuses it the pool is polled each millisecond. The cursor is drawn by Windows; the yellow border is turned off where the system allows (Windows 11). A screen that changes size makes the pool again. WinRT's own calls are loaded from combase.dll at run time: runtimeobject.lib names no DLL for #library. - capture_gpu.jai: what both sources share (the device on the screen's adapter, the region copy, the staging ring), so each is only "the next image's region into a texture". - Record_Options.capture: AUTO (Desktop Duplication, else Windows.Graphics.Capture when it says fall_back), DXGI or WGC. Sources open on the capture thread, as WinRT objects belong to their apartment; record_start waits for it to say how that went. Record_Options.testing simulates the two-GPU failure, forces polling, or keeps the border, for record-probe. - record-probe: -capture auto|dxgi|wgc, -simulate-unsupported, -poll, -border-test with -keep-border as its control, -cursor-test; DPI awareness set already is no longer reported as a failure. - On the RTX 5080 laptop: auto falls back on the simulated failure; WGC records 4K at 60 fps with nothing dropped; its cursor and the one cursor.jai draws for duplication are the same; the border is off for an unpackaged app (0 yellow edge pixels, 14,214 with it kept); WDA_EXCLUDEFROMCAPTURE is respected; polling holds 30 fps. The plan records the results. - README: Screen_Record's line, which held a carriage return in place of "\r" since 0.1.3.
106 lines
4.3 KiB
Plaintext
106 lines
4.3 KiB
Plaintext
// What both ways of taking the screen share (dxgi_source.jai, wgc_source.jai): a D3D11 device of
|
|
// their own on the adapter the screen is attached to, the region as last seen, copied out of each
|
|
// new image on the GPU, and the staging textures it is read back through.
|
|
|
|
Capture_Gpu :: struct {
|
|
adapter: *IDXGIAdapter1;
|
|
device: *ID3D11Device;
|
|
device_context: *ID3D11DeviceContext;
|
|
box: D3D11_BOX; // the region, in the screen's pixels
|
|
latest: *ID3D11Texture2D; // the region as last seen
|
|
staging: [RECORD_STAGING] *ID3D11Texture2D;
|
|
copy_ticks: s64; // spent taking the region out of images (QueryPerformanceCounter ticks)
|
|
}
|
|
|
|
RECORD_STAGING :: 3; // staging textures in turn: the one read is two frames behind the one copied
|
|
|
|
// What a source's next() found.
|
|
Capture_Next :: enum u8 { NONE; IMAGE; POINTER; IMAGE_AND_POINTER; LOST; FAILED; }
|
|
|
|
// The device on the screen's adapter, and the textures for a region (in the screen's pixels).
|
|
gpu_open :: (g: *Capture_Gpu, screen: Record_Screen, region: RECT) -> ok: bool, error: string {
|
|
factory: *IDXGIFactory1;
|
|
hr := CreateDXGIFactory1(*uid(IDXGIFactory1_UUID), xx *factory);
|
|
if FAILED(hr) return false, hresult_text("CreateDXGIFactory1", hr);
|
|
defer IUnknown_Release(factory);
|
|
hr = IDXGIFactory1_EnumAdapters1(factory, screen.adapter_index, *g.adapter);
|
|
if FAILED(hr) return false, hresult_text("the screen's adapter is gone", hr);
|
|
|
|
levels := D3D_FEATURE_LEVEL.[._11_1, ._11_0, ._10_1, ._10_0];
|
|
level: D3D_FEATURE_LEVEL;
|
|
hr = D3D11CreateDevice(g.adapter, .UNKNOWN, null, .BGRA_SUPPORT, levels.data, levels.count, D3D11_SDK_VERSION, *g.device, *level, *g.device_context);
|
|
if FAILED(hr) return false, hresult_text("D3D11CreateDevice on the screen's adapter", hr);
|
|
|
|
g.box = .{left = xx region.left, top = xx region.top, right = xx region.right, bottom = xx region.bottom, front = 0, back = 1};
|
|
desc: D3D11_TEXTURE2D_DESC;
|
|
desc.Width = cast(u32) (region.right - region.left);
|
|
desc.Height = cast(u32) (region.bottom - region.top);
|
|
desc.MipLevels = 1;
|
|
desc.ArraySize = 1;
|
|
desc.Format = .B8G8R8A8_UNORM;
|
|
desc.SampleDesc.Count = 1;
|
|
desc.Usage = .DEFAULT;
|
|
hr = ID3D11Device_CreateTexture2D(g.device, *desc, null, *g.latest);
|
|
if FAILED(hr) return false, hresult_text("a texture for the region", hr);
|
|
desc.Usage = .STAGING;
|
|
desc.CPUAccessFlags = .READ;
|
|
for * g.staging {
|
|
hr = ID3D11Device_CreateTexture2D(g.device, *desc, null, it);
|
|
if FAILED(hr) return false, hresult_text("a staging texture", hr);
|
|
}
|
|
return true, "";
|
|
}
|
|
|
|
// The region of a whole screen's image into g.latest.
|
|
gpu_copy_region :: (g: *Capture_Gpu, image: *ID3D11Texture2D) {
|
|
start := ticks_now();
|
|
ID3D11DeviceContext_CopySubresourceRegion(g.device_context, g.latest, 0, 0, 0, 0, image, 0, *g.box);
|
|
g.copy_ticks += ticks_now() - start;
|
|
}
|
|
|
|
gpu_close :: (g: *Capture_Gpu) {
|
|
for * g.staging com_release(it);
|
|
com_release(*g.latest);
|
|
com_release(*g.device_context);
|
|
com_release(*g.device);
|
|
com_release(*g.adapter);
|
|
}
|
|
|
|
#scope_module
|
|
|
|
com_release :: (p: *$T) {
|
|
if p.* { IUnknown_Release(cast(*IUnknown) p.*); p.* = null; }
|
|
}
|
|
|
|
ticks_now :: () -> s64 {
|
|
t: LARGE_INTEGER;
|
|
QueryPerformanceCounter(*t);
|
|
return t.QuadPart;
|
|
}
|
|
|
|
hresult_is :: (hr: HRESULT, code: u32) -> bool {
|
|
return cast,no_check(u32) hr == code;
|
|
}
|
|
|
|
// "what failed: 0x887A0004 (DXGI_ERROR_UNSUPPORTED)", temp.
|
|
hresult_text :: (what: string, hr: HRESULT) -> string {
|
|
code := cast,no_check(u32) hr;
|
|
name := "";
|
|
if code == {
|
|
case DXGI_ERROR_UNSUPPORTED; name = " (DXGI_ERROR_UNSUPPORTED)";
|
|
case DXGI_ERROR_NOT_CURRENTLY_AVAILABLE; name = " (DXGI_ERROR_NOT_CURRENTLY_AVAILABLE)";
|
|
case DXGI_ERROR_ACCESS_LOST; name = " (DXGI_ERROR_ACCESS_LOST)";
|
|
case 0x80070005; name = " (E_ACCESSDENIED)";
|
|
case 0x80004002; name = " (E_NOINTERFACE)";
|
|
case 0x80070057; name = " (E_INVALIDARG)";
|
|
case 0x80040154; name = " (REGDB_E_CLASSNOTREG)";
|
|
case 0x80004001; name = " (E_NOTIMPL)";
|
|
}
|
|
return tprint("%: 0x%%", what, formatInt(code, base = 16, minimum_digits = 8), name);
|
|
}
|
|
|
|
wide_text :: (wide: *s16) -> string {
|
|
text, ok := wide_to_utf8(cast(*u16) wide,, temp);
|
|
return ifx ok then text else "";
|
|
}
|