Blender V4.5
wm_playanim.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
14
15#include <algorithm>
16#include <cerrno>
17#include <cstdlib>
18#include <cstring>
19#include <fcntl.h>
20#include <sys/types.h>
21
22#ifndef WIN32
23# include <sys/times.h>
24# include <sys/wait.h>
25# include <unistd.h>
26#else
27# include <io.h>
28#endif
29#include "MEM_guardedalloc.h"
30
31#include "CLG_log.h"
32
33#include "BLI_fileops.h"
34#include "BLI_listbase.h"
36#include "BLI_path_utils.hh"
37#include "BLI_rect.h"
38#include "BLI_string.h"
39#include "BLI_system.h"
40#include "BLI_time.h"
41#include "BLI_utildefines.h"
42
44#include "IMB_imbuf.hh"
45#include "IMB_imbuf_types.hh"
46
47#include "MOV_read.hh"
48#include "MOV_util.hh"
49
50#include "BKE_image.hh"
51
52#include "BIF_glutil.hh"
53
54#include "GPU_context.hh"
55#include "GPU_framebuffer.hh"
56#include "GPU_immediate.hh"
57#include "GPU_immediate_util.hh"
58#include "GPU_init_exit.hh"
59#include "GPU_matrix.hh"
60#include "GPU_state.hh"
61
62#include "DNA_scene_types.h"
63#include "DNA_userdef_types.h"
64
65#include "BLF_api.hh"
66#include "GHOST_C-api.h"
67
68#include "DEG_depsgraph.hh"
69
70#include "wm_window_private.hh"
71
72#include "WM_api.hh" /* Only for #WM_main_playanim. */
73
74#ifdef WITH_AUDASPACE
75# include <AUD_Device.h>
76# include <AUD_Handle.h>
77# include <AUD_Sound.h>
78# include <AUD_Special.h>
79
80static struct {
81 AUD_Sound *source;
82 AUD_Handle *playback_handle;
83 AUD_Handle *scrub_handle;
84 AUD_Device *audio_device;
85} g_audaspace = {nullptr};
86#endif
87
88/* Simple limiter to avoid flooding memory. */
89#define USE_FRAME_CACHE_LIMIT
90#ifdef USE_FRAME_CACHE_LIMIT
91# define PLAY_FRAME_CACHE_MAX 30
92#endif
93
94static CLG_LogRef LOG = {"wm.playanim"};
95
96struct PlayState;
97static void playanim_window_zoom(PlayState &ps, const float zoom_offset);
99
100/* -------------------------------------------------------------------- */
103
109static bool buffer_from_filepath(const char *filepath,
110 void **r_mem,
111 size_t *r_size,
112 char **r_error_message)
113{
114 errno = 0;
115 const int file = BLI_open(filepath, O_BINARY | O_RDONLY, 0);
116 if (UNLIKELY(file == -1)) {
117 *r_error_message = BLI_sprintfN("failure '%s' to open file", strerror(errno));
118 return false;
119 }
120
121 bool success = false;
122 uchar *mem = nullptr;
123 const size_t size = BLI_file_descriptor_size(file);
124 int64_t size_read;
125 if (UNLIKELY(size == size_t(-1))) {
126 *r_error_message = BLI_sprintfN("failure '%s' to access size", strerror(errno));
127 }
128 else if (r_mem && UNLIKELY(!(mem = MEM_malloc_arrayN<uchar>(size, __func__)))) {
129 *r_error_message = BLI_sprintfN("error allocating buffer %" PRIu64 " size", uint64_t(size));
130 }
131 else if (r_mem && UNLIKELY((size_read = BLI_read(file, mem, size)) != size)) {
132 *r_error_message = BLI_sprintfN(
133 "error '%s' reading file "
134 "(expected %" PRIu64 ", was %" PRId64 ")",
135 strerror(errno),
136 uint64_t(size),
137 size_read);
138 }
139 else {
140 *r_size = size;
141 if (r_mem) {
142 *r_mem = mem;
143 mem = nullptr; /* `r_mem` owns, don't free on exit. */
144 }
145 success = true;
146 }
147
148 MEM_SAFE_FREE(mem);
149 close(file);
150 return success;
151}
152
154
157 WS_QUAL_LSHIFT = (1 << 0),
158 WS_QUAL_RSHIFT = (1 << 1),
159#define WS_QUAL_SHIFT (WS_QUAL_LSHIFT | WS_QUAL_RSHIFT)
160 WS_QUAL_LALT = (1 << 2),
161 WS_QUAL_RALT = (1 << 3),
162#define WS_QUAL_ALT (WS_QUAL_LALT | WS_QUAL_RALT)
163 WS_QUAL_LCTRL = (1 << 4),
164 WS_QUAL_RCTRL = (1 << 5),
165#define WS_QUAL_CTRL (WS_QUAL_LCTRL | WS_QUAL_RCTRL)
166 WS_QUAL_LMOUSE = (1 << 16),
167 WS_QUAL_MMOUSE = (1 << 17),
168 WS_QUAL_RMOUSE = (1 << 18),
170};
172
173struct GhostData {
174 GHOST_SystemHandle system;
175 GHOST_WindowHandle window;
176
178 GPUContext *gpu_context;
179
182};
183
184struct PlayArgs {
185 int argc;
186 char **argv;
187};
188
201
273
274/* For debugging. */
275#if 0
276static void print_ps(const PlayState &ps)
277{
278 printf("ps:\n");
279 printf(" direction=%d,\n", int(ps.direction));
280 printf(" once=%d,\n", ps.once);
281 printf(" pingpong=%d,\n", ps.pingpong);
282 printf(" no_frame_skip=%d,\n", ps.no_frame_skip);
283 printf(" single_step=%d,\n", ps.single_step);
284 printf(" wait=%d,\n", ps.wait);
285 printf(" stopped=%d,\n", ps.stopped);
286 printf(" go=%d,\n\n", ps.go);
287 fflush(stdout);
288}
289#endif
290
291static blender::int2 playanim_window_size_get(GHOST_WindowHandle ghost_window)
292{
293 ;
294 GHOST_RectangleHandle bounds = GHOST_GetClientBounds(ghost_window);
295 const float native_pixel_size = GHOST_GetNativePixelSize(ghost_window);
296 const blender::int2 window_size = {
297 int(GHOST_GetWidthRectangle(bounds) * native_pixel_size),
298 int(GHOST_GetHeightRectangle(bounds) * native_pixel_size),
299 };
301 return window_size;
302}
303
305{
306 /* Unified matrix, note it affects offset for drawing. */
307 /* NOTE: cannot use GPU_matrix_ortho_2d_set here because shader ignores. */
308 GPU_matrix_ortho_set(0.0f, 1.0f, 0.0f, 1.0f, -1.0, 1.0f);
309}
310
311/* Implementation. */
312static void playanim_event_qual_update(GhostData &ghost_data)
313{
314 bool val;
315
316 /* Shift. */
318 SET_FLAG_FROM_TEST(ghost_data.qual, val, WS_QUAL_LSHIFT);
319
321 SET_FLAG_FROM_TEST(ghost_data.qual, val, WS_QUAL_RSHIFT);
322
323 /* Control. */
325 SET_FLAG_FROM_TEST(ghost_data.qual, val, WS_QUAL_LCTRL);
326
328 SET_FLAG_FROM_TEST(ghost_data.qual, val, WS_QUAL_RCTRL);
329
330 /* Alt. */
332 SET_FLAG_FROM_TEST(ghost_data.qual, val, WS_QUAL_LALT);
333
335 SET_FLAG_FROM_TEST(ghost_data.qual, val, WS_QUAL_RALT);
336}
337
341 size_t size;
343 const char *filepath;
348 int frame;
350
351#ifdef USE_FRAME_CACHE_LIMIT
355#endif
356};
357
363static struct {
365 double swap_time;
367#ifdef WITH_AUDASPACE
368 double fps_movie;
369#endif
370} g_playanim = {
371 /*from_disk*/ false,
372 /*swap_time*/ 0.04,
373 /*total_time*/ 0.0,
374#ifdef WITH_AUDASPACE
375 /*fps_movie*/ 0.0,
376#endif
378
379#ifdef USE_FRAME_CACHE_LIMIT
380static struct {
389} g_frame_cache = {
390 /*pics*/ {nullptr, nullptr},
391 /*pics_len*/ 0,
392 /*pics_size_in_memory*/ 0,
393 /*memory_limit*/ 0,
395
397{
400 g_frame_cache.pics_len++;
401
402 if (g_frame_cache.memory_limit != 0) {
403 BLI_assert(pic->size_in_memory == 0);
405 g_frame_cache.pics_size_in_memory += pic->size_in_memory;
406 }
407}
408
410{
411 LinkData *node = pic->frame_cache_node;
412 IMB_freeImBuf(pic->ibuf);
413 if (g_frame_cache.memory_limit != 0) {
414 BLI_assert(pic->size_in_memory != 0);
415 g_frame_cache.pics_size_in_memory -= pic->size_in_memory;
416 pic->size_in_memory = 0;
417 }
418 pic->ibuf = nullptr;
419 pic->frame_cache_node = nullptr;
420 BLI_freelinkN(&g_frame_cache.pics, node);
421 g_frame_cache.pics_len--;
422}
423
424/* Don't free the current frame by moving it to the head of the list. */
431
433{
434 return g_frame_cache.memory_limit ?
435 (g_frame_cache.pics_size_in_memory > g_frame_cache.memory_limit) :
437}
438
439static void frame_cache_limit_apply(ImBuf *ibuf_keep)
440{
441 /* Really basic memory conservation scheme. Keep frames in a FIFO queue. */
442 LinkData *node = static_cast<LinkData *>(g_frame_cache.pics.last);
443 while (node && frame_cache_limit_exceeded()) {
444 PlayAnimPict *pic = static_cast<PlayAnimPict *>(node->data);
445 BLI_assert(pic->frame_cache_node == node);
446
447 node = node->prev;
448 if (pic->ibuf && pic->ibuf != ibuf_keep) {
450 }
451 }
452}
453
454#endif /* USE_FRAME_CACHE_LIMIT */
455
457{
458 ImBuf *ibuf = nullptr;
459
460 if (pic->ibuf) {
461 ibuf = pic->ibuf;
462 }
463 else if (pic->anim) {
465 }
466 else if (pic->mem) {
467 /* Use correct color-space here. */
469 pic->mem, pic->size, pic->IB_flags, pic->filepath, pic->filepath);
470 }
471 else {
472 /* Use correct color-space here. */
474 }
475
476 return ibuf;
477}
478
480{
481 if (step > 0) {
482 while (step-- && playanim) {
483 playanim = playanim->next;
484 }
485 }
486 else if (step < 0) {
487 while (step++ && playanim) {
488 playanim = playanim->prev;
489 }
490 }
491 return playanim;
492}
493
494static int pupdate_time()
495{
496 static double time_last;
497
498 double time = BLI_time_now_seconds();
499
500 g_playanim.total_time += (time - time_last);
501 time_last = time;
502 return (g_playanim.total_time < 0.0);
503}
504
505static void *ocio_transform_ibuf(const PlayDisplayContext &display_ctx,
506 ImBuf *ibuf,
507 bool *r_glsl_used,
508 eGPUTextureFormat *r_format,
509 eGPUDataFormat *r_data,
510 void **r_buffer_cache_handle)
511{
512 void *display_buffer;
513 bool force_fallback = false;
514 *r_glsl_used = false;
515 force_fallback |= (ED_draw_imbuf_method(ibuf) != IMAGE_DRAW_METHOD_GLSL);
516 force_fallback |= (ibuf->dither != 0.0f);
517
518 /* Default. */
519 *r_format = GPU_RGBA8;
520 *r_data = GPU_DATA_UBYTE;
521
522 /* Fallback to CPU based color space conversion. */
523 if (force_fallback) {
524 *r_glsl_used = false;
525 display_buffer = nullptr;
526 }
527 else if (ibuf->float_buffer.data) {
528 display_buffer = ibuf->float_buffer.data;
529
530 *r_data = GPU_DATA_FLOAT;
531 if (ibuf->channels == 4) {
532 *r_format = GPU_RGBA16F;
533 }
534 else if (ibuf->channels == 3) {
535 /* Alpha is implicitly 1. */
536 *r_format = GPU_RGB16F;
537 }
538
539 if (ibuf->float_buffer.colorspace) {
541 &display_ctx.display_settings,
543 ibuf->dither,
544 false,
545 false);
546 }
547 else {
549 &display_ctx.view_settings, &display_ctx.display_settings, ibuf->dither, false);
550 }
551 }
552 else if (ibuf->byte_buffer.data) {
553 display_buffer = ibuf->byte_buffer.data;
555 &display_ctx.display_settings,
557 ibuf->dither,
558 false,
559 false);
560 }
561 else {
562 display_buffer = nullptr;
563 }
564
565 /* There is data to be displayed, but GLSL is not initialized
566 * properly, in this case we fallback to CPU-based display transform. */
567 if ((ibuf->byte_buffer.data || ibuf->float_buffer.data) && !*r_glsl_used) {
568 display_buffer = IMB_display_buffer_acquire(
569 ibuf, &display_ctx.view_settings, &display_ctx.display_settings, r_buffer_cache_handle);
570 *r_format = GPU_RGBA8;
571 *r_data = GPU_DATA_UBYTE;
572 }
573
574 return display_buffer;
575}
576
577static void draw_display_buffer(const PlayDisplayContext &display_ctx,
578 ImBuf *ibuf,
579 const rctf *canvas,
580 const bool draw_flip[2])
581{
582 /* Format needs to be created prior to any #immBindShader call.
583 * Do it here because OCIO binds its own shader. */
586 bool glsl_used = false;
587 GPUVertFormat *imm_format = immVertexFormat();
589 uint texCoord = GPU_vertformat_attr_add(
590 imm_format, "texCoord", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
591
592 void *buffer_cache_handle = nullptr;
593 void *display_buffer = ocio_transform_ibuf(
594 display_ctx, ibuf, &glsl_used, &format, &data, &buffer_cache_handle);
595
596 /* NOTE: This may fail, especially for large images that exceed the GPU's texture size limit.
597 * Large images could be supported although this isn't so common for animation playback. */
598 GPUTexture *texture = GPU_texture_create_2d(
599 "display_buf", ibuf->x, ibuf->y, 1, format, GPU_TEXTURE_USAGE_SHADER_READ, nullptr);
600
601 if (texture) {
602 GPU_texture_update(texture, data, display_buffer);
604
606 }
607
608 if (!glsl_used) {
610 immUniformColor3f(1.0f, 1.0f, 1.0f);
611 }
612
614
615 rctf preview;
616 BLI_rctf_init(&preview, 0.0f, 1.0f, 0.0f, 1.0f);
617 if (draw_flip[0]) {
618 std::swap(preview.xmin, preview.xmax);
619 }
620 if (draw_flip[1]) {
621 std::swap(preview.ymin, preview.ymax);
622 }
623
624 immAttr2f(texCoord, preview.xmin, preview.ymin);
625 immVertex2f(pos, canvas->xmin, canvas->ymin);
626
627 immAttr2f(texCoord, preview.xmin, preview.ymax);
628 immVertex2f(pos, canvas->xmin, canvas->ymax);
629
630 immAttr2f(texCoord, preview.xmax, preview.ymax);
631 immVertex2f(pos, canvas->xmax, canvas->ymax);
632
633 immAttr2f(texCoord, preview.xmax, preview.ymin);
634 immVertex2f(pos, canvas->xmax, canvas->ymin);
635
636 immEnd();
637
638 if (texture) {
641 }
642
643 if (!glsl_used) {
645 }
646 else {
648 }
649
650 if (buffer_cache_handle) {
651 IMB_display_buffer_release(buffer_cache_handle);
652 }
653}
654
662static void playanim_toscreen_ex(GhostData &ghost_data,
663 const PlayDisplayContext &display_ctx,
664 const PlayAnimPict *picture,
665 ImBuf *ibuf,
666 /* Run-time drawing arguments (not used on-load). */
667 const int font_id,
668 const int frame_step,
669 const float draw_zoom,
670 const bool draw_flip[2],
671 const float frame_indicator_factor)
672{
675
676 GPUContext *restore_context = GPU_context_active_get();
679
680 GPU_clear_color(0.1f, 0.1f, 0.1f, 0.0f);
681
682 /* A null `ibuf` is an exceptional case and should almost never happen.
683 * if it does, this function displays a warning along with the file-path that failed. */
684 if (ibuf) {
685 /* Size within window. */
686 float span_x = (draw_zoom * ibuf->x) / float(display_ctx.size[0]);
687 float span_y = (draw_zoom * ibuf->y) / float(display_ctx.size[1]);
688
689 /* Offset within window. */
690 float offs_x = 0.5f * (1.0f - span_x);
691 float offs_y = 0.5f * (1.0f - span_y);
692
693 CLAMP(offs_x, 0.0f, 1.0f);
694 CLAMP(offs_y, 0.0f, 1.0f);
695
696 /* Checkerboard for case alpha. */
697 if (ibuf->planes == 32) {
699
701 offs_y,
702 offs_x + span_x,
703 offs_y + span_y,
704 blender::float4{0.15, 0.15, 0.15, 1.0},
705 blender::float4{0.20, 0.20, 0.20, 1.0},
706 8);
707 }
708 rctf canvas;
709 BLI_rctf_init(&canvas, offs_x, offs_x + span_x, offs_y, offs_y + span_y);
710
711 draw_display_buffer(display_ctx, ibuf, &canvas, draw_flip);
712
714 }
715
716 pupdate_time();
717
718 if ((font_id != -1) && picture) {
719 const int font_margin = int(10 * display_ctx.ui_scale);
720 float fsizex_inv, fsizey_inv;
721 char label[32 + FILE_MAX];
722 if (ibuf) {
723 SNPRINTF(label, "%s | %.2f frames/s", picture->filepath, frame_step / g_playanim.swap_time);
724 }
725 else {
726 SNPRINTF(label,
727 "%s | %s",
728 picture->filepath,
729 picture->error_message ? picture->error_message : "<unknown error>");
730 }
731
732 const blender::int2 window_size = playanim_window_size_get(ghost_data.window);
733 fsizex_inv = 1.0f / window_size[0];
734 fsizey_inv = 1.0f / window_size[1];
735
736 BLF_color4f(font_id, 1.0, 1.0, 1.0, 1.0);
737
738 /* FIXME(@ideasman42): Font positioning doesn't work because the aspect causes the position
739 * to be rounded to zero, investigate making BLF support this,
740 * for now use GPU matrix API to adjust the text position. */
741#if 0
742 BLF_enable(font_id, BLF_ASPECT);
743 BLF_aspect(font_id, fsizex_inv, fsizey_inv, 1.0f);
744 BLF_position(font_id, font_margin * fsizex_inv, font_margin * fsizey_inv, 0.0f);
745 BLF_draw(font_id, label, sizeof(label));
746#else
748 GPU_matrix_scale_2f(fsizex_inv, fsizey_inv);
749 GPU_matrix_translate_2f(font_margin, font_margin);
750 BLF_position(font_id, 0, 0, 0.0f);
751 BLF_draw(font_id, label, sizeof(label));
753#endif
754 }
755
756 if (frame_indicator_factor != -1.0f) {
757 float fac = frame_indicator_factor;
758 fac = 2.0f * fac - 1.0f;
763
765
767 immUniformColor3ub(0, 255, 0);
768
770 immVertex2f(pos, fac, -1.0f);
771 immVertex2f(pos, fac, 1.0f);
772 immEnd();
773
775
778 }
779
782 GPU_flush();
783 }
784
787 GPU_context_active_set(restore_context);
789}
790
791static void playanim_toscreen_on_load(GhostData &ghost_data,
792 const PlayDisplayContext &display_ctx,
793 const PlayAnimPict *picture,
794 ImBuf *ibuf)
795{
796 const int font_id = -1; /* Don't draw text. */
797 const int frame_step = -1;
798 const float zoom = 1.0f;
799 const float frame_indicator_factor = -1.0f;
800 const bool draw_flip[2] = {false, false};
801
802 playanim_toscreen_ex(ghost_data,
803 display_ctx,
804 picture,
805 ibuf,
806 font_id,
807 frame_step,
808 zoom,
809 draw_flip,
810 frame_indicator_factor);
811}
812
813static void playanim_toscreen(PlayState &ps, const PlayAnimPict *picture, ImBuf *ibuf)
814{
815 float frame_indicator_factor = -1.0f;
816 if (ps.show_frame_indicator) {
817 const int frame_range = static_cast<const PlayAnimPict *>(ps.picsbase.last)->frame -
818 static_cast<const PlayAnimPict *>(ps.picsbase.first)->frame;
819 if (frame_range > 0) {
820 frame_indicator_factor = float(double(picture->frame) / double(frame_range));
821 }
822 else {
824 "Multiple frames without a valid range!");
825 }
826 }
827
828 int font_id = -1;
830 /* Always inform the user of an error, this should be an exceptional case. */
831 (ibuf == nullptr))
832 {
833 font_id = ps.font_id;
834 }
835
836 BLI_assert(ps.loading == false);
838 ps.display_ctx,
839 picture,
840 ibuf,
841 font_id,
842 ps.frame_step,
843 ps.zoom,
844 ps.draw_flip,
845 frame_indicator_factor);
846}
847
849 GhostData &ghost_data,
850 const PlayDisplayContext &display_ctx,
851 const char *filepath_first,
852 const int frame_offset)
853{
854 /* OCIO_TODO: support different input color space. */
855 MovieReader *anim = MOV_open_file(filepath_first, IB_byte_data, 0, nullptr);
856 if (anim == nullptr) {
857 CLOG_WARN(&LOG, "couldn't open anim '%s'", filepath_first);
858 return;
859 }
860
862 if (ibuf) {
863 playanim_toscreen_on_load(ghost_data, display_ctx, nullptr, ibuf);
864 IMB_freeImBuf(ibuf);
865 }
866
867 for (int pic = 0; pic < MOV_get_duration_frames(anim, IMB_TC_NONE); pic++) {
868 PlayAnimPict *picture = MEM_callocN<PlayAnimPict>("Pict");
869 picture->anim = anim;
870 picture->frame = pic + frame_offset;
871 picture->IB_flags = IB_byte_data;
872 picture->filepath = BLI_sprintfN("%s : %4.d", filepath_first, pic + 1);
873 BLI_addtail(&picsbase, picture);
874 }
875
876 const PlayAnimPict *picture = static_cast<const PlayAnimPict *>(picsbase.last);
877 if (!(picture && picture->anim == anim)) {
878 MOV_close(anim);
879 CLOG_WARN(&LOG, "no frames added for: '%s'", filepath_first);
880 }
881}
882
884 GhostData &ghost_data,
885 const PlayDisplayContext &display_ctx,
886 const char *filepath_first,
887 const int frame_offset,
888 const int totframes,
889 const int frame_step,
890 const bool *loading_p)
891{
892 /* Load images into cache until the cache is full,
893 * this resolves choppiness for images that are slow to load, see: #81751. */
894 bool fill_cache = (
895#ifdef USE_FRAME_CACHE_LIMIT
896 true
897#else
898 false
899#endif
900 );
901
902 int fp_framenr;
903 struct {
904 char head[FILE_MAX], tail[FILE_MAX];
905 ushort digits;
906 } fp_decoded;
907
908 char filepath[FILE_MAX];
909 STRNCPY(filepath, filepath_first);
910 fp_framenr = BLI_path_sequence_decode(filepath,
911 fp_decoded.head,
912 sizeof(fp_decoded.head),
913 fp_decoded.tail,
914 sizeof(fp_decoded.tail),
915 &fp_decoded.digits);
916
917 pupdate_time();
918 g_playanim.total_time = 1.0;
919
920 for (int pic = 0; pic < totframes; pic++) {
921 if (!IMB_test_image(filepath)) {
922 break;
923 }
924
925 bool has_error = false;
926 char *error_message = nullptr;
927 void *mem = nullptr;
928 size_t size = -1;
930 filepath, g_playanim.from_disk ? nullptr : &mem, &size, &error_message))
931 {
932 has_error = true;
933 size = 0;
934 }
935
936 PlayAnimPict *picture = MEM_callocN<PlayAnimPict>("picture");
937 picture->size = size;
938 picture->IB_flags = IB_byte_data;
939 picture->mem = static_cast<uchar *>(mem);
940 picture->filepath = BLI_strdup(filepath);
941 picture->error_message = error_message;
942 picture->frame = pic + frame_offset;
943 BLI_addtail(&picsbase, picture);
944
945 pupdate_time();
946
947 const bool display_imbuf = g_playanim.total_time > 1.0;
948
949 if (has_error) {
950 CLOG_WARN(&LOG,
951 "Picture %s failed: %s",
952 filepath,
953 error_message ? error_message : "<unknown error>");
954 }
955 else if (display_imbuf || fill_cache) {
956 /* OCIO_TODO: support different input color space. */
957 ImBuf *ibuf = ibuf_from_picture(picture);
958
959 if (ibuf) {
960 if (display_imbuf) {
961 playanim_toscreen_on_load(ghost_data, display_ctx, picture, ibuf);
962 }
963#ifdef USE_FRAME_CACHE_LIMIT
964 if (fill_cache) {
965 picture->ibuf = ibuf;
966 frame_cache_add(picture);
967 fill_cache = !frame_cache_limit_exceeded();
968 }
969 else
970#endif
971 {
972 IMB_freeImBuf(ibuf);
973 }
974 }
975
976 if (display_imbuf) {
977 pupdate_time();
978 g_playanim.total_time = 0.0;
979 }
980 }
981
982 /* Create a new file-path each time. */
983 fp_framenr += frame_step;
985 sizeof(filepath),
986 fp_decoded.head,
987 fp_decoded.tail,
988 fp_decoded.digits,
989 fp_framenr);
990
991 while (GHOST_ProcessEvents(ghost_data.system, false)) {
992 GHOST_DispatchEvents(ghost_data.system);
993 if (*loading_p == false) {
994 break;
995 }
996 }
997 }
998}
999
1000static void build_pict_list(ListBase &picsbase,
1001 GhostData &ghost_data,
1002 const PlayDisplayContext &display_ctx,
1003 const char *filepath_first,
1004 const int totframes,
1005 const int frame_step,
1006 bool *loading_p)
1007{
1008 *loading_p = true;
1009
1010 /* NOTE(@ideasman42): When loading many files (e.g. expanded from shell globing)
1011 * it's important the frame number increases each time. Otherwise playing `*.png`
1012 * in a directory will expand into many arguments, each calling this function adding
1013 * a frame that's set to zero. */
1014 const PlayAnimPict *picture_last = static_cast<PlayAnimPict *>(picsbase.last);
1015 const int frame_offset = picture_last ? (picture_last->frame + 1) : 0;
1016
1017 bool do_image_load = false;
1018 if (MOV_is_movie_file(filepath_first)) {
1019 build_pict_list_from_anim(picsbase, ghost_data, display_ctx, filepath_first, frame_offset);
1020
1021 if (picsbase.last == picture_last) {
1022 /* FFMPEG detected JPEG2000 as a video which would load with zero duration.
1023 * Resolve this by using images as a fallback when a video file has no frames to display. */
1024 do_image_load = true;
1025 }
1026 }
1027 else {
1028 do_image_load = true;
1029 }
1030
1031 if (do_image_load) {
1033 ghost_data,
1034 display_ctx,
1035 filepath_first,
1036 frame_offset,
1037 totframes,
1038 frame_step,
1039 loading_p);
1040 }
1041
1042 *loading_p = false;
1043}
1044
1045static void update_sound_fps()
1046{
1047#ifdef WITH_AUDASPACE
1048 if (g_audaspace.playback_handle) {
1049 /* Swap-time stores the 1.0/fps ratio. */
1050 double speed = 1.0 / (g_playanim.swap_time * g_playanim.fps_movie);
1051
1052 AUD_Handle_setPitch(g_audaspace.playback_handle, speed);
1053 }
1054#endif
1055}
1056
1057static void playanim_change_frame_tag(PlayState &ps, int cx)
1058{
1059 ps.need_frame_update = true;
1060 ps.frame_cursor_x = cx;
1061}
1062
1064{
1065 if (!ps.need_frame_update) {
1066 return;
1067 }
1069 return;
1070 }
1071
1073 const int i_last = static_cast<PlayAnimPict *>(ps.picsbase.last)->frame;
1074 /* Without this the frame-indicator location isn't closest to the cursor. */
1075 const int correct_rounding = (window_size[0] / (i_last + 1)) / 2;
1076 const int i = clamp_i(
1077 (i_last * (ps.frame_cursor_x + correct_rounding)) / window_size[0], 0, i_last);
1078
1079#ifdef WITH_AUDASPACE
1080 if (g_audaspace.scrub_handle) {
1081 AUD_Handle_stop(g_audaspace.scrub_handle);
1082 g_audaspace.scrub_handle = nullptr;
1083 }
1084
1085 if (g_audaspace.playback_handle) {
1086 AUD_Status status = AUD_Handle_getStatus(g_audaspace.playback_handle);
1087 if (status != AUD_STATUS_PLAYING) {
1088 AUD_Handle_stop(g_audaspace.playback_handle);
1089 g_audaspace.playback_handle = AUD_Device_play(
1090 g_audaspace.audio_device, g_audaspace.source, 1);
1091 if (g_audaspace.playback_handle) {
1092 AUD_Handle_setPosition(g_audaspace.playback_handle, i / g_playanim.fps_movie);
1093 g_audaspace.scrub_handle = AUD_pauseAfter(g_audaspace.playback_handle,
1094 1.0 / g_playanim.fps_movie);
1095 }
1097 }
1098 else {
1099 AUD_Handle_setPosition(g_audaspace.playback_handle, i / g_playanim.fps_movie);
1100 g_audaspace.scrub_handle = AUD_pauseAfter(g_audaspace.playback_handle,
1101 1.0 / g_playanim.fps_movie);
1102 }
1103 }
1104 else if (g_audaspace.source) {
1105 g_audaspace.playback_handle = AUD_Device_play(g_audaspace.audio_device, g_audaspace.source, 1);
1106 if (g_audaspace.playback_handle) {
1107 AUD_Handle_setPosition(g_audaspace.playback_handle, i / g_playanim.fps_movie);
1108 g_audaspace.scrub_handle = AUD_pauseAfter(g_audaspace.playback_handle,
1109 1.0 / g_playanim.fps_movie);
1110 }
1112 }
1113#endif
1114
1115 ps.picture = static_cast<PlayAnimPict *>(BLI_findlink(&ps.picsbase, i));
1116 BLI_assert(ps.picture != nullptr);
1117
1118 ps.single_step = true;
1119 ps.wait = false;
1120 ps.next_frame = 0;
1121
1122 ps.need_frame_update = false;
1123}
1124
1126{
1127#ifdef WITH_AUDASPACE
1128 /* TODO: store in ps direct? */
1129 const int i = BLI_findindex(&ps.picsbase, ps.picture);
1130 if (g_audaspace.playback_handle) {
1131 AUD_Handle_stop(g_audaspace.playback_handle);
1132 }
1133 g_audaspace.playback_handle = AUD_Device_play(g_audaspace.audio_device, g_audaspace.source, 1);
1134 if (g_audaspace.playback_handle) {
1135 AUD_Handle_setPosition(g_audaspace.playback_handle, i / g_playanim.fps_movie);
1136 }
1138#else
1139 UNUSED_VARS(ps);
1140#endif
1141}
1142
1143static void playanim_audio_stop(PlayState & /*ps*/)
1144{
1145#ifdef WITH_AUDASPACE
1146 if (g_audaspace.playback_handle) {
1147 AUD_Handle_stop(g_audaspace.playback_handle);
1148 g_audaspace.playback_handle = nullptr;
1149 }
1150#endif
1151}
1152
1153static bool ghost_event_proc(GHOST_EventHandle ghost_event, GHOST_TUserDataPtr ps_void_ptr)
1154{
1155 PlayState &ps = *static_cast<PlayState *>(ps_void_ptr);
1156 const GHOST_TEventType type = GHOST_GetEventType(ghost_event);
1158 /* Convert ghost event into value keyboard or mouse. */
1159 const int val = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventButtonDown);
1160 GHOST_SystemHandle ghost_system = ps.ghost_data.system;
1161 GHOST_WindowHandle ghost_window = ps.ghost_data.window;
1162
1163 // print_ps(ps);
1164
1166
1167 /* First check if we're busy loading files. */
1168 if (ps.loading) {
1169 switch (type) {
1171 case GHOST_kEventKeyUp: {
1172 const GHOST_TEventKeyData *key_data = static_cast<const GHOST_TEventKeyData *>(data);
1173 switch (key_data->key) {
1174 case GHOST_kKeyEsc:
1175 ps.loading = false;
1176 break;
1177 default:
1178 break;
1179 }
1180 break;
1181 }
1182 default:
1183 break;
1184 }
1185 return true;
1186 }
1187
1188 if (ps.wait && ps.stopped == false) {
1189 ps.stopped = true;
1190 }
1191
1192 if (ps.wait) {
1193 pupdate_time();
1194 g_playanim.total_time = 0.0;
1195 }
1196
1197 switch (type) {
1199 case GHOST_kEventKeyUp: {
1200 const GHOST_TEventKeyData *key_data = static_cast<const GHOST_TEventKeyData *>(data);
1201 switch (key_data->key) {
1202 case GHOST_kKeyA:
1203 if (val) {
1205 }
1206 break;
1207 case GHOST_kKeyI:
1208 if (val) {
1210 }
1211 break;
1212 case GHOST_kKeyP:
1213 if (val) {
1214 ps.pingpong = !ps.pingpong;
1215 }
1216 break;
1217 case GHOST_kKeyF: {
1218 if (val) {
1219 int axis = (ps.ghost_data.qual & WS_QUAL_SHIFT) ? 1 : 0;
1220 ps.draw_flip[axis] = !ps.draw_flip[axis];
1221 }
1222 break;
1223 }
1224 case GHOST_kKey1:
1225 case GHOST_kKeyNumpad1:
1226 if (val) {
1227 g_playanim.swap_time = ps.frame_step / 60.0;
1229 }
1230 break;
1231 case GHOST_kKey2:
1232 case GHOST_kKeyNumpad2:
1233 if (val) {
1234 g_playanim.swap_time = ps.frame_step / 50.0;
1236 }
1237 break;
1238 case GHOST_kKey3:
1239 case GHOST_kKeyNumpad3:
1240 if (val) {
1241 g_playanim.swap_time = ps.frame_step / 30.0;
1243 }
1244 break;
1245 case GHOST_kKey4:
1246 case GHOST_kKeyNumpad4:
1247 if (ps.ghost_data.qual & WS_QUAL_SHIFT) {
1248 g_playanim.swap_time = ps.frame_step / 24.0;
1250 }
1251 else {
1252 g_playanim.swap_time = ps.frame_step / 25.0;
1254 }
1255 break;
1256 case GHOST_kKey5:
1257 case GHOST_kKeyNumpad5:
1258 if (val) {
1259 g_playanim.swap_time = ps.frame_step / 20.0;
1261 }
1262 break;
1263 case GHOST_kKey6:
1264 case GHOST_kKeyNumpad6:
1265 if (val) {
1266 g_playanim.swap_time = ps.frame_step / 15.0;
1268 }
1269 break;
1270 case GHOST_kKey7:
1271 case GHOST_kKeyNumpad7:
1272 if (val) {
1273 g_playanim.swap_time = ps.frame_step / 12.0;
1275 }
1276 break;
1277 case GHOST_kKey8:
1278 case GHOST_kKeyNumpad8:
1279 if (val) {
1280 g_playanim.swap_time = ps.frame_step / 10.0;
1282 }
1283 break;
1284 case GHOST_kKey9:
1285 case GHOST_kKeyNumpad9:
1286 if (val) {
1287 g_playanim.swap_time = ps.frame_step / 6.0;
1289 }
1290 break;
1292 if (val) {
1293 ps.single_step = true;
1294 ps.wait = false;
1296
1297 if (ps.ghost_data.qual & WS_QUAL_SHIFT) {
1298 ps.picture = static_cast<PlayAnimPict *>(ps.picsbase.first);
1299 ps.next_frame = 0;
1300 }
1301 else {
1302 ps.next_frame = -1;
1303 }
1304 }
1305 break;
1307 if (val) {
1308 ps.wait = false;
1310
1311 if (ps.ghost_data.qual & WS_QUAL_SHIFT) {
1312 ps.next_frame = ps.direction = -1;
1313 }
1314 else {
1315 ps.next_frame = -10;
1316 ps.single_step = true;
1317 }
1318 }
1319 break;
1321 if (val) {
1322 ps.single_step = true;
1323 ps.wait = false;
1325
1326 if (ps.ghost_data.qual & WS_QUAL_SHIFT) {
1327 ps.picture = static_cast<PlayAnimPict *>(ps.picsbase.last);
1328 ps.next_frame = 0;
1329 }
1330 else {
1331 ps.next_frame = 1;
1332 }
1333 }
1334 break;
1335 case GHOST_kKeyUpArrow:
1336 if (val) {
1337 ps.wait = false;
1338 if (ps.ghost_data.qual & WS_QUAL_SHIFT) {
1339 ps.next_frame = ps.direction = 1;
1340 if (ps.single_step == false) {
1342 }
1343 }
1344 else {
1345 ps.next_frame = 10;
1346 ps.single_step = true;
1348 }
1349 }
1350 break;
1351
1352 case GHOST_kKeySlash:
1354 if (val) {
1355 if (ps.ghost_data.qual & WS_QUAL_SHIFT) {
1356 if (ps.picture && ps.picture->ibuf) {
1357 printf(" Name: %s | Speed: %.2f frames/s\n",
1358 ps.picture->ibuf->filepath,
1359 ps.frame_step / g_playanim.swap_time);
1360 }
1361 }
1362 else {
1363 g_playanim.swap_time = ps.frame_step / 5.0;
1365 }
1366 }
1367 break;
1368 case GHOST_kKey0:
1369 case GHOST_kKeyNumpad0:
1370 if (val) {
1371 if (ps.once) {
1372 ps.once = ps.wait = false;
1373 }
1374 else {
1375 ps.picture = nullptr;
1376 ps.once = true;
1377 ps.wait = false;
1378 }
1379 }
1380 break;
1381
1382 case GHOST_kKeySpace:
1383 if (val) {
1384 if (ps.wait || ps.single_step) {
1385 ps.wait = ps.single_step = false;
1387 }
1388 else {
1389 ps.single_step = true;
1390 ps.wait = true;
1392 }
1393 }
1394 break;
1395 case GHOST_kKeyEnter:
1397 if (val) {
1398 ps.wait = ps.single_step = false;
1400 }
1401 break;
1402 case GHOST_kKeyPeriod:
1404 if (val) {
1405 if (ps.single_step) {
1406 ps.wait = false;
1407 }
1408 else {
1409 ps.single_step = true;
1410 ps.wait = !ps.wait;
1412 }
1413 }
1414 break;
1415 case GHOST_kKeyEqual:
1416 case GHOST_kKeyPlus:
1417 case GHOST_kKeyNumpadPlus: {
1418 if (val == 0) {
1419 break;
1420 }
1421 if (ps.ghost_data.qual & WS_QUAL_CTRL) {
1422 playanim_window_zoom(ps, 0.1f);
1423 }
1424 else {
1425 if (g_playanim.swap_time > ps.frame_step / 60.0) {
1426 g_playanim.swap_time /= 1.1;
1428 }
1429 }
1430 break;
1431 }
1432 case GHOST_kKeyMinus:
1433 case GHOST_kKeyNumpadMinus: {
1434 if (val == 0) {
1435 break;
1436 }
1437 if (ps.ghost_data.qual & WS_QUAL_CTRL) {
1438 playanim_window_zoom(ps, -0.1f);
1439 }
1440 else {
1441 if (g_playanim.swap_time < ps.frame_step / 5.0) {
1442 g_playanim.swap_time *= 1.1;
1444 }
1445 }
1446 break;
1447 }
1448 case GHOST_kKeyEsc:
1449 ps.go = false;
1450 break;
1451 default:
1452 break;
1453 }
1454 break;
1455 }
1457 case GHOST_kEventButtonUp: {
1458 const GHOST_TEventButtonData *bd = static_cast<const GHOST_TEventButtonData *>(data);
1459 int cx, cy;
1460 const blender::int2 window_size = playanim_window_size_get(ghost_window);
1461
1462 const bool inside_window = (GHOST_GetCursorPosition(ghost_system, ghost_window, &cx, &cy) ==
1463 GHOST_kSuccess) &&
1464 (cx >= 0 && cx < window_size[0] && cy >= 0 &&
1465 cy <= window_size[1]);
1466
1467 if (bd->button == GHOST_kButtonMaskLeft) {
1468 if (type == GHOST_kEventButtonDown) {
1469 if (inside_window) {
1472 }
1473 }
1474 else {
1476 }
1477 }
1478 else if (bd->button == GHOST_kButtonMaskMiddle) {
1479 if (type == GHOST_kEventButtonDown) {
1480 if (inside_window) {
1482 }
1483 }
1484 else {
1486 }
1487 }
1488 else if (bd->button == GHOST_kButtonMaskRight) {
1489 if (type == GHOST_kEventButtonDown) {
1490 if (inside_window) {
1492 }
1493 }
1494 else {
1496 }
1497 }
1498 break;
1499 }
1501 if (ps.ghost_data.qual & WS_QUAL_LMOUSE) {
1502 const GHOST_TEventCursorData *cd = static_cast<const GHOST_TEventCursorData *>(data);
1503 int cx, cy;
1504
1505 /* Ignore 'in-between' events, since they can make scrubbing lag.
1506 *
1507 * Ideally we would keep into the event queue and see if this is the last motion event.
1508 * however the API currently doesn't support this. */
1509 {
1510 int x_test, y_test;
1511 if (GHOST_GetCursorPosition(ghost_system, ghost_window, &cx, &cy) == GHOST_kSuccess) {
1512 GHOST_ScreenToClient(ghost_window, cd->x, cd->y, &x_test, &y_test);
1513 if (cx != x_test || cy != y_test) {
1514 /* We're not the last event... skipping. */
1515 break;
1516 }
1517 }
1518 }
1519
1521 }
1522 break;
1523 }
1527 break;
1528 }
1531 float zoomx, zoomy;
1532
1533 ps.display_ctx.size = playanim_window_size_get(ghost_window);
1535
1536 zoomx = float(ps.display_ctx.size[0]) / ps.ibuf_size[0];
1537 zoomy = float(ps.display_ctx.size[1]) / ps.ibuf_size[1];
1538
1539 /* Zoom always show entire image. */
1540 ps.zoom = std::min(zoomx, zoomy);
1541
1542 GPU_viewport(0, 0, ps.display_ctx.size[0], ps.display_ctx.size[1]);
1543 GPU_scissor(0, 0, ps.display_ctx.size[0], ps.display_ctx.size[1]);
1544
1546
1547 g_playanim.total_time = 0.0;
1548
1549 playanim_toscreen(ps, ps.picture, ps.picture ? ps.picture->ibuf : nullptr);
1550
1551 break;
1552 }
1555 ps.go = false;
1556 break;
1557 }
1559 /* Rely on frame-change to redraw. */
1561 break;
1562 }
1564 const GHOST_TEventDragnDropData *ddd = static_cast<const GHOST_TEventDragnDropData *>(data);
1565
1567 const GHOST_TStringArray *stra = static_cast<const GHOST_TStringArray *>(ddd->data);
1568 ps.argc_next = stra->count;
1569 ps.argv_next = MEM_malloc_arrayN<char *>(size_t(ps.argc_next), __func__);
1570 for (int i = 0; i < stra->count; i++) {
1571 ps.argv_next[i] = BLI_strdup(reinterpret_cast<const char *>(stra->strings[i]));
1572 }
1573 ps.go = false;
1574 printf("dropped %s, %d file(s)\n", ps.argv_next[0], ps.argc_next);
1575 }
1576 break;
1577 }
1578 default:
1579 /* Quiet warnings. */
1580 break;
1581 }
1582
1583 return true;
1584}
1585
1586static GHOST_WindowHandle playanim_window_open(
1587 GHOST_SystemHandle ghost_system, const char *title, int posx, int posy, int sizex, int sizey)
1588{
1589 GHOST_GPUSettings gpusettings = {0};
1590 const eGPUBackendType gpu_backend = GPU_backend_type_selection_get();
1591 gpusettings.context_type = wm_ghost_drawing_context_type(gpu_backend);
1592 gpusettings.preferred_device.index = U.gpu_preferred_index;
1593 gpusettings.preferred_device.vendor_id = U.gpu_preferred_vendor_id;
1594 gpusettings.preferred_device.device_id = U.gpu_preferred_device_id;
1595
1596 {
1597 bool screen_size_valid = false;
1598 uint32_t screen_size[2];
1599 if ((GHOST_GetMainDisplayDimensions(ghost_system, &screen_size[0], &screen_size[1]) ==
1600 GHOST_kSuccess) &&
1601 (screen_size[0] > 0) && (screen_size[1] > 0))
1602 {
1603 screen_size_valid = true;
1604 }
1605 else {
1606 /* Unlikely the screen size fails to access,
1607 * if this happens it's still important to clamp the window size by *something*. */
1608 screen_size[0] = 1024;
1609 screen_size[1] = 1024;
1610 }
1611
1612 if (screen_size_valid) {
1614 posy = (screen_size[1] - posy - sizey);
1615 }
1616 }
1617 else {
1618 posx = 0;
1619 posy = 0;
1620 }
1621
1622 /* NOTE: ideally the GPU could be queried for the maximum supported window size,
1623 * this isn't so simple as the GPU back-end's capabilities are initialized *after* the window
1624 * has been created. Further, it's quite unlikely the users main monitor size is larger
1625 * than the maximum window size supported by the GPU. */
1626
1627 /* Clamp the size so very large requests aren't rejected by the GPU.
1628 * Halve until a usable range is reached instead of scaling down to meet the screen size
1629 * since fractional scaling tends not to look so nice. */
1630 while (sizex >= int(screen_size[0]) || sizey >= int(screen_size[1])) {
1631 sizex /= 2;
1632 sizey /= 2;
1633 }
1634 /* Unlikely but ensure the size is *never* zero. */
1635 CLAMP_MIN(sizex, 1);
1636 CLAMP_MIN(sizey, 1);
1637 }
1638
1640 nullptr,
1641 title,
1642 posx,
1643 posy,
1644 sizex,
1645 sizey,
1646 /* Could optionally start full-screen. */
1648 false,
1649 gpusettings);
1650}
1651
1652static void playanim_window_zoom(PlayState &ps, const float zoom_offset)
1653{
1655 // blender::int2 ofs; /* UNUSED. */
1656
1657 if (ps.zoom + zoom_offset > 0.0f) {
1658 ps.zoom += zoom_offset;
1659 }
1660
1661 // playanim_window_get_position(&ofs[0], &ofs[1]);
1662 // size = playanim_window_size_get(ps.ghost_data.window);
1663 // ofs[0] += size[0] / 2; /* UNUSED. */
1664 // ofs[1] += size[1] / 2; /* UNUSED. */
1665 size[0] = ps.zoom * ps.ibuf_size[0];
1666 size[1] = ps.zoom * ps.ibuf_size[1];
1667 // ofs[0] -= size[0] / 2; /* UNUSED. */
1668 // ofs[1] -= size[1] / 2; /* UNUSED. */
1669 // window_set_position(ps.ghost_data.window, size[0], size[1]);
1671}
1672
1674{
1675 const float scale = (GHOST_GetDPIHint(ps.ghost_data.window) *
1677 const float font_size_base = 11.0f; /* Font size un-scaled. */
1678 const int font_size = int((font_size_base * scale) + 0.5f);
1679 bool changed = false;
1680 if (ps.font_size != font_size) {
1681 BLF_size(ps.font_id, font_size);
1682 ps.font_size = font_size;
1683 changed = true;
1684 }
1685 if (ps.display_ctx.ui_scale != scale) {
1686 ps.display_ctx.ui_scale = scale;
1687 }
1688 return changed;
1689}
1690
1695static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_next)
1696{
1697 ImBuf *ibuf = nullptr;
1698 blender::int2 window_pos = {0, 0};
1699 int frame_start = -1;
1700 int frame_end = -1;
1701
1702 PlayState ps{};
1703
1704 ps.go = true;
1705 ps.direction = true;
1706 ps.next_frame = 1;
1707 ps.once = false;
1708 ps.pingpong = false;
1709 ps.no_frame_skip = false;
1710 ps.single_step = false;
1711 ps.wait = false;
1712 ps.stopped = false;
1713 ps.loading = false;
1714 ps.picture = nullptr;
1715 ps.show_frame_indicator = false;
1716 ps.argc_next = 0;
1717 ps.argv_next = nullptr;
1718 ps.zoom = 1.0f;
1719 ps.draw_flip[0] = false;
1720 ps.draw_flip[1] = false;
1721
1722 ps.frame_step = 1;
1723
1724 ps.font_id = -1;
1725
1726 IMB_init();
1727 MOV_init();
1728
1733 ps.display_ctx.ui_scale = 1.0f;
1734
1735 while ((argc > 0) && (argv[0][0] == '-')) {
1736 switch (argv[0][1]) {
1737 case 'm': {
1738 g_playanim.from_disk = true;
1739 break;
1740 }
1741 case 'p': {
1742 if (argc > 2) {
1743 window_pos[0] = atoi(argv[1]);
1744 window_pos[1] = atoi(argv[2]);
1745 argc -= 2;
1746 argv += 2;
1747 }
1748 else {
1749 printf("too few arguments for -p (need 2): skipping\n");
1750 }
1751 break;
1752 }
1753 case 'f': {
1754 if (argc > 2) {
1755 double fps = atof(argv[1]);
1756 double fps_base = atof(argv[2]);
1757 if (fps == 0.0) {
1758 fps = 1;
1759 printf("invalid fps, forcing 1\n");
1760 }
1761 g_playanim.swap_time = fps_base / fps;
1762 argc -= 2;
1763 argv += 2;
1764 }
1765 else {
1766 printf("too few arguments for -f (need 2): skipping\n");
1767 }
1768 break;
1769 }
1770 case 's': {
1771 frame_start = atoi(argv[1]);
1772 CLAMP(frame_start, 1, MAXFRAME);
1773 argc--;
1774 argv++;
1775 break;
1776 }
1777 case 'e': {
1778 frame_end = atoi(argv[1]);
1779 CLAMP(frame_end, 1, MAXFRAME);
1780 argc--;
1781 argv++;
1782 break;
1783 }
1784 case 'j': {
1785 ps.frame_step = atoi(argv[1]);
1786 CLAMP(ps.frame_step, 1, MAXFRAME);
1787 g_playanim.swap_time *= ps.frame_step;
1788 argc--;
1789 argv++;
1790 break;
1791 }
1792 case 'c': {
1793#ifdef USE_FRAME_CACHE_LIMIT
1794 const int memory_in_mb = max_ii(0, atoi(argv[1]));
1795 g_frame_cache.memory_limit = size_t(memory_in_mb) * (1024 * 1024);
1796#endif
1797 argc--;
1798 argv++;
1799 break;
1800 }
1801 default: {
1802 printf("unknown option '%c': skipping\n", argv[0][1]);
1803 break;
1804 }
1805 }
1806 argc--;
1807 argv++;
1808 }
1809
1810 if (argc == 0) {
1811 printf("%s: no filepath argument given\n", __func__);
1812 exit(EXIT_FAILURE);
1813 }
1814
1815 const char *filepath = argv[0];
1816
1817 if (MOV_is_movie_file(filepath)) {
1818 /* OCIO_TODO: support different input color spaces. */
1819 MovieReader *anim = MOV_open_file(filepath, IB_byte_data, 0, nullptr);
1820 if (anim) {
1822 MOV_close(anim);
1823 anim = nullptr;
1824 }
1825 }
1826 else if (!IMB_test_image(filepath)) {
1827 printf("%s: '%s' not an image file\n", __func__, filepath);
1828 exit(EXIT_FAILURE);
1829 }
1830
1831 if (ibuf == nullptr) {
1832 /* OCIO_TODO: support different input color space. */
1834 }
1835
1836 if (ibuf == nullptr) {
1837 printf("%s: '%s' couldn't open\n", __func__, filepath);
1838 exit(EXIT_FAILURE);
1839 }
1840
1841 /* Select GPU backend. */
1843
1844 /* Init GHOST and open window. */
1845 GHOST_EventConsumerHandle ghost_event_consumer = nullptr;
1846 {
1847 ghost_event_consumer = GHOST_CreateEventConsumer(ghost_event_proc, &ps);
1848
1850
1853
1854 if (UNLIKELY(ps.ghost_data.system == nullptr)) {
1855 /* GHOST will have reported the back-ends that failed to load. */
1856 CLOG_WARN(&LOG, "GHOST: unable to initialize, exiting!");
1857 /* This will leak memory, it's preferable to crashing. */
1858 exit(EXIT_FAILURE);
1859 }
1860
1861 GHOST_AddEventConsumer(ps.ghost_data.system, ghost_event_consumer);
1862
1864
1866 "Blender Animation Player",
1867 window_pos[0],
1868 window_pos[1],
1869 ibuf->x,
1870 ibuf->y);
1871 }
1872
1873 // GHOST_ActivateWindowDrawingContext(ps.ghost_data.window);
1874
1875 /* Init Blender GPU context. */
1877 GPU_init();
1878
1879 /* Initialize the font. */
1880 BLF_init();
1881 ps.font_id = BLF_load_mono_default(false);
1882
1883 ps.font_size = -1; /* Force update. */
1885
1886 ps.ibuf_size[0] = ibuf->x;
1887 ps.ibuf_size[1] = ibuf->y;
1888
1889 ps.display_ctx.size = ps.ibuf_size;
1890
1893 GPU_clear_color(0.1f, 0.1f, 0.1f, 0.0f);
1894
1895 {
1897 GPU_viewport(0, 0, window_size[0], window_size[1]);
1898 GPU_scissor(0, 0, window_size[0], window_size[1]);
1900 }
1901
1904
1905 /* One of the frames was invalid or not passed in. */
1906 if (frame_start == -1 || frame_end == -1) {
1907 frame_start = 1;
1908 if (argc == 1) {
1909 /* A single file was passed in, attempt to load all images from an image sequence.
1910 * (if it is an image sequence). */
1911 frame_end = MAXFRAME;
1912 }
1913 else {
1914 /* Multiple files passed in, show each file without expanding image sequences.
1915 * This occurs when dropping multiple files. */
1916 frame_end = 1;
1917 }
1918 }
1919
1921 ps.ghost_data,
1922 ps.display_ctx,
1923 filepath,
1924 (frame_end - frame_start) + 1,
1925 ps.frame_step,
1926 &ps.loading);
1927
1928#ifdef WITH_AUDASPACE
1929 g_audaspace.source = AUD_Sound_file(filepath);
1930 if (!BLI_listbase_is_empty(&ps.picsbase)) {
1931 const MovieReader *anim_movie = static_cast<PlayAnimPict *>(ps.picsbase.first)->anim;
1932 if (anim_movie) {
1933 g_playanim.fps_movie = MOV_get_fps(anim_movie);
1934 /* Enforce same fps for movie as sound. */
1935 g_playanim.swap_time = ps.frame_step / g_playanim.fps_movie;
1936 }
1937 }
1938#endif
1939
1940 for (int i = 1; i < argc; i++) {
1941 filepath = argv[i];
1943 ps.ghost_data,
1944 ps.display_ctx,
1945 filepath,
1946 (frame_end - frame_start) + 1,
1947 ps.frame_step,
1948 &ps.loading);
1949 }
1950
1951 IMB_freeImBuf(ibuf);
1952 ibuf = nullptr;
1953
1954 pupdate_time();
1955 g_playanim.total_time = 0.0;
1956
1957/* Newly added in 2.6x, without this images never get freed. */
1958#define USE_IMB_CACHE
1959
1960 while (ps.go) {
1961 if (ps.pingpong) {
1962 ps.direction = -ps.direction;
1963 }
1964
1965 if (ps.direction == 1) {
1966 ps.picture = static_cast<PlayAnimPict *>(ps.picsbase.first);
1967 }
1968 else {
1969 ps.picture = static_cast<PlayAnimPict *>(ps.picsbase.last);
1970 }
1971
1972 if (ps.picture == nullptr) {
1973 printf("couldn't find pictures\n");
1974 ps.go = false;
1975 }
1976 if (ps.pingpong) {
1977 if (ps.direction == 1) {
1978 ps.picture = ps.picture->next;
1979 }
1980 else {
1981 ps.picture = ps.picture->prev;
1982 }
1983 }
1984 g_playanim.total_time = std::min(g_playanim.total_time, 0.0);
1985
1986#ifdef WITH_AUDASPACE
1987 if (g_audaspace.playback_handle) {
1988 AUD_Handle_stop(g_audaspace.playback_handle);
1989 }
1990 g_audaspace.playback_handle = AUD_Device_play(g_audaspace.audio_device, g_audaspace.source, 1);
1992#endif
1993
1994 while (ps.picture) {
1995 bool has_event;
1996#ifndef USE_IMB_CACHE
1997 if (ibuf != nullptr && ibuf->ftype == IMB_FTYPE_NONE) {
1998 IMB_freeImBuf(ibuf);
1999 }
2000#endif
2001
2002 ibuf = ibuf_from_picture(ps.picture);
2003
2004 {
2005#ifdef USE_IMB_CACHE
2006 ps.picture->ibuf = ibuf;
2007#endif
2008 if (ibuf) {
2009#ifdef USE_FRAME_CACHE_LIMIT
2010 if (ps.picture->frame_cache_node == nullptr) {
2012 }
2013 else {
2015 }
2017#endif /* USE_FRAME_CACHE_LIMIT */
2018
2019 STRNCPY(ibuf->filepath, ps.picture->filepath);
2020 ibuf->fileframe = ps.picture->frame;
2021 }
2022
2023 while (pupdate_time()) {
2025 }
2026 g_playanim.total_time -= g_playanim.swap_time;
2027 playanim_toscreen(ps, ps.picture, ibuf);
2028 }
2029
2030 if (ps.once) {
2031 if (ps.picture->next == nullptr) {
2032 ps.wait = true;
2033 }
2034 else if (ps.picture->prev == nullptr) {
2035 ps.wait = true;
2036 }
2037 }
2038
2039 ps.next_frame = ps.direction;
2040
2042 GPUContext *restore_context = GPU_context_active_get();
2044 while ((has_event = GHOST_ProcessEvents(ps.ghost_data.system, false))) {
2046 }
2048 GPU_context_active_set(restore_context);
2049
2050 if (ps.go == false) {
2051 break;
2052 }
2054 if (!has_event) {
2056 }
2057 if (ps.wait) {
2058 continue;
2059 }
2060
2061 ps.wait = ps.single_step;
2062
2063 if (ps.wait == false && ps.stopped) {
2064 ps.stopped = false;
2065 }
2066
2067 pupdate_time();
2068
2069 if (ps.picture && ps.next_frame) {
2070 /* Advance to the next frame, always at least set one step.
2071 * Implement frame-skipping when enabled and playback is not fast enough. */
2072 while (ps.picture) {
2074
2075 if (ps.once && ps.picture != nullptr) {
2076 if (ps.picture->next == nullptr) {
2077 ps.wait = true;
2078 }
2079 else if (ps.picture->prev == nullptr) {
2080 ps.wait = true;
2081 }
2082 }
2083
2084 if (ps.wait || g_playanim.total_time < g_playanim.swap_time || ps.no_frame_skip) {
2085 break;
2086 }
2087 g_playanim.total_time -= g_playanim.swap_time;
2088 }
2089 if (ps.picture == nullptr && ps.single_step) {
2091 }
2092 }
2093 if (ps.go == false) {
2094 break;
2095 }
2096 }
2097 }
2098 while ((ps.picture = static_cast<PlayAnimPict *>(BLI_pophead(&ps.picsbase)))) {
2099 if (ps.picture->anim) {
2100 if ((ps.picture->next == nullptr) || (ps.picture->next->anim != ps.picture->anim)) {
2101 MOV_close(ps.picture->anim);
2102 }
2103 }
2104
2105 if (ps.picture->ibuf) {
2107 }
2108 if (ps.picture->mem) {
2109 MEM_freeN(ps.picture->mem);
2110 }
2111 if (ps.picture->error_message) {
2113 }
2115 MEM_freeN(ps.picture);
2116 }
2117
2118/* Cleanup. */
2119#ifndef USE_IMB_CACHE
2120 if (ibuf) {
2121 IMB_freeImBuf(ibuf);
2122 }
2123#endif
2124
2125#ifdef USE_FRAME_CACHE_LIMIT
2127 g_frame_cache.pics_len = 0;
2128 g_frame_cache.pics_size_in_memory = 0;
2129#endif
2130
2131#ifdef WITH_AUDASPACE
2132 if (g_audaspace.playback_handle) {
2133 AUD_Handle_stop(g_audaspace.playback_handle);
2134 g_audaspace.playback_handle = nullptr;
2135 }
2136 if (g_audaspace.scrub_handle) {
2137 AUD_Handle_stop(g_audaspace.scrub_handle);
2138 g_audaspace.scrub_handle = nullptr;
2139 }
2140 AUD_Sound_free(g_audaspace.source);
2141 g_audaspace.source = nullptr;
2142#endif
2143
2144 /* We still miss freeing a lot!
2145 * But many areas could skip initialization too for anim play. */
2146
2148
2149 BLF_exit();
2150
2151 /* NOTE: Must happen before GPU Context destruction as GPU resources are released via
2152 * Color Management module. */
2153 IMB_exit();
2154 MOV_exit();
2155
2156 if (ps.ghost_data.gpu_context) {
2158 GPU_exit();
2160 ps.ghost_data.gpu_context = nullptr;
2161 }
2162 GHOST_RemoveEventConsumer(ps.ghost_data.system, ghost_event_consumer);
2163 GHOST_DisposeEventConsumer(ghost_event_consumer);
2164
2166
2167 /* Early exit, IMB and BKE should be exited only in end. */
2168 if (ps.argv_next) {
2169 args_next->argc = ps.argc_next;
2170 args_next->argv = ps.argv_next;
2171 return true;
2172 }
2173
2175
2176#if 0
2178 if (totblock != 0) {
2179 /* Prints many `bAKey`, `bArgument` messages which are tricky to fix. */
2180 printf("Error Totblock: %d\n", totblock);
2182 }
2183#endif
2184
2185 return false;
2186}
2187
2188void WM_main_playanim(int argc, const char **argv)
2189{
2190#ifdef WITH_AUDASPACE
2191 {
2192 AUD_DeviceSpecs specs;
2193
2194 specs.rate = AUD_RATE_48000;
2195 specs.format = AUD_FORMAT_FLOAT32;
2196 specs.channels = AUD_CHANNELS_STEREO;
2197
2198 AUD_initOnce();
2199
2200 if (!(g_audaspace.audio_device = AUD_init(nullptr, specs, 1024, "Blender"))) {
2201 g_audaspace.audio_device = AUD_init("None", specs, 0, "Blender");
2202 }
2203 }
2204#endif
2205
2206 PlayArgs args_next = {0};
2207 do {
2208 PlayArgs args_free = args_next;
2209 args_next = {0};
2210
2211 if (wm_main_playanim_intern(argc, argv, &args_next)) {
2212 argc = args_next.argc;
2213 argv = const_cast<const char **>(args_next.argv);
2214 }
2215 else {
2216 argc = 0;
2217 argv = nullptr;
2218 }
2219
2220 if (args_free.argv) {
2221 for (int i = 0; i < args_free.argc; i++) {
2222 MEM_freeN(args_free.argv[i]);
2223 }
2224 MEM_freeN(args_free.argv);
2225 }
2226 } while (argv != nullptr);
2227
2228#ifdef WITH_AUDASPACE
2229 AUD_exit(g_audaspace.audio_device);
2230 AUD_exitOnce();
2231#endif
2232}
int ED_draw_imbuf_method(const ImBuf *ibuf)
Definition glutil.cc:604
void BLF_size(int fontid, float size)
Definition blf.cc:440
void BLF_aspect(int fontid, float x, float y, float z)
Definition blf.cc:374
@ BLF_ASPECT
Definition BLF_api.hh:438
void BLF_draw(int fontid, const char *str, size_t str_len, ResultBLF *r_info=nullptr) ATTR_NONNULL(2)
Definition blf.cc:582
int BLF_load_mono_default(bool unique)
void BLF_exit()
Definition blf.cc:70
void BLF_enable(int fontid, int option)
Definition blf.cc:320
int BLF_init()
Definition blf.cc:61
void BLF_color4f(int fontid, float r, float g, float b, float a)
Definition blf.cc:511
void BLF_position(int fontid, float x, float y, float z)
Definition blf.cc:385
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
File and directory operations.
#define O_BINARY
size_t BLI_file_descriptor_size(int file) ATTR_WARN_UNUSED_RESULT
Definition storage.cc:230
int BLI_open(const char *filepath, int oflag, int pmode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
int64_t BLI_read(int fd, void *buf, size_t nbytes)
Definition fileops_c.cc:96
int BLI_findindex(const ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:586
void * BLI_findlink(const ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:534
LinkData * BLI_genericNodeN(void *data)
Definition listbase.cc:922
BLI_INLINE bool BLI_listbase_is_empty(const ListBase *lb)
void BLI_freelinkN(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:270
void void BLI_freelistN(ListBase *listbase) ATTR_NONNULL(1)
Definition listbase.cc:497
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
void BLI_remlink(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:131
void BLI_addhead(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:91
void * BLI_pophead(ListBase *listbase) ATTR_NONNULL(1)
Definition listbase.cc:252
void void BLI_INLINE bool BLI_listbase_is_single(const ListBase *lb)
MINLINE int max_ii(int a, int b)
MINLINE int clamp_i(int value, int min, int max)
#define FILE_MAX
int BLI_path_sequence_decode(const char *path, char *head, size_t head_maxncpy, char *tail, size_t tail_maxncpy, unsigned short *r_digits_len)
Definition path_utils.cc:57
void BLI_path_sequence_encode(char *path, size_t path_maxncpy, const char *head, const char *tail, unsigned short numlen, int pic)
void BLI_rctf_init(struct rctf *rect, float xmin, float xmax, float ymin, float ymax)
Definition rct.cc:404
char * BLI_sprintfN(const char *__restrict format,...) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC ATTR_PRINTF_FORMAT(1
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.cc:41
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:599
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
unsigned char uchar
unsigned int uint
unsigned short ushort
void BLI_system_backtrace(FILE *fp)
Definition system.cc:102
Platform independent time functions.
void BLI_time_sleep_ms(int ms)
Definition time.cc:85
double BLI_time_now_seconds(void)
Definition time.cc:65
#define CLAMP(a, b, c)
#define UNUSED_VARS(...)
#define ENUM_OPERATORS(_type, _max)
#define SET_FLAG_FROM_TEST(value, test, flag)
#define UNLIKELY(x)
#define ELEM(...)
#define CLAMP_MIN(a, b)
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:181
void DEG_free_node_types()
#define MAXFRAME
@ IMAGE_DRAW_METHOD_GLSL
GHOST C-API function and type declarations.
int32_t GHOST_GetWidthRectangle(GHOST_RectangleHandle rectanglehandle)
GHOST_TSuccess GHOST_GetCursorPosition(const GHOST_SystemHandle systemhandle, const GHOST_WindowHandle windowhandle, int32_t *x, int32_t *y)
GHOST_TSuccess GHOST_SetClientSize(GHOST_WindowHandle windowhandle, uint32_t width, uint32_t height)
GHOST_TCapabilityFlag GHOST_GetCapabilities(void)
GHOST_EventConsumerHandle GHOST_CreateEventConsumer(GHOST_EventCallbackProcPtr eventCallback, GHOST_TUserDataPtr user_data)
GHOST_SystemHandle GHOST_CreateSystem(void)
GHOST_TSuccess GHOST_AddEventConsumer(GHOST_SystemHandle systemhandle, GHOST_EventConsumerHandle consumerhandle)
GHOST_TSuccess GHOST_RemoveEventConsumer(GHOST_SystemHandle systemhandle, GHOST_EventConsumerHandle consumerhandle)
uint16_t GHOST_GetDPIHint(GHOST_WindowHandle windowhandle)
void GHOST_DisposeRectangle(GHOST_RectangleHandle rectanglehandle)
bool GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, bool waitForEvent)
void GHOST_ScreenToClient(GHOST_WindowHandle windowhandle, int32_t inX, int32_t inY, int32_t *outX, int32_t *outY)
GHOST_TSuccess GHOST_DisposeEventConsumer(GHOST_EventConsumerHandle consumerhandle)
GHOST_TSuccess GHOST_DisposeWindow(GHOST_SystemHandle systemhandle, GHOST_WindowHandle windowhandle)
void GHOST_SetBacktraceHandler(GHOST_TBacktraceFn backtrace_fn)
int32_t GHOST_GetHeightRectangle(GHOST_RectangleHandle rectanglehandle)
GHOST_TSuccess GHOST_SwapWindowBuffers(GHOST_WindowHandle windowhandle)
GHOST_TEventDataPtr GHOST_GetEventData(GHOST_EventHandle eventhandle)
GHOST_TSuccess GHOST_ActivateWindowDrawingContext(GHOST_WindowHandle windowhandle)
GHOST_TSuccess GHOST_GetModifierKeyState(GHOST_SystemHandle systemhandle, GHOST_TModifierKey mask, bool *r_is_down)
GHOST_WindowHandle GHOST_CreateWindow(GHOST_SystemHandle systemhandle, GHOST_WindowHandle parent_windowhandle, const char *title, int32_t left, int32_t top, uint32_t width, uint32_t height, GHOST_TWindowState state, bool is_dialog, GHOST_GPUSettings gpuSettings)
float GHOST_GetNativePixelSize(GHOST_WindowHandle windowhandle)
GHOST_TEventType GHOST_GetEventType(GHOST_EventHandle eventhandle)
bool GHOST_UseNativePixels(void)
void GHOST_DispatchEvents(GHOST_SystemHandle systemhandle)
GHOST_RectangleHandle GHOST_GetClientBounds(GHOST_WindowHandle windowhandle)
GHOST_TSuccess GHOST_DisposeSystem(GHOST_SystemHandle systemhandle)
GHOST_TSuccess GHOST_GetMainDisplayDimensions(GHOST_SystemHandle systemhandle, uint32_t *r_width, uint32_t *r_height)
static GHOST_SystemCocoa * ghost_system
@ GHOST_kWindowStateNormal
void * GHOST_TUserDataPtr
Definition GHOST_Types.h:78
GHOST_TEventType
@ GHOST_kEventWindowClose
@ GHOST_kEventWindowMove
@ GHOST_kEventWindowSize
@ GHOST_kEventDraggingDropDone
@ GHOST_kEventCursorMove
@ GHOST_kEventButtonUp
@ GHOST_kEventWindowActivate
@ GHOST_kEventWindowDeactivate
@ GHOST_kEventButtonDown
@ GHOST_kEventKeyDown
@ GHOST_kEventWindowDPIHintChanged
@ GHOST_kEventKeyUp
@ GHOST_kEventQuitRequest
@ GHOST_kCapabilityWindowPosition
Definition GHOST_Types.h:97
@ GHOST_kKey5
@ GHOST_kKey4
@ GHOST_kKeyNumpad3
@ GHOST_kKeyNumpad1
@ GHOST_kKey3
@ GHOST_kKeyI
@ GHOST_kKeyEnter
@ GHOST_kKeyP
@ GHOST_kKeyNumpadSlash
@ GHOST_kKeyRightArrow
@ GHOST_kKeyNumpad4
@ GHOST_kKeyMinus
@ GHOST_kKey6
@ GHOST_kKey0
@ GHOST_kKeyDownArrow
@ GHOST_kKeyNumpadPeriod
@ GHOST_kKeyF
@ GHOST_kKey1
@ GHOST_kKey8
@ GHOST_kKeyNumpad2
@ GHOST_kKeyPeriod
@ GHOST_kKeyNumpadPlus
@ GHOST_kKey9
@ GHOST_kKeyNumpad5
@ GHOST_kKeyLeftArrow
@ GHOST_kKeyEqual
@ GHOST_kKey7
@ GHOST_kKeyNumpad6
@ GHOST_kKeyNumpad8
@ GHOST_kKeyNumpad9
@ GHOST_kKeyUpArrow
@ GHOST_kKeyNumpad0
@ GHOST_kKeyA
@ GHOST_kKey2
@ GHOST_kKeyNumpad7
@ GHOST_kKeyEsc
@ GHOST_kKeyPlus
@ GHOST_kKeySlash
@ GHOST_kKeyNumpadEnter
@ GHOST_kKeyNumpadMinus
@ GHOST_kKeySpace
const void * GHOST_TEventDataPtr
@ GHOST_kModifierKeyRightControl
@ GHOST_kModifierKeyLeftControl
@ GHOST_kModifierKeyRightAlt
@ GHOST_kModifierKeyRightShift
@ GHOST_kModifierKeyLeftAlt
@ GHOST_kModifierKeyLeftShift
@ GHOST_kSuccess
Definition GHOST_Types.h:80
void(* GHOST_TBacktraceFn)(void *file_handle)
Definition GHOST_Types.h:56
@ GHOST_kDragnDropTypeFilenames
@ GHOST_kButtonMaskRight
@ GHOST_kButtonMaskLeft
@ GHOST_kButtonMaskMiddle
void GPU_render_end()
void GPU_render_step(bool force_resource_release=false)
GPUContext * GPU_context_create(void *ghost_window, void *ghost_context)
void GPU_context_begin_frame(GPUContext *ctx)
void GPU_render_begin()
GPUContext * GPU_context_active_get()
eGPUBackendType GPU_backend_type_selection_get()
void GPU_context_discard(GPUContext *)
bool GPU_backend_type_selection_detect()
void GPU_context_end_frame(GPUContext *ctx)
void GPU_context_active_set(GPUContext *)
eGPUBackendType GPU_backend_get_type()
void GPU_backend_ghost_system_set(void *ghost_system_handle)
void GPU_clear_color(float red, float green, float blue, float alpha)
void immEnd()
void immUnbindProgram()
void immUniformColor3ub(unsigned char r, unsigned char g, unsigned char b)
void immVertex2f(uint attr_id, float x, float y)
void immBindBuiltinProgram(eGPUBuiltinShader shader_id)
GPUVertFormat * immVertexFormat()
void immUniformColor3f(float r, float g, float b)
void immAttr2f(uint attr_id, float x, float y)
void immBegin(GPUPrimType, uint vertex_len)
void imm_draw_box_checker_2d_ex(float x1, float y1, float x2, float y2, const float color_primary[4], const float color_secondary[4], int checker_size)
void GPU_init()
void GPU_exit()
void GPU_matrix_identity_projection_set()
void GPU_matrix_ortho_set(float left, float right, float bottom, float top, float near, float far)
void GPU_matrix_identity_set()
void GPU_matrix_scale_2f(float x, float y)
void GPU_matrix_push()
void GPU_matrix_push_projection()
void GPU_matrix_pop_projection()
void GPU_matrix_pop()
void GPU_matrix_translate_2f(float x, float y)
@ GPU_PRIM_TRI_FAN
@ GPU_PRIM_LINES
@ GPU_SHADER_3D_UNIFORM_COLOR
@ GPU_SHADER_3D_IMAGE_COLOR
void GPU_flush()
Definition gpu_state.cc:305
@ GPU_BLEND_NONE
Definition GPU_state.hh:85
@ GPU_BLEND_ALPHA
Definition GPU_state.hh:87
void GPU_blend(eGPUBlend blend)
Definition gpu_state.cc:42
void GPU_scissor(int x, int y, int width, int height)
Definition gpu_state.cc:193
void GPU_viewport(int x, int y, int width, int height)
Definition gpu_state.cc:199
void GPU_texture_bind(GPUTexture *texture, int unit)
GPUTexture * GPU_texture_create_2d(const char *name, int width, int height, int mip_len, eGPUTextureFormat format, eGPUTextureUsage usage, const float *data)
void GPU_texture_free(GPUTexture *texture)
void GPU_texture_unbind(GPUTexture *texture)
eGPUDataFormat
@ GPU_DATA_UBYTE
@ GPU_DATA_FLOAT
@ GPU_TEXTURE_USAGE_SHADER_READ
void GPU_texture_filter_mode(GPUTexture *texture, bool use_filter)
eGPUTextureFormat
@ GPU_RGBA16F
@ GPU_RGB16F
@ GPU_RGBA8
void GPU_texture_update(GPUTexture *texture, eGPUDataFormat data_format, const void *data)
@ GPU_FETCH_FLOAT
uint GPU_vertformat_attr_add(GPUVertFormat *, blender::StringRef name, GPUVertCompType, uint comp_len, GPUVertFetchMode)
@ GPU_COMP_F32
unsigned char * IMB_display_buffer_acquire(ImBuf *ibuf, const ColorManagedViewSettings *view_settings, const ColorManagedDisplaySettings *display_settings, void **cache_handle)
bool IMB_colormanagement_setup_glsl_draw(const ColorManagedViewSettings *view_settings, const ColorManagedDisplaySettings *display_settings, float dither, bool predivide)
void IMB_colormanagement_init_default_view_settings(ColorManagedViewSettings *view_settings, const ColorManagedDisplaySettings *display_settings)
void IMB_display_buffer_release(void *cache_handle)
@ COLOR_ROLE_DEFAULT_BYTE
const char * IMB_colormanagement_role_colorspace_name_get(int role)
void IMB_colormanagement_finish_glsl_draw()
bool IMB_colormanagement_setup_glsl_draw_from_space(const ColorManagedViewSettings *view_settings, const ColorManagedDisplaySettings *display_settings, const ColorSpace *from_colorspace, float dither, bool predivide, bool do_overlay_merge)
void IMB_exit()
Definition module.cc:23
ImBuf * IMB_load_image_from_filepath(const char *filepath, const int flags, char r_colorspace[IM_MAX_SPACE]=nullptr)
Definition readimage.cc:204
ImBuf * IMB_load_image_from_memory(const unsigned char *mem, const size_t size, const int flags, const char *descr, const char *filepath=nullptr, char r_colorspace[IM_MAX_SPACE]=nullptr)
Definition readimage.cc:116
void IMB_init()
Definition module.cc:16
void IMB_freeImBuf(ImBuf *ibuf)
bool IMB_test_image(const char *filepath)
size_t IMB_get_size_in_memory(const ImBuf *ibuf)
@ IMB_FTYPE_NONE
@ IMB_PROXY_NONE
@ IB_byte_data
Read Guarded memory(de)allocation.
@ IMB_TC_NONE
Definition MOV_enums.hh:46
#define U
BMesh const char void * data
long long int int64_t
unsigned long long int uint64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
static btDbvtVolume bounds(btDbvtNode **leaves, int count)
Definition btDbvt.cpp:299
uint pos
TEX_TEMPLATE DataVec texture(T, FltCoord, float=0.0f) RET
VecBase< float, D > step(VecOp< float, D >, VecOp< float, D >) RET
#define printf(...)
#define MEM_SAFE_FREE(v)
#define PRIu64
Definition inttypes.h:132
#define PRId64
Definition inttypes.h:78
format
#define LOG(severity)
Definition log.h:32
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
uint(* MEM_get_memory_blocks_in_use)(void)
Definition mallocn.cc:71
void(* MEM_printmemlist)(void)
Definition mallocn.cc:64
static uint totblock
void MOV_close(MovieReader *anim)
Definition movie_read.cc:58
MovieReader * MOV_open_file(const char *filepath, int ib_flags, int streamindex, char colorspace[IM_MAX_SPACE])
int MOV_get_duration_frames(MovieReader *anim, IMB_Timecode_Type tc)
float MOV_get_fps(const MovieReader *anim)
ImBuf * MOV_decode_frame(MovieReader *anim, int position, IMB_Timecode_Type tc, IMB_Proxy_Size preview_size)
void MOV_init()
bool MOV_is_movie_file(const char *filepath)
void MOV_exit()
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
GHOST_TDrawingContextType context_type
GHOST_GPUDevice preferred_device
GHOST_TDragnDropTypes dataType
GHOST_TDragnDropDataPtr data
eWS_Qual qual
GHOST_SystemHandle system
GPUContext * gpu_context
GHOST_WindowHandle window
const ColorSpace * colorspace
const ColorSpace * colorspace
char filepath[IMB_FILEPATH_SIZE]
ImBufFloatBuffer float_buffer
ImBufByteBuffer byte_buffer
unsigned char planes
enum eImbFileType ftype
void * data
struct LinkData * prev
void * last
void * first
const char * filepath
PlayAnimPict * prev
char * error_message
size_t size_in_memory
LinkData * frame_cache_node
PlayAnimPict * next
MovieReader * anim
char ** argv
ColorManagedViewSettings view_settings
ColorManagedDisplaySettings display_settings
blender::int2 size
short next_frame
blender::int2 ibuf_size
bool show_frame_indicator
PlayDisplayContext display_ctx
bool no_frame_skip
char ** argv_next
struct PlayAnimPict * picture
bool draw_flip[2]
ListBase picsbase
short direction
GhostData ghost_data
bool single_step
bool need_frame_update
int frame_cursor_x
float xmax
float xmin
float ymax
float ymin
i
Definition text_draw.cc:230
static blender::int2 playanim_window_size_get(GHOST_WindowHandle ghost_window)
static void playanim_audio_stop(PlayState &)
static void build_pict_list_from_image_sequence(ListBase &picsbase, GhostData &ghost_data, const PlayDisplayContext &display_ctx, const char *filepath_first, const int frame_offset, const int totframes, const int frame_step, const bool *loading_p)
static void playanim_audio_resume(PlayState &ps)
static PlayAnimPict * playanim_step(PlayAnimPict *playanim, int step)
static bool playanim_window_font_scale_from_dpi(PlayState &ps)
void WM_main_playanim(int argc, const char **argv)
size_t memory_limit
static void frame_cache_remove(PlayAnimPict *pic)
static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_next)
static void * ocio_transform_ibuf(const PlayDisplayContext &display_ctx, ImBuf *ibuf, bool *r_glsl_used, eGPUTextureFormat *r_format, eGPUDataFormat *r_data, void **r_buffer_cache_handle)
static void frame_cache_touch(PlayAnimPict *pic)
static void frame_cache_add(PlayAnimPict *pic)
static struct @103222067146275024323264252113111365357156357266 g_frame_cache
static void playanim_toscreen_ex(GhostData &ghost_data, const PlayDisplayContext &display_ctx, const PlayAnimPict *picture, ImBuf *ibuf, const int font_id, const int frame_step, const float draw_zoom, const bool draw_flip[2], const float frame_indicator_factor)
static bool buffer_from_filepath(const char *filepath, void **r_mem, size_t *r_size, char **r_error_message)
static struct @017106307321020001076167033344133163273125220047 g_playanim
static void playanim_event_qual_update(GhostData &ghost_data)
bool from_disk
static void playanim_window_zoom(PlayState &ps, const float zoom_offset)
static int pupdate_time()
static void frame_cache_limit_apply(ImBuf *ibuf_keep)
size_t pics_size_in_memory
static void playanim_change_frame(PlayState &ps)
static void update_sound_fps()
int pics_len
static void playanim_gpu_matrix()
static void playanim_toscreen_on_load(GhostData &ghost_data, const PlayDisplayContext &display_ctx, const PlayAnimPict *picture, ImBuf *ibuf)
static void playanim_toscreen(PlayState &ps, const PlayAnimPict *picture, ImBuf *ibuf)
static void playanim_change_frame_tag(PlayState &ps, int cx)
eWS_Qual
@ WS_QUAL_LCTRL
@ WS_QUAL_RMOUSE
@ WS_QUAL_RALT
@ WS_QUAL_MMOUSE
@ WS_QUAL_LMOUSE
@ WS_QUAL_LALT
@ WS_QUAL_RCTRL
@ WS_QUAL_LSHIFT
@ WS_QUAL_RSHIFT
#define WS_QUAL_SHIFT
#define PLAY_FRAME_CACHE_MAX
#define WS_QUAL_CTRL
double total_time
static GHOST_WindowHandle playanim_window_open(GHOST_SystemHandle ghost_system, const char *title, int posx, int posy, int sizex, int sizey)
static void draw_display_buffer(const PlayDisplayContext &display_ctx, ImBuf *ibuf, const rctf *canvas, const bool draw_flip[2])
static bool frame_cache_limit_exceeded()
static ImBuf * ibuf_from_picture(PlayAnimPict *pic)
ListBase pics
static bool ghost_event_proc(GHOST_EventHandle ghost_event, GHOST_TUserDataPtr ps_void_ptr)
static void build_pict_list_from_anim(ListBase &picsbase, GhostData &ghost_data, const PlayDisplayContext &display_ctx, const char *filepath_first, const int frame_offset)
double swap_time
static void build_pict_list(ListBase &picsbase, GhostData &ghost_data, const PlayDisplayContext &display_ctx, const char *filepath_first, const int totframes, const int frame_step, bool *loading_p)
#define WS_QUAL_MOUSE
GHOST_TDrawingContextType wm_ghost_drawing_context_type(const eGPUBackendType gpu_backend)