// DXGI Desktop Duplication: the screens as the GPU composes them, a frame each time one changes, // with the cursor apart (cursor.jai draws it in). // // It runs on a device made on the adapter the screen is attached to (capture_gpu.jai): duplicating // with a device on another adapter fails. On a laptop with two GPUs, where the screen hangs off one // and the process may run on the other, DuplicateOutput1 can fail with DXGI_ERROR_UNSUPPORTED all the // same; that, too many duplications of the screen at once, and a rotated screen are what dxgi_open // returns fall_back for, and Windows.Graphics.Capture (wgc_source.jai) takes over. // // DuplicateOutput1 (IDXGIOutput5) is used where there is one: the plain DuplicateOutput is documented // to fail for a process aware of each monitor's DPI, as mcap is (it did not on an RTX 5080 laptop). // legacy forces the plain one, for record-probe. Dxgi_Source :: struct { #as using gpu: Capture_Gpu; output: *IDXGIOutput1; output5: *IDXGIOutput5; // null before Windows 10 1703 duplication: *IDXGIOutputDuplication; legacy: bool; method: string; // which duplication call worked, for the log pointer_position: POINT; // the cursor's top left, in the screen's pixels pointer_visible: bool; shape: Cursor_Shape; } // Every screen attached to the desktop, on every adapter, in DXGI's order. Temp. record_list_screens :: () -> [] Record_Screen { screens: [..] Record_Screen; screens.allocator = temp; factory: *IDXGIFactory1; if FAILED(CreateDXGIFactory1(*uid(IDXGIFactory1_UUID), xx *factory)) return screens; defer IUnknown_Release(factory); adapter_index: u32; while true { adapter: *IDXGIAdapter1; if FAILED(IDXGIFactory1_EnumAdapters1(factory, adapter_index, *adapter)) break; defer IUnknown_Release(adapter); adapter_desc: DXGI_ADAPTER_DESC1; IDXGIAdapter1_GetDesc1(adapter, *adapter_desc); adapter_name := wide_text(adapter_desc.Description.data); output_index: u32; while true { output: *IDXGIOutput; if FAILED(IDXGIAdapter_EnumOutputs(adapter, output_index, *output)) break; desc: DXGI_OUTPUT_DESC; IDXGIOutput_GetDesc(output, *desc); IUnknown_Release(output); if desc.AttachedToDesktop != .FALSE { array_add(*screens, .{adapter_index = adapter_index, output_index = output_index, adapter = adapter_name, name = wide_text(desc.DeviceName.data), desktop = desc.DesktopCoordinates, rotation = desc.Rotation, monitor = desc.Monitor}); } output_index += 1; } adapter_index += 1; } return screens; } // Duplicates the screen. region is in the screen's pixels and lies on it. dxgi_open :: (s: *Dxgi_Source, screen: Record_Screen, region: RECT, legacy: bool) -> ok: bool, error: string, fall_back: bool { s.legacy = legacy; if screen.rotation != .IDENTITY && screen.rotation != .UNSPECIFIED return false, "the screen is rotated", true; ok, error := gpu_open(s, screen, region); if !ok return false, error, false; output: *IDXGIOutput; hr := IDXGIAdapter_EnumOutputs(s.adapter, screen.output_index, *output); if FAILED(hr) return false, hresult_text("the screen is gone", hr), false; defer IUnknown_Release(output); hr = IUnknown_QueryInterface(output, *uid(IDXGIOutput1_UUID), xx *s.output); if FAILED(hr) return false, hresult_text("the screen cannot be duplicated (no IDXGIOutput1)", hr), true; IUnknown_QueryInterface(output, *uid(IDXGIOutput5_UUID), xx *s.output5); // optional hr = dxgi_duplicate(s); if FAILED(hr) { // Unsupported: two GPUs, the device on the wrong one. Not currently available: too many duplications. fall_back := hresult_is(hr, DXGI_ERROR_UNSUPPORTED) || hresult_is(hr, DXGI_ERROR_NOT_CURRENTLY_AVAILABLE); return false, hresult_text(tprint("Desktop Duplication (%)", s.method), hr), fall_back; } // A process not aware of each monitor's DPI is told the desktop in scaled pixels, while the frames // come in the screen's own: the region would be the wrong part of the screen. duplication_desc: DXGI_OUTDUPL_DESC; IDXGIOutputDuplication_GetDesc(s.duplication, *duplication_desc); desktop_width := screen.desktop.right - screen.desktop.left; desktop_height := screen.desktop.bottom - screen.desktop.top; if duplication_desc.ModeDesc.Width != cast(u32) desktop_width || duplication_desc.ModeDesc.Height != cast(u32) 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", duplication_desc.ModeDesc.Width, duplication_desc.ModeDesc.Height, desktop_width, desktop_height), false; } return true, "", false; } // Duplicates the screen, again after the duplication was lost. dxgi_duplicate :: (s: *Dxgi_Source) -> HRESULT { com_release(*s.duplication); if s.output5 && !s.legacy { s.method = "DuplicateOutput1"; formats := DXGI_FORMAT.[.B8G8R8A8_UNORM]; return IDXGIOutput5_DuplicateOutput1(s.output5, s.device, 0, formats.count, formats.data, *s.duplication); } s.method = "DuplicateOutput"; return IDXGIOutput1_DuplicateOutput(s.output, s.device, *s.duplication); } // Waits up to timeout_ms for the screen to change. A new image's region goes into s.latest; a moved // or changed cursor updates s.pointer_* and s.shape. On LOST the duplication is gone: dxgi_duplicate. dxgi_next :: (s: *Dxgi_Source, timeout_ms: u32) -> Capture_Next, error: string { info: DXGI_OUTDUPL_FRAME_INFO; resource: *IDXGIResource; hr := IDXGIOutputDuplication_AcquireNextFrame(s.duplication, timeout_ms, *info, *resource); if hresult_is(hr, DXGI_ERROR_WAIT_TIMEOUT) return .NONE, ""; if hresult_is(hr, DXGI_ERROR_ACCESS_LOST) return .LOST, ""; if FAILED(hr) return .FAILED, hresult_text("AcquireNextFrame", hr); defer IDXGIOutputDuplication_ReleaseFrame(s.duplication); // held, it stalls the desktop defer IUnknown_Release(resource); image := info.LastPresentTime.QuadPart != 0; if image { texture: *ID3D11Texture2D; hr = IUnknown_QueryInterface(resource, *uid(ID3D11Texture2D_UUID), xx *texture); if FAILED(hr) return .FAILED, hresult_text("the frame is not a texture", hr); gpu_copy_region(s, texture); IUnknown_Release(texture); } pointer := info.LastMouseUpdateTime.QuadPart != 0; if pointer { s.pointer_position = info.PointerPosition.Position; s.pointer_visible = info.PointerPosition.Visible != .FALSE; if info.PointerShapeBufferSize { array_resize(*s.shape.data, info.PointerShapeBufferSize, initialize = false); needed: u32; shape_info: DXGI_OUTDUPL_POINTER_SHAPE_INFO; hr = IDXGIOutputDuplication_GetFramePointerShape(s.duplication, cast(u32) s.shape.data.count, s.shape.data.data, *needed, *shape_info); if SUCCEEDED(hr) { s.shape.type = cast(DXGI_OUTDUPL_POINTER_SHAPE_TYPE) shape_info.Type; s.shape.width = cast(s32) shape_info.Width; s.shape.height = cast(s32) shape_info.Height; s.shape.pitch = cast(s32) shape_info.Pitch; } else { array_reset_keeping_memory(*s.shape.data); } } } if image && pointer return .IMAGE_AND_POINTER, ""; if image return .IMAGE, ""; if pointer return .POINTER, ""; return .NONE, ""; } dxgi_close :: (s: *Dxgi_Source) { com_release(*s.duplication); com_release(*s.output5); com_release(*s.output); gpu_close(s); array_free(s.shape.data); s.shape = .{}; }