diff options
Diffstat (limited to 'source/blender/windowmanager/intern/wm_playanim.c')
-rw-r--r-- | source/blender/windowmanager/intern/wm_playanim.c | 484 |
1 files changed, 370 insertions, 114 deletions
diff --git a/source/blender/windowmanager/intern/wm_playanim.c b/source/blender/windowmanager/intern/wm_playanim.c index 58030920fc7..5300649a0cd 100644 --- a/source/blender/windowmanager/intern/wm_playanim.c +++ b/source/blender/windowmanager/intern/wm_playanim.c @@ -47,9 +47,11 @@ #include "BLI_fileops.h" #include "BLI_listbase.h" #include "BLI_path_util.h" +#include "BLI_rect.h" #include "BLI_string.h" #include "BLI_utildefines.h" +#include "IMB_colormanagement.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" @@ -95,64 +97,85 @@ static AUD_Device *audio_device = NULL; struct PlayState; static void playanim_window_zoom(struct PlayState *ps, const float zoom_offset); +/** + * The current state of the player. + * + * \warning Don't store results of parsing command-line arguments + * in this struct if they need to persist across playing back different + * files as these will be cleared when playing other files (drag & drop). + */ typedef struct PlayState { - /* window and viewport size */ + /** Window and viewport size. */ int win_x, win_y; - /* current zoom level */ + /** Current zoom level. */ float zoom; - /* playback state */ + /** Playback direction (-1, 1). */ short direction; + /** Set the next frame to implement frame stepping (using shortcuts). */ short next_frame; + /** Playback once then wait. */ bool once; - bool turbo; + /** Play forwards/backwards. */ bool pingpong; + /** Disable frame skipping. */ bool noskip; + /** Display current frame over the window. */ bool indicator; + /** Single-frame stepping has been enabled (frame loading and update pending). */ bool sstep; + /** Playback has stopped the image has been displayed. */ bool wait2; + /** Playback stopped state once stop/start variables have been handled. */ bool stopped; + /** + * When disabled the current animation will exit, + * after this either the application exits or a new animation window is opened. + * + * This is used so drag & drop can load new files which setup a newly created animation window. + */ bool go; - /* waiting for images to load */ + /** True when waiting for images to load. */ bool loading; - /* x/y image flip */ + /** X/Y image flip (set via key bindings). */ bool draw_flip[2]; + /** The number of frames to step each update (default to 1, command line argument). */ int fstep; - /* current picture */ + /** Current frame (picture). */ struct PlayAnimPict *picture; - /* set once at the start */ + /** Image size in pixels, set once at the start. */ int ibufx, ibufy; + /** Mono-space font ID. */ int fontid; - /* saves passing args */ - struct ImBuf *curframe_ibuf; - - /* restarts player for file drop */ + /** Restarts player for file drop (drag & drop). */ char dropped_file[FILE_MAX]; + /** Force update when scrubbing with the cursor. */ bool need_frame_update; + /** The current frame calculated by scrubbing the mouse cursor. */ int frame_cursor_x; + + ColorManagedViewSettings view_settings; + ColorManagedDisplaySettings display_settings; } PlayState; /* for debugging */ #if 0 -void print_ps(PlayState *ps) +static void print_ps(PlayState *ps) { printf("ps:\n"); printf(" direction=%d,\n", (int)ps->direction); - printf(" next=%d,\n", ps->next); printf(" once=%d,\n", ps->once); - printf(" turbo=%d,\n", ps->turbo); printf(" pingpong=%d,\n", ps->pingpong); printf(" noskip=%d,\n", ps->noskip); printf(" sstep=%d,\n", ps->sstep); - printf(" pause=%d,\n", ps->pause); printf(" wait2=%d,\n", ps->wait2); printf(" stopped=%d,\n", ps->stopped); printf(" go=%d,\n\n", ps->go); @@ -237,6 +260,12 @@ typedef struct PlayAnimPict { struct anim *anim; int frame; int IB_flags; + +#ifdef USE_FRAME_CACHE_LIMIT + /** Back pointer to the #LinkData node for this struct in the #g_frame_cache.pics list. */ + LinkData *frame_cache_node; + size_t size_in_memory; +#endif } PlayAnimPict; static struct ListBase picsbase = {NULL, NULL}; @@ -248,9 +277,103 @@ static double fps_movie; #endif #ifdef USE_FRAME_CACHE_LIMIT -static struct ListBase inmempicsbase = {NULL, NULL}; -static int added_images = 0; -#endif +static struct { + /** A list of #LinkData nodes referencing #PlayAnimPict to track cached frames. */ + struct ListBase pics; + /** Number if elements in `pics`. */ + int pics_len; + /** Keep track of memory used by #g_frame_cache.pics when `g_frame_cache.memory_limit != 0`. */ + size_t pics_size_in_memory; + /** Optionally limit the amount of memory used for cache (in bytes), ignored when zero. */ + size_t memory_limit; +} g_frame_cache = { + .pics = {NULL, NULL}, + .pics_len = 0, + .pics_size_in_memory = 0, + .memory_limit = 0, +}; + +static void frame_cache_add(PlayAnimPict *pic) +{ + pic->frame_cache_node = BLI_genericNodeN(pic); + BLI_addhead(&g_frame_cache.pics, pic->frame_cache_node); + g_frame_cache.pics_len++; + + if (g_frame_cache.memory_limit != 0) { + BLI_assert(pic->size_in_memory == 0); + pic->size_in_memory = IMB_get_size_in_memory(pic->ibuf); + g_frame_cache.pics_size_in_memory += pic->size_in_memory; + } +} + +static void frame_cache_remove(PlayAnimPict *pic) +{ + LinkData *node = pic->frame_cache_node; + IMB_freeImBuf(pic->ibuf); + if (g_frame_cache.memory_limit != 0) { + BLI_assert(pic->size_in_memory != 0); + g_frame_cache.pics_size_in_memory -= pic->size_in_memory; + pic->size_in_memory = 0; + } + pic->ibuf = NULL; + pic->frame_cache_node = NULL; + BLI_freelinkN(&g_frame_cache.pics, node); + g_frame_cache.pics_len--; +} + +/* Don't free the current frame by moving it to the head of the list. */ +static void frame_cache_touch(PlayAnimPict *pic) +{ + BLI_assert(pic->frame_cache_node->data == pic); + BLI_remlink(&g_frame_cache.pics, pic->frame_cache_node); + BLI_addhead(&g_frame_cache.pics, pic->frame_cache_node); +} + +static bool frame_cache_limit_exceeded(void) +{ + return g_frame_cache.memory_limit ? + (g_frame_cache.pics_size_in_memory > g_frame_cache.memory_limit) : + (g_frame_cache.pics_len > PLAY_FRAME_CACHE_MAX); +} + +static void frame_cache_limit_apply(ImBuf *ibuf_keep) +{ + /* Really basic memory conservation scheme. Keep frames in a FIFO queue. */ + LinkData *node = g_frame_cache.pics.last; + while (node && frame_cache_limit_exceeded()) { + PlayAnimPict *pic = node->data; + BLI_assert(pic->frame_cache_node == node); + + node = node->prev; + if (pic->ibuf && pic->ibuf != ibuf_keep) { + frame_cache_remove(pic); + } + } +} + +#endif /* USE_FRAME_CACHE_LIMIT */ + +static ImBuf *ibuf_from_picture(PlayAnimPict *pic) +{ + ImBuf *ibuf = NULL; + + if (pic->ibuf) { + ibuf = pic->ibuf; + } + else if (pic->anim) { + ibuf = IMB_anim_absolute(pic->anim, pic->frame, IMB_TC_NONE, IMB_PROXY_NONE); + } + else if (pic->mem) { + /* use correct colorspace here */ + ibuf = IMB_ibImageFromMemory(pic->mem, pic->size, pic->IB_flags, NULL, pic->name); + } + else { + /* use correct colorspace here */ + ibuf = IMB_loadiffname(pic->name, pic->IB_flags, NULL); + } + + return ibuf; +} static PlayAnimPict *playanim_step(PlayAnimPict *playanim, int step) { @@ -278,6 +401,151 @@ static int pupdate_time(void) return (ptottime < 0); } +static void *ocio_transform_ibuf(PlayState *ps, + ImBuf *ibuf, + bool *r_glsl_used, + eGPUTextureFormat *r_format, + eGPUDataFormat *r_data, + void **r_buffer_cache_handle) +{ + void *display_buffer; + bool force_fallback = false; + *r_glsl_used = false; + force_fallback |= (ED_draw_imbuf_method(ibuf) != IMAGE_DRAW_METHOD_GLSL); + force_fallback |= (ibuf->dither != 0.0f); + + /* Default */ + *r_format = GPU_RGBA8; + *r_data = GPU_DATA_UBYTE; + + /* Fallback to CPU based color space conversion. */ + if (force_fallback) { + *r_glsl_used = false; + display_buffer = NULL; + } + else if (ibuf->rect_float) { + display_buffer = ibuf->rect_float; + + *r_data = GPU_DATA_FLOAT; + if (ibuf->channels == 4) { + *r_format = GPU_RGBA16F; + } + else if (ibuf->channels == 3) { + /* Alpha is implicitly 1. */ + *r_format = GPU_RGB16F; + } + + if (ibuf->float_colorspace) { + *r_glsl_used = IMB_colormanagement_setup_glsl_draw_from_space(&ps->view_settings, + &ps->display_settings, + ibuf->float_colorspace, + ibuf->dither, + false, + false); + } + else { + *r_glsl_used = IMB_colormanagement_setup_glsl_draw( + &ps->view_settings, &ps->display_settings, ibuf->dither, false); + } + } + else if (ibuf->rect) { + display_buffer = ibuf->rect; + *r_glsl_used = IMB_colormanagement_setup_glsl_draw_from_space(&ps->view_settings, + &ps->display_settings, + ibuf->rect_colorspace, + ibuf->dither, + false, + false); + } + else { + display_buffer = NULL; + } + + /* There is data to be displayed, but GLSL is not initialized + * properly, in this case we fallback to CPU-based display transform. */ + if ((ibuf->rect || ibuf->rect_float) && !*r_glsl_used) { + display_buffer = IMB_display_buffer_acquire( + ibuf, &ps->view_settings, &ps->display_settings, r_buffer_cache_handle); + *r_format = GPU_RGBA8; + *r_data = GPU_DATA_UBYTE; + } + + return display_buffer; +} + +static void draw_display_buffer(PlayState *ps, ImBuf *ibuf) +{ + void *display_buffer; + + /* Format needs to be created prior to any #immBindShader call. + * Do it here because OCIO binds its own shader. */ + eGPUTextureFormat format; + eGPUDataFormat data; + bool glsl_used = false; + GPUVertFormat *imm_format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(imm_format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + uint texCoord = GPU_vertformat_attr_add( + imm_format, "texCoord", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + + void *buffer_cache_handle = NULL; + display_buffer = ocio_transform_ibuf(ps, ibuf, &glsl_used, &format, &data, &buffer_cache_handle); + + GPUTexture *texture = GPU_texture_create_2d("display_buf", ibuf->x, ibuf->y, 1, format, NULL); + GPU_texture_update(texture, data, display_buffer); + GPU_texture_filter_mode(texture, false); + + GPU_texture_bind(texture, 0); + + if (!glsl_used) { + immBindBuiltinProgram(GPU_SHADER_2D_IMAGE_COLOR); + immUniformColor3f(1.0f, 1.0f, 1.0f); + immUniform1i("image", 0); + } + + immBegin(GPU_PRIM_TRI_FAN, 4); + + rctf preview; + rctf canvas; + + BLI_rctf_init(&canvas, 0.0f, 1.0f, 0.0f, 1.0f); + BLI_rctf_init(&preview, 0.0f, 1.0f, 0.0f, 1.0f); + + if (ps->draw_flip[0]) { + SWAP(float, canvas.xmin, canvas.xmax); + } + if (ps->draw_flip[1]) { + SWAP(float, canvas.ymin, canvas.ymax); + } + + immAttr2f(texCoord, canvas.xmin, canvas.ymin); + immVertex2f(pos, preview.xmin, preview.ymin); + + immAttr2f(texCoord, canvas.xmin, canvas.ymax); + immVertex2f(pos, preview.xmin, preview.ymax); + + immAttr2f(texCoord, canvas.xmax, canvas.ymax); + immVertex2f(pos, preview.xmax, preview.ymax); + + immAttr2f(texCoord, canvas.xmax, canvas.ymin); + immVertex2f(pos, preview.xmax, preview.ymin); + + immEnd(); + + GPU_texture_unbind(texture); + GPU_texture_free(texture); + + if (!glsl_used) { + immUnbindProgram(); + } + else { + IMB_colormanagement_finish_glsl_draw(); + } + + if (buffer_cache_handle) { + IMB_display_buffer_release(buffer_cache_handle); + } +} + static void playanim_toscreen( PlayState *ps, PlayAnimPict *picture, struct ImBuf *ibuf, int fontid, int fstep) { @@ -285,13 +553,6 @@ static void playanim_toscreen( printf("%s: no ibuf for picture '%s'\n", __func__, picture ? picture->name : "<NIL>"); return; } - if (ibuf->rect == NULL && ibuf->rect_float) { - IMB_rect_from_float(ibuf); - imb_freerectfloatImBuf(ibuf); - } - if (ibuf->rect == NULL) { - return; - } GHOST_ActivateWindowDrawingContext(g_WS.ghost_window); @@ -321,19 +582,7 @@ static void playanim_toscreen( 8); } - IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR); - - immDrawPixelsTex(&state, - offs_x + (ps->draw_flip[0] ? span_x : 0.0f), - offs_y + (ps->draw_flip[1] ? span_y : 0.0f), - ibuf->x, - ibuf->y, - GPU_RGBA8, - false, - ibuf->rect, - ((ps->draw_flip[0] ? -1.0f : 1.0f)) * (ps->zoom / (float)ps->win_x), - ((ps->draw_flip[1] ? -1.0f : 1.0f)) * (ps->zoom / (float)ps->win_y), - NULL); + draw_display_buffer(ps, ibuf); GPU_blend(GPU_BLEND_NONE); @@ -413,6 +662,14 @@ static void build_pict_list_ex( } } else { + /* Load images into cache until the cache is full, + * this resolves choppiness for images that are slow to load, see: T81751. */ +#ifdef USE_FRAME_CACHE_LIMIT + bool fill_cache = true; +#else + bool fill_cache = false; +#endif + int count = 0; int fp_framenr; @@ -441,7 +698,7 @@ static void build_pict_list_ex( */ while (IMB_ispic(filepath) && totframes) { - bool hasevent; + bool has_event; size_t size; int file; @@ -499,22 +756,33 @@ static void build_pict_list_ex( pupdate_time(); - if (ptottime > 1.0) { + const bool display_imbuf = ptottime > 1.0; + + if (display_imbuf || fill_cache) { /* OCIO_TODO: support different input color space */ - struct ImBuf *ibuf; - if (picture->mem) { - ibuf = IMB_ibImageFromMemory( - picture->mem, picture->size, picture->IB_flags, NULL, picture->name); - } - else { - ibuf = IMB_loadiffname(picture->name, picture->IB_flags, NULL); - } + ImBuf *ibuf = ibuf_from_picture(picture); + if (ibuf) { - playanim_toscreen(ps, picture, ibuf, fontid, fstep); - IMB_freeImBuf(ibuf); + if (display_imbuf) { + playanim_toscreen(ps, picture, ibuf, fontid, fstep); + } +#ifdef USE_FRAME_CACHE_LIMIT + if (fill_cache) { + picture->ibuf = ibuf; + frame_cache_add(picture); + fill_cache = !frame_cache_limit_exceeded(); + } + else +#endif + { + IMB_freeImBuf(ibuf); + } + } + + if (display_imbuf) { + pupdate_time(); + ptottime = 0.0; } - pupdate_time(); - ptottime = 0.0; } /* create a new filepath each time */ @@ -522,7 +790,7 @@ static void build_pict_list_ex( BLI_path_sequence_encode( filepath, fp_decoded.head, fp_decoded.tail, fp_decoded.digits, fp_framenr); - while ((hasevent = GHOST_ProcessEvents(g_WS.ghost_system, 0))) { + while ((has_event = GHOST_ProcessEvents(g_WS.ghost_system, false))) { GHOST_DispatchEvents(g_WS.ghost_system); if (ps->loading == false) { return; @@ -622,16 +890,14 @@ static void change_frame(PlayState *ps) static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) { PlayState *ps = (PlayState *)ps_void; - GHOST_TEventType type = GHOST_GetEventType(evt); - int val; + const GHOST_TEventType type = GHOST_GetEventType(evt); + /* Convert ghost event into value keyboard or mouse. */ + const int val = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventButtonDown); // print_ps(ps); playanim_event_qual_update(); - /* convert ghost event into value keyboard or mouse */ - val = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventButtonDown); - /* first check if we're busy loading files */ if (ps->loading) { switch (type) { @@ -655,8 +921,8 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) return 1; } - if (ps->wait2 && ps->stopped) { - ps->stopped = false; + if (ps->wait2 && ps->stopped == false) { + ps->stopped = true; } if (ps->wait2) { @@ -815,9 +1081,9 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) case GHOST_kKeyNumpadSlash: if (val) { if (g_WS.qual & WS_QUAL_SHIFT) { - if (ps->curframe_ibuf) { + if (ps->picture && ps->picture->ibuf) { printf(" Name: %s | Speed: %.2f frames/s\n", - ps->curframe_ibuf->name, + ps->picture->ibuf->name, ps->fstep / swaptime); } } @@ -1054,7 +1320,8 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) playanim_gl_matrix(); ptottime = 0.0; - playanim_toscreen(ps, ps->picture, ps->curframe_ibuf, ps->fontid, ps->fstep); + playanim_toscreen( + ps, ps->picture, ps->picture ? ps->picture->ibuf : NULL, ps->fontid, ps->fstep); break; } @@ -1131,7 +1398,9 @@ static void playanim_window_zoom(PlayState *ps, const float zoom_offset) GHOST_SetClientSize(g_WS.ghost_window, sizex, sizey); } -/* return path for restart */ +/** + * \return The a path used to restart the animation player or NULL to exit. + */ static char *wm_main_playanim_intern(int argc, const char **argv) { struct ImBuf *ibuf = NULL; @@ -1150,7 +1419,6 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.direction = true; ps.next_frame = 1; ps.once = false; - ps.turbo = false; ps.pingpong = false; ps.noskip = false; ps.sstep = false; @@ -1168,6 +1436,11 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.fontid = -1; + STRNCPY(ps.display_settings.display_device, + IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DEFAULT_BYTE)); + IMB_colormanagement_init_default_view_settings(&ps.view_settings, &ps.display_settings); + + /* Skip the first argument which is assumed to be '-a' (used to launch this player). */ while (argc > 1) { if (argv[1][0] == '-') { switch (argv[1][1]) { @@ -1222,6 +1495,15 @@ static char *wm_main_playanim_intern(int argc, const char **argv) argc--; argv++; break; + case 'c': { +#ifdef USE_FRAME_CACHE_LIMIT + const int memory_in_mb = max_ii(0, atoi(argv[2])); + g_frame_cache.memory_limit = (size_t)memory_in_mb * (1024 * 1024); +#endif + argc--; + argv++; + break; + } default: printf("unknown option '%c': skipping\n", argv[1][1]); break; @@ -1389,60 +1671,29 @@ static char *wm_main_playanim_intern(int argc, const char **argv) #endif while (ps.picture) { - int hasevent; + bool has_event; #ifndef USE_IMB_CACHE if (ibuf != NULL && ibuf->ftype == IMB_FTYPE_NONE) { IMB_freeImBuf(ibuf); } #endif - if (ps.picture->ibuf) { - ibuf = ps.picture->ibuf; - } - else if (ps.picture->anim) { - ibuf = IMB_anim_absolute(ps.picture->anim, ps.picture->frame, IMB_TC_NONE, IMB_PROXY_NONE); - } - else if (ps.picture->mem) { - /* use correct colorspace here */ - ibuf = IMB_ibImageFromMemory( - ps.picture->mem, ps.picture->size, ps.picture->IB_flags, NULL, ps.picture->name); - } - else { - /* use correct colorspace here */ - ibuf = IMB_loadiffname(ps.picture->name, ps.picture->IB_flags, NULL); - } - if (ibuf) { -#ifdef USE_FRAME_CACHE_LIMIT - LinkData *node; -#endif + ibuf = ibuf_from_picture(ps.picture); + if (ibuf) { #ifdef USE_IMB_CACHE ps.picture->ibuf = ibuf; #endif #ifdef USE_FRAME_CACHE_LIMIT - /* Really basic memory conservation scheme. Keep frames in a FIFO queue. */ - node = inmempicsbase.last; - - while (node && added_images > PLAY_FRAME_CACHE_MAX) { - PlayAnimPict *pic = node->data; - - if (pic->ibuf && pic->ibuf != ibuf) { - LinkData *node_tmp; - IMB_freeImBuf(pic->ibuf); - pic->ibuf = NULL; - node_tmp = node->prev; - BLI_freelinkN(&inmempicsbase, node); - added_images--; - node = node_tmp; - } - else { - node = node->prev; - } + if (ps.picture->frame_cache_node == NULL) { + frame_cache_add(ps.picture); + } + else { + frame_cache_touch(ps.picture); } + frame_cache_limit_apply(ibuf); - BLI_addhead(&inmempicsbase, BLI_genericNodeN(ps.picture)); - added_images++; #endif /* USE_FRAME_CACHE_LIMIT */ BLI_strncpy(ibuf->name, ps.picture->name, sizeof(ibuf->name)); @@ -1474,14 +1725,14 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.next_frame = ps.direction; - while ((hasevent = GHOST_ProcessEvents(g_WS.ghost_system, 0))) { + while ((has_event = GHOST_ProcessEvents(g_WS.ghost_system, false))) { GHOST_DispatchEvents(g_WS.ghost_system); } if (ps.go == false) { break; } change_frame(&ps); - if (!hasevent) { + if (!has_event) { PIL_sleep_ms(1); } if (ps.wait2) { @@ -1490,14 +1741,15 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.wait2 = ps.sstep; - if (ps.wait2 == false && ps.stopped == false) { - ps.stopped = true; + if (ps.wait2 == false && ps.stopped) { + ps.stopped = false; } pupdate_time(); if (ps.picture && ps.next_frame) { - /* always at least set one step */ + /* Advance to the next frame, always at least set one step. + * Implement frame-skipping when enabled and playback is not fast enough. */ while (ps.picture) { ps.picture = playanim_step(ps.picture, ps.next_frame); @@ -1510,7 +1762,7 @@ static char *wm_main_playanim_intern(int argc, const char **argv) } } - if (ps.wait2 || ptottime < swaptime || ps.turbo || ps.noskip) { + if (ps.wait2 || ptottime < swaptime || ps.noskip) { break; } ptottime -= swaptime; @@ -1550,8 +1802,12 @@ static char *wm_main_playanim_intern(int argc, const char **argv) #endif BLI_freelistN(&picsbase); - BLI_freelistN(&inmempicsbase); - added_images = 0; + +#ifdef USE_FRAME_CACHE_LIMIT + BLI_freelistN(&g_frame_cache.pics); + g_frame_cache.pics_len = 0; + g_frame_cache.pics_size_in_memory = 0; +#endif #ifdef WITH_AUDASPACE if (playback_handle) { |