AyuGramDesktop/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
2021-05-29 17:00:09 +04:00

1067 lines
29 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/group/calls_group_viewport_opengl.h"
#include "calls/group/calls_group_viewport_tile.h"
#include "webrtc/webrtc_video_track.h"
#include "media/view/media_view_pip.h"
#include "calls/group/calls_group_members_row.h"
#include "ui/gl/gl_shader.h"
#include "data/data_peer.h"
#include "styles/style_calls.h"
#include <QtGui/QOpenGLShader>
namespace Calls::Group {
namespace {
using namespace Ui::GL;
constexpr auto kScaleForBlurTextureIndex = 3;
constexpr auto kFirstBlurPassTextureIndex = 4;
constexpr auto kBlurTextureSizeFactor = 1.7;
constexpr auto kBlurOpacity = 0.5;
ShaderPart FragmentBlurTexture(bool vertical, char prefix = 'v') {
const auto offsets = (vertical ? QString("0, 1") : QString("1, 0"));
const auto name = prefix + QString("_texcoord");
return {
.header = R"(
varying vec2 )" + name + R"(;
uniform sampler2D b_texture;
uniform float texelOffset;
const vec3 satLuminanceWeighting = vec3(0.2126, 0.7152, 0.0722);
const vec2 offsets = vec2()" + offsets + R"();
const int radius = 15;
const int diameter = 2 * radius + 1;
)",
.body = R"(
vec4 accumulated = vec4(0.);
for (int i = 0; i != diameter; i++) {
float stepOffset = float(i - radius) * texelOffset;
vec2 offset = vec2(stepOffset) * offsets;
vec4 sampled = vec4(texture2D(b_texture, )" + name + R"( + offset));
float fradius = float(radius);
float boxWeight = fradius + 1.0 - abs(float(i) - fradius);
accumulated += sampled * boxWeight;
}
vec3 blurred = accumulated.rgb / accumulated.a;
float satLuminance = dot(blurred, satLuminanceWeighting);
vec3 mixinColor = vec3(satLuminance);
result = vec4(clamp(mix(mixinColor, blurred, 1.1), 0.0, 1.0), 1.0);
)",
};
}
// Depends on FragmetSampleTexture().
[[nodiscard]] ShaderPart FragmentFrameColor() {
const auto blur = FragmentBlurTexture(true, 'b');
return {
.header = R"(
uniform vec4 frameBg;
uniform vec3 shadow; // fullHeight, shown, maxOpacity
const float backgroundOpacity = )" + QString::number(kBlurOpacity) + R"(;
float insideTexture() {
vec2 textureHalf = vec2(0.5, 0.5);
vec2 fromTextureCenter = abs(v_texcoord - textureHalf);
vec2 fromTextureEdge = max(fromTextureCenter, textureHalf) - textureHalf;
float outsideCheck = dot(fromTextureEdge, fromTextureEdge);
return step(outsideCheck, 0);
}
)" + blur.header + R"(
vec4 background() {
vec4 result;
)" + blur.body + R"(
return result;
}
)",
.body = R"(
float inside = insideTexture();
result = result * inside
+ (1. - inside) * (backgroundOpacity * background()
+ (1. - backgroundOpacity) * frameBg);
float shadowCoord = gl_FragCoord.y - roundRect.y;
float shadowValue = max(1. - (shadowCoord / shadow.x), 0.);
float shadowShown = shadowValue * shadow.y * shadow.z;
result = vec4(result.rgb * (1. - shadowShown), result.a);
)",
};
}
[[nodiscard]] QSize NonEmpty(QSize size) {
return QSize(std::max(size.width(), 1), std::max(size.height(), 1));
}
[[nodiscard]] QSize CountBlurredSize(
QSize unscaled,
QSize viewport,
float factor) {
factor *= kBlurTextureSizeFactor; // The more the scale - more blurred the image.
const auto area = viewport / int(std::round(factor * cScale() / 100));
const auto scaled = unscaled.scaled(area, Qt::KeepAspectRatio);
return (scaled.width() > unscaled.width()
|| scaled.height() > unscaled.height())
? unscaled
: NonEmpty(scaled);
}
[[nodiscard]] std::array<std::array<GLfloat, 2>, 4> CountTexCoords(
QSize unscaled,
QSize size,
bool expand,
bool swap = false) {
const auto scaled = NonEmpty(unscaled.scaled(
size,
expand ? Qt::KeepAspectRatioByExpanding : Qt::KeepAspectRatio));
const auto left = (size.width() - scaled.width()) / 2;
const auto top = (size.height() - scaled.height()) / 2;
const auto right = left + scaled.width();
const auto bottom = top + scaled.height();
auto dleft = float(left) / scaled.width();
auto dright = float(size.width() - left) / scaled.width();
auto dtop = float(top) / scaled.height();
auto dbottom = float(size.height() - top) / scaled.height();
if (swap) {
std::swap(dleft, dtop);
std::swap(dright, dbottom);
}
return { {
{ { -dleft, 1.f + dtop } },
{ { dright, 1.f + dtop } },
{ { dright, 1.f - dbottom } },
{ { -dleft, 1.f - dbottom } },
} };
}
void FillRectVertices(GLfloat *coords, Rect rect) {
coords[0] = coords[10] = rect.left();
coords[1] = coords[11] = rect.top();
coords[2] = rect.right();
coords[3] = rect.top();
coords[4] = coords[6] = rect.right();
coords[5] = coords[7] = rect.bottom();
coords[8] = rect.left();
coords[9] = rect.bottom();
}
void FillTriangles(
QOpenGLFunctions &f,
gsl::span<const GLfloat> coords,
not_null<QOpenGLBuffer*> buffer,
not_null<QOpenGLShaderProgram*> program,
QSize viewportWithFactor,
const QColor &color,
Fn<void()> additional = nullptr) {
Expects(coords.size() % 6 == 0);
if (coords.empty()) {
return;
}
buffer->bind();
buffer->allocate(coords.data(), coords.size() * sizeof(GLfloat));
f.glUseProgram(program->programId());
program->setUniformValue("viewport", QSizeF(viewportWithFactor));
program->setUniformValue("s_color", Uniform(color));
GLint position = program->attributeLocation("position");
f.glVertexAttribPointer(
position,
2,
GL_FLOAT,
GL_FALSE,
2 * sizeof(GLfloat),
nullptr);
f.glEnableVertexAttribArray(position);
if (additional) {
additional();
}
f.glDrawArrays(GL_TRIANGLES, 0, coords.size() / 2);
f.glDisableVertexAttribArray(position);
}
void FillTexturedRectangle(
QOpenGLFunctions &f,
not_null<QOpenGLShaderProgram*> program,
int skipVertices = 0) {
const auto shift = [&](int elements) {
return reinterpret_cast<const void*>(
(skipVertices * 4 + elements) * sizeof(GLfloat));
};
GLint position = program->attributeLocation("position");
f.glVertexAttribPointer(
position,
2,
GL_FLOAT,
GL_FALSE,
4 * sizeof(GLfloat),
shift(0));
f.glEnableVertexAttribArray(position);
GLint texcoord = program->attributeLocation("v_texcoordIn");
f.glVertexAttribPointer(
texcoord,
2,
GL_FLOAT,
GL_FALSE,
4 * sizeof(GLfloat),
shift(2));
f.glEnableVertexAttribArray(texcoord);
f.glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
f.glDisableVertexAttribArray(position);
f.glDisableVertexAttribArray(texcoord);
}
} // namespace
Viewport::RendererGL::RendererGL(not_null<Viewport*> owner)
: _owner(owner)
, _pinIcon(st::groupCallLargeVideo.pin)
, _muteIcon(st::groupCallLargeVideoCrossLine)
, _pinBackground(
(st::groupCallLargeVideo.pinPadding.top()
+ st::groupCallLargeVideo.pin.icon.height()
+ st::groupCallLargeVideo.pinPadding.bottom()) / 2,
st::radialBg) {
style::PaletteChanged(
) | rpl::start_with_next([=] {
_buttons.invalidate();
}, _lifetime);
}
void Viewport::RendererGL::init(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f) {
_factor = widget->devicePixelRatio();
_frameBuffer.emplace();
_frameBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw);
_frameBuffer->create();
_frameBuffer->bind();
constexpr auto kQuads = 6;
constexpr auto kQuadVertices = kQuads * 4;
constexpr auto kQuadValues = kQuadVertices * 4;
constexpr auto kValues = kQuadValues + 8; // Blur texture coordinates.
_frameBuffer->allocate(kValues * sizeof(GLfloat));
_downscaleProgram.yuv420.emplace();
_downscaleVertexShader = LinkProgram(
&*_downscaleProgram.yuv420,
VertexShader({
VertexPassTextureCoord(),
}),
FragmentShader({
FragmentSampleYUV420Texture(),
})).vertex;
_blurProgram.emplace();
LinkProgram(
&*_blurProgram,
_downscaleVertexShader,
FragmentShader({
FragmentBlurTexture(false),
}));
_frameProgram.yuv420.emplace();
_frameVertexShader = LinkProgram(
&*_frameProgram.yuv420,
VertexShader({
VertexViewportTransform(),
VertexPassTextureCoord(),
VertexPassTextureCoord('b'),
}),
FragmentShader({
FragmentSampleYUV420Texture(),
FragmentFrameColor(),
FragmentRoundCorners(),
})).vertex;
_bgBuffer.emplace();
_bgBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw);
_bgBuffer->create();
_bgProgram.emplace();
LinkProgram(
&*_bgProgram,
VertexShader({ VertexViewportTransform() }),
FragmentShader({ FragmentStaticColor() }));
_imageProgram.emplace();
LinkProgram(
&*_imageProgram,
_frameVertexShader,
FragmentShader({
FragmentSampleARGB32Texture(),
}));
}
void Viewport::RendererGL::ensureARGB32Program() {
Expects(_downscaleVertexShader != nullptr);
Expects(_frameVertexShader != nullptr);
_downscaleProgram.argb32.emplace();
LinkProgram(
&*_downscaleProgram.argb32,
_downscaleVertexShader,
FragmentShader({
FragmentSampleARGB32Texture(),
}));
_frameProgram.argb32.emplace();
LinkProgram(
&*_frameProgram.argb32,
_frameVertexShader,
FragmentShader({
FragmentSampleARGB32Texture(),
FragmentFrameColor(),
FragmentRoundCorners(),
}));
}
void Viewport::RendererGL::deinit(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f) {
_bgBuffer = std::nullopt;
_frameBuffer = std::nullopt;
_frameVertexShader = nullptr;
_bgProgram = std::nullopt;
_imageProgram = std::nullopt;
_downscaleProgram.argb32 = std::nullopt;
_downscaleProgram.yuv420 = std::nullopt;
_blurProgram = std::nullopt;
_frameProgram.argb32 = std::nullopt;
_frameProgram.yuv420 = std::nullopt;
for (auto &data : _tileData) {
data.textures.destroy(f);
}
_tileData.clear();
_tileDataIndices.clear();
_buttons.destroy(f);
}
void Viewport::RendererGL::resize(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f,
int w,
int h) {
_factor = widget->devicePixelRatio();
_viewport = QSize(w, h);
setDefaultViewport(f);
}
void Viewport::RendererGL::setDefaultViewport(QOpenGLFunctions &f) {
const auto size = _viewport * _factor;
f.glViewport(0, 0, size.width(), size.height());
}
void Viewport::RendererGL::paint(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f) {
_factor = widget->devicePixelRatio();
validateDatas();
fillBackground(f);
const auto defaultFramebufferObject = widget->defaultFramebufferObject();
auto index = 0;
for (const auto &tile : _owner->_tiles) {
paintTile(
f,
defaultFramebufferObject,
tile.get(),
_tileData[_tileDataIndices[index++]]);
}
}
void Viewport::RendererGL::fillBackground(QOpenGLFunctions &f) {
const auto radius = st::roundRadiusLarge;
const auto radiuses = QMargins{ radius, radius, radius, radius };
auto bg = QRegion(QRect(QPoint(), _viewport));
for (const auto &tile : _owner->_tiles) {
bg -= tile->geometry().marginsRemoved(radiuses);
}
if (bg.isEmpty()) {
return;
}
_bgTriangles.resize((bg.end() - bg.begin()) * 12);
auto coords = _bgTriangles.data();
for (const auto rect : bg) {
FillRectVertices(coords, transformRect(rect));
coords += 12;
}
FillTriangles(
f,
_bgTriangles,
&*_bgBuffer,
&*_bgProgram,
_viewport * _factor,
st::groupCallBg->c);
}
void Viewport::RendererGL::paintTile(
QOpenGLFunctions &f,
GLuint defaultFramebufferObject,
not_null<VideoTile*> tile,
TileData &tileData) {
const auto track = tile->track();
const auto data = track->frameWithInfo(false);
if (data.format == Webrtc::FrameFormat::None) {
return;
}
Assert(!data.yuv420->size.isEmpty());
const auto geometry = tile->geometry();
if (geometry.isEmpty()) {
return;
}
_rgbaFrame = (data.format == Webrtc::FrameFormat::ARGB32);
const auto x = geometry.x();
const auto y = geometry.y();
const auto width = geometry.width();
const auto height = geometry.height();
const auto &st = st::groupCallLargeVideo;
const auto shown = _owner->_controlsShownRatio;
const auto fullNameShift = st.namePosition.y() + st::normalFont->height;
const auto nameShift = anim::interpolate(fullNameShift, 0, shown);
const auto row = tile->row();
const auto style = row->computeIconState(MembersRowStyle::LargeVideo);
validateOutlineAnimation(tile, tileData);
const auto outline = tileData.outlined.value(tileData.outline ? 1. : 0.);
ensureButtonsImage();
// Frame.
const auto unscaled = Media::View::FlipSizeByRotation(
data.yuv420->size,
data.rotation);
const auto swap = (((data.rotation / 90) % 2) == 1);
const auto expand = !_owner->wide()/* && !tile->screencast()*/;
auto texCoords = CountTexCoords(unscaled, geometry.size(), expand, swap);
auto blurTexCoords = expand
? texCoords
: CountTexCoords(unscaled, geometry.size(), true);
const auto rect = transformRect(geometry);
auto toBlurTexCoords = std::array<std::array<GLfloat, 2>, 4> { {
{ { 0.f, 1.f } },
{ { 1.f, 1.f } },
{ { 1.f, 0.f } },
{ { 0.f, 0.f } },
} };
if (const auto shift = (data.rotation / 90); shift > 0) {
std::rotate(
toBlurTexCoords.begin(),
toBlurTexCoords.begin() + shift,
toBlurTexCoords.end());
std::rotate(
texCoords.begin(),
texCoords.begin() + shift,
texCoords.end());
}
// Pin.
const auto pin = _buttons.texturedRect(
tile->pinInner().translated(x, y),
tile->pinned() ? _pinOn : _pinOff,
geometry);
const auto pinRect = transformRect(pin.geometry);
// Mute.
const auto &icon = st::groupCallLargeVideoCrossLine.icon;
const auto iconLeft = x + width - st.iconPosition.x() - icon.width();
const auto iconTop = y + (height
- st.iconPosition.y()
- icon.height()
+ nameShift);
const auto mute = _buttons.texturedRect(
QRect(iconLeft, iconTop, icon.width(), icon.height()),
(row->state() == MembersRow::State::Active
? _muteOff
: _muteOn),
geometry);
const auto muteRect = transformRect(mute.geometry);
// Name.
const auto namePosition = QPoint(
x + st.namePosition.x(),
y + (height
- st.namePosition.y()
- st::semiboldFont->height
+ nameShift));
const auto name = _names.texturedRect(
QRect(namePosition, tileData.nameRect.size() / cIntRetinaFactor()),
tileData.nameRect,
geometry);
const auto nameRect = transformRect(name.geometry);
const GLfloat coords[] = {
// YUV -> RGB-for-blur quad.
-1.f, 1.f,
toBlurTexCoords[0][0], toBlurTexCoords[0][1],
1.f, 1.f,
toBlurTexCoords[1][0], toBlurTexCoords[1][1],
1.f, -1.f,
toBlurTexCoords[2][0], toBlurTexCoords[2][1],
-1.f, -1.f,
toBlurTexCoords[3][0], toBlurTexCoords[3][1],
// First RGB -> RGB blur pass.
-1.f, 1.f,
0.f, 1.f,
1.f, 1.f,
1.f, 1.f,
1.f, -1.f,
1.f, 0.f,
-1.f, -1.f,
0.f, 0.f,
// Second blur pass + paint final frame.
rect.left(), rect.top(),
texCoords[0][0], texCoords[0][1],
rect.right(), rect.top(),
texCoords[1][0], texCoords[1][1],
rect.right(), rect.bottom(),
texCoords[2][0], texCoords[2][1],
rect.left(), rect.bottom(),
texCoords[3][0], texCoords[3][1],
// Additional blurred background texture coordinates.
blurTexCoords[0][0], blurTexCoords[0][1],
blurTexCoords[1][0], blurTexCoords[1][1],
blurTexCoords[2][0], blurTexCoords[2][1],
blurTexCoords[3][0], blurTexCoords[3][1],
// Pin button.
pinRect.left(), pinRect.top(),
pin.texture.left(), pin.texture.bottom(),
pinRect.right(), pinRect.top(),
pin.texture.right(), pin.texture.bottom(),
pinRect.right(), pinRect.bottom(),
pin.texture.right(), pin.texture.top(),
pinRect.left(), pinRect.bottom(),
pin.texture.left(), pin.texture.top(),
// Mute icon.
muteRect.left(), muteRect.top(),
mute.texture.left(), mute.texture.bottom(),
muteRect.right(), muteRect.top(),
mute.texture.right(), mute.texture.bottom(),
muteRect.right(), muteRect.bottom(),
mute.texture.right(), mute.texture.top(),
muteRect.left(), muteRect.bottom(),
mute.texture.left(), mute.texture.top(),
// Name.
nameRect.left(), nameRect.top(),
name.texture.left(), name.texture.bottom(),
nameRect.right(), nameRect.top(),
name.texture.right(), name.texture.bottom(),
nameRect.right(), nameRect.bottom(),
name.texture.right(), name.texture.top(),
nameRect.left(), nameRect.bottom(),
name.texture.left(), name.texture.top(),
};
const auto blurSize = CountBlurredSize(unscaled, _viewport, _factor);
prepareObjects(f, tileData, blurSize);
f.glViewport(0, 0, blurSize.width(), blurSize.height());
_frameBuffer->bind();
_frameBuffer->write(0, coords, sizeof(coords));
bindFrame(f, data, tileData, _downscaleProgram);
tile->track()->markFrameShown();
drawDownscalePass(f, tileData);
drawFirstBlurPass(f, tileData, blurSize);
f.glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject);
setDefaultViewport(f);
bindFrame(f, data, tileData, _frameProgram);
const auto program = _rgbaFrame
? &*_frameProgram.argb32
: &*_frameProgram.yuv420;
const auto uniformViewport = QSizeF(_viewport * _factor);
program->setUniformValue("viewport", uniformViewport);
program->setUniformValue("frameBg", Uniform(st::groupCallBg->c));
program->setUniformValue("radiusOutline", QVector2D(
GLfloat(st::roundRadiusLarge * _factor),
(outline > 0) ? (st::groupCallOutline * _factor) : 0.f));
program->setUniformValue("roundRect", Uniform(rect));
program->setUniformValue("roundBg", Uniform(st::groupCallBg->c));
program->setUniformValue("outlineFg", QVector4D(
st::groupCallMemberActiveIcon->c.redF(),
st::groupCallMemberActiveIcon->c.greenF(),
st::groupCallMemberActiveIcon->c.blueF(),
st::groupCallMemberActiveIcon->c.alphaF() * outline));
const auto shadowHeight = st.shadowHeight * _factor;
const auto shadowAlpha = kShadowMaxAlpha / 255.f;
program->setUniformValue(
"shadow",
QVector3D(shadowHeight, shown, shadowAlpha));
f.glActiveTexture(_rgbaFrame ? GL_TEXTURE1 : GL_TEXTURE3);
tileData.textures.bind(
f,
tileData.textureIndex * 5 + kFirstBlurPassTextureIndex);
program->setUniformValue("b_texture", GLint(_rgbaFrame ? 1 : 3));
program->setUniformValue(
"texelOffset",
GLfloat(1.f / blurSize.height()));
GLint blurTexcoord = program->attributeLocation("b_texcoordIn");
f.glVertexAttribPointer(
blurTexcoord,
2,
GL_FLOAT,
GL_FALSE,
2 * sizeof(GLfloat),
reinterpret_cast<const void*>(48 * sizeof(GLfloat)));
f.glEnableVertexAttribArray(blurTexcoord);
FillTexturedRectangle(f, program, 8);
f.glDisableVertexAttribArray(blurTexcoord);
const auto pinVisible = _owner->wide()
&& (pin.geometry.bottom() > y);
if (nameShift == fullNameShift && !pinVisible) {
return;
}
f.glEnable(GL_BLEND);
f.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
const auto guard = gsl::finally([&] {
f.glDisable(GL_BLEND);
});
f.glUseProgram(_imageProgram->programId());
_imageProgram->setUniformValue("viewport", uniformViewport);
_imageProgram->setUniformValue("s_texture", GLint(0));
f.glActiveTexture(GL_TEXTURE0);
_buttons.bind(f);
if (pinVisible) {
FillTexturedRectangle(f, &*_imageProgram, 14);
}
if (nameShift == fullNameShift) {
return;
}
// Mute.
if (!muteRect.empty()) {
FillTexturedRectangle(f, &*_imageProgram, 18);
}
// Name.
if (!nameRect.empty()) {
_names.bind(f);
FillTexturedRectangle(f, &*_imageProgram, 22);
}
}
void Viewport::RendererGL::prepareObjects(
QOpenGLFunctions &f,
TileData &tileData,
QSize blurSize) {
tileData.textures.ensureCreated(f);
tileData.framebuffers.ensureCreated(f);
const auto create = [&](int index) {
tileData.textures.bind(f, tileData.textureIndex * 5 + index);
f.glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGB,
blurSize.width(),
blurSize.height(),
0,
GL_RGB,
GL_UNSIGNED_BYTE,
nullptr);
};
create(kScaleForBlurTextureIndex);
create(kFirstBlurPassTextureIndex);
}
void Viewport::RendererGL::bindFrame(
QOpenGLFunctions &f,
const Webrtc::FrameWithInfo &data,
TileData &tileData,
Program &program) {
const auto upload = (tileData.trackIndex != data.index);
tileData.trackIndex = data.index;
const auto uploadOne = [&](
GLint internalformat,
GLint format,
QSize size,
int stride,
const void *data) {
f.glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
f.glTexImage2D(
GL_TEXTURE_2D,
0,
internalformat,
size.width(),
size.height(),
0,
format,
GL_UNSIGNED_BYTE,
data);
f.glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
};
//if (upload) {
// tileData.textureIndex = 1 - tileData.textureIndex;
//}
if (_rgbaFrame) {
ensureARGB32Program();
f.glUseProgram(program.argb32->programId());
f.glActiveTexture(GL_TEXTURE0);
tileData.textures.bind(f, tileData.textureIndex);
if (upload) {
const auto &image = data.original;
const auto stride = image.bytesPerLine() / 4;
const auto data = image.constBits();
uploadOne(GL_RGB, GL_RGBA, image.size(), stride, data);
}
program.argb32->setUniformValue("s_texture", GLint(0));
} else {
const auto yuv = data.yuv420;
const auto otherSize = yuv->chromaSize;
f.glUseProgram(program.yuv420->programId());
f.glActiveTexture(GL_TEXTURE0);
tileData.textures.bind(f, tileData.textureIndex * 5 + 0);
if (upload) {
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
uploadOne(GL_RED, GL_RED, yuv->size, yuv->y.stride, yuv->y.data);
}
f.glActiveTexture(GL_TEXTURE1);
tileData.textures.bind(f, tileData.textureIndex * 5 + 1);
if (upload) {
uploadOne(GL_RED, GL_RED, otherSize, yuv->u.stride, yuv->u.data);
}
f.glActiveTexture(GL_TEXTURE2);
tileData.textures.bind(f, tileData.textureIndex * 5 + 2);
if (upload) {
uploadOne(GL_RED, GL_RED, otherSize, yuv->v.stride, yuv->v.data);
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
}
program.yuv420->setUniformValue("y_texture", GLint(0));
program.yuv420->setUniformValue("u_texture", GLint(1));
program.yuv420->setUniformValue("v_texture", GLint(2));
}
}
void Viewport::RendererGL::drawDownscalePass(
QOpenGLFunctions &f,
TileData &tileData) {
tileData.framebuffers.bind(f, 0);
f.glFramebufferTexture2D(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
tileData.textures.id(
tileData.textureIndex * 5 + kScaleForBlurTextureIndex),
0);
const auto program = _rgbaFrame
? &*_downscaleProgram.argb32
: &*_downscaleProgram.yuv420;
FillTexturedRectangle(f, program);
}
void Viewport::RendererGL::drawFirstBlurPass(
QOpenGLFunctions &f,
TileData &tileData,
QSize blurSize) {
tileData.framebuffers.bind(f, 1);
f.glFramebufferTexture2D(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
tileData.textures.id(
tileData.textureIndex * 5 + kFirstBlurPassTextureIndex),
0);
f.glUseProgram(_blurProgram->programId());
f.glActiveTexture(GL_TEXTURE0);
tileData.textures.bind(
f,
tileData.textureIndex * 5 + kScaleForBlurTextureIndex);
_blurProgram->setUniformValue("b_texture", GLint(0));
_blurProgram->setUniformValue(
"texelOffset",
GLfloat(1.f / blurSize.width()));
FillTexturedRectangle(f, &*_blurProgram, 4);
}
Rect Viewport::RendererGL::transformRect(const Rect &raster) const {
return {
raster.left() * _factor,
float(_viewport.height() - raster.bottom()) * _factor,
raster.width() * _factor,
raster.height() * _factor,
};
}
Rect Viewport::RendererGL::transformRect(const QRect &raster) const {
return {
raster.x() * _factor,
(_viewport.height() - raster.y() - raster.height()) * _factor,
raster.width() * _factor,
raster.height() * _factor,
};
}
void Viewport::RendererGL::ensureButtonsImage() {
if (_buttons) {
return;
}
const auto factor = cIntRetinaFactor();
const auto pinOnSize = VideoTile::PinInnerSize(true);
const auto pinOffSize = VideoTile::PinInnerSize(false);
const auto muteSize = st::groupCallLargeVideoCrossLine.icon.size();
const auto fullSize = QSize(
std::max({
pinOnSize.width(),
pinOffSize.width(),
2 * muteSize.width(),
}),
pinOnSize.height() + pinOffSize.height() + muteSize.height());
const auto imageSize = fullSize * factor;
auto image = _buttons.takeImage();
if (image.size() != imageSize) {
image = QImage(imageSize, QImage::Format_ARGB32_Premultiplied);
}
image.fill(Qt::transparent);
image.setDevicePixelRatio(cRetinaFactor());
{
auto p = Painter(&image);
auto hq = PainterHighQualityEnabler(p);
_pinOn = QRect(QPoint(), pinOnSize * factor);
VideoTile::PaintPinButton(
p,
true,
0,
0,
fullSize.width(),
&_pinBackground,
&_pinIcon);
_pinOff = QRect(
QPoint(0, pinOnSize.height()) * factor,
pinOffSize * factor);
VideoTile::PaintPinButton(
p,
false,
0,
pinOnSize.height(),
fullSize.width(),
&_pinBackground,
&_pinIcon);
const auto muteTop = pinOnSize.height() + pinOffSize.height();
_muteOn = QRect(QPoint(0, muteTop) * factor, muteSize * factor);
_muteIcon.paint(p, { 0, muteTop }, 1.);
_muteOff = QRect(
QPoint(muteSize.width(), muteTop) * factor,
muteSize * factor);
_muteIcon.paint(p, { muteSize.width(), muteTop }, 0.);
}
_buttons.setImage(std::move(image));
}
void Viewport::RendererGL::validateDatas() {
const auto &tiles = _owner->_tiles;
const auto &st = st::groupCallLargeVideo;
const auto count = int(tiles.size());
const auto factor = cIntRetinaFactor();
const auto nameHeight = st::semiboldFont->height * factor;
struct Request {
int index = 0;
bool updating = false;
};
auto requests = std::vector<Request>();
auto available = _names.image().width();
for (auto &data : _tileData) {
data.stale = true;
}
_tileDataIndices.resize(count);
const auto nameWidth = [&](int i) {
const auto row = tiles[i]->row();
const auto hasWidth = tiles[i]->geometry().width()
- st.iconPosition.x()
- st::groupCallLargeVideoCrossLine.icon.width()
- st.namePosition.x();
return std::clamp(row->name().maxWidth(), 1, hasWidth) * factor;
};
for (auto i = 0; i != count; ++i) {
tiles[i]->row()->lazyInitialize(st::groupCallMembersListItem);
const auto width = nameWidth(i);
if (width <= 0) {
continue;
}
if (width > available) {
available = width;
}
const auto peer = tiles[i]->row()->peer();
const auto j = ranges::find(_tileData, peer, &TileData::peer);
if (j != end(_tileData)) {
j->stale = false;
const auto index = (j - begin(_tileData));
_tileDataIndices[i] = index;
if (peer->nameVersion != j->nameVersion
|| width != j->nameRect.width()) {
const auto nameTop = index * nameHeight;
j->nameRect = QRect(0, nameTop, width, nameHeight);
requests.push_back({ .index = i, .updating = true });
}
} else {
_tileDataIndices[i] = -1;
requests.push_back({ .index = i, .updating = false });
}
}
if (requests.empty()) {
return;
}
auto maybeStaleAfter = begin(_tileData);
auto maybeStaleEnd = end(_tileData);
for (auto &request : requests) {
const auto i = request.index;
if (_tileDataIndices[i] >= 0) {
continue;
}
const auto peer = tiles[i]->row()->peer();
auto index = int(_tileData.size());
maybeStaleAfter = ranges::find(
maybeStaleAfter,
maybeStaleEnd,
true,
&TileData::stale);
if (maybeStaleAfter != maybeStaleEnd) {
index = (maybeStaleAfter - begin(_tileData));
maybeStaleAfter->peer = peer;
maybeStaleAfter->stale = false;
request.updating = true;
} else {
// This invalidates maybeStale*, but they're already equal.
_tileData.push_back({ .peer = peer });
}
_tileData[index].nameVersion = peer->nameVersion;
_tileData[index].nameRect = QRect(
0,
index * nameHeight,
nameWidth(i),
nameHeight);
_tileDataIndices[i] = index;
}
auto image = _names.takeImage();
const auto imageSize = QSize(
available * factor,
_tileData.size() * nameHeight);
const auto allocate = (image.size() != imageSize);
auto paintToImage = allocate
? QImage(imageSize, QImage::Format_ARGB32_Premultiplied)
: base::take(image);
paintToImage.setDevicePixelRatio(factor);
if (allocate && image.isNull()) {
paintToImage.fill(Qt::transparent);
}
{
auto p = Painter(&paintToImage);
if (!image.isNull()) {
p.setCompositionMode(QPainter::CompositionMode_Source);
p.drawImage(0, 0, image);
if (paintToImage.width() > image.width()) {
p.fillRect(
image.width() / factor,
0,
(paintToImage.width() - image.width()) / factor,
image.height() / factor,
Qt::transparent);
}
if (paintToImage.height() > image.height()) {
p.fillRect(
0,
image.height() / factor,
paintToImage.width() / factor,
(paintToImage.height() - image.height()) / factor,
Qt::transparent);
}
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
}
p.setPen(st::groupCallVideoTextFg);
for (const auto &request : requests) {
const auto i = request.index;
const auto index = _tileDataIndices[i];
const auto &data = _tileData[_tileDataIndices[i]];
const auto row = tiles[i]->row();
if (request.updating) {
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(
0,
data.nameRect.y() / factor,
paintToImage.width() / factor,
nameHeight / factor,
Qt::transparent);
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
}
row->name().drawLeftElided(
p,
0,
data.nameRect.y() / factor,
data.nameRect.width() / factor,
paintToImage.width() / factor);
}
}
_names.setImage(std::move(paintToImage));
}
void Viewport::RendererGL::validateOutlineAnimation(
not_null<VideoTile*> tile,
TileData &data) {
const auto outline = tile->row()->speaking();
if (data.outline == outline) {
return;
}
data.outline = outline;
data.outlined.start(
[=] { _owner->widget()->update(); },
outline ? 0. : 1.,
outline ? 1. : 0.,
st::fadeWrapDuration);
}
} // namespace Calls::Group