Skip to content

Commit

Permalink
MythRenderOpenGL: Greatly simplify drawing rounded rects
Browse files Browse the repository at this point in the history
- convert the shaders to use signed distance functions (https://
www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm)
- shader speed is probably on a par with the old implementation but the
actual rendering is simplified to 2 draw calls and 2 shaders, which
should improve performance
- subtle rendering artifacts are fixed (that resulted from compositing
the rectangle from different parts) and the overall result is a little
smoother than the old version
  • Loading branch information
mark-kendall committed Dec 10, 2020
1 parent 53aa869 commit 422c4ce
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 241 deletions.
259 changes: 55 additions & 204 deletions mythtv/libs/libmythui/opengl/mythrenderopengl.cpp
Expand Up @@ -956,225 +956,79 @@ void MythRenderOpenGL::DrawRect(QOpenGLFramebufferObject *Target,
const QRect Area, const QBrush &FillBrush,
const QPen &LinePen, int Alpha)
{
DrawRoundRect(Target, Area, 1, FillBrush, LinePen, Alpha);
DrawRoundRect(Target, Area, 0, FillBrush, LinePen, Alpha);
}


void MythRenderOpenGL::DrawRoundRect(QOpenGLFramebufferObject *Target,
const QRect Area, int CornerRadius,
const QBrush &FillBrush,
const QPen &LinePen, int Alpha)
{
makeCurrent();
BindFramebuffer(Target);

int lineWidth = LinePen.width();
int halfline = lineWidth / 2;
int rad = CornerRadius - halfline;

if ((Area.width() / 2) < rad)
rad = Area.width() / 2;

if ((Area.height() / 2) < rad)
rad = Area.height() / 2;
int dia = rad * 2;

QRect r(Area.left(), Area.top(), Area.width(), Area.height());

QRect tl(r.left(), r.top(), rad, rad);
QRect tr(r.left() + r.width() - rad, r.top(), rad, rad);
QRect bl(r.left(), r.top() + r.height() - rad, rad, rad);
QRect br(r.left() + r.width() - rad, r.top() + r.height() - rad, rad, rad);

glEnableVertexAttribArray(VERTEX_INDEX);
bool fill = FillBrush.style() != Qt::NoBrush;
bool edge = LinePen.style() != Qt::NoPen;
if (!(fill || edge))
return;

if (FillBrush.style() != Qt::NoBrush)
auto SetColor = [&](const QColor& Color)
{
// Get the shaders
QOpenGLShaderProgram* elip = m_defaultPrograms[kShaderCircle];
QOpenGLShaderProgram* fill = m_defaultPrograms[kShaderSimple];

// Set the fill color
if (m_fullRange)
{
glVertexAttrib4f(COLOR_INDEX,
FillBrush.color().red() / 255.0F,
FillBrush.color().green() / 255.0F,
FillBrush.color().blue() / 255.0F,
(FillBrush.color().alpha() / 255.0F) * (Alpha / 255.0F));
}
else
{
glVertexAttrib4f(COLOR_INDEX,
(FillBrush.color().red() * kLimitedRangeScale) + kLimitedRangeOffset,
(FillBrush.color().blue() * kLimitedRangeScale) + kLimitedRangeOffset,
(FillBrush.color().green() * kLimitedRangeScale) + kLimitedRangeOffset,
(FillBrush.color().alpha() / 255.0F) * (Alpha / 255.0F));
glVertexAttrib4f(COLOR_INDEX, Color.red() / 255.0F, Color.green() / 255.0F,
Color.blue() / 255.0F, (Color.alpha() / 255.0F) * (Alpha / 255.0F));
return;
}
glVertexAttrib4f(COLOR_INDEX, (Color.red() * kLimitedRangeScale) + kLimitedRangeOffset,
(Color.blue() * kLimitedRangeScale) + kLimitedRangeOffset,
(Color.green() * kLimitedRangeScale) + kLimitedRangeOffset,
(Color.alpha() / 255.0F) * (Alpha / 255.0F));
};

float halfwidth = Area.width() / 2.0F;
float halfheight = Area.height() / 2.0F;
float radius = CornerRadius;
if (radius > halfwidth) radius = halfwidth;
if (radius > halfheight) radius = halfheight;
float innerradius = radius - LinePen.width();
if (innerradius < 0.0F) innerradius = 0.0F;

// Set shader parameters
// Centre of the rectangle
m_parameters(0,0) = Area.left() + halfwidth;
m_parameters(1,0) = Area.top() + halfheight;
m_parameters(2,0) = radius;
m_parameters(3,0) = innerradius;
// Rectangle 'size' - distances from the centre to the edge
m_parameters(0,1) = halfwidth;
m_parameters(1,1) = halfheight;
// Adjust the size for the inner radius (edge)
m_parameters(2,1) = halfwidth - LinePen.width();
m_parameters(3,1) = halfheight - LinePen.width();

// Set the radius
m_parameters(2,0) = rad;
m_parameters(3,0) = rad - 1.0F;

// Enable the Circle shader
SetShaderProjection(elip);

// Draw the top left segment
m_parameters(0,0) = tl.left() + rad;
m_parameters(1,0) = tl.top() + rad;

SetShaderProgramParams(elip, m_parameters, "u_parameters");
GetCachedVBO(GL_TRIANGLE_STRIP, tl);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// Draw the top right segment
m_parameters(0,0) = tr.left();
m_parameters(1,0) = tr.top() + rad;
SetShaderProgramParams(elip, m_parameters, "u_parameters");
GetCachedVBO(GL_TRIANGLE_STRIP, tr);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// Draw the bottom left segment
m_parameters(0,0) = bl.left() + rad;
m_parameters(1,0) = bl.top();
SetShaderProgramParams(elip, m_parameters, "u_parameters");
GetCachedVBO(GL_TRIANGLE_STRIP, bl);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// Draw the bottom right segment
m_parameters(0,0) = br.left();
m_parameters(1,0) = br.top();
SetShaderProgramParams(elip, m_parameters, "u_parameters");
GetCachedVBO(GL_TRIANGLE_STRIP, br);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// Fill the remaining areas
QRect main(r.left() + rad, r.top(), r.width() - dia, r.height());
QRect left(r.left(), r.top() + rad, rad, r.height() - dia);
QRect right(r.left() + r.width() - rad, r.top() + rad, rad, r.height() - dia);

SetShaderProjection(fill);
makeCurrent();
BindFramebuffer(Target);
glEnableVertexAttribArray(VERTEX_INDEX);
GetCachedVBO(GL_TRIANGLE_STRIP, Area);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);

