tests/3d-regression mirrors the gal-regression pattern at renderer scale: shared C++ scenarios call real KiCad 3D-viewer code (opengl_utils, display lists + DrawCulled stencil subtraction, MODEL_3D VBOs, private generators via a rob-template accessor, and full reload()+Redraw() composites over a synthetic BOARD_ADAPTER). A native macOS harness renders them on real OpenGL into 47 committed goldens (bit-deterministic, FBO capture); the wasm harness compiles the same TUs against wasm/stubs/gl_ffp_stub.c no-ops so every scenario renders blank — the TDD red state (parity meter: 47/47 changed). Comparisons use the CI pixelmatch engine via the new generic compare-dirs.ts (floors.json levels; manifest.json cmp-guards registry drift). Documents an upstream bug: appendPostMachiningGeometry's countersink path adds middle quads without normals, silently erasing the walls of any display list it is batched into (3d-post-machining.png keeps the lists separate to record it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
120 lines
3.3 KiB
C++
120 lines
3.3 KiB
C++
/**
|
|
* 3D-renderer WebGL test harness (WASM) — runs the shared scenarios
|
|
* (tests/3d-regression/scenarios/) in the browser. Today the legacy GL
|
|
* surface is wasm/stubs/gl_ffp_stub.c no-ops, so every scenario renders a
|
|
* black canvas: the TDD red state the WebGL port turns green.
|
|
*
|
|
* 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;
|
|
}
|