- record_region, a new [global_hotkeys] action on Ctrl-PrintScreen (Windows only; none on macOS): starts a recording, or stops the one running. - The overlay's video mode (screen_capture_win32.jai): red outline and magnifier; the drag held to the screen it began on; under 240 x 180 (record_size_allowed, in Screen_Record) the outline is dashed, the label gives the minimum, and letting go takes nothing; a click or Enter takes the whole screen; no pixels copied. With immediate, no overlay at all: the screen under the cursor, for PrintScreen while recording. - src/app/screen_record.jai: records with modules/Screen_Record to %TEMP%/mcap at 30 fps; a recording that stops by itself keeps what it has, as does quitting (the shutdown step stops it). The MP4 goes into the library through the stills' jobs: a video .mcap in Screen Captures, the MP4 its original, a frame of it its rendered image, metadata.json as a still's, capture.json with Duration, FrameRate, VideoCodec, PosterTime, Encoder, Capture and DroppedFrames; the MP4 is deleted once it is in. The catalog reads a video's keys from capture.json too. - The capture thread makes itself aware of each monitor's DPI: ImGui makes only the main thread aware, and Desktop Duplication refused the app's capture thread with DXGI_ERROR_UNSUPPORTED (the fallback recorded with Windows.Graphics.Capture, and the log said so). - -record SECONDS records the screen under the cursor, puts it in the library and exits; -record-quit quits while recording instead. Both pass on a scratch library. - Tests: a "screen recording" group (the size rule; an MP4 from the module's encoder written as a recording reads back, and the catalog takes it as a video). The segment overlay test no longer depends on the colours chosen in the app's own data folder. - README, the format's README and the plan describe it.
116 lines
4.7 KiB
Plaintext
116 lines
4.7 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);
|
|
}
|
|
|
|
// The calling thread made aware of each monitor's DPI (Windows 10 1703 on), so the desktop it is told
|
|
// of is in the screens' own pixels.
|
|
gpu_thread_dpi_aware :: () {
|
|
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 :: cast(HANDLE) -4;
|
|
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
|
}
|
|
|
|
#scope_module
|
|
|
|
user32_dll :: #library,system "user32";
|
|
SetThreadDpiAwarenessContext :: (value: HANDLE) -> HANDLE #foreign user32_dll;
|
|
|
|
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 "";
|
|
}
|