GetCachedVBO(GL_TRIANGLE_STRIP, main);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
GetCachedVBO(GL_TRIANGLE_STRIP, left);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
GetCachedVBO(GL_TRIANGLE_STRIP, right);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
if (fill)
{
SetColor(FillBrush.color());
SetShaderProjection(m_defaultPrograms[kShaderRect]);
SetShaderProgramParams(m_defaultPrograms[kShaderRect], m_parameters, "u_parameters");
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
QOpenGLBuffer::release(QOpenGLBuffer::VertexBuffer);
}

if (LinePen.style() != Qt::NoPen)
if (edge)
{
// Get the shaders
QOpenGLShaderProgram* edge = m_defaultPrograms[kShaderCircleEdge];
QOpenGLShaderProgram* vline = m_defaultPrograms[kShaderVertLine];
QOpenGLShaderProgram* hline = m_defaultPrograms[kShaderHorizLine];

// Set the line color
if (m_fullRange)
{
glVertexAttrib4f(COLOR_INDEX,
LinePen.color().red() / 255.0F,
LinePen.color().green() / 255.0F,
LinePen.color().blue() / 255.0F,
(LinePen.color().alpha() / 255.0F) * (Alpha / 255.0F));
}
else
{
glVertexAttrib4f(COLOR_INDEX,
(LinePen.color().red() * kLimitedRangeScale) + kLimitedRangeOffset,
(LinePen.color().blue() * kLimitedRangeScale) + kLimitedRangeOffset,
(LinePen.color().green() * kLimitedRangeScale) + kLimitedRangeOffset,
(FillBrush.color().alpha() / 255.0F) * (Alpha / 255.0F));
}

// Set the radius and width
m_parameters(2,0) = rad - lineWidth / 2.0F;
m_parameters(3,0) = lineWidth / 2.0F;

// Enable the edge shader
SetShaderProjection(edge);

// Draw the top left edge segment
m_parameters(0,0) = tl.left() + rad;
m_parameters(1,0) = tl.top() + rad;
SetShaderProgramParams(edge, m_parameters, "u_parameters");
GetCachedVBO(GL_TRIANGLE_STRIP, tl);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// Draw the top right edge segment
m_parameters(0,0) = tr.left();
m_parameters(1,0) = tr.top() + rad;
SetShaderProgramParams(edge, m_parameters, "u_parameters");
GetCachedVBO(GL_TRIANGLE_STRIP, tr);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat),kVertexOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// Draw the bottom left edge segment
m_parameters(0,0) = bl.left() + rad;
m_parameters(1,0) = bl.top();
SetShaderProgramParams(edge, m_parameters, "u_parameters");
GetCachedVBO(GL_TRIANGLE_STRIP, bl);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// Draw the bottom right edge segment
m_parameters(0,0) = br.left();
m_parameters(1,0) = br.top();
SetShaderProgramParams(edge, m_parameters, "u_parameters");
GetCachedVBO(GL_TRIANGLE_STRIP, br);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// Vertical lines
SetShaderProjection(vline);

