From 0bb419c0b0615100d450b29a4641ada5d17018a5 Mon Sep 17 00:00:00 2001 From: RuiwenTang Date: Wed, 6 Nov 2024 12:33:49 +0800 Subject: [PATCH] gl_engine: Support new radial gradient data struct and calculation Change the shader and uniform struct to support new radial gradient. The mathematical calculation comes from https://skia.org/docs/dev/design/conical/ --- src/renderer/gl_engine/tvgGlCommon.h | 6 +- src/renderer/gl_engine/tvgGlRenderer.cpp | 13 ++- src/renderer/gl_engine/tvgGlShaderSrc.cpp | 107 ++++++++++++++++++++-- 3 files changed, 112 insertions(+), 14 deletions(-) diff --git a/src/renderer/gl_engine/tvgGlCommon.h b/src/renderer/gl_engine/tvgGlCommon.h index 077acadd..0ae4267e 100644 --- a/src/renderer/gl_engine/tvgGlCommon.h +++ b/src/renderer/gl_engine/tvgGlCommon.h @@ -89,9 +89,9 @@ struct GlLinearGradientBlock struct GlRadialGradientBlock { alignas(16) float nStops[4] = {}; - alignas(16) float centerPos[2] = {}; - alignas(8) float radius[2] = {}; - alignas(8) float stopPoints[MAX_GRADIENT_STOPS] = {}; + alignas(16) float centerPos[4] = {}; + alignas(16) float radius[2] = {}; + alignas(16) float stopPoints[MAX_GRADIENT_STOPS] = {}; alignas(16) float stopColors[4 * MAX_GRADIENT_STOPS] = {}; }; diff --git a/src/renderer/gl_engine/tvgGlRenderer.cpp b/src/renderer/gl_engine/tvgGlRenderer.cpp index a9117c16..cd0d400e 100644 --- a/src/renderer/gl_engine/tvgGlRenderer.cpp +++ b/src/renderer/gl_engine/tvgGlRenderer.cpp @@ -369,12 +369,15 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const Fill* fill, RenderUpdateFla } gradientBlock.nStops[0] = nStops * 1.f; - float x, y, r; - radialFill->radial(&x, &y, &r); + float x, y, r, fx, fy, fr; + radialFill->radial(&x, &y, &r, &fx, &fy, &fr); - gradientBlock.centerPos[0] = x; - gradientBlock.centerPos[1] = y; - gradientBlock.radius[0] = r; + gradientBlock.centerPos[0] = fx; + gradientBlock.centerPos[1] = fy; + gradientBlock.centerPos[2] = x; + gradientBlock.centerPos[3] = y; + gradientBlock.radius[0] = fr; + gradientBlock.radius[1] = r; gradientBinding = GlBindingResource{ 2, diff --git a/src/renderer/gl_engine/tvgGlShaderSrc.cpp b/src/renderer/gl_engine/tvgGlShaderSrc.cpp index ef386818..ad99f46b 100644 --- a/src/renderer/gl_engine/tvgGlShaderSrc.cpp +++ b/src/renderer/gl_engine/tvgGlShaderSrc.cpp @@ -198,7 +198,7 @@ std::string STR_LINEAR_GRADIENT_MAIN = TVG_COMPOSE_SHADER( std::string STR_RADIAL_GRADIENT_VARIABLES = TVG_COMPOSE_SHADER( layout(std140) uniform GradientInfo { \n vec4 nStops; \n - vec2 centerPos; \n + vec4 centerPos; \n vec2 radius; \n vec4 stopPoints[MAX_STOP_COUNT / 4]; \n vec4 stopColors[MAX_STOP_COUNT]; \n @@ -207,14 +207,109 @@ std::string STR_RADIAL_GRADIENT_VARIABLES = TVG_COMPOSE_SHADER( std::string STR_RADIAL_GRADIENT_MAIN = TVG_COMPOSE_SHADER( out vec4 FragColor; \n + \n + mat3 radial_matrix(vec2 p0, vec2 p1) \n + { \n + mat3 a = mat3(0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0); \n + mat3 b = mat3(p1.y - p0.y, p0.x - p1.x, 0.0, p1.x - p0.x, p1.y - p0.y, 0.0, p0.x, p0.y, 1.0); \n + return a * inverse(b); \n + } \n + \n + vec2 compute_radial_t(vec2 c0, float r0, vec2 c1, float r1, vec2 pos) \n + { \n + const float scalar_nearly_zero = 1.0 / float(1 << 12); \n + float d_center = distance(c0, c1); \n + float d_radius = r1 - r0; \n + bool radial = d_center < scalar_nearly_zero; \n + bool strip = abs(d_radius) < scalar_nearly_zero; \n + \n + if (radial) { \n + if (strip) return vec2(0.0, -1.0); \n + \n + float scale = 1.0 / d_radius; \n + float scale_sign = sign(d_radius); \n + float bias = r0 / d_radius; \n + vec2 pt = (pos - c0) * scale; \n + float t = length(pt) * scale_sign - bias; \n + return vec2(t, 1.0); \n + } else if (strip) { \n + mat3 transform = radial_matrix(c0, c1); \n + float r = r0 / d_center; \n + float r_2 = r * r; \n + vec2 pt = (transform * vec3(pos.xy, 1.0)).xy; \n + float t = r_2 - pt.y * pt.y; \n + \n + if (t < 0.0) return vec2(0.0, -1.0); \n + \n + t = pt.x + sqrt(t); \n + return vec2(t, 1.0); \n + } else { \n + float f = r0 / (r0 - r1); \n + bool is_swapped = abs(f - 1.0) < scalar_nearly_zero; \n + if (is_swapped) { \n + vec2 c = c0; \n + c0 = c1; \n + c1 = c; \n + f = 0.0; \n + } \n + vec2 cf = c0 * (1.0 - f) + c1 * f; \n + mat3 transform = radial_matrix(cf, c1); \n + \n + float scale_x = abs(1.0 - f); \n + float scale_y = scale_x; \n + float r1 = abs(r1 - r0) / d_center; \n + bool is_focal_on_circle = abs(r1 - 1.0) < scalar_nearly_zero; \n + if (is_focal_on_circle) { \n + scale_x *= 0.5; \n + scale_y *= 0.5; \n + } else { \n + scale_x *= r1 / (r1 * r1 - 1.0); \n + scale_y /= sqrt(abs(r1 * r1 - 1.0)); \n + } \n + transform = mat3(scale_x, 0.0, 0.0, 0.0, scale_y, 0.0, 0.0, 0.0, 1.0) * transform; \n + \n + vec2 pt = (transform * vec3(pos.xy, 1.0)).xy; \n + \n + float inv_r1 = 1.0 / r1; \n + float d_radius_sign = sign(1.0 - f); \n + bool is_well_behaved = !is_focal_on_circle && r1 > 1.0; \n + \n + float x_t = -1.0; \n + if (is_focal_on_circle) x_t = dot(pt, pt) / pt.x; \n + else if (is_well_behaved) x_t = length(pt) - pt.x * inv_r1; \n + else { \n + float temp = pt.x * pt.x - pt.y * pt.y; \n + if (temp >= 0.0) { \n + if (is_swapped || d_radius_sign < 0.0) { \n + x_t = -sqrt(temp) - pt.x * inv_r1; \n + } else { \n + x_t = sqrt(temp) - pt.x * inv_r1; \n + } \n + } \n + } \n + \n + if (!is_well_behaved && x_t < 0.0) return vec2(0.0, -1.0); \n + \n + float t = f + d_radius_sign * x_t; \n + if (is_swapped) t = 1.0 - t; \n + return vec2(t, 1.0); \n + } \n + } \n + \n void main() \n { \n vec2 pos = vPos; \n - float ba = uGradientInfo.radius.x; \n - float d = distance(uGradientInfo.centerPos, pos); \n - float t = (d / ba); \n - t = gradientWrap(t); \n - vec4 color = gradient(t, (d / ba), d); \n + vec2 res = compute_radial_t(uGradientInfo.centerPos.xy, \n + uGradientInfo.radius.x, \n + uGradientInfo.centerPos.zw, \n + uGradientInfo.radius.y, \n + pos); \n + if (res.y < 0.0) { \n + FragColor = vec4(0.0, 0.0, 0.0, 0.0); \n + return; \n + } \n + float t = gradientWrap(res.x); \n + vec4 color = gradient(t, res.x, length(pos - uGradientInfo.centerPos.xy)); \n FragColor = vec4(color.rgb * color.a, color.a); \n } );