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>
This commit is contained in:
parent
19e850d098
commit
071eef5454
14 changed files with 2496 additions and 6 deletions
73
wasm/gl1/README.md
Normal file
73
wasm/gl1/README.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# wasm/gl1 — GL 1.x fixed-function pipeline on WebGL2
|
||||
|
||||
Emulation layer that lets KiCad's 3D viewer renderer (`RENDER_3D_OPENGL`,
|
||||
pure GL 1.x fixed-function, compiled **unmodified**) render in the browser.
|
||||
Supersedes the no-op link stubs of `wasm/stubs/gl_ffp_stub.c` (git history
|
||||
has the old file): same public surface, real implementations.
|
||||
|
||||
TDD harness: `tests/3d-regression/` — 47 native-golden scenarios; parity via
|
||||
`npm run 3d:check:parity` (see that README).
|
||||
|
||||
## How the symbols resolve
|
||||
|
||||
In the WASM build, `gl*` are plain C functions split between two providers:
|
||||
|
||||
- **Emscripten's WebGL library** (`-sMAX_WEBGL_VERSION=2`) owns every
|
||||
modern/WebGL2 name (`glClear`, `glDrawArrays`, `glTexImage2D`, buffers,
|
||||
shaders, stencil...).
|
||||
- **This shim** owns the FFP-only names WebGL2 lacks (`glBegin`, display
|
||||
lists, matrix stack, `glLight*`/`glMaterial*`, client-array pointers,
|
||||
`glTexEnv*`, `glAlphaFunc`, GLU quadrics) — see `src/gl1_entry_ffp.cpp`.
|
||||
|
||||
The emulator must also *observe* a few Emscripten-owned calls (FFP `glEnable`
|
||||
caps, draws over client arrays, matrix readback, state recorded inside
|
||||
display lists). Those are intercepted with **wasm-ld `--wrap`**: every name in
|
||||
`wrapped_symbols.txt` gets a `-Wl,--wrap=<sym>` flag at both link sites, the
|
||||
interceptors live in `src/gl1_entry_wrapped.cpp`, and `__real_*` forwards to
|
||||
the WebGL library. No kicad/wxwidgets sources are touched.
|
||||
|
||||
Link sites (both read `sources.txt` + `wrapped_symbols.txt`):
|
||||
|
||||
- `tests/3d-regression/wasm/Makefile` (the TDD harness)
|
||||
- `scripts/kicad/build-kicad-target.sh` (`GL3D_LINK_FLAGS`, production
|
||||
`kicad_editor`)
|
||||
|
||||
## Draw routing
|
||||
|
||||
A `glDrawArrays`/`glDrawElements` call is FFP traffic iff `GL_VERTEX_ARRAY`
|
||||
client state is enabled: only GL1 code calls `glEnableClientState`, while the
|
||||
raytracer blit (`eda_3d_canvas_wasm.cpp`) and the 2D WebGL GAL drive their own
|
||||
GLSL programs and never touch client state — their draws pass through
|
||||
untouched.
|
||||
|
||||
## Invariants that are easy to break (learned from the native goldens)
|
||||
|
||||
- **Display lists snapshot client arrays at record time.** The renderer sets
|
||||
`gl*Pointer`, records `glDrawArrays` inside `glNewList`, then `delete[]`s
|
||||
the arrays right after `glEndList` (`layer_triangles.cpp`) — a recorder
|
||||
that stores pointers reads freed memory. Copy eagerly at record.
|
||||
- **Lights live in eye space, captured at `glLightfv` time.** `init_lights()`
|
||||
runs under an identity modelview → the directional lights are anchored to
|
||||
the camera. Re-deriving light directions at draw time breaks every lit
|
||||
scenario.
|
||||
- **Do not "fix" mismatched normal counts.** `generate_middle_triangles`
|
||||
rejecting countersink walls (normals ≠ vertices) is upstream KiCad behavior
|
||||
the goldens document; tolerating it would *diverge* from native.
|
||||
- **Lighting is per-vertex (Gouraud)** to match fixed-function output;
|
||||
per-fragment lighting visibly mismatches speculars on coarse meshes.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
sources.txt / wrapped_symbols.txt single source of truth for both link sites
|
||||
include/gl1_shim.h internal API + the state mirror
|
||||
src/gl1_entry_ffp.cpp the 52 FFP-only public entry points
|
||||
src/gl1_entry_wrapped.cpp __wrap_* interceptors (mechanism-aware TU)
|
||||
src/gl1_state.cpp state singleton, capability routing
|
||||
src/gl1_matrix.cpp MODELVIEW/PROJECTION stacks (+readback)
|
||||
src/gl1_immediate.cpp glBegin/glEnd + primitive conversion
|
||||
src/gl1_dlist.cpp display-list recorder/replayer
|
||||
src/gl1_draw.cpp draw execution (stream VBO, attrib setup)
|
||||
src/gl1_shaders.cpp the FFP uber-program (ES 3.00) + uniforms
|
||||
src/gl1_glu.cpp GLU quadrics (SGI tessellation) + gluPerspective
|
||||
```
|
||||
267
wasm/gl1/include/gl1_shim.h
Normal file
267
wasm/gl1/include/gl1_shim.h
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
* 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 );
|
||||
|
||||
// --- 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 );
|
||||
// Routed glDrawArrays/glDrawElements over FFP client-array state (M3/M5).
|
||||
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
|
||||
9
wasm/gl1/sources.txt
Normal file
9
wasm/gl1/sources.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
gl1_entry_ffp.cpp
|
||||
gl1_entry_wrapped.cpp
|
||||
gl1_state.cpp
|
||||
gl1_matrix.cpp
|
||||
gl1_immediate.cpp
|
||||
gl1_dlist.cpp
|
||||
gl1_draw.cpp
|
||||
gl1_shaders.cpp
|
||||
gl1_glu.cpp
|
||||
202
wasm/gl1/src/gl1_dlist.cpp
Normal file
202
wasm/gl1/src/gl1_dlist.cpp
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* gl1_dlist — display-list name allocation, recording and replay.
|
||||
*
|
||||
* Semantics the renderer depends on:
|
||||
* - glGenLists creates EXISTING (empty) lists: KiCad's pattern is
|
||||
* `id = glGenLists(1); if( glIsList(id) ) { glNewList(...); }`
|
||||
* (layer_triangles.cpp, render_3d_opengl.cpp:1327) — glIsList must be
|
||||
* true right after allocation or nothing ever renders.
|
||||
* - Only GL_COMPILE recording exists (COMPILE_AND_EXECUTE is asserted).
|
||||
* - M3 adds the command recorder (state + geometry + glDrawArrays with
|
||||
* client-array snapshots); until then recorded commands are dropped,
|
||||
* which keeps the pre-M3 red scenarios blank instead of crashing.
|
||||
*/
|
||||
|
||||
#include "gl1_shim.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace gl1
|
||||
{
|
||||
|
||||
struct DList
|
||||
{
|
||||
// M3: recorded command stream + baked static VBO.
|
||||
bool empty = true;
|
||||
};
|
||||
|
||||
static std::map<GLuint, DList> s_lists;
|
||||
static GLuint s_nextId = 1;
|
||||
static bool s_recording = false;
|
||||
static GLuint s_recordingId = 0;
|
||||
|
||||
|
||||
bool dlistRecording()
|
||||
{
|
||||
return s_recording;
|
||||
}
|
||||
|
||||
|
||||
GLuint dlistGenLists( GLsizei range )
|
||||
{
|
||||
if( range <= 0 )
|
||||
return 0;
|
||||
|
||||
const GLuint first = s_nextId;
|
||||
|
||||
for( GLsizei i = 0; i < range; ++i )
|
||||
s_lists[s_nextId++] = DList{};
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
|
||||
GLboolean dlistIsList( GLuint list )
|
||||
{
|
||||
return s_lists.count( list ) ? GL_TRUE : GL_FALSE;
|
||||
}
|
||||
|
||||
|
||||
void dlistNewList( GLuint list, GLenum mode )
|
||||
{
|
||||
if( list == 0 )
|
||||
{
|
||||
GL1_WARN_ONCE( "glNewList(0) is invalid — ignored" );
|
||||
return;
|
||||
}
|
||||
|
||||
if( mode != GL_COMPILE )
|
||||
GL1_WARN_ONCE( "glNewList: only GL_COMPILE is supported (got 0x%x)", mode );
|
||||
|
||||
if( s_recording )
|
||||
{
|
||||
GL1_WARN_ONCE( "glNewList while already recording — previous list discarded" );
|
||||
dlistEndList();
|
||||
}
|
||||
|
||||
s_lists[list] = DList{}; // re-recording replaces the old content
|
||||
s_recording = true;
|
||||
s_recordingId = list;
|
||||
}
|
||||
|
||||
|
||||
void dlistEndList()
|
||||
{
|
||||
if( !s_recording )
|
||||
{
|
||||
GL1_WARN_ONCE( "glEndList without glNewList — ignored" );
|
||||
return;
|
||||
}
|
||||
|
||||
s_recording = false;
|
||||
s_recordingId = 0;
|
||||
}
|
||||
|
||||
|
||||
void dlistCallList( GLuint list )
|
||||
{
|
||||
auto it = s_lists.find( list );
|
||||
|
||||
if( it == s_lists.end() )
|
||||
return; // calling a nonexistent list is a silent no-op in GL
|
||||
|
||||
// M3: replay the recorded command stream.
|
||||
}
|
||||
|
||||
|
||||
void dlistDeleteLists( GLuint list, GLsizei range )
|
||||
{
|
||||
for( GLsizei i = 0; i < range; ++i )
|
||||
s_lists.erase( list + (GLuint) i );
|
||||
}
|
||||
|
||||
|
||||
// --- recording hooks (M3 replaces these drops with the command recorder) ---
|
||||
|
||||
void dlistRecordEnable( GLenum cap, bool enable )
|
||||
{
|
||||
(void) cap;
|
||||
(void) enable;
|
||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
||||
}
|
||||
|
||||
|
||||
void dlistRecordBindTexture( GLenum target, GLuint texture )
|
||||
{
|
||||
(void) target;
|
||||
(void) texture;
|
||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
||||
}
|
||||
|
||||
|
||||
void dlistRecordBlendFunc( GLenum sfactor, GLenum dfactor )
|
||||
{
|
||||
(void) sfactor;
|
||||
(void) dfactor;
|
||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
||||
}
|
||||
|
||||
|
||||
void dlistRecordLineWidth( GLfloat width )
|
||||
{
|
||||
(void) width;
|
||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
||||
}
|
||||
|
||||
|
||||
void dlistRecordAlphaFunc( GLenum func, GLclampf ref )
|
||||
{
|
||||
(void) func;
|
||||
(void) ref;
|
||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
||||
}
|
||||
|
||||
|
||||
void dlistRecordNormal( float nx, float ny, float nz )
|
||||
{
|
||||
(void) nx;
|
||||
(void) ny;
|
||||
(void) nz;
|
||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
||||
}
|
||||
|
||||
|
||||
void dlistRecordColor( float r, float g, float b, float a )
|
||||
{
|
||||
(void) r;
|
||||
(void) g;
|
||||
(void) b;
|
||||
(void) a;
|
||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
||||
}
|
||||
|
||||
|
||||
void dlistRecordBegin( GLenum mode )
|
||||
{
|
||||
(void) mode;
|
||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
||||
}
|
||||
|
||||
|
||||
void dlistRecordVertex( float x, float y, float z )
|
||||
{
|
||||
(void) x;
|
||||
(void) y;
|
||||
(void) z;
|
||||
}
|
||||
|
||||
|
||||
void dlistRecordEnd()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void dlistRecordDrawArrays( GLenum mode, GLint first, GLsizei count )
|
||||
{
|
||||
(void) mode;
|
||||
(void) first;
|
||||
(void) count;
|
||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
||||
}
|
||||
|
||||
} // namespace gl1
|
||||
88
wasm/gl1/src/gl1_draw.cpp
Normal file
88
wasm/gl1/src/gl1_draw.cpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* gl1_draw — draw execution: streaming immediate-mode geometry and routed
|
||||
* client-array / user-VBO draws, all through the FFP uber-program.
|
||||
*
|
||||
* VAO policy: default VAO only, all four attributes respecified per draw. The
|
||||
* renderer binds its own VBO/IBO on the default VAO (3d_model.cpp), so a shim
|
||||
* VAO would hide the user's GL_ELEMENT_ARRAY_BUFFER binding. The shim never
|
||||
* binds GL_ELEMENT_ARRAY_BUFFER itself.
|
||||
*
|
||||
* GL_ARRAY_BUFFER discipline: GL1 code assumes the binding it last set — the
|
||||
* shim saves and restores it around its own streaming uploads so a later
|
||||
* gl*Pointer call captures the app's binding, not the shim's scratch VBO.
|
||||
*/
|
||||
|
||||
#include "gl1_shim.h"
|
||||
|
||||
namespace gl1
|
||||
{
|
||||
|
||||
static GLuint s_streamVBO = 0;
|
||||
|
||||
enum
|
||||
{
|
||||
ATTR_POSITION = 0,
|
||||
ATTR_NORMAL = 1,
|
||||
ATTR_COLOR = 2,
|
||||
ATTR_TEXCOORD = 3,
|
||||
};
|
||||
|
||||
|
||||
void drawImmVertices( GLenum mode, const ImmVertex* verts, GLsizei count )
|
||||
{
|
||||
if( !programSync() )
|
||||
return;
|
||||
|
||||
if( !s_streamVBO )
|
||||
glGenBuffers( 1, &s_streamVBO );
|
||||
|
||||
GLint prevArrayBuffer = 0;
|
||||
glGetIntegerv( GL_ARRAY_BUFFER_BINDING, &prevArrayBuffer );
|
||||
|
||||
glBindBuffer( GL_ARRAY_BUFFER, s_streamVBO );
|
||||
glBufferData( GL_ARRAY_BUFFER, (GLsizeiptr) ( count * sizeof( ImmVertex ) ), verts,
|
||||
GL_STREAM_DRAW );
|
||||
|
||||
const GLsizei stride = (GLsizei) sizeof( ImmVertex );
|
||||
|
||||
glEnableVertexAttribArray( ATTR_POSITION );
|
||||
glVertexAttribPointer( ATTR_POSITION, 3, GL_FLOAT, GL_FALSE, stride, (const void*) 0 );
|
||||
|
||||
glEnableVertexAttribArray( ATTR_NORMAL );
|
||||
glVertexAttribPointer( ATTR_NORMAL, 3, GL_FLOAT, GL_FALSE, stride,
|
||||
(const void*) ( 3 * sizeof( float ) ) );
|
||||
|
||||
glEnableVertexAttribArray( ATTR_COLOR );
|
||||
glVertexAttribPointer( ATTR_COLOR, 4, GL_FLOAT, GL_FALSE, stride,
|
||||
(const void*) ( 6 * sizeof( float ) ) );
|
||||
|
||||
glDisableVertexAttribArray( ATTR_TEXCOORD );
|
||||
glVertexAttrib2f( ATTR_TEXCOORD, 0.0f, 0.0f );
|
||||
|
||||
__real_glDrawArrays( mode, 0, count );
|
||||
|
||||
glBindBuffer( GL_ARRAY_BUFFER, (GLuint) prevArrayBuffer );
|
||||
}
|
||||
|
||||
|
||||
// Routed glDrawArrays over FFP client-array state (M3).
|
||||
void drawClientArrays( GLenum mode, GLint first, GLsizei count )
|
||||
{
|
||||
(void) mode;
|
||||
(void) first;
|
||||
(void) count;
|
||||
GL1_WARN_ONCE( "client-array glDrawArrays not implemented yet (M3) — draw dropped" );
|
||||
}
|
||||
|
||||
|
||||
// Routed glDrawElements over user VBO/IBO state (M5).
|
||||
void drawClientElements( GLenum mode, GLsizei count, GLenum type, const GLvoid* indices )
|
||||
{
|
||||
(void) mode;
|
||||
(void) count;
|
||||
(void) type;
|
||||
(void) indices;
|
||||
GL1_WARN_ONCE( "client-array glDrawElements not implemented yet (M5) — draw dropped" );
|
||||
}
|
||||
|
||||
} // namespace gl1
|
||||
623
wasm/gl1/src/gl1_entry_ffp.cpp
Normal file
623
wasm/gl1/src/gl1_entry_ffp.cpp
Normal file
|
|
@ -0,0 +1,623 @@
|
|||
/*
|
||||
* gl1_entry_ffp — the 52 public FFP entry points the shim owns.
|
||||
*
|
||||
* These are the GL 1.x names absent from Emscripten's WebGL2 library (the
|
||||
* exact surface wasm/stubs/gl_ffp_stub.c used to no-op). Signatures match
|
||||
* Emscripten's <GL/gl.h> / the project's <GL/glu.h> (GLU lives in gl1_glu.cpp).
|
||||
*
|
||||
* Per the GL_COMPILE contract, listable calls funnel into the display-list
|
||||
* recorder while recording instead of executing; client-array state calls,
|
||||
* glGenLists/glIsList/glDeleteLists and glGet* always execute immediately.
|
||||
* Listable calls the renderer never records (matrix, lighting, texenv...)
|
||||
* warn once and drop while recording rather than silently corrupting state.
|
||||
*/
|
||||
|
||||
#include "gl1_shim.h"
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
using namespace gl1;
|
||||
|
||||
// Guard for listable entry points that the recorder intentionally does not
|
||||
// support because the renderer never records them.
|
||||
#define GL1_UNRECORDED( name ) \
|
||||
if( dlistRecording() ) \
|
||||
{ \
|
||||
GL1_WARN_ONCE( name " inside glNewList is not supported — call dropped" ); \
|
||||
return; \
|
||||
}
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
// ---- Immediate mode ----------------------------------------------------
|
||||
|
||||
void glBegin( GLenum mode )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
dlistRecordBegin( mode );
|
||||
return;
|
||||
}
|
||||
|
||||
immBegin( mode );
|
||||
}
|
||||
|
||||
|
||||
void glEnd( void )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
dlistRecordEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
immEnd();
|
||||
}
|
||||
|
||||
|
||||
void glVertex2f( GLfloat x, GLfloat y )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
dlistRecordVertex( x, y, 0.0f );
|
||||
return;
|
||||
}
|
||||
|
||||
immVertex( x, y, 0.0f );
|
||||
}
|
||||
|
||||
|
||||
void glVertex3f( GLfloat x, GLfloat y, GLfloat z )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
dlistRecordVertex( x, y, z );
|
||||
return;
|
||||
}
|
||||
|
||||
immVertex( x, y, z );
|
||||
}
|
||||
|
||||
|
||||
void glVertex3d( GLdouble x, GLdouble y, GLdouble z )
|
||||
{
|
||||
glVertex3f( (GLfloat) x, (GLfloat) y, (GLfloat) z );
|
||||
}
|
||||
|
||||
|
||||
void glNormal3f( GLfloat nx, GLfloat ny, GLfloat nz )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
dlistRecordNormal( nx, ny, nz );
|
||||
return;
|
||||
}
|
||||
|
||||
S().currentNormal = glm::vec3( nx, ny, nz );
|
||||
}
|
||||
|
||||
|
||||
void glColor3f( GLfloat r, GLfloat g, GLfloat b )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
dlistRecordColor( r, g, b, 1.0f );
|
||||
return;
|
||||
}
|
||||
|
||||
S().currentColor = glm::vec4( r, g, b, 1.0f );
|
||||
}
|
||||
|
||||
|
||||
void glColor4f( GLfloat r, GLfloat g, GLfloat b, GLfloat a )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
dlistRecordColor( r, g, b, a );
|
||||
return;
|
||||
}
|
||||
|
||||
S().currentColor = glm::vec4( r, g, b, a );
|
||||
}
|
||||
|
||||
|
||||
// ---- Display lists ------------------------------------------------------
|
||||
|
||||
GLuint glGenLists( GLsizei range )
|
||||
{
|
||||
return dlistGenLists( range );
|
||||
}
|
||||
|
||||
|
||||
void glNewList( GLuint list, GLenum mode )
|
||||
{
|
||||
dlistNewList( list, mode );
|
||||
}
|
||||
|
||||
|
||||
void glEndList( void )
|
||||
{
|
||||
dlistEndList();
|
||||
}
|
||||
|
||||
|
||||
void glCallList( GLuint list )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
GL1_WARN_ONCE( "nested glCallList inside glNewList is not supported — dropped" );
|
||||
return;
|
||||
}
|
||||
|
||||
dlistCallList( list );
|
||||
}
|
||||
|
||||
|
||||
void glDeleteLists( GLuint list, GLsizei range )
|
||||
{
|
||||
dlistDeleteLists( list, range );
|
||||
}
|
||||
|
||||
|
||||
GLboolean glIsList( GLuint list )
|
||||
{
|
||||
return dlistIsList( list );
|
||||
}
|
||||
|
||||
|
||||
// ---- Matrix stack --------------------------------------------------------
|
||||
|
||||
void glMatrixMode( GLenum mode )
|
||||
{
|
||||
GL1_UNRECORDED( "glMatrixMode" );
|
||||
|
||||
if( mode != GL_MODELVIEW && mode != GL_PROJECTION )
|
||||
{
|
||||
GL1_WARN_ONCE( "glMatrixMode: unsupported mode 0x%x (only MODELVIEW/PROJECTION)", mode );
|
||||
return;
|
||||
}
|
||||
|
||||
S().matrixMode = mode;
|
||||
}
|
||||
|
||||
|
||||
void glLoadIdentity( void )
|
||||
{
|
||||
GL1_UNRECORDED( "glLoadIdentity" );
|
||||
matrixLoadIdentity();
|
||||
}
|
||||
|
||||
|
||||
void glLoadMatrixf( const GLfloat* m )
|
||||
{
|
||||
GL1_UNRECORDED( "glLoadMatrixf" );
|
||||
matrixLoadf( m );
|
||||
}
|
||||
|
||||
|
||||
void glPushMatrix( void )
|
||||
{
|
||||
GL1_UNRECORDED( "glPushMatrix" );
|
||||
matrixPush();
|
||||
}
|
||||
|
||||
|
||||
void glPopMatrix( void )
|
||||
{
|
||||
GL1_UNRECORDED( "glPopMatrix" );
|
||||
matrixPop();
|
||||
}
|
||||
|
||||
|
||||
void glTranslatef( GLfloat x, GLfloat y, GLfloat z )
|
||||
{
|
||||
GL1_UNRECORDED( "glTranslatef" );
|
||||
matrixTranslate( x, y, z );
|
||||
}
|
||||
|
||||
|
||||
void glRotatef( GLfloat angle, GLfloat x, GLfloat y, GLfloat z )
|
||||
{
|
||||
GL1_UNRECORDED( "glRotatef" );
|
||||
matrixRotate( angle, x, y, z );
|
||||
}
|
||||
|
||||
|
||||
void glScalef( GLfloat x, GLfloat y, GLfloat z )
|
||||
{
|
||||
GL1_UNRECORDED( "glScalef" );
|
||||
matrixScale( x, y, z );
|
||||
}
|
||||
|
||||
|
||||
void glScaled( GLdouble x, GLdouble y, GLdouble z )
|
||||
{
|
||||
GL1_UNRECORDED( "glScaled" );
|
||||
matrixScale( (float) x, (float) y, (float) z );
|
||||
}
|
||||
|
||||
|
||||
// ---- Fixed-function lighting / material ----------------------------------
|
||||
|
||||
void glShadeModel( GLenum mode )
|
||||
{
|
||||
GL1_UNRECORDED( "glShadeModel" );
|
||||
|
||||
if( mode != GL_SMOOTH )
|
||||
GL1_WARN_ONCE( "glShadeModel: only GL_SMOOTH is supported (got 0x%x)", mode );
|
||||
|
||||
S().shadeModel = mode;
|
||||
}
|
||||
|
||||
|
||||
void glLightfv( GLenum light, GLenum pname, const GLfloat* params )
|
||||
{
|
||||
GL1_UNRECORDED( "glLightfv" );
|
||||
|
||||
if( light < GL_LIGHT0 || light > GL_LIGHT7 )
|
||||
return;
|
||||
|
||||
State& s = S();
|
||||
Light& l = s.lights[light - GL_LIGHT0];
|
||||
|
||||
switch( pname )
|
||||
{
|
||||
case GL_AMBIENT:
|
||||
l.ambient = glm::make_vec4( params );
|
||||
break;
|
||||
|
||||
case GL_DIFFUSE:
|
||||
l.diffuse = glm::make_vec4( params );
|
||||
break;
|
||||
|
||||
case GL_SPECULAR:
|
||||
l.specular = glm::make_vec4( params );
|
||||
break;
|
||||
|
||||
case GL_POSITION:
|
||||
// GL 1.x: the position is transformed by the modelview matrix current
|
||||
// AT THIS CALL and stored in eye coordinates. init_lights() runs under
|
||||
// an identity modelview (directional lights anchored in eye space);
|
||||
// the per-frame headlight is set under the camera view matrix.
|
||||
l.posEye = s.mv.back() * glm::make_vec4( params );
|
||||
break;
|
||||
|
||||
default:
|
||||
GL1_WARN_ONCE( "glLightfv: unsupported pname 0x%x", pname );
|
||||
return;
|
||||
}
|
||||
|
||||
s.lightingDirty = true;
|
||||
}
|
||||
|
||||
|
||||
void glLightModeli( GLenum pname, GLint param )
|
||||
{
|
||||
GL1_UNRECORDED( "glLightModeli" );
|
||||
|
||||
if( pname == GL_LIGHT_MODEL_TWO_SIDE )
|
||||
{
|
||||
S().twoSide = ( param != 0 );
|
||||
S().lightingDirty = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
GL1_WARN_ONCE( "glLightModeli: unsupported pname 0x%x", pname );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void glLightModelfv( GLenum pname, const GLfloat* params )
|
||||
{
|
||||
GL1_UNRECORDED( "glLightModelfv" );
|
||||
|
||||
if( pname == GL_LIGHT_MODEL_AMBIENT )
|
||||
{
|
||||
S().lightModelAmbient = glm::make_vec4( params );
|
||||
S().lightingDirty = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
GL1_WARN_ONCE( "glLightModelfv: unsupported pname 0x%x", pname );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void glMaterialf( GLenum face, GLenum pname, GLfloat param )
|
||||
{
|
||||
GL1_UNRECORDED( "glMaterialf" );
|
||||
|
||||
if( face != GL_FRONT_AND_BACK )
|
||||
GL1_WARN_ONCE( "glMaterialf: only GL_FRONT_AND_BACK is supported (got 0x%x)", face );
|
||||
|
||||
if( pname == GL_SHININESS )
|
||||
{
|
||||
S().material.shininess = param;
|
||||
S().lightingDirty = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
GL1_WARN_ONCE( "glMaterialf: unsupported pname 0x%x", pname );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void glMaterialfv( GLenum face, GLenum pname, const GLfloat* params )
|
||||
{
|
||||
GL1_UNRECORDED( "glMaterialfv" );
|
||||
|
||||
if( face != GL_FRONT_AND_BACK )
|
||||
GL1_WARN_ONCE( "glMaterialfv: only GL_FRONT_AND_BACK is supported (got 0x%x)", face );
|
||||
|
||||
State& s = S();
|
||||
|
||||
switch( pname )
|
||||
{
|
||||
case GL_AMBIENT:
|
||||
s.material.ambient = glm::make_vec4( params );
|
||||
break;
|
||||
|
||||
case GL_DIFFUSE:
|
||||
s.material.diffuse = glm::make_vec4( params );
|
||||
break;
|
||||
|
||||
case GL_AMBIENT_AND_DIFFUSE:
|
||||
s.material.ambient = glm::make_vec4( params );
|
||||
s.material.diffuse = glm::make_vec4( params );
|
||||
break;
|
||||
|
||||
case GL_SPECULAR:
|
||||
s.material.specular = glm::make_vec4( params );
|
||||
break;
|
||||
|
||||
case GL_EMISSION:
|
||||
s.material.emission = glm::make_vec4( params );
|
||||
break;
|
||||
|
||||
case GL_SHININESS:
|
||||
s.material.shininess = params[0];
|
||||
break;
|
||||
|
||||
default:
|
||||
GL1_WARN_ONCE( "glMaterialfv: unsupported pname 0x%x", pname );
|
||||
return;
|
||||
}
|
||||
|
||||
s.lightingDirty = true;
|
||||
}
|
||||
|
||||
|
||||
void glColorMaterial( GLenum face, GLenum mode )
|
||||
{
|
||||
GL1_UNRECORDED( "glColorMaterial" );
|
||||
|
||||
if( face != GL_FRONT_AND_BACK || mode != GL_AMBIENT_AND_DIFFUSE )
|
||||
{
|
||||
GL1_WARN_ONCE( "glColorMaterial: only (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE) is "
|
||||
"supported (got 0x%x, 0x%x)", face, mode );
|
||||
}
|
||||
|
||||
S().colorMaterialMode = mode;
|
||||
S().lightingDirty = true;
|
||||
}
|
||||
|
||||
|
||||
// ---- Client-state vertex arrays (always execute, even while recording) ----
|
||||
|
||||
static ClientArray* clientArraySlot( GLenum cap )
|
||||
{
|
||||
State& s = S();
|
||||
|
||||
switch( cap )
|
||||
{
|
||||
case GL_VERTEX_ARRAY: return &s.clientArrays[CA_VERTEX];
|
||||
case GL_NORMAL_ARRAY: return &s.clientArrays[CA_NORMAL];
|
||||
case GL_COLOR_ARRAY: return &s.clientArrays[CA_COLOR];
|
||||
case GL_TEXTURE_COORD_ARRAY: return &s.clientArrays[CA_TEXCOORD];
|
||||
default:
|
||||
GL1_WARN_ONCE( "unsupported client-state cap 0x%x", cap );
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void glEnableClientState( GLenum cap )
|
||||
{
|
||||
if( ClientArray* a = clientArraySlot( cap ) )
|
||||
a->enabled = true;
|
||||
}
|
||||
|
||||
|
||||
void glDisableClientState( GLenum cap )
|
||||
{
|
||||
if( ClientArray* a = clientArraySlot( cap ) )
|
||||
a->enabled = false;
|
||||
}
|
||||
|
||||
|
||||
void glClientActiveTexture( GLenum texture )
|
||||
{
|
||||
if( texture != GL_TEXTURE0 )
|
||||
GL1_WARN_ONCE( "glClientActiveTexture: only GL_TEXTURE0 is supported (got 0x%x)",
|
||||
texture );
|
||||
}
|
||||
|
||||
|
||||
// GL1 semantics: gl*Pointer captures the GL_ARRAY_BUFFER binding current at
|
||||
// the call (0 = client memory, nonzero = byte offset into that VBO).
|
||||
static GLuint currentArrayBufferBinding()
|
||||
{
|
||||
GLint binding = 0;
|
||||
glGetIntegerv( GL_ARRAY_BUFFER_BINDING, &binding );
|
||||
return (GLuint) binding;
|
||||
}
|
||||
|
||||
|
||||
void glVertexPointer( GLint size, GLenum type, GLsizei stride, const GLvoid* ptr )
|
||||
{
|
||||
ClientArray& a = S().clientArrays[CA_VERTEX];
|
||||
a.size = size;
|
||||
a.type = type;
|
||||
a.stride = stride;
|
||||
a.pointer = ptr;
|
||||
a.boundBuffer = currentArrayBufferBinding();
|
||||
}
|
||||
|
||||
|
||||
void glNormalPointer( GLenum type, GLsizei stride, const GLvoid* ptr )
|
||||
{
|
||||
ClientArray& a = S().clientArrays[CA_NORMAL];
|
||||
a.size = 3;
|
||||
a.type = type;
|
||||
a.stride = stride;
|
||||
a.pointer = ptr;
|
||||
a.boundBuffer = currentArrayBufferBinding();
|
||||
}
|
||||
|
||||
|
||||
void glColorPointer( GLint size, GLenum type, GLsizei stride, const GLvoid* ptr )
|
||||
{
|
||||
ClientArray& a = S().clientArrays[CA_COLOR];
|
||||
a.size = size;
|
||||
a.type = type;
|
||||
a.stride = stride;
|
||||
a.pointer = ptr;
|
||||
a.boundBuffer = currentArrayBufferBinding();
|
||||
}
|
||||
|
||||
|
||||
void glTexCoordPointer( GLint size, GLenum type, GLsizei stride, const GLvoid* ptr )
|
||||
{
|
||||
ClientArray& a = S().clientArrays[CA_TEXCOORD];
|
||||
a.size = size;
|
||||
a.type = type;
|
||||
a.stride = stride;
|
||||
a.pointer = ptr;
|
||||
a.boundBuffer = currentArrayBufferBinding();
|
||||
}
|
||||
|
||||
|
||||
// ---- Fixed-function texture environment -----------------------------------
|
||||
|
||||
static void texEnvSet( GLenum pname, GLenum param )
|
||||
{
|
||||
State& s = S();
|
||||
|
||||
switch( pname )
|
||||
{
|
||||
case GL_TEXTURE_ENV_MODE: s.texEnvMode = param; break;
|
||||
case GL_COMBINE_RGB: s.combineRGB = param; break;
|
||||
case GL_COMBINE_ALPHA: s.combineAlpha = param; break;
|
||||
case GL_SRC0_RGB: s.srcRGB[0] = param; break;
|
||||
case GL_SRC1_RGB: s.srcRGB[1] = param; break;
|
||||
case GL_SRC2_RGB: s.srcRGB[2] = param; break;
|
||||
case GL_OPERAND0_RGB: s.operandRGB[0] = param; break;
|
||||
case GL_OPERAND1_RGB: s.operandRGB[1] = param; break;
|
||||
case GL_OPERAND2_RGB: s.operandRGB[2] = param; break;
|
||||
case GL_SRC0_ALPHA: s.srcAlpha[0] = param; break;
|
||||
case GL_SRC1_ALPHA: s.srcAlpha[1] = param; break;
|
||||
case GL_SRC2_ALPHA: s.srcAlpha[2] = param; break;
|
||||
case GL_OPERAND0_ALPHA: s.operandAlpha[0] = param; break;
|
||||
case GL_OPERAND1_ALPHA: s.operandAlpha[1] = param; break;
|
||||
case GL_OPERAND2_ALPHA: s.operandAlpha[2] = param; break;
|
||||
default:
|
||||
GL1_WARN_ONCE( "glTexEnv: unsupported pname 0x%x", pname );
|
||||
return;
|
||||
}
|
||||
|
||||
s.texEnvDirty = true;
|
||||
}
|
||||
|
||||
|
||||
void glTexEnvi( GLenum target, GLenum pname, GLint param )
|
||||
{
|
||||
GL1_UNRECORDED( "glTexEnvi" );
|
||||
|
||||
if( target != GL_TEXTURE_ENV )
|
||||
{
|
||||
GL1_WARN_ONCE( "glTexEnvi: unsupported target 0x%x", target );
|
||||
return;
|
||||
}
|
||||
|
||||
texEnvSet( pname, (GLenum) param );
|
||||
}
|
||||
|
||||
|
||||
void glTexEnvf( GLenum target, GLenum pname, GLfloat param )
|
||||
{
|
||||
GL1_UNRECORDED( "glTexEnvf" );
|
||||
|
||||
if( target != GL_TEXTURE_ENV )
|
||||
{
|
||||
GL1_WARN_ONCE( "glTexEnvf: unsupported target 0x%x", target );
|
||||
return;
|
||||
}
|
||||
|
||||
texEnvSet( pname, (GLenum) param );
|
||||
}
|
||||
|
||||
|
||||
void glTexEnvfv( GLenum target, GLenum pname, const GLfloat* params )
|
||||
{
|
||||
GL1_UNRECORDED( "glTexEnvfv" );
|
||||
|
||||
if( target != GL_TEXTURE_ENV )
|
||||
{
|
||||
GL1_WARN_ONCE( "glTexEnvfv: unsupported target 0x%x", target );
|
||||
return;
|
||||
}
|
||||
|
||||
if( pname == GL_TEXTURE_ENV_COLOR )
|
||||
{
|
||||
S().texEnvColor = glm::make_vec4( params );
|
||||
S().texEnvDirty = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
texEnvSet( pname, (GLenum) params[0] );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- Misc fixed-function state absent from GLES3 ---------------------------
|
||||
|
||||
void glAlphaFunc( GLenum func, GLclampf ref )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
dlistRecordAlphaFunc( func, ref );
|
||||
return;
|
||||
}
|
||||
|
||||
S().alphaFunc = func;
|
||||
S().alphaRef = ref;
|
||||
S().miscDirty = true;
|
||||
}
|
||||
|
||||
|
||||
void glPolygonMode( GLenum face, GLenum mode )
|
||||
{
|
||||
GL1_UNRECORDED( "glPolygonMode" );
|
||||
(void) face;
|
||||
|
||||
if( mode != GL_FILL )
|
||||
GL1_WARN_ONCE( "glPolygonMode: only GL_FILL is supported (got 0x%x)", mode );
|
||||
}
|
||||
|
||||
|
||||
void glClearDepth( GLclampd depth )
|
||||
{
|
||||
GL1_UNRECORDED( "glClearDepth" );
|
||||
glClearDepthf( (GLclampf) depth );
|
||||
}
|
||||
|
||||
|
||||
void glPointSize( GLfloat size )
|
||||
{
|
||||
GL1_UNRECORDED( "glPointSize" );
|
||||
S().pointSize = size;
|
||||
S().miscDirty = true;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
188
wasm/gl1/src/gl1_entry_wrapped.cpp
Normal file
188
wasm/gl1/src/gl1_entry_wrapped.cpp
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* gl1_entry_wrapped — interceptors for the Emscripten-owned GL names the
|
||||
* emulator must observe. The ONLY mechanism-aware TU: both link sites pass
|
||||
* -Wl,--wrap=<sym> for every name in ../wrapped_symbols.txt, so references
|
||||
* land on __wrap_* here and __real_* resolves back to the WebGL JS library.
|
||||
* (Fallback if --wrap ever breaks: macro-remap in gal/opengl/kiglew.h — only
|
||||
* this file would change.)
|
||||
*
|
||||
* Draw routing: a glDrawArrays/glDrawElements call belongs to the FFP
|
||||
* pipeline iff GL_VERTEX_ARRAY client state is enabled — only GL1 code uses
|
||||
* glEnableClientState, while the raytracer blit and the 2D WebGL GAL drive
|
||||
* their own GLSL programs with glVertexAttribPointer and never touch client
|
||||
* state. Their draws pass through untouched.
|
||||
*/
|
||||
|
||||
#include "gl1_shim.h"
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using namespace gl1;
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
void __wrap_glEnable( GLenum cap )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
dlistRecordEnable( cap, true );
|
||||
return;
|
||||
}
|
||||
|
||||
if( bool* slot = ffpCapSlot( cap ) )
|
||||
{
|
||||
if( !*slot )
|
||||
{
|
||||
*slot = true;
|
||||
onCapChanged( cap );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
__real_glEnable( cap );
|
||||
}
|
||||
|
||||
|
||||
void __wrap_glDisable( GLenum cap )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
dlistRecordEnable( cap, false );
|
||||
return;
|
||||
}
|
||||
|
||||
if( bool* slot = ffpCapSlot( cap ) )
|
||||
{
|
||||
if( *slot )
|
||||
{
|
||||
*slot = false;
|
||||
onCapChanged( cap );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
__real_glDisable( cap );
|
||||
}
|
||||
|
||||
|
||||
GLboolean __wrap_glIsEnabled( GLenum cap )
|
||||
{
|
||||
// glGet-class queries execute even during display-list recording.
|
||||
if( bool* slot = ffpCapSlot( cap ) )
|
||||
return *slot ? GL_TRUE : GL_FALSE;
|
||||
|
||||
return __real_glIsEnabled( cap );
|
||||
}
|
||||
|
||||
|
||||
void __wrap_glGetFloatv( GLenum pname, GLfloat* params )
|
||||
{
|
||||
if( pname == GL_MODELVIEW_MATRIX )
|
||||
{
|
||||
std::memcpy( params, glm::value_ptr( S().mv.back() ), 16 * sizeof( GLfloat ) );
|
||||
return;
|
||||
}
|
||||
|
||||
if( pname == GL_PROJECTION_MATRIX )
|
||||
{
|
||||
std::memcpy( params, glm::value_ptr( S().proj.back() ), 16 * sizeof( GLfloat ) );
|
||||
return;
|
||||
}
|
||||
|
||||
__real_glGetFloatv( pname, params );
|
||||
}
|
||||
|
||||
|
||||
void __wrap_glDrawArrays( GLenum mode, GLint first, GLsizei count )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
dlistRecordDrawArrays( mode, first, count );
|
||||
return;
|
||||
}
|
||||
|
||||
if( !S().clientArrays[CA_VERTEX].enabled )
|
||||
{
|
||||
// Modern-GL consumer (raytracer blit, 2D GAL, the shim itself never
|
||||
// reaches here) — pass through untouched.
|
||||
__real_glDrawArrays( mode, first, count );
|
||||
return;
|
||||
}
|
||||
|
||||
drawClientArrays( mode, first, count );
|
||||
}
|
||||
|
||||
|
||||
void __wrap_glDrawElements( GLenum mode, GLsizei count, GLenum type, const GLvoid* indices )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
GL1_WARN_ONCE( "glDrawElements inside glNewList is not supported — dropped" );
|
||||
return;
|
||||
}
|
||||
|
||||
if( !S().clientArrays[CA_VERTEX].enabled )
|
||||
{
|
||||
__real_glDrawElements( mode, count, type, indices );
|
||||
return;
|
||||
}
|
||||
|
||||
drawClientElements( mode, count, type, indices );
|
||||
}
|
||||
|
||||
|
||||
void __wrap_glBindTexture( GLenum target, GLuint texture )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
dlistRecordBindTexture( target, texture );
|
||||
return;
|
||||
}
|
||||
|
||||
if( target == GL_TEXTURE_2D )
|
||||
S().boundTexture2D = texture;
|
||||
|
||||
__real_glBindTexture( target, texture );
|
||||
}
|
||||
|
||||
|
||||
void __wrap_glBlendFunc( GLenum sfactor, GLenum dfactor )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
dlistRecordBlendFunc( sfactor, dfactor );
|
||||
return;
|
||||
}
|
||||
|
||||
__real_glBlendFunc( sfactor, dfactor );
|
||||
}
|
||||
|
||||
|
||||
void __wrap_glLineWidth( GLfloat width )
|
||||
{
|
||||
if( dlistRecording() )
|
||||
{
|
||||
dlistRecordLineWidth( width );
|
||||
return;
|
||||
}
|
||||
|
||||
S().lineWidth = width;
|
||||
__real_glLineWidth( width );
|
||||
}
|
||||
|
||||
|
||||
void __wrap_glHint( GLenum target, GLenum mode )
|
||||
{
|
||||
// Only the two WebGL2-legal hints pass through; the FFP hints
|
||||
// (LINE_SMOOTH_HINT, PERSPECTIVE_CORRECTION_HINT...) would raise
|
||||
// INVALID_ENUM and are swallowed.
|
||||
if( target == GL_GENERATE_MIPMAP_HINT || target == GL_FRAGMENT_SHADER_DERIVATIVE_HINT )
|
||||
__real_glHint( target, mode );
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
106
wasm/gl1/src/gl1_glu.cpp
Normal file
106
wasm/gl1/src/gl1_glu.cpp
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* gl1_glu — GLU quadrics and gluPerspective.
|
||||
*
|
||||
* M4 ports the SGI GLU reference tessellation (quad.c) for
|
||||
* gluCylinder/gluDisk/gluSphere — the native goldens were rendered with
|
||||
* Apple's SGI-derived GLU, so vertex placement and emission order must match.
|
||||
* The quadrics emit through the shim's internal immediate-mode path
|
||||
* (immBegin/immVertex/immEnd) so they also record into display lists.
|
||||
*
|
||||
* Note: the GLU *tesselator* (gluNewTess & co, declared in the same
|
||||
* wasm/stubs/GL/glu.h) is a separate concern implemented in
|
||||
* kicad/libs/kimath/glu_tess/ — not part of this shim.
|
||||
*/
|
||||
|
||||
#include "gl1_shim.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
// Real (heap-allocated) quadric state; only the modes the renderer uses are
|
||||
// honored — GLU_FILL draw style, GLU_SMOOTH normals, GLU_OUTSIDE orientation.
|
||||
struct GLUquadric
|
||||
{
|
||||
GLenum drawStyle;
|
||||
GLenum normals;
|
||||
};
|
||||
|
||||
|
||||
GLUquadric* gluNewQuadric( void )
|
||||
{
|
||||
GLUquadric* q = new GLUquadric;
|
||||
q->drawStyle = GLU_FILL;
|
||||
q->normals = GLU_SMOOTH;
|
||||
return q;
|
||||
}
|
||||
|
||||
|
||||
void gluDeleteQuadric( GLUquadric* q )
|
||||
{
|
||||
delete q;
|
||||
}
|
||||
|
||||
|
||||
void gluQuadricDrawStyle( GLUquadric* q, GLenum style )
|
||||
{
|
||||
if( !q )
|
||||
return;
|
||||
|
||||
if( style != GLU_FILL )
|
||||
GL1_WARN_ONCE( "gluQuadricDrawStyle: only GLU_FILL is supported (got 0x%x)", style );
|
||||
|
||||
q->drawStyle = style;
|
||||
}
|
||||
|
||||
|
||||
void gluQuadricNormals( GLUquadric* q, GLenum normals )
|
||||
{
|
||||
if( !q )
|
||||
return;
|
||||
|
||||
if( normals != GLU_SMOOTH )
|
||||
GL1_WARN_ONCE( "gluQuadricNormals: only GLU_SMOOTH is supported (got 0x%x)", normals );
|
||||
|
||||
q->normals = normals;
|
||||
}
|
||||
|
||||
|
||||
void gluCylinder( GLUquadric* q, double base, double top, double height, int slices, int stacks )
|
||||
{
|
||||
(void) q;
|
||||
(void) base;
|
||||
(void) top;
|
||||
(void) height;
|
||||
(void) slices;
|
||||
(void) stacks;
|
||||
GL1_WARN_ONCE( "gluCylinder not implemented yet (M4) — geometry dropped" );
|
||||
}
|
||||
|
||||
|
||||
void gluDisk( GLUquadric* q, double inner, double outer, int slices, int loops )
|
||||
{
|
||||
(void) q;
|
||||
(void) inner;
|
||||
(void) outer;
|
||||
(void) slices;
|
||||
(void) loops;
|
||||
GL1_WARN_ONCE( "gluDisk not implemented yet (M4) — geometry dropped" );
|
||||
}
|
||||
|
||||
|
||||
void gluSphere( GLUquadric* q, double radius, int slices, int stacks )
|
||||
{
|
||||
(void) q;
|
||||
(void) radius;
|
||||
(void) slices;
|
||||
(void) stacks;
|
||||
GL1_WARN_ONCE( "gluSphere not implemented yet (M4) — geometry dropped" );
|
||||
}
|
||||
|
||||
|
||||
void gluPerspective( double fovy, double aspect, double zNear, double zFar )
|
||||
{
|
||||
gl1::matrixPerspective( fovy, aspect, zNear, zFar );
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
152
wasm/gl1/src/gl1_immediate.cpp
Normal file
152
wasm/gl1/src/gl1_immediate.cpp
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* gl1_immediate — glBegin/glEnd vertex accumulation and primitive conversion.
|
||||
*
|
||||
* Also the internal emission path for GLU quadrics and display-list replay:
|
||||
* every piece of shim geometry funnels through immBegin/immVertex/immEnd so
|
||||
* primitive conversion and the draw pipeline live in exactly one place.
|
||||
*
|
||||
* WebGL2 has no GL_QUADS / GL_QUAD_STRIP / GL_LINE_LOOP / GL_POLYGON:
|
||||
* QUADS -> GL_TRIANGLES, each quad (0,1,2)(0,2,3)
|
||||
* QUAD_STRIP -> GL_TRIANGLE_STRIP (same vertex order covers the same area)
|
||||
* LINE_LOOP -> GL_LINE_STRIP with the first vertex appended
|
||||
* POLYGON -> unused by the renderer (assert-logged, drawn as a fan)
|
||||
*/
|
||||
|
||||
#include "gl1_shim.h"
|
||||
|
||||
namespace gl1
|
||||
{
|
||||
|
||||
static std::vector<ImmVertex> s_verts;
|
||||
static GLenum s_mode = 0;
|
||||
static bool s_active = false;
|
||||
|
||||
|
||||
bool immActive()
|
||||
{
|
||||
return s_active;
|
||||
}
|
||||
|
||||
|
||||
void immBegin( GLenum mode )
|
||||
{
|
||||
if( s_active )
|
||||
{
|
||||
GL1_WARN_ONCE( "glBegin inside glBegin/glEnd — call ignored" );
|
||||
return;
|
||||
}
|
||||
|
||||
s_mode = mode;
|
||||
s_active = true;
|
||||
s_verts.clear();
|
||||
|
||||
if( mode == GL_POLYGON )
|
||||
GL1_WARN_ONCE( "GL_POLYGON is not exercised by the renderer; drawing as a triangle fan" );
|
||||
}
|
||||
|
||||
|
||||
void immVertex( float x, float y, float z )
|
||||
{
|
||||
if( !s_active )
|
||||
{
|
||||
GL1_WARN_ONCE( "glVertex outside glBegin/glEnd — ignored" );
|
||||
return;
|
||||
}
|
||||
|
||||
const State& s = S();
|
||||
|
||||
ImmVertex v;
|
||||
v.px = x;
|
||||
v.py = y;
|
||||
v.pz = z;
|
||||
v.nx = s.currentNormal.x;
|
||||
v.ny = s.currentNormal.y;
|
||||
v.nz = s.currentNormal.z;
|
||||
v.r = s.currentColor.r;
|
||||
v.g = s.currentColor.g;
|
||||
v.b = s.currentColor.b;
|
||||
v.a = s.currentColor.a;
|
||||
v.u = 0.0f;
|
||||
v.v = 0.0f;
|
||||
|
||||
s_verts.push_back( v );
|
||||
}
|
||||
|
||||
|
||||
// Expands GL_QUADS into GL_TRIANGLES in place ((0,1,2)(0,2,3) per quad —
|
||||
// preserves winding; any trailing partial quad is dropped, as in GL).
|
||||
static void expandQuads( std::vector<ImmVertex>& verts )
|
||||
{
|
||||
const size_t quadCount = verts.size() / 4;
|
||||
std::vector<ImmVertex> tris;
|
||||
tris.reserve( quadCount * 6 );
|
||||
|
||||
for( size_t q = 0; q < quadCount; ++q )
|
||||
{
|
||||
const ImmVertex* v = &verts[q * 4];
|
||||
|
||||
tris.push_back( v[0] );
|
||||
tris.push_back( v[1] );
|
||||
tris.push_back( v[2] );
|
||||
|
||||
tris.push_back( v[0] );
|
||||
tris.push_back( v[2] );
|
||||
tris.push_back( v[3] );
|
||||
}
|
||||
|
||||
verts.swap( tris );
|
||||
}
|
||||
|
||||
|
||||
void immEnd()
|
||||
{
|
||||
if( !s_active )
|
||||
{
|
||||
GL1_WARN_ONCE( "glEnd without glBegin — ignored" );
|
||||
return;
|
||||
}
|
||||
|
||||
s_active = false;
|
||||
|
||||
if( s_verts.empty() )
|
||||
return;
|
||||
|
||||
GLenum drawMode = s_mode;
|
||||
|
||||
switch( s_mode )
|
||||
{
|
||||
case GL_QUADS:
|
||||
expandQuads( s_verts );
|
||||
drawMode = GL_TRIANGLES;
|
||||
break;
|
||||
|
||||
case GL_QUAD_STRIP:
|
||||
drawMode = GL_TRIANGLE_STRIP;
|
||||
break;
|
||||
|
||||
case GL_LINE_LOOP:
|
||||
s_verts.push_back( s_verts.front() );
|
||||
drawMode = GL_LINE_STRIP;
|
||||
break;
|
||||
|
||||
case GL_POLYGON:
|
||||
drawMode = GL_TRIANGLE_FAN;
|
||||
break;
|
||||
|
||||
case GL_POINTS:
|
||||
case GL_LINES:
|
||||
case GL_LINE_STRIP:
|
||||
case GL_TRIANGLES:
|
||||
case GL_TRIANGLE_STRIP:
|
||||
case GL_TRIANGLE_FAN:
|
||||
break;
|
||||
|
||||
default:
|
||||
GL1_WARN_ONCE( "glBegin: unsupported primitive 0x%x", s_mode );
|
||||
return;
|
||||
}
|
||||
|
||||
drawImmVertices( drawMode, s_verts.data(), (GLsizei) s_verts.size() );
|
||||
}
|
||||
|
||||
} // namespace gl1
|
||||
106
wasm/gl1/src/gl1_matrix.cpp
Normal file
106
wasm/gl1/src/gl1_matrix.cpp
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* gl1_matrix — GL_MODELVIEW / GL_PROJECTION matrix stacks.
|
||||
*
|
||||
* Only the operations the KiCad 3D renderer uses exist (no glOrtho/glFrustum/
|
||||
* glMultMatrix — projection and view arrive prebuilt via glLoadMatrixf, and
|
||||
* gluPerspective covers the gizmo). glGetFloatv(GL_MODELVIEW_MATRIX/
|
||||
* GL_PROJECTION_MATRIX) readback is served from these stacks by the
|
||||
* __wrap_glGetFloatv interceptor.
|
||||
*/
|
||||
|
||||
#include "gl1_shim.h"
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
namespace gl1
|
||||
{
|
||||
|
||||
// GL 1.5 minimums; KiCad never goes deeper than a few levels.
|
||||
static constexpr size_t MV_STACK_MAX = 64;
|
||||
static constexpr size_t PROJ_STACK_MAX = 8;
|
||||
|
||||
void matrixLoadIdentity()
|
||||
{
|
||||
S().currentTop() = glm::mat4( 1.0f );
|
||||
S().matricesDirty = true;
|
||||
}
|
||||
|
||||
|
||||
void matrixLoadf( const GLfloat* m )
|
||||
{
|
||||
S().currentTop() = glm::make_mat4( m );
|
||||
S().matricesDirty = true;
|
||||
}
|
||||
|
||||
|
||||
void matrixPush()
|
||||
{
|
||||
State& s = S();
|
||||
auto& stack = s.currentStack();
|
||||
|
||||
const size_t maxDepth = ( s.matrixMode == GL_PROJECTION ) ? PROJ_STACK_MAX : MV_STACK_MAX;
|
||||
|
||||
if( stack.size() >= maxDepth )
|
||||
{
|
||||
GL1_WARN_ONCE( "glPushMatrix: stack overflow (mode 0x%x)", s.matrixMode );
|
||||
return;
|
||||
}
|
||||
|
||||
stack.push_back( stack.back() );
|
||||
}
|
||||
|
||||
|
||||
void matrixPop()
|
||||
{
|
||||
State& s = S();
|
||||
auto& stack = s.currentStack();
|
||||
|
||||
if( stack.size() <= 1 )
|
||||
{
|
||||
GL1_WARN_ONCE( "glPopMatrix: stack underflow (mode 0x%x)", s.matrixMode );
|
||||
return;
|
||||
}
|
||||
|
||||
stack.pop_back();
|
||||
s.matricesDirty = true;
|
||||
}
|
||||
|
||||
|
||||
void matrixTranslate( float x, float y, float z )
|
||||
{
|
||||
glm::mat4& top = S().currentTop();
|
||||
top = glm::translate( top, glm::vec3( x, y, z ) );
|
||||
S().matricesDirty = true;
|
||||
}
|
||||
|
||||
|
||||
void matrixRotate( float angleDeg, float x, float y, float z )
|
||||
{
|
||||
glm::mat4& top = S().currentTop();
|
||||
top = glm::rotate( top, glm::radians( angleDeg ), glm::vec3( x, y, z ) );
|
||||
S().matricesDirty = true;
|
||||
}
|
||||
|
||||
|
||||
void matrixScale( float x, float y, float z )
|
||||
{
|
||||
glm::mat4& top = S().currentTop();
|
||||
top = glm::scale( top, glm::vec3( x, y, z ) );
|
||||
S().matricesDirty = true;
|
||||
}
|
||||
|
||||
|
||||
void matrixPerspective( double fovyDeg, double aspect, double zNear, double zFar )
|
||||
{
|
||||
// gluPerspective multiplies onto the current matrix (KiCad calls it right
|
||||
// after glLoadIdentity on GL_PROJECTION, but multiply is the GLU semantic).
|
||||
glm::mat4 p = glm::perspective( glm::radians( (float) fovyDeg ), (float) aspect,
|
||||
(float) zNear, (float) zFar );
|
||||
|
||||
glm::mat4& top = S().currentTop();
|
||||
top = top * p;
|
||||
S().matricesDirty = true;
|
||||
}
|
||||
|
||||
} // namespace gl1
|
||||
591
wasm/gl1/src/gl1_shaders.cpp
Normal file
591
wasm/gl1/src/gl1_shaders.cpp
Normal file
|
|
@ -0,0 +1,591 @@
|
|||
/*
|
||||
* gl1_shaders — the FFP uber-program (ES 3.00) and uniform synchronization.
|
||||
*
|
||||
* One program, uniform-flag branches (all dynamically uniform — cheap), no
|
||||
* variant cache: mid-frame FFP toggles (two-side in DrawCulled, texture/alpha
|
||||
* flips inside display lists) become uniform stores instead of program
|
||||
* switches.
|
||||
*
|
||||
* Lighting is computed PER-VERTEX (Gouraud) on purpose: the native goldens
|
||||
* come from a fixed-function pipeline that evaluates lighting at vertices and
|
||||
* interpolates colors — per-fragment lighting would visibly mismatch specular
|
||||
* highlights on the suite's coarse meshes.
|
||||
*
|
||||
* The GL 1.5 conventions implemented here (they are load-bearing for parity):
|
||||
* - light GL_POSITION is pre-transformed to EYE space at glLightfv time
|
||||
* - halfway vector H = normalize(L + (0,0,1)) (GL_LIGHT_MODEL_LOCAL_VIEWER
|
||||
* defaults to FALSE)
|
||||
* - no attenuation (KiCad leaves kc=1, kl=kq=0), no spotlights
|
||||
* - single-color model: specular folds into the one color before texturing
|
||||
* - GL_COLOR_MATERIAL(AMBIENT_AND_DIFFUSE): the per-vertex color replaces
|
||||
* material ambient+diffuse; alpha comes from the diffuse alpha
|
||||
* - texture COMBINE args resolve against the tracked+default state (GL 1.5
|
||||
* initial values for the SRC/OPERAND slots KiCad never sets); PREVIOUS is
|
||||
* the primary color at texture unit 0
|
||||
*/
|
||||
|
||||
#include "gl1_shim.h"
|
||||
|
||||
#include <glm/gtc/matrix_inverse.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
namespace gl1
|
||||
{
|
||||
|
||||
static const char* VS_SOURCE = R"(#version 300 es
|
||||
precision highp float;
|
||||
|
||||
layout(location = 0) in vec3 aPosition;
|
||||
layout(location = 1) in vec3 aNormal;
|
||||
layout(location = 2) in vec4 aColor;
|
||||
layout(location = 3) in vec2 aTexCoord;
|
||||
|
||||
uniform mat4 uModelView;
|
||||
uniform mat4 uProjection;
|
||||
uniform mat3 uNormalMatrix;
|
||||
uniform float uPointSize;
|
||||
|
||||
uniform bool uLighting;
|
||||
uniform bool uTwoSide;
|
||||
uniform bool uColorMaterial;
|
||||
|
||||
struct FfpLight
|
||||
{
|
||||
bool enabled;
|
||||
vec4 posEye;
|
||||
vec4 ambient;
|
||||
vec4 diffuse;
|
||||
vec4 specular;
|
||||
};
|
||||
|
||||
uniform FfpLight uLights[3];
|
||||
uniform vec4 uLightModelAmbient;
|
||||
uniform vec4 uMatAmbient;
|
||||
uniform vec4 uMatDiffuse;
|
||||
uniform vec4 uMatSpecular;
|
||||
uniform vec4 uMatEmission;
|
||||
uniform float uShininess;
|
||||
|
||||
out vec4 vFrontColor;
|
||||
out vec4 vBackColor;
|
||||
out vec2 vTexCoord;
|
||||
|
||||
vec4 lit( vec3 N, vec3 eyePos, vec4 matAmb, vec4 matDiff )
|
||||
{
|
||||
vec3 c = uMatEmission.rgb + matAmb.rgb * uLightModelAmbient.rgb;
|
||||
|
||||
for( int i = 0; i < 3; ++i )
|
||||
{
|
||||
if( !uLights[i].enabled )
|
||||
continue;
|
||||
|
||||
vec3 L = ( uLights[i].posEye.w == 0.0 )
|
||||
? normalize( uLights[i].posEye.xyz )
|
||||
: normalize( uLights[i].posEye.xyz - eyePos );
|
||||
|
||||
float ndotl = max( dot( N, L ), 0.0 );
|
||||
|
||||
float spec = 0.0;
|
||||
|
||||
if( ndotl > 0.0 )
|
||||
{
|
||||
vec3 H = normalize( L + vec3( 0.0, 0.0, 1.0 ) );
|
||||
float ndoth = max( dot( N, H ), 0.0 );
|
||||
spec = ( uShininess > 0.0 ) ? pow( ndoth, uShininess ) : 1.0;
|
||||
}
|
||||
|
||||
c += matAmb.rgb * uLights[i].ambient.rgb
|
||||
+ ndotl * matDiff.rgb * uLights[i].diffuse.rgb
|
||||
+ spec * uMatSpecular.rgb * uLights[i].specular.rgb;
|
||||
}
|
||||
|
||||
return vec4( clamp( c, 0.0, 1.0 ), clamp( matDiff.a, 0.0, 1.0 ) );
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 eye = uModelView * vec4( aPosition, 1.0 );
|
||||
|
||||
vTexCoord = aTexCoord;
|
||||
gl_PointSize = uPointSize;
|
||||
gl_Position = uProjection * eye;
|
||||
|
||||
if( uLighting )
|
||||
{
|
||||
vec3 N = normalize( uNormalMatrix * aNormal );
|
||||
vec4 matAmb = uColorMaterial ? aColor : uMatAmbient;
|
||||
vec4 matDiff = uColorMaterial ? aColor : uMatDiffuse;
|
||||
|
||||
vFrontColor = lit( N, eye.xyz, matAmb, matDiff );
|
||||
vBackColor = uTwoSide ? lit( -N, eye.xyz, matAmb, matDiff ) : vFrontColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
vFrontColor = clamp( aColor, 0.0, 1.0 );
|
||||
vBackColor = vFrontColor;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* FS_SOURCE = R"(#version 300 es
|
||||
precision highp float;
|
||||
|
||||
in vec4 vFrontColor;
|
||||
in vec4 vBackColor;
|
||||
in vec2 vTexCoord;
|
||||
|
||||
uniform bool uTexEnabled;
|
||||
uniform int uTexEnvMode; // 0=MODULATE, 1=COMBINE
|
||||
uniform sampler2D uTex0;
|
||||
uniform vec4 uTexEnvColor;
|
||||
|
||||
// COMBINE argument selectors: src 0=TEXTURE 1=CONSTANT 2=PRIMARY 3=PREVIOUS;
|
||||
// RGB op 0=SRC_COLOR 1=ONE_MINUS_SRC_COLOR 2=SRC_ALPHA 3=ONE_MINUS_SRC_ALPHA;
|
||||
// alpha op 0=SRC_ALPHA 1=ONE_MINUS_SRC_ALPHA;
|
||||
// func 0=MODULATE 1=INTERPOLATE 2=REPLACE.
|
||||
uniform int uCombineFuncRGB;
|
||||
uniform int uCombineFuncA;
|
||||
uniform ivec3 uCombineSrcRGB;
|
||||
uniform ivec3 uCombineOpRGB;
|
||||
uniform ivec3 uCombineSrcA;
|
||||
uniform ivec3 uCombineOpA;
|
||||
|
||||
uniform bool uAlphaTest;
|
||||
uniform int uAlphaFunc; // GL func - GL_NEVER, i.e. 0..7
|
||||
uniform float uAlphaRef;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
vec4 combineSource( int src, vec4 tex, vec4 primary )
|
||||
{
|
||||
if( src == 0 )
|
||||
return tex;
|
||||
if( src == 1 )
|
||||
return uTexEnvColor;
|
||||
|
||||
return primary; // PRIMARY, and PREVIOUS == primary at unit 0
|
||||
}
|
||||
|
||||
vec3 combineArgRGB( int src, int op, vec4 tex, vec4 primary )
|
||||
{
|
||||
vec4 s = combineSource( src, tex, primary );
|
||||
|
||||
if( op == 0 )
|
||||
return s.rgb;
|
||||
if( op == 1 )
|
||||
return vec3( 1.0 ) - s.rgb;
|
||||
if( op == 2 )
|
||||
return vec3( s.a );
|
||||
|
||||
return vec3( 1.0 - s.a );
|
||||
}
|
||||
|
||||
float combineArgA( int src, int op, vec4 tex, vec4 primary )
|
||||
{
|
||||
vec4 s = combineSource( src, tex, primary );
|
||||
|
||||
return ( op == 0 ) ? s.a : 1.0 - s.a;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 c = gl_FrontFacing ? vFrontColor : vBackColor;
|
||||
|
||||
if( uTexEnabled )
|
||||
{
|
||||
vec4 t = texture( uTex0, vTexCoord );
|
||||
|
||||
if( uTexEnvMode == 0 )
|
||||
{
|
||||
c = c * t;
|
||||
}
|
||||
else
|
||||
{
|
||||
vec3 a0 = combineArgRGB( uCombineSrcRGB.x, uCombineOpRGB.x, t, c );
|
||||
vec3 a1 = combineArgRGB( uCombineSrcRGB.y, uCombineOpRGB.y, t, c );
|
||||
vec3 a2 = combineArgRGB( uCombineSrcRGB.z, uCombineOpRGB.z, t, c );
|
||||
|
||||
vec3 rgb;
|
||||
|
||||
if( uCombineFuncRGB == 0 )
|
||||
rgb = a0 * a1;
|
||||
else if( uCombineFuncRGB == 1 )
|
||||
rgb = a0 * a2 + a1 * ( vec3( 1.0 ) - a2 );
|
||||
else
|
||||
rgb = a0;
|
||||
|
||||
float b0 = combineArgA( uCombineSrcA.x, uCombineOpA.x, t, c );
|
||||
float b1 = combineArgA( uCombineSrcA.y, uCombineOpA.y, t, c );
|
||||
float b2 = combineArgA( uCombineSrcA.z, uCombineOpA.z, t, c );
|
||||
|
||||
float alpha;
|
||||
|
||||
if( uCombineFuncA == 0 )
|
||||
alpha = b0 * b1;
|
||||
else if( uCombineFuncA == 1 )
|
||||
alpha = b0 * b2 + b1 * ( 1.0 - b2 );
|
||||
else
|
||||
alpha = b0;
|
||||
|
||||
c = clamp( vec4( rgb, alpha ), 0.0, 1.0 );
|
||||
}
|
||||
}
|
||||
|
||||
if( uAlphaTest )
|
||||
{
|
||||
bool pass;
|
||||
|
||||
if( uAlphaFunc == 0 ) pass = false; // NEVER
|
||||
else if( uAlphaFunc == 1 ) pass = c.a < uAlphaRef; // LESS
|
||||
else if( uAlphaFunc == 2 ) pass = c.a == uAlphaRef; // EQUAL
|
||||
else if( uAlphaFunc == 3 ) pass = c.a <= uAlphaRef; // LEQUAL
|
||||
else if( uAlphaFunc == 4 ) pass = c.a > uAlphaRef; // GREATER
|
||||
else if( uAlphaFunc == 5 ) pass = c.a != uAlphaRef; // NOTEQUAL
|
||||
else if( uAlphaFunc == 6 ) pass = c.a >= uAlphaRef; // GEQUAL
|
||||
else pass = true; // ALWAYS
|
||||
|
||||
if( !pass )
|
||||
discard;
|
||||
}
|
||||
|
||||
fragColor = c;
|
||||
}
|
||||
)";
|
||||
|
||||
|
||||
struct LightLocs
|
||||
{
|
||||
GLint enabled = -1;
|
||||
GLint posEye = -1;
|
||||
GLint ambient = -1;
|
||||
GLint diffuse = -1;
|
||||
GLint specular = -1;
|
||||
};
|
||||
|
||||
struct ProgramLocs
|
||||
{
|
||||
GLint modelView = -1;
|
||||
GLint projection = -1;
|
||||
GLint normalMatrix = -1;
|
||||
GLint pointSize = -1;
|
||||
|
||||
GLint lighting = -1;
|
||||
GLint twoSide = -1;
|
||||
GLint colorMaterial = -1;
|
||||
LightLocs lights[3];
|
||||
GLint lightModelAmbient = -1;
|
||||
GLint matAmbient = -1;
|
||||
GLint matDiffuse = -1;
|
||||
GLint matSpecular = -1;
|
||||
GLint matEmission = -1;
|
||||
GLint shininess = -1;
|
||||
|
||||
GLint texEnabled = -1;
|
||||
GLint texEnvMode = -1;
|
||||
GLint tex0 = -1;
|
||||
GLint texEnvColor = -1;
|
||||
GLint combineFuncRGB = -1;
|
||||
GLint combineFuncA = -1;
|
||||
GLint combineSrcRGB = -1;
|
||||
GLint combineOpRGB = -1;
|
||||
GLint combineSrcA = -1;
|
||||
GLint combineOpA = -1;
|
||||
|
||||
GLint alphaTest = -1;
|
||||
GLint alphaFunc = -1;
|
||||
GLint alphaRef = -1;
|
||||
};
|
||||
|
||||
static GLuint s_program = 0;
|
||||
static bool s_buildFailed = false;
|
||||
static ProgramLocs s_locs;
|
||||
|
||||
|
||||
static GLuint compileShader( GLenum type, const char* source )
|
||||
{
|
||||
GLuint shader = glCreateShader( type );
|
||||
glShaderSource( shader, 1, &source, nullptr );
|
||||
glCompileShader( shader );
|
||||
|
||||
GLint ok = GL_FALSE;
|
||||
glGetShaderiv( shader, GL_COMPILE_STATUS, &ok );
|
||||
|
||||
if( !ok )
|
||||
{
|
||||
char log[1024] = {};
|
||||
glGetShaderInfoLog( shader, sizeof( log ) - 1, nullptr, log );
|
||||
std::fprintf( stderr, "[gl1] %s shader compile failed:\n%s\n",
|
||||
type == GL_VERTEX_SHADER ? "vertex" : "fragment", log );
|
||||
glDeleteShader( shader );
|
||||
return 0;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
|
||||
static bool buildProgram()
|
||||
{
|
||||
GLuint vs = compileShader( GL_VERTEX_SHADER, VS_SOURCE );
|
||||
GLuint fs = compileShader( GL_FRAGMENT_SHADER, FS_SOURCE );
|
||||
|
||||
if( !vs || !fs )
|
||||
return false;
|
||||
|
||||
GLuint prog = glCreateProgram();
|
||||
glAttachShader( prog, vs );
|
||||
glAttachShader( prog, fs );
|
||||
glLinkProgram( prog );
|
||||
glDeleteShader( vs );
|
||||
glDeleteShader( fs );
|
||||
|
||||
GLint ok = GL_FALSE;
|
||||
glGetProgramiv( prog, GL_LINK_STATUS, &ok );
|
||||
|
||||
if( !ok )
|
||||
{
|
||||
char log[1024] = {};
|
||||
glGetProgramInfoLog( prog, sizeof( log ) - 1, nullptr, log );
|
||||
std::fprintf( stderr, "[gl1] program link failed:\n%s\n", log );
|
||||
glDeleteProgram( prog );
|
||||
return false;
|
||||
}
|
||||
|
||||
s_program = prog;
|
||||
|
||||
ProgramLocs& l = s_locs;
|
||||
l.modelView = glGetUniformLocation( prog, "uModelView" );
|
||||
l.projection = glGetUniformLocation( prog, "uProjection" );
|
||||
l.normalMatrix = glGetUniformLocation( prog, "uNormalMatrix" );
|
||||
l.pointSize = glGetUniformLocation( prog, "uPointSize" );
|
||||
|
||||
l.lighting = glGetUniformLocation( prog, "uLighting" );
|
||||
l.twoSide = glGetUniformLocation( prog, "uTwoSide" );
|
||||
l.colorMaterial = glGetUniformLocation( prog, "uColorMaterial" );
|
||||
|
||||
for( int i = 0; i < 3; ++i )
|
||||
{
|
||||
char name[48];
|
||||
std::snprintf( name, sizeof( name ), "uLights[%d].enabled", i );
|
||||
l.lights[i].enabled = glGetUniformLocation( prog, name );
|
||||
std::snprintf( name, sizeof( name ), "uLights[%d].posEye", i );
|
||||
l.lights[i].posEye = glGetUniformLocation( prog, name );
|
||||
std::snprintf( name, sizeof( name ), "uLights[%d].ambient", i );
|
||||
l.lights[i].ambient = glGetUniformLocation( prog, name );
|
||||
std::snprintf( name, sizeof( name ), "uLights[%d].diffuse", i );
|
||||
l.lights[i].diffuse = glGetUniformLocation( prog, name );
|
||||
std::snprintf( name, sizeof( name ), "uLights[%d].specular", i );
|
||||
l.lights[i].specular = glGetUniformLocation( prog, name );
|
||||
}
|
||||
|
||||
l.lightModelAmbient = glGetUniformLocation( prog, "uLightModelAmbient" );
|
||||
l.matAmbient = glGetUniformLocation( prog, "uMatAmbient" );
|
||||
l.matDiffuse = glGetUniformLocation( prog, "uMatDiffuse" );
|
||||
l.matSpecular = glGetUniformLocation( prog, "uMatSpecular" );
|
||||
l.matEmission = glGetUniformLocation( prog, "uMatEmission" );
|
||||
l.shininess = glGetUniformLocation( prog, "uShininess" );
|
||||
|
||||
l.texEnabled = glGetUniformLocation( prog, "uTexEnabled" );
|
||||
l.texEnvMode = glGetUniformLocation( prog, "uTexEnvMode" );
|
||||
l.tex0 = glGetUniformLocation( prog, "uTex0" );
|
||||
l.texEnvColor = glGetUniformLocation( prog, "uTexEnvColor" );
|
||||
l.combineFuncRGB = glGetUniformLocation( prog, "uCombineFuncRGB" );
|
||||
l.combineFuncA = glGetUniformLocation( prog, "uCombineFuncA" );
|
||||
l.combineSrcRGB = glGetUniformLocation( prog, "uCombineSrcRGB" );
|
||||
l.combineOpRGB = glGetUniformLocation( prog, "uCombineOpRGB" );
|
||||
l.combineSrcA = glGetUniformLocation( prog, "uCombineSrcA" );
|
||||
l.combineOpA = glGetUniformLocation( prog, "uCombineOpA" );
|
||||
|
||||
l.alphaTest = glGetUniformLocation( prog, "uAlphaTest" );
|
||||
l.alphaFunc = glGetUniformLocation( prog, "uAlphaFunc" );
|
||||
l.alphaRef = glGetUniformLocation( prog, "uAlphaRef" );
|
||||
|
||||
// The FFP surface only ever uses texture unit 0 (asserted at
|
||||
// glClientActiveTexture); bind the sampler once. glUseProgram is not a
|
||||
// wrapped symbol (draw routing keys on client-array state instead), so
|
||||
// this is the real WebGL entry point.
|
||||
glUseProgram( prog );
|
||||
glUniform1i( l.tex0, 0 );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
GLuint programId()
|
||||
{
|
||||
return s_program;
|
||||
}
|
||||
|
||||
|
||||
static int encodeCombineSrc( GLenum src )
|
||||
{
|
||||
switch( src )
|
||||
{
|
||||
case GL_TEXTURE: return 0;
|
||||
case GL_CONSTANT: return 1;
|
||||
case GL_PRIMARY_COLOR: return 2;
|
||||
case GL_PREVIOUS: return 3;
|
||||
default:
|
||||
GL1_WARN_ONCE( "unsupported COMBINE source 0x%x", src );
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int encodeCombineOpRGB( GLenum op )
|
||||
{
|
||||
switch( op )
|
||||
{
|
||||
case GL_SRC_COLOR: return 0;
|
||||
case GL_ONE_MINUS_SRC_COLOR: return 1;
|
||||
case GL_SRC_ALPHA: return 2;
|
||||
case GL_ONE_MINUS_SRC_ALPHA: return 3;
|
||||
default:
|
||||
GL1_WARN_ONCE( "unsupported COMBINE RGB operand 0x%x", op );
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int encodeCombineOpA( GLenum op )
|
||||
{
|
||||
switch( op )
|
||||
{
|
||||
case GL_SRC_ALPHA: return 0;
|
||||
case GL_ONE_MINUS_SRC_ALPHA: return 1;
|
||||
default:
|
||||
GL1_WARN_ONCE( "unsupported COMBINE alpha operand 0x%x", op );
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int encodeCombineFunc( GLenum func )
|
||||
{
|
||||
switch( func )
|
||||
{
|
||||
case GL_MODULATE: return 0;
|
||||
case GL_INTERPOLATE: return 1;
|
||||
case GL_REPLACE: return 2;
|
||||
default:
|
||||
GL1_WARN_ONCE( "unsupported COMBINE function 0x%x", func );
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool programSync()
|
||||
{
|
||||
if( s_buildFailed )
|
||||
return false;
|
||||
|
||||
if( !s_program )
|
||||
{
|
||||
if( !buildProgram() )
|
||||
{
|
||||
s_buildFailed = true;
|
||||
GL1_WARN_ONCE( "FFP program build failed — shim draws disabled" );
|
||||
return false;
|
||||
}
|
||||
|
||||
// First build: force a full upload.
|
||||
State& s0 = S();
|
||||
s0.matricesDirty = true;
|
||||
s0.lightingDirty = true;
|
||||
s0.texEnvDirty = true;
|
||||
s0.miscDirty = true;
|
||||
}
|
||||
|
||||
State& s = S();
|
||||
const ProgramLocs& l = s_locs;
|
||||
|
||||
glUseProgram( s_program );
|
||||
|
||||
if( s.matricesDirty )
|
||||
{
|
||||
s.matricesDirty = false;
|
||||
|
||||
const glm::mat4& mv = s.mv.back();
|
||||
glUniformMatrix4fv( l.modelView, 1, GL_FALSE, glm::value_ptr( mv ) );
|
||||
glUniformMatrix4fv( l.projection, 1, GL_FALSE, glm::value_ptr( s.proj.back() ) );
|
||||
|
||||
const glm::mat3 nm = glm::inverseTranspose( glm::mat3( mv ) );
|
||||
glUniformMatrix3fv( l.normalMatrix, 1, GL_FALSE, glm::value_ptr( nm ) );
|
||||
}
|
||||
|
||||
if( s.lightingDirty )
|
||||
{
|
||||
s.lightingDirty = false;
|
||||
|
||||
glUniform1i( l.lighting, s.lighting ? 1 : 0 );
|
||||
glUniform1i( l.twoSide, s.twoSide ? 1 : 0 );
|
||||
glUniform1i( l.colorMaterial, s.colorMaterial ? 1 : 0 );
|
||||
|
||||
for( int i = 0; i < 3; ++i )
|
||||
{
|
||||
const Light& lt = s.lights[i];
|
||||
glUniform1i( l.lights[i].enabled, s.lightEnabled[i] ? 1 : 0 );
|
||||
glUniform4fv( l.lights[i].posEye, 1, glm::value_ptr( lt.posEye ) );
|
||||
glUniform4fv( l.lights[i].ambient, 1, glm::value_ptr( lt.ambient ) );
|
||||
glUniform4fv( l.lights[i].diffuse, 1, glm::value_ptr( lt.diffuse ) );
|
||||
glUniform4fv( l.lights[i].specular, 1, glm::value_ptr( lt.specular ) );
|
||||
}
|
||||
|
||||
for( int i = 3; i < 8; ++i )
|
||||
{
|
||||
if( s.lightEnabled[i] )
|
||||
GL1_WARN_ONCE( "GL_LIGHT%d enabled but the shim models only lights 0-2", i );
|
||||
}
|
||||
|
||||
glUniform4fv( l.lightModelAmbient, 1, glm::value_ptr( s.lightModelAmbient ) );
|
||||
glUniform4fv( l.matAmbient, 1, glm::value_ptr( s.material.ambient ) );
|
||||
glUniform4fv( l.matDiffuse, 1, glm::value_ptr( s.material.diffuse ) );
|
||||
glUniform4fv( l.matSpecular, 1, glm::value_ptr( s.material.specular ) );
|
||||
glUniform4fv( l.matEmission, 1, glm::value_ptr( s.material.emission ) );
|
||||
glUniform1f( l.shininess, s.material.shininess );
|
||||
}
|
||||
|
||||
if( s.texEnvDirty )
|
||||
{
|
||||
s.texEnvDirty = false;
|
||||
|
||||
int mode = 0;
|
||||
|
||||
if( s.texEnvMode == GL_MODULATE )
|
||||
mode = 0;
|
||||
else if( s.texEnvMode == GL_COMBINE )
|
||||
mode = 1;
|
||||
else
|
||||
GL1_WARN_ONCE( "unsupported GL_TEXTURE_ENV_MODE 0x%x (treated as MODULATE)",
|
||||
s.texEnvMode );
|
||||
|
||||
glUniform1i( l.texEnvMode, mode );
|
||||
glUniform4fv( l.texEnvColor, 1, glm::value_ptr( s.texEnvColor ) );
|
||||
|
||||
glUniform1i( l.combineFuncRGB, encodeCombineFunc( s.combineRGB ) );
|
||||
glUniform1i( l.combineFuncA, encodeCombineFunc( s.combineAlpha ) );
|
||||
glUniform3i( l.combineSrcRGB, encodeCombineSrc( s.srcRGB[0] ),
|
||||
encodeCombineSrc( s.srcRGB[1] ), encodeCombineSrc( s.srcRGB[2] ) );
|
||||
glUniform3i( l.combineOpRGB, encodeCombineOpRGB( s.operandRGB[0] ),
|
||||
encodeCombineOpRGB( s.operandRGB[1] ), encodeCombineOpRGB( s.operandRGB[2] ) );
|
||||
glUniform3i( l.combineSrcA, encodeCombineSrc( s.srcAlpha[0] ),
|
||||
encodeCombineSrc( s.srcAlpha[1] ), encodeCombineSrc( s.srcAlpha[2] ) );
|
||||
glUniform3i( l.combineOpA, encodeCombineOpA( s.operandAlpha[0] ),
|
||||
encodeCombineOpA( s.operandAlpha[1] ), encodeCombineOpA( s.operandAlpha[2] ) );
|
||||
}
|
||||
|
||||
if( s.miscDirty )
|
||||
{
|
||||
s.miscDirty = false;
|
||||
|
||||
glUniform1i( l.texEnabled, s.texture2D ? 1 : 0 );
|
||||
glUniform1i( l.alphaTest, s.alphaTest ? 1 : 0 );
|
||||
glUniform1i( l.alphaFunc, (int) ( s.alphaFunc - GL_NEVER ) );
|
||||
glUniform1f( l.alphaRef, s.alphaRef );
|
||||
glUniform1f( l.pointSize, s.pointSize );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace gl1
|
||||
66
wasm/gl1/src/gl1_state.cpp
Normal file
66
wasm/gl1/src/gl1_state.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* gl1_state — the shim's GL 1.x state singleton and capability routing.
|
||||
*/
|
||||
|
||||
#include "gl1_shim.h"
|
||||
|
||||
namespace gl1
|
||||
{
|
||||
|
||||
State& S()
|
||||
{
|
||||
static State s;
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
bool* ffpCapSlot( GLenum cap )
|
||||
{
|
||||
State& s = S();
|
||||
|
||||
switch( cap )
|
||||
{
|
||||
case GL_LIGHTING: return &s.lighting;
|
||||
case GL_COLOR_MATERIAL: return &s.colorMaterial;
|
||||
case GL_TEXTURE_2D: return &s.texture2D;
|
||||
case GL_NORMALIZE: return &s.normalizeNormals;
|
||||
case GL_ALPHA_TEST: return &s.alphaTest;
|
||||
// Tracked-but-inert: WebGL2 has no equivalent caps and would raise
|
||||
// INVALID_ENUM; the suite's goldens are single-sample/aliased anyway.
|
||||
case GL_LINE_SMOOTH: return &s.lineSmooth;
|
||||
case GL_POINT_SMOOTH: return &s.pointSmooth;
|
||||
case GL_MULTISAMPLE: return &s.multisample;
|
||||
default:
|
||||
if( cap >= GL_LIGHT0 && cap <= GL_LIGHT7 )
|
||||
return &s.lightEnabled[cap - GL_LIGHT0];
|
||||
|
||||
return nullptr; // WebGL-native cap: forward
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void onCapChanged( GLenum cap )
|
||||
{
|
||||
State& s = S();
|
||||
|
||||
switch( cap )
|
||||
{
|
||||
case GL_LIGHTING:
|
||||
case GL_COLOR_MATERIAL:
|
||||
s.lightingDirty = true;
|
||||
s.miscDirty = true;
|
||||
break;
|
||||
|
||||
case GL_TEXTURE_2D:
|
||||
case GL_ALPHA_TEST:
|
||||
s.miscDirty = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
if( cap >= GL_LIGHT0 && cap <= GL_LIGHT7 )
|
||||
s.lightingDirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gl1
|
||||
10
wasm/gl1/wrapped_symbols.txt
Normal file
10
wasm/gl1/wrapped_symbols.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
glEnable
|
||||
glDisable
|
||||
glIsEnabled
|
||||
glDrawArrays
|
||||
glDrawElements
|
||||
glGetFloatv
|
||||
glBindTexture
|
||||
glBlendFunc
|
||||
glLineWidth
|
||||
glHint
|
||||
Loading…
Reference in a new issue