From 6fc29ba5f8fb3343a229aefad469e98df6d15609 Mon Sep 17 00:00:00 2001 From: mid <> Date: Sun, 9 Mar 2025 10:25:39 +0200 Subject: [PATCH] Support Twitch streaming, chroma key, errors, fixed modulation, node lifespan, fix bugs, many optimizations --- Makefile | 34 +-- hi/bs.h | 3 +- hi/img.h | 1 + hi/linearity.h | 4 + hi/microphone.c | 9 +- hi/node.c | 684 +++++++++++++++++++++++++++++++++++++++--------- hi/node.h | 59 ++++- hi/opus.c | 25 +- hi/relay.c | 22 +- hi/webcam.c | 1 - hi/webmdec.cpp | 44 ++-- hi/webmenc.cpp | 291 +------------------- hi/window.c | 9 +- ui/frame.cpp | 488 +++++++++++++++++++++++++--------- ui/frame.h | 17 ++ ui/timeline.cpp | 263 +++++++++++++++---- ui/timeline.h | 31 ++- 17 files changed, 1307 insertions(+), 678 deletions(-) diff --git a/Makefile b/Makefile index 6e6132d..e20c15b 100644 --- a/Makefile +++ b/Makefile @@ -1,25 +1,29 @@ CXXFLAGS := -D_POSIX_C_SOURCE=200809L -Wno-narrowing -march=native -flto -Wall -fvisibility=hidden -fPIC -msse4 -I./ -I/usr/local/include/sail `pkg-config --cflags pango opus libv4l2` '-Wl,-rpath,$$ORIGIN' -Wno-multichar -LDFLAGS := -lwebm -lpng -lvpx -lsail -lsail-manip `pkg-config --libs pango opus libv4l2` -lportaudio -lXtst +LDFLAGS := -lwebm -lpng -lvpx -lsail -lsail-manip `pkg-config --libs pango opus libv4l2` -lportaudio -lXtst -lrtmp -lfdk-aac ifneq ($(RELEASE),0) - CXXFLAGS := $(CXXFLAGS) -O0 -g3 -fsanitize=address -DMTR_ENABLED + CXXFLAGS := $(CXXFLAGS) -O0 -gdwarf-2 -DMTR_ENABLED else CXXFLAGS := $(CXXFLAGS) -O3 -fopenmp -DMTR_ENABLED endif all: - gcc $(CXXFLAGS) -std=c99 -shared -c -o node.o hi/node.c $(LDFLAGS) - gcc $(CXXFLAGS) -std=c99 -shared -c -o window.o hi/window.c $(LDFLAGS) - gcc $(CXXFLAGS) -std=c99 -shared -c -o microphone.o hi/microphone.c $(LDFLAGS) - gcc $(CXXFLAGS) -std=c99 -shared -c -o mode.o hi/mode.c $(LDFLAGS) - gcc $(CXXFLAGS) -std=c99 -shared -c -o img.o hi/img.c $(LDFLAGS) - g++ $(CXXFLAGS) -std=c++11 -shared -c -o webmdec.o hi/webmdec.cpp $(LDFLAGS) - g++ $(CXXFLAGS) -std=c++11 -shared -c -o webmenc.o hi/webmenc.cpp $(LDFLAGS) - 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) -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) + $(CC) $(CXXFLAGS) -std=c99 -shared -c -o node.o hi/node.c $(LDFLAGS) + $(CC) $(CXXFLAGS) -std=c99 -shared -c -o window.o hi/window.c $(LDFLAGS) + $(CC) $(CXXFLAGS) -std=c99 -shared -c -o microphone.o hi/microphone.c $(LDFLAGS) + $(CC) $(CXXFLAGS) -std=c99 -shared -c -o mode.o hi/mode.c $(LDFLAGS) + $(CC) $(CXXFLAGS) -std=c99 -shared -c -o img.o hi/img.c $(LDFLAGS) + $(CXX) $(CXXFLAGS) -std=c++11 -shared -c -o webmdec.o hi/webmdec.cpp $(LDFLAGS) + $(CXX) $(CXXFLAGS) -std=c++11 -shared -c -o webmenc.o hi/webmenc.cpp $(LDFLAGS) + $(CC) $(CXXFLAGS) -std=c++11 -shared -c -o vpxenc.o hi/vpxenc.c $(LDFLAGS) + $(CC) $(CXXFLAGS) -std=c99 -shared -c -o opus.o hi/opus.c $(LDFLAGS) + $(CC) $(CXXFLAGS) -std=c99 -shared -c -o webcam.o hi/webcam.c $(LDFLAGS) + $(CC) $(CXXFLAGS) -std=c99 -shared -c -o scale.o hi/relay.c $(LDFLAGS) + $(CC) $(CXXFLAGS) -std=c99 -shared -c -o minitrace.o hi/minitrace.c $(LDFLAGS) + $(CC) $(CXXFLAGS) -std=c99 -shared -c -o h264enc.o hi/h264enc.c $(LDFLAGS) + $(CC) $(CXXFLAGS) -std=c99 -shared -c -o rtmp.o hi/rtmp.c $(LDFLAGS) + $(CC) $(CXXFLAGS) -std=c99 -shared -c -o aaclc.o hi/aaclc.c $(LDFLAGS) + $(CC) $(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 h264enc.o rtmp.o aaclc.o vpxenc.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` + $(CXX) $(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/hi/bs.h b/hi/bs.h index fdaa4a1..70f1259 100644 --- a/hi/bs.h +++ b/hi/bs.h @@ -6,6 +6,7 @@ extern "C" { #endif #define CUTIHI_BS_FLAG_KEY 1 +#define CUTIHI_BS_SETUP_PACKET 2 typedef struct { uint64_t timestamp; @@ -23,4 +24,4 @@ typedef struct { } #endif -#endif \ No newline at end of file +#endif diff --git a/hi/img.h b/hi/img.h index fccc685..b8fb0d8 100644 --- a/hi/img.h +++ b/hi/img.h @@ -15,6 +15,7 @@ typedef struct CHiImage { uint16_t height; union { uint16_t *data16; + uint8_t *data8; }; uint8_t owned; } CHiImage; diff --git a/hi/linearity.h b/hi/linearity.h index 3816363..db13a90 100644 --- a/hi/linearity.h +++ b/hi/linearity.h @@ -83,6 +83,10 @@ static inline __m128 log2f4(__m128 x) return _mm_add_ps(p, e); } +__attribute__((optimize("O3"))) static inline __m128 apply_gamma_ps(__m128 z, __m128 gamma) { + return exp2f4(_mm_mul_ps(log2f4(z), gamma)); +} + __attribute__((optimize("O3"))) static inline __m128i apply_gamma_epi32(__m128i z, __m128 gamma) { __m128 zf = _mm_cvtepi32_ps(z); zf = _mm_mul_ps(zf, _mm_set1_ps(1.f / 65535)); diff --git a/hi/microphone.c b/hi/microphone.c index be8598e..6998142 100644 --- a/hi/microphone.c +++ b/hi/microphone.c @@ -40,8 +40,8 @@ static int pacallback(const void *input_, void *output, unsigned long samples, c /*static size_t total = 0; for(size_t i = 0; i < pabufsize; i++) { - paBuffer[paBufferWriteIdx] = sin(total++ * 440.0 / 24000 * 3.141592653) * 0.1; - paBufferWriteIdx = (paBufferWriteIdx + 1) % pabufsize; + node->paBuffer[node->paBufferWriteIdx] = sin(total++ * 440.0 / 24000 * 3.141592653) * 0.3; + node->paBufferWriteIdx = (node->paBufferWriteIdx + 1) % pabufsize; }*/ return paContinue; @@ -111,8 +111,6 @@ static int microphone_perform(CHiPubNode *pubn) { pubn->sources[0].type = CUTIHI_VAL_SAMPLE; pubn->sources[0].data.sample = ret; - pubn->clean = 0; - MTR_END("CHi", "microphone_perform"); return 1; @@ -130,7 +128,6 @@ CUTIVIS CHiPubNode *CHi_Microphone() { n->Start = microphone_start; n->Perform = microphone_perform; n->Stop = microphone_stop; - n->clean = 0; n->sinkCount = 1; n->sinks = calloc(sizeof(*n->sinks), n->sinkCount); n->sourceCount = 1; @@ -180,7 +177,6 @@ static int exportwav_perform(CHiPubNode *pubn) { fwrite(buf->data16, 2, buf->width, n->output); - pubn->clean = 0; return 1; } CUTIVIS int CHi_ExportWav_Stop(CHiPubNode *pubn) { @@ -204,7 +200,6 @@ CUTIVIS CHiPubNode *CHi_ExportWav() { n->pubn.Start = CHi_ExportWav_Start; n->pubn.Perform = exportwav_perform; n->pubn.Stop = CHi_ExportWav_Stop; - n->pubn.clean = 0; n->pubn.sinkCount = 2; n->pubn.sinks = calloc(sizeof(*n->pubn.sinks), n->pubn.sinkCount); n->pubn.sourceCount = 0; diff --git a/hi/node.c b/hi/node.c index 0573a8e..0bab149 100644 --- a/hi/node.c +++ b/hi/node.c @@ -142,10 +142,17 @@ CUTIVIS void CHi_RegisterNode(CHiNodeGraph* ng, CHiPubNode* n) { ng->nodes[ng->count++] = n; n->ng = ng; + + if(ng->compilationStatus == CUTIHI_COMP_RUNNING) { + n->Start(n); + } } CUTIVIS void CHi_MakeDirty(CHiNodeGraph *ng, CHiPubNode *n) { - for(size_t i = 0; i < ng->count; i++) { + for(int adj = 0; adj < ng->adjacencyCount; adj++) { + if(ng->adjacencies[adj][0] == n) { + //n->clean = 0; + } } } @@ -192,6 +199,11 @@ static int topological_sort(CHiNodeGraph *ng) { return 1; } CUTIVIS int CHi_ConfigureSink(CHiPubNode *n, size_t i, CHiValue v) { + if(n->sinkCount <= i) { + n->sinks = realloc(n->sinks, sizeof(*n->sinks) * (i + 1)); + memset(&n->sinks[i], 0, sizeof(*n->sinks)); + } + if(n->sinks[i].type == CUTIHI_VAL_KEYED) { n->sinks[i].data.keyed->current = v; return 1; @@ -205,6 +217,7 @@ CUTIVIS int CHi_ConfigureSink(CHiPubNode *n, size_t i, CHiValue v) { adjacency_remove(old.data.linked.to, n); } + // Check if viable n->sinks[i] = v; if(n->ng && !topological_sort(n->ng)) { n->sinks[i] = old; @@ -220,6 +233,8 @@ CUTIVIS int CHi_ConfigureSink(CHiPubNode *n, size_t i, CHiValue v) { adjacency_add(v.data.linked.to, n); } + CHi_MakeDirty(n->ng, n); + return 1; } @@ -299,9 +314,7 @@ CUTIVIS void CHi_DeleteKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, size_t idx) kfs->count--; } -CUTIVIS size_t CHi_GetClosestKeyframe(CHiNodeGraph *ng, size_t kfsIdx, float t) { - CHiKeyframes *kfs = ng->keyframesList.keyframes[kfsIdx]; - +CUTIVIS size_t CHi_GetClosestKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, float t) { if(kfs->count == 1) { return 0; } @@ -347,11 +360,55 @@ CUTIVIS int CHi_Hysteresis(CHiPubNode *root) { } } - root->Perform(root); + //if(!root->clean) { + root->Perform(root); + //} return 1; } +static bool error_changes(CHiPubNode *n) { + for(int e = 0; e < CUTIHI_MAX_ERRORS; e++) { + if(n->errors.active[e] != n->errors.activeLast[e]) { + return true; + } + } + + return false; +} + +static void save_errors(CHiPubNode *n) { + for(int e = 0; e < CUTIHI_MAX_ERRORS; e++) { + n->errors.activeLast[e] = n->errors.active[e]; + n->errors.active[e] = false; + } +} + +static void perform_step(CHiNodeGraph *ng) { + pthread_mutex_lock(&ng->mut); + + for(size_t nIdx = 0; nIdx < ng->count; nIdx++) { + save_errors(ng->nodes[nIdx]); + } + + for(size_t nIdx = 0; nIdx < ng->count; nIdx++) { + CHiPubNode *n = ng->nodes[nIdx]; + + n->Perform(n); + + if(error_changes(n)) { + if(ng->eventOnError) + ng->eventOnError(ng, n); + } + } + + if(ng->eventOnFrameComplete) { + ng->eventOnFrameComplete(ng); + } + + pthread_mutex_unlock(&ng->mut); +} + bool timespec_less(const struct timespec l, const struct timespec r) { if(l.tv_sec == r.tv_sec) { return l.tv_nsec < r.tv_nsec; @@ -401,11 +458,35 @@ void *compile_thread(void *ctx_) { ctx->ng->time = ctx->ng->timedelta = 0; puts("START"); - for(size_t nIdx = 0; nIdx < ctx->ng->count; nIdx++) { - if(ctx->ng->nodes[nIdx]->Start) { - ctx->ng->nodes[nIdx]->Start(ctx->ng->nodes[nIdx]); - } else { - ctx->ng->nodes[nIdx]->Perform(ctx->ng->nodes[nIdx]); + + { + ssize_t nIdx; + for(nIdx = 0; nIdx < ctx->ng->count; nIdx++) { + bool success; + if(ctx->ng->nodes[nIdx]->Start) { + success = ctx->ng->nodes[nIdx]->Start(ctx->ng->nodes[nIdx]); + } else { + success = ctx->ng->nodes[nIdx]->Perform(ctx->ng->nodes[nIdx]); + } + + if(!success) { + break; + } + } + + if(nIdx != ctx->ng->count) { + // Starting failed; stop all previous nodes + + ctx->ng->eventOnError(ctx->ng, ctx->ng->nodes[nIdx]); + + nIdx--; + for(; nIdx >= 0; nIdx--) { + if(ctx->ng->nodes[nIdx]->Stop) { + ctx->ng->nodes[nIdx]->Stop(ctx->ng->nodes[nIdx]); + } + } + + goto stop; } } @@ -427,13 +508,7 @@ void *compile_thread(void *ctx_) { CHi_Time_Set(ctx->ng, timespecToFloat(timespec_sub(now, start))); - for(size_t nIdx = 0; nIdx < ctx->ng->count; nIdx++) { - ctx->ng->nodes[nIdx]->Perform(ctx->ng->nodes[nIdx]); - } - - if(ctx->ng->eventOnFrameComplete) { - ctx->ng->eventOnFrameComplete(ctx->ng); - } + perform_step(ctx->ng); do { clock_gettime(CLOCK_MONOTONIC, &now); @@ -444,9 +519,7 @@ void *compile_thread(void *ctx_) { for(uint64_t frm = 0; ctx->ng->compilationStatus != CUTIHI_COMP_KILL_YOURSELF && (ctx->ng->duration == -1 || frm < ctx->ng->duration * 30);) { CHi_Time_Set(ctx->ng, frm / 30.f); - for(size_t nIdx = 0; nIdx < ctx->ng->count; nIdx++) { - ctx->ng->nodes[nIdx]->Perform(ctx->ng->nodes[nIdx]); - } + perform_step(ctx->ng); struct timespec last; clock_gettime(CLOCK_MONOTONIC, &last); @@ -454,10 +527,6 @@ void *compile_thread(void *ctx_) { clock_gettime(CLOCK_MONOTONIC, &now); diff += timespec_sub(now, last).tv_nsec; - if(ctx->ng->eventOnFrameComplete) { - ctx->ng->eventOnFrameComplete(ctx->ng); - } - frm++; } } @@ -473,6 +542,7 @@ void *compile_thread(void *ctx_) { ctx->ng->eventOnStopComplete(ctx->ng); } +stop: ctx->ng->compilationStatus = CUTIHI_COMP_READY; free(ctx); @@ -496,57 +566,87 @@ CUTIVIS void CHi_StopCompilation(CHiNodeGraph *ng) { } } +typedef struct { + CHiPubNode pubn; + char *cachePath; + CHiImage *cacheImg; +} ImageNode; static int image_perform(CHiPubNode *node) { - if(node->clean) return 1; + ImageNode *internal = (ImageNode*) node; node->sources->type = CUTIHI_VAL_SAMPLE; - if(node->sources->data.sample) CHi_Image_Free(node->sources->data.sample); - - struct sail_image *simg; - SAIL_TRY(sail_load_from_file(node->sinks[0].data.text, &simg)); - - struct sail_image *cimg; - sail_convert_image(simg, SAIL_PIXEL_FORMAT_BPP64_BGRA, &cimg); - - sail_destroy_image(simg); - - CHiImage *img = CHi_Image_New(2, 4, (cimg->bytes_per_line + 15) & ~15, cimg->width, cimg->height, NULL); - CHi_Restride(cimg->pixels, img->data16, cimg->bytes_per_line, img->stride, img->height); - node->sources->data.sample = img; - - for(size_t y = 0; y < img->height; y++) { - for(size_t x = 0; x < img->stride; x += 16) { - __m128i pixels = _mm_load_si128((__m128i*) ((uintptr_t) img->data16 + y * img->stride + x)); - pixels = apply_gamma_epi16(pixels, _mm_set_ps(1.0f, 2.2f, 2.2f, 2.2f)); - _mm_stream_si128((__m128i*) ((uintptr_t) img->data16 + y * img->stride + x), pixels); + const char *fn = node->sinks[CUTIHI_IMAGE_IN_FILE].data.text; + if(fn && (!internal->cachePath || strcmp(internal->cachePath, fn))) { + if(node->sinks[CUTIHI_IMAGE_IN_FILE].type == CUTIHI_VAL_NONE) { + return 1; + } + + if(node->sinks[CUTIHI_IMAGE_IN_FILE].type != CUTIHI_VAL_TEXT) { + node->errors.active[0] = true; + strncpy(node->errors.code[0], "invalid type", CUTIHI_ERR_SIZE); + node->errors.sink[0] = CUTIHI_IMAGE_IN_FILE; + return 1; + } + + if(internal->cacheImg) { + CHi_Image_Free(internal->cacheImg); + internal->cacheImg = NULL; } } - sail_destroy_image(cimg); + if(!internal->cacheImg) { + struct sail_image *simg; + if(sail_load_from_file(fn, &simg) != SAIL_OK) { + node->errors.active[0] = true; + strncpy(node->errors.code[0], "invalid file", CUTIHI_ERR_SIZE); + node->errors.sink[0] = CUTIHI_IMAGE_IN_FILE; + return 1; + } + + struct sail_image *cimg; + sail_convert_image(simg, SAIL_PIXEL_FORMAT_BPP64_BGRA, &cimg); + + sail_destroy_image(simg); + simg = NULL; + + CHiImage *img = CHi_Image_New(2, 4, (cimg->bytes_per_line + 15) & ~15, cimg->width, cimg->height, NULL); + CHi_Restride(cimg->pixels, img->data16, cimg->bytes_per_line, img->stride, img->height); + internal->cacheImg = img; + + for(size_t y = 0; y < img->height; y++) { + for(size_t x = 0; x < img->stride; x += 16) { + __m128i pixels = _mm_load_si128((__m128i*) ((uintptr_t) img->data16 + y * img->stride + x)); + pixels = apply_gamma_epi16(pixels, _mm_set_ps(1.0f, 2.2f, 2.2f, 2.2f)); + _mm_stream_si128((__m128i*) ((uintptr_t) img->data16 + y * img->stride + x), pixels); + } + } + + sail_destroy_image(cimg); + } + + if(CHi_Node_Active(node)) { + node->sources->data.sample = internal->cacheImg; + } else { + node->sources->data.sample = NULL; + } - node->clean = 0; return 1; -err: - node->sources->data.sample = NULL; - return 0; } CUTIVIS CHiPubNode *CHi_Image() { - CHiPubNode *n = calloc(1, sizeof(*n)); + CHiPubNode *n = calloc(1, sizeof(ImageNode)); n->type = CUTIHI_T('CIma','ge '); n->Start = n->Stop = NULL; n->Perform = image_perform; - n->clean = 0; n->sinkCount = 1; n->sinks = calloc(sizeof(*n->sinks), 1); n->sourceCount = 1; n->sources = calloc(sizeof(*n->sources), 1); + ((ImageNode*) n)->cachePath = strdup(""); return n; } static int embed_perform(CHiPubNode *node) { - if(node->clean) return 1; - MTR_BEGIN("CHi", "embed_perform"); node->sources[0].type = CUTIHI_VAL_SAMPLE; @@ -557,7 +657,7 @@ static int embed_perform(CHiPubNode *node) { CHiImage *dest = node->sources->data.sample = CHi_Image_New(2, 4, main->stride, main->width, main->height, NULL); memcpy(dest->data16, main->data16, main->stride * main->height); - for(int sid = 0; sid < CUTIHI_EMBED_MAX_SMALLS; sid++) { + for(int sid = 0; sid < (node->sinkCount - 1) / 3; sid++) { CHiImage *sub = CHi_Crawl(&node->sinks[1 + sid * 3])->data.sample; if(!sub) continue; @@ -590,7 +690,6 @@ static int embed_perform(CHiPubNode *node) { MTR_END("CHi", "embed_perform"); - node->clean = 0; return 1; } CUTIVIS CHiPubNode *CHi_Embed() { @@ -598,37 +697,32 @@ CUTIVIS CHiPubNode *CHi_Embed() { n->type = CUTIHI_T('CEmb','ed '); n->Start = n->Stop = NULL; n->Perform = embed_perform; - n->clean = 0; n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 1 + 3 * CUTIHI_EMBED_MAX_SMALLS); - for(int i = 0; i < CUTIHI_EMBED_MAX_SMALLS; i++) { - n->sinks[2 + i * 3].type = CUTIHI_VAL_VEC4; - n->sinks[2 + i * 3].data.vec4[0] = 0; - n->sinks[2 + i * 3].data.vec4[1] = 0; - n->sinks[3 + i * 3].type = CUTIHI_VAL_VEC4; - n->sinks[3 + i * 3].data.vec4[0] = 1; - } n->sources = calloc(sizeof(*n->sources), n->sourceCount = 1); return n; } static int constantsample_perform(CHiPubNode *node) { - if(node->clean) return 1; - node->sources[0].type = CUTIHI_VAL_SAMPLE; if(node->sources->data.sample) CHi_Image_Free(node->sources->data.sample); CHiValue *sink = CHi_Crawl(&node->sinks[0]); + CHiValue *sz = CHi_Crawl(&node->sinks[1]); - CHiImage *img = CHi_Image_New(2, 4, 8 * 16, 16, 16, NULL); - for(int i = 0; i < 256; i++) { - img->data16[i * 4 + 0] = sink->data.vec4[2] * 65535; - img->data16[i * 4 + 1] = sink->data.vec4[1] * 65535; - img->data16[i * 4 + 2] = sink->data.vec4[0] * 65535; - img->data16[i * 4 + 3] = 65535; + size_t w = sz->data.vec4[0] < 1 ? 1 : sz->data.vec4[0]; + size_t h = sz->data.vec4[1] < 1 ? 1 : sz->data.vec4[1]; + + CHiImage *img = CHi_Image_New(2, 4, 8 * w, w, h, NULL); + if(CHi_Node_Active(node)) { + for(size_t i = 0; i < w * h; i++) { + img->data16[i * 4 + 0] = sink->data.vec4[2] * 65535; + img->data16[i * 4 + 1] = sink->data.vec4[1] * 65535; + img->data16[i * 4 + 2] = sink->data.vec4[0] * 65535; + img->data16[i * 4 + 3] = 65535; + } } node->sources->data.sample = img; - node->clean = 0; return 1; } CUTIVIS CHiPubNode *CHi_ConstantSample() { @@ -636,27 +730,172 @@ CUTIVIS CHiPubNode *CHi_ConstantSample() { n->type = CUTIHI_T('CCns','tCol'); n->Start = n->Stop = NULL; n->Perform = constantsample_perform; - n->clean = 0; - n->sinkCount = 1; - n->sinks = calloc(sizeof(*n->sinks), 1); + n->sinkCount = 2; + n->sinks = calloc(sizeof(*n->sinks), n->sinkCount); n->sourceCount = 1; - n->sources = calloc(sizeof(*n->sources), 1); + n->sources = calloc(sizeof(*n->sources), n->sourceCount); + + n->sinks[0].type = CUTIHI_VAL_VEC4; + n->sinks[0].data.vec4[0] = 1280; + n->sinks[0].data.vec4[1] = 720; + return n; } +static __m128i _mm_mullo_epi32(__m128i a, __m128i b) { + // Plagiarized from a plagiarization of Agner Fog's code + __m128i a13 = _mm_shuffle_epi32(a, 0xF5); // (-,a3,-,a1) + __m128i b13 = _mm_shuffle_epi32(b, 0xF5); // (-,b3,-,b1) + __m128i prod02 = _mm_mul_epu32(a, b); // (-,a2*b2,-,a0*b0) + __m128i prod13 = _mm_mul_epu32(a13, b13); // (-,a3*b3,-,a1*b1) + __m128i prod01 = _mm_unpacklo_epi32(prod02,prod13); // (-,-,a1*b1,a0*b0) + __m128i prod23 = _mm_unpackhi_epi32(prod02,prod13); // (-,-,a3*b3,a2*b2) + __m128i prod = _mm_unpacklo_epi64(prod01,prod23); // (ab3,ab2,ab1,ab0) + return prod; +} + static int modulate_perform(CHiPubNode *node) { - if(node->clean) return 1; - MTR_BEGIN("CHi", "modulate_perform"); + CHiValue *imgsrc = CHi_Crawl(&node->sinks[0]); + + if(!imgsrc || imgsrc->type == CUTIHI_VAL_NONE) { + return 1; + } + + if(imgsrc->type != CUTIHI_VAL_SAMPLE) { + node->errors.active[0] = true; + strncpy(node->errors.code[0], "invalid type", CUTIHI_ERR_SIZE); + node->errors.sink[0] = 0; + return 1; + } + 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); + if(!CHi_Node_Active(node)) { + node->sources->data.sample = NULL; + return; + } + + CHiImage *src = imgsrc->data.sample; + + assert(src->stride % 16 == 0); + + CHiImage *dst = CHi_Image_New(2, 4, src->stride, src->width, src->height, NULL); + + node->sources->data.sample = dst; + + float V = CHi_Crawl(&node->sinks[1])->data.vec4[0]; + float S = CHi_Crawl(&node->sinks[2])->data.vec4[0]; + float H = CHi_Crawl(&node->sinks[3])->data.vec4[0] * 3.1415926535897 / 180; + + float sH = sinf(H); + float cH = cosf(H); + + __m128i row1 = _mm_set_epi32( + 0, + 32768 * (+0.180472 * S * sH + 0.7874000 * S * cH + 0.2126 * V), + 32768 * (-0.715274 * S * cH + 0.6069280 * S * sH + 0.7152 * V), + 32768 * (-0.787400 * S * sH - 0.0721258 * S * cH + 0.0722 * V) + ); + + __m128i row2 = _mm_set_epi32( + 0, + 32768 * (-0.212585 * S * cH - 0.1472940 * S * sH + 0.2126 * V), + 32768 * (-0.095334 * S * sH + 0.2847960 * S * cH + 0.7152 * V), + 32768 * (-0.072211 * S * cH + 0.2426280 * S * sH + 0.0722 * V) + ); + + __m128i row3 = _mm_set_epi32( + 0, + 32768 * (-0.212652 * S * cH + 0.9278000 * S * sH + 0.2126 * V), + 32768 * (-0.842814 * S * sH - 0.7151480 * S * cH + 0.7152 * V), + 32768 * (-0.084987 * S * sH + 0.9278000 * S * cH + 0.0722 * V) + ); + + for(size_t b = 0; b < dst->stride * dst->height; b += 16) { + __m128i rgba2U16 = _mm_load_si128((__m128i*) ((uintptr_t) src->data16 + b)); + __m128i rgba2S16 = _mm_srli_epi16(rgba2U16, 1); + + __m128i rgbaS16Lo = _mm_unpacklo_epi16(rgba2S16, _mm_setzero_si128()); + __m128i rgbaS16Hi = _mm_unpackhi_epi16(rgba2S16, _mm_setzero_si128()); + + rgba2S16 = _mm_setzero_si128(); + + do { + __m128i newR = _mm_mullo_epi32(rgbaS16Lo, row1); + __m128i newG = _mm_mullo_epi32(rgbaS16Lo, row2); + __m128i newB = _mm_mullo_epi32(rgbaS16Lo, row3); + __m128i newA = _mm_mullo_epi32(rgbaS16Lo, _mm_set_epi32(1, 0, 0, 0)); + + newR = _mm_srai_epi32(newR, 16); + newG = _mm_srai_epi32(newG, 16); + newB = _mm_srai_epi32(newB, 16); + + newR = _mm_hadd_epi32(newR, _mm_setzero_si128()); + newG = _mm_hadd_epi32(newG, _mm_setzero_si128()); + newB = _mm_hadd_epi32(newB, _mm_setzero_si128()); + newA = _mm_hadd_epi32(newA, _mm_setzero_si128()); + + newR = _mm_hadd_epi32(newR, _mm_setzero_si128()); + newG = _mm_hadd_epi32(newG, _mm_setzero_si128()); + newB = _mm_hadd_epi32(newB, _mm_setzero_si128()); + newA = _mm_hadd_epi32(newA, _mm_setzero_si128()); + + newR = _mm_max_epi16(_mm_min_epi16(newR, _mm_set1_epi32(16383)), _mm_set1_epi32(0)); + newG = _mm_max_epi16(_mm_min_epi16(newG, _mm_set1_epi32(16383)), _mm_set1_epi32(0)); + newB = _mm_max_epi16(_mm_min_epi16(newB, _mm_set1_epi32(16383)), _mm_set1_epi32(0)); + newA = _mm_max_epi16(_mm_min_epi16(newA, _mm_set1_epi32(16383)), _mm_set1_epi32(0)); + + newR = _mm_shuffle_epi8(newR, _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1, 0, -128, -128, -128, -128)); + newG = _mm_shuffle_epi8(newG, _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1, 0, -128, -128)); + newB = _mm_shuffle_epi8(newB, _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1, 0)); + newA = _mm_shuffle_epi8(newA, _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, 1, 0, -128, -128, -128, -128, -128, -128)); + + rgba2S16 = _mm_or_si128(rgba2S16, _mm_or_si128(_mm_or_si128(_mm_or_si128(newR, newG), newB), newA)); + } while(0); + + do { + __m128i newR = _mm_mullo_epi32(rgbaS16Hi, row1); + __m128i newG = _mm_mullo_epi32(rgbaS16Hi, row2); + __m128i newB = _mm_mullo_epi32(rgbaS16Hi, row3); + __m128i newA = _mm_mullo_epi32(rgbaS16Hi, _mm_set_epi32(1, 0, 0, 0)); + + newR = _mm_srai_epi32(newR, 16); + newG = _mm_srai_epi32(newG, 16); + newB = _mm_srai_epi32(newB, 16); + + newR = _mm_hadd_epi32(newR, _mm_setzero_si128()); + newG = _mm_hadd_epi32(newG, _mm_setzero_si128()); + newB = _mm_hadd_epi32(newB, _mm_setzero_si128()); + newA = _mm_hadd_epi32(newA, _mm_setzero_si128()); + + newR = _mm_hadd_epi32(newR, _mm_setzero_si128()); + newG = _mm_hadd_epi32(newG, _mm_setzero_si128()); + newB = _mm_hadd_epi32(newB, _mm_setzero_si128()); + newA = _mm_hadd_epi32(newA, _mm_setzero_si128()); + + newR = _mm_max_epi16(_mm_min_epi16(newR, _mm_set1_epi32(16383)), _mm_set1_epi32(0)); + newG = _mm_max_epi16(_mm_min_epi16(newG, _mm_set1_epi32(16383)), _mm_set1_epi32(0)); + newB = _mm_max_epi16(_mm_min_epi16(newB, _mm_set1_epi32(16383)), _mm_set1_epi32(0)); + newA = _mm_max_epi16(_mm_min_epi16(newA, _mm_set1_epi32(16383)), _mm_set1_epi32(0)); + + newR = _mm_shuffle_epi8(newR, _mm_set_epi8(-128, -128, 1, 0, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128)); + newG = _mm_shuffle_epi8(newG, _mm_set_epi8(-128, -128, -128, -128, 1, 0, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128)); + newB = _mm_shuffle_epi8(newB, _mm_set_epi8(-128, -128, -128, -128, -128, -128, 1, 0, -128, -128, -128, -128, -128, -128, -128, -128)); + newA = _mm_shuffle_epi8(newA, _mm_set_epi8(1, 0, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128)); + + rgba2S16 = _mm_or_si128(rgba2S16, _mm_or_si128(_mm_or_si128(_mm_or_si128(newR, newG), newB), newA)); + } while(0); + + rgba2U16 = _mm_slli_epi16(rgba2S16, 2); + + _mm_store_si128((__m128i*) ((uintptr_t) dst->data16 + b), rgba2U16); + } MTR_END("CHi", "modulate_perform"); - node->clean = 0; return 1; } CUTIVIS CHiPubNode *CHi_Modulate() { @@ -664,11 +903,17 @@ CUTIVIS CHiPubNode *CHi_Modulate() { n->type = CUTIHI_T('CMod','ulat'); n->Start = n->Stop = NULL; n->Perform = modulate_perform; - n->clean = 0; n->sinkCount = 4; n->sinks = calloc(sizeof(*n->sinks), n->sinkCount); n->sourceCount = 1; n->sources = calloc(sizeof(*n->sources), n->sourceCount); + + n->sinks[0].type = CUTIHI_VAL_VEC4; + n->sinks[0].data.vec4[0] = 1; + + n->sinks[1].type = CUTIHI_VAL_VEC4; + n->sinks[1].data.vec4[0] = 1; + return n; } @@ -718,7 +963,6 @@ static void update_keyed_values(CHiNodeGraph *ng) { static int time_perform(CHiPubNode *node) { node->sources->type = CUTIHI_VAL_VEC4; node->sources->data.vec4[0] = node->ng->time; - node->clean = 0; return 1; } @@ -739,7 +983,6 @@ CUTIVIS CHiPubNode *CHi_Time() { n->type = CUTIHI_T('CTim','e '); n->Start = n->Stop = NULL; n->Perform = time_perform; - n->clean = 0; n->sinkCount = 0; n->sinks = NULL; n->sourceCount = 1; @@ -747,34 +990,63 @@ CUTIVIS CHiPubNode *CHi_Time() { return n; } -static PangoFontMap *pfontmap; -static PangoContext *pcontext; -static PangoFontDescription * pfontdesc; -static PangoLayout *playout; +struct TextNode { + CHiPubNode pubn; + PangoFontMap *pfontmap; + PangoContext *pcontext; + PangoFontDescription * pfontdesc; + PangoLayout *playout; + char *cacheText; + char *cacheFontName; +}; 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); - pcontext = pango_font_map_create_context(pfontmap); - pango_context_set_language(pcontext, pango_language_from_string("en_US")); - pango_context_set_base_dir(pcontext, PANGO_DIRECTION_LTR); - pfontdesc = pango_font_description_from_string("Open Sans 48"); - playout = pango_layout_new(pcontext); - pango_layout_set_font_description(playout, pfontdesc); + struct TextNode *this = (struct TextNode*) n; + + CHiValue *valFontName = CHi_Crawl(&n->sinks[3]); + CHiValue *valDPI = CHi_Crawl(&n->sinks[2]); + CHiValue *valCol = CHi_Crawl(&n->sinks[1]); + CHiValue *valText = CHi_Crawl(&n->sinks[0]); + + if(!this->cacheFontName || strcmp(this->cacheFontName, valFontName->data.text)) { + if(this->cacheFontName) free(this->cacheFontName); + this->cacheFontName = strdup(valFontName->data.text); + + this->pfontmap = pango_ft2_font_map_new(); + pango_ft2_font_map_set_resolution(PANGO_FT2_FONT_MAP(this->pfontmap), 72, 72); + this->pcontext = pango_font_map_create_context(this->pfontmap); + pango_context_set_language(this->pcontext, pango_language_from_string("en_US")); + pango_context_set_base_dir(this->pcontext, PANGO_DIRECTION_LTR); + this->pfontdesc = pango_font_description_from_string(this->cacheFontName); + this->playout = pango_layout_new(this->pcontext); + pango_layout_set_font_description(this->playout, this->pfontdesc); + + free(this->cacheText); + this->cacheText = NULL; } - pango_layout_set_markup(playout, CHi_Crawl(&n->sinks[0])->data.text, -1); - pango_ft2_font_map_set_resolution(PANGO_FT2_FONT_MAP(pfontmap), CHi_Crawl(&n->sinks[2])->data.vec4[0], CHi_Crawl(&n->sinks[2])->data.vec4[0]); + if(!this->cacheText || strcmp(this->cacheText, valText->data.text)) { + if(this->cacheText) free(this->cacheText); + this->cacheText = strdup(valText->data.text); + + pango_layout_set_markup(this->playout, valText->data.text, -1); + } + + pango_ft2_font_map_set_resolution(PANGO_FT2_FONT_MAP(this->pfontmap), valDPI->data.vec4[0], valDPI->data.vec4[0]); PangoRectangle extents; - pango_layout_get_extents(playout, NULL, &extents); + pango_layout_get_extents(this->playout, NULL, &extents); n->sources[0].type = CUTIHI_VAL_SAMPLE; - if(n->sources->data.sample) CHi_Image_Free(n->sources->data.sample); + if(n->sources->data.sample) { + CHi_Image_Free(n->sources->data.sample); + n->sources->data.sample = NULL; + } + + if(!CHi_Node_Active(n)) { + return 1; + } size_t width = (PANGO_PIXELS(extents.width) + 15) & ~15; CHiImage *chiret = CHi_Image_New(2, 4, 8 * width, width, PANGO_PIXELS(extents.height), NULL); @@ -788,18 +1060,18 @@ static int text_perform(CHiPubNode *n) { bmp.pitch = chiret->width; bmp.pixel_mode = FT_PIXEL_MODE_GRAY; bmp.num_grays = 256; - pango_ft2_render_layout(&bmp, playout, PANGO_PIXELS(extents.x) + (PANGO_PIXELS(extents.width) + 15) % 16 / 4, PANGO_PIXELS(extents.y)); + pango_ft2_render_layout(&bmp, this->playout, PANGO_PIXELS(extents.x) + (PANGO_PIXELS(extents.width) + 15) % 16 / 4, PANGO_PIXELS(extents.y)); __m128i ones = _mm_set1_epi64x( - (((size_t) (n->sinks[1].data.vec4[2] * 255) % 256) << 0) | - (((size_t) (n->sinks[1].data.vec4[1] * 255) % 256) << 16) | - (((size_t) (n->sinks[1].data.vec4[0] * 255) % 256) << 32) | + (((size_t) (valCol->data.vec4[2] * 255) % 256) << 0) | + (((size_t) (valCol->data.vec4[1] * 255) % 256) << 16) | + (((size_t) (valCol->data.vec4[0] * 255) % 256) << 32) | 0x0100000000000000 ); for(size_t p = 0; p < bmp.width * bmp.rows; p += 2) { __m128i alphad0 = - _mm_mullo_epi16(ones, _mm_set_epi16(bmp.buffer[p + 1], bmp.buffer[p + 1], bmp.buffer[p + 1], bmp.buffer[p + 1], bmp.buffer[p + 0], bmp.buffer[p + 0], bmp.buffer[p + 0], bmp.buffer[p + 0])); + _mm_mullo_epi16(ones, _mm_set_epi16(bmp.buffer[p + 1], 0xFF, 0xFF, 0xFF, bmp.buffer[p + 0], 0xFF, 0xFF, 0xFF)); _mm_stream_si128((__m128i*) &chiret->data16[p * 4], alphad0); } @@ -807,23 +1079,33 @@ static int text_perform(CHiPubNode *n) { MTR_END("CHi", "text_perform"); - n->clean = 0; return 1; } CUTIVIS CHiPubNode *CHi_Text() { - CHiPubNode *n = calloc(1, sizeof(*n)); + CHiPubNode *n = calloc(1, sizeof(struct TextNode)); n->type = CUTIHI_T('CTex','t '); n->Start = n->Stop = NULL; n->Perform = text_perform; - n->clean = 0; - n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 3); - n->sinks[2].type = CUTIHI_VAL_VEC4; - n->sinks[2].data.vec4[0] = 72; + n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 4); n->sources = calloc(sizeof(*n->sources), n->sourceCount = 1); + + n->sinks[0].type = CUTIHI_VAL_TEXT; + n->sinks[0].data.text = strdup("Title Text"); + + n->sinks[2].type = CUTIHI_VAL_VEC4; + n->sinks[2].data.vec4[0] = 256; + + n->sinks[3].type = CUTIHI_VAL_TEXT; + n->sinks[3].data.text = strdup("Sans-Serif"); + return n; } static int mixer_perform(CHiPubNode *n) { + if(n->sinkCount == 0) { + return 1; + } + n->sources[0].type = CUTIHI_VAL_SAMPLE; MTR_BEGIN("CHi", "mixer_perform"); @@ -833,26 +1115,40 @@ static int mixer_perform(CHiPubNode *n) { n->sources[0].data.sample = NULL; } - CHiImage *src0 = CHi_Crawl(&n->sinks[0])->data.sample; - CHiImage *src1 = CHi_Crawl(&n->sinks[1])->data.sample; + size_t width = 0, height = 0, stride = 0; - if(!src0 && !src1) { + for(int s = 0; s < n->sinkCount; s++) { + CHiValue *val = CHi_Crawl(&n->sinks[s]); + if(val && val->type == CUTIHI_VAL_SAMPLE) { + if(width == 0 || height == 0) { + width = val->data.sample->width; + height = val->data.sample->height; + stride = val->data.sample->stride; + } else { + assert(val->data.sample->width == width && val->data.sample->height == height); + } + } + } + + if(width == 0 || height == 0) { return 1; } - assert(src0->width == src1->width && src0->height == src1->height); + n->sources[0].data.sample = CHi_Image_New(2, 1, (stride + 15) & ~15, width, height, NULL); - n->sources[0].data.sample = CHi_Image_New(2, 1, (src0->stride + 15) & ~15, src0->width, src0->height, NULL); - - for(size_t b = 0; b < src0->stride; b += 16) { - __m128i a0 = src0 ? _mm_load_si128((__m128i*) ((uintptr_t) src0->data16 + b)) : _mm_setzero_si128(); - __m128i a1 = src1 ? _mm_load_si128((__m128i*) ((uintptr_t) src1->data16 + b)) : _mm_setzero_si128(); - _mm_stream_si128((__m128i*) ((uintptr_t) n->sources[0].data.sample->data16 + b), _mm_adds_epi16(a0, a1)); + for(size_t b = 0; b < stride; b += 16) { + __m128i sum = _mm_setzero_si128(); + for(int s = 0; s < n->sinkCount; s++) { + CHiValue *val = CHi_Crawl(&n->sinks[s]); + if(val && val->type == CUTIHI_VAL_SAMPLE) { + sum = _mm_adds_epi16(sum, _mm_load_si128((__m128i*) ((uintptr_t) val->data.sample->data16 + b))); + } + } + _mm_stream_si128((__m128i*) ((uintptr_t) n->sources[0].data.sample->data16 + b), sum); } MTR_END("CHi", "mixer_perform"); - n->clean = 0; return 1; } CUTIVIS CHiPubNode *CHi_Mixer() { @@ -860,7 +1156,6 @@ CUTIVIS CHiPubNode *CHi_Mixer() { n->type = CUTIHI_T('CMix','er '); n->Start = n->Stop = NULL; n->Perform = mixer_perform; - n->clean = 0; n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 2); n->sources = calloc(sizeof(*n->sources), n->sourceCount = 1); return n; @@ -874,12 +1169,134 @@ CUTIVIS CHiPubNode *CHi_Preview() { n->type = CUTIHI_T('CPre','view'); n->Start = n->Stop = NULL; n->Perform = preview_perform; - n->clean = 0; n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 1); - n->sinks[0].type = CUTIHI_VAL_SAMPLE; - n->sinks[0].data.sample = NULL; n->sources = NULL; n->sourceCount = 0; + + n->sinks[0].type = CUTIHI_VAL_SAMPLE; + n->sinks[0].data.sample = NULL; + + return n; +} + +static int chromakey_perform(CHiPubNode *n) { + CHiValue *sampleV = CHi_Crawl(&n->sinks[0]); + CHiValue *colorV = CHi_Crawl(&n->sinks[1]); + + if(!sampleV || sampleV->type != CUTIHI_VAL_SAMPLE || !sampleV->data.sample) { + return 1; + } + + CHiImage *src = sampleV->data.sample; + + if(n->sources[0].data.sample) { + CHi_Image_Free(n->sources[0].data.sample); + } + + n->sources[0].type = CUTIHI_VAL_SAMPLE; + + CHiImage *dst = n->sources[0].data.sample = CHi_Image_New(2, 4, (src->width * src->bpc * src->channels + 15) & ~15, src->width, src->height, NULL); + + int16_t uKey = 32767 * (colorV->data.vec4[0] * -0.1146 + colorV->data.vec4[1] * -0.3854 + colorV->data.vec4[2] * +0.5000); + int16_t vKey = 32767 * (colorV->data.vec4[0] * +0.5000 + colorV->data.vec4[1] * -0.4542 + colorV->data.vec4[2] * -0.0458); + + __m128i row2 = _mm_set_epi32(0, -3755, -12628, 16384); + __m128i row3 = _mm_set_epi32(0, 16384, -14883, -1501); + + float threshold0 = 300; + float threshold1 = 3000; + + for(size_t y = 0; y < src->height; y++) { + for(size_t off = 0; off < dst->stride; off += 16) { + + __m128i rgba2U16 = _mm_load_si128((__m128i*) ((uintptr_t) src->data16 + y * src->stride + off)); + + __m128i rgba2S16 = _mm_srli_epi16(rgba2U16, 1); + + __m128i rgbaS16Lo = _mm_unpacklo_epi16(rgba2S16, _mm_setzero_si128()); + __m128i rgbaS16Hi = _mm_unpackhi_epi16(rgba2S16, _mm_setzero_si128()); + + __m128i alphas = _mm_setzero_si128(); + + { + __m128i uProd = _mm_mullo_epi32(row2, rgbaS16Lo); + __m128i vProd = _mm_mullo_epi32(row3, rgbaS16Lo); + + uProd = _mm_srai_epi32(uProd, 15); + uProd = _mm_hadd_epi32(uProd, _mm_setzero_si128()); + uProd = _mm_hadd_epi32(uProd, _mm_setzero_si128()); + + vProd = _mm_srai_epi32(vProd, 15); + vProd = _mm_hadd_epi32(vProd, _mm_setzero_si128()); + vProd = _mm_hadd_epi32(vProd, _mm_setzero_si128()); + + __m128 diffU = _mm_cvtepi32_ps(_mm_sub_epi32(uProd, _mm_set1_epi32(uKey))); + __m128 diffV = _mm_cvtepi32_ps(_mm_sub_epi32(vProd, _mm_set1_epi32(vKey))); + + __m128 distance = _mm_sqrt_ps(_mm_add_ps(_mm_mul_ps(diffU, diffU), _mm_mul_ps(diffV, diffV))); + + __m128 alpha = (__m128) _mm_bslli_si128((__m128i) distance, 12); + alpha = _mm_sub_ps(alpha, _mm_set1_ps(threshold0)); + alpha = _mm_max_ps(alpha, _mm_set1_ps(0)); + alpha = _mm_mul_ps(alpha, _mm_set1_ps(65535 / (threshold1 - threshold0))); + alpha = _mm_min_ps(alpha, _mm_set1_ps(65535)); + + __m128i z = _mm_hadd_epi16(_mm_add_epi32(_mm_and_si128(_mm_cvtps_epi32(alpha), _mm_set_epi32(0xFFFFFFFF, 0, 0, 0)), _mm_set_epi32(0, 65535, 65535, 65535)), _mm_setzero_si128()); + + alphas = _mm_or_si128(alphas, z); + } + + { + __m128i uProd = _mm_mullo_epi32(row2, rgbaS16Hi); + __m128i vProd = _mm_mullo_epi32(row3, rgbaS16Hi); + + uProd = _mm_srai_epi32(uProd, 15); + uProd = _mm_hadd_epi32(uProd, _mm_setzero_si128()); + uProd = _mm_hadd_epi32(uProd, _mm_setzero_si128()); + + vProd = _mm_srai_epi32(vProd, 15); + vProd = _mm_hadd_epi32(vProd, _mm_setzero_si128()); + vProd = _mm_hadd_epi32(vProd, _mm_setzero_si128()); + + __m128 diffU = _mm_cvtepi32_ps(_mm_sub_epi32(uProd, _mm_set1_epi32(uKey))); + __m128 diffV = _mm_cvtepi32_ps(_mm_sub_epi32(vProd, _mm_set1_epi32(vKey))); + + __m128 distance = _mm_sqrt_ps(_mm_add_ps(_mm_mul_ps(diffU, diffU), _mm_mul_ps(diffV, diffV))); + + __m128 alpha = (__m128) _mm_bslli_si128((__m128i) distance, 12); + alpha = _mm_sub_ps(alpha, _mm_set1_ps(threshold0)); + alpha = _mm_max_ps(alpha, _mm_set1_ps(0)); + alpha = _mm_mul_ps(alpha, _mm_set1_ps(65535 / (threshold1 - threshold0))); + alpha = _mm_min_ps(alpha, _mm_set1_ps(65535)); + + __m128i z = _mm_hadd_epi16(_mm_add_epi32(_mm_and_si128(_mm_cvtps_epi32(alpha), _mm_set_epi32(0xFFFFFFFF, 0, 0, 0)), _mm_set_epi32(0, 65535, 65535, 65535)), _mm_setzero_si128()); + + alphas = _mm_or_si128(alphas, _mm_bslli_si128(z, 8)); + } + + rgba2U16 = _mm_mulhi_epu16(rgba2U16, alphas); + + _mm_stream_si128((__m128i*) ((uintptr_t) dst->data16 + y * src->stride + off), rgba2U16); + + } + } + + return 1; +} +CUTIVIS CHiPubNode *CHi_ChromaKey() { + CHiPubNode *n = calloc(1, sizeof(*n)); + n->type = CUTIHI_T('CChr','omaK'); + n->Start = n->Stop = NULL; + n->Perform = chromakey_perform; + n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 2); + n->sources = calloc(sizeof(*n->sources), n->sourceCount = 1); + + n->sinks[1].type = CUTIHI_VAL_VEC4; // Default green + n->sinks[1].data.vec4[0] = 0; + n->sinks[1].data.vec4[1] = 1; + n->sinks[1].data.vec4[2] = 0; + n->sinks[1].data.vec4[3] = 1; + return n; } @@ -1105,6 +1522,12 @@ CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader reader, void *ud) n = CHi_Camera(); } else if(type == CUTIHI_T('CCmp','nScl')) { n = CHi_ComponentScale(); + } else if(type == CUTIHI_T('CEnc','H264')) { + n = CHi_EncodeH264(); + } else if(type == CUTIHI_T('CStr','RTMP')) { + n = CHi_StreamRTMP(); + } else if(type == CUTIHI_T('CEnc','AACL')) { + n = CHi_EncodeAAC(); } n->ng = ng; @@ -1148,3 +1571,8 @@ CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader reader, void *ud) return 0; } + +CUTIVIS bool CHi_Node_Active(CHiPubNode *pubn) { + float now = CHi_Time_Get(pubn->ng); + return pubn->lifespan.start <= now && (pubn->lifespan.end == 0 || now < pubn->lifespan.end); +} diff --git a/hi/node.h b/hi/node.h index 885f350..2519648 100644 --- a/hi/node.h +++ b/hi/node.h @@ -3,18 +3,23 @@ #include #include +#include + +#include #include"defs.h" #include"bs.h" -#include - #ifdef __cplusplus extern "C" { #endif -#define CUTIHI_T(a, b) ((((uint64_t) htonl(b)) << 32) | htonl(a)) +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define CUTIHI_T(a, b) ((((uint64_t) __builtin_bswap32(b)) << 32) | __builtin_bswap32(a)) +#else +#define CUTIHI_T(a, b) ((((uint64_t) b) << 32) | a +#endif typedef size_t(*CHiSaveWriter)(void *ud, const void *data, size_t len); typedef size_t(*CHiLoadReader)(void *ud, void *data, size_t len); @@ -28,9 +33,11 @@ typedef enum { CUTIHI_VAL_VEC4 = 5, CUTIHI_VAL_WEAK_PTR = 6, - CUTIHI_VAL_VP9BS = 666, - CUTIHI_VAL_VP8BS = 667, + CUTIHI_VAL_VP8BS = 666, + CUTIHI_VAL_VP9BS = 667, CUTIHI_VAL_OPUSBS = 668, + CUTIHI_VAL_H264BS = 669, + CUTIHI_VAL_AACBS = 670, } CHiValType; struct CHiImage; @@ -57,6 +64,15 @@ typedef struct { CHiValueRaw data; } CHiValue; +#define CUTIHI_ERR_SIZE 16 +#define CUTIHI_MAX_ERRORS 4 +typedef struct { + bool activeLast[CUTIHI_MAX_ERRORS]; + bool active[CUTIHI_MAX_ERRORS]; + char code[CUTIHI_MAX_ERRORS][CUTIHI_ERR_SIZE]; + int sink[CUTIHI_MAX_ERRORS]; +} CHiErrors; + typedef struct CHiPubNode { uint64_t type; @@ -67,7 +83,6 @@ typedef struct CHiPubNode { int (*Perform)(struct CHiPubNode *node); int (*Start)(struct CHiPubNode *node); int (*Stop)(struct CHiPubNode *node); - char clean; void (*Destroy)(struct CHiPubNode *node); @@ -81,6 +96,13 @@ typedef struct CHiPubNode { struct CHiNodeGraph *ng; + CHiErrors errors; + + struct { + float start; + float end; + } lifespan; + char _dfsmark; } CHiPubNode; @@ -127,6 +149,7 @@ typedef struct CHiNodeGraph { void *ud; void(*eventOnStopComplete)(struct CHiNodeGraph*); void(*eventOnFrameComplete)(struct CHiNodeGraph*); + void(*eventOnError)(struct CHiNodeGraph*, CHiPubNode*); CHiCompilationStatus compilationStatus; @@ -137,6 +160,9 @@ typedef struct CHiNodeGraph { float time; float timedelta; + + // This is necessary for live changes of the node graph + pthread_mutex_t mut; } CHiNodeGraph; CUTIVIS CHiNodeGraph *CHi_NewNodeGraph(); @@ -151,7 +177,7 @@ CUTIVIS int CHi_ConfigureSink(CHiPubNode*, size_t, CHiValue); CUTIVIS void CHi_MakeKeyframe(CHiNodeGraph *ng, CHiPubNode *n, size_t idx); CUTIVIS size_t CHi_MoveKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, size_t idx, float to); CUTIVIS size_t CHi_MoveKeyframeBy(CHiNodeGraph *ng, CHiKeyframes *kfs, size_t idx, float dt); -CUTIVIS size_t CHi_GetClosestKeyframe(CHiNodeGraph *ng, size_t kfsIdx, float t); +CUTIVIS size_t CHi_GetClosestKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, float t); CUTIVIS void CHi_SetExtrapolationMode(CHiNodeGraph *ng, CHiPubNode *n, size_t sinkIdx, CHiExtrapolationMode mode, float* params); CUTIVIS void CHi_DeleteKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, size_t idx); @@ -162,7 +188,7 @@ CUTIVIS void CHi_BeginCompilation(CHiNodeGraph *ng); CUTIVIS void CHi_StopCompilation(CHiNodeGraph *ng); #define CUTIHI_IMAGE_IN_FILE 0 -#define CUTIHI_IMAGE_OUT_SAMPLE 1 +#define CUTIHI_IMAGE_OUT_SAMPLE 0 CUTIVIS CHiPubNode *CHi_Image(); #define CUTIHI_EMBED_OUT_MAIN 0 @@ -170,6 +196,7 @@ CUTIVIS CHiPubNode *CHi_Image(); CUTIVIS CHiPubNode *CHi_Embed(); #define CUTIHI_CONSTANTSAMPLE_IN_COLOR 0 +#define CUTIHI_CONSTANTSAMPLE_IN_SIZE 1 #define CUTIHI_CONSTANTSAMPLE_OUT_SAMPLE 1 CUTIVIS CHiPubNode *CHi_ConstantSample(); @@ -227,12 +254,8 @@ CUTIVIS CHiPubNode *CHi_Microphone(); CUTIVIS CHiPubNode *CHi_Text(); CUTIVIS CHiPubNode *CHi_ExportWav(); -CUTIVIS int CHi_ExportWav_Start(CHiPubNode*); -CUTIVIS int CHi_ExportWav_Stop(CHiPubNode*); CUTIVIS CHiPubNode *CHi_EncodeOpus(); -CUTIVIS int CHi_EncodeOpus_Start(CHiPubNode*); -CUTIVIS int CHi_EncodeOpus_Stop(CHiPubNode*); CUTIVIS CHiPubNode *CHi_Camera(); @@ -244,9 +267,19 @@ CUTIVIS CHiPubNode *CHi_Keyhook(); CUTIVIS CHiPubNode *CHi_Mixer(); +CUTIVIS CHiPubNode *CHi_ChromaKey(); + +CUTIVIS CHiPubNode *CHi_EncodeH264(); + +CUTIVIS CHiPubNode *CHi_StreamRTMP(); + +CUTIVIS CHiPubNode *CHi_EncodeAAC(); + CUTIVIS CHiValue *CHi_Crawl(CHiValue*); -CUTIVIS void CHi_Save(CHiNodeGraph *ng); +//CUTIVIS void CHi_Save(CHiNodeGraph *ng); + +CUTIVIS bool CHi_Node_Active(CHiPubNode*); #ifdef __cplusplus } diff --git a/hi/opus.c b/hi/opus.c index 25fc313..a4dddac 100644 --- a/hi/opus.c +++ b/hi/opus.c @@ -52,18 +52,7 @@ static int encodeopus_perform(CHiPubNode *pubn) { return 1; } -CUTIVIS CHiPubNode *CHi_EncodeOpus() { - 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; - ret->pubn.Stop = CHi_EncodeOpus_Stop; - ret->pubn.clean = 0; - ret->pubn.sinks = calloc(sizeof(*ret->pubn.sinks), ret->pubn.sinkCount = 1); - ret->pubn.sources = calloc(sizeof(*ret->pubn.sources), ret->pubn.sourceCount = 1); - return &ret->pubn; -} -CUTIVIS int CHi_EncodeOpus_Start(CHiPubNode *pubn) { +static int CHi_EncodeOpus_Start(CHiPubNode *pubn) { struct CHiEncodeOpusNode *n = (struct CHiEncodeOpusNode*) pubn; int error; @@ -76,10 +65,20 @@ CUTIVIS int CHi_EncodeOpus_Start(CHiPubNode *pubn) { return 1; } -CUTIVIS int CHi_EncodeOpus_Stop(CHiPubNode *pubn) { +static int CHi_EncodeOpus_Stop(CHiPubNode *pubn) { struct CHiEncodeOpusNode *n = (struct CHiEncodeOpusNode*) pubn; opus_encoder_destroy(n->enc); free(n->pcmbuf); return 1; } +CUTIVIS CHiPubNode *CHi_EncodeOpus() { + 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; + ret->pubn.Stop = CHi_EncodeOpus_Stop; + ret->pubn.sinks = calloc(sizeof(*ret->pubn.sinks), ret->pubn.sinkCount = 1); + ret->pubn.sources = calloc(sizeof(*ret->pubn.sources), ret->pubn.sourceCount = 1); + return &ret->pubn; +} diff --git a/hi/relay.c b/hi/relay.c index c15591a..967203f 100644 --- a/hi/relay.c +++ b/hi/relay.c @@ -39,7 +39,6 @@ CUTIVIS CHiPubNode *CHi_ComponentScale() { n->type = CUTIHI_T('CCmp','nScl'); n->Start = n->Stop = NULL; n->Perform = scale_perform; - n->clean = 0; n->sinkCount = 2; n->sinks = calloc(sizeof(*n->sinks), n->sinkCount); n->sourceCount = 1; @@ -48,12 +47,14 @@ CUTIVIS CHiPubNode *CHi_ComponentScale() { } static Display *dpy; +static XkbDescPtr xKeyboardDesc; + typedef struct { CHiPubNode pub; XRecordContext rc; pthread_t thrd; - atomic_int key; + char key[64]; atomic_bool on; } CHiKeyhookNode; @@ -68,8 +69,10 @@ static void keyhook_handler(XPointer ud, XRecordInterceptData *recdata) { CHiKeyhookNode *n = (CHiKeyhookNode*) ud; - printf("%i\n", key); - if(!repeat && key == n->key) { + char keyname[XkbKeyNameLength + 1] = {}; + memcpy(keyname, xKeyboardDesc->names->keys[key].name, XkbKeyNameLength); + + if(!repeat && !strcmp(keyname, n->key)) { if(type == KeyPress) { n->on = 1; } else if(type == KeyRelease) { @@ -93,7 +96,10 @@ static void *keyhook_thread(void *ud) { } static int keyhook_perform(CHiPubNode *n) { - ((CHiKeyhookNode*) n)->key = CHi_Crawl(&n->sinks[0])->data.vec4[0]; + CHiKeyhookNode *me = (CHiKeyhookNode*) n; + + strncpy(me->key, CHi_Crawl(&n->sinks[0])->data.text, 63); + me->key[63] = '\0'; n->sources[0].type = CUTIHI_VAL_VEC4; @@ -123,17 +129,19 @@ CUTIVIS CHiPubNode *CHi_Keyhook() { 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); n->pub.sourceCount = 1; n->pub.sources = calloc(sizeof(*n->pub.sources), n->pub.sourceCount); n->on = 0; - n->key = 0; + n->key[0] = '\n'; if(!dpy) { dpy = XOpenDisplay(NULL); + + xKeyboardDesc = XkbGetMap(dpy, 0, XkbUseCoreKbd); + XkbGetNames(dpy, XkbKeyNamesMask, xKeyboardDesc); } pthread_create(&n->thrd, NULL, keyhook_thread, n); diff --git a/hi/webcam.c b/hi/webcam.c index 54c79ce..08ad0c6 100644 --- a/hi/webcam.c +++ b/hi/webcam.c @@ -127,7 +127,6 @@ CUTIVIS CHiPubNode *CHi_Camera() { CHiPubNode *pubn = calloc(1, sizeof(*pubn)); pubn->type = CUTIHI_T('CWeb','Cam '); - pubn->clean = 0; pubn->Start = pubn->Stop = NULL; pubn->Perform = camera_perform; pubn->sinks = calloc(sizeof(*pubn->sinks), pubn->sinkCount = 0); diff --git a/hi/webmdec.cpp b/hi/webmdec.cpp index fc8d29b..3093bd7 100644 --- a/hi/webmdec.cpp +++ b/hi/webmdec.cpp @@ -280,14 +280,22 @@ webm::Status CueParser::OnCuePoint(const webm::ElementMetadata &metadata, const static int movie_perform(CHiPubNode *pub) { CHiMovieNode *node = (CHiMovieNode*) ((uintptr_t) pub - offsetof(CHiMovieNode, pub)); + pub->sources[0].type = CUTIHI_VAL_SAMPLE; + if(pub->sources[0].data.sample) { + CHi_Image_Free(pub->sources[0].data.sample); + pub->sources[0].data.sample = nullptr; + } + + if(!CHi_Node_Active(pub)) { + return 1; + } + MTR_BEGIN("CHi", "movie_perform"); int64_t t; - if(pub->sinks[1].type == CUTIHI_VAL_NONE) t = CHi_Time_Get(pub->ng) * 1000; + if(pub->sinks[1].type == CUTIHI_VAL_NONE) t = (CHi_Time_Get(pub->ng) - pub->lifespan.start) * 1000; else t = CHi_Crawl(&pub->sinks[1])->data.vec4[0] * 1000; - pub->sources[0].type = CUTIHI_VAL_SAMPLE; - char *filepath = CHi_Crawl(&pub->sinks[0])->data.text; if(!node->filepathCache || strcmp(node->filepathCache, filepath) != 0) { @@ -331,15 +339,13 @@ static int movie_perform(CHiPubNode *pub) { return 1; } - if(pub->sources[0].data.sample) { - CHi_Image_Free(pub->sources[0].data.sample); - pub->sources[0].data.sample = nullptr; - } - if(t >= 0 && t < 1000 * node->duration) { if(t < node->timeCache || (t - node->timeCache) > 5000) { + fseek(node->vf, 0, SEEK_SET); + fseek(node->af, 0, SEEK_SET); + if(node->cuepoints.size() > 0) { size_t i; @@ -351,19 +357,16 @@ static int movie_perform(CHiPubNode *pub) { if(i != 0) i--; - for(webm::Element &p : node->cuepoints[i].cue_track_positions) { - if(p.value().track.value() == node->videoTrack) { - fseek(node->vf, node->segmentOff + p.value().cluster_position.value(), SEEK_SET); - fseek(node->af, node->segmentOff + p.value().cluster_position.value(), SEEK_SET); - break; + if(node->cuepoints[i].time.value() <= t) { + for(webm::Element &p : node->cuepoints[i].cue_track_positions) { + if(p.value().track.value() == node->videoTrack) { + fseek(node->vf, node->segmentOff + p.value().cluster_position.value(), SEEK_SET); + fseek(node->af, node->segmentOff + p.value().cluster_position.value(), SEEK_SET); + break; + } } } - } else { - - fseek(node->vf, 0, SEEK_SET); - fseek(node->af, 0, SEEK_SET); - } } @@ -380,6 +383,8 @@ static int movie_perform(CHiPubNode *pub) { pub->sources[0].data.sample = node->fp.output; + node->fp.output = nullptr; + node->timeCache = t; } @@ -406,8 +411,6 @@ static int movie_perform(CHiPubNode *pub) { pub->sources[1].type = CUTIHI_VAL_SAMPLE; pub->sources[1].data.sample = aud; - pub->clean = 0; - MTR_END("CHi", "movie_perform"); return 1; @@ -441,7 +444,6 @@ CUTIVIS CHiPubNode *CHi_Movie() { 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); n->pub.sinks[1].type = CUTIHI_VAL_VEC4; diff --git a/hi/webmenc.cpp b/hi/webmenc.cpp index df65040..e36a6aa 100644 --- a/hi/webmenc.cpp +++ b/hi/webmenc.cpp @@ -24,295 +24,7 @@ #include"linearity.h" -struct CHiEncodeVP9Node { - vpx_codec_ctx_t codec; - vpx_codec_enc_cfg_t cfg; - - enum { - WAITING, IN_PROGRESS - } state; - uint8_t *outY, *outU, *outV; - uint16_t strideY, strideU, strideV; - - vpx_codec_iface_t *iface; - - CHiPubNode pub; -}; - -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; - - if(node->state == CHiEncodeVP9Node::WAITING) return 1; - - CHiImage *rgbIn = (CHiImage*) CHi_Crawl(&pub->sinks[0])->data.sample; - - #pragma omp parallel for simd - for(size_t y = 0; y < node->cfg.g_h; y += 2) { - for(size_t x = 0; x < node->cfg.g_w; x += 16) { - __m128i rgb, partY, partU, partV, dotY, dotU, dotV; - - __m128i wipY0 = _mm_setzero_si128(); - __m128i wipY1 = _mm_setzero_si128(); - __m128i wipU = _mm_setzero_si128(); - __m128i wipV = _mm_setzero_si128(); - - __m128i tempU = _mm_setzero_si128(); - __m128i tempV = _mm_setzero_si128(); - -#define DO_DAH_DOO_DOO(LoOrHi, shufY, shufUV) \ - /* Process top two */\ - rgb = _mm_srli_epi16(apply_gamma_epi16(line0, _mm_set1_ps(1 / 2.2f)), 8); \ - /* Start matrix multiplication (BT.709 + full->studio range) */\ - partY = _mm_mullo_epi16(rgb, _mm_set_epi16(0, 47, 157, 16, 0, 47, 157, 16));\ - partU = _mm_mullo_epi16(rgb, _mm_set_epi16(0, -25, -85, 110, 0, -25, -85, 110));\ - partV = _mm_mullo_epi16(rgb, _mm_set_epi16(0, 110, -100, -10, 0, 110, -100, -10));\ - /* Finish mat-mul with dot products */\ - dotY = _mm_madd_epi16(partY, _mm_set1_epi16(1));\ - dotY = _mm_hadd_epi32(dotY, _mm_setzero_si128());\ - dotU = _mm_madd_epi16(partU, _mm_set1_epi16(1));\ - dotU = _mm_hadd_epi32(dotU, _mm_setzero_si128());\ - dotV = _mm_madd_epi16(partV, _mm_set1_epi16(1));\ - dotV = _mm_hadd_epi32(dotV, _mm_setzero_si128());\ - /* Insert Ys */\ - wipY0 = _mm_or_si128(wipY0, _mm_shuffle_epi8(dotY, shufY));\ - /* Save top UV */\ - tempU = dotU;\ - tempV = dotV;\ - \ - /* Process bottom two */\ - rgb = _mm_srli_epi16(apply_gamma_epi16(line1, _mm_set1_ps(1 / 2.2f)), 8); \ - /* Start matrix multiplication (BT.709 + full->studio range) */\ - partY = _mm_mullo_epi16(rgb, _mm_set_epi16(0, 47, 157, 16, 0, 47, 157, 16));\ - partU = _mm_mullo_epi16(rgb, _mm_set_epi16(0, -25, -85, 110, 0, -25, -85, 110));\ - partV = _mm_mullo_epi16(rgb, _mm_set_epi16(0, 110, -100, -10, 0, 110, -100, -10));\ - /* Finish mat-mul with dot products */\ - dotY = _mm_madd_epi16(partY, _mm_set1_epi16(1));\ - dotY = _mm_hadd_epi32(dotY, _mm_setzero_si128());\ - dotU = _mm_madd_epi16(partU, _mm_set1_epi16(1));\ - dotU = _mm_hadd_epi32(dotU, _mm_setzero_si128());\ - dotV = _mm_madd_epi16(partV, _mm_set1_epi16(1));\ - dotV = _mm_hadd_epi32(dotV, _mm_setzero_si128());\ - /* Insert Ys */\ - wipY1 = _mm_or_si128(wipY1, _mm_shuffle_epi8(dotY, shufY));\ - /* Save bottom UVs */\ - tempU = _mm_hadd_epi32(_mm_add_epi32(tempU, dotU), _mm_setzero_si128());\ - tempV = _mm_hadd_epi32(_mm_add_epi32(tempV, dotV), _mm_setzero_si128());\ - \ - /* Insert UVs */\ - wipU = _mm_or_si128(wipU, _mm_shuffle_epi8(_mm_srli_epi32(tempU, 2), shufUV));\ - wipV = _mm_or_si128(wipV, _mm_shuffle_epi8(_mm_srli_epi32(tempV, 2), shufUV)); - - __m128i line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 0) * 8)); // Load two pixels - __m128i line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 0) * 8)); // Load two pixels - - DO_DAH_DOO_DOO(_mm_unpacklo_epi8, - _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 5, 1), - _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1)); - - line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 2) * 8)); // Load two pixels - line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 2) * 8)); // Load two pixels - - DO_DAH_DOO_DOO(_mm_unpacklo_epi8, - _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 5, 1, -128, -128), - _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1, -128)); - - line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 4) * 8)); // Load two pixels - line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 4) * 8)); // Load two pixels - - DO_DAH_DOO_DOO(_mm_unpacklo_epi8, - _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 5, 1, -128, -128, -128, -128), - _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1, -128, -128)); - - line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 6) * 8)); // Load two pixels - line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 6) * 8)); // Load two pixels - - DO_DAH_DOO_DOO(_mm_unpacklo_epi8, - _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, 5, 1, -128, -128, -128, -128, -128, -128), - _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1, -128, -128, -128)); - - line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 8) * 8)); // Load two pixels - line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 8) * 8)); // Load two pixels - - DO_DAH_DOO_DOO(_mm_unpacklo_epi8, - _mm_set_epi8(-128, -128, -128, -128, -128, -128, 5, 1, -128, -128, -128, -128, -128, -128, -128, -128), - _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1, -128, -128, -128, -128)); - - line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 10) * 8)); // Load two pixels - line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 10) * 8)); // Load two pixels - - DO_DAH_DOO_DOO(_mm_unpacklo_epi8, - _mm_set_epi8(-128, -128, -128, -128, 5, 1, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128), - _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1, -128, -128, -128, -128, -128)); - - line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 12) * 8)); // Load two pixels - line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 12) * 8)); // Load two pixels - - DO_DAH_DOO_DOO(_mm_unpacklo_epi8, - _mm_set_epi8(-128, -128, 5, 1, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128), - _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, 1, -128, -128, -128, -128, -128, -128)); - - line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 14) * 8)); // Load two pixels - line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 14) * 8)); // Load two pixels - - DO_DAH_DOO_DOO(_mm_unpacklo_epi8, - _mm_set_epi8( 5, 1, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128), - _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, 1, -128, -128, -128, -128, -128, -128, -128)); - - _mm_stream_si128((__m128i*) &node->outY[node->strideY * (y + 0) + x], _mm_add_epi8(_mm_set1_epi8(16), wipY0)); - _mm_stream_si128((__m128i*) &node->outY[node->strideY * (y + 1) + x], _mm_add_epi8(_mm_set1_epi8(16), wipY1)); - _mm_storeu_si128((__m128i*) &node->outU[node->strideU * (y / 2) + x / 2], _mm_add_epi8(wipU, _mm_set1_epi8(128))); - _mm_storeu_si128((__m128i*) &node->outV[node->strideV * (y / 2) + x / 2], _mm_add_epi8(wipV, _mm_set1_epi8(128))); - } - } - - vpx_image_t vpxraw; - vpxraw.fmt = VPX_IMG_FMT_I420; - vpxraw.cs = VPX_CS_BT_709; - vpxraw.range = VPX_CR_STUDIO_RANGE; - vpxraw.bit_depth = 8; - vpxraw.w = vpxraw.d_w = node->cfg.g_w; - vpxraw.h = vpxraw.d_h = node->cfg.g_h; - vpxraw.r_w = vpxraw.r_h = 0; - vpxraw.x_chroma_shift = vpxraw.y_chroma_shift = 1; - vpxraw.img_data_owner = 0; - vpxraw.self_allocd = 0; - vpxraw.bps = 12; - vpxraw.stride[VPX_PLANE_Y] = node->strideY; - vpxraw.planes[VPX_PLANE_Y] = node->outY; - vpxraw.stride[VPX_PLANE_U] = node->strideU; - vpxraw.planes[VPX_PLANE_U] = node->outU; - vpxraw.stride[VPX_PLANE_V] = node->strideV; - vpxraw.planes[VPX_PLANE_V] = node->outV; - - vpx_codec_encode(&node->codec, &vpxraw, CHi_Time_Get(pub->ng) * 1000.f, 1, 0, VPX_DL_REALTIME); - - auto ret = (CHiBSFrames*) malloc(sizeof(CHiBSFrames)); - ret->count = 0; - - vpx_codec_iter_t iter = NULL; - const vpx_codec_cx_pkt_t *pkt; - while((pkt = vpx_codec_get_cx_data(&node->codec, &iter)) != NULL) { - if(pkt->kind == VPX_CODEC_CX_FRAME_PKT) { - ret = (CHiBSFrames*) realloc(ret, sizeof(CHiBSFrames) + sizeof(CHiBSFrame) * (ret->count + 1)); - ret->data[ret->count].timestamp = pkt->data.frame.pts; - ret->data[ret->count].sz = pkt->data.frame.sz; - ret->data[ret->count].flags = pkt->data.frame.flags & VPX_FRAME_IS_KEY; - ret->data[ret->count].ptr = malloc(ret->data[ret->count].sz); - memcpy(ret->data[ret->count].ptr, pkt->data.frame.buf, ret->data[ret->count].sz); - ret->count++; - } - } - -// if(pktRet) v->queueOut.enqueue(pktRet); - - //memcpy(node->vpxraw.planes[VPX_PLANE_Y], VIPS_IMAGE_ADDR(y, 0, 0), node->vpxraw.stride[VPX_PLANE_Y] * node->vpxraw.d_h); - //memcpy(node->vpxraw.planes[VPX_PLANE_U], VIPS_IMAGE_ADDR(u, 0, 0), node->vpxraw.stride[VPX_PLANE_U] * (node->vpxraw.d_h >> node->vpxraw.y_chroma_shift)); - //memcpy(node->vpxraw.planes[VPX_PLANE_V], VIPS_IMAGE_ADDR(v, 0, 0), node->vpxraw.stride[VPX_PLANE_V] * (node->vpxraw.d_h >> node->vpxraw.y_chroma_shift)); - - //const vpx_codec_cx_pkt_t *pkt; - //while(!node->queueOut.try_dequeue(pkt)) usleep(0); - - 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*) 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); - n->state = CHiEncodeVP9Node::WAITING; - n->iface = vpx_codec_vp8_cx(); - return &n->pub; -} - -CUTIVIS CHiPubNode *CHi_EncodeVP9() { - 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); - n->state = CHiEncodeVP9Node::WAITING; - n->iface = vpx_codec_vp9_cx(); - return &n->pub; -} - -CUTIVIS int CHi_EncodeVP9_Start(CHiPubNode *pubn) { - CHiEncodeVP9Node *node = (CHiEncodeVP9Node*) ((uintptr_t) pubn - offsetof(CHiEncodeVP9Node, pub)); - - node->state = CHiEncodeVP9Node::IN_PROGRESS; - - CHiImage *firstFrame = (CHiImage*) CHi_Crawl(&pubn->sinks[0])->data.sample; - - vpx_codec_enc_config_default(node->iface, &node->cfg, 0); - node->cfg.g_w = firstFrame->width; - node->cfg.g_h = firstFrame->height; - node->cfg.g_timebase.num = 1; - node->cfg.g_timebase.den = 30; - node->cfg.g_lag_in_frames = 0; - node->cfg.g_threads = 8; - node->cfg.kf_mode = VPX_KF_AUTO; - node->cfg.kf_max_dist = 300; - node->cfg.rc_end_usage = VPX_VBR; - node->cfg.rc_target_bitrate = 512; - node->cfg.rc_min_quantizer = 4; - node->cfg.rc_max_quantizer = 48; - - vpx_codec_enc_init(&node->codec, node->iface, &node->cfg, 0); - vpx_codec_control(&node->codec, VP8E_SET_CPUUSED, 8); - vpx_codec_control(&node->codec, VP9E_SET_ROW_MT, 1); - vpx_codec_control(&node->codec, VP9E_SET_TILE_COLUMNS, 2); - vpx_codec_control(&node->codec, VP9E_SET_TILE_ROWS, 1); - vpx_codec_control(&node->codec, VP9E_SET_TUNE_CONTENT, VP9E_CONTENT_SCREEN); - - node->strideY = (node->cfg.g_w + 64) & ~63; - node->strideU = (node->cfg.g_w / 2 + 64) & ~63; - node->strideV = (node->cfg.g_w / 2 + 64) & ~63; - - node->outY = (uint8_t*) _mm_malloc(node->strideY * node->cfg.g_h, 16); - node->outU = (uint8_t*) _mm_malloc(node->strideU * node->cfg.g_h / 2, 16); - node->outV = (uint8_t*) _mm_malloc(node->strideV * node->cfg.g_h / 2, 16); - - return 1; -} - -CUTIVIS int CHi_EncodeVP9_Stop(CHiPubNode *pubn) { - CHiEncodeVP9Node *node = (CHiEncodeVP9Node*) ((uintptr_t) pubn - offsetof(CHiEncodeVP9Node, pub)); - - node->state = CHiEncodeVP9Node::WAITING; - - _mm_free(node->outY); - _mm_free(node->outU); - _mm_free(node->outV); - - vpx_codec_destroy(&node->codec); - - return 1; -} +#include"yuv.h" struct CHiMuxWebmNode { CHiPubNode pub; @@ -386,7 +98,6 @@ CUTIVIS CHiPubNode *CHi_MuxWebm() { 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; diff --git a/hi/window.c b/hi/window.c index b3fa567..c7504e5 100644 --- a/hi/window.c +++ b/hi/window.c @@ -114,8 +114,6 @@ static int window_perform(CHiPubNode *n) { n->sources[0].type = CUTIHI_VAL_SAMPLE; n->sources[0].data.sample = w->vcache; - n->clean = 0; - MTR_END("CHi", "window_perform"); return 1; @@ -144,7 +142,6 @@ CUTIVIS CHiPubNode *CHi_Window() { 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; @@ -168,7 +165,7 @@ 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); + //XFree(list); return status >= Success ? numItems : 0; } @@ -186,7 +183,7 @@ 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); + //XFree(list); status = XGetWMName(d, list[idx], &windowName); if(status >= Success) { @@ -209,7 +206,7 @@ CUTIVIS uintptr_t CHi_Window_GetSourceData(size_t idx) { if(status >= Success) { Window ret = list[idx]; - XFree(list); + //XFree(list); return ret; } diff --git a/ui/frame.cpp b/ui/frame.cpp index 945f37d..8ae4c10 100644 --- a/ui/frame.cpp +++ b/ui/frame.cpp @@ -28,93 +28,155 @@ #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}}; +static Frame *globaldis; + +std::string node_name_from_id(uint64_t id) { + static std::unordered_map NODE_ID_NAMES = { + {CUTIHI_T('CPre','view'), "Preview"}, + {CUTIHI_T('CMix','er '), "Mixer"}, + {CUTIHI_T('CTex','t '), "Text"}, + {CUTIHI_T('CTim','e '), "Time"}, + {CUTIHI_T('CMod','ulat'), "Modulate"}, + {CUTIHI_T('CCns','tCol'), "Constant"}, + {CUTIHI_T('CEmb','ed '), "Frame"}, + {CUTIHI_T('CIma','ge '), "Image"}, + {CUTIHI_T('CWin','dow '), "Window"}, + {CUTIHI_T('CInA','udio'), "Microphone"}, + {CUTIHI_T('CExp','Wave'), "Mux .wav"}, + {CUTIHI_T('CMov','ie '), "Movie"}, + {CUTIHI_T('CEnc','GVP8'), "Encode VP8"}, + {CUTIHI_T('CEnc','GVP9'), "Encode VP9"}, + {CUTIHI_T('CEnc','H264'), "Encode H264"}, + {CUTIHI_T('CExp','Webm'), "Mux .webm"}, + {CUTIHI_T('CKey','hook'), "Keyhook"}, + {CUTIHI_T('CEnc','Opus'), "Encode Opus"}, + {CUTIHI_T('CWeb','Cam '), "Live Digital Camera"}, + {CUTIHI_T('CCmp','nScl'), "Scale"}, + {CUTIHI_T('CChr','omaK'), "Chroma Key"}, + {CUTIHI_T('CStr','RTMP'), "Stream RTMP"}, + {CUTIHI_T('CEnc','AACL'), "Encode AAC-LC"}, + }; + + auto nameit = NODE_ID_NAMES.find(id); + if(nameit == NODE_ID_NAMES.end()) { + char n[9] = {}; + memcpy(n, &id, 8); + + return "Unknown (" + std::string{n} + ")"; } else { - puts("Unknown node type."); + return nameit->second; } } +GrNode::Port::Port(wxString name) : Port(name, GrNode::Port::Type::NONE, false, 1, 1) {} +GrNode::Port::Port(wxString name, Type type) : Port(name, type, false, 1, 1) {} +GrNode::Port::Port(wxString name, Type type, bool separator) : Port(name, type, separator, 1, 1) {} +GrNode::Port::Port(wxString name, Type type, bool separator, float dragScalingX, float dragScalingY) : name(name), type(type), separator(separator) { + dragScaling[0] = dragScalingX; + dragScaling[1] = dragScalingY; +} + +static void ShapeGrNode(GrNode *gn) { + gn->name = node_name_from_id(gn->logical->type); + + if(gn->logical->type == CUTIHI_T('CPre','view')) { + gn->sinks = {{"Video", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CMix','er ')) { + gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}}; + + int numSinks = gn->logical->sinkCount; + while(numSinks > 0 && gn->logical->sinks[numSinks - 1].type == CUTIHI_VAL_NONE) { + numSinks--; + } + numSinks++; + + gn->sinks.clear(); + for(int s = 0; s < numSinks; s++) { + gn->sinks.push_back({std::string{"Sink "} + std::to_string(s + 1), GrNode::Port::Type::SAMPLE}); + } + } else if(gn->logical->type == CUTIHI_T('CTex','t ')) { + gn->sinks = {{"Text", GrNode::Port::Type::TEXT}, {"Color", GrNode::Port::Type::COLOR}, {"DPI", GrNode::Port::Type::VEC1}, {"Font", GrNode::Port::Type::TEXT}}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CTim','e ')) { + gn->sinks = {}; + gn->sources = {{"t", GrNode::Port::Type::VEC1}}; + } else if(gn->logical->type == CUTIHI_T('CMod','ulat')) { + gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Brightness", GrNode::Port::Type::VEC1, false, 0.01, 0}, {"Contrast", GrNode::Port::Type::VEC1, false, 0.01, 0}, {"Hue", GrNode::Port::Type::VEC1}}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CCns','tCol')) { + gn->sinks = {{"Color", GrNode::Port::Type::COLOR}, {"Resolution", GrNode::Port::Type::VEC2}}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CEmb','ed ')) { + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + gn->sinks = {{"Frame", GrNode::Port::Type::SAMPLE, true}}; + + int numSubs = 0; + while(gn->logical->sinkCount > (1 + numSubs * 3 + 0) && gn->logical->sinks[1 + numSubs * 3 + 0].type != CUTIHI_VAL_NONE) { + numSubs++; + } + numSubs++; + + for(int s = 0; s < numSubs; s++) { + gn->sinks.push_back({"Sub " + std::to_string(s + 1), GrNode::Port::Type::SAMPLE}); + gn->sinks.push_back({" Pos", GrNode::Port::Type::VEC2}); + gn->sinks.push_back({" Size", GrNode::Port::Type::VEC1, true}); + } + } else if(gn->logical->type == CUTIHI_T('CIma','ge ')) { + 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->sinks = {{"Name", GrNode::Port::Type::WINDOW_SOURCE}}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CInA','udio')) { + 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->sinks = {{"Filename", GrNode::Port::Type::FILE_SAVE}, {"Audio", GrNode::Port::Type::SAMPLE}}; + gn->sources = {}; + } else if(gn->logical->type == CUTIHI_T('CMov','ie ')) { + 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->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}}; + gn->sources = {{"Bitstream"}}; + } else if(gn->logical->type == CUTIHI_T('CEnc','GVP9')) { + gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}}; + gn->sources = {{"Bitstream"}}; + } else if(gn->logical->type == CUTIHI_T('CEnc','H264')) { + gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}}; + gn->sources = {{"Bitstream"}}; + } else if(gn->logical->type == CUTIHI_T('CExp','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->sinks = {{"Key", GrNode::Port::Type::TEXT}, {"Smooth Time", GrNode::Port::Type::VEC1}}; + gn->sources = {{"Bool", GrNode::Port::Type::VEC1}}; + } else if(gn->logical->type == CUTIHI_T('CEnc','Opus')) { + gn->sinks = {{"Audio", GrNode::Port::Type::SAMPLE}}; + gn->sources = {{"Bitstream"}}; + } else if(gn->logical->type == CUTIHI_T('CWeb','Cam ')) { + gn->sinks = {}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CCmp','nScl')) { + gn->sinks = {{"Vector", GrNode::Port::Type::VEC4}, {"Sample", GrNode::Port::Type::SAMPLE}}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CChr','omaK')) { + gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Color", GrNode::Port::Type::COLOR}}; + gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; + } else if(gn->logical->type == CUTIHI_T('CStr','RTMP')) { + gn->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"URL", GrNode::Port::Type::TEXT}}; + gn->sources = {}; + } else if(gn->logical->type == CUTIHI_T('CEnc','AACL')) { + gn->sinks = {{"Audio", GrNode::Port::Type::SAMPLE}}; + gn->sources = {{"Bitstream"}}; + } + + gn->Fit(); +} + Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystemSettings::GetMetric(wxSYS_SCREEN_X) / 2, wxSystemSettings::GetMetric(wxSYS_SCREEN_Y) / 2}) { + globaldis = this; + aui.SetManagedWindow(this); viewer = new ImageViewer(this); @@ -167,9 +229,7 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem }, 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]; - }); + GrNode *gn = graph->get_graphical(graph->backendNG->nodes[n]); int32_t pos[2] = {gn->GetPosition().x, gn->GetPosition().y}; fwrite(pos, sizeof(pos), 1, f); @@ -207,7 +267,6 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem gnode->logical = graph->backendNG->nodes[n]; ShapeGrNode(gnode); - gnode->Fit(); graph->gnodes.push_back(gnode); } @@ -238,10 +297,10 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem 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); }); + CHi_SetDuration(graph->backendNG, toolbar.duration->GetSeconds()); toolbar.durationEnable->SetValue(true); toolbar.durationEnable->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent&){ @@ -296,12 +355,23 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem }); }; + graph->backendNG->eventOnError = +[](CHiNodeGraph *ng, CHiPubNode *n) { + wxTheApp->CallAfter([ng, n](){ + auto frame = (Frame*) ng->ud; + + GrNode *gn = frame->graph->get_graphical(n); + gn->Refresh(); + }); + }; + tlba->Realize(); aui.SetFlags(wxAUI_MGR_LIVE_RESIZE | wxAUI_MGR_DEFAULT); aui.Update(); Centre(); + + graph->CreatePreviewNode(); } Frame::~Frame() { @@ -313,7 +383,7 @@ bool GrNode::MouseOverPort(wxPoint point, bool &source, int &i) { int p = (point.y - 26) / 20; if((point.x >= 15 || p >= (int) sinks.size()) && (point.x < GetSize().x - 15 || p >= (int) sources.size())) return false; - int isSource = point.x >= GetSize().x - 10; + int isSource = point.x >= GetSize().x / 2; source = isSource; i = p; @@ -329,11 +399,25 @@ void GrNode::MakeKeyframe(int sinkIdx) { ((Frame*) ng->GetParent())->timeline->Refresh(); } +static bool has_errors(CHiPubNode *pn) { + for(int i = 0; i < CUTIHI_MAX_ERRORS; i++) { + if(pn->errors.active[i]) return true; + } + return false; +} + GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) { Bind(wxEVT_PAINT, [this](wxPaintEvent &ev){ + wxColour bgColor; + if(has_errors(logical)) { + bgColor = wxColour{168, 118, 118}; + } else { + bgColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + } + wxPaintDC dc(this); - dc.SetBrush(wxBrush{wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)}); - dc.SetPen(HasFocus() ? *wxRED_PEN : *wxBLACK_PEN); + dc.SetBrush(wxBrush{bgColor}); + dc.SetPen(HasFocus() ? *wxGREY_PEN : *wxBLACK_PEN); dc.DrawRoundedRectangle(5, 0, GetSize().x - 10, GetSize().y, 5); dc.DrawText(name, GetSize().x / 2 - dc.GetTextExtent(name).x / 2, 2); @@ -364,6 +448,10 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) dc.DrawText(p.name, 15, (y += 20) - sz.y / 2); dc.DrawCircle(5, y, 5); + if(p.separator) { + dc.DrawLine(5, y + 10, GetSize().x / 2, y + 10); + } + i++; } y = 13; @@ -388,6 +476,10 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) dc.DrawText(p.name, GetSize().x - sz.x - 15, (y += 20) - sz.y / 2); dc.DrawCircle(GetSize().x - 6, y, 5); + if(p.separator) { + dc.DrawLine(GetSize().x / 2, y + 10, GetSize().x - 5, y + 10); + } + i++; } }); @@ -425,9 +517,38 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) SetPosition(GetPosition() + neu - parent->dragPos); parent->dragPos = neu; } + } else if(wxGetMouseState().MiddleIsDown()) { + if(HasCapture()) { + wxPoint neu = ClientToScreen(ev.GetPosition()); + wxPoint diff = neu - parent->dragPos; + parent->dragPos = neu; + + CHiValue newv = *CHi_Crawl(&this->logical->sinks[sinkValueDragging]); + + if(newv.type == CUTIHI_VAL_NONE) { + newv.type = CUTIHI_VAL_VEC4; + newv.data.vec4[0] = newv.data.vec4[1] = newv.data.vec4[2] = 0; + newv.data.vec4[3] = 1; + } + + assert(newv.type == CUTIHI_VAL_VEC4); + + newv.data.vec4[0] += diff.x * sinks[sinkValueDragging].dragScaling[0]; + newv.data.vec4[1] += diff.y * sinks[sinkValueDragging].dragScaling[1]; + + pthread_mutex_lock(&this->logical->ng->mut); + CHi_ConfigureSink(this->logical, sinkValueDragging, newv); + pthread_mutex_unlock(&this->logical->ng->mut); + + parent->Dirtify(this); + } } parent->Refresh(); - SetFocus(); + + // Set focus if currently focused item is not a control/entry/editor + if(!dynamic_cast(wxWindow::FindFocus())) { + SetFocus(); + } }); Bind(wxEVT_LEFT_UP, [parent, this](wxMouseEvent &ev){ if(HasCapture()) { @@ -446,6 +567,7 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) wxColourData data; data.SetChooseFull(true); + data.SetChooseAlpha(true); CHiValue *currentVal = CHi_Crawl(&this->logical->sinks[p]); if(currentVal->type == CUTIHI_VAL_VEC4) { @@ -458,7 +580,10 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) } wxColourDialog dlg(this, &data); + if(dlg.ShowModal() == wxID_OK) { + pthread_mutex_lock(&this->logical->ng->mut); + CHiValue newv; newv.type = CUTIHI_VAL_VEC4; newv.data.vec4[0] = dlg.GetColourData().GetColour().Red() / 255.f; @@ -467,24 +592,34 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) newv.data.vec4[3] = dlg.GetColourData().GetColour().Alpha() / 255.f; CHi_ConfigureSink(this->logical, p, newv); parent->Dirtify(this); + + pthread_mutex_unlock(&this->logical->ng->mut); } } else if(sinks[p].type == Port::Type::FILE_OPEN) { wxFileDialog dlg(this, wxFileSelectorPromptStr, wxEmptyString, wxEmptyString, wxFileSelectorDefaultWildcardStr, wxFD_OPEN | wxFD_PREVIEW); if(dlg.ShowModal() == wxID_OK) { + pthread_mutex_lock(&this->logical->ng->mut); + CHiValue newv; newv.type = CUTIHI_VAL_TEXT; newv.data.text = strdup(dlg.GetPath().utf8_str()); CHi_ConfigureSink(this->logical, p, newv); parent->Dirtify(this); + + pthread_mutex_unlock(&this->logical->ng->mut); } } else if(sinks[p].type == Port::Type::FILE_SAVE) { wxFileDialog dlg(this, wxFileSelectorPromptStr, wxEmptyString, wxEmptyString, wxFileSelectorDefaultWildcardStr, wxFD_SAVE | wxFD_PREVIEW | wxFD_OVERWRITE_PROMPT); if(dlg.ShowModal() == wxID_OK) { + pthread_mutex_lock(&this->logical->ng->mut); + CHiValue newv; newv.type = CUTIHI_VAL_TEXT; newv.data.text = strdup(dlg.GetPath().utf8_str()); CHi_ConfigureSink(this->logical, p, newv); parent->Dirtify(this); + + pthread_mutex_unlock(&this->logical->ng->mut); } } else if(sinks[p].type >= Port::Type::VEC1 && sinks[p].type <= Port::Type::VEC4) { auto ctrls = std::make_shared>(); @@ -494,6 +629,8 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) if(ev.GetKeyCode() == WXK_RETURN) { double d; if(tc->GetValue().ToDouble(&d)) { + pthread_mutex_lock(&this->logical->ng->mut); + CHiValue newv = *CHi_Crawl(&this->logical->sinks[p]); newv.type = CUTIHI_VAL_VEC4; newv.data.vec4[i] = d; @@ -505,6 +642,8 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) CallAfter([tc](){tc->Destroy();}); parent->Dirtify(this); + + pthread_mutex_unlock(&this->logical->ng->mut); } } else if(ev.GetKeyCode() == WXK_TAB) { ctrls->operator[]((i + ctrls->size() + (wxGetKeyState(WXK_SHIFT) ? -1 : 1)) % ctrls->size())->SetFocus(); @@ -520,6 +659,8 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) ctrl->SetValue(wxString{CHi_Crawl(&this->logical->sinks[p])->data.text}); ctrl->SetFocus(); ctrl->Bind(wxEVT_KILL_FOCUS, [=](wxFocusEvent &ev){ + pthread_mutex_lock(&this->logical->ng->mut); + CHiValue newv; newv.type = CUTIHI_VAL_TEXT; char *c = (char*) malloc(ctrl->GetValue().Len() + 1); @@ -528,9 +669,13 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) CHi_ConfigureSink(this->logical, p, newv); CallAfter([ctrl](){ctrl->Destroy();}); parent->Dirtify(this); + + pthread_mutex_unlock(&this->logical->ng->mut); }); ctrl->Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){ if(ev.GetKeyCode() == WXK_RETURN) { + pthread_mutex_lock(&this->logical->ng->mut); + CHiValue newv; newv.type = CUTIHI_VAL_TEXT; char *c = (char*) malloc(ctrl->GetValue().Len() + 1); @@ -539,6 +684,8 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) CHi_ConfigureSink(this->logical, p, newv); CallAfter([ctrl](){ctrl->Destroy();}); parent->Dirtify(this); + + pthread_mutex_unlock(&this->logical->ng->mut); } else ev.Skip(); }); } else if(sinks[p].type == Port::Type::MIC_SOURCE) { @@ -551,11 +698,15 @@ 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) { + pthread_mutex_lock(&this->logical->ng->mut); + CHiValue newv; 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); + + pthread_mutex_unlock(&this->logical->ng->mut); } } else if(sinks[p].type == Port::Type::WINDOW_SOURCE) { std::vector choicesOrig; @@ -570,11 +721,15 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) wxSingleChoiceDialog dlg(this, "", "Choose Source", choices.size(), choices.data(), (void**) nullptr); if(dlg.ShowModal() == wxID_OK) { + pthread_mutex_lock(&this->logical->ng->mut); + CHiValue newv; newv.type = CUTIHI_VAL_TEXT; newv.data.text = strdup(choicesOrig[dlg.GetSelection()]); CHi_ConfigureSink(this->logical, p, newv); parent->Dirtify(this); + + pthread_mutex_unlock(&this->logical->ng->mut); } } } @@ -609,9 +764,13 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) } if(!isSource) { + pthread_mutex_lock(&daNode->ng->mut); + CHiValue val; val.type = CUTIHI_VAL_NONE; CHi_ConfigureSink(daNode, daPortIdx, val); + + pthread_mutex_unlock(&daNode->ng->mut); } parent->Dirtify(this); @@ -619,6 +778,27 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) } } }); + + Bind(wxEVT_MIDDLE_DOWN, [parent, this](wxMouseEvent &ev){ + bool isSource; + int p; + if(MouseOverPort(ev.GetPosition(), isSource, p)) { + if(!isSource && (sinks[p].type == GrNode::Port::Type::VEC1 || sinks[p].type == GrNode::Port::Type::VEC2 || sinks[p].type == GrNode::Port::Type::VEC3 || sinks[p].type == GrNode::Port::Type::VEC4)) { + CaptureMouse(); + parent->dragPos = ClientToScreen(ev.GetPosition()); + sinkValueDragging = p; + + ev.StopPropagation(); + } + } + }); + Bind(wxEVT_MIDDLE_UP, [parent, this](wxMouseEvent &ev){ + if(HasCapture()) { + ReleaseMouse(); + + ev.StopPropagation(); + } + }); } GrNode::~GrNode() { @@ -719,8 +899,14 @@ void ImageViewer::SetImage(CHiImage *chim) { ResizeImage(siez); } void ImageViewer::ResizeImage(float size) { + float w = size, h = (float) bufH / bufW * siez; + + if(w <= 1 || h <= 1) { + return; + } + siez = size; - img.Rescale(siez, (float) bufH / bufW * siez); + img.Rescale(w, h); bm = wxBitmap(img); Refresh(); } @@ -729,41 +915,46 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) { backendNG = CHi_NewNodeGraph(); backendNG->ud = f; - { - GrNode *v = new GrNode(this); - v->logical = CHi_Preview(); - - ShapeGrNode(v); - - CHi_RegisterNode(backendNG, v->logical); - gnodes.push_back(v); - } - Bind(wxEVT_CONTEXT_MENU, [this, f](wxContextMenuEvent &ev){ + wxMenu *menuExports = new wxMenu(); + int idEncodeH264 = menuExports->Append(wxID_ANY, "Encode H264", "")->GetId(); + int idEncodeVp8 = menuExports->Append(wxID_ANY, "Encode VP8", "")->GetId(); + int idEncodeVp9 = menuExports->Append(wxID_ANY, "Encode VP9", "")->GetId(); + int idEncodeOpus = menuExports->Append(wxID_ANY, "Encode Opus", "")->GetId(); + int idEncodeAAC = menuExports->Append(wxID_ANY, "Encode AAC-LC", "")->GetId(); + int idMuxWebm = menuExports->Append(wxID_ANY, "Mux WebM", "")->GetId(); + int idMuxWav = menuExports->Append(wxID_ANY, "Muv Wav", "")->GetId(); + int idStreamRTMP = menuExports->Append(wxID_ANY, "Stream RTMP", "")->GetId(); + + wxMenu *menuRealtime = new wxMenu(); + int idMicrophone = menuRealtime->Append(wxID_ANY, "Microphone", "")->GetId(); + int idCamera = menuRealtime->Append(wxID_ANY, "Live Digital Camera", "")->GetId(); + int idKeyhook = menuRealtime->Append(wxID_ANY, "Keyhook", "")->GetId(); + int idWindow = menuRealtime->Append(wxID_ANY, "Window", "")->GetId(); + wxMenu menu; + menu.Append(wxID_ANY, "Exports", menuExports); + menu.Append(wxID_ANY, "Realtime", menuRealtime); + + menu.AppendSeparator(); + int idConstant = menu.Append(wxID_ANY, "Constant", "")->GetId(); int idImage = menu.Append(wxID_ANY, "Image", "")->GetId(); int idMovie = menu.Append(wxID_ANY, "Movie", "")->GetId(); - int idWindow = menu.Append(wxID_ANY, "Window", "")->GetId(); int idText = menu.Append(wxID_ANY, "Text", "")->GetId(); - int idMicrophone = menu.Append(wxID_ANY, "Microphone", "")->GetId(); - int idMixer = menu.Append(wxID_ANY, "Mixer", "")->GetId(); - int idCamera = menu.Append(wxID_ANY, "Live Digital Camera", "")->GetId(); + int idMixer = menu.Append(wxID_ANY, "Audio Mixer", "")->GetId(); int idTime = menu.Append(wxID_ANY, "Time", "")->GetId(); - int idEmbed = menu.Append(wxID_ANY, "Embed", "")->GetId(); + int idEmbed = menu.Append(wxID_ANY, "Frame", "")->GetId(); int idComponentScale = menu.Append(wxID_ANY, "Scale", "")->GetId(); int idModulate = menu.Append(wxID_ANY, "Modulate", "")->GetId(); - int idKeyhook = menu.Append(wxID_ANY, "Keyhook (Live)", "")->GetId(); - int idEncodeVp8 = menu.Append(wxID_ANY, "Encode VP8", "")->GetId(); - int idEncodeVp9 = menu.Append(wxID_ANY, "Encode VP9", "")->GetId(); - int idEncodeOpus = menu.Append(wxID_ANY, "Encode Opus", "")->GetId(); - int idMuxWebm = menu.Append(wxID_ANY, "Mux WebM", "")->GetId(); - int idMuxWav = menu.Append(wxID_ANY, "Muv Wav", "")->GetId(); + int idChromaKey = menu.Append(wxID_ANY, "Chroma Key", "")->GetId(); wxPoint position = ScreenToClient(wxGetMousePosition()); - menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){ + auto fn = [=](wxCommandEvent &ev) { std::function after = [](){}; + pthread_mutex_lock(&backendNG->mut); + GrNode *noed = nullptr; if(ev.GetId() == idConstant) { noed = new GrNode(this); @@ -791,6 +982,9 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) { float params[4] = {1}; CHi_SetExtrapolationMode(backendNG, noed->logical, portIdx, CUTIHI_EXTRAPOLATION_CONSTANT, params); }; + } else if(ev.GetId() == idEncodeH264) { + noed = new GrNode(this); + noed->logical = CHi_EncodeH264(); } else if(ev.GetId() == idEncodeVp9) { noed = new GrNode(this); noed->logical = CHi_EncodeVP9(); @@ -830,19 +1024,36 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) { } else if(ev.GetId() == idKeyhook) { noed = new GrNode(this); noed->logical = CHi_Keyhook(); + } else if(ev.GetId() == idChromaKey) { + noed = new GrNode(this); + noed->logical = CHi_ChromaKey(); + } else if(ev.GetId() == idStreamRTMP) { + noed = new GrNode(this); + noed->logical = CHi_StreamRTMP(); + } else if(ev.GetId() == idEncodeAAC) { + noed = new GrNode(this); + noed->logical = CHi_EncodeAAC(); } if(noed) { ShapeGrNode(noed); - noed->Fit(); noed->SetPosition(position); CHi_RegisterNode(backendNG, noed->logical); gnodes.push_back(noed); after(); + + ((Frame*) GetParent())->timeline->ResetRows(); } - }); + + pthread_mutex_unlock(&backendNG->mut); + }; + + menuExports->Bind(wxEVT_MENU, fn); + menuRealtime->Bind(wxEVT_MENU, fn); + menu.Bind(wxEVT_MENU, fn); + PopupMenu(&menu); }); Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &ev){ @@ -897,12 +1108,18 @@ NodeGraph::~NodeGraph() { } void NodeGraph::Alinken(Link l) { + pthread_mutex_lock(&backendNG->mut); + CHiValue newv; newv.type = CUTIHI_VAL_LINKED; newv.data.linked.to = l.output->logical; newv.data.linked.idx = l.o; - if(!CHi_ConfigureSink(l.input->logical, l.i, newv)) { + int err = CHi_ConfigureSink(l.input->logical, l.i, newv); + + pthread_mutex_unlock(&backendNG->mut); + + if(!err) { ((Frame*) GetParent())->stba->SetStatusText("Uh oh! Hur-hur, there's a WAACKY cycle! Can't do, sorry friend."); return; } @@ -914,6 +1131,8 @@ void NodeGraph::Alinken(Link l) { } } + ShapeGrNode(l.input); + links.push_back(l); Dirtify(l.input); @@ -921,7 +1140,7 @@ void NodeGraph::Alinken(Link l) { } void NodeGraph::Dirtify(GrNode *g) { - g->logical->clean = 0; + /*g->logical->clean = 0; for(auto &it : links) { if(it.output == g) { Dirtify(it.input); @@ -936,7 +1155,9 @@ void NodeGraph::Dirtify(GrNode *g) { ((Frame*) GetParent())->viewer->SetImage(val->data.sample); } } - } + }*/ + + CHi_Hysteresis(gnodes[0]->logical); } bool operator==(const NodeGraph::Link &l, const NodeGraph::Link &r) { @@ -961,3 +1182,32 @@ bool NodeGraph::DetectCycles(GrNode *root) { std::set p{}; return dfs(this, p, root); } + +void NodeGraph::CreatePreviewNode() { + GrNode *v = new GrNode(this); + v->logical = CHi_Preview(); + ShapeGrNode(v); + + CHi_RegisterNode(backendNG, v->logical); + gnodes.push_back(v); + + v->SetPosition(wxPoint{GetSize().x - v->GetSize().x - 8, 8}); + + // The Preview node is actually a NOOP + // Must inject our own function in Perform + v->logical->Perform = +[](CHiPubNode *preview){ + CHiValue *val = CHi_Crawl(&preview->sinks[0]); + + if(val->type == CUTIHI_VAL_SAMPLE && val->data.sample) { + globaldis->viewer->SetImage(val->data.sample); + } + + return 1; + }; +} + +GrNode *NodeGraph::get_graphical(CHiPubNode *n) { + return *std::find_if(gnodes.begin(), gnodes.end(), [=](GrNode *gn){ + return gn->logical == n; + }); +} diff --git a/ui/frame.h b/ui/frame.h index f18aca9..edbdba0 100644 --- a/ui/frame.h +++ b/ui/frame.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -21,6 +22,8 @@ struct ImageViewer; struct Timeline; struct ctTimeCtrl; +std::string node_name_from_id(uint64_t id); + struct Frame : wxFrame { wxAuiManager aui; @@ -48,6 +51,14 @@ struct GrNode : wxPanel { enum class Type { NONE, FILE_OPEN, COLOR, VEC1, VEC2, VEC3, VEC4, TEXT, SAMPLE, FILE_SAVE, MIC_SOURCE, WINDOW_SOURCE } type; + bool separator; + + float dragScaling[2]; + + Port(wxString name); + Port(wxString name, Type type); + Port(wxString name, Type type, bool separator); + Port(wxString name, Type type, bool separator, float dragScalingX, float dragScalingY); }; std::vector sinks; std::vector sources; @@ -56,6 +67,8 @@ struct GrNode : wxPanel { CHiPubNode *logical; + int sinkValueDragging; + GrNode(NodeGraph*); virtual ~GrNode(); @@ -120,6 +133,10 @@ struct NodeGraph : wxPanel { void Dirtify(GrNode *g); bool DetectCycles(GrNode*); + + void CreatePreviewNode(); + + GrNode *get_graphical(CHiPubNode*); }; diff --git a/ui/timeline.cpp b/ui/timeline.cpp index f5deb60..2844721 100644 --- a/ui/timeline.cpp +++ b/ui/timeline.cpp @@ -20,30 +20,113 @@ static T mod(T a, T b) { #define ZERO_TIME_BASE 128 -bool Timeline::MouseOverKF(wxPoint p, size_t &kfsIdxRet, size_t &kfIdxRet) { - auto f = (Frame*) GetParent(); +Timeline::Row *Timeline::GetRow(int y) { + y = y / bmpKf.GetHeight(); - int kfsIdx = p.y / bmpKf.GetHeight() - 1; + if(y == 0 || y > rows.size()) { + return nullptr; + } - if(kfsIdx < 0 || kfsIdx >= f->graph->backendNG->keyframesList.count) { + y--; + + return &rows.at(y); +} +bool Timeline::MouseOverKF(wxPoint p, CHiKeyframes* &kfs, size_t &kfIdxRet) { + Timeline::Row *row = GetRow(p.y); + + if(!row || row->type != Timeline::Row::KEYFRAMES) { return false; } + auto f = (Frame*) GetParent(); + + kfs = row->kfs; + float t = (p.x + camX - ZERO_TIME_BASE) / (float) scale; float threshold = bmpKf.GetWidth() / (float) scale / 2; - size_t idx = CHi_GetClosestKeyframe(f->graph->backendNG, kfsIdx, t); + size_t idx = CHi_GetClosestKeyframe(f->graph->backendNG, kfs, t); - if(fabs(f->graph->backendNG->keyframesList.keyframes[kfsIdx]->times[idx] - t) > threshold) { + if(fabs(kfs->times[idx] - t) > threshold) { return false; } - kfsIdxRet = kfsIdx; kfIdxRet = idx; return true; } +void Timeline::ResetRows() { + rows.clear(); + + auto frame = (Frame*) GetParent(); + + auto kfsList = &frame->graph->backendNG->keyframesList; + + int y = bmpKf.GetHeight(); + + for(size_t kfsIdx = 0; kfsIdx < kfsList->count; kfsIdx++) { + rows.emplace_back(); + + Timeline::Row &row = rows.back(); + + row.type = Timeline::Row::KEYFRAMES; + row.kfs = kfsList->keyframes[kfsIdx]; + + row.y = y; + row.h = bmpKf.GetHeight(); + + y += row.h; + } + + for(GrNode *gn : frame->graph->gnodes) { + rows.emplace_back(); + + Timeline::Row &row = rows.back(); + + row.type = Timeline::Row::NODE_LIFESPAN; + row.gn = gn; + + row.y = y; + row.h = bmpKf.GetHeight(); + + y += row.h; + } +} + +float Timeline::SnapTime(float t) { + float minDist = FLT_MAX; + float newT = t; + + auto f = (Frame*) GetParent(); + + std::vector times; + for(Timeline::Row &row : rows) { + if(row.type == Timeline::Row::NODE_LIFESPAN) { + times.push_back(row.gn->logical->lifespan.start); + times.push_back(row.gn->logical->lifespan.end); + } else if(row.type == Timeline::Row::KEYFRAMES) { + for(size_t kfIdx = 0; kfIdx < row.kfs->count; kfIdx++) { + times.push_back(row.kfs->times[kfIdx]); + } + } + } + + if(times.size() == 0) { + return t; + } + + std::sort(times.begin(), times.end()); + + auto it = std::lower_bound(times.begin(), times.end(), t); + + if(it == times.end() || (it != times.begin() && (*it - t) >= (t - *std::prev(it)))) { + it = std::prev(it); + } + + return *it; +} + Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) { bmpKf = wxBitmap{"keyframe.bmp", wxBITMAP_TYPE_ANY}; bmpKfExtrap = wxBitmap{"keyframe_extrap.bmp", wxBITMAP_TYPE_ANY}; @@ -53,7 +136,9 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) { Bind(wxEVT_MIDDLE_DOWN, [=](wxMouseEvent &ev){ captureMode = Timeline::CaptureMode::CAM; CaptureMouse(); + mouseX = ev.GetX(); + mouseY = ev.GetY(); }); Bind(wxEVT_MIDDLE_UP, [=](wxMouseEvent &ev){ if(HasCapture() && captureMode == Timeline::CaptureMode::CAM) { @@ -62,42 +147,39 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) { }); Bind(wxEVT_LEFT_DOWN, [=](wxMouseEvent &ev){ + Timeline::Row *row = GetRow(ev.GetPosition().y); + auto f = (Frame*) GetParent(); - size_t kfsIdx, kfIdx; - if(MouseOverKF(ev.GetPosition(), kfsIdx, kfIdx)) { - captureMode = Timeline::CaptureMode::KF; - CaptureMouse(); - mouseX = ev.GetX(); - - this->captureKfsIdx = kfsIdx; - this->captureKfIdx = kfIdx; - } else { - float t = (ev.GetX() + camX - ZERO_TIME_BASE) / (float) scale; - + float t = (ev.GetX() + camX - ZERO_TIME_BASE) / (float) scale; + if(ev.ControlDown()) { + t = SnapTime(t); + } + + if(!row) { // Snap to closest keyframe, in all keyframes - if(ev.ControlDown()) { - float minDist = FLT_MAX; - float newT = t; - - for(size_t kfsIdx = 0; kfsIdx < f->graph->backendNG->keyframesList.count; kfsIdx++) { - size_t kfIdx = CHi_GetClosestKeyframe(f->graph->backendNG, kfsIdx, t); - - float dist = fabs(f->graph->backendNG->keyframesList.keyframes[kfsIdx]->times[kfIdx] - t); - - if(dist < minDist) { - minDist = dist; - newT = f->graph->backendNG->keyframesList.keyframes[kfsIdx]->times[kfIdx]; - } - } - - t = newT; - } CHi_Time_Set(f->graph->backendNG, t < 0 ? 0 : t); Refresh(); f->graph->Dirtify(f->graph->gnodes[0]); + } else if(row->type == Timeline::Row::KEYFRAMES) { + CHiKeyframes *kfs; + size_t kfIdx; + if(MouseOverKF(ev.GetPosition(), kfs, kfIdx)) { + captureMode = Timeline::CaptureMode::KF; + CaptureMouse(); + + mouseX = ev.GetX(); + mouseY = ev.GetY(); + + this->captureKfs = kfs; + this->captureKfIdx = kfIdx; + } + } else if(row->type == Timeline::Row::NODE_LIFESPAN) { + row->gn->logical->lifespan.start = t; + + Refresh(); } }); Bind(wxEVT_LEFT_UP, [=](wxMouseEvent &ev){ @@ -105,6 +187,20 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) { ReleaseMouse(); } }); + Bind(wxEVT_RIGHT_DOWN, [=](wxMouseEvent &ev){ + Timeline::Row *row = GetRow(ev.GetPosition().y); + + if(row && row->type == Timeline::Row::NODE_LIFESPAN) { + float t = (ev.GetX() + camX - ZERO_TIME_BASE) / (float) scale; + if(ev.ControlDown()) { + t = SnapTime(t); + } + + row->gn->logical->lifespan.end = t; + + Refresh(); + } + }); Bind(wxEVT_MOTION, [=](wxMouseEvent &ev){ auto f = (Frame*) GetParent(); @@ -112,9 +208,14 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) { if(HasCapture()) { if(captureMode == Timeline::CaptureMode::CAM) { camX += mouseX - ev.GetX(); + camY += mouseY - ev.GetY(); + if(camX < 0) { camX = 0; } + if(camY < 0) { + camY = 0; + } Refresh(); } else if(captureMode == Timeline::CaptureMode::KF) { @@ -122,7 +223,7 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) { float timeDiff = (float) diff / this->scale; - captureKfIdx = CHi_MoveKeyframeBy(f->graph->backendNG, f->graph->backendNG->keyframesList.keyframes[captureKfsIdx], captureKfIdx, timeDiff); + captureKfIdx = CHi_MoveKeyframeBy(f->graph->backendNG, captureKfs, captureKfIdx, timeDiff); Refresh(); @@ -130,13 +231,13 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) { } mouseX = ev.GetX(); + mouseY = ev.GetY(); } else { // This is really baad.. - size_t kfsIdx, kfIdx; - if(GetToolTipText() == "" && MouseOverKF(ScreenToClient(wxGetMousePosition()), kfsIdx, kfIdx)) { - CHiKeyframes *kfs = f->graph->backendNG->keyframesList.keyframes[kfsIdx]; - + CHiKeyframes *kfs; + size_t kfIdx; + if(GetToolTipText() == "" && MouseOverKF(ScreenToClient(wxGetMousePosition()), kfs, kfIdx)) { CHiPubNode *node = kfs->node; auto it = std::find_if(f->graph->gnodes.begin(), f->graph->gnodes.end(), [=](GrNode *g){ @@ -209,8 +310,9 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) { Bind(wxEVT_CONTEXT_MENU, [=](wxContextMenuEvent &ev){ wxPoint position = ScreenToClient(wxGetMousePosition()); - size_t kfsIdx, kfIdx; - if(MouseOverKF(ScreenToClient(wxGetMousePosition()), kfsIdx, kfIdx)) { + CHiKeyframes *kfs; + size_t kfIdx; + if(MouseOverKF(ScreenToClient(wxGetMousePosition()), kfs, kfIdx)) { wxMenu menu; int idDel = menu.Append(wxID_ANY, "Delete")->GetId(); @@ -218,7 +320,6 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) { menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){ if(ev.GetId() == idDel) { auto f = (Frame*) GetParent(); - auto kfs = f->graph->backendNG->keyframesList.keyframes[kfsIdx]; CHi_DeleteKeyframe(f->graph->backendNG, kfs, kfIdx); @@ -242,31 +343,83 @@ void Timeline::Paint(wxPaintEvent &ev) { dc.SetPen(wxPen{wxColour{160, 60, 60}}); + wxColour defaultTextColor = dc.GetTextForeground(); + + dc.SetTextForeground(wxColour{160, 60, 60}); + { - int x = CHi_Time_Get(frame->graph->backendNG) * scale - camX + ZERO_TIME_BASE; - dc.DrawLine(x, 0, x, GetSize().y); + float curTime = CHi_Time_Get(frame->graph->backendNG); + + int x = curTime * scale - camX + ZERO_TIME_BASE; + + if(x >= ZERO_TIME_BASE) { + dc.DrawLine(x, 0, x, GetSize().y); + } + + unsigned int H = (unsigned int) std::floor(curTime / 3600); + unsigned int M = (unsigned int) std::floor(curTime / 60) % 60; + float S = ::fmodf(curTime, 60); + + wxString s; + if(H == 0 && M == 0) { + s = wxString::Format("%.03f", S); + } else if(H == 0) { + s = wxString::Format("%02u:%.03f", M, S); + } else { + s = wxString::Format("%02u:%02u:%.03f", M, S); + } + + wxCoord w, h; + dc.GetTextExtent(s, &w, &h); + + dc.DrawText(s, x + 2, GetSize().y - h); } + dc.SetTextForeground(defaultTextColor); + dc.SetPen(wxPen{wxSystemSettings::GetColour(wxSYS_COLOUR_INACTIVECAPTIONTEXT)}); float t = std::ceil((float) camX / scale); for(int64_t x = ZERO_TIME_BASE + mod(-camX, scale); x < GetSize().x; x += scale) { - dc.DrawLine(x, 0, x, 10); - dc.DrawText(wxString::Format("%gs", t), x + 4, 0); + dc.DrawLine(x, 0 - camY, x, 10 - camY); + dc.DrawText(wxString::Format("%gs", t), x + 4, 0 - camY); t++; } - auto kfsList = &frame->graph->backendNG->keyframesList; - - for(size_t kfsIdx = 0; kfsIdx < kfsList->count; kfsIdx++) { + for(Timeline::Row &row : rows) { - CHiKeyframes *kfs = kfsList->keyframes[kfsIdx]; - - for(size_t kfIdx = 0; kfIdx < kfs->count; kfIdx++) { - wxBitmap &bmp = kfIdx == kfs->count - 1 && kfs->extrapolationMode != CUTIHI_EXTRAPOLATION_NONE ? bmpKfExtrap : bmpKf; + if(row.type == Timeline::Row::KEYFRAMES) { + CHiKeyframes *kfs = row.kfs; - dc.DrawBitmap(bmp, ZERO_TIME_BASE - camX + scale * kfs->times[kfIdx] - bmpKf.GetWidth() / 2, bmpKf.GetHeight() * (kfsIdx + 1)); + for(size_t kfIdx = 0; kfIdx < kfs->count; kfIdx++) { + wxBitmap &bmp = kfIdx == kfs->count - 1 && kfs->extrapolationMode != CUTIHI_EXTRAPOLATION_NONE ? bmpKfExtrap : bmpKf; + + dc.DrawBitmap(bmp, ZERO_TIME_BASE - camX + scale * kfs->times[kfIdx] - bmpKf.GetWidth() / 2, row.y - camY); + } + } else if(row.type == Timeline::Row::NODE_LIFESPAN) { + GrNode *gn = row.gn; + + float start = gn->logical->lifespan.start; + float end = gn->logical->lifespan.end; + + if(end == 0) { + end = gn->logical->ng->duration; + } + + start *= scale; + end *= scale; + + int32_t x1 = ZERO_TIME_BASE + std::max(0, start - camX); + int32_t x2 = ZERO_TIME_BASE + std::max(0, end - camX); + + int32_t y = row.y - camY; + int32_t h = row.h; + + dc.SetBrush(*wxLIGHT_GREY_BRUSH); + dc.DrawRectangle(x1, y, x2 - x1, h); + + dc.DrawText(node_name_from_id(gn->logical->type), 0, y); } } diff --git a/ui/timeline.h b/ui/timeline.h index 9553111..bc6c096 100644 --- a/ui/timeline.h +++ b/ui/timeline.h @@ -5,13 +5,36 @@ #include struct Frame; +struct GrNode; +struct CHiKeyframes; struct Timeline : wxPanel { int64_t camX = 0; + int64_t camY = 0; int64_t mouseX = 0; + int64_t mouseY = 0; - size_t captureKfsIdx, captureKfIdx; + CHiKeyframes *captureKfs; + size_t captureKfIdx; + + struct Row { + enum Type { + KEYFRAMES, + NODE_LIFESPAN + }; + + int y, h; + + Type type; + + union { + CHiKeyframes *kfs; + GrNode *gn; + }; + }; + + std::vector rows; enum class CaptureMode { CAM, KF @@ -24,7 +47,11 @@ struct Timeline : wxPanel { void Paint(wxPaintEvent&); - bool MouseOverKF(wxPoint p, size_t &kfsIdx, size_t &kfIdx); + Timeline::Row *GetRow(int y); + bool MouseOverKF(wxPoint p, CHiKeyframes* &kfs, size_t &kfIdx); + float SnapTime(float t); + + void ResetRows(); }; #endif