libobs: Add color space management

This provides the framework for automatically compositing SDR and HDR
sources together. Source will need to leverage the new
video_get_color_space to opt into HDR support.
master
jpark37 2022-03-16 01:41:00 -07:00 committed by Jim
parent a8bc994f07
commit 525f964b3d
7 changed files with 568 additions and 129 deletions

View File

@ -502,6 +502,19 @@ Source Definition Structure (obs_source_info)
- **OBS_MEDIA_STATE_ENDED** - Ended
- **OBS_MEDIA_STATE_ERROR** - Error
.. member:: enum gs_color_space (*obs_source_info.video_get_color_space)(void *data, size_t count, const enum gs_color_space *preferred_spaces)
Returns the color space of the source. Assume GS_CS_SRGB if not
implemented.
There's an optimization an SDR source can do when rendering to HDR.
Check if the active space is GS_CS_709_EXTENDED, and return
GS_CS_709_EXTENDED instead of GS_CS_SRGB to avoid an redundant
conversion. This optimization can only be done if the pixel shader
outputs linear 709, which is why it's not performed by default.
:return: The color space of the video
.. _source_signal_handler_reference:
@ -873,6 +886,18 @@ General Source Functions
---------------------
.. function:: enum gs_color_space obs_source_get_color_space(obs_source_t *source, size_t count, const enum gs_color_space *preferred_spaces)
Calls the :c:member:`obs_source_info.video_get_color_space` of the
source to get its color space. Assumes GS_CS_SRGB if not implemented.
Disabled filters are skipped, and async video sources can figure out
the color space for themselves.
:return: The color space of the source
---------------------
.. function:: bool obs_source_get_texcoords_centered(obs_source_t *source)
Hints whether or not the source will blend texels.
@ -1396,6 +1421,16 @@ Functions used by filters
---------------------
.. function:: bool obs_source_process_filter_begin_with_color_space(obs_source_t *filter, enum gs_color_format format, enum gs_color_space space, enum obs_allow_direct_render allow_direct)
Similar to obs_source_process_filter_begin, but also set the active
color space.
:return: *true* if filtering should continue, *false* if the filter
is bypassed for whatever reason
---------------------
.. function:: void obs_source_process_filter_end(obs_source_t *filter, gs_effect_t *effect, uint32_t width, uint32_t height)
Draws the filter using the effect's "Draw" technique.
@ -1538,6 +1573,16 @@ Functions used by transitions
---------------------
.. function:: enum gs_color_space obs_transition_video_get_color_space(obs_source_t *transition)
Figure out the color space that encompasses both child sources.
The wider space wins.
:return: The color space of the transition
---------------------
.. function:: bool obs_transition_audio_render(obs_source_t *transition, uint64_t *ts_out, struct obs_source_audio_mix *audio, uint32_t mixers, size_t channels, size_t sample_rate, obs_transition_audio_mix_callback_t mix_a_callback, obs_transition_audio_mix_callback_t mix_b_callback)
Helper function used for transitioning audio. Typically you'd call

View File

