From 0021fbbaa98350c898097a9c5a3e11316dc06a03 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Wed, 8 Dec 2021 22:38:33 +0200 Subject: [PATCH] gg: support `VGG_STOP_AT_FRAME=120 VGG_SCREENSHOT_FOLDER=. VGG_SCREENSHOT_FRAMES=10,20,30 ./v -d gg_record run examples/gg/bezier_anim.v` (#12767) --- cmd/tools/vtest-all.v | 2 +- vlib/gg/gg.v | 6 ++-- vlib/gg/recorder.v | 49 +++++++++++++++++++++++++++++++++ vlib/sokol/sapp/sapp_v.c.v | 50 ++++++++++++++++++---------------- vlib/sokol/sapp/screenshot.c.v | 42 ++++++++++++++++++++++++++++ 5 files changed, 123 insertions(+), 26 deletions(-) create mode 100644 vlib/gg/recorder.v create mode 100644 vlib/sokol/sapp/screenshot.c.v diff --git a/cmd/tools/vtest-all.v b/cmd/tools/vtest-all.v index 55d581b129..58713f0936 100644 --- a/cmd/tools/vtest-all.v +++ b/cmd/tools/vtest-all.v @@ -181,7 +181,7 @@ fn get_all_commands() []Command { } $if macos || linux { res << Command{ - line: '$vexe -o v.c cmd/v && cc -Werror -I "$vroot/thirdparty/stdatomic/nix" v.c -lpthread && rm -rf a.out' + line: '$vexe -o v.c cmd/v && cc -Werror -I "$vroot/thirdparty/stdatomic/nix" v.c -lpthread -lm && rm -rf a.out' label: 'v.c should be buildable with no warnings...' okmsg: 'v.c can be compiled without warnings. This is good :)' rmfile: 'v.c' diff --git a/vlib/gg/gg.v b/vlib/gg/gg.v index 1959f39833..f5ad3899f5 100644 --- a/vlib/gg/gg.v +++ b/vlib/gg/gg.v @@ -106,6 +106,8 @@ fn gg_frame_fn(user_data voidptr) { // return } + ctx.record_frame() + if ctx.ui_mode && !ctx.needs_refresh { // Draw 3 more frames after the "stop refresh" command ctx.ticks++ @@ -250,8 +252,8 @@ fn gg_fail_fn(msg &char, user_data voidptr) { } } -pub fn (gg &Context) run() { - sapp.run(&gg.window) +pub fn (ctx &Context) run() { + sapp.run(&ctx.window) } // quit closes the context window and exits the event loop for it diff --git a/vlib/gg/recorder.v b/vlib/gg/recorder.v new file mode 100644 index 0000000000..8c072e2491 --- /dev/null +++ b/vlib/gg/recorder.v @@ -0,0 +1,49 @@ +module gg + +import os +import sokol.sapp + +[heap] +pub struct SSRecorderSettings { +pub mut: + stop_at_frame i64 = -1 + screenshot_frames []u64 + screenshot_folder string + screenshot_prefix string +} + +const recorder_settings = new_gg_recorder_settings() + +fn new_gg_recorder_settings() &SSRecorderSettings { + $if gg_record ? { + stop_frame := os.getenv_opt('VGG_STOP_AT_FRAME') or { '-1' }.i64() + frames := os.getenv('VGG_SCREENSHOT_FRAMES').split_any(',').map(it.u64()) + folder := os.getenv('VGG_SCREENSHOT_FOLDER') + prefix := os.join_path_single(folder, os.file_name(os.executable()).all_before('.') + '_') + return &SSRecorderSettings{ + stop_at_frame: stop_frame + screenshot_frames: frames + screenshot_folder: folder + screenshot_prefix: prefix + } + } $else { + return &SSRecorderSettings{} + } +} + +[if gg_record ?] +pub fn (mut ctx Context) record_frame() { + if ctx.frame in gg.recorder_settings.screenshot_frames { + screenshot_file_path := '$gg.recorder_settings.screenshot_prefix${ctx.frame}.png' + $if gg_record_trace ? { + eprintln('>>> screenshoting $screenshot_file_path') + } + sapp.screenshot_png(screenshot_file_path) or { panic(err) } + } + if ctx.frame == gg.recorder_settings.stop_at_frame { + $if gg_record_trace ? { + eprintln('>>> exiting at frame $ctx.frame') + } + exit(0) + } +} diff --git a/vlib/sokol/sapp/sapp_v.c.v b/vlib/sokol/sapp/sapp_v.c.v index 95cb19f618..093ba7378f 100644 --- a/vlib/sokol/sapp/sapp_v.c.v +++ b/vlib/sokol/sapp/sapp_v.c.v @@ -1,50 +1,54 @@ module sapp import os +import stbi // v_sapp_gl_read_rgba_pixels reads pixles from the OpenGL buffer into `pixels`. fn C.v_sapp_gl_read_rgba_pixels(x int, y int, width int, height int, pixels charptr) // screenshot takes a screenshot of the current window. -[inline] pub fn screenshot(path string) ? { if !path.ends_with('.ppm') { return error(@MOD + '.' + @FN + ' currently only supports .ppm files.') } + return screenshot_ppm(path) +} - w := width() - h := height() +// screenshot_ppm takes a screenshot of the current window as a .ppm file +[manualfree] +pub fn screenshot_ppm(path string) ? { + ss := screenshot_window() + write_rgba_to_ppm(path, ss.width, ss.height, 4, ss.pixels) ? + unsafe { ss.destroy() } +} - size := w * h * 4 // - mut pixels := []byte{len: size, init: 0} - - C.v_sapp_gl_read_rgba_pixels(0, 0, w, h, pixels.data) - - // TODO use separate thread for writing the data - // TODO use stbi to support more formats - // stbi.write_png(path, w, h, components, pixels.data, 3 * w) - // stbi.write_tga(path, w, h, components, pixels.data) - write_rgba_to_ppm(path, w, h, 4, pixels) ? - - unsafe { - pixels.free() - } +// screenshot_png takes a screenshot of the current window as a .png file +[manualfree] +pub fn screenshot_png(path string) ? { + ss := screenshot_window() + stbi.set_flip_vertically_on_write(true) + stbi.stbi_write_png(path, ss.width, ss.height, 4, ss.pixels, ss.width * 4) ? + unsafe { ss.destroy() } } // write_rgba_to_ppm writes `pixels` data in RGBA format to PPM3 format. -fn write_rgba_to_ppm(path string, w int, h int, components int, pixels []byte) ? { +fn write_rgba_to_ppm(path string, w int, h int, components int, pixels &byte) ? { mut f_out := os.create(path) ? + defer { + f_out.close() + } f_out.writeln('P3') ? f_out.writeln('$w $h') ? f_out.writeln('255') ? for i := h - 1; i >= 0; i-- { for j := 0; j < w; j++ { idx := i * w * components + j * components - r := int(pixels[idx]) - g := int(pixels[idx + 1]) - b := int(pixels[idx + 2]) - f_out.write_string('$r $g $b ') ? + unsafe { + r := int(pixels[idx]) + g := int(pixels[idx + 1]) + b := int(pixels[idx + 2]) + f_out.write_string('$r $g $b ') ? + } } } - f_out.close() } diff --git a/vlib/sokol/sapp/screenshot.c.v b/vlib/sokol/sapp/screenshot.c.v new file mode 100644 index 0000000000..fd3dcbf336 --- /dev/null +++ b/vlib/sokol/sapp/screenshot.c.v @@ -0,0 +1,42 @@ +module sapp + +[heap] +pub struct Screenshot { + width int + height int + size int +mut: + pixels &byte +} + +[manualfree] +pub fn screenshot_window() &Screenshot { + img_width := width() + img_height := height() + img_size := img_width * img_height * 4 + img_pixels := unsafe { &byte(malloc(img_size)) } + C.v_sapp_gl_read_rgba_pixels(0, 0, img_width, img_height, img_pixels) + return &Screenshot{ + width: img_width + height: img_height + size: img_size + pixels: img_pixels + } +} + +// free - free *only* the Screenshot pixels. +[unsafe] +pub fn (mut ss Screenshot) free() { + unsafe { + free(ss.pixels) + ss.pixels = &byte(0) + } +} + +// destroy - free the Screenshot pixels, +// then free the screenshot data structure itself. +[unsafe] +pub fn (mut ss Screenshot) destroy() { + unsafe { ss.free() } + unsafe { free(ss) } +}