win-capture: Add HDR support to Game Capture
This commit is contained in:
parent
08f50a7d22
commit
d35e7d3107
@ -34,4 +34,7 @@ GameCapture.HookRate.Slow="Slow"
|
||||
GameCapture.HookRate.Normal="Normal (recommended)"
|
||||
GameCapture.HookRate.Fast="Fast"
|
||||
GameCapture.HookRate.Fastest="Fastest"
|
||||
GameCapture.Rgb10a2Space="RGB10A2 Color Space"
|
||||
GameCapture.Rgb10a2Space.Srgb="sRGB"
|
||||
GameCapture.Rgb10a2Space.2020PQ="Rec. 2020 (PQ)"
|
||||
Mode="Mode"
|
||||
|
@ -38,6 +38,7 @@
|
||||
#define SETTING_CAPTURE_OVERLAYS "capture_overlays"
|
||||
#define SETTING_ANTI_CHEAT_HOOK "anti_cheat_hook"
|
||||
#define SETTING_HOOK_RATE "hook_rate"
|
||||
#define SETTING_RGBA10A2_SPACE "rgb10a2_space"
|
||||
|
||||
/* deprecated */
|
||||
#define SETTING_ANY_FULLSCREEN "capture_any_fullscreen"
|
||||
@ -68,6 +69,9 @@
|
||||
#define TEXT_HOOK_RATE_NORMAL obs_module_text("GameCapture.HookRate.Normal")
|
||||
#define TEXT_HOOK_RATE_FAST obs_module_text("GameCapture.HookRate.Fast")
|
||||
#define TEXT_HOOK_RATE_FASTEST obs_module_text("GameCapture.HookRate.Fastest")
|
||||
#define TEXT_RGBA10A2_SPACE obs_module_text("GameCapture.Rgb10a2Space")
|
||||
#define TEXT_RGBA10A2_SPACE_SRGB obs_module_text("GameCapture.Rgb10a2Space.Srgb")
|
||||
#define TEXT_RGBA10A2_SPACE_2020PQ obs_module_text("GameCapture.Rgb10a2Space.2020PQ")
|
||||
|
||||
#define TEXT_MODE_ANY TEXT_ANY_FULLSCREEN
|
||||
#define TEXT_MODE_WINDOW obs_module_text("GameCapture.CaptureWindow")
|
||||
@ -94,6 +98,9 @@ enum hook_rate {
|
||||
HOOK_RATE_FASTEST
|
||||
};
|
||||
|
||||
#define RGBA10A2_SPACE_SRGB "srgb"
|
||||
#define RGBA10A2_SPACE_2020PQ "2020pq"
|
||||
|
||||
struct game_capture_config {
|
||||
char *title;
|
||||
char *class;
|
||||
@ -107,6 +114,7 @@ struct game_capture_config {
|
||||
bool capture_overlays;
|
||||
bool anticheat_hook;
|
||||
enum hook_rate hook_rate;
|
||||
bool is_10a2_2020pq;
|
||||
};
|
||||
|
||||
typedef DPI_AWARENESS_CONTEXT(WINAPI *PFN_SetThreadDpiAwarenessContext)(
|
||||
@ -156,6 +164,7 @@ struct game_capture {
|
||||
gs_texture_t *texture;
|
||||
gs_texture_t *extra_texture;
|
||||
gs_texrender_t *extra_texrender;
|
||||
bool is_10a2_2020pq;
|
||||
bool linear_sample;
|
||||
struct hook_info *global_hook_info;
|
||||
HANDLE keepalive_mutex;
|
||||
@ -433,6 +442,9 @@ static inline void get_config(struct game_capture_config *cfg,
|
||||
obs_data_get_bool(settings, SETTING_ANTI_CHEAT_HOOK);
|
||||
cfg->hook_rate =
|
||||
(enum hook_rate)obs_data_get_int(settings, SETTING_HOOK_RATE);
|
||||
cfg->is_10a2_2020pq =
|
||||
strcmp(obs_data_get_string(settings, SETTING_RGBA10A2_SPACE),
|
||||
"2020pq") == 0;
|
||||
}
|
||||
|
||||
static inline int s_cmp(const char *str1, const char *str2)
|
||||
@ -529,6 +541,7 @@ static void game_capture_update(void *data, obs_data_t *settings)
|
||||
gc->retry_interval = DEFAULT_RETRY_INTERVAL *
|
||||
hook_rate_to_float(gc->config.hook_rate);
|
||||
gc->wait_for_target_startup = false;
|
||||
gc->is_10a2_2020pq = gc->config.is_10a2_2020pq;
|
||||
|
||||
dstr_free(&gc->title);
|
||||
dstr_free(&gc->class);
|
||||
@ -1617,7 +1630,7 @@ static inline bool init_shmem_capture(struct game_capture *gc)
|
||||
gs_texrender_t *extra_texrender = NULL;
|
||||
if (!linear_sample) {
|
||||
extra_texrender =
|
||||
gs_texrender_create(GS_BGRA, GS_ZS_NONE);
|
||||
gs_texrender_create(GS_RGBA16F, GS_ZS_NONE);
|
||||
success = extra_texrender != NULL;
|
||||
if (!success)
|
||||
warn("init_shmem_capture: failed to create extra texrender");
|
||||
@ -1662,7 +1675,8 @@ static inline bool init_shtex_capture(struct game_capture *gc)
|
||||
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);
|
||||
ten_bit_srgb ? GS_RGBA16F
|
||||
: gs_generalize_format(format);
|
||||
const bool linear_sample = (linear_format == format);
|
||||
gs_texture_t *extra_texture = NULL;
|
||||
gs_texrender_t *extra_texrender = NULL;
|
||||
@ -1943,14 +1957,23 @@ static void game_capture_render(void *data, gs_effect_t *unused)
|
||||
gs_effect_t *const effect = obs_get_base_effect(
|
||||
allow_transparency ? OBS_EFFECT_DEFAULT : OBS_EFFECT_OPAQUE);
|
||||
|
||||
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 {
|
||||
enum gs_color_space source_space = GS_CS_SRGB;
|
||||
bool is_10a2_compressed = false;
|
||||
if (gs_texture_get_color_format(texture) == GS_R10G10B10A2) {
|
||||
is_10a2_compressed = true;
|
||||
source_space = gc->is_10a2_2020pq ? GS_CS_709_EXTENDED
|
||||
: GS_CS_SRGB_16F;
|
||||
} else if (gs_texture_get_color_format(texture) == GS_RGBA16F) {
|
||||
source_space = GS_CS_709_SCRGB;
|
||||
}
|
||||
|
||||
bool linear_sample = gc->linear_sample;
|
||||
const enum gs_color_space current_space = gs_get_color_space();
|
||||
const bool texcoords_centered =
|
||||
obs_source_get_texcoords_centered(gc->source);
|
||||
if (!linear_sample && !texcoords_centered) {
|
||||
if (gs_texture_get_color_format(texture) == GS_R10G10B10A2) {
|
||||
gs_texrender_t *const texrender = gc->extra_texrender;
|
||||
gs_texrender_reset(texrender);
|
||||
const uint32_t cx = gs_texture_get_width(texture);
|
||||
@ -1958,9 +1981,6 @@ static void game_capture_render(void *data, gs_effect_t *unused)
|
||||
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);
|
||||
@ -1968,15 +1988,74 @@ static void game_capture_render(void *data, gs_effect_t *unused)
|
||||
gs_effect_get_param_by_name(
|
||||
default_effect, "image");
|
||||
gs_effect_set_texture(image, texture);
|
||||
while (gs_effect_loop(default_effect, "Draw")) {
|
||||
|
||||
const char *tech_name = "DrawSrgbDecompress";
|
||||
if (gc->is_10a2_2020pq) {
|
||||
tech_name = "DrawPQ";
|
||||
|
||||
const float multiplier =
|
||||
10000.f /
|
||||
obs_get_video_sdr_white_level();
|
||||
gs_effect_set_float(
|
||||
gs_effect_get_param_by_name(
|
||||
default_effect,
|
||||
"multiplier"),
|
||||
multiplier);
|
||||
}
|
||||
|
||||
while (gs_effect_loop(default_effect,
|
||||
tech_name)) {
|
||||
gs_draw_sprite(texture, 0, 0, 0);
|
||||
}
|
||||
gs_enable_blending(true);
|
||||
|
||||
gs_texrender_end(texrender);
|
||||
|
||||
texture = gs_texrender_get_texture(texrender);
|
||||
}
|
||||
|
||||
is_10a2_compressed = false;
|
||||
} else {
|
||||
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);
|
||||
|
||||
gs_texrender_end(texrender);
|
||||
|
||||
texture = gs_texrender_get_texture(texrender);
|
||||
texture = gs_texrender_get_texture(
|
||||
texrender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1985,9 +2064,91 @@ static void game_capture_render(void *data, gs_effect_t *unused)
|
||||
|
||||
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"
|
||||
|
||||
const char *tech_name = "Draw";
|
||||
float multiplier = 1.f;
|
||||
switch (source_space) {
|
||||
case GS_CS_SRGB:
|
||||
switch (current_space) {
|
||||
case GS_CS_SRGB:
|
||||
if (allow_transparency && !linear_sample)
|
||||
tech_name = "DrawSrgbDecompress";
|
||||
break;
|
||||
case GS_CS_SRGB_16F:
|
||||
case GS_CS_709_EXTENDED:
|
||||
if (!linear_sample)
|
||||
tech_name = "DrawSrgbDecompress";
|
||||
break;
|
||||
case GS_CS_709_SCRGB:
|
||||
if (linear_sample)
|
||||
tech_name = "DrawMultiply";
|
||||
else
|
||||
tech_name = "DrawSrgbDecompressMultiply";
|
||||
multiplier = obs_get_video_sdr_white_level() / 80.f;
|
||||
}
|
||||
break;
|
||||
case GS_CS_SRGB_16F:
|
||||
linear_sample = true;
|
||||
switch (current_space) {
|
||||
case GS_CS_SRGB:
|
||||
case GS_CS_SRGB_16F:
|
||||
case GS_CS_709_EXTENDED:
|
||||
tech_name = is_10a2_compressed ? "DrawSrgbDecompress"
|
||||
: "Draw";
|
||||
break;
|
||||
case GS_CS_709_SCRGB:
|
||||
tech_name = is_10a2_compressed
|
||||
? "DrawSrgbDecompressMultiply"
|
||||
: "DrawMultiply";
|
||||
multiplier = obs_get_video_sdr_white_level() / 80.f;
|
||||
}
|
||||
break;
|
||||
case GS_CS_709_EXTENDED:
|
||||
linear_sample = true;
|
||||
if (is_10a2_compressed) {
|
||||
switch (current_space) {
|
||||
case GS_CS_SRGB:
|
||||
case GS_CS_SRGB_16F:
|
||||
tech_name = "DrawTonemapPQ";
|
||||
multiplier = 10000.f /
|
||||
obs_get_video_sdr_white_level();
|
||||
break;
|
||||
case GS_CS_709_EXTENDED:
|
||||
tech_name = "DrawPQ";
|
||||
multiplier = 10000.f /
|
||||
obs_get_video_sdr_white_level();
|
||||
break;
|
||||
case GS_CS_709_SCRGB:
|
||||
tech_name = "DrawPQ";
|
||||
multiplier = 10000.f / 80.f;
|
||||
}
|
||||
} else {
|
||||
switch (current_space) {
|
||||
case GS_CS_SRGB:
|
||||
case GS_CS_SRGB_16F:
|
||||
tech_name = "DrawTonemap";
|
||||
break;
|
||||
case GS_CS_709_SCRGB:
|
||||
tech_name = "DrawMultiply";
|
||||
multiplier =
|
||||
obs_get_video_sdr_white_level() / 80.f;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GS_CS_709_SCRGB:
|
||||
linear_sample = true;
|
||||
switch (current_space) {
|
||||
case GS_CS_SRGB:
|
||||
case GS_CS_SRGB_16F:
|
||||
tech_name = "DrawMultiplyTonemap";
|
||||
multiplier = 80.f / obs_get_video_sdr_white_level();
|
||||
break;
|
||||
case GS_CS_709_EXTENDED:
|
||||
tech_name = "DrawMultiply";
|
||||
multiplier = 80.f / obs_get_video_sdr_white_level();
|
||||
}
|
||||
}
|
||||
|
||||
while (gs_effect_loop(effect, tech_name)) {
|
||||
const bool previous = gs_framebuffer_srgb_enabled();
|
||||
gs_enable_framebuffer_srgb(allow_transparency || linear_sample);
|
||||
@ -1996,23 +2157,36 @@ static void game_capture_render(void *data, gs_effect_t *unused)
|
||||
gs_effect_set_texture_srgb(image, texture);
|
||||
else
|
||||
gs_effect_set_texture(image, texture);
|
||||
gs_effect_set_float(gs_effect_get_param_by_name(effect,
|
||||
"multiplier"),
|
||||
multiplier);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (!allow_transparency && gc->config.cursor && !gc->cursor_hidden) {
|
||||
if (gc->config.cursor && !gc->cursor_hidden) {
|
||||
gs_effect_t *const default_effect =
|
||||
obs_get_base_effect(OBS_EFFECT_DEFAULT);
|
||||
|
||||
while (gs_effect_loop(default_effect, "Draw")) {
|
||||
const char *cursor_tech_name = "Draw";
|
||||
float cursor_multiplier = 1.f;
|
||||
if (current_space == GS_CS_709_SCRGB) {
|
||||
cursor_tech_name = "DrawMultiply";
|
||||
cursor_multiplier =
|
||||
obs_get_video_sdr_white_level() / 80.f;
|
||||
}
|
||||
|
||||
const bool previous = gs_set_linear_srgb(true);
|
||||
|
||||
while (gs_effect_loop(default_effect, cursor_tech_name)) {
|
||||
gs_effect_set_float(gs_effect_get_param_by_name(
|
||||
effect, "multiplier"),
|
||||
cursor_multiplier);
|
||||
game_capture_render_cursor(gc);
|
||||
}
|
||||
|
||||
gs_set_linear_srgb(previous);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2047,6 +2221,8 @@ static void game_capture_defaults(obs_data_t *settings)
|
||||
obs_data_set_default_bool(settings, SETTING_ANTI_CHEAT_HOOK, true);
|
||||
obs_data_set_default_int(settings, SETTING_HOOK_RATE,
|
||||
(int)HOOK_RATE_NORMAL);
|
||||
obs_data_set_default_string(settings, SETTING_RGBA10A2_SPACE,
|
||||
RGBA10A2_SPACE_SRGB);
|
||||
}
|
||||
|
||||
static bool mode_callback(obs_properties_t *ppts, obs_property_t *p,
|
||||
@ -2236,10 +2412,57 @@ static obs_properties_t *game_capture_properties(void *data)
|
||||
obs_property_list_add_int(p, TEXT_HOOK_RATE_FAST, HOOK_RATE_FAST);
|
||||
obs_property_list_add_int(p, TEXT_HOOK_RATE_FASTEST, HOOK_RATE_FASTEST);
|
||||
|
||||
p = obs_properties_add_list(ppts, SETTING_RGBA10A2_SPACE,
|
||||
TEXT_RGBA10A2_SPACE, OBS_COMBO_TYPE_LIST,
|
||||
OBS_COMBO_FORMAT_STRING);
|
||||
obs_property_list_add_string(p, TEXT_RGBA10A2_SPACE_SRGB,
|
||||
RGBA10A2_SPACE_SRGB);
|
||||
obs_property_list_add_string(p, TEXT_RGBA10A2_SPACE_2020PQ,
|
||||
RGBA10A2_SPACE_2020PQ);
|
||||
|
||||
UNUSED_PARAMETER(data);
|
||||
return ppts;
|
||||
}
|
||||
|
||||
enum gs_color_space
|
||||
game_capture_get_color_space(void *data, size_t count,
|
||||
const enum gs_color_space *preferred_spaces)
|
||||
{
|
||||
enum gs_color_space capture_space = GS_CS_SRGB;
|
||||
|
||||
struct game_capture *const gc = data;
|
||||
if (gc->texture) {
|
||||
const enum gs_color_format format =
|
||||
gs_texture_get_color_format(gc->texture);
|
||||
if (((format == GS_R10G10B10A2) && gc->is_10a2_2020pq) ||
|
||||
(format == GS_RGBA16F)) {
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (preferred_spaces[i] == GS_CS_709_SCRGB)
|
||||
return GS_CS_709_SCRGB;
|
||||
}
|
||||
|
||||
capture_space = GS_CS_709_EXTENDED;
|
||||
} else if (format == GS_R10G10B10A2) {
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (preferred_spaces[i] == GS_CS_SRGB_16F)
|
||||
return GS_CS_SRGB_16F;
|
||||
}
|
||||
|
||||
capture_space = GS_CS_709_EXTENDED;
|
||||
}
|
||||
}
|
||||
|
||||
enum gs_color_space space = capture_space;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
const enum gs_color_space preferred_space = preferred_spaces[i];
|
||||
space = preferred_space;
|
||||
if (preferred_space == capture_space)
|
||||
break;
|
||||
}
|
||||
|
||||
return space;
|
||||
}
|
||||
|
||||
struct obs_source_info game_capture_info = {
|
||||
.id = "game_capture",
|
||||
.type = OBS_SOURCE_TYPE_INPUT,
|
||||
@ -2256,4 +2479,5 @@ struct obs_source_info game_capture_info = {
|
||||
.video_tick = game_capture_tick,
|
||||
.video_render = game_capture_render,
|
||||
.icon_type = OBS_ICON_TYPE_GAME_CAPTURE,
|
||||
.video_get_color_space = game_capture_get_color_space,
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user