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
|
|
@ -48,6 +48,7 @@ KICAD_INCLUDES = -I../native \
|
||||||
-I$(KICAD_ROOT)/thirdparty/nlohmann_json \
|
-I$(KICAD_ROOT)/thirdparty/nlohmann_json \
|
||||||
-I$(KICAD_ROOT)/thirdparty \
|
-I$(KICAD_ROOT)/thirdparty \
|
||||||
-I$(PROJECT_ROOT)/wasm/stubs \
|
-I$(PROJECT_ROOT)/wasm/stubs \
|
||||||
|
-I$(PROJECT_ROOT)/wasm/gl1/include \
|
||||||
-I$(SYSROOT)/include
|
-I$(SYSROOT)/include
|
||||||
|
|
||||||
ifdef DEBUG
|
ifdef DEBUG
|
||||||
|
|
@ -74,11 +75,11 @@ BASE_LDFLAGS = $(DEPS_EH_FLAGS) \
|
||||||
-sEXPORT_NAME='create3DTest' \
|
-sEXPORT_NAME='create3DTest' \
|
||||||
-sENVIRONMENT=web,worker
|
-sENVIRONMENT=web,worker
|
||||||
|
|
||||||
# Pure WebGL 2.0 context; the FFP calls are stubbed C no-ops (no
|
# Pure WebGL 2.0 context; the FFP surface is implemented by wasm/gl1 (no
|
||||||
# LEGACY_GL_EMULATION), same as the production kicad_editor build.
|
# LEGACY_GL_EMULATION), same as the production kicad_editor build.
|
||||||
EM_GL_FLAGS = -sMAX_WEBGL_VERSION=2
|
EM_GL_FLAGS = -sMAX_WEBGL_VERSION=2
|
||||||
|
|
||||||
LDFLAGS = $(DEBUG_LDFLAGS) $(BASE_LDFLAGS) $(EM_GL_FLAGS) $(WX_LDFLAGS)
|
LDFLAGS = $(DEBUG_LDFLAGS) $(BASE_LDFLAGS) $(EM_GL_FLAGS) $(WX_LDFLAGS) $(GL1_WRAP_LDFLAGS)
|
||||||
|
|
||||||
MAIN_SRCS = 3d_webgl_test.cpp
|
MAIN_SRCS = 3d_webgl_test.cpp
|
||||||
|
|
||||||
|
|
@ -157,11 +158,18 @@ KICAD_3D_SRCS = \
|
||||||
$(KICAD_ROOT)/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp \
|
$(KICAD_ROOT)/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp \
|
||||||
$(KICAD_ROOT)/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp
|
$(KICAD_ROOT)/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp
|
||||||
|
|
||||||
# THE RED STATE: legacy fixed-function GL as no-ops.
|
# The GL1->WebGL2 emulation layer (wasm/gl1): implements the FFP surface the
|
||||||
FFP_STUB_SRCS = $(PROJECT_ROOT)/wasm/stubs/gl_ffp_stub.c
|
# renderer needs and --wrap-intercepts the Emscripten-owned names it must
|
||||||
|
# observe. sources.txt / wrapped_symbols.txt are shared with the production
|
||||||
|
# link site (scripts/kicad/build-kicad-target.sh).
|
||||||
|
GL1_DIR = $(PROJECT_ROOT)/wasm/gl1
|
||||||
|
GL1_SRCS := $(addprefix $(GL1_DIR)/src/,$(shell cat $(GL1_DIR)/sources.txt))
|
||||||
|
|
||||||
SRCS = $(MAIN_SRCS) $(SCENARIO_SRCS) $(TESTIMPL_SRCS) $(KICAD_3D_SRCS)
|
comma := ,
|
||||||
OBJS = $(SRCS:.cpp=.o) $(FFP_STUB_SRCS:.c=.o)
|
GL1_WRAP_LDFLAGS := $(foreach sym,$(shell cat $(GL1_DIR)/wrapped_symbols.txt),-Wl$(comma)--wrap=$(sym))
|
||||||
|
|
||||||
|
SRCS = $(MAIN_SRCS) $(SCENARIO_SRCS) $(TESTIMPL_SRCS) $(KICAD_3D_SRCS) $(GL1_SRCS)
|
||||||
|
OBJS = $(SRCS:.cpp=.o)
|
||||||
DEPS = $(SRCS:.cpp=.d)
|
DEPS = $(SRCS:.cpp=.d)
|
||||||
|
|
||||||
TARGET = $(OUTPUT_DIR)/3d_webgl_test.js
|
TARGET = $(OUTPUT_DIR)/3d_webgl_test.js
|
||||||
|
|
@ -188,6 +196,7 @@ clean:
|
||||||
rm -f $(OUTPUT_DIR)/3d_webgl_test.wasm
|
rm -f $(OUTPUT_DIR)/3d_webgl_test.wasm
|
||||||
rm -f $(OUTPUT_DIR)/3d_webgl_test.html
|
rm -f $(OUTPUT_DIR)/3d_webgl_test.html
|
||||||
rm -f ../scenarios/*.o ../scenarios/*.d ../native/*.o ../native/*.d
|
rm -f ../scenarios/*.o ../scenarios/*.d ../native/*.o ../native/*.d
|
||||||
|
rm -f $(GL1_DIR)/src/*.o $(GL1_DIR)/src/*.d
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
|
|
|
||||||
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