#include #include "multi-canvas-source.h" struct multi_canvas_info { obs_source_t *source; uint32_t width; uint32_t height; DARRAY(obs_canvas_t *) canvas; DARRAY(uint32_t) widths; DARRAY(uint32_t) heights; DARRAY(gs_texrender_t *) renders; }; const char *multi_canvas_get_name(void *type_data) { UNUSED_PARAMETER(type_data); return "vertical_multi_canvas"; } void *multi_canvas_create(obs_data_t *settings, obs_source_t *source) { UNUSED_PARAMETER(settings); struct multi_canvas_info *multi_canvas = bzalloc(sizeof(struct multi_canvas_info)); multi_canvas->source = source; return multi_canvas; } void multi_canvas_destroy(void *data) { struct multi_canvas_info *mc = data; da_free(mc->canvas); da_free(mc->widths); da_free(mc->heights); for (size_t i = 0; i < mc->renders.num; i++) { gs_texrender_destroy(mc->renders.array[i]); } da_free(mc->renders); bfree(data); } bool view_get_width(void *data, struct obs_video_info *ovi) { uint32_t *width = data; if (ovi->base_width > *width) *width = ovi->base_width; return true; } bool view_get_height(void *data, struct obs_video_info *ovi) { uint32_t *height = data; if (ovi->base_height > *height) *height = ovi->base_height; return true; } static void multi_canvas_video_render(void *data, gs_effect_t *effect) { struct multi_canvas_info *mc = data; gs_matrix_push(); for (uint32_t i = 0; i < MAX_CHANNELS; i++) { obs_source_t *s = obs_get_output_source(i); if (!s) continue; if (obs_source_removed(s)) { obs_source_release(s); continue; } gs_matrix_push(); gs_blend_state_push(); obs_source_video_render(s); gs_blend_state_pop(); gs_matrix_pop(); obs_source_release(s); } struct obs_video_info ovi; obs_get_video_info(&ovi); gs_matrix_translate3f((float)ovi.base_width, 0.0f, 0.0f); effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); for (size_t i = 0; i < mc->canvas.num; i++) { obs_canvas_t *canvas = mc->canvas.array[i]; const enum gs_color_format format = gs_get_format_from_space(gs_get_color_space()); if (gs_texrender_get_format(mc->renders.array[i]) != format) { gs_texrender_destroy(mc->renders.array[i]); mc->renders.array[i] = gs_texrender_create(format, GS_ZS_NONE); } gs_texrender_reset(mc->renders.array[i]); gs_blend_state_push(); gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); if (gs_texrender_begin_with_color_space(mc->renders.array[i], mc->widths.array[i], mc->heights.array[i], gs_get_color_space())) { struct vec4 clear_color; vec4_zero(&clear_color); gs_clear(GS_CLEAR_COLOR, &clear_color, 0.0f, 0); gs_ortho(0.0f, (float)mc->widths.array[i], 0.0f, (float)mc->heights.array[i], -100.0f, 100.0f); obs_canvas_render(canvas); gs_texrender_end(mc->renders.array[i]); } gs_blend_state_pop(); gs_texture_t *tex = gs_texrender_get_texture(mc->renders.array[i]); if (tex) { const bool previous = gs_framebuffer_srgb_enabled(); gs_enable_framebuffer_srgb(true); gs_effect_set_texture_srgb(gs_effect_get_param_by_name(effect, "image"), tex); while (gs_effect_loop(effect, "Draw")) gs_draw_sprite(tex, 0, mc->widths.array[i], mc->heights.array[i]); gs_enable_framebuffer_srgb(previous); } gs_matrix_translate3f((float)mc->widths.array[i], 0.0f, 0.0f); } gs_matrix_pop(); } uint32_t multi_canvas_get_width(void *data) { struct multi_canvas_info *mc = data; return mc->width; } uint32_t multi_canvas_get_height(void *data) { struct multi_canvas_info *mc = data; return mc->height; } void multi_canvas_update_size(struct multi_canvas_info *mc) { struct obs_video_info ovi; obs_get_video_info(&ovi); uint32_t width = ovi.base_width; uint32_t height = ovi.base_height; for (size_t i = 0; i < mc->widths.num; i++) { width += mc->widths.array[i]; if (mc->heights.array[i] > height) height = mc->heights.array[i]; } mc->width = width; mc->height = height; } void multi_canvas_source_add_canvas(void *data, obs_canvas_t *canvas, uint32_t width, uint32_t height) { struct multi_canvas_info *mc = data; for (size_t i = 0; i < mc->canvas.num; i++) { if (mc->canvas.array[i] == canvas) return; } da_push_back(mc->widths, &width); da_push_back(mc->heights, &height); da_push_back(mc->canvas, &canvas); gs_texrender_t *render = gs_texrender_create(GS_RGBA, GS_ZS_NONE); da_push_back(mc->renders, &render); multi_canvas_update_size(mc); } void multi_canvas_source_remove_canvas(void *data, obs_canvas_t *canvas) { struct multi_canvas_info *mc = data; for (size_t i = 0; i < mc->canvas.num; i++) { if (mc->canvas.array[i] == canvas) { gs_texrender_destroy(mc->renders.array[i]); da_erase(mc->canvas, i); da_erase(mc->widths, i); da_erase(mc->heights, i); da_erase(mc->renders, i); break; } } multi_canvas_update_size(mc); } struct obs_source_info multi_canvas_source = { .id = "vertical_multi_canvas_source", .type = OBS_SOURCE_TYPE_INPUT, .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CAP_DISABLED | OBS_SOURCE_CUSTOM_DRAW, .get_name = multi_canvas_get_name, .create = multi_canvas_create, .destroy = multi_canvas_destroy, .video_render = multi_canvas_video_render, .get_width = multi_canvas_get_width, .get_height = multi_canvas_get_height, };