Support Twitch streaming, chroma key, errors, fixed modulation, node lifespan, fix bugs, many optimizations

This commit is contained in:
mid 2025-03-09 10:25:39 +02:00
parent 8f053bbdb1
commit 6fc29ba5f8
17 changed files with 1307 additions and 678 deletions

View File

@ -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 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) ifneq ($(RELEASE),0)
CXXFLAGS := $(CXXFLAGS) -O0 -g3 -fsanitize=address -DMTR_ENABLED CXXFLAGS := $(CXXFLAGS) -O0 -gdwarf-2 -DMTR_ENABLED
else else
CXXFLAGS := $(CXXFLAGS) -O3 -fopenmp -DMTR_ENABLED CXXFLAGS := $(CXXFLAGS) -O3 -fopenmp -DMTR_ENABLED
endif endif
all: all:
gcc $(CXXFLAGS) -std=c99 -shared -c -o node.o hi/node.c $(LDFLAGS) $(CC) $(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) $(CC) $(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) $(CC) $(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) $(CC) $(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) $(CC) $(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) $(CXX) $(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) $(CXX) $(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) $(CC) $(CXXFLAGS) -std=c++11 -shared -c -o vpxenc.o hi/vpxenc.c $(LDFLAGS)
gcc $(CXXFLAGS) -std=c99 -shared -c -o webcam.o hi/webcam.c $(LDFLAGS) $(CC) $(CXXFLAGS) -std=c99 -shared -c -o opus.o hi/opus.c $(LDFLAGS)
gcc $(CXXFLAGS) -std=c99 -shared -c -o scale.o hi/relay.c $(LDFLAGS) $(CC) $(CXXFLAGS) -std=c99 -shared -c -o webcam.o hi/webcam.c $(LDFLAGS)
gcc $(CXXFLAGS) -std=c99 -shared -c -o minitrace.o hi/minitrace.c $(LDFLAGS) $(CC) $(CXXFLAGS) -std=c99 -shared -c -o scale.o hi/relay.c $(LDFLAGS)
gcc $(CXXFLAGS) -shared -o libcutihi.so -shared node.o webmdec.o webmenc.o window.o microphone.o mode.o img.o opus.o webcam.o scale.o minitrace.o $(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`

View File

@ -6,6 +6,7 @@ extern "C" {
#endif #endif
#define CUTIHI_BS_FLAG_KEY 1 #define CUTIHI_BS_FLAG_KEY 1
#define CUTIHI_BS_SETUP_PACKET 2
typedef struct { typedef struct {
uint64_t timestamp; uint64_t timestamp;

View File

@ -15,6 +15,7 @@ typedef struct CHiImage {
uint16_t height; uint16_t height;
union { union {
uint16_t *data16; uint16_t *data16;
uint8_t *data8;
}; };
uint8_t owned; uint8_t owned;
} CHiImage; } CHiImage;

View File

@ -83,6 +83,10 @@ static inline __m128 log2f4(__m128 x)
return _mm_add_ps(p, e); 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) { __attribute__((optimize("O3"))) static inline __m128i apply_gamma_epi32(__m128i z, __m128 gamma) {
__m128 zf = _mm_cvtepi32_ps(z); __m128 zf = _mm_cvtepi32_ps(z);
zf = _mm_mul_ps(zf, _mm_set1_ps(1.f / 65535)); zf = _mm_mul_ps(zf, _mm_set1_ps(1.f / 65535));

View File

@ -40,8 +40,8 @@ static int pacallback(const void *input_, void *output, unsigned long samples, c
/*static size_t total = 0; /*static size_t total = 0;
for(size_t i = 0; i < pabufsize; i++) { for(size_t i = 0; i < pabufsize; i++) {
paBuffer[paBufferWriteIdx] = sin(total++ * 440.0 / 24000 * 3.141592653) * 0.1; node->paBuffer[node->paBufferWriteIdx] = sin(total++ * 440.0 / 24000 * 3.141592653) * 0.3;
paBufferWriteIdx = (paBufferWriteIdx + 1) % pabufsize; node->paBufferWriteIdx = (node->paBufferWriteIdx + 1) % pabufsize;
}*/ }*/
return paContinue; return paContinue;
@ -111,8 +111,6 @@ static int microphone_perform(CHiPubNode *pubn) {
pubn->sources[0].type = CUTIHI_VAL_SAMPLE; pubn->sources[0].type = CUTIHI_VAL_SAMPLE;
pubn->sources[0].data.sample = ret; pubn->sources[0].data.sample = ret;
pubn->clean = 0;
MTR_END("CHi", "microphone_perform"); MTR_END("CHi", "microphone_perform");
return 1; return 1;
@ -130,7 +128,6 @@ CUTIVIS CHiPubNode *CHi_Microphone() {
n->Start = microphone_start; n->Start = microphone_start;
n->Perform = microphone_perform; n->Perform = microphone_perform;
n->Stop = microphone_stop; n->Stop = microphone_stop;
n->clean = 0;
n->sinkCount = 1; n->sinkCount = 1;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount); n->sinks = calloc(sizeof(*n->sinks), n->sinkCount);
n->sourceCount = 1; n->sourceCount = 1;
@ -180,7 +177,6 @@ static int exportwav_perform(CHiPubNode *pubn) {
fwrite(buf->data16, 2, buf->width, n->output); fwrite(buf->data16, 2, buf->width, n->output);
pubn->clean = 0;
return 1; return 1;
} }
CUTIVIS int CHi_ExportWav_Stop(CHiPubNode *pubn) { CUTIVIS int CHi_ExportWav_Stop(CHiPubNode *pubn) {
@ -204,7 +200,6 @@ CUTIVIS CHiPubNode *CHi_ExportWav() {
n->pubn.Start = CHi_ExportWav_Start; n->pubn.Start = CHi_ExportWav_Start;
n->pubn.Perform = exportwav_perform; n->pubn.Perform = exportwav_perform;
n->pubn.Stop = CHi_ExportWav_Stop; n->pubn.Stop = CHi_ExportWav_Stop;
n->pubn.clean = 0;
n->pubn.sinkCount = 2; n->pubn.sinkCount = 2;
n->pubn.sinks = calloc(sizeof(*n->pubn.sinks), n->pubn.sinkCount); n->pubn.sinks = calloc(sizeof(*n->pubn.sinks), n->pubn.sinkCount);
n->pubn.sourceCount = 0; n->pubn.sourceCount = 0;

680
hi/node.c
View File

@ -142,10 +142,17 @@ CUTIVIS void CHi_RegisterNode(CHiNodeGraph* ng, CHiPubNode* n) {
ng->nodes[ng->count++] = n; ng->nodes[ng->count++] = n;
n->ng = ng; n->ng = ng;
if(ng->compilationStatus == CUTIHI_COMP_RUNNING) {
n->Start(n);
}
} }
CUTIVIS void CHi_MakeDirty(CHiNodeGraph *ng, CHiPubNode *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; return 1;
} }
CUTIVIS int CHi_ConfigureSink(CHiPubNode *n, size_t i, CHiValue v) { 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) { if(n->sinks[i].type == CUTIHI_VAL_KEYED) {
n->sinks[i].data.keyed->current = v; n->sinks[i].data.keyed->current = v;
return 1; return 1;
@ -205,6 +217,7 @@ CUTIVIS int CHi_ConfigureSink(CHiPubNode *n, size_t i, CHiValue v) {
adjacency_remove(old.data.linked.to, n); adjacency_remove(old.data.linked.to, n);
} }
// Check if viable
n->sinks[i] = v; n->sinks[i] = v;
if(n->ng && !topological_sort(n->ng)) { if(n->ng && !topological_sort(n->ng)) {
n->sinks[i] = old; 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); adjacency_add(v.data.linked.to, n);
} }
CHi_MakeDirty(n->ng, n);
return 1; return 1;
} }
@ -299,9 +314,7 @@ CUTIVIS void CHi_DeleteKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, size_t idx)
kfs->count--; kfs->count--;
} }
CUTIVIS size_t CHi_GetClosestKeyframe(CHiNodeGraph *ng, size_t kfsIdx, float t) { CUTIVIS size_t CHi_GetClosestKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, float t) {
CHiKeyframes *kfs = ng->keyframesList.keyframes[kfsIdx];
if(kfs->count == 1) { if(kfs->count == 1) {
return 0; return 0;
} }
@ -347,11 +360,55 @@ CUTIVIS int CHi_Hysteresis(CHiPubNode *root) {
} }
} }
root->Perform(root); //if(!root->clean) {
root->Perform(root);
//}
return 1; 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) { bool timespec_less(const struct timespec l, const struct timespec r) {
if(l.tv_sec == r.tv_sec) { if(l.tv_sec == r.tv_sec) {
return l.tv_nsec < r.tv_nsec; return l.tv_nsec < r.tv_nsec;
@ -401,11 +458,35 @@ void *compile_thread(void *ctx_) {
ctx->ng->time = ctx->ng->timedelta = 0; ctx->ng->time = ctx->ng->timedelta = 0;
puts("START"); 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]); ssize_t nIdx;
} else { for(nIdx = 0; nIdx < ctx->ng->count; nIdx++) {
ctx->ng->nodes[nIdx]->Perform(ctx->ng->nodes[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))); CHi_Time_Set(ctx->ng, timespecToFloat(timespec_sub(now, start)));
for(size_t nIdx = 0; nIdx < ctx->ng->count; nIdx++) { perform_step(ctx->ng);
ctx->ng->nodes[nIdx]->Perform(ctx->ng->nodes[nIdx]);
}
if(ctx->ng->eventOnFrameComplete) {
ctx->ng->eventOnFrameComplete(ctx->ng);
}
do { do {
clock_gettime(CLOCK_MONOTONIC, &now); 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);) { 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); CHi_Time_Set(ctx->ng, frm / 30.f);
for(size_t nIdx = 0; nIdx < ctx->ng->count; nIdx++) { perform_step(ctx->ng);
ctx->ng->nodes[nIdx]->Perform(ctx->ng->nodes[nIdx]);
}
struct timespec last; struct timespec last;
clock_gettime(CLOCK_MONOTONIC, &last); clock_gettime(CLOCK_MONOTONIC, &last);
@ -454,10 +527,6 @@ void *compile_thread(void *ctx_) {
clock_gettime(CLOCK_MONOTONIC, &now); clock_gettime(CLOCK_MONOTONIC, &now);
diff += timespec_sub(now, last).tv_nsec; diff += timespec_sub(now, last).tv_nsec;
if(ctx->ng->eventOnFrameComplete) {
ctx->ng->eventOnFrameComplete(ctx->ng);
}
frm++; frm++;
} }
} }
@ -473,6 +542,7 @@ void *compile_thread(void *ctx_) {
ctx->ng->eventOnStopComplete(ctx->ng); ctx->ng->eventOnStopComplete(ctx->ng);
} }
stop:
ctx->ng->compilationStatus = CUTIHI_COMP_READY; ctx->ng->compilationStatus = CUTIHI_COMP_READY;
free(ctx); 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) { static int image_perform(CHiPubNode *node) {
if(node->clean) return 1; ImageNode *internal = (ImageNode*) node;
node->sources->type = CUTIHI_VAL_SAMPLE; node->sources->type = CUTIHI_VAL_SAMPLE;
if(node->sources->data.sample) CHi_Image_Free(node->sources->data.sample); 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;
}
struct sail_image *simg; if(node->sinks[CUTIHI_IMAGE_IN_FILE].type != CUTIHI_VAL_TEXT) {
SAIL_TRY(sail_load_from_file(node->sinks[0].data.text, &simg)); 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;
}
struct sail_image *cimg; if(internal->cacheImg) {
sail_convert_image(simg, SAIL_PIXEL_FORMAT_BPP64_BGRA, &cimg); CHi_Image_Free(internal->cacheImg);
internal->cacheImg = NULL;
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);
} }
} }
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; return 1;
err:
node->sources->data.sample = NULL;
return 0;
} }
CUTIVIS CHiPubNode *CHi_Image() { CUTIVIS CHiPubNode *CHi_Image() {
CHiPubNode *n = calloc(1, sizeof(*n)); CHiPubNode *n = calloc(1, sizeof(ImageNode));
n->type = CUTIHI_T('CIma','ge '); n->type = CUTIHI_T('CIma','ge ');
n->Start = n->Stop = NULL; n->Start = n->Stop = NULL;
n->Perform = image_perform; n->Perform = image_perform;
n->clean = 0;
n->sinkCount = 1; n->sinkCount = 1;
n->sinks = calloc(sizeof(*n->sinks), 1); n->sinks = calloc(sizeof(*n->sinks), 1);
n->sourceCount = 1; n->sourceCount = 1;
n->sources = calloc(sizeof(*n->sources), 1); n->sources = calloc(sizeof(*n->sources), 1);
((ImageNode*) n)->cachePath = strdup("");
return n; return n;
} }
static int embed_perform(CHiPubNode *node) { static int embed_perform(CHiPubNode *node) {
if(node->clean) return 1;
MTR_BEGIN("CHi", "embed_perform"); MTR_BEGIN("CHi", "embed_perform");
node->sources[0].type = CUTIHI_VAL_SAMPLE; 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); 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); 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; CHiImage *sub = CHi_Crawl(&node->sinks[1 + sid * 3])->data.sample;
if(!sub) continue; if(!sub) continue;
@ -590,7 +690,6 @@ static int embed_perform(CHiPubNode *node) {
MTR_END("CHi", "embed_perform"); MTR_END("CHi", "embed_perform");
node->clean = 0;
return 1; return 1;
} }
CUTIVIS CHiPubNode *CHi_Embed() { CUTIVIS CHiPubNode *CHi_Embed() {
@ -598,37 +697,32 @@ CUTIVIS CHiPubNode *CHi_Embed() {
n->type = CUTIHI_T('CEmb','ed '); n->type = CUTIHI_T('CEmb','ed ');
n->Start = n->Stop = NULL; n->Start = n->Stop = NULL;
n->Perform = embed_perform; n->Perform = embed_perform;
n->clean = 0;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 1 + 3 * CUTIHI_EMBED_MAX_SMALLS); 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); n->sources = calloc(sizeof(*n->sources), n->sourceCount = 1);
return n; return n;
} }
static int constantsample_perform(CHiPubNode *node) { static int constantsample_perform(CHiPubNode *node) {
if(node->clean) return 1;
node->sources[0].type = CUTIHI_VAL_SAMPLE; node->sources[0].type = CUTIHI_VAL_SAMPLE;
if(node->sources->data.sample) CHi_Image_Free(node->sources->data.sample); if(node->sources->data.sample) CHi_Image_Free(node->sources->data.sample);
CHiValue *sink = CHi_Crawl(&node->sinks[0]); 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); size_t w = sz->data.vec4[0] < 1 ? 1 : sz->data.vec4[0];
for(int i = 0; i < 256; i++) { size_t h = sz->data.vec4[1] < 1 ? 1 : sz->data.vec4[1];
img->data16[i * 4 + 0] = sink->data.vec4[2] * 65535;
img->data16[i * 4 + 1] = sink->data.vec4[1] * 65535; CHiImage *img = CHi_Image_New(2, 4, 8 * w, w, h, NULL);
img->data16[i * 4 + 2] = sink->data.vec4[0] * 65535; if(CHi_Node_Active(node)) {
img->data16[i * 4 + 3] = 65535; 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->sources->data.sample = img;
node->clean = 0;
return 1; return 1;
} }
CUTIVIS CHiPubNode *CHi_ConstantSample() { CUTIVIS CHiPubNode *CHi_ConstantSample() {
@ -636,27 +730,172 @@ CUTIVIS CHiPubNode *CHi_ConstantSample() {
n->type = CUTIHI_T('CCns','tCol'); n->type = CUTIHI_T('CCns','tCol');
n->Start = n->Stop = NULL; n->Start = n->Stop = NULL;
n->Perform = constantsample_perform; n->Perform = constantsample_perform;
n->clean = 0; n->sinkCount = 2;
n->sinkCount = 1; n->sinks = calloc(sizeof(*n->sinks), n->sinkCount);
n->sinks = calloc(sizeof(*n->sinks), 1);
n->sourceCount = 1; 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; return n;
} }
static int modulate_perform(CHiPubNode *node) { static __m128i _mm_mullo_epi32(__m128i a, __m128i b) {
if(node->clean) return 1; // 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) {
MTR_BEGIN("CHi", "modulate_perform"); 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; node->sources[0].type = CUTIHI_VAL_SAMPLE;
if(node->sources->data.sample) CHi_Image_Free(node->sources->data.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"); MTR_END("CHi", "modulate_perform");
node->clean = 0;
return 1; return 1;
} }
CUTIVIS CHiPubNode *CHi_Modulate() { CUTIVIS CHiPubNode *CHi_Modulate() {
@ -664,11 +903,17 @@ CUTIVIS CHiPubNode *CHi_Modulate() {
n->type = CUTIHI_T('CMod','ulat'); n->type = CUTIHI_T('CMod','ulat');
n->Start = n->Stop = NULL; n->Start = n->Stop = NULL;
n->Perform = modulate_perform; n->Perform = modulate_perform;
n->clean = 0;
n->sinkCount = 4; n->sinkCount = 4;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount); n->sinks = calloc(sizeof(*n->sinks), n->sinkCount);
n->sourceCount = 1; n->sourceCount = 1;
n->sources = calloc(sizeof(*n->sources), n->sourceCount); 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; return n;
} }
@ -718,7 +963,6 @@ static void update_keyed_values(CHiNodeGraph *ng) {
static int time_perform(CHiPubNode *node) { static int time_perform(CHiPubNode *node) {
node->sources->type = CUTIHI_VAL_VEC4; node->sources->type = CUTIHI_VAL_VEC4;
node->sources->data.vec4[0] = node->ng->time; node->sources->data.vec4[0] = node->ng->time;
node->clean = 0;
return 1; return 1;
} }
@ -739,7 +983,6 @@ CUTIVIS CHiPubNode *CHi_Time() {
n->type = CUTIHI_T('CTim','e '); n->type = CUTIHI_T('CTim','e ');
n->Start = n->Stop = NULL; n->Start = n->Stop = NULL;
n->Perform = time_perform; n->Perform = time_perform;
n->clean = 0;
n->sinkCount = 0; n->sinkCount = 0;
n->sinks = NULL; n->sinks = NULL;
n->sourceCount = 1; n->sourceCount = 1;
@ -747,34 +990,63 @@ CUTIVIS CHiPubNode *CHi_Time() {
return n; return n;
} }
static PangoFontMap *pfontmap; struct TextNode {
static PangoContext *pcontext; CHiPubNode pubn;
static PangoFontDescription * pfontdesc; PangoFontMap *pfontmap;
static PangoLayout *playout; PangoContext *pcontext;
PangoFontDescription * pfontdesc;
PangoLayout *playout;
char *cacheText;
char *cacheFontName;
};
static int text_perform(CHiPubNode *n) { static int text_perform(CHiPubNode *n) {
if(n->clean) return 1;
MTR_BEGIN("CHi", "text_perform"); MTR_BEGIN("CHi", "text_perform");
if(!pfontmap) { struct TextNode *this = (struct TextNode*) n;
pfontmap = pango_ft2_font_map_new();
pango_ft2_font_map_set_resolution(PANGO_FT2_FONT_MAP(pfontmap), 72, 72); CHiValue *valFontName = CHi_Crawl(&n->sinks[3]);
pcontext = pango_font_map_create_context(pfontmap); CHiValue *valDPI = CHi_Crawl(&n->sinks[2]);
pango_context_set_language(pcontext, pango_language_from_string("en_US")); CHiValue *valCol = CHi_Crawl(&n->sinks[1]);
pango_context_set_base_dir(pcontext, PANGO_DIRECTION_LTR); CHiValue *valText = CHi_Crawl(&n->sinks[0]);
pfontdesc = pango_font_description_from_string("Open Sans 48");
playout = pango_layout_new(pcontext); if(!this->cacheFontName || strcmp(this->cacheFontName, valFontName->data.text)) {
pango_layout_set_font_description(playout, pfontdesc); 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); if(!this->cacheText || strcmp(this->cacheText, valText->data.text)) {
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) 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; PangoRectangle extents;
pango_layout_get_extents(playout, NULL, &extents); pango_layout_get_extents(this->playout, NULL, &extents);
n->sources[0].type = CUTIHI_VAL_SAMPLE; 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; size_t width = (PANGO_PIXELS(extents.width) + 15) & ~15;
CHiImage *chiret = CHi_Image_New(2, 4, 8 * width, width, PANGO_PIXELS(extents.height), NULL); 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.pitch = chiret->width;
bmp.pixel_mode = FT_PIXEL_MODE_GRAY; bmp.pixel_mode = FT_PIXEL_MODE_GRAY;
bmp.num_grays = 256; 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( __m128i ones = _mm_set1_epi64x(
(((size_t) (n->sinks[1].data.vec4[2] * 255) % 256) << 0) | (((size_t) (valCol->data.vec4[2] * 255) % 256) << 0) |
(((size_t) (n->sinks[1].data.vec4[1] * 255) % 256) << 16) | (((size_t) (valCol->data.vec4[1] * 255) % 256) << 16) |
(((size_t) (n->sinks[1].data.vec4[0] * 255) % 256) << 32) | (((size_t) (valCol->data.vec4[0] * 255) % 256) << 32) |
0x0100000000000000 0x0100000000000000
); );
for(size_t p = 0; p < bmp.width * bmp.rows; p += 2) { for(size_t p = 0; p < bmp.width * bmp.rows; p += 2) {
__m128i alphad0 = __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); _mm_stream_si128((__m128i*) &chiret->data16[p * 4], alphad0);
} }
@ -807,23 +1079,33 @@ static int text_perform(CHiPubNode *n) {
MTR_END("CHi", "text_perform"); MTR_END("CHi", "text_perform");
n->clean = 0;
return 1; return 1;
} }
CUTIVIS CHiPubNode *CHi_Text() { CUTIVIS CHiPubNode *CHi_Text() {
CHiPubNode *n = calloc(1, sizeof(*n)); CHiPubNode *n = calloc(1, sizeof(struct TextNode));
n->type = CUTIHI_T('CTex','t '); n->type = CUTIHI_T('CTex','t ');
n->Start = n->Stop = NULL; n->Start = n->Stop = NULL;
n->Perform = text_perform; n->Perform = text_perform;
n->clean = 0; n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 4);
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 3);
n->sinks[2].type = CUTIHI_VAL_VEC4;
n->sinks[2].data.vec4[0] = 72;
n->sources = calloc(sizeof(*n->sources), n->sourceCount = 1); 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; return n;
} }
static int mixer_perform(CHiPubNode *n) { static int mixer_perform(CHiPubNode *n) {
if(n->sinkCount == 0) {
return 1;
}
n->sources[0].type = CUTIHI_VAL_SAMPLE; n->sources[0].type = CUTIHI_VAL_SAMPLE;
MTR_BEGIN("CHi", "mixer_perform"); MTR_BEGIN("CHi", "mixer_perform");
@ -833,26 +1115,40 @@ static int mixer_perform(CHiPubNode *n) {
n->sources[0].data.sample = NULL; n->sources[0].data.sample = NULL;
} }
CHiImage *src0 = CHi_Crawl(&n->sinks[0])->data.sample; size_t width = 0, height = 0, stride = 0;
CHiImage *src1 = CHi_Crawl(&n->sinks[1])->data.sample;
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; 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 < stride; b += 16) {
__m128i sum = _mm_setzero_si128();
for(size_t b = 0; b < src0->stride; b += 16) { for(int s = 0; s < n->sinkCount; s++) {
__m128i a0 = src0 ? _mm_load_si128((__m128i*) ((uintptr_t) src0->data16 + b)) : _mm_setzero_si128(); CHiValue *val = CHi_Crawl(&n->sinks[s]);
__m128i a1 = src1 ? _mm_load_si128((__m128i*) ((uintptr_t) src1->data16 + b)) : _mm_setzero_si128(); if(val && val->type == CUTIHI_VAL_SAMPLE) {
_mm_stream_si128((__m128i*) ((uintptr_t) n->sources[0].data.sample->data16 + b), _mm_adds_epi16(a0, a1)); 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"); MTR_END("CHi", "mixer_perform");
n->clean = 0;
return 1; return 1;
} }
CUTIVIS CHiPubNode *CHi_Mixer() { CUTIVIS CHiPubNode *CHi_Mixer() {
@ -860,7 +1156,6 @@ CUTIVIS CHiPubNode *CHi_Mixer() {
n->type = CUTIHI_T('CMix','er '); n->type = CUTIHI_T('CMix','er ');
n->Start = n->Stop = NULL; n->Start = n->Stop = NULL;
n->Perform = mixer_perform; n->Perform = mixer_perform;
n->clean = 0;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 2); n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 2);
n->sources = calloc(sizeof(*n->sources), n->sourceCount = 1); n->sources = calloc(sizeof(*n->sources), n->sourceCount = 1);
return n; return n;
@ -874,12 +1169,134 @@ CUTIVIS CHiPubNode *CHi_Preview() {
n->type = CUTIHI_T('CPre','view'); n->type = CUTIHI_T('CPre','view');
n->Start = n->Stop = NULL; n->Start = n->Stop = NULL;
n->Perform = preview_perform; n->Perform = preview_perform;
n->clean = 0;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 1); 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->sources = NULL;
n->sourceCount = 0; 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; return n;
} }
@ -1105,6 +1522,12 @@ CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader reader, void *ud)
n = CHi_Camera(); n = CHi_Camera();
} else if(type == CUTIHI_T('CCmp','nScl')) { } else if(type == CUTIHI_T('CCmp','nScl')) {
n = CHi_ComponentScale(); 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; n->ng = ng;
@ -1148,3 +1571,8 @@ CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader reader, void *ud)
return 0; 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);
}

View File

@ -3,18 +3,23 @@
#include<stdint.h> #include<stdint.h>
#include<stddef.h> #include<stddef.h>
#include<stdbool.h>
#include<pthread.h>
#include"defs.h" #include"defs.h"
#include"bs.h" #include"bs.h"
#include<arpa/inet.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #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(*CHiSaveWriter)(void *ud, const void *data, size_t len);
typedef size_t(*CHiLoadReader)(void *ud, 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_VEC4 = 5,
CUTIHI_VAL_WEAK_PTR = 6, CUTIHI_VAL_WEAK_PTR = 6,
CUTIHI_VAL_VP9BS = 666, CUTIHI_VAL_VP8BS = 666,
CUTIHI_VAL_VP8BS = 667, CUTIHI_VAL_VP9BS = 667,
CUTIHI_VAL_OPUSBS = 668, CUTIHI_VAL_OPUSBS = 668,
CUTIHI_VAL_H264BS = 669,
CUTIHI_VAL_AACBS = 670,
} CHiValType; } CHiValType;
struct CHiImage; struct CHiImage;
@ -57,6 +64,15 @@ typedef struct {
CHiValueRaw data; CHiValueRaw data;
} CHiValue; } 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 { typedef struct CHiPubNode {
uint64_t type; uint64_t type;
@ -67,7 +83,6 @@ typedef struct CHiPubNode {
int (*Perform)(struct CHiPubNode *node); int (*Perform)(struct CHiPubNode *node);
int (*Start)(struct CHiPubNode *node); int (*Start)(struct CHiPubNode *node);
int (*Stop)(struct CHiPubNode *node); int (*Stop)(struct CHiPubNode *node);
char clean;
void (*Destroy)(struct CHiPubNode *node); void (*Destroy)(struct CHiPubNode *node);
@ -81,6 +96,13 @@ typedef struct CHiPubNode {
struct CHiNodeGraph *ng; struct CHiNodeGraph *ng;
CHiErrors errors;
struct {
float start;
float end;
} lifespan;
char _dfsmark; char _dfsmark;
} CHiPubNode; } CHiPubNode;
@ -127,6 +149,7 @@ typedef struct CHiNodeGraph {
void *ud; void *ud;
void(*eventOnStopComplete)(struct CHiNodeGraph*); void(*eventOnStopComplete)(struct CHiNodeGraph*);
void(*eventOnFrameComplete)(struct CHiNodeGraph*); void(*eventOnFrameComplete)(struct CHiNodeGraph*);
void(*eventOnError)(struct CHiNodeGraph*, CHiPubNode*);
CHiCompilationStatus compilationStatus; CHiCompilationStatus compilationStatus;
@ -137,6 +160,9 @@ typedef struct CHiNodeGraph {
float time; float time;
float timedelta; float timedelta;
// This is necessary for live changes of the node graph
pthread_mutex_t mut;
} CHiNodeGraph; } CHiNodeGraph;
CUTIVIS CHiNodeGraph *CHi_NewNodeGraph(); 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 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_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_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_SetExtrapolationMode(CHiNodeGraph *ng, CHiPubNode *n, size_t sinkIdx, CHiExtrapolationMode mode, float* params);
CUTIVIS void CHi_DeleteKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, size_t idx); 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); CUTIVIS void CHi_StopCompilation(CHiNodeGraph *ng);
#define CUTIHI_IMAGE_IN_FILE 0 #define CUTIHI_IMAGE_IN_FILE 0
#define CUTIHI_IMAGE_OUT_SAMPLE 1 #define CUTIHI_IMAGE_OUT_SAMPLE 0
CUTIVIS CHiPubNode *CHi_Image(); CUTIVIS CHiPubNode *CHi_Image();
#define CUTIHI_EMBED_OUT_MAIN 0 #define CUTIHI_EMBED_OUT_MAIN 0
@ -170,6 +196,7 @@ CUTIVIS CHiPubNode *CHi_Image();
CUTIVIS CHiPubNode *CHi_Embed(); CUTIVIS CHiPubNode *CHi_Embed();
#define CUTIHI_CONSTANTSAMPLE_IN_COLOR 0 #define CUTIHI_CONSTANTSAMPLE_IN_COLOR 0
#define CUTIHI_CONSTANTSAMPLE_IN_SIZE 1
#define CUTIHI_CONSTANTSAMPLE_OUT_SAMPLE 1 #define CUTIHI_CONSTANTSAMPLE_OUT_SAMPLE 1
CUTIVIS CHiPubNode *CHi_ConstantSample(); CUTIVIS CHiPubNode *CHi_ConstantSample();
@ -227,12 +254,8 @@ CUTIVIS CHiPubNode *CHi_Microphone();
CUTIVIS CHiPubNode *CHi_Text(); CUTIVIS CHiPubNode *CHi_Text();
CUTIVIS CHiPubNode *CHi_ExportWav(); CUTIVIS CHiPubNode *CHi_ExportWav();
CUTIVIS int CHi_ExportWav_Start(CHiPubNode*);
CUTIVIS int CHi_ExportWav_Stop(CHiPubNode*);
CUTIVIS CHiPubNode *CHi_EncodeOpus(); CUTIVIS CHiPubNode *CHi_EncodeOpus();
CUTIVIS int CHi_EncodeOpus_Start(CHiPubNode*);
CUTIVIS int CHi_EncodeOpus_Stop(CHiPubNode*);
CUTIVIS CHiPubNode *CHi_Camera(); CUTIVIS CHiPubNode *CHi_Camera();
@ -244,9 +267,19 @@ CUTIVIS CHiPubNode *CHi_Keyhook();
CUTIVIS CHiPubNode *CHi_Mixer(); 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 CHiValue *CHi_Crawl(CHiValue*);
CUTIVIS void CHi_Save(CHiNodeGraph *ng); //CUTIVIS void CHi_Save(CHiNodeGraph *ng);
CUTIVIS bool CHi_Node_Active(CHiPubNode*);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -52,18 +52,7 @@ static int encodeopus_perform(CHiPubNode *pubn) {
return 1; return 1;
} }
CUTIVIS CHiPubNode *CHi_EncodeOpus() { static int CHi_EncodeOpus_Start(CHiPubNode *pubn) {
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) {
struct CHiEncodeOpusNode *n = (struct CHiEncodeOpusNode*) pubn; struct CHiEncodeOpusNode *n = (struct CHiEncodeOpusNode*) pubn;
int error; int error;
@ -76,10 +65,20 @@ CUTIVIS int CHi_EncodeOpus_Start(CHiPubNode *pubn) {
return 1; return 1;
} }
CUTIVIS int CHi_EncodeOpus_Stop(CHiPubNode *pubn) { static int CHi_EncodeOpus_Stop(CHiPubNode *pubn) {
struct CHiEncodeOpusNode *n = (struct CHiEncodeOpusNode*) pubn; struct CHiEncodeOpusNode *n = (struct CHiEncodeOpusNode*) pubn;
opus_encoder_destroy(n->enc); opus_encoder_destroy(n->enc);
free(n->pcmbuf); free(n->pcmbuf);
return 1; 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;
}

View File

@ -39,7 +39,6 @@ CUTIVIS CHiPubNode *CHi_ComponentScale() {
n->type = CUTIHI_T('CCmp','nScl'); n->type = CUTIHI_T('CCmp','nScl');
n->Start = n->Stop = NULL; n->Start = n->Stop = NULL;
n->Perform = scale_perform; n->Perform = scale_perform;
n->clean = 0;
n->sinkCount = 2; n->sinkCount = 2;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount); n->sinks = calloc(sizeof(*n->sinks), n->sinkCount);
n->sourceCount = 1; n->sourceCount = 1;
@ -48,12 +47,14 @@ CUTIVIS CHiPubNode *CHi_ComponentScale() {
} }
static Display *dpy; static Display *dpy;
static XkbDescPtr xKeyboardDesc;
typedef struct { typedef struct {
CHiPubNode pub; CHiPubNode pub;
XRecordContext rc; XRecordContext rc;
pthread_t thrd; pthread_t thrd;
atomic_int key; char key[64];
atomic_bool on; atomic_bool on;
} CHiKeyhookNode; } CHiKeyhookNode;
@ -68,8 +69,10 @@ static void keyhook_handler(XPointer ud, XRecordInterceptData *recdata) {
CHiKeyhookNode *n = (CHiKeyhookNode*) ud; CHiKeyhookNode *n = (CHiKeyhookNode*) ud;
printf("%i\n", key); char keyname[XkbKeyNameLength + 1] = {};
if(!repeat && key == n->key) { memcpy(keyname, xKeyboardDesc->names->keys[key].name, XkbKeyNameLength);
if(!repeat && !strcmp(keyname, n->key)) {
if(type == KeyPress) { if(type == KeyPress) {
n->on = 1; n->on = 1;
} else if(type == KeyRelease) { } else if(type == KeyRelease) {
@ -93,7 +96,10 @@ static void *keyhook_thread(void *ud) {
} }
static int keyhook_perform(CHiPubNode *n) { 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; n->sources[0].type = CUTIHI_VAL_VEC4;
@ -123,17 +129,19 @@ CUTIVIS CHiPubNode *CHi_Keyhook() {
n->pub.Start = n->pub.Stop = NULL; n->pub.Start = n->pub.Stop = NULL;
n->pub.Perform = keyhook_perform; n->pub.Perform = keyhook_perform;
n->pub.Destroy = keyhook_destroy; n->pub.Destroy = keyhook_destroy;
n->pub.clean = 0;
n->pub.sinkCount = 2; n->pub.sinkCount = 2;
n->pub.sinks = calloc(sizeof(*n->pub.sinks), n->pub.sinkCount); n->pub.sinks = calloc(sizeof(*n->pub.sinks), n->pub.sinkCount);
n->pub.sourceCount = 1; n->pub.sourceCount = 1;
n->pub.sources = calloc(sizeof(*n->pub.sources), n->pub.sourceCount); n->pub.sources = calloc(sizeof(*n->pub.sources), n->pub.sourceCount);
n->on = 0; n->on = 0;
n->key = 0; n->key[0] = '\n';
if(!dpy) { if(!dpy) {
dpy = XOpenDisplay(NULL); dpy = XOpenDisplay(NULL);
xKeyboardDesc = XkbGetMap(dpy, 0, XkbUseCoreKbd);
XkbGetNames(dpy, XkbKeyNamesMask, xKeyboardDesc);
} }
pthread_create(&n->thrd, NULL, keyhook_thread, n); pthread_create(&n->thrd, NULL, keyhook_thread, n);

View File

@ -127,7 +127,6 @@ CUTIVIS CHiPubNode *CHi_Camera() {
CHiPubNode *pubn = calloc(1, sizeof(*pubn)); CHiPubNode *pubn = calloc(1, sizeof(*pubn));
pubn->type = CUTIHI_T('CWeb','Cam '); pubn->type = CUTIHI_T('CWeb','Cam ');
pubn->clean = 0;
pubn->Start = pubn->Stop = NULL; pubn->Start = pubn->Stop = NULL;
pubn->Perform = camera_perform; pubn->Perform = camera_perform;
pubn->sinks = calloc(sizeof(*pubn->sinks), pubn->sinkCount = 0); pubn->sinks = calloc(sizeof(*pubn->sinks), pubn->sinkCount = 0);

View File

@ -280,14 +280,22 @@ webm::Status CueParser::OnCuePoint(const webm::ElementMetadata &metadata, const
static int movie_perform(CHiPubNode *pub) { static int movie_perform(CHiPubNode *pub) {
CHiMovieNode *node = (CHiMovieNode*) ((uintptr_t) pub - offsetof(CHiMovieNode, 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"); MTR_BEGIN("CHi", "movie_perform");
int64_t t; 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; 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; char *filepath = CHi_Crawl(&pub->sinks[0])->data.text;
if(!node->filepathCache || strcmp(node->filepathCache, filepath) != 0) { if(!node->filepathCache || strcmp(node->filepathCache, filepath) != 0) {
@ -331,15 +339,13 @@ static int movie_perform(CHiPubNode *pub) {
return 1; 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 >= 0 && t < 1000 * node->duration) {
if(t < node->timeCache || (t - node->timeCache) > 5000) { 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) { if(node->cuepoints.size() > 0) {
size_t i; size_t i;
@ -351,19 +357,16 @@ static int movie_perform(CHiPubNode *pub) {
if(i != 0) i--; if(i != 0) i--;
for(webm::Element<webm::CueTrackPositions> &p : node->cuepoints[i].cue_track_positions) { if(node->cuepoints[i].time.value() <= t) {
if(p.value().track.value() == node->videoTrack) { for(webm::Element<webm::CueTrackPositions> &p : node->cuepoints[i].cue_track_positions) {
fseek(node->vf, node->segmentOff + p.value().cluster_position.value(), SEEK_SET); if(p.value().track.value() == node->videoTrack) {
fseek(node->af, node->segmentOff + p.value().cluster_position.value(), SEEK_SET); fseek(node->vf, node->segmentOff + p.value().cluster_position.value(), SEEK_SET);
break; 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; pub->sources[0].data.sample = node->fp.output;
node->fp.output = nullptr;
node->timeCache = t; node->timeCache = t;
} }
@ -406,8 +411,6 @@ static int movie_perform(CHiPubNode *pub) {
pub->sources[1].type = CUTIHI_VAL_SAMPLE; pub->sources[1].type = CUTIHI_VAL_SAMPLE;
pub->sources[1].data.sample = aud; pub->sources[1].data.sample = aud;
pub->clean = 0;
MTR_END("CHi", "movie_perform"); MTR_END("CHi", "movie_perform");
return 1; return 1;
@ -441,7 +444,6 @@ CUTIVIS CHiPubNode *CHi_Movie() {
n->pub.type = CUTIHI_T('CMov','ie '); n->pub.type = CUTIHI_T('CMov','ie ');
n->pub.Perform = movie_perform; n->pub.Perform = movie_perform;
n->pub.Destroy = movie_destroy; n->pub.Destroy = movie_destroy;
n->pub.clean = 0;
n->pub.sinkCount = 2; n->pub.sinkCount = 2;
n->pub.sinks = (CHiValue*) calloc(sizeof(*n->pub.sinks), n->pub.sinkCount); n->pub.sinks = (CHiValue*) calloc(sizeof(*n->pub.sinks), n->pub.sinkCount);
n->pub.sinks[1].type = CUTIHI_VAL_VEC4; n->pub.sinks[1].type = CUTIHI_VAL_VEC4;

View File

@ -24,295 +24,7 @@
#include"linearity.h" #include"linearity.h"
struct CHiEncodeVP9Node { #include"yuv.h"
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;
}
struct CHiMuxWebmNode { struct CHiMuxWebmNode {
CHiPubNode pub; CHiPubNode pub;
@ -386,7 +98,6 @@ CUTIVIS CHiPubNode *CHi_MuxWebm() {
n->pub.Perform = muxwebm_perform; n->pub.Perform = muxwebm_perform;
n->pub.Destroy = muxwebm_destroy; n->pub.Destroy = muxwebm_destroy;
n->pub.Stop = CHi_MuxWebm_Stop; 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.sinks = (CHiValue*) calloc(sizeof(*n->pub.sinks), n->pub.sinkCount = 3);
n->pub.sourceCount = 0; n->pub.sourceCount = 0;
n->pub.sources = NULL; n->pub.sources = NULL;

View File

@ -114,8 +114,6 @@ static int window_perform(CHiPubNode *n) {
n->sources[0].type = CUTIHI_VAL_SAMPLE; n->sources[0].type = CUTIHI_VAL_SAMPLE;
n->sources[0].data.sample = w->vcache; n->sources[0].data.sample = w->vcache;
n->clean = 0;
MTR_END("CHi", "window_perform"); MTR_END("CHi", "window_perform");
return 1; return 1;
@ -144,7 +142,6 @@ CUTIVIS CHiPubNode *CHi_Window() {
n->pub.Start = n->pub.Stop = NULL; n->pub.Start = n->pub.Stop = NULL;
n->pub.Perform = window_perform; n->pub.Perform = window_perform;
n->pub.Destroy = window_destroy; n->pub.Destroy = window_destroy;
n->pub.clean = 0;
n->pub.sinkCount = 1; n->pub.sinkCount = 1;
n->pub.sinks = calloc(sizeof(*n->pub.sinks), 1); n->pub.sinks = calloc(sizeof(*n->pub.sinks), 1);
n->pub.sourceCount = 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); 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; 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); int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list);
if(status >= Success) { if(status >= Success) {
XFree(list); //XFree(list);
status = XGetWMName(d, list[idx], &windowName); status = XGetWMName(d, list[idx], &windowName);
if(status >= Success) { if(status >= Success) {
@ -209,7 +206,7 @@ CUTIVIS uintptr_t CHi_Window_GetSourceData(size_t idx) {
if(status >= Success) { if(status >= Success) {
Window ret = list[idx]; Window ret = list[idx];
XFree(list); //XFree(list);
return ret; return ret;
} }

View File

@ -28,93 +28,155 @@
#include<hi/linearity.h> #include<hi/linearity.h>
static void ShapeGrNode(GrNode *gn) { static Frame *globaldis;
if(gn->logical->type == CUTIHI_T('CPre','view')) {
gn->name = "Preview"; std::string node_name_from_id(uint64_t id) {
gn->sinks = {{"Video", GrNode::Port::Type::SAMPLE}}; static std::unordered_map<uint64_t, std::string> NODE_ID_NAMES = {
} else if(gn->logical->type == CUTIHI_T('CMix','er ')) { {CUTIHI_T('CPre','view'), "Preview"},
gn->name = "Mixer"; {CUTIHI_T('CMix','er '), "Mixer"},
gn->sinks = {{"Sink 1", GrNode::Port::Type::SAMPLE}, {"Sink 2", GrNode::Port::Type::SAMPLE}}; {CUTIHI_T('CTex','t '), "Text"},
gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}}; {CUTIHI_T('CTim','e '), "Time"},
} else if(gn->logical->type == CUTIHI_T('CTex','t ')) { {CUTIHI_T('CMod','ulat'), "Modulate"},
gn->name = "Text"; {CUTIHI_T('CCns','tCol'), "Constant"},
gn->sinks = {{"Text", GrNode::Port::Type::TEXT}, {"Color", GrNode::Port::Type::COLOR}, {"DPI", GrNode::Port::Type::VEC1}}; {CUTIHI_T('CEmb','ed '), "Frame"},
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; {CUTIHI_T('CIma','ge '), "Image"},
} else if(gn->logical->type == CUTIHI_T('CTim','e ')) { {CUTIHI_T('CWin','dow '), "Window"},
gn->name = "Time"; {CUTIHI_T('CInA','udio'), "Microphone"},
gn->sinks = {}; {CUTIHI_T('CExp','Wave'), "Mux .wav"},
gn->sources = {{"t", GrNode::Port::Type::VEC1}}; {CUTIHI_T('CMov','ie '), "Movie"},
} else if(gn->logical->type == CUTIHI_T('CMod','ulat')) { {CUTIHI_T('CEnc','GVP8'), "Encode VP8"},
gn->name = "Modulate"; {CUTIHI_T('CEnc','GVP9'), "Encode VP9"},
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Brightness", GrNode::Port::Type::VEC1}, {"Contrast", GrNode::Port::Type::VEC1}, {"Hue", GrNode::Port::Type::VEC1}}; {CUTIHI_T('CEnc','H264'), "Encode H264"},
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; {CUTIHI_T('CExp','Webm'), "Mux .webm"},
} else if(gn->logical->type == CUTIHI_T('CCns','tCol')) { {CUTIHI_T('CKey','hook'), "Keyhook"},
gn->name = "Constant"; {CUTIHI_T('CEnc','Opus'), "Encode Opus"},
gn->sinks = {{"Color", GrNode::Port::Type::COLOR}}; {CUTIHI_T('CWeb','Cam '), "Live Digital Camera"},
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; {CUTIHI_T('CCmp','nScl'), "Scale"},
} else if(gn->logical->type == CUTIHI_T('CEmb','ed ')) { {CUTIHI_T('CChr','omaK'), "Chroma Key"},
gn->name = "Embed"; {CUTIHI_T('CStr','RTMP'), "Stream RTMP"},
gn->sinks = { {CUTIHI_T('CEnc','AACL'), "Encode AAC-LC"},
{"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}, auto nameit = NODE_ID_NAMES.find(id);
{"Sub 3", GrNode::Port::Type::SAMPLE}, {" Pos", GrNode::Port::Type::VEC2}, {" Size", GrNode::Port::Type::VEC1} if(nameit == NODE_ID_NAMES.end()) {
}; char n[9] = {};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; memcpy(n, &id, 8);
} else if(gn->logical->type == CUTIHI_T('CIma','ge ')) {
gn->name = "Image"; return "Unknown (" + std::string{n} + ")";
gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CWin','dow ')) {
gn->name = "Window";
gn->sinks = {{"Name", GrNode::Port::Type::WINDOW_SOURCE}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CInA','udio')) {
gn->name = "Microphone";
gn->sinks = {{"Source", GrNode::Port::Type::MIC_SOURCE}};
gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CExp','Wave')) {
gn->name = "Mux Wav";
gn->sinks = {{"Filename", GrNode::Port::Type::FILE_SAVE}, {"Audio", GrNode::Port::Type::SAMPLE}};
gn->sources = {};
} else if(gn->logical->type == CUTIHI_T('CMov','ie ')) {
gn->name = "Movie";
gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}, {"Time", GrNode::Port::Type::VEC1}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Audio", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CEnc','GVP8')) {
gn->name = "Encode VP8";
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Bitstream"}};
} else if(gn->logical->type == CUTIHI_T('CEnc','GVP9')) {
gn->name = "Encode VP9";
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Bitstream"}};
} else if(gn->logical->type == CUTIHI_T('CExp','Webm')) {
gn->name = "Mux WebM";
gn->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"Filename", GrNode::Port::Type::FILE_SAVE}};
gn->sources = {};
} else if(gn->logical->type == CUTIHI_T('CKey','hook')) {
gn->name = "Keyhook";
gn->sinks = {{"Key", GrNode::Port::Type::VEC1}, {"Smooth Time", GrNode::Port::Type::VEC1}};
gn->sources = {{"Bool", GrNode::Port::Type::VEC1}};
} else if(gn->logical->type == CUTIHI_T('CEnc','Opus')) {
gn->name = "Encode Opus";
gn->sinks = {{"Audio", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Bitstream"}};
} else if(gn->logical->type == CUTIHI_T('CWeb','Cam ')) {
gn->name = "Live Digital Camera";
gn->sinks = {};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(gn->logical->type == CUTIHI_T('CCmp','nScl')) {
gn->name = "Scale";
gn->sinks = {{"Vector", GrNode::Port::Type::VEC4}, {"Sample", GrNode::Port::Type::SAMPLE}};
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else { } 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}) { 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); aui.SetManagedWindow(this);
viewer = new ImageViewer(this); viewer = new ImageViewer(this);
@ -167,9 +229,7 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
}, f); }, f);
for(size_t n = 0; n < graph->backendNG->count; n++) { for(size_t n = 0; n < graph->backendNG->count; n++) {
GrNode *gn = *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){ GrNode *gn = graph->get_graphical(graph->backendNG->nodes[n]);
return gn->logical == graph->backendNG->nodes[n];
});
int32_t pos[2] = {gn->GetPosition().x, gn->GetPosition().y}; int32_t pos[2] = {gn->GetPosition().x, gn->GetPosition().y};
fwrite(pos, sizeof(pos), 1, f); 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]; gnode->logical = graph->backendNG->nodes[n];
ShapeGrNode(gnode); ShapeGrNode(gnode);
gnode->Fit();
graph->gnodes.push_back(gnode); 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.durationEnable = new wxCheckBox(tlba, wxID_ANY, "");
toolbar.duration = new ctTimeCtrl(tlba, 120); toolbar.duration = new ctTimeCtrl(tlba, 120);
toolbar.duration->Bind(wxEVT_COMMAND_TEXT_UPDATED, [=](wxCommandEvent&){ toolbar.duration->Bind(wxEVT_COMMAND_TEXT_UPDATED, [=](wxCommandEvent&){
CHi_SetDuration(graph->backendNG, toolbar.durationEnable->IsChecked() ? toolbar.duration->GetSeconds() : -1); CHi_SetDuration(graph->backendNG, toolbar.durationEnable->IsChecked() ? toolbar.duration->GetSeconds() : -1);
}); });
CHi_SetDuration(graph->backendNG, toolbar.duration->GetSeconds());
toolbar.durationEnable->SetValue(true); toolbar.durationEnable->SetValue(true);
toolbar.durationEnable->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent&){ 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(); tlba->Realize();
aui.SetFlags(wxAUI_MGR_LIVE_RESIZE | wxAUI_MGR_DEFAULT); aui.SetFlags(wxAUI_MGR_LIVE_RESIZE | wxAUI_MGR_DEFAULT);
aui.Update(); aui.Update();
Centre(); Centre();
graph->CreatePreviewNode();
} }
Frame::~Frame() { Frame::~Frame() {
@ -313,7 +383,7 @@ bool GrNode::MouseOverPort(wxPoint point, bool &source, int &i) {
int p = (point.y - 26) / 20; 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; 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; source = isSource;
i = p; i = p;
@ -329,11 +399,25 @@ void GrNode::MakeKeyframe(int sinkIdx) {
((Frame*) ng->GetParent())->timeline->Refresh(); ((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}) { GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) {
Bind(wxEVT_PAINT, [this](wxPaintEvent &ev){ 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); wxPaintDC dc(this);
dc.SetBrush(wxBrush{wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)}); dc.SetBrush(wxBrush{bgColor});
dc.SetPen(HasFocus() ? *wxRED_PEN : *wxBLACK_PEN); dc.SetPen(HasFocus() ? *wxGREY_PEN : *wxBLACK_PEN);
dc.DrawRoundedRectangle(5, 0, GetSize().x - 10, GetSize().y, 5); dc.DrawRoundedRectangle(5, 0, GetSize().x - 10, GetSize().y, 5);
dc.DrawText(name, GetSize().x / 2 - dc.GetTextExtent(name).x / 2, 2); 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.DrawText(p.name, 15, (y += 20) - sz.y / 2);
dc.DrawCircle(5, y, 5); dc.DrawCircle(5, y, 5);
if(p.separator) {
dc.DrawLine(5, y + 10, GetSize().x / 2, y + 10);
}
i++; i++;
} }
y = 13; 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.DrawText(p.name, GetSize().x - sz.x - 15, (y += 20) - sz.y / 2);
dc.DrawCircle(GetSize().x - 6, y, 5); dc.DrawCircle(GetSize().x - 6, y, 5);
if(p.separator) {
dc.DrawLine(GetSize().x / 2, y + 10, GetSize().x - 5, y + 10);
}
i++; i++;
} }
}); });
@ -425,9 +517,38 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
SetPosition(GetPosition() + neu - parent->dragPos); SetPosition(GetPosition() + neu - parent->dragPos);
parent->dragPos = neu; 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(); parent->Refresh();
SetFocus();
// Set focus if currently focused item is not a control/entry/editor
if(!dynamic_cast<wxControl*>(wxWindow::FindFocus())) {
SetFocus();
}
}); });
Bind(wxEVT_LEFT_UP, [parent, this](wxMouseEvent &ev){ Bind(wxEVT_LEFT_UP, [parent, this](wxMouseEvent &ev){
if(HasCapture()) { if(HasCapture()) {
@ -446,6 +567,7 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
wxColourData data; wxColourData data;
data.SetChooseFull(true); data.SetChooseFull(true);
data.SetChooseAlpha(true);
CHiValue *currentVal = CHi_Crawl(&this->logical->sinks[p]); CHiValue *currentVal = CHi_Crawl(&this->logical->sinks[p]);
if(currentVal->type == CUTIHI_VAL_VEC4) { 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); wxColourDialog dlg(this, &data);
if(dlg.ShowModal() == wxID_OK) { if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv; CHiValue newv;
newv.type = CUTIHI_VAL_VEC4; newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[0] = dlg.GetColourData().GetColour().Red() / 255.f; 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; newv.data.vec4[3] = dlg.GetColourData().GetColour().Alpha() / 255.f;
CHi_ConfigureSink(this->logical, p, newv); CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this); parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
} }
} else if(sinks[p].type == Port::Type::FILE_OPEN) { } else if(sinks[p].type == Port::Type::FILE_OPEN) {
wxFileDialog dlg(this, wxFileSelectorPromptStr, wxEmptyString, wxEmptyString, wxFileSelectorDefaultWildcardStr, wxFD_OPEN | wxFD_PREVIEW); wxFileDialog dlg(this, wxFileSelectorPromptStr, wxEmptyString, wxEmptyString, wxFileSelectorDefaultWildcardStr, wxFD_OPEN | wxFD_PREVIEW);
if(dlg.ShowModal() == wxID_OK) { if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv; CHiValue newv;
newv.type = CUTIHI_VAL_TEXT; newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(dlg.GetPath().utf8_str()); newv.data.text = strdup(dlg.GetPath().utf8_str());
CHi_ConfigureSink(this->logical, p, newv); CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this); parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
} }
} else if(sinks[p].type == Port::Type::FILE_SAVE) { } else if(sinks[p].type == Port::Type::FILE_SAVE) {
wxFileDialog dlg(this, wxFileSelectorPromptStr, wxEmptyString, wxEmptyString, wxFileSelectorDefaultWildcardStr, wxFD_SAVE | wxFD_PREVIEW | wxFD_OVERWRITE_PROMPT); wxFileDialog dlg(this, wxFileSelectorPromptStr, wxEmptyString, wxEmptyString, wxFileSelectorDefaultWildcardStr, wxFD_SAVE | wxFD_PREVIEW | wxFD_OVERWRITE_PROMPT);
if(dlg.ShowModal() == wxID_OK) { if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv; CHiValue newv;
newv.type = CUTIHI_VAL_TEXT; newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(dlg.GetPath().utf8_str()); newv.data.text = strdup(dlg.GetPath().utf8_str());
CHi_ConfigureSink(this->logical, p, newv); CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this); parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
} }
} else if(sinks[p].type >= Port::Type::VEC1 && sinks[p].type <= Port::Type::VEC4) { } else if(sinks[p].type >= Port::Type::VEC1 && sinks[p].type <= Port::Type::VEC4) {
auto ctrls = std::make_shared<std::vector<wxTextCtrl*>>(); auto ctrls = std::make_shared<std::vector<wxTextCtrl*>>();
@ -494,6 +629,8 @@ GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80})
if(ev.GetKeyCode() == WXK_RETURN) { if(ev.GetKeyCode() == WXK_RETURN) {
double d; double d;
if(tc->GetValue().ToDouble(&d)) { if(tc->GetValue().ToDouble(&d)) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv = *CHi_Crawl(&this->logical->sinks[p]); CHiValue newv = *CHi_Crawl(&this->logical->sinks[p]);
newv.type = CUTIHI_VAL_VEC4; newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[i] = d; 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();}); CallAfter([tc](){tc->Destroy();});
parent->Dirtify(this); parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
} }
} else if(ev.GetKeyCode() == WXK_TAB) { } else if(ev.GetKeyCode() == WXK_TAB) {
ctrls->operator[]((i + ctrls->size() + (wxGetKeyState(WXK_SHIFT) ? -1 : 1)) % ctrls->size())->SetFocus(); 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->SetValue(wxString{CHi_Crawl(&this->logical->sinks[p])->data.text});
ctrl->SetFocus(); ctrl->SetFocus();
ctrl->Bind(wxEVT_KILL_FOCUS, [=](wxFocusEvent &ev){ ctrl->Bind(wxEVT_KILL_FOCUS, [=](wxFocusEvent &ev){
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv; CHiValue newv;
newv.type = CUTIHI_VAL_TEXT; newv.type = CUTIHI_VAL_TEXT;
char *c = (char*) malloc(ctrl->GetValue().Len() + 1); 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); CHi_ConfigureSink(this->logical, p, newv);
CallAfter([ctrl](){ctrl->Destroy();}); CallAfter([ctrl](){ctrl->Destroy();});
parent->Dirtify(this); parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
}); });
ctrl->Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){ ctrl->Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){
if(ev.GetKeyCode() == WXK_RETURN) { if(ev.GetKeyCode() == WXK_RETURN) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv; CHiValue newv;
newv.type = CUTIHI_VAL_TEXT; newv.type = CUTIHI_VAL_TEXT;
char *c = (char*) malloc(ctrl->GetValue().Len() + 1); 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); CHi_ConfigureSink(this->logical, p, newv);
CallAfter([ctrl](){ctrl->Destroy();}); CallAfter([ctrl](){ctrl->Destroy();});
parent->Dirtify(this); parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
} else ev.Skip(); } else ev.Skip();
}); });
} else if(sinks[p].type == Port::Type::MIC_SOURCE) { } 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()); wxSingleChoiceDialog dlg(this, "", "Choose Source", choices.size(), choices.data(), datae.data());
if(dlg.ShowModal() == wxID_OK) { if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv; CHiValue newv;
newv.type = CUTIHI_VAL_TEXT; newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(CHi_Microphone_GetSourceName((size_t) (uintptr_t) dlg.GetSelectionData())); newv.data.text = strdup(CHi_Microphone_GetSourceName((size_t) (uintptr_t) dlg.GetSelectionData()));
CHi_ConfigureSink(this->logical, p, newv); CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this); parent->Dirtify(this);
pthread_mutex_unlock(&this->logical->ng->mut);
} }
} else if(sinks[p].type == Port::Type::WINDOW_SOURCE) { } else if(sinks[p].type == Port::Type::WINDOW_SOURCE) {
std::vector<const char*> choicesOrig; std::vector<const char*> 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); wxSingleChoiceDialog dlg(this, "", "Choose Source", choices.size(), choices.data(), (void**) nullptr);
if(dlg.ShowModal() == wxID_OK) { if(dlg.ShowModal() == wxID_OK) {
pthread_mutex_lock(&this->logical->ng->mut);
CHiValue newv; CHiValue newv;
newv.type = CUTIHI_VAL_TEXT; newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(choicesOrig[dlg.GetSelection()]); newv.data.text = strdup(choicesOrig[dlg.GetSelection()]);
CHi_ConfigureSink(this->logical, p, newv); CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this); 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) { if(!isSource) {
pthread_mutex_lock(&daNode->ng->mut);
CHiValue val; CHiValue val;
val.type = CUTIHI_VAL_NONE; val.type = CUTIHI_VAL_NONE;
CHi_ConfigureSink(daNode, daPortIdx, val); CHi_ConfigureSink(daNode, daPortIdx, val);
pthread_mutex_unlock(&daNode->ng->mut);
} }
parent->Dirtify(this); 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() { GrNode::~GrNode() {
@ -719,8 +899,14 @@ void ImageViewer::SetImage(CHiImage *chim) {
ResizeImage(siez); ResizeImage(siez);
} }
void ImageViewer::ResizeImage(float size) { void ImageViewer::ResizeImage(float size) {
float w = size, h = (float) bufH / bufW * siez;
if(w <= 1 || h <= 1) {
return;
}
siez = size; siez = size;
img.Rescale(siez, (float) bufH / bufW * siez); img.Rescale(w, h);
bm = wxBitmap(img); bm = wxBitmap(img);
Refresh(); Refresh();
} }
@ -729,41 +915,46 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
backendNG = CHi_NewNodeGraph(); backendNG = CHi_NewNodeGraph();
backendNG->ud = f; 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){ 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; 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 idConstant = menu.Append(wxID_ANY, "Constant", "")->GetId();
int idImage = menu.Append(wxID_ANY, "Image", "")->GetId(); int idImage = menu.Append(wxID_ANY, "Image", "")->GetId();
int idMovie = menu.Append(wxID_ANY, "Movie", "")->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 idText = menu.Append(wxID_ANY, "Text", "")->GetId();
int idMicrophone = menu.Append(wxID_ANY, "Microphone", "")->GetId(); int idMixer = menu.Append(wxID_ANY, "Audio Mixer", "")->GetId();
int idMixer = menu.Append(wxID_ANY, "Mixer", "")->GetId();
int idCamera = menu.Append(wxID_ANY, "Live Digital Camera", "")->GetId();
int idTime = menu.Append(wxID_ANY, "Time", "")->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 idComponentScale = menu.Append(wxID_ANY, "Scale", "")->GetId();
int idModulate = menu.Append(wxID_ANY, "Modulate", "")->GetId(); int idModulate = menu.Append(wxID_ANY, "Modulate", "")->GetId();
int idKeyhook = menu.Append(wxID_ANY, "Keyhook (Live)", "")->GetId(); int idChromaKey = menu.Append(wxID_ANY, "Chroma Key", "")->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();
wxPoint position = ScreenToClient(wxGetMousePosition()); wxPoint position = ScreenToClient(wxGetMousePosition());
menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){ auto fn = [=](wxCommandEvent &ev) {
std::function<void()> after = [](){}; std::function<void()> after = [](){};
pthread_mutex_lock(&backendNG->mut);
GrNode *noed = nullptr; GrNode *noed = nullptr;
if(ev.GetId() == idConstant) { if(ev.GetId() == idConstant) {
noed = new GrNode(this); noed = new GrNode(this);
@ -791,6 +982,9 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
float params[4] = {1}; float params[4] = {1};
CHi_SetExtrapolationMode(backendNG, noed->logical, portIdx, CUTIHI_EXTRAPOLATION_CONSTANT, params); 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) { } else if(ev.GetId() == idEncodeVp9) {
noed = new GrNode(this); noed = new GrNode(this);
noed->logical = CHi_EncodeVP9(); noed->logical = CHi_EncodeVP9();
@ -830,19 +1024,36 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
} else if(ev.GetId() == idKeyhook) { } else if(ev.GetId() == idKeyhook) {
noed = new GrNode(this); noed = new GrNode(this);
noed->logical = CHi_Keyhook(); 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) { if(noed) {
ShapeGrNode(noed); ShapeGrNode(noed);
noed->Fit();
noed->SetPosition(position); noed->SetPosition(position);
CHi_RegisterNode(backendNG, noed->logical); CHi_RegisterNode(backendNG, noed->logical);
gnodes.push_back(noed); gnodes.push_back(noed);
after(); 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); PopupMenu(&menu);
}); });
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &ev){ Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &ev){
@ -897,12 +1108,18 @@ NodeGraph::~NodeGraph() {
} }
void NodeGraph::Alinken(Link l) { void NodeGraph::Alinken(Link l) {
pthread_mutex_lock(&backendNG->mut);
CHiValue newv; CHiValue newv;
newv.type = CUTIHI_VAL_LINKED; newv.type = CUTIHI_VAL_LINKED;
newv.data.linked.to = l.output->logical; newv.data.linked.to = l.output->logical;
newv.data.linked.idx = l.o; 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."); ((Frame*) GetParent())->stba->SetStatusText("Uh oh! Hur-hur, there's a WAACKY cycle! Can't do, sorry friend.");
return; return;
} }
@ -914,6 +1131,8 @@ void NodeGraph::Alinken(Link l) {
} }
} }
ShapeGrNode(l.input);
links.push_back(l); links.push_back(l);
Dirtify(l.input); Dirtify(l.input);
@ -921,7 +1140,7 @@ void NodeGraph::Alinken(Link l) {
} }
void NodeGraph::Dirtify(GrNode *g) { void NodeGraph::Dirtify(GrNode *g) {
g->logical->clean = 0; /*g->logical->clean = 0;
for(auto &it : links) { for(auto &it : links) {
if(it.output == g) { if(it.output == g) {
Dirtify(it.input); Dirtify(it.input);
@ -936,7 +1155,9 @@ void NodeGraph::Dirtify(GrNode *g) {
((Frame*) GetParent())->viewer->SetImage(val->data.sample); ((Frame*) GetParent())->viewer->SetImage(val->data.sample);
} }
} }
} }*/
CHi_Hysteresis(gnodes[0]->logical);
} }
bool operator==(const NodeGraph::Link &l, const NodeGraph::Link &r) { bool operator==(const NodeGraph::Link &l, const NodeGraph::Link &r) {
@ -961,3 +1182,32 @@ bool NodeGraph::DetectCycles(GrNode *root) {
std::set<GrNode*> p{}; std::set<GrNode*> p{};
return dfs(this, p, root); 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;
});
}

View File

@ -3,6 +3,7 @@
#include<vector> #include<vector>
#include<set> #include<set>
#include<unordered_map>
#include<wx/frame.h> #include<wx/frame.h>
#include<wx/panel.h> #include<wx/panel.h>
#include<wx/aui/aui.h> #include<wx/aui/aui.h>
@ -21,6 +22,8 @@ struct ImageViewer;
struct Timeline; struct Timeline;
struct ctTimeCtrl; struct ctTimeCtrl;
std::string node_name_from_id(uint64_t id);
struct Frame : wxFrame { struct Frame : wxFrame {
wxAuiManager aui; wxAuiManager aui;
@ -48,6 +51,14 @@ struct GrNode : wxPanel {
enum class Type { enum class Type {
NONE, FILE_OPEN, COLOR, VEC1, VEC2, VEC3, VEC4, TEXT, SAMPLE, FILE_SAVE, MIC_SOURCE, WINDOW_SOURCE NONE, FILE_OPEN, COLOR, VEC1, VEC2, VEC3, VEC4, TEXT, SAMPLE, FILE_SAVE, MIC_SOURCE, WINDOW_SOURCE
} type; } 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<Port> sinks; std::vector<Port> sinks;
std::vector<Port> sources; std::vector<Port> sources;
@ -56,6 +67,8 @@ struct GrNode : wxPanel {
CHiPubNode *logical; CHiPubNode *logical;
int sinkValueDragging;
GrNode(NodeGraph*); GrNode(NodeGraph*);
virtual ~GrNode(); virtual ~GrNode();
@ -120,6 +133,10 @@ struct NodeGraph : wxPanel {
void Dirtify(GrNode *g); void Dirtify(GrNode *g);
bool DetectCycles(GrNode*); bool DetectCycles(GrNode*);
void CreatePreviewNode();
GrNode *get_graphical(CHiPubNode*);
}; };

View File

@ -20,30 +20,113 @@ static T mod(T a, T b) {
#define ZERO_TIME_BASE 128 #define ZERO_TIME_BASE 128
bool Timeline::MouseOverKF(wxPoint p, size_t &kfsIdxRet, size_t &kfIdxRet) { Timeline::Row *Timeline::GetRow(int y) {
auto f = (Frame*) GetParent(); 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; return false;
} }
auto f = (Frame*) GetParent();
kfs = row->kfs;
float t = (p.x + camX - ZERO_TIME_BASE) / (float) scale; float t = (p.x + camX - ZERO_TIME_BASE) / (float) scale;
float threshold = bmpKf.GetWidth() / (float) scale / 2; 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; return false;
} }
kfsIdxRet = kfsIdx;
kfIdxRet = idx; kfIdxRet = idx;
return true; 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<float> 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) { Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
bmpKf = wxBitmap{"keyframe.bmp", wxBITMAP_TYPE_ANY}; bmpKf = wxBitmap{"keyframe.bmp", wxBITMAP_TYPE_ANY};
bmpKfExtrap = wxBitmap{"keyframe_extrap.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){ Bind(wxEVT_MIDDLE_DOWN, [=](wxMouseEvent &ev){
captureMode = Timeline::CaptureMode::CAM; captureMode = Timeline::CaptureMode::CAM;
CaptureMouse(); CaptureMouse();
mouseX = ev.GetX(); mouseX = ev.GetX();
mouseY = ev.GetY();
}); });
Bind(wxEVT_MIDDLE_UP, [=](wxMouseEvent &ev){ Bind(wxEVT_MIDDLE_UP, [=](wxMouseEvent &ev){
if(HasCapture() && captureMode == Timeline::CaptureMode::CAM) { 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){ Bind(wxEVT_LEFT_DOWN, [=](wxMouseEvent &ev){
Timeline::Row *row = GetRow(ev.GetPosition().y);
auto f = (Frame*) GetParent(); auto f = (Frame*) GetParent();
size_t kfsIdx, kfIdx; float t = (ev.GetX() + camX - ZERO_TIME_BASE) / (float) scale;
if(MouseOverKF(ev.GetPosition(), kfsIdx, kfIdx)) { if(ev.ControlDown()) {
captureMode = Timeline::CaptureMode::KF; t = SnapTime(t);
CaptureMouse(); }
mouseX = ev.GetX();
this->captureKfsIdx = kfsIdx;
this->captureKfIdx = kfIdx;
} else {
float t = (ev.GetX() + camX - ZERO_TIME_BASE) / (float) scale;
if(!row) {
// Snap to closest keyframe, in all keyframes // 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); CHi_Time_Set(f->graph->backendNG, t < 0 ? 0 : t);
Refresh(); Refresh();
f->graph->Dirtify(f->graph->gnodes[0]); 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){ Bind(wxEVT_LEFT_UP, [=](wxMouseEvent &ev){
@ -105,6 +187,20 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
ReleaseMouse(); 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){ Bind(wxEVT_MOTION, [=](wxMouseEvent &ev){
auto f = (Frame*) GetParent(); auto f = (Frame*) GetParent();
@ -112,9 +208,14 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
if(HasCapture()) { if(HasCapture()) {
if(captureMode == Timeline::CaptureMode::CAM) { if(captureMode == Timeline::CaptureMode::CAM) {
camX += mouseX - ev.GetX(); camX += mouseX - ev.GetX();
camY += mouseY - ev.GetY();
if(camX < 0) { if(camX < 0) {
camX = 0; camX = 0;
} }
if(camY < 0) {
camY = 0;
}
Refresh(); Refresh();
} else if(captureMode == Timeline::CaptureMode::KF) { } 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; 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(); Refresh();
@ -130,13 +231,13 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
} }
mouseX = ev.GetX(); mouseX = ev.GetX();
mouseY = ev.GetY();
} else { } else {
// This is really baad.. // This is really baad..
size_t kfsIdx, kfIdx; CHiKeyframes *kfs;
if(GetToolTipText() == "" && MouseOverKF(ScreenToClient(wxGetMousePosition()), kfsIdx, kfIdx)) { size_t kfIdx;
CHiKeyframes *kfs = f->graph->backendNG->keyframesList.keyframes[kfsIdx]; if(GetToolTipText() == "" && MouseOverKF(ScreenToClient(wxGetMousePosition()), kfs, kfIdx)) {
CHiPubNode *node = kfs->node; CHiPubNode *node = kfs->node;
auto it = std::find_if(f->graph->gnodes.begin(), f->graph->gnodes.end(), [=](GrNode *g){ 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){ Bind(wxEVT_CONTEXT_MENU, [=](wxContextMenuEvent &ev){
wxPoint position = ScreenToClient(wxGetMousePosition()); wxPoint position = ScreenToClient(wxGetMousePosition());
size_t kfsIdx, kfIdx; CHiKeyframes *kfs;
if(MouseOverKF(ScreenToClient(wxGetMousePosition()), kfsIdx, kfIdx)) { size_t kfIdx;
if(MouseOverKF(ScreenToClient(wxGetMousePosition()), kfs, kfIdx)) {
wxMenu menu; wxMenu menu;
int idDel = menu.Append(wxID_ANY, "Delete")->GetId(); 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){ menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){
if(ev.GetId() == idDel) { if(ev.GetId() == idDel) {
auto f = (Frame*) GetParent(); auto f = (Frame*) GetParent();
auto kfs = f->graph->backendNG->keyframesList.keyframes[kfsIdx];
CHi_DeleteKeyframe(f->graph->backendNG, kfs, kfIdx); CHi_DeleteKeyframe(f->graph->backendNG, kfs, kfIdx);
@ -242,31 +343,83 @@ void Timeline::Paint(wxPaintEvent &ev) {
dc.SetPen(wxPen{wxColour{160, 60, 60}}); 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; float curTime = CHi_Time_Get(frame->graph->backendNG);
dc.DrawLine(x, 0, x, GetSize().y);
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)}); dc.SetPen(wxPen{wxSystemSettings::GetColour(wxSYS_COLOUR_INACTIVECAPTIONTEXT)});
float t = std::ceil((float) camX / scale); float t = std::ceil((float) camX / scale);
for(int64_t x = ZERO_TIME_BASE + mod<int64_t>(-camX, scale); x < GetSize().x; x += scale) { for(int64_t x = ZERO_TIME_BASE + mod<int64_t>(-camX, scale); x < GetSize().x; x += scale) {
dc.DrawLine(x, 0, x, 10); dc.DrawLine(x, 0 - camY, x, 10 - camY);
dc.DrawText(wxString::Format("%gs", t), x + 4, 0); dc.DrawText(wxString::Format("%gs", t), x + 4, 0 - camY);
t++; t++;
} }
auto kfsList = &frame->graph->backendNG->keyframesList; for(Timeline::Row &row : rows) {
for(size_t kfsIdx = 0; kfsIdx < kfsList->count; kfsIdx++) { if(row.type == Timeline::Row::KEYFRAMES) {
CHiKeyframes *kfs = row.kfs;
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;
for(size_t kfIdx = 0; kfIdx < kfs->count; kfIdx++) { dc.DrawBitmap(bmp, ZERO_TIME_BASE - camX + scale * kfs->times[kfIdx] - bmpKf.GetWidth() / 2, row.y - camY);
wxBitmap &bmp = kfIdx == kfs->count - 1 && kfs->extrapolationMode != CUTIHI_EXTRAPOLATION_NONE ? bmpKfExtrap : bmpKf; }
} else if(row.type == Timeline::Row::NODE_LIFESPAN) {
GrNode *gn = row.gn;
dc.DrawBitmap(bmp, ZERO_TIME_BASE - camX + scale * kfs->times[kfIdx] - bmpKf.GetWidth() / 2, bmpKf.GetHeight() * (kfsIdx + 1)); 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<int32_t>(0, start - camX);
int32_t x2 = ZERO_TIME_BASE + std::max<int32_t>(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);
} }
} }

View File

@ -5,13 +5,36 @@
#include<wx/panel.h> #include<wx/panel.h>
struct Frame; struct Frame;
struct GrNode;
struct CHiKeyframes;
struct Timeline : wxPanel { struct Timeline : wxPanel {
int64_t camX = 0; int64_t camX = 0;
int64_t camY = 0;
int64_t mouseX = 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<Row> rows;
enum class CaptureMode { enum class CaptureMode {
CAM, KF CAM, KF
@ -24,7 +47,11 @@ struct Timeline : wxPanel {
void Paint(wxPaintEvent&); 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 #endif