feat(3d): gl1 shim M3+M5 — display-list recorder, client arrays, VBO routing; 44/47 under parity floor
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
071eef5454
commit
3fe4f6fb00
6 changed files with 565 additions and 87 deletions
|
|
@ -209,6 +209,35 @@ bool* ffpCapSlot( GLenum cap );
|
||||||
// Marks the state blocks a cap flip invalidates.
|
// Marks the state blocks a cap flip invalidates.
|
||||||
void onCapChanged( GLenum cap );
|
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) ---
|
// --- matrix module (gl1_matrix.cpp) ---
|
||||||
void matrixLoadIdentity();
|
void matrixLoadIdentity();
|
||||||
void matrixLoadf( const GLfloat* m );
|
void matrixLoadf( const GLfloat* m );
|
||||||
|
|
@ -231,7 +260,10 @@ bool immActive();
|
||||||
// Uploads `count` interleaved ImmVertex records to the streaming VBO and draws
|
// Uploads `count` interleaved ImmVertex records to the streaming VBO and draws
|
||||||
// them with the FFP program. `mode` must already be a WebGL2-legal primitive.
|
// them with the FFP program. `mode` must already be a WebGL2-legal primitive.
|
||||||
void drawImmVertices( GLenum mode, const ImmVertex* verts, GLsizei count );
|
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 drawClientArrays( GLenum mode, GLint first, GLsizei count );
|
||||||
void drawClientElements( GLenum mode, GLsizei count, GLenum type, const GLvoid* indices );
|
void drawClientElements( GLenum mode, GLsizei count, GLenum type, const GLvoid* indices );
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,22 +7,71 @@
|
||||||
* (layer_triangles.cpp, render_3d_opengl.cpp:1327) — glIsList must be
|
* (layer_triangles.cpp, render_3d_opengl.cpp:1327) — glIsList must be
|
||||||
* true right after allocation or nothing ever renders.
|
* true right after allocation or nothing ever renders.
|
||||||
* - Only GL_COMPILE recording exists (COMPILE_AND_EXECUTE is asserted).
|
* - Only GL_COMPILE recording exists (COMPILE_AND_EXECUTE is asserted).
|
||||||
* - M3 adds the command recorder (state + geometry + glDrawArrays with
|
* - THE invariant (do not weaken): a recorded glDrawArrays dereferences the
|
||||||
* client-array snapshots); until then recorded commands are dropped,
|
* client arrays AT COMPILE TIME. The renderer frees them right after
|
||||||
* which keeps the pre-M3 red scenarios blank instead of crashing.
|
* 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 "gl1_shim.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace gl1
|
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<uint8_t> 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<std::array<SnapArray, CA_COUNT>> snap;
|
||||||
|
};
|
||||||
|
|
||||||
struct DList
|
struct DList
|
||||||
{
|
{
|
||||||
// M3: recorded command stream + baked static VBO.
|
std::vector<Cmd> cmds;
|
||||||
bool empty = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::map<GLuint, DList> s_lists;
|
static std::map<GLuint, DList> s_lists;
|
||||||
|
|
@ -31,6 +80,13 @@ static bool s_recording = false;
|
||||||
static GLuint s_recordingId = 0;
|
static GLuint s_recordingId = 0;
|
||||||
|
|
||||||
|
|
||||||
|
static std::vector<Cmd>* recCmds()
|
||||||
|
{
|
||||||
|
auto it = s_lists.find( s_recordingId );
|
||||||
|
return it != s_lists.end() ? &it->second.cmds : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool dlistRecording()
|
bool dlistRecording()
|
||||||
{
|
{
|
||||||
return s_recording;
|
return s_recording;
|
||||||
|
|
@ -70,7 +126,7 @@ void dlistNewList( GLuint list, GLenum mode )
|
||||||
|
|
||||||
if( s_recording )
|
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();
|
dlistEndList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,7 +156,85 @@ void dlistCallList( GLuint list )
|
||||||
if( it == s_lists.end() )
|
if( it == s_lists.end() )
|
||||||
return; // calling a nonexistent list is a silent no-op in GL
|
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<Cmd>* cmds = recCmds() )
|
||||||
|
cmds->push_back( std::move( c ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void dlistRecordEnable( GLenum cap, bool enable )
|
void dlistRecordEnable( GLenum cap, bool enable )
|
||||||
{
|
{
|
||||||
(void) cap;
|
Cmd c{ Cmd::Kind::ENABLE };
|
||||||
(void) enable;
|
c.e0 = cap;
|
||||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
c.i0 = enable ? 1 : 0;
|
||||||
|
push( std::move( c ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void dlistRecordBindTexture( GLenum target, GLuint texture )
|
void dlistRecordBindTexture( GLenum target, GLuint texture )
|
||||||
{
|
{
|
||||||
(void) target;
|
Cmd c{ Cmd::Kind::BIND_TEXTURE };
|
||||||
(void) texture;
|
c.e0 = target;
|
||||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
c.u0 = texture;
|
||||||
|
push( std::move( c ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void dlistRecordBlendFunc( GLenum sfactor, GLenum dfactor )
|
void dlistRecordBlendFunc( GLenum sfactor, GLenum dfactor )
|
||||||
{
|
{
|
||||||
(void) sfactor;
|
Cmd c{ Cmd::Kind::BLEND_FUNC };
|
||||||
(void) dfactor;
|
c.e0 = sfactor;
|
||||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
c.e1 = dfactor;
|
||||||
|
push( std::move( c ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void dlistRecordLineWidth( GLfloat width )
|
void dlistRecordLineWidth( GLfloat width )
|
||||||
{
|
{
|
||||||
(void) width;
|
Cmd c{ Cmd::Kind::LINE_WIDTH };
|
||||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
c.f[0] = width;
|
||||||
|
push( std::move( c ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void dlistRecordAlphaFunc( GLenum func, GLclampf ref )
|
void dlistRecordAlphaFunc( GLenum func, GLclampf ref )
|
||||||
{
|
{
|
||||||
(void) func;
|
Cmd c{ Cmd::Kind::ALPHA_FUNC };
|
||||||
(void) ref;
|
c.e0 = func;
|
||||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
c.f[0] = ref;
|
||||||
|
push( std::move( c ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void dlistRecordNormal( float nx, float ny, float nz )
|
void dlistRecordNormal( float nx, float ny, float nz )
|
||||||
{
|
{
|
||||||
(void) nx;
|
Cmd c{ Cmd::Kind::NORMAL };
|
||||||
(void) ny;
|
c.f[0] = nx;
|
||||||
(void) nz;
|
c.f[1] = ny;
|
||||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
c.f[2] = nz;
|
||||||
|
push( std::move( c ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void dlistRecordColor( float r, float g, float b, float a )
|
void dlistRecordColor( float r, float g, float b, float a )
|
||||||
{
|
{
|
||||||
(void) r;
|
Cmd c{ Cmd::Kind::COLOR };
|
||||||
(void) g;
|
c.f[0] = r;
|
||||||
(void) b;
|
c.f[1] = g;
|
||||||
(void) a;
|
c.f[2] = b;
|
||||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
c.f[3] = a;
|
||||||
|
push( std::move( c ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void dlistRecordBegin( GLenum mode )
|
void dlistRecordBegin( GLenum mode )
|
||||||
{
|
{
|
||||||
(void) mode;
|
Cmd c{ Cmd::Kind::BEGIN };
|
||||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
c.e0 = mode;
|
||||||
|
push( std::move( c ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void dlistRecordVertex( float x, float y, float z )
|
void dlistRecordVertex( float x, float y, float z )
|
||||||
{
|
{
|
||||||
(void) x;
|
Cmd c{ Cmd::Kind::VERTEX };
|
||||||
(void) y;
|
c.f[0] = x;
|
||||||
(void) z;
|
c.f[1] = y;
|
||||||
|
c.f[2] = z;
|
||||||
|
push( std::move( c ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void dlistRecordEnd()
|
void dlistRecordEnd()
|
||||||
{
|
{
|
||||||
|
push( Cmd{ Cmd::Kind::END } );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void dlistRecordDrawArrays( GLenum mode, GLint first, GLsizei count )
|
void dlistRecordDrawArrays( GLenum mode, GLint first, GLsizei count )
|
||||||
{
|
{
|
||||||
(void) mode;
|
if( count <= 0 )
|
||||||
(void) first;
|
return;
|
||||||
(void) count;
|
|
||||||
GL1_WARN_ONCE( "display-list recorder not implemented yet (M3) — commands dropped" );
|
Cmd c{ Cmd::Kind::DRAW_ARRAYS };
|
||||||
|
c.e0 = mode;
|
||||||
|
c.i0 = count;
|
||||||
|
c.snap = std::make_shared<std::array<SnapArray, CA_COUNT>>();
|
||||||
|
|
||||||
|
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
|
} // namespace gl1
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,13 @@
|
||||||
|
|
||||||
#include "gl1_shim.h"
|
#include "gl1_shim.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
namespace gl1
|
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
|
enum
|
||||||
{
|
{
|
||||||
|
|
@ -65,24 +68,223 @@ void drawImmVertices( GLenum mode, const ImmVertex* verts, GLsizei count )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Routed glDrawArrays over FFP client-array state (M3).
|
// Constant (current-state) value for a disabled attribute array.
|
||||||
void drawClientArrays( GLenum mode, GLint first, GLsizei count )
|
static void setConstantAttrib( int attr )
|
||||||
{
|
{
|
||||||
(void) mode;
|
const State& s = S();
|
||||||
(void) first;
|
|
||||||
(void) count;
|
glDisableVertexAttribArray( attr );
|
||||||
GL1_WARN_ONCE( "client-array glDrawArrays not implemented yet (M3) — draw dropped" );
|
|
||||||
|
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<uint8_t> 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 drawClientElements( GLenum mode, GLsizei count, GLenum type, const GLvoid* indices )
|
||||||
{
|
{
|
||||||
(void) mode;
|
if( count <= 0 || !programSync() )
|
||||||
(void) count;
|
return;
|
||||||
(void) type;
|
|
||||||
(void) indices;
|
// The renderer's indexed draws (3d_model.cpp) are always fully VBO-backed:
|
||||||
GL1_WARN_ONCE( "client-array glDrawElements not implemented yet (M5) — draw dropped" );
|
// 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
|
} // namespace gl1
|
||||||
|
|
|
||||||
|
|
@ -590,9 +590,7 @@ void glAlphaFunc( GLenum func, GLclampf ref )
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
S().alphaFunc = func;
|
stateAlphaFunc( func, ref );
|
||||||
S().alphaRef = ref;
|
|
||||||
S().miscDirty = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,18 +32,7 @@ void __wrap_glEnable( GLenum cap )
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( bool* slot = ffpCapSlot( cap ) )
|
stateEnable( cap, true );
|
||||||
{
|
|
||||||
if( !*slot )
|
|
||||||
{
|
|
||||||
*slot = true;
|
|
||||||
onCapChanged( cap );
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
__real_glEnable( cap );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -55,18 +44,7 @@ void __wrap_glDisable( GLenum cap )
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( bool* slot = ffpCapSlot( cap ) )
|
stateEnable( cap, false );
|
||||||
{
|
|
||||||
if( *slot )
|
|
||||||
{
|
|
||||||
*slot = false;
|
|
||||||
onCapChanged( cap );
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
__real_glDisable( cap );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -144,10 +122,7 @@ void __wrap_glBindTexture( GLenum target, GLuint texture )
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( target == GL_TEXTURE_2D )
|
stateBindTexture( target, texture );
|
||||||
S().boundTexture2D = texture;
|
|
||||||
|
|
||||||
__real_glBindTexture( target, texture );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -159,7 +134,7 @@ void __wrap_glBlendFunc( GLenum sfactor, GLenum dfactor )
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
__real_glBlendFunc( sfactor, dfactor );
|
stateBlendFunc( sfactor, dfactor );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -171,8 +146,7 @@ void __wrap_glLineWidth( GLfloat width )
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
S().lineWidth = width;
|
stateLineWidth( width );
|
||||||
__real_glLineWidth( width );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
} // namespace gl1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue