win-capture: Add HDR support to Game Capture

This commit is contained in:
jpark37 2022-03-11 22:12:57 -08:00 committed by Jim
parent 08f50a7d22
commit d35e7d3107
2 changed files with 270 additions and 43 deletions

View File

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

View File

@ -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"
@ -49,25 +50,28 @@
#define HOTKEY_START "hotkey_start"
#define HOTKEY_STOP "hotkey_stop"
#define TEXT_MODE obs_module_text("Mode")
#define TEXT_GAME_CAPTURE obs_module_text("GameCapture")
#define TEXT_ANY_FULLSCREEN obs_module_text("GameCapture.AnyFullscreen")
#define TEXT_SLI_COMPATIBILITY obs_module_text("SLIFix")
#define TEXT_ALLOW_TRANSPARENCY obs_module_text("AllowTransparency")
#define TEXT_WINDOW obs_module_text("WindowCapture.Window")
#define TEXT_MATCH_PRIORITY obs_module_text("WindowCapture.Priority")
#define TEXT_MATCH_TITLE obs_module_text("WindowCapture.Priority.Title")
#define TEXT_MATCH_CLASS obs_module_text("WindowCapture.Priority.Class")
#define TEXT_MATCH_EXE obs_module_text("WindowCapture.Priority.Exe")
#define TEXT_CAPTURE_CURSOR obs_module_text("CaptureCursor")
#define TEXT_LIMIT_FRAMERATE obs_module_text("GameCapture.LimitFramerate")
#define TEXT_CAPTURE_OVERLAYS obs_module_text("GameCapture.CaptureOverlays")
#define TEXT_ANTI_CHEAT_HOOK obs_module_text("GameCapture.AntiCheatHook")
#define TEXT_HOOK_RATE obs_module_text("GameCapture.HookRate")
#define TEXT_HOOK_RATE_SLOW obs_module_text("GameCapture.HookRate.Slow")
#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_MODE obs_module_text("Mode")
#define TEXT_GAME_CAPTURE obs_module_text("GameCapture")
#define TEXT_ANY_FULLSCREEN obs_module_text("GameCapture.AnyFullscreen")
#define TEXT_SLI_COMPATIBILITY obs_module_text("SLIFix")
#define TEXT_ALLOW_TRANSPARENCY obs_module_text("AllowTransparency")
#define TEXT_WINDOW obs_module_text("WindowCapture.Window")
#define TEXT_MATCH_PRIORITY obs_module_text("WindowCapture.Priority")
#define TEXT_MATCH_TITLE obs_module_text("WindowCapture.Priority.Title")
#define TEXT_MATCH_CLASS obs_module_text("WindowCapture.Priority.Class")
#define TEXT_MATCH_EXE obs_module_text("WindowCapture.Priority.Exe")
#define TEXT_CAPTURE_CURSOR obs_module_text("CaptureCursor")
#define TEXT_LIMIT_FRAMERATE obs_module_text("GameCapture.LimitFramerate")
#define TEXT_CAPTURE_OVERLAYS obs_module_text("GameCapture.CaptureOverlays")
#define TEXT_ANTI_CHEAT_HOOK obs_module_text("GameCapture.AntiCheatHook")
#define TEXT_HOOK_RATE obs_module_text("GameCapture.HookRate")
#define TEXT_HOOK_RATE_SLOW obs_module_text("GameCapture.HookRate.Slow")
#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,16 +1988,75 @@ 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_enable_framebuffer_srgb(previous);
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);
}
}
}
linear_sample = true;
@ -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"
: "Draw";
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,
};