#include // Both pipelines are `#version 330 core` and stay untouched by the upload // strategy: the 4.3+ path changes how vertex data reaches the GPU, never the // GLSL, so one program pair serves every context the library supports. The // declarations here are the contract the render layer's attribute layouts and // uniform setters mirror (see program.hpp, vertex_array.hpp, shaders.hpp). namespace plot::render { const char* const kLineVS = R"(#version 330 core layout(location = 0) in vec2 a_p0; // per-instance segment start, data space layout(location = 1) in vec2 a_p1; // per-instance segment end uniform vec2 u_scale; // data -> pixels uniform vec2 u_offset; // pixels uniform vec2 u_viewport; // render target size, pixels uniform float u_width; // stroke width, pixels out float v_dist; // signed distance to the segment axis, pixels out float v_along; // distance travelled from a_p0, pixels void main() { vec2 p0 = a_p0 * u_scale + u_offset; vec2 p1 = a_p1 * u_scale + u_offset; vec2 d = p1 - p0; float len = length(d); float half_w = u_width * 0.5 + 1.0; // + one pixel of AA fringe // A degenerate segment has no direction: pin the normal, and give the quad // area by stretching its two ends apart along x. Collapsing all four corners // onto p0 would rasterise nothing, and a flat trace staged as p == p (the // dense path's min == max buckets) would then silently vanish. When // len > 1e-6 the two ends are p0 and p1 exactly as before. vec2 n = len > 1e-6 ? vec2(-d.y, d.x) / len : vec2(0.0, 1.0); vec2 e0 = len > 1e-6 ? p0 : p0 - vec2(half_w, 0.0); vec2 e1 = len > 1e-6 ? p1 : p0 + vec2(half_w, 0.0); // gl_VertexID picks the corner: 0,1 are the sides of the segment's start and // 2,3 of its end, which is the order GL_TRIANGLE_STRIP expects. float side = (gl_VertexID & 1) == 0 ? -1.0 : 1.0; vec2 base = (gl_VertexID & 2) == 0 ? e0 : e1; vec2 px = base + n * (side * half_w); // Rect is top-left origin, GL is bottom-left: flip y once, here, and never // again in the fragment shader or in a caller. vec2 ndc = vec2(px.x / u_viewport.x * 2.0 - 1.0, 1.0 - px.y / u_viewport.y * 2.0); gl_Position = vec4(ndc, 0.0, 1.0); v_dist = side * half_w; v_along = (gl_VertexID & 2) == 0 ? 0.0 : len; } )"; const char* const kLineFS = R"(#version 330 core in float v_dist; // signed distance to the segment axis, pixels uniform float u_width; // stroke width, pixels uniform vec4 u_color; out vec4 o_color; void main() { float half_w = u_width * 0.5; float d = abs(v_dist); if (d > half_w + 1.0) { discard; // outside the fringe; nothing to blend } // Ramp over the last pixel of the half width so a thin stroke keeps its // weight instead of aliasing into a dashed line. float alpha = 1.0 - smoothstep(half_w - 0.5, half_w + 0.5, d); o_color = vec4(u_color.rgb, u_color.a * alpha); } )"; const char* const kTextVS = R"(#version 330 core layout(location = 0) in vec2 a_pos; // per-instance glyph quad top-left, pixels layout(location = 1) in vec2 a_uv; // per-instance glyph atlas texel origin uniform vec2 u_viewport; // render target size, pixels uniform vec2 u_px_range; // pixels per glyph cell on screen out vec2 v_texel; // atlas texel coordinate of this fragment const vec2 kGlyphTexels = vec2(8.0); // BitmapFont::kGlyphPx void main() { vec2 corner = vec2(float(gl_VertexID & 1), float((gl_VertexID >> 1) & 1)); vec2 px = a_pos + corner * u_px_range; // Rect is top-left origin, GL is bottom-left: the one and only y flip. vec2 ndc = vec2(px.x / u_viewport.x * 2.0 - 1.0, 1.0 - px.y / u_viewport.y * 2.0); gl_Position = vec4(ndc, 0.0, 1.0); v_texel = a_uv + corner * kGlyphTexels; } )"; const char* const kTextFS = R"(#version 330 core in vec2 v_texel; // atlas texel coordinate of this fragment uniform sampler2D u_atlas; // GL_R8 glyph atlas, GL_NEAREST uniform vec2 u_tex_size; // atlas size in texels uniform vec4 u_color; out vec4 o_color; void main() { // Sample the centre of the texel the fragment lands in. GL_NEAREST alone // would tie at a texel boundary, where the driver may pick the neighbouring // glyph cell; rounding to a centre removes the tie by construction. vec2 texel = clamp(floor(v_texel), vec2(0.0), u_tex_size - vec2(1.0)); float coverage = texture(u_atlas, (texel + vec2(0.5)) / u_tex_size).r; o_color = vec4(u_color.rgb, u_color.a * coverage); } )"; } // namespace plot::render