first build attempt

This commit is contained in:
2026-04-23 08:05:03 -07:00
parent a75186cab6
commit f68524a6ae
125 changed files with 23271 additions and 463 deletions

18
build/shaders/bloom.frag Normal file
View File

@@ -0,0 +1,18 @@
/*
* MIT License
* Author: Mark Allyn
*
* bloom.frag — stub for a future dedicated two-pass Gaussian bloom.
* Currently bloom is applied inline in phosphor.frag.
* This shader is a pass-through so the CMake target compiles cleanly.
*/
#version 330 core
in vec2 vTexCoord;
out vec4 fragColor;
uniform sampler2D u_texture;
void main() {
fragColor = texture(u_texture, vTexCoord);
}

20
build/shaders/bloom.vert Normal file
View File

@@ -0,0 +1,20 @@
/*
* MIT License
* Author: Mark Allyn
*
* bloom.vert — vertex shader for the bloom post-processing pass.
* Identical to sweep.vert: fullscreen clip-space quad with UV passthrough.
* The actual bloom is currently implemented inline in phosphor.frag;
* this shader is reserved for a separate two-pass Gaussian bloom
* if higher quality is required in a future revision.
*/
#version 330 core
layout(location = 0) in vec2 aPos;
out vec2 vTexCoord;
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
vTexCoord = aPos * 0.5 + 0.5;
}

View File

@@ -0,0 +1,17 @@
/*
* MIT License
* Author: Mark Allyn
*
* graticule.frag — single-colour fragment shader shared by the
* incandescent bearing graticule and the yellow cursor overlay.
* The colour (and alpha) is set via a uniform so one shader serves both.
*/
#version 330 core
out vec4 fragColor;
uniform vec4 u_color; // RGBA; use alpha < 1 for soft edges if needed
void main() {
fragColor = u_color;
}

View File

@@ -0,0 +1,26 @@
/*
* MIT License
* Author: Mark Allyn
*
* graticule.vert — vertex shader for the incandescent bearing graticule,
* the yellow cursor, and any other 2-D screen-space line geometry.
*
* Vertices are supplied in window pixels (origin top-left, y down).
* The shader converts them to OpenGL NDC (origin bottom-left, y up).
*/
#version 330 core
layout(location = 0) in vec2 aPos; // screen pixels, top-left origin
uniform vec2 u_viewportSize; // (WINDOW_WIDTH, WINDOW_HEIGHT)
void main() {
// Convert: pixel → NDC
// ndc.x: 0 → -1, width → +1
// ndc.y: 0 → +1 (top), height → -1 (bottom)
vec2 ndc = vec2(
aPos.x / u_viewportSize.x * 2.0 - 1.0,
-aPos.y / u_viewportSize.y * 2.0 + 1.0
);
gl_Position = vec4(ndc, 0.0, 1.0);
}

View File

