From bf27941f5f04aa52750b34c9a204f4e7bab9bf5d Mon Sep 17 00:00:00 2001 From: jpark37 Date: Tue, 6 Jul 2021 22:10:57 -0700 Subject: [PATCH] libobs-winrt, win-capture: Linear SRGB support Update window, display, and game capture to always bilinear filter in linear space, even if the source texture is not SRGB typed. This helps resolve confusion in situations where we were filtering in nonlinear space vs. linear space, like when toggling an empty crop filter. --- libobs-winrt/winrt-capture.cpp | 24 +- libobs-winrt/winrt-capture.h | 3 +- plugins/win-capture/dc-capture.c | 51 +++-- plugins/win-capture/dc-capture.h | 4 +- .../win-capture/duplicator-monitor-capture.c | 91 ++++---- plugins/win-capture/game-capture.c | 207 +++++++++++++----- plugins/win-capture/monitor-capture.c | 2 +- plugins/win-capture/window-capture.c | 10 +- 8 files changed, 263 insertions(+), 129 deletions(-) diff --git a/libobs-winrt/winrt-capture.cpp b/libobs-winrt/winrt-capture.cpp index 5a0ce8fe2..d97b4b2aa 100644 --- a/libobs-winrt/winrt-capture.cpp +++ b/libobs-winrt/winrt-capture.cpp @@ -564,24 +564,20 @@ extern "C" EXPORT void winrt_capture_free(struct winrt_capture *capture) } } -static void draw_texture(struct winrt_capture *capture, gs_effect_t *effect) +static void draw_texture(struct winrt_capture *capture) { - gs_texture_t *const texture = capture->texture; + gs_effect_t *const effect = obs_get_base_effect(OBS_EFFECT_OPAQUE); gs_technique_t *tech = gs_effect_get_technique(effect, "Draw"); gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image"); - size_t passes; - - const bool linear_srgb = gs_get_linear_srgb(); const bool previous = gs_framebuffer_srgb_enabled(); - gs_enable_framebuffer_srgb(linear_srgb); + gs_enable_framebuffer_srgb(true); + gs_enable_blending(false); - if (linear_srgb) - gs_effect_set_texture_srgb(image, texture); - else - gs_effect_set_texture(image, texture); + gs_texture_t *const texture = capture->texture; + gs_effect_set_texture_srgb(image, texture); - passes = gs_technique_begin(tech); + const size_t passes = gs_technique_begin(tech); for (size_t i = 0; i < passes; i++) { if (gs_technique_begin_pass(tech, i)) { gs_draw_sprite(texture, 0, 0, 0); @@ -591,6 +587,7 @@ static void draw_texture(struct winrt_capture *capture, gs_effect_t *effect) } gs_technique_end(tech); + gs_enable_blending(true); gs_enable_framebuffer_srgb(previous); } @@ -627,11 +624,10 @@ extern "C" EXPORT BOOL winrt_capture_show_cursor(struct winrt_capture *capture, return success; } -extern "C" EXPORT void winrt_capture_render(struct winrt_capture *capture, - gs_effect_t *effect) +extern "C" EXPORT void winrt_capture_render(struct winrt_capture *capture) { if (capture->texture_written) - draw_texture(capture, effect); + draw_texture(capture); } extern "C" EXPORT uint32_t diff --git a/libobs-winrt/winrt-capture.h b/libobs-winrt/winrt-capture.h index 06252b204..9f2ccb4d5 100644 --- a/libobs-winrt/winrt-capture.h +++ b/libobs-winrt/winrt-capture.h @@ -20,8 +20,7 @@ EXPORT void winrt_capture_free(struct winrt_capture *capture); EXPORT BOOL winrt_capture_active(const struct winrt_capture *capture); EXPORT BOOL winrt_capture_show_cursor(struct winrt_capture *capture, BOOL visible); -EXPORT void winrt_capture_render(struct winrt_capture *capture, - gs_effect_t *effect); +EXPORT void winrt_capture_render(struct winrt_capture *capture); EXPORT uint32_t winrt_capture_width(const struct winrt_capture *capture); EXPORT uint32_t winrt_capture_height(const struct winrt_capture *capture); diff --git a/plugins/win-capture/dc-capture.c b/plugins/win-capture/dc-capture.c index 7cb317225..a8839ba89 100644 --- a/plugins/win-capture/dc-capture.c +++ b/plugins/win-capture/dc-capture.c @@ -5,14 +5,27 @@ static inline void init_textures(struct dc_capture *capture) { - if (capture->compatibility) + if (capture->compatibility) { capture->texture = gs_texture_create(capture->width, capture->height, GS_BGRA, 1, NULL, GS_DYNAMIC); - else + } else { capture->texture = gs_texture_create_gdi(capture->width, capture->height); + if (capture->texture) { + capture->extra_texture = gs_texture_create( + capture->width, capture->height, GS_BGRA, 1, + NULL, 0); + if (!capture->extra_texture) { + blog(LOG_WARNING, "[dc_capture_init] Failed to " + "create textures"); + gs_texture_destroy(capture->texture); + capture->texture = NULL; + } + } + } + if (!capture->texture) { blog(LOG_WARNING, "[dc_capture_init] Failed to " "create textures"); @@ -73,6 +86,7 @@ void dc_capture_free(struct dc_capture *capture) } obs_enter_graphics(); + gs_texture_destroy(capture->extra_texture); gs_texture_destroy(capture->texture); obs_leave_graphics(); @@ -165,41 +179,48 @@ void dc_capture_capture(struct dc_capture *capture, HWND window) capture->texture_written = true; } -static void draw_texture(struct dc_capture *capture, gs_effect_t *effect) +static void draw_texture(struct dc_capture *capture, bool texcoords_centered) { - gs_texture_t *texture = capture->texture; + gs_effect_t *effect = obs_get_base_effect(OBS_EFFECT_OPAQUE); gs_technique_t *tech = gs_effect_get_technique(effect, "Draw"); gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image"); - size_t passes; - const bool linear_srgb = gs_get_linear_srgb() && capture->compatibility; + gs_texture_t *texture = capture->texture; + const bool compatibility = capture->compatibility; + bool linear_sample = compatibility; + if (!linear_sample && !texcoords_centered) { + gs_texture_t *const extra_texture = capture->extra_texture; + gs_copy_texture(extra_texture, texture); + texture = extra_texture; + linear_sample = true; + } const bool previous = gs_framebuffer_srgb_enabled(); - gs_enable_framebuffer_srgb(linear_srgb); + gs_enable_framebuffer_srgb(linear_sample); + gs_enable_blending(false); - if (linear_srgb) + if (linear_sample) gs_effect_set_texture_srgb(image, texture); else gs_effect_set_texture(image, texture); - passes = gs_technique_begin(tech); + const uint32_t flip = compatibility ? GS_FLIP_V : 0; + const size_t passes = gs_technique_begin(tech); for (size_t i = 0; i < passes; i++) { if (gs_technique_begin_pass(tech, i)) { - if (capture->compatibility) - gs_draw_sprite(texture, GS_FLIP_V, 0, 0); - else - gs_draw_sprite(texture, 0, 0, 0); + gs_draw_sprite(texture, flip, 0, 0); gs_technique_end_pass(tech); } } gs_technique_end(tech); + gs_enable_blending(true); gs_enable_framebuffer_srgb(previous); } -void dc_capture_render(struct dc_capture *capture, gs_effect_t *effect) +void dc_capture_render(struct dc_capture *capture, bool texcoords_centered) { if (capture->valid && capture->texture_written) - draw_texture(capture, effect); + draw_texture(capture, texcoords_centered); } diff --git a/plugins/win-capture/dc-capture.h b/plugins/win-capture/dc-capture.h index 8ff4bc3e2..15c08090f 100644 --- a/plugins/win-capture/dc-capture.h +++ b/plugins/win-capture/dc-capture.h @@ -7,6 +7,7 @@ struct dc_capture { gs_texture_t *texture; + gs_texture_t *extra_texture; bool texture_written; int x, y; uint32_t width; @@ -31,4 +32,5 @@ extern void dc_capture_init(struct dc_capture *capture, int x, int y, extern void dc_capture_free(struct dc_capture *capture); extern void dc_capture_capture(struct dc_capture *capture, HWND window); -extern void dc_capture_render(struct dc_capture *capture, gs_effect_t *effect); +extern void dc_capture_render(struct dc_capture *capture, + bool texcoords_centered); diff --git a/plugins/win-capture/duplicator-monitor-capture.c b/plugins/win-capture/duplicator-monitor-capture.c index ad02b4b4c..fc9ee6587 100644 --- a/plugins/win-capture/duplicator-monitor-capture.c +++ b/plugins/win-capture/duplicator-monitor-capture.c @@ -39,8 +39,7 @@ typedef struct winrt_capture *(*PFN_winrt_capture_init_monitor)( typedef void (*PFN_winrt_capture_free)(struct winrt_capture *capture); typedef BOOL (*PFN_winrt_capture_active)(const struct winrt_capture *capture); -typedef void (*PFN_winrt_capture_render)(struct winrt_capture *capture, - gs_effect_t *effect); +typedef void (*PFN_winrt_capture_render)(struct winrt_capture *capture); typedef uint32_t (*PFN_winrt_capture_width)(const struct winrt_capture *capture); typedef uint32_t (*PFN_winrt_capture_height)( const struct winrt_capture *capture); @@ -488,18 +487,18 @@ static void draw_cursor(struct duplicator_capture *capture) capture->rot % 180 == 0 ? capture->height : capture->width); } -static void duplicator_capture_render(void *data, gs_effect_t *effect) +static void duplicator_capture_render(void *data, gs_effect_t *unused) { + UNUSED_PARAMETER(unused); + struct duplicator_capture *capture = data; if (capture->method == METHOD_WGC) { - gs_effect_t *const opaque = - obs_get_base_effect(OBS_EFFECT_OPAQUE); if (capture->capture_winrt) { if (capture->exports.winrt_capture_active( capture->capture_winrt)) { capture->exports.winrt_capture_render( - capture->capture_winrt, opaque); + capture->capture_winrt); } else { capture->exports.winrt_capture_free( capture->capture_winrt); @@ -507,54 +506,62 @@ static void duplicator_capture_render(void *data, gs_effect_t *effect) } } } else { - gs_texture_t *texture; - int rot; - if (!capture->duplicator) return; - texture = gs_duplicator_get_texture(capture->duplicator); + gs_texture_t *const texture = + gs_duplicator_get_texture(capture->duplicator); if (!texture) return; - effect = obs_get_base_effect(OBS_EFFECT_OPAQUE); + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(true); + gs_enable_blending(false); - rot = capture->rot; + const int rot = capture->rot; + if (rot != 0) { + float x = 0.0f; + float y = 0.0f; - while (gs_effect_loop(effect, "Draw")) { - if (rot != 0) { - float x = 0.0f; - float y = 0.0f; - - switch (rot) { - case 90: - x = (float)capture->height; - break; - case 180: - x = (float)capture->width; - y = (float)capture->height; - break; - case 270: - y = (float)capture->width; - break; - } - - gs_matrix_push(); - gs_matrix_translate3f(x, y, 0.0f); - gs_matrix_rotaa4f(0.0f, 0.0f, 1.0f, - RAD((float)rot)); + switch (rot) { + case 90: + x = (float)capture->height; + break; + case 180: + x = (float)capture->width; + y = (float)capture->height; + break; + case 270: + y = (float)capture->width; + break; } - obs_source_draw(texture, 0, 0, 0, 0, false); - - if (rot != 0) - gs_matrix_pop(); + gs_matrix_push(); + gs_matrix_translate3f(x, y, 0.0f); + gs_matrix_rotaa4f(0.0f, 0.0f, 1.0f, RAD((float)rot)); } - if (capture->capture_cursor) { - effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); + gs_effect_t *const opaque_effect = + obs_get_base_effect(OBS_EFFECT_OPAQUE); + while (gs_effect_loop(opaque_effect, "Draw")) { + gs_eparam_t *image = gs_effect_get_param_by_name( + opaque_effect, "image"); + gs_effect_set_texture_srgb(image, texture); - while (gs_effect_loop(effect, "Draw")) { + gs_draw_sprite(texture, 0, 0, 0); + } + + if (rot != 0) + gs_matrix_pop(); + + gs_enable_blending(true); + gs_enable_framebuffer_srgb(previous); + + if (capture->capture_cursor) { + gs_effect_t *const default_effect = + obs_get_base_effect(OBS_EFFECT_DEFAULT); + + while (gs_effect_loop(default_effect, "Draw")) { draw_cursor(capture); } } @@ -670,7 +677,7 @@ struct obs_source_info duplicator_capture_info = { .id = "monitor_capture", .type = OBS_SOURCE_TYPE_INPUT, .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | - OBS_SOURCE_DO_NOT_DUPLICATE, + OBS_SOURCE_DO_NOT_DUPLICATE | OBS_SOURCE_SRGB, .get_name = duplicator_capture_getname, .create = duplicator_capture_create, .destroy = duplicator_capture_destroy, diff --git a/plugins/win-capture/game-capture.c b/plugins/win-capture/game-capture.c index c18371aeb..abc2a9896 100644 --- a/plugins/win-capture/game-capture.c +++ b/plugins/win-capture/game-capture.c @@ -149,7 +149,9 @@ struct game_capture { ipc_pipe_server_t pipe; gs_texture_t *texture; - bool supports_srgb; + gs_texture_t *extra_texture; + gs_texrender_t *extra_texrender; + bool linear_sample; struct hook_info *global_hook_info; HANDLE keepalive_mutex; HANDLE hook_init; @@ -332,12 +334,14 @@ static void stop_capture(struct game_capture *gc) close_handle(&gc->texture_mutexes[0]); close_handle(&gc->texture_mutexes[1]); - if (gc->texture) { - obs_enter_graphics(); - gs_texture_destroy(gc->texture); - obs_leave_graphics(); - gc->texture = NULL; - } + obs_enter_graphics(); + gs_texrender_destroy(gc->extra_texrender); + gs_texture_destroy(gc->extra_texture); + gs_texture_destroy(gc->texture); + obs_leave_graphics(); + gc->extra_texrender = NULL; + gc->extra_texture = NULL; + gc->texture = NULL; if (gc->active) info("capture stopped"); @@ -1552,49 +1556,104 @@ static inline bool is_16bit_format(uint32_t format) static inline bool init_shmem_capture(struct game_capture *gc) { - enum gs_color_format format; - - gc->texture_buffers[0] = - (uint8_t *)gc->data + gc->shmem_data->tex1_offset; - gc->texture_buffers[1] = - (uint8_t *)gc->data + gc->shmem_data->tex2_offset; - - gc->convert_16bit = is_16bit_format(gc->global_hook_info->format); - format = gc->convert_16bit - ? GS_BGRA - : convert_format(gc->global_hook_info->format); + const uint32_t dxgi_format = gc->global_hook_info->format; + const bool convert_16bit = is_16bit_format(dxgi_format); + const enum gs_color_format format = + convert_16bit ? GS_BGRA : convert_format(dxgi_format); obs_enter_graphics(); + gs_texrender_destroy(gc->extra_texrender); + gs_texture_destroy(gc->extra_texture); gs_texture_destroy(gc->texture); - gc->texture = + gs_texture_t *const texture = gs_texture_create(gc->cx, gc->cy, format, 1, NULL, GS_DYNAMIC); obs_leave_graphics(); - if (!gc->texture) { + bool success = texture != NULL; + if (success) { + const bool linear_sample = format != GS_R10G10B10A2; + + gs_texrender_t *extra_texrender = NULL; + if (!linear_sample) { + extra_texrender = + gs_texrender_create(GS_BGRA, GS_ZS_NONE); + success = extra_texrender != NULL; + if (!success) + warn("init_shmem_capture: failed to create extra texrender"); + } + + if (success) { + gc->texture_buffers[0] = (uint8_t *)gc->data + + gc->shmem_data->tex1_offset; + gc->texture_buffers[1] = (uint8_t *)gc->data + + gc->shmem_data->tex2_offset; + gc->convert_16bit = convert_16bit; + + gc->texture = texture; + gc->extra_texture = NULL; + gc->extra_texrender = extra_texrender; + gc->linear_sample = linear_sample; + gc->copy_texture = copy_shmem_tex; + } else { + gs_texture_destroy(texture); + } + } else { warn("init_shmem_capture: failed to create texture"); - return false; } - gc->supports_srgb = true; - gc->copy_texture = copy_shmem_tex; - return true; + return success; } static inline bool init_shtex_capture(struct game_capture *gc) { obs_enter_graphics(); + gs_texrender_destroy(gc->extra_texrender); + gs_texture_destroy(gc->extra_texture); gs_texture_destroy(gc->texture); - gc->texture = gs_texture_open_shared(gc->shtex_data->tex_handle); - enum gs_color_format format = gs_texture_get_color_format(gc->texture); - gc->supports_srgb = gs_is_srgb_format(format); + gs_texture_t *const texture = + gs_texture_open_shared(gc->shtex_data->tex_handle); + bool success = texture != NULL; + if (success) { + enum gs_color_format format = + gs_texture_get_color_format(texture); + const bool ten_bit_srgb = (format == GS_R10G10B10A2); + enum gs_color_format linear_format = + ten_bit_srgb ? GS_BGRA : gs_generalize_format(format); + const bool linear_sample = (linear_format == format); + gs_texture_t *extra_texture = NULL; + gs_texrender_t *extra_texrender = NULL; + if (!linear_sample) { + if (ten_bit_srgb) { + extra_texrender = gs_texrender_create( + linear_format, GS_ZS_NONE); + success = extra_texrender != NULL; + if (!success) + warn("init_shtex_capture: failed to create extra texrender"); + } else { + extra_texture = gs_texture_create( + gs_texture_get_width(texture), + gs_texture_get_height(texture), + linear_format, 1, NULL, 0); + success = extra_texture != NULL; + if (!success) + warn("init_shtex_capture: failed to create extra texture"); + } + } + + if (success) { + gc->texture = texture; + gc->linear_sample = linear_sample; + gc->extra_texture = extra_texture; + gc->extra_texrender = extra_texrender; + } else { + gs_texture_destroy(texture); + } + } else { + warn("init_shtex_capture: failed to open shared handle"); + } obs_leave_graphics(); - if (!gc->texture) { - warn("init_shtex_capture: failed to open shared handle"); - return false; - } - - return true; + return success; } static bool start_capture(struct game_capture *gc) @@ -1802,36 +1861,86 @@ static inline void game_capture_render_cursor(struct game_capture *gc) gc->global_hook_info->cy); } -static void game_capture_render(void *data, gs_effect_t *effect) +static void game_capture_render(void *data, gs_effect_t *unused) { + UNUSED_PARAMETER(unused); + struct game_capture *gc = data; if (!gc->texture || !gc->active) return; - effect = obs_get_base_effect(gc->config.allow_transparency - ? OBS_EFFECT_DEFAULT - : OBS_EFFECT_OPAQUE); + const bool allow_transparency = gc->config.allow_transparency; + gs_effect_t *const effect = obs_get_base_effect( + allow_transparency ? OBS_EFFECT_DEFAULT : OBS_EFFECT_OPAQUE); - const bool linear_srgb = gs_get_linear_srgb() && gc->supports_srgb; - const bool previous = gs_set_linear_srgb(linear_srgb); + bool linear_sample = gc->linear_sample; + gs_texture_t *texture = gc->texture; + if (!linear_sample && !obs_source_get_texcoords_centered(gc->source)) { + gs_texture_t *const extra_texture = gc->extra_texture; + if (extra_texture) { + gs_copy_texture(extra_texture, texture); + texture = extra_texture; + } else { + gs_texrender_t *const texrender = gc->extra_texrender; + gs_texrender_reset(texrender); + const uint32_t cx = gs_texture_get_width(texture); + const uint32_t cy = gs_texture_get_height(texture); + if (gs_texrender_begin(texrender, cx, cy)) { + gs_effect_t *const default_effect = + obs_get_base_effect(OBS_EFFECT_DEFAULT); + const bool previous = + gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(false); + gs_enable_blending(false); + gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, + -100.0f, 100.0f); + gs_eparam_t *const image = + gs_effect_get_param_by_name( + default_effect, "image"); + gs_effect_set_texture(image, texture); + while (gs_effect_loop(default_effect, "Draw")) { + gs_draw_sprite(texture, 0, 0, 0); + } + gs_enable_blending(true); + gs_enable_framebuffer_srgb(previous); - while (gs_effect_loop(effect, "Draw")) { - obs_source_draw(gc->texture, 0, 0, 0, 0, - gc->global_hook_info->flip); + gs_texrender_end(texrender); - if (gc->config.allow_transparency && gc->config.cursor && + texture = gs_texrender_get_texture(texrender); + } + } + + linear_sample = true; + } + + gs_eparam_t *const image = gs_effect_get_param_by_name(effect, "image"); + const uint32_t flip = gc->global_hook_info->flip ? GS_FLIP_V : 0; + const char *tech_name = allow_transparency && !linear_sample + ? "DrawSrgbDecompress" + : "Draw"; + while (gs_effect_loop(effect, tech_name)) { + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(allow_transparency || linear_sample); + gs_enable_blending(allow_transparency); + if (linear_sample) + gs_effect_set_texture_srgb(image, texture); + else + gs_effect_set_texture(image, texture); + gs_draw_sprite(texture, flip, 0, 0); + gs_enable_blending(true); + gs_enable_framebuffer_srgb(previous); + + if (allow_transparency && gc->config.cursor && !gc->cursor_hidden) { game_capture_render_cursor(gc); } } - gs_set_linear_srgb(previous); + if (!allow_transparency && gc->config.cursor && !gc->cursor_hidden) { + gs_effect_t *const default_effect = + obs_get_base_effect(OBS_EFFECT_DEFAULT); - if (!gc->config.allow_transparency && gc->config.cursor && - !gc->cursor_hidden) { - effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); - - while (gs_effect_loop(effect, "Draw")) { + while (gs_effect_loop(default_effect, "Draw")) { game_capture_render_cursor(gc); } } diff --git a/plugins/win-capture/monitor-capture.c b/plugins/win-capture/monitor-capture.c index 88e0f84c3..0e12345fe 100644 --- a/plugins/win-capture/monitor-capture.c +++ b/plugins/win-capture/monitor-capture.c @@ -152,7 +152,7 @@ static void monitor_capture_render(void *data, gs_effect_t *effect) { struct monitor_capture *capture = data; dc_capture_render(&capture->data, - obs_get_base_effect(OBS_EFFECT_OPAQUE)); + obs_source_get_texcoords_centered(capture->source)); UNUSED_PARAMETER(effect); } diff --git a/plugins/win-capture/window-capture.c b/plugins/win-capture/window-capture.c index 72171f6b7..c9b1cf8b1 100644 --- a/plugins/win-capture/window-capture.c +++ b/plugins/win-capture/window-capture.c @@ -37,8 +37,7 @@ typedef void (*PFN_winrt_capture_free)(struct winrt_capture *capture); typedef BOOL (*PFN_winrt_capture_active)(const struct winrt_capture *capture); typedef BOOL (*PFN_winrt_capture_show_cursor)(struct winrt_capture *capture, BOOL visible); -typedef void (*PFN_winrt_capture_render)(struct winrt_capture *capture, - gs_effect_t *effect); +typedef void (*PFN_winrt_capture_render)(struct winrt_capture *capture); typedef uint32_t (*PFN_winrt_capture_width)(const struct winrt_capture *capture); typedef uint32_t (*PFN_winrt_capture_height)( const struct winrt_capture *capture); @@ -574,13 +573,12 @@ static void wc_tick(void *data, float seconds) static void wc_render(void *data, gs_effect_t *effect) { struct window_capture *wc = data; - gs_effect_t *const opaque = obs_get_base_effect(OBS_EFFECT_OPAQUE); if (wc->method == METHOD_WGC) { if (wc->capture_winrt) { if (wc->exports.winrt_capture_active( wc->capture_winrt)) { wc->exports.winrt_capture_render( - wc->capture_winrt, opaque); + wc->capture_winrt); } else { wc->exports.winrt_capture_free( wc->capture_winrt); @@ -588,7 +586,9 @@ static void wc_render(void *data, gs_effect_t *effect) } } } else { - dc_capture_render(&wc->capture, opaque); + dc_capture_render( + &wc->capture, + obs_source_get_texcoords_centered(wc->source)); } UNUSED_PARAMETER(effect);