From 6ebe0084a3f0f27c7525212af3a993828d384709 Mon Sep 17 00:00:00 2001 From: Troy James Sobotka Date: Mon, 11 May 2020 16:07:15 -0700 Subject: [PATCH] Implement Histogram Basic pass at histogram. Uses a power based scale compared against the total pixel count. Probably needs a UI toggle to flip lines on and off as the text and lines in OpenGL are a large cycles sucker. Could be likely much faster when migrated to a geometry shader approach. Uses the scaled viewport resolution to increase performance as much as possible. --- app/shaders/rgbhistogram.frag | 37 +++++ app/shaders/rgbhistogram.vert | 29 ++++ app/shaders/rgbhistogram_secondary.frag | 35 +++++ app/shaders/rgbwaveform.frag | 60 +++----- app/widget/scope/histogram/histogram.cpp | 171 ++++++++++++++++++++++- app/widget/scope/histogram/histogram.h | 17 ++- app/widget/scope/scopebase/scopebase.h | 2 +- app/widget/scope/waveform/waveform.cpp | 11 +- 8 files changed, 314 insertions(+), 48 deletions(-) create mode 100644 app/shaders/rgbhistogram.frag create mode 100644 app/shaders/rgbhistogram.vert create mode 100644 app/shaders/rgbhistogram_secondary.frag diff --git a/app/shaders/rgbhistogram.frag b/app/shaders/rgbhistogram.frag new file mode 100644 index 000000000..435949138 --- /dev/null +++ b/app/shaders/rgbhistogram.frag @@ -0,0 +1,37 @@ +#version 150 + +uniform sampler2D ove_maintex; +uniform vec2 ove_resolution; +uniform vec2 ove_viewport; + +uniform float histogram_scale; + +in vec2 ove_texcoord; + +out vec4 fragColor; + +void main(void) { + float histogram_width = ceil(histogram_scale * ove_viewport.y); + float quantisation = 1.0 / (histogram_width - 1.0); + vec3 cur_col = vec3(0.0); + vec3 sum = vec3(0.0); + float ratio = 0.0; + + for (int i = 0; i < histogram_width; i++) { + ratio = float(i) / float(histogram_width - 1); + cur_col = texture( + ove_maintex, + vec2(ove_texcoord.y, ratio) + ).rgb; + + sum += step(vec3(ove_texcoord.x - quantisation), cur_col) * + step(cur_col, vec3(ove_texcoord.x + quantisation)) + + ( + // Account for values beyond the upper x limit. + step(1.0 - quantisation, ove_texcoord.x) * + step(vec3(1.0 - quantisation), cur_col) + ); + } + + fragColor = vec4(sum, 1.0); +} diff --git a/app/shaders/rgbhistogram.vert b/app/shaders/rgbhistogram.vert new file mode 100644 index 000000000..92536144c --- /dev/null +++ b/app/shaders/rgbhistogram.vert @@ -0,0 +1,29 @@ +#version 150 + +uniform float histogram_scale; +uniform vec2 ove_resolution; + +in vec4 a_position; +in vec2 a_texcoord; + +out vec2 ove_texcoord; + +mat4 scale_mat4(vec3 scale) { + return mat4( + scale.x, 0.0, 0.0, 0.0, + 0.0, scale.y, 0.0, 0.0, + 0.0, 0.0, scale.z, 0.0, + 0.0, 0.0, 0.0, 1.0 + ); +} + +void main() { + // Create identity matrix + mat4 transform = mat4(1.0); + + // Scale the scope + transform *= scale_mat4(vec3(histogram_scale, histogram_scale, 1.0)); + + gl_Position = transform * a_position; + ove_texcoord = a_texcoord; +} \ No newline at end of file diff --git a/app/shaders/rgbhistogram_secondary.frag b/app/shaders/rgbhistogram_secondary.frag new file mode 100644 index 000000000..474db6b74 --- /dev/null +++ b/app/shaders/rgbhistogram_secondary.frag @@ -0,0 +1,35 @@ +#version 150 + +uniform sampler2D ove_maintex; +uniform vec2 ove_resolution; +uniform vec2 ove_viewport; + +uniform float histogram_scale; +uniform float histogram_power; + +in vec2 ove_texcoord; + +out vec4 fragColor; + +void main(void) { + vec3 col = vec3(0.0); + float histogram_height = ceil(ove_viewport.y * histogram_scale); + vec3 histogram_ratio = vec3(0.0); + vec3 sum = vec3(0.0); + float ratio = 0.0; + vec3 total_pixels = vec3(ceil(ove_viewport.x * ove_resolution.y * + histogram_scale)); + + for (int i = 0; i < histogram_height; i++) { + ratio = float(i) / float(histogram_height - 1.0); + sum += texture( + ove_maintex, + vec2(ove_texcoord.x, ratio) + ).rgb; + } + + histogram_ratio = pow(sum / total_pixels, vec3(histogram_power)); + col = step(vec3(ove_texcoord.y), histogram_ratio); + + fragColor = vec4(col, 1.0); +} diff --git a/app/shaders/rgbwaveform.frag b/app/shaders/rgbwaveform.frag index 4e6b13f08..ab677523f 100644 --- a/app/shaders/rgbwaveform.frag +++ b/app/shaders/rgbwaveform.frag @@ -1,6 +1,3 @@ -// Adapted from "RGB Waveform" by lebek -// https://www.shadertoy.com/view/4dK3Wc - #version 150 uniform sampler2D ove_maintex; @@ -11,33 +8,14 @@ uniform vec3 luma_coeffs; uniform float waveform_scale; uniform vec2 waveform_dims; uniform vec4 waveform_region; -uniform vec4 waveform_uv; +uniform vec4 waveform_region_uv; in vec2 ove_texcoord; out vec4 fragColor; void main(void) { - vec3 col = vec3(0.0); - // Set an increment default to 10 bit encodings. This would likely be - // better served as a UI control, as waveforms will change their combing - // based on how granular the increment is set. For example, it can be - // challenging to spot 8 bit combing with an increment of 1. / 2.^8 - 1. - float increment = 1.0 / (pow(2, 10) - 1.0); - float maxb = waveform_dims.y + increment; - float minb = waveform_dims.y - increment; - - // Intensity would make sense to also expose via the UI, as a density - // slider allows you to peek past certain values or reveal very low - // values. Hard coding it for now, as there isn't a clear way to have - // the various bit depth / code values always display at a consistent - // emission output strength. - float intensity = 0.10; - - int y_lim = int(waveform_dims.y); - - vec3 cur_col = vec3(0.0); - vec3 cur_lum = vec3(0.0); + vec4 col = vec4(0.0); if ( (gl_FragCoord.x >= waveform_region.x) && @@ -45,28 +23,34 @@ void main(void) { (gl_FragCoord.x < waveform_region.z) && (gl_FragCoord.y < waveform_region.w) ) { - // col = vec3(0.5, 0.5, 0.0); - // int start = int(waveform_region.y); - int stop = int(waveform_dims.y); + float increment = 0.5; + float uv_increment = increment / (ove_viewport.y - 1.0); + float intensity = 0.10; + vec4 cur_col = vec4(0.0); float ratio = 0.0; - float waveform_x = (ove_texcoord.x - waveform_uv.x) / waveform_scale; - float waveform_y = (ove_texcoord.y - waveform_uv.y) / waveform_scale; + vec2 waveform_current_uv = vec2( + (ove_texcoord.x - waveform_region_uv.x) / waveform_scale, + (ove_texcoord.y - waveform_region_uv.y) / waveform_scale + ); + for (int i = 0; i < waveform_dims.y; i++) { ratio = float(i) / float(waveform_dims.y - 1); - cur_col = texture( + cur_col.rgb = texture( ove_maintex, - vec2(waveform_x, ratio) + vec2(waveform_current_uv.x, ratio) ).rgb; - col += step(vec3(waveform_y - increment), cur_col) * - step(cur_col, vec3(waveform_y + increment)) * intensity; + cur_col.w = dot(cur_col.rgb, luma_coeffs); - cur_lum = vec3(dot(cur_col, luma_coeffs)); - - col += step(vec3(waveform_y - increment), cur_lum) * - step(cur_lum, vec3(waveform_y + increment)) * intensity; + col += ( + step(vec4(waveform_current_uv.y - uv_increment), cur_col) * + step(cur_col, vec4(waveform_current_uv.y + uv_increment)) * + intensity) + + (step(1.0 - uv_increment, waveform_current_uv.y) * + step(vec4(1.0 - uv_increment), cur_col) * intensity); } } - fragColor = vec4(col, 1.0); + col.rgb += vec3(col.w); + fragColor = vec4(col.rgb, 1.0); } diff --git a/app/widget/scope/histogram/histogram.cpp b/app/widget/scope/histogram/histogram.cpp index 9665e32de..be96bb2bd 100644 --- a/app/widget/scope/histogram/histogram.cpp +++ b/app/widget/scope/histogram/histogram.cpp @@ -23,8 +23,9 @@ #include #include -#include "common/clamp.h" -#include "common/functiontimer.h" +#include "common/qtutils.h" +#include "node/node.h" +#include "render/backend/opengl/openglrenderfunctions.h" OLIVE_NAMESPACE_ENTER @@ -33,4 +34,170 @@ HistogramScope::HistogramScope(QWidget* parent) : { } +HistogramScope::~HistogramScope() +{ + CleanUp(); + + if (context()) { + disconnect(context(), &QOpenGLContext::aboutToBeDestroyed, this, + &HistogramScope::CleanUp); + } +} + +void HistogramScope::initializeGL() +{ + ScopeBase::initializeGL(); + + pipeline_secondary_ = CreateSecondaryShader(); + + connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, + &HistogramScope::CleanUp, Qt::DirectConnection); +} + +void HistogramScope::AssertAdditionalTextures() +{ + if (!texture_row_sums_.IsCreated() + || texture_row_sums_.width() != width() + || texture_row_sums_.height() != height()) { + texture_row_sums_.Destroy(); + texture_row_sums_.Create(context(), VideoRenderingParams(width(), + height(), managed_tex_.format())); + } +} + +void HistogramScope::CleanUp() +{ + makeCurrent(); + + pipeline_secondary_ = nullptr; + texture_row_sums_.Destroy(); + + doneCurrent(); +} + +OpenGLShaderPtr HistogramScope::CreateShader() +{ + OpenGLShaderPtr pipeline = OpenGLShader::Create(); + + pipeline->create(); + pipeline->addShaderFromSourceCode(QOpenGLShader::Vertex, + OpenGLShader::CodeDefaultVertex()); + pipeline->addShaderFromSourceCode(QOpenGLShader::Fragment, + Node::ReadFileAsString(":/shaders/rgbhistogram.frag")); + pipeline->link(); + + return pipeline; +} + +OpenGLShaderPtr HistogramScope::CreateSecondaryShader() +{ + OpenGLShaderPtr pipeline_secondary_ = OpenGLShader::Create(); + + pipeline_secondary_->create(); + pipeline_secondary_->addShaderFromSourceCode(QOpenGLShader::Vertex, + Node::ReadFileAsString(":/shaders/rgbhistogram.vert")); + pipeline_secondary_->addShaderFromSourceCode(QOpenGLShader::Fragment, + Node::ReadFileAsString(":/shaders/rgbhistogram_secondary.frag")); + pipeline_secondary_->link(); + + return pipeline_secondary_; +} + +void HistogramScope::DrawScope() +{ + float histogram_scale = 0.80f; + // This value is eyeballed for usefulness. Until we have a geometry + // shader approach, it is impossible to normalize against a peak + // sum of image values. + float histogram_base = 2.5f; + float histogram_power = 1.0f / histogram_base; + + pipeline()->bind(); + pipeline()->setUniformValue("ove_resolution", managed_tex().width(), + managed_tex().height()); + pipeline()->setUniformValue("ove_viewport", width(), height()); + pipeline()->setUniformValue("histogram_scale", histogram_scale); + pipeline()->release(); + + AssertAdditionalTextures(); + + framebuffer_.Attach(&texture_row_sums_, true); + framebuffer_.Bind(); + + managed_tex().Bind(); + + OpenGLRenderFunctions::Blit(pipeline()); + + managed_tex().Release(); + + framebuffer_.Release(); + framebuffer_.Detach(); + + pipeline_secondary_->bind(); + pipeline_secondary_->setUniformValue("ove_resolution", + texture_row_sums_.width(), texture_row_sums_.height()); + pipeline_secondary_->setUniformValue("ove_viewport", width(), height()); + pipeline_secondary_->setUniformValue("histogram_scale", histogram_scale); + pipeline_secondary_->setUniformValue("histogram_power", histogram_power); + pipeline_secondary_->release(); + + texture_row_sums_.Bind(); + + OpenGLRenderFunctions::Blit(pipeline_secondary_); + + texture_row_sums_.Release(); + + // Draw line overlays + QPainter p(this); + QFont font = p.font(); + font.setPixelSize(10); + QFontMetrics font_metrics = QFontMetrics(font); + QString label; + std::vector histogram_increments = { + 0.00, + 0.25, + 0.50, + 1.0 + }; + + int histogram_steps = histogram_increments.size(); + QVector histogram_lines(histogram_steps + 1); + int font_x_offset = 0; + int font_y_offset = font_metrics.capHeight() / 2.0f; + + p.setCompositionMode(QPainter::CompositionMode_Plus); + + p.setPen(QColor(0.0, 0.6 * 255.0, 0.0)); + p.setFont(font); + + float histogram_dim_x = ceil((width() - 1.0) * histogram_scale); + float histogram_dim_y = ceil((height() - 1.0) * histogram_scale); + float histogram_start_dim_x = + ((width() - 1.0) - histogram_dim_x) / 2.0f; + float histogram_start_dim_y = + ((height() - 1.0) - histogram_dim_y) / 2.0f; + float histogram_end_dim_x = (width() - 1.0) - histogram_start_dim_x; + + // for (int i=0; i <= histogram_steps; i++) { + for(std::vector::iterator it = histogram_increments.begin(); + it != histogram_increments.end(); it++) { + histogram_lines[it - histogram_increments.begin()].setLine( + histogram_start_dim_x, + (histogram_dim_y * pow(1.0 - *it, histogram_base)) + + histogram_start_dim_y, + histogram_end_dim_x, + (histogram_dim_y * pow(1.0 - *it, histogram_base)) + + histogram_start_dim_y); + label = QString::number( + *it * 100, 'f', 1) + "%"; + font_x_offset = QFontMetricsWidth(font_metrics, label) + 4; + + p.drawText( + histogram_start_dim_x - font_x_offset, + (histogram_dim_y * pow(1.0 - *it, histogram_base)) + + histogram_start_dim_y + font_y_offset, label); + } + p.drawLines(histogram_lines); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/scope/histogram/histogram.h b/app/widget/scope/histogram/histogram.h index 625074a89..70751355f 100644 --- a/app/widget/scope/histogram/histogram.h +++ b/app/widget/scope/histogram/histogram.h @@ -31,11 +31,24 @@ class HistogramScope : public ScopeBase public: HistogramScope(QWidget* parent = nullptr); + virtual ~HistogramScope() override; + protected: - //virtual OpenGLShaderPtr CreateShader() override; + virtual void initializeGL() override; - //virtual void DrawScope() override; + virtual OpenGLShaderPtr CreateShader() override; + OpenGLShaderPtr CreateSecondaryShader(); + void AssertAdditionalTextures(); + + virtual void DrawScope() override; + +private: + OpenGLShaderPtr pipeline_secondary_; + OpenGLTexture texture_row_sums_; + +private slots: + void CleanUp(); }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/scope/scopebase/scopebase.h b/app/widget/scope/scopebase/scopebase.h index 3098af212..cec2256b1 100644 --- a/app/widget/scope/scopebase/scopebase.h +++ b/app/widget/scope/scopebase/scopebase.h @@ -55,7 +55,7 @@ protected: OpenGLTexture& managed_tex(); -private: +protected: void UploadTextureFromBuffer(); OpenGLShaderPtr pipeline_; diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index daefe84e7..78e054836 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -76,12 +76,13 @@ void WaveformScope::DrawScope() waveform_start_dim_x, waveform_start_dim_y, waveform_end_dim_x, waveform_end_dim_y); - float waveform_start_uv_x = waveform_start_dim_x / width(); - float waveform_start_uv_y = waveform_start_dim_y / height(); - float waveform_end_uv_x = waveform_end_dim_x / width(); - float waveform_end_uv_y = waveform_end_dim_y / height(); + float waveform_start_uv_x = (waveform_start_dim_x - 1.0) / (width() - 1.0); + float waveform_start_uv_y = (waveform_start_dim_y - 1.0) / (height() - 1.0); + float waveform_end_uv_x = (waveform_end_dim_x - 1.0) / (width() - 1.0); + float waveform_end_uv_y = (waveform_end_dim_y - 1.0) / (height() - 1.0); + pipeline()->setUniformValue( - "waveform_uv", + "waveform_region_uv", waveform_start_uv_x, waveform_start_uv_y, waveform_end_uv_x, waveform_end_uv_y);