diff --git a/CMakeLists.txt b/CMakeLists.txt index ec7d34b2..d131cc85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1110,6 +1110,7 @@ if(T_CHECK) list(APPEND PROJECT_DEFINITIONS ENABLE_FILTER_COLOR_GRADE ) + set(REQUIRE_LUT ON) endif() # Filter/Displacement diff --git a/data/effects/color-grade.effect b/data/effects/color-grade.effect index f54f5826..34ba7616 100644 --- a/data/effects/color-grade.effect +++ b/data/effects/color-grade.effect @@ -1,18 +1,38 @@ -// Parameters -uniform float4x4 ViewProj; +#include "shared.effect" + +//------------------------------------------------------------------------------ +// Uniforms +//------------------------------------------------------------------------------ + +// Texture to which the effect is applied to. uniform texture2d image; + +// Lift: (rgb + lift.rgb) + lift.a uniform float4 pLift; + +// Gamma: pow(pow(rgb, gamma.rgb), gamma.a) uniform float4 pGamma; + +// Gain: mul(rgb, gain.rgb) * gain.a uniform float4 pGain; + +// Offset: (rgb + offset.rgb) + offset.a uniform float4 pOffset; + +// Tinting uniform int pTintDetection; // 0 = HSV, 1 = HSL, 2 = YUV HD SDR uniform int pTintMode; // 0 = Linear, 1 = Exp, 2 = Exp2, 3 = Log, 4 = Log10 uniform float pTintExponent; uniform float3 pTintLow; uniform float3 pTintMid; uniform float3 pTintHig; + +// Color Correction uniform float4 pCorrection; +//------------------------------------------------------------------------------ +// Defines +//------------------------------------------------------------------------------ #define TINT_DETECTION_HSV 0 #define TINT_DETECTION_HSL 1 #define TINT_DETECTION_YUV_SDR 2 @@ -26,146 +46,30 @@ uniform float4 pCorrection; #define C_e 2,7182818284590452353602874713527 #define C_log2_e 1.4426950408889634073599246810019 // Windows calculator: log(e(1)) / log(2) -// Data -sampler_state def_sampler { - Filter = Point; - AddressU = Clamp; - AddressV = Clamp; - MinLOD = 0; - MaxLOD = 0; +//------------------------------------------------------------------------------ +// Functionality +//------------------------------------------------------------------------------ + +float3 grade_lift(float3 v) { // same as grade_offset? + return (v.rgb + pLift.rgb) + pLift.a; }; -struct VertDataIn { - float4 pos : POSITION; - float2 uv : TEXCOORD0; +float3 grade_gamma(float3 v) { + float3 s = sign(v); // Store sign for later use. + float3 u = abs(v); // Remove sign + u.rgb = pow(pow(u.rgb, pGamma.rgb), pGamma.a); + return u * s; // Restore sign. }; -struct VertDataOut { - float4 pos : POSITION; - float2 uv : TEXCOORD0; +float3 grade_gain(float3 v) { + return (v.rgb * pGain.rgb) * pGain.a; }; -VertDataOut VSDefault(VertDataIn v) -{ - VertDataOut ov; - ov.pos = mul(float4(v.pos.xyz, 1.0), ViewProj); - ov.uv = v.uv; - return ov; -} +float3 grade_offset(float3 v) { + return (v.rgb + pOffset.rgb) + pOffset.a; +}; -// Utility functions ----------------------------------------------------------- -float4 RGBtoYUV(float4 rgba, float3x3 yuv) { - return float4( - rgba.r * yuv._m00 + rgba.g * yuv._m01 + rgba.b * yuv._m02, - rgba.r * yuv._m10 + rgba.g * yuv._m11 + rgba.b * yuv._m12, - rgba.r * yuv._m20 + rgba.g * yuv._m21 + rgba.b * yuv._m22, - rgba.a - ) + float4(0,0.5,0.5,0); -} - -float4 YUVtoRGB(float4 yuva, float3x3 yuvi) { - yuva.gb -= 0.5; - return float4( - yuva.r * yuvi._m00 + yuva.g * yuvi._m01 + yuva.b * yuvi._m02, - yuva.r * yuvi._m10 + yuva.g * yuvi._m11 + yuva.b * yuvi._m12, - yuva.r * yuvi._m20 + yuva.g * yuvi._m21 + yuva.b * yuvi._m22, - yuva.a); -} - -float4 RGBtoHSV(float4 RGBA) { - const float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); - const float e = 1.0e-10; - float4 p = lerp(float4(RGBA.bg, K.wz), float4(RGBA.gb, K.xy), step(RGBA.b, RGBA.g)); - float4 q = lerp(float4(p.xyw, RGBA.r), float4(RGBA.r, p.yzx), step(p.x, RGBA.r)); - float d = q.x - min(q.w, q.y); - return float4(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x, RGBA.a); -} - -float4 HSVtoRGB(float4 HSVA) { - const float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - float4 v = float4(0,0,0,0); - v.rgb = HSVA.z * lerp(K.xxx, clamp(abs(frac(HSVA.xxx + K.xyz) * 6.0 - K.www) - K.xxx, 0.0, 1.0), HSVA.y); - v.a = HSVA.a; - return v; -} - -float4 HSLtoRGB(float4 HSLA) { - float3 rgb = clamp( - abs( - fmod( - HSLA.x * 6.0 + float3(0.0, 4.0, 2.0), - 6.0 - ) - 3.0 - ) - 1.0, - 0.0, - 1.0 - ); - return float4(HSLA.z + HSLA.y * (rgb - 0.5) * (1.0 - abs(2.0 * HSLA.z - 1.0)), HSLA.a); -} - -float4 RGBtoHSL(float4 RGBA) { - float h = 0.0; - float s = 0.0; - float l = 0.0; - float r = RGBA.r; - float g = RGBA.g; - float b = RGBA.b; - float cMin = min( r, min( g, b ) ); - float cMax = max( r, max( g, b ) ); - - l = ( cMax + cMin ) / 2.0; - if ( cMax > cMin ) { - float cDelta = cMax - cMin; - - //s = l < .05 ? cDelta / ( cMax + cMin ) : cDelta / ( 2.0 - ( cMax + cMin ) ); Original - s = l < .0 ? cDelta / ( cMax + cMin ) : cDelta / ( 2.0 - ( cMax + cMin ) ); - - if ( r == cMax ) { - h = ( g - b ) / cDelta; - } else if ( g == cMax ) { - h = 2.0 + ( b - r ) / cDelta; - } else { - h = 4.0 + ( r - g ) / cDelta; - } - - if ( h < 0.0) { - h += 6.0; - } - h = h / 6.0; - } - return float4( h, s, l, RGBA.a ); -} - -// Actual Code ----------------------------------------------------------------- -float4 Lift(float4 v) -{ - v.rgb = pLift.aaa + v.rgb; - v.rgb = pLift.rgb + v.rgb; - return v; -} - -float4 Gamma(float4 v) -{ - v.rgb = pow(pow(v.rgb, pGamma.rgb), pGamma.aaa); - return v; -} - -float4 Gain(float4 v) -{ - v.rgb *= pGain.rgb; - v.rgb *= pGain.a; - return v; -} - -float4 Offset(float4 v) -{ - v.rgb = pOffset.aaa + v.rgb; - v.rgb = pOffset.rgb + v.rgb; - return v; -} - -float4 Tint(float4 v) -{ +float3 grade_tint(float3 v) { float value = 0.; if (pTintDetection == TINT_DETECTION_HSV) { // HSV value = RGBtoHSV(v).z; @@ -199,29 +103,38 @@ float4 Tint(float4 v) } v.rgb *= tint; return v; -} +}; -float4 Correction(float4 v) -{ - float4 v1 = RGBtoHSV(v); - v1.r += pCorrection.r; - v1.g *= pCorrection.g; - v1.b *= pCorrection.b; - float4 v2 = HSVtoRGB(v1); +float3 grade_colorcorrection(float3 v) { + float3 v1 = RGBtoHSV(v); + v1.r += pCorrection.r; // Hue Shift + v1.g *= pCorrection.g; // Saturation Multiplier + v1.b *= pCorrection.b; // Lightness Multiplier + float3 v2 = HSVtoRGB(v1); + + // Contrast v2.rgb = ((v2.rgb - 0.5) * max(pCorrection.a, 0)) + 0.5; - return v2; -} -float4 PSColorGrade(VertDataOut v) : TARGET -{ - return Correction(Tint(Offset(Gain(Gamma(Lift(image.Sample(def_sampler, v.uv))))))); -} + return v2; +}; + +float4 PSDraw(VertexData vtx) : TARGET { + float4 v1 = image.Sample(PointClampSampler, vtx.uv); + float3 v2 = grade_lift(v1.rgb); + float3 v3 = grade_gamma(v2); + float3 v4 = grade_gain(v3); + float3 v5 = grade_offset(v4); + float3 v6 = grade_tint(v5); + float3 v7 = grade_colorcorrection(v6); + float3 vf = v7; + return float4(vf, v1.a); +}; technique Draw { pass { - vertex_shader = VSDefault(v); - pixel_shader = PSColorGrade(v); - } -} + vertex_shader = DefaultVertexShader(vtx); + pixel_shader = PSDraw(vtx); + }; +}; diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index a94c8a2c..1d99a131 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -215,6 +215,14 @@ Filter.ColorGrade.Correction.Hue="Hue Shift" Filter.ColorGrade.Correction.Saturation="Saturation" Filter.ColorGrade.Correction.Lightness="Lightness" Filter.ColorGrade.Correction.Contrast="Contrast" +Filter.ColorGrade.RenderMode="Render Mode" +Filter.ColorGrade.RenderMode.Description="The color grading effect is an expensive operation on the GPU, so two rendering modes exist:\n- 'Direct Rendering' calculates the entire color grade for every single pixel.\n- '#-Bit Look-Up Table' calculates a LUT first, and then renders using said LUT instead, which\nis significantly faster but sacrifices some accuracy. A 2-Bit LUT will be super fast but it\nwill not be as accurate as a 8-Bit LUT would be." +Filter.ColorGrade.RenderMode.Direct="Direct Rendering" +Filter.ColorGrade.RenderMode.LUT.2Bit="2-Bit Look-Up Table" +Filter.ColorGrade.RenderMode.LUT.4Bit="4-Bit Look-Up Table" +Filter.ColorGrade.RenderMode.LUT.6Bit="6-Bit Look-Up Table" +Filter.ColorGrade.RenderMode.LUT.8Bit="8-Bit Look-Up Table" +Filter.ColorGrade.RenderMode.LUT.10Bit="10-Bit Look-Up Table" # Filter - Displacement Filter.Displacement="Displacement Mapping" diff --git a/source/filters/filter-color-grade.cpp b/source/filters/filter-color-grade.cpp index 010e223e..7b887e19 100644 --- a/source/filters/filter-color-grade.cpp +++ b/source/filters/filter-color-grade.cpp @@ -54,6 +54,14 @@ #define ST_CORRECTION ST ".Correction" #define ST_CORRECTION_(x) ST_CORRECTION "." D_VSTR(x) +#define ST_RENDERMODE ST ".RenderMode" +#define ST_RENDERMODE_DIRECT ST_RENDERMODE ".Direct" +#define ST_RENDERMODE_LUT_2BIT ST_RENDERMODE ".LUT.2Bit" +#define ST_RENDERMODE_LUT_4BIT ST_RENDERMODE ".LUT.4Bit" +#define ST_RENDERMODE_LUT_6BIT ST_RENDERMODE ".LUT.6Bit" +#define ST_RENDERMODE_LUT_8BIT ST_RENDERMODE ".LUT.8Bit" +#define ST_RENDERMODE_LUT_10BIT ST_RENDERMODE ".LUT.10Bit" + #define RED Red #define GREEN Green #define BLUE Blue @@ -76,37 +84,64 @@ using namespace streamfx::filter::color_grade; +// TODO: Figure out a way to merge _lut_rt, _lut_texture, _rt_source, _rt_grad, _tex_source, _tex_grade, _source_updated and _grade_updated. +// Seriously this is too much GPU space wasted on unused trash. + +#define LOCAL_PREFIX " " + color_grade_instance::~color_grade_instance() {} -color_grade_instance::color_grade_instance(obs_data_t* data, obs_source_t* self) : obs::source_instance(data, self) +color_grade_instance::color_grade_instance(obs_data_t* data, obs_source_t* self) + : obs::source_instance(data, self), _effect(), + + _lift(), _gamma(), _gain(), _offset(), _tint_detection(), _tint_luma(), _tint_exponent(), _tint_low(), + _tint_mid(), _tint_hig(), _correction(), _lut_enabled(true), _lut_depth(), + + _cache_rt(), _cache_texture(), _cache_fresh(false), + + _lut_initialized(false), _lut_dirty(true), _lut_producer(), _lut_consumer() { - { - auto file = streamfx::data_file_path("effects/color-grade.effect").u8string(); + // Load the color grading effect. + auto path = streamfx::data_file_path("effects/color-grade.effect"); + if (!std::filesystem::exists(path)) { + DLOG_ERROR(LOCAL_PREFIX "Failed to locate effect file '%s'.", path.u8string().c_str()); + throw std::runtime_error("Failed to load color grade effect."); + } else { try { - _effect = gs::effect::create(file); - } catch (std::runtime_error& ex) { - DLOG_ERROR(" Loading effect '%s' failed with error(s): %s", file.c_str(), ex.what()); + _effect = gs::effect::create(path.u8string()); + } catch (std::exception const& ex) { + DLOG_ERROR(LOCAL_PREFIX "Failed to load effect '%s': %s", path.u8string().c_str(), ex.what()); throw; } } - { - _rt_source = std::make_unique(GS_RGBA, GS_ZS_NONE); - { - auto op = _rt_source->render(1, 1); - } - _tex_source = _rt_source->get_texture(); + + // Initialize LUT work flow. + try { + _lut_producer = std::make_shared(); + _lut_consumer = std::make_shared(); + _lut_initialized = true; + } catch (std::exception const& ex) { + DLOG_WARNING(LOCAL_PREFIX "Failed to initialize LUT rendering, falling back to direct rendering.\n%s", + ex.what()); + _lut_initialized = false; } - { - _rt_grade = std::make_unique(GS_RGBA, GS_ZS_NONE); - { - auto op = _rt_grade->render(1, 1); - } - _tex_grade = _rt_grade->get_texture(); + + // Allocate render target for rendering. + try { + allocate_rendertarget(GS_RGBA); + } catch (std::exception const& ex) { + DLOG_ERROR(LOCAL_PREFIX "Failed to acquire render target for rendering: %s", ex.what()); + throw; } update(data); } +void color_grade_instance::allocate_rendertarget(gs_color_format format) +{ + _cache_rt = std::make_unique(format, GS_ZS_NONE); +} + float_t fix_gamma_value(double_t v) { if (v < 0.0) { @@ -157,86 +192,110 @@ void color_grade_instance::update(obs_data_t* data) _correction.y = static_cast(obs_data_get_double(data, ST_CORRECTION_(SATURATION)) / 100.0); _correction.z = static_cast(obs_data_get_double(data, ST_CORRECTION_(LIGHTNESS)) / 100.0); _correction.w = static_cast(obs_data_get_double(data, ST_CORRECTION_(CONTRAST)) / 100.0); -} -void color_grade_instance::video_tick(float) -{ - _source_updated = false; - _grade_updated = false; -} + { + int64_t v = obs_data_get_int(data, ST_RENDERMODE); -void color_grade_instance::video_render(gs_effect_t* effect) -{ - // Grab initial values. - obs_source_t* parent = obs_filter_get_parent(_self); - obs_source_t* target = obs_filter_get_target(_self); - uint32_t width = obs_source_get_base_width(target); - uint32_t height = obs_source_get_base_height(target); - gs_effect_t* effect_default = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); + // LUT status depends on selected option. + _lut_enabled = v != 0; // 0 (Direct) - // Skip filter if anything is wrong. - if (!parent || !target || !width || !height || !effect_default) { - obs_source_skip_video_filter(_self); - return; + if (v == -1) { + _lut_depth = gfx::lut::color_depth::_8; + } else if (v > 0) { + _lut_depth = static_cast(v); + } } + if (_lut_enabled && _lut_initialized) + _lut_dirty = true; +} + +void color_grade_instance::prepare_effect() +{ + if (auto p = _effect.get_parameter("pLift"); p) { + p.set_float4(_lift); + } + + if (auto p = _effect.get_parameter("pGamma"); p) { + p.set_float4(_gamma); + } + + if (auto p = _effect.get_parameter("pGain"); p) { + p.set_float4(_gain); + } + + if (auto p = _effect.get_parameter("pOffset"); p) { + p.set_float4(_offset); + } + + if (auto p = _effect.get_parameter("pLift"); p) { + p.set_float4(_lift); + } + + if (auto p = _effect.get_parameter("pTintDetection"); p) { + p.set_int(static_cast(_tint_detection)); + } + + if (auto p = _effect.get_parameter("pTintMode"); p) { + p.set_int(static_cast(_tint_luma)); + } + + if (auto p = _effect.get_parameter("pTintExponent"); p) { + p.set_float(_tint_exponent); + } + + if (auto p = _effect.get_parameter("pTintLow"); p) { + p.set_float3(_tint_low); + } + + if (auto p = _effect.get_parameter("pTintMid"); p) { + p.set_float3(_tint_mid); + } + + if (auto p = _effect.get_parameter("pTintHig"); p) { + p.set_float3(_tint_hig); + } + + if (auto p = _effect.get_parameter("pCorrection"); p) { + p.set_float4(_correction); + } +} + +void color_grade_instance::rebuild_lut() +{ #ifdef ENABLE_PROFILING - gs::debug_marker gdmp{gs::debug_color_source, "Color Grading '%s'", obs_source_get_name(_self)}; + gs::debug_marker gdm{gs::debug_color_cache, "Rebuild LUT"}; #endif - if (!_source_updated) { -#ifdef ENABLE_PROFILING - gs::debug_marker gdm{gs::debug_color_cache, "Cache"}; -#endif + // Generate a fresh LUT texture. + auto lut_texture = _lut_producer->produce(_lut_depth); - if (obs_source_process_filter_begin(_self, GS_RGBA, OBS_ALLOW_DIRECT_RENDERING)) { - auto op = _rt_source->render(width, height); - gs_blend_state_push(); - gs_reset_blend_state(); - gs_set_cull_mode(GS_NEITHER); - gs_enable_color(true, true, true, true); - gs_enable_blending(false); - gs_enable_depth_test(false); - gs_enable_stencil_test(false); - gs_enable_stencil_write(false); - gs_ortho(0, static_cast(width), 0, static_cast(height), -1., 1.); - obs_source_process_filter_end(_self, effect ? effect : effect_default, width, height); - gs_blend_state_pop(); + // Modify the LUT with our color grade. + if (lut_texture) { + // Check if we have a render target to work with and if it's the correct format. + if (!_lut_rt || (lut_texture->get_color_format() != _lut_rt->get_color_format())) { + // Create a new render target with new format. + _lut_rt = std::make_unique(lut_texture->get_color_format(), GS_ZS_NONE); } - _tex_source = _rt_source->get_texture(); - _source_updated = true; - } + // Prepare our color grade effect. + prepare_effect(); - if (!_grade_updated) { -#ifdef ENABLE_PROFILING - gs::debug_marker gdm{gs::debug_color_convert, "Calculate"}; -#endif + // Assign texture. + if (auto p = _effect.get_parameter("image"); p) { + p.set_texture(lut_texture); + } - { - auto op = _rt_grade->render(width, height); + { // Begin rendering. + auto op = _lut_rt->render(lut_texture->get_width(), lut_texture->get_height()); + + // Set up graphics context. + gs_ortho(0, 1, 0, 1, 0, 1); gs_blend_state_push(); - gs_reset_blend_state(); - gs_set_cull_mode(GS_NEITHER); - gs_enable_color(true, true, true, true); gs_enable_blending(false); - gs_enable_depth_test(false); + gs_enable_color(true, true, true, true); gs_enable_stencil_test(false); gs_enable_stencil_write(false); - gs_ortho(0, 1, 0, 1, -1., 1.); - - _effect.get_parameter("image").set_texture(_tex_source); - _effect.get_parameter("pLift").set_float4(_lift); - _effect.get_parameter("pGamma").set_float4(_gamma); - _effect.get_parameter("pGain").set_float4(_gain); - _effect.get_parameter("pOffset").set_float4(_offset); - _effect.get_parameter("pTintDetection").set_int(static_cast(_tint_detection)); - _effect.get_parameter("pTintMode").set_int(static_cast(_tint_luma)); - _effect.get_parameter("pTintExponent").set_float(_tint_exponent); - _effect.get_parameter("pTintLow").set_float3(_tint_low); - _effect.get_parameter("pTintMid").set_float3(_tint_mid); - _effect.get_parameter("pTintHig").set_float3(_tint_hig); - _effect.get_parameter("pCorrection").set_float4(_correction); while (gs_effect_loop(_effect.get_object(), "Draw")) { streamfx::gs_draw_fullscreen_tri(); @@ -245,21 +304,140 @@ void color_grade_instance::video_render(gs_effect_t* effect) gs_blend_state_pop(); } - _tex_grade = _rt_grade->get_texture(); - _source_updated = true; + _lut_rt->get_texture(_lut_texture); + if (!_lut_texture) { + throw std::runtime_error("Failed to produce modified LUT texture."); + } + } else { + throw std::runtime_error("Failed to produce LUT texture."); + } + + _lut_dirty = false; +} + +void color_grade_instance::video_tick(float) +{ + _ccache_fresh = false; + _cache_fresh = false; +} + +void color_grade_instance::video_render(gs_effect_t*) +{ + // Grab initial values. + obs_source_t* parent = obs_filter_get_parent(_self); + obs_source_t* target = obs_filter_get_target(_self); + uint32_t width = obs_source_get_base_width(target); + uint32_t height = obs_source_get_base_height(target); + + // Skip filter if anything is wrong. + if (!parent || !target || !width || !height) { + obs_source_skip_video_filter(_self); + return; } - // Render final result. - { #ifdef ENABLE_PROFILING - gs::debug_marker gdm{gs::debug_color_render, "Render"}; + gs::debug_marker gdmp{gs::debug_color_source, "Color Grading '%s'", obs_source_get_name(_self)}; #endif + // TODO: Optimize this once (https://github.com/obsproject/obs-studio/pull/4199) is merged. + // - We can skip the original capture and reduce the overall impact of this. + + // 1. Capture the filter/source rendered above this. + if (!_ccache_fresh) { +#ifdef ENABLE_PROFILING + gs::debug_marker gdmp{gs::debug_color_cache, "Cache '%s'", obs_source_get_name(target)}; +#endif + if (!_ccache_rt) { + _ccache_rt = std::make_shared(GS_RGBA, GS_ZS_NONE); + } + + { + auto op = _ccache_rt->render(width, height); + gs_ortho(0, static_cast(width), 0, static_cast(height), 0, 1); + + obs_source_process_filter_begin(_self, GS_RGBA, OBS_ALLOW_DIRECT_RENDERING); + obs_source_process_filter_end(_self, obs_get_base_effect(OBS_EFFECT_DEFAULT), width, height); + } + + _ccache_rt->get_texture(_ccache_texture); + if (!_ccache_texture) { + throw std::runtime_error("Failed to cache original source."); + } + + _ccache_fresh = true; + } + + // 2. Apply one of the two rendering methods (LUT or Direct). + if (_lut_initialized && _lut_enabled) { + try { +#ifdef ENABLE_PROFILING + gs::debug_marker gdm{gs::debug_color_convert, "LUT Rendering"}; +#endif + if (_lut_dirty) { + rebuild_lut(); + _cache_fresh = false; + } + + if (!_cache_fresh) { + { // Render the source to the cache. + auto op = _cache_rt->render(width, height); + gs_ortho(0, 1., 0, 1., 0, 1); + + auto effect = _lut_consumer->prepare(_lut_depth, _lut_texture); + effect->get_parameter("image").set_texture(_ccache_texture); + while (gs_effect_loop(effect->get_object(), "Draw")) { + streamfx::gs_draw_fullscreen_tri(); + } + } + + _cache_rt->get_texture(_cache_texture); + _cache_fresh = true; + } + } catch (std::exception const& ex) { + _lut_rt.reset(); + _lut_texture.reset(); + _lut_enabled = false; + DLOG_WARNING(LOCAL_PREFIX "Reverting to direct rendering due to error: %s", ex.what()); + } + } + if ((!_lut_initialized || !_lut_enabled) && !_cache_fresh) { +#ifdef ENABLE_PROFILING + gs::debug_marker gdm{gs::debug_color_convert, "Direct Rendering"}; +#endif + // Reallocate the rendertarget if necessary. + if (_cache_rt->get_color_format() != GS_RGBA) { + allocate_rendertarget(GS_RGBA); + } + + { // Render the source to the cache. + auto op = _cache_rt->render(width, height); + gs_ortho(0, static_cast(width), 0, static_cast(height), 0, 1); + // TODO: Check if clearing things is required. + + prepare_effect(); + obs_source_process_filter_begin(_self, GS_RGBA, OBS_ALLOW_DIRECT_RENDERING); + obs_source_process_filter_end(_self, _effect.get_object(), width, height); + } + + _cache_rt->get_texture(_cache_texture); + _cache_fresh = true; + } + + if (!_cache_texture) { + throw std::runtime_error("Failed to cache processed source."); + } + + // 3. Render the output cache. + { +#ifdef ENABLE_PROFILING + gs::debug_marker gdm{gs::debug_color_cache_render, "Draw Cache"}; +#endif auto shader = obs_get_base_effect(OBS_EFFECT_DEFAULT); + gs_enable_depth_test(false); while (gs_effect_loop(shader, "Draw")) { gs_effect_set_texture(gs_effect_get_param_by_name(shader, "image"), - _tex_grade ? _tex_grade->get_object() : nullptr); + _cache_texture ? _cache_texture->get_object() : nullptr); gs_draw_sprite(nullptr, 0, width, height); } } @@ -269,7 +447,7 @@ color_grade_factory::color_grade_factory() { _info.id = PREFIX "filter-color-grade"; _info.type = OBS_SOURCE_TYPE_FILTER; - _info.output_flags = OBS_SOURCE_VIDEO; + _info.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW; set_resolution_enabled(false); finish_setup(); @@ -318,6 +496,8 @@ void color_grade_factory::get_defaults2(obs_data_t* data) obs_data_set_default_double(data, ST_CORRECTION_(SATURATION), 100.0); obs_data_set_default_double(data, ST_CORRECTION_(LIGHTNESS), 100.0); obs_data_set_default_double(data, ST_CORRECTION_(CONTRAST), 100.0); + + obs_data_set_default_int(data, ST_RENDERMODE, -1); } obs_properties_t* color_grade_factory::get_properties2(color_grade_instance* data) @@ -433,6 +613,24 @@ obs_properties_t* color_grade_factory::get_properties2(color_grade_instance* dat } obs_properties_add_float_slider(grp, ST_TINT_EXPONENT, D_TRANSLATE(ST_TINT_EXPONENT), 0., 10., 0.01); + + { + auto p = obs_properties_add_list(grp, ST_RENDERMODE, D_TRANSLATE(ST_RENDERMODE), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_RENDERMODE))); + std::pair els[] = { + {S_STATE_AUTOMATIC, -1}, + {ST_RENDERMODE_DIRECT, 0}, + {ST_RENDERMODE_LUT_2BIT, static_cast(gfx::lut::color_depth::_2)}, + {ST_RENDERMODE_LUT_4BIT, static_cast(gfx::lut::color_depth::_4)}, + {ST_RENDERMODE_LUT_6BIT, static_cast(gfx::lut::color_depth::_6)}, + {ST_RENDERMODE_LUT_8BIT, static_cast(gfx::lut::color_depth::_8)}, + //{ST_RENDERMODE_LUT_10BIT, static_cast(gfx::lut::color_depth::_10)}, + }; + for (auto kv : els) { + obs_property_list_add_int(p, D_TRANSLATE(kv.first), kv.second); + } + } } return pr; diff --git a/source/filters/filter-color-grade.hpp b/source/filters/filter-color-grade.hpp index 4edfe910..404d7bce 100644 --- a/source/filters/filter-color-grade.hpp +++ b/source/filters/filter-color-grade.hpp @@ -19,6 +19,9 @@ #pragma once #include +#include "gfx/lut/gfx-lut-consumer.hpp" +#include "gfx/lut/gfx-lut-producer.hpp" +#include "gfx/lut/gfx-lut.hpp" #include "obs/gs/gs-mipmapper.hpp" #include "obs/gs/gs-rendertarget.hpp" #include "obs/gs/gs-texture.hpp" @@ -44,37 +47,53 @@ namespace streamfx::filter::color_grade { class color_grade_instance : public obs::source_instance { gs::effect _effect; - // Source - std::unique_ptr _rt_source; - std::shared_ptr _tex_source; - bool _source_updated; + // User Configuration + vec4 _lift; + vec4 _gamma; + vec4 _gain; + vec4 _offset; + detection_mode _tint_detection; + luma_mode _tint_luma; + float_t _tint_exponent; + vec3 _tint_low; + vec3 _tint_mid; + vec3 _tint_hig; + vec4 _correction; + bool _lut_enabled; + gfx::lut::color_depth _lut_depth; - // Grading - std::unique_ptr _rt_grade; - std::shared_ptr _tex_grade; - bool _grade_updated; + // Capture Cache + std::shared_ptr _ccache_rt; + std::shared_ptr _ccache_texture; + bool _ccache_fresh; - // Parameters - vec4 _lift; - vec4 _gamma; - vec4 _gain; - vec4 _offset; - detection_mode _tint_detection; - luma_mode _tint_luma; - float_t _tint_exponent; - vec3 _tint_low; - vec3 _tint_mid; - vec3 _tint_hig; - vec4 _correction; + // LUT work flow + bool _lut_initialized; + bool _lut_dirty; + std::shared_ptr _lut_producer; + std::shared_ptr _lut_consumer; + std::shared_ptr _lut_rt; + std::shared_ptr _lut_texture; + + // Render Cache + std::shared_ptr _cache_rt; + std::shared_ptr _cache_texture; + bool _cache_fresh; public: color_grade_instance(obs_data_t* data, obs_source_t* self); virtual ~color_grade_instance(); + void allocate_rendertarget(gs_color_format format); + virtual void load(obs_data_t* data) override; virtual void migrate(obs_data_t* data, uint64_t version) override; virtual void update(obs_data_t* data) override; + void prepare_effect(); + + void rebuild_lut(); + virtual void video_tick(float_t time) override; virtual void video_render(gs_effect_t* effect) override; };