m_parameters(1,0) = lineWidth / 2.0F;
QRect vl(r.left(), r.top() + rad, lineWidth, r.height() - dia);

// Draw the left line segment
m_parameters(0,0) = vl.left() + lineWidth;
SetShaderProgramParams(vline, m_parameters, "u_parameters");
GetCachedVBO(GL_TRIANGLE_STRIP, vl);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// Draw the right line segment
vl.translate(r.width() - lineWidth, 0);
m_parameters(0,0) = vl.left();
SetShaderProgramParams(vline, m_parameters, "u_parameters");
GetCachedVBO(GL_TRIANGLE_STRIP, vl);
SetColor(LinePen.color());
SetShaderProjection(m_defaultPrograms[kShaderEdge]);
SetShaderProgramParams(m_defaultPrograms[kShaderEdge], m_parameters, "u_parameters");
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// Horizontal lines
SetShaderProjection(hline);
QRect hl(r.left() + rad, r.top(), r.width() - dia, lineWidth);

// Draw the top line segment
m_parameters(0,0) = hl.top() + lineWidth;
SetShaderProgramParams(hline, m_parameters, "u_parameters");
GetCachedVBO(GL_TRIANGLE_STRIP, hl);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// Draw the bottom line segment
hl.translate(0, r.height() - lineWidth);
m_parameters(0,0) = hl.top();
SetShaderProgramParams(hline, m_parameters, "u_parameters");
GetCachedVBO(GL_TRIANGLE_STRIP, hl);
glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
QOpenGLBuffer::release(QOpenGLBuffer::VertexBuffer);
}

QOpenGLBuffer::release(QOpenGLBuffer::VertexBuffer);
glDisableVertexAttribArray(VERTEX_INDEX);
doneCurrent();
}
Expand Down Expand Up @@ -1654,13 +1508,10 @@ bool MythRenderOpenGL::CreateDefaultShaders(void)
{
m_defaultPrograms[kShaderSimple] = CreateShaderProgram(kSimpleVertexShader, kSimpleFragmentShader);
m_defaultPrograms[kShaderDefault] = CreateShaderProgram(kDefaultVertexShader, m_fullRange ? kDefaultFragmentShader : kDefaultFragmentShaderLimited);
m_defaultPrograms[kShaderCircle] = CreateShaderProgram(kDrawVertexShader, kCircleFragmentShader);
m_defaultPrograms[kShaderCircleEdge] = CreateShaderProgram(kDrawVertexShader, kCircleEdgeFragmentShader);
m_defaultPrograms[kShaderVertLine] = CreateShaderProgram(kDrawVertexShader, kVertLineFragmentShader);
m_defaultPrograms[kShaderHorizLine] = CreateShaderProgram(kDrawVertexShader, kHorizLineFragmentShader);
m_defaultPrograms[kShaderRect] = CreateShaderProgram(kDrawVertexShader, kRoundedRectShader);
m_defaultPrograms[kShaderEdge] = CreateShaderProgram(kDrawVertexShader, kRoundedEdgeShader);
return m_defaultPrograms[kShaderSimple] && m_defaultPrograms[kShaderDefault] &&
m_defaultPrograms[kShaderCircle] && m_defaultPrograms[kShaderCircleEdge] &&
m_defaultPrograms[kShaderVertLine] &&m_defaultPrograms[kShaderHorizLine];
m_defaultPrograms[kShaderRect] && m_defaultPrograms[kShaderEdge];
}

