- src/app/screen_record_win32.jai: while recording, a red frame just outside the region (one
window shaped by a window region, clicks let through) and a toolbar beside it (below the
region at its right, else above, else inside a whole screen's top right): a blinking dot,
the time less time paused, the size, frames dropped and screen losses, and Pause/Resume,
Stop, and Discard, confirmed in the toolbar itself ("Yes, discard / No"). GDI windows that
never take the focus, kept out of every capture (WDA_EXCLUDEFROMCAPTURE). Buttons only ask;
screen_record_frame acts on the next frame. On the shutdown thread they are hidden, not
destroyed.
- modules/Screen_Record: record_pause; while paused the source is drained but nothing is sent,
and frames' times leave the pause out, so the video has no gap. Record_Stats.paused_seconds;
Record_Testing.LOSE_DUPLICATION loses Desktop Duplication every 3 s.
- Recordings left in %TEMP%/mcap by a crash are offered at startup: into the library (no window
known; size from its frames, date from its name), deleted, or later.
- [video] in settings.config: fps (1-120, 30), cursor (true), capture (auto, dxgi, wgc); written
into existing files on the next launch.
- The Win32 GDI bindings move to src/base/platform/win32_gdi_bindings.jai, shared by the overlay
and the toolbar.
- -record-region X Y W H and -record-ui-visible for -record; -pause SECONDS and -lose for
record-probe. capture.json's Paused.
- Checked: the toolbar is not in a whole-screen recording's frame; a 10 s pause leaves a 6.2 s
video of a 16 s run at a steady 30 fps (DXGI and WGC); 3 simulated losses in 10 s recover with
no gap. Tests: a leftover's date and its write (22 checks in the recording group); all pass.
- Fixed on the way: a recording's path repeated the temporary folder ("%1" is the first argument).
413 lines
21 KiB
Plaintext
413 lines
21 KiB
Plaintext
// record-probe: phase 1 of video-capture-plan.html. Records a screen, or a region of one, with
|
|
// modules/Screen_Record (DXGI Desktop Duplication and FFmpeg) and says how it went: which screens
|
|
// there are and on which adapter, which duplication call and encoder worked, frames captured, written
|
|
// and dropped, time per stage, and the file's size. It also answers what the plan leaves to phase 1:
|
|
//
|
|
// -capture HOW auto (Desktop Duplication, else Windows.Graphics.Capture), dxgi or wgc.
|
|
// -border-test counts yellow pixels along the screen's edges, with GDI, before recording and a
|
|
// second in: does Windows draw its capture border (Windows.Graphics.Capture)?
|
|
// -keep-border leaves Windows.Graphics.Capture's border on: the border test's control.
|
|
// -simulate-unsupported Desktop Duplication fails as on a laptop with two GPUs: the fallback's path.
|
|
// -poll Windows.Graphics.Capture polled, as when it will not take its frame handler.
|
|
// -pause SECONDS pauses 2 s in, for that long: the video should be that much shorter, with no gap.
|
|
// -lose Desktop Duplication lost every 3 s, as a mode change or the secure desktop loses it.
|
|
// -cursor-test puts the mouse in the middle of the region while recording, and back after;
|
|
// with -snapshot, cursor.png is the frame around it, four times as large.
|
|
// -legacy the plain DuplicateOutput, not DuplicateOutput1: does it fail for a process aware
|
|
// of each monitor's DPI, as mcap is (this probe is, unless -dpi-unaware)?
|
|
// -cuda makes a CUDA context first, as onnxruntime does in mcap: does duplication still work?
|
|
// -exclude-test puts two squares on the region, one kept out of captures with
|
|
// WDA_EXCLUDEFROMCAPTURE: is it out of the recording, and the other in?
|
|
// -animate moves a square across the region the whole time, so the screen changes every
|
|
// frame: for measuring throughput.
|
|
// -check FILE reads FILE back with FFmpeg and says what it holds, and nothing else: for a
|
|
// recording cut short by a crash.
|
|
// -snapshot DIR the last frame captured (captured.png) and a frame decoded back from the file
|
|
// (decoded.png), to compare by eye.
|
|
//
|
|
// Build: jai first.jai - record_probe
|
|
// Run: bin/record-probe [-list] [-screen N] [-region X Y W H] [-seconds S] [-fps F] [-encoder NAME]
|
|
// [-out FILE.mp4] [-no-cursor] [-legacy] [-cuda] [-exclude-test] [-snapshot DIR]
|
|
// [-animate] [-capture auto|dxgi|wgc] [-border-test] [-keep-border] [-cursor-test]
|
|
// [-simulate-unsupported] [-poll] [-pause SECONDS] [-lose] [-dpi-unaware]
|
|
// Exit code: 0 recorded, 1 failed, 3 Desktop Duplication cannot run here (the fallback's case).
|
|
|
|
main :: () {
|
|
args := get_command_line_arguments();
|
|
options: Record_Options;
|
|
options.path = tprint("%/record-probe.mp4", get_working_directory());
|
|
seconds: float64 = 10;
|
|
list_only, cuda, exclude_test, dpi_unaware, animate, border_test, cursor_test := false, false, false, false, false, false, false;
|
|
snapshot_folder: string;
|
|
pause_seconds: float64;
|
|
i := 1;
|
|
number :: (index: s64) -> s32 #expand { return cast(s32) string_to_int(`args[index]); }
|
|
while i < args.count {
|
|
a := args[i];
|
|
more := args.count - i - 1;
|
|
if a == "-list" { list_only = true; i += 1; continue; }
|
|
if a == "-screen" && more >= 1 { options.screen = number(i + 1); i += 2; continue; }
|
|
if a == "-region" && more >= 4 {
|
|
x, y, w, h := number(i + 1), number(i + 2), number(i + 3), number(i + 4);
|
|
options.region = .{x, y, x + w, y + h};
|
|
i += 5; continue;
|
|
}
|
|
if a == "-seconds" && more >= 1 { seconds = string_to_float64(args[i + 1]); i += 2; continue; }
|
|
if a == "-fps" && more >= 1 { options.fps = number(i + 1); i += 2; continue; }
|
|
if a == "-encoder" && more >= 1 { options.encoder = args[i + 1]; i += 2; continue; }
|
|
if a == "-out" && more >= 1 { options.path = args[i + 1]; i += 2; continue; }
|
|
if a == "-snapshot" && more >= 1 { snapshot_folder = args[i + 1]; i += 2; continue; }
|
|
if a == "-no-cursor" { options.cursor = false; i += 1; continue; }
|
|
if a == "-legacy" { options.legacy_duplicate = true; i += 1; continue; }
|
|
if a == "-cuda" { cuda = true; i += 1; continue; }
|
|
if a == "-exclude-test" { exclude_test = true; i += 1; continue; }
|
|
if a == "-dpi-unaware" { dpi_unaware = true; i += 1; continue; }
|
|
if a == "-animate" { animate = true; i += 1; continue; }
|
|
if a == "-border-test" { border_test = true; i += 1; continue; }
|
|
if a == "-keep-border" { options.testing |= .KEEP_BORDER; i += 1; continue; }
|
|
if a == "-simulate-unsupported" { options.testing |= .DXGI_UNSUPPORTED; i += 1; continue; }
|
|
if a == "-poll" { options.testing |= .WGC_POLL; i += 1; continue; }
|
|
if a == "-lose" { options.testing |= .LOSE_DUPLICATION; i += 1; continue; }
|
|
if a == "-pause" && more >= 1 { pause_seconds = string_to_float64(args[i + 1]); i += 2; continue; }
|
|
if a == "-cursor-test" { cursor_test = true; i += 1; continue; }
|
|
if a == "-capture" && more >= 1 {
|
|
how := args[i + 1];
|
|
if how == "auto" options.capture = .AUTO;
|
|
else if how == "dxgi" options.capture = .DXGI;
|
|
else if how == "wgc" options.capture = .WGC;
|
|
else { print("-capture takes auto, dxgi or wgc\n"); exit(2); }
|
|
i += 2; continue;
|
|
}
|
|
if a == "-check" && more >= 1 {
|
|
image, info, ok, error := video_poster(args[i + 1]);
|
|
if !ok { print("%: cannot be read: %\n", args[i + 1], error); exit(1); }
|
|
print("%: % x %, % s, %, % fps; a frame decoded at % s\n", args[i + 1], info.width, info.height, FF(info.duration, 2), info.codec, FF(info.frame_rate, 1), FF(info.poster_time, 2));
|
|
exit(0);
|
|
}
|
|
print("Unknown argument %\n", a);
|
|
exit(2);
|
|
}
|
|
|
|
if !dpi_unaware {
|
|
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 :: cast(HANDLE) -4;
|
|
if !SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) {
|
|
error := GetLastError();
|
|
ERROR_ACCESS_DENIED :: 5;
|
|
if error != ERROR_ACCESS_DENIED print("Could not become per-monitor DPI aware (v2): error %\n", error);
|
|
// Access denied: the awareness was set already, as it can be once only. dxgi_open and wgc_open
|
|
// refuse when the desktop they are told of is scaled.
|
|
}
|
|
}
|
|
print("DPI awareness: %\n", ifx dpi_unaware then "none" else "per monitor (v2), as mcap");
|
|
|
|
screens := record_list_screens();
|
|
print("Screens:\n");
|
|
for screens {
|
|
d := it.desktop;
|
|
print(" %: % on adapter % (%): % x % at %, %%\n", it_index, it.name, it.adapter_index, it.adapter,
|
|
d.right - d.left, d.bottom - d.top, d.left, d.top,
|
|
ifx it.rotation == .IDENTITY || it.rotation == .UNSPECIFIED then "" else tprint(", rotated %", it.rotation));
|
|
}
|
|
if list_only return;
|
|
|
|
if cuda {
|
|
ok, text := cuda_make_context();
|
|
print("CUDA: %\n", text);
|
|
if !ok exit(1);
|
|
}
|
|
|
|
border_screen := screens[ifx options.screen >= 0 then options.screen else 0].desktop;
|
|
border_before: s64;
|
|
if border_test border_before = yellow_edge_pixels(border_screen);
|
|
|
|
recorder: Recorder;
|
|
ok, error, fall_back := record_start(*recorder, options);
|
|
if !ok {
|
|
print("Cannot record: %\n", error);
|
|
if fall_back print("Desktop Duplication cannot run here: this is the case for the Windows.Graphics.Capture fallback.\n");
|
|
exit(ifx fall_back then cast(s32) 3 else 1);
|
|
}
|
|
stats := record_stats(*recorder);
|
|
if stats.fell_back_from print("Desktop Duplication could not run (%), so:\n", stats.fell_back_from);
|
|
print("Recording % x % for % s at up to % fps with % and %, to %\n", stats.width, stats.height, seconds, options.fps, stats.source, stats.encoder, options.path);
|
|
|
|
region := options.region;
|
|
if region.right <= region.left region = screens[ifx options.screen >= 0 then options.screen else 0].desktop;
|
|
excluded, control: HWND;
|
|
if exclude_test excluded, control = exclusion_windows(region);
|
|
saved_cursor: POINT;
|
|
cursor_at: POINT;
|
|
if cursor_test {
|
|
GetCursorPos(*saved_cursor);
|
|
cursor_at = .{region.left + (region.right - region.left) / 2, region.top + (region.bottom - region.top) / 2};
|
|
SetCursorPos(cursor_at.x, cursor_at.y);
|
|
}
|
|
mover: HWND;
|
|
if animate mover = square_window("record-probe animate", 0x0000A5FF, region.left, region.top, 120);
|
|
|
|
start := current_time_monotonic();
|
|
last_print := start;
|
|
border_during: s64 = -1;
|
|
while to_float64_seconds(current_time_monotonic() - start) < seconds {
|
|
pump_messages();
|
|
if mover { // back and forth across the region, about 240 times a second
|
|
elapsed := to_float64_seconds(current_time_monotonic() - start);
|
|
span := region.right - region.left - 120;
|
|
phase := fmod_cycling(elapsed * 0.5, 2.0);
|
|
x := region.left + cast(s32) (span * ifx phase < 1 then phase else 2 - phase);
|
|
SetWindowPos(mover, HWND_TOPMOST, x, region.top + (region.bottom - region.top) / 2 - 60, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE);
|
|
sleep_milliseconds(4);
|
|
} else {
|
|
sleep_milliseconds(20);
|
|
}
|
|
if to_float64_seconds(current_time_monotonic() - last_print) >= 1 {
|
|
last_print = current_time_monotonic();
|
|
s := record_stats(*recorder);
|
|
print(" % s: % captured, % sent (% repeats), % written, % dropped, % lost, % KB\n",
|
|
FF(to_float64_seconds(last_print - start), 0), s.captured, s.emitted, s.repeats, s.written, s.dropped, s.lost, s.bytes / 1024);
|
|
}
|
|
if pause_seconds > 0 {
|
|
elapsed := to_float64_seconds(current_time_monotonic() - start);
|
|
should := elapsed >= 2 && elapsed < 2 + pause_seconds;
|
|
if should != record_paused(*recorder) {
|
|
record_pause(*recorder, should);
|
|
print(" % s: %\n", FF(elapsed, 1), ifx should then "paused" else "resumed");
|
|
}
|
|
}
|
|
if border_test && border_during < 0 && to_float64_seconds(current_time_monotonic() - start) >= 1.5
|
|
border_during = yellow_edge_pixels(border_screen);
|
|
failed, _ := record_failed(*recorder);
|
|
if failed break;
|
|
}
|
|
|
|
last, width, height := record_last_frame(*recorder);
|
|
if mover DestroyWindow(mover);
|
|
if cursor_test SetCursorPos(saved_cursor.x, saved_cursor.y);
|
|
stopped, stop_error := record_stop(*recorder);
|
|
s := recorder.stats;
|
|
print("\n%\n", ifx stopped then "Recorded." else tprint("Recording failed: %", stop_error));
|
|
ms_each :: (ticks: s64, count: s64) -> string #expand {
|
|
if !count return "-";
|
|
return tprint("% ms", FF(cast(float64) ticks / `recorder.frequency * 1000 / count, 2));
|
|
}
|
|
file_size := file_size_of(options.path);
|
|
print(" source: %\n", s.source);
|
|
print(" encoder: %\n", s.encoder);
|
|
print(" size: % x %\n", s.width, s.height);
|
|
print(" seconds: %\n", FF(s.seconds, 1));
|
|
print(" captured: % new images of the screen (% a second), % cursor-only changes\n", s.captured, FF(s.captured / s.seconds, 1), s.pointer_moves);
|
|
print(" sent: % frames (% a second), % of them repeats of an unchanged screen\n", s.emitted, FF(s.emitted / s.seconds, 1), s.repeats);
|
|
print(" written: %, dropped %, duplication lost %, paused % s\n", s.written, s.dropped, s.lost, FF(s.paused_seconds, 1));
|
|
print(" each: copy % (per image), read back % , encode % (per frame)\n",
|
|
ms_each(s.copy_ticks, s.captured), ms_each(s.readback_ticks, s.emitted), ms_each(s.encode_ticks, s.written));
|
|
print(" file: % KB, % MB a minute\n", file_size / 1024, FF(cast(float64) file_size / (1024 * 1024) / s.seconds * 60, 1));
|
|
|
|
if exclude_test {
|
|
excluded_rect := window_rect_in(excluded, region);
|
|
control_rect := window_rect_in(control, region);
|
|
excluded_seen := colour_fraction(last, width, height, excluded_rect, EXCLUDED_BGR);
|
|
control_seen := colour_fraction(last, width, height, control_rect, CONTROL_BGR);
|
|
print(" exclusion: the excluded square is % in the last frame, the control square %: %\n",
|
|
percent(excluded_seen), percent(control_seen),
|
|
ifx control_seen < 0.9 then "inconclusive (the control is not there)"
|
|
else ifx excluded_seen < 0.01 then "WDA_EXCLUDEFROMCAPTURE is respected"
|
|
else "WDA_EXCLUDEFROMCAPTURE is NOT respected");
|
|
DestroyWindow(excluded);
|
|
DestroyWindow(control);
|
|
}
|
|
|
|
if border_test {
|
|
print(" border: % yellow pixels along the screen's edges before recording, % while: %\n", border_before, border_during,
|
|
ifx border_during < 0 then "not measured (too short)"
|
|
else ifx border_during > border_before + 200 then "a capture border is drawn"
|
|
else "no capture border");
|
|
}
|
|
|
|
if snapshot_folder {
|
|
make_directory_if_it_does_not_exist(snapshot_folder, recursive = true);
|
|
rgba := bgra_to_rgba(last);
|
|
if cursor_test { // 48 x 48 around where the mouse was put, four times as large
|
|
SIDE :: 48; SCALE :: 4;
|
|
crop := NewArray(SIDE * SCALE * SIDE * SCALE * 4, u8,, temp);
|
|
cx, cy := cursor_at.x - region.left - 8, cursor_at.y - region.top - 8;
|
|
for y: 0..SIDE * SCALE - 1 for x: 0..SIDE * SCALE - 1 {
|
|
sx, sy := clamp(cx + x / SCALE, 0, width - 1), clamp(cy + y / SCALE, 0, height - 1);
|
|
memcpy(crop.data + (y * SIDE * SCALE + x) * 4, rgba.data + (cast(s64) sy * width + sx) * 4, 4);
|
|
}
|
|
cursor_path := tprint("%/cursor.png", snapshot_folder);
|
|
stbi_write_png(temp_c_string(cursor_path), SIDE * SCALE, SIDE * SCALE, 4, crop.data, SIDE * SCALE * 4);
|
|
print(" cursor: %\n", cursor_path);
|
|
}
|
|
captured_path := tprint("%/captured.png", snapshot_folder);
|
|
stbi_write_png(temp_c_string(captured_path), width, height, 4, rgba.data, width * 4);
|
|
image, info, decoded, decode_error := video_poster(options.path);
|
|
if decoded {
|
|
decoded_path := tprint("%/decoded.png", snapshot_folder);
|
|
stbi_write_png(temp_c_string(decoded_path), image.width, image.height, 4, image.pixels, image.width * 4);
|
|
print(" snapshot: % and % (decoded at % s of %: %, % fps)\n", captured_path, decoded_path, FF(info.poster_time, 2), FF(info.duration, 2), info.codec, FF(info.frame_rate, 1));
|
|
image_free(*image);
|
|
} else {
|
|
print(" snapshot: %; the file could not be decoded back: %\n", captured_path, decode_error);
|
|
}
|
|
}
|
|
exit(ifx stopped then cast(s32) 0 else 1);
|
|
}
|
|
|
|
#scope_file
|
|
|
|
EXCLUDED_BGR :: u8.[255, 0, 255]; // magenta
|
|
CONTROL_BGR :: u8.[255, 255, 0]; // cyan
|
|
|
|
// Two squares on the region, topmost: one kept out of captures, one not.
|
|
exclusion_windows :: (region: RECT) -> excluded: HWND, control: HWND {
|
|
width := region.right - region.left;
|
|
height := region.bottom - region.top;
|
|
side := min(cast(s32) 160, width / 4, height / 2);
|
|
y := region.top + (height - side) / 2;
|
|
excluded := square_window("record-probe excluded", 0x00FF00FF, region.left + width / 4 - side / 2, y, side);
|
|
control := square_window("record-probe control", 0x00FFFF00, region.left + 3 * width / 4 - side / 2, y, side);
|
|
WDA_EXCLUDEFROMCAPTURE :: 0x11;
|
|
if !SetWindowDisplayAffinity(excluded, WDA_EXCLUDEFROMCAPTURE) print("SetWindowDisplayAffinity failed; the test cannot say anything.\n");
|
|
pump_messages();
|
|
return excluded, control;
|
|
}
|
|
|
|
// A square of one colour, topmost, that never takes the focus.
|
|
square_window :: (class_name: string, colour: u32, x: s32, y: s32, side: s32) -> HWND {
|
|
instance := GetModuleHandleW(null);
|
|
wc: WNDCLASSEXW;
|
|
wc.cbSize = size_of(WNDCLASSEXW);
|
|
wc.lpfnWndProc = xx DefWindowProcW;
|
|
wc.hInstance = instance;
|
|
wc.hbrBackground = CreateSolidBrush(colour);
|
|
wc.lpszClassName = utf8_to_wide(class_name,, temp);
|
|
RegisterClassExW(*wc);
|
|
return CreateWindowExW(WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE, utf8_to_wide(class_name,, temp), utf8_to_wide(class_name,, temp),
|
|
WS_POPUP | WS_VISIBLE, x, y, side, side, null, null, instance, null);
|
|
}
|
|
|
|
// Pixels along a screen's outer 6 pixels that look like Windows' yellow capture border, with GDI.
|
|
yellow_edge_pixels :: (screen: RECT) -> s64 {
|
|
width, height := screen.right - screen.left, screen.bottom - screen.top;
|
|
desktop := GetDC(null);
|
|
defer ReleaseDC(null, desktop);
|
|
dc := CreateCompatibleDC(desktop);
|
|
defer DeleteDC(dc);
|
|
bitmap := CreateCompatibleBitmap(desktop, width, height);
|
|
defer DeleteObject(bitmap);
|
|
old := SelectObject(dc, bitmap);
|
|
BitBlt(dc, 0, 0, width, height, desktop, screen.left, screen.top, cast(ROP) (cast(u32) ROP.SRCCOPY | 0x40000000)); // CAPTUREBLT
|
|
SelectObject(dc, old);
|
|
|
|
info: BITMAPINFO;
|
|
info.bmiHeader.biSize = size_of(BITMAPINFOHEADER);
|
|
info.bmiHeader.biWidth = width;
|
|
info.bmiHeader.biHeight = -height;
|
|
info.bmiHeader.biPlanes = 1;
|
|
info.bmiHeader.biBitCount = 32;
|
|
info.bmiHeader.biCompression = BI_RGB;
|
|
pixels := NewArray(cast(s64) width * height * 4, u8,, temp);
|
|
GetDIBits(dc, bitmap, 0, xx height, pixels.data, *info, DIB_RGB_COLORS);
|
|
|
|
EDGE :: 6;
|
|
count: s64;
|
|
for y: 0..height - 1 for x: 0..width - 1 {
|
|
if x >= EDGE && x < width - EDGE && y >= EDGE && y < height - EDGE continue;
|
|
p := pixels.data + (cast(s64) y * width + x) * 4;
|
|
b, g, r := cast(s32) p[0], cast(s32) p[1], cast(s32) p[2];
|
|
if r > 200 && g > 120 && b < 90 && r - b > 130 count += 1;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// A window's rectangle, in the region's pixels, a few pixels in from its edges.
|
|
window_rect_in :: (window: HWND, region: RECT) -> RECT {
|
|
r: RECT;
|
|
GetWindowRect(window, *r);
|
|
return .{r.left - region.left + 4, r.top - region.top + 4, r.right - region.left - 4, r.bottom - region.top - 4};
|
|
}
|
|
|
|
colour_fraction :: (pixels: [] u8, width: s32, height: s32, r: RECT, bgr: [3] u8) -> float64 {
|
|
total, same: s64;
|
|
for y: max(r.top, 0)..min(r.bottom, height) - 1 for x: max(r.left, 0)..min(r.right, width) - 1 {
|
|
p := pixels.data + (cast(s64) y * width + x) * 4;
|
|
total += 1;
|
|
if abs(cast(s32) p[0] - bgr[0]) <= 8 && abs(cast(s32) p[1] - bgr[1]) <= 8 && abs(cast(s32) p[2] - bgr[2]) <= 8 same += 1;
|
|
}
|
|
return ifx total then cast(float64) same / total else 0;
|
|
}
|
|
|
|
pump_messages :: () {
|
|
msg: MSG;
|
|
while PeekMessageW(*msg, null, 0, 0, PM_REMOVE) {
|
|
TranslateMessage(*msg);
|
|
DispatchMessageW(*msg);
|
|
}
|
|
}
|
|
|
|
// A CUDA context on the first device, as onnxruntime makes for OCR and labels in mcap, and kept.
|
|
cuda_make_context :: () -> ok: bool, text: string {
|
|
library := LoadLibraryW(utf8_to_wide("nvcuda.dll",, temp));
|
|
if !library return false, "nvcuda.dll is not installed";
|
|
Cu_Init :: #type (flags: u32) -> s32 #c_call;
|
|
Cu_Device :: #type (device: *s32, ordinal: s32) -> s32 #c_call;
|
|
Cu_Context :: #type (cuda_context: **void, flags: u32, device: s32) -> s32 #c_call;
|
|
Cu_Name :: #type (name: *u8, length: s32, device: s32) -> s32 #c_call;
|
|
cu_init := cast(Cu_Init) GetProcAddress(library, "cuInit");
|
|
cu_device_get := cast(Cu_Device) GetProcAddress(library, "cuDeviceGet");
|
|
cu_ctx_create := cast(Cu_Context) GetProcAddress(library, "cuCtxCreate_v2");
|
|
cu_name := cast(Cu_Name) GetProcAddress(library, "cuDeviceGetName");
|
|
if !cu_init || !cu_device_get || !cu_ctx_create || !cu_name return false, "nvcuda.dll lacks the calls needed";
|
|
result := cu_init(0);
|
|
if result return false, tprint("cuInit failed (%)", result);
|
|
device: s32;
|
|
result = cu_device_get(*device, 0);
|
|
if result return false, tprint("cuDeviceGet failed (%)", result);
|
|
name: [256] u8;
|
|
cu_name(name.data, name.count, device);
|
|
cuda_context: *void;
|
|
result = cu_ctx_create(*cuda_context, 0, device);
|
|
if result return false, tprint("cuCtxCreate failed (%)", result);
|
|
return true, tprint("a context on %, kept for the recording", to_string(name.data));
|
|
}
|
|
|
|
bgra_to_rgba :: (pixels: [] u8) -> [] u8 {
|
|
out := NewArray(pixels.count, u8,, temp);
|
|
for i: 0..pixels.count / 4 - 1 {
|
|
out[i * 4 + 0] = pixels[i * 4 + 2];
|
|
out[i * 4 + 1] = pixels[i * 4 + 1];
|
|
out[i * 4 + 2] = pixels[i * 4 + 0];
|
|
out[i * 4 + 3] = 255;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
file_size_of :: (path: string) -> s64 {
|
|
_, size, ok := file_modtime_and_size(path);
|
|
return ifx ok then size else 0;
|
|
}
|
|
|
|
percent :: (fraction: float64) -> string {
|
|
return join(tprint("%", FF(fraction * 100, 1)), "%",, temp);
|
|
}
|
|
|
|
FF :: (value: float64, decimals: s64) -> FormatFloat #expand {
|
|
return formatFloat(value, trailing_width = decimals, zero_removal = .NO);
|
|
}
|
|
|
|
SetProcessDpiAwarenessContext :: (value: HANDLE) -> BOOL #foreign user32_dll;
|
|
SetWindowDisplayAffinity :: (window: HWND, affinity: u32) -> BOOL #foreign user32_dll;
|
|
SetCursorPos :: (x: s32, y: s32) -> BOOL #foreign user32_dll;
|
|
user32_dll :: #library,system "user32";
|
|
|
|
#import "Basic";
|
|
#import "String";
|
|
#import "File";
|
|
#import "File_Utilities";
|
|
#import "Math";
|
|
#import "Windows";
|
|
#import "Windows_Utf8";
|
|
#import "Screen_Record";
|
|
#import "Image_Decode";
|
|
#import "stb_image_write";
|