/* * SPDX-FileCopyrightText: 2022 Han Young * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "weatherbackground.h" #include #include #include #include #include #include #include #include class StarsRendererBase { public: float speedModifier = 1, aspectRatio = 1, scaleFactor = 1; int minFPS = 24; virtual ~StarsRendererBase() = default; virtual void render() = 0; }; class SnowRendererBase { public: float aspectRatio = 1; float speedModifier = 1; int minFPS = 60; virtual ~SnowRendererBase() = default; virtual void render() = 0; }; class StarsRendererLegacy : public StarsRendererBase, protected QOpenGLFunctions { public: StarsRendererLegacy(std::function &dice) { initializeOpenGLFunctions(); int starCount = 30; for (int i = 0; i < starCount; i++) { starPositions.push_back({dice(), dice()}); float originalOpacity = dice() / 10; float animationSpeed = dice() / 10 + 1; starMetaData.push_back({animationSpeed, originalOpacity, dice() > 0}); starColors.push_back({1, 1, 1, dice() / 10}); } const char *vertexShaderSrc2 = "attribute highp vec4 posAttr;\n" "attribute highp vec4 colorAttr;\n" "varying lowp vec4 col;\n" "void main() {\n" " col = colorAttr;\n" " gl_PointSize = 3.0;\n" " gl_Position = posAttr;\n" "}\n"; const char *fragmentShaderSrc = "varying lowp vec4 col;\n" "void main() {\n" " gl_FragColor = col;\n" "}\n"; program.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc2); program.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc); program.link(); vertexAttr = program.attributeLocation("posAttr"); colorAttr = program.attributeLocation("colorAttr"); } void render() override { program.bind(); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); program.setAttributeArray(vertexAttr, starPositions.data()); program.setAttributeArray(colorAttr, starColors.data()); glDrawArrays(GL_POINTS, 0, starPositions.size()); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); program.release(); float theta = 0.01; for (int i = 0; i < int(starPositions.size()); i++) { auto &[speed, opacity, direction] = starMetaData[i]; float _theta = theta * speed * speedModifier; if (!direction) { _theta = -_theta; } starColors[i].setW(starColors[i].w() + _theta); if (starColors[i].w() > 1) { direction = false; } else if (starColors[i].w() < opacity) { direction = true; } } } private: std::vector starPositions; std::vector starColors; std::vector> starMetaData; // animation speed, original opacity, direction QOpenGLShaderProgram program; int vertexAttr, colorAttr; }; class StarsRenderer : public StarsRendererBase, protected QOpenGLExtraFunctions { public: StarsRenderer(std::function &dice) { initializeOpenGLFunctions(); int starCount = 80; for (int i = 0; i < starCount; i++) { starPositions.push_back({dice(), dice(), 0, 1}); float originalOpacity = dice() / 10; float animationSpeed = dice() / 10 + 1; starMetaData.push_back({animationSpeed, originalOpacity, dice() > 0}); starColors.push_back({1, 1, 1, dice() / 10}); } int num_segments = 10; float x = 0.005; // we start at angle = 0 float y = 0; float theta = 2 * 3.1415926 / float(num_segments); float c = cosf(theta); // precalculate the sine and cosine float s = sinf(theta); float t; for (int ii = 0; ii < num_segments; ii++) { starVertexs.push_back(QVector4D{x, y, 0, 1}); // output vertex // apply the rotation matrix t = x; x = c * x - s * y; y = s * t + c * y; } const char *vertexShaderSrc2 = "#version 330 core\n" "layout (location = 0) in vec4 colorAttr;\n" "layout (location = 1) in vec4 posAttr;\n" "layout (location = 2) in vec4 vertAttr;\n" "uniform mat4 matrix;\n" "out vec4 col;\n" "void main() {\n" " col = colorAttr;\n" " gl_Position = vec4((matrix * vertAttr).xy + posAttr.xy, 0, 1);\n" "}\n"; const char *fragmentShaderSrc = "#version 330 core\n" "out vec4 FragColor;\n" "in vec4 col;\n" "void main() {\n" " FragColor = col;\n" "}\n"; program.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc2); program.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc); program.link(); vertexAttr = program.attributeLocation("vertAttr"); posAttr = program.attributeLocation("posAttr"); colorAttr = program.attributeLocation("colorAttr"); matrixL = program.uniformLocation("matrix"); } void render() override { glVertexAttribDivisor(colorAttr, 1); glVertexAttribDivisor(posAttr, 1); program.bind(); program.setAttributeArray(posAttr, starPositions.data()); program.setAttributeArray(vertexAttr, starVertexs.data()); program.setAttributeArray(colorAttr, starColors.data()); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glEnableVertexAttribArray(2); QMatrix4x4 matrix; matrix.scale(scaleFactor, scaleFactor * aspectRatio); program.setUniformValue(matrixL, matrix); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, starVertexs.size(), starPositions.size()); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); glDisableVertexAttribArray(2); program.release(); glVertexAttribDivisor(colorAttr, 0); glVertexAttribDivisor(posAttr, 0); float theta = 0.01; for (int i = 0; i < int(starPositions.size()); i++) { auto &[speed, opacity, direction] = starMetaData[i]; float _theta = theta * speed * speedModifier; if (!direction) { _theta = -_theta; } starColors[i].setW(starColors[i].w() + _theta); if (starColors[i].w() > 1) { direction = false; } else if (starColors[i].w() < opacity) { direction = true; } } } private: std::vector starPositions; std::vector starColors; std::vector starVertexs; std::vector> starMetaData; // animation speed, original opacity, direction QOpenGLShaderProgram program; int posAttr, vertexAttr, colorAttr, matrixL; }; class SunRenderer : protected QOpenGLFunctions { public: float aspectRatio = 1, speedModifier = 1; int minFPS = 60; SunRenderer() : vertices({QVector3D{-1, -1, 0}, {1, -1, 0}, {1, 1, 0}, {-1, 1, 0}}) , innerColor({1, 0.4313, 0, 0.5}) , outerColor({1, 0.5607, 0, 0.5}) , outerOuterColor({1, 0.6274, 0, 0.4}) , pos(QVector2D{0.9, -0.9}) , innerWidth(0.8 / 2.5) , outerWidth(1.6 / 2.5) , outerOuterWidth(3.2 / 2.5) { initializeOpenGLFunctions(); for (int i = 0; i < int(innerSun.size()); i++) { innerSun[i] = i * (90 / innerSun.size()); } for (int i = 0; i < int(outerSun.size()); i++) { outerSun[i] = i * (90 / outerSun.size()); } for (int i = 0; i < int(outerOuterSun.size()); i++) { outerOuterSun[i] = i * (90 / int(outerOuterSun.size())); } const char *vertexShaderSrc2 = "attribute highp vec4 posAttr;\n" "uniform highp mat4 matrix;\n" "uniform lowp vec4 sunCol;\n" "varying lowp vec4 col;\n" "void main() {\n" " col = sunCol;\n" " gl_Position = matrix * posAttr;\n" "}\n"; const char *fragmentShaderSrc = "varying lowp vec4 col;\n" "void main() {\n" " gl_FragColor = col;\n" "}\n"; program.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc2); program.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc); program.link(); vertexAttr = program.attributeLocation("posAttr"); sunColorL = program.uniformLocation("sunCol"); matrixL = program.uniformLocation("matrix"); } void render() { program.bind(); glEnableVertexAttribArray(0); program.setAttributeArray(vertexAttr, vertices.data()); program.setUniformValue(sunColorL, outerOuterColor); for (auto &rotation : outerOuterSun) { QMatrix4x4 matrix; matrix.translate(pos.x(), pos.y()); matrix.scale(outerOuterWidth, outerOuterWidth * aspectRatio); matrix.rotate(rotation, 0, 0, 1); program.setUniformValue(matrixL, matrix); glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size()); rotation += 0.6 * speedModifier; } program.setUniformValue(sunColorL, outerColor); for (auto &rotation : outerSun) { QMatrix4x4 matrix; matrix.translate(pos.x(), pos.y()); matrix.scale(outerWidth, outerWidth * aspectRatio); matrix.rotate(rotation, 0, 0, 1); program.setUniformValue(matrixL, matrix); glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size()); rotation += 0.375 * speedModifier; } program.setUniformValue(sunColorL, innerColor); for (auto &rotation : innerSun) { QMatrix4x4 matrix; matrix.translate(pos.x(), pos.y()); matrix.scale(innerWidth, innerWidth * aspectRatio); matrix.rotate(rotation, 0, 0, 1); program.setUniformValue(matrixL, matrix); glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size()); rotation += 0.3 * speedModifier; } glDisableVertexAttribArray(0); program.release(); } private: std::array vertices; std::array innerSun; std::array outerSun; std::array outerOuterSun; QVector4D innerColor, outerColor, outerOuterColor; QVector2D pos; float innerWidth, outerWidth, outerOuterWidth; QOpenGLShaderProgram program; int vertexAttr, sunColorL, matrixL; }; class BackgroundRenderer : protected QOpenGLFunctions { public: bool inAnimation = false; BackgroundRenderer() { initializeOpenGLFunctions(); const char *vertexShaderSrc2 = "attribute highp vec4 posAttr;\n" "attribute lowp vec4 colAttr;\n" "varying lowp vec4 col;\n" "void main() {\n" " col = colAttr;\n" " gl_Position = posAttr;\n" "}\n"; const char *fragmentShaderSrc = "varying lowp vec4 col;\n" "void main() {\n" " gl_FragColor = col;\n" "}\n"; program.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc2); program.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc); program.link(); vertexAttr = program.attributeLocation("posAttr"); colAttr = program.attributeLocation("colAttr"); } void setColors(QVector4D topColor, QVector4D bottomColor) { if (topColor != m_topColor || bottomColor != m_bottomColor) { m_oldBottomColor = m_bottomColor; m_oldTopColor = m_topColor; m_topColor = topColor; m_bottomColor = bottomColor; if (!colorInitialized) { colorInitialized = true; } else { animationStartTime = std::chrono::system_clock::now(); inAnimation = true; } } } void render() { QList backgroundV = {{-1, 1, 0}, {1, 1, 0}, {1, -1, 0}, {-1, -1, 0}}; auto topColor = m_topColor, bottomColor = m_bottomColor; if (inAnimation) { auto diff = std::chrono::duration_cast(std::chrono::system_clock::now() - animationStartTime).count(); float percentage = float(diff) / float(animationTime); if (percentage >= 1) { inAnimation = false; percentage = 0.999; // prevent negative RGB } topColor = topColor * percentage + m_oldTopColor * (1 - percentage); bottomColor = bottomColor * percentage + m_oldBottomColor * (1 - percentage); } QList backgroundC = {bottomColor, bottomColor, topColor, topColor}; program.bind(); program.setAttributeArray(vertexAttr, backgroundV.constData()); program.setAttributeArray(colAttr, backgroundC.constData()); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glDrawArrays(GL_TRIANGLE_FAN, 0, backgroundV.size()); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); program.release(); } private: QOpenGLShaderProgram program; int vertexAttr, colAttr; QVector4D m_topColor; QVector4D m_bottomColor; QVector4D m_oldTopColor; QVector4D m_oldBottomColor; int animationTime = 800; // milliseconds bool colorInitialized = false; std::chrono::time_point animationStartTime; }; class RainRenderer : protected QOpenGLFunctions { public: int minFPS = 60; RainRenderer(std::function &dice) { initializeOpenGLFunctions(); int max = 100; for (int i = 0; i < max; i++) { droplets.push_back({{dice() * 2 - 1, -1 - (dice() + 1) / 4}, 0.1f + (dice() + 1) / 10, 3 + dice() / 2}); } for (auto &[pos, length, speed] : droplets) { vertices.push_back(pos.toVector3D()); vertices.push_back({pos.x(), pos.y() + length, 0}); } const char *vertexShaderSrc = "attribute highp vec4 posAttr;\n" "void main() {\n" " gl_Position = posAttr;\n" "}\n"; const char *fragmentShaderSrc = "void main() {\n" " gl_FragColor = vec4(1,1,1,0.6);\n" "}\n"; shader.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc); shader.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc); shader.link(); vertexAttr = shader.attributeLocation("posAttr"); } void render() { shader.bind(); shader.setAttributeArray(vertexAttr, vertices.constData()); glEnableVertexAttribArray(0); glLineWidth(3); glDrawArrays(GL_LINES, 0, vertices.size()); glDisableVertexAttribArray(0); for (int i = 0; i < vertices.size() - 1; i += 2) { auto &s = vertices[i]; auto &e = vertices[i + 1]; auto &[pos, length, speed] = droplets[i / 2]; e[1] += 0.02 * speed; s[1] += 0.02 * speed; if (s[1] > 1) { vertices[i] = {pos.x(), pos.y() - length, 0}; vertices[i + 1] = {pos.x(), pos.y(), 0}; } } shader.release(); } private: QList vertices; std::vector> droplets; // pos, length, speed QOpenGLShaderProgram shader; int vertexAttr; std::function dice; }; class SnowRenderer : public SnowRendererBase, protected QOpenGLExtraFunctions { public: SnowRenderer(std::function &dice) { initializeOpenGLFunctions(); int max = 80, num_segments = 200; float x = 1; // we start at angle = 0 float y = 0; float theta = 2 * 3.1415926 / float(num_segments); float c = cosf(theta); // precalculate the sine and cosine float s = sinf(theta); float t; for (int ii = 0; ii < num_segments; ii++) { vertices.push_back(QVector3D{x, y, 0}); // output vertex // apply the rotation matrix t = x; x = c * x - s * y; y = s * t + c * y; } for (int i = 0; i < max; i++) { float r = 0.02 + (dice() + 1) / 50; // 0.02 - 0.06 QVector2D pos = {dice(), dice()}; flakes.push_back((dice() + 2) / 2); positions.push_back(pos); flakeData.push_back({float(0.75 + dice() / 4), r}); } const char *vertexShaderSrc = "#version 330 core\n" "layout (location = 0) in vec2 posAttr;\n" "layout (location = 1) in vec2 flakeDataAttr;\n" // opacity, radius "layout (location = 2) in vec2 vertAttr;\n" "uniform highp mat4 matrix;\n" "out lowp vec4 col;\n" "void main() {\n" " col = vec4(1,1,1,flakeDataAttr.x);\n" " gl_Position = vec4((matrix * vec4(vertAttr.x * flakeDataAttr.y, vertAttr.y * flakeDataAttr.y, 0, 1)).xy + posAttr, 0 , 1);\n" "}\n"; const char *fragmentShaderSrc = "#version 330 core\n" "in lowp vec4 col;\n" "out lowp vec4 FragColor;\n" "void main() {\n" " FragColor = col;\n" "}\n"; shader.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc); shader.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc); shader.link(); vertexAttr = shader.attributeLocation("vertAttr"); matrixL = shader.uniformLocation("matrix"); flakeDataAttr = shader.attributeLocation("flakeDataAttr"); translateAttr = shader.attributeLocation("posAttr"); } void render() override { shader.bind(); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glEnableVertexAttribArray(2); shader.setAttributeArray(vertexAttr, vertices.data()); shader.setAttributeArray(translateAttr, positions.data()); shader.setAttributeArray(flakeDataAttr, flakeData.data()); glVertexAttribDivisor(translateAttr, 1); glVertexAttribDivisor(flakeDataAttr, 1); QMatrix4x4 matrix; matrix.scale(1, aspectRatio); shader.setUniformValue(matrixL, matrix); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, vertices.size(), positions.size()); int i = 0; for (auto &speed : flakes) { auto &pos = positions[i]; auto r = flakeData[i].y(); float translate = 0.003 * speed * speedModifier; if (translate + pos.y() - 2 * r > 1.0) { translate = -pos.y() - 1 - r; } pos.setY(pos.y() + translate); i++; } glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); glDisableVertexAttribArray(2); glVertexAttribDivisor(translateAttr, 0); glVertexAttribDivisor(flakeDataAttr, 0); shader.release(); } private: std::vector vertices; std::vector flakeData; std::vector positions; std::vector flakes; // speed QOpenGLShaderProgram shader; int vertexAttr, flakeDataAttr, matrixL, translateAttr; std::function dice; }; class SnowRendererLegacy : public SnowRendererBase, protected QOpenGLFunctions { public: SnowRendererLegacy(std::function &dice) { initializeOpenGLFunctions(); int max = 80, num_segments = 200; float x = 1; // we start at angle = 0 float y = 0; float theta = 2 * 3.1415926 / float(num_segments); float c = cosf(theta); // precalculate the sine and cosine float s = sinf(theta); float t; for (int ii = 0; ii < num_segments; ii++) { vertices.push_back(QVector3D{x, y, 0}); // output vertex // apply the rotation matrix t = x; x = c * x - s * y; y = s * t + c * y; } for (int i = 0; i < max; i++) { float r = 0.02 + (dice() + 1) / 50; // 0.02 - 0.06 QVector2D originalPos = {dice(), dice()}; flakes.push_back({originalPos, 0, r, (dice() + 2) / 2, 0.75 + dice() / 4}); } const char *vertexShaderSrc = "attribute highp vec4 posAttr;\n" "uniform lowp vec4 flakeColor;\n" "uniform highp mat4 matrix;\n" "varying lowp vec4 col;\n" "void main() {\n" " col = flakeColor;\n" " gl_Position = matrix * posAttr;\n" "}\n"; const char *fragmentShaderSrc = "varying lowp vec4 col;\n" "void main() {\n" " gl_FragColor = col;\n" "}\n"; shader.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc); shader.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc); shader.link(); vertexAttr = shader.attributeLocation("posAttr"); translateL = shader.uniformLocation("matrix"); flakeColorL = shader.uniformLocation("flakeColor"); } void render() override { shader.bind(); glEnableVertexAttribArray(0); shader.setAttributeArray(vertexAttr, vertices.data()); for (auto &[pos, translate, r, speed, opacity] : flakes) { QMatrix4x4 matrix; matrix.translate(pos.x(), pos.y() + translate); matrix.scale(r, r * aspectRatio); shader.setUniformValue(translateL, matrix); shader.setUniformValue(flakeColorL, QVector4D{1, 1, 1, opacity}); glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size()); translate += 0.003 * speed * speedModifier; if (translate + pos.y() - 2 * r > 1.0) { translate = -pos.y() - 1 - r; } } glDisableVertexAttribArray(0); shader.release(); } private: std::vector vertices; std::vector> flakes; // pos, currentTranslate, r, speed, opacity QOpenGLShaderProgram shader; int vertexAttr, translateL, flakeColorL; std::function dice; }; class CloudsRenderer : protected QOpenGLFunctions { public: float speedModifier = 1; int minFPS = 24; float aspectRatio = 1; QColor cloudColor; CloudsRenderer(std::function &dice) { initializeOpenGLFunctions(); const char *vertexShaderSrc = "attribute highp vec4 posAttr;\n" "uniform lowp vec4 cloudCol;\n" "varying lowp vec4 col;\n" "uniform highp mat4 matrix;\n" "void main() {\n" " col = cloudCol;\n" " gl_Position = matrix * posAttr;\n" "}\n"; const char *fragmentShaderSrc = "varying lowp vec4 col;\n" "void main() {\n" " gl_FragColor = col;\n" "}\n"; program.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc); program.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc); program.link(); vertexAttr = program.attributeLocation("posAttr"); cloudColorL = program.uniformLocation("cloudCol"); matrixL = program.uniformLocation("matrix"); std::array opacity = {0.2f, 0.6, 0.2, 0.2, 0.5, 0.3, 0.5}; std::array radii = {0.8f, 0.8f, 0.43f, 0.46f, 0.8f, 0.6f, 0.8f}; float step = 2.0 / (opacity.size() - 1); float x = -1.2 + step; int i = 0; for (auto op : opacity) { Cloud cloud; QVector2D pos = {x, -1.0f - (dice() + 1) / 20}; QVector2D coorChange = {dice() / 10, dice() / 10}; cloud.pos = pos; cloud.endVector = coorChange; cloud.translate = cloud.endVector; cloud.translate.normalize(); cloud.translate.setX(cloud.translate.x() / 100); cloud.translate.setY(cloud.translate.y() / 100); cloud.theta = cloud.translate; cloud.theta.setX(cloud.theta.x() / 30); cloud.theta.setY(cloud.theta.y() / 30); x += step; cloud.opacity = op; cloud.r = radii[i++]; m_clouds.push_back(cloud); } int num_segments = 200; float theta = 2 * 3.1415926 / float(num_segments); float c = cosf(theta); // precalculate the sine and cosine float s = sinf(theta); float t; x = 1; // we start at angle = 0 float y = 0; for (int ii = 0; ii < num_segments; ii++) { vertices.push_back(QVector3D{x, y, 0}); // output vertex // apply the rotation matrix t = x; x = c * x - s * y; y = s * t + c * y; } }; void render() { program.bind(); glEnableVertexAttribArray(0); program.setAttributeArray(vertexAttr, vertices.data()); for (auto &[endVector, translate, theta, pos, r, opacity] : m_clouds) { program.setUniformValue(cloudColorL, QVector4D{static_cast(cloudColor.redF()), static_cast(cloudColor.greenF()), static_cast(cloudColor.blueF()), 0.65f * opacity}); if (translate.length() > endVector.length()) { endVector.setX(-endVector.x()); endVector.setY(-endVector.y()); theta.setX(-theta.x()); theta.setY(-theta.y()); } translate += theta * speedModifier; QMatrix4x4 matrix; matrix.translate(translate.x() + pos.x(), translate.y() + pos.y()); matrix.scale(r, r * aspectRatio); program.setUniformValue(matrixL, matrix); glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size()); } glDisableVertexAttribArray(0); program.release(); } private: struct Cloud { QVector2D endVector; QVector2D translate; QVector2D theta; QVector2D pos; float r; float opacity; }; QOpenGLShaderProgram program; std::vector vertices; int vertexAttr; int cloudColorL; int matrixL; std::vector m_clouds; }; WeatherBackgroundContentRenderer::WeatherBackgroundContentRenderer() { initializeOpenGLFunctions(); std::default_random_engine generator; std::uniform_real_distribution distribution(-1, 1); auto *ctx = QOpenGLContext::currentContext(); if (ctx->format().majorVersion() < 3) { m_legacyMode = true; } m_legacyMode = true; dice = std::bind(distribution, generator); background = new BackgroundRenderer(); } void WeatherBackgroundContentRenderer::render() { glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glEnable(GL_BLEND); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GL_ONE); glDisable(GL_DEPTH_TEST); background->render(); if (m_showCloud) { m_clouds->render(); } if (m_showRain) { m_rain->render(); } if (m_showSun) { m_sun->render(); } if (m_showStar) { m_stars->render(); } if (m_showSnow) { m_snow->render(); } } QOpenGLFramebufferObject *WeatherBackgroundContentRenderer::createFramebufferObject(const QSize &size) { QOpenGLFramebufferObjectFormat format; format.setSamples(4); return new QOpenGLFramebufferObject(size, format); } void WeatherBackgroundContentRenderer::synchronize(QQuickFramebufferObject *item) { if (WeatherBackgroundRenderer *bgItem = dynamic_cast(item)) { if (!bgItem->isVisible()) { return; } aspectRatio = bgItem->width() / bgItem->height(); m_showCloud = bgItem->cloud(); m_showRain = bgItem->rain(); m_showSun = bgItem->sun(); m_showStar = bgItem->star(); m_showSnow = bgItem->snow(); m_colourTop = bgItem->colorTop(); m_colourBottom = bgItem->colorBottom(); m_cloudColor = bgItem->cloudColor(); background->setColors({static_cast(m_colourTop.redF()), static_cast(m_colourTop.greenF()), static_cast(m_colourTop.blueF()), static_cast(m_colourTop.alphaF())}, {static_cast(m_colourBottom.redF()), static_cast(m_colourBottom.greenF()), static_cast(m_colourBottom.blueF()), static_cast(m_colourBottom.alphaF())}); // isn't very elegant, but easiest to write without virtual base class int minFPS = std::numeric_limits::lowest(); if (background->inAnimation) { minFPS = 60; } if (m_showCloud) { if (!m_clouds) { m_clouds = new CloudsRenderer(dice); } m_clouds->aspectRatio = aspectRatio; m_clouds->cloudColor = m_cloudColor; minFPS = std::max(minFPS, m_clouds->minFPS); } if (m_showRain) { if (!m_rain) { m_rain = new RainRenderer(dice); } minFPS = std::max(minFPS, m_rain->minFPS); } if (m_showSun) { if (!m_sun) { m_sun = new SunRenderer(); } m_sun->aspectRatio = aspectRatio; minFPS = std::max(minFPS, m_sun->minFPS); } if (m_showStar) { if (!m_stars) { if (!m_legacyMode) { m_stars = new StarsRenderer(dice); } else { m_stars = new StarsRendererLegacy(dice); } } m_stars->scaleFactor = 500 / bgItem->width(); m_stars->aspectRatio = aspectRatio; minFPS = std::max(minFPS, m_stars->minFPS); } if (m_showSnow) { if (!m_snow) { if (!m_legacyMode) { m_snow = new SnowRenderer(dice); } else { m_snow = new SnowRendererLegacy(dice); } } m_snow->aspectRatio = aspectRatio; minFPS = std::max(minFPS, m_snow->minFPS); } // avoid bug, eg. non of the components is visible if (minFPS < 10) { minFPS = 60; } if (minFPS != m_minFPS) { m_minFPS = minFPS; float speedModifier = 60 / minFPS; if (m_showCloud) { m_clouds->speedModifier = speedModifier; } // skip rain, rain running on 60 fps which is the standard if (m_showSun) { m_sun->speedModifier = speedModifier; } if (m_showStar) { m_stars->speedModifier = speedModifier; } bgItem->modifiedMinFPS = minFPS; } } } WeatherBackgroundRenderer::WeatherBackgroundRenderer(QQuickItem *parent) : QQuickFramebufferObject(parent) , m_timer(new QTimer(this)) { connect(m_timer, &QTimer::timeout, this, &WeatherBackgroundRenderer::handleTimeout); connect(this, &QQuickItem::visibleChanged, this, [this]() { if (isVisible()) { m_timer->start(1000 / minFPS); } else { m_timer->stop(); } }); m_timer->start(1000 / minFPS); } void WeatherBackgroundRenderer::handleTimeout() { if (isVisible()) { update(); if (modifiedMinFPS != minFPS) { minFPS = modifiedMinFPS; m_timer->start(1000 / minFPS); } else if (!m_timer->isActive()) { m_timer->start(1000 / minFPS); } } else { m_timer->stop(); } } QQuickFramebufferObject::Renderer *WeatherBackgroundRenderer::createRenderer() const { return new WeatherBackgroundContentRenderer(); } bool WeatherBackgroundRenderer::rain() const { return m_rain; } bool WeatherBackgroundRenderer::cloud() const { return m_cloud; } bool WeatherBackgroundRenderer::sun() const { return m_sun; } bool WeatherBackgroundRenderer::star() const { return m_star; } bool WeatherBackgroundRenderer::snow() const { return m_snow; } QColor WeatherBackgroundRenderer::colorTop() const { return m_colourTop; } QColor WeatherBackgroundRenderer::colorBottom() const { return m_colourBottom; } QColor WeatherBackgroundRenderer::cloudColor() const { return m_cloudColor; } void WeatherBackgroundRenderer::setRain(bool r) { m_rain = r; } void WeatherBackgroundRenderer::setCloud(bool c) { m_cloud = c; } void WeatherBackgroundRenderer::setSun(bool s) { m_sun = s; } void WeatherBackgroundRenderer::setStar(bool s) { m_star = s; } void WeatherBackgroundRenderer::setSnow(bool s) { m_snow = s; } void WeatherBackgroundRenderer::setColorTop(const QColor &c) { m_colourTop = c; } void WeatherBackgroundRenderer::setColorBottom(const QColor &c) { m_colourBottom = c; } void WeatherBackgroundRenderer::setCloudColor(const QColor &c) { m_cloudColor = c; } #include "moc_weatherbackground.cpp"