1042 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1042 lines
		
	
	
	
		
			29 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_opengl.h"
 | 
						|
 | 
						|
#include "ui/gl/gl_shader.h"
 | 
						|
#include "ui/painter.h"
 | 
						|
#include "media/stories/media_stories_view.h"
 | 
						|
#include "media/streaming/media_streaming_common.h"
 | 
						|
#include "platform/platform_overlay_widget.h"
 | 
						|
#include "base/platform/base_platform_info.h"
 | 
						|
#include "core/crash_reports.h"
 | 
						|
#include "styles/style_media_view.h"
 | 
						|
 | 
						|
namespace Media::View {
 | 
						|
namespace {
 | 
						|
 | 
						|
using namespace Ui::GL;
 | 
						|
 | 
						|
constexpr auto kRadialLoadingOffset = 4;
 | 
						|
constexpr auto kThemePreviewOffset = kRadialLoadingOffset + 4;
 | 
						|
constexpr auto kDocumentBubbleOffset = kThemePreviewOffset + 4;
 | 
						|
constexpr auto kSaveMsgOffset = kDocumentBubbleOffset + 4;
 | 
						|
constexpr auto kFooterOffset = kSaveMsgOffset + 4;
 | 
						|
constexpr auto kCaptionOffset = kFooterOffset + 4;
 | 
						|
constexpr auto kGroupThumbsOffset = kCaptionOffset + 4;
 | 
						|
constexpr auto kControlsOffset = kGroupThumbsOffset + 4;
 | 
						|
constexpr auto kControlValues = 4 * 4 + 4 * 4; // over + icon
 | 
						|
 | 
						|
[[nodiscard]] ShaderPart FragmentApplyControlsFade() {
 | 
						|
	return {
 | 
						|
		.header = R"(
 | 
						|
uniform sampler2D f_texture;
 | 
						|
uniform vec4 shadowTopRect;
 | 
						|
uniform vec4 shadowBottomSkipOpacityFullFade;
 | 
						|
)",
 | 
						|
		.body = R"(
 | 
						|
	float topHeight = shadowTopRect.w;
 | 
						|
	float bottomHeight = shadowBottomSkipOpacityFullFade.x;
 | 
						|
	float bottomSkip = shadowBottomSkipOpacityFullFade.y;
 | 
						|
	float opacity = shadowBottomSkipOpacityFullFade.z;
 | 
						|
	float fullFade = shadowBottomSkipOpacityFullFade.w;
 | 
						|
	float viewportHeight = shadowTopRect.y + topHeight;
 | 
						|
	float fullHeight = topHeight + bottomHeight;
 | 
						|
	float topY = min(
 | 
						|
		(viewportHeight - gl_FragCoord.y) / fullHeight,
 | 
						|
		topHeight / fullHeight);
 | 
						|
	float topX = (gl_FragCoord.x - shadowTopRect.x) / shadowTopRect.z;
 | 
						|
	vec4 fadeTop = texture2D(f_texture, vec2(topX, topY)) * opacity;
 | 
						|
	float bottomY = max(bottomSkip + fullHeight - gl_FragCoord.y, topHeight)
 | 
						|
		/ fullHeight;
 | 
						|
	vec4 fadeBottom = texture2D(f_texture, vec2(0.5, bottomY)) * opacity;
 | 
						|
	float fade = min((1. - fadeTop.a) * (1. - fadeBottom.a), fullFade);
 | 
						|
	result.rgb = result.rgb * fade;
 | 
						|
)",
 | 
						|
	};
 | 
						|
}
 | 
						|
 | 
						|
[[nodiscard]] ShaderPart FragmentPlaceOnTransparentBackground() {
 | 
						|
	return {
 | 
						|
		.header = R"(
 | 
						|
uniform vec4 transparentBg;
 | 
						|
uniform vec4 transparentFg;
 | 
						|
uniform float transparentSize;
 | 
						|
)",
 | 
						|
		.body = R"(
 | 
						|
	vec2 checkboardLadder = floor(gl_FragCoord.xy / transparentSize);
 | 
						|
	float checkboard = mod(checkboardLadder.x + checkboardLadder.y, 2.0);
 | 
						|
	vec4 checkboardColor = checkboard * transparentBg
 | 
						|
		+ (1. - checkboard) * transparentFg;
 | 
						|
	result += checkboardColor * (1. - result.a);
 | 
						|
)",
 | 
						|
	};
 | 
						|
}
 | 
						|
 | 
						|
[[nodiscard]] ShaderPart FragmentRoundedCorners() {
 | 
						|
	return {
 | 
						|
		.header = R"(
 | 
						|
uniform vec4 roundRect;
 | 
						|
uniform float roundRadius;
 | 
						|
 | 
						|
float roundedCorner() {
 | 
						|
	vec2 rectHalf = roundRect.zw / 2.;
 | 
						|
	vec2 rectCenter = roundRect.xy + rectHalf;
 | 
						|
	vec2 fromRectCenter = abs(gl_FragCoord.xy - rectCenter);
 | 
						|
	vec2 vectorRadius = vec2(roundRadius + 0.5, roundRadius + 0.5);
 | 
						|
	vec2 fromCenterWithRadius = fromRectCenter + vectorRadius;
 | 
						|
	vec2 fromRoundingCenter = max(fromCenterWithRadius, rectHalf)
 | 
						|
		- rectHalf;
 | 
						|
	float rounded = length(fromRoundingCenter) - roundRadius;
 | 
						|
 | 
						|
	return 1. - smoothstep(0., 1., rounded);
 | 
						|
}
 | 
						|
)",
 | 
						|
		.body = R"(
 | 
						|
	result *= roundedCorner();
 | 
						|
)",
 | 
						|
	};
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
OverlayWidget::RendererGL::RendererGL(not_null<OverlayWidget*> owner)
 | 
						|
