diff --git a/CMakeLists.txt b/CMakeLists.txt index 220528a..86cc0cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,10 @@ PRIVATE ui/gl/gl_detection.h ui/gl/gl_image.cpp ui/gl/gl_image.h + ui/gl/gl_math.cpp + ui/gl/gl_math.h + ui/gl/gl_shader.cpp + ui/gl/gl_shader.h ui/gl/gl_surface.cpp ui/gl/gl_surface.h ui/image/image_prepare.cpp diff --git a/ui/gl/gl_image.cpp b/ui/gl/gl_image.cpp index c1e7c20..291bbe1 100644 --- a/ui/gl/gl_image.cpp +++ b/ui/gl/gl_image.cpp @@ -82,4 +82,31 @@ void Image::destroy(QOpenGLFunctions &f) { _cacheKey = 0; } +TexturedRect Image::texturedRect( + const QRect &geometry, + const QRect &texture, + const QRect &clip) { + Expects(!_image.isNull()); + + const auto visible = clip.isNull() + ? geometry + : clip.intersected(geometry); + const auto xFactor = texture.width() / geometry.width(); + const auto yFactor = texture.height() / geometry.height(); + const auto usedTexture = QRect( + texture.x() + (visible.x() - geometry.x()) * xFactor, + texture.y() + (visible.y() - geometry.y()) * yFactor, + visible.width() * xFactor, + visible.height() * yFactor); + const auto dimensions = QSizeF(_image.size()); + return { + .geometry = Rect(visible), + .texture = Rect(QRectF( + usedTexture.x() / dimensions.width(), + usedTexture.y() / dimensions.height(), + usedTexture.width() / dimensions.width(), + usedTexture.height() / dimensions.height())), + }; +} + } // namespace Ui::GL diff --git a/ui/gl/gl_image.h b/ui/gl/gl_image.h index 3adb235..edf1f8d 100644 --- a/ui/gl/gl_image.h +++ b/ui/gl/gl_image.h @@ -6,6 +6,8 @@ // #pragma once +#include "ui/gl/gl_math.h" + #include namespace Ui::GL { @@ -47,6 +49,11 @@ private: }; +struct TexturedRect { + Rect geometry; + Rect texture; +}; + class Image final { public: void setImage(QImage image); @@ -57,6 +64,11 @@ public: void bind(QOpenGLFunctions &f); void destroy(QOpenGLFunctions &f); + [[nodiscard]] TexturedRect texturedRect( + const QRect &geometry, + const QRect &texture, + const QRect &clip = QRect()); + explicit operator bool() const { return !_image.isNull(); } diff --git a/ui/gl/gl_math.cpp b/ui/gl/gl_math.cpp new file mode 100644 index 0000000..ad37155 --- /dev/null +++ b/ui/gl/gl_math.cpp @@ -0,0 +1,35 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#include "ui/gl/gl_math.h" + +namespace Ui::GL { + +QVector4D Uniform(const QRect &rect, float factor) { + return QVector4D( + rect.x() * factor, + rect.y() * factor, + rect.width() * factor, + rect.height() * factor); +} + +QVector4D Uniform(const Rect &rect) { + return QVector4D(rect.x(), rect.y(), rect.width(), rect.height()); +} + +QVector4D Uniform(const QColor &color) { + return QVector4D( + color.redF(), + color.greenF(), + color.blueF(), + color.alphaF()); +} + +QSizeF Uniform(QSize size) { + return size; +} + +} // namespace Ui::GL diff --git a/ui/gl/gl_math.h b/ui/gl/gl_math.h new file mode 100644 index 0000000..4424052 --- /dev/null +++ b/ui/gl/gl_math.h @@ -0,0 +1,75 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +#include +#include + +namespace Ui::GL { + +class Rect final { +public: + Rect(QRect rect) + : _x(rect.x()) + , _y(rect.y()) + , _width(rect.width()) + , _height(rect.height()) { + } + + Rect(QRectF rect) + : _x(rect.x()) + , _y(rect.y()) + , _width(rect.width()) + , _height(rect.height()) { + } + + Rect(float x, float y, float width, float height) + : _x(x) + , _y(y) + , _width(width) + , _height(height) { + } + + [[nodiscard]] float x() const { + return _x; + } + [[nodiscard]] float y() const { + return _y; + } + [[nodiscard]] float width() const { + return _width; + } + [[nodiscard]] float height() const { + return _height; + } + [[nodiscard]] float left() const { + return _x; + } + [[nodiscard]] float top() const { + return _y; + } + [[nodiscard]] float right() const { + return _x + _width; + } + [[nodiscard]] float bottom() const { + return _y + _height; + } + +private: + float _x = 0; + float _y = 0; + float _width = 0; + float _height = 0; + +}; + +[[nodiscard]] QVector4D Uniform(const QRect &rect, float factor); +[[nodiscard]] QVector4D Uniform(const Rect &rect); +[[nodiscard]] QVector4D Uniform(const QColor &color); +[[nodiscard]] QSizeF Uniform(QSize size); + +} // namespace Ui::GL diff --git a/ui/gl/gl_shader.cpp b/ui/gl/gl_shader.cpp new file mode 100644 index 0000000..8e7e659 --- /dev/null +++ b/ui/gl/gl_shader.cpp @@ -0,0 +1,183 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#include "ui/gl/gl_shader.h" + +#include "base/debug_log.h" + +namespace Ui::GL { + +QString VertexShader(const std::vector &parts) { + const auto accumulate = [&](auto proj) { + return ranges::accumulate(parts, QString(), std::plus<>(), proj); + }; + return R"( +#version 120 +attribute vec2 position; +)" + accumulate(&ShaderPart::header) + R"( +void main() { + vec4 result = vec4(position, 0., 1.); +)" + accumulate(&ShaderPart::body) + R"( + gl_Position = result; +} +)"; +} + +QString FragmentShader(const std::vector &parts) { + const auto accumulate = [&](auto proj) { + return ranges::accumulate(parts, QString(), std::plus<>(), proj); + }; + return R"( +#version 120 +)" + accumulate(&ShaderPart::header) + R"( +void main() { + vec4 result = vec4(0., 0., 0., 0.); +)" + accumulate(&ShaderPart::body) + R"( + gl_FragColor = result; +} +)"; +} + +ShaderPart VertexPassTextureCoord() { + return { + .header = R"( +attribute vec2 texcoord; +varying vec2 v_texcoord; +)", + .body = R"( + v_texcoord = texcoord; +)", + }; +} + +ShaderPart FragmentSampleARGB32Texture() { + return { + .header = R"( +varying vec2 v_texcoord; +uniform sampler2D s_texture; +)", + .body = R"( + result = texture2D(s_texture, v_texcoord); + result = vec4(result.b, result.g, result.r, result.a); +)", + }; +} + +ShaderPart FragmentSampleYUV420Texture() { + return { + .header = R"( +varying vec2 v_texcoord; +uniform sampler2D y_texture; +uniform sampler2D u_texture; +uniform sampler2D v_texture; +)", + .body = R"( + float y = texture2D(y_texture, v_texcoord).r; + float u = texture2D(u_texture, v_texcoord).r - 0.5; + float v = texture2D(v_texture, v_texcoord).r - 0.5; + result = vec4(y + 1.403 * v, y - 0.344 * u - 0.714 * v, y + 1.77 * u, 1); +)", + }; +} + +ShaderPart VertexViewportTransform() { + return { + .header = R"( +uniform vec2 viewport; +vec4 transform(vec4 position) { + return vec4( + vec2(-1, -1) + 2 * position.xy / viewport, + position.z, + position.w); +} +)", + .body = R"( + result = transform(result); +)", + }; +} + +ShaderPart FragmentRoundCorners() { + return { + .header = R"( +uniform vec4 roundRect; +uniform vec4 roundBg; +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 d = length(fromRoundingCenter) - roundRadius; + return 1. - smoothstep(0., 1., d); +} +)", + .body = R"( + float rounded = roundedCorner(); + result = result * rounded + roundBg * (1. - rounded); +)", + }; +} + +ShaderPart FragmentStaticColor() { + return { + .header = R"( +uniform vec4 s_color; +)", + .body = R"( + result = s_color; +)", + }; +} + +not_null MakeShader( + not_null program, + QOpenGLShader::ShaderType type, + const QString &source) { + const auto result = new QOpenGLShader(type, program); + if (!result->compileSourceCode(source)) { + LOG(("Shader Compilation Failed: %1, error %2." + ).arg(source + ).arg(result->log())); + } + program->addShader(result); + return result; +} + +Program LinkProgram( + not_null program, + std::variant> vertex, + std::variant> fragment) { + const auto vertexAsSource = v::is(vertex); + const auto v = vertexAsSource + ? MakeShader( + program, + QOpenGLShader::Vertex, + v::get(vertex)) + : v::get>(vertex); + if (!vertexAsSource) { + program->addShader(v); + } + const auto fragmentAsSource = v::is(fragment); + const auto f = fragmentAsSource + ? MakeShader( + program, + QOpenGLShader::Fragment, + v::get(fragment)) + : v::get>(fragment); + if (!fragmentAsSource) { + program->addShader(f); + } + if (!program->link()) { + LOG(("Shader Link Failed: %1.").arg(program->log())); + } + return { v, f }; +} + +} // namespace Ui::GL diff --git a/ui/gl/gl_shader.h b/ui/gl/gl_shader.h new file mode 100644 index 0000000..cacb1f3 --- /dev/null +++ b/ui/gl/gl_shader.h @@ -0,0 +1,46 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +#include +#include + +class OpenGLShaderProgram; + +namespace Ui::GL { + +struct ShaderPart { + QString header; + QString body; +}; + +[[nodiscard]] QString VertexShader(const std::vector &parts); +[[nodiscard]] QString FragmentShader(const std::vector &parts); + +[[nodiscard]] ShaderPart VertexPassTextureCoord(); +[[nodiscard]] ShaderPart FragmentSampleARGB32Texture(); +[[nodiscard]] ShaderPart FragmentSampleYUV420Texture(); +[[nodiscard]] ShaderPart VertexViewportTransform(); +[[nodiscard]] ShaderPart FragmentRoundCorners(); +[[nodiscard]] ShaderPart FragmentStaticColor(); + +not_null MakeShader( + not_null program, + QOpenGLShader::ShaderType type, + const QString &source); + +struct Program { + not_null vertex; + not_null fragment; +}; + +Program LinkProgram( + not_null program, + std::variant> vertex, + std::variant> fragment); + +} // namespace Ui::GL