From 8f053bbdb13d5cbb723eb5de936be705a7fd4785 Mon Sep 17 00:00:00 2001 From: mid <> Date: Sun, 30 Jun 2024 14:43:13 +0300 Subject: [PATCH] Add Save/Load functionality --- Makefile | 7 +- README.md | 8 + btn_load.bmp | Bin 0 -> 3722 bytes btn_save.bmp | Bin 0 -> 3722 bytes hi/microphone.c | 27 ++- hi/minitrace.c | 487 ++++++++++++++++++++++++++++++++++++++++++++++++ hi/minitrace.h | 276 +++++++++++++++++++++++++++ hi/node.c | 317 +++++++++++++++++++++++++++---- hi/node.h | 23 +-- hi/opus.c | 2 +- hi/relay.c | 14 +- hi/webcam.c | 2 +- hi/webmdec.cpp | 39 +++- hi/webmenc.cpp | 36 +++- hi/window.c | 31 ++- ui/frame.cpp | 367 ++++++++++++++++++++++++------------ ui/frame.h | 22 +-- 17 files changed, 1454 insertions(+), 204 deletions(-) create mode 100644 btn_load.bmp create mode 100644 btn_save.bmp create mode 100644 hi/minitrace.c create mode 100644 hi/minitrace.h diff --git a/Makefile b/Makefile index 9b99b26..6e6132d 100644 --- a/Makefile +++ b/Makefile @@ -3,9 +3,9 @@ CXXFLAGS := -D_POSIX_C_SOURCE=200809L -Wno-narrowing -march=native -flto -Wall - LDFLAGS := -lwebm -lpng -lvpx -lsail -lsail-manip `pkg-config --libs pango opus libv4l2` -lportaudio -lXtst ifneq ($(RELEASE),0) - CXXFLAGS := $(CXXFLAGS) -O0 -g3 -fsanitize=address + CXXFLAGS := $(CXXFLAGS) -O0 -g3 -fsanitize=address -DMTR_ENABLED else - CXXFLAGS := $(CXXFLAGS) -O3 -fopenmp + CXXFLAGS := $(CXXFLAGS) -O3 -fopenmp -DMTR_ENABLED endif all: @@ -19,6 +19,7 @@ all: gcc $(CXXFLAGS) -std=c99 -shared -c -o opus.o hi/opus.c $(LDFLAGS) gcc $(CXXFLAGS) -std=c99 -shared -c -o webcam.o hi/webcam.c $(LDFLAGS) gcc $(CXXFLAGS) -std=c99 -shared -c -o scale.o hi/relay.c $(LDFLAGS) - gcc $(CXXFLAGS) -shared -o libcutihi.so -shared node.o webmdec.o webmenc.o window.o microphone.o mode.o img.o opus.o webcam.o scale.o $(LDFLAGS) + gcc $(CXXFLAGS) -std=c99 -shared -c -o minitrace.o hi/minitrace.c $(LDFLAGS) + gcc $(CXXFLAGS) -shared -o libcutihi.so -shared node.o webmdec.o webmenc.o window.o microphone.o mode.o img.o opus.o webcam.o scale.o minitrace.o $(LDFLAGS) g++ $(CXXFLAGS) -std=c++11 `wx-config --cflags base,adv,core,aui` -o cuticle ui/main.cpp ui/frame.cpp ui/textctrl.cpp ui/timeline.cpp -L./ -lcutihi $(LDFLAGS) `wx-config --libs base,adv,core,aui` diff --git a/README.md b/README.md index 6d8373a..bfe2a87 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,15 @@ Features: * WebM decoding (VP8, VP9 video; Opus audio) * WebM encoding (VP8, VP9 video; Opus audio) * 16-bit linear RGB processing +* Fixed 48kHz internal sample rate, 30Hz frame rate * Powerful editing capability brought by node graphs * Node keyframing Development-wise, the software currently needs a large amount of refactoring, but it's mostly capable for basic recording. + +Todo: + +* Masking polygons +* Configuable framerate +* More container & codec support +* Use vaapi for accelerating video processing diff --git a/btn_load.bmp b/btn_load.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8872782fb03ed8909f26da76eeab5e918126518f GIT binary patch literal 3722 zcmchZe@t6d6vrRPH1o&&OQU2ulO-mYY>9suwWW-)Ov2Uz0|y(R><2OP$Kn)Ybk>cT zWo|QODzd4As;vBSqfjaR9yyd`OZ4e|$-4={cq9gl?0d z+f%=z!{LxX7T6CKs@M7x#OSH&<-=YFr$Gt06Y#oX%&r}->ae~K5SRjOARXKcw3Ec( z!TCY3KCgip2bEwEhyyXGL=NS=U^acRj$zDOL89s#IoS=&s5ztQ^E&u!dO;Dm9r$}A zQ#QQ-s(>w&{GN2%rp`i86qRSX3MzNE?{FdKG!QjMvXk&>0ZYIfXifkM#UdxuR@X~Z z>#R|+Q;qY`tk`kCzPT=vUfBI6M-zcD=x~`A`ud$;S%<8yQrhwL z>!al-_8qA>{$4$32CU5UPioof-sMt| z{u+$`m7A}@y+8rH&d)(1cq*8TzQ+o8wQsM8Qa?YJy6w*D^*Nq2H=ixy%QEF;J^JkP z8VKtVaSwY#FPP>ak zcO0tz{2vCDU;v!eN3wTKPsZq4_lj|74xz8SYTOyvHc$X~|Dv6L9_AkbKLg?( zU8SUw7_*HA`>TCTX4y#Dh|Q8yaL>qbe}k=H9*A`Q8!^`mD7SAJ9T+<$hGQ{ zRo^6uPs`|G;=EegT=$m24RmR0OoWnW{5#6)dqS*%H6rPMjJYeUUvs{4CY}fVS9Nt4+`T1WAs(CI;UP9Z4O8sYF{_7sf z?@!l#{@jBv50=mu7plp2u7Zkx$QHE^Bewy}w*Rcp{r@272E_Xiz#!s94VsqwASCu8 za&mv``TZ||AHSE|!S^w83h`B7DToJp^d*bp|6TAiaBht7Gbax4?~IA0&;NgX09FHj v4+-)yjs=lNSnjbwz-K7>kG1)lE%(8H4Qnxc#=Ix|P8Ik5FZ&lXb6Ec$CbFZM literal 0 HcmV?d00001 diff --git a/btn_save.bmp b/btn_save.bmp new file mode 100644 index 0000000000000000000000000000000000000000..fa49e5d43777272c946900c00dda02dfd6a98f04 GIT binary patch literal 3722 zcmchZZBSHY6vvl^naad3O&>B-8>T)slMOS?!lL2;)5x0;V2(zeo9=icX@ z^M9Um&V3*{A%Bj_b>-uC4p<20fgp^6VO5w9d&v#ox-U|x+&_Fv(QzqR@`Pd&qWDc^ zFPcmymCS6|O&}ix^Rt=0SObKBNMHmbpa!VHjLCCsnHOO9fgwOLGp3LDLiZ_*w}78z zO`*+p+Vv$#61tBgelqwDi~?_6F`ia^Xs|ZdGaTr_A3$jNvNcANQE~kf?b`Q1DbV?1 z?Zs5xQOlt!lSvu~pi7cTqEgF|zIWQzlx=KVt@A_KyXQ0a?Qhx>Rc|~oADRyUO(ww} zNzkSvClC7Cqu*z>QZ4E0DyX9Fm{0U|$9aCjAQ^_SpKe)q0`>x^1l59cYh2Z~)-Bgc zEML3ycz^$t19|`SY0C#=dzyE&7B}p#EN&>QmZ>_>xYzLc>1{(fXR@fEd1s5+Xnq@7 zy!UaiD$%A@B|5ZfCdnS9-fj6vwcMduztZ~J*!GqWsnV>!51(;=Yrq`GCFJgM><{4! z`K$zLnV_Xt7KO$;BF-h)Bk)c{E>L;tuf+I~GlTEJGl=Kl_cpX=bQU)4zFBRqLaohz z%LVK2ZO)@)+v04jnSDL+>9Z1F!)ra%fGr##(bvXIpEe>OQPtLaYOQWh`ne z2tL?QG@bgq{>MP5AjY{U^wrB@v!Ne`TI6BxgK~em&L52blRo0JRtR_+JU5AXxPOFL z=PS>mCiBsEY2VcAI*!+spUAMrAzzW~I~P2WXpd5yf4u)PC;?rdPa(bN<(?E<^x&F~ zq+xtJ!mkJZr@$(}`I`m&^a`V)B_zgdmB2k3kMAoPzE}r;XY6+$d<^CQ zfBNe&X9kqoxr`3=7tr^=w~^_lp5E$O;aU&Q1qwuxBZ_wPenRbc&eEw{7TWw%rrX!9 z9YX9F(8?Mh7jRv8JOXxs8}T*`<@M&!!|^{!T=l=z62}ds&HLWky^d~=3<+1V-MvWZ zov~sKtl>}pFy`*De#JnE$Daqzdza{S=dytM%k3K4iM5YUjCuSAAO1?~x>ALH88ps< z$@?G0voFE*cv}SJ^lYWOqjy{}IM%U&qXD%!H#iq)9;`g8xi-@SoqZj_VmdNF1Gm@M|*kJuXE zEB~y|{r@6p2gG@BqY?gM4*@Or!6e^Xh{^pO@chL)3&3{pE$Emc=iy%pmI5_UqOVGD zc+NnaUD~O@XHF2{?~L)M&;NfEfi%E>2L%}zPX!T2SnjcIz%>;8$J%_)miu6)VJ(Jh T%sJt|sp8rHWrv}e!uo#!(+`*s literal 0 HcmV?d00001 diff --git a/hi/microphone.c b/hi/microphone.c index c499e26..be8598e 100644 --- a/hi/microphone.c +++ b/hi/microphone.c @@ -9,6 +9,8 @@ #include #include"microphone.h" +#include"minitrace.h" + #define pabufsize (48000*5) typedef struct CHiMicrophoneNode { @@ -48,11 +50,26 @@ static int pacallback(const void *input_, void *output, unsigned long samples, c static int microphone_start(CHiPubNode *pubn) { CHiMicrophoneNode *node = (void*) pubn; + int dev; + for(dev = 0; dev < CHi_Microphone_GetSourceCount(); dev++) { + if(!strcmp(CHi_Microphone_GetSourceName(dev), CHi_Crawl(&pubn->sinks[0])->data.text)) { + break; + } + } + + if(dev == CHi_Microphone_GetSourceCount()) { + for(dev = 0; dev < CHi_Microphone_GetSourceCount(); dev++) { + if(!strcmp(CHi_Microphone_GetSourceName(dev), "default")) { + break; + } + } + } + PaStreamParameters params = { - .device = pubn->sinks[0].data.vec4[0], + .device = dev, .channelCount = 1, .sampleFormat = paInt16, - .suggestedLatency = Pa_GetDeviceInfo(pubn->sinks[0].data.vec4[0])->defaultLowInputLatency, + .suggestedLatency = Pa_GetDeviceInfo(dev)->defaultLowInputLatency, }; Pa_OpenStream(&node->paStream, ¶ms, NULL, 48000, 0, paNoFlag, pacallback, pubn); Pa_StartStream(node->paStream); @@ -72,6 +89,8 @@ static int microphone_stop(CHiPubNode *pubn) { static int microphone_perform(CHiPubNode *pubn) { CHiMicrophoneNode *node = (void*) pubn; + MTR_BEGIN("CHi", "microphone_perform"); + if(pubn->sources[0].data.sample) { CHi_Image_Free(pubn->sources[0].data.sample); } @@ -94,6 +113,8 @@ static int microphone_perform(CHiPubNode *pubn) { pubn->clean = 0; + MTR_END("CHi", "microphone_perform"); + return 1; } @@ -178,7 +199,7 @@ CUTIVIS int CHi_ExportWav_Stop(CHiPubNode *pubn) { return 1; } CUTIVIS CHiPubNode *CHi_ExportWav() { - struct CHiExportWavNode *n = malloc(sizeof(*n)); + struct CHiExportWavNode *n = calloc(1, sizeof(*n)); n->pubn.type = CUTIHI_T('CExp','Wave'); n->pubn.Start = CHi_ExportWav_Start; n->pubn.Perform = exportwav_perform; diff --git a/hi/minitrace.c b/hi/minitrace.c new file mode 100644 index 0000000..1825c7f --- /dev/null +++ b/hi/minitrace.c @@ -0,0 +1,487 @@ +// minitrace +// Copyright 2014 by Henrik Rydgård +// http://www.github.com/hrydgard/minitrace +// Released under the MIT license. + +// See minitrace.h for basic documentation. + +#include +#include +#include + +#ifdef _WIN32 +#pragma warning (disable:4996) +#define WIN32_LEAN_AND_MEAN +#include +#define __thread __declspec(thread) +#define pthread_mutex_t CRITICAL_SECTION +#define pthread_mutex_init(a, b) InitializeCriticalSection(a) +#define pthread_mutex_lock(a) EnterCriticalSection(a) +#define pthread_mutex_unlock(a) LeaveCriticalSection(a) +#define pthread_mutex_destroy(a) DeleteCriticalSection(a) +#else +#include +#include +#include +#include +#endif + +#include "minitrace.h" + +#ifdef __GNUC__ +#define ATTR_NORETURN __attribute__((noreturn)) +#else +#define ATTR_NORETURN +#endif + +#define ARRAY_SIZE(x) sizeof(x)/sizeof(x[0]) +#define TRUE 1 +#define FALSE 0 + +// Ugh, this struct is already pretty heavy. +// Will probably need to move arguments to a second buffer to support more than one. +typedef struct raw_event { + const char *name; + const char *cat; + void *id; + int64_t ts; + uint32_t pid; + uint32_t tid; + char ph; + mtr_arg_type arg_type; + const char *arg_name; + union { + const char *a_str; + int a_int; + double a_double; + }; +} raw_event_t; + +static raw_event_t *event_buffer; +static raw_event_t *flush_buffer; +static volatile int event_count; +static int is_tracing = FALSE; +static int is_flushing = FALSE; +static int events_in_progress = 0; +static int64_t time_offset; +static int first_line = 1; +static FILE *f; +static __thread int cur_thread_id; // Thread local storage +static int cur_process_id; +static pthread_mutex_t mutex; +static pthread_mutex_t event_mutex; + +#define STRING_POOL_SIZE 100 +static char *str_pool[100]; + +// forward declaration +void mtr_flush_with_state(int); + +// Tiny portability layer. +// Exposes: +// get_cur_thread_id() +// get_cur_process_id() +// mtr_time_s() +// pthread basics +#ifdef _WIN32 +static int get_cur_thread_id() { + return (int)GetCurrentThreadId(); +} +static int get_cur_process_id() { + return (int)GetCurrentProcessId(); +} + +static uint64_t _frequency = 0; +static uint64_t _starttime = 0; +double mtr_time_s() { + if (_frequency == 0) { + QueryPerformanceFrequency((LARGE_INTEGER*)&_frequency); + QueryPerformanceCounter((LARGE_INTEGER*)&_starttime); + } + __int64 time; + QueryPerformanceCounter((LARGE_INTEGER*)&time); + return ((double) (time - _starttime) / (double) _frequency); +} + +// Ctrl+C handling for Windows console apps +static BOOL WINAPI CtrlHandler(DWORD fdwCtrlType) { + if (is_tracing && fdwCtrlType == CTRL_C_EVENT) { + printf("Ctrl-C detected! Flushing trace and shutting down.\n\n"); + mtr_flush(); + mtr_shutdown(); + } + ExitProcess(1); +} + +void mtr_register_sigint_handler() { + // For console apps: + SetConsoleCtrlHandler(&CtrlHandler, TRUE); +} + +#else + +static inline int get_cur_thread_id() { + return (int)(intptr_t)pthread_self(); +} +static inline int get_cur_process_id() { + return (int)getpid(); +} + +#if defined(BLACKBERRY) +double mtr_time_s() { + struct timespec time; + clock_gettime(CLOCK_MONOTONIC, &time); // Linux must use CLOCK_MONOTONIC_RAW due to time warps + return time.tv_sec + time.tv_nsec / 1.0e9; +} +#else +double mtr_time_s() { + static time_t start; + struct timeval tv; + gettimeofday(&tv, NULL); + if (start == 0) { + start = tv.tv_sec; + } + tv.tv_sec -= start; + return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0; +} +#endif // !BLACKBERRY + +static void termination_handler(int signum) ATTR_NORETURN; +static void termination_handler(int signum) { + (void) signum; + if (is_tracing) { + printf("Ctrl-C detected! Flushing trace and shutting down.\n\n"); + mtr_flush(); + fwrite("\n]}\n", 1, 4, f); + fclose(f); + } + exit(1); +} + +void mtr_register_sigint_handler() { +#ifndef MTR_ENABLED + return; +#endif + // Avoid altering set-to-be-ignored handlers while registering. + if (signal(SIGINT, &termination_handler) == SIG_IGN) + signal(SIGINT, SIG_IGN); +} + +#endif + +void mtr_init_from_stream(void *stream) { +#ifndef MTR_ENABLED + return; +#endif + event_buffer = (raw_event_t *)malloc(INTERNAL_MINITRACE_BUFFER_SIZE * sizeof(raw_event_t)); + flush_buffer = (raw_event_t *)malloc(INTERNAL_MINITRACE_BUFFER_SIZE * sizeof(raw_event_t)); + is_flushing = FALSE; + is_tracing = 1; + event_count = 0; + f = (FILE *)stream; + const char *header = "{\"traceEvents\":[\n"; + fwrite(header, 1, strlen(header), f); + time_offset = (uint64_t)(mtr_time_s() * 1000000); + first_line = 1; + pthread_mutex_init(&mutex, 0); + pthread_mutex_init(&event_mutex, 0); +} + +void mtr_init(const char *json_file) { +#ifndef MTR_ENABLED + return; +#endif + mtr_init_from_stream(fopen(json_file, "wb")); +} + +void mtr_shutdown() { + int i; +#ifndef MTR_ENABLED + return; +#endif + pthread_mutex_lock(&mutex); + is_tracing = FALSE; + pthread_mutex_unlock(&mutex); + mtr_flush_with_state(TRUE); + + fwrite("\n]}\n", 1, 4, f); + fclose(f); + pthread_mutex_destroy(&mutex); + pthread_mutex_destroy(&event_mutex); + f = 0; + free(event_buffer); + event_buffer = 0; + free(flush_buffer); + flush_buffer = 0; + for (i = 0; i < STRING_POOL_SIZE; i++) { + if (str_pool[i]) { + free(str_pool[i]); + str_pool[i] = 0; + } + } +} + +const char *mtr_pool_string(const char *str) { + int i; + for (i = 0; i < STRING_POOL_SIZE; i++) { + if (!str_pool[i]) { + str_pool[i] = (char*)malloc(strlen(str) + 1); + strcpy(str_pool[i], str); + return str_pool[i]; + } else { + if (!strcmp(str, str_pool[i])) + return str_pool[i]; + } + } + return "string pool full"; +} + +void mtr_start() { +#ifndef MTR_ENABLED + return; +#endif + pthread_mutex_lock(&mutex); + is_tracing = TRUE; + pthread_mutex_unlock(&mutex); +} + +void mtr_stop() { +#ifndef MTR_ENABLED + return; +#endif + pthread_mutex_lock(&mutex); + is_tracing = FALSE; + pthread_mutex_unlock(&mutex); +} + +// TODO: fwrite more than one line at a time. +// Flushing is thread safe and process async +// using double-buffering mechanism. +// Aware: only one flushing process may be +// running at any point of time +void mtr_flush_with_state(int is_last) { +#ifndef MTR_ENABLED + return; +#endif + int i = 0; + char linebuf[1024]; + char arg_buf[1024]; + char id_buf[256]; + int event_count_copy = 0; + int events_in_progress_copy = 1; + raw_event_t *event_buffer_tmp = NULL; + + // small critical section to swap buffers + // - no any new events can be spawn while + // swapping since they tied to the same mutex + // - checks for any flushing in process + pthread_mutex_lock(&mutex); + // if not flushing already + if (is_flushing) { + pthread_mutex_unlock(&mutex); + return; + } + is_flushing = TRUE; + event_count_copy = event_count; + event_buffer_tmp = flush_buffer; + flush_buffer = event_buffer; + event_buffer = event_buffer_tmp; + event_count = 0; + // waiting for any unfinished events before swap + while (events_in_progress_copy != 0) { + pthread_mutex_lock(&event_mutex); + events_in_progress_copy = events_in_progress; + pthread_mutex_unlock(&event_mutex); + } + pthread_mutex_unlock(&mutex); + + for (i = 0; i < event_count_copy; i++) { + raw_event_t *raw = &flush_buffer[i]; + int len; + switch (raw->arg_type) { + case MTR_ARG_TYPE_INT: + snprintf(arg_buf, ARRAY_SIZE(arg_buf), "\"%s\":%i", raw->arg_name, raw->a_int); + break; + case MTR_ARG_TYPE_STRING_CONST: + snprintf(arg_buf, ARRAY_SIZE(arg_buf), "\"%s\":\"%s\"", raw->arg_name, raw->a_str); + break; + case MTR_ARG_TYPE_STRING_COPY: + if (strlen(raw->a_str) > 700) { + snprintf(arg_buf, ARRAY_SIZE(arg_buf), "\"%s\":\"%.*s\"", raw->arg_name, 700, raw->a_str); + } else { + snprintf(arg_buf, ARRAY_SIZE(arg_buf), "\"%s\":\"%s\"", raw->arg_name, raw->a_str); + } + break; + case MTR_ARG_TYPE_NONE: + arg_buf[0] = '\0'; + break; + } + if (raw->id) { + switch (raw->ph) { + case 'S': + case 'T': + case 'F': + // TODO: Support full 64-bit pointers + snprintf(id_buf, ARRAY_SIZE(id_buf), ",\"id\":\"0x%08x\"", (uint32_t)(uintptr_t)raw->id); + break; + case 'X': + snprintf(id_buf, ARRAY_SIZE(id_buf), ",\"dur\":%i", (int)raw->a_double); + break; + } + } else { + id_buf[0] = 0; + } + const char *cat = raw->cat; +#ifdef _WIN32 + // On Windows, we often end up with backslashes in category. + char temp[256]; + { + int len = (int)strlen(cat); + int i; + if (len > 255) len = 255; + for (i = 0; i < len; i++) { + temp[i] = cat[i] == '\\' ? '/' : cat[i]; + } + temp[len] = 0; + cat = temp; + } +#endif + + len = snprintf(linebuf, ARRAY_SIZE(linebuf), "%s{\"cat\":\"%s\",\"pid\":%i,\"tid\":%i,\"ts\":%" PRId64 ",\"ph\":\"%c\",\"name\":\"%s\",\"args\":{%s}%s}", + first_line ? "" : ",\n", + cat, raw->pid, raw->tid, raw->ts - time_offset, raw->ph, raw->name, arg_buf, id_buf); + fwrite(linebuf, 1, len, f); + first_line = 0; + + if (raw->arg_type == MTR_ARG_TYPE_STRING_COPY) { + free((void*)raw->a_str); + } + #ifdef MTR_COPY_EVENT_CATEGORY_AND_NAME + free(raw->name); + free(raw->cat); + #endif + } + + pthread_mutex_lock(&mutex); + is_flushing = is_last; + pthread_mutex_unlock(&mutex); +} + +void mtr_flush() { + mtr_flush_with_state(FALSE); +} + +void internal_mtr_raw_event(const char *category, const char *name, char ph, void *id) { +#ifndef MTR_ENABLED + return; +#endif + pthread_mutex_lock(&mutex); + if (!is_tracing || event_count >= INTERNAL_MINITRACE_BUFFER_SIZE) { + pthread_mutex_unlock(&mutex); + return; + } + raw_event_t *ev = &event_buffer[event_count]; + ++event_count; + pthread_mutex_lock(&event_mutex); + ++events_in_progress; + pthread_mutex_unlock(&event_mutex); + pthread_mutex_unlock(&mutex); + + double ts = mtr_time_s(); + if (!cur_thread_id) { + cur_thread_id = get_cur_thread_id(); + } + if (!cur_process_id) { + cur_process_id = get_cur_process_id(); + } + +#ifdef MTR_COPY_EVENT_CATEGORY_AND_NAME + const size_t category_len = strlen(category); + ev->cat = malloc(category_len + 1); + strcpy(ev->cat, category); + + const size_t name_len = strlen(name); + ev->name = malloc(name_len + 1); + strcpy(ev->name, name); + +#else + ev->cat = category; + ev->name = name; +#endif + + ev->id = id; + ev->ph = ph; + if (ev->ph == 'X') { + double x; + memcpy(&x, id, sizeof(double)); + ev->ts = (int64_t)(x * 1000000); + ev->a_double = (ts - x) * 1000000; + } else { + ev->ts = (int64_t)(ts * 1000000); + } + ev->tid = cur_thread_id; + ev->pid = cur_process_id; + ev->arg_type = MTR_ARG_TYPE_NONE; + + pthread_mutex_lock(&event_mutex); + --events_in_progress; + pthread_mutex_unlock(&event_mutex); +} + +void internal_mtr_raw_event_arg(const char *category, const char *name, char ph, void *id, mtr_arg_type arg_type, const char *arg_name, void *arg_value) { +#ifndef MTR_ENABLED + return; +#endif + pthread_mutex_lock(&mutex); + if (!is_tracing || event_count >= INTERNAL_MINITRACE_BUFFER_SIZE) { + pthread_mutex_unlock(&mutex); + return; + } + raw_event_t *ev = &event_buffer[event_count]; + ++event_count; + pthread_mutex_lock(&event_mutex); + ++events_in_progress; + pthread_mutex_unlock(&event_mutex); + pthread_mutex_unlock(&mutex); + + if (!cur_thread_id) { + cur_thread_id = get_cur_thread_id(); + } + if (!cur_process_id) { + cur_process_id = get_cur_process_id(); + } + double ts = mtr_time_s(); + +#ifdef MTR_COPY_EVENT_CATEGORY_AND_NAME + const size_t category_len = strlen(category); + ev->cat = malloc(category_len + 1); + strcpy(ev->cat, category); + + const size_t name_len = strlen(name); + ev->name = malloc(name_len + 1); + strcpy(ev->name, name); + +#else + ev->cat = category; + ev->name = name; +#endif + + ev->id = id; + ev->ts = (int64_t)(ts * 1000000); + ev->ph = ph; + ev->tid = cur_thread_id; + ev->pid = cur_process_id; + ev->arg_type = arg_type; + ev->arg_name = arg_name; + switch (arg_type) { + case MTR_ARG_TYPE_INT: ev->a_int = (int)(uintptr_t)arg_value; break; + case MTR_ARG_TYPE_STRING_CONST: ev->a_str = (const char*)arg_value; break; + case MTR_ARG_TYPE_STRING_COPY: ev->a_str = strdup((const char*)arg_value); break; + case MTR_ARG_TYPE_NONE: break; + } + + pthread_mutex_lock(&event_mutex); + --events_in_progress; + pthread_mutex_unlock(&event_mutex); +} + diff --git a/hi/minitrace.h b/hi/minitrace.h new file mode 100644 index 0000000..dddacd0 --- /dev/null +++ b/hi/minitrace.h @@ -0,0 +1,276 @@ +// Minitrace +// +// Copyright 2014 by Henrik Rydgård +// http://www.github.com/hrydgard/minitrace +// Released under the MIT license. +// +// Ultra-light dependency free library for performance tracing C/C++ applications. +// Produces traces compatible with Google Chrome's trace viewer. +// Simply open "about:tracing" in Chrome and load the produced JSON. +// +// This contains far less template magic than the original libraries from Chrome +// because this is meant to be usable from C. +// +// See README.md for a tutorial. +// +// The trace format is documented here: +// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit +// More: +// http://www.altdevblogaday.com/2012/08/21/using-chrometracing-to-view-your-inline-profiling-data/ + +#ifndef MINITRACE_H +#define MINITRACE_H + +#include + +#ifdef MTR_BUILDING_WITH_CMAKE +#include "minitrace_export.h" +#else +#define MINITRACE_EXPORT +#endif + +// If MTR_ENABLED is not defined, Minitrace does nothing and has near zero overhead. +// Preferably, set this flag in your build system. If you can't just uncomment this line. +// #define MTR_ENABLED + +// By default, will collect up to 1000000 events, then you must flush. +// It's recommended that you simply call mtr_flush on a background thread +// occasionally. It's safe...ish. +#define INTERNAL_MINITRACE_BUFFER_SIZE 1000000 + +#ifdef __cplusplus +extern "C" { +#endif + +// Initializes Minitrace. Must be called very early during startup of your executable, +// before any MTR_ statements. +MINITRACE_EXPORT void mtr_init(const char *json_file); +// Same as above, but allows passing in a custom stream (FILE *), as returned by +// fopen(). It should be opened for writing, preferably in binary mode to avoid +// processing of line endings (i.e. the "wb" mode). +MINITRACE_EXPORT void mtr_init_from_stream(void *stream); + +// Shuts down minitrace cleanly, flushing the trace buffer. +MINITRACE_EXPORT void mtr_shutdown(void); + +// Lets you enable and disable Minitrace at runtime. +// May cause strange discontinuities in the output. +// Minitrace is enabled on startup by default. +MINITRACE_EXPORT void mtr_start(void); +MINITRACE_EXPORT void mtr_stop(void); + +// Flushes the collected data to disk, clearing the buffer for new data. +MINITRACE_EXPORT void mtr_flush(void); + +// Returns the current time in seconds. Used internally by Minitrace. No caching. +MINITRACE_EXPORT double mtr_time_s(void); + +// Registers a handler that will flush the trace on Ctrl+C. +// Works on Linux and MacOSX, and in Win32 console applications. +MINITRACE_EXPORT void mtr_register_sigint_handler(void); + +// Utility function that should rarely be used. +// If str is semi dynamic, store it permanently in a small pool so we don't need to malloc it. +// The pool fills up fast though and performance isn't great. +// Returns a fixed string if the pool is full. +MINITRACE_EXPORT const char *mtr_pool_string(const char *str); + +// Commented-out types will be supported in the future. +typedef enum { + MTR_ARG_TYPE_NONE = 0, + MTR_ARG_TYPE_INT = 1, // I + // MTR_ARG_TYPE_FLOAT = 2, // TODO + // MTR_ARG_TYPE_DOUBLE = 3, // TODO + MTR_ARG_TYPE_STRING_CONST = 8, // C + MTR_ARG_TYPE_STRING_COPY = 9, + // MTR_ARG_TYPE_JSON_COPY = 10, +} mtr_arg_type; + +// TODO: Add support for more than one argument (metadata) per event +// Having more costs speed and memory. +#define MTR_MAX_ARGS 1 + +// Only use the macros to call these. +MINITRACE_EXPORT void internal_mtr_raw_event(const char *category, const char *name, char ph, void *id); +MINITRACE_EXPORT void internal_mtr_raw_event_arg(const char *category, const char *name, char ph, void *id, mtr_arg_type arg_type, const char *arg_name, void *arg_value); + +#ifdef MTR_ENABLED + +// c - category. Can be filtered by in trace viewer (or at least that's the intention). +// A good use is to pass __FILE__, there are macros further below that will do it for you. +// n - name. Pass __FUNCTION__ in most cases, unless you are marking up parts of one. + +// Scopes. In C++, use MTR_SCOPE. In C, always match them within the same scope. +#define MTR_BEGIN(c, n) internal_mtr_raw_event(c, n, 'B', 0) +#define MTR_END(c, n) internal_mtr_raw_event(c, n, 'E', 0) +#define MTR_SCOPE(c, n) MTRScopedTrace ____mtr_scope(c, n) +#define MTR_SCOPE_LIMIT(c, n, l) MTRScopedTraceLimit ____mtr_scope(c, n, l) + +// Async events. Can span threads. ID identifies which events to connect in the view. +#define MTR_START(c, n, id) internal_mtr_raw_event(c, n, 'S', (void *)(id)) +#define MTR_STEP(c, n, id, step) internal_mtr_raw_event_arg(c, n, 'T', (void *)(id), MTR_ARG_TYPE_STRING_CONST, "step", (void *)(step)) +#define MTR_FINISH(c, n, id) internal_mtr_raw_event(c, n, 'F', (void *)(id)) + +// Flow events. Like async events, but displayed in a more fancy way in the viewer. +#define MTR_FLOW_START(c, n, id) internal_mtr_raw_event(c, n, 's', (void *)(id)) +#define MTR_FLOW_STEP(c, n, id, step) internal_mtr_raw_event_arg(c, n, 't', (void *)(id), MTR_ARG_TYPE_STRING_CONST, "step", (void *)(step)) +#define MTR_FLOW_FINISH(c, n, id) internal_mtr_raw_event(c, n, 'f', (void *)(id)) + +// The same macros, but with a single named argument which shows up as metadata in the viewer. +// _I for int. +// _C is for a const string arg. +// _S will copy the string, freeing on flush (expensive but sometimes necessary). +// but required if the string was generated dynamically. + +// Note that it's fine to match BEGIN_S with END and BEGIN with END_S, etc. +#define MTR_BEGIN_C(c, n, aname, astrval) internal_mtr_raw_event_arg(c, n, 'B', 0, MTR_ARG_TYPE_STRING_CONST, aname, (void *)(astrval)) +#define MTR_END_C(c, n, aname, astrval) internal_mtr_raw_event_arg(c, n, 'E', 0, MTR_ARG_TYPE_STRING_CONST, aname, (void *)(astrval)) +#define MTR_SCOPE_C(c, n, aname, astrval) MTRScopedTraceArg ____mtr_scope(c, n, MTR_ARG_TYPE_STRING_CONST, aname, (void *)(astrval)) + +#define MTR_BEGIN_S(c, n, aname, astrval) internal_mtr_raw_event_arg(c, n, 'B', 0, MTR_ARG_TYPE_STRING_COPY, aname, (void *)(astrval)) +#define MTR_END_S(c, n, aname, astrval) internal_mtr_raw_event_arg(c, n, 'E', 0, MTR_ARG_TYPE_STRING_COPY, aname, (void *)(astrval)) +#define MTR_SCOPE_S(c, n, aname, astrval) MTRScopedTraceArg ____mtr_scope(c, n, MTR_ARG_TYPE_STRING_COPY, aname, (void *)(astrval)) + +#define MTR_BEGIN_I(c, n, aname, aintval) internal_mtr_raw_event_arg(c, n, 'B', 0, MTR_ARG_TYPE_INT, aname, (void*)(intptr_t)(aintval)) +#define MTR_END_I(c, n, aname, aintval) internal_mtr_raw_event_arg(c, n, 'E', 0, MTR_ARG_TYPE_INT, aname, (void*)(intptr_t)(aintval)) +#define MTR_SCOPE_I(c, n, aname, aintval) MTRScopedTraceArg ____mtr_scope(c, n, MTR_ARG_TYPE_INT, aname, (void*)(intptr_t)(aintval)) + +// Instant events. For things with no duration. +#define MTR_INSTANT(c, n) internal_mtr_raw_event(c, n, 'I', 0) +#define MTR_INSTANT_C(c, n, aname, astrval) internal_mtr_raw_event_arg(c, n, 'I', 0, MTR_ARG_TYPE_STRING_CONST, aname, (void *)(astrval)) +#define MTR_INSTANT_I(c, n, aname, aintval) internal_mtr_raw_event_arg(c, n, 'I', 0, MTR_ARG_TYPE_INT, aname, (void *)(aintval)) + +// Counters (can't do multi-value counters yet) +#define MTR_COUNTER(c, n, val) internal_mtr_raw_event_arg(c, n, 'C', 0, MTR_ARG_TYPE_INT, n, (void *)(intptr_t)(val)) + +// Metadata. Call at the start preferably. Must be const strings. + +#define MTR_META_PROCESS_NAME(n) internal_mtr_raw_event_arg("", "process_name", 'M', 0, MTR_ARG_TYPE_STRING_COPY, "name", (void *)(n)) +#define MTR_META_THREAD_NAME(n) internal_mtr_raw_event_arg("", "thread_name", 'M', 0, MTR_ARG_TYPE_STRING_COPY, "name", (void *)(n)) +#define MTR_META_THREAD_SORT_INDEX(i) internal_mtr_raw_event_arg("", "thread_sort_index", 'M', 0, MTR_ARG_TYPE_INT, "sort_index", (void *)(i)) + +#else + +#define MTR_BEGIN(c, n) +#define MTR_END(c, n) +#define MTR_SCOPE(c, n) +#define MTR_START(c, n, id) +#define MTR_STEP(c, n, id, step) +#define MTR_FINISH(c, n, id) +#define MTR_FLOW_START(c, n, id) +#define MTR_FLOW_STEP(c, n, id, step) +#define MTR_FLOW_FINISH(c, n, id) +#define MTR_INSTANT(c, n) + +#define MTR_BEGIN_C(c, n, aname, astrval) +#define MTR_END_C(c, n, aname, astrval) +#define MTR_SCOPE_C(c, n, aname, astrval) + +#define MTR_BEGIN_S(c, n, aname, astrval) +#define MTR_END_S(c, n, aname, astrval) +#define MTR_SCOPE_S(c, n, aname, astrval) + +#define MTR_BEGIN_I(c, n, aname, aintval) +#define MTR_END_I(c, n, aname, aintval) +#define MTR_SCOPE_I(c, n, aname, aintval) + +#define MTR_INSTANT(c, n) +#define MTR_INSTANT_C(c, n, aname, astrval) +#define MTR_INSTANT_I(c, n, aname, aintval) + +// Counters (can't do multi-value counters yet) +#define MTR_COUNTER(c, n, val) + +// Metadata. Call at the start preferably. Must be const strings. + +#define MTR_META_PROCESS_NAME(n) + +#define MTR_META_THREAD_NAME(n) +#define MTR_META_THREAD_SORT_INDEX(i) + +#endif + +// Shortcuts for simple function timing with automatic categories and names. + +#define MTR_BEGIN_FUNC() MTR_BEGIN(__FILE__, __FUNCTION__) +#define MTR_END_FUNC() MTR_END(__FILE__, __FUNCTION__) +#define MTR_SCOPE_FUNC() MTR_SCOPE(__FILE__, __FUNCTION__) +#define MTR_INSTANT_FUNC() MTR_INSTANT(__FILE__, __FUNCTION__) +#define MTR_SCOPE_FUNC_LIMIT_S(l) MTRScopedTraceLimit ____mtr_scope(__FILE__, __FUNCTION__, l) +#define MTR_SCOPE_FUNC_LIMIT_MS(l) MTRScopedTraceLimit ____mtr_scope(__FILE__, __FUNCTION__, (double)l * 0.000001) + +// Same, but with a single argument of the usual types. +#define MTR_BEGIN_FUNC_S(aname, arg) MTR_BEGIN_S(__FILE__, __FUNCTION__, aname, arg) +#define MTR_END_FUNC_S(aname, arg) MTR_END_S(__FILE__, __FUNCTION__, aname, arg) +#define MTR_SCOPE_FUNC_S(aname, arg) MTR_SCOPE_S(__FILE__, __FUNCTION__, aname, arg) + +#define MTR_BEGIN_FUNC_C(aname, arg) MTR_BEGIN_C(__FILE__, __FUNCTION__, aname, arg) +#define MTR_END_FUNC_C(aname, arg) MTR_END_C(__FILE__, __FUNCTION__, aname, arg) +#define MTR_SCOPE_FUNC_C(aname, arg) MTR_SCOPE_C(__FILE__, __FUNCTION__, aname, arg) + +#define MTR_BEGIN_FUNC_I(aname, arg) MTR_BEGIN_I(__FILE__, __FUNCTION__, aname, arg) +#define MTR_END_FUNC_I(aname, arg) MTR_END_I(__FILE__, __FUNCTION__, aname, arg) +#define MTR_SCOPE_FUNC_I(aname, arg) MTR_SCOPE_I(__FILE__, __FUNCTION__, aname, arg) + +#ifdef __cplusplus +} + +#ifdef MTR_ENABLED +// These are optimized to use X events (combined B and E). Much easier to do in C++ than in C. +class MTRScopedTrace { +public: + MTRScopedTrace(const char *category, const char *name) + : category_(category), name_(name) { + start_time_ = mtr_time_s(); + } + ~MTRScopedTrace() { + internal_mtr_raw_event(category_, name_, 'X', &start_time_); + } + +private: + const char *category_; + const char *name_; + double start_time_; +}; + +// Only outputs a block if execution time exceeded the limit. +// TODO: This will effectively call mtr_time_s twice at the end, which is bad. +class MTRScopedTraceLimit { +public: + MTRScopedTraceLimit(const char *category, const char *name, double limit_s) + : category_(category), name_(name), limit_(limit_s) { + start_time_ = mtr_time_s(); + } + ~MTRScopedTraceLimit() { + double end_time = mtr_time_s(); + if (end_time - start_time_ >= limit_) { + internal_mtr_raw_event(category_, name_, 'X', &start_time_); + } + } + +private: + const char *category_; + const char *name_; + double start_time_; + double limit_; +}; + +class MTRScopedTraceArg { +public: + MTRScopedTraceArg(const char *category, const char *name, mtr_arg_type arg_type, const char *arg_name, void *arg_value) + : category_(category), name_(name) { + internal_mtr_raw_event_arg(category, name, 'B', 0, arg_type, arg_name, arg_value); + } + ~MTRScopedTraceArg() { + internal_mtr_raw_event(category_, name_, 'E', 0); + } + +private: + const char *category_; + const char *name_; +}; +#endif + +#endif + +#endif diff --git a/hi/node.c b/hi/node.c index 07aaa2b..0573a8e 100644 --- a/hi/node.c +++ b/hi/node.c @@ -15,6 +15,7 @@ #include #include #include +#include"minitrace.h" #include"linearity.h" @@ -69,18 +70,60 @@ static void adjacency_remove(CHiPubNode *source, CHiPubNode *sink) { } CUTIVIS CHiNodeGraph *CHi_NewNodeGraph() { + static int inited = 0; + if(!inited) { + inited = 1; + mtr_init("CHiTrace.json"); + } + CHiNodeGraph *ret = calloc(1, sizeof(*ret)); - ret->count = 0; - ret->nodes = malloc(sizeof(*ret->nodes) * (ret->capacity = 8)); - ret->eventOnStopComplete = NULL; - ret->eventOnFrameComplete = NULL; - ret->compilationStatus = CUTIHI_COMP_READY; - ret->adjacencyCount = 0; - ret->adjacencyCapacity = 8; - ret->adjacencies = malloc(sizeof(CHiAdjacency) * ret->adjacencyCapacity); + + CHi_NodeGraphReset(ret); + return ret; } +CUTIVIS CHiNodeGraph *CHi_NodeGraphReset(CHiNodeGraph *ng) { + for(size_t n = 0; n < ng->count; n++) { + if(ng->nodes[n]->Destroy) { + ng->nodes[n]->Destroy(ng->nodes[n]); + } else { + free(ng->nodes[n]); + } + } + + if(ng->nodes) { + free(ng->nodes); + } + if(ng->adjacencies) { + free(ng->adjacencies); + } + if(ng->keyframesList.keyframes) { + free(ng->keyframesList.keyframes); + } + + void *eOnStop = ng->eventOnStopComplete; + void *eOnFrame = ng->eventOnFrameComplete; + void *ud = ng->ud; + + memset(ng, 0, sizeof(*ng)); + + ng->count = 0; + ng->nodes = malloc(sizeof(*ng->nodes) * (ng->capacity = 8)); + ng->eventOnStopComplete = NULL; + ng->eventOnFrameComplete = NULL; + ng->compilationStatus = CUTIHI_COMP_READY; + ng->adjacencyCount = 0; + ng->adjacencyCapacity = 8; + ng->adjacencies = malloc(sizeof(CHiAdjacency) * ng->adjacencyCapacity); + + ng->eventOnStopComplete = eOnStop; + ng->eventOnFrameComplete = eOnFrame; + ng->ud = ud; + + return ng; +} + CUTIVIS CHiValue *CHi_Crawl(CHiValue *v) { while(v->type == CUTIHI_VAL_LINKED || v->type == CUTIHI_VAL_KEYED) { if(v->type == CUTIHI_VAL_LINKED) { @@ -434,6 +477,8 @@ void *compile_thread(void *ctx_) { free(ctx); + mtr_flush(); + return NULL; } CUTIVIS void CHi_BeginCompilation(CHiNodeGraph *ng) { @@ -487,7 +532,7 @@ err: return 0; } CUTIVIS CHiPubNode *CHi_Image() { - CHiPubNode *n = malloc(sizeof(*n)); + CHiPubNode *n = calloc(1, sizeof(*n)); n->type = CUTIHI_T('CIma','ge '); n->Start = n->Stop = NULL; n->Perform = image_perform; @@ -502,6 +547,8 @@ CUTIVIS CHiPubNode *CHi_Image() { static int embed_perform(CHiPubNode *node) { if(node->clean) return 1; + MTR_BEGIN("CHi", "embed_perform"); + node->sources[0].type = CUTIHI_VAL_SAMPLE; CHiImage *main = CHi_Crawl(&node->sinks[0])->data.sample; @@ -541,11 +588,13 @@ static int embed_perform(CHiPubNode *node) { } } + MTR_END("CHi", "embed_perform"); + node->clean = 0; return 1; } CUTIVIS CHiPubNode *CHi_Embed() { - CHiPubNode *n = malloc(sizeof(*n)); + CHiPubNode *n = calloc(1, sizeof(*n)); n->type = CUTIHI_T('CEmb','ed '); n->Start = n->Stop = NULL; n->Perform = embed_perform; @@ -583,7 +632,7 @@ static int constantsample_perform(CHiPubNode *node) { return 1; } CUTIVIS CHiPubNode *CHi_ConstantSample() { - CHiPubNode *n = malloc(sizeof(*n)); + CHiPubNode *n = calloc(1, sizeof(*n)); n->type = CUTIHI_T('CCns','tCol'); n->Start = n->Stop = NULL; n->Perform = constantsample_perform; @@ -598,16 +647,20 @@ CUTIVIS CHiPubNode *CHi_ConstantSample() { static int modulate_perform(CHiPubNode *node) { if(node->clean) return 1; + MTR_BEGIN("CHi", "modulate_perform"); + node->sources[0].type = CUTIHI_VAL_SAMPLE; if(node->sources->data.sample) CHi_Image_Free(node->sources->data.sample); node->sources->data.sample = CHi_Image_New(2, 4, 8 * 16, 16, 16, NULL); + MTR_END("CHi", "modulate_perform"); + node->clean = 0; return 1; } CUTIVIS CHiPubNode *CHi_Modulate() { - CHiPubNode *n = malloc(sizeof(*n)); + CHiPubNode *n = calloc(1, sizeof(*n)); n->type = CUTIHI_T('CMod','ulat'); n->Start = n->Stop = NULL; n->Perform = modulate_perform; @@ -682,7 +735,7 @@ CUTIVIS float CHi_Time_GetDelta(CHiNodeGraph *ng) { return ng->timedelta; } CUTIVIS CHiPubNode *CHi_Time() { - CHiPubNode *n = malloc(sizeof(*n)); + CHiPubNode *n = calloc(1, sizeof(*n)); n->type = CUTIHI_T('CTim','e '); n->Start = n->Stop = NULL; n->Perform = time_perform; @@ -701,6 +754,8 @@ static PangoLayout *playout; static int text_perform(CHiPubNode *n) { if(n->clean) return 1; + MTR_BEGIN("CHi", "text_perform"); + if(!pfontmap) { pfontmap = pango_ft2_font_map_new(); pango_ft2_font_map_set_resolution(PANGO_FT2_FONT_MAP(pfontmap), 72, 72); @@ -750,11 +805,13 @@ static int text_perform(CHiPubNode *n) { } free(bmp.buffer); + MTR_END("CHi", "text_perform"); + n->clean = 0; return 1; } CUTIVIS CHiPubNode *CHi_Text() { - CHiPubNode *n = malloc(sizeof(*n)); + CHiPubNode *n = calloc(1, sizeof(*n)); n->type = CUTIHI_T('CTex','t '); n->Start = n->Stop = NULL; n->Perform = text_perform; @@ -769,6 +826,8 @@ CUTIVIS CHiPubNode *CHi_Text() { static int mixer_perform(CHiPubNode *n) { n->sources[0].type = CUTIHI_VAL_SAMPLE; + MTR_BEGIN("CHi", "mixer_perform"); + if(n->sources[0].data.sample) { CHi_Image_Free(n->sources[0].data.sample); n->sources[0].data.sample = NULL; @@ -791,6 +850,8 @@ static int mixer_perform(CHiPubNode *n) { _mm_stream_si128((__m128i*) ((uintptr_t) n->sources[0].data.sample->data16 + b), _mm_adds_epi16(a0, a1)); } + MTR_END("CHi", "mixer_perform"); + n->clean = 0; return 1; } @@ -809,7 +870,7 @@ static int preview_perform(CHiPubNode *n) { return 1; } CUTIVIS CHiPubNode *CHi_Preview() { - CHiPubNode *n = malloc(sizeof(*n)); + CHiPubNode *n = calloc(1, sizeof(*n)); n->type = CUTIHI_T('CPre','view'); n->Start = n->Stop = NULL; n->Perform = preview_perform; @@ -839,6 +900,7 @@ static void save_chival(CHiNodeGraph *ng, CHiSaveWriter writer, CHiValType type, assert(index < ng->count); writer(ud, &(uint64_t) {index}, sizeof(uint64_t)); + writer(ud, &(uint16_t) {data.linked.idx}, sizeof(uint16_t)); } else if(type == CUTIHI_VAL_KEYED) { size_t index; for(index = 0; index < ng->keyframesList.count; index++) { @@ -852,30 +914,42 @@ static void save_chival(CHiNodeGraph *ng, CHiSaveWriter writer, CHiValType type, } } +static void load_chival(CHiNodeGraph *ng, CHiLoadReader reader, CHiValType type, CHiValueRaw *data, void *ud) { + if(type == CUTIHI_VAL_TEXT) { + uint32_t len; + reader(ud, &len, sizeof(len)); + + data->text = malloc(len + 1); + + reader(ud, data->text, len); + + data->text[len] = 0; + } else if(type == CUTIHI_VAL_VEC4) { + reader(ud, data->vec4, sizeof(data->vec4)); + } else if(type == CUTIHI_VAL_LINKED) { + uint64_t index; + reader(ud, &index, sizeof(index)); + + data->linked.to = ng->nodes[index]; + + uint16_t idx; + reader(ud, &idx, sizeof(idx)); + + data->linked.idx = idx; + } else if(type == CUTIHI_VAL_KEYED) { + uint64_t index; + reader(ud, &index, sizeof(index)); + + data->keyed = ng->keyframesList.keyframes[index]; + } +} + CUTIVIS int CHi_NodeGraphSave(CHiNodeGraph *ng, CHiSaveWriter writer, void *ud) { writer(ud, "\x71\x74\xCE\xA0", 4); writer(ud, &(float) {ng->duration}, sizeof(float)); writer(ud, &(float) {ng->time}, sizeof(float)); - writer(ud, &(uint64_t) {ng->count}, sizeof(uint64_t)); - - for(size_t i = 0; i < ng->count; i++) { - CHiPubNode *node = ng->nodes[i]; - - writer(ud, &(uint16_t) {node->type}, sizeof(uint16_t)); - - node->Save(node, ud, writer); - - writer(ud, &(uint16_t) {node->sinkCount}, sizeof(uint16_t)); - - for(size_t sink = 0; sink < node->sinkCount; sink++) { - writer(ud, &(uint16_t) {node->sinks[sink].type}, sizeof(uint16_t)); - - save_chival(ng, writer, node->sinks[sink].type, node->sinks[sink].data, ud); - } - } - writer(ud, &(uint64_t) {ng->keyframesList.count}, sizeof(uint64_t)); for(size_t i = 0; i < ng->keyframesList.count; i++) { @@ -895,5 +969,182 @@ CUTIVIS int CHi_NodeGraphSave(CHiNodeGraph *ng, CHiSaveWriter writer, void *ud) writer(ud, kfs->extrapolationParameter, sizeof(kfs->extrapolationParameter)); } + writer(ud, &(uint64_t) {ng->count}, sizeof(uint64_t)); + + for(size_t i = 0; i < ng->count; i++) { + CHiPubNode *node = ng->nodes[i]; + + writer(ud, &(uint64_t) {node->type}, sizeof(uint64_t)); + } + + for(size_t i = 0; i < ng->count; i++) { + CHiPubNode *node = ng->nodes[i]; + + if(node->Save) { + node->Save(node, ud, writer); + } + + writer(ud, &(uint16_t) {node->sinkCount}, sizeof(uint16_t)); + + for(size_t sink = 0; sink < node->sinkCount; sink++) { + writer(ud, &(uint16_t) {node->sinks[sink].type}, sizeof(uint16_t)); + + save_chival(ng, writer, node->sinks[sink].type, node->sinks[sink].data, ud); + } + } + + return 0; +} + +CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader reader, void *ud) { + { + char magic[4]; + reader(ud, magic, sizeof(magic)); + if(memcmp(magic, "\x71\x74\xCE\xA0", 4)) { + return 1; + } + } + + CHi_NodeGraphReset(ng); + + reader(ud, &ng->duration, sizeof(float)); + reader(ud, &ng->time, sizeof(float)); + + { + uint64_t count; + reader(ud, &count, sizeof(count)); + ng->keyframesList.count = count; + } + + for(size_t i = 0; i < ng->keyframesList.count; i++) { + CHiKeyframes *kfs = ng->keyframesList.keyframes[i] = calloc(1, sizeof(*kfs)); + + { + uint16_t type; + reader(ud, &type, sizeof(type)); + kfs->type = type; + } + + { + uint64_t count; + reader(ud, &count, sizeof(count)); + kfs->count = count; + } + + kfs->times = calloc(kfs->count, sizeof(*kfs->times)); + + reader(ud, kfs->times, kfs->count * sizeof(*kfs->times)); + + for(size_t k = 0; k < kfs->count; k++) { + load_chival(ng, reader, kfs->type, &kfs->values[k], ud); + } + + { + uint16_t extrap; + reader(ud, &extrap, sizeof(extrap)); + kfs->extrapolationMode = extrap; + } + + reader(ud, kfs->extrapolationParameter, sizeof(kfs->extrapolationParameter)); + } + + { + uint64_t count; + reader(ud, &count, sizeof(count)); + ng->count = count; + } + + ng->capacity = ng->count < 8 ? 8 : ng->count; + + ng->nodes = calloc(ng->capacity, sizeof(*ng->nodes)); + + for(size_t i = 0; i < ng->count; i++) { + uint64_t type; + + reader(ud, &type, sizeof(type)); + + CHiPubNode *n = NULL; + + if(type == CUTIHI_T('CPre','view')) { + n = CHi_Preview(); + } else if(type == CUTIHI_T('CMix','er ')) { + n = CHi_Mixer(); + } else if(type == CUTIHI_T('CTex','t ')) { + n = CHi_Text(); + } else if(type == CUTIHI_T('CTim','e ')) { + n = CHi_Time(); + } else if(type == CUTIHI_T('CMod','ulat')) { + n = CHi_Modulate(); + } else if(type == CUTIHI_T('CCns','tCol')) { + n = CHi_ConstantSample(); + } else if(type == CUTIHI_T('CEmb','ed ')) { + n = CHi_Embed(); + } else if(type == CUTIHI_T('CIma','ge ')) { + n = CHi_Image(); + } else if(type == CUTIHI_T('CWin','dow ')) { + n = CHi_Window(); + } else if(type == CUTIHI_T('CInA','udio')) { + n = CHi_Microphone(); + } else if(type == CUTIHI_T('CExp','Wave')) { + n = CHi_ExportWav(); + } else if(type == CUTIHI_T('CMov','ie ')) { + n = CHi_Movie(); + } else if(type == CUTIHI_T('CEnc','GVP8')) { + n = CHi_EncodeVP8(); + } else if(type == CUTIHI_T('CEnc','GVP9')) { + n = CHi_EncodeVP9(); + } else if(type == CUTIHI_T('CExp','Webm')) { + n = CHi_MuxWebm(); + } else if(type == CUTIHI_T('CKey','hook')) { + n = CHi_Keyhook(); + } else if(type == CUTIHI_T('CKey','hook')) { + n = CHi_Keyhook(); + } else if(type == CUTIHI_T('CEnc','Opus')) { + n = CHi_EncodeOpus(); + } else if(type == CUTIHI_T('CWeb','Cam ')) { + n = CHi_Camera(); + } else if(type == CUTIHI_T('CCmp','nScl')) { + n = CHi_ComponentScale(); + } + + n->ng = ng; + + if(!n) { + CHi_NodeGraphReset(ng); + puts("Error: Unknown node type!"); + return 1; + } + + ng->nodes[i] = n; + } + + for(size_t i = 0; i < ng->count; i++) { + CHiPubNode *n = ng->nodes[i]; + + { + uint16_t u16; + reader(ud, &u16, sizeof(u16)); + n->sinkCount = u16; + } + + n->sinks = calloc(n->sinkCount, sizeof(*n->sinks)); + + for(size_t s = 0; s < n->sinkCount; s++) { + { + uint16_t u16; + reader(ud, &u16, sizeof(u16)); + n->sinks[s].type = u16; + } + + load_chival(ng, reader, n->sinks[s].type, &n->sinks[s].data, ud); + + if(n->sinks[s].type == CUTIHI_VAL_LINKED) { + adjacency_add(n->sinks[s].data.linked.to, n); + } + } + } + + update_keyed_values(ng); + return 0; } diff --git a/hi/node.h b/hi/node.h index 0826362..885f350 100644 --- a/hi/node.h +++ b/hi/node.h @@ -17,6 +17,7 @@ extern "C" { #define CUTIHI_T(a, b) ((((uint64_t) htonl(b)) << 32) | htonl(a)) typedef size_t(*CHiSaveWriter)(void *ud, const void *data, size_t len); +typedef size_t(*CHiLoadReader)(void *ud, void *data, size_t len); typedef enum { CUTIHI_VAL_NONE = 0, @@ -68,6 +69,8 @@ typedef struct CHiPubNode { int (*Stop)(struct CHiPubNode *node); char clean; + void (*Destroy)(struct CHiPubNode *node); + int (*Save)(struct CHiPubNode *node, void *ud, CHiSaveWriter writer); size_t sinkCount; @@ -137,7 +140,9 @@ typedef struct CHiNodeGraph { } CHiNodeGraph; CUTIVIS CHiNodeGraph *CHi_NewNodeGraph(); +CUTIVIS CHiNodeGraph *CHi_NodeGraphReset(CHiNodeGraph *ng); CUTIVIS int CHi_NodeGraphSave(CHiNodeGraph *ng, CHiSaveWriter writer, void *ud); +CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader reader, void *ud); CUTIVIS void CHi_RegisterNode(CHiNodeGraph*, CHiPubNode*); CUTIVIS void CHi_MakeDirty(CHiNodeGraph*, CHiPubNode*); @@ -156,22 +161,18 @@ CUTIVIS void CHi_SetDuration(CHiNodeGraph *ng, float); CUTIVIS void CHi_BeginCompilation(CHiNodeGraph *ng); CUTIVIS void CHi_StopCompilation(CHiNodeGraph *ng); -#define CUTIHI_NODE_IMAGE 0 #define CUTIHI_IMAGE_IN_FILE 0 #define CUTIHI_IMAGE_OUT_SAMPLE 1 CUTIVIS CHiPubNode *CHi_Image(); -#define CUTIHI_NODE_EMBED 1 #define CUTIHI_EMBED_OUT_MAIN 0 #define CUTIHI_EMBED_MAX_SMALLS 3 CUTIVIS CHiPubNode *CHi_Embed(); -#define CUTIHI_NODE_CONSTANTSAMPLE 2 #define CUTIHI_CONSTANTSAMPLE_IN_COLOR 0 #define CUTIHI_CONSTANTSAMPLE_OUT_SAMPLE 1 CUTIVIS CHiPubNode *CHi_ConstantSample(); -#define CUTIHI_NODE_MODULATE 3 #define CUTIHI_MODULATE_IN_SAMPLE 0 #define CUTIHI_MODULATE_BRIGHTNESS 1 #define CUTIHI_MODULATE_CONTAST 2 @@ -179,21 +180,18 @@ CUTIVIS CHiPubNode *CHi_ConstantSample(); #define CUTIHI_MODULATE_OUT_SAMPLE 4 CUTIVIS CHiPubNode *CHi_Modulate(); -#define CUTIHI_NODE_MOVIE 4 #define CUTIHI_MOVIE_IN_FILE 0 #define CUTIHI_MOVIE_IN_TIME 1 #define CUTIHI_MOVIE_IN_DELAY 2 #define CUTIHI_MOVIE_OUT_SAMPLE 2 CUTIVIS CHiPubNode *CHi_Movie(); -#define CUTIHI_NODE_TIME 5 #define CUTIHI_TIME_OUT 0 CUTIVIS void CHi_Time_Set(CHiNodeGraph *ng, float); CUTIVIS float CHi_Time_Get(CHiNodeGraph *ng); CUTIVIS float CHi_Time_GetDelta(CHiNodeGraph *ng); CUTIVIS CHiPubNode *CHi_Time(); -#define CUTIHI_NODE_ENCODEVP9 6 #define CUTIHI_ENCODEVP9_IN_SAMPLE 0 #define CUTIHI_ENCODEVP9_IN_BITRATE 1 #define CUTIHI_ENCODEVP9_IN_BITRATE 1 @@ -204,7 +202,6 @@ CUTIVIS CHiPubNode *CHi_EncodeVP8(); CUTIVIS int CHi_EncodeVP9_Start(CHiPubNode*); CUTIVIS int CHi_EncodeVP9_Stop(CHiPubNode*); -#define CUTIHI_NODE_MUXWEBM 7 #define CUTIHI_MUXWEBM_IN_BITSTREAM 0 #define CUTIHI_MUXWEBM_IN_AUDIOBITSTREAM 1 #define CUTIHI_MUXWEBM_IN_FILENAME 2 @@ -212,7 +209,6 @@ CUTIVIS CHiPubNode *CHi_MuxWebm(); CUTIVIS int CHi_MuxWebm_Start(CHiPubNode*); CUTIVIS int CHi_MuxWebm_Stop(CHiPubNode*); -#define CUTIHI_NODE_WINDOW 8 #define CUTIHI_WINDOW_IN_WINDOWNAME 0 #define CUTIHI_WINDOW_OUT_SAMPLE 1 CUTIVIS CHiPubNode *CHi_Window(); @@ -222,39 +218,30 @@ CUTIVIS const char *CHi_Window_GetSourceName(size_t); CUTIVIS uintptr_t CHi_Window_GetSourceData(size_t); CUTIVIS size_t CHi_Window_GetNextSource(size_t); -#define CUTIHI_NODE_MICROPHONE 9 #define CUTIHI_MICROPHONE_IN_NAME 0 #define CUTIHI_MICROPHONE_OUT_AUDIO 1 CUTIVIS CHiPubNode *CHi_Microphone(); -#define CUTIHI_NODE_TEXT 10 #define CUTIHI_TEXT_IN_TEXT 0 #define CUTIHI_TEXT_OUT_SAMPLE 0 CUTIVIS CHiPubNode *CHi_Text(); -#define CUTIHI_NODE_EXPORTWAV 11 CUTIVIS CHiPubNode *CHi_ExportWav(); CUTIVIS int CHi_ExportWav_Start(CHiPubNode*); CUTIVIS int CHi_ExportWav_Stop(CHiPubNode*); -#define CUTIHI_NODE_ENCODEOPUS 12 CUTIVIS CHiPubNode *CHi_EncodeOpus(); CUTIVIS int CHi_EncodeOpus_Start(CHiPubNode*); CUTIVIS int CHi_EncodeOpus_Stop(CHiPubNode*); -#define CUTIHI_NODE_CAMERA 13 CUTIVIS CHiPubNode *CHi_Camera(); -#define CUTIHI_NODE_PREVIEW 14 CUTIVIS CHiPubNode *CHi_Preview(); -#define CUTIHI_NODE_COMPONENT_SCALE 15 CUTIVIS CHiPubNode *CHi_ComponentScale(); -#define CUTIHI_NODE_KEYHOOK 16 CUTIVIS CHiPubNode *CHi_Keyhook(); -#define CUTIHI_NODE_MIXER 18 CUTIVIS CHiPubNode *CHi_Mixer(); CUTIVIS CHiValue *CHi_Crawl(CHiValue*); diff --git a/hi/opus.c b/hi/opus.c index e3da848..25fc313 100644 --- a/hi/opus.c +++ b/hi/opus.c @@ -53,7 +53,7 @@ static int encodeopus_perform(CHiPubNode *pubn) { return 1; } CUTIVIS CHiPubNode *CHi_EncodeOpus() { - struct CHiEncodeOpusNode *ret = malloc(sizeof(*ret)); + struct CHiEncodeOpusNode *ret = calloc(1, sizeof(*ret)); ret->pubn.type = CUTIHI_T('CEnc','Opus'); ret->pubn.Start = CHi_EncodeOpus_Start; ret->pubn.Perform = &encodeopus_perform; diff --git a/hi/relay.c b/hi/relay.c index 10a1fe4..c15591a 100644 --- a/hi/relay.c +++ b/hi/relay.c @@ -35,7 +35,7 @@ static int scale_perform(CHiPubNode *n) { } CUTIVIS CHiPubNode *CHi_ComponentScale() { - CHiPubNode *n = malloc(sizeof(*n)); + CHiPubNode *n = calloc(1, sizeof(*n)); n->type = CUTIHI_T('CCmp','nScl'); n->Start = n->Stop = NULL; n->Perform = scale_perform; @@ -108,11 +108,21 @@ static int keyhook_perform(CHiPubNode *n) { return 1; } +static void keyhook_destroy(CHiPubNode *pubn) { + CHiKeyhookNode *n = (void*) pubn; + + XRecordDisableContext(dpy, n->rc); + XRecordFreeContext(dpy, n->rc); + + free(n); +} + CUTIVIS CHiPubNode *CHi_Keyhook() { - CHiKeyhookNode *n = malloc(sizeof(*n)); + CHiKeyhookNode *n = calloc(1, sizeof(*n)); n->pub.type = CUTIHI_T('CKey','hook'); n->pub.Start = n->pub.Stop = NULL; n->pub.Perform = keyhook_perform; + n->pub.Destroy = keyhook_destroy; n->pub.clean = 0; n->pub.sinkCount = 2; n->pub.sinks = calloc(sizeof(*n->pub.sinks), n->pub.sinkCount); diff --git a/hi/webcam.c b/hi/webcam.c index 7f942ed..54c79ce 100644 --- a/hi/webcam.c +++ b/hi/webcam.c @@ -125,7 +125,7 @@ CUTIVIS CHiPubNode *CHi_Camera() { xioctl(camId, VIDIOC_STREAMON, &(enum v4l2_buf_type) {V4L2_BUF_TYPE_VIDEO_CAPTURE}); } - CHiPubNode *pubn = malloc(sizeof(*pubn)); + CHiPubNode *pubn = calloc(1, sizeof(*pubn)); pubn->type = CUTIHI_T('CWeb','Cam '); pubn->clean = 0; pubn->Start = pubn->Stop = NULL; diff --git a/hi/webmdec.cpp b/hi/webmdec.cpp index 0cb0693..fc8d29b 100644 --- a/hi/webmdec.cpp +++ b/hi/webmdec.cpp @@ -21,6 +21,8 @@ #include +#include"minitrace.h" + #include"linearity.h" struct CHiMovieNode; @@ -50,6 +52,12 @@ struct AudioParser final : webm::Callback { size_t sampleReadI = 0; int16_t sampleArray[SAMPLE_ARR]; + ~AudioParser() { + if(opus) { + opus_decoder_destroy(opus); + } + } + webm::Status OnClusterBegin(const webm::ElementMetadata &metadata, const webm::Cluster &cluster, webm::Action *action) final override { currentClusterTimecode = cluster.timecode.value(); return webm::Status(webm::Status::kOkCompleted); @@ -272,6 +280,8 @@ webm::Status CueParser::OnCuePoint(const webm::ElementMetadata &metadata, const static int movie_perform(CHiPubNode *pub) { CHiMovieNode *node = (CHiMovieNode*) ((uintptr_t) pub - offsetof(CHiMovieNode, pub)); + MTR_BEGIN("CHi", "movie_perform"); + int64_t t; if(pub->sinks[1].type == CUTIHI_VAL_NONE) t = CHi_Time_Get(pub->ng) * 1000; else t = CHi_Crawl(&pub->sinks[1])->data.vec4[0] * 1000; @@ -295,7 +305,7 @@ static int movie_perform(CHiPubNode *pub) { node->vparser.Feed(&cp, &node->vreader); free(node->filepathCache); - node->filepathCache = filepath; + node->filepathCache = strdup(filepath); node->timeCache = std::numeric_limits::max(); if(node->vcodecid == "V_VP9") { @@ -397,15 +407,40 @@ static int movie_perform(CHiPubNode *pub) { pub->sources[1].data.sample = aud; pub->clean = 0; + + MTR_END("CHi", "movie_perform"); + return 1; } +static void movie_destroy(CHiPubNode *pub) { + CHiMovieNode *node = (CHiMovieNode*) ((uintptr_t) pub - offsetof(CHiMovieNode, pub)); + + if(node->filepathCache) { + free(node->filepathCache); + } + + if(node->af) { + fclose(node->af); + } + + if(node->vf) { + fclose(node->vf); + vpx_codec_destroy(&node->codec); + } + + node->~CHiMovieNode(); + + free(node); +} + extern "C" { CUTIVIS CHiPubNode *CHi_Movie() { - CHiMovieNode *n = (CHiMovieNode*) malloc(sizeof(*n)); + CHiMovieNode *n = (CHiMovieNode*) calloc(1, sizeof(*n)); new (n) CHiMovieNode(); n->pub.type = CUTIHI_T('CMov','ie '); n->pub.Perform = movie_perform; + n->pub.Destroy = movie_destroy; n->pub.clean = 0; n->pub.sinkCount = 2; n->pub.sinks = (CHiValue*) calloc(sizeof(*n->pub.sinks), n->pub.sinkCount); diff --git a/hi/webmenc.cpp b/hi/webmenc.cpp index ccdf1d6..df65040 100644 --- a/hi/webmenc.cpp +++ b/hi/webmenc.cpp @@ -20,6 +20,8 @@ #include +#include"minitrace.h" + #include"linearity.h" struct CHiEncodeVP9Node { @@ -40,6 +42,8 @@ struct CHiEncodeVP9Node { static int encodevp9_perform(CHiPubNode *pub) { CHiEncodeVP9Node *node = (CHiEncodeVP9Node*) ((uintptr_t) pub - offsetof(CHiEncodeVP9Node, pub)); + MTR_BEGIN("CHi", "encodevp9_perform"); + pub->sources[0].type = CUTIHI_VAL_VP9BS; pub->sources[0].data.bitstream = NULL; @@ -215,16 +219,24 @@ static int encodevp9_perform(CHiPubNode *pub) { pub->sources[0].data.bitstream = ret; + MTR_END("CHi", "encodevp9_perform"); + return 1; } +static void encodevp_destroy(CHiPubNode *pub) { + CHiEncodeVP9Node *node = (CHiEncodeVP9Node*) ((uintptr_t) pub - offsetof(CHiEncodeVP9Node, pub)); + free(node); +} + CUTIVIS CHiPubNode *CHi_EncodeVP8() { - CHiEncodeVP9Node *n = (CHiEncodeVP9Node*) malloc(sizeof(*n)); + CHiEncodeVP9Node *n = (CHiEncodeVP9Node*) calloc(1, sizeof(*n)); new (n) CHiEncodeVP9Node(); n->pub.type = CUTIHI_T('CEnc','GVP8'); n->pub.Start = CHi_EncodeVP9_Start; n->pub.Perform = encodevp9_perform; n->pub.Stop = CHi_EncodeVP9_Stop; + n->pub.Destroy = encodevp_destroy; n->pub.clean = 0; n->pub.sinks = (CHiValue*) calloc(sizeof(*n->pub.sinks), n->pub.sinkCount = 1); n->pub.sources = (CHiValue*) calloc(sizeof(*n->pub.sources), n->pub.sourceCount = 1); @@ -234,12 +246,13 @@ CUTIVIS CHiPubNode *CHi_EncodeVP8() { } CUTIVIS CHiPubNode *CHi_EncodeVP9() { - CHiEncodeVP9Node *n = (CHiEncodeVP9Node*) malloc(sizeof(*n)); + CHiEncodeVP9Node *n = (CHiEncodeVP9Node*) calloc(1, sizeof(*n)); new (n) CHiEncodeVP9Node(); n->pub.type = CUTIHI_T('CEnc','GVP9'); n->pub.Start = CHi_EncodeVP9_Start; n->pub.Perform = encodevp9_perform; n->pub.Stop = CHi_EncodeVP9_Stop; + n->pub.Destroy = encodevp_destroy; n->pub.clean = 0; n->pub.sinks = (CHiValue*) calloc(sizeof(*n->pub.sinks), n->pub.sinkCount = 1); n->pub.sources = (CHiValue*) calloc(sizeof(*n->pub.sources), n->pub.sourceCount = 1); @@ -296,6 +309,8 @@ CUTIVIS int CHi_EncodeVP9_Stop(CHiPubNode *pubn) { _mm_free(node->outU); _mm_free(node->outV); + vpx_codec_destroy(&node->codec); + return 1; } @@ -312,6 +327,8 @@ struct CHiMuxWebmNode { static int muxwebm_perform(CHiPubNode *pubn) { using namespace mkvmuxer; + MTR_BEGIN("CHi", "muxwebm_perform"); + CHiMuxWebmNode *alln = (CHiMuxWebmNode*) pubn; if(pubn->sinks[1].data.linked.to) { @@ -350,20 +367,29 @@ static int muxwebm_perform(CHiPubNode *pubn) { alln->videoBacklog.pop(); } + MTR_END("CHi", "muxwebm_perform"); + return 1; } +static void muxwebm_destroy(CHiPubNode *pubn) { + CHiMuxWebmNode *n = (CHiMuxWebmNode*) pubn; + + n->~CHiMuxWebmNode(); + + free(n); +} CUTIVIS CHiPubNode *CHi_MuxWebm() { - CHiMuxWebmNode *n = (CHiMuxWebmNode*) malloc(sizeof(*n)); + CHiMuxWebmNode *n = (CHiMuxWebmNode*) calloc(1, sizeof(*n)); + new (n) CHiMuxWebmNode; n->pub.type = CUTIHI_T('CExp','Webm'); n->pub.Start = CHi_MuxWebm_Start; n->pub.Perform = muxwebm_perform; + n->pub.Destroy = muxwebm_destroy; n->pub.Stop = CHi_MuxWebm_Stop; n->pub.clean = 0; n->pub.sinks = (CHiValue*) calloc(sizeof(*n->pub.sinks), n->pub.sinkCount = 3); n->pub.sourceCount = 0; n->pub.sources = NULL; - new (&n->audioBacklog) std::queue(); - new (&n->videoBacklog) std::queue(); return &n->pub; } CUTIVIS int CHi_MuxWebm_Start(CHiPubNode *pubn) { diff --git a/hi/window.c b/hi/window.c index f7fde1a..b3fa567 100644 --- a/hi/window.c +++ b/hi/window.c @@ -18,6 +18,8 @@ #include"linearity.h" +#include"minitrace.h" + static Display *d; static Window root; @@ -69,6 +71,8 @@ typedef struct { static int window_perform(CHiPubNode *n) { CHiWindowNode *w = (CHiWindowNode*) n; + MTR_BEGIN("CHi", "window_perform"); + Window toshoot; if(!find_window(d, &toshoot, CHi_Crawl(&n->sinks[0])->data.text)) return 0; @@ -112,26 +116,43 @@ static int window_perform(CHiPubNode *n) { n->clean = 0; + MTR_END("CHi", "window_perform"); + return 1; } +static void window_destroy(CHiPubNode *pubn) { + CHiWindowNode *n = (void*) pubn; + + if(n->vcache) { + XShmDetach(d, &n->shminfo); + shmdt(n->shminfo.shmaddr); + XDestroyImage(n->ximg); + } + + free(pubn); +} + CUTIVIS CHiPubNode *CHi_Window() { if(!d) { d = XOpenDisplay(NULL); root = RootWindow(d, DefaultScreen(d)); } - CHiWindowNode *n = malloc(sizeof(*n)); + CHiWindowNode *n = calloc(1, sizeof(*n)); n->pub.type = CUTIHI_T('CWin','dow '); n->pub.Start = n->pub.Stop = NULL; n->pub.Perform = window_perform; + n->pub.Destroy = window_destroy; n->pub.clean = 0; n->pub.sinkCount = 1; n->pub.sinks = calloc(sizeof(*n->pub.sinks), 1); n->pub.sourceCount = 1; n->pub.sources = calloc(sizeof(*n->pub.sources), 1); + n->xcache = 0; n->vcache = NULL; + return &n->pub; } @@ -147,6 +168,8 @@ CUTIVIS size_t CHi_Window_GetSourceCount() { int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list); + XFree(list); + return status >= Success ? numItems : 0; } @@ -163,6 +186,8 @@ CUTIVIS const char *CHi_Window_GetSourceName(size_t idx) { int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list); if(status >= Success) { + XFree(list); + status = XGetWMName(d, list[idx], &windowName); if(status >= Success) { found = 1; @@ -183,7 +208,9 @@ CUTIVIS uintptr_t CHi_Window_GetSourceData(size_t idx) { int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list); if(status >= Success) { - return list[idx]; + Window ret = list[idx]; + XFree(list); + return ret; } return 0; diff --git a/ui/frame.cpp b/ui/frame.cpp index a195d43..945f37d 100644 --- a/ui/frame.cpp +++ b/ui/frame.cpp @@ -28,12 +28,97 @@ #include +static void ShapeGrNode(GrNode *gn) { + if(gn->logical->type == CUTIHI_T('CPre','view')) { + gn->name = "Preview"; + gn->sinks = {{"Video", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CMix','er ')) { + gn->name = "Mixer"; + gn->sinks = {{"Sink 1", GrNode::Port::Type::SAMPLE}, {"Sink 2", GrNode::Port::Type::SAMPLE}}; + gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CTex','t ')) { + gn->name = "Text"; + gn->sinks = {{"Text", GrNode::Port::Type::TEXT}, {"Color", GrNode::Port::Type::COLOR}, {"DPI", GrNode::Port::Type::VEC1}}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CTim','e ')) { + gn->name = "Time"; + gn->sinks = {}; + gn->sources = {{"t", GrNode::Port::Type::VEC1}}; + } else if(gn->logical->type == CUTIHI_T('CMod','ulat')) { + gn->name = "Modulate"; + gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Brightness", GrNode::Port::Type::VEC1}, {"Contrast", GrNode::Port::Type::VEC1}, {"Hue", GrNode::Port::Type::VEC1}}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CCns','tCol')) { + gn->name = "Constant"; + gn->sinks = {{"Color", GrNode::Port::Type::COLOR}}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CEmb','ed ')) { + gn->name = "Embed"; + gn->sinks = { + {"Frame", GrNode::Port::Type::SAMPLE}, + {"Sub 1", GrNode::Port::Type::SAMPLE}, {" Pos", GrNode::Port::Type::VEC2}, {" Size", GrNode::Port::Type::VEC1}, + {"Sub 2", GrNode::Port::Type::SAMPLE}, {" Pos", GrNode::Port::Type::VEC2}, {" Size", GrNode::Port::Type::VEC1}, + {"Sub 3", GrNode::Port::Type::SAMPLE}, {" Pos", GrNode::Port::Type::VEC2}, {" Size", GrNode::Port::Type::VEC1} + }; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CIma','ge ')) { + gn->name = "Image"; + gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CWin','dow ')) { + gn->name = "Window"; + gn->sinks = {{"Name", GrNode::Port::Type::WINDOW_SOURCE}}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CInA','udio')) { + gn->name = "Microphone"; + gn->sinks = {{"Source", GrNode::Port::Type::MIC_SOURCE}}; + gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CExp','Wave')) { + gn->name = "Mux Wav"; + gn->sinks = {{"Filename", GrNode::Port::Type::FILE_SAVE}, {"Audio", GrNode::Port::Type::SAMPLE}}; + gn->sources = {}; + } else if(gn->logical->type == CUTIHI_T('CMov','ie ')) { + gn->name = "Movie"; + gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}, {"Time", GrNode::Port::Type::VEC1}}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Audio", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CEnc','GVP8')) { + gn->name = "Encode VP8"; + gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}}; + gn->sources = {{"Bitstream"}}; + } else if(gn->logical->type == CUTIHI_T('CEnc','GVP9')) { + gn->name = "Encode VP9"; + gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}}; + gn->sources = {{"Bitstream"}}; + } else if(gn->logical->type == CUTIHI_T('CExp','Webm')) { + gn->name = "Mux WebM"; + gn->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"Filename", GrNode::Port::Type::FILE_SAVE}}; + gn->sources = {}; + } else if(gn->logical->type == CUTIHI_T('CKey','hook')) { + gn->name = "Keyhook"; + gn->sinks = {{"Key", GrNode::Port::Type::VEC1}, {"Smooth Time", GrNode::Port::Type::VEC1}}; + gn->sources = {{"Bool", GrNode::Port::Type::VEC1}}; + } else if(gn->logical->type == CUTIHI_T('CEnc','Opus')) { + gn->name = "Encode Opus"; + gn->sinks = {{"Audio", GrNode::Port::Type::SAMPLE}}; + gn->sources = {{"Bitstream"}}; + } else if(gn->logical->type == CUTIHI_T('CWeb','Cam ')) { + gn->name = "Live Digital Camera"; + gn->sinks = {}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CCmp','nScl')) { + gn->name = "Scale"; + gn->sinks = {{"Vector", GrNode::Port::Type::VEC4}, {"Sample", GrNode::Port::Type::SAMPLE}}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else { + puts("Unknown node type."); + } +} + Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystemSettings::GetMetric(wxSYS_SCREEN_X) / 2, wxSystemSettings::GetMetric(wxSYS_SCREEN_Y) / 2}) { aui.SetManagedWindow(this); viewer = new ImageViewer(this); graph = new NodeGraph(this); - compsets = new CompositionSettings(this); timeline = new Timeline(this); auto info = wxAuiPaneInfo().Center().Caption("Preview").BestSize(GetSize().x, GetSize().y * 3 / 4); @@ -42,9 +127,6 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem info = wxAuiPaneInfo().Bottom().Caption("Node Graph").BestSize(GetSize().x, GetSize().y * 1 / 4); aui.AddPane(graph, info); - info = wxAuiPaneInfo().Right().Caption("Composition"); - aui.AddPane(compsets, info); - info = wxAuiPaneInfo().Bottom().Row(2).Caption("Timeline"); aui.AddPane(timeline, info); @@ -55,15 +137,165 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem [CUTIHI_MODE_LIVE] = wxBitmap{"tlml.bmp", wxBITMAP_TYPE_ANY}, [CUTIHI_MODE_OFFLINE] = wxBitmap{"tlmo.bmp", wxBITMAP_TYPE_ANY} }; - int modeTool = tlba->AddTool(wxID_ANY, "Mode", wxBitmap{"tlml.bmp", wxBITMAP_TYPE_ANY})->GetId(); + auto modeTool = tlba->AddTool(wxID_ANY, "Mode", wxBitmap{"tlml.bmp", wxBITMAP_TYPE_ANY}); CHi_SetMode(CUTIHI_MODE_LIVE); + + tlba->AddTool(wxID_OPEN, "Load", wxBitmap{"btn_load.bmp", wxBITMAP_TYPE_ANY}); + tlba->AddTool(wxID_SAVE, "Save", wxBitmap{"btn_save.bmp", wxBITMAP_TYPE_ANY}); + tlba->Bind(wxEVT_COMMAND_TOOL_CLICKED, [=](wxCommandEvent &ev){ - if(ev.GetId() == modeTool) { + if(ev.GetId() == modeTool->GetId()) { CHi_SetMode((CHiMode) ((CHi_GetMode() + 1) % 2)); - tlba->SetToolNormalBitmap(modeTool, modeImgs[CHi_GetMode()]); + tlba->SetToolNormalBitmap(modeTool->GetId(), modeImgs[CHi_GetMode()]); + + // wxToolBarToolBase::SetShortHelp doesn't work + if(CHi_GetMode() == CUTIHI_MODE_LIVE) { + tlba->SetToolShortHelp(modeTool->GetId(), "Set to live processing."); + } else { + tlba->SetToolShortHelp(modeTool->GetId(), "Set to offline processing."); + } + } else if(ev.GetId() == wxID_SAVE) { + wxFileDialog save{this, "Save", "", "", "Cuticle Project (*.ctc)|*.ctc", wxFD_SAVE | wxFD_OVERWRITE_PROMPT}; + + if(save.ShowModal() == wxID_OK) { + FILE *f = fopen(save.GetPath().mb_str(), "wb"); + + CHi_NodeGraphSave(graph->backendNG, +[](void *ud, const void *data, size_t len){ + auto f = (FILE*) ud; + + return fwrite(data, 1, len, f); + }, f); + + for(size_t n = 0; n < graph->backendNG->count; n++) { + GrNode *gn = *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){ + return gn->logical == graph->backendNG->nodes[n]; + }); + + int32_t pos[2] = {gn->GetPosition().x, gn->GetPosition().y}; + fwrite(pos, sizeof(pos), 1, f); + } + + fclose(f); + } + } else if(ev.GetId() == wxID_OPEN) { + wxFileDialog load{this, "Load", "", "", "Cuticle Project (*.ctc)|*.ctc", wxFD_OPEN | wxFD_FILE_MUST_EXIST}; + + if(load.ShowModal() == wxID_OK) { + FILE *f = fopen(load.GetPath().mb_str(), "rb"); + + if(CHi_NodeGraphLoad(graph->backendNG, +[](void *ud, void *data, size_t len){ + auto f = (FILE*) ud; + + return fread(data, 1, len, f); + }, f) == 0) { + + for(GrNode *gnode : graph->gnodes) { + gnode->Destroy(); + } + + graph->gnodes.clear(); + graph->links.clear(); + + for(size_t n = 0; n < graph->backendNG->count; n++) { + GrNode *gnode = new GrNode(graph); + + int32_t pos[2]; + fread(pos, sizeof(pos), 1, f); + + gnode->SetPosition({pos[0], pos[1]}); + + gnode->logical = graph->backendNG->nodes[n]; + + ShapeGrNode(gnode); + gnode->Fit(); + + graph->gnodes.push_back(gnode); + } + + for(size_t n = 0; n < graph->backendNG->count; n++) { + CHiPubNode *node = graph->backendNG->nodes[n]; + + for(size_t s = 0; s < node->sinkCount; s++) { + if(node->sinks[s].type == CUTIHI_VAL_LINKED) { + graph->links.push_back(NodeGraph::Link{graph->gnodes[n], s, *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){ return gn->logical == node->sinks[s].data.linked.to; }), node->sinks[s].data.linked.idx}); + } + } + } + + toolbar.duration->SetSeconds(std::max(0.f, graph->backendNG->duration)); + + if(graph->backendNG->duration <= 0) { + toolbar.durationEnable->SetValue(false); + toolbar.duration->Enable(false); + } + } + + fclose(f); + } + } + }); + tlba->AddSeparator(); + + toolbar.durationEnable = new wxCheckBox(tlba, wxID_ANY, ""); + toolbar.duration = new ctTimeCtrl(tlba, 120); + + toolbar.duration->Bind(wxEVT_COMMAND_TEXT_UPDATED, [=](wxCommandEvent&){ + CHi_SetDuration(graph->backendNG, toolbar.durationEnable->IsChecked() ? toolbar.duration->GetSeconds() : -1); + }); + + toolbar.durationEnable->SetValue(true); + toolbar.durationEnable->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent&){ + toolbar.duration->Enable(toolbar.durationEnable->GetValue()); + + if(!toolbar.durationEnable->GetValue()) { + CHi_SetDuration(graph->backendNG, -1); } }); + toolbar.btnPerform = new wxButton(tlba, wxID_ANY, "Compile"); + toolbar.btnPerform->Bind(wxEVT_BUTTON, [=](wxCommandEvent &ev){ + if(toolbar.btnPerform->GetLabel() == "Kill") { + CHi_StopCompilation(graph->backendNG); + toolbar.btnPerform->Disable(); + tlba->EnableTool(wxID_SAVE, false); + tlba->EnableTool(wxID_OPEN, false); + } else { + CHi_BeginCompilation(graph->backendNG); + toolbar.btnPerform->SetLabel("Kill"); + + std::thread{[=](){ + while(graph->backendNG->compilationStatus == CUTIHI_COMP_RUNNING) { + CallAfter([=](){ + float t = CHi_Time_Get(graph->backendNG); + stba->SetStatusText(wxString::Format("%02i:%02i:%06.03fs", (int) (t / 3600), (int) (t / 60), fmodf(t, 60))); + }); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + CallAfter([=](){ + stba->SetStatusText("Compilation has ended."); + }); + }}.detach(); + } + }); + + tlba->AddControl(toolbar.durationEnable); + tlba->AddControl(toolbar.duration); + tlba->AddControl(toolbar.btnPerform); + + graph->backendNG->eventOnStopComplete = +[](CHiNodeGraph *ng){ + wxTheApp->CallAfter([ng](){ + wxButton *btn = ((Frame*) ng->ud)->toolbar.btnPerform; + btn->Enable(); + btn->SetLabel("Compile"); + + auto tlba = ((Frame*) ng->ud)->tlba; + tlba->EnableTool(wxID_SAVE, true); + tlba->EnableTool(wxID_OPEN, true); + }); + }; + tlba->Realize(); aui.SetFlags(wxAUI_MGR_LIVE_RESIZE | wxAUI_MGR_DEFAULT); @@ -320,8 +552,8 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) wxSingleChoiceDialog dlg(this, "", "Choose Source", choices.size(), choices.data(), datae.data()); if(dlg.ShowModal() == wxID_OK) { CHiValue newv; - newv.type = CUTIHI_VAL_VEC4; - newv.data.vec4[0] = (size_t) (uintptr_t) dlg.GetSelectionData(); + newv.type = CUTIHI_VAL_TEXT; + newv.data.text = strdup(CHi_Microphone_GetSourceName((size_t) (uintptr_t) dlg.GetSelectionData())); CHi_ConfigureSink(this->logical, p, newv); parent->Dirtify(this); } @@ -500,8 +732,8 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) { { GrNode *v = new GrNode(this); v->logical = CHi_Preview(); - v->name = "Preview"; - v->sinks = {{"Video", GrNode::Port::Type::SAMPLE}}; + + ShapeGrNode(v); CHi_RegisterNode(backendNG, v->logical); gnodes.push_back(v); @@ -536,39 +768,18 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) { if(ev.GetId() == idConstant) { noed = new GrNode(this); noed->logical = CHi_ConstantSample(); - printf("%p\n", noed->logical->sinks[0].data.vec4); - noed->name = "Constant"; - noed->sinks = {{"Color", GrNode::Port::Type::COLOR}}; - noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(ev.GetId() == idImage) { noed = new GrNode(this); noed->logical = CHi_Image(); - noed->name = "Image"; - noed->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}}; - noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(ev.GetId() == idEmbed) { noed = new GrNode(this); noed->logical = CHi_Embed(); - noed->name = "Embed"; - noed->sinks = { - {"Frame", GrNode::Port::Type::SAMPLE}, - {"Sub 1", GrNode::Port::Type::SAMPLE}, {" Pos", GrNode::Port::Type::VEC2}, {" Size", GrNode::Port::Type::VEC1}, - {"Sub 2", GrNode::Port::Type::SAMPLE}, {" Pos", GrNode::Port::Type::VEC2}, {" Size", GrNode::Port::Type::VEC1}, - {"Sub 3", GrNode::Port::Type::SAMPLE}, {" Pos", GrNode::Port::Type::VEC2}, {" Size", GrNode::Port::Type::VEC1} - }; - noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(ev.GetId() == idModulate) { noed = new GrNode(this); noed->logical = CHi_Modulate(); - noed->name = "Modulate"; - noed->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Brightness", GrNode::Port::Type::VEC1}, {"Contrast", GrNode::Port::Type::VEC1}, {"Hue", GrNode::Port::Type::VEC1}}; - noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(ev.GetId() == idMovie) { noed = new GrNode(this); noed->logical = CHi_Movie(); - noed->name = "Movie"; - noed->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}, {"Time", GrNode::Port::Type::VEC1}}; - noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Audio", GrNode::Port::Type::SAMPLE}}; after = [=](){ size_t portIdx = std::distance(noed->sinks.begin(), std::find_if(noed->sinks.begin(), noed->sinks.end(), [](GrNode::Port& p) -> bool { @@ -583,84 +794,47 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) { } else if(ev.GetId() == idEncodeVp9) { noed = new GrNode(this); noed->logical = CHi_EncodeVP9(); - noed->name = "Encode VP9"; - noed->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}}; - noed->sources = {{"Bitstream"}}; } else if(ev.GetId() == idEncodeVp8) { noed = new GrNode(this); noed->logical = CHi_EncodeVP8(); - noed->name = "Encode VP8"; - noed->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}}; - noed->sources = {{"Bitstream"}}; } else if(ev.GetId() == idMuxWebm) { noed = new GrNode(this); noed->logical = CHi_MuxWebm(); - noed->name = "Mux WebM"; - noed->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"Filename", GrNode::Port::Type::FILE_SAVE}}; - noed->sources = {}; } else if(ev.GetId() == idWindow) { noed = new GrNode(this); noed->logical = CHi_Window(); - noed->name = "Window"; - noed->sinks = {{"Name", GrNode::Port::Type::WINDOW_SOURCE}}; - noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(ev.GetId() == idText) { noed = new GrNode(this); noed->logical = CHi_Text(); - noed->name = "Text"; - noed->sinks = {{"Text", GrNode::Port::Type::TEXT}, {"Color", GrNode::Port::Type::COLOR}, {"DPI", GrNode::Port::Type::VEC1}}; - noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(ev.GetId() == idTime) { noed = new GrNode(this); noed->logical = CHi_Time(); - noed->name = "Time"; - noed->sinks = {}; - noed->sources = {{"t", GrNode::Port::Type::VEC1}}; } else if(ev.GetId() == idMicrophone) { noed = new GrNode(this); noed->logical = CHi_Microphone(); - noed->name = "Microphone"; - noed->sinks = {{"Source", GrNode::Port::Type::MIC_SOURCE}}; - noed->sources = {{"Audio", GrNode::Port::Type::SAMPLE}}; } else if(ev.GetId() == idMixer) { noed = new GrNode(this); noed->logical = CHi_Mixer(); - noed->name = "Mixer"; - noed->sinks = {{"Sink 1", GrNode::Port::Type::SAMPLE}, {"Sink 2", GrNode::Port::Type::SAMPLE}}; - noed->sources = {{"Audio", GrNode::Port::Type::SAMPLE}}; } else if(ev.GetId() == idMuxWav) { noed = new GrNode(this); noed->logical = CHi_ExportWav(); - noed->name = "Mux Wav"; - noed->sinks = {{"Filename", GrNode::Port::Type::FILE_SAVE}, {"Audio", GrNode::Port::Type::SAMPLE}}; - noed->sources = {}; } else if(ev.GetId() == idEncodeOpus) { noed = new GrNode(this); noed->logical = CHi_EncodeOpus(); - noed->name = "Encode Opus"; - noed->sinks = {{"Audio", GrNode::Port::Type::SAMPLE}}; - noed->sources = {{"Bitstream"}}; } else if(ev.GetId() == idCamera) { noed = new GrNode(this); noed->logical = CHi_Camera(); - noed->name = "Live Digital Camera"; - noed->sinks = {}; - noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(ev.GetId() == idComponentScale) { noed = new GrNode(this); noed->logical = CHi_ComponentScale(); - noed->name = "Scale"; - noed->sinks = {{"Vector", GrNode::Port::Type::VEC4}, {"Sample", GrNode::Port::Type::SAMPLE}}; - noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(ev.GetId() == idKeyhook) { noed = new GrNode(this); noed->logical = CHi_Keyhook(); - noed->name = "Keyhook"; - noed->sinks = {{"Key", GrNode::Port::Type::VEC1}, {"Smooth Time", GrNode::Port::Type::VEC1}}; - noed->sources = {{"Bool", GrNode::Port::Type::VEC1}}; } if(noed) { + ShapeGrNode(noed); + noed->Fit(); noed->SetPosition(position); CHi_RegisterNode(backendNG, noed->logical); @@ -754,7 +928,7 @@ void NodeGraph::Dirtify(GrNode *g) { } } - if(g == gnodes[0]) { + if(g->logical->type == CUTIHI_T('CPre','view')) { if(CHi_Hysteresis(g->logical)) { CHiValue *val = CHi_Crawl(&g->logical->sinks[0]); @@ -787,52 +961,3 @@ bool NodeGraph::DetectCycles(GrNode *root) { std::set p{}; return dfs(this, p, root); } - -CompositionSettings::CompositionSettings(Frame *parent) : wxPanel(parent, wxID_ANY) { - auto sz = new wxBoxSizer(wxVERTICAL); - - sz->Add(this->durationEnable = new wxCheckBox(this, wxID_ANY, "Duration"), 0, wxALIGN_CENTER); - sz->Add(this->duration = new ctTimeCtrl(this, 120), 0, wxEXPAND); - - durationEnable->SetValue(true); - durationEnable->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent&){ - duration->Enable(durationEnable->GetValue()); - }); - - sz->Add(this->btnPerform = new wxButton(this, wxID_ANY, "Compile"), 0, wxEXPAND); - btnPerform->Bind(wxEVT_BUTTON, [=](wxCommandEvent &ev){ - if(btnPerform->GetLabel() == "Kill") { - CHi_StopCompilation(parent->graph->backendNG); - btnPerform->Disable(); - } else { - CHi_SetDuration(parent->graph->backendNG, durationEnable->IsChecked() ? duration->GetSeconds() : -1); - CHi_BeginCompilation(parent->graph->backendNG); - btnPerform->SetLabel("Kill"); - - std::thread{[=](){ - while(parent->graph->backendNG->compilationStatus == CUTIHI_COMP_RUNNING) { - parent->CallAfter([=](){ - float t = CHi_Time_Get(parent->graph->backendNG); - parent->stba->SetStatusText(wxString::Format("%02i:%02i:%06.03fs", (int) (t / 3600), (int) (t / 60), fmodf(t, 60))); - }); - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - parent->CallAfter([=](){ - parent->stba->SetStatusText("Compilation has ended."); - }); - }}.detach(); - } - }); - - parent->graph->backendNG->eventOnStopComplete = +[](CHiNodeGraph *ng){ - wxTheApp->CallAfter([ng](){ - wxButton *btn = ((Frame*) ng->ud)->compsets->btnPerform; - btn->Enable(); - btn->SetLabel("Compile"); - }); - }; - - SetSizerAndFit(sz); -} diff --git a/ui/frame.h b/ui/frame.h index 9432b90..f18aca9 100644 --- a/ui/frame.h +++ b/ui/frame.h @@ -18,19 +18,26 @@ struct NodeGraph; struct ImageViewer; -struct CompositionSettings; struct Timeline; +struct ctTimeCtrl; + struct Frame : wxFrame { wxAuiManager aui; ImageViewer *viewer; NodeGraph *graph; - CompositionSettings *compsets; Timeline *timeline; wxStatusBar *stba; wxToolBar *tlba; + struct { + ctTimeCtrl *duration; + wxCheckBox *durationEnable; + + wxButton *btnPerform; + } toolbar; + Frame(); virtual ~Frame(); }; @@ -59,17 +66,6 @@ struct GrNode : wxPanel { virtual void Fit() override; }; -struct ctTimeCtrl; -struct CompositionSettings : wxPanel { - ctTimeCtrl *duration; - wxCheckBox *durationEnable; - - wxButton *btnPerform; - - CompositionSettings(Frame*); - virtual ~CompositionSettings() = default; -}; - struct ImageViewer : wxPanel { wxPoint pos; wxImage img;