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