@@ -0,0 +1,84 @@
/*
* MIT License
* Author: Mark Allyn
*
* phosphor.frag — maps the single-channel phosphor energy texture to
* the P7 colour sequence (blue → green → yellow-green → dark) and
* applies a simple inline bloom (box-filter glow) to bright pixels.
*
* Coordinate system: gl_FragCoord.xy in GL viewport pixels (origin
* bottom-left). Scope centre is passed as u_scopeCenter in the same
* coordinate system.
*/
#version 330 core
out vec4 fragColor;
uniform sampler2D u_phosphor; // GL_R32F phosphor energy FBO
uniform vec2 u_scopeCenter; // scope centre in GL viewport pixels (bottom-left origin)
uniform float u_scopeRadius; // scope radius in pixels
uniform float u_gain; // receiver gain [0,1] — scales brightness
uniform float u_bloomStep; // UV step for bloom sample (≈ 2.5 / FBO_SIZE)
uniform float u_bloomStrength; // additive blend weight for bloom
// P7 energy thresholds (match settings.h)
const float T_BLUE = 0.82;
const float T_GREEN = 0.55;
const float T_YGREE = 0.22;
const float T_DARK = 0.03;
// P7 colour anchors
const vec3 C_BLUE = vec3(0.30, 0.70, 1.00);
const vec3 C_GREEN = vec3(0.05, 1.00, 0.30);
const vec3 C_YGREE = vec3(0.50, 1.00, 0.05);
const vec3 C_YELLW = vec3(0.70, 0.70, 0.00);
const vec3 C_BLACK = vec3(0.00, 0.00, 0.00);
vec3 p7Color(float e) {
if (e >= T_BLUE)
return mix(C_GREEN, C_BLUE, (e - T_GREEN) / (T_BLUE - T_GREEN));
if (e >= T_GREEN)
return mix(C_YGREE, C_GREEN, (e - T_YGREE) / (T_GREEN - T_YGREE));
if (e >= T_YGREE)
return mix(C_YELLW, C_YGREE, (e - T_DARK ) / (T_YGREE - T_DARK ));
if (e >= T_DARK)
return mix(C_BLACK, C_YELLW, e / T_DARK);
return C_BLACK;
}
void main() {
// Fragment position relative to scope centre
vec2 delta = (gl_FragCoord.xy - u_scopeCenter) / u_scopeRadius;
float dist = length(delta);
if (dist > 1.0) {
fragColor = vec4(0.0); // outside scope circle — transparent black
return;
}
// Map from PPI delta [-1,+1] to phosphor texture UV [0,1]
// delta.x = east, delta.y = north (both y directions already match)
vec2 uv = delta * 0.5 + 0.5;
float energy = texture(u_phosphor, uv).r * u_gain;
// Inline bloom: weighted box-filter over a 5×5 neighbourhood
float bloom = 0.0;
float wsum = 0.0;
for (int dx = -2; dx <= 2; dx++) {
for (int dy = -2; dy <= 2; dy++) {
float w = exp(-float(dx*dx + dy*dy) * 0.45);
float e = texture(u_phosphor, uv + vec2(dx, dy) * u_bloomStep).r;
bloom += e * w;
wsum += w;
}
}
bloom = (bloom / wsum) * u_bloomStrength;
float finalE = clamp(energy + bloom, 0.0, 1.0);
vec3 col = p7Color(finalE);
// Soft-edge vignette at the scope boundary
float edge = smoothstep(1.0, 0.97, dist);
fragColor = vec4(col * edge, 1.0);
}

View File

@@ -0,0 +1,15 @@
/*
* MIT License
* Author: Mark Allyn
*
* phosphor.vert — vertex shader for the phosphor display pass.
* Renders a fullscreen quad; the fragment shader clips to the
* scope circle and maps the phosphor FBO to P7 colours.
*/
#version 330 core
layout(location = 0) in vec2 aPos; // clip-space quad [-1,+1]^2
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
}

116
build/shaders/sweep.frag Normal file
View File