: _owner(owner) {
 | 
						|
	style::PaletteChanged(
 | 
						|
	) | rpl::start_with_next([=] {
 | 
						|
		_controlsFadeImage.invalidate();
 | 
						|
		_radialImage.invalidate();
 | 
						|
		_documentBubbleImage.invalidate();
 | 
						|
		_themePreviewImage.invalidate();
 | 
						|
		_saveMsgImage.invalidate();
 | 
						|
		_footerImage.invalidate();
 | 
						|
		_captionImage.invalidate();
 | 
						|
		invalidateControls();
 | 
						|
	}, _lifetime);
 | 
						|
 | 
						|
	crl::on_main(this, [=] {
 | 
						|
		_owner->_storiesChanged.events(
 | 
						|
		) | rpl::start_with_next([=] {
 | 
						|
			invalidateControls();
 | 
						|
		}, _lifetime);
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::init(
 | 
						|
		not_null<QOpenGLWidget*> widget,
 | 
						|
		QOpenGLFunctions &f) {
 | 
						|
	constexpr auto kQuads = 8;
 | 
						|
	constexpr auto kQuadVertices = kQuads * 4;
 | 
						|
	constexpr auto kQuadValues = kQuadVertices * 4;
 | 
						|
	constexpr auto kControlsValues = kControlsCount * kControlValues;
 | 
						|
	constexpr auto kRoundingQuads = 4;
 | 
						|
	constexpr auto kRoundingVertices = kRoundingQuads * 6;
 | 
						|
	constexpr auto kRoundingValues = kRoundingVertices * 2;
 | 
						|
	constexpr auto kStoriesSiblingValues = kStoriesSiblingPartsCount * 16;
 | 
						|
	constexpr auto kValues = kQuadValues
 | 
						|
		+ kControlsValues
 | 
						|
		+ kRoundingValues
 | 
						|
		+ kStoriesSiblingValues;
 | 
						|
 | 
						|
	_contentBuffer.emplace();
 | 
						|
	_contentBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw);
 | 
						|
	_contentBuffer->create();
 | 
						|
	_contentBuffer->bind();
 | 
						|
	_contentBuffer->allocate(kValues * sizeof(GLfloat));
 | 
						|
 | 
						|
	_textures.ensureCreated(f);
 | 
						|
 | 
						|
	_imageProgram.emplace();
 | 
						|
	_texturedVertexShader = LinkProgram(
 | 
						|
		&*_imageProgram,
 | 
						|
		VertexShader({
 | 
						|
			VertexViewportTransform(),
 | 
						|
			VertexPassTextureCoord(),
 | 
						|
		}),
 | 
						|
		FragmentShader({
 | 
						|
			FragmentSampleARGB32Texture(),
 | 
						|
		})).vertex;
 | 
						|
 | 
						|
	_staticContentProgram.emplace();
 | 
						|
	LinkProgram(
 | 
						|
		&*_staticContentProgram,
 | 
						|
		_texturedVertexShader,
 | 
						|
		FragmentShader({
 | 
						|
			FragmentSampleARGB32Texture(),
 | 
						|
			FragmentApplyControlsFade(),
 | 
						|
			FragmentRoundedCorners()
 | 
						|
		}));
 | 
						|
 | 
						|
	_withTransparencyProgram.emplace();
 | 
						|
	LinkProgram(
 | 
						|
		&*_withTransparencyProgram,
 | 
						|
		_texturedVertexShader,
 | 
						|
		FragmentShader({
 | 
						|
			FragmentSampleARGB32Texture(),
 | 
						|
			FragmentPlaceOnTransparentBackground(),
 | 
						|
			FragmentApplyControlsFade()
 | 
						|
		}));
 | 
						|
 | 
						|
	_yuv420Program.emplace();
 | 
						|
	LinkProgram(
 | 
						|
		&*_yuv420Program,
 | 
						|
		_texturedVertexShader,
 | 
						|
		FragmentShader({
 | 
						|
			FragmentSampleYUV420Texture(),
 | 
						|
			FragmentApplyControlsFade(),
 | 
						|
			FragmentRoundedCorners()
 | 
						|
		}));
 | 
						|
 | 
						|
	_nv12Program.emplace();
 | 
						|
	LinkProgram(
 | 
						|
		&*_nv12Program,
 | 
						|
		_texturedVertexShader,
 | 
						|
		FragmentShader({
 | 
						|
			FragmentSampleNV12Texture(),
 | 
						|
			FragmentApplyControlsFade(),
 | 
						|
			FragmentRoundedCorners()
 | 
						|
		}));
 | 
						|
 | 
						|
	_fillProgram.emplace();
 | 
						|
	LinkProgram(
 | 
						|
		&*_fillProgram,
 | 
						|
		VertexShader({ VertexViewportTransform() }),
 | 
						|
		FragmentShader({ FragmentStaticColor() }));
 | 
						|
 | 
						|
	_controlsProgram.emplace();
 | 
						|
	LinkProgram(
 | 
						|
		&*_controlsProgram,
 | 
						|
		_texturedVertexShader,
 | 
						|
		FragmentShader({
 | 
						|
			FragmentSampleARGB32Texture(),
 | 
						|
			FragmentGlobalOpacity(),
 | 
						|
		}));
 | 
						|
 | 
						|
	_roundedCornersProgram.emplace();
 | 
						|
	LinkProgram(
 | 
						|
		&*_roundedCornersProgram,
 | 
						|
		VertexShader({ VertexViewportTransform() }),
 | 
						|
		FragmentShader({
 | 
						|
			{ .body = "result = vec4(1.);" },
 | 
						|
			FragmentRoundedCorners(),
 | 
						|
		}));
 | 
						|
 | 
						|
	const auto renderer = reinterpret_cast<const char*>(
 | 
						|
		f.glGetString(GL_RENDERER));
 | 
						|
	CrashReports::SetAnnotation(
 | 
						|
		"OpenGL Renderer",
 | 
						|
		renderer ? renderer : "[nullptr]");
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::deinit(
 | 
						|
		not_null<QOpenGLWidget*> widget,
 | 
						|
		QOpenGLFunctions *f) {
 | 
						|
	_textures.destroy(f);
 | 
						|
	_imageProgram = std::nullopt;
 | 
						|
	_texturedVertexShader = nullptr;
 | 
						|
	_withTransparencyProgram = std::nullopt;
 | 
						|
	_yuv420Program = std::nullopt;
 | 
						|
	_nv12Program = std::nullopt;
 | 
						|
	_fillProgram = std::nullopt;
 | 
						|
	_controlsProgram = std::nullopt;
 | 
						|
	_contentBuffer = std::nullopt;
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paint(
 | 
						|
		not_null<QOpenGLWidget*> widget,
 | 
						|
		QOpenGLFunctions &f) {
 | 
						|
	if (handleHideWorkaround(f)) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto factor = widget->devicePixelRatio();
 | 
						|
	if (_factor != factor) {
 | 
						|
		_factor = factor;
 | 
						|
		_controlsImage.invalidate();
 | 
						|
 | 
						|
		// We use the fact that fade texture atlas
 | 
						|
		// takes exactly full texture size. In case we
 | 
						|
		// just invalidate it we may get larger image
 | 
						|
		// in case of moving from greater _factor to lesser.
 | 
						|
		_controlsFadeImage.destroy(&f);
 | 
						|
	}
 | 
						|
	_blendingEnabled = false;
 | 
						|
	_viewport = widget->size();
 | 
						|
	_uniformViewport = QVector2D(
 | 
						|
		_viewport.width() * _factor,
 | 
						|
		_viewport.height() * _factor);
 | 
						|
	_f = &f;
 | 
						|
	_owner->paint(this);
 | 
						|
	_f = nullptr;
 | 
						|
}
 | 
						|
 | 
						|
std::optional<QColor> OverlayWidget::RendererGL::clearColor() {
 | 
						|
	if (Platform::IsWindows() && _owner->_hideWorkaround) {
 | 
						|
		return QColor(0, 0, 0, 0);
 | 
						|
	} else if (_owner->_fullScreenVideo) {
 | 
						|
		return st::mediaviewVideoBg->c;
 | 
						|
	} else {
 | 
						|
		return st::mediaviewBg->c;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool OverlayWidget::RendererGL::handleHideWorkaround(QOpenGLFunctions &f) {
 | 
						|
	// This is needed on Windows,
 | 
						|
	// because on reopen it blinks with the last shown content.
 | 
						|
	return Platform::IsWindows() && _owner->_hideWorkaround;
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintBackground() {
 | 
						|
	_contentBuffer->bind();
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::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;
 | 
						|
	}
 | 
						|
	Assert(!data.yuv->size.isEmpty());
 | 
						|
	const auto program = (data.format == Streaming::FrameFormat::NV12)
 | 
						|
		? &*_nv12Program
 | 
						|
		: &*_yuv420Program;
 | 
						|
	program->bind();
 | 
						|
	const auto nv12 = (data.format == Streaming::FrameFormat::NV12);
 | 
						|
	const auto yuv = data.yuv;
 | 
						|
	const auto nv12changed = (_chromaNV12 != nv12);
 | 
						|
 | 
						|
	const auto upload = (_trackFrameIndex != data.index)
 | 
						|
		|| (_streamedIndex != _owner->streamedIndex());
 | 
						|
	_trackFrameIndex = data.index;
 | 
						|
	_streamedIndex = _owner->streamedIndex();
 | 
						|
 | 
						|
	_f->glActiveTexture(GL_TEXTURE0);
 | 
						|
	_textures.bind(*_f, 3);
 | 
						|
	if (upload) {
 | 
						|
		_f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 | 
						|
		uploadTexture(
 | 
						|
			GL_ALPHA,
 | 
						|
			GL_ALPHA,
 | 
						|
			yuv->size,
 | 
						|
			_lumaSize,
 | 
						|
			yuv->y.stride,
 | 
						|
			yuv->y.data);
 | 
						|
		_lumaSize = yuv->size;
 | 
						|
	}
 | 
						|
	_f->glActiveTexture(GL_TEXTURE1);
 | 
						|
	_textures.bind(*_f, 4);
 | 
						|
	if (upload) {
 | 
						|
		uploadTexture(
 | 
						|
			nv12 ? GL_RG : GL_ALPHA,
 | 
						|
			nv12 ? GL_RG : GL_ALPHA,
 | 
						|
			yuv->chromaSize,
 | 
						|
			nv12changed ? QSize() : _chromaSize,
 | 
						|
			yuv->u.stride / (nv12 ? 2 : 1),
 | 
						|
			yuv->u.data);
 | 
						|
		if (nv12) {
 | 
						|
			_chromaSize = yuv->chromaSize;
 | 
						|
			_f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
 | 
						|
		}
 | 
						|
		_chromaNV12 = nv12;
 | 
						|
	}
 | 
						|
 | 
						|
	validateControlsFade();
 | 
						|
	if (nv12) {
 | 
						|
		_f->glActiveTexture(GL_TEXTURE2);
 | 
						|
		_controlsFadeImage.bind(*_f);
 | 
						|
	} else {
 | 
						|
		_f->glActiveTexture(GL_TEXTURE2);
 | 
						|
		_textures.bind(*_f, 5);
 | 
						|
		if (upload) {
 | 
						|
			uploadTexture(
 | 
						|
				GL_ALPHA,
 | 
						|
				GL_ALPHA,
 | 
						|
				yuv->chromaSize,
 | 
						|
				_chromaSize,
 | 
						|
				yuv->v.stride,
 | 
						|
				yuv->v.data);
 | 
						|
			_chromaSize = yuv->chromaSize;
 | 
						|
			_f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
 | 
						|
		}
 | 
						|
 | 
						|
		_f->glActiveTexture(GL_TEXTURE3);
 | 
						|
		_controlsFadeImage.bind(*_f);
 | 
						|
	}
 | 
						|
	program->setUniformValue("y_texture", GLint(0));
 | 
						|
	if (nv12) {
 | 
						|
		program->setUniformValue("uv_texture", GLint(1));
 | 
						|
	} else {
 | 
						|
		program->setUniformValue("u_texture", GLint(1));
 | 
						|
		program->setUniformValue("v_texture", GLint(2));
 | 
						|
	}
 | 
						|
	program->setUniformValue("f_texture", GLint(nv12 ? 2 : 3));
 | 
						|
 | 
						|
	toggleBlending(geometry.roundRadius > 0.);
 | 
						|
	paintTransformedContent(program, geometry, false);
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintTransformedStaticContent(
 | 
						|
		const QImage &image,
 | 
						|
		ContentGeometry geometry,
 | 
						|
		bool semiTransparent,
 | 
						|
		bool fillTransparentBackground,
 | 
						|
		int index) {
 | 
						|
	Expects(index >= 0 && index < 3);
 | 
						|
	Expects(image.isNull()
 | 
						|
		|| image.format() == QImage::Format_RGB32
 | 
						|
		|| image.format() == QImage::Format_ARGB32_Premultiplied);
 | 
						|
 | 
						|
	if (geometry.rect.isEmpty()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	auto &program = fillTransparentBackground
 | 
						|
		? _withTransparencyProgram
 | 
						|
		: _staticContentProgram;
 | 
						|
	program->bind();
 | 
						|
	if (fillTransparentBackground) {
 | 
						|
		program->setUniformValue(
 | 
						|
			"transparentBg",
 | 
						|
			st::mediaviewTransparentBg->c);
 | 
						|
		program->setUniformValue(
 | 
						|
			"transparentFg",
 | 
						|
			st::mediaviewTransparentFg->c);
 | 
						|
		program->setUniformValue(
 | 
						|
			"transparentSize",
 | 
						|
			st::transparentPlaceholderSize * _factor);
 | 
						|
	}
 | 
						|
 | 
						|
	_f->glActiveTexture(GL_TEXTURE0);
 | 
						|
	_textures.bind(*_f, index);
 | 
						|
	const auto cacheKey = image.isNull() ? qint64(-1) : image.cacheKey();
 | 
						|
	const auto upload = (_cacheKeys[index] != cacheKey);
 | 
						|
	if (upload) {
 | 
						|
		_cacheKeys[index] = cacheKey;
 | 
						|
		if (image.isNull()) {
 | 
						|
			// Upload transparent 2x2 texture.
 | 
						|
			const auto stride = 2;
 | 
						|
			const uint32_t data[4] = { 0 };
 | 
						|
			uploadTexture(
 | 
						|
				Ui::GL::kFormatRGBA,
 | 
						|
				Ui::GL::kFormatRGBA,
 | 
						|
				QSize(2, 2),
 | 
						|
				_rgbaSize[index],
 | 
						|
				stride,
 | 
						|
				data);
 | 
						|
			_rgbaSize[index] = QSize(2, 2);
 | 
						|
		} else {
 | 
						|
			const auto stride = image.bytesPerLine() / 4;
 | 
						|
			const auto data = image.constBits();
 | 
						|
			uploadTexture(
 | 
						|
				Ui::GL::kFormatRGBA,
 | 
						|
				Ui::GL::kFormatRGBA,
 | 
						|
				image.size(),
 | 
						|
				_rgbaSize[index],
 | 
						|
				stride,
 | 
						|
				data);
 | 
						|
			_rgbaSize[index] = image.size();
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	validateControlsFade();
 | 
						|
	_f->glActiveTexture(GL_TEXTURE1);
 | 
						|
	_controlsFadeImage.bind(*_f);
 | 
						|
 | 
						|
	program->setUniformValue("s_texture", GLint(0));
 | 
						|
	program->setUniformValue("f_texture", GLint(1));
 | 
						|
 | 
						|
	toggleBlending((geometry.roundRadius > 0.)
 | 
						|
		|| (semiTransparent && !fillTransparentBackground));
 | 
						|
	paintTransformedContent(&*program, geometry, fillTransparentBackground);
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintTransformedContent(
 | 
						|
		not_null<QOpenGLShaderProgram*> program,
 | 
						|
		ContentGeometry geometry,
 | 
						|
		bool fillTransparentBackground) {
 | 
						|
	const auto rect = transformRect(geometry.rect);
 | 
						|
	const auto centerx = rect.x() + rect.width() / 2;
 | 
						|
	const auto centery = rect.y() + rect.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 std::array<float, 2>{
 | 
						|
			centerx + (x * rcos + y * rsin),
 | 
						|
			centery + (y * rcos - x * rsin)
 | 
						|
		};
 | 
						|
	};
 | 
						|
	const auto topleft = rotated(rect.left(), rect.top());
 | 
						|
	const auto topright = rotated(rect.right(), rect.top());
 | 
						|
	const auto bottomright = rotated(rect.right(), rect.bottom());
 | 
						|
	const auto bottomleft = rotated(rect.left(), rect.bottom());
 | 
						|
	const GLfloat coords[] = {
 | 
						|
		topleft[0], topleft[1],
 | 
						|
		0.f, 1.f,
 | 
						|
 | 
						|
		topright[0], topright[1],
 | 
						|
		1.f, 1.f,
 | 
						|
 | 
						|
		bottomright[0], bottomright[1],
 | 
						|
		1.f, 0.f,
 | 
						|
 | 
						|
		bottomleft[0], bottomleft[1],
 | 
						|
		0.f, 0.f,
 | 
						|
	};
 | 
						|
 | 
						|
	_contentBuffer->write(0, coords, sizeof(coords));
 | 
						|
 | 
						|
	program->setUniformValue("viewport", _uniformViewport);
 | 
						|
	if (_owner->_stories) {
 | 
						|
		const auto &top = st::storiesShadowTop.size();
 | 
						|
		const auto shadowTop = geometry.topShadowShown
 | 
						|
			? geometry.rect.y()
 | 
						|
			: geometry.rect.y() - top.height();
 | 
						|
		program->setUniformValue(
 | 
						|
			"shadowTopRect",
 | 
						|
			Uniform(transformRect(
 | 
						|
				QRect(QPoint(geometry.rect.x(), shadowTop), top))));
 | 
						|
	} else {
 | 
						|
		const auto &top = st::mediaviewShadowTop.size();
 | 
						|
		const auto point = QPoint(
 | 
						|
			_shadowTopFlip ? 0 : (_viewport.width() - top.width()),
 | 
						|
			0);
 | 
						|
		program->setUniformValue(
 | 
						|
			"shadowTopRect",
 | 
						|
			Uniform(transformRect(QRect(point, top))));
 | 
						|
	}
 | 
						|
	const auto &bottom = _owner->_stories
 | 
						|
		? st::storiesShadowBottom
 | 
						|
		: st::mediaviewShadowBottom;
 | 
						|
	program->setUniformValue("shadowBottomSkipOpacityFullFade", QVector4D(
 | 
						|
		bottom.height() * _factor,
 | 
						|
		geometry.bottomShadowSkip * _factor,
 | 
						|
		geometry.controlsOpacity,
 | 
						|
		1.f - float(geometry.fade)));
 | 
						|
	if (!fillTransparentBackground) {
 | 
						|
		program->setUniformValue(
 | 
						|
			"roundRect",
 | 
						|
			geometry.roundRadius ? Uniform(rect) : QVector4D(
 | 
						|
				0,
 | 
						|
				0,
 | 
						|
				_uniformViewport.x(),
 | 
						|
				_uniformViewport.y()));
 | 
						|
		program->setUniformValue(
 | 
						|
			"roundRadius",
 | 
						|
			GLfloat(geometry.roundRadius * _factor));
 | 
						|
	}
 | 
						|
	FillTexturedRectangle(*_f, &*program);
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::uploadTexture(
 | 
						|
		GLint internalformat,
 | 
						|
		GLint format,
 | 
						|
		QSize size,
 | 
						|
		QSize hasSize,
 | 
						|
		int stride,
 | 
						|
		const void *data) const {
 | 
						|
	_f->glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
 | 
						|
	if (hasSize != size) {
 | 
						|
		_f->glTexImage2D(
 | 
						|
			GL_TEXTURE_2D,
 | 
						|
			0,
 | 
						|
			internalformat,
 | 
						|
			size.width(),
 | 
						|
			size.height(),
 | 
						|
			0,
 | 
						|
			format,
 | 
						|
			GL_UNSIGNED_BYTE,
 | 
						|
			data);
 | 
						|
	} else {
 | 
						|
		_f->glTexSubImage2D(
 | 
						|
			GL_TEXTURE_2D,
 | 
						|
			0,
 | 
						|
			0,
 | 
						|
			0,
 | 
						|
			size.width(),
 | 
						|
			size.height(),
 | 
						|
			format,
 | 
						|
			GL_UNSIGNED_BYTE,
 | 
						|
			data);
 | 
						|
	}
 | 
						|
	_f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintRadialLoading(
 | 
						|
		QRect inner,
 | 
						|
		bool radial,
 | 
						|
		float64 radialOpacity) {
 | 
						|
	paintUsingRaster(_radialImage, inner, [&](Painter &&p) {
 | 
						|
		const auto newInner = QRect(QPoint(), inner.size());
 | 
						|
		_owner->paintRadialLoadingContent(p, newInner, radial, radialOpacity);
 | 
						|
	}, kRadialLoadingOffset, true);
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintThemePreview(QRect outer) {
 | 
						|
	paintUsingRaster(_themePreviewImage, outer, [&](Painter &&p) {
 | 
						|
		p.translate(-outer.topLeft());
 | 
						|
		_owner->paintThemePreviewContent(p, outer, outer);
 | 
						|
	}, kThemePreviewOffset);
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintDocumentBubble(
 | 
						|
		QRect outer,
 | 
						|
		QRect icon) {
 | 
						|
	paintUsingRaster(_documentBubbleImage, outer, [&](Painter &&p) {
 | 
						|
		const auto newOuter = QRect(QPoint(), outer.size());
 | 
						|
		const auto newIcon = icon.translated(-outer.topLeft());
 | 
						|
		_owner->paintDocumentBubbleContent(p, newOuter, newIcon, newOuter);
 | 
						|
	}, kDocumentBubbleOffset);
 | 
						|
	_owner->paintRadialLoading(this);
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintSaveMsg(QRect outer) {
 | 
						|
	paintUsingRaster(_saveMsgImage, outer, [&](Painter &&p) {
 | 
						|
		const auto newOuter = QRect(QPoint(), outer.size());
 | 
						|
		_owner->paintSaveMsgContent(p, newOuter, newOuter);
 | 
						|
	}, kSaveMsgOffset, true);
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintControlsStart() {
 | 
						|
	validateControls();
 | 
						|
	_f->glActiveTexture(GL_TEXTURE0);
 | 
						|
	_controlsImage.bind(*_f);
 | 
						|
	toggleBlending(true);
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintControl(
 | 
						|
		Over control,
 | 
						|
		QRect over,
 | 
						|
		float64 overOpacity,
 | 
						|
		QRect inner,
 | 
						|
		float64 innerOpacity,
 | 
						|
		const style::icon &icon) {
 | 
						|
	const auto stories = (_owner->_stories != nullptr);
 | 
						|
	const auto meta = ControlMeta(control, stories);
 | 
						|
	Assert(meta.icon == &icon);
 | 
						|
 | 
						|
	const auto overAlpha = overOpacity * kOverBackgroundOpacity;
 | 
						|
	const auto offset = kControlsOffset + (meta.index * kControlValues) / 4;
 | 
						|
	const auto fgOffset = offset + 4;
 | 
						|
	const auto overRect = _controlsImage.texturedRect(
 | 
						|
		over,
 | 
						|
		_controlsTextures[kControlsCount]);
 | 
						|
	const auto overGeometry = transformRect(over);
 | 
						|
	const auto iconRect = _controlsImage.texturedRect(
 | 
						|
		inner,
 | 
						|
		_controlsTextures[meta.index]);
 | 
						|
	const auto iconGeometry = transformRect(iconRect.geometry);
 | 
						|
	const GLfloat coords[] = {
 | 
						|
		overGeometry.left(), overGeometry.top(),
 | 
						|
		overRect.texture.left(), overRect.texture.bottom(),
 | 
						|
 | 
						|
		overGeometry.right(), overGeometry.top(),
 | 
						|
		overRect.texture.right(), overRect.texture.bottom(),
 | 
						|
 | 
						|
		overGeometry.right(), overGeometry.bottom(),
 | 
						|
		overRect.texture.right(), overRect.texture.top(),
 | 
						|
 | 
						|
		overGeometry.left(), overGeometry.bottom(),
 | 
						|
		overRect.texture.left(), overRect.texture.top(),
 | 
						|
 | 
						|
		iconGeometry.left(), iconGeometry.top(),
 | 
						|
		iconRect.texture.left(), iconRect.texture.bottom(),
 | 
						|
 | 
						|
		iconGeometry.right(), iconGeometry.top(),
 | 
						|
		iconRect.texture.right(), iconRect.texture.bottom(),
 | 
						|
 | 
						|
		iconGeometry.right(), iconGeometry.bottom(),
 | 
						|
		iconRect.texture.right(), iconRect.texture.top(),
 | 
						|
 | 
						|
		iconGeometry.left(), iconGeometry.bottom(),
 | 
						|
		iconRect.texture.left(), iconRect.texture.top(),
 | 
						|
	};
 | 
						|
	_controlsProgram->bind();
 | 
						|
	_controlsProgram->setUniformValue("viewport", _uniformViewport);
 | 
						|
	if (!over.isEmpty() && overOpacity > 0) {
 | 
						|
		_contentBuffer->write(
 | 
						|
			offset * 4 * sizeof(GLfloat),
 | 
						|
			coords,
 | 
						|
			sizeof(coords));
 | 
						|
		_controlsProgram->setUniformValue("g_opacity", GLfloat(overAlpha));
 | 
						|
		FillTexturedRectangle(*_f, &*_controlsProgram, offset);
 | 
						|
	} else {
 | 
						|
		_contentBuffer->write(
 | 
						|
			fgOffset * 4 * sizeof(GLfloat),
 | 
						|
			coords + (fgOffset - offset) * 4,
 | 
						|
			sizeof(coords) - (fgOffset - offset) * 4 * sizeof(GLfloat));
 | 
						|
	}
 | 
						|
	_controlsProgram->setUniformValue("g_opacity", GLfloat(innerOpacity));
 | 
						|
	FillTexturedRectangle(*_f, &*_controlsProgram, fgOffset);
 | 
						|
}
 | 
						|
 | 
						|
auto OverlayWidget::RendererGL::ControlMeta(Over control, bool stories)
 | 
						|
-> Control {
 | 
						|
	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, &st::mediaviewSave };
 | 
						|
	case Over::Share: return { 3, &st::mediaviewShare };
 | 
						|
	case Over::Rotate: return { 4, &st::mediaviewRotate };
 | 
						|
	case Over::More: return { 5, &st::mediaviewMore };
 | 
						|
	}
 | 
						|
	Unexpected("Control value in OverlayWidget::RendererGL::ControlIndex.");
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::validateControls() {
 | 
						|
	if (!_controlsImage.image().isNull()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto stories = (_owner->_stories != nullptr);
 | 
						|
	const auto metas = {
 | 
						|
		ControlMeta(Over::Left, stories),
 | 
						|
		ControlMeta(Over::Right, stories),
 | 
						|
		ControlMeta(Over::Save, stories),
 | 
						|
		ControlMeta(Over::Share, stories),
 | 
						|
		ControlMeta(Over::Rotate, stories),
 | 
						|
		ControlMeta(Over::More, stories),
 | 
						|
	};
 | 
						|
	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) * _factor,
 | 
						|
		QImage::Format_ARGB32_Premultiplied);
 | 
						|
	image.fill(Qt::transparent);
 | 
						|
	image.setDevicePixelRatio(_factor);
 | 
						|
	{
 | 
						|
		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) * _factor,
 | 
						|
				meta.icon->size() * _factor);
 | 
						|
			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) * _factor,
 | 
						|
			QSize(st::mediaviewIconOver, st::mediaviewIconOver) * _factor);
 | 
						|
		height += st::mediaviewIconOver;
 | 
						|
	}
 | 
						|
	_controlsImage.setImage(std::move(image));
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::invalidateControls() {
 | 
						|
	_controlsImage.invalidate();
 | 
						|
	ranges::fill(_controlsTextures, QRect());
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::validateControlsFade() {
 | 
						|
	const auto forStories = (_owner->_stories != nullptr);
 | 
						|
	const auto flip = !forStories && !_owner->topShadowOnTheRight();
 | 
						|
	if (!_controlsFadeImage.image().isNull()
 | 
						|
		&& _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) * _factor,
 | 
						|
		QImage::Format_ARGB32_Premultiplied);
 | 
						|
	image.fill(Qt::transparent);
 | 
						|
	image.setDevicePixelRatio(_factor);
 | 
						|
 | 
						|
	auto p = QPainter(&image);
 | 
						|
	top.paint(p, 0, 0, width);
 | 
						|
	bottom.fill(
 | 
						|
		p,
 | 
						|
		QRect(0, bottomTop, width, bottom.height()));
 | 
						|
	p.end();
 | 
						|
 | 
						|
	if (flip) {
 | 
						|
		image = std::move(image).mirrored(true, false);
 | 
						|
	}
 | 
						|
 | 
						|
	_controlsFadeImage.setImage(std::move(image));
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintFooter(QRect outer, float64 opacity) {
 | 
						|
	paintUsingRaster(_footerImage, outer, [&](Painter &&p) {
 | 
						|
		const auto newOuter = QRect(QPoint(), outer.size());
 | 
						|
		_owner->paintFooterContent(p, newOuter, newOuter, opacity);
 | 
						|
	}, kFooterOffset, true);
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintCaption(QRect outer, float64 opacity) {
 | 
						|
	paintUsingRaster(_captionImage, outer, [&](Painter &&p) {
 | 
						|
		const auto newOuter = QRect(QPoint(), outer.size());
 | 
						|
		_owner->paintCaptionContent(p, newOuter, newOuter, opacity);
 | 
						|
	}, kCaptionOffset, true);
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintGroupThumbs(
 | 
						|
		QRect outer,
 | 
						|
		float64 opacity) {
 | 
						|
	paintUsingRaster(_groupThumbsImage, outer, [&](Painter &&p) {
 | 
						|
		const auto newOuter = QRect(QPoint(), outer.size());
 | 
						|
		_owner->paintGroupThumbsContent(p, newOuter, newOuter, opacity);
 | 
						|
	}, kGroupThumbsOffset, true);
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintRoundedCorners(int radius) {
 | 
						|
	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 GLfloat coords[] = {
 | 
						|
		topLeft.left(), topLeft.top(),
 | 
						|
		topLeft.right(), topLeft.top(),
 | 
						|
		topLeft.right(), topLeft.bottom(),
 | 
						|
		topLeft.right(), topLeft.bottom(),
 | 
						|
		topLeft.left(), topLeft.bottom(),
 | 
						|
		topLeft.left(), topLeft.top(),
 | 
						|
 | 
						|
		topRight.left(), topRight.top(),
 | 
						|
		topRight.right(), topRight.top(),
 | 
						|
		topRight.right(), topRight.bottom(),
 | 
						|
		topRight.right(), topRight.bottom(),
 | 
						|
		topRight.left(), topRight.bottom(),
 | 
						|
		topRight.left(), topRight.top(),
 | 
						|
 | 
						|
		bottomRight.left(), bottomRight.top(),
 | 
						|
		bottomRight.right(), bottomRight.top(),
 | 
						|
		bottomRight.right(), bottomRight.bottom(),
 | 
						|
		bottomRight.right(), bottomRight.bottom(),
 | 
						|
		bottomRight.left(), bottomRight.bottom(),
 | 
						|
		bottomRight.left(), bottomRight.top(),
 | 
						|
 | 
						|
		bottomLeft.left(), bottomLeft.top(),
 | 
						|
		bottomLeft.right(), bottomLeft.top(),
 | 
						|
		bottomLeft.right(), bottomLeft.bottom(),
 | 
						|
		bottomLeft.right(), bottomLeft.bottom(),
 | 
						|
		bottomLeft.left(), bottomLeft.bottom(),
 | 
						|
		bottomLeft.left(), bottomLeft.top(),
 | 
						|
	};
 | 
						|
	const auto offset = kControlsOffset
 | 
						|
		+ (kControlsCount * kControlValues) / 4;
 | 
						|
	const auto byteOffset = offset * 4 * sizeof(GLfloat);
 | 
						|
	_contentBuffer->write(byteOffset, coords, sizeof(coords));
 | 
						|
	_roundedCornersProgram->bind();
 | 
						|
	_roundedCornersProgram->setUniformValue("viewport", _uniformViewport);
 | 
						|
	const auto roundRect = transformRect(QRect(QPoint(), _viewport));
 | 
						|
	_roundedCornersProgram->setUniformValue("roundRect", Uniform(roundRect));
 | 
						|
	_roundedCornersProgram->setUniformValue(
 | 
						|
		"roundRadius",
 | 
						|
		GLfloat(radius * _factor));
 | 
						|
 | 
						|
	_f->glEnable(GL_BLEND);
 | 
						|
	_f->glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
 | 
						|
 | 
						|
	GLint position = _roundedCornersProgram->attributeLocation("position");
 | 
						|
	_f->glVertexAttribPointer(
 | 
						|
		position,
 | 
						|
		2,
 | 
						|
		GL_FLOAT,
 | 
						|
		GL_FALSE,
 | 
						|
		2 * sizeof(GLfloat),
 | 
						|
		reinterpret_cast<const void*>(byteOffset));
 | 
						|
	_f->glEnableVertexAttribArray(position);
 | 
						|
 | 
						|
	_f->glDrawArrays(GL_TRIANGLES, 0, base::array_size(coords) / 2);
 | 
						|
 | 
						|
	_f->glDisableVertexAttribArray(position);
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintStoriesSiblingPart(
 | 
						|
		int index,
 | 
						|
		const QImage &image,
 | 
						|
		QRect rect,
 | 
						|
		float64 opacity) {
 | 
						|
	Expects(index >= 0 && index < kStoriesSiblingPartsCount);
 | 
						|
 | 
						|
	_f->glActiveTexture(GL_TEXTURE0);
 | 
						|
 | 
						|
	auto &part = _storiesSiblingParts[index];
 | 
						|
	part.setImage(image);
 | 
						|
	part.bind(*_f);
 | 
						|
 | 
						|
	const auto textured = part.texturedRect(
 | 
						|
		rect,
 | 
						|
		QRect(QPoint(), image.size()));
 | 
						|
	const auto geometry = transformRect(textured.geometry);
 | 
						|
	const GLfloat coords[] = {
 | 
						|
		geometry.left(), geometry.top(),
 | 
						|
		textured.texture.left(), textured.texture.bottom(),
 | 
						|
 | 
						|
		geometry.right(), geometry.top(),
 | 
						|
		textured.texture.right(), textured.texture.bottom(),
 | 
						|
 | 
						|
		geometry.right(), geometry.bottom(),
 | 
						|
		textured.texture.right(), textured.texture.top(),
 | 
						|
 | 
						|
		geometry.left(), geometry.bottom(),
 | 
						|
		textured.texture.left(), textured.texture.top(),
 | 
						|
	};
 | 
						|
	const auto offset = kControlsOffset
 | 
						|
		+ (kControlsCount * kControlValues) / 4
 | 
						|
		+ (6 * 2 * 4) / 4 // rounding
 | 
						|
		+ (index * 4);
 | 
						|
	const auto byteOffset = offset * 4 * sizeof(GLfloat);
 | 
						|
	_contentBuffer->write(byteOffset, coords, sizeof(coords));
 | 
						|
 | 
						|
	_controlsProgram->bind();
 | 
						|
	_controlsProgram->setUniformValue("viewport", _uniformViewport);
 | 
						|
	_contentBuffer->write(
 | 
						|
		offset * 4 * sizeof(GLfloat),
 | 
						|
		coords,
 | 
						|
		sizeof(coords));
 | 
						|
	_controlsProgram->setUniformValue("g_opacity", GLfloat(opacity));
 | 
						|
	FillTexturedRectangle(*_f, &*_controlsProgram, offset);
 | 
						|
}
 | 
						|
 | 
						|
//
 | 
						|
//void OverlayWidget::RendererGL::invalidate() {
 | 
						|
//	_trackFrameIndex = -1;
 | 
						|
//	_streamedIndex = -1;
 | 
						|
//	const auto images = {
 | 
						|
//		&_radialImage,
 | 
						|
//		&_documentBubbleImage,
 | 
						|
//		&_themePreviewImage,
 | 
						|
//		&_saveMsgImage,
 | 
						|
//		&_footerImage,
 | 
						|
//		&_captionImage,
 | 
						|
//		&_groupThumbsImage,
 | 
						|
//		&_controlsImage,
 | 
						|
//	};
 | 
						|
//	for (const auto image : images) {
 | 
						|
//		image->setImage(QImage());
 | 
						|
//	}
 | 
						|
//	invalidateControls();
 | 
						|
//}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::paintUsingRaster(
 | 
						|
		Ui::GL::Image &image,
 | 
						|
		QRect rect,
 | 
						|
		Fn<void(Painter&&)> method,
 | 
						|
		int bufferOffset,
 | 
						|
		bool transparent) {
 | 
						|
	auto raster = image.takeImage();
 | 
						|
	const auto size = rect.size() * _factor;
 | 
						|
	if (raster.width() < size.width() || raster.height() < size.height()) {
 | 
						|
		raster = QImage(size, QImage::Format_ARGB32_Premultiplied);
 | 
						|
		Assert(!raster.isNull());
 | 
						|
		raster.setDevicePixelRatio(_factor);
 | 
						|
		if (!transparent
 | 
						|
			&& (raster.width() > size.width()
 | 
						|
				|| raster.height() > size.height())) {
 | 
						|
			raster.fill(Qt::transparent);
 | 
						|
		}
 | 
						|
	} else if (raster.devicePixelRatio() != _factor) {
 | 
						|
		raster.setDevicePixelRatio(_factor);
 | 
						|
	}
 | 
						|
 | 
						|
	if (transparent) {
 | 
						|
		raster.fill(Qt::transparent);
 | 
						|
	}
 | 
						|
	method(Painter(&raster));
 | 
						|
 | 
						|
	_f->glActiveTexture(GL_TEXTURE0);
 | 
						|
 | 
						|
	image.setImage(std::move(raster), size);
 | 
						|
	image.bind(*_f);
 | 
						|
 | 
						|
	const auto textured = image.texturedRect(rect, QRect(QPoint(), size));
 | 
						|
	const auto geometry = transformRect(textured.geometry);
 | 
						|
	const GLfloat coords[] = {
 | 
						|
		geometry.left(), geometry.top(),
 | 
						|
		textured.texture.left(), textured.texture.bottom(),
 | 
						|
 | 
						|
		geometry.right(), geometry.top(),
 | 
						|
		textured.texture.right(), textured.texture.bottom(),
 | 
						|
 | 
						|
		geometry.right(), geometry.bottom(),
 | 
						|
		textured.texture.right(), textured.texture.top(),
 | 
						|
 | 
						|
		geometry.left(), geometry.bottom(),
 | 
						|
		textured.texture.left(), textured.texture.top(),
 | 
						|
	};
 | 
						|
	_contentBuffer->write(
 | 
						|
		bufferOffset * 4 * sizeof(GLfloat),
 | 
						|
		coords,
 | 
						|
		sizeof(coords));
 | 
						|
 | 
						|
	_imageProgram->bind();
 | 
						|
	_imageProgram->setUniformValue("viewport", _uniformViewport);
 | 
						|
	_imageProgram->setUniformValue("s_texture", GLint(0));
 | 
						|
 | 
						|
	toggleBlending(transparent);
 | 
						|
	FillTexturedRectangle(*_f, &*_imageProgram, bufferOffset);
 | 
						|
}
 | 
						|
 | 
						|
void OverlayWidget::RendererGL::toggleBlending(bool enabled) {
 | 
						|
	if (_blendingEnabled == enabled) {
 | 
						|
		return;
 | 
						|
	} else if (enabled) {
 | 
						|
		_f->glEnable(GL_BLEND);
 | 
						|
		_f->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
 | 
						|
	} else {
 | 
						|
		_f->glDisable(GL_BLEND);
 | 
						|
	}
 | 
						|
	_blendingEnabled = enabled;
 | 
						|
}
 | 
						|
 | 
						|
Rect OverlayWidget::RendererGL::transformRect(const Rect &raster) const {
 | 
						|
	return TransformRect(raster, _viewport, _factor);
 | 
						|
}
 | 
						|
 | 
						|
Rect OverlayWidget::RendererGL::transformRect(const QRectF &raster) const {
 | 
						|
	return TransformRect(raster, _viewport, _factor);
 | 
						|
}
 | 
						|
 | 
						|
Rect OverlayWidget::RendererGL::transformRect(const QRect &raster) const {
 | 
						|
	return TransformRect(Rect(raster), _viewport, _factor);
 | 
						|
}
 | 
						|
 | 
						|
} // namespace Media::View
 |