feat(3d): gl1 shim M0-M2 — FFP-on-WebGL2 substrate + lighting; 12 scenarios truly green
wasm/gl1: the GL1.x fixed-function emulation layer replacing the
gl_ffp_stub.c no-ops in the 3d-regression harness link. This batch:
- symbol split: 52 FFP-only entry points implemented; 10 Emscripten-owned
names intercepted via wasm-ld --wrap (sources.txt/wrapped_symbols.txt are
the shared manifests for both link sites; production hookup lands in M7)
- matrix stacks (MODELVIEW/PROJECTION, glGetFloatv readback), immediate
mode with all 8 GL1 primitive conversions, GL1-default state mirror
- full GL 1.5 Gouraud lighting uber-shader (eye-space light capture at
glLightfv time, color-material, two-side, COMBINE evaluator + alpha test
wired but inert until M3/M5)
- draw routing: FFP traffic identified by GL_VERTEX_ARRAY client state;
blit/2D-GAL draws pass through untouched
- display lists: correct glGenLists/glIsList existing-empty semantics;
recorder itself is M3 (recorded commands drop with a one-time warning)
Parity: 20/47 under the 0.02 floor, of which 12 verified genuinely
rendering (bg-gradient x2, bounding-box, half-open-cylinder, segment x2,
material x3, light x3 — eyeballed against baselines); the other 8 are
small-geometry scenarios whose missing GLU/display-list content sits under
the floor (become real in M3/M4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:37:51 +02:00
|
|
|
/*
|
|
|
|
|
* gl1_shim — GL 1.x fixed-function pipeline emulated on WebGL2.
|
|
|
|
|
*
|
|
|
|
|
* Internal header shared by the wasm/gl1/src modules. The public surface
|
|
|
|
|
* is the set of C entry points in gl1_entry_ffp.cpp (FFP-only names absent from
|
|
|
|
|
* WebGL2, previously no-op'd by wasm/stubs/gl_ffp_stub.c) plus the __wrap_*
|
|
|
|
|
* interceptors in gl1_entry_wrapped.cpp (Emscripten-owned names the emulator
|
|
|
|
|
* must observe; see wrapped_symbols.txt and the -Wl,--wrap flags both link
|
|
|
|
|
* sites derive from it).
|
|
|
|
|
*
|
|
|
|
|
* Everything here is main-thread-only (both consumers render on the main
|
|
|
|
|
* browser thread) — no TLS, no atomics, no exceptions.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#ifndef GL1_SHIM_H
|
|
|
|
|
#define GL1_SHIM_H
|
|
|
|
|
|
|
|
|
|
#define GL_GLEXT_PROTOTYPES
|
|
|
|
|
#include <GL/gl.h>
|
|
|
|
|
#include <GL/glu.h> // wasm/stubs/GL/glu.h via -I wasm/stubs
|
|
|
|
|
|
|
|
|
|
#include <glm/glm.hpp>
|
|
|
|
|
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
// One-time diagnostics: the suite requires a clean console, but unsupported
|
|
|
|
|
// paths must not fail silently. Each call site warns exactly once.
|
|
|
|
|
#define GL1_WARN_ONCE( fmt, ... ) \
|
|
|
|
|
do \
|
|
|
|
|
{ \
|
|
|
|
|
static bool _gl1_warned = false; \
|
|
|
|
|
if( !_gl1_warned ) \
|
|
|
|
|
{ \
|
|
|
|
|
_gl1_warned = true; \
|
|
|
|
|
std::fprintf( stderr, "[gl1] " fmt "\n", ##__VA_ARGS__ ); \
|
|
|
|
|
} \
|
|
|
|
|
} while( 0 )
|
|
|
|
|
|
|
|
|
|
// The __real_* counterparts of every wrapped symbol (resolved by wasm-ld back
|
|
|
|
|
// to the Emscripten WebGL JS-library import). Shim-internal code that needs
|
|
|
|
|
// the true WebGL behavior calls these directly — never the public names.
|
|
|
|
|
extern "C"
|
|
|
|
|
{
|
|
|
|
|
void __real_glEnable( GLenum cap );
|
|
|
|
|
void __real_glDisable( GLenum cap );
|
|
|
|
|
GLboolean __real_glIsEnabled( GLenum cap );
|
|
|
|
|
void __real_glDrawArrays( GLenum mode, GLint first, GLsizei count );
|
|
|
|
|
void __real_glDrawElements( GLenum mode, GLsizei count, GLenum type, const GLvoid* indices );
|
|
|
|
|
void __real_glGetFloatv( GLenum pname, GLfloat* params );
|
|
|
|
|
void __real_glBindTexture( GLenum target, GLuint texture );
|
|
|
|
|
void __real_glBlendFunc( GLenum sfactor, GLenum dfactor );
|
|
|
|
|
void __real_glLineWidth( GLfloat width );
|
|
|
|
|
void __real_glHint( GLenum target, GLenum mode );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace gl1
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// State mirror — the GL 1.x state the emulator owns. WebGL-native state
|
|
|
|
|
// (blend, depth, stencil, cull, viewport, textures, buffers...) is NOT
|
|
|
|
|
// mirrored; those calls pass through untouched.
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
struct Light
|
|
|
|
|
{
|
|
|
|
|
// GL 1.5 defaults: LIGHT0 gets white diffuse/specular, others black
|
|
|
|
|
// (applied in State::State).
|
|
|
|
|
glm::vec4 ambient{ 0.0f, 0.0f, 0.0f, 1.0f };
|
|
|
|
|
glm::vec4 diffuse{ 0.0f, 0.0f, 0.0f, 1.0f };
|
|
|
|
|
glm::vec4 specular{ 0.0f, 0.0f, 0.0f, 1.0f };
|
|
|
|
|
// GL_POSITION is transformed by the modelview CURRENT AT THE glLightfv CALL
|
|
|
|
|
// and stored in eye space — this is what eye-anchors KiCad's directional
|
|
|
|
|
// lights (init_lights() runs under an identity modelview).
|
|
|
|
|
glm::vec4 posEye{ 0.0f, 0.0f, 1.0f, 0.0f };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Material
|
|
|
|
|
{
|
|
|
|
|
glm::vec4 ambient{ 0.2f, 0.2f, 0.2f, 1.0f };
|
|
|
|
|
glm::vec4 diffuse{ 0.8f, 0.8f, 0.8f, 1.0f };
|
|
|
|
|
glm::vec4 specular{ 0.0f, 0.0f, 0.0f, 1.0f };
|
|
|
|
|
glm::vec4 emission{ 0.0f, 0.0f, 0.0f, 1.0f };
|
|
|
|
|
float shininess = 0.0f;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct ClientArray
|
|
|
|
|
{
|
|
|
|
|
bool enabled = false;
|
|
|
|
|
GLint size = 4;
|
|
|
|
|
GLenum type = GL_FLOAT;
|
|
|
|
|
GLsizei stride = 0;
|
|
|
|
|
const GLvoid* pointer = nullptr;
|
|
|
|
|
// GL1 semantics: gl*Pointer captures the GL_ARRAY_BUFFER binding current at
|
|
|
|
|
// the call; 0 = client memory, nonzero = offset into that VBO.
|
|
|
|
|
GLuint boundBuffer = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Interleaved immediate-mode vertex (glBegin/glEnd stream and display-list
|
|
|
|
|
// geometry bake share this layout). Matches the attribute setup in gl1_draw.
|
|
|
|
|
struct ImmVertex
|
|
|
|
|
{
|
|
|
|
|
float px, py, pz;
|
|
|
|
|
float nx, ny, nz;
|
|
|
|
|
float r, g, b, a;
|
|
|
|
|
float u, v;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static_assert( sizeof( ImmVertex ) == 12 * sizeof( float ), "ImmVertex must stay tightly packed" );
|
|
|
|
|
|
|
|
|
|
enum ClientArrayIndex
|
|
|
|
|
{
|
|
|
|
|
CA_VERTEX = 0,
|
|
|
|
|
CA_NORMAL,
|
|
|
|
|
CA_COLOR,
|
|
|
|
|
CA_TEXCOORD,
|
|
|
|
|
CA_COUNT
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct State
|
|
|
|
|
{
|
|
|
|
|
// --- matrix stacks ---
|
|
|
|
|
GLenum matrixMode = GL_MODELVIEW;
|
|
|
|
|
std::vector<glm::mat4> mv; // modelview stack, top = back()
|
|
|
|
|
std::vector<glm::mat4> proj; // projection stack, top = back()
|
|
|
|
|
|
|
|
|
|
// --- immediate-mode current attributes (persist across Begin/End) ---
|
|
|
|
|
glm::vec4 currentColor{ 1.0f, 1.0f, 1.0f, 1.0f };
|
|
|
|
|
glm::vec3 currentNormal{ 0.0f, 0.0f, 1.0f };
|
|
|
|
|
|
|
|
|
|
// --- FFP capabilities (tracked; never forwarded to WebGL) ---
|
|
|
|
|
bool lighting = false;
|
|
|
|
|
bool lightEnabled[8] = {};
|
|
|
|
|
bool colorMaterial = false;
|
|
|
|
|
bool texture2D = false;
|
|
|
|
|
bool normalizeNormals = false;
|
|
|
|
|
bool alphaTest = false;
|
|
|
|
|
// Tracked only so glIsEnabled stays consistent; no rendering effect here
|
|
|
|
|
// (forwarding them would raise INVALID_ENUM in WebGL2).
|
|
|
|
|
bool lineSmooth = false;
|
|
|
|
|
bool pointSmooth = false;
|
|
|
|
|
bool multisample = false;
|
|
|
|
|
|
|
|
|
|
// --- lighting rig / materials ---
|
|
|
|
|
Light lights[8];
|
|
|
|
|
glm::vec4 lightModelAmbient{ 0.2f, 0.2f, 0.2f, 1.0f };
|
|
|
|
|
bool twoSide = false;
|
|
|
|
|
Material material; // GL_FRONT_AND_BACK only (asserted at the entry point)
|
|
|
|
|
GLenum colorMaterialMode = GL_AMBIENT_AND_DIFFUSE;
|
|
|
|
|
|
|
|
|
|
// --- texture environment, unit 0 (GL 1.5 initial values) ---
|
|
|
|
|
GLenum texEnvMode = GL_MODULATE;
|
|
|
|
|
glm::vec4 texEnvColor{ 0.0f, 0.0f, 0.0f, 0.0f };
|
|
|
|
|
GLenum combineRGB = GL_MODULATE;
|
|
|
|
|
GLenum combineAlpha = GL_MODULATE;
|
|
|
|
|
GLenum srcRGB[3] = { GL_TEXTURE, GL_PREVIOUS, GL_CONSTANT };
|
|
|
|
|
GLenum operandRGB[3] = { GL_SRC_COLOR, GL_SRC_COLOR, GL_SRC_ALPHA };
|
|
|
|
|
GLenum srcAlpha[3] = { GL_TEXTURE, GL_PREVIOUS, GL_CONSTANT };
|
|
|
|
|
GLenum operandAlpha[3] = { GL_SRC_ALPHA, GL_SRC_ALPHA, GL_SRC_ALPHA };
|
|
|
|
|
|
|
|
|
|
// --- alpha test ---
|
|
|
|
|
GLenum alphaFunc = GL_ALWAYS;
|
|
|
|
|
float alphaRef = 0.0f;
|
|
|
|
|
|
|
|
|
|
// --- misc ---
|
|
|
|
|
GLenum shadeModel = GL_SMOOTH;
|
|
|
|
|
float pointSize = 1.0f;
|
|
|
|
|
float lineWidth = 1.0f;
|
|
|
|
|
GLuint boundTexture2D = 0; // mirror of the unit-0 GL_TEXTURE_2D binding
|
|
|
|
|
|
|
|
|
|
// --- client arrays ---
|
|
|
|
|
ClientArray clientArrays[CA_COUNT];
|
|
|
|
|
|
|
|
|
|
// Nonzero while a non-shim GLSL program is bound (raytracer blit, 2D GAL):
|
|
|
|
|
// routed draws pass through untouched.
|
|
|
|
|
GLuint externalProgram = 0;
|
|
|
|
|
|
|
|
|
|
// --- dirty flags (uniform re-upload gates) ---
|
|
|
|
|
bool matricesDirty = true;
|
|
|
|
|
bool lightingDirty = true;
|
|
|
|
|
bool texEnvDirty = true;
|
|
|
|
|
bool miscDirty = true;
|
|
|
|
|
|
|
|
|
|
State()
|
|
|
|
|
{
|
|
|
|
|
mv.reserve( 64 );
|
|
|
|
|
proj.reserve( 8 );
|
|
|
|
|
mv.push_back( glm::mat4( 1.0f ) );
|
|
|
|
|
proj.push_back( glm::mat4( 1.0f ) );
|
|
|
|
|
|
|
|
|
|
lights[0].diffuse = glm::vec4( 1.0f, 1.0f, 1.0f, 1.0f );
|
|
|
|
|
lights[0].specular = glm::vec4( 1.0f, 1.0f, 1.0f, 1.0f );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glm::mat4& mvTop() { return mv.back(); }
|
|
|
|
|
glm::mat4& projTop() { return proj.back(); }
|
|
|
|
|
glm::mat4& currentTop() { return matrixMode == GL_PROJECTION ? proj.back() : mv.back(); }
|
|
|
|
|
std::vector<glm::mat4>& currentStack() { return matrixMode == GL_PROJECTION ? proj : mv; }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
State& S();
|
|
|
|
|
|
|
|
|
|
// Returns the tracked-flag slot for FFP-only glEnable/glDisable caps, or
|
|
|
|
|
// nullptr for caps WebGL owns natively (forward those).
|
|
|
|
|
bool* ffpCapSlot( GLenum cap );
|
|
|
|
|
|
|
|
|
|
// Marks the state blocks a cap flip invalidates.
|
|
|
|
|
void onCapChanged( GLenum cap );
|
|
|
|
|
|
2026-07-03 11:48:22 +02:00
|
|
|
// Immediate-execution state mutators shared by the __wrap_* interceptors and
|
|
|
|
|
// display-list replay (identical semantics, minus the recording check).
|
|
|
|
|
void stateEnable( GLenum cap, bool enable );
|
|
|
|
|
void stateBindTexture( GLenum target, GLuint texture );
|
|
|
|
|
void stateBlendFunc( GLenum sfactor, GLenum dfactor );
|
|
|
|
|
void stateLineWidth( GLfloat width );
|
|
|
|
|
void stateAlphaFunc( GLenum func, GLclampf ref );
|
|
|
|
|
|
fix(3d): blank render + lost position after 3D viewer close/reopen (gl1 context guard)
Closing the viewer destroys its wxGLCanvas's WebGL context; reopening mints a
new one. The gl1 shim cached GL names (FFP program, stream/scratch VBOs) in
never-reset statics behind `if (!handle)` guards — in the new context every
draw died with INVALID_OPERATION and the viewer showed only the clear color
("Reload time 0.031 s" is benign: warm model caches make the rebuild fast).
contextSync() (gl1_state.cpp) now detects the context change in programSync()
— the one choke point every shim draw crosses, and a path the 2D GAL never
reaches (a first attempt checking in the glBindTexture wrap saw the GAL's
context and thrash-rebuilt the program 23x per run) — and drops the cached
names for lazy rebuild in the new context. Context identity is a monotonic id
stamped on Emscripten's per-context record: the numeric
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE is recycled, so a destroy-then-create can
return the same number and a handle comparison detects nothing.
The lost-position half is a wxwidgets wasm fix (pointer bump: GetFromWindow
reports display 0; saved geometry used to carry display=(unsigned)-1, which
LoadWindowState treats as "display not found" and re-centres the frame).
TDD (red observed before each fix, green after):
- tests/kicad/3d-viewer-reopen.spec.ts (new, own worker like the deadlock
spec): load board, open viewer, render-gate, drag by the titlebar, close
via the x, reopen; asserts the board re-renders (was: 1 distinct colour for
90 s) and the window position is restored (was: re-centred to 0,0 after
closing at 40,90). Green run logs exactly one [gl1] context-change line.
- 3d-regression harness: recreateContext() destroys the context AND swaps in
a fresh canvas element (a browser canvas keeps its context for life, so
same-element recreation hands back the live old context and hides the bug);
the new 3d-webgl spec test renders redraw-mini-board-navigator before and
after recreation and requires pixel-identical output. Parity: 47/47, zero
drift.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 15:55:31 +02:00
|
|
|
// --- context-generation guard (gl1_state.cpp) ---
|
|
|
|
|
// GL object names live and die with the WebGL context, and the 3D viewer's
|
|
|
|
|
// close/reopen destroys and recreates it (~wxGLCanvas destroys the context
|
|
|
|
|
// with the canvas; the next open mints new ones). Called from programSync()
|
|
|
|
|
// ONLY: that is the single choke point every shim draw crosses, it always
|
|
|
|
|
// runs under the 3D context, and — critically — it is a path the 2D GAL never
|
|
|
|
|
// reaches, so alternating 2D/3D paints cannot ping-pong the owner (a check in
|
|
|
|
|
// the glBindTexture wrap did exactly that: every caller crosses a wrap).
|
|
|
|
|
// On a context change it drops every cached name so the shim rebuilds lazily
|
|
|
|
|
// in the new context. Deliberately never touches display-list or
|
|
|
|
|
// immediate-mode state: the change can be detected mid-scene-rebuild, and
|
|
|
|
|
// those modules are context-agnostic CPU state.
|
|
|
|
|
void contextSync();
|
fix(3d): blank viewer after raytracing round-trip — owner-context FFP routing + VAO isolation (gl1)
Switching OpenGL → raytracing → OpenGL could leave the viewer showing only the
background gradient, with mid-session "[gl1] WebGL context changed" thrash and
INVALID_OPERATION storms on BOTH WebGL contexts. Traced mechanism: the shim's
FFP draw routing keyed on one process-global client-array flag; an interrupted
fixed-function window (MODEL_3D::BeginDrawMulti loops, or the switch-back
reload re-recording display lists with the GL context lock released across
JSPI suspensions) left it set, after which the raytracer blit's and the 2D
GAL's glDrawArrays were routed through the FFP pipeline — and one misrouted
draw permanently repointed the VICTIM's own VAO attributes at shim buffers
(the blit's attribute 0 collides with ATTR_POSITION), so both stayed
broken/blank even after the flag cleared.
Shim fixes (wasm/gl1):
- Owner-context routing gate: __wrap_glDrawArrays/Elements route into the FFP
pipeline only under the shim's owner context (adopted at the first FFP
client-state mutation or programSync in a context); foreign-context draws
always pass through — the 2D GAL can never be misrouted and the context
guard can never thrash.
- VAO isolation (ScopedDefaultVAO): draw executors do their attribute setup on
VAO 0 and restore the caller's binding — a misrouted draw can no longer
corrupt the caller.
- contextSync() resets the whole client-array mirror on a context change
(enables/pointers/VBO names all described the dead context).
kicad pointer bump (d6e3dc1a87a): blit preamble disables the four client
arrays (same-context firewall) + DoRePaint hidden-parent early return now
clears m_is_currently_painting like its six siblings (a standalone
sufficient cause of a permanently blank viewer).
TDD (each observed red before its fix, green after; harness = authoritative):
- T1 VAO corruption, T2 foreign-context routing + guard thrash, T3 stale
client-state surviving context recreation — tests/e2e/3d-webgl.spec.ts over
new harness choreography (appQuad/ffpMakeStale/createSecondContext/
quadDrawFresh). Parity stays 47/47 with zero drift.
- tests/kicad/3d-viewer-engine-toggle.spec.ts (new, CI-skipped like the
deadlock spec): real round-trip happy-path gate — board re-renders, zero
[gl1] lines, zero INVALID_OPERATION. (The raytraced image itself never
displays on the wasm build — the pre-existing inert-toggle KNOWN ISSUE in
3d-viewer-deadlock.spec.ts, out of scope here; the engine switch and the
poisoning reload path run regardless.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 11:24:26 +02:00
|
|
|
// True when the CURRENT WebGL context is the shim's owner context (or no owner
|
|
|
|
|
// exists yet). The __wrap_* draw interceptors gate FFP routing on this: only
|
|
|
|
|
// the owner context's draws can be fixed-function traffic — a draw under any
|
|
|
|
|
// other context (the 2D GAL, a recreated canvas) always passes through, no
|
|
|
|
|
// matter what the global client-array mirror says. (gl1_state.cpp)
|
|
|
|
|
bool contextIsOwner();
|
fix(3d): blank render + lost position after 3D viewer close/reopen (gl1 context guard)
Closing the viewer destroys its wxGLCanvas's WebGL context; reopening mints a
new one. The gl1 shim cached GL names (FFP program, stream/scratch VBOs) in
never-reset statics behind `if (!handle)` guards — in the new context every
draw died with INVALID_OPERATION and the viewer showed only the clear color
("Reload time 0.031 s" is benign: warm model caches make the rebuild fast).
contextSync() (gl1_state.cpp) now detects the context change in programSync()
— the one choke point every shim draw crosses, and a path the 2D GAL never
reaches (a first attempt checking in the glBindTexture wrap saw the GAL's
context and thrash-rebuilt the program 23x per run) — and drops the cached
names for lazy rebuild in the new context. Context identity is a monotonic id
stamped on Emscripten's per-context record: the numeric
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE is recycled, so a destroy-then-create can
return the same number and a handle comparison detects nothing.
The lost-position half is a wxwidgets wasm fix (pointer bump: GetFromWindow
reports display 0; saved geometry used to carry display=(unsigned)-1, which
LoadWindowState treats as "display not found" and re-centres the frame).
TDD (red observed before each fix, green after):
- tests/kicad/3d-viewer-reopen.spec.ts (new, own worker like the deadlock
spec): load board, open viewer, render-gate, drag by the titlebar, close
via the x, reopen; asserts the board re-renders (was: 1 distinct colour for
90 s) and the window position is restored (was: re-centred to 0,0 after
closing at 40,90). Green run logs exactly one [gl1] context-change line.
- 3d-regression harness: recreateContext() destroys the context AND swaps in
a fresh canvas element (a browser canvas keeps its context for life, so
same-element recreation hands back the live old context and hides the bug);
the new 3d-webgl spec test renders redraw-mini-board-navigator before and
after recreation and requires pixel-identical output. Parity: 47/47, zero
drift.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 15:55:31 +02:00
|
|
|
// Per-TU cache drops invoked by contextSync() on a context change.
|
|
|
|
|
void shadersDropContextObjects(); // FFP program + uniform locations + fail latch
|
|
|
|
|
void drawDropContextObjects(); // stream/scratch VBOs
|
|
|
|
|
|
2026-07-03 11:48:22 +02:00
|
|
|
// GL1 normalized-attribute rule: integer colors and normals are normalized,
|
|
|
|
|
// floats are not (positions/texcoords are float-only in this codebase).
|
|
|
|
|
bool attribNormalized( int arrayIndex, GLenum type );
|
|
|
|
|
|
|
|
|
|
// Effective byte stride of a client array (GL stride 0 = tightly packed).
|
|
|
|
|
GLsizei attribEffectiveStride( GLint size, GLenum type, GLsizei stride );
|
|
|
|
|
|
|
|
|
|
// One vertex attribute's data source for an array draw: either client memory
|
|
|
|
|
// to be uploaded (cpuData) or a byte offset into a user VBO (buffer/offset).
|
|
|
|
|
struct AttribSource
|
|
|
|
|
{
|
|
|
|
|
bool enabled = false;
|
|
|
|
|
GLint size = 4;
|
|
|
|
|
GLenum type = GL_FLOAT;
|
|
|
|
|
bool normalized = false;
|
|
|
|
|
GLsizei stride = 0; // effective byte stride, never 0
|
|
|
|
|
const void* cpuData = nullptr;
|
|
|
|
|
GLuint buffer = 0;
|
|
|
|
|
GLintptr offset = 0;
|
|
|
|
|
};
|
|
|
|
|
|
feat(3d): gl1 shim M0-M2 — FFP-on-WebGL2 substrate + lighting; 12 scenarios truly green
wasm/gl1: the GL1.x fixed-function emulation layer replacing the
gl_ffp_stub.c no-ops in the 3d-regression harness link. This batch:
- symbol split: 52 FFP-only entry points implemented; 10 Emscripten-owned
names intercepted via wasm-ld --wrap (sources.txt/wrapped_symbols.txt are
the shared manifests for both link sites; production hookup lands in M7)
- matrix stacks (MODELVIEW/PROJECTION, glGetFloatv readback), immediate
mode with all 8 GL1 primitive conversions, GL1-default state mirror
- full GL 1.5 Gouraud lighting uber-shader (eye-space light capture at
glLightfv time, color-material, two-side, COMBINE evaluator + alpha test
wired but inert until M3/M5)
- draw routing: FFP traffic identified by GL_VERTEX_ARRAY client state;
blit/2D-GAL draws pass through untouched
- display lists: correct glGenLists/glIsList existing-empty semantics;
recorder itself is M3 (recorded commands drop with a one-time warning)
Parity: 20/47 under the 0.02 floor, of which 12 verified genuinely
rendering (bg-gradient x2, bounding-box, half-open-cylinder, segment x2,
material x3, light x3 — eyeballed against baselines); the other 8 are
small-geometry scenarios whose missing GLU/display-list content sits under
the floor (become real in M3/M4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:37:51 +02:00
|
|
|
// --- matrix module (gl1_matrix.cpp) ---
|
|
|
|
|
void matrixLoadIdentity();
|
|
|
|
|
void matrixLoadf( const GLfloat* m );
|
|
|
|
|
void matrixPush();
|
|
|
|
|
void matrixPop();
|
|
|
|
|
void matrixTranslate( float x, float y, float z );
|
|
|
|
|
void matrixRotate( float angleDeg, float x, float y, float z );
|
|
|
|
|
void matrixScale( float x, float y, float z );
|
|
|
|
|
void matrixPerspective( double fovyDeg, double aspect, double zNear, double zFar );
|
|
|
|
|
|
|
|
|
|
// --- immediate-mode module (gl1_immediate.cpp) ---
|
|
|
|
|
// Also the emission path for GLU quadrics and display-list replay, so all
|
|
|
|
|
// geometry funnels through one primitive-conversion + draw pipeline.
|
|
|
|
|
void immBegin( GLenum mode );
|
|
|
|
|
void immVertex( float x, float y, float z );
|
|
|
|
|
void immEnd();
|
|
|
|
|
bool immActive();
|
|
|
|
|
|
|
|
|
|
// --- draw module (gl1_draw.cpp) ---
|
|
|
|
|
// Uploads `count` interleaved ImmVertex records to the streaming VBO and draws
|
|
|
|
|
// them with the FFP program. `mode` must already be a WebGL2-legal primitive.
|
|
|
|
|
void drawImmVertices( GLenum mode, const ImmVertex* verts, GLsizei count );
|
2026-07-03 11:48:22 +02:00
|
|
|
// Non-indexed draw over explicit per-attribute sources (client arrays,
|
|
|
|
|
// display-list snapshots). Sources are pre-rebased: vertex 0 = first vertex.
|
|
|
|
|
void drawArraysWithSources( GLenum mode, GLsizei count, const AttribSource aSrc[CA_COUNT] );
|
|
|
|
|
// Routed glDrawArrays/glDrawElements over FFP client-array state.
|
feat(3d): gl1 shim M0-M2 — FFP-on-WebGL2 substrate + lighting; 12 scenarios truly green
wasm/gl1: the GL1.x fixed-function emulation layer replacing the
gl_ffp_stub.c no-ops in the 3d-regression harness link. This batch:
- symbol split: 52 FFP-only entry points implemented; 10 Emscripten-owned
names intercepted via wasm-ld --wrap (sources.txt/wrapped_symbols.txt are
the shared manifests for both link sites; production hookup lands in M7)
- matrix stacks (MODELVIEW/PROJECTION, glGetFloatv readback), immediate
mode with all 8 GL1 primitive conversions, GL1-default state mirror
- full GL 1.5 Gouraud lighting uber-shader (eye-space light capture at
glLightfv time, color-material, two-side, COMBINE evaluator + alpha test
wired but inert until M3/M5)
- draw routing: FFP traffic identified by GL_VERTEX_ARRAY client state;
blit/2D-GAL draws pass through untouched
- display lists: correct glGenLists/glIsList existing-empty semantics;
recorder itself is M3 (recorded commands drop with a one-time warning)
Parity: 20/47 under the 0.02 floor, of which 12 verified genuinely
rendering (bg-gradient x2, bounding-box, half-open-cylinder, segment x2,
material x3, light x3 — eyeballed against baselines); the other 8 are
small-geometry scenarios whose missing GLU/display-list content sits under
the floor (become real in M3/M4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:37:51 +02:00
|
|
|
void drawClientArrays( GLenum mode, GLint first, GLsizei count );
|
|
|
|
|
void drawClientElements( GLenum mode, GLsizei count, GLenum type, const GLvoid* indices );
|
|
|
|
|
|
|
|
|
|
// --- shader module (gl1_shaders.cpp) ---
|
|
|
|
|
// Binds the FFP program and re-uploads whatever state is dirty. Returns false
|
|
|
|
|
// (once, with a console warning) if the program failed to build.
|
|
|
|
|
bool programSync();
|
|
|
|
|
GLuint programId();
|
|
|
|
|
|
|
|
|
|
// --- display-list module (gl1_dlist.cpp) ---
|
|
|
|
|
bool dlistRecording();
|
|
|
|
|
GLuint dlistGenLists( GLsizei range );
|
|
|
|
|
GLboolean dlistIsList( GLuint list );
|
|
|
|
|
void dlistNewList( GLuint list, GLenum mode );
|
|
|
|
|
void dlistEndList();
|
|
|
|
|
void dlistCallList( GLuint list );
|
|
|
|
|
void dlistDeleteLists( GLuint list, GLsizei range );
|
|
|
|
|
// Recording hooks (called from entry points / wrappers while recording).
|
|
|
|
|
void dlistRecordEnable( GLenum cap, bool enable );
|
|
|
|
|
void dlistRecordBindTexture( GLenum target, GLuint texture );
|
|
|
|
|
void dlistRecordBlendFunc( GLenum sfactor, GLenum dfactor );
|
|
|
|
|
void dlistRecordLineWidth( GLfloat width );
|
|
|
|
|
void dlistRecordAlphaFunc( GLenum func, GLclampf ref );
|
|
|
|
|
void dlistRecordNormal( float nx, float ny, float nz );
|
|
|
|
|
void dlistRecordColor( float r, float g, float b, float a );
|
|
|
|
|
void dlistRecordBegin( GLenum mode );
|
|
|
|
|
void dlistRecordVertex( float x, float y, float z );
|
|
|
|
|
void dlistRecordEnd();
|
|
|
|
|
void dlistRecordDrawArrays( GLenum mode, GLint first, GLsizei count );
|
|
|
|
|
|
|
|
|
|
} // namespace gl1
|
|
|
|
|
|
|
|
|
|
#endif // GL1_SHIM_H
|