diff --git a/Telegram/SourceFiles/calls/group/calls_group_large_video.cpp b/Telegram/SourceFiles/calls/group/calls_group_large_video.cpp index 71346def21..a7686bc2e9 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_large_video.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_large_video.cpp @@ -32,65 +32,122 @@ namespace { constexpr auto kShadowMaxAlpha = 80; -const char *FrameVertexShader() { +struct ShaderPart { + QString header; + QString body; +}; + +[[nodiscard]] QString VertexShader(const std::vector &parts) { + const auto accumulate = [&](auto proj) { + return ranges::accumulate(parts, QString(), std::plus<>(), proj); + }; return R"( #version 130 in vec2 position; -in vec2 texcoord; -uniform vec2 viewport; -out vec2 v_texcoord; -vec4 transform(vec2 pos) { - return vec4(vec2(-1, -1) + 2 * pos / viewport, 0., 1.); -} +)" + accumulate(&ShaderPart::header) + R"( void main() { - gl_Position = transform(position); - v_texcoord = texcoord; + vec4 result = vec4(position, 0., 1.); +)" + accumulate(&ShaderPart::body) + R"( + gl_Position = result; } )"; } -const char *FrameFragmentShader() { +[[nodiscard]] QString FragmentShader(const std::vector &parts) { + const auto accumulate = [&](auto proj) { + return ranges::accumulate(parts, QString(), std::plus<>(), proj); + }; return R"( #version 130 +out vec4 fragColor; +)" + accumulate(&ShaderPart::header) + R"( +void main() { + vec4 result = vec4(0., 0., 0., 0.); +)" + accumulate(&ShaderPart::body) + R"( + fragColor = result; +} +)"; +} + +[[nodiscard]] ShaderPart VertexPassTextureCoord() { + return { + .header = R"( +in vec2 texcoord; +out vec2 v_texcoord; +)", + .body = R"( + v_texcoord = texcoord; +)", + }; +} + +[[nodiscard]] ShaderPart FragmentSampleTexture() { + return { + .header = R"( in vec2 v_texcoord; uniform sampler2D s_texture; -out vec4 fragColor; -void main() { - vec4 color = texture(s_texture, v_texcoord); - fragColor = vec4(color.b, color.g, color.r, color.a); -} -)"; +)", + .body = R"( + result = texture(s_texture, v_texcoord); + result = vec4(result.b, result.g, result.r, result.a); +)", + }; } -const char *FillVertexShader() { - return R"( -#version 130 -in vec2 position; +[[nodiscard]] ShaderPart VertexViewportTransform() { + return { + .header = R"( uniform vec2 viewport; -vec4 transform(vec2 pos) { - return vec4(vec2(-1, -1) + 2 * pos / viewport, 0., 1.); +vec4 transform(vec4 position) { + return vec4( + vec2(-1, -1) + 2 * position.xy / viewport, + position.z, + position.w); } -void main() { - gl_Position = transform(position); -} -)"; +)", + .body = R"( + result = transform(result); +)", + }; } -const char *FillFragmentShader() { - return R"( -#version 130 -uniform vec4 s_color; -out vec4 fragColor; -void main() { - fragColor = s_color; +[[nodiscard]] ShaderPart FragmentRoundCorners() { + return { + .header = R"( +uniform vec2 viewport; +uniform float roundRadius; +float roundedCorner() { + vec2 viewportHalf = viewport / 2; + vec2 fromViewportCenter = abs(gl_FragCoord.xy - viewportHalf); + vec2 vectorRadius = vec2(roundRadius + 0.5, roundRadius + 0.5); + vec2 fromCenterWithRadius = fromViewportCenter + vectorRadius; + vec2 fromRoundingCenter = max(fromCenterWithRadius, viewportHalf) + - viewportHalf; + float d = length(fromRoundingCenter) - roundRadius; + return 1. - smoothstep(0., 1., d); } -)"; +)", + .body = R"( + result = vec4(result.r, result.g, result.b, result.a * roundedCorner()); +)", + }; +} + +[[nodiscard]] ShaderPart FragmentStaticColor() { + return { + .header = R"( +uniform vec4 s_color; +)", + .body = R"( + result = s_color; +)", + }; } not_null MakeShader( not_null program, QOpenGLShader::ShaderType type, - const char *source) { + const QString &source) { const auto result = new QOpenGLShader(type, program); if (!result->compileSourceCode(source)) { LOG(("Shader Compilation Failed: %1, error %2." @@ -103,8 +160,8 @@ not_null MakeShader( void LinkProgram( not_null program, - const char *vertexSource, - const char *fragmentSource) { + const QString &vertexSource, + const QString &fragmentSource) { MakeShader(program, QOpenGLShader::Vertex, vertexSource); MakeShader(program, QOpenGLShader::Fragment, fragmentSource); if (!program->link()) { @@ -112,6 +169,73 @@ void LinkProgram( } } +class Quads final { +public: + void fill(QRect rect); + void paint( + not_null f, + not_null buffer, + not_null program, + QSize viewport, + const QColor &color, + Fn additional = nullptr); + +private: + static constexpr auto kMaxTriangles = 8; + std::array coordinates{ 0 }; + int triangles = 0; + +}; + +void Quads::fill(QRect rect) { + Expects(triangles + 2 <= kMaxTriangles); + + auto i = triangles * 6; + coordinates[i + 0] = coordinates[i + 10] = rect.x(); + coordinates[i + 1] = coordinates[i + 11] = rect.y(); + coordinates[i + 2] = rect.x() + rect.width(); + coordinates[i + 3] = rect.y(); + coordinates[i + 4] = coordinates[i + 6] = rect.x() + rect.width(); + coordinates[i + 5] = coordinates[i + 7] = rect.y() + rect.height(); + coordinates[i + 8] = rect.x(); + coordinates[i + 9] = rect.y() + rect.height(); + triangles += 2; +} + +void Quads::paint( + not_null f, + not_null buffer, + not_null program, + QSize viewport, + const QColor &color, + Fn additional) { + if (!triangles) { + return; + } + buffer->bind(); + buffer->allocate(coordinates.data(), triangles * 6 * sizeof(GLfloat)); + + f->glUseProgram(program->programId()); + program->setUniformValue("viewport", QSizeF(viewport)); + program->setUniformValue("s_color", QVector4D( + color.redF(), + color.greenF(), + color.blueF(), + color.alphaF())); + + GLint position = program->attributeLocation("position"); + f->glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (void*)0); + f->glEnableVertexAttribArray(position); + + if (additional) { + additional(); + } + + f->glDrawArrays(GL_TRIANGLES, 0, triangles * 3); + + f->glDisableVertexAttribArray(position); +} + } // namespace struct LargeVideo::PinButton { @@ -158,9 +282,11 @@ private: std::array _textures = {}; std::optional _frameBuffer; std::optional _fillBuffer; + std::optional _cornersBgBuffer; std::optional _frameProgram; std::optional _fillProgram; - qint64 _key = 0; + std::optional _cornersBgProgram; + qint64 _frameKey = 0; }; @@ -192,18 +318,41 @@ void LargeVideo::RendererGL::init( f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } _frameBuffer.emplace(); - _frameBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw); + _frameBuffer->setUsagePattern(QOpenGLBuffer::StaticDraw); _frameBuffer->create(); + _frameProgram.emplace(); + LinkProgram( + &*_frameProgram, + VertexShader({ + VertexViewportTransform(), + VertexPassTextureCoord(), + }), + FragmentShader({ + FragmentSampleTexture(), + FragmentRoundCorners(), + })); _fillBuffer.emplace(); - _fillBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw); + _fillBuffer->setUsagePattern(QOpenGLBuffer::StaticDraw); _fillBuffer->create(); - - _frameProgram.emplace(); - LinkProgram(&*_frameProgram, FrameVertexShader(), FrameFragmentShader()); - _fillProgram.emplace(); - LinkProgram(&*_fillProgram, FillVertexShader(), FillFragmentShader()); + LinkProgram( + &*_fillProgram, + VertexShader({ VertexViewportTransform() }), + FragmentShader({ + FragmentStaticColor(), + FragmentRoundCorners(), + })); + + + _cornersBgBuffer.emplace(); + _cornersBgBuffer->setUsagePattern(QOpenGLBuffer::StaticDraw); + _cornersBgBuffer->create(); + _cornersBgProgram.emplace(); + LinkProgram( + &*_cornersBgProgram, + VertexShader({ VertexViewportTransform() }), + FragmentShader({ FragmentStaticColor() })); } void LargeVideo::RendererGL::deinit( @@ -217,6 +366,7 @@ void LargeVideo::RendererGL::deinit( _fillBuffer = std::nullopt; _frameProgram = std::nullopt; _fillProgram = std::nullopt; + _cornersBgProgram = std::nullopt; } void LargeVideo::RendererGL::resize( @@ -245,11 +395,9 @@ void LargeVideo::RendererGL::paint( const auto [image, rotation] = _owner->_track ? _owner->_track.track->frameOriginalWithRotation() : std::pair(); - if (image.isNull()) { - f->glClearColor(bgvector[0], bgvector[1], bgvector[2], bgvector[3]); - f->glClear(GL_COLOR_BUFFER_BIT); - return; - } + + f->glEnable(GL_BLEND); + f->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); const auto scaled = Media::View::FlipSizeByRotation( image.size(), @@ -259,107 +407,108 @@ void LargeVideo::RendererGL::paint( const auto top = (size.height() - scaled.height()) / 2; const auto right = left + scaled.width(); const auto bottom = top + scaled.height(); + const auto radius = GLfloat(st::roundRadiusLarge * cIntRetinaFactor()); - auto texcoords = std::array, 4> { { - { {0, 1}}, - { {1, 1} }, - { {1, 0} }, - { {0, 0} }, - } }; - if (rotation > 0) { - std::rotate( - texcoords.begin(), - texcoords.begin() + (rotation / 90), - texcoords.end()); - } - const GLfloat vertices[] = { - float(left), float(top), texcoords[0][0], texcoords[0][1], - float(right), float(top), texcoords[1][0], texcoords[1][1], - float(right), float(bottom), texcoords[2][0], texcoords[2][1], - float(left), float(bottom), texcoords[3][0], texcoords[3][1], - }; + auto cornersBg = Quads(); + const auto cornerBgSize = QSize(int(radius), int(radius)); + cornersBg.fill({ QPoint(), cornerBgSize }); + cornersBg.fill({ QPoint(size.width() - int(radius), 0), cornerBgSize }); + cornersBg.fill({ + QPoint(size.width() - int(radius), size.height() - int(radius)), + cornerBgSize + }); + cornersBg.fill({ QPoint(0, size.height() - int(radius)), cornerBgSize }); + cornersBg.paint( + f, + &*_cornersBgBuffer, + &*_cornersBgProgram, + size, + st::groupCallBg->c); - f->glUseProgram(_frameProgram->programId()); - f->glActiveTexture(GL_TEXTURE0); - f->glBindTexture(GL_TEXTURE_2D, _textures[0]); - const auto key = image.cacheKey(); - if (_key != key) { - _key = key; - f->glPixelStorei(GL_UNPACK_ROW_LENGTH, image.bytesPerLine() / 4); - f->glTexImage2D( - GL_TEXTURE_2D, - 0, - GL_RGB, - image.width(), - image.height(), - 0, - GL_RGBA, - GL_UNSIGNED_BYTE, - image.constBits()); - f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - } - _owner->_track.track->markFrameShown(); + auto sideQuads = Quads(); + if (!image.isNull()) { + auto texcoords = std::array, 4> { { + { { 0, 1 }}, + { { 1, 1 } }, + { { 1, 0 } }, + { { 0, 0 } }, + } }; + if (rotation > 0) { + std::rotate( + texcoords.begin(), + texcoords.begin() + (rotation / 90), + texcoords.end()); + } + const GLfloat vertices[] = { + float(left), float(top), texcoords[0][0], texcoords[0][1], + float(right), float(top), texcoords[1][0], texcoords[1][1], + float(right), float(bottom), texcoords[2][0], texcoords[2][1], + float(left), float(bottom), texcoords[3][0], texcoords[3][1], + }; - _frameBuffer->bind(); - _frameBuffer->allocate(vertices, sizeof(vertices)); + f->glUseProgram(_frameProgram->programId()); + f->glActiveTexture(GL_TEXTURE0); + f->glBindTexture(GL_TEXTURE_2D, _textures[0]); + const auto key = image.cacheKey(); + if (_frameKey != key) { + _frameKey = key; + f->glPixelStorei(GL_UNPACK_ROW_LENGTH, image.bytesPerLine() / 4); + f->glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_RGB, + image.width(), + image.height(), + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + image.constBits()); + f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + } + _owner->_track.track->markFrameShown(); - _frameProgram->setUniformValue("viewport", QSizeF(size)); - _frameProgram->setUniformValue("s_texture", GLint(0)); + _frameBuffer->bind(); + _frameBuffer->allocate(vertices, sizeof(vertices)); - GLint position = _frameProgram->attributeLocation("position"); - f->glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)0); - f->glEnableVertexAttribArray(position); + _frameProgram->setUniformValue("viewport", QSizeF(size)); + _frameProgram->setUniformValue("s_texture", GLint(0)); + _frameProgram->setUniformValue("roundRadius", radius); - GLint texcoord = _frameProgram->attributeLocation("texcoord"); - f->glVertexAttribPointer(texcoord, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)(2 * sizeof(GLfloat))); - f->glEnableVertexAttribArray(texcoord); - - f->glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - - f->glDisableVertexAttribArray(position); - f->glDisableVertexAttribArray(texcoord); - - constexpr auto kMaxTriangles = 8; - auto coordinates = std::array{ 0 }; - auto triangles = 0; - const auto fill = [&](QRect rect) { - auto i = triangles * 6; - coordinates[i + 0] = coordinates[i + 10] = rect.x(); - coordinates[i + 1] = coordinates[i + 11] = rect.y(); - coordinates[i + 2] = rect.x() + rect.width(); - coordinates[i + 3] = rect.y(); - coordinates[i + 4] = coordinates[i + 6] = rect.x() + rect.width(); - coordinates[i + 5] = coordinates[i + 7] = rect.y() + rect.height(); - coordinates[i + 8] = rect.x(); - coordinates[i + 9] = rect.y() + rect.height(); - triangles += 2; - }; - if (left > 0) { - fill({ 0, 0, left, size.height() }); - } - if (right < size.width()) { - fill({ right, 0, size.width() - right, size.height() }); - } - if (top > 0) { - fill({ 0, 0, size.width(), top }); - } - if (bottom < size.height()) { - fill({ 0, bottom, size.width(), size.height() - bottom }); - } - if (triangles > 0) { - _fillBuffer->bind(); - _fillBuffer->allocate(coordinates.data(), triangles * 6 * sizeof(GLfloat)); - - f->glUseProgram(_fillProgram->programId()); - _fillProgram->setUniformValue("viewport", QSizeF(size)); - _fillProgram->setUniformValue("s_color", bgvector); - - GLint position = _fillProgram->attributeLocation("position"); - f->glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (void*)0); + GLint position = _frameProgram->attributeLocation("position"); + f->glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)0); f->glEnableVertexAttribArray(position); - f->glDrawArrays(GL_TRIANGLES, 0, triangles * 3); + GLint texcoord = _frameProgram->attributeLocation("texcoord"); + f->glVertexAttribPointer(texcoord, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)(2 * sizeof(GLfloat))); + f->glEnableVertexAttribArray(texcoord); + + f->glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + f->glDisableVertexAttribArray(position); + f->glDisableVertexAttribArray(texcoord); + + if (left > 0) { + sideQuads.fill({ 0, 0, left, size.height() }); + } + if (right < size.width()) { + sideQuads.fill({ right, 0, size.width() - right, size.height() }); + } + if (top > 0) { + sideQuads.fill({ 0, 0, size.width(), top }); + } + if (bottom < size.height()) { + sideQuads.fill({ 0, bottom, size.width(), size.height() - bottom }); + } + } else { + sideQuads.fill({ 0, 0, size.width(), size.height() }); } + sideQuads.paint( + f, + &*_fillBuffer, + &*_fillProgram, + size, + st::groupCallMembersBg->c, + [&] { _fillProgram->setUniformValue("roundRadius", radius); }); } LargeVideo::LargeVideo( @@ -378,6 +527,7 @@ LargeVideo::LargeVideo( ? std::make_unique(widget(), st) : nullptr) , _smallLayout(!_pinButton) { + widget()->resize(0, 0); widget()->setVisible(visible); if (_smallLayout) { widget()->setCursor(style::cur_pointer);