From 0c2441ae7d13c31b7f07f091550293eaae743d0e Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 16 Jan 2024 19:19:46 +0100 Subject: [PATCH] prototyping --- GLMakie/assets/shader/line_segments.frag | 91 +++ GLMakie/assets/shader/lines.frag | 188 +++-- GLMakie/assets/shader/lines.geom | 937 +++++------------------ GLMakie/src/GLMakie.jl | 18 +- GLMakie/src/drawing_primitives.jl | 1 + GLMakie/src/glshaders/lines.jl | 3 +- 6 files changed, 452 insertions(+), 786 deletions(-) create mode 100644 GLMakie/assets/shader/line_segments.frag diff --git a/GLMakie/assets/shader/line_segments.frag b/GLMakie/assets/shader/line_segments.frag new file mode 100644 index 00000000000..3258941acc4 --- /dev/null +++ b/GLMakie/assets/shader/line_segments.frag @@ -0,0 +1,91 @@ +{{GLSL_VERSION}} +{{GLSL_EXTENSIONS}} +{{SUPPORTED_EXTENSIONS}} + +struct Nothing{ //Nothing type, to encode if some variable doesn't contain any data + bool _; //empty structs are not allowed +}; + +in vec4 f_color; +in vec2 f_uv; +in float f_thickness; +flat in uvec2 f_id; +flat in vec2 f_uv_minmax; +{{pattern_type}} pattern; + +uniform float pattern_length; +uniform bool fxaa; + +// Half width of antialiasing smoothstep +#define ANTIALIAS_RADIUS 0.8 + +float aastep(float threshold1, float dist) { + return smoothstep(threshold1-ANTIALIAS_RADIUS, threshold1+ANTIALIAS_RADIUS, dist); +} + +float aastep(float threshold1, float threshold2, float dist) { + // We use 2x pixel space in the geometry shaders which passes through + // in uv.y, so we need to treat it here by using 2 * ANTIALIAS_RADIUS + float AA = 2 * ANTIALIAS_RADIUS; + return smoothstep(threshold1 - AA, threshold1 + AA, dist) - + smoothstep(threshold2 - AA, threshold2 + AA, dist); +} + +float aastep_scaled(float threshold1, float threshold2, float dist) { + float AA = ANTIALIAS_RADIUS / pattern_length; + return smoothstep(threshold1 - AA, threshold1 + AA, dist) - + smoothstep(threshold2 - AA, threshold2 + AA, dist); +} + + +void write2framebuffer(vec4 color, uvec2 id); + +// Signed distance fields for lines +// x/y pattern +float get_sd(sampler2D pattern, vec2 uv){ + return texture(pattern, uv).x; +} + +// x pattern +vec2 get_sd(sampler1D pattern, vec2 uv){ + return vec2(texture(pattern, uv.x).x, uv.y); +} + +// normal line type +// Note that this just returns uv, so get full manual control in geom shader +vec2 get_sd(Nothing _, vec2 uv){ + return uv; +} + +void main(){ + vec4 color = vec4(f_color.rgb, 0.0); + vec2 xy = get_sd(pattern, f_uv); + + float alpha, alpha2, alpha3; + if (!fxaa) { + alpha = aastep(0.0, xy.x); + alpha2 = aastep(-f_thickness, f_thickness, xy.y); + alpha3 = aastep_scaled(f_uv_minmax.x, f_uv_minmax.y, f_uv.x); + } else { + alpha = step(0.0, xy.x); + alpha2 = step(-f_thickness, xy.y) - step(f_thickness, xy.y); + alpha3 = step(f_uv_minmax.x, f_uv.x) - step(f_uv_minmax.y, f_uv.x); + } + + color = vec4(f_color.rgb, f_color.a * alpha * alpha2 * alpha3); + + // Debug: Show uv values in line direction (repeating) + // color = vec4(mod(f_uv.x, 1.0), 0, 0, 1); + + // Debug: Show uv values in line direction with pattern + // color.r = 0.5; + // color.g = mod(f_uv.x, 1.0); + // color.b = mod(f_uv.x, 1.0); + // color.a = 0.2 + 0.8 * color.a; + + // Debug: Show AA padding in red + // color.r = 1 - color.a; + // color.a = 0.5 + 0.5 * color.a; + + write2framebuffer(color, f_id); +} \ No newline at end of file diff --git a/GLMakie/assets/shader/lines.frag b/GLMakie/assets/shader/lines.frag index 0de6518c2da..bf69aa62039 100644 --- a/GLMakie/assets/shader/lines.frag +++ b/GLMakie/assets/shader/lines.frag @@ -7,10 +7,17 @@ struct Nothing{ //Nothing type, to encode if some variable doesn't contain any d }; in vec4 f_color; -in vec2 f_uv; -in float f_thickness; +in vec3 f_quad_sdf; +in vec2 f_joint_cutoff; +in float f_line_width; +in float f_cumulative_length; flat in uvec2 f_id; -flat in vec2 f_uv_minmax; + +// in vec2 f_rect_sdf; +// in vec2 f_joint_smooth; +// flat in float f_line_length; +// flat in float f_line_offset; + {{pattern_type}} pattern; uniform float pattern_length; @@ -23,69 +30,148 @@ float aastep(float threshold1, float dist) { return smoothstep(threshold1-ANTIALIAS_RADIUS, threshold1+ANTIALIAS_RADIUS, dist); } -float aastep(float threshold1, float threshold2, float dist) { - // We use 2x pixel space in the geometry shaders which passes through - // in uv.y, so we need to treat it here by using 2 * ANTIALIAS_RADIUS - float AA = 2 * ANTIALIAS_RADIUS; - return smoothstep(threshold1 - AA, threshold1 + AA, dist) - - smoothstep(threshold2 - AA, threshold2 + AA, dist); +// Pattern sampling +float get_pattern_sdf(sampler2D pattern, vec2 uv){ + // make this texture repeating + // TODO + // vec2 uv2 = vec2((uv.x + f_line_offset) / pattern_length, 0.5 * uv.y / f_line_width); + return texture(pattern, uv).x; } - -float aastep_scaled(float threshold1, float threshold2, float dist) { - float AA = ANTIALIAS_RADIUS / pattern_length; - return smoothstep(threshold1 - AA, threshold1 + AA, dist) - - smoothstep(threshold2 - AA, threshold2 + AA, dist); +float get_pattern_sdf(sampler1D pattern, vec2 uv){ + // make this texture repeating + return texture(pattern, uv.x / pattern_length).x; +} +float get_pattern_sdf(Nothing _, vec2 uv){ + return -10.0; } - void write2framebuffer(vec4 color, uvec2 id); -// Signed distance fields for lines -// x/y pattern -float get_sd(sampler2D pattern, vec2 uv){ - return texture(pattern, uv).x; -} - -// x pattern -vec2 get_sd(sampler1D pattern, vec2 uv){ - return vec2(texture(pattern, uv.x).x, uv.y); -} -// normal line type -// Note that this just returns uv, so get full manual control in geom shader -vec2 get_sd(Nothing _, vec2 uv){ - return uv; -} +#define DEBUG void main(){ - vec4 color = vec4(f_color.rgb, 0.0); - vec2 xy = get_sd(pattern, f_uv); + // Metrics we need: + // line length for pattern + // line width sdf w/ AA + // line start/end/truncation sdf w/ AA + // hard join edge sdf + + + // New version + +#ifndef DEBUG + // We effectively start with a rectangle that's fully drawn, i.e. sdf < 0 + + // Remove overlap of rects at joint + // things that need to be cut have sdf >= 0 + if (max(f_joint_cutoff.x, f_joint_cutoff.y) >= 0.0) + discard; + + // smoothly cut out line start, end and edge of truncated joint (if applicable) + float sdf = max(f_quad_sdf.x, f_quad_sdf.y); + + // smoothly cut out edges at +- 0.5 * line width + sdf = max(sdf, abs(f_quad_sdf.z) - f_line_width); + + // draw + vec4 color = f_color; - float alpha, alpha2, alpha3; if (!fxaa) { - alpha = aastep(0.0, xy.x); - alpha2 = aastep(-f_thickness, f_thickness, xy.y); - alpha3 = aastep_scaled(f_uv_minmax.x, f_uv_minmax.y, f_uv.x); + color.a *= aastep(0.0, -sdf); } else { - alpha = step(0.0, xy.x); - alpha2 = step(-f_thickness, xy.y) - step(f_thickness, xy.y); - alpha3 = step(f_uv_minmax.x, f_uv.x) - step(f_uv_minmax.y, f_uv.x); + color.a *= step(0.0, -sdf); + } + + // float aa = aastep(0.0, sdf); + // vec4 color = vec4(0.0, 0.7, 0.1, 1); + // color.rgb = mix(color.rgb, vec3(1,0,0), aa); + +#endif + + + // 1. and 2. probably not mergeable because 2 needs to be an actual sdf that + // can be cut off smoothly + + // discard join edge + // seems like a vert/geom shader thing + // if (f_joint_cutoff.x > 0.0 || f_joint_cutoff.y > 0.0) { + // // discard; + // } + + // signed distance to edge of rect (line without join) + // - start/end need rect_sdf to be 0 at p1/p2 (could be done with f_joint_smooth) + // - pattern needs rect_sdf to be 0 at p1 + // - joins need rect_sdf to extend/ignore x + // ^- probably possible by merging sdfs correctly? + // - max(min(rect.x, smooth.x), min(rect.x, smooth.y))? + // - yea but necessitates smooth >= 0.0 outside of rect sdf + // float sdf = max(abs(f_rect_sdf.x) - f_line_length, abs(f_rect_sdf.y) - f_line_width); + + // smooth out truncated join + // sdf = max(sdf, dot(gl_Position.xy - P1, miter_n_a) - miter_a_offset); + // sdf = max(sdf, max(f_joint_smooth.x, f_joint_smooth.y)); + + // fix up line pattern fetch + // float pattern_sdf = texture(pattern, mod(f_rect_sdf.x + line_length_offset, pattern_length) / pattern_length ); + // sdf = max(sdf, get_pattern_sdf(pattern, f_rect_sdf)); + + // draw + // vec4 color = f_color; + + // if (fxaa) + // color.a *= aastep(0.0, sdf); + // else + // color.a *= step(0.0, sdf); + + + + +#ifdef DEBUG + // show geom in white + vec4 color = vec4(1, 1, 1, 0.5); + + // show line smooth clipping from quad_sdf + float sdf_width = abs(f_quad_sdf.z) - f_line_width; + color.r = 0.9 - 0.5 * aastep(0.0, sdf_width); + color.b = 0.9 - 0.5 * aastep(0.0, f_quad_sdf.y); + color.g = 0.9 - 0.5 * aastep(0.0, f_quad_sdf.x); + + // show how the joint overlap is cut off + if (max(f_joint_cutoff.x, f_joint_cutoff.y) > 0.0) { + color.r += 0.3; + color.gb -= vec2(0.3); } +#endif - color = vec4(f_color.rgb, f_color.a * alpha * alpha2 * alpha3); - // Debug: Show uv values in line direction (repeating) - // color = vec4(mod(f_uv.x, 1.0), 0, 0, 1); + /* + // show geom in black + vec4 color = vec4(0,0,0,0.6); - // Debug: Show uv values in line direction with pattern - // color.r = 0.5; - // color.g = mod(f_uv.x, 1.0); - // color.b = mod(f_uv.x, 1.0); - // color.a = 0.2 + 0.8 * color.a; + // show rect sdf in red + float rect = max(abs(f_rect_sdf.x) - f_line_length, abs(f_rect_sdf.y) - f_line_width); + color.r = 0.5 * aastep(0.0, -rect); - // Debug: Show AA padding in red - // color.r = 1 - color.a; - // color.a = 0.5 + 0.5 * color.a; + // sharp join cutoff - color yellow + // if (max(f_joint_cutoff.x, f_joint_cutoff.y) > 0.0) { + // color.rg += vec2(0.7); + // } + + // smooth cutoff - mark in blue what's considered outside + color.b = aastep(0.0, min(f_joint_smooth.x, f_joint_smooth.y)); + + // what's inside that wasn't before? + // rect > 0 and smooth < 0 + + // color.b = aastep(0.0, f_joint_smooth.x); + // color.b = aastep(0.0, f_joint_smooth.y); + + // color.g = 0.5 * aastep(0.0, f_joint_smooth.x); + // color.b = 0.5 * aastep(0.0, f_joint_smooth.y); + // float smooth_join = max(min(rect.x, f_joint_smooth.x), min(rect.x, f_joint_smooth.y)); + // color.rg = vec2(0.5, 0.5) * aastep(0.0, smooth_join); + */ write2framebuffer(color, f_id); -} +} \ No newline at end of file diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index f5683063430..eb968bd0cb9 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -9,7 +9,7 @@ struct Nothing{ //Nothing type, to encode if some variable doesn't contain any d {{define_fast_path}} layout(lines_adjacency) in; -layout(triangle_strip, max_vertices = 11) out; +layout(triangle_strip, max_vertices = 4) out; in vec4 g_color[]; in float g_lastlen[]; @@ -18,756 +18,136 @@ in int g_valid_vertex[]; in float g_thickness[]; out vec4 f_color; -out vec2 f_uv; -out float f_thickness; +out vec3 f_quad_sdf; // smooth edges (along length and width) +out vec2 f_joint_cutoff; // hard edges (joint) +out float f_line_width; +out float f_cumulative_length; flat out uvec2 f_id; -flat out vec2 f_uv_minmax; out vec3 o_view_pos; out vec3 o_view_normal; uniform vec2 resolution; -uniform float pattern_length; -uniform sampler1D pattern_sections; - -float px2uv = 0.5 / pattern_length; // Constants #define MITER_LIMIT -0.4 #define AA_THICKNESS 4 -vec3 screen_space(vec4 vertex) -{ - return vec3(vertex.xy * resolution, vertex.z) / vertex.w; +vec3 screen_space(vec4 vertex) { + return vec3((0.5 * vertex.xy + 0.5) * resolution, vertex.z) / vertex.w; } //////////////////////////////////////////////////////////////////////////////// -/// Emit Vertex Methods +/// new version //////////////////////////////////////////////////////////////////////////////// +/* +How it works: +1. geom shader generates a large enough quad: + - width: max(linewidth) + AA pad + - length: line segment length + join pad + AA pad +2. fragment shader generates SDF and takes care of AA, clean line joins + - generate rect sdf matching line segment w/o truncation but with join extension + - adjust sdf to truncate join without AA +*/ + +struct LineData { + vec3 p1, p2, v; + float segment_length, extrusion_a, extrusion_b; + vec2 n, miter_v_a, miter_n_a, miter_v_b, miter_n_b; + float miter_offset_a, miter_offset_b; + bool is_start, is_end; +}; -// Manual uv calculation -// - position in screen space (double resolution as generally used) -// - uv with uv.u normalized (0..1), uv.v unnormalized (0..pattern_length) -void emit_vertex(vec3 position, vec2 uv, int index, float thickness) -{ - f_uv = uv; - f_color = g_color[index]; - gl_Position = vec4((position.xy / resolution), position.z, 1.0); - f_id = g_id[index]; - // linewidth scaling may shrink the effective linewidth - f_thickness = thickness; - EmitVertex(); -} -// default for miter joins -void emit_vertex(vec3 position, vec2 uv, int index) -{ - emit_vertex(position, uv, index, g_thickness[index]); -} - -// For center point -void emit_vertex(vec3 position, vec2 uv) -{ - f_uv = uv; - f_color = 0.5 * (g_color[1] + g_color[2]); - gl_Position = vec4((position.xy / resolution), position.z, 1.0); - f_id = g_id[1]; - f_thickness = 0.5 * (g_thickness[1] + g_thickness[2]); - EmitVertex(); -} - -// Debug -void emit_vertex(vec3 position, vec2 uv, int index, vec4 color, float thickness) -{ - f_uv = uv; - f_color = color; - gl_Position = vec4((position.xy / resolution), position.z, 1.0); - f_id = g_id[index]; - f_thickness = thickness; - EmitVertex(); -} -// default for miter joins -void emit_vertex(vec3 position, vec2 uv , int index, vec4 color) -{ - emit_vertex(position, uv , index, color, g_thickness[index]); -} -void emit_vertex(vec3 position, vec2 uv, vec4 color) -{ - f_uv = uv; - f_color = color; - gl_Position = vec4((position.xy / resolution), position.z, 1.0); - f_id = g_id[1]; - f_thickness = 0.5 * (g_thickness[1] + g_thickness[2]); - EmitVertex(); -} - +void emit_vertex(vec3 origin, vec2 center, LineData line, int index, vec2 geom_offset) { + vec3 position = origin + geom_offset.x * line.v + vec3(geom_offset.y * line.n, 0); -// With offset calculations for core line segment -void emit_vertex(vec3 position, vec2 offset, vec2 line_dir, vec2 uv, int index) -{ - emit_vertex( - position + vec3(offset, 0), - vec2(uv.x + px2uv * dot(line_dir, offset), uv.y), - index, - abs(uv.y) - AA_THICKNESS - ); - // `abs(uv.y) - AA_THICKNESS` corrects for enlarged AA padding between - // segments of different linewidth, see #2953 -} + // sdf prototyping -void emit_vertex(vec3 position, vec2 offset, vec2 line_dir, vec2 uv) -{ - emit_vertex( - position + vec3(offset, 0), - vec2(uv.x + px2uv * dot(line_dir, offset), uv.y) + // joint hard cutoff + // if line start/end move hard cutoff out so smooth rect cutoff can act + vec2 VP1 = position.xy - line.p1.xy; + vec2 VP2 = position.xy - line.p2.xy; + f_joint_cutoff = vec2( + line.is_start ? -10.0 : dot(VP1, -line.miter_v_a), + line.is_end ? -10.0 : dot(VP2, line.miter_v_b) ); -} - - -//////////////////////////////////////////////////////////////////////////////// -/// Draw full line segment -//////////////////////////////////////////////////////////////////////////////// - - -// Generate line segment with 3 triangles -// - p1, p2 are the line start and end points in pixel space -// - miter_a and miter_b are the offsets from p1 and p2 respectively that -// generate the line segment quad. This should include thickness and AA -// - u1, u2 are the u values at p1 and p2. These should be in uv scale (px2uv applied) -// - thickness_aa1, thickness_aa2 are linewidth at p1 and p2 with AA added. They -// double as uv.y values, which are in pixel space -// - v1 is the line direction of this segment (xy component) -void generate_line_segment( - vec3 p1, vec2 miter_a, float u1, float thickness_aa1, - vec3 p2, vec2 miter_b, float u2, float thickness_aa2, - vec2 v1, float segment_length - ) -{ - float line_offset_a = dot(miter_a, v1); - float line_offset_b = dot(miter_b, v1); - - if (abs(line_offset_a) + abs(line_offset_b) < segment_length+1){ - // _________ - // \ / - // \_____/ - // <---> - // Line segment is extensive (minimum width positive) - - emit_vertex(p1, +miter_a, v1, vec2(u1, -thickness_aa1), 1); - emit_vertex(p1, -miter_a, v1, vec2(u1, thickness_aa1), 1); - emit_vertex(p2, +miter_b, v1, vec2(u2, -thickness_aa2), 2); - emit_vertex(p2, -miter_b, v1, vec2(u2, thickness_aa2), 2); - } else { - // ____ - // \ / - // \/ - // /\ - // >--< - // Line segment has zero or negative width on short side - - // Pulled apart, we draw these two triangles (vertical lines added) - // ___ ___ - // \ | | / - // X | | X - // \| |/ - // - // where X is u1/p1 (left) and u2/p2 (right) respectively. To avoid - // drawing outside the line segment due to AA padding, we cut off the - // left triangle on the right side at u2 via f_uv_minmax.y, and - // analogously the right triangle at u1 via f_uv_minmax.x. - // These triangles will still draw over each other like this. - - // incoming side - float old = f_uv_minmax.y; - f_uv_minmax.y = u2; - - emit_vertex(p1, -miter_a, v1, vec2(u1, -thickness_aa1), 1); - emit_vertex(p1, +miter_a, v1, vec2(u1, +thickness_aa1), 1); - if (line_offset_a > 0){ // finish triangle on -miter_a side - emit_vertex(p1, 2 * line_offset_a * v1 - miter_a, v1, vec2(u1, -thickness_aa1)); - } else { - emit_vertex(p1, -2 * line_offset_a * v1 + miter_a, v1, vec2(u1, +thickness_aa1)); - } - - EndPrimitive(); - - // outgoing side - f_uv_minmax.x = u1; - f_uv_minmax.y = old; - - emit_vertex(p2, -miter_b, v1, vec2(u2, -thickness_aa2), 2); - emit_vertex(p2, +miter_b, v1, vec2(u2, +thickness_aa2), 2); - if (line_offset_b < 0){ // finish triangle on -miter_b side - emit_vertex(p2, 2 * line_offset_b * v1 - miter_b, v1, vec2(u2, -thickness_aa2)); - } else { - emit_vertex(p2, -2 * line_offset_b * v1 + miter_b, v1, vec2(u2, +thickness_aa2)); - } - } -} - -// Debug Version -// Generates more triangles and colors them individually so they can be differentiated -void generate_line_segment_debug( - vec3 p1, vec2 miter_a, float u1, float thickness_aa1, - vec3 p2, vec2 miter_b, float u2, float thickness_aa2, - vec2 v1, float segment_length - ) -{ - float line_offset_a = dot(miter_a, v1); - float line_offset_b = dot(miter_b, v1); - - if (abs(line_offset_a) + abs(line_offset_b) < segment_length + 1 ){ - emit_vertex(p1 - vec3(miter_a, 0), vec2(u1 - px2uv * dot(v1, miter_a), thickness_aa1), 1, vec4(1, 0, 0, 0.5)); - emit_vertex(p1 + vec3(miter_a, 0), vec2(u1 + px2uv * dot(v1, miter_a), -thickness_aa1), 1, vec4(1, 0, 0, 0.5)); - emit_vertex(p2 - vec3(miter_b, 0), vec2(u2 - px2uv * dot(v1, miter_b), thickness_aa2), 2, vec4(1, 0, 0, 0.5)); - - EndPrimitive(); - - emit_vertex(p1 + vec3(miter_a, 0), vec2(u1 + px2uv * dot(v1, miter_a), -thickness_aa1), 1, vec4(0, 0, 1, 0.5)); - emit_vertex(p2 - vec3(miter_b, 0), vec2(u2 - px2uv * dot(v1, miter_b), thickness_aa2), 2, vec4(0, 0, 1, 0.5)); - emit_vertex(p2 + vec3(miter_b, 0), vec2(u2 + px2uv * dot(v1, miter_b), -thickness_aa2), 2, vec4(0, 0, 1, 0.5)); - - // Mid point version - /* - vec3 pc = 0.5 * (p1 + p2); - vec2 miter_c = 0.5 * (miter_a + miter_b); - float uc = 0.5 * (u1 + u2); - float thickness_aac = 0.5 * (thickness_aa1 + thickness_aa2); - - if (dot(miter_a, v1) < dot(miter_b, v1)){ - emit_vertex(p1 + vec3(miter_a, 0), vec2(u1 + px2uv * dot(v1, miter_a), -thickness_aa1), 1, vec4(1, 0, 0, 0.5)); - emit_vertex(p1 - vec3(miter_a, 0), vec2(u1 - px2uv * dot(v1, miter_a), thickness_aa1), 1, vec4(1, 0, 0, 0.5)); - emit_vertex(pc + vec3(miter_c, 0), vec2(uc + px2uv * dot(v1, miter_c), -thickness_aac), vec4(1, 0, 0, 0.5)); - - EndPrimitive(); - - emit_vertex(p1 - vec3(miter_a, 0), vec2(u1 - px2uv * dot(v1, miter_a), thickness_aa1), 1, vec4(0, 1, 0, 0.5)); - emit_vertex(pc + vec3(miter_c, 0), vec2(uc + px2uv * dot(v1, miter_c), -thickness_aac), vec4(0, 1, 0, 0.5)); - emit_vertex(p2 - vec3(miter_b, 0), vec2(u2 - px2uv * dot(v1, miter_b), thickness_aa2), 2, vec4(0, 1, 0, 0.5)); - - EndPrimitive(); - - emit_vertex(pc + vec3(miter_c, 0), vec2(uc + px2uv * dot(v1, miter_c), -thickness_aac), vec4(0, 0, 1, 0.5)); - emit_vertex(p2 - vec3(miter_b, 0), vec2(u2 - px2uv * dot(v1, miter_b), thickness_aa2), 2, vec4(0, 0, 1, 0.5)); - emit_vertex(p2 + vec3(miter_b, 0), vec2(u2 + px2uv * dot(v1, miter_b), -thickness_aa2), 2, vec4(0, 0, 1, 0.5)); - - } else { - // subtractive side has more space - emit_vertex(p1 - vec3(miter_a, 0), vec2(u1 - px2uv * dot(v1, miter_a), -thickness_aa1), 1, vec4(1, 0, 0, 0.5)); - emit_vertex(p1 + vec3(miter_a, 0), vec2(u1 + px2uv * dot(v1, miter_a), thickness_aa1), 1, vec4(1, 0, 0, 0.5)); - emit_vertex(pc - vec3(miter_c, 0), vec2(uc - px2uv * dot(v1, miter_c), -thickness_aac), vec4(1, 0, 0, 0.5)); - - EndPrimitive(); - - emit_vertex(p1 + vec3(miter_a, 0), vec2(u1 + px2uv * dot(v1, miter_a), thickness_aa1), 1, vec4(0, 1, 0, 0.5)); - emit_vertex(pc - vec3(miter_c, 0), vec2(uc - px2uv * dot(v1, miter_c), -thickness_aac), vec4(0, 1, 0, 0.5)); - emit_vertex(p2 + vec3(miter_b, 0), vec2(u2 + px2uv * dot(v1, miter_b), thickness_aa2), 2, vec4(0, 1, 0, 0.5)); - - EndPrimitive(); - - emit_vertex(pc - vec3(miter_c, 0), vec2(uc - px2uv * dot(v1, miter_c), -thickness_aac), vec4(0, 0, 1, 0.5)); - emit_vertex(p2 + vec3(miter_b, 0), vec2(u2 + px2uv * dot(v1, miter_b), thickness_aa2), 2, vec4(0, 0, 1, 0.5)); - emit_vertex(p2 - vec3(miter_b, 0), vec2(u2 - px2uv * dot(v1, miter_b), -thickness_aa2), 2, vec4(0, 0, 1, 0.5)); - } - */ - } else { - // incoming side - float old = f_uv_minmax.y; - f_uv_minmax.y = u2; - - emit_vertex(p1 - vec3(miter_a, 0), vec2(u1 - px2uv * dot(v1, miter_a), -thickness_aa1), 1, vec4(1, 0, 0, 0.5)); - emit_vertex(p1 + vec3(miter_a, 0), vec2(u1 + px2uv * dot(v1, miter_a), thickness_aa1), 1, vec4(1, 0, 0, 0.5)); - if (line_offset_a > 0){ // finish triangle on -miter_a side - emit_vertex( - p1 + vec3(2 * line_offset_a * v1 - miter_a, 0), - vec2(u1 + px2uv * (2 * line_offset_a - dot(v1, miter_a)), -thickness_aa1), - 1, vec4(1, 0, 0, 0.5) - ); - } else { - emit_vertex( - p1 + vec3(-2 * line_offset_a * v1 + miter_a, 0), - vec2(u1 + px2uv * (-2 * line_offset_a + dot(v1, miter_a)), thickness_aa1), - 1, vec4(1, 0, 0, 0.5) - ); - } - - EndPrimitive(); - f_uv_minmax.x = u1; - f_uv_minmax.y = old; - - // outgoing side - emit_vertex(p2 - vec3(miter_b, 0), vec2(u2 - px2uv * dot(v1, miter_b), -thickness_aa2), 2, vec4(0, 0, 1, 0.5)); - emit_vertex(p2 + vec3(miter_b, 0), vec2(u2 + px2uv * dot(v1, miter_b), thickness_aa2), 2, vec4(0, 0, 1, 0.5)); - if (line_offset_b < 0){ // finish triangle on -miter_b side - emit_vertex( - p2 + vec3(2 * line_offset_b * v1 - miter_b, 0), - vec2(u2 + px2uv * (2 * line_offset_b - dot(v1, miter_b)), -thickness_aa2), - 2, vec4(0, 0, 1, 0.5) - ); - } else { - emit_vertex( - p2 + vec3(-2 * line_offset_b * v1 + miter_b, 0), - vec2(u2 + px2uv * (-2 * line_offset_b + dot(v1, miter_b)), thickness_aa2), - 2, vec4(0, 0, 1, 0.5) - ); - } - } -} - - -//////////////////////////////////////////////////////////////////////////////// -/// Patterned line -//////////////////////////////////////////////////////////////////////////////// - - - -void draw_patterned_line(bool isvalid[4]) -{ - // This sets a min and max value foir uv.u at which anti-aliasing is forced. - // With this setting it's never triggered. - f_uv_minmax = vec2(-1.0e12, 1.0e12); - - // get the four vertices passed to the shader - // without FAST_PATH the conversions happen on the CPU - vec3 p0 = gl_in[0].gl_Position.xyz; // start of previous segment - vec3 p1 = gl_in[1].gl_Position.xyz; // end of previous segment, start of current segment - vec3 p2 = gl_in[2].gl_Position.xyz; // end of current segment, start of next segment - vec3 p3 = gl_in[3].gl_Position.xyz; // end of next segment - - // linewidth with padding for anti aliasing - float thickness_aa1 = g_thickness[1] + AA_THICKNESS; - float thickness_aa2 = g_thickness[2] + AA_THICKNESS; - - // determine the direction of each of the 3 segments (previous, current, next) - vec3 v1 = p2 - p1; - float segment_length = length(v1.xy); - v1 /= segment_length; - vec3 v0 = v1; - vec3 v2 = v1; - - if (p1 != p0 && isvalid[0]) { - v0 = (p1 - p0) / length((p1 - p0).xy); - } - if (p3 != p2 && isvalid[3]) { - v2 = (p3 - p2) / length((p3 - p2).xy); - } - - // determine the normal of each of the 3 segments (previous, current, next) - vec2 n0 = vec2(-v0.y, v0.x); - vec2 n1 = vec2(-v1.y, v1.x); - vec2 n2 = vec2(-v2.y, v2.x); - - // The pattern may look like this: - // - // pattern_sections index - // 0 1 2 3 - // |########| |####| |(repeat) - // left right left right - // variable in loop - // - // We first figure out the extended size of this line segment, starting - // from the left end of the first relevant pattern section and ending at the - // right end of the last relevant pattern section. E.g.: - // - // g_lastlen[1] g_lastlen[2] (2x pixel coords) - // edge1 edge2 (1x pixel coords) - // | | - // |####| |########| |####| |########| |####| |########| - // | first | | last | - // | pattern| | pattern| - // | section| | section| - // start stop (pattern coords (normalized)) - // - // start_width and stop_width are the widths of the start and stop sections. - float start, stop, start_width, stop_width, temp; - float left, right, edge1, edge2, inv_pl, left_offset, right_offset; - - // normalized single pixel scale - start = g_lastlen[2] * px2uv; - stop = g_lastlen[1] * px2uv; - start_width = 0.0; - stop_width = 0.0; - - inv_pl = 1.0 / pattern_length; - edge1 = 0.5 * g_lastlen[1]; - edge2 = 0.5 * g_lastlen[2]; - - int pattern_texsize = textureSize(pattern_sections, 0); - - for (int i = 0; i < pattern_texsize - 1; i = i + 2) - { - left = texelFetch(pattern_sections, i, 0).x; - right = texelFetch(pattern_sections, i+1, 0).x; - - // update left side - temp = ceil((edge1 - right) * inv_pl) + left * inv_pl; - if (temp < start) - start_width = right - left; - start = min(start, temp); - - // update right side - temp = floor((edge2 - left) * inv_pl) + right * inv_pl; - if (temp > stop) - stop_width = right - left; - stop = max(stop, temp); - } - // Technically start and stop should be offset by another - // 1 / (2 * textureSize(pattern)) so the line segment is normalized to - // pattern texel centers rather than the left edge, but we have enough - // AA_THICKNESS for it to be irrelevant. - - // if there is something to draw... - if (stop > start){ - - // setup for sharp corners - // miter_a / miter_b - // ___ ↑ ___ - // | .|. | - // length_a _|_ .' | '. _|_ length_b - // .' | '. - // .' | '. - // .' .' '. '. - // .' '. - // - vec2 miter_a = normalize(n0 + n1); - vec2 miter_b = normalize(n1 + n2); - float length_a = 1.0 / dot(miter_a, n1); - float length_b = 1.0 / dot(miter_b, n1); - - // if we have a sharp corner: - // max(g_thickness[1], proj(length_a * miter_a, v1)) without AA padding - // otherwise just g_thickness[1] - left_offset = g_thickness[1] * max(1.0, float(dot(v0.xy, v1.xy) >= MITER_LIMIT) * abs(dot(miter_a, v1.xy)) * length_a); - right_offset = g_thickness[2] * max(1.0, float(dot(v1.xy, v2.xy) >= MITER_LIMIT) * abs(dot(miter_b, v1.xy)) * length_b); - - // Finish length_a/b - length_a *= thickness_aa1; - length_b *= thickness_aa2; - - // if the "on" section of the pattern at start extends over the whole - // potential corner we draw the corner. If not we extend the line. - // - // g_lastlen[1] - // . - |---.---------- - // : | : - // : | : - // : | : - // : - '---:---------- - // start : - // start + start_width - // - // Equivalent to - // (start * pattern_length < g_lastlen[1] - left_offset) && - // (start * pattern_length + 2 * start_width > g_lastlen[1] + left_offset) - if ( - isvalid[0] && - abs(2 * start * pattern_length - g_lastlen[1] + start_width) < (start_width - left_offset) - ) - { - // if the corner is too sharp, we do a truncated miter join - // ----------c. - // ----------a.'. - // | '.'. - // x_ '.'. - // ------. '--b d - // / / / - // / / / - // - // x is the point the two line segments meet (here p1) - // a, b are the outer corners of the line segments - // a, b, x define the triangle we need to fill to make the line continuous - // c, d are a, b with padding for AA included - // Note that the padding generated by c, d is reduced on the triangle - // so we need to add another rectangle there to ensure enough padding - if( dot( v0.xy, v1.xy ) < MITER_LIMIT ){ - - bool gap = dot( v0.xy, n1 ) > 0; - - // Another view of a truncated join (with lines joining like a V). - // - // uv.y = 0 in line segment - // / - // . -- uv.x = u0 in truncated join - // .' '. uv.y = thickness in line segment - // .' '. / uv.y = thickness + AA_THICKNESS in line segment - // .'_________'. /_ uv.x = start in truncated join (constraint for AA) - // .'_____________'. _ uv.x = -proj_AA in truncated join (derived from line segment + constraint) - // | | - // |_______________| _ uv.x = -proj_AA - AA_THICKNESS in truncated join - // - // Here the / annotations come from the connecting line segment and are to - // be viewed on the diagonal. The -- and _ annotations are relevant to the - // truncated join and viewed vertically. - // Note that `start` marks off-to-on edge in the pattern. So values - // greater than `start` will be drawn and smaller will be discarded. - // With how we pick start and get in this branch u0 will always be - // in a solidly drawn region of the pattern. - float u0 = start + thickness_aa1 * abs(dot(miter_a, n1)) * px2uv; - float proj_AA = start - AA_THICKNESS * abs(dot(miter_a, n1)) * px2uv; - - // to save some space - vec2 off0 = thickness_aa1 * n0; - vec2 off1 = thickness_aa1 * n1; - vec2 off_AA = AA_THICKNESS * miter_a; - float u_AA = AA_THICKNESS * px2uv; - - if(gap){ - emit_vertex(p1, vec2(u0, 0), 1); - emit_vertex(p1 + vec3(off0, 0), vec2(proj_AA, +thickness_aa1), 1); - emit_vertex(p1 + vec3(off1, 0), vec2(proj_AA, -thickness_aa1), 1); - emit_vertex(p1 + vec3(off0 + off_AA, 0), vec2(proj_AA - u_AA, +thickness_aa1), 1); - emit_vertex(p1 + vec3(off1 + off_AA, 0), vec2(proj_AA - u_AA, -thickness_aa1), 1); - EndPrimitive(); - }else{ - emit_vertex(p1, vec2(u0, 0), 1); - emit_vertex(p1 - vec3(off1, 0), vec2(proj_AA, +thickness_aa1), 1); - emit_vertex(p1 - vec3(off0, 0), vec2(proj_AA, -thickness_aa1), 1); - emit_vertex(p1 - vec3(off1 + off_AA, 0), vec2(proj_AA - u_AA, +thickness_aa1), 1); - emit_vertex(p1 - vec3(off0 + off_AA, 0), vec2(proj_AA - u_AA, -thickness_aa1), 1); - EndPrimitive(); - } - - miter_a = n1; - length_a = thickness_aa1; - start = g_lastlen[1] * px2uv; - - } else { // otherwise we do a sharp join - start = g_lastlen[1] * px2uv; - } - } else { - // We don't need to treat the join, so resize the line segment to - // the drawn region. (This may extend the line too) - miter_a = n1; - length_a = thickness_aa1; - // If the line starts with this segment or the center of the "on" - // section of the pattern is in this segment, we draw it, else - // we skip past the first "on" section. - if (!isvalid[0] || (start > (g_lastlen[1] - start_width) * px2uv)) - start = start - AA_THICKNESS * px2uv; - else - start = start + (start_width + 0.5 * AA_THICKNESS) * inv_pl; - p1 += (2 * start * pattern_length - g_lastlen[1]) * v1; - } - - - // The other end of the line is analogous - // (stop * pattern_length - 2 * stop_width < g_lastlen[2] - right_offset) && - // (stop * pattern_length > g_lastlen[2] + right_offset) - // (stop * pattern_length - stop_width - g_lastlen[2] < (stop_width - right_offset)) && - // (stop * pattern_length - stop_width - g_lastlen[2] > -(stop_width - right_offset)) - if ( - isvalid[3] && - abs(2*stop * pattern_length - g_lastlen[2] - stop_width) < (stop_width - right_offset) - ) - { - if( dot( v1.xy, v2.xy ) < MITER_LIMIT ){ - // setup for truncated join (flat line end) - miter_b = n1; - length_b = thickness_aa2; - stop = g_lastlen[2] * px2uv; - } else { - // setup for sharp join - stop = g_lastlen[2] * px2uv; - } - } else { - miter_b = n1; - length_b = thickness_aa2; - if (isvalid[3] && (stop > (g_lastlen[2] + stop_width) * px2uv)) - stop = stop - (stop_width + 0.5 * AA_THICKNESS) * inv_pl; - else - stop = stop + AA_THICKNESS * px2uv; - p2 += (2 * stop * pattern_length - g_lastlen[2]) * v1; - } - - // to save some space - miter_a *= length_a; - miter_b *= length_b; - - // If this segment starts or ends a line we force anti-aliasing to - // happen at the respective edge. - if (!isvalid[0]) - f_uv_minmax.x = g_lastlen[1] * px2uv; - if (!isvalid[3]) - f_uv_minmax.y = g_lastlen[2] * px2uv; - - // generate rectangle for this segment - - // Normal Version - generate_line_segment( - p1, miter_a, start, thickness_aa1, - p2, miter_b, stop, thickness_aa2, - v1.xy, segment_length - ); - - // Debug - show each triangle - // generate_line_segment_debug( - // p1, miter_a, start, thickness_aa1, - // p2, miter_b, stop, thickness_aa2, - // v1.xy, segment_length - // ); + // note miter_v_a and miter_v_b are different, so no -width .. + width merge + // also no max - } - return; -} - - - -//////////////////////////////////////////////////////////////////////////////// -/// Solid lines -//////////////////////////////////////////////////////////////////////////////// - - - -void draw_solid_line(bool isvalid[4]) -{ - // This sets a min and max value foir uv.u at which anti-aliasing is forced. - // With this setting it's never triggered. - f_uv_minmax = vec2(-1.0e12, 1.0e12); - - // get the four vertices passed to the shader - // without FAST_PATH the conversions happen on the CPU - vec3 p0 = screen_space(gl_in[0].gl_Position); // start of previous segment - vec3 p1 = screen_space(gl_in[1].gl_Position); // end of previous segment, start of current segment - vec3 p2 = screen_space(gl_in[2].gl_Position); // end of current segment, start of next segment - vec3 p3 = screen_space(gl_in[3].gl_Position); // end of next segment - - // determine the direction of each of the 3 segments (previous, current, next) - vec3 v1 = p2 - p1; - float segment_length = length(v1.xy); - v1 /= segment_length; - vec3 v0 = v1; - vec3 v2 = v1; - - if (p1 != p0 && isvalid[0]) { - v0 = (p1 - p0) / length((p1 - p0).xy); - } - if (p3 != p2 && isvalid[3]) { - v2 = (p3 - p2) / length((p3 - p2).xy); - } - - // determine the normal of each of the 3 segments (previous, current, next) - vec2 n0 = vec2(-v0.y, v0.x); - vec2 n1 = vec2(-v1.y, v1.x); - vec2 n2 = vec2(-v2.y, v2.x); - - // determine stretching of AA border due to linewidth change - float temp = (g_thickness[2] - g_thickness[1]) / segment_length; - float edge_scale = sqrt(1 + temp * temp); - - // linewidth with padding for anti aliasing (used for geometry) - float thickness_aa1 = g_thickness[1] + edge_scale * AA_THICKNESS; - float thickness_aa2 = g_thickness[2] + edge_scale * AA_THICKNESS; - - // Setup for sharp corners (see above) - vec2 miter_a = normalize(n0 + n1); - vec2 miter_b = normalize(n1 + n2); - float length_a = thickness_aa1 / dot(miter_a, n1); - float length_b = thickness_aa2 / dot(miter_b, n1); - - // truncated miter join (see above) - if( dot( v0.xy, v1.xy ) < MITER_LIMIT ){ - bool gap = dot( v0.xy, n1 ) > 0; - // In this case uv's are used as signed distance field values, so we - // want 0 where we had start before. - float u0 = thickness_aa1 * abs(dot(miter_a, n1)) * px2uv; - float proj_AA = AA_THICKNESS * abs(dot(miter_a, n1)) * px2uv; - - // to save some space - vec2 off0 = thickness_aa1 * n0; - vec2 off1 = thickness_aa1 * n1; - vec2 off_AA = AA_THICKNESS * miter_a; - float u_AA = AA_THICKNESS * px2uv; - - if(gap){ - emit_vertex(p1, vec2(+ u0, 0), 1); - emit_vertex(p1 + vec3(off0, 0), vec2(- proj_AA, +thickness_aa1), 1); - emit_vertex(p1 + vec3(off1, 0), vec2(- proj_AA, -thickness_aa1), 1); - emit_vertex(p1 + vec3(off0 + off_AA, 0), vec2(- proj_AA - u_AA, +thickness_aa1), 1); - emit_vertex(p1 + vec3(off1 + off_AA, 0), vec2(- proj_AA - u_AA, -thickness_aa1), 1); - EndPrimitive(); - }else{ - emit_vertex(p1, vec2(+ u0, 0), 1); - emit_vertex(p1 - vec3(off1, 0), vec2(- proj_AA, +thickness_aa1), 1); - emit_vertex(p1 - vec3(off0, 0), vec2(- proj_AA, -thickness_aa1), 1); - emit_vertex(p1 - vec3(off1 + off_AA, 0), vec2(- proj_AA - u_AA, +thickness_aa1), 1); - emit_vertex(p1 - vec3(off0 + off_AA, 0), vec2(- proj_AA - u_AA, -thickness_aa1), 1); - EndPrimitive(); - } - - miter_a = n1; - length_a = thickness_aa1; - } - - // we have miter join on next segment, do normal line cut off - if( dot( v1.xy, v2.xy ) <= MITER_LIMIT ){ - miter_b = n1; - length_b = thickness_aa2; - } - - // Without a pattern (linestyle) we use uv.u directly as a signed distance - // field. We only care about u1 - u0 being the correct distance and - // u0 > AA_THICHKNESS at all times. - float u1 = 10000.0; - float u2 = u1 + segment_length; - - miter_a *= length_a; - miter_b *= length_b; - - // To treat line starts and ends we elongate the line in the respective - // direction and enforce an AA border at the original start/end position - // with f_uv_minmax. - if (!isvalid[0]) - { - float corner_offset = max(0, abs(dot(miter_b, v1.xy)) - segment_length); - f_uv_minmax.x = px2uv * (u1 - corner_offset); - p1 -= (corner_offset + AA_THICKNESS) * v1; - u1 -= (corner_offset + AA_THICKNESS); - segment_length += corner_offset; - } - - if (!isvalid[3]) - { - float corner_offset = max(0, abs(dot(miter_a, v1.xy)) - segment_length); - f_uv_minmax.y = px2uv * (u2 + corner_offset); - p2 += (corner_offset + AA_THICKNESS) * v1; - u2 += (corner_offset + AA_THICKNESS); - segment_length += corner_offset; - } - - // scaling of uv.y due to different linewidths - // the padding for AA_THICKNESS should always have AA_THICKNESS width in uv - thickness_aa1 = g_thickness[1] / edge_scale + AA_THICKNESS; - thickness_aa2 = g_thickness[2] / edge_scale + AA_THICKNESS; - - // Generate line segment - u1 *= px2uv; - u2 *= px2uv; - - // Normal Version - generate_line_segment( - p1, miter_a, u1, thickness_aa1, - p2, miter_b, u2, thickness_aa2, - v1.xy, segment_length - ); - - // Debug - show each triangle - // generate_line_segment_debug( - // p1, miter_a, u1, thickness_aa1, - // p2, miter_b, u2, thickness_aa2, - // v1.xy, segment_length + // rect + // vec2 VC = position.xy - center; + // can't have absolute, will break interpolation + // f_rect_sdf = vec2( + // abs(dot(VC, v)) - 0.5 * segment_length, // - extrusion, apply in frag? do we need 0 edge? + // abs(dot(VC, n)) - thickness // ); - - return; + // f_rect_sdf = vec2(dot(VC, line.v.xy), dot(VC, line.n)); + + // joint soft cutoff + // need miter_offset to be big if no truncation + // f_joint_smooth = vec2(100.0, 100.0); + // if (line.miter_offset_a < 0.5) // truncated join condition + // f_joint_smooth.x = dot(VP1, line.miter_n_a) - 0.5 * g_thickness[1] * line.miter_offset_a; + // if (line.miter_offset_b < 0.5) // truncated join condition + // f_joint_smooth.y = dot(VP2, line.miter_n_b) - 0.5 * g_thickness[2] * line.miter_offset_b; + + + /* + ####### + width sdf: + vert/geom: x = dot(vertex_pos - center, line.n) + frag: sd = abs(x) - unpadded_linewidth + length sdf: + start sdf: + vert/geom: x = dot(vertex_pos - P1, -line.v) + frag: sd = x + stop sdf: + vert/goem: x = dot(vertex_pos - P2, line.v); + frag: sd = x + smooth truncated join sdf: + vert/geom: x = dot(vertex_pos - P1/P2, sign(dot(miter_v, v)) * miter_v) - miter_distance + frag: sd = x + sharp join sdf: + vert/geom: x = -10.0 + frag: sd = x + */ + + // SDF in (v dir at P1, v dir at P2, n dir) (default is sharp joint) + f_quad_sdf = vec3(-1.0, -1.0, dot(position.xy - center, line.n)); + + if (line.is_start) // flat line end + f_quad_sdf.x = dot(VP1, -line.v.xy); + else if (line.miter_offset_a < 0.5) // truncated joint + f_quad_sdf.x = dot(VP1, line.miter_n_a) - 0.5 * g_thickness[1] * line.miter_offset_a; + + if (line.is_end) // flat line end + f_quad_sdf.y = dot(VP2, line.v.xy); + else if (line.miter_offset_b < 0.5) // truncated joint + f_quad_sdf.y = dot(VP2, line.miter_n_b) - 0.5 * g_thickness[2] * line.miter_offset_b; + + + // f_line_length = 0.5 * line.segment_length; + f_line_width = 0.5 * g_thickness[index]; + f_color = g_color[index]; + gl_Position = vec4((2.0 * position.xy / resolution) - 1.0, position.z, 1.0); + f_id = g_id[index]; + // f_line_offset = 0.5 * (g_lastlen[1] + g_lastlen[2]); // rect_sdf.x is centered + f_cumulative_length = g_lastlen[index]; // TODO + EmitVertex(); } +void emit_quad(LineData line) { + vec2 center = 0.5 * (line.p1.xy + line.p2.xy); + float geom_linewidth = 0.5 * max(g_thickness[1], g_thickness[2]) + AA_THICKNESS; + emit_vertex(line.p1, center, line, 1, vec2(- (line.extrusion_a + AA_THICKNESS), -geom_linewidth)); + emit_vertex(line.p1, center, line, 1, vec2(- (line.extrusion_a + AA_THICKNESS), +geom_linewidth)); + emit_vertex(line.p2, center, line, 2, vec2(+ (line.extrusion_b + AA_THICKNESS), -geom_linewidth)); + emit_vertex(line.p2, center, line, 2, vec2(+ (line.extrusion_b + AA_THICKNESS), +geom_linewidth)); - -//////////////////////////////////////////////////////////////////////////////// -/// Main -//////////////////////////////////////////////////////////////////////////////// - - + EndPrimitive(); +} void main(void) { @@ -780,7 +160,6 @@ void main(void) return; } - // We mark each of the four vertices as valid or not. Vertices can be // marked invalid on input (eg, if they contain NaN). We also mark them // invalid if they repeat in the index buffer. This allows us to render to @@ -800,13 +179,111 @@ void main(void) return; } - // get the four vertices passed to the shader - // without FAST_PATH the conversions happen on the CPU + // Time to generate our quad. For this we need to find out how far a join + // extends the line. First let's get some vectors we need. + + // Get the four vertices passed to the shader in pixel space. + // Without FAST_PATH the conversions happen on the CPU #ifdef FAST_PATH - draw_solid_line(isvalid); + vec3 p0 = screen_space(gl_in[0].gl_Position); // start of previous segment + vec3 p1 = screen_space(gl_in[1].gl_Position); // end of previous segment, start of current segment + vec3 p2 = screen_space(gl_in[2].gl_Position); // end of current segment, start of next segment + vec3 p3 = screen_space(gl_in[3].gl_Position); // end of next segment #else - draw_patterned_line(isvalid); + vec3 p0 = gl_in[0].gl_Position.xyz; // start of previous segment + vec3 p1 = gl_in[1].gl_Position.xyz; // end of previous segment, start of current segment + vec3 p2 = gl_in[2].gl_Position.xyz; // end of current segment, start of next segment + vec3 p3 = gl_in[3].gl_Position.xyz; // end of next segment #endif + // determine the direction of each of the 3 segments (previous, current, next) + vec3 v1 = p2 - p1; + float segment_length = length(v1.xy); + v1 = v1 / segment_length; + vec3 v0 = v1; + vec3 v2 = v1; + if (p1 != p0 && isvalid[0]) + v0 = (p1 - p0) / length(p1.xy - p0.xy); + if (p3 != p2 && isvalid[3]) + v2 = (p3 - p2) / length(p3.xy - p2.xy); + + // determine the normal of each of the 3 segments (previous, current, next) + vec2 n0 = vec2(-v0.y, v0.x); + vec2 n1 = vec2(-v1.y, v1.x); + vec2 n2 = vec2(-v2.y, v2.x); + + // Compute variables for line joints + LineData line; + line.p1 = p1; + line.p2 = p2; + line.v = v1; + line.n = n1; + line.segment_length = segment_length; + line.is_start = !isvalid[0]; + line.is_end = !isvalid[3]; + + // We create a second (imaginary line for each of the joins which averages the + // directions of the previous lines. For the corner at P1 this line has + // normal = miter_n_a = normalize(n0 + n1) + // direction = miter_v_a = normalize(v0 + v1) = vec2(normal.y, -normal.x) + line.miter_n_a = normalize(n0 + n1); + line.miter_n_b = normalize(n1 + n2); + line.miter_v_a = vec2(line.miter_n_a.y, -line.miter_n_a.x); + line.miter_v_b = vec2(line.miter_n_b.y, -line.miter_n_b.x); + + // The normal of this new line defines the edge between two line segments + // with a sharp join: + // _______________ + // |'. ^ + // ^ | '. miter_n_a | n1 + // v0 | | '._________ + // | n0 | --> + // | <-- | v1 + // | | + // + // From the triangle with unit vectors (miter_n_a, v1, n1) and the linewidth + // g_thickness[1] along n1 direction follows the necessary extrusion for + // sharp corners: + // dot(length_a * miter_n_a, n1) = g_thickness[1] + // extrusion = dot(length_a * miter_n_a, v1) + // = g_thickness[1] * dot(miter_n_a, v1) / dot(miter_n_a, n1) + // + // For truncated corners the extrusion will always be <= that of the sharp + // corner, so we can just clamp the extrusion at the appropriate maximum + // value. Truncation happens when the angle between v0 and v1 exceeds some + // value, e.g. 120°, or half of that between miter_v_a and v1. We choose + // truncation if + // dot(miter_v_a, v1) < 0.5 (120° between segments) + // or equivalently + // dot(miter_n_a, n1) < 0.5 + // giving use the limit: + float linewidth = 0.5 * max(g_thickness[1], g_thickness[2]); + line.miter_offset_a = dot(line.miter_n_a, n1); + line.miter_offset_b = dot(line.miter_n_b, n1); + line.extrusion_a = linewidth * abs(dot(line.miter_n_a, v1.xy)) / max(0.5, line.miter_offset_a); + line.extrusion_b = linewidth * abs(dot(line.miter_n_b, v1.xy)) / max(0.5, line.miter_offset_b); + + // For truncated joins we also need to know how far the edge of the joint + // (between a and b) is from the center point which the line segments share + // (x). + // ----------a. + // | '. + // x '. + // ------. '--_b + // / / + // / / + // + // This distance is given by linewidth * dot(miter_n_a, n1) + line.miter_offset_a = isvalid[0] ? line.miter_offset_a : 1.0; // else this may create 2 edges + line.miter_offset_b = isvalid[3] ? line.miter_offset_b : 1.0; // else this may create 2 edges + + // For the distance we also need the miter normals to consistently point + // outwards (i.e. towards the a-b line). We can enforce this using the line + // directions + // line.miter_n_a *= (dot(line.miter_n_a, v1.xy) < 0.0 ? 1.0 : -1.0); + // line.miter_n_b *= (dot(line.miter_n_b, v1.xy) < 0.0 ? -1.0 : 1.0); + + emit_quad(line); + return; } diff --git a/GLMakie/src/GLMakie.jl b/GLMakie/src/GLMakie.jl index 47361006a81..0c6244f24a5 100644 --- a/GLMakie/src/GLMakie.jl +++ b/GLMakie/src/GLMakie.jl @@ -56,15 +56,25 @@ end const GL_ASSET_DIR = RelocatableFolders.@path joinpath(@__DIR__, "..", "assets") const SHADER_DIR = RelocatableFolders.@path joinpath(GL_ASSET_DIR, "shader") -const LOADED_SHADERS = Dict{String, ShaderSource}() - +const LOADED_SHADERS = Dict{String, Tuple{Float64, ShaderSource}}() function loadshader(name) # Turns out, joinpath is so slow, that it actually makes sense # To memoize it :-O # when creating 1000 plots with the PlotSpec API, timing drop from 1.5s to 1s just from this change: - return get!(LOADED_SHADERS, name) do - return ShaderSource(joinpath(SHADER_DIR, name)) + # Note that we need to check if the file is still valid to enable hot reloading of shaders + path = joinpath(SHADER_DIR, name) + if haskey(LOADED_SHADERS, name) + cached_time, src = LOADED_SHADERS[name] + file_time = Base.Filesystem.mtime(joinpath(SHADER_DIR, name)) + # return source if valid + (file_time == cached_time) && return src end + + # replace source if invalid/add new source + mtime = Base.Filesystem.mtime(path) + src = ShaderSource(path) + LOADED_SHADERS[name] = (mtime, src) + return src end gl_texture_atlas() = Makie.get_texture_atlas(2048, 64) diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 9a66f835321..5dee577e480 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -476,6 +476,7 @@ function draw_atomic(screen::Screen, scene::Scene, @nospecialize(plot::Lines)) output end end + @info data[:fast] return draw_lines(screen, positions, data) end end diff --git a/GLMakie/src/glshaders/lines.jl b/GLMakie/src/glshaders/lines.jl index 562460de1f6..8bf52d62c68 100644 --- a/GLMakie/src/glshaders/lines.jl +++ b/GLMakie/src/glshaders/lines.jl @@ -135,7 +135,8 @@ function draw_linesegments(screen, positions::VectorTypes{T}, data::Dict) where transparency = false shader = GLVisualizeShader( screen, - "fragment_output.frag", "util.vert", "line_segment.vert", "line_segment.geom", "lines.frag", + "fragment_output.frag", "util.vert", "line_segment.vert", "line_segment.geom", + "line_segments.frag", view = Dict( "buffers" => output_buffers(screen, to_value(transparency)), "buffer_writes" => output_buffer_writes(screen, to_value(transparency)),