1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "media/view/media_view_overlay_opengl.h"
9
10 #include "ui/gl/gl_shader.h"
11 #include "media/streaming/media_streaming_common.h"
12 #include "base/platform/base_platform_info.h"
13 #include "core/crash_reports.h"
14 #include "styles/style_media_view.h"
15
16 namespace Media::View {
17 namespace {
18
19 using namespace Ui::GL;
20
21 constexpr auto kRadialLoadingOffset = 4;
22 constexpr auto kThemePreviewOffset = kRadialLoadingOffset + 4;
23 constexpr auto kDocumentBubbleOffset = kThemePreviewOffset + 4;
24 constexpr auto kSaveMsgOffset = kDocumentBubbleOffset + 4;
25 constexpr auto kFooterOffset = kSaveMsgOffset + 4;
26 constexpr auto kCaptionOffset = kFooterOffset + 4;
27 constexpr auto kGroupThumbsOffset = kCaptionOffset + 4;
28 constexpr auto kControlsOffset = kGroupThumbsOffset + 4;
29 constexpr auto kControlValues = 2 * 4 + 4 * 4;
30
FragmentPlaceOnTransparentBackground()31 [[nodiscard]] ShaderPart FragmentPlaceOnTransparentBackground() {
32 return {
33 .header = R"(
34 uniform vec4 transparentBg;
35 uniform vec4 transparentFg;
36 uniform float transparentSize;
37 )",
38 .body = R"(
39 vec2 checkboardLadder = floor(gl_FragCoord.xy / transparentSize);
40 float checkboard = mod(checkboardLadder.x + checkboardLadder.y, 2.0);
41 vec4 checkboardColor = checkboard * transparentBg
42 + (1. - checkboard) * transparentFg;
43 result += checkboardColor * (1. - result.a);
44 )",
45 };
46 }
47
48 } // namespace
49
RendererGL(not_null<OverlayWidget * > owner)50 OverlayWidget::RendererGL::RendererGL(not_null<OverlayWidget*> owner)
51 : _owner(owner) {
52 style::PaletteChanged(
53 ) | rpl::start_with_next([=] {
54 _radialImage.invalidate();
55 _documentBubbleImage.invalidate();
56 _themePreviewImage.invalidate();
57 _saveMsgImage.invalidate();
58 _footerImage.invalidate();
59 _captionImage.invalidate();
60 invalidateControls();
61 }, _lifetime);
62 }
63
init(not_null<QOpenGLWidget * > widget,QOpenGLFunctions & f)64 void OverlayWidget::RendererGL::init(
65 not_null<QOpenGLWidget*> widget,
66 QOpenGLFunctions &f) {
67 constexpr auto kQuads = 8;
68 constexpr auto kQuadVertices = kQuads * 4;
69 constexpr auto kQuadValues = kQuadVertices * 4;
70 constexpr auto kControlsValues = kControlsCount * kControlValues;
71 constexpr auto kValues = kQuadValues + kControlsValues;
72
73 _contentBuffer.emplace();
74 _contentBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw);
75 _contentBuffer->create();
76 _contentBuffer->bind();
77 _contentBuffer->allocate(kValues * sizeof(GLfloat));
78
79 _textures.ensureCreated(f);
80
81 _imageProgram.emplace();
82 _texturedVertexShader = LinkProgram(
83 &*_imageProgram,
84 VertexShader({
85 VertexViewportTransform(),
86 VertexPassTextureCoord(),
87 }),
88 FragmentShader({
89 FragmentSampleARGB32Texture(),
90 })).vertex;
91
92 _withTransparencyProgram.emplace();
93 LinkProgram(
94 &*_withTransparencyProgram,
95 _texturedVertexShader,
96 FragmentShader({
97 FragmentSampleARGB32Texture(),
98 FragmentPlaceOnTransparentBackground(),
99 }));
100
101 _yuv420Program.emplace();
102 LinkProgram(
103 &*_yuv420Program,
104 _texturedVertexShader,
105 FragmentShader({
106 FragmentSampleYUV420Texture(),
107 }));
108
109 _fillProgram.emplace();
110 LinkProgram(
111 &*_fillProgram,
112 VertexShader({ VertexViewportTransform() }),
113 FragmentShader({ FragmentStaticColor() }));
114
115 _controlsProgram.emplace();
116 LinkProgram(
117 &*_controlsProgram,
118 _texturedVertexShader,
119 FragmentShader({
120 FragmentSampleARGB32Texture(),
121 FragmentGlobalOpacity(),
122 }));
123
124 const auto renderer = reinterpret_cast<const char*>(
125 f.glGetString(GL_RENDERER));
126 CrashReports::SetAnnotation(
127 "OpenGL Renderer",
128 renderer ? renderer : "[nullptr]");
129 }
130
deinit(not_null<QOpenGLWidget * > widget,QOpenGLFunctions * f)131 void OverlayWidget::RendererGL::deinit(
132 not_null<QOpenGLWidget*> widget,
133 QOpenGLFunctions *f) {
134 _textures.destroy(f);
135 _imageProgram = std::nullopt;
136 _texturedVertexShader = nullptr;
137 _withTransparencyProgram = std::nullopt;
138 _yuv420Program = std::nullopt;
139 _fillProgram = std::nullopt;
140 _controlsProgram = std::nullopt;
141 _contentBuffer = std::nullopt;
142 }
143
paint(not_null<QOpenGLWidget * > widget,QOpenGLFunctions & f)144 void OverlayWidget::RendererGL::paint(
145 not_null<QOpenGLWidget*> widget,
146 QOpenGLFunctions &f) {
147 if (handleHideWorkaround(f)) {
148 return;
149 }
150 const auto factor = widget->devicePixelRatio();
151 if (_factor != factor) {
152 _factor = factor;
153 _controlsImage.invalidate();
154 }
155 _blendingEnabled = false;
156 _viewport = widget->size();
157 _uniformViewport = QVector2D(
158 _viewport.width() * _factor,
159 _viewport.height() * _factor);
160 _f = &f;
161 _owner->paint(this);
162 _f = nullptr;
163 }
164
clearColor()165 std::optional<QColor> OverlayWidget::RendererGL::clearColor() {
166 if (Platform::IsWindows() && _owner->_hideWorkaround) {
167 return QColor(0, 0, 0, 0);
168 } else if (_owner->_fullScreenVideo) {
169 return st::mediaviewVideoBg->c;
170 } else {
171 return st::mediaviewBg->c;
172 }
173 }
174
handleHideWorkaround(QOpenGLFunctions & f)175 bool OverlayWidget::RendererGL::handleHideWorkaround(QOpenGLFunctions &f) {
176 // This is needed on Windows,
177 // because on reopen it blinks with the last shown content.
178 return Platform::IsWindows() && _owner->_hideWorkaround;
179 }
180
paintBackground()181 void OverlayWidget::RendererGL::paintBackground() {
182 _contentBuffer->bind();
183 }
184
paintTransformedVideoFrame(ContentGeometry geometry)185 void OverlayWidget::RendererGL::paintTransformedVideoFrame(
186 ContentGeometry geometry) {
187 const auto data = _owner->videoFrameWithInfo();
188 if (data.format == Streaming::FrameFormat::None) {
189 return;
190 }
191 if (data.format == Streaming::FrameFormat::ARGB32) {
192 Assert(!data.original.isNull());
193 paintTransformedStaticContent(data.original, geometry, false, false);
194 return;
195 }
196 Assert(data.format == Streaming::FrameFormat::YUV420);
197 Assert(!data.yuv420->size.isEmpty());
198 const auto yuv = data.yuv420;
199 _yuv420Program->bind();
200
201 const auto upload = (_trackFrameIndex != data.index)
202 || (_streamedIndex != _owner->streamedIndex());
203 _trackFrameIndex = data.index;
204 _streamedIndex = _owner->streamedIndex();
205
206 _f->glActiveTexture(GL_TEXTURE0);
207 _textures.bind(*_f, 1);
208 if (upload) {
209 _f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
210 uploadTexture(
211 GL_ALPHA,
212 GL_ALPHA,
213 yuv->size,
214 _lumaSize,
215 yuv->y.stride,
216 yuv->y.data);
217 _lumaSize = yuv->size;
218 }
219 _f->glActiveTexture(GL_TEXTURE1);
220 _textures.bind(*_f, 2);
221 if (upload) {
222 uploadTexture(
223 GL_ALPHA,
224 GL_ALPHA,
225 yuv->chromaSize,
226 _chromaSize,
227 yuv->u.stride,
228 yuv->u.data);
229 }
230 _f->glActiveTexture(GL_TEXTURE2);
231 _textures.bind(*_f, 3);
232 if (upload) {
233 uploadTexture(
234 GL_ALPHA,
235 GL_ALPHA,
236 yuv->chromaSize,
237 _chromaSize,
238 yuv->v.stride,
239 yuv->v.data);
240 _chromaSize = yuv->chromaSize;
241 _f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
242 }
243 _yuv420Program->setUniformValue("y_texture", GLint(0));
244 _yuv420Program->setUniformValue("u_texture", GLint(1));
245 _yuv420Program->setUniformValue("v_texture", GLint(2));
246
247 toggleBlending(false);
248 paintTransformedContent(&*_yuv420Program, geometry);
249 }
250
paintTransformedStaticContent(const QImage & image,ContentGeometry geometry,bool semiTransparent,bool fillTransparentBackground)251 void OverlayWidget::RendererGL::paintTransformedStaticContent(
252 const QImage &image,
253 ContentGeometry geometry,
254 bool semiTransparent,
255 bool fillTransparentBackground) {
256 Expects(image.isNull()
257 || image.format() == QImage::Format_RGB32
258 || image.format() == QImage::Format_ARGB32_Premultiplied);
259
260 if (geometry.rect.isEmpty()) {
261 return;
262 }
263
264 auto &program = fillTransparentBackground
265 ? _withTransparencyProgram
266 : _imageProgram;
267 program->bind();
268 if (fillTransparentBackground) {
269 program->setUniformValue(
270 "transparentBg",
271 st::mediaviewTransparentBg->c);
272 program->setUniformValue(
273 "transparentFg",
274 st::mediaviewTransparentFg->c);
275 program->setUniformValue(
276 "transparentSize",
277 st::transparentPlaceholderSize * _factor);
278 }
279
280 _f->glActiveTexture(GL_TEXTURE0);
281 _textures.bind(*_f, 0);
282 const auto cacheKey = image.isNull() ? qint64(-1) : image.cacheKey();
283 const auto upload = (_cacheKey != cacheKey);
284 if (upload) {
285 _cacheKey = cacheKey;
286 if (image.isNull()) {
287 // Upload transparent 2x2 texture.
288 const auto stride = 2;
289 const uint32_t data[4] = { 0 };
290 uploadTexture(
291 Ui::GL::kFormatRGBA,
292 Ui::GL::kFormatRGBA,
293 QSize(2, 2),
294 _rgbaSize,
295 stride,
296 data);
297 } else {
298 const auto stride = image.bytesPerLine() / 4;
299 const auto data = image.constBits();
300 uploadTexture(
301 Ui::GL::kFormatRGBA,
302 Ui::GL::kFormatRGBA,
303 image.size(),
304 _rgbaSize,
305 stride,
306 data);
307 _rgbaSize = image.size();
308 }
309 }
310 program->setUniformValue("s_texture", GLint(0));
311
312 toggleBlending(semiTransparent && !fillTransparentBackground);
313 paintTransformedContent(&*program, geometry);
314 }
315
paintTransformedContent(not_null<QOpenGLShaderProgram * > program,ContentGeometry geometry)316 void OverlayWidget::RendererGL::paintTransformedContent(
317 not_null<QOpenGLShaderProgram*> program,
318 ContentGeometry geometry) {
319 const auto rect = transformRect(geometry.rect);
320 const auto centerx = rect.x() + rect.width() / 2;
321 const auto centery = rect.y() + rect.height() / 2;
322 const auto rsin = float(std::sin(geometry.rotation * M_PI / 180.));
323 const auto rcos = float(std::cos(geometry.rotation * M_PI / 180.));
324 const auto rotated = [&](float x, float y) -> std::array<float, 2> {
325 x -= centerx;
326 y -= centery;
327 return std::array<float, 2>{
328 centerx + (x * rcos + y * rsin),
329 centery + (y * rcos - x * rsin)
330 };
331 };
332 const auto topleft = rotated(rect.left(), rect.top());
333 const auto topright = rotated(rect.right(), rect.top());
334 const auto bottomright = rotated(rect.right(), rect.bottom());
335 const auto bottomleft = rotated(rect.left(), rect.bottom());
336 const GLfloat coords[] = {
337 topleft[0], topleft[1],
338 0.f, 1.f,
339
340 topright[0], topright[1],
341 1.f, 1.f,
342
343 bottomright[0], bottomright[1],
344 1.f, 0.f,
345
346 bottomleft[0], bottomleft[1],
347 0.f, 0.f,
348 };
349
350 _contentBuffer->write(0, coords, sizeof(coords));
351
352 program->setUniformValue("viewport", _uniformViewport);
353
354 FillTexturedRectangle(*_f, &*program);
355 }
356
uploadTexture(GLint internalformat,GLint format,QSize size,QSize hasSize,int stride,const void * data) const357 void OverlayWidget::RendererGL::uploadTexture(
358 GLint internalformat,
359 GLint format,
360 QSize size,
361 QSize hasSize,
362 int stride,
363 const void *data) const {
364 _f->glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
365 if (hasSize != size) {
366 _f->glTexImage2D(
367 GL_TEXTURE_2D,
368 0,
369 internalformat,
370 size.width(),
371 size.height(),
372 0,
373 format,
374 GL_UNSIGNED_BYTE,
375 data);
376 } else {
377 _f->glTexSubImage2D(
378 GL_TEXTURE_2D,
379 0,
380 0,
381 0,
382 size.width(),
383 size.height(),
384 format,
385 GL_UNSIGNED_BYTE,
386 data);
387 }
388 _f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
389 }
390
paintRadialLoading(QRect inner,bool radial,float64 radialOpacity)391 void OverlayWidget::RendererGL::paintRadialLoading(
392 QRect inner,
393 bool radial,
394 float64 radialOpacity) {
395 paintUsingRaster(_radialImage, inner, [&](Painter &&p) {
396 const auto newInner = QRect(QPoint(), inner.size());
397 _owner->paintRadialLoadingContent(p, newInner, radial, radialOpacity);
398 }, kRadialLoadingOffset, true);
399 }
400
paintThemePreview(QRect outer)401 void OverlayWidget::RendererGL::paintThemePreview(QRect outer) {
402 paintUsingRaster(_themePreviewImage, outer, [&](Painter &&p) {
403 const auto newOuter = QRect(QPoint(), outer.size());
404 _owner->paintThemePreviewContent(p, newOuter, newOuter);
405 }, kThemePreviewOffset);
406 }
407
paintDocumentBubble(QRect outer,QRect icon)408 void OverlayWidget::RendererGL::paintDocumentBubble(
409 QRect outer,
410 QRect icon) {
411 paintUsingRaster(_documentBubbleImage, outer, [&](Painter &&p) {
412 const auto newOuter = QRect(QPoint(), outer.size());
413 const auto newIcon = icon.translated(-outer.topLeft());
414 _owner->paintDocumentBubbleContent(p, newOuter, newIcon, newOuter);
415 }, kDocumentBubbleOffset);
416 _owner->paintRadialLoading(this);
417 }
418
paintSaveMsg(QRect outer)419 void OverlayWidget::RendererGL::paintSaveMsg(QRect outer) {
420 paintUsingRaster(_saveMsgImage, outer, [&](Painter &&p) {
421 const auto newOuter = QRect(QPoint(), outer.size());
422 _owner->paintSaveMsgContent(p, newOuter, newOuter);
423 }, kSaveMsgOffset, true);
424 }
425
paintControlsStart()426 void OverlayWidget::RendererGL::paintControlsStart() {
427 validateControls();
428 _f->glActiveTexture(GL_TEXTURE0);
429 _controlsImage.bind(*_f);
430 toggleBlending(true);
431 }
432
paintControl(OverState control,QRect outer,float64 outerOpacity,QRect inner,float64 innerOpacity,const style::icon & icon)433 void OverlayWidget::RendererGL::paintControl(
434 OverState control,
435 QRect outer,
436 float64 outerOpacity,
437 QRect inner,
438 float64 innerOpacity,
439 const style::icon &icon) {
440 const auto meta = ControlMeta(control);
441 Assert(meta.icon == &icon);
442
443 const auto &bg = st::mediaviewControlBg->c;
444 const auto bgAlpha = int(base::SafeRound(bg.alpha() * outerOpacity));
445 const auto offset = kControlsOffset + (meta.index * kControlValues) / 4;
446 const auto fgOffset = offset + 2;
447 const auto bgRect = transformRect(outer);
448 const auto iconRect = _controlsImage.texturedRect(
449 inner,
450 _controlsTextures[meta.index]);
451 const auto iconGeometry = transformRect(iconRect.geometry);
452 const GLfloat coords[] = {
453 bgRect.left(), bgRect.top(),
454 bgRect.right(), bgRect.top(),
455 bgRect.right(), bgRect.bottom(),
456 bgRect.left(), bgRect.bottom(),
457
458 iconGeometry.left(), iconGeometry.top(),
459 iconRect.texture.left(), iconRect.texture.bottom(),
460
461 iconGeometry.right(), iconGeometry.top(),
462 iconRect.texture.right(), iconRect.texture.bottom(),
463
464 iconGeometry.right(), iconGeometry.bottom(),
465 iconRect.texture.right(), iconRect.texture.top(),
466
467 iconGeometry.left(), iconGeometry.bottom(),
468 iconRect.texture.left(), iconRect.texture.top(),
469 };
470 if (!outer.isEmpty() && bgAlpha > 0) {
471 _contentBuffer->write(
472 offset * 4 * sizeof(GLfloat),
473 coords,
474 sizeof(coords));
475 _fillProgram->bind();
476 _fillProgram->setUniformValue("viewport", _uniformViewport);
477 FillRectangle(
478 *_f,
479 &*_fillProgram,
480 offset,
481 QColor(bg.red(), bg.green(), bg.blue(), bgAlpha));
482 } else {
483 _contentBuffer->write(
484 fgOffset * 4 * sizeof(GLfloat),
485 coords + (fgOffset - offset) * 4,
486 sizeof(coords) - (fgOffset - offset) * 4 * sizeof(GLfloat));
487 }
488 _controlsProgram->bind();
489 _controlsProgram->setUniformValue("g_opacity", GLfloat(innerOpacity));
490 _controlsProgram->setUniformValue("viewport", _uniformViewport);
491 FillTexturedRectangle(*_f, &*_controlsProgram, fgOffset);
492 }
493
ControlMeta(OverState control)494 auto OverlayWidget::RendererGL::ControlMeta(OverState control)
495 -> Control {
496 switch (control) {
497 case OverLeftNav: return { 0, &st::mediaviewLeft };
498 case OverRightNav: return { 1, &st::mediaviewRight };
499 case OverClose: return { 2, &st::mediaviewClose };
500 case OverSave: return { 3, &st::mediaviewSave };
501 case OverRotate: return { 4, &st::mediaviewRotate };
502 case OverMore: return { 5, &st::mediaviewMore };
503 }
504 Unexpected("Control value in OverlayWidget::RendererGL::ControlIndex.");
505 }
506
validateControls()507 void OverlayWidget::RendererGL::validateControls() {
508 if (!_controlsImage.image().isNull()) {
509 return;
510 }
511 const auto metas = {
512 ControlMeta(OverLeftNav),
513 ControlMeta(OverRightNav),
514 ControlMeta(OverClose),
515 ControlMeta(OverSave),
516 ControlMeta(OverRotate),
517 ControlMeta(OverMore),
518 };
519 auto maxWidth = 0;
520 auto fullHeight = 0;
521 for (const auto &meta : metas) {
522 maxWidth = std::max(meta.icon->width(), maxWidth);
523 fullHeight += meta.icon->height();
524 }
525 auto image = QImage(
526 QSize(maxWidth, fullHeight) * _factor,
527 QImage::Format_ARGB32_Premultiplied);
528 image.fill(Qt::transparent);
529 image.setDevicePixelRatio(_factor);
530 {
531 auto p = QPainter(&image);
532 auto index = 0;
533 auto height = 0;
534 for (const auto &meta : metas) {
535 meta.icon->paint(p, 0, height, maxWidth);
536 _controlsTextures[index++] = QRect(
537 QPoint(0, height) * _factor,
538 meta.icon->size() * _factor);
539 height += meta.icon->height();
540 }
541 }
542 _controlsImage.setImage(std::move(image));
543 }
544
invalidateControls()545 void OverlayWidget::RendererGL::invalidateControls() {
546 _controlsImage.invalidate();
547 ranges::fill(_controlsTextures, QRect());
548 }
549
paintFooter(QRect outer,float64 opacity)550 void OverlayWidget::RendererGL::paintFooter(QRect outer, float64 opacity) {
551 paintUsingRaster(_footerImage, outer, [&](Painter &&p) {
552 const auto newOuter = QRect(QPoint(), outer.size());
553 _owner->paintFooterContent(p, newOuter, newOuter, opacity);
554 }, kFooterOffset, true);
555 }
556
paintCaption(QRect outer,float64 opacity)557 void OverlayWidget::RendererGL::paintCaption(QRect outer, float64 opacity) {
558 paintUsingRaster(_captionImage, outer, [&](Painter &&p) {
559 const auto newOuter = QRect(QPoint(), outer.size());
560 _owner->paintCaptionContent(p, newOuter, newOuter, opacity);
561 }, kCaptionOffset, true);
562 }
563
paintGroupThumbs(QRect outer,float64 opacity)564 void OverlayWidget::RendererGL::paintGroupThumbs(
565 QRect outer,
566 float64 opacity) {
567 paintUsingRaster(_groupThumbsImage, outer, [&](Painter &&p) {
568 const auto newOuter = QRect(QPoint(), outer.size());
569 _owner->paintGroupThumbsContent(p, newOuter, newOuter, opacity);
570 }, kGroupThumbsOffset, true);
571 }
572
invalidate()573 void OverlayWidget::RendererGL::invalidate() {
574 _trackFrameIndex = -1;
575 _streamedIndex = -1;
576 const auto images = {
577 &_radialImage,
578 &_documentBubbleImage,
579 &_themePreviewImage,
580 &_saveMsgImage,
581 &_footerImage,
582 &_captionImage,
583 &_groupThumbsImage,
584 &_controlsImage,
585 };
586 for (const auto image : images) {
587 image->setImage(QImage());
588 }
589 invalidateControls();
590 }
591
paintUsingRaster(Ui::GL::Image & image,QRect rect,Fn<void (Painter &&)> method,int bufferOffset,bool transparent)592 void OverlayWidget::RendererGL::paintUsingRaster(
593 Ui::GL::Image &image,
594 QRect rect,
595 Fn<void(Painter&&)> method,
596 int bufferOffset,
597 bool transparent) {
598 auto raster = image.takeImage();
599 const auto size = rect.size() * _factor;
600 if (raster.width() < size.width() || raster.height() < size.height()) {
601 raster = QImage(size, QImage::Format_ARGB32_Premultiplied);
602 raster.setDevicePixelRatio(_factor);
603 if (!transparent
604 && (raster.width() > size.width()
605 || raster.height() > size.height())) {
606 raster.fill(Qt::transparent);
607 }
608 } else if (raster.devicePixelRatio() != _factor) {
609 raster.setDevicePixelRatio(_factor);
610 }
611
612 if (transparent) {
613 raster.fill(Qt::transparent);
614 }
615 method(Painter(&raster));
616
617 _f->glActiveTexture(GL_TEXTURE0);
618
619 image.setImage(std::move(raster), size);
620 image.bind(*_f);
621
622 const auto textured = image.texturedRect(rect, QRect(QPoint(), size));
623 const auto geometry = transformRect(textured.geometry);
624 const GLfloat coords[] = {
625 geometry.left(), geometry.top(),
626 textured.texture.left(), textured.texture.bottom(),
627
628 geometry.right(), geometry.top(),
629 textured.texture.right(), textured.texture.bottom(),
630
631 geometry.right(), geometry.bottom(),
632 textured.texture.right(), textured.texture.top(),
633
634 geometry.left(), geometry.bottom(),
635 textured.texture.left(), textured.texture.top(),
636 };
637 _contentBuffer->write(
638 bufferOffset * 4 * sizeof(GLfloat),
639 coords,
640 sizeof(coords));
641
642 _imageProgram->bind();
643 _imageProgram->setUniformValue("viewport", _uniformViewport);
644 _imageProgram->setUniformValue("s_texture", GLint(0));
645
646 toggleBlending(transparent);
647 FillTexturedRectangle(*_f, &*_imageProgram, bufferOffset);
648 }
649
toggleBlending(bool enabled)650 void OverlayWidget::RendererGL::toggleBlending(bool enabled) {
651 if (_blendingEnabled == enabled) {
652 return;
653 } else if (enabled) {
654 _f->glEnable(GL_BLEND);
655 _f->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
656 } else {
657 _f->glDisable(GL_BLEND);
658 }
659 _blendingEnabled = enabled;
660 }
661
transformRect(const Rect & raster) const662 Rect OverlayWidget::RendererGL::transformRect(const Rect &raster) const {
663 return TransformRect(raster, _viewport, _factor);
664 }
665
transformRect(const QRectF & raster) const666 Rect OverlayWidget::RendererGL::transformRect(const QRectF &raster) const {
667 return TransformRect(raster, _viewport, _factor);
668 }
669
transformRect(const QRect & raster) const670 Rect OverlayWidget::RendererGL::transformRect(const QRect &raster) const {
671 return TransformRect(Rect(raster), _viewport, _factor);
672 }
673
674 } // namespace Media::View
675