void MythRenderOpenGL::DeleteDefaultShaders(void)
Expand Down
6 changes: 2 additions & 4 deletions mythtv/libs/libmythui/opengl/mythrenderopengl.h
Expand Up @@ -85,10 +85,8 @@ enum DefaultShaders
{
kShaderSimple = 0,
kShaderDefault,
kShaderCircle,
kShaderCircleEdge,
kShaderVertLine,
kShaderHorizLine,
kShaderRect,
kShaderEdge,
kShaderCount,
};

Expand Down
50 changes: 17 additions & 33 deletions mythtv/libs/libmythui/opengl/mythrenderopenglshaders.h
Expand Up @@ -66,52 +66,36 @@ static const QString kDrawVertexShader =
" v_position = a_position;\n"
"}\n";

static const QString kCircleFragmentShader =
"varying highp vec4 v_color;\n"
"varying highp vec2 v_position;\n"
"uniform highp mat4 u_parameters;\n"
"void main(void)\n"
"{\n"
" highp float dis = distance(v_position.xy, u_parameters[0].xy);\n"
" highp float mult = smoothstep(u_parameters[0].z, u_parameters[0].w, dis);\n"
" gl_FragColor = v_color * vec4(1.0, 1.0, 1.0, mult);\n"
"}\n";

static const QString kCircleEdgeFragmentShader =
"varying highp vec4 v_color;\n"
"varying highp vec2 v_position;\n"
"uniform highp mat4 u_parameters;\n"
"void main(void)\n"
static const QString kSDF =
"highp float SignedDistance(highp vec2 p, highp vec2 b, highp float r)\n"
"{\n"
" highp float dis = distance(v_position.xy, u_parameters[0].xy);\n"
" highp float rad = u_parameters[0].z;\n"
" highp float wid = u_parameters[0].w;\n"
" highp float mult = smoothstep(rad + wid, rad + (wid - 1.0), dis) * smoothstep(rad - (wid + 1.0), rad - wid, dis);\n"
" gl_FragColor = v_color * vec4(1.0, 1.0, 1.0, mult);\n"
" return length(max(abs(p) - b + r, 0.0)) - r;"
"}\n";

static const QString kVertLineFragmentShader =
static const QString kRoundedRectShader =
"varying highp vec4 v_color;\n"
"varying highp vec2 v_position;\n"
"uniform highp mat4 u_parameters;\n"
+ kSDF +
"void main(void)\n"
"{\n"
" highp float dis = abs(u_parameters[0].x - v_position.x);\n"
" highp float y = u_parameters[0].y * 2.0;\n"
" highp float mult = smoothstep(y, y - 0.1, dis) * smoothstep(-0.1, 0.0, dis);\n"
" gl_FragColor = v_color * vec4(1.0, 1.0, 1.0, mult);\n"
" highp float dist = SignedDistance(v_position - u_parameters[0].xy,\n"
" u_parameters[1].xy, u_parameters[0].z);\n"
" gl_FragColor = v_color * vec4(1.0, 1.0, 1.0, smoothstep(0.0, -1.0, dist));\n"
"}\n";

static const QString kHorizLineFragmentShader =
static const QString kRoundedEdgeShader =
"varying highp vec4 v_color;\n"
"varying highp vec2 v_position;\n"
"uniform highp mat4 u_parameters;\n"
+ kSDF +
"void main(void)\n"
"{\n"
" highp float dis = abs(u_parameters[0].x - v_position.y);\n"
" highp float x = u_parameters[0].y * 2.0;\n"
" highp float mult = smoothstep(x, x - 0.1, dis) * smoothstep(-0.1, 0.0, dis);\n"
" gl_FragColor = v_color * vec4(1.0, 1.0, 1.0, mult);\n"
" highp float outer = SignedDistance(v_position - u_parameters[0].xy,\n"
" u_parameters[1].xy, u_parameters[0].z);\n"
" highp float inner = SignedDistance(v_position - u_parameters[0].xy,\n"
" u_parameters[1].zw, u_parameters[0].w);\n"
" highp float both = smoothstep(0.0, -1.0, outer) * smoothstep(-1.0, 0.0, inner);\n"
" gl_FragColor = vec4(v_color.rgb, v_color.a * both);\n"
"}\n";

#endif // MYTHRENDER_OPENGL_SHADERS_H
#endif

0 comments on commit 422c4ce

Please sign in to comment.