tdesktop/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp

926 lines
26 KiB
C++
Raw Normal View History

2021-06-02 16:36:24 +00:00
/*
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 "media/view/media_view_overlay_opengl.h"
#include "ui/gl/gl_shader.h"
2022-09-16 20:23:27 +00:00
#include "ui/painter.h"
2021-06-03 12:57:48 +00:00
#include "media/streaming/media_streaming_common.h"
#include "platform/platform_overlay_widget.h"
2021-06-02 16:36:24 +00:00
#include "base/platform/base_platform_info.h"
#include "core/crash_reports.h"
#include "styles/style_media_view.h"
2021-06-02 16:36:24 +00:00
namespace Media::View {
namespace {
using namespace Ui::GL;
constexpr auto kRadialLoadingOffset = 4;
constexpr auto kThemePreviewOffset = kRadialLoadingOffset + 4;
constexpr auto kDocumentBubbleOffset = kThemePreviewOffset + 4;
constexpr auto kSaveMsgOffset = kDocumentBubbleOffset + 4;
constexpr auto kFooterOffset = kSaveMsgOffset + 4;
constexpr auto kCaptionOffset = kFooterOffset + 4;
constexpr auto kGroupThumbsOffset = kCaptionOffset + 4;
constexpr auto kControlsOffset = kGroupThumbsOffset + 4;
constexpr auto kControlValues = 4 * 4 + 4 * 4; // over + icon
[[nodiscard]] ShaderPart FragmentApplyControlsFade() {
return {
.header = R"(
uniform sampler2D f_texture;
uniform vec4 shadowTopRect;
uniform vec2 shadowBottomAndOpacity;
)",
.body = R"(
float topHeight = shadowTopRect.w;
float bottomHeight = shadowBottomAndOpacity.x;
float opacity = shadowBottomAndOpacity.y;
float viewportHeight = shadowTopRect.y + topHeight;
float fullHeight = topHeight + bottomHeight;
float topY = min(
(viewportHeight - gl_FragCoord.y) / fullHeight,
topHeight / fullHeight);
float topX = (gl_FragCoord.x - shadowTopRect.x) / shadowTopRect.z;
vec4 fadeTop = texture2D(f_texture, vec2(topX, topY)) * opacity;
float bottomY = max(fullHeight - gl_FragCoord.y, topHeight) / fullHeight;
vec4 fadeBottom = texture2D(f_texture, vec2(0.5, bottomY)) * opacity;
result.rgb = result.rgb * (1. - fadeTop.a) * (1. - fadeBottom.a);
)",
};
}
2021-06-03 12:57:48 +00:00
[[nodiscard]] ShaderPart FragmentPlaceOnTransparentBackground() {
return {
.header = R"(
uniform vec4 transparentBg;
uniform vec4 transparentFg;
uniform float transparentSize;
)",
.body = R"(
vec2 checkboardLadder = floor(gl_FragCoord.xy / transparentSize);
float checkboard = mod(checkboardLadder.x + checkboardLadder.y, 2.0);
vec4 checkboardColor = checkboard * transparentBg
+ (1. - checkboard) * transparentFg;
result += checkboardColor * (1. - result.a);
)",
};
}
[[nodiscard]] ShaderPart FragmentRoundedCorners() {
return {
.header = R"(
uniform vec4 roundRect;
uniform float roundRadius;
float roundedCorner() {
vec2 rectHalf = roundRect.zw / 2.;
vec2 rectCenter = roundRect.xy + rectHalf;
vec2 fromRectCenter = abs(gl_FragCoord.xy - rectCenter);
vec2 vectorRadius = vec2(roundRadius + 0.5, roundRadius + 0.5);
vec2 fromCenterWithRadius = fromRectCenter + vectorRadius;
vec2 fromRoundingCenter = max(fromCenterWithRadius, rectHalf)
- rectHalf;
float rounded = length(fromRoundingCenter) - roundRadius;
return 1. - smoothstep(0., 1., rounded);
}
)",
.body = R"(
result = vec4(roundedCorner());
)",
};
}
} // namespace
OverlayWidget::RendererGL::RendererGL(not_null<OverlayWidget*> owner)
: _owner(owner) {
style::PaletteChanged(
) | rpl::start_with_next([=] {
_controlsFadeImage.invalidate();
_radialImage.invalidate();
_documentBubbleImage.invalidate();
_themePreviewImage.invalidate();
_saveMsgImage.invalidate();
_footerImage.invalidate();
_captionImage.invalidate();
invalidateControls();
}, _lifetime);
}
2021-06-02 16:36:24 +00:00
void OverlayWidget::RendererGL::init(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f) {
constexpr auto kQuads = 8;
constexpr auto kQuadVertices = kQuads * 4;
constexpr auto kQuadValues = kQuadVertices * 4;
constexpr auto kControlsValues = kControlsCount * kControlValues;
constexpr auto kRoundingQuads = 4;
constexpr auto kRoundingVertices = kRoundingQuads * 6;
constexpr auto kRoundingValues = kRoundingVertices * 2;
constexpr auto kValues = kQuadValues + kControlsValues + kRoundingValues;
_contentBuffer.emplace();
_contentBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw);
_contentBuffer->create();
_contentBuffer->bind();
_contentBuffer->allocate(kValues * sizeof(GLfloat));
_textures.ensureCreated(f);
_imageProgram.emplace();
2021-06-03 12:57:48 +00:00
_texturedVertexShader = LinkProgram(
&*_imageProgram,
VertexShader({
VertexViewportTransform(),
VertexPassTextureCoord(),
}),
FragmentShader({
FragmentSampleARGB32Texture(),
2021-06-03 12:57:48 +00:00
})).vertex;
_staticContentProgram.emplace();
LinkProgram(
&*_staticContentProgram,
_texturedVertexShader,
FragmentShader({
FragmentSampleARGB32Texture(),
FragmentApplyControlsFade()
}));
2021-06-03 12:57:48 +00:00
_withTransparencyProgram.emplace();
LinkProgram(
&*_withTransparencyProgram,
_texturedVertexShader,
FragmentShader({
FragmentSampleARGB32Texture(),
FragmentPlaceOnTransparentBackground(),
FragmentApplyControlsFade()
2021-06-03 12:57:48 +00:00
}));
_yuv420Program.emplace();
LinkProgram(
&*_yuv420Program,
_texturedVertexShader,
FragmentShader({
FragmentSampleYUV420Texture(),
FragmentApplyControlsFade()
}));
_nv12Program.emplace();
LinkProgram(
&*_nv12Program,
_texturedVertexShader,
FragmentShader({
FragmentSampleNV12Texture(),
FragmentApplyControlsFade()
}));
_fillProgram.emplace();
LinkProgram(
&*_fillProgram,
VertexShader({ VertexViewportTransform() }),
FragmentShader({ FragmentStaticColor() }));
_controlsProgram.emplace();
LinkProgram(
&*_controlsProgram,
_texturedVertexShader,
FragmentShader({
FragmentSampleARGB32Texture(),
FragmentGlobalOpacity(),
}));
_roundedCornersProgram.emplace();
LinkProgram(
&*_roundedCornersProgram,
VertexShader({ VertexViewportTransform() }),
FragmentShader({ FragmentRoundedCorners() }));
const auto renderer = reinterpret_cast<const char*>(
f.glGetString(GL_RENDERER));
CrashReports::SetAnnotation(
"OpenGL Renderer",
renderer ? renderer : "[nullptr]");
2021-06-02 16:36:24 +00:00
}
void OverlayWidget::RendererGL::deinit(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions *f) {
_textures.destroy(f);
_imageProgram = std::nullopt;
2021-06-03 12:57:48 +00:00
_texturedVertexShader = nullptr;
_withTransparencyProgram = std::nullopt;
_yuv420Program = std::nullopt;
_nv12Program = std::nullopt;
_fillProgram = std::nullopt;
_controlsProgram = std::nullopt;
_contentBuffer = std::nullopt;
2021-06-02 16:36:24 +00:00
}
void OverlayWidget::RendererGL::paint(
2021-06-02 16:36:24 +00:00
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f) {
if (handleHideWorkaround(f)) {
return;
}
const auto factor = widget->devicePixelRatio();
if (_factor != factor) {
_factor = factor;
_controlsImage.invalidate();
// We use the fact that fade texture atlas
// takes exactly full texture size. In case we
// just invalidate it we may get larger image
// in case of moving from greater _factor to lesser.
_controlsFadeImage.destroy(&f);
}
2021-06-11 15:51:43 +00:00
_blendingEnabled = false;
_viewport = widget->size();
_uniformViewport = QVector2D(
_viewport.width() * _factor,
_viewport.height() * _factor);
2021-06-02 16:36:24 +00:00
_f = &f;
_owner->paint(this);
_f = nullptr;
2021-06-02 16:36:24 +00:00
}
std::optional<QColor> OverlayWidget::RendererGL::clearColor() {
if (Platform::IsWindows() && _owner->_hideWorkaround) {
return QColor(0, 0, 0, 0);
} else if (_owner->_fullScreenVideo) {
return st::mediaviewVideoBg->c;
} else {
return st::mediaviewBg->c;
2021-06-02 16:36:24 +00:00
}
}
bool OverlayWidget::RendererGL::handleHideWorkaround(QOpenGLFunctions &f) {
2021-06-02 16:36:24 +00:00
// This is needed on Windows,
// because on reopen it blinks with the last shown content.
return Platform::IsWindows() && _owner->_hideWorkaround;
2021-06-02 16:36:24 +00:00
}
void OverlayWidget::RendererGL::paintBackground() {
_contentBuffer->bind();
2021-06-02 16:36:24 +00:00
}
void OverlayWidget::RendererGL::paintTransformedVideoFrame(
ContentGeometry geometry) {
2021-06-03 12:57:48 +00:00
const auto data = _owner->videoFrameWithInfo();
if (data.format == Streaming::FrameFormat::None) {
return;
2022-01-21 15:01:33 +00:00
} else if (data.format == Streaming::FrameFormat::ARGB32) {
Assert(!data.image.isNull());
2022-01-21 15:01:33 +00:00
paintTransformedStaticContent(
data.image,
2022-01-21 15:01:33 +00:00
geometry,
data.alpha,
data.alpha);
2021-06-03 12:57:48 +00:00
return;
}
Assert(!data.yuv->size.isEmpty());
const auto program = (data.format == Streaming::FrameFormat::NV12)
? &*_nv12Program
: &*_yuv420Program;
program->bind();
const auto nv12 = (data.format == Streaming::FrameFormat::NV12);
const auto yuv = data.yuv;
const auto nv12changed = (_chromaNV12 != nv12);
2021-06-03 12:57:48 +00:00
const auto upload = (_trackFrameIndex != data.index)
|| (_streamedIndex != _owner->streamedIndex());
_trackFrameIndex = data.index;
_streamedIndex = _owner->streamedIndex();
_f->glActiveTexture(GL_TEXTURE0);
_textures.bind(*_f, 1);
2021-06-03 12:57:48 +00:00
if (upload) {
_f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
uploadTexture(
GL_ALPHA,
GL_ALPHA,
2021-06-03 12:57:48 +00:00
yuv->size,
_lumaSize,
yuv->y.stride,
yuv->y.data);
_lumaSize = yuv->size;
}
_f->glActiveTexture(GL_TEXTURE1);
_textures.bind(*_f, 2);
2021-06-03 12:57:48 +00:00
if (upload) {
uploadTexture(
nv12 ? GL_RG : GL_ALPHA,
nv12 ? GL_RG : GL_ALPHA,
2021-06-03 12:57:48 +00:00
yuv->chromaSize,
nv12changed ? QSize() : _chromaSize,
yuv->u.stride / (nv12 ? 2 : 1),
2021-06-03 12:57:48 +00:00
yuv->u.data);
if (nv12) {
_chromaSize = yuv->chromaSize;
_f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
}
_chromaNV12 = nv12;
2021-06-03 12:57:48 +00:00
}
validateControlsFade();
if (nv12) {
_f->glActiveTexture(GL_TEXTURE2);
_controlsFadeImage.bind(*_f);
} else {
_f->glActiveTexture(GL_TEXTURE2);
_textures.bind(*_f, 3);
if (upload) {
uploadTexture(
GL_ALPHA,
GL_ALPHA,
yuv->chromaSize,
_chromaSize,
yuv->v.stride,
yuv->v.data);
_chromaSize = yuv->chromaSize;
_f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
}
_f->glActiveTexture(GL_TEXTURE3);
_controlsFadeImage.bind(*_f);
}
program->setUniformValue("y_texture", GLint(0));
if (nv12) {
program->setUniformValue("uv_texture", GLint(1));
} else {
program->setUniformValue("u_texture", GLint(1));
program->setUniformValue("v_texture", GLint(2));
2021-06-03 12:57:48 +00:00
}
program->setUniformValue("f_texture", GLint(nv12 ? 2 : 3));
2021-06-03 12:57:48 +00:00
toggleBlending(false);
paintTransformedContent(program, geometry);
2021-06-02 16:36:24 +00:00
}
void OverlayWidget::RendererGL::paintTransformedStaticContent(
const QImage &image,
ContentGeometry geometry,
bool semiTransparent,
2021-06-02 16:36:24 +00:00
bool fillTransparentBackground) {
Expects(image.isNull()
|| image.format() == QImage::Format_RGB32
|| image.format() == QImage::Format_ARGB32_Premultiplied);
if (geometry.rect.isEmpty()) {
return;
}
2021-06-03 12:57:48 +00:00
auto &program = fillTransparentBackground
? _withTransparencyProgram
: _staticContentProgram;
2021-06-08 09:25:07 +00:00
program->bind();
2021-06-03 12:57:48 +00:00
if (fillTransparentBackground) {
program->setUniformValue(
"transparentBg",
st::mediaviewTransparentBg->c);
2021-06-03 12:57:48 +00:00
program->setUniformValue(
"transparentFg",
st::mediaviewTransparentFg->c);
2021-06-03 12:57:48 +00:00
program->setUniformValue(
"transparentSize",
st::transparentPlaceholderSize * _factor);
}
_f->glActiveTexture(GL_TEXTURE0);
_textures.bind(*_f, 0);
const auto cacheKey = image.isNull() ? qint64(-1) : image.cacheKey();
2021-06-03 12:57:48 +00:00
const auto upload = (_cacheKey != cacheKey);
if (upload) {
_cacheKey = cacheKey;
if (image.isNull()) {
// Upload transparent 2x2 texture.
const auto stride = 2;
const uint32_t data[4] = { 0 };
uploadTexture(
2021-07-14 17:04:08 +00:00
Ui::GL::kFormatRGBA,
Ui::GL::kFormatRGBA,
QSize(2, 2),
_rgbaSize,
stride,
data);
} else {
const auto stride = image.bytesPerLine() / 4;
const auto data = image.constBits();
uploadTexture(
2021-07-14 17:04:08 +00:00
Ui::GL::kFormatRGBA,
Ui::GL::kFormatRGBA,
image.size(),
_rgbaSize,
stride,
data);
_rgbaSize = image.size();
}
2021-06-03 12:57:48 +00:00
}
validateControlsFade();
_f->glActiveTexture(GL_TEXTURE1);
_controlsFadeImage.bind(*_f);
program->setUniformValue("s_texture", GLint(0));
program->setUniformValue("f_texture", GLint(1));
2021-06-03 12:57:48 +00:00
toggleBlending(semiTransparent && !fillTransparentBackground);
paintTransformedContent(&*program, geometry);
2021-06-03 12:57:48 +00:00
}
void OverlayWidget::RendererGL::paintTransformedContent(
not_null<QOpenGLShaderProgram*> program,
ContentGeometry geometry) {
const auto rect = transformRect(geometry.rect);
const auto centerx = rect.x() + rect.width() / 2;
const auto centery = rect.y() + rect.height() / 2;
2021-06-08 21:06:01 +00:00
const auto rsin = float(std::sin(geometry.rotation * M_PI / 180.));
const auto rcos = float(std::cos(geometry.rotation * M_PI / 180.));
const auto rotated = [&](float x, float y) -> std::array<float, 2> {
x -= centerx;
y -= centery;
2021-06-08 21:06:01 +00:00
return std::array<float, 2>{
centerx + (x * rcos + y * rsin),
centery + (y * rcos - x * rsin)
};
};
const auto topleft = rotated(rect.left(), rect.top());
const auto topright = rotated(rect.right(), rect.top());
const auto bottomright = rotated(rect.right(), rect.bottom());
const auto bottomleft = rotated(rect.left(), rect.bottom());
const GLfloat coords[] = {
topleft[0], topleft[1],
0.f, 1.f,
topright[0], topright[1],
1.f, 1.f,
bottomright[0], bottomright[1],
1.f, 0.f,
bottomleft[0], bottomleft[1],
0.f, 0.f,
};
_contentBuffer->write(0, coords, sizeof(coords));
program->setUniformValue("viewport", _uniformViewport);
const auto &top = st::mediaviewShadowTop.size();
const auto point = QPoint(_shadowTopFlip ? 0 : (_viewport.width() - top.width()), 0);
program->setUniformValue(
"shadowTopRect",
Uniform(transformRect(QRect(point, top))));
const auto &bottom = st::mediaviewShadowBottom;
program->setUniformValue(
"shadowBottomAndOpacity",
QVector2D(bottom.height() * _factor, geometry.controlsOpacity));
2021-06-03 12:57:48 +00:00
FillTexturedRectangle(*_f, &*program);
}
void OverlayWidget::RendererGL::uploadTexture(
GLint internalformat,
GLint format,
QSize size,
QSize hasSize,
int stride,
const void *data) const {
_f->glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
if (hasSize != size) {
_f->glTexImage2D(
GL_TEXTURE_2D,
0,
internalformat,
size.width(),
size.height(),
0,
format,
GL_UNSIGNED_BYTE,
data);
} else {
_f->glTexSubImage2D(
GL_TEXTURE_2D,
0,
0,
0,
size.width(),
size.height(),
format,
GL_UNSIGNED_BYTE,
data);
}
_f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
2021-06-02 16:36:24 +00:00
}
void OverlayWidget::RendererGL::paintRadialLoading(
QRect inner,
bool radial,
float64 radialOpacity) {
paintUsingRaster(_radialImage, inner, [&](Painter &&p) {
2021-06-02 16:36:24 +00:00
const auto newInner = QRect(QPoint(), inner.size());
_owner->paintRadialLoadingContent(p, newInner, radial, radialOpacity);
}, kRadialLoadingOffset, true);
2021-06-02 16:36:24 +00:00
}
void OverlayWidget::RendererGL::paintThemePreview(QRect outer) {
paintUsingRaster(_themePreviewImage, outer, [&](Painter &&p) {
p.translate(-outer.topLeft());
_owner->paintThemePreviewContent(p, outer, outer);
}, kThemePreviewOffset);
2021-06-02 16:36:24 +00:00
}
void OverlayWidget::RendererGL::paintDocumentBubble(
QRect outer,
QRect icon) {
paintUsingRaster(_documentBubbleImage, outer, [&](Painter &&p) {
2021-06-02 16:36:24 +00:00
const auto newOuter = QRect(QPoint(), outer.size());
const auto newIcon = icon.translated(-outer.topLeft());
_owner->paintDocumentBubbleContent(p, newOuter, newIcon, newOuter);
}, kDocumentBubbleOffset);
2021-06-02 16:36:24 +00:00
_owner->paintRadialLoading(this);
}
void OverlayWidget::RendererGL::paintSaveMsg(QRect outer) {
paintUsingRaster(_saveMsgImage, outer, [&](Painter &&p) {
2021-06-02 16:36:24 +00:00
const auto newOuter = QRect(QPoint(), outer.size());
_owner->paintSaveMsgContent(p, newOuter, newOuter);
}, kSaveMsgOffset, true);
2021-06-02 16:36:24 +00:00
}
void OverlayWidget::RendererGL::paintControlsStart() {
validateControls();
_f->glActiveTexture(GL_TEXTURE0);
_controlsImage.bind(*_f);
toggleBlending(true);
}
2021-06-02 16:36:24 +00:00
void OverlayWidget::RendererGL::paintControl(
OverState control,
QRect over,
float64 overOpacity,
2021-06-02 16:36:24 +00:00
QRect inner,
float64 innerOpacity,
const style::icon &icon) {
const auto meta = ControlMeta(control);
Assert(meta.icon == &icon);
const auto overAlpha = overOpacity * kOverBackgroundOpacity;
const auto offset = kControlsOffset + (meta.index * kControlValues) / 4;
const auto fgOffset = offset + 4;
const auto overRect = _controlsImage.texturedRect(
over,
_controlsTextures[kControlsCount]);
const auto overGeometry = transformRect(over);
const auto iconRect = _controlsImage.texturedRect(
inner,
_controlsTextures[meta.index]);
const auto iconGeometry = transformRect(iconRect.geometry);
const GLfloat coords[] = {
overGeometry.left(), overGeometry.top(),
overRect.texture.left(), overRect.texture.bottom(),
overGeometry.right(), overGeometry.top(),
overRect.texture.right(), overRect.texture.bottom(),
overGeometry.right(), overGeometry.bottom(),
overRect.texture.right(), overRect.texture.top(),
overGeometry.left(), overGeometry.bottom(),
overRect.texture.left(), overRect.texture.top(),
iconGeometry.left(), iconGeometry.top(),
iconRect.texture.left(), iconRect.texture.bottom(),
iconGeometry.right(), iconGeometry.top(),
iconRect.texture.right(), iconRect.texture.bottom(),
iconGeometry.right(), iconGeometry.bottom(),
iconRect.texture.right(), iconRect.texture.top(),
iconGeometry.left(), iconGeometry.bottom(),
iconRect.texture.left(), iconRect.texture.top(),
};
_controlsProgram->bind();
_controlsProgram->setUniformValue("viewport", _uniformViewport);
if (!over.isEmpty() && overOpacity > 0) {
_contentBuffer->write(
offset * 4 * sizeof(GLfloat),
coords,
sizeof(coords));
_controlsProgram->setUniformValue("g_opacity", GLfloat(overAlpha));
FillTexturedRectangle(*_f, &*_controlsProgram, offset);
} else {
_contentBuffer->write(
fgOffset * 4 * sizeof(GLfloat),
coords + (fgOffset - offset) * 4,
sizeof(coords) - (fgOffset - offset) * 4 * sizeof(GLfloat));
}
_controlsProgram->setUniformValue("g_opacity", GLfloat(innerOpacity));
FillTexturedRectangle(*_f, &*_controlsProgram, fgOffset);
}
auto OverlayWidget::RendererGL::ControlMeta(OverState control)
-> Control {
switch (control) {
case OverLeftNav: return { 0, &st::mediaviewLeft };
case OverRightNav: return { 1, &st::mediaviewRight };
case OverSave: return { 2, &st::mediaviewSave };
case OverRotate: return { 3, &st::mediaviewRotate };
case OverMore: return { 4, &st::mediaviewMore };
}
Unexpected("Control value in OverlayWidget::RendererGL::ControlIndex.");
}
void OverlayWidget::RendererGL::validateControls() {
if (!_controlsImage.image().isNull()) {
return;
}
const auto metas = {
ControlMeta(OverLeftNav),
ControlMeta(OverRightNav),
ControlMeta(OverSave),
ControlMeta(OverRotate),
ControlMeta(OverMore),
};
auto maxWidth = 0;
auto fullHeight = 0;
for (const auto &meta : metas) {
maxWidth = std::max(meta.icon->width(), maxWidth);
fullHeight += meta.icon->height();
}
maxWidth = std::max(st::mediaviewIconOver, maxWidth);
fullHeight += st::mediaviewIconOver;
auto image = QImage(
QSize(maxWidth, fullHeight) * _factor,
QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
image.setDevicePixelRatio(_factor);
{
auto p = QPainter(&image);
auto index = 0;
auto height = 0;
for (const auto &meta : metas) {
meta.icon->paint(p, 0, height, maxWidth);
_controlsTextures[index++] = QRect(
QPoint(0, height) * _factor,
meta.icon->size() * _factor);
height += meta.icon->height();
}
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(OverBackgroundColor());
p.drawEllipse(
QRect(0, height, st::mediaviewIconOver, st::mediaviewIconOver));
_controlsTextures[index++] = QRect(
QPoint(0, height) * _factor,
QSize(st::mediaviewIconOver, st::mediaviewIconOver) * _factor);
height += st::mediaviewIconOver;
}
_controlsImage.setImage(std::move(image));
}
void OverlayWidget::RendererGL::invalidateControls() {
_controlsImage.invalidate();
ranges::fill(_controlsTextures, QRect());
2021-06-02 16:36:24 +00:00
}
void OverlayWidget::RendererGL::validateControlsFade() {
const auto flip = !_owner->topShadowOnTheRight();
if (!_controlsFadeImage.image().isNull()
&& _shadowTopFlip == flip) {
return;
}
_shadowTopFlip = flip;
const auto width = st::mediaviewShadowTop.width();
const auto bottomTop = st::mediaviewShadowTop.height();
const auto height = bottomTop + st::mediaviewShadowBottom.height();
auto image = QImage(
QSize(width, height) * _factor,
QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
image.setDevicePixelRatio(_factor);
auto p = QPainter(&image);
st::mediaviewShadowTop.paint(p, 0, 0, width);
st::mediaviewShadowBottom.fill(
p,
QRect(0, bottomTop, width, st::mediaviewShadowBottom.height()));
p.end();
if (flip) {
image = std::move(image).mirrored(true, false);
}
_controlsFadeImage.setImage(std::move(image));
_shadowTopTexture = QRect(
QPoint(),
QSize(width, st::mediaviewShadowTop.height()) * _factor);
_shadowBottomTexture = QRect(
QPoint(0, bottomTop) * _factor,
QSize(width, st::mediaviewShadowBottom.height()) * _factor);
}
2021-06-02 16:36:24 +00:00
void OverlayWidget::RendererGL::paintFooter(QRect outer, float64 opacity) {
paintUsingRaster(_footerImage, outer, [&](Painter &&p) {
2021-06-02 16:36:24 +00:00
const auto newOuter = QRect(QPoint(), outer.size());
_owner->paintFooterContent(p, newOuter, newOuter, opacity);
}, kFooterOffset, true);
2021-06-02 16:36:24 +00:00
}
void OverlayWidget::RendererGL::paintCaption(QRect outer, float64 opacity) {
paintUsingRaster(_captionImage, outer, [&](Painter &&p) {
2021-06-02 16:36:24 +00:00
const auto newOuter = QRect(QPoint(), outer.size());
_owner->paintCaptionContent(p, newOuter, newOuter, opacity);
}, kCaptionOffset, true);
2021-06-02 16:36:24 +00:00
}
void OverlayWidget::RendererGL::paintGroupThumbs(
QRect outer,
float64 opacity) {
paintUsingRaster(_groupThumbsImage, outer, [&](Painter &&p) {
2021-06-02 16:36:24 +00:00
const auto newOuter = QRect(QPoint(), outer.size());
_owner->paintGroupThumbsContent(p, newOuter, newOuter, opacity);
}, kGroupThumbsOffset, true);
2021-06-02 16:36:24 +00:00
}
void OverlayWidget::RendererGL::paintRoundedCorners(int radius) {
const auto topLeft = transformRect(QRect(0, 0, radius, radius));
const auto topRight = transformRect(
QRect(_viewport.width() - radius, 0, radius, radius));
const auto bottomRight = transformRect(QRect(
_viewport.width() - radius,
_viewport.height() - radius,
radius,
radius));
const auto bottomLeft = transformRect(
QRect(0, _viewport.height() - radius, radius, radius));
const GLfloat coords[] = {
topLeft.left(), topLeft.top(),
topLeft.right(), topLeft.top(),
topLeft.right(), topLeft.bottom(),
topLeft.right(), topLeft.bottom(),
topLeft.left(), topLeft.bottom(),
topLeft.left(), topLeft.top(),
topRight.left(), topRight.top(),
topRight.right(), topRight.top(),
topRight.right(), topRight.bottom(),
topRight.right(), topRight.bottom(),
topRight.left(), topRight.bottom(),
topRight.left(), topRight.top(),
bottomRight.left(), bottomRight.top(),
bottomRight.right(), bottomRight.top(),
bottomRight.right(), bottomRight.bottom(),
bottomRight.right(), bottomRight.bottom(),
bottomRight.left(), bottomRight.bottom(),
bottomRight.left(), bottomRight.top(),
bottomLeft.left(), bottomLeft.top(),
bottomLeft.right(), bottomLeft.top(),
bottomLeft.right(), bottomLeft.bottom(),
bottomLeft.right(), bottomLeft.bottom(),
bottomLeft.left(), bottomLeft.bottom(),
bottomLeft.left(), bottomLeft.top(),
};
const auto offset = kControlsOffset
+ (kControlsCount * kControlValues) / 4;
const auto byteOffset = offset * 4 * sizeof(GLfloat);
_contentBuffer->write(byteOffset, coords, sizeof(coords));
_roundedCornersProgram->bind();
_roundedCornersProgram->setUniformValue("viewport", _uniformViewport);
const auto roundRect = transformRect(QRect(QPoint(), _viewport));
_roundedCornersProgram->setUniformValue("roundRect", Uniform(roundRect));
_roundedCornersProgram->setUniformValue(
"roundRadius",
GLfloat(radius * _factor));
_f->glEnable(GL_BLEND);
_f->glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
GLint position = _roundedCornersProgram->attributeLocation("position");
_f->glVertexAttribPointer(
position,
2,
GL_FLOAT,
GL_FALSE,
2 * sizeof(GLfloat),
reinterpret_cast<const void*>(byteOffset));
_f->glEnableVertexAttribArray(position);
_f->glDrawArrays(GL_TRIANGLES, 0, base::array_size(coords) / 2);
_f->glDisableVertexAttribArray(position);
}
//
//void OverlayWidget::RendererGL::invalidate() {
// _trackFrameIndex = -1;
// _streamedIndex = -1;
// const auto images = {
// &_radialImage,
// &_documentBubbleImage,
// &_themePreviewImage,
// &_saveMsgImage,
// &_footerImage,
// &_captionImage,
// &_groupThumbsImage,
// &_controlsImage,
// };
// for (const auto image : images) {
// image->setImage(QImage());
// }
// invalidateControls();
//}
2021-06-03 12:57:48 +00:00
void OverlayWidget::RendererGL::paintUsingRaster(
Ui::GL::Image &image,
QRect rect,
2021-06-02 16:36:24 +00:00
Fn<void(Painter&&)> method,
int bufferOffset,
bool transparent) {
auto raster = image.takeImage();
const auto size = rect.size() * _factor;
if (raster.width() < size.width() || raster.height() < size.height()) {
raster = QImage(size, QImage::Format_ARGB32_Premultiplied);
raster.setDevicePixelRatio(_factor);
if (!transparent
&& (raster.width() > size.width()
|| raster.height() > size.height())) {
raster.fill(Qt::transparent);
}
} else if (raster.devicePixelRatio() != _factor) {
raster.setDevicePixelRatio(_factor);
2021-06-02 16:36:24 +00:00
}
if (transparent) {
raster.fill(Qt::transparent);
}
method(Painter(&raster));
_f->glActiveTexture(GL_TEXTURE0);
image.setImage(std::move(raster), size);
image.bind(*_f);
const auto textured = image.texturedRect(rect, QRect(QPoint(), size));
const auto geometry = transformRect(textured.geometry);
const GLfloat coords[] = {
geometry.left(), geometry.top(),
textured.texture.left(), textured.texture.bottom(),
geometry.right(), geometry.top(),
textured.texture.right(), textured.texture.bottom(),
geometry.right(), geometry.bottom(),
textured.texture.right(), textured.texture.top(),
geometry.left(), geometry.bottom(),
textured.texture.left(), textured.texture.top(),
};
_contentBuffer->write(
bufferOffset * 4 * sizeof(GLfloat),
coords,
sizeof(coords));
2021-06-08 09:25:07 +00:00
_imageProgram->bind();
_imageProgram->setUniformValue("viewport", _uniformViewport);
_imageProgram->setUniformValue("s_texture", GLint(0));
toggleBlending(transparent);
FillTexturedRectangle(*_f, &*_imageProgram, bufferOffset);
}
void OverlayWidget::RendererGL::toggleBlending(bool enabled) {
if (_blendingEnabled == enabled) {
return;
} else if (enabled) {
_f->glEnable(GL_BLEND);
_f->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
} else {
_f->glDisable(GL_BLEND);
2021-06-02 16:36:24 +00:00
}
_blendingEnabled = enabled;
}
Rect OverlayWidget::RendererGL::transformRect(const Rect &raster) const {
return TransformRect(raster, _viewport, _factor);
}
Rect OverlayWidget::RendererGL::transformRect(const QRectF &raster) const {
return TransformRect(raster, _viewport, _factor);
}
Rect OverlayWidget::RendererGL::transformRect(const QRect &raster) const {
return TransformRect(Rect(raster), _viewport, _factor);
2021-06-02 16:36:24 +00:00
}
} // namespace Media::View