Files
plotter/src/render/shaders.cpp
T

116 lines
4.4 KiB
C++

#include <plot/render/shaders.hpp>
// 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