// Windows.Graphics.Capture: the screen as Windows' own capture API hands it over, for when Desktop // Duplication cannot run (dxgi_source.jai). It works across GPUs, hands over frames the right way up // on a rotated screen, and draws the cursor in itself. // // It is WinRT, bound here by hand: the few interfaces it needs, as vtables after IInspectable's six // methods. Everything is made and used on the capture thread, which is in the multithreaded apartment // (RoInitialize), with a free-threaded frame pool, so no dispatcher queue or message loop is needed. // // A frame's arrival sets an event, through a small handler object (Frame_Handler); if Windows will not // take the handler, the pool is polled every millisecond instead. Windows 11 lets the yellow border it // draws around a screen being captured be turned off (IsBorderRequired); Windows 10 always draws it. Wgc_Source :: struct { #as using gpu: Capture_Gpu; screen_size: Size_Int32; // the frames' size, as the pool was made for item: *Capture_Item; winrt_device: *IInspectable; // IDirect3DDevice, over gpu.device pool: *Frame_Pool; session: *Capture_Session; handler: Frame_Handler; handler_token: s64; polling: bool; // Windows would not take the handler border: string; // what came of turning the border off, for the log } // Starts capturing the screen. region is in the screen's pixels and lies on it. wgc_open :: (s: *Wgc_Source, screen: Record_Screen, region: RECT, cursor: bool, testing: Record_Testing = 0) -> ok: bool, error: string { ok, error := gpu_open(s, screen, region); if !ok return false, error; session_statics: *Session_Statics; hr := activation_factory("Windows.Graphics.Capture.GraphicsCaptureSession", IID_SESSION_STATICS, xx *session_statics); if FAILED(hr) return false, hresult_text("Windows.Graphics.Capture is not on this system", hr); supported: u8; session_statics.vtable.IsSupported(session_statics, *supported); com_release(*session_statics); if !supported return false, "Windows.Graphics.Capture is not supported here"; interop: *Capture_Item_Interop; hr = activation_factory("Windows.Graphics.Capture.GraphicsCaptureItem", IID_ITEM_INTEROP, xx *interop); if FAILED(hr) return false, hresult_text("IGraphicsCaptureItemInterop", hr); hr = interop.vtable.CreateForMonitor(interop, screen.monitor, *IID_ITEM, xx *s.item); com_release(*interop); if FAILED(hr) return false, hresult_text("a capture item for the screen", hr); s.item.vtable.get_Size(s.item, *s.screen_size); desktop_width := screen.desktop.right - screen.desktop.left; desktop_height := screen.desktop.bottom - screen.desktop.top; if s.screen_size.width != desktop_width || s.screen_size.height != desktop_height { return false, tprint("the screen is % x %, but this process sees it as % x %: it must be aware of each monitor's DPI", s.screen_size.width, s.screen_size.height, desktop_width, desktop_height); } dxgi_device: *IDXGIDevice; hr = IUnknown_QueryInterface(s.device, *uid(IDXGIDevice_UUID), xx *dxgi_device); if FAILED(hr) return false, hresult_text("the device's IDXGIDevice", hr); inspectable: *IInspectable; hr = CreateDirect3D11DeviceFromDXGIDevice(dxgi_device, *inspectable); com_release(*dxgi_device); if FAILED(hr) return false, hresult_text("CreateDirect3D11DeviceFromDXGIDevice", hr); hr = inspectable.vtable.QueryInterface(inspectable, *IID_DIRECT3D_DEVICE, xx *s.winrt_device); com_release(*inspectable); if FAILED(hr) return false, hresult_text("the device as IDirect3DDevice", hr); pool_statics: *Frame_Pool_Statics2; hr = activation_factory("Windows.Graphics.Capture.Direct3D11CaptureFramePool", IID_POOL_STATICS2, xx *pool_statics); if FAILED(hr) return false, hresult_text("Direct3D11CaptureFramePool's statics", hr); hr = pool_statics.vtable.CreateFreeThreaded(pool_statics, s.winrt_device, PIXEL_FORMAT_B8G8R8A8, POOL_BUFFERS, size_packed(s.screen_size), *s.pool); com_release(*pool_statics); if FAILED(hr) return false, hresult_text("a free-threaded frame pool", hr); hr = s.pool.vtable.CreateCaptureSession(s.pool, s.item, *s.session); if FAILED(hr) return false, hresult_text("a capture session", hr); // The cursor (Windows 10 2004 on) and the border (Windows 11): optional, by what the system has. session2: *Capture_Session2; if SUCCEEDED(s.session.vtable.QueryInterface(s.session, *IID_SESSION2, xx *session2)) { session2.vtable.put_IsCursorCaptureEnabled(session2, xx cursor); com_release(*session2); } session3: *Capture_Session3; if testing & .KEEP_BORDER { s.border = "border left on"; } else if SUCCEEDED(s.session.vtable.QueryInterface(s.session, *IID_SESSION3, xx *session3)) { hr = session3.vtable.put_IsBorderRequired(session3, 0); s.border = ifx SUCCEEDED(hr) then "border off" else hresult_text("the border could not be turned off", hr); com_release(*session3); } else { s.border = "border always drawn (no IGraphicsCaptureSession3: Windows 10)"; } s.handler.vtable = *frame_handler_vtable; s.handler.event = CreateEventW(null, 0, 0, null); hr = ifx testing & .WGC_POLL then cast,no_check(HRESULT) 0x80004002 else s.pool.vtable.add_FrameArrived(s.pool, *s.handler, *s.handler_token); if FAILED(hr) { s.polling = true; timeBeginPeriod(1); // so polling every millisecond is every millisecond log("[record] FrameArrived would not take the handler (%); polling the frame pool.", hresult_text("add_FrameArrived", hr)); } hr = s.session.vtable.StartCapture(s.session); if FAILED(hr) return false, hresult_text("StartCapture", hr); return true, ""; } // Waits up to timeout_ms for a new frame; the newest's region goes into s.latest. wgc_next :: (s: *Wgc_Source, timeout_ms: u32) -> Capture_Next, error: string { frame := wgc_take_newest(s); if !frame { if s.polling { start := ticks_now(); frequency: LARGE_INTEGER; QueryPerformanceFrequency(*frequency); while !frame && (ticks_now() - start) * 1000 / frequency.QuadPart < timeout_ms { sleep_milliseconds(1); frame = wgc_take_newest(s); } } else { WaitForSingleObject(s.handler.event, timeout_ms); frame = wgc_take_newest(s); } } if !frame return .NONE, ""; defer close_and_release(frame); // The screen changed size (a new resolution): the pool is made again; the region must still fit. size: Size_Int32; frame.vtable.get_ContentSize(frame, *size); if size.width != s.screen_size.width || size.height != s.screen_size.height { s.screen_size = size; hr := s.pool.vtable.Recreate(s.pool, s.winrt_device, PIXEL_FORMAT_B8G8R8A8, POOL_BUFFERS, size_packed(size)); if FAILED(hr) return .FAILED, hresult_text("the frame pool could not be made again", hr); if cast(s32) s.box.right > size.width || cast(s32) s.box.bottom > size.height return .FAILED, tprint("the screen is now % x %, and the region is not on it", size.width, size.height); return .NONE, ""; } surface: *IInspectable; hr := frame.vtable.get_Surface(frame, *surface); if FAILED(hr) return .FAILED, hresult_text("the frame's surface", hr); defer com_release(*surface); access: *Dxgi_Interface_Access; hr = surface.vtable.QueryInterface(surface, *IID_DXGI_INTERFACE_ACCESS, xx *access); if FAILED(hr) return .FAILED, hresult_text("the surface's IDirect3DDxgiInterfaceAccess", hr); defer com_release(*access); texture: *ID3D11Texture2D; hr = access.vtable.GetInterface(access, *uid(ID3D11Texture2D_UUID), xx *texture); if FAILED(hr) return .FAILED, hresult_text("the surface's texture", hr); gpu_copy_region(s, texture); IUnknown_Release(texture); return .IMAGE, ""; } wgc_close :: (s: *Wgc_Source) { if s.pool && s.handler_token s.pool.vtable.remove_FrameArrived(s.pool, s.handler_token); if s.session close_and_release(s.session); if s.pool close_and_release(s.pool); s.session, s.pool = null, null; com_release(*s.item); com_release(*s.winrt_device); if s.handler.event CloseHandle(s.handler.event); if s.polling timeEndPeriod(1); gpu_close(s); s.* = .{}; } // The thread's apartment: WinRT on this thread from here on. wgc_thread_start :: () -> ok: bool { if !winrt_load() return false; return SUCCEEDED(winrt.RoInitialize(RO_INIT_MULTITHREADED)); } wgc_thread_end :: () { if winrt.RoUninitialize winrt.RoUninitialize(); } #scope_file POOL_BUFFERS :: 2; PIXEL_FORMAT_B8G8R8A8 :: 87; // DirectXPixelFormat.B8G8R8A8UIntNormalized RO_INIT_MULTITHREADED :: 1; // The pool's newest frame, the older ones let go; null when there is none. wgc_take_newest :: (s: *Wgc_Source) -> *Capture_Frame { newest: *Capture_Frame; while true { frame: *Capture_Frame; if FAILED(s.pool.vtable.TryGetNextFrame(s.pool, *frame)) || !frame break; if newest close_and_release(newest); newest = frame; } return newest; } // IClosable.Close, which hands a frame back to the pool at once, then Release. close_and_release :: (object: *$T) { closable: *Closable; if SUCCEEDED(object.vtable.QueryInterface(object, *IID_CLOSABLE, xx *closable)) { closable.vtable.Close(closable); closable.vtable.Release(closable); } object.vtable.Release(object); } activation_factory :: (class_name: string, iid: GUID, factory: **void) -> HRESULT { wide := utf8_to_wide(class_name,, temp); length: u32; while wide[length] length += 1; name: HSTRING; if !winrt.loaded return cast,no_check(HRESULT) 0x80004001; // E_NOTIMPL: no WinRT on this system hr := winrt.WindowsCreateString(wide, length, *name); if FAILED(hr) return hr; defer winrt.WindowsDeleteString(name); iid_copy := iid; return winrt.RoGetActivationFactory(name, *iid_copy, factory); } size_packed :: (size: Size_Int32) -> u64 { // SizeInt32 by value: 8 bytes, in one register width := cast(u64) cast,no_check(u32) size.width; height := cast(u64) cast,no_check(u32) size.height; return width | (height << 32); } // ---------------------------------------------------------------- the FrameArrived handler // TypedEventHandler: a COM object of our own whose Invoke sets an // event. It lives in the Wgc_Source and is never freed by its count; it is unregistered first. Frame_Handler :: struct { vtable: *Frame_Handler_VTable; event: HANDLE; } Frame_Handler_VTable :: struct { QueryInterface: (this: *Frame_Handler, iid: *GUID, out: **void) -> HRESULT #c_call; AddRef: (this: *Frame_Handler) -> u32 #c_call; Release: (this: *Frame_Handler) -> u32 #c_call; Invoke: (this: *Frame_Handler, sender: *void, args: *void) -> HRESULT #c_call; } frame_handler_vtable := Frame_Handler_VTable.{handler_query, handler_add_ref, handler_release, handler_invoke}; handler_query :: (this: *Frame_Handler, iid: *GUID, out: **void) -> HRESULT #c_call { if guid_equal(iid, *IID_UNKNOWN) || guid_equal(iid, *IID_AGILE_OBJECT) || guid_equal(iid, *IID_FRAME_ARRIVED_HANDLER) { out.* = this; return 0; } out.* = null; return cast,no_check(HRESULT) 0x80004002; // E_NOINTERFACE } handler_add_ref :: (this: *Frame_Handler) -> u32 #c_call { return 1; } handler_release :: (this: *Frame_Handler) -> u32 #c_call { return 1; } handler_invoke :: (this: *Frame_Handler, sender: *void, args: *void) -> HRESULT #c_call { SetEvent(this.event); return 0; } guid_equal :: (a: *GUID, b: *GUID) -> bool #c_call { if a.Data1 != b.Data1 || a.Data2 != b.Data2 || a.Data3 != b.Data3 return false; for 0..7 if a.Data4[it] != b.Data4[it] return false; return true; } // ---------------------------------------------------------------- bindings d3d11_dll :: #library,system "d3d11"; kernel32_dll :: #library,system "kernel32"; HSTRING :: *void; // WinRT's own calls, from combase.dll at run time: its import library is runtimeobject.lib, which // names no DLL of its own for #library to find. winrt: struct { loaded: bool; RoInitialize: (init_type: s32) -> HRESULT #c_call; RoUninitialize: () #c_call; RoGetActivationFactory: (class_id: HSTRING, iid: *GUID, factory: **void) -> HRESULT #c_call; WindowsCreateString: (source: *u16, length: u32, out: *HSTRING) -> HRESULT #c_call; WindowsDeleteString: (text: HSTRING) -> HRESULT #c_call; } winrt_load :: () -> bool { if winrt.loaded return true; library := LoadLibraryW(utf8_to_wide("combase.dll",, temp)); if !library return false; winrt.RoInitialize = xx GetProcAddress(library, "RoInitialize"); winrt.RoUninitialize = xx GetProcAddress(library, "RoUninitialize"); winrt.RoGetActivationFactory = xx GetProcAddress(library, "RoGetActivationFactory"); winrt.WindowsCreateString = xx GetProcAddress(library, "WindowsCreateString"); winrt.WindowsDeleteString = xx GetProcAddress(library, "WindowsDeleteString"); winrt.loaded = winrt.RoInitialize && winrt.RoUninitialize && winrt.RoGetActivationFactory && winrt.WindowsCreateString && winrt.WindowsDeleteString; return winrt.loaded; } CreateDirect3D11DeviceFromDXGIDevice :: (device: *IDXGIDevice, out: **IInspectable) -> HRESULT #foreign d3d11_dll; SetEvent :: (event: HANDLE) -> s32 #foreign kernel32_dll; Size_Int32 :: struct { width, height: s32; } IID_UNKNOWN := #run guid("00000000-0000-0000-C000-000000000046"); IID_AGILE_OBJECT := #run guid("94ea2b94-e9cc-49e0-c0ff-ee64ca8f5b90"); IID_FRAME_ARRIVED_HANDLER := #run guid("51a947f7-79cf-5a3e-a3a5-1289cfa6dfe8"); // TypedEventHandler IID_ITEM_INTEROP := #run guid("3628E81B-3CAC-4C60-B7F4-23CE0E0C3356"); IID_ITEM := #run guid("79C3F95B-31F7-4EC2-A464-632EF5D30760"); IID_SESSION_STATICS := #run guid("2224A540-5974-49AA-B232-0882536F4CB5"); IID_SESSION2 := #run guid("2C39AE40-7D2E-5044-804E-8B6799D4CF9E"); IID_SESSION3 := #run guid("F2CDD966-22AE-5EA1-9596-3A289344C3BE"); IID_POOL_STATICS2 := #run guid("589B103F-6BBC-5DF5-A991-02E28B3B66D5"); IID_DIRECT3D_DEVICE := #run guid("A37624AB-8D5F-4650-9D3E-9EAE3D9BC670"); IID_DXGI_INTERFACE_ACCESS := #run guid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1"); IID_CLOSABLE := #run guid("30D5A829-7FA4-4026-83BB-D75BAE4EA99E"); guid :: (text: string) -> GUID { hex :: (s: string) -> u64 { value: u64; for 0..s.count - 1 { c := s[it]; digit: u64 = ifx c >= #char "a" then cast(u64) (c - #char "a" + 10) else ifx c >= #char "A" then cast(u64) (c - #char "A" + 10) else cast(u64) (c - #char "0"); value = value * 16 + digit; } return value; } g: GUID; g.Data1 = cast(u32) hex(slice(text, 0, 8)); g.Data2 = cast(u16) hex(slice(text, 9, 4)); g.Data3 = cast(u16) hex(slice(text, 14, 4)); for 0..1 g.Data4[it] = cast(u8) hex(slice(text, 19 + it * 2, 2)); for 0..5 g.Data4[it + 2] = cast(u8) hex(slice(text, 24 + it * 2, 2)); return g; } // IUnknown, then IInspectable's three: every WinRT interface starts so. Inspectable_Methods :: struct { QueryInterface: (this: *void, iid: *GUID, out: **void) -> HRESULT #c_call; AddRef: (this: *void) -> u32 #c_call; Release: (this: *void) -> u32 #c_call; GetIids: *void; GetRuntimeClassName: *void; GetTrustLevel: *void; } IInspectable :: struct { vtable: *Inspectable_Methods; } Closable :: struct { vtable: *struct { using base: Inspectable_Methods; Close: (this: *Closable) -> HRESULT #c_call; }; } Session_Statics :: struct { vtable: *struct { using base: Inspectable_Methods; IsSupported: (this: *Session_Statics, out: *u8) -> HRESULT #c_call; }; } Capture_Item_Interop :: struct { vtable: *struct { QueryInterface: (this: *void, iid: *GUID, out: **void) -> HRESULT #c_call; AddRef: (this: *void) -> u32 #c_call; Release: (this: *void) -> u32 #c_call; CreateForWindow: (this: *Capture_Item_Interop, window: HWND, iid: *GUID, out: **void) -> HRESULT #c_call; CreateForMonitor: (this: *Capture_Item_Interop, monitor: HMONITOR, iid: *GUID, out: **void) -> HRESULT #c_call; }; } Capture_Item :: struct { vtable: *struct { using base: Inspectable_Methods; get_DisplayName: *void; get_Size: (this: *Capture_Item, out: *Size_Int32) -> HRESULT #c_call; add_Closed: *void; remove_Closed: *void; }; } Frame_Pool_Statics2 :: struct { vtable: *struct { using base: Inspectable_Methods; CreateFreeThreaded: (this: *Frame_Pool_Statics2, device: *IInspectable, format: s32, buffers: s32, size: u64, out: **Frame_Pool) -> HRESULT #c_call; }; } Frame_Pool :: struct { vtable: *struct { using base: Inspectable_Methods; Recreate: (this: *Frame_Pool, device: *IInspectable, format: s32, buffers: s32, size: u64) -> HRESULT #c_call; TryGetNextFrame: (this: *Frame_Pool, out: **Capture_Frame) -> HRESULT #c_call; add_FrameArrived: (this: *Frame_Pool, handler: *Frame_Handler, token: *s64) -> HRESULT #c_call; remove_FrameArrived: (this: *Frame_Pool, token: s64) -> HRESULT #c_call; CreateCaptureSession: (this: *Frame_Pool, item: *Capture_Item, out: **Capture_Session) -> HRESULT #c_call; get_DispatcherQueue: *void; }; } Capture_Frame :: struct { vtable: *struct { using base: Inspectable_Methods; get_Surface: (this: *Capture_Frame, out: **IInspectable) -> HRESULT #c_call; get_SystemRelativeTime: (this: *Capture_Frame, out: *s64) -> HRESULT #c_call; get_ContentSize: (this: *Capture_Frame, out: *Size_Int32) -> HRESULT #c_call; }; } Capture_Session :: struct { vtable: *struct { using base: Inspectable_Methods; StartCapture: (this: *Capture_Session) -> HRESULT #c_call; }; } Capture_Session2 :: struct { vtable: *struct { using base: Inspectable_Methods; get_IsCursorCaptureEnabled: (this: *Capture_Session2, out: *u8) -> HRESULT #c_call; put_IsCursorCaptureEnabled: (this: *Capture_Session2, value: u8) -> HRESULT #c_call; }; } Capture_Session3 :: struct { vtable: *struct { using base: Inspectable_Methods; get_IsBorderRequired: (this: *Capture_Session3, out: *u8) -> HRESULT #c_call; put_IsBorderRequired: (this: *Capture_Session3, value: u8) -> HRESULT #c_call; }; } Dxgi_Interface_Access :: struct { vtable: *struct { QueryInterface: (this: *void, iid: *GUID, out: **void) -> HRESULT #c_call; AddRef: (this: *void) -> u32 #c_call; Release: (this: *void) -> u32 #c_call; GetInterface: (this: *Dxgi_Interface_Access, iid: *GUID, out: **void) -> HRESULT #c_call; }; }