[gegl] bin: add frame cache
- From: Øyvind "pippin" Kolås <ok src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gegl] bin: add frame cache
- Date: Wed, 15 May 2019 09:41:23 +0000 (UTC)
commit af72f8b744daebec8a86483520cb6cdf9dac686f
Author: Øyvind Kolås <pippin gimp org>
Date: Tue May 14 21:22:33 2019 +0200
bin: add frame cache
When enabled GEGL is caching all rendered frames and associated pcm data for
now uncompressed, you should have enough disk-space - and without deleting
stale data, and for many uses the disk space used is excessive, it will be
suited for realtime playback and editing of short segments, adding more file
formats will make it usable will less scratch disk-space, as a solution with
SSD it seems to make for a very good real-time full resolution preview backing
cache.
bin/lua/init.lua | 3 +
bin/lua/preferences.lua | 23 +++
bin/ui-core.c | 386 ++++++++++++++++++++++++++++++++++--------------
bin/ui.h | 5 +
4 files changed, 305 insertions(+), 112 deletions(-)
---
diff --git a/bin/lua/init.lua b/bin/lua/init.lua
index df664dc25..33dc92ebf 100644
--- a/bin/lua/init.lua
+++ b/bin/lua/init.lua
@@ -70,6 +70,8 @@ struct _GeState {
GeglNode *processor_node; /* the node we have a processor for */
GeglProcessor *processor;
GeglBuffer *processor_buffer;
+ GeglBuffer *cached_buffer;
+ int frame_cache;
int renderer_state;
int editing_op_name;
char editing_buf[1024];
@@ -83,6 +85,7 @@ struct _GeState {
float u, v;
float scale;
+ float fps;
int is_fit;
int show_bounding_box;
diff --git a/bin/lua/preferences.lua b/bin/lua/preferences.lua
index f91ef9dfd..c856c9c7f 100644
--- a/bin/lua/preferences.lua
+++ b/bin/lua/preferences.lua
@@ -26,6 +26,9 @@ mrg:print("\n")
mrg:text_listen_done ()
+
+
+
mrg:text_listen(Mrg.PRESS, function(ev)
if Gegl.config().mipmap_rendering then
Gegl.config().mipmap_rendering = false
@@ -85,6 +88,25 @@ end
mrg:text_listen_done ()
mrg:print("\n")
+
+mrg:text_listen(Mrg.PRESS, function(ev)
+ if o.frame_cache ~= 0 then
+ o.frame_cache = 0
+ else
+ o.frame_cache = 1
+ end
+ ev:stop_propagate()
+end)
+mrg:print("frame caching")
+if o.frame_cache ~= 0 then
+ mrg:print(" yes")
+else
+ mrg:print(" no")
+end
+mrg:print("\n")
+mrg:text_listen_done ()
+
+
mrg:set_style("font-size: 3vh");
mrg:print("threads: " .. Gegl.config().threads .. "\n")
@@ -98,3 +120,4 @@ mrg:print("application-license: " .. Gegl.config().application_license.. "\n")
+
diff --git a/bin/ui-core.c b/bin/ui-core.c
index b0e07322e..89d7ef332 100644
--- a/bin/ui-core.c
+++ b/bin/ui-core.c
@@ -456,6 +456,7 @@ Setting settings[]=
INT_PROP(show_bounding_box, "show bounding box of active node"),
INT_PROP(show_controls, "show image viewer controls (maybe merge with show-graph and give better name)"),
INT_PROP(nearest_neighbor, "nearest neighbor"),
+ INT_PROP(frame_cache, "store all rendered frames on disk uncompressed for fast scrubbing"),
FLOAT_PROP(slide_pause, "display scale factor"),
FLOAT_PROP(pos, "clip time position"),
FLOAT_PROP(duration, "clip duration, computed on load of clip"),
@@ -792,7 +793,8 @@ store_index (GeState *state, const char *path);
static void load_path_inner (GeState *o, char *path);
-
+gchar *pos_hash (GeState *o);
+static int has_quit = 0;
static gboolean renderer_task (gpointer data)
{
@@ -800,25 +802,35 @@ static gboolean renderer_task (gpointer data)
static gdouble progress = 0.0;
void *old_processor = o->processor;
GeglBuffer *old_buffer = o->processor_buffer;
+ static char *hash = NULL;
- if (renderer == GEGL_RENDERER_BLIT||
- renderer == GEGL_RENDERER_BLIT_MIPMAP)
- o->renderer_state = 4;
+ static GeglAudioFragment *cached_audio = NULL;
switch (o->renderer_state)
{
case 0:
+ if (renderer == GEGL_RENDERER_BLIT||
+ renderer == GEGL_RENDERER_BLIT_MIPMAP)
+ {
+ o->renderer_state = 4;
+ break;
+ }
+
if (renderer_dirty)
{
renderer_dirty = 0;
+ if (o->cached_buffer)
+ {
+ g_object_unref (o->cached_buffer);
+ o->cached_buffer = NULL;
+ }
if (o->processor_node != o->sink)
{
o->processor = gegl_node_new_processor (o->sink, NULL);
o->processor_buffer = g_object_ref (gegl_processor_get_buffer (o->processor));
- if (old_buffer)
- g_object_unref (old_buffer);
- if (old_processor)
- g_object_unref (old_processor);
+ g_clear_object (&old_buffer);
+ g_clear_object (&old_processor);
+
if(1){
GeglRectangle rect = {o->u / o->scale, o->v / o->scale, mrg_width (o->mrg) / o->scale,
mrg_height (o->mrg) / o->scale};
@@ -826,28 +838,116 @@ static gboolean renderer_task (gpointer data)
}
}
o->renderer_state = 1;
+ if (hash)
+ g_free (hash);
+ hash = pos_hash (o);
+
+ g_clear_object (&cached_audio);
+ //if (o->frame_cache)
+ {
+ char path[1024];
+ sprintf (path, "/tmp/gegl/%s", hash);
+ if (g_file_test (path, G_FILE_TEST_EXISTS))
+ {
+ if (o->cached_buffer)
+ g_object_unref (o->cached_buffer);
+ o->cached_buffer = gegl_buffer_open (path); /* maybe load is faster? */
+ fprintf (stderr, "!");
+ o->renderer_state = 3;
+ }
+ sprintf (path, "/tmp/gegl/%s.pcm", hash);
+ if (g_file_test (path, G_FILE_TEST_EXISTS))
+ {
+ char *contents = NULL;
+ g_file_get_contents (path, &contents, NULL, NULL);
+ if (contents)
+ {
+ gchar *p;
+ GString *word = g_string_new ("");
+ int element_no = 0;
+ int channels = 2;
+ int max_samples = 2000;
+ cached_audio = gegl_audio_fragment_new (44100, 2, 0, 44100);
+ for (p = contents; p==contents || p[-1] != '\0'; p++)
+ {
+ switch (p[0])
+ {
+ case '\0':case ' ':
+ if (word->len > 0)
+ {
+ switch (element_no++)
+ {
+ case 0:
+ gegl_audio_fragment_set_sample_rate (cached_audio, g_strtod (word->str, NULL));
+ break;
+ case 1:
+ channels = g_strtod (word->str, NULL);
+ gegl_audio_fragment_set_channels (cached_audio, channels);
+ break;
+ case 2:
+ gegl_audio_fragment_set_channel_layout (cached_audio, g_strtod (word->str, NULL));
+ break;
+ case 3:
+ gegl_audio_fragment_set_sample_count (cached_audio, g_strtod (word->str, NULL));
+ break;
+ default:
+ {
+ int sample_no = element_no - 4;
+ int channel_no = sample_no % channels;
+ sample_no/=2;
+ if (sample_no < max_samples)
+ cached_audio->data[channel_no][sample_no] = g_strtod (word->str, NULL);
+ }
+ break;
+ }
+ }
+
+ g_string_assign (word, "");
+ break;
+ default:
+ g_string_append_c (word, p[0]);
+ break;
+ }
+ }
+ g_string_free (word, TRUE);
+
+
+ g_free (contents);
+ }
+ }
+
+ }
}
else
if (thumb_queue)
{
o->renderer_state = 4;
+ break;
}
else
g_usleep (4000);
- break; // fallthrough
+ //break; // fallthrough
case 1:
- if (gegl_processor_work (o->processor, &progress))
+ if (o->cached_buffer)
{
if (o->renderer_state)
- o->renderer_state = 1;
+ o->renderer_state = 3;
}
else
{
- if (o->renderer_state)
- o->renderer_state = 3;
+ if (gegl_processor_work (o->processor, &progress))
+ {
+ if (o->renderer_state)
+ o->renderer_state = 1;
+ }
+ else
+ {
+ if (o->renderer_state)
+ o->renderer_state = 3;
+ }
}
- break;
+ break;
case 3:
mrg_gegl_dirty ();
switch (renderer)
@@ -860,7 +960,12 @@ static gboolean renderer_task (gpointer data)
g_usleep (4000);
break;
}
- o->renderer_state = 0;
+
+
+ if ((o->frame_cache && !o->cached_buffer) || o->is_video )
+ o->renderer_state = 5;
+ else
+ o->renderer_state = 0;
break;
case 4:
@@ -908,7 +1013,103 @@ static gboolean renderer_task (gpointer data)
o->renderer_state = 0;
break;
+
+
+ case 5:
+ if (o->frame_cache && !o->cached_buffer) // store cached render of frame
+ {
+ char path[1024];
+ sprintf (path, "/tmp/gegl/%s", hash);
+ if (!g_file_test (path, G_FILE_TEST_EXISTS))
+ {
+ gegl_buffer_save (o->processor_buffer, path, NULL);
+ }
+ else
+ {
+ fprintf (stderr, "odd cache resave\n");
+ }
+
+
+
+ }
+ if (o->is_video)
+ {
+ GeglAudioFragment *audio = NULL;
+ if (cached_audio)
+ {
+ audio = cached_audio;
+ g_object_ref (cached_audio);
+ }
+ else
+ {
+ gegl_node_get (o->source, "audio", &audio, NULL);
+ }
+ if (audio)
+ {
+ int sample_count = gegl_audio_fragment_get_sample_count (audio);
+ if (sample_count > 0)
+ {
+ int i;
+ if (!audio_started)
+ {
+ open_audio (o->mrg, gegl_audio_fragment_get_sample_rate (audio));
+ audio_started = 1;
+ }
+ {
+ uint16_t temp_buf[sample_count * 2];
+ for (i = 0; i < sample_count; i++)
+ {
+ temp_buf[i*2] = audio->data[0][i] * 32767.0 * 0.46;
+ temp_buf[i*2+1] = audio->data[1][i] * 32767.0 * 0.46;
+ }
+ mrg_pcm_queue (o->mrg, (void*)&temp_buf[0], sample_count);
+
+ /* after queing our currently decoded audio frame, we
+ wait until the pcm buffer is nearly ready to play
+ back our content
+ */
+ while (mrg_pcm_get_queued_length (o->mrg) > (1.0/o->fps) * 1.25 )
+ g_usleep (100);
+ }
+
+ }
+ if (audio != cached_audio && o->frame_cache) {
+ int i, c;
+ GString *str = g_string_new ("");
+ int sample_count = gegl_audio_fragment_get_sample_count (audio);
+ int channels = gegl_audio_fragment_get_channels (audio);
+ char path[1024];
+ sprintf (path, "/tmp/gegl/%s.pcm", hash);
+
+ g_string_append_printf (str, "%i %i %i %i",
+ gegl_audio_fragment_get_sample_rate (audio),
+ gegl_audio_fragment_get_channels (audio),
+ gegl_audio_fragment_get_channel_layout (audio),
+ gegl_audio_fragment_get_sample_count (audio));
+
+ for (i = 0; i < sample_count; i++)
+ for (c = 0; c < channels; c++)
+ g_string_append_printf (str, " %0.5f", audio->data[c][i]);
+
+ g_file_set_contents (path, str->str, -1, NULL);
+ g_string_free (str, TRUE);
+
+ }
+
+ g_object_unref (audio);
+ }
+ }
+ o->renderer_state = 0;
+ break;
}
+
+ if (has_quit)
+ {
+ if (hash)
+ g_free (hash);
+ hash = NULL;
+ }
+
return TRUE;
}
@@ -917,7 +1118,6 @@ static gboolean renderer_idle (Mrg *mrg, gpointer data)
return renderer_task (data);
}
-static int has_quit = 0;
static gpointer renderer_thread (gpointer data)
{
while (!has_quit)
@@ -1086,25 +1286,28 @@ int mrg_ui_main (int argc, char **argv, char **ops)
return 0;
}
-int cmd_apos (COMMAND_ARGS); /* "apos", 1, "<>", "set the animation time, this is time relative to clip,
meaning 0.0 is first frame of clips timeline."*/
-int
-cmd_apos (COMMAND_ARGS)
+static void set_clip_position (GeState *o, double position)
{
- GeState *o = global_state;
- o->pos = g_strtod (argv[1], NULL);
+ position = ceilf(position * o->fps) / o->fps; // quantize position
+
+ o->pos = position;
gegl_node_set_time (o->sink, o->pos + o->start);
if (o->is_video)
{
- double fps = 0.0;
- gint frames = 0;
gint frame = 0;
- gegl_node_get (o->source, "frame-rate", &fps, "frames", &frames, NULL);
- frame = (o->pos + o->start) * fps;
+ frame = ceilf ((o->pos + o->start) * o->fps);
gegl_node_set (o->source, "frame", frame, NULL);
}
+}
+int cmd_apos (COMMAND_ARGS); /* "apos", 1, "<>", "set the animation time, this is time relative to clip,
meaning 0.0 is first frame of clips timeline."*/
+int
+cmd_apos (COMMAND_ARGS)
+{
+ GeState *o = global_state;
+ set_clip_position (o, g_strtod (argv[1], NULL));
return 0;
}
@@ -1738,7 +1941,7 @@ static int deferred_redraw_action (Mrg *mrg, void *data)
return 0;
}
-static void deferred_redraw (Mrg *mrg, MrgRectangle *rect)
+static inline void deferred_redraw (Mrg *mrg, MrgRectangle *rect)
{
MrgRectangle r; /* copy in call stack of dereference rectangle if pointer
is passed in */
@@ -4258,6 +4461,26 @@ static void do_commandline_run (MrgEvent *event, void *data1, void *data2)
mrg_event_stop_propagate (event);
}
+gchar *pos_hash (GeState *o)
+{
+ GChecksum *hash;
+ char *ret;
+ gchar *frame_recipe;
+ frame_recipe = gegl_serialize (NULL, o->sink, NULL, GEGL_SERIALIZE_BAKE_ANIM);
+ hash = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (hash, (void*)frame_recipe, -1);
+ g_checksum_update (hash, (void*)o->src_path, -1); /*
+ we add this in to make the identical source-buffer based recipies hash to different results
+ for now this hack doesn't matter since the frame_recipe is unused
+ would be better to rely only on hash of recipe and have recipe be complete thus using real gegl:load
+ */
+ ret = g_strdup (g_checksum_get_string(hash));
+ g_checksum_free (hash);
+ fprintf (stderr, "{%s}\n\n", frame_recipe);
+ g_free (frame_recipe);
+ return ret;
+}
+
static void iterate_frame (GeState *o)
{
Mrg *mrg = o->mrg;
@@ -4285,64 +4508,7 @@ static void iterate_frame (GeState *o)
}
mrg_queue_draw (o->mrg, NULL);
}
- else if (o->is_video)
- {
- int frames = 0;
- int frame_no;
- gegl_node_get (o->source, "frame", &frame_no, NULL);
- frame_no++;
- gegl_node_get (o->source, "frames", &frames, NULL);
- if (frame_no >= frames)
- frame_no = 0;
- gegl_node_set (o->source, "frame", frame_no, NULL);
- //queue_draw (o);
- mrg_queue_draw (o->mrg, NULL);
- {
- GeglAudioFragment *audio = NULL;
- gdouble fps;
- /* XXX:
- this currently goes wrong with threaded rendering, since we miss audio frames
- from the renderer thread, moving this to the render thread would solve that.
- */
- gegl_node_get (o->source, "audio", &audio, "frame-rate", &fps, NULL);
- if (audio)
- {
- int sample_count = gegl_audio_fragment_get_sample_count (audio);
- if (sample_count > 0)
- {
- int i;
- if (!audio_started)
- {
- open_audio (mrg, gegl_audio_fragment_get_sample_rate (audio));
- audio_started = 1;
- }
- {
- uint16_t temp_buf[sample_count * 2];
- for (i = 0; i < sample_count; i++)
- {
- temp_buf[i*2] = audio->data[0][i] * 32767.0 * 0.46;
- temp_buf[i*2+1] = audio->data[1][i] * 32767.0 * 0.46;
- }
-
- mrg_pcm_queue (mrg, (void*)&temp_buf[0], sample_count);
-
- /* after queing our currently decoded audio frame, we
- wait until the pcm buffer is nearly ready to play
- back our content
- */
- while (mrg_pcm_get_queued_length (mrg) > (1.0/fps) * 1.25 )
- g_usleep (100);
- }
-
-
- o->prev_frame_played = frame_no;
- deferred_redraw (mrg, NULL);
- }
- g_object_unref (audio);
- }
- }
- }
-
+ else
{
static uint32_t frame_accum = 0;
uint32_t ms = mrg_ms (mrg);
@@ -4352,13 +4518,10 @@ static void iterate_frame (GeState *o)
make realtime video playback more wrong, with buffering
that already is bad on clip change */
{
- o->pos += delta/1000.0;
-
-
- if (frame_accum > 1000 / 25) // 25fps
+ if (frame_accum > 1000 / o->fps)
{
- gegl_node_set_time (o->sink, o->pos + o->start);
- frame_accum = 0;
+ set_clip_position (o, o->pos + 1.0 / o->fps);
+ frame_accum = frame_accum-1000/o->fps;
}
frame_accum += delta;
}
@@ -4374,19 +4537,13 @@ static void iterate_frame (GeState *o)
}
else
{
- //fprintf (stderr, "%.3f/%.3f %.3f %f%%\n", o->pos, o->duration, o->end, 100.0*(o->pos/o->duration ));
}
-
-
- mrg_queue_draw (mrg, NULL);
-
prev_ms = ms;
- }
-
+ mrg_queue_draw (mrg, NULL);
+ }
}
-
static void ui_show_bindings (Mrg *mrg, void *data)
{
float em = mrg_em (mrg);
@@ -5007,7 +5164,7 @@ resolve_lua_file2 (const char *basepath, gboolean add_gegl, const char *basename
return NULL;
}
#endif
-#ifdef HAVE_LUA
+#ifdef HAVE_LUA
static char *
resolve_lua_file (const char *basename)
{
@@ -5167,7 +5324,12 @@ static void gegl_ui (Mrg *mrg, void *data)
case GEGL_RENDERER_IDLE:
if (o->processor_buffer)
{
- GeglBuffer *buffer = g_object_ref (o->processor_buffer);
+ GeglBuffer *buffer;
+
+ if (o->cached_buffer)
+ buffer = g_object_ref (o->cached_buffer);
+ else
+ buffer = g_object_ref (o->processor_buffer);
mrg_gegl_buffer_blit (mrg,
0, 0,
mrg_width (mrg), mrg_height (mrg),
@@ -5610,6 +5772,7 @@ static void load_path_inner (GeState *o,
if (o->dir_scale <= 0.001)
o->dir_scale = 1.0;
o->rev = 0;
+ o->fps = 40.0;
o->start = o->end = 0.0;
o->duration = -1;
@@ -5666,6 +5829,8 @@ static void load_path_inner (GeState *o,
}
else if (gegl_str_has_video_suffix (path))
{
+ gint frames = 0;
+ double fps = 0.0;
o->is_video = 1;
o->playing = 1;
o->gegl = gegl_node_new ();
@@ -5675,12 +5840,13 @@ static void load_path_inner (GeState *o,
"operation", "gegl:ff-load", "path", path, NULL);
gegl_node_link_many (o->source, o->sink, NULL);
+ gegl_node_process (o->source);
+
+ gegl_node_get (o->source, "frame-rate", &fps, "frames", &frames, NULL);
+ o->fps = fps;
+
if (o->duration < 0)
{
- double fps = 0.0;
- gint frames = 0;
- gegl_node_process (o->source);
- gegl_node_get (o->source, "frame-rate", &fps, "frames", &frames, NULL);
if (fps > 0.0 && frames > 0)
o->duration = frames / fps;
}
@@ -5688,17 +5854,12 @@ static void load_path_inner (GeState *o,
if (o->duration > 0)
{
- double fps = 0.0;
- gint frames = 0;
gint frame = 0;
- gegl_node_process (o->source);
- gegl_node_get (o->source, "frame-rate", &fps, "frames", &frames, NULL);
frame = o->start * fps;
gegl_node_set (o->source, "frame", frame, NULL);
}
-
}
else
{
@@ -6245,9 +6406,6 @@ static void load_into_buffer (GeState *o, const char *path)
gegl_node_process (sink);
g_object_unref (gegl);
-
-
-
{
GExiv2Orientation orientation = path_get_orientation (path);
#if 0
@@ -6838,6 +6996,10 @@ cmd_toggle (COMMAND_ARGS)
{
o->playing = !o->playing;
}
+ else if (!strcmp(argv[1], "loop-current"))
+ {
+ o->loop_current = !o->loop_current;
+ }
queue_draw (o);
return 0;
}
diff --git a/bin/ui.h b/bin/ui.h
index 89ac8284a..634ec2dde 100644
--- a/bin/ui.h
+++ b/bin/ui.h
@@ -97,6 +97,10 @@ struct _GeState {
GeglNode *processor_node; /* the node we have a processor for */
GeglProcessor *processor;
GeglBuffer *processor_buffer;
+
+ GeglBuffer *cached_buffer;
+ int frame_cache; /* turns on a violent caching regime caching all completed renders */
+
int renderer_state;
int editing_op_name;
char editing_buf[1024];
@@ -109,6 +113,7 @@ struct _GeState {
float u, v;
float scale;
+ float fps;
int is_fit;
int show_bounding_box;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]