mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-08-08 20:53:54 +00:00
1869 lines
52 KiB
C++
1869 lines
52 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 "media/view/media_view_overlay_rhi.h"
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
|
|
|
#include "ui/rhi/rhi_shader.h"
|
|
#include "ui/painter.h"
|
|
#include "data/data_peer_values.h"
|
|
#include "media/streaming/media_streaming_common.h"
|
|
#include "media/view/media_view_video_stream.h"
|
|
#include "platform/platform_overlay_widget.h"
|
|
#include "base/debug_log.h"
|
|
#include "styles/style_media_view.h"
|
|
|
|
#include <rhi/qrhi.h>
|
|
|
|
namespace Media::View {
|
|
namespace {
|
|
|
|
using namespace Ui::GL;
|
|
|
|
struct ImageUniforms {
|
|
float viewport[2];
|
|
float g_opacity;
|
|
float _pad0;
|
|
};
|
|
static_assert(sizeof(ImageUniforms) % 16 == 0);
|
|
|
|
struct ContentUniforms {
|
|
float viewport[2];
|
|
float _pad0[2];
|
|
float shadowTopRect[4];
|
|
float shadowBottomSkipOpacityFullFade[4];
|
|
float roundRect[4];
|
|
float roundRadius;
|
|
float _pad1[3];
|
|
};
|
|
static_assert(sizeof(ContentUniforms) == 80);
|
|
|
|
struct TransparentContentUniforms {
|
|
float viewport[2];
|
|
float _pad0[2];
|
|
float shadowTopRect[4];
|
|
float shadowBottomSkipOpacityFullFade[4];
|
|
float transparentBg[4];
|
|
float transparentFg[4];
|
|
float transparentSize;
|
|
float _pad1[3];
|
|
};
|
|
static_assert(sizeof(TransparentContentUniforms) == 96);
|
|
|
|
struct RoundedCornersUniforms {
|
|
float viewport[2];
|
|
float _pad0[2];
|
|
float roundRect[4];
|
|
float roundRadius;
|
|
float _pad1[3];
|
|
};
|
|
static_assert(sizeof(RoundedCornersUniforms) == 48);
|
|
|
|
[[nodiscard]] QRectF StoryCropTextureRect(
|
|
QSizeF imageSize,
|
|
QSizeF targetSize) {
|
|
if (imageSize.isEmpty() || targetSize.isEmpty()) {
|
|
return QRectF(0., 0., 1., 1.);
|
|
}
|
|
const auto targetAspect = targetSize.width() / targetSize.height();
|
|
const auto imageAspect = imageSize.width() / imageSize.height();
|
|
if (imageAspect > targetAspect) {
|
|
const auto cropW = imageSize.height() * targetAspect;
|
|
const auto offset = (imageSize.width() - cropW) / 2.;
|
|
return QRectF(
|
|
offset / imageSize.width(),
|
|
0.,
|
|
cropW / imageSize.width(),
|
|
1.);
|
|
} else if (imageAspect < targetAspect) {
|
|
const auto cropH = imageSize.width() / targetAspect;
|
|
const auto offset = (imageSize.height() - cropH) / 2.;
|
|
return QRectF(
|
|
0.,
|
|
offset / imageSize.height(),
|
|
1.,
|
|
cropH / imageSize.height());
|
|
}
|
|
return QRectF(0., 0., 1., 1.);
|
|
}
|
|
|
|
[[nodiscard]] QShader LoadShader(const QString &name) {
|
|
return Ui::Rhi::ShaderFromFile(
|
|
u":/shaders/"_q + name + u".qsb"_q);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
OverlayWidget::RendererRhi::~RendererRhi() {
|
|
releaseResources();
|
|
}
|
|
|
|
OverlayWidget::RendererRhi::RendererRhi(not_null<OverlayWidget*> owner)
|
|
: _owner(owner) {
|
|
style::PaletteChanged(
|
|
) | rpl::on_next([=] {
|
|
ranges::fill(_cacheKeys, quint64(0));
|
|
invalidateControls();
|
|
}, _lifetime);
|
|
|
|
crl::on_main(this, [=] {
|
|
_owner->_storiesChanged.events(
|
|
) | rpl::on_next([=] {
|
|
if (_owner->_storiesSession) {
|
|
Data::AmPremiumValue(
|
|
_owner->_storiesSession
|
|
) | rpl::on_next([=] {
|
|
ranges::fill(_cacheKeys, quint64(0));
|
|
invalidateControls();
|
|
}, _storiesLifetime);
|
|
} else {
|
|
_storiesLifetime.destroy();
|
|
ranges::fill(_cacheKeys, quint64(0));
|
|
invalidateControls();
|
|
}
|
|
}, _lifetime);
|
|
});
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::initialize(
|
|
QRhi *rhi,
|
|
QRhiRenderTarget *rt,
|
|
QRhiCommandBuffer *cb) {
|
|
if (_initialized && _rhi == rhi) {
|
|
return;
|
|
}
|
|
releaseResources();
|
|
|
|
_rhi = rhi;
|
|
_rt = rt;
|
|
_cb = cb;
|
|
|
|
_vertexBuffer = rhi->newBuffer(
|
|
QRhiBuffer::Dynamic,
|
|
QRhiBuffer::VertexBuffer,
|
|
kMaxDraws * kVertexSize);
|
|
_vertexBuffer->create();
|
|
|
|
_fillVertexBuffer = rhi->newBuffer(
|
|
QRhiBuffer::Dynamic,
|
|
QRhiBuffer::VertexBuffer,
|
|
4 * 4 * 2 * sizeof(float));
|
|
_fillVertexBuffer->create();
|
|
|
|
_uniformBuffer = rhi->newBuffer(
|
|
QRhiBuffer::Dynamic,
|
|
QRhiBuffer::UniformBuffer,
|
|
kMaxDraws * 256);
|
|
_uniformBuffer->create();
|
|
|
|
_sampler = rhi->newSampler(
|
|
QRhiSampler::Linear,
|
|
QRhiSampler::Linear,
|
|
QRhiSampler::None,
|
|
QRhiSampler::ClampToEdge,
|
|
QRhiSampler::ClampToEdge);
|
|
_sampler->create();
|
|
|
|
_placeholderTexture = rhi->newTexture(QRhiTexture::RGBA8, QSize(1, 1));
|
|
_placeholderTexture->create();
|
|
|
|
createPipelines();
|
|
_initialized = true;
|
|
|
|
LOG(("[RENDERER_TEST] component=overlay backend=%1 device=%2 status=OK")
|
|
.arg(rhi->backendName())
|
|
.arg(rhi->driverInfo().deviceName));
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::createPipelines() {
|
|
const auto rpDesc = _rt->renderPassDescriptor();
|
|
|
|
const auto argb32Vert = LoadShader(u"argb32.vert"_q);
|
|
const auto argb32Frag = LoadShader(u"argb32.frag"_q);
|
|
const auto controlsFrag = LoadShader(u"controls.frag"_q);
|
|
|
|
auto *sampleSrb = _rhi->newShaderResourceBindings();
|
|
sampleSrb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer,
|
|
sizeof(ImageUniforms)),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholderTexture,
|
|
_sampler),
|
|
});
|
|
sampleSrb->create();
|
|
_perDrawSrbs.push_back(sampleSrb);
|
|
|
|
auto *imagePipeline = _rhi->newGraphicsPipeline();
|
|
imagePipeline->setShaderStages({
|
|
{ QRhiShaderStage::Vertex, argb32Vert },
|
|
{ QRhiShaderStage::Fragment, argb32Frag },
|
|
});
|
|
QRhiVertexInputLayout inputLayout;
|
|
inputLayout.setBindings({ { 4 * sizeof(float) } });
|
|
inputLayout.setAttributes({
|
|
{ 0, 0, QRhiVertexInputAttribute::Float2, 0 },
|
|
{ 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) },
|
|
});
|
|
imagePipeline->setVertexInputLayout(inputLayout);
|
|
imagePipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
|
|
imagePipeline->setShaderResourceBindings(sampleSrb);
|
|
imagePipeline->setRenderPassDescriptor(rpDesc);
|
|
imagePipeline->create();
|
|
_imagePipeline = imagePipeline;
|
|
|
|
auto *imageBlendPipeline = _rhi->newGraphicsPipeline();
|
|
imageBlendPipeline->setShaderStages({
|
|
{ QRhiShaderStage::Vertex, argb32Vert },
|
|
{ QRhiShaderStage::Fragment, controlsFrag },
|
|
});
|
|
imageBlendPipeline->setVertexInputLayout(inputLayout);
|
|
QRhiGraphicsPipeline::TargetBlend blend;
|
|
blend.enable = true;
|
|
blend.srcColor = QRhiGraphicsPipeline::One;
|
|
blend.dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
|
|
blend.srcAlpha = QRhiGraphicsPipeline::One;
|
|
blend.dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
|
|
imageBlendPipeline->setTargetBlends({ blend });
|
|
imageBlendPipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
|
|
imageBlendPipeline->setShaderResourceBindings(sampleSrb);
|
|
imageBlendPipeline->setRenderPassDescriptor(rpDesc);
|
|
imageBlendPipeline->create();
|
|
_imageBlendPipeline = imageBlendPipeline;
|
|
|
|
const auto staticContentFrag = LoadShader(u"static_content.frag"_q);
|
|
const auto transparentContentFrag = LoadShader(
|
|
u"transparent_content.frag"_q);
|
|
|
|
auto *contentSrb = _rhi->newShaderResourceBindings();
|
|
contentSrb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer,
|
|
0,
|
|
sizeof(ContentUniforms)),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholderTexture,
|
|
_sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
2,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholderTexture,
|
|
_sampler),
|
|
});
|
|
contentSrb->create();
|
|
_perDrawSrbs.push_back(contentSrb);
|
|
|
|
auto *staticPipeline = _rhi->newGraphicsPipeline();
|
|
staticPipeline->setShaderStages({
|
|
{ QRhiShaderStage::Vertex, argb32Vert },
|
|
{ QRhiShaderStage::Fragment, staticContentFrag },
|
|
});
|
|
staticPipeline->setVertexInputLayout(inputLayout);
|
|
staticPipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
|
|
staticPipeline->setShaderResourceBindings(contentSrb);
|
|
staticPipeline->setRenderPassDescriptor(rpDesc);
|
|
staticPipeline->create();
|
|
_staticContentPipeline = staticPipeline;
|
|
|
|
auto *staticBlendPipeline = _rhi->newGraphicsPipeline();
|
|
staticBlendPipeline->setShaderStages({
|
|
{ QRhiShaderStage::Vertex, argb32Vert },
|
|
{ QRhiShaderStage::Fragment, staticContentFrag },
|
|
});
|
|
staticBlendPipeline->setVertexInputLayout(inputLayout);
|
|
staticBlendPipeline->setTargetBlends({ blend });
|
|
staticBlendPipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
|
|
staticBlendPipeline->setShaderResourceBindings(contentSrb);
|
|
staticBlendPipeline->setRenderPassDescriptor(rpDesc);
|
|
staticBlendPipeline->create();
|
|
_staticContentBlendPipeline = staticBlendPipeline;
|
|
|
|
auto *transparentSrb = _rhi->newShaderResourceBindings();
|
|
transparentSrb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer,
|
|
0,
|
|
sizeof(TransparentContentUniforms)),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholderTexture,
|
|
_sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
2,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholderTexture,
|
|
_sampler),
|
|
});
|
|
transparentSrb->create();
|
|
_perDrawSrbs.push_back(transparentSrb);
|
|
|
|
auto *transparentPipeline = _rhi->newGraphicsPipeline();
|
|
transparentPipeline->setShaderStages({
|
|
{ QRhiShaderStage::Vertex, argb32Vert },
|
|
{ QRhiShaderStage::Fragment, transparentContentFrag },
|
|
});
|
|
transparentPipeline->setVertexInputLayout(inputLayout);
|
|
transparentPipeline->setTargetBlends({ blend });
|
|
transparentPipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
|
|
transparentPipeline->setShaderResourceBindings(transparentSrb);
|
|
transparentPipeline->setRenderPassDescriptor(rpDesc);
|
|
transparentPipeline->create();
|
|
_transparentContentPipeline = transparentPipeline;
|
|
|
|
const auto fillVert = LoadShader(u"fill.vert"_q);
|
|
const auto roundedCornersFrag = LoadShader(u"rounded_corners.frag"_q);
|
|
|
|
auto *roundedSrb = _rhi->newShaderResourceBindings();
|
|
roundedSrb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer,
|
|
0,
|
|
sizeof(RoundedCornersUniforms)),
|
|
});
|
|
roundedSrb->create();
|
|
_perDrawSrbs.push_back(roundedSrb);
|
|
|
|
QRhiVertexInputLayout fillInputLayout;
|
|
fillInputLayout.setBindings({ { 2 * sizeof(float) } });
|
|
fillInputLayout.setAttributes({
|
|
{ 0, 0, QRhiVertexInputAttribute::Float2, 0 },
|
|
});
|
|
|
|
QRhiGraphicsPipeline::TargetBlend cornerBlend;
|
|
cornerBlend.enable = true;
|
|
cornerBlend.srcColor = QRhiGraphicsPipeline::Zero;
|
|
cornerBlend.dstColor = QRhiGraphicsPipeline::SrcAlpha;
|
|
cornerBlend.srcAlpha = QRhiGraphicsPipeline::Zero;
|
|
cornerBlend.dstAlpha = QRhiGraphicsPipeline::SrcAlpha;
|
|
|
|
auto *roundedPipeline = _rhi->newGraphicsPipeline();
|
|
roundedPipeline->setShaderStages({
|
|
{ QRhiShaderStage::Vertex, fillVert },
|
|
{ QRhiShaderStage::Fragment, roundedCornersFrag },
|
|
});
|
|
roundedPipeline->setVertexInputLayout(fillInputLayout);
|
|
roundedPipeline->setTargetBlends({ cornerBlend });
|
|
roundedPipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
|
|
roundedPipeline->setShaderResourceBindings(roundedSrb);
|
|
roundedPipeline->setRenderPassDescriptor(rpDesc);
|
|
roundedPipeline->create();
|
|
_roundedCornersPipeline = roundedPipeline;
|
|
|
|
const auto yuv420Frag = LoadShader(u"yuv420_content.frag"_q);
|
|
const auto nv12Frag = LoadShader(u"nv12_content.frag"_q);
|
|
|
|
auto *yuv420Srb = _rhi->newShaderResourceBindings();
|
|
yuv420Srb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer,
|
|
0,
|
|
sizeof(ContentUniforms)),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholderTexture,
|
|
_sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
2,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholderTexture,
|
|
_sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
3,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholderTexture,
|
|
_sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
4,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholderTexture,
|
|
_sampler),
|
|
});
|
|
yuv420Srb->create();
|
|
_perDrawSrbs.push_back(yuv420Srb);
|
|
|
|
auto createYuvPipeline = [&](const QShader &frag, bool blending,
|
|
QRhiShaderResourceBindings *srb) {
|
|
auto *pipeline = _rhi->newGraphicsPipeline();
|
|
pipeline->setShaderStages({
|
|
{ QRhiShaderStage::Vertex, argb32Vert },
|
|
{ QRhiShaderStage::Fragment, frag },
|
|
});
|
|
pipeline->setVertexInputLayout(inputLayout);
|
|
if (blending) {
|
|
pipeline->setTargetBlends({ blend });
|
|
}
|
|
pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
|
|
pipeline->setShaderResourceBindings(srb);
|
|
pipeline->setRenderPassDescriptor(rpDesc);
|
|
pipeline->create();
|
|
return pipeline;
|
|
};
|
|
|
|
_yuv420Pipeline = createYuvPipeline(yuv420Frag, false, yuv420Srb);
|
|
_yuv420BlendPipeline = createYuvPipeline(yuv420Frag, true, yuv420Srb);
|
|
|
|
auto *nv12Srb = _rhi->newShaderResourceBindings();
|
|
nv12Srb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer,
|
|
0,
|
|
sizeof(ContentUniforms)),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholderTexture,
|
|
_sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
2,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholderTexture,
|
|
_sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
3,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_placeholderTexture,
|
|
_sampler),
|
|
});
|
|
nv12Srb->create();
|
|
_perDrawSrbs.push_back(nv12Srb);
|
|
|
|
_nv12Pipeline = createYuvPipeline(nv12Frag, false, nv12Srb);
|
|
_nv12BlendPipeline = createYuvPipeline(nv12Frag, true, nv12Srb);
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::render(
|
|
QRhi *rhi,
|
|
QRhiRenderTarget *rt,
|
|
QRhiCommandBuffer *cb) {
|
|
if (_owner->_hideWorkaround) {
|
|
return;
|
|
}
|
|
_rhi = rhi;
|
|
_rt = rt;
|
|
_cb = cb;
|
|
_nextVertexSlot = 0;
|
|
_nextPoolIndex = 0;
|
|
for (auto *srb : _perDrawSrbs) {
|
|
delete srb;
|
|
}
|
|
_perDrawSrbs.clear();
|
|
_drawCommands.clear();
|
|
|
|
const auto size = rt->pixelSize();
|
|
const auto factor = _owner->widget()->devicePixelRatioF();
|
|
if (_factor != factor) {
|
|
_factor = factor;
|
|
_ifactor = int(std::ceil(factor));
|
|
ranges::fill(_cacheKeys, quint64(0));
|
|
invalidateControls();
|
|
delete _controlsFadeTexture;
|
|
_controlsFadeTexture = nullptr;
|
|
}
|
|
_viewport = QSize(
|
|
int(size.width() / _factor),
|
|
int(size.height() / _factor));
|
|
|
|
_rub = rhi->nextResourceUpdateBatch();
|
|
_pendingVideoStream = nullptr;
|
|
|
|
_owner->paint(this);
|
|
|
|
if (_pendingVideoStream) {
|
|
_pendingVideoStream->borrowedPaintOffscreen(_rhi, _rt, _cb);
|
|
}
|
|
|
|
if (const auto notch = _owner->topNotchSkip()) {
|
|
auto blackImage = QImage(1, 1, QImage::Format_ARGB32_Premultiplied);
|
|
blackImage.fill(Qt::black);
|
|
auto *blackTex = acquirePoolTexture(QSize(1, 1));
|
|
_rub->uploadTexture(
|
|
blackTex,
|
|
QRhiTextureUploadDescription(
|
|
QRhiTextureUploadEntry(0, 0,
|
|
QRhiTextureSubresourceUploadDescription(blackImage))));
|
|
const auto notchRect = transformRect(
|
|
QRect(0, 0, _owner->width(), notch));
|
|
const float notchCoords[] = {
|
|
notchRect.left(), notchRect.bottom(), 0.f, 0.f,
|
|
notchRect.right(), notchRect.bottom(), 1.f, 0.f,
|
|
notchRect.left(), notchRect.top(), 0.f, 1.f,
|
|
notchRect.right(), notchRect.top(), 1.f, 1.f,
|
|
};
|
|
drawTexturedQuad(_imagePipeline, blackTex, notchCoords);
|
|
}
|
|
|
|
cb->beginPass(rt, QColor(0, 0, 0, 0), { 1.0f, 0 }, _rub);
|
|
_rub = nullptr;
|
|
|
|
for (const auto &cmd : _drawCommands) {
|
|
cb->setGraphicsPipeline(cmd.pipeline);
|
|
cb->setShaderResources(cmd.srb);
|
|
cb->setViewport({
|
|
0, 0,
|
|
float(rt->pixelSize().width()),
|
|
float(rt->pixelSize().height()) });
|
|
if (cmd.fillVertex) {
|
|
const QRhiCommandBuffer::VertexInput vbufBinding(
|
|
_fillVertexBuffer,
|
|
cmd.vertexIndex * 4 * 2 * sizeof(float));
|
|
cb->setVertexInput(0, 1, &vbufBinding);
|
|
} else {
|
|
const QRhiCommandBuffer::VertexInput vbufBinding(
|
|
_vertexBuffer, cmd.vertexIndex * kVertexSize);
|
|
cb->setVertexInput(0, 1, &vbufBinding);
|
|
}
|
|
cb->draw(4);
|
|
}
|
|
_drawCommands.clear();
|
|
|
|
if (_pendingVideoStream) {
|
|
_pendingVideoStream->borrowedPaintOnscreen(_rhi, _rt, _cb);
|
|
_pendingVideoStream = nullptr;
|
|
}
|
|
|
|
cb->endPass();
|
|
}
|
|
|
|
QRhiTexture *OverlayWidget::RendererRhi::acquirePoolTexture(QSize size) {
|
|
if (_nextPoolIndex < int(_texturePool.size())) {
|
|
auto &entry = _texturePool[_nextPoolIndex++];
|
|
if (entry.size == size) {
|
|
return entry.texture;
|
|
}
|
|
delete entry.texture;
|
|
entry.texture = _rhi->newTexture(QRhiTexture::BGRA8, size);
|
|
entry.texture->create();
|
|
entry.size = size;
|
|
return entry.texture;
|
|
}
|
|
auto *tex = _rhi->newTexture(QRhiTexture::BGRA8, size);
|
|
tex->create();
|
|
_texturePool.push_back({ tex, size });
|
|
_nextPoolIndex++;
|
|
return tex;
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::releaseResources() {
|
|
_drawCommands.clear();
|
|
|
|
for (auto &entry : _texturePool) {
|
|
delete entry.texture;
|
|
}
|
|
_texturePool.clear();
|
|
|
|
delete _imagePipeline;
|
|
_imagePipeline = nullptr;
|
|
delete _imageBlendPipeline;
|
|
_imageBlendPipeline = nullptr;
|
|
delete _staticContentPipeline;
|
|
_staticContentPipeline = nullptr;
|
|
delete _staticContentBlendPipeline;
|
|
_staticContentBlendPipeline = nullptr;
|
|
delete _transparentContentPipeline;
|
|
_transparentContentPipeline = nullptr;
|
|
delete _roundedCornersPipeline;
|
|
_roundedCornersPipeline = nullptr;
|
|
delete _yuv420Pipeline;
|
|
_yuv420Pipeline = nullptr;
|
|
delete _yuv420BlendPipeline;
|
|
_yuv420BlendPipeline = nullptr;
|
|
delete _nv12Pipeline;
|
|
_nv12Pipeline = nullptr;
|
|
delete _nv12BlendPipeline;
|
|
_nv12BlendPipeline = nullptr;
|
|
|
|
for (auto &tex : _storiesSiblingTextures) {
|
|
delete tex;
|
|
tex = nullptr;
|
|
}
|
|
ranges::fill(_storiesSiblingSizes, QSize());
|
|
ranges::fill(_storiesSiblingCacheKeys, quint64(0));
|
|
|
|
delete _controlsAtlasTexture;
|
|
_controlsAtlasTexture = nullptr;
|
|
_controlsAtlasSize = QSize();
|
|
ranges::fill(_controlsTextures, QRect());
|
|
|
|
delete _controlsFadeTexture;
|
|
_controlsFadeTexture = nullptr;
|
|
_controlsFadeSize = QSize();
|
|
|
|
for (auto *srb : _perDrawSrbs) {
|
|
delete srb;
|
|
}
|
|
_perDrawSrbs.clear();
|
|
|
|
for (auto &tex : _rgbaTextures) {
|
|
delete tex;
|
|
tex = nullptr;
|
|
}
|
|
ranges::fill(_rgbaSizes, QSize());
|
|
ranges::fill(_cacheKeys, quint64(0));
|
|
|
|
delete _yTexture;
|
|
_yTexture = nullptr;
|
|
delete _uTexture;
|
|
_uTexture = nullptr;
|
|
delete _vTexture;
|
|
_vTexture = nullptr;
|
|
delete _uvTexture;
|
|
_uvTexture = nullptr;
|
|
_lumaSize = QSize();
|
|
_chromaSize = QSize();
|
|
_chromaNV12 = false;
|
|
_usingExternalVideoTextures = false;
|
|
_trackFrameIndex = 0;
|
|
_streamedIndex = 0;
|
|
#ifdef Q_OS_MAC
|
|
_metalTextureCache.flush();
|
|
#endif // Q_OS_MAC
|
|
|
|
delete _placeholderTexture;
|
|
_placeholderTexture = nullptr;
|
|
|
|
delete _vertexBuffer;
|
|
_vertexBuffer = nullptr;
|
|
delete _fillVertexBuffer;
|
|
_fillVertexBuffer = nullptr;
|
|
delete _uniformBuffer;
|
|
_uniformBuffer = nullptr;
|
|
delete _sampler;
|
|
_sampler = nullptr;
|
|
|
|
_initialized = false;
|
|
}
|
|
|
|
QColor OverlayWidget::RendererRhi::rhiClearColor() {
|
|
if (_owner->_hideWorkaround) {
|
|
return QColor(0, 0, 0, 0);
|
|
} else if (_owner->_fullScreenVideo) {
|
|
return st::mediaviewVideoBg->c;
|
|
} else {
|
|
return st::mediaviewBg->c;
|
|
}
|
|
}
|
|
|
|
std::optional<QColor> OverlayWidget::RendererRhi::clearColor() {
|
|
if (_owner->_hideWorkaround) {
|
|
return QColor(0, 0, 0, 0);
|
|
} else if (_owner->_fullScreenVideo) {
|
|
return st::mediaviewVideoBg->c;
|
|
} else {
|
|
return st::mediaviewBg->c;
|
|
}
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::drawTexturedQuad(
|
|
QRhiGraphicsPipeline *pipeline,
|
|
QRhiTexture *texture,
|
|
const float *coords,
|
|
float opacity,
|
|
bool blend) {
|
|
if (_nextVertexSlot >= kMaxDraws || !texture) {
|
|
return;
|
|
}
|
|
const auto slot = _nextVertexSlot++;
|
|
const auto vOffset = slot * kVertexSize;
|
|
const auto uOffset = slot * 256;
|
|
|
|
_rub->updateDynamicBuffer(
|
|
_vertexBuffer, vOffset, kVertexSize, coords);
|
|
|
|
ImageUniforms uniforms{};
|
|
uniforms.viewport[0] = _viewport.width() * _factor;
|
|
uniforms.viewport[1] = _viewport.height() * _factor;
|
|
uniforms.g_opacity = opacity;
|
|
_rub->updateDynamicBuffer(
|
|
_uniformBuffer, uOffset, sizeof(ImageUniforms), &uniforms);
|
|
|
|
auto *srb = _rhi->newShaderResourceBindings();
|
|
srb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer,
|
|
uOffset,
|
|
sizeof(ImageUniforms)),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
texture,
|
|
_sampler),
|
|
});
|
|
srb->create();
|
|
_perDrawSrbs.push_back(srb);
|
|
|
|
_drawCommands.push_back({
|
|
.pipeline = blend ? _imageBlendPipeline : pipeline,
|
|
.srb = srb,
|
|
.vertexIndex = slot,
|
|
});
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintUsingRaster(
|
|
QRect rect,
|
|
Fn<void(Painter&)> method,
|
|
bool transparent,
|
|
float opacity) {
|
|
if (!_imagePipeline || rect.isEmpty()) {
|
|
return;
|
|
}
|
|
const auto size = rect.size() * _ifactor;
|
|
auto raster = QImage(size, QImage::Format_ARGB32_Premultiplied);
|
|
raster.setDevicePixelRatio(_factor);
|
|
raster.fill(Qt::transparent);
|
|
{
|
|
auto painter = Painter(&raster);
|
|
method(painter);
|
|
}
|
|
|
|
auto *tex = acquirePoolTexture(size);
|
|
_rub->uploadTexture(
|
|
tex,
|
|
QRhiTextureUploadDescription(
|
|
QRhiTextureUploadEntry(0, 0,
|
|
QRhiTextureSubresourceUploadDescription(raster))));
|
|
|
|
const auto rRect = transformRect(rect);
|
|
const float coords[] = {
|
|
rRect.left(), rRect.bottom(),
|
|
0.f, 0.f,
|
|
|
|
rRect.right(), rRect.bottom(),
|
|
1.f, 0.f,
|
|
|
|
rRect.left(), rRect.top(),
|
|
0.f, 1.f,
|
|
|
|
rRect.right(), rRect.top(),
|
|
1.f, 1.f,
|
|
};
|
|
|
|
drawTexturedQuad(
|
|
_imagePipeline,
|
|
tex,
|
|
coords,
|
|
opacity,
|
|
transparent);
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::validateControlsFade() {
|
|
const auto forStories = (_owner->_stories != nullptr);
|
|
const auto flip = !forStories && !_owner->topShadowOnTheRight();
|
|
if (_controlsFadeTexture
|
|
&& _shadowTopFlip == flip
|
|
&& _shadowsForStories == forStories) {
|
|
return;
|
|
}
|
|
_shadowTopFlip = flip;
|
|
_shadowsForStories = forStories;
|
|
const auto &top = _shadowsForStories
|
|
? st::storiesShadowTop
|
|
: st::mediaviewShadowTop;
|
|
const auto &bottom = _shadowsForStories
|
|
? st::storiesShadowBottom
|
|
: st::mediaviewShadowBottom;
|
|
const auto width = top.width();
|
|
const auto bottomTop = top.height();
|
|
const auto height = bottomTop + bottom.height();
|
|
|
|
auto image = QImage(
|
|
QSize(width, height) * _ifactor,
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
image.fill(Qt::transparent);
|
|
image.setDevicePixelRatio(_ifactor);
|
|
{
|
|
auto p = QPainter(&image);
|
|
top.paint(p, 0, 0, width);
|
|
bottom.fill(p, QRect(0, bottomTop, width, bottom.height()));
|
|
}
|
|
if (flip) {
|
|
image = std::move(image).mirrored(true, false);
|
|
}
|
|
|
|
const auto size = image.size();
|
|
if (!_controlsFadeTexture || _controlsFadeSize != size) {
|
|
delete _controlsFadeTexture;
|
|
_controlsFadeTexture = _rhi->newTexture(
|
|
QRhiTexture::BGRA8,
|
|
size);
|
|
_controlsFadeTexture->create();
|
|
_controlsFadeSize = size;
|
|
}
|
|
_rub->uploadTexture(
|
|
_controlsFadeTexture,
|
|
QRhiTextureUploadDescription(
|
|
QRhiTextureUploadEntry(0, 0,
|
|
QRhiTextureSubresourceUploadDescription(image))));
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::fillShadowUniforms(
|
|
float *shadowTopRect,
|
|
float *shadowBottomSkipOpacityFullFade,
|
|
ContentGeometry geometry) const {
|
|
if (_owner->_stories) {
|
|
const auto &top = st::storiesShadowTop.size();
|
|
const auto shadowTop = geometry.topShadowShown
|
|
? geometry.rect.y()
|
|
: geometry.rect.y() - top.height();
|
|
const auto tRect = transformRect(
|
|
QRect(QPoint(geometry.rect.x(), shadowTop), top));
|
|
shadowTopRect[0] = tRect.x();
|
|
shadowTopRect[1] = tRect.y();
|
|
shadowTopRect[2] = tRect.width();
|
|
shadowTopRect[3] = tRect.height();
|
|
} else {
|
|
const auto &top = st::mediaviewShadowTop.size();
|
|
const auto point = QPoint(
|
|
_shadowTopFlip ? 0 : (_viewport.width() - top.width()),
|
|
0);
|
|
const auto tRect = transformRect(QRect(point, top));
|
|
shadowTopRect[0] = tRect.x();
|
|
shadowTopRect[1] = tRect.y();
|
|
shadowTopRect[2] = tRect.width();
|
|
shadowTopRect[3] = tRect.height();
|
|
}
|
|
const auto &bottom = _owner->_stories
|
|
? st::storiesShadowBottom
|
|
: st::mediaviewShadowBottom;
|
|
shadowBottomSkipOpacityFullFade[0] = bottom.height() * _factor;
|
|
shadowBottomSkipOpacityFullFade[1] =
|
|
geometry.bottomShadowSkip * _factor;
|
|
shadowBottomSkipOpacityFullFade[2] = geometry.controlsOpacity;
|
|
shadowBottomSkipOpacityFullFade[3] = 1.f - float(geometry.fade);
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::drawContentQuad(
|
|
QRhiTexture *contentTexture,
|
|
const float *coords,
|
|
ContentGeometry geometry,
|
|
bool fillTransparentBackground,
|
|
bool blend) {
|
|
if (_nextVertexSlot >= kMaxDraws || !contentTexture) {
|
|
return;
|
|
}
|
|
|
|
validateControlsFade();
|
|
|
|
const auto slot = _nextVertexSlot++;
|
|
const auto vOffset = slot * kVertexSize;
|
|
const auto uOffset = slot * 256;
|
|
|
|
_rub->updateDynamicBuffer(
|
|
_vertexBuffer, vOffset, kVertexSize, coords);
|
|
|
|
const auto vw = _viewport.width() * _factor;
|
|
const auto vh = _viewport.height() * _factor;
|
|
|
|
auto *pipeline = fillTransparentBackground
|
|
? _transparentContentPipeline
|
|
: (blend ? _staticContentBlendPipeline : _staticContentPipeline);
|
|
|
|
if (fillTransparentBackground) {
|
|
const auto c_bg = st::mediaviewTransparentBg->c;
|
|
const auto c_fg = st::mediaviewTransparentFg->c;
|
|
TransparentContentUniforms uniforms{};
|
|
uniforms.viewport[0] = vw;
|
|
uniforms.viewport[1] = vh;
|
|
fillShadowUniforms(
|
|
uniforms.shadowTopRect,
|
|
uniforms.shadowBottomSkipOpacityFullFade,
|
|
geometry);
|
|
uniforms.transparentBg[0] = float(c_bg.redF());
|
|
uniforms.transparentBg[1] = float(c_bg.greenF());
|
|
uniforms.transparentBg[2] = float(c_bg.blueF());
|
|
uniforms.transparentBg[3] = float(c_bg.alphaF());
|
|
uniforms.transparentFg[0] = float(c_fg.redF());
|
|
uniforms.transparentFg[1] = float(c_fg.greenF());
|
|
uniforms.transparentFg[2] = float(c_fg.blueF());
|
|
uniforms.transparentFg[3] = float(c_fg.alphaF());
|
|
uniforms.transparentSize =
|
|
st::transparentPlaceholderSize * _factor;
|
|
_rub->updateDynamicBuffer(
|
|
_uniformBuffer,
|
|
uOffset,
|
|
sizeof(TransparentContentUniforms),
|
|
&uniforms);
|
|
} else {
|
|
ContentUniforms uniforms{};
|
|
uniforms.viewport[0] = vw;
|
|
uniforms.viewport[1] = vh;
|
|
fillShadowUniforms(
|
|
uniforms.shadowTopRect,
|
|
uniforms.shadowBottomSkipOpacityFullFade,
|
|
geometry);
|
|
const auto rRect = scaleRect(
|
|
transformRect(geometry.rect),
|
|
geometry.scale);
|
|
if (geometry.roundRadius) {
|
|
uniforms.roundRect[0] = rRect.x();
|
|
uniforms.roundRect[1] = rRect.y();
|
|
uniforms.roundRect[2] = rRect.width();
|
|
uniforms.roundRect[3] = rRect.height();
|
|
} else {
|
|
uniforms.roundRect[0] = 0;
|
|
uniforms.roundRect[1] = 0;
|
|
uniforms.roundRect[2] = vw;
|
|
uniforms.roundRect[3] = vh;
|
|
}
|
|
uniforms.roundRadius = geometry.roundRadius * _factor;
|
|
_rub->updateDynamicBuffer(
|
|
_uniformBuffer,
|
|
uOffset,
|
|
sizeof(ContentUniforms),
|
|
&uniforms);
|
|
}
|
|
|
|
auto *srb = _rhi->newShaderResourceBindings();
|
|
srb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer,
|
|
uOffset,
|
|
fillTransparentBackground
|
|
? int(sizeof(TransparentContentUniforms))
|
|
: int(sizeof(ContentUniforms))),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
contentTexture,
|
|
_sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
2,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_controlsFadeTexture
|
|
? _controlsFadeTexture
|
|
: _placeholderTexture,
|
|
_sampler),
|
|
});
|
|
srb->create();
|
|
_perDrawSrbs.push_back(srb);
|
|
|
|
_drawCommands.push_back({
|
|
.pipeline = pipeline,
|
|
.srb = srb,
|
|
.vertexIndex = slot,
|
|
});
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintBackground() {
|
|
const auto &bg = _owner->_fullScreenVideo
|
|
? st::mediaviewVideoBg
|
|
: st::mediaviewBg;
|
|
const auto c = bg->c;
|
|
const auto vw = float(_viewport.width() * _factor);
|
|
const auto vh = float(_viewport.height() * _factor);
|
|
|
|
auto bgImage = QImage(1, 1, QImage::Format_ARGB32_Premultiplied);
|
|
bgImage.fill(c);
|
|
|
|
auto *tex = acquirePoolTexture(QSize(1, 1));
|
|
_rub->uploadTexture(
|
|
tex,
|
|
QRhiTextureUploadDescription(
|
|
QRhiTextureUploadEntry(0, 0,
|
|
QRhiTextureSubresourceUploadDescription(bgImage))));
|
|
|
|
const float coords[] = {
|
|
0.f, vh, 0.f, 0.f,
|
|
vw, vh, 1.f, 0.f,
|
|
0.f, 0.f, 0.f, 1.f,
|
|
vw, 0.f, 1.f, 1.f,
|
|
};
|
|
drawTexturedQuad(_imagePipeline, tex, coords);
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintVideoStream() {
|
|
_pendingVideoStream = _owner->_videoStream.get();
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintTransformedVideoFrame(
|
|
ContentGeometry geometry) {
|
|
const auto data = _owner->videoFrameWithInfo();
|
|
if (data.format == Streaming::FrameFormat::None) {
|
|
return;
|
|
} else if (data.format == Streaming::FrameFormat::ARGB32) {
|
|
Assert(!data.image.isNull());
|
|
paintTransformedStaticContent(
|
|
data.image,
|
|
geometry,
|
|
data.alpha,
|
|
data.alpha);
|
|
return;
|
|
}
|
|
const auto nativeTexture =
|
|
(data.format == Streaming::FrameFormat::NativeTexture);
|
|
const auto nv12 = nativeTexture
|
|
|| (data.format == Streaming::FrameFormat::NV12);
|
|
const auto yuv = data.yuv;
|
|
const auto nv12changed = !nativeTexture && (_chromaNV12 != nv12);
|
|
|
|
const auto upload = (_trackFrameIndex != data.index)
|
|
|| (_streamedIndex != _owner->streamedIndex());
|
|
|
|
_trackFrameIndex = data.index;
|
|
_streamedIndex = _owner->streamedIndex();
|
|
|
|
if (upload) {
|
|
auto zeroCopied = false;
|
|
#ifdef Q_OS_MAC
|
|
if (nativeTexture && data.nativeFrame
|
|
&& data.nativeFrame->pixelBuffer) {
|
|
if (_metalTextureCache.createTexturesFromPixelBuffer(
|
|
_rhi,
|
|
data.nativeFrame->pixelBuffer,
|
|
&_yTexture,
|
|
&_uvTexture,
|
|
&_lumaSize,
|
|
&_chromaSize)) {
|
|
_chromaNV12 = true;
|
|
zeroCopied = true;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
#endif // Q_OS_MAC
|
|
if (!zeroCopied) {
|
|
if (_usingExternalVideoTextures) {
|
|
delete _yTexture;
|
|
_yTexture = nullptr;
|
|
delete _uTexture;
|
|
_uTexture = nullptr;
|
|
delete _vTexture;
|
|
_vTexture = nullptr;
|
|
delete _uvTexture;
|
|
_uvTexture = nullptr;
|
|
_lumaSize = QSize();
|
|
_chromaSize = QSize();
|
|
#ifdef Q_OS_MAC
|
|
_metalTextureCache.flush();
|
|
#endif // Q_OS_MAC
|
|
}
|
|
if (!yuv || yuv->size.isEmpty()) {
|
|
return;
|
|
}
|
|
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 (nv12) {
|
|
if (!_uvTexture || nv12changed
|
|
|| _chromaSize != yuv->chromaSize) {
|
|
delete _uvTexture;
|
|
_uvTexture = _rhi->newTexture(
|
|
QRhiTexture::RG8,
|
|
yuv->chromaSize);
|
|
_uvTexture->create();
|
|
_chromaSize = yuv->chromaSize;
|
|
}
|
|
auto uvDesc = QRhiTextureSubresourceUploadDescription(
|
|
yuv->u.data,
|
|
yuv->u.stride * yuv->chromaSize.height());
|
|
uvDesc.setDataStride(yuv->u.stride);
|
|
_rub->uploadTexture(
|
|
_uvTexture,
|
|
QRhiTextureUploadDescription(
|
|
QRhiTextureUploadEntry(0, 0, uvDesc)));
|
|
} else {
|
|
if (!_uTexture || nv12changed
|
|
|| _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)));
|
|
}
|
|
_chromaNV12 = nv12;
|
|
} // if (!zeroCopied)
|
|
_usingExternalVideoTextures = zeroCopied;
|
|
}
|
|
|
|
validateControlsFade();
|
|
|
|
if (_nextVertexSlot >= kMaxDraws) {
|
|
return;
|
|
}
|
|
|
|
const auto textureRect = _owner->_stories
|
|
? StoryCropTextureRect(
|
|
QSizeF(nativeTexture ? _lumaSize : yuv->size),
|
|
geometry.rect.size())
|
|
: QRectF(0., 0., 1., 1.);
|
|
const auto texLeft = float(textureRect.x());
|
|
const auto texRight = float(textureRect.x() + textureRect.width());
|
|
const auto texTop = float(textureRect.y());
|
|
const auto texBottom = float(textureRect.y() + textureRect.height());
|
|
|
|
const auto rRect = scaleRect(
|
|
transformRect(geometry.rect),
|
|
geometry.scale);
|
|
const auto centerx = rRect.x() + rRect.width() / 2;
|
|
const auto centery = rRect.y() + rRect.height() / 2;
|
|
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;
|
|
return { centerx + x * rcos + y * rsin,
|
|
centery + y * rcos - x * rsin };
|
|
};
|
|
const auto tl = rotated(rRect.left(), rRect.bottom());
|
|
const auto tr = rotated(rRect.right(), rRect.bottom());
|
|
const auto bl = rotated(rRect.left(), rRect.top());
|
|
const auto br = rotated(rRect.right(), rRect.top());
|
|
|
|
const auto slot = _nextVertexSlot++;
|
|
const auto vOffset = slot * kVertexSize;
|
|
const auto uOffset = slot * 256;
|
|
const float coords[] = {
|
|
tl[0], tl[1], texLeft, texTop,
|
|
tr[0], tr[1], texRight, texTop,
|
|
bl[0], bl[1], texLeft, texBottom,
|
|
br[0], br[1], texRight, texBottom,
|
|
};
|
|
_rub->updateDynamicBuffer(
|
|
_vertexBuffer, vOffset, kVertexSize, coords);
|
|
|
|
const auto vw = _viewport.width() * _factor;
|
|
const auto vh = _viewport.height() * _factor;
|
|
ContentUniforms uniforms{};
|
|
uniforms.viewport[0] = vw;
|
|
uniforms.viewport[1] = vh;
|
|
fillShadowUniforms(
|
|
uniforms.shadowTopRect,
|
|
uniforms.shadowBottomSkipOpacityFullFade,
|
|
geometry);
|
|
if (geometry.roundRadius) {
|
|
uniforms.roundRect[0] = rRect.x();
|
|
uniforms.roundRect[1] = rRect.y();
|
|
uniforms.roundRect[2] = rRect.width();
|
|
uniforms.roundRect[3] = rRect.height();
|
|
} else {
|
|
uniforms.roundRect[0] = 0;
|
|
uniforms.roundRect[1] = 0;
|
|
uniforms.roundRect[2] = vw;
|
|
uniforms.roundRect[3] = vh;
|
|
}
|
|
uniforms.roundRadius = geometry.roundRadius * _factor;
|
|
_rub->updateDynamicBuffer(
|
|
_uniformBuffer, uOffset, sizeof(ContentUniforms), &uniforms);
|
|
|
|
const auto blendEnabled = (geometry.roundRadius > 0.);
|
|
auto *srb = _rhi->newShaderResourceBindings();
|
|
if (nv12) {
|
|
srb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer,
|
|
uOffset,
|
|
sizeof(ContentUniforms)),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_yTexture,
|
|
_sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
2,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_uvTexture,
|
|
_sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
3,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_controlsFadeTexture
|
|
? _controlsFadeTexture
|
|
: _placeholderTexture,
|
|
_sampler),
|
|
});
|
|
} else {
|
|
srb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer,
|
|
uOffset,
|
|
sizeof(ContentUniforms)),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
1,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_yTexture,
|
|
_sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
2,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_uTexture,
|
|
_sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
3,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_vTexture,
|
|
_sampler),
|
|
QRhiShaderResourceBinding::sampledTexture(
|
|
4,
|
|
QRhiShaderResourceBinding::FragmentStage,
|
|
_controlsFadeTexture
|
|
? _controlsFadeTexture
|
|
: _placeholderTexture,
|
|
_sampler),
|
|
});
|
|
}
|
|
srb->create();
|
|
_perDrawSrbs.push_back(srb);
|
|
|
|
auto *pipeline = nv12
|
|
? (blendEnabled ? _nv12BlendPipeline : _nv12Pipeline)
|
|
: (blendEnabled ? _yuv420BlendPipeline : _yuv420Pipeline);
|
|
_drawCommands.push_back({
|
|
.pipeline = pipeline,
|
|
.srb = srb,
|
|
.vertexIndex = slot,
|
|
});
|
|
|
|
if (_owner->_recognitionResult.success
|
|
&& !_owner->_recognitionResult.items.empty()) {
|
|
const auto opacity = _owner->_recognitionAnimation.value(
|
|
_owner->_showRecognitionResults ? 1. : 0.);
|
|
if (opacity > 0.) {
|
|
paintRecognitionOverlay(data.image, geometry);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintRecognitionOverlay(
|
|
const QImage &image,
|
|
ContentGeometry geometry) {
|
|
if (!_owner->_recognitionResult.success
|
|
|| _owner->_recognitionResult.items.empty()) {
|
|
return;
|
|
}
|
|
const auto opacity = _owner->_recognitionAnimation.value(
|
|
_owner->_showRecognitionResults ? 1. : 0.);
|
|
if (opacity <= 0.) {
|
|
return;
|
|
}
|
|
const auto rect = geometry.rect;
|
|
const auto overlaySize = rect.size().toSize() * _ifactor;
|
|
if (overlaySize.isEmpty()) {
|
|
return;
|
|
}
|
|
auto overlay = QImage(
|
|
overlaySize,
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
overlay.setDevicePixelRatio(_factor);
|
|
overlay.fill(QColor(0, 0, 0, int(77 * opacity)));
|
|
|
|
const auto scale = rect.width() / float(image.width());
|
|
{
|
|
auto p = QPainter(&overlay);
|
|
p.setCompositionMode(QPainter::CompositionMode_Clear);
|
|
for (const auto &item : _owner->_recognitionResult.items) {
|
|
const auto &r = item.rect;
|
|
p.fillRect(QRectF(
|
|
r.x() * _ifactor * scale,
|
|
r.y() * _ifactor * scale,
|
|
r.width() * _ifactor * scale,
|
|
r.height() * _ifactor * scale), Qt::transparent);
|
|
}
|
|
const auto spans = _owner->_recognition.spans();
|
|
if (!spans.empty()) {
|
|
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
const auto color = QColor(48, 128, 255, int(128 * opacity));
|
|
for (const auto &span : spans) {
|
|
const auto band = _owner->_recognition.bandFor(
|
|
span.item,
|
|
span.from,
|
|
span.till);
|
|
if (!band.isEmpty()) {
|
|
p.fillRect(QRectF(
|
|
band.x() * _ifactor * scale,
|
|
band.y() * _ifactor * scale,
|
|
band.width() * _ifactor * scale,
|
|
band.height() * _ifactor * scale), color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto *tex = acquirePoolTexture(overlaySize);
|
|
_rub->uploadTexture(
|
|
tex,
|
|
QRhiTextureUploadDescription(
|
|
QRhiTextureUploadEntry(0, 0,
|
|
QRhiTextureSubresourceUploadDescription(overlay))));
|
|
|
|
const auto rRect = scaleRect(transformRect(rect), geometry.scale);
|
|
const auto centerx = rRect.x() + rRect.width() / 2;
|
|
const auto centery = rRect.y() + rRect.height() / 2;
|
|
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;
|
|
return { centerx + x * rcos + y * rsin,
|
|
centery + y * rcos - x * rsin };
|
|
};
|
|
const auto tl = rotated(rRect.left(), rRect.bottom());
|
|
const auto tr = rotated(rRect.right(), rRect.bottom());
|
|
const auto bl = rotated(rRect.left(), rRect.top());
|
|
const auto br = rotated(rRect.right(), rRect.top());
|
|
const float coords[] = {
|
|
tl[0], tl[1], 0.f, 0.f,
|
|
tr[0], tr[1], 1.f, 0.f,
|
|
bl[0], bl[1], 0.f, 1.f,
|
|
br[0], br[1], 1.f, 1.f,
|
|
};
|
|
drawTexturedQuad(_imagePipeline, tex, coords, 1.f, true);
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintTransformedStaticContent(
|
|
const QImage &image,
|
|
ContentGeometry geometry,
|
|
bool semiTransparent,
|
|
bool fillTransparentBackground,
|
|
int index) {
|
|
Expects(index >= 0 && index < 3);
|
|
|
|
if (image.isNull() || geometry.rect.isEmpty() || !_imagePipeline) {
|
|
return;
|
|
}
|
|
|
|
const auto cacheKey = image.cacheKey();
|
|
const auto upload = (_cacheKeys[index] != cacheKey);
|
|
if (upload) {
|
|
_cacheKeys[index] = cacheKey;
|
|
if (!_rgbaTextures[index]
|
|
|| _rgbaSizes[index] != image.size()) {
|
|
delete _rgbaTextures[index];
|
|
_rgbaTextures[index] = _rhi->newTexture(
|
|
QRhiTexture::BGRA8,
|
|
image.size());
|
|
_rgbaTextures[index]->create();
|
|
_rgbaSizes[index] = image.size();
|
|
}
|
|
_rub->uploadTexture(
|
|
_rgbaTextures[index],
|
|
QRhiTextureUploadDescription(
|
|
QRhiTextureUploadEntry(0, 0,
|
|
QRhiTextureSubresourceUploadDescription(image))));
|
|
}
|
|
|
|
const auto textureRect = _owner->_stories
|
|
? StoryCropTextureRect(
|
|
QSizeF(image.size()),
|
|
geometry.rect.size())
|
|
: QRectF(0., 0., 1., 1.);
|
|
const auto texLeft = float(textureRect.x());
|
|
const auto texRight = float(textureRect.x() + textureRect.width());
|
|
const auto texTop = float(textureRect.y());
|
|
const auto texBottom = float(textureRect.y() + textureRect.height());
|
|
|
|
const auto rRect = scaleRect(
|
|
transformRect(geometry.rect),
|
|
geometry.scale);
|
|
const auto centerx = rRect.x() + rRect.width() / 2;
|
|
const auto centery = rRect.y() + rRect.height() / 2;
|
|
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;
|
|
return { centerx + x * rcos + y * rsin,
|
|
centery + y * rcos - x * rsin };
|
|
};
|
|
const auto tl = rotated(rRect.left(), rRect.bottom());
|
|
const auto tr = rotated(rRect.right(), rRect.bottom());
|
|
const auto bl = rotated(rRect.left(), rRect.top());
|
|
const auto br = rotated(rRect.right(), rRect.top());
|
|
const float coords[] = {
|
|
tl[0], tl[1], texLeft, texTop,
|
|
tr[0], tr[1], texRight, texTop,
|
|
bl[0], bl[1], texLeft, texBottom,
|
|
br[0], br[1], texRight, texBottom,
|
|
};
|
|
|
|
const auto blend = (geometry.roundRadius > 0.)
|
|
|| (semiTransparent && !fillTransparentBackground);
|
|
drawContentQuad(
|
|
_rgbaTextures[index],
|
|
coords,
|
|
geometry,
|
|
fillTransparentBackground,
|
|
blend);
|
|
paintRecognitionOverlay(image, geometry);
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintRadialLoading(
|
|
QRect inner,
|
|
bool radial,
|
|
float64 radialOpacity) {
|
|
paintUsingRaster(inner, [&](Painter &p) {
|
|
const auto newInner = QRect(QPoint(), inner.size());
|
|
_owner->paintRadialLoadingContent(p, newInner, radial, radialOpacity);
|
|
}, true);
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintThemePreview(QRect outer) {
|
|
paintUsingRaster(outer, [&](Painter &p) {
|
|
const auto newOuter = QRect(QPoint(), outer.size());
|
|
_owner->paintThemePreviewContent(p, newOuter, newOuter);
|
|
});
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintDocumentBubble(
|
|
QRect outer,
|
|
QRect icon) {
|
|
paintUsingRaster(outer, [&](Painter &p) {
|
|
const auto newOuter = QRect(QPoint(), outer.size());
|
|
const auto newIcon = icon.translated(-outer.topLeft());
|
|
_owner->paintDocumentBubbleContent(p, newOuter, newIcon, newOuter);
|
|
}, true);
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintSaveMsg(QRect outer) {
|
|
paintUsingRaster(outer, [&](Painter &p) {
|
|
const auto newOuter = QRect(QPoint(), outer.size());
|
|
_owner->paintSaveMsgContent(p, newOuter, newOuter);
|
|
}, true);
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintChapter(QRect outer) {
|
|
paintUsingRaster(outer, [&](Painter &p) {
|
|
const auto newOuter = QRect(QPoint(), outer.size());
|
|
_owner->paintChapterContent(p, newOuter, newOuter);
|
|
}, true);
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintSpeedBoost(QRect outer) {
|
|
paintUsingRaster(outer, [&](Painter &p) {
|
|
const auto newOuter = QRect(QPoint(), outer.size());
|
|
_owner->paintSpeedBoostContent(p, newOuter, newOuter);
|
|
}, true);
|
|
}
|
|
|
|
auto OverlayWidget::RendererRhi::controlMeta(Over control) const
|
|
-> Control {
|
|
const auto stories = [&] {
|
|
return (_owner->_stories != nullptr);
|
|
};
|
|
switch (control) {
|
|
case Over::Left: return {
|
|
0,
|
|
stories() ? &st::storiesLeft : &st::mediaviewLeft
|
|
};
|
|
case Over::Right: return {
|
|
1,
|
|
stories() ? &st::storiesRight : &st::mediaviewRight
|
|
};
|
|
case Over::Save: return {
|
|
2,
|
|
(_owner->saveControlLocked()
|
|
? &st::mediaviewSaveLocked
|
|
: &st::mediaviewSave)
|
|
};
|
|
case Over::Share: return { 3, &st::mediaviewShare };
|
|
case Over::Rotate: return { 4, &st::mediaviewRotate };
|
|
case Over::More: return { 5, &st::mediaviewMore };
|
|
case Over::Draw: return { 6, &st::mediaviewDraw };
|
|
case Over::Recognize: return { 7, &st::mediaviewRecognize };
|
|
}
|
|
Unexpected("Control value in OverlayWidget::RendererRhi::controlMeta.");
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::validateControls() {
|
|
if (_controlsAtlasTexture) {
|
|
return;
|
|
}
|
|
const auto metas = {
|
|
controlMeta(Over::Left),
|
|
controlMeta(Over::Right),
|
|
controlMeta(Over::Save),
|
|
controlMeta(Over::Share),
|
|
controlMeta(Over::Rotate),
|
|
controlMeta(Over::More),
|
|
controlMeta(Over::Draw),
|
|
controlMeta(Over::Recognize),
|
|
};
|
|
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) * _ifactor,
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
image.fill(Qt::transparent);
|
|
image.setDevicePixelRatio(_ifactor);
|
|
{
|
|
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) * _ifactor,
|
|
meta.icon->size() * _ifactor);
|
|
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) * _ifactor,
|
|
QSize(st::mediaviewIconOver, st::mediaviewIconOver) * _ifactor);
|
|
}
|
|
|
|
const auto size = image.size();
|
|
_controlsAtlasTexture = _rhi->newTexture(QRhiTexture::BGRA8, size);
|
|
_controlsAtlasTexture->create();
|
|
_controlsAtlasSize = size;
|
|
_rub->uploadTexture(
|
|
_controlsAtlasTexture,
|
|
QRhiTextureUploadDescription(
|
|
QRhiTextureUploadEntry(0, 0,
|
|
QRhiTextureSubresourceUploadDescription(image))));
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::invalidateControls() {
|
|
delete _controlsAtlasTexture;
|
|
_controlsAtlasTexture = nullptr;
|
|
ranges::fill(_controlsTextures, QRect());
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintControlsStart() {
|
|
validateControls();
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintControl(
|
|
Over control,
|
|
QRect over,
|
|
float64 overOpacity,
|
|
QRect inner,
|
|
float64 innerOpacity,
|
|
const style::icon &icon) {
|
|
if (!_controlsAtlasTexture) {
|
|
return;
|
|
}
|
|
const auto meta = controlMeta(control);
|
|
Assert(meta.icon == &icon);
|
|
|
|
const auto atlasW = float(_controlsAtlasSize.width());
|
|
const auto atlasH = float(_controlsAtlasSize.height());
|
|
|
|
if (!over.isEmpty() && overOpacity > 0.) {
|
|
const auto overAlpha = float(overOpacity * kOverBackgroundOpacity);
|
|
const auto &overTex = _controlsTextures[kControlsCount];
|
|
const auto overGeometry = transformRect(over);
|
|
// QRect::right()/bottom() return x+width-1 / y+height-1
|
|
// (inclusive), which is one texel short of the atlas sub-rect.
|
|
// Sampling only up to that coordinate stretches the (width-1)
|
|
// texels across the full destination quad, making the rendered
|
|
// icon / hover circle look one pixel too wide and clips its
|
|
// right/bottom antialiased edge.
|
|
const auto tl = overTex.left() / atlasW;
|
|
const auto tr = (overTex.x() + overTex.width()) / atlasW;
|
|
const auto tt = overTex.top() / atlasH;
|
|
const auto tb = (overTex.y() + overTex.height()) / atlasH;
|
|
const float overCoords[] = {
|
|
overGeometry.left(), overGeometry.bottom(), tl, tt,
|
|
overGeometry.right(), overGeometry.bottom(), tr, tt,
|
|
overGeometry.left(), overGeometry.top(), tl, tb,
|
|
overGeometry.right(), overGeometry.top(), tr, tb,
|
|
};
|
|
drawTexturedQuad(
|
|
_imagePipeline,
|
|
_controlsAtlasTexture,
|
|
overCoords,
|
|
overAlpha,
|
|
true);
|
|
}
|
|
|
|
const auto &iconTex = _controlsTextures[meta.index];
|
|
const auto iconGeometry = transformRect(inner);
|
|
const auto tl = iconTex.left() / atlasW;
|
|
const auto tr = (iconTex.x() + iconTex.width()) / atlasW;
|
|
const auto tt = iconTex.top() / atlasH;
|
|
const auto tb = (iconTex.y() + iconTex.height()) / atlasH;
|
|
const float iconCoords[] = {
|
|
iconGeometry.left(), iconGeometry.bottom(), tl, tt,
|
|
iconGeometry.right(), iconGeometry.bottom(), tr, tt,
|
|
iconGeometry.left(), iconGeometry.top(), tl, tb,
|
|
iconGeometry.right(), iconGeometry.top(), tr, tb,
|
|
};
|
|
drawTexturedQuad(
|
|
_imagePipeline,
|
|
_controlsAtlasTexture,
|
|
iconCoords,
|
|
float(innerOpacity),
|
|
true);
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintFooter(
|
|
QRect outer,
|
|
float64 opacity) {
|
|
if (outer.isEmpty() || opacity <= 0.) {
|
|
return;
|
|
}
|
|
paintUsingRaster(outer, [&](Painter &p) {
|
|
const auto newOuter = QRect(QPoint(), outer.size());
|
|
_owner->paintFooterContent(p, newOuter, newOuter, opacity);
|
|
}, true);
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintCaption(
|
|
QRect outer,
|
|
float64 opacity) {
|
|
if (outer.isEmpty() || opacity <= 0.) {
|
|
return;
|
|
}
|
|
paintUsingRaster(outer, [&](Painter &p) {
|
|
const auto newOuter = QRect(QPoint(), outer.size());
|
|
_owner->paintCaptionContent(p, newOuter, newOuter, opacity);
|
|
}, true);
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintGroupThumbs(
|
|
QRect outer,
|
|
float64 opacity) {
|
|
if (outer.isEmpty() || opacity <= 0.) {
|
|
return;
|
|
}
|
|
paintUsingRaster(outer, [&](Painter &p) {
|
|
const auto newOuter = QRect(QPoint(), outer.size());
|
|
_owner->paintGroupThumbsContent(p, newOuter, newOuter, opacity);
|
|
}, true);
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintRoundedCorners(int radius) {
|
|
if (!_roundedCornersPipeline || radius <= 0) {
|
|
return;
|
|
}
|
|
|
|
const auto vw = _viewport.width() * _factor;
|
|
const auto vh = _viewport.height() * _factor;
|
|
|
|
RoundedCornersUniforms uniforms{};
|
|
uniforms.viewport[0] = vw;
|
|
uniforms.viewport[1] = vh;
|
|
const auto roundRect = transformRect(QRect(QPoint(), _viewport));
|
|
uniforms.roundRect[0] = roundRect.x();
|
|
uniforms.roundRect[1] = roundRect.y();
|
|
uniforms.roundRect[2] = roundRect.width();
|
|
uniforms.roundRect[3] = roundRect.height();
|
|
uniforms.roundRadius = radius * _factor;
|
|
|
|
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 auto corners = {
|
|
std::ref(topLeft),
|
|
std::ref(topRight),
|
|
std::ref(bottomRight),
|
|
std::ref(bottomLeft),
|
|
};
|
|
|
|
auto cornerIndex = 0;
|
|
for (const auto &corner : corners) {
|
|
if (_nextVertexSlot >= kMaxDraws) {
|
|
break;
|
|
}
|
|
const auto slot = _nextVertexSlot++;
|
|
const auto uOffset = slot * 256;
|
|
|
|
const float coords[] = {
|
|
corner.get().left(), corner.get().bottom(),
|
|
corner.get().right(), corner.get().bottom(),
|
|
corner.get().left(), corner.get().top(),
|
|
corner.get().right(), corner.get().top(),
|
|
};
|
|
|
|
_rub->updateDynamicBuffer(
|
|
_fillVertexBuffer,
|
|
cornerIndex * int(sizeof(coords)),
|
|
sizeof(coords),
|
|
coords);
|
|
|
|
_rub->updateDynamicBuffer(
|
|
_uniformBuffer,
|
|
uOffset,
|
|
sizeof(RoundedCornersUniforms),
|
|
&uniforms);
|
|
|
|
auto *srb = _rhi->newShaderResourceBindings();
|
|
srb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(
|
|
0,
|
|
QRhiShaderResourceBinding::VertexStage
|
|
| QRhiShaderResourceBinding::FragmentStage,
|
|
_uniformBuffer,
|
|
uOffset,
|
|
sizeof(RoundedCornersUniforms)),
|
|
});
|
|
srb->create();
|
|
_perDrawSrbs.push_back(srb);
|
|
|
|
_drawCommands.push_back({
|
|
.pipeline = _roundedCornersPipeline,
|
|
.srb = srb,
|
|
.vertexIndex = cornerIndex,
|
|
.fillVertex = true,
|
|
});
|
|
++cornerIndex;
|
|
}
|
|
}
|
|
|
|
void OverlayWidget::RendererRhi::paintStoriesSiblingPart(
|
|
int index,
|
|
const QImage &image,
|
|
QRect rect,
|
|
float64 opacity) {
|
|
Expects(index >= 0 && index < kStoriesSiblingPartsCount);
|
|
|
|
if (image.isNull() || rect.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
const auto cacheKey = image.cacheKey();
|
|
if (_storiesSiblingCacheKeys[index] != cacheKey
|
|
|| !_storiesSiblingTextures[index]
|
|
|| _storiesSiblingSizes[index] != image.size()) {
|
|
_storiesSiblingCacheKeys[index] = cacheKey;
|
|
delete _storiesSiblingTextures[index];
|
|
_storiesSiblingTextures[index] = _rhi->newTexture(
|
|
QRhiTexture::BGRA8,
|
|
image.size());
|
|
_storiesSiblingTextures[index]->create();
|
|
_storiesSiblingSizes[index] = image.size();
|
|
_rub->uploadTexture(
|
|
_storiesSiblingTextures[index],
|
|
QRhiTextureUploadDescription(
|
|
QRhiTextureUploadEntry(0, 0,
|
|
QRhiTextureSubresourceUploadDescription(image))));
|
|
}
|
|
|
|
const auto rRect = transformRect(rect);
|
|
const float coords[] = {
|
|
rRect.left(), rRect.bottom(), 0.f, 0.f,
|
|
rRect.right(), rRect.bottom(), 1.f, 0.f,
|
|
rRect.left(), rRect.top(), 0.f, 1.f,
|
|
rRect.right(), rRect.top(), 1.f, 1.f,
|
|
};
|
|
drawTexturedQuad(
|
|
_imagePipeline,
|
|
_storiesSiblingTextures[index],
|
|
coords,
|
|
float(opacity),
|
|
true);
|
|
}
|
|
|
|
Rect OverlayWidget::RendererRhi::transformRect(const Rect &raster) const {
|
|
return TransformRect(raster, _viewport, _factor);
|
|
}
|
|
|
|
Rect OverlayWidget::RendererRhi::transformRect(const QRectF &raster) const {
|
|
return TransformRect(raster, _viewport, _factor);
|
|
}
|
|
|
|
Rect OverlayWidget::RendererRhi::transformRect(const QRect &raster) const {
|
|
return TransformRect(Rect(raster), _viewport, _factor);
|
|
}
|
|
|
|
Rect OverlayWidget::RendererRhi::scaleRect(
|
|
const Rect &unscaled,
|
|
float64 scale) const {
|
|
const auto added = scale - 1.;
|
|
const auto addw = unscaled.width() * added;
|
|
const auto addh = unscaled.height() * added;
|
|
return Rect(
|
|
unscaled.x() - addw / 2,
|
|
unscaled.y() - addh / 2,
|
|
unscaled.width() + addw,
|
|
unscaled.height() + addh);
|
|
}
|
|
|
|
} // namespace Media::View
|
|
|
|
#endif // Qt >= 6.7
|