From 3fe4f6fb00f4807dab23955d95b84bfcbd0ff507 Mon Sep 17 00:00:00 2001 From: Istvan Matejcsok <119620946+matejcsok-ee@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:48:22 +0200 Subject: [PATCH] =?UTF-8?q?feat(3d):=20gl1=20shim=20M3+M5=20=E2=80=94=20di?= =?UTF-8?q?splay-list=20recorder,=20client=20arrays,=20VBO=20routing;=2044?= =?UTF-8?q?/47=20under=20parity=20floor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - display lists: literal command replay through the shim state machine (state leaks + between-list material changes get GL semantics for free); recorded glDrawArrays EAGERLY snapshots enabled client-memory arrays at compile time (the renderer frees them right after glEndList) and records buffer+offset for VBO-backed pointers - client-array draws: shared AttribSource path packs client-memory arrays into one scratch-VBO upload and binds user VBOs with original offsets (GL_BYTE normals / GL_UNSIGNED_BYTE colors normalized per GL1) - glDrawElements over user VBO/IBO passes indices through untouched (ELEMENT_ARRAY_BUFFER never touched by the shim) - state mutators (enable/bindTexture/blendFunc/lineWidth/alphaFunc) shared between the __wrap interceptors and list replay Parity: 44/47 under the 0.02 floor (was 20). Verified visually: mini-board Tier-3 composite (mask translucency + stencil-punched TH holes), stencil hole subtraction, alpha-tested seg-end discs, COMBINE const-alpha model transparency, and post-machining reproducing the upstream countersink bug (walls absent, matching the native golden). Remaining 3: camera-* trio needing the M4 GLU quadrics (+5 small-geometry false-greens they share). Co-Authored-By: Claude Fable 5 --- wasm/gl1/include/gl1_shim.h | 34 +++- wasm/gl1/src/gl1_dlist.cpp | 270 ++++++++++++++++++++++++----- wasm/gl1/src/gl1_draw.cpp | 228 ++++++++++++++++++++++-- wasm/gl1/src/gl1_entry_ffp.cpp | 4 +- wasm/gl1/src/gl1_entry_wrapped.cpp | 36 +--- wasm/gl1/src/gl1_state.cpp | 82 +++++++++ 6 files changed, 566 insertions(+), 88 deletions(-) diff --git a/wasm/gl1/include/gl1_shim.h b/wasm/gl1/include/gl1_shim.h index 338546b..46ae4ed 100644 --- a/wasm/gl1/include/gl1_shim.h +++ b/wasm/gl1/include/gl1_shim.h @@ -209,6 +209,35 @@ bool* ffpCapSlot( GLenum cap ); // Marks the state blocks a cap flip invalidates. void onCapChanged( GLenum cap ); +// Immediate-execution state mutators shared by the __wrap_* interceptors and +// display-list replay (identical semantics, minus the recording check). +void stateEnable( GLenum cap, bool enable ); +void stateBindTexture( GLenum target, GLuint texture ); +void stateBlendFunc( GLenum sfactor, GLenum dfactor ); +void stateLineWidth( GLfloat width ); +void stateAlphaFunc( GLenum func, GLclampf ref ); + +// GL1 normalized-attribute rule: integer colors and normals are normalized, +// floats are not (positions/texcoords are float-only in this codebase). +bool attribNormalized( int arrayIndex, GLenum type ); + +// Effective byte stride of a client array (GL stride 0 = tightly packed). +GLsizei attribEffectiveStride( GLint size, GLenum type, GLsizei stride ); + +// One vertex attribute's data source for an array draw: either client memory +// to be uploaded (cpuData) or a byte offset into a user VBO (buffer/offset). +struct AttribSource +{ + bool enabled = false; + GLint size = 4; + GLenum type = GL_FLOAT; + bool normalized = false; + GLsizei stride = 0; // effective byte stride, never 0 + const void* cpuData = nullptr; + GLuint buffer = 0; + GLintptr offset = 0; +}; + // --- matrix module (gl1_matrix.cpp) --- void matrixLoadIdentity(); void matrixLoadf( const GLfloat* m ); @@ -231,7 +260,10 @@ bool immActive(); // 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). +// Non-indexed draw over explicit per-attribute sources (client arrays, +// display-list snapshots). Sources are pre-rebased: vertex 0 = first vertex. +void drawArraysWithSources( GLenum mode, GLsizei count, const AttribSource aSrc[CA_COUNT] ); +// Routed glDrawArrays/glDrawElements over FFP client-array state. void drawClientArrays( GLenum mode, GLint first, GLsizei count ); void drawClientElements( GLenum mode, GLsizei count, GLenum type, const GLvoid* indices ); diff --git a/wasm/gl1/src/gl1_dlist.cpp b/wasm/gl1/src/gl1_dlist.cpp index efdb6c7..c8447ff 100644 --- a/wasm/gl1/src/gl1_dlist.cpp +++ b/wasm/gl1/src/gl1_dlist.cpp @@ -7,22 +7,71 @@ * (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. + * - THE invariant (do not weaken): a recorded glDrawArrays dereferences the + * client arrays AT COMPILE TIME. The renderer frees them right after + * glEndList (layer_triangles.cpp seg-ends uvArray), so every enabled + * client-memory array is snapshotted eagerly here. VBO-backed pointers + * record the buffer name + offset instead (no copy). + * + * Replay is a literal command replay through the same shim state machine and + * draw pipeline the live calls use — state mutations inside a list correctly + * leak into post-glCallList state (GL semantics), and materials changed + * BETWEEN glCallList calls (setLayerMaterial -> DrawAll) are honored because + * draws always read the live uniform state. */ #include "gl1_shim.h" +#include #include +#include namespace gl1 { +// Eager copy of one client array's data for a recorded draw (or a reference +// into a user VBO when the pointer was VBO-backed at record time). +struct SnapArray +{ + bool enabled = false; + GLint size = 4; + GLenum type = GL_FLOAT; + GLsizei stride = 0; // effective byte stride + std::vector data; // client-memory snapshot (empty if VBO) + GLuint buffer = 0; + GLintptr offset = 0; +}; + +struct Cmd +{ + enum class Kind : uint8_t + { + ENABLE, // e0 = cap, i0 = on/off + BIND_TEXTURE,// e0 = target, u0 = texture + BLEND_FUNC, // e0 = sfactor, e1 = dfactor + LINE_WIDTH, // f[0] + ALPHA_FUNC, // e0 = func, f[0] = ref + NORMAL, // f[0..2] + COLOR, // f[0..3] + BEGIN, // e0 = mode + VERTEX, // f[0..2] + END, + DRAW_ARRAYS, // e0 = mode, i0 = count, snap + }; + + Kind kind; + GLenum e0 = 0; + GLenum e1 = 0; + GLuint u0 = 0; + GLint i0 = 0; + float f[4] = {}; + + std::shared_ptr> snap; +}; + struct DList { - // M3: recorded command stream + baked static VBO. - bool empty = true; + std::vector cmds; }; static std::map s_lists; @@ -31,6 +80,13 @@ static bool s_recording = false; static GLuint s_recordingId = 0; +static std::vector* recCmds() +{ + auto it = s_lists.find( s_recordingId ); + return it != s_lists.end() ? &it->second.cmds : nullptr; +} + + bool dlistRecording() { return s_recording; @@ -70,7 +126,7 @@ void dlistNewList( GLuint list, GLenum mode ) if( s_recording ) { - GL1_WARN_ONCE( "glNewList while already recording — previous list discarded" ); + GL1_WARN_ONCE( "glNewList while already recording — previous list kept as-is" ); dlistEndList(); } @@ -100,7 +156,85 @@ void dlistCallList( GLuint list ) if( it == s_lists.end() ) return; // calling a nonexistent list is a silent no-op in GL - // M3: replay the recorded command stream. + State& s = S(); + + for( const Cmd& c : it->second.cmds ) + { + switch( c.kind ) + { + case Cmd::Kind::ENABLE: + stateEnable( c.e0, c.i0 != 0 ); + break; + + case Cmd::Kind::BIND_TEXTURE: + stateBindTexture( c.e0, c.u0 ); + break; + + case Cmd::Kind::BLEND_FUNC: + stateBlendFunc( c.e0, c.e1 ); + break; + + case Cmd::Kind::LINE_WIDTH: + stateLineWidth( c.f[0] ); + break; + + case Cmd::Kind::ALPHA_FUNC: + stateAlphaFunc( c.e0, c.f[0] ); + break; + + case Cmd::Kind::NORMAL: + s.currentNormal = glm::vec3( c.f[0], c.f[1], c.f[2] ); + break; + + case Cmd::Kind::COLOR: + s.currentColor = glm::vec4( c.f[0], c.f[1], c.f[2], c.f[3] ); + break; + + case Cmd::Kind::BEGIN: + immBegin( c.e0 ); + break; + + case Cmd::Kind::VERTEX: + immVertex( c.f[0], c.f[1], c.f[2] ); + break; + + case Cmd::Kind::END: + immEnd(); + break; + + case Cmd::Kind::DRAW_ARRAYS: + { + AttribSource src[CA_COUNT]; + + for( int i = 0; i < CA_COUNT; ++i ) + { + const SnapArray& sa = ( *c.snap )[i]; + src[i].enabled = sa.enabled; + + if( !sa.enabled ) + continue; + + src[i].size = sa.size; + src[i].type = sa.type; + src[i].normalized = attribNormalized( i, sa.type ); + src[i].stride = sa.stride; + + if( !sa.data.empty() ) + { + src[i].cpuData = sa.data.data(); + } + else + { + src[i].buffer = sa.buffer; + src[i].offset = sa.offset; + } + } + + drawArraysWithSources( c.e0, c.i0, src ); + break; + } + } + } } @@ -111,92 +245,148 @@ void dlistDeleteLists( GLuint list, GLsizei range ) } -// --- recording hooks (M3 replaces these drops with the command recorder) --- +// --- recording hooks --- + +static void push( Cmd&& c ) +{ + if( std::vector* cmds = recCmds() ) + cmds->push_back( std::move( c ) ); +} + void dlistRecordEnable( GLenum cap, bool enable ) { - (void) cap; - (void) enable; - GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" ); + Cmd c{ Cmd::Kind::ENABLE }; + c.e0 = cap; + c.i0 = enable ? 1 : 0; + push( std::move( c ) ); } void dlistRecordBindTexture( GLenum target, GLuint texture ) { - (void) target; - (void) texture; - GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" ); + Cmd c{ Cmd::Kind::BIND_TEXTURE }; + c.e0 = target; + c.u0 = texture; + push( std::move( c ) ); } void dlistRecordBlendFunc( GLenum sfactor, GLenum dfactor ) { - (void) sfactor; - (void) dfactor; - GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" ); + Cmd c{ Cmd::Kind::BLEND_FUNC }; + c.e0 = sfactor; + c.e1 = dfactor; + push( std::move( c ) ); } void dlistRecordLineWidth( GLfloat width ) { - (void) width; - GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" ); + Cmd c{ Cmd::Kind::LINE_WIDTH }; + c.f[0] = width; + push( std::move( c ) ); } void dlistRecordAlphaFunc( GLenum func, GLclampf ref ) { - (void) func; - (void) ref; - GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" ); + Cmd c{ Cmd::Kind::ALPHA_FUNC }; + c.e0 = func; + c.f[0] = ref; + push( std::move( c ) ); } 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" ); + Cmd c{ Cmd::Kind::NORMAL }; + c.f[0] = nx; + c.f[1] = ny; + c.f[2] = nz; + push( std::move( c ) ); } 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" ); + Cmd c{ Cmd::Kind::COLOR }; + c.f[0] = r; + c.f[1] = g; + c.f[2] = b; + c.f[3] = a; + push( std::move( c ) ); } void dlistRecordBegin( GLenum mode ) { - (void) mode; - GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" ); + Cmd c{ Cmd::Kind::BEGIN }; + c.e0 = mode; + push( std::move( c ) ); } void dlistRecordVertex( float x, float y, float z ) { - (void) x; - (void) y; - (void) z; + Cmd c{ Cmd::Kind::VERTEX }; + c.f[0] = x; + c.f[1] = y; + c.f[2] = z; + push( std::move( c ) ); } void dlistRecordEnd() { + push( Cmd{ Cmd::Kind::END } ); } 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" ); + if( count <= 0 ) + return; + + Cmd c{ Cmd::Kind::DRAW_ARRAYS }; + c.e0 = mode; + c.i0 = count; + c.snap = std::make_shared>(); + + const State& s = S(); + + for( int i = 0; i < CA_COUNT; ++i ) + { + const ClientArray& ca = s.clientArrays[i]; + SnapArray& sa = ( *c.snap )[i]; + + sa.enabled = ca.enabled; + + if( !ca.enabled ) + continue; + + sa.size = ca.size; + sa.type = ca.type; + sa.stride = attribEffectiveStride( ca.size, ca.type, ca.stride ); + + if( ca.boundBuffer == 0 ) + { + // EAGER copy — the caller may (and does) free this memory right + // after glEndList. Whole strided block, last vertex tight-sized. + const GLsizei tight = attribEffectiveStride( ca.size, ca.type, 0 ); + const size_t bytes = (size_t) ( count - 1 ) * sa.stride + tight; + const auto* base = (const uint8_t*) ca.pointer + (size_t) first * sa.stride; + + sa.data.assign( base, base + bytes ); + } + else + { + sa.buffer = ca.boundBuffer; + sa.offset = (GLintptr) ca.pointer + (GLintptr) first * sa.stride; + } + } + + push( std::move( c ) ); } } // namespace gl1 diff --git a/wasm/gl1/src/gl1_draw.cpp b/wasm/gl1/src/gl1_draw.cpp index 2fd6fe3..95c2937 100644 --- a/wasm/gl1/src/gl1_draw.cpp +++ b/wasm/gl1/src/gl1_draw.cpp @@ -14,10 +14,13 @@ #include "gl1_shim.h" +#include + namespace gl1 { -static GLuint s_streamVBO = 0; +static GLuint s_streamVBO = 0; // immediate-mode interleaved stream +static GLuint s_scratchVBO = 0; // client-array upload staging enum { @@ -65,24 +68,223 @@ void drawImmVertices( GLenum mode, const ImmVertex* verts, GLsizei count ) } -// Routed glDrawArrays over FFP client-array state (M3). -void drawClientArrays( GLenum mode, GLint first, GLsizei count ) +// Constant (current-state) value for a disabled attribute array. +static void setConstantAttrib( int attr ) { - (void) mode; - (void) first; - (void) count; - GL1_WARN_ONCE( "client-array glDrawArrays not implemented yet (M3) — draw dropped" ); + const State& s = S(); + + glDisableVertexAttribArray( attr ); + + switch( attr ) + { + case ATTR_NORMAL: + glVertexAttrib3f( ATTR_NORMAL, s.currentNormal.x, s.currentNormal.y, s.currentNormal.z ); + break; + + case ATTR_COLOR: + glVertexAttrib4f( ATTR_COLOR, s.currentColor.r, s.currentColor.g, s.currentColor.b, + s.currentColor.a ); + break; + + case ATTR_TEXCOORD: + glVertexAttrib2f( ATTR_TEXCOORD, 0.0f, 0.0f ); + break; + + default: + break; + } +} + + +// Binds all four attributes from the given sources: client-memory sources are +// packed into one scratch-VBO upload; VBO-backed sources bind the user's +// buffer with the given byte offset. Restores GL_ARRAY_BUFFER when done. +static bool setupAttribSources( const AttribSource aSrc[CA_COUNT], GLsizei count ) +{ + if( !aSrc[CA_VERTEX].enabled ) + { + GL1_WARN_ONCE( "array draw without an enabled GL_VERTEX_ARRAY — dropped" ); + return false; + } + + static std::vector staging; + staging.clear(); + + GLintptr cpuOffsets[CA_COUNT] = {}; + bool anyCpu = false; + + for( int i = 0; i < CA_COUNT; ++i ) + { + const AttribSource& src = aSrc[i]; + + if( !src.enabled || !src.cpuData ) + continue; + + anyCpu = true; + + // Whole strided block; the last vertex only needs its tight size. + const GLsizei tight = attribEffectiveStride( src.size, src.type, 0 ); + const size_t bytes = (size_t) ( count - 1 ) * src.stride + tight; + + // 4-byte-align each sub-range inside the scratch VBO. + const size_t aligned = ( staging.size() + 3u ) & ~size_t( 3 ); + staging.resize( aligned + bytes ); + std::memcpy( staging.data() + aligned, src.cpuData, bytes ); + cpuOffsets[i] = (GLintptr) aligned; + } + + if( anyCpu ) + { + if( !s_scratchVBO ) + glGenBuffers( 1, &s_scratchVBO ); + + glBindBuffer( GL_ARRAY_BUFFER, s_scratchVBO ); + glBufferData( GL_ARRAY_BUFFER, (GLsizeiptr) staging.size(), staging.data(), + GL_STREAM_DRAW ); + } + + static const GLint attrOf[CA_COUNT] = { ATTR_POSITION, ATTR_NORMAL, ATTR_COLOR, + ATTR_TEXCOORD }; + + for( int i = 0; i < CA_COUNT; ++i ) + { + const AttribSource& src = aSrc[i]; + const GLint attr = attrOf[i]; + + if( !src.enabled ) + { + setConstantAttrib( attr ); + continue; + } + + if( src.cpuData ) + glBindBuffer( GL_ARRAY_BUFFER, s_scratchVBO ); + else + glBindBuffer( GL_ARRAY_BUFFER, src.buffer ); + + glEnableVertexAttribArray( attr ); + glVertexAttribPointer( attr, src.size, src.type, + src.normalized ? GL_TRUE : GL_FALSE, src.stride, + (const void*) ( src.cpuData ? cpuOffsets[i] : src.offset ) ); + } + + return true; +} + + +void drawArraysWithSources( GLenum mode, GLsizei count, const AttribSource aSrc[CA_COUNT] ) +{ + if( count <= 0 || !programSync() ) + return; + + switch( mode ) + { + case GL_POINTS: + case GL_LINES: + case GL_LINE_STRIP: + case GL_TRIANGLES: + case GL_TRIANGLE_STRIP: + case GL_TRIANGLE_FAN: + break; + + default: + // The renderer only issues array draws with WebGL2-legal primitives + // (GL_TRIANGLES everywhere, GL_LINES for model bboxes). + GL1_WARN_ONCE( "array draw with unsupported primitive 0x%x — dropped", mode ); + return; + } + + GLint prevArrayBuffer = 0; + glGetIntegerv( GL_ARRAY_BUFFER_BINDING, &prevArrayBuffer ); + + if( setupAttribSources( aSrc, count ) ) + __real_glDrawArrays( mode, 0, count ); + + glBindBuffer( GL_ARRAY_BUFFER, (GLuint) prevArrayBuffer ); +} + + +// Builds the per-attribute sources from the live client-array state, +// rebasing so that vertex 0 of the draw is `first`. +static void buildLiveSources( AttribSource aOut[CA_COUNT], GLint first ) +{ + const State& s = S(); + + for( int i = 0; i < CA_COUNT; ++i ) + { + const ClientArray& ca = s.clientArrays[i]; + AttribSource& src = aOut[i]; + + src.enabled = ca.enabled; + + if( !ca.enabled ) + continue; + + src.size = ca.size; + src.type = ca.type; + src.normalized = attribNormalized( i, ca.type ); + src.stride = attribEffectiveStride( ca.size, ca.type, ca.stride ); + + if( ca.boundBuffer == 0 ) + { + src.cpuData = (const uint8_t*) ca.pointer + (size_t) first * src.stride; + } + else + { + src.cpuData = nullptr; + src.buffer = ca.boundBuffer; + src.offset = (GLintptr) ca.pointer + (GLintptr) first * src.stride; + } + } +} + + +void drawClientArrays( GLenum mode, GLint first, GLsizei count ) +{ + AttribSource src[CA_COUNT]; + buildLiveSources( src, first ); + drawArraysWithSources( mode, count, src ); } -// 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" ); + if( count <= 0 || !programSync() ) + return; + + // The renderer's indexed draws (3d_model.cpp) are always fully VBO-backed: + // vertex attribs offset into a bound GL_ARRAY_BUFFER, indices offset into + // a bound GL_ELEMENT_ARRAY_BUFFER. WebGL2 requires the index buffer. + GLint ibo = 0; + glGetIntegerv( GL_ELEMENT_ARRAY_BUFFER_BINDING, &ibo ); + + if( ibo == 0 ) + { + GL1_WARN_ONCE( "glDrawElements without a bound index buffer — dropped " + "(client-memory indices are not supported)" ); + return; + } + + AttribSource src[CA_COUNT]; + buildLiveSources( src, 0 ); + + for( int i = 0; i < CA_COUNT; ++i ) + { + if( src[i].enabled && src[i].cpuData ) + { + GL1_WARN_ONCE( "glDrawElements over client-memory vertex arrays is not supported " + "— dropped" ); + return; + } + } + + GLint prevArrayBuffer = 0; + glGetIntegerv( GL_ARRAY_BUFFER_BINDING, &prevArrayBuffer ); + + if( setupAttribSources( src, count ) ) + __real_glDrawElements( mode, count, type, indices ); + + glBindBuffer( GL_ARRAY_BUFFER, (GLuint) prevArrayBuffer ); } } // namespace gl1 diff --git a/wasm/gl1/src/gl1_entry_ffp.cpp b/wasm/gl1/src/gl1_entry_ffp.cpp index 69ea909..ec6b874 100644 --- a/wasm/gl1/src/gl1_entry_ffp.cpp +++ b/wasm/gl1/src/gl1_entry_ffp.cpp @@ -590,9 +590,7 @@ void glAlphaFunc( GLenum func, GLclampf ref ) return; } - S().alphaFunc = func; - S().alphaRef = ref; - S().miscDirty = true; + stateAlphaFunc( func, ref ); } diff --git a/wasm/gl1/src/gl1_entry_wrapped.cpp b/wasm/gl1/src/gl1_entry_wrapped.cpp index 4df492b..1fc7777 100644 --- a/wasm/gl1/src/gl1_entry_wrapped.cpp +++ b/wasm/gl1/src/gl1_entry_wrapped.cpp @@ -32,18 +32,7 @@ void __wrap_glEnable( GLenum cap ) return; } - if( bool* slot = ffpCapSlot( cap ) ) - { - if( !*slot ) - { - *slot = true; - onCapChanged( cap ); - } - - return; - } - - __real_glEnable( cap ); + stateEnable( cap, true ); } @@ -55,18 +44,7 @@ void __wrap_glDisable( GLenum cap ) return; } - if( bool* slot = ffpCapSlot( cap ) ) - { - if( *slot ) - { - *slot = false; - onCapChanged( cap ); - } - - return; - } - - __real_glDisable( cap ); + stateEnable( cap, false ); } @@ -144,10 +122,7 @@ void __wrap_glBindTexture( GLenum target, GLuint texture ) return; } - if( target == GL_TEXTURE_2D ) - S().boundTexture2D = texture; - - __real_glBindTexture( target, texture ); + stateBindTexture( target, texture ); } @@ -159,7 +134,7 @@ void __wrap_glBlendFunc( GLenum sfactor, GLenum dfactor ) return; } - __real_glBlendFunc( sfactor, dfactor ); + stateBlendFunc( sfactor, dfactor ); } @@ -171,8 +146,7 @@ void __wrap_glLineWidth( GLfloat width ) return; } - S().lineWidth = width; - __real_glLineWidth( width ); + stateLineWidth( width ); } diff --git a/wasm/gl1/src/gl1_state.cpp b/wasm/gl1/src/gl1_state.cpp index 43dab39..aced108 100644 --- a/wasm/gl1/src/gl1_state.cpp +++ b/wasm/gl1/src/gl1_state.cpp @@ -63,4 +63,86 @@ void onCapChanged( GLenum cap ) } } + +void stateEnable( GLenum cap, bool enable ) +{ + if( bool* slot = ffpCapSlot( cap ) ) + { + if( *slot != enable ) + { + *slot = enable; + onCapChanged( cap ); + } + + return; + } + + if( enable ) + __real_glEnable( cap ); + else + __real_glDisable( cap ); +} + + +void stateBindTexture( GLenum target, GLuint texture ) +{ + if( target == GL_TEXTURE_2D ) + S().boundTexture2D = texture; + + __real_glBindTexture( target, texture ); +} + + +void stateBlendFunc( GLenum sfactor, GLenum dfactor ) +{ + __real_glBlendFunc( sfactor, dfactor ); +} + + +void stateLineWidth( GLfloat width ) +{ + S().lineWidth = width; + __real_glLineWidth( width ); +} + + +void stateAlphaFunc( GLenum func, GLclampf ref ) +{ + State& s = S(); + s.alphaFunc = func; + s.alphaRef = ref; + s.miscDirty = true; +} + + +bool attribNormalized( int arrayIndex, GLenum type ) +{ + // GL1 fixed-function semantics: integer color components map to [0,1] and + // integer normals to [-1,1]; float data is used as-is. + if( type == GL_FLOAT ) + return false; + + return arrayIndex == CA_COLOR || arrayIndex == CA_NORMAL; +} + + +static GLsizei componentSize( GLenum type ) +{ + switch( type ) + { + case GL_BYTE: + case GL_UNSIGNED_BYTE: return 1; + case GL_SHORT: + case GL_UNSIGNED_SHORT: return 2; + case GL_FLOAT: + default: return 4; + } +} + + +GLsizei attribEffectiveStride( GLint size, GLenum type, GLsizei stride ) +{ + return stride != 0 ? stride : size * componentSize( type ); +} + } // namespace gl1