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.
master
jpark37 2021-07-06 22:10:57 -07:00
parent ebfbe1a78e
commit bf27941f5f
8 changed files with 263 additions and 129 deletions

View File

@ -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

View File

@ -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);

View File

@ -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);
}

View File

@ -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);

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);