mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/telegramdesktop/tdesktop
synced 2026-09-21 00:23:56 +08:00
The fragment shaders converted gl_FragCoord.y to the bottom-left based system of the rectangle uniforms unconditionally, which only holds for Vulkan, Metal and D3D, where gl_FragCoord.y counts from the top. On OpenGL it counts from the bottom, so the media viewer and PiP fades, the content rounding and the incoming call shadow came out mirrored there. The conversion is now driven by a fragCoordYUp uniform, kept at offset 8 in every block that needs it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1136 lines
31 KiB
C++
1136 lines
31 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)
|
|
|
|
// std140 puts the vec3 at offset 16, the NDC Y flip fills the hole.
|
|
struct IncomingShadowUniforms {
|
|
float viewport[2];
|
|
float fragCoordYUp;
|
|
float flipY;
|
|
float shadow[3];
|
|
float _pad1;
|
|
};
|
|
static_assert(sizeof(IncomingShadowUniforms) == 32);
|
|
|
|
namespace {
|
|
|
|
// Vulkan is the only backend with the NDC Y axis pointing down.
|
|
[[nodiscard]] float NdcFlipY(not_null<QRhi*> rhi) {
|
|
return rhi->isYUpInNDC() ? 1.f : -1.f;
|
|
}
|
|
|
|
// OpenGL is the only backend with gl_FragCoord.y counting from the bottom.
|
|
[[nodiscard]] float FragCoordYUp(not_null<QRhi*> rhi) {
|
|
return rhi->isYUpInFramebuffer() ? 1.f : 0.f;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
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);
|
|
|
|
_shadowVertexBuffer = rhi->newBuffer(
|
|
QRhiBuffer::Dynamic,
|
|
QRhiBuffer::VertexBuffer,
|
|
4 * 4 * sizeof(float));
|
|
_shadowVertexBuffer->create();
|
|
_shadowUniformBuffer = rhi->newBuffer(
|
|
QRhiBuffer::Dynamic,
|
|
QRhiBuffer::UniformBuffer,
|
|
256);
|
|
_shadowUniformBuffer->create();
|
|
|
|
validateShadowImage();
|
|
|
|
_shadowSrb = rhi->newShaderResourceBindings();
|
|
_shadowSrb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage,
|
|
_shadowUniformBuffer),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_shadowTexture, _sampler),
|
|
});
|
|
_shadowSrb->create();
|
|
|
|
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(_shadowSrb);
|
|
_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.flipY = NdcFlipY(rhi);
|
|
uniforms.fragCoordYUp = FragCoordYUp(rhi);
|
|
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);
|
|
|
|
#ifndef Q_OS_MAC
|
|
prepareTitleShadow(rub, pw, ph);
|
|
#endif
|
|
|
|
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(cb, pw, ph);
|
|
#endif
|
|
|
|
cb->endPass();
|
|
}
|
|
|
|
// Only records resource updates, must be called outside a render
|
|
// pass, the batch is then passed to beginPass.
|
|
void prepareTitleShadow(
|
|
QRhiResourceUpdateBatch *rub,
|
|
float pw,
|
|
float ph) {
|
|
if (!_shadowBlendPipeline) {
|
|
return;
|
|
}
|
|
ensureShadowUploaded(rub);
|
|
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;
|
|
|
|
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,
|
|
};
|
|
rub->updateDynamicBuffer(
|
|
_shadowVertexBuffer, 0, sizeof(shadowCoords), shadowCoords);
|
|
// { viewport, unused, flipY }, see the vertex shader Params block.
|
|
const float viewport2[] = { pw, ph, 0.f, NdcFlipY(_rhi) };
|
|
rub->updateDynamicBuffer(
|
|
_shadowUniformBuffer, 0, sizeof(viewport2), viewport2);
|
|
}
|
|
|
|
void paintTitleShadow(QRhiCommandBuffer *cb, float pw, float ph) {
|
|
if (!_shadowBlendPipeline) {
|
|
return;
|
|
}
|
|
cb->setGraphicsPipeline(_shadowBlendPipeline);
|
|
cb->setShaderResources(_shadowSrb);
|
|
cb->setViewport({ 0, 0, pw, ph });
|
|
const QRhiCommandBuffer::VertexInput vbuf2(_shadowVertexBuffer, 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 _shadowSrb; _shadowSrb = nullptr;
|
|
delete _shadowTexture; _shadowTexture = nullptr;
|
|
delete _shadowVertexBuffer; _shadowVertexBuffer = nullptr;
|
|
delete _shadowUniformBuffer; _shadowUniformBuffer = 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;
|
|
QRhiShaderResourceBindings *_shadowSrb = nullptr;
|
|
QRhiTexture *_shadowTexture = nullptr;
|
|
QRhiBuffer *_shadowVertexBuffer = nullptr;
|
|
QRhiBuffer *_shadowUniformBuffer = 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
|