@ -762,6 +762,7 @@ struct obs_source {
gs_texrender_t *filter_texrender;
enum obs_allow_direct_render allow_direct;
bool rendering_filter;
bool filter_bypass_active;
/* sources specific hotkeys */
obs_hotkey_pair_id mute_unmute_key;
@ -803,6 +804,9 @@ struct obs_source {
enum obs_transition_scale_type transition_scale_type;
struct matrix4 transition_matrices[2];
/* color space */
gs_texrender_t *color_space_texrender;
struct audio_monitor *monitor;
enum obs_monitoring_type monitoring_type;

View File

@ -506,18 +506,6 @@ static void update_item_transform(struct obs_scene_item *item, bool update_tex)
if (!update_tex)
return;
if (item->item_render && !item_texture_enabled(item)) {
obs_enter_graphics();
gs_texrender_destroy(item->item_render);
item->item_render = NULL;
obs_leave_graphics();
} else if (!item->item_render && item_texture_enabled(item)) {
obs_enter_graphics();
item->item_render = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
obs_leave_graphics();
}
os_atomic_set_bool(&item->update_transform, false);
}
@ -556,7 +544,9 @@ static inline bool item_texture_enabled(const struct obs_scene_item *item)
(item_is_scene(item) && !item->is_group);
}
static void render_item_texture(struct obs_scene_item *item)
static void render_item_texture(struct obs_scene_item *item,
enum gs_color_space current_space,
enum gs_color_space source_space)
{
gs_texture_t *tex = gs_texrender_get_texture(item->item_render);
if (!tex) {
@ -570,8 +560,8 @@ static void render_item_texture(struct obs_scene_item *item)
enum obs_scale_type type = item->scale_filter;
uint32_t cx = gs_texture_get_width(tex);
uint32_t cy = gs_texture_get_height(tex);
const char *tech = "Draw";
bool upscale = false;
if (type != OBS_SCALE_DISABLE) {
if (type == OBS_SCALE_POINT) {
gs_eparam_t *image =
@ -581,9 +571,6 @@ static void render_item_texture(struct obs_scene_item *item)
} else if (!close_float(item->output_scale.x, 1.0f, EPSILON) ||
!close_float(item->output_scale.y, 1.0f, EPSILON)) {
gs_eparam_t *scale_param;
gs_eparam_t *scale_i_param;
if (item->output_scale.x < 0.5f ||
item->output_scale.y < 0.5f) {
effect = obs->video.bilinear_lowres_effect;
@ -593,21 +580,22 @@ static void render_item_texture(struct obs_scene_item *item)
effect = obs->video.lanczos_effect;
} else if (type == OBS_SCALE_AREA) {
effect = obs->video.area_effect;
if ((item->output_scale.x >= 1.0f) &&
(item->output_scale.y >= 1.0f))
tech = "DrawUpscale";
upscale = (item->output_scale.x >= 1.0f) &&
(item->output_scale.y >= 1.0f);
}
scale_param = gs_effect_get_param_by_name(
effect, "base_dimension");
gs_eparam_t *const scale_param =
gs_effect_get_param_by_name(effect,
"base_dimension");
if (scale_param) {
struct vec2 base_res = {(float)cx, (float)cy};
gs_effect_set_vec2(scale_param, &base_res);
}
scale_i_param = gs_effect_get_param_by_name(
effect, "base_dimension_i");
gs_eparam_t *const scale_i_param =
gs_effect_get_param_by_name(effect,
"base_dimension_i");
if (scale_i_param) {
struct vec2 base_res_i = {1.0f / (float)cx,
1.0f / (float)cy};
@ -617,6 +605,87 @@ static void render_item_texture(struct obs_scene_item *item)
}
}
float multiplier = 1.f;
switch (current_space) {
case GS_CS_709_SCRGB:
switch (source_space) {
case GS_CS_SRGB:
case GS_CS_709_EXTENDED:
multiplier = obs_get_video_sdr_white_level() / 80.f;
}
}
switch (source_space) {
case GS_CS_709_SCRGB:
switch (current_space) {
case GS_CS_SRGB:
case GS_CS_709_EXTENDED:
multiplier = 80.f / obs_get_video_sdr_white_level();
}
}
const char *tech_name = "Draw";
if (upscale) {
tech_name = "DrawUpscale";
switch (source_space) {
case GS_CS_SRGB:
switch (current_space) {
case GS_CS_709_SCRGB:
tech_name = "DrawUpscaleMultiply";
}
break;
case GS_CS_709_EXTENDED:
switch (current_space) {
case GS_CS_SRGB:
tech_name = "DrawUpscaleTonemap";
break;
case GS_CS_709_SCRGB:
tech_name = "DrawUpscaleMultiply";
}
break;
case GS_CS_709_SCRGB:
switch (current_space) {
case GS_CS_SRGB:
tech_name = "DrawUpscaleMultiplyTonemap";
break;
case GS_CS_709_EXTENDED:
tech_name = "DrawUpscaleMultiply";
}
}
} else {
switch (source_space) {
case GS_CS_SRGB:
switch (current_space) {
case GS_CS_709_SCRGB:
tech_name = "DrawMultiply";
}
break;
case GS_CS_709_EXTENDED:
switch (current_space) {
case GS_CS_SRGB:
tech_name = "DrawTonemap";
break;
case GS_CS_709_SCRGB:
tech_name = "DrawMultiply";
}
break;
case GS_CS_709_SCRGB:
switch (current_space) {
case GS_CS_SRGB:
tech_name = "DrawMultiplyTonemap";
break;
case GS_CS_709_EXTENDED:
tech_name = "DrawMultiply";
}
}
}
gs_eparam_t *const multiplier_param =
gs_effect_get_param_by_name(effect, "multiplier");
if (multiplier_param)
gs_effect_set_float(multiplier_param, multiplier);
gs_blend_state_push();
gs_blend_function_separate(
@ -626,7 +695,7 @@ static void render_item_texture(struct obs_scene_item *item)
obs_blend_mode_params[item->blend_type].dst_alpha);
gs_blend_op(obs_blend_mode_params[item->blend_type].op);
while (gs_effect_loop(effect, tech))
while (gs_effect_loop(effect, tech_name))
obs_source_draw(tex, 0, 0, 0, 0, 0);
gs_blend_state_pop();
@ -653,6 +722,26 @@ static inline void render_item(struct obs_scene_item *item)
GS_DEBUG_MARKER_BEGIN_FORMAT(GS_DEBUG_COLOR_ITEM, "Item: %s",
obs_source_get_name(item->source));
const bool use_texrender = item_texture_enabled(item);
obs_source_t *const source = item->source;
const enum gs_color_space current_space = gs_get_color_space();
const enum gs_color_space source_space =
obs_source_get_color_space(source, 1, &current_space);
const enum gs_color_format format =
gs_get_format_from_space(source_space);
if (item->item_render &&
(!use_texrender ||
(gs_texrender_get_format(item->item_render) != format))) {
gs_texrender_destroy(item->item_render);
item->item_render = NULL;
}
if (!item->item_render && use_texrender) {
item->item_render = gs_texrender_create(format, GS_ZS_NONE);
}
if (item->item_render) {
uint32_t width = obs_source_get_width(item->source);
uint32_t height = obs_source_get_height(item->source);
@ -664,7 +753,9 @@ static inline void render_item(struct obs_scene_item *item)
uint32_t cx = calc_cx(item, width);
uint32_t cy = calc_cy(item, height);
if (cx && cy && gs_texrender_begin(item->item_render, cx, cy)) {
if (cx && cy &&
gs_texrender_begin_with_color_space(item->item_render, cx,
cy, source_space)) {
float cx_scale = (float)width / (float)cx;
float cy_scale = (float)height / (float)cy;
struct vec4 clear_color;
@ -712,7 +803,7 @@ static inline void render_item(struct obs_scene_item *item)
gs_matrix_push();
gs_matrix_mul(&item->draw_transform);
if (item->item_render) {
render_item_texture(item);
render_item_texture(item, current_space, source_space);
} else if (item->user_visible &&
transition_active(item->show_transition)) {
const int cx = obs_source_get_width(item->source);
@ -980,18 +1071,6 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data)
obs_data_release(hide_data);
}
if (item->item_render && !item_texture_enabled(item)) {
obs_enter_graphics();
gs_texrender_destroy(item->item_render);
item->item_render = NULL;
obs_leave_graphics();
} else if (!item->item_render && item_texture_enabled(item)) {
obs_enter_graphics();
item->item_render = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
obs_leave_graphics();
}
obs_source_release(source);
update_item_transform(item, false);
@ -1393,6 +1472,32 @@ static bool scene_audio_render(void *data, uint64_t *ts_out,
return true;
}
enum gs_color_space
scene_video_get_color_space(void *data, size_t count,
const enum gs_color_space *preferred_spaces)
{
UNUSED_PARAMETER(data);
enum gs_color_space canvas_space = GS_CS_SRGB;
struct obs_video_info ovi;
if (obs_get_video_info(&ovi)) {
switch (ovi.colorspace) {
case VIDEO_CS_2020_PQ:
case VIDEO_CS_2020_HLG:
canvas_space = GS_CS_709_EXTENDED;
}
}
enum gs_color_space space = canvas_space;
for (size_t i = 0; i < count; ++i) {
space = preferred_spaces[i];
if (space == canvas_space)
break;
}
return space;
}
const struct obs_source_info scene_info = {
.id = "scene",
.type = OBS_SOURCE_TYPE_SCENE,
@ -1410,7 +1515,9 @@ const struct obs_source_info scene_info = {
.load = scene_load,
.save = scene_save,
.enum_active_sources = scene_enum_active_sources,
.enum_all_sources = scene_enum_all_sources};
.enum_all_sources = scene_enum_all_sources,
.video_get_color_space = scene_video_get_color_space,
};
const struct obs_source_info group_info = {
.id = "group",
@ -1428,7 +1535,9 @@ const struct obs_source_info group_info = {
.load = scene_load,
.save = scene_save,
.enum_active_sources = scene_enum_active_sources,
.enum_all_sources = scene_enum_all_sources};
.enum_all_sources = scene_enum_all_sources,
.video_get_color_space = scene_video_get_color_space,
};
static inline obs_scene_t *create_id(const char *id, const char *name)
{
@ -1552,13 +1661,6 @@ static inline void duplicate_item_data(struct obs_scene_item *dst,
if (defer_texture_update) {
os_atomic_set_bool(&dst->update_transform, true);
} else {
if (!dst->item_render && item_texture_enabled(dst)) {
obs_enter_graphics();
dst->item_render =
gs_texrender_create(GS_RGBA, GS_ZS_NONE);
obs_leave_graphics();
}
}
if (duplicate_private_data) {
@ -1999,12 +2101,6 @@ static obs_sceneitem_t *obs_scene_add_internal(obs_scene_t *scene,
item->visible = true;
}
if (create_texture && item_texture_enabled(item)) {
obs_enter_graphics();
item->item_render = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
obs_leave_graphics();
}
full_lock(scene);
if (insert_after) {

View File

@ -665,7 +665,7 @@ void obs_transition_enum_sources(obs_source_t *transition,
}
static inline void render_child(obs_source_t *transition, obs_source_t *child,
size_t idx)
size_t idx, enum gs_color_space space)
{
uint32_t cx = get_cx(transition);
uint32_t cy = get_cy(transition);
@ -673,7 +673,16 @@ static inline void render_child(obs_source_t *transition, obs_source_t *child,
if (!child)
return;
if (gs_texrender_begin(transition->transition_texrender[idx], cx, cy)) {
enum gs_color_format format = gs_get_format_from_space(space);
if (gs_texrender_get_format(transition->transition_texrender[idx]) !=
format) {
gs_texrender_destroy(transition->transition_texrender[idx]);
transition->transition_texrender[idx] =
gs_texrender_create(format, GS_ZS_NONE);
}
if (gs_texrender_begin_with_color_space(
transition->transition_texrender[idx], cx, cy, space)) {
vec4_zero(&blank);
gs_clear(GS_CLEAR_COLOR, &blank, 0.0f, 0);
gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f);
@ -754,9 +763,14 @@ void obs_transition_video_render(obs_source_t *transition,
uint32_t cx;
uint32_t cy;
const enum gs_color_space current_space = gs_get_color_space();
const enum gs_color_space source_space =
obs_source_get_color_space(transition, 1,
&current_space);
for (size_t i = 0; i < 2; i++) {
if (state.s[i]) {
render_child(transition, state.s[i], i);
render_child(transition, state.s[i], i,
source_space);
tex[i] = get_texture(transition, i);
if (!tex[i])
tex[i] = obs->video.transparent_texture;
@ -806,6 +820,47 @@ void obs_transition_video_render(obs_source_t *transition,
handle_stop(transition);
}
static enum gs_color_space mix_spaces(enum gs_color_space a,
enum gs_color_space b)
{
assert((a == GS_CS_SRGB) || (a == GS_CS_709_EXTENDED));
assert((b == GS_CS_SRGB) || (b == GS_CS_709_EXTENDED));
return ((a == GS_CS_709_EXTENDED) || (b == GS_CS_709_EXTENDED))
? GS_CS_709_EXTENDED
: GS_CS_SRGB;
}
enum gs_color_space
obs_transition_video_get_color_space(obs_source_t *transition)
{
obs_source_t *source0 = transition->transition_sources[0];
obs_source_t *source1 = transition->transition_sources[1];
const enum gs_color_space dual_spaces[] = {
GS_CS_SRGB,
GS_CS_709_EXTENDED,
};
enum gs_color_space space = GS_CS_SRGB;
if (source0) {
space = mix_spaces(space,
obs_source_get_color_space(
source0, OBS_COUNTOF(dual_spaces),
dual_spaces));
}
if (source1) {
space = mix_spaces(space,
obs_source_get_color_space(
source1, OBS_COUNTOF(dual_spaces),
dual_spaces));
}
return space;
}
bool obs_transition_video_render_direct(obs_source_t *transition,
enum obs_transition_target target)
{

View File

@ -705,6 +705,8 @@ static void obs_source_destroy_defer(struct obs_source *source)
}
if (source->filter_texrender)
gs_texrender_destroy(source->filter_texrender);
if (source->color_space_texrender)
gs_texrender_destroy(source->color_space_texrender);
gs_leave_context();
for (i = 0; i < MAX_AV_PLANES; i++)
@ -2253,6 +2255,66 @@ static void rotate_async_video(obs_source_t *source, long rotation)
static inline void obs_source_render_async_video(obs_source_t *source)
{
if (source->async_textures[0] && source->async_active) {
gs_effect_t *const effect =
obs_get_base_effect(OBS_EFFECT_DEFAULT);
const char *tech_name = "Draw";
float multiplier = 1.0;
const enum gs_color_space source_space = GS_CS_SRGB;
const enum gs_color_space current_space = gs_get_color_space();
bool linear_srgb = gs_get_linear_srgb();
switch (source_space) {
case GS_CS_SRGB:
switch (current_space) {
case GS_CS_SRGB:
if (linear_srgb &&
!source->async_linear_alpha) {
tech_name = "DrawNonlinearAlpha";
}
break;
case GS_CS_709_SCRGB:
tech_name = "DrawMultiply";
multiplier =
obs_get_video_sdr_white_level() / 80.0f;
linear_srgb = true;
}
break;
case GS_CS_709_EXTENDED:
switch (current_space) {
case GS_CS_SRGB:
tech_name = "DrawTonemap";
linear_srgb = true;
break;
case GS_CS_709_SCRGB:
tech_name = "DrawMultiply";
multiplier =
obs_get_video_sdr_white_level() / 80.0f;
}
break;
case GS_CS_709_SCRGB:
switch (current_space) {
case GS_CS_SRGB:
tech_name = "DrawMultiplyTonemap";
multiplier =
80.0f / obs_get_video_sdr_white_level();
linear_srgb = true;
break;
case GS_CS_709_EXTENDED:
tech_name = "DrawMultiply";
multiplier =
80.0f / obs_get_video_sdr_white_level();
}
}
const bool previous = gs_set_linear_srgb(linear_srgb);
gs_technique_t *const tech =
gs_effect_get_technique(effect, tech_name);
gs_effect_set_float(gs_effect_get_param_by_name(effect,
"multiplier"),
multiplier);
gs_technique_begin(tech);
gs_technique_begin_pass(tech, 0);
long rotation = source->async_rotation;
if (rotation) {
gs_matrix_push();
@ -2262,6 +2324,11 @@ static inline void obs_source_render_async_video(obs_source_t *source)
if (rotation) {
gs_matrix_pop();
}
gs_technique_end_pass(tech);
gs_technique_end(tech);
gs_set_linear_srgb(previous);
}
}
@ -2280,20 +2347,177 @@ static inline void obs_source_render_filters(obs_source_t *source)
obs_source_release(first_filter);
}
static inline uint32_t get_async_width(const obs_source_t *source)
{
return ((source->async_rotation % 180) == 0) ? source->async_width
: source->async_height;
}
static inline uint32_t get_async_height(const obs_source_t *source)
{
return ((source->async_rotation % 180) == 0) ? source->async_height
: source->async_width;
}
static uint32_t get_base_width(const obs_source_t *source)
{
bool is_filter = !!source->filter_parent;
bool func_valid = source->context.data && source->info.get_width;
if (source->info.type == OBS_SOURCE_TYPE_TRANSITION) {
return source->enabled ? source->transition_actual_cx : 0;
} else if (func_valid && (!is_filter || source->enabled)) {
return source->info.get_width(source->context.data);
} else if (is_filter) {
return get_base_width(source->filter_target);
}
return source->async_active ? get_async_width(source) : 0;
}
static uint32_t get_base_height(const obs_source_t *source)
{
bool is_filter = !!source->filter_parent;
bool func_valid = source->context.data && source->info.get_height;
if (source->info.type == OBS_SOURCE_TYPE_TRANSITION) {
return source->enabled ? source->transition_actual_cy : 0;
} else if (func_valid && (!is_filter || source->enabled)) {
return source->info.get_height(source->context.data);
} else if (is_filter) {
return get_base_height(source->filter_target);
}
return source->async_active ? get_async_height(source) : 0;
}
static void source_render(obs_source_t *source, gs_effect_t *effect)
{
void *const data = source->context.data;
const enum gs_color_space current_space = gs_get_color_space();
const enum gs_color_space source_space =
obs_source_get_color_space(source, 1, &current_space);
const char *convert_tech = NULL;
float multiplier = 1.0;
enum gs_color_format format = gs_get_format_from_space(source_space);
switch (source_space) {
case GS_CS_SRGB:
switch (current_space) {
case GS_CS_709_EXTENDED:
convert_tech = "Draw";
break;
case GS_CS_709_SCRGB:
convert_tech = "DrawMultiply";
multiplier = obs_get_video_sdr_white_level() / 80.0f;
}
break;
case GS_CS_709_EXTENDED:
switch (current_space) {
case GS_CS_SRGB:
convert_tech = "DrawTonemap";
break;
case GS_CS_709_SCRGB:
convert_tech = "DrawMultiply";
multiplier = obs_get_video_sdr_white_level() / 80.0f;
}
break;
case GS_CS_709_SCRGB:
switch (current_space) {
case GS_CS_SRGB:
convert_tech = "DrawMultiplyTonemap";
multiplier = 80.0f / obs_get_video_sdr_white_level();
break;
case GS_CS_709_EXTENDED:
convert_tech = "DrawMultiply";
multiplier = 80.0f / obs_get_video_sdr_white_level();
}
}
if (convert_tech) {
if (source->color_space_texrender) {
if (gs_texrender_get_format(
source->color_space_texrender) != format) {
gs_texrender_destroy(
source->color_space_texrender);
source->color_space_texrender = NULL;
}
}
if (!source->color_space_texrender) {
source->color_space_texrender =
gs_texrender_create(format, GS_ZS_NONE);
}
gs_texrender_reset(source->color_space_texrender);
const int cx = get_base_width(source);
const int cy = get_base_height(source);
if (gs_texrender_begin_with_color_space(
source->color_space_texrender, cx, cy,
source_space)) {
gs_enable_blending(false);
gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f,
100.0f);
source->info.video_render(data, effect);
gs_enable_blending(true);
gs_texrender_end(source->color_space_texrender);
gs_effect_t *default_effect = obs->video.default_effect;
gs_technique_t *tech = gs_effect_get_technique(
default_effect, convert_tech);
const bool previous = gs_framebuffer_srgb_enabled();
gs_enable_framebuffer_srgb(true);
gs_texture_t *const tex = gs_texrender_get_texture(
source->color_space_texrender);
gs_effect_set_texture_srgb(
gs_effect_get_param_by_name(default_effect,
"image"),
tex);
gs_effect_set_float(
gs_effect_get_param_by_name(default_effect,
"multiplier"),
multiplier);
const size_t passes = gs_technique_begin(tech);
for (size_t i = 0; i < passes; i++) {
gs_technique_begin_pass(tech, i);
gs_draw_sprite(tex, 0, 0, 0);
gs_technique_end_pass(tech);
}
gs_technique_end(tech);
gs_enable_framebuffer_srgb(previous);
}
} else {
source->info.video_render(data, effect);
}
}
void obs_source_default_render(obs_source_t *source)
{
gs_effect_t *effect = obs->video.default_effect;
gs_technique_t *tech = gs_effect_get_technique(effect, "Draw");
size_t passes, i;
if (source->context.data) {
gs_effect_t *effect = obs->video.default_effect;
gs_technique_t *tech = gs_effect_get_technique(effect, "Draw");
size_t passes, i;
passes = gs_technique_begin(tech);
for (i = 0; i < passes; i++) {
gs_technique_begin_pass(tech, i);
if (source->context.data)
source->info.video_render(source->context.data, effect);
gs_technique_end_pass(tech);
passes = gs_technique_begin(tech);
for (i = 0; i < passes; i++) {
gs_technique_begin_pass(tech, i);
source_render(source, effect);
gs_technique_end_pass(tech);
}
gs_technique_end(tech);
}
gs_technique_end(tech);
}
static inline void obs_source_main_render(obs_source_t *source)
@ -2310,11 +2534,11 @@ static inline void obs_source_main_render(obs_source_t *source)
gs_set_linear_srgb(false);
}
if (default_effect)
if (default_effect) {
obs_source_default_render(source);
else if (source->context.data)
source->info.video_render(source->context.data,
custom_draw ? NULL : gs_get_effect());
} else if (source->context.data) {
source_render(source, custom_draw ? NULL : gs_get_effect());
}
if (!srgb_aware)
gs_set_linear_srgb(previous_srgb);
@ -2397,54 +2621,6 @@ void obs_source_video_render(obs_source_t *source)
}
}
static inline uint32_t get_async_width(const obs_source_t *source)
{
return ((source->async_rotation % 180) == 0) ? source->async_width
: source->async_height;
}
static inline uint32_t get_async_height(const obs_source_t *source)
{
return ((source->async_rotation % 180) == 0) ? source->async_height
: source->async_width;
}
static uint32_t get_base_width(const obs_source_t *source)
{
bool is_filter = !!source->filter_parent;
bool func_valid = source->context.data && source->info.get_width;
if (source->info.type == OBS_SOURCE_TYPE_TRANSITION) {
return source->enabled ? source->transition_actual_cx : 0;
} else if (func_valid && (!is_filter || source->enabled)) {
return source->info.get_width(source->context.data);
} else if (is_filter) {
return get_base_width(source->filter_target);
}
return source->async_active ? get_async_width(source) : 0;
}
static uint32_t get_base_height(const obs_source_t *source)
{
bool is_filter = !!source->filter_parent;
bool func_valid = source->context.data && source->info.get_height;
if (source->info.type == OBS_SOURCE_TYPE_TRANSITION) {
return source->enabled ? source->transition_actual_cy : 0;
} else if (func_valid && (!is_filter || source->enabled)) {
return source->info.get_height(source->context.data);
} else if (is_filter) {
return get_base_height(source->filter_target);
}
return source->async_active ? get_async_height(source) : 0;
}
static uint32_t get_recurse_width(obs_source_t *source)
{
uint32_t width;
@ -2494,6 +2670,36 @@ uint32_t obs_source_get_height(obs_source_t *source)
: get_base_height(source);
}
enum gs_color_space
obs_source_get_color_space(obs_source_t *source, size_t count,
const enum gs_color_space *preferred_spaces)
{
if (!data_valid(source, "obs_source_get_color_space"))
return GS_CS_SRGB;
if (source->info.type != OBS_SOURCE_TYPE_FILTER &&
(source->info.output_flags & OBS_SOURCE_VIDEO) == 0) {
if (source->filter_parent)
return obs_source_get_color_space(
source->filter_parent, count, preferred_spaces);
}
if (!source->context.data || !source->enabled) {
if (source->filter_parent)
return obs_source_get_color_space(
source->filter_parent, count, preferred_spaces);
}
if (source->info.output_flags & OBS_SOURCE_ASYNC)
return GS_CS_SRGB;
assert(source->context.data);
return source->info.video_get_color_space
? source->info.video_get_color_space(
source->context.data, count, preferred_spaces)
: GS_CS_SRGB;
}
uint32_t obs_source_get_base_width(obs_source_t *source)
{
if (!data_valid(source, "obs_source_get_base_width"))
@ -3804,27 +4010,40 @@ static inline void render_filter_tex(gs_texture_t *tex, gs_effect_t *effect,
static inline bool can_bypass(obs_source_t *target, obs_source_t *parent,
uint32_t filter_flags, uint32_t parent_flags,
enum obs_allow_direct_render allow_direct)
enum obs_allow_direct_render allow_direct,
enum gs_color_space space)
{
return (target == parent) &&
(allow_direct == OBS_ALLOW_DIRECT_RENDERING) &&
((parent_flags & OBS_SOURCE_CUSTOM_DRAW) == 0) &&
((parent_flags & OBS_SOURCE_ASYNC) == 0) &&
((filter_flags & OBS_SOURCE_SRGB) ==
(parent_flags & OBS_SOURCE_SRGB));
(parent_flags & OBS_SOURCE_SRGB) &&
space == gs_get_color_space());
}
bool obs_source_process_filter_begin(obs_source_t *filter,
enum gs_color_format format,
enum obs_allow_direct_render allow_direct)
{
return obs_source_process_filter_begin_with_color_space(
filter, format, GS_CS_SRGB, allow_direct);
}
bool obs_source_process_filter_begin_with_color_space(
obs_source_t *filter, enum gs_color_format format,
enum gs_color_space space, enum obs_allow_direct_render allow_direct)
{
obs_source_t *target, *parent;
uint32_t filter_flags, parent_flags;
int cx, cy;
if (!obs_ptr_valid(filter, "obs_source_process_filter_begin"))
if (!obs_ptr_valid(filter,
"obs_source_process_filter_begin_with_color_space"))
return false;
filter->filter_bypass_active = false;
target = obs_filter_get_target(filter);
parent = obs_filter_get_parent(filter);
@ -3850,8 +4069,9 @@ bool obs_source_process_filter_begin(obs_source_t *filter,
* filter in the chain for the parent, then render the parent directly
* using the filter effect instead of rendering to texture to reduce
* the total number of passes */
if (can_bypass(target, parent, filter_flags, parent_flags,
allow_direct)) {
if (can_bypass(target, parent, filter_flags, parent_flags, allow_direct,
space)) {
filter->filter_bypass_active = true;
return true;
}
@ -3871,7 +4091,8 @@ bool obs_source_process_filter_begin(obs_source_t *filter,
gs_texrender_create(format, GS_ZS_NONE);
}
if (gs_texrender_begin(filter->filter_texrender, cx, cy)) {
if (gs_texrender_begin_with_color_space(filter->filter_texrender, cx,
cy, space)) {
gs_blend_state_push();
gs_blend_function_separate(GS_BLEND_SRCALPHA,
GS_BLEND_INVSRCALPHA, GS_BLEND_ONE,
@ -3903,11 +4124,14 @@ void obs_source_process_filter_tech_end(obs_source_t *filter,
{
obs_source_t *target, *parent;
gs_texture_t *texture;
uint32_t filter_flags, parent_flags;
uint32_t filter_flags;
if (!filter)
return;
const bool filter_bypass_active = filter->filter_bypass_active;
filter->filter_bypass_active = false;
target = obs_filter_get_target(filter);
parent = obs_filter_get_parent(filter);
@ -3915,15 +4139,13 @@ void obs_source_process_filter_tech_end(obs_source_t *filter,
return;
filter_flags = filter->info.output_flags;
parent_flags = parent->info.output_flags;
const bool previous =
gs_set_linear_srgb((filter_flags & OBS_SOURCE_SRGB) != 0);
const char *tech = tech_name ? tech_name : "Draw";
if (can_bypass(target, parent, filter_flags, parent_flags,
filter->allow_direct)) {
if (filter_bypass_active) {
render_filter_bypass(target, effect, tech);
} else {
texture = gs_texrender_get_texture(filter->filter_texrender);

View File

@ -546,6 +546,11 @@ struct obs_source_info {
/** Missing files **/
obs_missing_files_t *(*missing_files)(void *data);
/** Get color space **/
enum gs_color_space (*video_get_color_space)(
void *data, size_t count,
const enum gs_color_space *preferred_spaces);
};
EXPORT void obs_register_source_s(const struct obs_source_info *info,

View File

@ -1017,6 +1017,11 @@ EXPORT uint32_t obs_source_get_width(obs_source_t *source);
/** Gets the height of a source (if it has video) */
EXPORT uint32_t obs_source_get_height(obs_source_t *source);
/** Gets the color space of a source (if it has video) */
EXPORT enum gs_color_space
obs_source_get_color_space(obs_source_t *source, size_t count,
const enum gs_color_space *preferred_spaces);
/** Hints whether or not the source will blend texels */
EXPORT bool obs_source_get_texcoords_centered(obs_source_t *source);
@ -1386,6 +1391,10 @@ obs_source_process_filter_begin(obs_source_t *filter,
enum gs_color_format format,
enum obs_allow_direct_render allow_direct);
EXPORT bool obs_source_process_filter_begin_with_color_space(
obs_source_t *filter, enum gs_color_format format,
enum gs_color_space space, enum obs_allow_direct_render allow_direct);
/**
* Draws the filter.
*
@ -1570,6 +1579,9 @@ EXPORT void
obs_transition_video_render(obs_source_t *transition,
obs_transition_video_render_callback_t callback);
EXPORT enum gs_color_space
obs_transition_video_get_color_space(obs_source_t *transition);
/** Directly renders its sub-source instead of to texture. Returns false if no
* longer transitioning */
EXPORT bool