@@ -0,0 +1,116 @@
/*
* MIT License
* Author: Mark Allyn
*
* sweep.frag — phosphor accumulation update shader.
*
* For each texel in the 1024×1024 phosphor FBO:
* 1. Decay the previous frame's energy by u_decayFactor.
* 2. If the texel's PPI bearing falls within the current sweep arc
* [u_beamAnglePrev, u_beamAngle], add contributions from:
* - range rings (beam-painted per the P7 spec)
* - target echoes
* 3. Output the updated single-channel energy value.
*
* PPI convention: north = +y, east = +x; bearing = atan2(x, y)
* in degrees, clockwise from north.
*/
#version 330 core
in vec2 vTexCoord;
layout(location = 0) out vec4 fragOut; // .r = energy; .gba unused
uniform sampler2D u_prevPhosphor; // previous frame's energy texture (GL_R32F)
uniform float u_decayFactor; // exp(-decay_rate * dt)
uniform float u_beamAngle; // current beam angle, degrees CW from north
uniform float u_beamAnglePrev; // beam angle at previous frame
uniform float u_sweepBg; // ambient sweep-line energy (makes beam visible)
uniform float u_halfBeamDeg; // half-beamwidth for target blobs (display widening)
// Targets: .x = range_norm (0-1), .y = bearing_deg, .z = brightness, .w = size_norm
uniform vec4 u_targets[32];
uniform int u_targetCount;
// Range rings: up to 4 normalised radii
uniform float u_ringRadii[4];
uniform int u_ringCount;
uniform float u_ringWidth; // half-width in normalised range units
uniform float u_ringBrightness;
// ----------------------------------------------------------------
// Smallest unsigned angular distance between two bearings [0,360)
float angleDiff(float a, float b) {
float d = mod(abs(a - b), 360.0);
return (d > 180.0) ? (360.0 - d) : d;
}
// True if bearing b is inside the arc [prev, curr] swept this frame.
// Handles the 0/360 wraparound when the sweep crosses north.
bool inSweep(float b, float prev, float curr) {
if (curr >= prev) {
return (b >= prev && b <= curr);
}
// Wraparound: arc crosses 360→0
return (b >= prev || b <= curr);
}
// ----------------------------------------------------------------
void main() {
vec2 pos = vTexCoord * 2.0 - 1.0; // PPI coords: (-1,-1) SW … (+1,+1) NE
float rng = length(pos);
if (rng > 1.0) {
fragOut = vec4(0.0);
return;
}
// Bearing: clockwise from north — atan2(east, north) = atan2(x, y)
float brg = degrees(atan(pos.x, pos.y));
if (brg < 0.0) brg += 360.0;
// Decay previous value
float energy = texture(u_prevPhosphor, vTexCoord).r * u_decayFactor;
if (inSweep(brg, u_beamAnglePrev, u_beamAngle)) {
float contrib = u_sweepBg; // beam passage gives a faint ambient glow
// ---- Range rings (painted at every bearing as beam sweeps) ----
for (int i = 0; i < u_ringCount; i++) {
float d = abs(rng - u_ringRadii[i]);
if (d < u_ringWidth) {
float w = 1.0 - d / u_ringWidth;
contrib = max(contrib, u_ringBrightness * w * w);
}
}
// ---- Target echoes ----
for (int i = 0; i < u_targetCount; i++) {
float tRng = u_targets[i].x;
float tBrg = u_targets[i].y;
float tBrt = u_targets[i].z;
float tSize = u_targets[i].w;
if (tRng <= 0.0 || tBrt <= 0.0) continue;
// Angular proximity: beam must be sweeping over the target's bearing
float dBrg = angleDiff(brg, tBrg);
if (dBrg >= u_halfBeamDeg) continue;
// Range proximity: pixel must be within the target blob
float dRng = abs(rng - tRng);
if (dRng >= tSize) continue;
float bw = 1.0 - dBrg / u_halfBeamDeg; // angular taper
float rw = 1.0 - dRng / tSize; // range taper
contrib = max(contrib, tBrt * bw * rw);
}
energy = max(energy, contrib);
}
fragOut = vec4(clamp(energy, 0.0, 1.0), 0.0, 0.0, 1.0);
}

18
build/shaders/sweep.vert Normal file
View File

@@ -0,0 +1,18 @@
/*
* MIT License
* Author: Mark Allyn
*
* sweep.vert — fullscreen quad vertex shader used by the phosphor
* accumulation ping-pong pass. vTexCoord maps 1:1 to the phosphor
* FBO in PPI space (u=0 west, u=1 east, v=0 south, v=1 north).
*/
#version 330 core
layout(location = 0) in vec2 aPos; // clip-space quad [-1,+1]^2
out vec2 vTexCoord;
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
vTexCoord = aPos * 0.5 + 0.5; // [0,1]^2
}

20
build/shaders/text.frag Normal file
View File

@@ -0,0 +1,20 @@
/*
* MIT License
* Author: Mark Allyn
*
* text.frag — FreeType glyph-atlas fragment shader.
* The atlas is a single-channel GL_RED texture; the sampled value
* is used as alpha and multiplied by the text colour uniform.
*/
#version 330 core
in vec2 vTexCoord;
out vec4 fragColor;
uniform sampler2D u_glyphAtlas;
uniform vec3 u_textColor;
void main() {
float alpha = texture(u_glyphAtlas, vTexCoord).r;
fragColor = vec4(u_textColor, alpha);
}

27
build/shaders/text.vert Normal file
View File

@@ -0,0 +1,27 @@
/*
* MIT License
* Author: Mark Allyn
*
* text.vert — vertex shader for FreeType glyph-atlas text rendering.
* Each glyph is a textured quad. Vertices are in window pixels
* (top-left origin); the shader converts to NDC and passes the
* glyph atlas UV coordinates to the fragment shader.
*/
#version 330 core
layout(location = 0) in vec4 aVertex; // xy = screen pos (px), zw = atlas UV
out vec2 vTexCoord;
uniform vec2 u_viewportSize; // (WINDOW_WIDTH, WINDOW_HEIGHT)
void main() {
vec2 pos = aVertex.xy;
vTexCoord = aVertex.zw;
vec2 ndc = vec2(
pos.x / u_viewportSize.x * 2.0 - 1.0,
-pos.y / u_viewportSize.y * 2.0 + 1.0
);
gl_Position = vec4(ndc, 0.0, 1.0);
}