mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-08-06 20:03:12 +00:00
1091 lines
30 KiB
C++
1091 lines
30 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/calls_video_incoming.h"
|
|
|
|
#include "ui/gl/gl_surface.h"
|
|
#include "ui/gl/gl_shader.h"
|
|
#include "ui/gl/gl_image.h"
|
|
#include "ui/gl/gl_primitives.h"
|
|
#include "ui/rhi/rhi_renderer.h"
|
|
#include "ui/rhi/rhi_shader.h"
|
|
#include "ui/painter.h"
|
|
#include "media/view/media_view_pip.h"
|
|
#include "webrtc/webrtc_video_track.h"
|
|
#include "styles/style_calls.h"
|
|
|
|
#include <QOpenGLShader>
|
|
#include <QOpenGLBuffer>
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
|
#include <rhi/qrhi.h>
|
|
#endif
|
|
|
|
namespace Calls {
|
|
namespace {
|
|
|
|
constexpr auto kBottomShadowAlphaMax = 74;
|
|
|
|
using namespace Ui::GL;
|
|
|
|
[[nodiscard]] ShaderPart FragmentBottomShadow() {
|
|
return {
|
|
.header = R"(
|
|
uniform vec3 shadow; // fullHeight, shadowTop, maxOpacity
|
|
)",
|
|
.body = R"(
|
|
float shadowCoord = shadow.y - gl_FragCoord.y;
|
|
float shadowValue = clamp(shadowCoord / shadow.x, 0., 1.);
|
|
float shadowShown = shadowValue * shadow.z;
|
|
result = vec4(min(result.rgb, vec3(1.)) * (1. - shadowShown), result.a);
|
|
)",
|
|
};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class Panel::Incoming::RendererGL final : public Ui::GL::Renderer {
|
|
public:
|
|
explicit RendererGL(not_null<Incoming*> owner);
|
|
|
|
void init(QOpenGLFunctions &f) override;
|
|
|
|
void deinit(QOpenGLFunctions *f) override;
|
|
|
|
void paint(
|
|
not_null<QOpenGLWidget*> widget,
|
|
QOpenGLFunctions &f) override;
|
|
|
|
private:
|
|
void uploadTexture(
|
|
QOpenGLFunctions &f,
|
|
GLint internalformat,
|
|
GLint format,
|
|
QSize size,
|
|
QSize hasSize,
|
|
int stride,
|
|
const void *data) const;
|
|
void validateShadowImage();
|
|
|
|
const not_null<Incoming*> _owner;
|
|
|
|
QSize _viewport;
|
|
float _factor = 1.;
|
|
int _ifactor = 1;
|
|
QVector2D _uniformViewport;
|
|
|
|
std::optional<QOpenGLBuffer> _contentBuffer;
|
|
std::optional<QOpenGLShaderProgram> _argb32Program;
|
|
QOpenGLShader *_texturedVertexShader = nullptr;
|
|
std::optional<QOpenGLShaderProgram> _yuv420Program;
|
|
std::optional<QOpenGLShaderProgram> _imageProgram;
|
|
Ui::GL::Textures<4> _textures;
|
|
QSize _rgbaSize;
|
|
QSize _lumaSize;
|
|
QSize _chromaSize;
|
|
int _trackFrameIndex = 0;
|
|
|
|
Ui::GL::Image _controlsShadowImage;
|
|
QRect _controlsShadowLeft;
|
|
QRect _controlsShadowRight;
|
|
|
|
rpl::lifetime _lifetime;
|
|
|
|
};
|
|
|
|
class Panel::Incoming::RendererSW final : public Ui::GL::Renderer {
|
|
public:
|
|
explicit RendererSW(not_null<Incoming*> owner);
|
|
|
|
void paintFallback(
|
|
Painter &p,
|
|
const QRegion &clip,
|
|
Ui::GL::Backend backend) override;
|
|
|
|
private:
|
|
void initBottomShadow();
|
|
void fillTopShadow(QPainter &p);
|
|
void fillBottomShadow(QPainter &p);
|
|
|
|
const not_null<Incoming*> _owner;
|
|
|
|
QImage _bottomShadow;
|
|
|
|
};
|
|
|
|
Panel::Incoming::RendererGL::RendererGL(not_null<Incoming*> owner)
|
|
: _owner(owner) {
|
|
style::PaletteChanged(
|
|
) | rpl::on_next([=] {
|
|
_controlsShadowImage.invalidate();
|
|
}, _lifetime);
|
|
}
|
|
|
|
void Panel::Incoming::RendererGL::init(QOpenGLFunctions &f) {
|
|
constexpr auto kQuads = 2;
|
|
constexpr auto kQuadVertices = kQuads * 4;
|
|
constexpr auto kQuadValues = kQuadVertices * 4;
|
|
|
|
_contentBuffer.emplace();
|
|
_contentBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
|
_contentBuffer->create();
|
|
_contentBuffer->bind();
|
|
_contentBuffer->allocate(kQuadValues * sizeof(GLfloat));
|
|
|
|
_textures.ensureCreated(f);
|
|
|
|
_imageProgram.emplace();
|
|
_texturedVertexShader = LinkProgram(
|
|
&*_imageProgram,
|
|
VertexShader({
|
|
VertexViewportTransform(),
|
|
VertexPassTextureCoord(),
|
|
}),
|
|
FragmentShader({
|
|
FragmentSampleARGB32Texture(),
|
|
})).vertex;
|
|
|
|
_argb32Program.emplace();
|
|
LinkProgram(
|
|
&*_argb32Program,
|
|
_texturedVertexShader,
|
|
FragmentShader({
|
|
FragmentSampleARGB32Texture(),
|
|
FragmentBottomShadow(),
|
|
}));
|
|
|
|
_yuv420Program.emplace();
|
|
LinkProgram(
|
|
&*_yuv420Program,
|
|
_texturedVertexShader,
|
|
FragmentShader({
|
|
FragmentSampleYUV420Texture(),
|
|
FragmentBottomShadow(),
|
|
}));
|
|
}
|
|
|
|
void Panel::Incoming::RendererGL::deinit(QOpenGLFunctions *f) {
|
|
_controlsShadowImage.destroy(f);
|
|
_textures.destroy(f);
|
|
_imageProgram = std::nullopt;
|
|
_texturedVertexShader = nullptr;
|
|
_argb32Program = std::nullopt;
|
|
_yuv420Program = std::nullopt;
|
|
_contentBuffer = std::nullopt;
|
|
}
|
|
|
|
void Panel::Incoming::RendererGL::paint(
|
|
not_null<QOpenGLWidget*> widget,
|
|
QOpenGLFunctions &f) {
|
|
const auto markGuard = gsl::finally([&] {
|
|
_owner->_track->markFrameShown();
|
|
});
|
|
const auto data = _owner->_track->frameWithInfo(false);
|
|
if (data.format == Webrtc::FrameFormat::None) {
|
|
return;
|
|
}
|
|
|
|
const auto factor = widget->devicePixelRatioF();
|
|
if (_factor != factor) {
|
|
_factor = factor;
|
|
_ifactor = int(std::ceil(_factor));
|
|
_controlsShadowImage.invalidate();
|
|
}
|
|
_viewport = widget->size();
|
|
_uniformViewport = QVector2D(
|
|
_viewport.width() * _factor,
|
|
_viewport.height() * _factor);
|
|
|
|
const auto rgbaFrame = (data.format == Webrtc::FrameFormat::ARGB32);
|
|
const auto upload = (_trackFrameIndex != data.index);
|
|
_trackFrameIndex = data.index;
|
|
auto &program = rgbaFrame ? _argb32Program : _yuv420Program;
|
|
program->bind();
|
|
if (rgbaFrame) {
|
|
Assert(!data.original.isNull());
|
|
f.glActiveTexture(GL_TEXTURE0);
|
|
_textures.bind(f, 0);
|
|
if (upload) {
|
|
uploadTexture(
|
|
f,
|
|
Ui::GL::kFormatRGBA,
|
|
Ui::GL::kFormatRGBA,
|
|
data.original.size(),
|
|
_rgbaSize,
|
|
data.original.bytesPerLine() / 4,
|
|
data.original.constBits());
|
|
_rgbaSize = data.original.size();
|
|
}
|
|
program->setUniformValue("s_texture", GLint(0));
|
|
} else {
|
|
Assert(data.format == Webrtc::FrameFormat::YUV420);
|
|
Assert(!data.yuv420->size.isEmpty());
|
|
const auto yuv = data.yuv420;
|
|
|
|
f.glActiveTexture(GL_TEXTURE0);
|
|
_textures.bind(f, 1);
|
|
if (upload) {
|
|
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
uploadTexture(
|
|
f,
|
|
GL_ALPHA,
|
|
GL_ALPHA,
|
|
yuv->size,
|
|
_lumaSize,
|
|
yuv->y.stride,
|
|
yuv->y.data);
|
|
_lumaSize = yuv->size;
|
|
}
|
|
f.glActiveTexture(GL_TEXTURE1);
|
|
_textures.bind(f, 2);
|
|
if (upload) {
|
|
uploadTexture(
|
|
f,
|
|
GL_ALPHA,
|
|
GL_ALPHA,
|
|
yuv->chromaSize,
|
|
_chromaSize,
|
|
yuv->u.stride,
|
|
yuv->u.data);
|
|
}
|
|
f.glActiveTexture(GL_TEXTURE2);
|
|
_textures.bind(f, 3);
|
|
if (upload) {
|
|
uploadTexture(
|
|
f,
|
|
GL_ALPHA,
|
|
GL_ALPHA,
|
|
yuv->chromaSize,
|
|
_chromaSize,
|
|
yuv->v.stride,
|
|
yuv->v.data);
|
|
_chromaSize = yuv->chromaSize;
|
|
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
}
|
|
program->setUniformValue("y_texture", GLint(0));
|
|
program->setUniformValue("u_texture", GLint(1));
|
|
program->setUniformValue("v_texture", GLint(2));
|
|
}
|
|
const auto rect = TransformRect(
|
|
widget->rect(),
|
|
_viewport,
|
|
_factor);
|
|
std::array<std::array<GLfloat, 2>, 4> texcoords = { {
|
|
{ { 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(
|
|
begin(texcoords),
|
|
begin(texcoords) + shift,
|
|
end(texcoords));
|
|
}
|
|
|
|
const auto width = widget->parentWidget()->width();
|
|
const auto left = (_owner->_topControlsAlignment == style::al_left);
|
|
validateShadowImage();
|
|
const auto position = left
|
|
? QPoint()
|
|
: QPoint(width - st::callTitleShadowRight.width(), 0);
|
|
const auto translated = position - widget->pos();
|
|
const auto shadowArea = QRect(translated, st::callTitleShadowLeft.size());
|
|
const auto shadow = _controlsShadowImage.texturedRect(
|
|
shadowArea,
|
|
(left ? _controlsShadowLeft : _controlsShadowRight),
|
|
widget->rect());
|
|
const auto shadowRect = TransformRect(
|
|
shadow.geometry,
|
|
_viewport,
|
|
_factor);
|
|
|
|
const GLfloat coords[] = {
|
|
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],
|
|
|
|
shadowRect.left(), shadowRect.top(),
|
|
shadow.texture.left(), shadow.texture.bottom(),
|
|
|
|
shadowRect.right(), shadowRect.top(),
|
|
shadow.texture.right(), shadow.texture.bottom(),
|
|
|
|
shadowRect.right(), shadowRect.bottom(),
|
|
shadow.texture.right(), shadow.texture.top(),
|
|
|
|
shadowRect.left(), shadowRect.bottom(),
|
|
shadow.texture.left(), shadow.texture.top(),
|
|
};
|
|
|
|
_contentBuffer->bind();
|
|
_contentBuffer->write(0, coords, sizeof(coords));
|
|
|
|
const auto bottomShadowArea = QRect(
|
|
0,
|
|
widget->parentWidget()->height() - st::callBottomShadowSize,
|
|
widget->parentWidget()->width(),
|
|
st::callBottomShadowSize);
|
|
const auto bottomShadowFill = bottomShadowArea.intersected(
|
|
widget->geometry()).translated(-widget->pos());
|
|
const auto shadowHeight = bottomShadowFill.height();
|
|
const auto shadowAlpha = (shadowHeight * kBottomShadowAlphaMax)
|
|
/ (st::callBottomShadowSize * 255.);
|
|
|
|
program->setUniformValue("viewport", _uniformViewport);
|
|
program->setUniformValue("shadow", QVector3D(
|
|
shadowHeight * _factor,
|
|
TransformRect(bottomShadowFill, _viewport, _factor).bottom(),
|
|
shadowAlpha));
|
|
|
|
FillTexturedRectangle(f, &*program);
|
|
|
|
#ifndef Q_OS_MAC
|
|
if (!shadowRect.empty()) {
|
|
f.glEnable(GL_BLEND);
|
|
f.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
|
const auto guard = gsl::finally([&] {
|
|
f.glDisable(GL_BLEND);
|
|
});
|
|
|
|
_imageProgram->bind();
|
|
_imageProgram->setUniformValue("viewport", _uniformViewport);
|
|
_imageProgram->setUniformValue("s_texture", GLint(0));
|
|
|
|
f.glActiveTexture(GL_TEXTURE0);
|
|
_controlsShadowImage.bind(f);
|
|
|
|
FillTexturedRectangle(f, &*_imageProgram, 4);
|
|
}
|
|
#endif // Q_OS_MAC
|
|
}
|
|
|
|
void Panel::Incoming::RendererGL::validateShadowImage() {
|
|
if (_controlsShadowImage) {
|
|
return;
|
|
}
|
|
const auto size = st::callTitleShadowLeft.size();
|
|
const auto full = QSize(size.width(), 2 * size.height()) * _ifactor;
|
|
auto image = QImage(full, QImage::Format_ARGB32_Premultiplied);
|
|
image.setDevicePixelRatio(_ifactor);
|
|
image.fill(Qt::transparent);
|
|
{
|
|
auto p = QPainter(&image);
|
|
st::callTitleShadowLeft.paint(p, 0, 0, size.width());
|
|
_controlsShadowLeft = QRect(0, 0, full.width(), full.height() / 2);
|
|
st::callTitleShadowRight.paint(p, 0, size.height(), size.width());
|
|
_controlsShadowRight = QRect(
|
|
0,
|
|
full.height() / 2,
|
|
full.width(),
|
|
full.height() / 2);
|
|
}
|
|
_controlsShadowImage.setImage(std::move(image));
|
|
}
|
|
|
|
void Panel::Incoming::RendererGL::uploadTexture(
|
|
QOpenGLFunctions &f,
|
|
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);
|
|
}
|
|
|
|
Panel::Incoming::RendererSW::RendererSW(not_null<Incoming*> owner)
|
|
: _owner(owner) {
|
|
initBottomShadow();
|
|
}
|
|
|
|
void Panel::Incoming::RendererSW::paintFallback(
|
|
Painter &p,
|
|
const QRegion &clip,
|
|
Ui::GL::Backend backend) {
|
|
const auto markGuard = gsl::finally([&] {
|
|
_owner->_track->markFrameShown();
|
|
});
|
|
const auto data = _owner->_track->frameWithInfo(true);
|
|
const auto &image = data.original;
|
|
const auto rotation = data.rotation;
|
|
if (image.isNull()) {
|
|
p.fillRect(clip.boundingRect(), Qt::black);
|
|
} else {
|
|
const auto rect = _owner->widget()->rect();
|
|
using namespace Media::View;
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
if (UsePainterRotation(rotation)) {
|
|
if (rotation) {
|
|
p.save();
|
|
p.rotate(rotation);
|
|
}
|
|
p.drawImage(RotatedRect(rect, rotation), image);
|
|
if (rotation) {
|
|
p.restore();
|
|
}
|
|
} else if (rotation) {
|
|
p.drawImage(rect, RotateFrameImage(image, rotation));
|
|
} else {
|
|
p.drawImage(rect, image);
|
|
}
|
|
fillBottomShadow(p);
|
|
fillTopShadow(p);
|
|
}
|
|
}
|
|
|
|
void Panel::Incoming::RendererSW::initBottomShadow() {
|
|
auto image = QImage(
|
|
QSize(1, st::callBottomShadowSize) * style::DevicePixelRatio(),
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
const auto colorFrom = uint32(0);
|
|
const auto colorTill = uint32(kBottomShadowAlphaMax);
|
|
const auto rows = image.height();
|
|
const auto step = (uint64(colorTill - colorFrom) << 32) / rows;
|
|
auto accumulated = uint64();
|
|
auto bytes = image.bits();
|
|
for (auto y = 0; y != rows; ++y) {
|
|
accumulated += step;
|
|
const auto color = (colorFrom + uint32(accumulated >> 32)) << 24;
|
|
for (auto x = 0; x != image.width(); ++x) {
|
|
*(reinterpret_cast<uint32*>(bytes) + x) = color;
|
|
}
|
|
bytes += image.bytesPerLine();
|
|
}
|
|
_bottomShadow = std::move(image);
|
|
}
|
|
|
|
void Panel::Incoming::RendererSW::fillTopShadow(QPainter &p) {
|
|
#ifndef Q_OS_MAC
|
|
const auto widget = _owner->widget();
|
|
const auto width = widget->parentWidget()->width();
|
|
const auto left = (_owner->_topControlsAlignment == style::al_left);
|
|
const auto &icon = left
|
|
? st::callTitleShadowLeft
|
|
: st::callTitleShadowRight;
|
|
const auto position = left
|
|
? QPoint()
|
|
: QPoint(width - icon.width(), 0);
|
|
const auto shadowArea = QRect(position, icon.size());
|
|
const auto fill = shadowArea.intersected(
|
|
widget->geometry()).translated(-widget->pos());
|
|
if (fill.isEmpty()) {
|
|
return;
|
|
}
|
|
p.save();
|
|
p.setClipRect(fill);
|
|
icon.paint(p, position - widget->pos(), width);
|
|
p.restore();
|
|
#endif // Q_OS_MAC
|
|
}
|
|
|
|
void Panel::Incoming::RendererSW::fillBottomShadow(QPainter &p) {
|
|
const auto widget = _owner->widget();
|
|
const auto shadowArea = QRect(
|
|
0,
|
|
widget->parentWidget()->height() - st::callBottomShadowSize,
|
|
widget->parentWidget()->width(),
|
|
st::callBottomShadowSize);
|
|
const auto fill = shadowArea.intersected(
|
|
widget->geometry()).translated(-widget->pos());
|
|
if (fill.isEmpty()) {
|
|
return;
|
|
}
|
|
const auto factor = style::DevicePixelRatio();
|
|
p.drawImage(
|
|
fill,
|
|
_bottomShadow,
|
|
QRect(
|
|
0,
|
|
(factor
|
|
* (fill.y() - shadowArea.translated(-widget->pos()).y())),
|
|
factor,
|
|
factor * fill.height()));
|
|
}
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
|
|
|
struct IncomingShadowUniforms {
|
|
float viewport[2];
|
|
float shadow[3];
|
|
float _pad0;
|
|
};
|
|
static_assert(sizeof(IncomingShadowUniforms) == 24);
|
|
|
|
class Panel::Incoming::RendererRhi final
|
|
: public Ui::GL::Renderer
|
|
, public Ui::Rhi::Renderer {
|
|
public:
|
|
explicit RendererRhi(not_null<Incoming*> owner) : _owner(owner) {
|
|
}
|
|
~RendererRhi() {
|
|
releaseResources();
|
|
}
|
|
|
|
void initialize(
|
|
QRhi *rhi,
|
|
QRhiRenderTarget *rt,
|
|
QRhiCommandBuffer *cb) override {
|
|
if (_initialized && _rhi == rhi) {
|
|
return;
|
|
}
|
|
_rhi = rhi;
|
|
_vertexBuffer = rhi->newBuffer(
|
|
QRhiBuffer::Dynamic,
|
|
QRhiBuffer::VertexBuffer,
|
|
4 * 4 * sizeof(float));
|
|
_vertexBuffer->create();
|
|
_uniformBuffer = rhi->newBuffer(
|
|
QRhiBuffer::Dynamic,
|
|
QRhiBuffer::UniformBuffer,
|
|
256);
|
|
_uniformBuffer->create();
|
|
_sampler = rhi->newSampler(
|
|
QRhiSampler::Linear, QRhiSampler::Linear,
|
|
QRhiSampler::None,
|
|
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
|
|
_sampler->create();
|
|
_placeholder = rhi->newTexture(QRhiTexture::BGRA8, QSize(1, 1));
|
|
_placeholder->create();
|
|
|
|
const auto rpDesc = rt->renderPassDescriptor();
|
|
const auto vs = Ui::Rhi::ShaderFromFile(
|
|
u":/shaders/argb32.vert.qsb"_q);
|
|
const auto argb32Fs = Ui::Rhi::ShaderFromFile(
|
|
u":/shaders/incoming_shadow.frag.qsb"_q);
|
|
const auto yuv420Fs = Ui::Rhi::ShaderFromFile(
|
|
u":/shaders/incoming_yuv420.frag.qsb"_q);
|
|
|
|
QRhiVertexInputLayout layout;
|
|
layout.setBindings({ { 4 * sizeof(float) } });
|
|
layout.setAttributes({
|
|
{ 0, 0, QRhiVertexInputAttribute::Float2, 0 },
|
|
{ 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) },
|
|
});
|
|
|
|
_argb32Srb = rhi->newShaderResourceBindings();
|
|
_argb32Srb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1, QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholder, _sampler),
|
|
});
|
|
_argb32Srb->create();
|
|
|
|
_argb32Pipeline = rhi->newGraphicsPipeline();
|
|
_argb32Pipeline->setShaderStages({
|
|
{ QRhiShaderStage::Vertex, vs },
|
|
{ QRhiShaderStage::Fragment, argb32Fs },
|
|
});
|
|
_argb32Pipeline->setVertexInputLayout(layout);
|
|
_argb32Pipeline->setTopology(
|
|
QRhiGraphicsPipeline::TriangleStrip);
|
|
_argb32Pipeline->setShaderResourceBindings(_argb32Srb);
|
|
_argb32Pipeline->setRenderPassDescriptor(rpDesc);
|
|
_argb32Pipeline->create();
|
|
|
|
_yuv420Srb = rhi->newShaderResourceBindings();
|
|
_yuv420Srb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1, QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholder, _sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
2, QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholder, _sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
3, QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholder, _sampler),
|
|
});
|
|
_yuv420Srb->create();
|
|
|
|
_yuv420Pipeline = rhi->newGraphicsPipeline();
|
|
_yuv420Pipeline->setShaderStages({
|
|
{ QRhiShaderStage::Vertex, vs },
|
|
{ QRhiShaderStage::Fragment, yuv420Fs },
|
|
});
|
|
_yuv420Pipeline->setVertexInputLayout(layout);
|
|
_yuv420Pipeline->setTopology(
|
|
QRhiGraphicsPipeline::TriangleStrip);
|
|
_yuv420Pipeline->setShaderResourceBindings(_yuv420Srb);
|
|
_yuv420Pipeline->setRenderPassDescriptor(rpDesc);
|
|
_yuv420Pipeline->create();
|
|
|
|
#ifndef Q_OS_MAC
|
|
const auto shadowFs = Ui::Rhi::ShaderFromFile(
|
|
u":/shaders/argb32.frag.qsb"_q);
|
|
QRhiGraphicsPipeline::TargetBlend blend;
|
|
blend.enable = true;
|
|
blend.srcColor = QRhiGraphicsPipeline::One;
|
|
blend.dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
|
|
blend.srcAlpha = QRhiGraphicsPipeline::One;
|
|
blend.dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
|
|
_shadowBlendPipeline = rhi->newGraphicsPipeline();
|
|
_shadowBlendPipeline->setShaderStages({
|
|
{ QRhiShaderStage::Vertex, vs },
|
|
{ QRhiShaderStage::Fragment, shadowFs },
|
|
});
|
|
_shadowBlendPipeline->setVertexInputLayout(layout);
|
|
_shadowBlendPipeline->setTargetBlends({ blend });
|
|
_shadowBlendPipeline->setTopology(
|
|
QRhiGraphicsPipeline::TriangleStrip);
|
|
_shadowBlendPipeline->setShaderResourceBindings(_argb32Srb);
|
|
_shadowBlendPipeline->setRenderPassDescriptor(rpDesc);
|
|
_shadowBlendPipeline->create();
|
|
#endif
|
|
|
|
_initialized = true;
|
|
}
|
|
|
|
void render(
|
|
QRhi *rhi,
|
|
QRhiRenderTarget *rt,
|
|
QRhiCommandBuffer *cb) override {
|
|
_rhi = rhi;
|
|
const auto markGuard = gsl::finally([&] {
|
|
_owner->_track->markFrameShown();
|
|
});
|
|
const auto data = _owner->_track->frameWithInfo(false);
|
|
if (data.format == Webrtc::FrameFormat::None) {
|
|
auto *rub = rhi->nextResourceUpdateBatch();
|
|
cb->beginPass(rt, Qt::black, { 1.0f, 0 }, rub);
|
|
cb->endPass();
|
|
return;
|
|
}
|
|
|
|
auto *rub = rhi->nextResourceUpdateBatch();
|
|
const auto rgbaFrame =
|
|
(data.format == Webrtc::FrameFormat::ARGB32);
|
|
const auto upload = (_trackFrameIndex != data.index);
|
|
_trackFrameIndex = data.index;
|
|
const auto rotation = data.rotation;
|
|
|
|
auto *pipeline = _argb32Pipeline;
|
|
auto *srb = _argb32Srb;
|
|
|
|
if (rgbaFrame) {
|
|
Assert(!data.original.isNull());
|
|
const auto &image = data.original;
|
|
if (upload || !_rgbaTexture
|
|
|| _rgbaSize != image.size()) {
|
|
delete _rgbaTexture;
|
|
_rgbaTexture = rhi->newTexture(
|
|
QRhiTexture::BGRA8, image.size());
|
|
_rgbaTexture->create();
|
|
_rgbaSize = image.size();
|
|
}
|
|
if (upload) {
|
|
rub->uploadTexture(_rgbaTexture,
|
|
QRhiTextureUploadDescription(
|
|
QRhiTextureUploadEntry(0, 0,
|
|
QRhiTextureSubresourceUploadDescription(
|
|
image))));
|
|
}
|
|
srb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1, QRhiShaderResourceBinding::FragmentStage,
|
|
_rgbaTexture, _sampler),
|
|
});
|
|
} else {
|
|
Assert(data.format == Webrtc::FrameFormat::YUV420);
|
|
Assert(!data.yuv420->size.isEmpty());
|
|
const auto yuv = data.yuv420;
|
|
pipeline = _yuv420Pipeline;
|
|
srb = _yuv420Srb;
|
|
|
|
if (upload) {
|
|
if (!_yTexture || _lumaSize != yuv->size) {
|
|
delete _yTexture;
|
|
_yTexture = rhi->newTexture(
|
|
QRhiTexture::R8, yuv->size);
|
|
_yTexture->create();
|
|
_lumaSize = yuv->size;
|
|
}
|
|
auto yDesc = QRhiTextureSubresourceUploadDescription(
|
|
yuv->y.data,
|
|
yuv->y.stride * yuv->size.height());
|
|
yDesc.setDataStride(yuv->y.stride);
|
|
rub->uploadTexture(_yTexture,
|
|
QRhiTextureUploadDescription(
|
|
QRhiTextureUploadEntry(0, 0, yDesc)));
|
|
|
|
if (!_uTexture || _chromaSize != yuv->chromaSize) {
|
|
delete _uTexture;
|
|
_uTexture = rhi->newTexture(
|
|
QRhiTexture::R8, yuv->chromaSize);
|
|
_uTexture->create();
|
|
delete _vTexture;
|
|
_vTexture = rhi->newTexture(
|
|
QRhiTexture::R8, yuv->chromaSize);
|
|
_vTexture->create();
|
|
_chromaSize = yuv->chromaSize;
|
|
}
|
|
auto uDesc = QRhiTextureSubresourceUploadDescription(
|
|
yuv->u.data,
|
|
yuv->u.stride * yuv->chromaSize.height());
|
|
uDesc.setDataStride(yuv->u.stride);
|
|
rub->uploadTexture(_uTexture,
|
|
QRhiTextureUploadDescription(
|
|
QRhiTextureUploadEntry(0, 0, uDesc)));
|
|
auto vDesc = QRhiTextureSubresourceUploadDescription(
|
|
yuv->v.data,
|
|
yuv->v.stride * yuv->chromaSize.height());
|
|
vDesc.setDataStride(yuv->v.stride);
|
|
rub->uploadTexture(_vTexture,
|
|
QRhiTextureUploadDescription(
|
|
QRhiTextureUploadEntry(0, 0, vDesc)));
|
|
}
|
|
srb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1, QRhiShaderResourceBinding::FragmentStage,
|
|
_yTexture, _sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
2, QRhiShaderResourceBinding::FragmentStage,
|
|
_uTexture, _sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
3, QRhiShaderResourceBinding::FragmentStage,
|
|
_vTexture, _sampler),
|
|
});
|
|
}
|
|
srb->create();
|
|
|
|
const auto pw = float(rt->pixelSize().width());
|
|
const auto ph = float(rt->pixelSize().height());
|
|
const auto factor = style::DevicePixelRatio();
|
|
|
|
const auto widget = _owner->widget();
|
|
const auto bottomShadowArea = QRect(
|
|
0,
|
|
widget->parentWidget()->height()
|
|
- st::callBottomShadowSize,
|
|
widget->parentWidget()->width(),
|
|
st::callBottomShadowSize);
|
|
const auto bottomShadowFill = bottomShadowArea.intersected(
|
|
widget->geometry()).translated(-widget->pos());
|
|
const auto shadowHeight = bottomShadowFill.height();
|
|
const auto shadowAlpha = float(
|
|
shadowHeight * kBottomShadowAlphaMax)
|
|
/ float(st::callBottomShadowSize * 255);
|
|
const auto viewport = QSize(
|
|
int(pw / factor), int(ph / factor));
|
|
const auto shadowBottom = Ui::GL::TransformRect(
|
|
Ui::GL::Rect(bottomShadowFill),
|
|
viewport,
|
|
factor);
|
|
|
|
IncomingShadowUniforms uniforms{};
|
|
uniforms.viewport[0] = pw;
|
|
uniforms.viewport[1] = ph;
|
|
uniforms.shadow[0] = shadowHeight * factor;
|
|
uniforms.shadow[1] = shadowBottom.bottom();
|
|
uniforms.shadow[2] = shadowAlpha;
|
|
rub->updateDynamicBuffer(
|
|
_uniformBuffer, 0, sizeof(uniforms), &uniforms);
|
|
|
|
std::array<std::array<float, 2>, 4> texCoords = { {
|
|
{ { 0.f, 1.f } }, { { 1.f, 1.f } },
|
|
{ { 1.f, 0.f } }, { { 0.f, 0.f } },
|
|
} };
|
|
if (const auto shift = (rotation / 90); shift != 0) {
|
|
std::rotate(
|
|
texCoords.begin(),
|
|
texCoords.begin() + shift,
|
|
texCoords.end());
|
|
}
|
|
const float coords[] = {
|
|
0.f, ph, texCoords[3][0], texCoords[3][1],
|
|
pw, ph, texCoords[2][0], texCoords[2][1],
|
|
0.f, 0.f, texCoords[0][0], texCoords[0][1],
|
|
pw, 0.f, texCoords[1][0], texCoords[1][1],
|
|
};
|
|
rub->updateDynamicBuffer(
|
|
_vertexBuffer, 0, sizeof(coords), coords);
|
|
|
|
cb->beginPass(rt, Qt::black, { 1.0f, 0 }, rub);
|
|
cb->setGraphicsPipeline(pipeline);
|
|
cb->setShaderResources(srb);
|
|
cb->setViewport({ 0, 0, pw, ph });
|
|
const QRhiCommandBuffer::VertexInput vbuf(_vertexBuffer, 0);
|
|
cb->setVertexInput(0, 1, &vbuf);
|
|
cb->draw(4);
|
|
|
|
#ifndef Q_OS_MAC
|
|
paintTitleShadow(rt, cb, pw, ph);
|
|
#endif
|
|
|
|
cb->endPass();
|
|
}
|
|
|
|
void paintTitleShadow(
|
|
QRhiRenderTarget *rt,
|
|
QRhiCommandBuffer *cb,
|
|
float pw,
|
|
float ph) {
|
|
if (!_shadowBlendPipeline) {
|
|
return;
|
|
}
|
|
validateShadowImage();
|
|
if (!_shadowTexture) {
|
|
return;
|
|
}
|
|
const auto factor = style::DevicePixelRatio();
|
|
const auto widget = _owner->widget();
|
|
const auto left = (_owner->_topControlsAlignment
|
|
== style::al_left);
|
|
const auto width = widget->parentWidget()->width();
|
|
const auto position = left
|
|
? QPoint()
|
|
: QPoint(
|
|
width - st::callTitleShadowRight.width(),
|
|
0);
|
|
const auto translated = position - widget->pos();
|
|
const auto shadowArea = QRect(
|
|
translated,
|
|
st::callTitleShadowLeft.size());
|
|
const auto viewport = QSize(
|
|
int(pw / factor), int(ph / factor));
|
|
const auto shadowRect = Ui::GL::TransformRect(
|
|
Ui::GL::Rect(shadowArea),
|
|
viewport,
|
|
factor);
|
|
|
|
const auto &texRect = left
|
|
? _shadowLeftRect : _shadowRightRect;
|
|
const auto atlasW = float(_shadowSize.width());
|
|
const auto atlasH = float(_shadowSize.height());
|
|
const auto tl = texRect.left() / atlasW;
|
|
const auto tr = texRect.right() / atlasW;
|
|
const auto tt = texRect.top() / atlasH;
|
|
const auto tb = texRect.bottom() / atlasH;
|
|
|
|
auto *rub2 = _rhi->nextResourceUpdateBatch();
|
|
ensureShadowUploaded(rub2);
|
|
const float shadowCoords[] = {
|
|
shadowRect.left(), shadowRect.bottom(), tl, tt,
|
|
shadowRect.right(), shadowRect.bottom(), tr, tt,
|
|
shadowRect.left(), shadowRect.top(), tl, tb,
|
|
shadowRect.right(), shadowRect.top(), tr, tb,
|
|
};
|
|
rub2->updateDynamicBuffer(
|
|
_vertexBuffer, 0, sizeof(shadowCoords), shadowCoords);
|
|
const float viewport2[] = { pw, ph, 0.f, 0.f };
|
|
rub2->updateDynamicBuffer(
|
|
_uniformBuffer, 0, sizeof(viewport2), viewport2);
|
|
|
|
_argb32Srb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage,
|
|
_uniformBuffer),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_shadowTexture, _sampler),
|
|
});
|
|
_argb32Srb->create();
|
|
|
|
cb->resourceUpdate(rub2);
|
|
cb->setGraphicsPipeline(_shadowBlendPipeline);
|
|
cb->setShaderResources(_argb32Srb);
|
|
cb->setViewport({ 0, 0, pw, ph });
|
|
const QRhiCommandBuffer::VertexInput vbuf2(_vertexBuffer, 0);
|
|
cb->setVertexInput(0, 1, &vbuf2);
|
|
cb->draw(4);
|
|
}
|
|
|
|
void validateShadowImage() {
|
|
if (_shadowTexture) {
|
|
return;
|
|
}
|
|
const auto ifactor = int(std::ceil(
|
|
style::DevicePixelRatio()));
|
|
const auto size = st::callTitleShadowLeft.size();
|
|
const auto full = QSize(
|
|
size.width(), 2 * size.height()) * ifactor;
|
|
auto image = QImage(
|
|
full, QImage::Format_ARGB32_Premultiplied);
|
|
image.setDevicePixelRatio(ifactor);
|
|
image.fill(Qt::transparent);
|
|
{
|
|
auto p = QPainter(&image);
|
|
st::callTitleShadowLeft.paint(
|
|
p, 0, 0, size.width());
|
|
_shadowLeftRect = QRect(
|
|
0, 0, full.width(), full.height() / 2);
|
|
st::callTitleShadowRight.paint(
|
|
p, 0, size.height(), size.width());
|
|
_shadowRightRect = QRect(
|
|
0, full.height() / 2,
|
|
full.width(), full.height() / 2);
|
|
}
|
|
_shadowTexture = _rhi->newTexture(
|
|
QRhiTexture::BGRA8, full);
|
|
_shadowTexture->create();
|
|
_shadowSize = full;
|
|
_shadowUploadImage = std::move(image);
|
|
}
|
|
|
|
QImage _shadowUploadImage;
|
|
|
|
void ensureShadowUploaded(QRhiResourceUpdateBatch *rub) {
|
|
if (_shadowUploadImage.isNull()) {
|
|
return;
|
|
}
|
|
rub->uploadTexture(
|
|
_shadowTexture,
|
|
QRhiTextureUploadDescription(
|
|
QRhiTextureUploadEntry(0, 0,
|
|
QRhiTextureSubresourceUploadDescription(
|
|
_shadowUploadImage))));
|
|
_shadowUploadImage = QImage();
|
|
}
|
|
|
|
void releaseResources() override {
|
|
delete _argb32Pipeline; _argb32Pipeline = nullptr;
|
|
delete _yuv420Pipeline; _yuv420Pipeline = nullptr;
|
|
delete _shadowBlendPipeline; _shadowBlendPipeline = nullptr;
|
|
delete _argb32Srb; _argb32Srb = nullptr;
|
|
delete _yuv420Srb; _yuv420Srb = nullptr;
|
|
delete _shadowTexture; _shadowTexture = nullptr;
|
|
delete _rgbaTexture; _rgbaTexture = nullptr;
|
|
delete _yTexture; _yTexture = nullptr;
|
|
delete _uTexture; _uTexture = nullptr;
|
|
delete _vTexture; _vTexture = nullptr;
|
|
delete _placeholder; _placeholder = nullptr;
|
|
delete _vertexBuffer; _vertexBuffer = nullptr;
|
|
delete _uniformBuffer; _uniformBuffer = nullptr;
|
|
delete _sampler; _sampler = nullptr;
|
|
_initialized = false;
|
|
}
|
|
|
|
QColor rhiClearColor() override { return Qt::black; }
|
|
|
|
private:
|
|
const not_null<Incoming*> _owner;
|
|
QRhi *_rhi = nullptr;
|
|
QRhiBuffer *_vertexBuffer = nullptr;
|
|
QRhiBuffer *_uniformBuffer = nullptr;
|
|
QRhiSampler *_sampler = nullptr;
|
|
QRhiTexture *_placeholder = nullptr;
|
|
QRhiTexture *_rgbaTexture = nullptr;
|
|
QSize _rgbaSize;
|
|
QRhiTexture *_yTexture = nullptr;
|
|
QRhiTexture *_uTexture = nullptr;
|
|
QRhiTexture *_vTexture = nullptr;
|
|
QSize _lumaSize;
|
|
QSize _chromaSize;
|
|
int _trackFrameIndex = 0;
|
|
QRhiGraphicsPipeline *_argb32Pipeline = nullptr;
|
|
QRhiGraphicsPipeline *_yuv420Pipeline = nullptr;
|
|
QRhiGraphicsPipeline *_shadowBlendPipeline = nullptr;
|
|
QRhiShaderResourceBindings *_argb32Srb = nullptr;
|
|
QRhiShaderResourceBindings *_yuv420Srb = nullptr;
|
|
QRhiTexture *_shadowTexture = nullptr;
|
|
QSize _shadowSize;
|
|
QRect _shadowLeftRect;
|
|
QRect _shadowRightRect;
|
|
bool _initialized = false;
|
|
};
|
|
#endif // Qt >= 6.7
|
|
|
|
Panel::Incoming::Incoming(
|
|
not_null<QWidget*> parent,
|
|
not_null<Webrtc::VideoTrack*> track,
|
|
Ui::GL::Backend backend)
|
|
: _surface(Ui::GL::CreateSurface(parent, chooseRenderer(backend)))
|
|
, _track(track) {
|
|
widget()->setAttribute(Qt::WA_OpaquePaintEvent);
|
|
widget()->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
}
|
|
|
|
not_null<QWidget*> Panel::Incoming::widget() const {
|
|
return _surface->rpWidget();
|
|
}
|
|
|
|
not_null<Ui::RpWidgetWrap*> Panel::Incoming::rp() const {
|
|
return _surface.get();
|
|
}
|
|
|
|
void Panel::Incoming::setControlsAlignment(style::align align) {
|
|
if (_topControlsAlignment != align) {
|
|
_topControlsAlignment = align;
|
|
widget()->update();
|
|
}
|
|
}
|
|
|
|
Ui::GL::ChosenRenderer Panel::Incoming::chooseRenderer(
|
|
Ui::GL::Backend backend) {
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
|
if (backend == Ui::GL::Backend::QRhi) {
|
|
_opengl = true;
|
|
return {
|
|
.renderer = std::make_unique<RendererRhi>(this),
|
|
.backend = Ui::GL::Backend::QRhi,
|
|
};
|
|
}
|
|
#endif // Qt >= 6.7
|
|
_opengl = (backend == Ui::GL::Backend::OpenGL);
|
|
return {
|
|
.renderer = (_opengl
|
|
? std::unique_ptr<Ui::GL::Renderer>(
|
|
std::make_unique<RendererGL>(this))
|
|
: std::make_unique<RendererSW>(this)),
|
|
.backend = backend,
|
|
};
|
|
}
|
|
|
|
} // namespace Calls
|