diff --git a/CMakeLists.txt b/CMakeLists.txt index 420ab03..25a7af9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ set(SOURCES_COMMON src/render/dprint.c src/render/drect_border.c src/render/dtext.c + src/render/dupdate_hook.c src/render/dvline.c src/render/topti.c src/rtc/rtc.c diff --git a/include/gint/display.h b/include/gint/display.h index d9342b4..3a648bc 100644 --- a/include/gint/display.h +++ b/include/gint/display.h @@ -14,6 +14,7 @@ extern "C" { #endif #include +#include /* Platform-specific functions include VRAM management and the definition of the color_t type. */ @@ -62,6 +63,25 @@ extern "C" { fx-CG 50: 11 ms */ void dupdate(void); +/* dupdate_set_hook(): Define a function to be called after each dupdate() + + This functions configures the update hook, which is called after each + dupdate() has sent VRAM to the display (but before VRAMs are switched when + triple-buffering is used on fx-CG 50). + + The hook is mostly useful to send a copy of the frame to another medium, + typically through a USB connection to a projector-style application. See + usb_fxlink_videocapture() in for an example. + + The function is an indirect call; create one with the GINT_CALL() macro from + . Pass GINT_CALL_NULL to disable the feature. + + @function Indirect call to perform after each dupdate(). */ +void dupdate_set_hook(gint_call_t function); + +/* dupdate_get_hook(): Get a copy of the dupdate() hook */ +gint_call_t dupdate_get_hook(void); + //--- // Area rendering functions //--- diff --git a/include/gint/usb-ff-bulk.h b/include/gint/usb-ff-bulk.h index b5ea8fc..6b3fa6e 100644 --- a/include/gint/usb-ff-bulk.h +++ b/include/gint/usb-ff-bulk.h @@ -97,8 +97,8 @@ typedef struct Returns false if the parameters are invalid or don't fit, in this case the contents of the header are unchanged. */ -bool usb_fxlink_fill_header(usb_fxlink_header_t *header, char *application, - char *type, uint32_t data_size); +bool usb_fxlink_fill_header(usb_fxlink_header_t *header, + char const *application, char const *type, uint32_t data_size); //--- // Short functions for fxlink built-in types @@ -168,6 +168,23 @@ void usb_fxlink_screenshot_gray(bool onscreen); and size allow, and 1 byte otherwise. If size is 0, strlen(text) is used. */ void usb_fxlink_text(char const *text, int size); +/* usb_fxlink_videocapture(): Send a frame for a video recording + + This function is essentially the same as usb_fxlink_screenshot(). It sends a + capture of the VRAM to fxlink but uses the "video" type, which fxlink + displays in real-time or saves as a video file. The meaning of the onscreen + setting is identical to usb_fxlink_screenshot(). + + This function can be called with onscreen=false as a dupdate() hook to + automatically send new frames to fxlink. */ +void usb_fxlink_videocapture(bool onscreen); + +#ifdef FX9860G +/* usb_fxlink_videocapture_gray(): Send a gray frame for a video recording + Like usb_fxlink_videocapture(), but uses VRAM data from the gray engine. */ +void usb_fxlink_videocapture_gray(bool onscreen); +#endif + #ifdef __cplusplus } #endif diff --git a/src/render-cg/dupdate.c b/src/render-cg/dupdate.c index 35c0192..ea70ef7 100644 --- a/src/render-cg/dupdate.c +++ b/src/render-cg/dupdate.c @@ -7,6 +7,9 @@ void dupdate(void) { r61524_display(gint_vram, 0, 224, R61524_DMA); + gint_call_t hook = dupdate_get_hook(); + if(hook.function) gint_call(hook); + /* The DMA is still running, so we need to switch VRAMs to avoid overwriting the data which is about to be sent. */ dvram_switch(); diff --git a/src/render-fx/dupdate.c b/src/render-fx/dupdate.c index d9fb71b..c1543da 100644 --- a/src/render-fx/dupdate.c +++ b/src/render-fx/dupdate.c @@ -11,16 +11,23 @@ uint32_t *gint_vram = fx_vram; /* The current rendering mode */ struct rendering_mode const *dmode = NULL; -/* dupdate() - push the video RAM to the display driver */ +/* dupdate(): Push the video RAM to the display driver */ void dupdate(void) { + bool run_default = true; + if(dmode && dmode->dupdate) { - /* Call the overridden dupdate(), but continue if itreturns + /* Call the overridden dupdate(), but continue if it returns non-zero (this is used when stopping the gray engine) */ int rc = dmode->dupdate(); - if(rc == 0) return; + run_default = (rc != 0); + } + if(run_default) + { + t6k11_display(gint_vram, 0, 64, 16); } - t6k11_display(gint_vram, 0, 64, 16); + gint_call_t hook = dupdate_get_hook(); + if(hook.function) gint_call(hook); } diff --git a/src/render/dupdate_hook.c b/src/render/dupdate_hook.c new file mode 100644 index 0000000..28af0bc --- /dev/null +++ b/src/render/dupdate_hook.c @@ -0,0 +1,16 @@ +#include + +/* Hook to be called after each dupdate(). */ +static gint_call_t hook = GINT_CALL_NULL; + +/* dupdate_set_hook(): Define a function to be called after each dupdate() */ +void dupdate_set_hook(gint_call_t function) +{ + hook = function; +} + +/* dupdate_get_hook(): Get a copy of the dupdate() hook */ +gint_call_t dupdate_get_hook(void) +{ + return hook; +} diff --git a/src/usb/classes/ff-bulk-gray.c b/src/usb/classes/ff-bulk-gray.c index 8dac23d..e6c3df8 100644 --- a/src/usb/classes/ff-bulk-gray.c +++ b/src/usb/classes/ff-bulk-gray.c @@ -5,7 +5,7 @@ #include #include -void usb_fxlink_screenshot_gray(GUNUSED bool onscreen) +static void capture_vram_gray(GUNUSED bool onscreen, char const *type) { uint32_t *light, *dark; if(onscreen) dgray_getscreen(&light, &dark); @@ -14,7 +14,7 @@ void usb_fxlink_screenshot_gray(GUNUSED bool onscreen) usb_fxlink_header_t header; usb_fxlink_image_t subheader; - usb_fxlink_fill_header(&header, "fxlink", "image", + usb_fxlink_fill_header(&header, "fxlink", type, 2048 + sizeof subheader); subheader.width = htole32(DWIDTH); @@ -29,4 +29,14 @@ void usb_fxlink_screenshot_gray(GUNUSED bool onscreen) usb_commit_sync(pipe); } +void usb_fxlink_screenshot_gray(bool onscreen) +{ + capture_vram_gray(onscreen, "image"); +} + +void usb_fxlink_videocapture_gray(bool onscreen) +{ + capture_vram_gray(onscreen, "video"); +} + #endif /* FX9860G */ diff --git a/src/usb/classes/ff-bulk.c b/src/usb/classes/ff-bulk.c index cf6deda..4aa910d 100644 --- a/src/usb/classes/ff-bulk.c +++ b/src/usb/classes/ff-bulk.c @@ -60,8 +60,8 @@ int usb_ff_bulk_output(void) // fxlink protocol //--- -bool usb_fxlink_fill_header(usb_fxlink_header_t *header, char *application, - char *type, uint32_t data_size) +bool usb_fxlink_fill_header(usb_fxlink_header_t *header, + char const *application, char const *type, uint32_t data_size) { if(strlen(application) > 16 || strlen(type) > 16) return false; @@ -77,7 +77,7 @@ bool usb_fxlink_fill_header(usb_fxlink_header_t *header, char *application, return true; } -void usb_fxlink_screenshot(GUNUSED bool onscreen) +static void capture_vram(GUNUSED bool onscreen, char const *type) { void *source = gint_vram; int size, format; @@ -100,7 +100,7 @@ void usb_fxlink_screenshot(GUNUSED bool onscreen) usb_fxlink_header_t header; usb_fxlink_image_t subheader; - usb_fxlink_fill_header(&header, "fxlink", "image", + usb_fxlink_fill_header(&header, "fxlink", type, size + sizeof subheader); subheader.width = htole32(DWIDTH); @@ -114,6 +114,11 @@ void usb_fxlink_screenshot(GUNUSED bool onscreen) usb_commit_sync(pipe); } +void usb_fxlink_screenshot(bool onscreen) +{ + capture_vram(onscreen, "image"); +} + void usb_fxlink_text(char const *text, int size) { if(size == 0) size = strlen(text); @@ -130,3 +135,8 @@ void usb_fxlink_text(char const *text, int size) usb_write_sync(pipe, text, size, unit_size, false); usb_commit_sync(pipe); } + +void usb_fxlink_videocapture(bool onscreen) +{ + capture_vram(onscreen, "video"); +}