pcbjam/tests/3d-regression/wasm/3d_webgl_test.cpp
Istvan Matejcsok 27992f4f18 feat(3d): gl1 shim M4+M6+M7 — GLU quadrics, production link, stub retired; 47/47 parity
- gl1_glu: gluCylinder/gluDisk/gluSphere per the SGI GLU reference
  tessellation (sin/cos phase, strip emission order, pole fans — Apple's
  GLU derives from the same source, so vertices match the goldens);
  emission goes through the public glBegin/glVertex entry points so
  quadrics record into display lists
- glLineWidth>1 one-time warning (browsers clamp to 1; affected coverage
  stays under the parity floor — zero floors.json overrides needed)
- production link site: build-kicad-target.sh now compiles wasm/gl1 from
  sources.txt and applies wrapped_symbols.txt --wrap flags (same manifests
  as the test harness); kicad_editor's RENDER_3D_OPENGL links against the
  real emulation layer instead of no-ops
- wasm/stubs/gl_ffp_stub.c deleted (role fully superseded; history in git)
- docs: 3d-regression README port-status section (floor-blind small
  geometry + line-width caveats), harness comments de-red-stated

Parity: 47/47 under the 0.02 floor, zero overrides. Native golden path
byte-identical to bb0e238 (whose gate ran green) — scenarios/, native/,
baseline/, manifest, floors, spec all untouched by the port.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:45:58 +02:00

120 lines
3.3 KiB
C++

/**
* 3D-renderer WebGL test harness (WASM) — runs the shared scenarios
* (tests/3d-regression/scenarios/) in the browser. The legacy GL surface is
* implemented by the wasm/gl1 GL1->WebGL2 emulation layer; the parity level
* (npm run 3d:check:parity) compares these renders against the native goldens.
*
* Unlike the GAL harness this needs no wx window: the renderer draws into
* whatever context is current, so a direct emscripten WebGL2 context on
* #canvas is enough — created with the attributes the suite requires
* (stencil for DrawCulled, no MSAA to match the native single-sample FBO,
* preserveDrawingBuffer for Playwright canvas screenshots).
*/
#include <emscripten.h>
#include <emscripten/html5.h>
#include "scene3d_test_ctx.h"
#include "scene3d_test_scenarios.h"
#include <cstdio>
static const int CAPTURE_WIDTH = 800; // must match manifest.json + native FBO
static const int CAPTURE_HEIGHT = 600;
static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE g_context = 0;
static SCENE3D_CTX* g_ctx = nullptr;
extern "C"
{
EMSCRIPTEN_KEEPALIVE
int getTotalScenarios()
{
return Scene3DTest::GetScenarioCount();
}
EMSCRIPTEN_KEEPALIVE
const char* getScenarioName( int aIndex )
{
return Scene3DTest::GetScenarioName( aIndex );
}
EMSCRIPTEN_KEEPALIVE
int getCanvasWidth()
{
return CAPTURE_WIDTH;
}
EMSCRIPTEN_KEEPALIVE
int getCanvasHeight()
{
return CAPTURE_HEIGHT;
}
EMSCRIPTEN_KEEPALIVE
int runScenario( int aIndex )
{
if( aIndex < 0 || aIndex >= Scene3DTest::GetScenarioCount() )
return -1;
if( emscripten_webgl_make_context_current( g_context ) != EMSCRIPTEN_RESULT_SUCCESS )
return -2;
if( !g_ctx )
{
g_ctx = new SCENE3D_CTX( CAPTURE_WIDTH, CAPTURE_HEIGHT );
g_ctx->InitOnce();
}
std::printf( "[3d-webgl] scenario %d: %s\n", aIndex, Scene3DTest::GetScenarioName( aIndex ) );
// Start from a cleared frame; scenarios call BeginFrame themselves.
glViewport( 0, 0, CAPTURE_WIDTH, CAPTURE_HEIGHT );
glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
glClearDepth( 1.0f );
glClearStencil( 0 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
Scene3DTest::RenderScenario( *g_ctx, aIndex );
glFinish();
return 0;
}
} // extern "C"
int main()
{
EmscriptenWebGLContextAttributes attrs;
emscripten_webgl_init_context_attributes( &attrs );
attrs.majorVersion = 2;
attrs.minorVersion = 0;
attrs.alpha = false;
attrs.depth = true;
attrs.stencil = true; // DrawCulled hole subtraction
attrs.antialias = false; // native goldens are single-sample
attrs.preserveDrawingBuffer = true; // Playwright canvas.screenshot()
emscripten_set_canvas_element_size( "#canvas", CAPTURE_WIDTH, CAPTURE_HEIGHT );
g_context = emscripten_webgl_create_context( "#canvas", &attrs );
if( g_context <= 0 )
{
std::fprintf( stderr, "[3d-webgl] failed to create WebGL2 context (%ld)\n",
(long) g_context );
return 1;
}
emscripten_webgl_make_context_current( g_context );
std::printf( "[3d-webgl] ready: %d scenarios, %dx%d\n", Scene3DTest::GetScenarioCount(),
CAPTURE_WIDTH, CAPTURE_HEIGHT );
EM_ASM( { if( window._threeDTestReady ) window._threeDTestReady(); } );
return 0;
}