Blender V5.0
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_string_utf8.h"
40#include "BLI_system.h"
41#include "BLI_time.h"
42#include "BLI_utildefines.h"
43
45#include "IMB_imbuf.hh"
46#include "IMB_imbuf_types.hh"
47
48#include "MOV_read.hh"
49#include "MOV_util.hh"
50
51#include "BKE_blender.hh"
52#include "BKE_image.hh"
53
54#include "BIF_glutil.hh"
55
56#include "GPU_context.hh"
57#include "GPU_framebuffer.hh"
58#include "GPU_immediate.hh"
59#include "GPU_immediate_util.hh"
60#include "GPU_init_exit.hh"
61#include "GPU_matrix.hh"
62#include "GPU_state.hh"
63
64#include "DNA_scene_types.h"
65#include "DNA_userdef_types.h"
66
67#include "BLF_api.hh"
68#include "GHOST_C-api.h"
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 = {"image"};
95
97static const char *message_prefix = "Animation Player";
98
99struct PlayState;
100static void playanim_window_zoom(PlayState &ps, const float zoom_offset);
102
103/* -------------------------------------------------------------------- */
106
112static bool buffer_from_filepath(const char *filepath,
113 void **r_mem,
114 size_t *r_size,
115 char **r_error_message)
116{
117 errno = 0;
118 const int file = BLI_open(filepath, O_BINARY | O_RDONLY, 0);
119 if (UNLIKELY(file == -1)) {
120 *r_error_message = BLI_sprintfN("failure '%s' to open file", strerror(errno));
121 return false;
122 }
123
124 bool success = false;
125 uchar *mem = nullptr;
126 const size_t size = BLI_file_descriptor_size(file);
127 int64_t size_read;
128 if (UNLIKELY(size == size_t(-1))) {
129 *r_error_message = BLI_sprintfN("failure '%s' to access size", strerror(errno));
130 }
131 else if (r_mem && UNLIKELY(!(mem = MEM_malloc_arrayN<uchar>(size, __func__)))) {
132 *r_error_message = BLI_sprintfN("error allocating buffer %" PRIu64 " size", uint64_t(size));
133 }
134 else if (r_mem && UNLIKELY((size_read = BLI_read(file, mem, size)) != size)) {
135 *r_error_message = BLI_sprintfN(
136 "error '%s' reading file "
137 "(expected %" PRIu64 ", was %" PRId64 ")",
138 strerror(errno),
139 uint64_t(size),
140 size_read);
141 }
142 else {
143 *r_size = size;
144 if (r_mem) {
145 *r_mem = mem;
146 mem = nullptr; /* `r_mem` owns, don't free on exit. */
147 }
148 success = true;
149 }
150
151 MEM_SAFE_FREE(mem);
152 close(file);
153 return success;
154}
155
157
160 WS_QUAL_LSHIFT = (1 << 0),
161 WS_QUAL_RSHIFT = (1 << 1),
162#define WS_QUAL_SHIFT (WS_QUAL_LSHIFT | WS_QUAL_RSHIFT)
163 WS_QUAL_LALT = (1 << 2),
164 WS_QUAL_RALT = (1 << 3),
165#define WS_QUAL_ALT (WS_QUAL_LALT | WS_QUAL_RALT)
166 WS_QUAL_LCTRL = (1 << 4),
167 WS_QUAL_RCTRL = (1 << 5),
168#define WS_QUAL_CTRL (WS_QUAL_LCTRL | WS_QUAL_RCTRL)
169 WS_QUAL_LMOUSE = (1 << 16),
170 WS_QUAL_MMOUSE = (1 << 17),
171 WS_QUAL_RMOUSE = (1 << 18),
173};
175
176struct GhostData {
177 GHOST_SystemHandle system;
178 GHOST_WindowHandle window;
179
181 GPUContext *gpu_context;
182
185};
186
187struct PlayArgs {
188 int argc;
189 char **argv;
190};
191
204
276
277/* For debugging. */
278#if 0
279static void print_ps(const PlayState &ps)
280{
281 printf("ps:\n");
282 printf(" direction=%d,\n", int(ps.direction));
283 printf(" once=%d,\n", ps.once);
284 printf(" pingpong=%d,\n", ps.pingpong);
285 printf(" no_frame_skip=%d,\n", ps.no_frame_skip);
286 printf(" single_step=%d,\n", ps.single_step);
287 printf(" wait=%d,\n", ps.wait);
288 printf(" stopped=%d,\n", ps.stopped);
289 printf(" go=%d,\n\n", ps.go);
290 fflush(stdout);
291}
292#endif
293
294static blender::int2 playanim_window_size_get(GHOST_WindowHandle ghost_window)
295{
296 ;
297 GHOST_RectangleHandle bounds = GHOST_GetClientBounds(ghost_window);
298 const float native_pixel_size = GHOST_GetNativePixelSize(ghost_window);
299 const blender::int2 window_size = {
300 int(GHOST_GetWidthRectangle(bounds) * native_pixel_size),
301 int(GHOST_GetHeightRectangle(bounds) * native_pixel_size),
302 };
304 return window_size;
305}
306
308{
309 /* Unified matrix, note it affects offset for drawing. */
310 /* NOTE: cannot use GPU_matrix_ortho_2d_set here because shader ignores. */
311 GPU_matrix_ortho_set(0.0f, 1.0f, 0.0f, 1.0f, -1.0, 1.0f);
312}
313
314/* Implementation. */
315static void playanim_event_qual_update(GhostData &ghost_data)
316{
317 bool val;
318
319 /* Shift. */
321 SET_FLAG_FROM_TEST(ghost_data.qual, val, WS_QUAL_LSHIFT);
322
324 SET_FLAG_FROM_TEST(ghost_data.qual, val, WS_QUAL_RSHIFT);
325
326 /* Control. */
328 SET_FLAG_FROM_TEST(ghost_data.qual, val, WS_QUAL_LCTRL);
329
331 SET_FLAG_FROM_TEST(ghost_data.qual, val, WS_QUAL_RCTRL);
332
333 /* Alt. */
335 SET_FLAG_FROM_TEST(ghost_data.qual, val, WS_QUAL_LALT);
336
338 SET_FLAG_FROM_TEST(ghost_data.qual, val, WS_QUAL_RALT);
339}
340
344 size_t size;
346 const char *filepath;
351 int frame;
353
354#ifdef USE_FRAME_CACHE_LIMIT
358#endif
359};
360
366static struct {
368 double swap_time;
370#ifdef WITH_AUDASPACE
371 double fps_movie;
372#endif
373} g_playanim = {
374 /*from_disk*/ false,
375 /*swap_time*/ 0.04,
376 /*total_time*/ 0.0,
377#ifdef WITH_AUDASPACE
378 /*fps_movie*/ 0.0,
379#endif
381
382#ifdef USE_FRAME_CACHE_LIMIT
383static struct {
392} g_frame_cache = {
393 /*pics*/ {nullptr, nullptr},
394 /*pics_len*/ 0,
395 /*pics_size_in_memory*/ 0,
396 /*memory_limit*/ 0,
398
400{
403 g_frame_cache.pics_len++;
404
405 if (g_frame_cache.memory_limit != 0) {
406 BLI_assert(pic->size_in_memory == 0);
408 g_frame_cache.pics_size_in_memory += pic->size_in_memory;
409 }
410}
411
413{
414 LinkData *node = pic->frame_cache_node;
415 IMB_freeImBuf(pic->ibuf);
416 if (g_frame_cache.memory_limit != 0) {
417 BLI_assert(pic->size_in_memory != 0);
418 g_frame_cache.pics_size_in_memory -= pic->size_in_memory;
419 pic->size_in_memory = 0;
420 }
421 pic->ibuf = nullptr;
422 pic->frame_cache_node = nullptr;
423 BLI_freelinkN(&g_frame_cache.pics, node);
424 g_frame_cache.pics_len--;
425}
426
427/* Don't free the current frame by moving it to the head of the list. */
434
436{
437 return g_frame_cache.memory_limit ?
438 (g_frame_cache.pics_size_in_memory > g_frame_cache.memory_limit) :
440}
441
442static void frame_cache_limit_apply(ImBuf *ibuf_keep)
443{
444 /* Really basic memory conservation scheme. Keep frames in a FIFO queue. */
445 LinkData *node = static_cast<LinkData *>(g_frame_cache.pics.last);
446 while (node && frame_cache_limit_exceeded()) {
447 PlayAnimPict *pic = static_cast<PlayAnimPict *>(node->data);
448 BLI_assert(pic->frame_cache_node == node);
449
450 node = node->prev;
451 if (pic->ibuf && pic->ibuf != ibuf_keep) {
453 }
454 }
455}
456
457#endif /* USE_FRAME_CACHE_LIMIT */
458
460{
461 ImBuf *ibuf = nullptr;
462
463 if (pic->ibuf) {
464 ibuf = pic->ibuf;
465 }
466 else if (pic->anim) {
468 }
469 else if (pic->mem) {
470 /* Use correct color-space here. */
472 pic->mem, pic->size, pic->IB_flags, pic->filepath, pic->filepath);
473 }
474 else {
475 /* Use correct color-space here. */
477 }
478
479 return ibuf;
480}
481
483{
484 if (step > 0) {
485 while (step-- && playanim) {
486 playanim = playanim->next;
487 }
488 }
489 else if (step < 0) {
490 while (step++ && playanim) {
491 playanim = playanim->prev;
492 }
493 }
494 return playanim;
495}
496
497static int pupdate_time()
498{
499 static double time_last;
500
501 double time = BLI_time_now_seconds();
502
503 g_playanim.total_time += (time - time_last);
504 time_last = time;
505 return (g_playanim.total_time < 0.0);
506}
507
508static void *ocio_transform_ibuf(const PlayDisplayContext &display_ctx,
509 ImBuf *ibuf,
510 bool *r_glsl_used,
512 eGPUDataFormat *r_data,
513 void **r_buffer_cache_handle)
514{
515 void *display_buffer;
516 bool force_fallback = false;
517 *r_glsl_used = false;
518 force_fallback |= (ED_draw_imbuf_method(ibuf) != IMAGE_DRAW_METHOD_GLSL);
519 force_fallback |= (ibuf->dither != 0.0f);
520
521 /* Default. */
522 *r_format = blender::gpu::TextureFormat::UNORM_8_8_8_8;
523 *r_data = GPU_DATA_UBYTE;
524
525 /* Fallback to CPU based color space conversion. */
526 if (force_fallback) {
527 *r_glsl_used = false;
528 display_buffer = nullptr;
529 }
530 else if (ibuf->float_buffer.data) {
531 display_buffer = ibuf->float_buffer.data;
532
533 *r_data = GPU_DATA_FLOAT;
534 if (ibuf->channels == 4) {
535 *r_format = blender::gpu::TextureFormat::SFLOAT_16_16_16_16;
536 }
537 else if (ibuf->channels == 3) {
538 /* Alpha is implicitly 1. */
539 *r_format = blender::gpu::TextureFormat::SFLOAT_16_16_16;
540 }
541
542 if (ibuf->float_buffer.colorspace) {
544 &display_ctx.display_settings,
546 ibuf->dither,
547 false,
548 false);
549 }
550 else {
552 &display_ctx.view_settings, &display_ctx.display_settings, ibuf->dither, false);
553 }
554 }
555 else if (ibuf->byte_buffer.data) {
556 display_buffer = ibuf->byte_buffer.data;
558 &display_ctx.display_settings,
560 ibuf->dither,
561 false,
562 false);
563 }
564 else {
565 display_buffer = nullptr;
566 }
567
568 /* There is data to be displayed, but GLSL is not initialized
569 * properly, in this case we fallback to CPU-based display transform. */
570 if ((ibuf->byte_buffer.data || ibuf->float_buffer.data) && !*r_glsl_used) {
571 display_buffer = IMB_display_buffer_acquire(
572 ibuf, &display_ctx.view_settings, &display_ctx.display_settings, r_buffer_cache_handle);
573 *r_format = blender::gpu::TextureFormat::UNORM_8_8_8_8;
574 *r_data = GPU_DATA_UBYTE;
575 }
576
577 return display_buffer;
578}
579
580static void draw_display_buffer(const PlayDisplayContext &display_ctx,
581 ImBuf *ibuf,
582 const rctf *canvas,
583 const bool draw_flip[2])
584{
585 /* Format needs to be created prior to any #immBindShader call.
586 * Do it here because OCIO binds its own shader. */
589 bool glsl_used = false;
590 GPUVertFormat *imm_format = immVertexFormat();
591 uint pos = GPU_vertformat_attr_add(imm_format, "pos", blender::gpu::VertAttrType::SFLOAT_32_32);
592 uint texCoord = GPU_vertformat_attr_add(
593 imm_format, "texCoord", blender::gpu::VertAttrType::SFLOAT_32_32);
594
595 void *buffer_cache_handle = nullptr;
596 void *display_buffer = ocio_transform_ibuf(
597 display_ctx, ibuf, &glsl_used, &format, &data, &buffer_cache_handle);
598
599 /* NOTE: This may fail, especially for large images that exceed the GPU's texture size limit.
600 * Large images could be supported although this isn't so common for animation playback. */
602 "display_buf", ibuf->x, ibuf->y, 1, format, GPU_TEXTURE_USAGE_SHADER_READ, nullptr);
603
604 if (texture) {
605 GPU_texture_update(texture, data, display_buffer);
607
609 }
610
611 if (!glsl_used) {
613 immUniformColor3f(1.0f, 1.0f, 1.0f);
614 }
615
617
618 rctf preview;
619 BLI_rctf_init(&preview, 0.0f, 1.0f, 0.0f, 1.0f);
620 if (draw_flip[0]) {
621 std::swap(preview.xmin, preview.xmax);
622 }
623 if (draw_flip[1]) {
624 std::swap(preview.ymin, preview.ymax);
625 }
626
627 immAttr2f(texCoord, preview.xmin, preview.ymin);
628 immVertex2f(pos, canvas->xmin, canvas->ymin);
629
630 immAttr2f(texCoord, preview.xmin, preview.ymax);
631 immVertex2f(pos, canvas->xmin, canvas->ymax);
632
633 immAttr2f(texCoord, preview.xmax, preview.ymax);
634 immVertex2f(pos, canvas->xmax, canvas->ymax);
635
636 immAttr2f(texCoord, preview.xmax, preview.ymin);
637 immVertex2f(pos, canvas->xmax, canvas->ymin);
638
639 immEnd();
640
641 if (texture) {
644 }
645
646 if (!glsl_used) {
648 }
649 else {
651 }
652
653 if (buffer_cache_handle) {
654 IMB_display_buffer_release(buffer_cache_handle);
655 }
656}
657
665static void playanim_toscreen_ex(GhostData &ghost_data,
666 const PlayDisplayContext &display_ctx,
667 const PlayAnimPict *picture,
668 ImBuf *ibuf,
669 /* Run-time drawing arguments (not used on-load). */
670 const int font_id,
671 const int frame_step,
672 const float draw_zoom,
673 const bool draw_flip[2],
674 const float frame_indicator_factor)
675{
678
680 GPUContext *restore_context = GPU_context_active_get();
681
684
685 GPU_clear_color(0.1f, 0.1f, 0.1f, 0.0f);
686
687 /* A null `ibuf` is an exceptional case and should almost never happen.
688 * if it does, this function displays a warning along with the file-path that failed. */
689 if (ibuf) {
690 /* Size within window. */
691 float span_x = (draw_zoom * ibuf->x) / float(display_ctx.size[0]);
692 float span_y = (draw_zoom * ibuf->y) / float(display_ctx.size[1]);
693
694 /* Offset within window. */
695 float offs_x = 0.5f * (1.0f - span_x);
696 float offs_y = 0.5f * (1.0f - span_y);
697
698 CLAMP(offs_x, 0.0f, 1.0f);
699 CLAMP(offs_y, 0.0f, 1.0f);
700
701 /* Checkerboard for case alpha. */
702 if (ibuf->planes == 32) {
704
706 offs_y,
707 offs_x + span_x,
708 offs_y + span_y,
709 blender::float4{0.15, 0.15, 0.15, 1.0},
710 blender::float4{0.20, 0.20, 0.20, 1.0},
711 8);
712 }
713 rctf canvas;
714 BLI_rctf_init(&canvas, offs_x, offs_x + span_x, offs_y, offs_y + span_y);
715
716 draw_display_buffer(display_ctx, ibuf, &canvas, draw_flip);
717
719 }
720
721 pupdate_time();
722
723 if ((font_id != -1) && picture) {
724 const int font_margin = int(10 * display_ctx.ui_scale);
725 float fsizex_inv, fsizey_inv;
726 char label[32 + FILE_MAX];
727 if (ibuf) {
728 SNPRINTF(label, "%s | %.2f frames/s", picture->filepath, frame_step / g_playanim.swap_time);
729 }
730 else {
731 SNPRINTF(label,
732 "%s | %s",
733 picture->filepath,
734 picture->error_message ? picture->error_message : "<unknown error>");
735 }
736
737 const blender::int2 window_size = playanim_window_size_get(ghost_data.window);
738 fsizex_inv = 1.0f / window_size[0];
739 fsizey_inv = 1.0f / window_size[1];
740
741 BLF_color4f(font_id, 1.0, 1.0, 1.0, 1.0);
742
743 /* FIXME(@ideasman42): Font positioning doesn't work because the aspect causes the position
744 * to be rounded to zero, investigate making BLF support this,
745 * for now use GPU matrix API to adjust the text position. */
746#if 0
747 BLF_enable(font_id, BLF_ASPECT);
748 BLF_aspect(font_id, fsizex_inv, fsizey_inv, 1.0f);
749 BLF_position(font_id, font_margin * fsizex_inv, font_margin * fsizey_inv, 0.0f);
750 BLF_draw(font_id, label, sizeof(label));
751#else
753 GPU_matrix_scale_2f(fsizex_inv, fsizey_inv);
754 GPU_matrix_translate_2f(font_margin, font_margin);
755 BLF_position(font_id, 0, 0, 0.0f);
756 BLF_draw(font_id, label, sizeof(label));
758#endif
759 }
760
761 if (frame_indicator_factor != -1.0f) {
762 float fac = frame_indicator_factor;
763 fac = 2.0f * fac - 1.0f;
768
770 immVertexFormat(), "pos", blender::gpu::VertAttrType::SFLOAT_32_32);
771
773 immUniformColor3ub(0, 255, 0);
774
776 immVertex2f(pos, fac, -1.0f);
777 immVertex2f(pos, fac, 1.0f);
778 immEnd();
779
781
784 }
785
788 GPU_flush();
789 }
790
793 GPU_context_active_set(restore_context);
795}
796
797static void playanim_toscreen_on_load(GhostData &ghost_data,
798 const PlayDisplayContext &display_ctx,
799 const PlayAnimPict *picture,
800 ImBuf *ibuf)
801{
802 const int font_id = -1; /* Don't draw text. */
803 const int frame_step = -1;
804 const float zoom = 1.0f;
805 const float frame_indicator_factor = -1.0f;
806 const bool draw_flip[2] = {false, false};
807
808 playanim_toscreen_ex(ghost_data,
809 display_ctx,
810 picture,
811 ibuf,
812 font_id,
813 frame_step,
814 zoom,
815 draw_flip,
816 frame_indicator_factor);
817}
818
819static void playanim_toscreen(PlayState &ps, const PlayAnimPict *picture, ImBuf *ibuf)
820{
821 float frame_indicator_factor = -1.0f;
822 if (ps.show_frame_indicator) {
823 const int frame_range = static_cast<const PlayAnimPict *>(ps.picsbase.last)->frame -
824 static_cast<const PlayAnimPict *>(ps.picsbase.first)->frame;
825 if (frame_range > 0) {
826 frame_indicator_factor = float(double(picture->frame) / double(frame_range));
827 }
828 else {
830 "Multiple frames without a valid range!");
831 }
832 }
833
834 int font_id = -1;
836 /* Always inform the user of an error, this should be an exceptional case. */
837 (ibuf == nullptr))
838 {
839 font_id = ps.font_id;
840 }
841
842 BLI_assert(ps.loading == false);
844 ps.display_ctx,
845 picture,
846 ibuf,
847 font_id,
848 ps.frame_step,
849 ps.zoom,
850 ps.draw_flip,
851 frame_indicator_factor);
852}
853
855 GhostData &ghost_data,
856 const PlayDisplayContext &display_ctx,
857 const char *filepath_first,
858 const int frame_offset)
859{
860 /* OCIO_TODO: support different input color space. */
861 MovieReader *anim = MOV_open_file(filepath_first, IB_byte_data, 0, false, nullptr);
862 if (anim == nullptr) {
863 CLOG_WARN(&LOG, "couldn't open anim '%s'", filepath_first);
864 return;
865 }
866
868 if (ibuf) {
869 playanim_toscreen_on_load(ghost_data, display_ctx, nullptr, ibuf);
870 IMB_freeImBuf(ibuf);
871 }
872
873 for (int pic = 0; pic < MOV_get_duration_frames(anim, IMB_TC_NONE); pic++) {
874 PlayAnimPict *picture = MEM_callocN<PlayAnimPict>("Pict");
875 picture->anim = anim;
876 picture->frame = pic + frame_offset;
877 picture->IB_flags = IB_byte_data;
878 picture->filepath = BLI_sprintfN("%s : %4.d", filepath_first, pic + 1);
879 BLI_addtail(&picsbase, picture);
880 }
881
882 const PlayAnimPict *picture = static_cast<const PlayAnimPict *>(picsbase.last);
883 if (!(picture && picture->anim == anim)) {
884 MOV_close(anim);
885 CLOG_WARN(&LOG, "no frames added for: '%s'", filepath_first);
886 }
887}
888
890 GhostData &ghost_data,
891 const PlayDisplayContext &display_ctx,
892 const char *filepath_first,
893 const int frame_offset,
894 const int totframes,
895 const int frame_step,
896 const bool *loading_p)
897{
898 /* Load images into cache until the cache is full,
899 * this resolves choppiness for images that are slow to load, see: #81751. */
900 bool fill_cache = (
901#ifdef USE_FRAME_CACHE_LIMIT
902 true
903#else
904 false
905#endif
906 );
907
908 int fp_framenr;
909 struct {
910 char head[FILE_MAX], tail[FILE_MAX];
911 ushort digits;
912 } fp_decoded;
913
914 char filepath[FILE_MAX];
915 STRNCPY(filepath, filepath_first);
916 fp_framenr = BLI_path_sequence_decode(filepath,
917 fp_decoded.head,
918 sizeof(fp_decoded.head),
919 fp_decoded.tail,
920 sizeof(fp_decoded.tail),
921 &fp_decoded.digits);
922
923 pupdate_time();
924 g_playanim.total_time = 1.0;
925
926 for (int pic = 0; pic < totframes; pic++) {
927 if (!IMB_test_image(filepath)) {
928 break;
929 }
930
931 bool has_error = false;
932 char *error_message = nullptr;
933 void *mem = nullptr;
934 size_t size = -1;
936 filepath, g_playanim.from_disk ? nullptr : &mem, &size, &error_message))
937 {
938 has_error = true;
939 size = 0;
940 }
941
942 PlayAnimPict *picture = MEM_callocN<PlayAnimPict>("picture");
943 picture->size = size;
944 picture->IB_flags = IB_byte_data;
945 picture->mem = static_cast<uchar *>(mem);
946 picture->filepath = BLI_strdup(filepath);
947 picture->error_message = error_message;
948 picture->frame = pic + frame_offset;
949 BLI_addtail(&picsbase, picture);
950
951 pupdate_time();
952
953 const bool display_imbuf = g_playanim.total_time > 1.0;
954
955 if (has_error) {
956 CLOG_WARN(&LOG,
957 "Picture %s failed: %s",
958 filepath,
959 error_message ? error_message : "<unknown error>");
960 }
961 else if (display_imbuf || fill_cache) {
962 /* OCIO_TODO: support different input color space. */
963 ImBuf *ibuf = ibuf_from_picture(picture);
964
965 if (ibuf) {
966 if (display_imbuf) {
967 playanim_toscreen_on_load(ghost_data, display_ctx, picture, ibuf);
968 }
969#ifdef USE_FRAME_CACHE_LIMIT
970 if (fill_cache) {
971 picture->ibuf = ibuf;
972 frame_cache_add(picture);
973 fill_cache = !frame_cache_limit_exceeded();
974 }
975 else
976#endif
977 {
978 IMB_freeImBuf(ibuf);
979 }
980 }
981
982 if (display_imbuf) {
983 pupdate_time();
984 g_playanim.total_time = 0.0;
985 }
986 }
987
988 /* Create a new file-path each time. */
989 fp_framenr += frame_step;
991 sizeof(filepath),
992 fp_decoded.head,
993 fp_decoded.tail,
994 fp_decoded.digits,
995 fp_framenr);
996
997 while (GHOST_ProcessEvents(ghost_data.system, false)) {
998 GHOST_DispatchEvents(ghost_data.system);
999 if (*loading_p == false) {
1000 break;
1001 }
1002 }
1003 }
1004}
1005
1006static void build_pict_list(ListBase &picsbase,
1007 GhostData &ghost_data,
1008 const PlayDisplayContext &display_ctx,
1009 const char *filepath_first,
1010 const int totframes,
1011 const int frame_step,
1012 bool *loading_p)
1013{
1014 *loading_p = true;
1015
1016 /* NOTE(@ideasman42): When loading many files (e.g. expanded from shell globing)
1017 * it's important the frame number increases each time. Otherwise playing `*.png`
1018 * in a directory will expand into many arguments, each calling this function adding
1019 * a frame that's set to zero. */
1020 const PlayAnimPict *picture_last = static_cast<PlayAnimPict *>(picsbase.last);
1021 const int frame_offset = picture_last ? (picture_last->frame + 1) : 0;
1022
1023 bool do_image_load = false;
1024 if (MOV_is_movie_file(filepath_first)) {
1025 build_pict_list_from_anim(picsbase, ghost_data, display_ctx, filepath_first, frame_offset);
1026
1027 if (picsbase.last == picture_last) {
1028 /* FFMPEG detected JPEG2000 as a video which would load with zero duration.
1029 * Resolve this by using images as a fallback when a video file has no frames to display. */
1030 do_image_load = true;
1031 }
1032 }
1033 else {
1034 do_image_load = true;
1035 }
1036
1037 if (do_image_load) {
1039 ghost_data,
1040 display_ctx,
1041 filepath_first,
1042 frame_offset,
1043 totframes,
1044 frame_step,
1045 loading_p);
1046 }
1047
1048 *loading_p = false;
1049}
1050
1051static void update_sound_fps()
1052{
1053#ifdef WITH_AUDASPACE
1054 if (g_audaspace.playback_handle) {
1055 /* Swap-time stores the 1.0/fps ratio. */
1056 double speed = 1.0 / (g_playanim.swap_time * g_playanim.fps_movie);
1057
1058 AUD_Handle_setPitch(g_audaspace.playback_handle, speed);
1059 }
1060#endif
1061}
1062
1063static void playanim_change_frame_tag(PlayState &ps, int cx)
1064{
1065 ps.need_frame_update = true;
1066 ps.frame_cursor_x = cx;
1067}
1068
1070{
1071 if (!ps.need_frame_update) {
1072 return;
1073 }
1075 return;
1076 }
1077
1079 const int i_last = static_cast<PlayAnimPict *>(ps.picsbase.last)->frame;
1080 /* Without this the frame-indicator location isn't closest to the cursor. */
1081 const int correct_rounding = (window_size[0] / (i_last + 1)) / 2;
1082 const int i = clamp_i(
1083 (i_last * (ps.frame_cursor_x + correct_rounding)) / window_size[0], 0, i_last);
1084
1085#ifdef WITH_AUDASPACE
1086 if (g_audaspace.scrub_handle) {
1087 AUD_Handle_stop(g_audaspace.scrub_handle);
1088 g_audaspace.scrub_handle = nullptr;
1089 }
1090
1091 if (g_audaspace.playback_handle) {
1092 AUD_Status status = AUD_Handle_getStatus(g_audaspace.playback_handle);
1093 if (status != AUD_STATUS_PLAYING) {
1094 AUD_Handle_stop(g_audaspace.playback_handle);
1095 g_audaspace.playback_handle = AUD_Device_play(
1096 g_audaspace.audio_device, g_audaspace.source, 1);
1097 if (g_audaspace.playback_handle) {
1098 AUD_Handle_setPosition(g_audaspace.playback_handle, i / g_playanim.fps_movie);
1099 g_audaspace.scrub_handle = AUD_pauseAfter(g_audaspace.playback_handle,
1100 1.0 / g_playanim.fps_movie);
1101 }
1103 }
1104 else {
1105 AUD_Handle_setPosition(g_audaspace.playback_handle, i / g_playanim.fps_movie);
1106 g_audaspace.scrub_handle = AUD_pauseAfter(g_audaspace.playback_handle,
1107 1.0 / g_playanim.fps_movie);
1108 }
1109 }
1110 else if (g_audaspace.source) {
1111 g_audaspace.playback_handle = AUD_Device_play(g_audaspace.audio_device, g_audaspace.source, 1);
1112 if (g_audaspace.playback_handle) {
1113 AUD_Handle_setPosition(g_audaspace.playback_handle, i / g_playanim.fps_movie);
1114 g_audaspace.scrub_handle = AUD_pauseAfter(g_audaspace.playback_handle,
1115 1.0 / g_playanim.fps_movie);
1116 }
1118 }
1119#endif
1120
1121 ps.picture = static_cast<PlayAnimPict *>(BLI_findlink(&ps.picsbase, i));
1122 BLI_assert(ps.picture != nullptr);
1123
1124 ps.single_step = true;
1125 ps.wait = false;
1126 ps.next_frame = 0;
1127
1128 ps.need_frame_update = false;
1129}
1130
1132{
1133#ifdef WITH_AUDASPACE
1134 /* TODO: store in ps direct? */
1135 const int i = BLI_findindex(&ps.picsbase, ps.picture);
1136 if (g_audaspace.playback_handle) {
1137 AUD_Handle_stop(g_audaspace.playback_handle);
1138 }
1139 g_audaspace.playback_handle = AUD_Device_play(g_audaspace.audio_device, g_audaspace.source, 1);
1140 if (g_audaspace.playback_handle) {
1141 AUD_Handle_setPosition(g_audaspace.playback_handle, i / g_playanim.fps_movie);
1142 }
1144#else
1145 UNUSED_VARS(ps);
1146#endif
1147}
1148
1149static void playanim_audio_stop(PlayState & /*ps*/)
1150{
1151#ifdef WITH_AUDASPACE
1152 if (g_audaspace.playback_handle) {
1153 AUD_Handle_stop(g_audaspace.playback_handle);
1154 g_audaspace.playback_handle = nullptr;
1155 }
1156#endif
1157}
1158
1159static bool ghost_event_proc(GHOST_EventHandle ghost_event, GHOST_TUserDataPtr ps_void_ptr)
1160{
1161 PlayState &ps = *static_cast<PlayState *>(ps_void_ptr);
1162 const GHOST_TEventType type = GHOST_GetEventType(ghost_event);
1164 /* Convert ghost event into value keyboard or mouse. */
1165 const int val = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventButtonDown);
1166 GHOST_SystemHandle ghost_system = ps.ghost_data.system;
1167 GHOST_WindowHandle ghost_window = ps.ghost_data.window;
1168
1169 // print_ps(ps);
1170
1172
1173 /* First check if we're busy loading files. */
1174 if (ps.loading) {
1175 switch (type) {
1177 case GHOST_kEventKeyUp: {
1178 const GHOST_TEventKeyData *key_data = static_cast<const GHOST_TEventKeyData *>(data);
1179 switch (key_data->key) {
1180 case GHOST_kKeyEsc:
1181 ps.loading = false;
1182 break;
1183 default:
1184 break;
1185 }
1186 break;
1187 }
1188 default:
1189 break;
1190 }
1191 return true;
1192 }
1193
1194 if (ps.wait && ps.stopped == false) {
1195 ps.stopped = true;
1196 }
1197
1198 if (ps.wait) {
1199 pupdate_time();
1200 g_playanim.total_time = 0.0;
1201 }
1202
1203 switch (type) {
1205 case GHOST_kEventKeyUp: {
1206 const GHOST_TEventKeyData *key_data = static_cast<const GHOST_TEventKeyData *>(data);
1207 switch (key_data->key) {
1208 case GHOST_kKeyA:
1209 if (val) {
1211 }
1212 break;
1213 case GHOST_kKeyI:
1214 if (val) {
1216 }
1217 break;
1218 case GHOST_kKeyP:
1219 if (val) {
1220 ps.pingpong = !ps.pingpong;
1221 }
1222 break;
1223 case GHOST_kKeyF: {
1224 if (val) {
1225 int axis = (ps.ghost_data.qual & WS_QUAL_SHIFT) ? 1 : 0;
1226 ps.draw_flip[axis] = !ps.draw_flip[axis];
1227 }
1228 break;
1229 }
1230 case GHOST_kKey1:
1231 case GHOST_kKeyNumpad1:
1232 if (val) {
1233 g_playanim.swap_time = ps.frame_step / 60.0;
1235 }
1236 break;
1237 case GHOST_kKey2:
1238 case GHOST_kKeyNumpad2:
1239 if (val) {
1240 g_playanim.swap_time = ps.frame_step / 50.0;
1242 }
1243 break;
1244 case GHOST_kKey3:
1245 case GHOST_kKeyNumpad3:
1246 if (val) {
1247 g_playanim.swap_time = ps.frame_step / 30.0;
1249 }
1250 break;
1251 case GHOST_kKey4:
1252 case GHOST_kKeyNumpad4:
1253 if (ps.ghost_data.qual & WS_QUAL_SHIFT) {
1254 g_playanim.swap_time = ps.frame_step / 24.0;
1256 }
1257 else {
1258 g_playanim.swap_time = ps.frame_step / 25.0;
1260 }
1261 break;
1262 case GHOST_kKey5:
1263 case GHOST_kKeyNumpad5:
1264 if (val) {
1265 g_playanim.swap_time = ps.frame_step / 20.0;
1267 }
1268 break;
1269 case GHOST_kKey6:
1270 case GHOST_kKeyNumpad6:
1271 if (val) {
1272 g_playanim.swap_time = ps.frame_step / 15.0;
1274 }
1275 break;
1276 case GHOST_kKey7:
1277 case GHOST_kKeyNumpad7:
1278 if (val) {
1279 g_playanim.swap_time = ps.frame_step / 12.0;
1281 }
1282 break;
1283 case GHOST_kKey8:
1284 case GHOST_kKeyNumpad8:
1285 if (val) {
1286 g_playanim.swap_time = ps.frame_step / 10.0;
1288 }
1289 break;
1290 case GHOST_kKey9:
1291 case GHOST_kKeyNumpad9:
1292 if (val) {
1293 g_playanim.swap_time = ps.frame_step / 6.0;
1295 }
1296 break;
1298 if (val) {
1299 ps.single_step = true;
1300 ps.wait = false;
1302
1303 if (ps.ghost_data.qual & WS_QUAL_SHIFT) {
1304 ps.picture = static_cast<PlayAnimPict *>(ps.picsbase.first);
1305 ps.next_frame = 0;
1306 }
1307 else {
1308 ps.next_frame = -1;
1309 }
1310 }
1311 break;
1313 if (val) {
1314 ps.wait = false;
1316
1317 if (ps.ghost_data.qual & WS_QUAL_SHIFT) {
1318 ps.next_frame = ps.direction = -1;
1319 }
1320 else {
1321 ps.next_frame = -10;
1322 ps.single_step = true;
1323 }
1324 }
1325 break;
1327 if (val) {
1328 ps.single_step = true;
1329 ps.wait = false;
1331
1332 if (ps.ghost_data.qual & WS_QUAL_SHIFT) {
1333 ps.picture = static_cast<PlayAnimPict *>(ps.picsbase.last);
1334 ps.next_frame = 0;
1335 }
1336 else {
1337 ps.next_frame = 1;
1338 }
1339 }
1340 break;
1341 case GHOST_kKeyUpArrow:
1342 if (val) {
1343 ps.wait = false;
1344 if (ps.ghost_data.qual & WS_QUAL_SHIFT) {
1345 ps.next_frame = ps.direction = 1;
1346 if (ps.single_step == false) {
1348 }
1349 }
1350 else {
1351 ps.next_frame = 10;
1352 ps.single_step = true;
1354 }
1355 }
1356 break;
1357
1358 case GHOST_kKeySlash:
1360 if (val) {
1361 if (ps.ghost_data.qual & WS_QUAL_SHIFT) {
1362 if (ps.picture && ps.picture->ibuf) {
1363 printf(" Name: %s | Speed: %.2f frames/s\n",
1364 ps.picture->ibuf->filepath,
1365 ps.frame_step / g_playanim.swap_time);
1366 }
1367 }
1368 else {
1369 g_playanim.swap_time = ps.frame_step / 5.0;
1371 }
1372 }
1373 break;
1374 case GHOST_kKey0:
1375 case GHOST_kKeyNumpad0:
1376 if (val) {
1377 if (ps.once) {
1378 ps.once = ps.wait = false;
1379 }
1380 else {
1381 ps.picture = nullptr;
1382 ps.once = true;
1383 ps.wait = false;
1384 }
1385 }
1386 break;
1387
1388 case GHOST_kKeySpace:
1389 if (val) {
1390 if (ps.wait || ps.single_step) {
1391 ps.wait = ps.single_step = false;
1393 }
1394 else {
1395 ps.single_step = true;
1396 ps.wait = true;
1398 }
1399 }
1400 break;
1401 case GHOST_kKeyEnter:
1403 if (val) {
1404 ps.wait = ps.single_step = false;
1406 }
1407 break;
1408 case GHOST_kKeyPeriod:
1410 if (val) {
1411 if (ps.single_step) {
1412 ps.wait = false;
1413 }
1414 else {
1415 ps.single_step = true;
1416 ps.wait = !ps.wait;
1418 }
1419 }
1420 break;
1421 case GHOST_kKeyEqual:
1422 case GHOST_kKeyPlus:
1423 case GHOST_kKeyNumpadPlus: {
1424 if (val == 0) {
1425 break;
1426 }
1427 if (ps.ghost_data.qual & WS_QUAL_CTRL) {
1428 playanim_window_zoom(ps, 0.1f);
1429 }
1430 else {
1431 if (g_playanim.swap_time > ps.frame_step / 60.0) {
1432 g_playanim.swap_time /= 1.1;
1434 }
1435 }
1436 break;
1437 }
1438 case GHOST_kKeyMinus:
1439 case GHOST_kKeyNumpadMinus: {
1440 if (val == 0) {
1441 break;
1442 }
1443 if (ps.ghost_data.qual & WS_QUAL_CTRL) {
1444 playanim_window_zoom(ps, -0.1f);
1445 }
1446 else {
1447 if (g_playanim.swap_time < ps.frame_step / 5.0) {
1448 g_playanim.swap_time *= 1.1;
1450 }
1451 }
1452 break;
1453 }
1454 case GHOST_kKeyEsc:
1455 ps.go = false;
1456 break;
1457 default:
1458 break;
1459 }
1460 break;
1461 }
1463 case GHOST_kEventButtonUp: {
1464 const GHOST_TEventButtonData *bd = static_cast<const GHOST_TEventButtonData *>(data);
1465 int cx, cy;
1466 const blender::int2 window_size = playanim_window_size_get(ghost_window);
1467
1468 const bool inside_window = (GHOST_GetCursorPosition(ghost_system, ghost_window, &cx, &cy) ==
1469 GHOST_kSuccess) &&
1470 (cx >= 0 && cx < window_size[0] && cy >= 0 &&
1471 cy <= window_size[1]);
1472
1473 if (bd->button == GHOST_kButtonMaskLeft) {
1474 if (type == GHOST_kEventButtonDown) {
1475 if (inside_window) {
1478 }
1479 }
1480 else {
1482 }
1483 }
1484 else if (bd->button == GHOST_kButtonMaskMiddle) {
1485 if (type == GHOST_kEventButtonDown) {
1486 if (inside_window) {
1488 }
1489 }
1490 else {
1492 }
1493 }
1494 else if (bd->button == GHOST_kButtonMaskRight) {
1495 if (type == GHOST_kEventButtonDown) {
1496 if (inside_window) {
1498 }
1499 }
1500 else {
1502 }
1503 }
1504 break;
1505 }
1507 if (ps.ghost_data.qual & WS_QUAL_LMOUSE) {
1508 const GHOST_TEventCursorData *cd = static_cast<const GHOST_TEventCursorData *>(data);
1509 int cx, cy;
1510
1511 /* Ignore 'in-between' events, since they can make scrubbing lag.
1512 *
1513 * Ideally we would keep into the event queue and see if this is the last motion event.
1514 * however the API currently doesn't support this. */
1515 {
1516 int x_test, y_test;
1517 if (GHOST_GetCursorPosition(ghost_system, ghost_window, &cx, &cy) == GHOST_kSuccess) {
1518 GHOST_ScreenToClient(ghost_window, cd->x, cd->y, &x_test, &y_test);
1519 if (cx != x_test || cy != y_test) {
1520 /* We're not the last event... skipping. */
1521 break;
1522 }
1523 }
1524 }
1525
1527 }
1528 break;
1529 }
1533 break;
1534 }
1537 float zoomx, zoomy;
1538
1539 ps.display_ctx.size = playanim_window_size_get(ghost_window);
1541
1542 zoomx = float(ps.display_ctx.size[0]) / ps.ibuf_size[0];
1543 zoomy = float(ps.display_ctx.size[1]) / ps.ibuf_size[1];
1544
1545 /* Zoom always show entire image. */
1546 ps.zoom = std::min(zoomx, zoomy);
1547
1548 GPU_viewport(0, 0, ps.display_ctx.size[0], ps.display_ctx.size[1]);
1549 GPU_scissor(0, 0, ps.display_ctx.size[0], ps.display_ctx.size[1]);
1550
1552
1553 g_playanim.total_time = 0.0;
1554
1555 playanim_toscreen(ps, ps.picture, ps.picture ? ps.picture->ibuf : nullptr);
1556
1557 break;
1558 }
1561 ps.go = false;
1562 break;
1563 }
1565 /* Rely on frame-change to redraw. */
1567 break;
1568 }
1570 const GHOST_TEventDragnDropData *ddd = static_cast<const GHOST_TEventDragnDropData *>(data);
1571
1573 const GHOST_TStringArray *stra = static_cast<const GHOST_TStringArray *>(ddd->data);
1574 ps.argc_next = stra->count;
1575 ps.argv_next = MEM_malloc_arrayN<char *>(size_t(ps.argc_next), __func__);
1576 for (int i = 0; i < stra->count; i++) {
1577 ps.argv_next[i] = BLI_strdup(reinterpret_cast<const char *>(stra->strings[i]));
1578 }
1579 ps.go = false;
1580 printf("dropped %s, %d file(s)\n", ps.argv_next[0], ps.argc_next);
1581 }
1582 break;
1583 }
1584 default:
1585 /* Quiet warnings. */
1586 break;
1587 }
1588
1589 return true;
1590}
1591
1592static GHOST_WindowHandle playanim_window_open(
1593 GHOST_SystemHandle ghost_system, const char *title, int posx, int posy, int sizex, int sizey)
1594{
1595 GHOST_GPUSettings gpu_settings = {0};
1596 const GPUBackendType gpu_backend = GPU_backend_type_selection_get();
1597 gpu_settings.context_type = wm_ghost_drawing_context_type(gpu_backend);
1598 gpu_settings.preferred_device.index = U.gpu_preferred_index;
1599 gpu_settings.preferred_device.vendor_id = U.gpu_preferred_vendor_id;
1600 gpu_settings.preferred_device.device_id = U.gpu_preferred_device_id;
1602 gpu_settings.flags |= GHOST_gpuVSyncIsOverridden;
1604 }
1605
1606 {
1607 bool screen_size_valid = false;
1608 uint32_t screen_size[2];
1609 if ((GHOST_GetMainDisplayDimensions(ghost_system, &screen_size[0], &screen_size[1]) ==
1610 GHOST_kSuccess) &&
1611 (screen_size[0] > 0) && (screen_size[1] > 0))
1612 {
1613 screen_size_valid = true;
1614 }
1615 else {
1616 /* Unlikely the screen size fails to access,
1617 * if this happens it's still important to clamp the window size by *something*. */
1618 screen_size[0] = 1024;
1619 screen_size[1] = 1024;
1620 }
1621
1622 if (screen_size_valid) {
1624 posy = (screen_size[1] - posy - sizey);
1625 }
1626 }
1627 else {
1628 posx = 0;
1629 posy = 0;
1630 }
1631
1632 /* NOTE: ideally the GPU could be queried for the maximum supported window size,
1633 * this isn't so simple as the GPU back-end's capabilities are initialized *after* the window
1634 * has been created. Further, it's quite unlikely the users main monitor size is larger
1635 * than the maximum window size supported by the GPU. */
1636
1637 /* Clamp the size so very large requests aren't rejected by the GPU.
1638 * Halve until a usable range is reached instead of scaling down to meet the screen size
1639 * since fractional scaling tends not to look so nice. */
1640 while (sizex >= int(screen_size[0]) || sizey >= int(screen_size[1])) {
1641 sizex /= 2;
1642 sizey /= 2;
1643 }
1644 /* Unlikely but ensure the size is *never* zero. */
1645 CLAMP_MIN(sizex, 1);
1646 CLAMP_MIN(sizey, 1);
1647 }
1648
1650 nullptr,
1651 title,
1652 posx,
1653 posy,
1654 sizex,
1655 sizey,
1656 /* Could optionally start full-screen. */
1658 false,
1659 gpu_settings);
1660}
1661
1662static void playanim_window_zoom(PlayState &ps, const float zoom_offset)
1663{
1665 // blender::int2 ofs; /* UNUSED. */
1666
1667 if (ps.zoom + zoom_offset > 0.0f) {
1668 ps.zoom += zoom_offset;
1669 }
1670
1671 // playanim_window_get_position(&ofs[0], &ofs[1]);
1672 // size = playanim_window_size_get(ps.ghost_data.window);
1673 // ofs[0] += size[0] / 2; /* UNUSED. */
1674 // ofs[1] += size[1] / 2; /* UNUSED. */
1675 size[0] = ps.zoom * ps.ibuf_size[0];
1676 size[1] = ps.zoom * ps.ibuf_size[1];
1677 // ofs[0] -= size[0] / 2; /* UNUSED. */
1678 // ofs[1] -= size[1] / 2; /* UNUSED. */
1679 // window_set_position(ps.ghost_data.window, size[0], size[1]);
1681}
1682
1684{
1685 const float scale = (GHOST_GetDPIHint(ps.ghost_data.window) *
1687 const float font_size_base = 11.0f; /* Font size un-scaled. */
1688 const int font_size = int((font_size_base * scale) + 0.5f);
1689 bool changed = false;
1690 if (ps.font_size != font_size) {
1691 BLF_size(ps.font_id, font_size);
1692 ps.font_size = font_size;
1693 changed = true;
1694 }
1695 if (ps.display_ctx.ui_scale != scale) {
1696 ps.display_ctx.ui_scale = scale;
1697 }
1698 return changed;
1699}
1700
1705static std::optional<int> wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_next)
1706{
1707 ImBuf *ibuf = nullptr;
1708 blender::int2 window_pos = {0, 0};
1709 int frame_start = -1;
1710 int frame_end = -1;
1711
1712 PlayState ps{};
1713
1714 ps.go = true;
1715 ps.direction = true;
1716 ps.next_frame = 1;
1717 ps.once = false;
1718 ps.pingpong = false;
1719 ps.no_frame_skip = false;
1720 ps.single_step = false;
1721 ps.wait = false;
1722 ps.stopped = false;
1723 ps.loading = false;
1724 ps.picture = nullptr;
1725 ps.show_frame_indicator = false;
1726 ps.argc_next = 0;
1727 ps.argv_next = nullptr;
1728 ps.zoom = 1.0f;
1729 ps.draw_flip[0] = false;
1730 ps.draw_flip[1] = false;
1731
1732 ps.frame_step = 1;
1733
1734 ps.font_id = -1;
1735
1736 IMB_init();
1737 MOV_init();
1738
1743 ps.display_ctx.ui_scale = 1.0f;
1744
1745 while ((argc > 0) && (argv[0][0] == '-')) {
1746 switch (argv[0][1]) {
1747 case 'm': {
1748 g_playanim.from_disk = true;
1749 break;
1750 }
1751 case 'p': {
1752 if (argc > 2) {
1753 window_pos[0] = atoi(argv[1]);
1754 window_pos[1] = atoi(argv[2]);
1755 argc -= 2;
1756 argv += 2;
1757 }
1758 else {
1759 printf("too few arguments for -p (need 2): skipping\n");
1760 }
1761 break;
1762 }
1763 case 'f': {
1764 if (argc > 2) {
1765 double fps = atof(argv[1]);
1766 double fps_base = atof(argv[2]);
1767 if (fps == 0.0) {
1768 fps = 1;
1769 printf("invalid fps, forcing 1\n");
1770 }
1771 g_playanim.swap_time = fps_base / fps;
1772 argc -= 2;
1773 argv += 2;
1774 }
1775 else {
1776 printf("too few arguments for -f (need 2): skipping\n");
1777 }
1778 break;
1779 }
1780 case 's': {
1781 frame_start = atoi(argv[1]);
1782 CLAMP(frame_start, 1, MAXFRAME);
1783 argc--;
1784 argv++;
1785 break;
1786 }
1787 case 'e': {
1788 frame_end = atoi(argv[1]);
1789 CLAMP(frame_end, 1, MAXFRAME);
1790 argc--;
1791 argv++;
1792 break;
1793 }
1794 case 'j': {
1795 ps.frame_step = atoi(argv[1]);
1796 CLAMP(ps.frame_step, 1, MAXFRAME);
1797 g_playanim.swap_time *= ps.frame_step;
1798 argc--;
1799 argv++;
1800 break;
1801 }
1802 case 'c': {
1803#ifdef USE_FRAME_CACHE_LIMIT
1804 const int memory_in_mb = max_ii(0, atoi(argv[1]));
1805 g_frame_cache.memory_limit = size_t(memory_in_mb) * (1024 * 1024);
1806#endif
1807 argc--;
1808 argv++;
1809 break;
1810 }
1811 default: {
1812 printf("unknown option '%c': skipping\n", argv[0][1]);
1813 break;
1814 }
1815 }
1816 argc--;
1817 argv++;
1818 }
1819
1820 const char *filepath = nullptr;
1821 GHOST_EventConsumerHandle ghost_event_consumer = nullptr;
1822
1823 {
1824 std::optional<int> exit_code = [&]() -> std::optional<int> {
1825 if (argc == 0) {
1826 fprintf(stderr, "%s: no filepath argument given\n", message_prefix);
1827 return EXIT_FAILURE;
1828 }
1829
1830 filepath = argv[0];
1831 if (MOV_is_movie_file(filepath)) {
1832 /* OCIO_TODO: support different input color spaces. */
1833 /* Image buffer is used for display, which does support displaying any buffer from any
1834 * colorspace. Skip colorspace conversions in the movie module to improve performance. */
1835 MovieReader *anim = MOV_open_file(filepath, IB_byte_data, 0, true, nullptr);
1836 if (anim) {
1838 MOV_close(anim);
1839 anim = nullptr;
1840 }
1841 }
1842 else if (IMB_test_image(filepath)) {
1843 /* Pass. */
1844 }
1845 else {
1846 fprintf(stderr, "%s: '%s' not an image file\n", message_prefix, filepath);
1847 return EXIT_FAILURE;
1848 }
1849
1850 if (ibuf == nullptr) {
1851 /* OCIO_TODO: support different input color space. */
1853 }
1854
1855 if (ibuf == nullptr) {
1856 fprintf(stderr, "%s: '%s' couldn't open\n", message_prefix, filepath);
1857 return EXIT_FAILURE;
1858 }
1859
1860 /* Select GPU backend. */
1862
1863 /* Init GHOST and open window. */
1866
1868 if (UNLIKELY(ps.ghost_data.system == nullptr)) {
1869 /* GHOST will have reported the back-ends that failed to load. */
1870 fprintf(stderr, "%s: unable to initialize GHOST, exiting!\n", message_prefix);
1871 return EXIT_FAILURE;
1872 }
1873
1875
1877
1879 "Blender Animation Player",
1880 window_pos[0],
1881 window_pos[1],
1882 ibuf->x,
1883 ibuf->y);
1884
1885 if (UNLIKELY(ps.ghost_data.window == nullptr)) {
1886 fprintf(stderr, "%s: unable to create window, exiting!\n", message_prefix);
1887 return EXIT_FAILURE;
1888 }
1889
1890 ghost_event_consumer = GHOST_CreateEventConsumer(ghost_event_proc, &ps);
1891 GHOST_AddEventConsumer(ps.ghost_data.system, ghost_event_consumer);
1892
1893 return std::nullopt;
1894 }();
1895
1896 if (exit_code) {
1897 if (ps.ghost_data.system) {
1899 }
1900 if (ibuf) {
1901 IMB_freeImBuf(ibuf);
1902 }
1903 IMB_exit();
1904 MOV_exit();
1905 return exit_code;
1906 }
1907 }
1908
1909 // GHOST_ActivateWindowDrawingContext(ps.ghost_data.window);
1910
1911 /* Init Blender GPU context. */
1913 GPU_init();
1914
1915 /* Initialize the font. */
1916 BLF_init();
1917 ps.font_id = BLF_load_mono_default(false);
1918
1919 ps.font_size = -1; /* Force update. */
1921
1922 ps.ibuf_size[0] = ibuf->x;
1923 ps.ibuf_size[1] = ibuf->y;
1924
1925 ps.display_ctx.size = ps.ibuf_size;
1926
1930 GPU_clear_color(0.1f, 0.1f, 0.1f, 0.0f);
1931
1932 {
1934 GPU_viewport(0, 0, window_size[0], window_size[1]);
1935 GPU_scissor(0, 0, window_size[0], window_size[1]);
1937 }
1938
1941
1942 /* One of the frames was invalid or not passed in. */
1943 if (frame_start == -1 || frame_end == -1) {
1944 frame_start = 1;
1945 if (argc == 1) {
1946 /* A single file was passed in, attempt to load all images from an image sequence.
1947 * (if it is an image sequence). */
1948 frame_end = MAXFRAME;
1949 }
1950 else {
1951 /* Multiple files passed in, show each file without expanding image sequences.
1952 * This occurs when dropping multiple files. */
1953 frame_end = 1;
1954 }
1955 }
1956
1958 ps.ghost_data,
1959 ps.display_ctx,
1960 filepath,
1961 (frame_end - frame_start) + 1,
1962 ps.frame_step,
1963 &ps.loading);
1964
1965#ifdef WITH_AUDASPACE
1966 g_audaspace.source = AUD_Sound_file(filepath);
1967 if (!BLI_listbase_is_empty(&ps.picsbase)) {
1968 const MovieReader *anim_movie = static_cast<PlayAnimPict *>(ps.picsbase.first)->anim;
1969 if (anim_movie) {
1970 g_playanim.fps_movie = MOV_get_fps(anim_movie);
1971 /* Enforce same fps for movie as sound. */
1972 g_playanim.swap_time = ps.frame_step / g_playanim.fps_movie;
1973 }
1974 }
1975#endif
1976
1977 for (int i = 1; i < argc; i++) {
1978 filepath = argv[i];
1980 ps.ghost_data,
1981 ps.display_ctx,
1982 filepath,
1983 (frame_end - frame_start) + 1,
1984 ps.frame_step,
1985 &ps.loading);
1986 }
1987
1988 IMB_freeImBuf(ibuf);
1989 ibuf = nullptr;
1990
1991 pupdate_time();
1992 g_playanim.total_time = 0.0;
1993
1994/* Newly added in 2.6x, without this images never get freed. */
1995#define USE_IMB_CACHE
1996
1997 while (ps.go) {
1998 if (ps.pingpong) {
1999 ps.direction = -ps.direction;
2000 }
2001
2002 if (ps.direction == 1) {
2003 ps.picture = static_cast<PlayAnimPict *>(ps.picsbase.first);
2004 }
2005 else {
2006 ps.picture = static_cast<PlayAnimPict *>(ps.picsbase.last);
2007 }
2008
2009 if (ps.picture == nullptr) {
2010 printf("couldn't find pictures\n");
2011 ps.go = false;
2012 }
2013 if (ps.pingpong) {
2014 if (ps.direction == 1) {
2015 ps.picture = ps.picture->next;
2016 }
2017 else {
2018 ps.picture = ps.picture->prev;
2019 }
2020 }
2021 g_playanim.total_time = std::min(g_playanim.total_time, 0.0);
2022
2023#ifdef WITH_AUDASPACE
2024 if (g_audaspace.playback_handle) {
2025 AUD_Handle_stop(g_audaspace.playback_handle);
2026 }
2027 g_audaspace.playback_handle = AUD_Device_play(g_audaspace.audio_device, g_audaspace.source, 1);
2029#endif
2030
2031 while (ps.picture) {
2032 bool has_event;
2033#ifndef USE_IMB_CACHE
2034 if (ibuf != nullptr && ibuf->ftype == IMB_FTYPE_NONE) {
2035 IMB_freeImBuf(ibuf);
2036 }
2037#endif
2038
2039 ibuf = ibuf_from_picture(ps.picture);
2040
2041 {
2042#ifdef USE_IMB_CACHE
2043 ps.picture->ibuf = ibuf;
2044#endif
2045 if (ibuf) {
2046#ifdef USE_FRAME_CACHE_LIMIT
2047 if (ps.picture->frame_cache_node == nullptr) {
2049 }
2050 else {
2052 }
2054#endif /* USE_FRAME_CACHE_LIMIT */
2055
2056 STRNCPY(ibuf->filepath, ps.picture->filepath);
2057 ibuf->fileframe = ps.picture->frame;
2058 }
2059
2060 while (pupdate_time()) {
2062 }
2063 g_playanim.total_time -= g_playanim.swap_time;
2064 playanim_toscreen(ps, ps.picture, ibuf);
2065 }
2066
2067 if (ps.once) {
2068 if (ps.picture->next == nullptr) {
2069 ps.wait = true;
2070 }
2071 else if (ps.picture->prev == nullptr) {
2072 ps.wait = true;
2073 }
2074 }
2075
2076 ps.next_frame = ps.direction;
2077
2079 GPUContext *restore_context = GPU_context_active_get();
2081 while ((has_event = GHOST_ProcessEvents(ps.ghost_data.system, false))) {
2083 }
2085 GPU_context_active_set(restore_context);
2086
2087 if (ps.go == false) {
2088 break;
2089 }
2091 if (!has_event) {
2093 }
2094 if (ps.wait) {
2095 continue;
2096 }
2097
2098 ps.wait = ps.single_step;
2099
2100 if (ps.wait == false && ps.stopped) {
2101 ps.stopped = false;
2102 }
2103
2104 pupdate_time();
2105
2106 if (ps.picture && ps.next_frame) {
2107 /* Advance to the next frame, always at least set one step.
2108 * Implement frame-skipping when enabled and playback is not fast enough. */
2109 while (ps.picture) {
2111
2112 if (ps.once && ps.picture != nullptr) {
2113 if (ps.picture->next == nullptr) {
2114 ps.wait = true;
2115 }
2116 else if (ps.picture->prev == nullptr) {
2117 ps.wait = true;
2118 }
2119 }
2120
2121 if (ps.wait || g_playanim.total_time < g_playanim.swap_time || ps.no_frame_skip) {
2122 break;
2123 }
2124 g_playanim.total_time -= g_playanim.swap_time;
2125 }
2126 if (ps.picture == nullptr && ps.single_step) {
2128 }
2129 }
2130 if (ps.go == false) {
2131 break;
2132 }
2133 }
2134 }
2135 while ((ps.picture = static_cast<PlayAnimPict *>(BLI_pophead(&ps.picsbase)))) {
2136 if (ps.picture->anim) {
2137 if ((ps.picture->next == nullptr) || (ps.picture->next->anim != ps.picture->anim)) {
2138 MOV_close(ps.picture->anim);
2139 }
2140 }
2141
2142 if (ps.picture->ibuf) {
2144 }
2145 if (ps.picture->mem) {
2146 MEM_freeN(ps.picture->mem);
2147 }
2148 if (ps.picture->error_message) {
2150 }
2152 MEM_freeN(ps.picture);
2153 }
2154
2155/* Cleanup. */
2156#ifndef USE_IMB_CACHE
2157 if (ibuf) {
2158 IMB_freeImBuf(ibuf);
2159 }
2160#endif
2161
2162#ifdef USE_FRAME_CACHE_LIMIT
2164 g_frame_cache.pics_len = 0;
2165 g_frame_cache.pics_size_in_memory = 0;
2166#endif
2167
2168#ifdef WITH_AUDASPACE
2169 if (g_audaspace.playback_handle) {
2170 AUD_Handle_stop(g_audaspace.playback_handle);
2171 g_audaspace.playback_handle = nullptr;
2172 }
2173 if (g_audaspace.scrub_handle) {
2174 AUD_Handle_stop(g_audaspace.scrub_handle);
2175 g_audaspace.scrub_handle = nullptr;
2176 }
2177 AUD_Sound_free(g_audaspace.source);
2178 g_audaspace.source = nullptr;
2179#endif
2180
2181 /* Free subsystems the animation player is responsible for starting.
2182 * The rest is handled by #BKE_blender_atexit, see early-exit logic in `creator.cc`. */
2183
2184 BLF_exit();
2185
2186 /* NOTE: Must happen before GPU Context destruction as GPU resources are released via
2187 * Color Management module. */
2188 IMB_exit();
2189 MOV_exit();
2190
2191 if (ps.ghost_data.gpu_context) {
2193 GPU_exit();
2195 ps.ghost_data.gpu_context = nullptr;
2196 }
2197 GHOST_RemoveEventConsumer(ps.ghost_data.system, ghost_event_consumer);
2198 GHOST_DisposeEventConsumer(ghost_event_consumer);
2199
2201
2203
2204 if (ps.argv_next) {
2205 args_next->argc = ps.argc_next;
2206 args_next->argv = ps.argv_next;
2207 /* Returning none, run this function again with the *next* arguments. */
2208 return std::nullopt;
2209 }
2210
2211 return EXIT_SUCCESS;
2212}
2213
2214int WM_main_playanim(int argc, const char **argv)
2215{
2216#ifdef WITH_AUDASPACE
2217 {
2218 AUD_DeviceSpecs specs;
2219
2220 specs.rate = AUD_RATE_48000;
2221 specs.format = AUD_FORMAT_FLOAT32;
2222 specs.channels = AUD_CHANNELS_STEREO;
2223
2224 AUD_initOnce();
2225
2226 if (!(g_audaspace.audio_device = AUD_init(nullptr, specs, 1024, "Blender"))) {
2227 g_audaspace.audio_device = AUD_init("None", specs, 0, "Blender");
2228 }
2229 }
2230#endif
2231
2232 std::optional<int> exit_code = std::nullopt;
2233 PlayArgs args_next = {0};
2234 do {
2235 PlayArgs args_free = args_next;
2236 args_next = {0};
2237
2238 if ((exit_code = wm_main_playanim_intern(argc, argv, &args_next))) {
2239 argc = 0;
2240 argv = nullptr;
2241 }
2242 else {
2243 argc = args_next.argc;
2244 argv = const_cast<const char **>(args_next.argv);
2245 }
2246
2247 if (args_free.argv) {
2248 for (int i = 0; i < args_free.argc; i++) {
2249 MEM_freeN(args_free.argv[i]);
2250 }
2251 MEM_freeN(args_free.argv);
2252 }
2253 } while (argv != nullptr);
2254 /* Set in the loop. */
2255 BLI_assert(exit_code.has_value());
2256
2257#ifdef WITH_AUDASPACE
2258 AUD_exit(g_audaspace.audio_device);
2259 AUD_exitOnce();
2260#endif
2261
2262 /* Cleanup sub-systems started before this function was called. */
2264
2265 return exit_code.value();
2266}
int ED_draw_imbuf_method(const ImBuf *ibuf)
Definition glutil.cc:619
Blender util stuff.
void BKE_blender_atexit()
Definition blender.cc:515
void BLF_size(int fontid, float size)
Definition blf.cc:443
void BLF_enable(int fontid, FontFlags flag)
Definition blf.cc:320
void BLF_aspect(int fontid, float x, float y, float z)
Definition blf.cc:377
void BLF_draw(int fontid, const char *str, size_t str_len, ResultBLF *r_info=nullptr) ATTR_NONNULL(2)
Definition blf.cc:585
int BLF_load_mono_default(bool unique)
void BLF_exit()
Definition blf.cc:70
int BLF_init()
Definition blf.cc:61
void BLF_color4f(int fontid, float r, float g, float b, float a)
Definition blf.cc:514
void BLF_position(int fontid, float x, float y, float z)
Definition blf.cc:388
@ BLF_ASPECT
Definition BLF_enums.hh:38
#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:217
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:58
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:604
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
#define STRNCPY_UTF8(dst, src)
unsigned char uchar
unsigned int uint
unsigned short ushort
void BLI_system_backtrace(FILE *fp)
Definition system.cc:103
Platform independent time functions.
void BLI_time_sleep_ms(int ms)
Definition time.cc:133
double BLI_time_now_seconds(void)
Definition time.cc:113
#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:189
#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_SwapWindowBufferRelease(GHOST_WindowHandle windowhandle)
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)
GHOST_TSuccess GHOST_SwapWindowBufferAcquire(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_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)
float GHOST_GetNativePixelSize(GHOST_WindowHandle windowhandle)
GHOST_TEventType GHOST_GetEventType(GHOST_EventHandle eventhandle)
bool GHOST_UseNativePixels(void)
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 gpu_settings)
void GHOST_DispatchEvents(GHOST_SystemHandle systemhandle)
void GHOST_UseWindowFrame(bool use_window_frame)
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:55
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
@ 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:57
@ GHOST_gpuVSyncIsOverridden
void(* GHOST_TBacktraceFn)(void *file_handle)
Definition GHOST_Types.h:53
@ GHOST_kDragnDropTypeFilenames
@ GHOST_kButtonMaskRight
@ GHOST_kButtonMaskLeft
@ GHOST_kButtonMaskMiddle
GHOST_TVSyncModes
bool GPU_backend_vsync_is_overridden()
int GPU_backend_vsync_get()
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()
void GPU_context_discard(GPUContext *)
bool GPU_backend_type_selection_detect()
void GPU_context_end_frame(GPUContext *ctx)
void GPU_context_active_set(GPUContext *)
GPUBackendType GPU_backend_get_type()
void GPU_backend_ghost_system_set(void *ghost_system_handle)
GPUBackendType GPU_backend_type_selection_get()
void GPU_clear_color(float red, float green, float blue, float alpha)
void immEnd()
void immUnbindProgram()
void immBindBuiltinProgram(GPUBuiltinShader shader_id)
void immUniformColor3ub(unsigned char r, unsigned char g, unsigned char b)
void immVertex2f(uint attr_id, float x, float y)
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(GPUBlend 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_unbind(blender::gpu::Texture *texture)
eGPUDataFormat
@ GPU_DATA_UBYTE
@ GPU_DATA_FLOAT
@ GPU_TEXTURE_USAGE_SHADER_READ
blender::gpu::Texture * GPU_texture_create_2d(const char *name, int width, int height, int mip_len, blender::gpu::TextureFormat format, eGPUTextureUsage usage, const float *data)
void GPU_texture_filter_mode(blender::gpu::Texture *texture, bool use_filter)
void GPU_texture_bind(blender::gpu::Texture *texture, int unit)
void GPU_texture_free(blender::gpu::Texture *texture)
void GPU_texture_update(blender::gpu::Texture *texture, eGPUDataFormat data_format, const void *data)
uint GPU_vertformat_attr_add(GPUVertFormat *format, blender::StringRef name, blender::gpu::VertAttrType type)
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)
@ COLOR_ROLE_DEFAULT_BYTE
void IMB_display_buffer_release(void *cache_handle)
const char * IMB_colormanagement_role_colorspace_name_get(int role)
void IMB_colormanagement_finish_glsl_draw()
void IMB_colormanagement_init_untonemapped_view_settings(ColorManagedViewSettings *view_settings, const ColorManagedDisplaySettings *display_settings)
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:21
ImBuf * IMB_load_image_from_filepath(const char *filepath, const int flags, char r_colorspace[IM_MAX_SPACE]=nullptr)
Definition readimage.cc:189
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:121
void IMB_init()
Definition module.cc:15
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.
#define MEM_SAFE_FREE(v)
@ 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
nullptr float
uint pos
#define printf(...)
VecBase< float, D > step(VecOp< float, D >, VecOp< float, D >) RET
TEX_TEMPLATE DataVec texture(T, FltCoord, float=0.0f) RET
#define PRIu64
Definition inttypes.h:132
#define PRId64
Definition inttypes.h:78
format
#define LOG(level)
Definition log.h:97
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
void MOV_close(MovieReader *anim)
Definition movie_read.cc:66
int MOV_get_duration_frames(MovieReader *anim, IMB_Timecode_Type tc)
MovieReader * MOV_open_file(const char *filepath, const int ib_flags, const int streamindex, const bool keep_original_colorspace, char colorspace[IM_MAX_SPACE])
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
const int status
GHOST_TVSyncModes vsync
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 const char * message_prefix
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)
size_t memory_limit
static void frame_cache_remove(PlayAnimPict *pic)
static struct @353253306317277323035105337375312051226160241145 g_frame_cache
static std::optional< int > wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_next)
static struct @110333101231117362165207356317070002324214002213 g_playanim
static void frame_cache_touch(PlayAnimPict *pic)
int WM_main_playanim(int argc, const char **argv)
static void frame_cache_add(PlayAnimPict *pic)
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 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
static void * ocio_transform_ibuf(const PlayDisplayContext &display_ctx, ImBuf *ibuf, bool *r_glsl_used, blender::gpu::TextureFormat *r_format, eGPUDataFormat *r_data, void **r_buffer_cache_handle)
#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 GPUBackendType gpu_backend)
bool WM_init_window_frame_get()